1. 程式人生 > >語法分析演算法LR(1)基礎教程

語法分析演算法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 ●

顯而易見,我們可以總結出下面兩條規律:

  1. 所有以●結尾的狀態,操作就是規約,其它狀態操作就是移入。
  2. 假如當前狀態● 後是一個非終結符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