這是另乙個關於follow集和first集的求解文章,兩個結合著看效果更佳
前段時間為了做編譯器,猛啃了一下編譯原理。語法分析部分用的是比較簡單上手的ll(1), 自認為ll(1)的理論部分理解得不錯,在這裡寫出來跟大家share一下。
關於什麼是ll(1),就不贅述了,書上也說得很清楚,就是從左向右掃瞄輸入,然後產生最左推導(就是每次都把最左邊的非終結字元用產生式代替)。
比如有產生式 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)|
把它分解後變成
(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→
最容易算的是first(addop)和first(mulop),因為他們的分支都直接以終結字元開始。
first(mulop)=, first(addop)=
first(factor)=
term的分支一邊是term自己(就是說把term的first內容加到本身,不對自己產生變化),一邊是factor(把factor的first集合內容加到term的first集合中),因此first(term)=first(factor) 同理first(exp)=first(term)
當然這個例子只是為了說明first集合的計算,本身存在左遞迴,是不能做ll1運算的。
其實follow集合是專門為了空操作而存在的
我們可以來看這個例子:
•a→tb | p
•t→ε | a
•p→c
我們知道first(t) = frist(p) = ,當遇到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→
exp為開始符號,所以把$放入follow(exp)中,由式(1)知first(addop)要加入follow(exp)中,此時follow(exp)=,同理,follow(addop)=,由式(2),把follow(exp)加入到follow(term)中……
由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) = first(e』)=
follow(e) = follow(e』) =
m[n,t]n+
$ee→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』.......依次下去,直到匹配結束。
編譯原理 LL 1 語法分析
直接輸入根據已知文法構造的分析表m,對於輸入的文法和符號串,所編制的語法分析程式應能正確判斷此串是否為文法的句子,並要求輸出分析過程。c 實現如下 includeusing namespace std const int l num 100000 const int max 100 分析表容量 co...
LL(1)語法分析
ll 1 分析法的功能是利用ll 1 控制程式根據顯示棧棧頂內容 向前看符號以及ll 1 分析表,對輸入符號串自上而下的分析過程。可通過消除左遞迴 提取左因子把非ll 1 文法改造成ll 1 文法。在 ll 1 分析程式設計過程中,最重要的兩個問題是 分析表的構造和相關資料結構的設計。而 分析表的構...
LL 1 語法分析
ll 1 文法分析是自頂向下的分析方法,也可以被看作是輸入串的最左推導過程,ll 1 中1的意思就是可以根據可以根據當前輸入串中的乙個字元來判斷是由哪乙個產生式產生。下面給出文法 e te e ate 代表空集 t ft t mft f i e i 0 1 2 9 a m 首先要構造first集合與...