在我們得到了Context-free grammar 之后,下一步就要將它轉換成一棵語法分析樹了,語法分析樹使得我們的編譯器能夠識別輸入串是否符合我們的Context-free grammar(中文翻譯為上下文無關語言)
有兩種方法能夠將Context-free grammar轉換為語法分析樹。今天我們只介紹自頂向下的方法。
自頂向下的語法分析是從根節點開始,深度優先地創建語法分析樹的各個節點。有遞歸向下分析和預測分析兩大類方法。
遞歸向下分析
遞歸向下的語法分析可能需要回溯(aka需要重復掃描輸入),考慮以下文法: S -> aBc ,B -> bc | b ,當我們用遞歸向下分析,輸入為abc時,語法樹如下圖:

當我們第一次匹配時識別失敗了(a匹配a,bc匹配B,最后一個c未匹配到),輸入必須回到b,用B的另外一種方式匹配。
遞歸向下的分析十分直觀,實現起來也比較方便,但效率較低,所以一般不采用。遞歸向下的分析方法實際上是深度優先搜索+回溯。而下面要說的預測分析則是用高效的動態規划來實現語法分析。
遞歸預測向下分析
在討論使用動態規划的預測向下分析之前,我們先來看一種特殊的預測向下分析。它在本質上也是遞歸的,唯一的區別在於它不需要回溯。考慮以下文法:A -> aBb | bAB,偽代碼實現如下:
proc A {
case 當前標記 {
‘a’:匹配a, 移動到下個標記;
調用函數B;
匹配b, 移動到下個標記;
‘b’:匹配b,移動到下個標記;
調用函數A;
調用函數B;
}
}
其實這種分析方式與前者的區別就在於它用了case語句來預測A的兩種可能性,從而做出不同的判斷。但這種方式的效率也是不如動態規划的。
非遞歸預測向下分析
非遞歸預測向下分析是表驅動的分析方法,也叫做LL(1)分析。第一個"L"表示從左到右掃描。第二個"L"表示產生最左推導。"1"表示每次只要往前走一步就可以決定語法分析的動作。
所謂表驅動就是通過查表的方式來分析一個輸入流是否符合文法。假設我們已經得到了這張語法分析表,現在來具體分析這種方式是如何工作的。
首先我們需要一個棧來存儲start symbol,即語法樹的根。然后從表中查找當棧頂為S,輸入為a時對應的文法,然后將S替換為aBa(注意入棧順序),然后a與輸入的a匹配,非終結標志B對應到了b,此時查找表中相應的文法,將B彈出棧,將bB壓入棧(注意順序)。以此類推直到棧底的終止字符匹配到了輸入的終止字符,表示匹配成功。
上面是實例,下面我們給出一個高度的分析行為概括:
當棧頂為X,當前輸入為a時,有以下四種分析行為:
1.如果X和a都為終止符號$,匹配成功,停止匹配。
2.如果X和a都是同一種終結標志(terminal symbol),將X彈出棧,將輸入移動到下個標志。(表示該標志成功匹配,准備匹配下個標志)
3.如果X是非終結標志(nonterminal symbol),查詢語法分析表,找到[S,a],如果[S,a]為 X->Y1Y2Y3...Yk,則將Y1Y2Y3...Yk逆序放入棧中。(即Y1為棧頂)
4.不符合以上三種情況,匹配失敗,進入錯誤恢復模式。
可以看到,有了這張語法分析表之后分析起來非常的方便。那么我們如何構建這張語法分析表呢?
首先我們需要用到兩個函數first(a),follow(A),下面詳細解釋兩個函數的含義以及如何計算他們。
first(a) : 可以從a推導得到的串的首符號(終結符號)的合集。
計算規則如下:
1.如果X是終結符號,first(X)={X}
2.如果X是非終結符號且X->ε是一個文法規則,那么ε屬於first(X)
3.如果X是非終結符號且X->Y1Y2Y3...Yn是一個文法規則,那么:①如果終結符號a在first(Yi)中且ε在所有的first(Yj) (j-1,2,...i-1)中,那么a也屬於first(X) ②如果ε在所有的first(Yj) (j=1,2...n) 那么ε也屬於first(X)
4.如果X本身為ε,那么first(X)={ε}
以上的規則將一直使用直到沒有元素能夠加入到任何first()當中。
follow(A):從A之后可以立即得到(可以理解為與A相鄰)的終結符號的集合,其中A是非終結符號。
計算規則如下:
1.如果A->aBb是一個文法規則,那么所有在first(b)中的元素除了ε都包含在follow(B)中。
2.如果A->aB是一個文法規則或者A->aBb是一個文法規則且ε包含在first(b)中,那么在follow(A)中的所有元素都在follow(B)中。即follow(A)屬於follow(B)
以上的規則也將一直使用直到沒有元素能夠加入到任何follow()當中。
下面給出兩個實例讓讀者自行思考。
接下來讓我們使用這兩個函數來完成語法分析表構建的算法。
對於在語法合集G中的每條語法 ,(以A->a來表示):
for 每個終結符號 p in first(a):
將A->a 加入到表中的M[A,p]
if ε in first(a)
for 每個follow(A)中的終結符號p:
將A->a 加入到M[A,p]
if ε in first(a) 並且 $ 屬於follow(A):
將A->a 加入到M[A,$]
當然並不是所有的語法規則都是LL文法的,也就是說有可能出現在一個表中的某行某列存在多個文法規則,比如下圖
在M[E,e]中出現了兩個文法規則使得語法分析產生了二義性(ambiguity)。可以看出LL文法並不是萬能的。那么如果我們碰到了這樣的情況應該怎么辦呢?
首先我們可以先將存在左遞歸的文法消除成非左遞歸的文法。其次我們還可以提取左公因子,如果這樣處理之后還是不行的話那么說明這個語法本身就存在二義性或者它天生就不是LL文法。