、 (一)LR分析法
LR分析定義:從左到右掃描(L)輸入串,構造最右推導的逆過程(R),是自下而上分析法的核心。
LR分析法特點:
-
- 嚴格的規范規約。
- 比遞歸下降分析法、LL(1)分析法對文法的限制要少得多,適用范圍廣,適用於大多數上下文無關文法描述的語言。
- 分析速度快,能准確定位錯誤。
LR分析法缺點:手工構造分析程序工作量相當大。
LR分析器的組成:
-
- 總控程序:執行分析表所規定的動作,對進行操作。所有的LR分析器相同。
- 分析棧:又分為符號棧和狀態棧。
- 符號棧:存放分析過程中移進或歸約的符號。
- 狀態棧:狀態棧存放的是狀態(標記號),記錄分析過程中從開始的某一歸約階段的整個分析歷史或預測掃描了能遇到的分析符號
- 分析表:LR分析器的核心。其功能指示分析器是移進還是規約,根據不同的文法類要采用不同的構造方法。(后邊會具體描述)
LR分析器模型:

根據上圖可以看出LR分析程序依次將輸入串以及當前狀態移入分析棧,然后根據分析棧和當前輸入串去查找分析表去判斷下一步應該進行什么操作。
我們最終的目的是通過一系列操作去構造這個LR分析表。
四種LR分析方法以及范圍:在后續博客中我們會依次講解LR(0)、SLR(1)、LR(1)。
圖中看出一個LR(0)文法必定是SLR(1)、LALR(1)和LR(1)文法;LALR(1)文法必定是LR(1)文法。

(二)LR(0)分析法基本概念
LR(0)定義:從左到右掃描(L)輸入串,構造最右推導的逆過程(R),0代表不向前看任意符號即不進行展望或預測。
LR(0)分析法流程(移進-歸約):
-
- 識別活前綴(目的是為了尋找句柄)NFA->DFA->項目集規范族(DFA的元素)
- CLOSURE(求規范族)->GO(DFA邊)
- 構造LR(0)分析表
同樣先講解幾個定義:活前綴、增廣文法(拓廣文法)、LR(0)項目。
-
- 活前綴(可歸前綴):目的是為了尋找LR分析中可歸約串(句柄),采取歸約過程前符號棧中的內容,稱為可歸前綴。這種前綴包含句柄且不包含句柄之后的任何符號。

為了加強對活前綴定義的理解,我們舉個例子。
文法G(E): E->E+T | T T->T*F | F F->(E) | id 句型E+id*id的活前綴是什么? 答案:E、E+、E+id

先畫出句型E+id*id的語法樹,如上圖。
根據定義,前綴的尾符號最多包含到句型的句柄。從圖中可以得出句型E+id*id的句柄是id。
這里有個方法(個人見解,如果錯誤及時提出):我們找到句柄(id)之后查看它左側的所有葉子節點,即本例中的E和+,然后將E、+、id(句柄)按從左到右的順序組合成E+id。
組合完之后我們要找的活前綴就是E+id的所有前綴串:E、E+、E+id(前綴串包括本身)。這樣就得出了活前綴。
-
- 增廣文法:假定文法G是一個以S為開始符號的文法,構造一個新的文法G‘,稱G'是G的增廣文法,G'定義如下。
- 只增加一個新的非終結符S’(G‘的開始符號);
- 增加一個新的產生式S’->S;
- 增廣文法會有一個僅含項目S'->S·的狀態,這是唯一的接受態;
- 增廣文法:假定文法G是一個以S為開始符號的文法,構造一個新的文法G‘,稱G'是G的增廣文法,G'定義如下。
例如文法G(S): S->aAc
①添加一個新的文法G'(S')
②添加新的產生式S’->S
③新的文法:G‘(S’):S' -> S , S -> aAc
文法G’就是G的增廣文法,這樣做的目的是為了保證 開始符號指向非終結符;也就是說如果不使用增廣文法,無法確保開始符號一定能推導出非終結符,比如例子中文法G如果改為S -> aac,這樣就是開始符號指向終結符。
-
- LR(0)項目集:在文法G中每個產生式的右部適當位置添加一個圓點構成項目。
每個項目的含義是:欲用改產生式歸約時,圓點前面的部分為已經識別了的句柄部分,圓點后面的部分為期望的后綴部分。

