語法分析演算法LR(1)基礎教程
基本概念
首先解釋一下基本概念
詞法分析和語法分析:編譯或者解釋一門語言,必經兩個步驟:詞法分析和語法分析,詞法分析就是把原始碼的字元流變成計算機可理解的詞彙:token,語法分析就是把token流變成一顆結構化的語法樹,以便後面的程式去翻譯或者分析。比如,假如計算機要想識別整數四則運算,詞法分析器那麼就要認識整數、加減乘除四種運算子號,以及左右括號這些token,而要根據運算子的結合性,把四則運算構建成語法樹。
3 + 4 × 5
最後得到的語法樹可能長成下面的樣子
AdditiveExpression:
AdditiveExpression
MultipleExpression:
Integer:3
"+"
MultipleExpression:
Integer:4
"×"
Integer:5
上面這顆樹用程式語言中的資料結構表達出來,就是比較容易被計算機理解和處理的了。(設計模式中的Interpreter模式,就是省略前面的步驟直接使用這種結構的)
詞法分析可以簡單地用正則表達式來完成,而本文則專門講一種語法分析的經典演算法:LR(1)
定義語法——BNF/EBNF
上面我們提到了四則運算,不過我們沒有嚴格定義四則運算到底是什麼,比如,有沒有括號、用×還是*來表示乘法,但是計算機語言需要一個非常嚴謹的定義方式,現在廣泛使用的就是BNF及其擴充套件EBNF。如我們以以下方式定義帶括號四則運算:
<Expression> ::= <AdditiveExpression>
<AdditiveExpression> ::= <AdditiveExpression> "+" <MultipleExpression> | <AdditiveExpression> "-" <MultipleExpression> | <MultipleExpression>
<MultipleExpression> ::= <MultipleExpression> "×" <PrimaryExpression> | <MultipleExpression> "/" <PrimaryExpression> | <PrimaryExpression>
<PrimaryExpression> ::= "(" <Expression> ")" | <Integer>
其中Expression、AdditiveExpression、MultipleExpression 、PrimaryExpression、Integer我們成為Symbol(語法符號,C++連結器報的錯誤裡面說找不到符號,就是這個詞),詞法分析產生的每一個token也同時是Symbol,如果一個Symbol可以由若干個Symbol組成,那麼稱為non-terminal symbol(非終結符),否則稱terminal symbol,最後生成語法樹以後,terminal symbol就是所有的葉子節點。
因為對BNF的一些批評,後來出現了EBNF,它是BNF的擴充套件版本。
Expression ::= AdditiveExpression
AdditiveExpression ::= AdditiveExpression "+" MultipleExpression , AdditiveExpression "-" MultipleExpression , MultipleExpression
MultipleExpression ::= MultipleExpression "×" PrimaryExpression , MultipleExpression "÷" PrimaryExpression , PrimaryExpression
PrimaryExpression ::= "(" Expression ")" , Integer
在現在大多數語言規範中,都在使用BNF/EBNF或者與之非常接近的方法來描述語法。
LL與LR
常見的語法分析演算法有LL和LR。
LL的第一個L表示from Left to right,第二個L表示Left most推導。
LR的第一個L和LL的第一個L含義相同,第二個R表示Right most推導。
在通常的描述中,後面還有一個括號裡面的數字如,LL(0)、LL(1)、LL(4)、LR(0)、LR(1)這樣,括號裡面的數字表示用於決策所需的後續token數。
LR(1)狀態機構建
LR(1)分析從外部看起來,每次接受一個字元,最後接收程式終結符時,內部剛好形成一顆語法樹。
在內部,每當我們接受一個Symbol之後,我們就需要做一個決定:是把新的Symbol跟原有的Symbol立即合併成更高階的Symbol,還是把新的Symbol暫放呢?
在LR(1)中,有兩個術語:reduce(歸約)和shift(移入)。規約就是把已經讀入的低階Symbol組合成高階Symbol,如Integer "x" Integer 可以reduce成MultipleExpression
仍然以上面的 3 + 4 × 5 為例,當我們接受3的時候,實際上3已經是一個完整的PrimaryExpression了,而進一步,一個PrimaryExpression也是MultipleExpression,我們可以一直將3歸約到一個完整的Expression
Expression:
AdditiveExpression:
MultipleExpression:
PrimaryExpression:
Integer:3
但是我們此時不應當直接reduce到Expression,這是一個錯誤的語法樹結構。
考慮以下兩種情況:
3 + 4 × 5,在讀入×時,
此時 3 + 4顯然不應該被reduce
3 × 4 + 5,在讀入+時
此時 3 × 4 顯然應該被reduce
為了正確處理以上的問題,我們要構建一個狀態機來指導LR(1)運算,何時reduce,何時shift。
由BNF我們可以得到的語法規則,以四則運算為例,最終我們想要的是一個Expression,即我們的語法樹最終根節點是Expression 。
我們認為輸入的token序列最終會形成一個Expression,那麼考慮一個問題,狀態機的最初狀態0,能夠接受哪些Symbol的shift呢?
第一個考慮到,狀態機可以接受Expression這個Symbol,這將導致我們進入最終狀態。
此外因為有語法規則Expression ::= AdditiveExpression,狀態0還能接受AdditiveExpression
又因為有語法規則AdditiveExpression ::= MultipleExpression "+" MultipleExpression , MultipleExpression "-" MultipleExpression , MultipleExpression
狀態0還能接受MultipleExpression
……
從這個分析過程不難看出,狀態0可以接受所有Expression,以及所有能生成Expression的規則的第一個Symbol,並按此規則遞迴。
接下來我們要關心遷移問題了,毫無疑問接受Expression這個Symbol以後,直接進入最終狀態,然而對於其它情況,如接受了一個MultipleExpression ,則會產生更多可能性,我們把AdditiveExpression ::= MultipleExpression "+" MultipleExpression , MultipleExpression "-" MultipleExpression , MultipleExpression看成3條規則,把所有的規則全都拆分出來.
每一條規則都將會形成若干狀態遷移規則:
Expression ::= AdditiveExpression
● AdditiveExpression
AdditiveExpression ●
AdditiveExpression ::= AdditiveExpression "+" MultipleExpression , AdditiveExpression "-" MultipleExpression , MultipleExpression
● AdditiveExpression "+" MultipleExpression
AdditiveExpression ● "+" MultipleExpression
AdditiveExpression "+" ● MultipleExpression
AdditiveExpression "+" MultipleExpression ●
● AdditiveExpression "-" MultipleExpression
AdditiveExpression ● "-" MultipleExpression
AdditiveExpression "-" ● MultipleExpression
AdditiveExpression "-" MultipleExpression ●
● MultipleExpression
MultipleExpression ●
MultipleExpression ::= MultipleExpression "×" PrimaryExpression , MultipleExpression "÷" PrimaryExpression , PrimaryExpression● MultipleExpression "×" PrimaryExpression
MultipleExpression ● "×" PrimaryExpression
MultipleExpression "×" ● PrimaryExpression
MultipleExpression "×" PrimaryExpression ●
● MultipleExpression "÷" PrimaryExpression
MultipleExpression ● "÷" PrimaryExpression
MultipleExpression "÷" ● PrimaryExpression
MultipleExpression "÷" PrimaryExpression ●
● PrimaryExpression
PrimaryExpression ●
PrimaryExpression ::= "(" Expression ")" , Integer
● "(" Expression ")"
"(" ● Expression ")"
"(" Expression ● ")"
"(" Expression ")" ●
● Integer
Integer ●
顯而易見,我們可以總結出下面兩條規律:
- 所有以●結尾的狀態,操作就是規約,其它狀態操作就是移入。
- 假如當前狀態● 後是一個非終結符X,那麼所有能規約到X的規則中以●開頭的狀態遷移規則也適用於當前狀態。
我們現在可以認為● Expression是初始狀態,Expression ● 是結束狀態,根據規則2,可以得到以下狀態遷移適用當前狀態
● AdditiveExpression
● AdditiveExpression "+" MultipleExpression
● AdditiveExpression "-" MultipleExpression
● MultipleExpression
● MultipleExpression "×" PrimaryExpression
● MultipleExpression "÷" PrimaryExpression
● PrimaryExpression
● "(" Expression ")"
● Integer
同樣開頭的遷移規則必須歸併,實際上,第一次我們可以得到JSON表示的以下狀態結構(我們用JS程式設計師最喜歡的$表示reduce)
{
AdditiveExpression:{
"+": {MultipleExpression:{$:"AdditiveExpression"}},
"-": {MultipleExpression:{$:"AdditiveExpression"}},
$: "Expression"
},
MultipleExpression:{
"×": {PrimaryExpression:{$:"MultipleExpression"}},
"÷": {PrimaryExpression:{$:"MultipleExpression"}},
$: "AdditiveExpression"
},
PrimaryExpression:{$:"MultipleExpression"},
"(":{Expression:{")":{$:"PrimaryExpression"}}},
Integer:{$:"PrimaryExpression"}
}
注意,這個狀態遷移關係僅僅是分析了第一個狀態後的結果,要想得到完整地狀態機,還要對所有後續狀態遞迴地應用規則2。這將可能產生無限迴圈,為了避免這種情況,我們應該對每個狀態做hash,然後把迴圈的情況直接指向之前的結果。
做完這些之後,我們就得到了一個完整的LR(1)狀態機。
轉載:http://www.cnblogs.com/winter-cn/archive/2011/07/20/LR1.html