《編譯原理》-用例題理解-自頂向下語法分析及 FIRST,FOLLOW,SELECT集,LL(1)文法


《編譯原理》-用例題理解-自頂向下語法分析及 FIRST,FOLLOW,SELECT集,LL(1)文法

此編譯原理確定某高級程序設計語言編譯原理,理論基礎,學習筆記

本筆記是對教材《編譯原理》- 張晶老師版 做學習筆記。

最近在學《編譯原理》,前三章感覺還可以理解,到了第四章就感覺這難度就上來了。就是說過了詞法分析,剛到語法分析,就開始頭大了,於是想做個筆記,本篇就是第 4 章的筆記。

(一)前言

第4章 - 自頂向下的語法分析

語法分析

語法分析是在詞法分析識別出的單詞符號串的基礎上,分析並判定句子的語法結構是否符合語法規則

自頂向下分析法

自頂向下分析法就是從文法的開始符號出發,不斷建立直接推導,試圖構造一個最左推導序列,最終由它推導出與輸入符號串完全匹配(相同)的句子。

從語法樹的角度看,自頂向下分析法就是以開始符號為根節點,試圖向下構造一棵語法樹,其端末結符號串與輸入符號串相同

問題1:左遞歸問題

若采用自頂向下的語法分析,應消除文法中存在的左遞歸。

因為左遞歸的存在,有可能使推導不能結束,分析陷入循環狀態。
例如:A → Aa | b

左遞歸還比較好理解,例如:我需要匹配的字符串是 bsd 就需要從左端 b 開始是被,推導時,先用 A -> Aa 推導 AAa,此時不符合條件,但因為還有非終結符,不能結束,而是繼續推導。此時就會陷入死循環。

所以要避免這類情況就要消除左遞歸。

消除文法的左遞歸

課本上直接左遞歸,間接左遞歸,提取左公因子書上比較詳細,一般可以理解,跳過了

問題2:回溯問題

因為推導時,右側有的情況,從各種可能的選擇中隨機挑選一種,並希望它是正確的。如果以后發現它是錯誤的,必須退回去,再試另外的選擇這種方式稱為回溯

回溯代價極高,效率很低,所以也要避免。

所以需要FIRST 集,FOLLOW 集和 SELECT 集,這一堆集出馬了。

(二)關於 FIRST 集 - 首終結符集

如果一個文法的每個產生式的右部都由終結符開始,有相同左部的產生式,它們的右部由不同的終結符開始,這樣的文法在推導過程中就可以根據當前的輸入符號來決定選擇哪個產生式往下推導,它的分析過程是唯一確定的,不會產生回溯現象

簡單的說,右部都以非終結符開頭,每個產生式的右部第一個非終結符又都不一樣就更好了,我們就可以根據語法唯一的選擇產生式,

例如:需要識別輸入符號串 bsd

文法:
(1)S -> aBC
(2)B -> cC
(3)C -> bB
(4)C -> d
......
我們知道 b 就可以選擇 (3)

但是一般文法並不滿足上述格式,例如:
S -> Af | Be
此時,還想使用相同方法,我們可以通過確定 Af 和 Be 推到最后的首終結符集(FIRST 集)來確定。

FIRST 集的定義

簡單的說 FIRST 集就是一個文法符號串的開始符號集合

設 G=(VT,VN,S,P)是上下文無關文法,
FIRST(α)={a|α=>aβ,a∈VT,α,β∈v*}
若 α=
> ε(經過0或多步推導可以推出為空串),則規定 ε ∈ FIRST(α)

FIRST(α) 是 α 的所有可能推導的開頭終結符或可能的 ε。

例題 4.3:求 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)

(三)關於 FOLLOW 集 - 后隨集

如果僅適用 FIRST 只能根據首字符不同選擇產生式,如果首字符不同...

FOLLOW 集的定義

簡單的說 FOLLOW 集就是一個文法符號的后跟終結符號的集合。

設 G =(VT,VN,S,P)是上下文無關文法,A∈VN,S是開始符號。
FOLLOW(A)={a|S=>*…Aa…,a ∈VT}
若有 S=>*…A(就是說 A 已經是最后一個時,沒有后面的),則規定 # ∈ FOLLOW(A)

FOLLOW(A) 是所有出現在緊接 A 之后的終結符或 “#”;

FOLLOW 計算規則