LR(0)項目分類:
-
-
-
- 移進項目:A->α• aβ,圓點后邊為終結符,對應移進狀態,把a移進符號棧。
- 待約項目:A -> α • Bβ,圓點后邊為非終結符,對應待約狀態,需要等待分析完非終結符B之后再繼續分析A的右部,相當於在語法樹中進入B的子節點。
- 歸約項目:A -> α •,圓點在產生式最后,形成了可歸前綴(句柄),進入歸約狀態。
- 接受狀態:S'->S•,如果接受一個S(最終規約)則進入接受狀態。
- 初始狀態:S'->•S 。
- 有效項目:通過下面兩幅圖來說明有效項目。
-
-


對於B->•cB和B->•d這兩個項目對於活前綴bc的無效。因為圓點之前沒有活前綴bc的前綴串。
(三)LR(0)構造分析表(通過例子講解構造分析表的流程)
有一文法G(E): E->AE | b ,A->a
1、構造增廣文法
G(S): S -> E#(#為結束符號) , E->AE | b ,A->a
2、列出增廣文法的項目集(注意圓點的位置,為了方便下邊構造NFA,將每個項目編號)
1) S->·E 2) S-> E· 3) E->·AE 4) E-> A·E 5) E-> AE· 6) E->·b 7) E-> b· 8) A->·a 9) A-> a·
3、構造識別活前綴的NFA

如果構造識別活前綴的NFA?這里可以分兩種情況
Ⅰ、指向下一項目條件為空串(例如圖中1指向3和6)
從開始符號即項目1開始,圓點位置后邊為E(非終結符),即這是一個待約項目。這時需要將1指向(輸入條件為空串)滿足以下產生式的項目。
-
-
-
- 產生式左部為E
- 產生式右部第一個為圓點
-
-
滿足以上兩個條件的項目,輸入條件為空。即圖中項目1指向3和6。因為1圓點后邊為非終結符E,且項目3和6產生式左側為E,產生式右側第一個為圓點(以圓點開始)。
而項目1不能指向4、5、7原因是因為這三個項目產生式右側不是以圓點開始的。不能指向8的原因是因為8的產生式左側不是E。
Ⅱ、指向下一項目條件不為空串(例如圖中1指向2,說明輸入了終結符活非終結符,從而進入了下一個狀態)
當狀態轉換條件不為空串時,說明讀取了一個輸入字符,從而進入了下一個狀態。例如項目8讀入一個字符a之后圓點后移,正好就是9,因此8->9的條件是輸入符號a。
4、構造識別活前綴的DFA(注意:DFA和NFA的狀態編號沒有任何聯系,DFA狀態編號對應項目編號,NFA編號可自己定義但要按順序)

構造DFA有兩種方法:
Ⅰ、根據NFA構造DFA
DFA同樣是從S->·E(開始符號)開始。那么在DFA的狀態0中為什么會有,E->·AE、E->·b和A->·a呢?
當我們查看NFA時可以發現,項目1(S->·E)經過任意個空串之后能夠到達項目3、6、8,查看項目集可以發現項目3、6、8正好是E->·AE、E->·b和A->·a。
因此可以得出結論,根據當前狀態,如果經過任意個空串之后能后到達的狀態屬於同一個DFA狀態(即屬於DFA中的同一個方框)。
如果還沒有明白的話,我們再使用DFA的狀態3作講解。
狀態0輸入A之后可以從E->·AE轉為狀態3 E->A·E,這時先將E->A·E寫入方框,然后查看NFA看項目E->A·E經過任意空串能達到哪個項目,並將對應的項目寫入E->A·E所在方框;
可以發現項目E->A·E(即項目4)經過任意空串可以到達3、6、8。因此將項目3、6、8寫入E->A·E所在方框。
Ⅱ、不根據NFA構造DFA(一般情況下,構造NFA比較耗時,所以及時不構造NFA也能寫出DFA)
同樣從S->·E開始,將S->·E寫入方框。
①發現圓點后邊為非終結符E,這時尋找滿足以下兩個條件的產生式:1、產生式左部為E;2、產生式右部以圓點為開始;
②根據①講滿足條件的E->·AE和E->·b寫入方框。
③發現E->·AE這個項目圓點后還有非終結符A,這時再尋找產生式左部為A,右部以圓點開始的產生式,經過尋找講A->·a加入方框。
這樣不斷迭代,直到方框中圓點后均為終結符(A->·a、E->·b)或者圓點后為終結符但是它的下一個狀態已經被我們加入方框,這樣這個狀態的方框已經畫完。然后我們再根據圓點位置判斷下一個要輸入的符號。
5、根據構造好的DFA判斷該文法是不是LR(0)文法
Ⅰ、判斷LR(0)文法的依據,活前綴識別自動機(DFA)中每個狀態(項目集)不存在下述兩種情況:
①既含移進項目又含歸約項目;
②含有多個歸約項目;
如果能同時滿足以上兩點則,該文法就是一個LR(0)文法。
Ⅱ、舉一個反例:



有如上四個項目集,前三個項目集均不滿足上述第一條,最后一個不滿足上述第二條。
項目集1:S‘ -> E· 是一個歸約項目,但是在該項目集中還存在E -> E· + T;這時就會有沖突,當我們輸入字符E的時候,我們下一步是要進行對S->E' 的歸約呢,還是要繼續讀入E -> E ·+ T中的終結符+(移進)呢?這樣就會存在沖突。
項目集2:同樣,當輸入T之后,我們是要對E->T·進行歸約,還是繼續將T->T· * F中的終結符*讀入棧(移進)呢?這也存在沖突。
項目集9:同上,無法判斷要對E->E+T·進行歸約,還是對T->T·*F中的*進行移進操作。
項目集3:當輸入字符d之后,是要將d歸約為E,還是歸約為T呢?這同樣也存在着沖突。
判斷LR(0)文法總結:可以發現前三個項目集都是無法判斷下一步要進行歸約還是移進,這種沖突稱為移進-歸約(s-r)沖突。
第四個項目集,無法判斷下一步要歸約為哪一個產生式,因此這種沖突稱為歸約-歸約(r-r)沖突。
只要某個文法項目集存在上述兩種沖突的任意一個都不是LR(0)文法。
6、判斷之后,該文法如果是LR(0)文法,則根據規則構造LR(0)分析表。
某文法描述如下:

這里有一個項目集,我們使用該項目集構造分析表。

首先需要知道分析表的結構:
①表頭有ACTION,和GOTO動作,ACTION下的列寫的是終結符,GOTO下的列寫的是非終結符;ACTION有三個狀態移進(s)、歸約(r)和接受(acc),GOTO中直接寫項目集編號。
②第一列是DFA中項目集的編號(狀態編號)。
直接給出該項目集分析表然后我們一步一步講解。

-
-
- 從狀態0開始,當遇到字符串a,將a移進,同時轉換為成狀態3,所以在對應的表中位置就寫入s3。
- 當狀態0遇到字符b時,將b移進,同時轉換為狀態4,在對應表中位置寫入s4。
- 當狀態0遇到非終結符B,跳轉到狀態2,在狀態0所在行GOTO表的B所在列,直接寫狀態編號2即可。
- 當狀態0遇到S,跳轉到狀態1,GOTO表中直接寫狀態編號1。
- 狀態1為接受狀態,期望輸入一個#號,因此在#號位置寫入acc(接受狀態)。
- 狀態2遇到字符a,將a移進,同時轉換為狀態3,在對應表中位置寫入s3。
- 狀態2遇到字符b,將b移進,同時轉換為狀態4,在對應表中位置寫入s4。
- 當狀態2遇到非終結符B,跳轉到狀態5,在狀態2所在行GOTO表的B所在列,直接寫狀態編號5即可。
- 狀態3遇到字符a,將a移進,同時轉換為狀態3,在對應表中位置寫入s3。
- 狀態3遇到字符b,將b移進,同時轉換為狀態4,在對應表中位置寫入s4。
- 當狀態3遇到非終結符B,跳轉到狀態6,在狀態3所在行GOTO表的B所在列,直接寫狀態編號6即可。
- 因為狀態4、5、6為歸約項目,所以4、5、6所在行接受任意終結符都進行歸約。例如狀態2接受B跳轉到狀態5,在表中就是GOTO5,然后這時按照文法規則(1)(上上圖中的文法對應的編號)進行歸約,表格中寫入r1。
- 同理在狀態4中,終結符對應的每一列寫入r3,狀態6中終結符對應的每一列寫入r2。
-
經過一系列操作,我們的LR(0)分析表就已經構造完成。以上就是構造LR(0)分析表所需要的六個步驟。
以上均為個人學習總結,如有錯誤或異議歡迎提出(自下而上分析法未完待續......)。
