左遞迴(left recursion)
想像你向朋友問路,他卻開口說「首先,向我問路……」。你會在踏出一步之前就永遠繞圈。左遞迴就是文法版的這個陷阱:一條規則要做的第一件事就是再次展開同一個變數,於是天真的由上而下剖析器不斷展開卻從不消耗任何輸入。
若變數 A 有規則 A -> A alpha——右側以 A 本身開頭——則 A 是直接左遞迴。若有一串規則在左側繞回 A,例如 A -> B beta 且 B -> A gamma,則它是間接左遞迴。左遞迴對於定義語言完全沒問題,而且對左結合運算子很自然(A -> A + b | b 描述向左生長的 b、b+b、b+b+b)。問題純粹是程序性的:一個由上而下的遞迴下降剖析器若想配對 A 而先嘗試規則 A -> A alpha,會在沒消耗任何輸入的情況下再次對 A 呼叫自己,無限遞迴。
因此,文法在能被由上而下剖析(LL 剖析、遞迴下降)或轉成 Greibach 正規形式(它直接禁止開頭是變數)之前,必須移除左遞迴。修法是用一個輔助變數把左遞迴改寫成右遞迴,保持語言不變,但重要的是會改變剖析樹(向左傾變成向右傾)。由下而上的剖析器(LR、移位-歸約)其實偏好左遞迴、毫無困難——所以左遞迴是不是個錯誤,完全取決於你打算使用的剖析方法。
Expr -> Expr + Term | Term 是直接左遞迴:一個遞迴下降剖析器呼叫 Expr() 後會立刻在沒讀入任何輸入的情況下再次呼叫 Expr(),永遠繞圈。語言(Term、Term+Term……)沒問題;壞掉的只有由上而下的程序。
A -> A alpha 讓由上而下剖析器在沒消耗輸入的情況下對 A 遞迴——無限迴圈。
左遞迴不是語言的缺陷,只是某些剖析方法的問題。由下而上的 LR 剖析器處理起來毫無困難、甚至更偏好它;無法容忍它的是由上而下(LL、遞迴下降)剖析器與 GNF。