編譯原理學習筆記----
Thompson算法由正規式構造NFA
例如:求正規式 1(0|1)*101 的NFA
首先將正規式r=1(0|1)*101分解成r=r1,r2r3
將r2,r3展開得:
不確定有窮自動機(NFA)
確定有窮自動機(DFA)
一個確定的有窮自動機M是一個五元組:M=(K, ∑,f,S,Z)其中,
1)K是一個有窮集,他的每個元素稱為一種狀態。
2) ∑是一個有窮字母表,他的每個元素稱為一個輸入符號,所以∑稱為輸入符號表。
3)f是轉換函數,是KX∑-->K 上的映像,例如f(ki,a)=kj這就意味着,當前狀態為k,輸入字符a后,將轉換到下一狀態kj,我們把kj稱為ki的一個后繼狀態;
4)S屬於K,是唯一的一個出態。
5)Z屬於K,是一個終態,終態也稱為可接受狀態或結束狀態。
例如:
這個DFA可以表示一個狀態圖:
也可以用狀態矩陣顯示,行表示狀態,列表示輸入符號,終態在表的右端標1,非終態在表的右邊標0。
NFA相對於DFA的2點區別:
(NFA的不確定性)
1.當前狀態下,對同一字符可能有多於一個的下一狀態。
2.可能存在ε狀態轉移。
NFA轉DFA
例如:給出NFA的圖
求其最小DFA,求法如下:
(ps:因為集合C和D接收一個輸入字符a或b時都得到同一個集合(即同一狀態),所以C和D不可區分,可以去掉D這一行)。
由正規式的NFA構造CFG(上下文無關文法)
例如:從正規式r=(a|b)*abb的NFA構造CFG
正規式對應的NFA為:
一般方法:
A → HT
H →ε| aH | bH
T → abb
所以,可以改寫為:
A0 → aA0|bA0|aA1
A1 → bA2
A2 → bA3
A3 → ε
將每一個狀態看做一個非終結符,相當於照圖寫出狀態經一步能達到的狀態轉移。
文法類型
若文法G=(N,T,P,S)的每個產生式α→β中,均有α∈(N∪T)*,且至少含有一個非終結符,β∈(N∪T)*,則稱G為0型文法。
對0型文法施加以下第i條限制,即得到i型文法。
1.G的任何產生式α→β(S→ε除外)滿足|α|≤|β|;
2.G的任何產生式形如A→β,其中A∈N,β∈(N∪T)*;
3.G的任何產生式形如A→a或者A→aB(或者A→Ba),其中A和B∈N,a∈T。
LL(1)文法:一種自上而下的分析方法
構造LL(1)文法:
第一步:計算first集合
算法:每次選取右部第一個符號,推導直至右部第一個符號是終結符,把其加入First集合中。
例如:計算以下文法的First集合
L →E;L|ε
E →TE'
E'→+TE'|-TE'|ε
T →FT'
T'→*FT'|/FT'|mod FT'|ε
F →(E)|id|num
計算First集合要自下而上分析。
因為(E)第一個符號為終結符,所以將(直接加入First集合,同理id,num也加入First集合。
再如:T->FT',右部第一個符號是非終結符F,將其推導至F->(E)|id|num,發現3個推導式中第一個符號都為終結符,所以將他們都加入First集合。
需要注意的是,若產生式中包含ε也要加入First集合。
由以上的分析方法可計算出:
FIRST(F/T/E)= {( id num}
FIRST(T') = {* / mod ε}
FIRST(E') = {+ - ε}
FIRST(L) = {ε ( id num}
第二步:計算Follow集合(Folow集合的算法稍微復雜一些,教科書上的解釋很復雜,我自己總結了一下,通俗的描述出來)
算法:4條規則( 求Follow(U) )
1.U是文法開始符號,則直接加入 # 。
2.形如 ...Ua... ,加入 a 。
3.形如 S->...UP... ,加入First(P),並且,如果First(P)中包含 ε 符號時,要去掉 ε 並加入Follow(S)。
4.形如 P->...U ,加入Follow(P)。
例如:計算以下文法的Follow集合
L →E;L|ε
E →TE'
E'→+TE'|-TE'|ε
T →FT'
T'→*FT'|/FT'|mod FT'|ε
F →(E)|id|num
計算Follow集合要自上而下分析。
因為L是開始符號,所以Follow(L)={#}
求E的Follow集合時,先將跟E有關的式子列出
L →E;L|ε
F →(E)|id|num
形如...Ua...,所以將;和)加入Follow(E)集合。
由以上分析方法即可求出:
FOLLOW(L) = {#}
FOLL0W(E/E')= {) ;}
FOLLOW(T/T')= {+ - ; )}
FOLLOW(F) = {+ - * / mod ) ;}
第三步:構造預測分析表
首先觀察一下預測分析表和咱們求出的First和Follow集合:
FIRST(F/T/E)= {( id num}
FIRST(T') = {* / mod ε}
FIRST(E') = {+ - ε}
FIRST(L) = {ε ( id num}
FOLLOW(L) = {#}
FOLL0W(E/E')= {) ;}
FOLLOW(T/T')= {+ - ; )}
FOLLOW(F) = {+ - * / mod ) ;}
比如,求L這行,發現First(L)集合中有ε ( id num這幾個符號,因此在這行的這幾個符號中填入L的產生式(ε產生式可以忽略,並且如果有多個產生式,就填相關的產生式),又因為First(L)中包含ε,因此將這行存在於Follow(L)集合中所有的終結符填入ε。
第四步:根據預測分析表以格局的形式寫出對某輸入序列的分析過程。
例如:
匹配過程就是匹配到棧內容中的非終結符時,根據當前輸入中匹配的終結符查預測分析表,選取相應的產生式,將產生式倒過來寫,替換棧中匹配的非終結符。
LL(1)文法的判別:
簡單判斷:看看文法有無左因子,有無左遞歸,有就不是。
定義判斷:
假設文法中有A->B|C
1.若First(B)∩First(C)有終結符,則不是LL(1)文法。
2.若B=>*ε 而且 C=>*ε,則不是LL(1)文法。
3.若B=>*ε 且First(C)∩Follow(A)有終結符,則不是LL(1)文法。
LR(0)文法:
LR(0)項目集:產生式右部用.分隔。例如:E->T*F 的LR(0)項目有
E->.T*F
E->T.*F
E->T*.F
E->T*F.
識別活前綴的DFA:
例如:文法為
E→E*T|T
T→T+F|F
F→id
給出它的識別活前綴的DFA。
解答:
就是將LR(0)項目集進行推導,直至產生式推導完畢。但是,要注意的是,如果推導出的產生式的 . 后面有非終結符,要將非終結符的活前綴一起寫出來。例如,I5狀態中的 E->E.*T 經過 * 推導出 E->E*.T , T 是非終結符,所以,下一個狀態中要加入
T->.T+F
T->.F
F->.id
項目集存在移進/歸約沖突:某一狀態即可歸約(.在產生式最后,推導完畢)又可以經過某字符到達下一狀態。
例如:某文法活前綴的DFA
分析可知:I2 , I11存在移進/歸約沖突。
LR(0)文法判斷:存在移進/歸約或者歸約/歸約沖突沖突就不是LR(0),如果沖突可解決就是SLR(1)。
SLR(1)文法判斷:
當LR(0)文法存在移進/歸約沖突時即狀態集中同時存在
A→β1.β2 和 B→β.
如果FIRST(β2)∩FOLLOW(B)=Φ,則沖突可解決,是SLR(1)文法。
存在歸約/歸約沖突時即同時存在
A→α. 和 B→β.
如果FOLLOW(A)∩FOLLOW(B)=Φ,則沖突可解決,是SLR(1)文法。
句型、短語、直接短語、句柄
短語:以非終結符為根子樹中所有從左到右的葉子。
直接短語:子樹中只有父子兩代的子樹的葉子節點。
句柄:子樹中最左邊的那棵只有父子兩代的子樹的所有葉子結點自左至右排列起來,就是該句型的句柄。
例如:有文法
E→E+T|T
T→T*F|F
F→id
和句型:id1+id2*id3。
畫出其分析樹:
句柄:因為最左部有子樹F1-id1只有父子兩代關系,所以句柄為id1。
后綴式、三地址碼、三元式、四元式
后綴式計算方法:將最后計算的計算符號放到最后,把它的左操作數放到開始(ps:左操作數如果是一個表示達也要重復之前操作並將結果放到中間),右操作數如果是一個表達式,則重復之前的操作將結果放到中間。
例如:求 3+5*2/7 的后綴式
分析:最后運算的符號是+,左操作數是3,右操作數是表達式5*2/7,將3放到最前面,+放到最后面,接着計算5*2/7的后綴式,同樣5放到最前面,*放到最后並加入到之前的后綴式中間,計算2/7的后綴式,2放到最前面,/放到最后面,右操作數7放到中間,並將其整體加入到之前的后綴式中間。
因此,得到
3 5 2 * 7 / +
三元式、四元式
例如:計算 x:=(a+b)*(a+b) 的三元式和四元式。
首先畫出它的注釋語法樹:
由注釋語法樹從葉子節點寫到根結點很容易可得三元式:
(1)(+, a, b )
(2)(+, a, b )
(3)(*,(1),(2))
(4)(:=,x, (3))
和四元式:
(1)(+, a, b, T1)
(2)(+, a, b, T2)
(3)(*, T1,T2,T3)
(4)(:=,x, T3,T4)
其實四元式也就是給三元式每一步生成的式子用一個變量表示。
三地址碼計算方法
例如,求 x := a + b * c 的三地址碼序列:
(*,b,c,T1)
(+,a,T1,T2)
(:=,x,T2,T3)
將最先運算的表達式用一個變量T1表示,依次用T2,T3...表示其他運算,三地址碼序列就是將表達式樹用四元式組表示。