(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)分情況:

  • 如果 A -> αB,就把 FOLLOW(A) 加至 FOLLOW(B) 中
  • A -> αBβ 是一個產生式,此時 β 可以推成 ε,就是相當於也能推出 A -> αB,也把 FOLLOW(A) 加至 FOLLOW(B) 中

注意: (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 后面的可以推出空串,間接最后一個

例題 4.4:求 FOLLOW 集

題目:

給定文法 G[S]:
(1)S -> eT|RT
(2)T -> DR|ε
(3)R -> dR|ε
(4)D -> a|bd

詳解:
計算時,要同時考慮四個規則是否滿足,就是都要考慮
對產生式(1):

  • 1.滿足規則(1),因為 S 是開始符號,可以得到 FOLLOW(S) = {#}
  • 2.不滿足規則(2)
  • 3.滿足規則(3),對 S -> RT,應把 FIRST(T) - {ε} = {a,b} 加到 FOLLOW(R);
  • 4.滿足規則(4),將 FOLLOW(S)={#} 加到 FOLLOW(T)

對產生式(2):

  • 1.不滿足規則(1)
  • 2.不滿足規則(2)
  • 3.滿足規則(3),對 T -> DR,應把 FIRST(R) - {ε} = {d} 加到 FOLLOW(D);
  • 4.滿足規則(4),將 FOLLOW(T)={#} 加到 FOLLOW(R)

對產生式(3):

  • 1.不滿足規則(1)
  • 2.不滿足規則(2)
  • 3.不滿足規則(3)
  • 4.不滿足規則(4),前后 R 和 R 一樣不用加

對產生式(4):

  • 1.不滿足規則(1)
  • 2.不滿足規則(2)
  • 3.不滿足規則(3)
  • 4.不滿足規則(4)

最終結果:

FOLLOW(S) = {#}
FOLLOW(T) = {#}
FOLLOW(R) = {a, b, #}
FOLLOW(D) = {d, #}

(三)關於 SELECT 集 - 可選集

首先

SELECT 集的定義

注意區分 α 和 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)。

用表達式描述:

例題 4.5:求 SELECT 集

題目:

給定文法 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):

  • 1.滿足規則(1),S 是開始符號,FOLLOW(S) 為 {#}
  • 2.不滿足規則(2)
  • 3.不滿足規則(3)
  • 4.滿足規則(4),FOLLOW(S) = {#} 加到 FOLLOW(A)

對產生式(2):

  • 1.不滿足規則(1)
  • 2.不滿足規則(2)
  • 3.不滿足規則(3)
  • 4.不滿足規則(4)

對產生式(3):

  • 1.滿足規則(1)
  • 2.不滿足規則(2)
  • 3.不滿足規則(3),FIRST(S) - {ε} = {a, c} 加到 FOLLOW(A)
  • 4.不滿足規則(4)

對產生式(4):

  • 1.不滿足規則(1)
  • 2.不滿足規則(2)
  • 3.不滿足規則(3)
  • 4.不滿足規則(4)

綜合可以看出 FOLLOW(A) = {a, c, #}

(四)關於 LL(1) 文法

LL(1) 文法名稱的含義:

名稱 含義
第一個L 從左到右掃描輸入串
第二個L 使用最左推導方法
1 只需查看一個輸入符號便可決定選擇哪個產生式進行推導

文法是 LL(1) 文法的充分必要條件:
若某文法是LL(1)文法,那么它能夠唯一確定選用的產生式

判斷文法是否是 LL(1) 文法步驟如下:

  1. 求出能推出ε的非終結符;
  2. 計算FIRST集;
  3. 計算FOLLOW集;
  4. 計算SELECT集;
  5. SELECT集相交是否為空。
例題 4.6:判斷文法是否是 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) 分析法(預測分析法)

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),總控程序每次都執行下述三個動作之一:

  • 若 X=a=‘ # ’,則分析成功。
  • 若 X=a≠‘ # ’,則把 X 從棧頂彈出,讓 a 指向下一個輸入符號。
  • 若 X 為一非終結符,則查分析 表M。若 M[X,a] 中為A—產生式,將 A 自棧頂彈出,將產生式右部符號串按逆序逐一推入棧中;當產生式為 A 時,則只將 A→ε彈出即可。若 M[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) 文法的分析可以采用兩種方法:

  • 一種是上一節介紹的 LL(1) 分析法,它利用 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) 分析法


免責聲明!

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



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