編譯原理LL(1)詳解


前段時間為了做編譯器,猛啃了一下編譯原理。語法分析部分用的是比較簡單上手的LL(1), 自認為LL(1)的理論部分理解得不錯,在這里寫出來跟大家share一下。

關於什么是LL(1),就不贅述了,書上也說得很清楚,就是從左向右掃描輸入,然后產生最左推導(就是每次都把最左邊的非終結字符用產生式代替)。

(一)為什么我們需要First集合

比如有產生式 A-> + T | - P , 當我們讀到串為 +開頭的時候,我們可以很直接地判斷選擇 A-> + T 這個生成式;串為- 開頭的時候,選擇 A-> - P 這個生成式。但如果文法是類似於A →T |  P 這樣的都以非終結字符開頭的呢?一眼就很難判斷的,我們就需要知道,T 是怎么展開的,如果 T -> a |b ,P->c|d , 那當串以a或b開頭的時候,我們顯然需要選擇A →T ,而當串以c或d開頭的時候,就應該選擇A->P 這個生成式了。也就是說,我們需要知道T這個分支和P這個分支,都可以用什么終結字符開頭。因此我們需要計算每個生成式的開始記號的集合,也就是First集合。

下面給出First集合的算法:

•直接收取:如果存在 T→a … (a為終止符), 把a直接放到First(T)中,很顯然A →T |  P →a…|P,遇到a的時候走T這條分支
•反復推送: T→E … ,把First(E)的元素加入到First(T)中
 
這里舉個簡單的例子:四則運算
•exp→exp addop term | term
•addop→ +| -
•term →term mulop factor | factor
•mulop→∗|/
•factor →(exp)|<number>
 
把它分解后變成
(1)exp→exp addop term
(2) exp→term
(3) addop→+
(4) addop→-
(5) term→term mulop factor
(6) term→factor
(7) mulop→∗
(8) mulop→/
(9)factor→(exp)
(10) factor→<number>
 
最容易算的是First(addop)和First(mulop),因為他們的分支都直接以終結字符開始。
First(mulop)={*,/}, First(addop)={+,-}
First(factor)={(,<number>}
term的分支一邊是term自己(就是說把term的First內容加到本身,不對自己產生變化),一邊是factor(把factor的first集合內容加到term的first集合中),因此First(term)=First(factor) 同理First(exp)=First(term)
 
當然這個例子只是為了說明First集合的計算,本身存在左遞歸,是不能做LL1運算的。
 

(二)感覺First集合已經足夠了,為什么還需要Follow集合

其實Follow集合是專門為了空操作而存在的 
我們可以來看這個例子:
•A→Tb | P
•T→ε | a
•P→c
 
我們知道First(T) = {ε , a} Frist(P) = {c},當遇到a開頭的串的時候,選擇A→Tb,遇到c開頭的串,選擇 A-> P
但其實,由於ε在First(T) 里,我們可以得到
A→Tb | P         →(ε│a)b | P      → (b| ab) | c
也就是說,當串以b開頭的時候,其實選擇的也是A→Tb
 
所以,為了特殊處理當一個非終結字符可以推出空時候的情況,我們需要知道它后面緊跟的是什么終結符合,這個終結符號也是能被這個串所直接接收的。
•定義:FOLLOW(A)是可能在某些句型中緊跟在A右邊的終結符號的集合
 
Follow集合的算法
S→…U…算Follow(U)
1.如果存在一個產生式 A→…UP,那么First(P)中除了ε 之外都應該放入Follow(U)中
2.如果存在一個產生式 A→…U,或者存在產生式 A→…UP,且First(P)中包含ε,則Follow(A)中的所有元素都在Follow(U)中
3.將$放入Follow(S)中,其中S是開始符號,$是輸入右端的結束標識

 2的推導: S->EAT, A可以用…U代替,S->E…UT,所以A后面出現的終止符和U后面出現的終止符一樣

2和3可得出: S->…U,那么Follow(S)的元素就在Follow(U)中,所以$在Follow(U)中

 

還是剛才的例子:

(1)exp→exp addop term
(2) exp→term
(3) addop→+
(4) addop→-
(5) term→term mulop factor
(6) term→factor
(7) mulop→∗
(8) mulop→/
(9)factor→(exp)
(10) factor→<number>
Exp為開始符號,所以把$放入Follow(exp)中,由式(1)知First(addop)要加入Follow(exp)中,此時Follow(exp)={+,-,$},同理,Follow(addop)={(,number},由式(2),把Follow(exp)加入到Follow(term)中……
 

(三) 從First,Follow到預測分析表

由First集合和Follow集合就可以得出我們需要的預測分析表了,先來感官性地認識一下:

我們可以看到表格的Y方向是所有的非終結符(也就是所有生成式的左邊部分的集合),X方向是所有終結符。

表格的每一項表示,當我目前在N這個非終結符,遇到T這個終結符之后,應該選擇的生成式。 比如在stmt時候,如果遇到s,則選擇stmt->s這個生成式。

從開始符號出發,每遇到一個輸入,就判斷往哪邊走,最后走完為止,如果中間沒有路可以走了,就說明語法有錯。

 

下面來看預測分析表是怎么生成的。其實跟剛才First集合和Follow集合的思路一致。

M[N,T] 其中N為非終止符,T為終止符
算法:為每個非終結符A和產生式 A→ α重復以下兩個步驟:
1)對於First(α)中的每個記號a,都將 A→ α 添加到項目 M[ A, a ]中(即,當輸入中遇到a,選擇A→ α 這一產生式)
2)如果ε在First(α)中,對於Follow(A)中的每個元素a,(記號或者$), 都將A→ α 添加到項目 M[ A, a ]中。

就是正常的話直接看終結符,在哪個分支就往哪個分支走。但如果這個分支的First集合里有ε,那么需要看它后面的終止字符集合。

 

例子:

E→nE′
E′→ +nE′ | ε
 
First(E) = { n }  First(E’)= { + , ε}
Follow(E) = Follow(E’) = {$}
 
M[N,T] n + $
E E→nE′    
E'   E′→ +nE′  E′→ε

 

 

根據它來做的出棧入棧如下,比如分析 3+4=5(棧中的#只是為了計算結果,可以不理)

首先把$和開始字符E入棧,然后讀取輸入串。第一個字符是3,也就是n,M[E,n]是E→nE′,所以我們把E出棧,把n和E’入棧。從右到左入棧,即先入E’,再入n。這個時候,輸入n和棧頂n匹配,把n出棧,讀取字符串的下一個字符,即+,棧頂的E'遇到+根據表格知道應該選擇E′→ +nE′ ,把E’出棧,E’,n,+入棧,+和輸入的+匹配,出棧,頂端為E’.......依次下去,直到匹配結束。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM