此編譯原理肯定某高級程序設計語言編譯原理,理論基礎,學習筆記java
本筆記是對教材《編譯原理》- 張晶老師版 作學習筆記。數組
最近在學《編譯原理》,前三章感受還能夠理解,到了第四章就感受這難度就上來了。就是說過了詞法分析,剛到語法分析,就開始頭大了,因而想作個筆記,本篇就是第 4 章的筆記。函數
第4章 - 自頂向下的語法分析學習
語法分析設計
語法分析是在詞法分析識別出的單詞符號串的基礎上,分析並斷定句子的語法結構是否符合語法規則3d
自頂向下分析法code
自頂向下分析法就是從文法的開始符號出發,不斷創建直接推導,試圖構造一個最左推導序列,最終由它推導出與輸入符號串徹底匹配(相同)的句子。blog
從語法樹的角度看,自頂向下分析法就是以開始符號爲根節點,試圖向下構造一棵語法樹,其端末結符號串與輸入符號串相同。遞歸
若採用自頂向下的語法分析,應消除文法中存在的左遞歸。token
由於左遞歸的存在,有可能使推導不能結束,分析陷入循環狀態。
例如:A → Aa | b
左遞歸還比較好理解,例如:我須要匹配的字符串是 bsd 就須要從左端 b 開始是被,推導時,先用 A -> Aa 推導 AAa,此時不符合條件,但由於還有非終結符,不能結束,而是繼續推導。此時就會陷入死循環。
因此要避免這類狀況就要消除左遞歸。
消除文法的左遞歸
課本上直接左遞歸,間接左遞歸,提取左公因子書上比較詳細,通常能夠理解,跳過了
由於推導時,右側有或的狀況,從各類可能的選擇中隨機挑選一種,並但願它是正確的。若是之後發現它是錯誤的,必須退回去,再試另外的選擇這種方式稱爲回溯。
回溯代價極高,效率很低,因此也要避免。
因此須要FIRST 集,FOLLOW 集和 SELECT 集,這一堆集出馬了。
若是一個文法的每一個產生式的右部都由終結符開始,有相同左部的產生式,它們的右部由不一樣的終結符開始,這樣的文法在推導過程當中就能夠根據當前的輸入符號來決定選擇哪一個產生式往下推導,它的分析過程是惟一肯定的,不會產生回溯現象
簡單的說,右部都以非終結符開頭,每一個產生式的右部第一個非終結符又都不同就更好了,咱們就能夠根據語法惟一的選擇產生式,
例如:須要識別輸入符號串 bsd
文法:
(1)S -> aBC
(2)B -> cC
(3)C -> bB
(4)C -> d
......
咱們知道 b 就能夠選擇 (3)
可是通常文法並不知足上述格式,例如:
S -> Af | Be
此時,還想使用相同方法,咱們能夠經過肯定 Af 和 Be 推到最後的首終結符集(FIRST 集)來肯定。
簡單的說 FIRST 集就是一個文法符號串的開始符號集合
設 G=(VT,VN,S,P)是上下文無關文法,
FIRST(α)={a|α=>aβ,a∈VT,α,β∈v*}
若 α=> ε(通過0或多步推導能夠推出爲空串),則規定 ε ∈ FIRST(α)
FIRST(α) 是 α 的全部可能推導的開頭終結符或可能的 ε。
題目:
給定文法 G[S]: (1)S -> Af (2)S -> Be (3)A -> a (4)A -> cA (5)B -> b (6)B -> dB
詳解:
求(1)中的 Af 的 FIRST 集,注意,由於若是推出爲空時用 ε,因此 A 後面的 f 是沒用的,咱們只分析 A 的第一個終結符的集。
由於(3)和(4)都是由 A 推導,因此兩個都考慮
FIRST(Af) = FIRST(a) ∪ FIRST(cA) = {a,c}
同理可求出:
FIRST(Be)
FIRST(a)
FIRST(cA)
FIRST(dB)
若是僅適用 FIRST 只能根據首字符不一樣選擇產生式,若是首字符不一樣...
簡單的說 FOLLOW 集就是一個文法符號的後跟終結符號的集合。
設 G =(VT,VN,S,P)是上下文無關文法,A∈VN,S是開始符號。
FOLLOW(A)={a|S=>*…Aa…,a ∈VT}
如有 S=>*…A(就是說 A 已是最後一個時,沒有後面的),則規定 # ∈ FOLLOW(A)
FOLLOW(A) 是全部出如今緊接 A 以後的終結符或 「#」;
(1) 對於文法的開始符號 S,置 # 到 FOLLOW(S) 中;
(2)若 A -> αBaβ 是一個產生式,a 爲終結符,則把 a 加至 FOLLOW(B) 中;
(3)若 A -> αBβ 是一個產生式,則把 FIRST(β) - {ε} 加至 FOLLOW(B) 中;
(4)若 A -> αB 是一個產生式,或 A -> αBβ 是一個產生式,而 β =*> ε,
則把 FOLLOW(A) 加至 FOLLOW(B) 中
提示:
(1)就是說若是對開始符號求 FOLLOW(S) ,直接來個 # ∈FOLLOW(S) ,不過要表示成 {#}
(2)就是把後面的緊跟的終結符,就直接加到 FOLLOW 集
(3)正經的求 B 的 FOLLOW 集,就是 B 後面 β 的 FIRST(β) - {ε}
(4)分狀況:
注意: (4)中這裏是 FOLLOW(A) 加至 FOLLOW(B) ,就是左部的 FOLLOW 集,加到其推導出的右部的最後一個非終結符的 FOLLOW 集,
例如:須要識別輸入符號串 :bsAcD,求 FOLLOW(B) 的時候
此時 FOLLOW(A) 就含有 c,若是 A -> dB,即此時 FOLLOW(B) 也應該有 c
記憶方式:
提示:這是規則,不是求某個固定誰的 FOLLOW 集,而涉及多個非終結符的 FOLLOW 集,因此建議對每一個產生式對這 4 個規則都要考慮,否則很容易漏。
規則(1)看左側爲開始符;
規則(2)右側看 B 後是否緊跟終結符;
規則(3)右側看 B 後緊跟的是否有非終結符
規則(4)右側看 B 是否是最後一個,或 B 後面的能夠推出空串,間接最後一個
題目:
給定文法 G[S]: (1)S -> eT|RT (2)T -> DR|ε (3)R -> dR|ε (4)D -> a|bd
詳解:
計算時,要同時考慮四個規則是否知足,就是都要考慮
對產生式(1):
對產生式(2):
對產生式(3):
對產生式(4):
最終結果:
FOLLOW(S) = {#}
FOLLOW(T) = {#}
FOLLOW(R) = {a, b, #}
FOLLOW(D) = {d, #}
首先
注意區分 α 和 A
給定文法 G,對於產生式 A→α,α ∈ V*,則可選集 SELECT(A→α) 有:
(1)若 α ≠ ε,且 α ≠+ ε,則 SELECT(A→α) = FIRST(α)
(2)若 α ≠ ε,但 α =+ ε,則 SELECT(A→α) = FIRST(α) ∪ FOLLOW(A)
(3)若 α = ε,則 SELECT(A→α) = FOLLOW(A)
描述(關鍵):
(1)第1條說,α ≠ ε,且經過1次或屢次推不出 ε,SELECT(A→α) = FIRST(α)
(2)第2條是,α ≠ ε,α 經有限步可推出 ε,注意是一個 α,一個 A,SELECT(A→α) = FIRST(α) ∪ FOLLOW(A)
(3)第3條是說若是 α 自己就是 ε,SELECT(A→α) = FOLLOW(A)。
用表達式描述:
題目:
給定文法 G[S]: (1)S -> aA (2)S -> c (3)A -> aAS (4)A -> ε
詳解:
對(1)使用規則(1),得 SELECT(S -> aA) = FISRT(aA) = {a},此時雖然 A 能夠推出 ε,但規則中指的是總體 aA
對(2)使用規則(1),得 SELECT(S -> c) = FISRT(c) = {c}
對(3)使用規則(1),得 SELECT(A -> bAS) = FISRT(bAS) = {b}
對(4)使用規則(3),得 SELECT(A -> ε) = FOLLOW(A) = {a, c, #}
下面再回顧一下 FOLLOW 集的求法。
對本題求 FOLLOW(A) ,先求出 FIRST(S) = {a, c}:
對產生式(1):
對產生式(2):
對產生式(3):
對產生式(4):
綜合能夠看出 FOLLOW(A) = {a, c, #}
LL(1) 文法名稱的含義:
名稱| 含義
--- | ----
第一個L | 從左到右掃描輸入串
第二個L | 使用最左推導方法
1 | 只需查看一個輸入符號即可決定選擇哪一個產生式進行推導
文法是 LL(1) 文法的充分必要條件:
若某文法是LL(1)文法,那麼它可以惟一肯定選用的產生式
判斷文法是不是 LL(1) 文法步驟以下:
題目:
給定文法 G[S]: (1)E -> TE' (2)E' -> ATE'|ε (3)T -> FT' (4)T' -> MFT'|ε (5)F -> (E)|i (6)A -> +|- (7)M -> *|/
答案:
FIRST 集,FOLLOW 集,SELECT 集以下
(圖片來自教材:《編譯原理》張晶老師版)
很是要注意的是:
咱們知道判斷是否爲 LL(1) 文法條件是:根據同一非終結符的 SELECT 集是否相交,相交不是,不相交則是。
那麼 E 和 E' 的統一非終結符嗎?LL(1) 是否爲空,要比較 SELECT(E) 和 SELECT(E') 嗎?
答案是:不是統一非終結符,不用也不能比較他倆。由於他倆沒有關係,E' 相似因而中間變量,用其餘的字母替換也不影響此文法的功能
詳解:
由於:
SELECT(E' -> ATE') ∩ SELECT(E' -> ε)= ∅
SELECT(T' -> MFT') ∩ SELECT(T' -> ε)= ∅
SELECT(F -> (E)) ∩ SELECT(F -> i)= ∅
SELECT(A -> +) ∩ SELECT(A -> -)= ∅
SELECT(M -> *) ∩ SELECT(M -> /)= ∅
因此 SELECT 集相交是否爲空,文法 G[S] 是 LL(1) 文法
LL(1) 分析法,也稱預測分析法,採用這種方法的分析器由一張分析表、一個分析棧和一個控制程序組成,圖形化比表示爲:
(圖片來自教材:《編譯原理》張晶老師版)
這個語法分析過程徹底由預先根據文法設計的分析表 M 以及 分析棧S 進行控制(控制程序)
分析表和控制程序: 對於不一樣的文法,會有不一樣的分析表 M,但這種語法分析方法的總控程序是同樣的。
分析棧: 分析棧 S 用於存放文法符號。分析開始時,棧底先放一個 ‘ # ’,而後,放進文法的開始符號。隨着分析的展開,放入相應符號。
關於分析表:
(1)分析表是一個二維數組 M[A,a],其中 A 是非終結符,a 是終結符或 #。
(2)M[A,a] 中如有產生式,代表 A 可用該產生式推導,以求與輸入符號 a 匹配。
(3)M[A,a] 中若爲空,代表 A 不可能推導出與 a 匹配的字符串。
怎麼求分析表:
對文法 G 的每一個產生式 A->α 執行如下步驟:
(1)若 a∈SELECT (A->α), 則把 A->α 加至 M[A,a] 中;
(2)把全部無定義的 M[A,a] 標上「出錯標誌」。
爲了使表簡化,表中空白處爲出錯。
關於控制程序:
控制程序在任什麼時候候都是按分析棧棧頂符號 X 和當前的輸入符號 a 行事的。對於任何(X,a),總控程序每次都執行下述三個動做之一:
題目(同上):
給定文法 G[S]: (1)E -> TE' (2)E' -> ATE'|ε (3)T -> FT' (4)T' -> MFT'|ε (5)F -> (E)|i (6)A -> +|- (7)M -> *|/
答案:
FIRST 集,FOLLOW 集,SELECT 集以下
(圖片來自教材:《編譯原理》張晶老師版)
文法 G(E) 的預測分析表 M:
提示: 根據 SELECT 可選集對應
(圖片來自教材:《編譯原理》張晶老師版)
**用上述文法 G(E) 識別句子 i+i*i 的分析過程:**
(圖片來自教材:《編譯原理》張晶老師版)
詳細分析:
(1)執行順序:
步驟 1 爲初始化,分析棧放入 # 後,放入開始符號;
根據分析棧棧首爲 E,餘留串首爲 i,可定位到 E -> TE',逆序放入分析棧;
此時,棧首爲 T,串首爲 i,根據 T 和 i 定位到 T -> FT',逆序放入;
此時,棧首爲 F,串首爲 i,根據 F 和 i 定位到 F -> i,只有一個,直接放入;
此時,棧首爲 i,串首爲 i,則是識別成功,餘留串中第二個,依次...
(2)再根據預測分析表 M 選出產生式,沒有則調用出錯處理程序
(3)注意必定要逆序入分析棧,爲何要逆序放入分析棧呢?
由於是 LL(1) 分析法, LL(1) 文法是從左向右掃描,第二個L是最左推導的意思,最左推導就是每次都先推最左邊的一個非終結符。LL(1) 分析法是每次拿出分析棧的棧頂,若是不逆向最左端的非終結符就會在棧中,無法拿出來繼續推導。經過逆序,能夠實現每次拿出棧頂,就是拿出最左非終結符,就能夠實現最左推導
再回顧 LL(1) 文法名稱的含義:
名稱| 含義
--- | ----
第一個L | 從左到右掃描輸入串
第二個L | 使用最左推導方法
1 | 只需查看一個輸入符號即可決定選擇哪一個產生式進行推導
(4)當分析棧棧頂元素和輸入串最左端相同時,符合,分析棧棧頂出棧,識別下一個餘留輸入串。
對於 LL(1) 文法的分析能夠採用兩種方法:
爲每個非終結符編制一個子程序,
子程序的名字表示一個產生式左部的非終結符,
程序體是按該產生式右部的符號串順序編寫的。
每匹配一個終結符,則再讀入下一個符號,對於產生式右部的每一個非終結符,則調用相應子程序。
當一個非終結符對應多個候選式時,子程序體按 SELECT 集決定選用哪一個候選式。
題目:
給定文法 G[S]: (1)S -> AaB|Bb (2)A -> aD|D (3)B -> d|e|ε (4)D -> fD|g
答案:
FIRST 集,FOLLOW 集,SELECT 集以下
(圖片來自教材:《編譯原理》張晶老師版)
由於:
可知此文法是 LL(1) 文法
遞歸降低法分析:
主函數: scan; call S; if token = '#' then accept else error;
scan 表示調用詞法分析程序讀入下一個單詞至變量 token;
error 表示報錯處理。
函數 S: if token in {a,f,g} then { call A; match(a); call B; } else if token in {d,e,b} then { call B; match(b); } else error;
match(a) 表示若當前輸入單詞爲 a,則調用 scan,不然調用 error
上述遞歸降低分析器徹底是按照產生式的形式編寫的,處理針對四非終結符要編寫四個函數,還要有主函數。
當分析句子時,須要調用許多與文法非終結符對應的函數。
遞歸降低法的優勢:
(1)分析器編寫速度快
(2)因爲分析器非緊密對應性,容易保證語法分析器的正確性,至少使得任何錯誤都變得簡單和易於發現
遞歸降低法的缺點: (1)在語法分析期間高深度的遞歸調用影響了分析器的效率,許多時間須要花費在遞歸子程序之間的鏈接上 (2)若是瞎用的高級語言不容許遞歸,那麼就不能使用遞歸降低法,能夠用 LL(1) 分析法