定義
一個文法是左遞歸的,若我們可以找出其中存在某非終端符號A,最終會推導出來的句型(sentential form)裡面包含以自己為最左符號(left-symbol)的句型。
直接左遞歸
直接左遞歸(Immediate left recursion)以下面的句型規則出現:
![左遞歸](/img/7/b01/wZwpmL0IjNyMDOwEzN0MTN1UTM1QDN5MjM5ADMwAjMwUzLxczLwQzLt92YucmbvRWdo5Cd0FmLzE2LvoDc0RHa.jpg)
![左遞歸](/img/0/92c/wZwpmL3AzM1UzMyEDMyADN0UTMyITNykTO0EDMwAjMwUzLxAzL3UzLt92YucmbvRWdo5Cd0FmLyE2LvoDc0RHa.jpg)
![左遞歸](/img/8/ee0/wZwpmLwMjN2czN5YjN5ADN0UTMyITNykTO0EDMwAjMwUzL2YzL0IzLt92YucmbvRWdo5Cd0FmL0E2LvoDc0RHa.jpg)
![左遞歸](/img/c/c8f/wZwpmL3gTMycjM4QTOwADN0UTMyITNykTO0EDMwAjMwUzL0kzL3MzLt92YucmbvRWdo5Cd0FmLxE2LvoDc0RHa.jpg)
這裡 跟 代表不同的非終端符號跟終端符號組成的序列,並且{\displaystyle \beta }不一定要包含 。舉例來說,以下規則
![左遞歸](/img/6/6d7/wZwpmLxUTN3MzN0IDN0MTN1UTM1QDN5MjM5ADMwAjMwUzLyQzL0gzLt92YucmbvRWdo5Cd0FmLwE2LvoDc0RHa.jpg)
就是一個直接左遞歸的例子。 這規則的遞歸下降分析器(recursive descent parser)可能會像這樣:
function Expr()
{
Expr(); match('+'); Term();
}
然後這個遞歸下降分析器在嘗試去解析包含此規則的文法時,會陷入一個無窮的遞歸。
間接左遞歸
間接左遞歸(indirect left recursion)最簡單的形式如下:
![左遞歸](/img/2/fe5/wZwpmL3IjNwYTM1IzN0MTN1UTM1QDN5MjM5ADMwAjMwUzLyczLwczLt92YucmbvRWdo5Cd0FmL0E2LvoDc0RHa.jpg)
![左遞歸](/img/5/d62/wZwpmLzgzN4ITO2kjN0MTN1UTM1QDN5MjM5ADMwAjMwUzL5YzLwMzLt92YucmbvRWdo5Cd0FmL0E2LvoDc0RHa.jpg)
![左遞歸](/img/4/465/wZwpmL4ITO5ETNyUDN0MTN1UTM1QDN5MjM5ADMwAjMwUzL1QzLzQzLt92YucmbvRWdo5Cd0FmLyE2LvoDc0RHa.jpg)
這規則可能產生 這種生成。
簡單的說,間接左遞歸就是,並非在一條規則內完成左遞歸,而是在許多條規則之後,於產生的句子最左邊出現了一開始的非終端符號。
![左遞歸](/img/e/f03/wZwpmLzAjM2YDM2IDN0MTN1UTM1QDN5MjM5ADMwAjMwUzLyQzL2czLt92YucmbvRWdo5Cd0FmLxE2LvoDc0RHa.jpg)
更一般化的說法,對非終端符號 ,間接左遞歸被定義為以下的型態:
![左遞歸](/img/d/6a4/wZwpmL2EjN4cDO2kTN0MTN1UTM1QDN5MjM5ADMwAjMwUzL5UzLwIzLt92YucmbvRWdo5Cd0FmLyE2LvoDc0RHa.jpg)
![左遞歸](/img/0/522/wZwpmLygTM2UDM0MzN0MTN1UTM1QDN5MjM5ADMwAjMwUzLzczLxEzLt92YucmbvRWdo5Cd0FmLyE2LvoDc0RHa.jpg)
![左遞歸](/img/3/c0a/wZwpmL2cDM3EDNwAzNwMzM1UTM1QDN5MjM5ADMwAjMwUzLwczLwgzLt92YucmbvRWdo5Cd0FmLwE2LvoDc0RHa.jpg)
![左遞歸](/img/d/d85/wZwpmL4ATN0cTOzcTN0MTN1UTM1QDN5MjM5ADMwAjMwUzL3UzL2AzLt92YucmbvRWdo5Cd0FmL0E2LvoDc0RHa.jpg)
![左遞歸](/img/3/be3/wZwpmLwgTN2UTOykTN0MTN1UTM1QDN5MjM5ADMwAjMwUzL5UzLxgzLt92YucmbvRWdo5Cd0FmLxE2LvoDc0RHa.jpg)
這裡的 都是一堆終端與非終端符號的序列。
容納左遞歸
一個包含左遞歸的形式文法不能以簡易的遞歸下降分析器進行語法分析,除非將文法轉變為weakly equivalent的右遞歸形式 (相對的,在LALR分析器裡面則比較偏好左遞歸,因為比起右遞歸來說會使用比較少的堆疊);然而,比較複雜的由上而下(top-down)語法分析器裡面可以藉由使用縮減(by use of curtailment)來實做一般的上下文無關文法。 在2006年, Frost 和 Hafiz 提出一個算法,可以容納包含直接左遞歸生成規則的模糊文法(ambiguous grammers)。在2007年,Frost,Hafiz和Callaghan 將此算法延伸為一個完整的,可以適用並在多項式時間內處理直接或間接左遞歸,而且可以為高度模糊文法接近指數數目的分析樹,產生小一些多項式空間的表示法。這些人後來在Haskell程式語言裡面以這個算法實做了一個的分析器組合(parser combinator)的集合。
移除左遞歸
移除直接左遞歸
一個一般化後移除直接左遞歸的算法如下所述。 這個方法已經有過許多的改進,包括Robert C. Moore所撰寫,名為"Removing Left Recursion from Context-Free Grammars"的改進。
對於每個規則如下:
![左遞歸](/img/9/766/wZwpmLyITOxkTN4IDN0MTN1UTM1QDN5MjM5ADMwAjMwUzLyQzLxgzLt92YucmbvRWdo5Cd0FmLyE2LvoDc0RHa.jpg)
注意這裡:
A 是一個有左遞歸的非終端符號
![左遞歸](/img/0/92c/wZwpmL3AzM1UzMyEDMyADN0UTMyITNykTO0EDMwAjMwUzLxAzL3UzLt92YucmbvRWdo5Cd0FmLyE2LvoDc0RHa.jpg)
![左遞歸](/img/8/252/wZwpmLxITMwczN5cjN0MTN1UTM1QDN5MjM5ADMwAjMwUzL3YzL0YzLt92YucmbvRWdo5Cd0FmLzE2LvoDc0RHa.jpg)
是一個終端與非終端符號的序列,而且不為空字串
![左遞歸](/img/8/ee0/wZwpmLwMjN2czN5YjN5ADN0UTMyITNykTO0EDMwAjMwUzL2YzL0IzLt92YucmbvRWdo5Cd0FmL0E2LvoDc0RHa.jpg)
是一個不以A開頭的,以終端與非終端符號組成的序列)
將A的規則改成以下規則:
![左遞歸](/img/1/283/wZwpmLzcTM4kTM3ATN0MTN1UTM1QDN5MjM5ADMwAjMwUzLwUzLyUzLt92YucmbvRWdo5Cd0FmLwE2LvoDc0RHa.jpg)
然後對新創造出來非終端符號的規則
![左遞歸](/img/8/2ce/wZwpmL2EDOxgTN1ITN0MTN1UTM1QDN5MjM5ADMwAjMwUzLyUzL2UzLt92YucmbvRWdo5Cd0FmLxE2LvoDc0RHa.jpg)
這個新創造出來的符號常被稱為"尾巴"(tail),或者"rest"(剩餘)
舉例,考慮以下規則
![左遞歸](/img/0/cba/wZwpmLzcDO1EjNwgjN0MTN1UTM1QDN5MjM5ADMwAjMwUzL4YzLyMzLt92YucmbvRWdo5Cd0FmL0E2LvoDc0RHa.jpg)
我們可以改寫為
![左遞歸](/img/1/a72/wZwpmLxMDM2YDNxQDN0MTN1UTM1QDN5MjM5ADMwAjMwUzL0QzLxYzLt92YucmbvRWdo5Cd0FmLwE2LvoDc0RHa.jpg)
![左遞歸](/img/c/032/wZwpmL2EzNwczMwITN0MTN1UTM1QDN5MjM5ADMwAjMwUzLyUzLwQzLt92YucmbvRWdo5Cd0FmL0E2LvoDc0RHa.jpg)
然後最後一個規則可以縮短改寫為
![左遞歸](/img/e/1f1/wZwpmLxYDO2kzM2kTN0MTN1UTM1QDN5MjM5ADMwAjMwUzL5UzLwMzLt92YucmbvRWdo5Cd0FmLzE2LvoDc0RHa.jpg)
來避免掉左遞歸的出現
移除間接左遞歸
![左遞歸](/img/1/3c9/wZwpmL4UjM3gjNxYTM5czN0UTMyITNykTO0EDMwAjMwUzL2EzL0gzLt92YucmbvRWdo5Cd0FmLxE2LvoDc0RHa.jpg)
![左遞歸](/img/1/4f8/wZwpmL2QDO4ITO2IzN0MTN1UTM1QDN5MjM5ADMwAjMwUzLyczLxgzLt92YucmbvRWdo5Cd0FmLwE2LvoDc0RHa.jpg)
![左遞歸](/img/b/db2/wZwpmL0cTM0IzM1EjN0MTN1UTM1QDN5MjM5ADMwAjMwUzLxYzL1IzLt92YucmbvRWdo5Cd0FmLxE2LvoDc0RHa.jpg)
如果文法內不存在 (代表空字串)的生成 (不存在 這樣的規則),而且不是循環(cyclic)的文法(對所有非終端符號A,不存在像是 這種形式的規則),以下這個一般化的算法可以用來去除文法的間接左遞歸:
![左遞歸](/img/7/ccc/wZwpmL4QDN3UjNwMzN0MTN1UTM1QDN5MjM5ADMwAjMwUzLzczLzgzLt92YucmbvRWdo5Cd0FmLwE2LvoDc0RHa.jpg)
將所有非終端符號以某個固定的順序 排列。
從 i = 1 到 n {
從 j = 1 到 i – 1 {
![左遞歸](/img/c/aab/wZwpmLygjNzQzN3cTN0MTN1UTM1QDN5MjM5ADMwAjMwUzL3UzLxczLt92YucmbvRWdo5Cd0FmLxE2LvoDc0RHa.jpg)
設 的生成規則為
![左遞歸](/img/c/430/wZwpmL0UDM4kDN5MDN0MTN1UTM1QDN5MjM5ADMwAjMwUzLzQzL3MzLt92YucmbvRWdo5Cd0FmLwE2LvoDc0RHa.jpg)
![左遞歸](/img/b/7c6/wZwpmL2MTO2ADMxAzN0MTN1UTM1QDN5MjM5ADMwAjMwUzLwczL2MzLt92YucmbvRWdo5Cd0FmLwE2LvoDc0RHa.jpg)
將所有規則 換成
![左遞歸](/img/d/671/wZwpmL2ETMwIzN5QDN0MTN1UTM1QDN5MjM5ADMwAjMwUzL0QzL2IzLt92YucmbvRWdo5Cd0FmLwE2LvoDc0RHa.jpg)
![左遞歸](/img/f/109/wZwpmLycjNxMzMyAjNxMzM1UTM1QDN5MjM5ADMwAjMwUzLwYzLyQzLt92YucmbvRWdo5Cd0FmLyE2LvoDc0RHa.jpg)
移除 規則中的直接左遞歸。
}
}
陷阱
上面的轉換使用右遞歸的文法來避免掉左遞歸的出現;但是這樣會改變規則的結合律。左遞歸會創造出向左的結合律;但是右遞歸則會創造出向右的結合律。
範例:
一開始我們拿到以下文法:
![左遞歸](/img/6/422/wZwpmL2YTN1UDNxUTN0MTN1UTM1QDN5MjM5ADMwAjMwUzL1UzLzIzLt92YucmbvRWdo5Cd0FmLwE2LvoDc0RHa.jpg)
![左遞歸](/img/6/ed8/wZwpmLyIDM4kjNyMzN0MTN1UTM1QDN5MjM5ADMwAjMwUzLzczLwMzLt92YucmbvRWdo5Cd0FmLxE2LvoDc0RHa.jpg)
![左遞歸](/img/0/142/wZwpmLzMzM2EzM3EjN0MTN1UTM1QDN5MjM5ADMwAjMwUzLxYzLzYzLt92YucmbvRWdo5Cd0FmLxE2LvoDc0RHa.jpg)
在我們使用上面的轉換方式來移除掉左遞歸之後,我們取得了以下文法:
![左遞歸](/img/8/6a2/wZwpmL0ITN0UTO2kTN0MTN1UTM1QDN5MjM5ADMwAjMwUzL5UzL1UzLt92YucmbvRWdo5Cd0FmLxE2LvoDc0RHa.jpg)
![左遞歸](/img/c/343/wZwpmL1ATN0cDMyETN0MTN1UTM1QDN5MjM5ADMwAjMwUzLxUzL1MzLt92YucmbvRWdo5Cd0FmLxE2LvoDc0RHa.jpg)
![左遞歸](/img/3/1e8/wZwpmLxgTNyAjM5ETN0MTN1UTM1QDN5MjM5ADMwAjMwUzLxUzLyIzLt92YucmbvRWdo5Cd0FmLzE2LvoDc0RHa.jpg)
![左遞歸](/img/8/05f/wZwpmL2cjMwgTM4ITN0MTN1UTM1QDN5MjM5ADMwAjMwUzLyUzL2QzLt92YucmbvRWdo5Cd0FmL0E2LvoDc0RHa.jpg)
![左遞歸](/img/0/142/wZwpmLzMzM2EzM3EjN0MTN1UTM1QDN5MjM5ADMwAjMwUzLxYzLzYzLt92YucmbvRWdo5Cd0FmLxE2LvoDc0RHa.jpg)
我們將字串 'a + a + a'用一個LALR分析器(這種分析器可以處理左遞歸的文法)使用原先的文法來分析,會得到下面的分析樹(parse tree):
整個分析樹是往左邊長,代表在這裡的規則,'+'這個符號是左結合(left associative)的,或者說這規則代表(a + a) + a。
但是我們改變了文法之後,那這個分析樹會變成 :
![左遞歸](/img/3/b41/wZwpmL4gjNyAzM0gTN0MTN1UTM1QDN5MjM5ADMwAjMwUzL4UzL3IzLt92YucmbvRWdo5Cd0FmLyE2LvoDc0RHa.jpg)
整個分析樹是往左邊長,代表在這裡的規則,'+'這個符號是左結合(left associative)的,或者說這規則代表(a + a) + a。
但是我們改變了文法之後,那這個分析樹會變成:
![左遞歸](/img/b/836/wZwpmLxgjMycTM3ITN0MTN1UTM1QDN5MjM5ADMwAjMwUzLyUzLwAzLt92YucmbvRWdo5Cd0FmLyE2LvoDc0RHa.jpg)
我們可以看出這棵樹現在是往右邊成長,意思上代表了a + ( a + a)。我們將'+'的結合律改變了, 變成是右結合的規則。 在處理加法的文法時這不是什麼問題,但是如果我們現在處理的是減法,這就會變成是很嚴重的問題。
問題的關鍵在於有很多常用的算術規則要求左結合的規則。我們有幾種解決辦法: (a) 將規則重新改為左遞歸,(b) 使用更多的非終端符號來改寫規則,以強迫文法合乎正確的結合(c) 如果使用YACC或者Bison,他們有所謂算符宣告(operator declarations),%left,%right and%nonassoc,這一些算符可以告訴語法分析器產生程式(parser generator)應該遵從哪一種結合。