默認的規則
VN是非終結符集合
VT是終結符集合
終結符和非終結符的集合是互斥的
一般字母的大寫是非終結符,小寫是終結符
V=VN⋃VT
G是文法/語言
P是產生式,也就是推導的式子a→bc
S是開始符號
a→bc是推導bc→a是規約
::=和→或者其他的箭頭符號是一樣意思
字母後的∗表示0個或多個
字母後的+表示1個或多個
0型文法
設 G=(VN,VT,P,S),如果G的每個產生式α→β 都滿足 α,β∈(VN⋃VT)∗, 並且α至少有一個非終結符!
這樣的文法G就是一個0型文法!
- 0型文法的能力相當於Turing Machine
- 任何0型文法語言都是遞歸可枚舉的;屬於遞歸可枚舉集的文法,一定是0型語言
- 0型文法對文法規則的表示形式不作任何限制,從而爲定義的語言提供充分的描述功能
- 但是,0型文法不保證語言的遞歸性,不能確定語句是否合法,所以很少用於定義自然語言!
1型文法(上下文有關文法)
上下文有關文法對應線性有界自動機,在0型文法的基礎上,規定了
∀α→β,have∣α∣≤∣β∣
2型文法(上下文無關文法)
上下文物管文法對應下推自動機——下推自動機比其他有限狀態自動機複雜,除了有限狀態組成部分外,還包括一個長度不受限制的棧!
2型文法在1型文法的基礎上,規定了
∀α→β,haveα∈VN
- 上下文無關意味着產生式左部只有一個非終結符!
- 2型文法已經廣泛用於定義程序設計語言,這種文法規定了左部必須只能是單一的非終結符——非終結符通過文法規則的擴展性重寫是相互獨立的,不受其他符號的影響,所以稱爲上下文無關文法!<.font>
3型文法(正則文法)
正則文法對應有限狀態自動機,在2型文法的基礎上,規定了
A→α∣αB (右線性)
or
A→α∣Bα (左線性)
-
3型文法規則表示形式高度受限制,這使得正則語言可以用有限狀態自動機程序做高效分析。
-
有限狀態自動機有若干狀態,其中肯定有一個起始狀態,並至少有一個結束狀態
-
有限狀態自動機的輸入會導致狀態變化,並在到達目標狀態時停機
-
有限狀態自動機以文法左部的非終結符指示當前狀態,右部的終結符作爲輸入,終結符後的非終結符(右線性)就是自動機到達的下一狀態 —— 狀態原來就是非終結符 !
-
如果輸入結束並且此時自動機處於結束狀態,則輸入就作爲一個合法語句被接受,否則輸入語句就是非法語句
-
雖然正則文法簡單,但是規則限制太多,無法用於描述自然語言
BNF
::=是定義
∣是或
<>是用於括起非終結符
EBNF
擴展BNF是描述編程語言和形式語言的正規方式的上下文物管文法的元語法符號表示法
()內看做一項
.是一條生成規則的結束
{}內是零次或任意多次的項
[]內是0次或一次的項
""內是終結符
要用當然都是用的EBNF啦,BNF只有三個怎麼用?
LL
LL分析器是一種自頂向下的上下文無關語法分析器。第一個L是Left to right,第二個L是Leftmost derivation,即分析中使用最左推導。
使用LL分析的就是LL文法!
LL(k)
LL(k)是向前看k個符號纔可以決定如何推導,即選擇哪個產生式!
在語句的推導過程中,某個階段可能出現有多個產生式可以選擇的情況,如果選擇了某個產生式,並且在之後的過程中出錯,那麼就要進行回溯!!!
爲了不進行回溯,需要進行預測,即向前看k個符號來決定選擇哪個產生式!
——所以,不會回溯的文法就是LL(k)文法!
下面是幾個不同的LL(k):
LL(0):<A>::=<b> 不需要向前看(不需要選擇),當前的符號只有一個
LL(1):<A>::=<b>∣<c>∣<d> 需要向前看一個,才能決定選擇哪個
LL(2):<A>::=<bd>∣<c>∣<bc> 需要向前看兩個,才能決定選擇哪個
FIRST集
設G=(VN,VT,P,S)是上下文無關文法, FIRST(X)={a∣X⇒∗aβ,a∈VT,X,β∈V∗}。若X⇒∗ε, 則規定 ε∈FIRST(X),稱 FIRST(X)是X的開始符號集或者首字符集!!!
X有若干的產生式,在這些產生式中部分產生式的右部的第一個字符是終結符,則這個終結符就是FIRST集中的元素!!!
計算FIRST集
計算FIRST集就是找出屬於FIRST集的元素(話說,這一通集合計算後,就是集合論集大拿了吧)
- ifX∈VT,soFIRST(X)=X
- ifX∈VN,andX→a...,a∈VT,soa∈FIRST(x)
- ifX∈VN,X→ϵ,soϵ∈FIRST(X)
- 若X∈VN;Y1,Y2,...,Yi∈VN,且有產生式X→Y1Y2...Yi都可以⇒∗ϵ 時(1≤i≤n),則 FIRST(X)=FIRST(Y1)⋃FIRST(Y2)⋃......⋃FIRST(Yi)
- 當(4)中所有的Yi⇒∗ϵ,(i=1,2,...,n),則FIRST(X)=FIRST(Y1)⋃FIRST(Y2)⋃......⋃FIRST(Yn)⋃{ϵ}
- 左右都只有一個符號的話,左右的FIRST集等價
- 遇到右部第一個符號是非終結符時,需要迭代進去!!!
FOLLOW集
設G=(VN,VT,P,S)是上下文無關文法,X∈VN,S是開始符號,FOLLOW(X)={a∣S⇒∗μXβ, 並且 a∈VT,a∈FIRST(β),μ∈VT∗,β∈V+} ,
若 S⇒μXβ,並且 β⇒∗ε,則 #∈FOLLOW(X)
- #作爲輸入串的結束符——因爲FOLLOW是空嘛,所以自然要結束了!!!
X是產生式的右部,前面的符號是終結符或空,後面的符號中的第一個符號就是X的FOLLOW
計算FOLLOW集
- 設S爲文法開始符號,{#}加入FOLLOW(S)中
- 若A→aBβ是一個產生式,則把FIRST(β)的非空元素加入FOLLOW(B)中。如果β⇒∗ε,則把FOLLOW(A)也加入FOLLOW(B)中
- 反覆使用(2)直到每個非終結符的FOLLOW集不再增大爲止
SELECT集
有上下文無關文法的產生式X→a,X∈VN,a∈V∗
如果a⇏∗ε,則SELECT(X→a)=FIRST(X)
如果a⇒∗ε,則SELECT(X→a)=(FIRST(a)−{ε})⋃FOLLOW(X)
SELECT(X→a)要麼是X右部第一個符號,要麼是X跟着的符號中的第一個符號
計算SELECT集
有產生式A→a,這個a就是單個符號,不是幾個符號的或!!!
- 求FIRST(a)
- 若ε∈/FIRST(a),則令SELECT(A→a)=FIRST(a),否則求FOLLOW(A),令SELECT(A→a)=FIRST(a)⋃FOLLOW(A)
計算三種集合的例子
有下面幾個產生式
E→TE′
E′→+TE′∣ε
T→FT′
T′→∗FT′∣ε
F→id∣(E)
求這幾個產生式的三種集合!
解:
求FIRST集
1)先看看簡單的右部第一個符號是終結符的產生式
FIRST(F)={id,(}
FIRST(T′)={∗,ε}
FIRST(E′)={+,ε}
2)再看右部第一個符號是非終結符的產生式
E的右部第一個是T,T的右部第一個是F
由於F不能推導出ε,所以 FIRST(T)=FIRST(F)={id,(}
由於T不能推導出ε,所以 FIRST(E)=FIRST(T)={id,(}
求FOLLOW集
1)先看E,E後面的第一個符號只有{)}, 則FOLLOW(E)={),#}
2)再看E′,E′後面的第一個符號是空, 即E′→ε,則FOLLOW(E′)=FOLLOW(E)={),#}
3)T後面第一個符號是E′,則FOLLOW(T)要算上FIRST(E′),還有E′→ε,則FOLLOW(E′)該加入FOLLOW(T)中,則FOLLOW(T)={+,),#}
4)T′後面是空, 則FOLLOW(T′)=FOLLOW(T)={+,),#}
5)F後面是T′,則FIRST(T′)的非空元素該加入FOLLOW(F)。由於T′→ε,則FOLLOW(T′)要加入FOLLOW(f),則FOLLOW(F)={∗,+,),#}
求SELECT集
select處理的式子右部必須只有一個符號
1)因爲FIRST(TE′)中沒有空,所以SELECT(E→TE′)=FIRST(E)={id,(}
2)因爲FIRST(+TE′)中沒有空,所以SELECT(E′→+TE′)=FIRST(+TE′)={+}
3)因爲FIRST(ε)是空,所以SELECT(E′→ε)=(FIRST(ε)−{ε})⋃FOLLOW(E′)={),#}
4)因爲FIRST(FT′)中沒有空,所以SELECT(T→FT′)=FIRST(FT′)={id,(}
5)因爲FIRST(∗FT′)中沒有空,所以SELECT(T′→∗FT′)=FIRST(∗FT′)={∗}
6 )因爲FIRST(ε)是空,所以SELECT(T′→ε)=(FIRST(ε)−{ε})⋃FOLLOW(T′)={+,),#}
7)因爲FIRST(id)中沒有空,所以SELECT(F→id)=FIRST(id)={id}
8)因爲FIRST((E))中沒有空,所以SELECT(F→(E))=FIRST((E))={(}
LL(1)(常用)
一個上下文無關文法是LL(1)文法的充分必要條件是對每個非終結符X的兩個不同產生式X→a,X→β,滿足SELECT(X→a)⋂SELECT(X→β)=ϕ,其中a,β不同時⇒∗ε
- LL(1)文法既不是二義性的,也不含左遞歸,對LL(1)文法的所有句子均可進行確定的自頂向下語法分析
上下文無關文法 + 相同左部的產生式的SELECT集爲空 = LL(1)文法
再看這個例子LL(1):<A>::=<b>∣<c>∣<d>,同樣的左部,可以有三個不同的右部,只要保證每個右部的第一個符號不是相同的,那麼就能向前看一個就能選擇出產生式!!!
遞歸子程序法
確定的自頂向下分析方法有兩種:預測分析法和遞歸子程序法。
遞歸子程序法要求文法滿足LL(1),實現思想是對文法中每個非終結符編寫一個遞歸過程,每個過程的功能是識別由該非終結符推出的串,當某個非終結符的產生式有多個候選式時,能夠按LL(1)形式可唯一地確定選擇某個候選進行推導。
由於遞歸子程序法對每個過程可能存在直接或間接的遞歸調用,在退出之前可能又被調用。所以,通常在入口處需要保存現場,出口出恢復現場!!!這通常使用棧來實現。
1.語法定義舉例(遞歸子程序法)
<program> ::= <statement> { statement } "#"
<statement> ::= <expression> "\n"
<expression> ::= <multiplicative_expression>
{ "+" <multiplicative_expression> | "-" <multiplicative_expression> }
<multiplicative_expression> ::= <primary_expression>
{ "*" <primary_expression> | "/" <primary_expression> }
<primary_expression> ::= <integer> | "(" <expression> ")"
<integer> ::= "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"
2.語法描述圖舉例(遞歸子程序法)
3.構造語法分析程序舉例(遞歸子程序法)
void main(){
get_token();
program();
}
void program(){
while(token != '#')
statement();
}
void statement(){
if(token != '\n')expression();
else get_token();
}
void expression(){
multiplicative_expression();
if(token == '+' || token =='-'){
get_token();
multiplicative_expression();
}
}
void multiplicative_expression(){
primary_expression();
if(token =='*' || token =='/'){
get_token();
primary_expression();
}
}
void primary_expression(){
primary_expression();
if(token == INTEGER)get_token();
else if(token=='('){
get_token();
expression();
get_token();
if(token !=')')error("lack of right parenthesis");
}
else error("exception");
}
文法等價
根據前面的分析可知,LL(1)文法中,兩個相同左部的產生式的幾個候選式的第一個符號不可能是相同的,即候選式沒有左公共因子!因爲如果有的話,那麼這個文法至少是LL(k),k≥2。
因爲要求FIRST集,所以左因子肯定不能遞歸,不然就沒完沒了了!
1.提取左邊的公共因子
如果文法中有A→aβ∣aγ的產生式,這導致了相同產生式的右部的FIRST集相交,SELECT(A→aβ)⋂SELECT(A→aγ)=ϕ!
解:
現在有A→aβ∣aγ
=>A→a(β∣γ)
引入A′
=>A→aA′,A→β∣γ
寫成更一般的形式
=>A→aA′,A′→β1∣β2∣...∣βn
如果β1∣β2∣...∣βn中仍然含有左公共因子,就要再次進行提取,反覆知道所有的產生式都沒用左公共因子爲止!!!
2.消除左遞歸
一個文法含有下列形式的產生式之一時:
- A→Aβ,A∈VN,β∈V∗
- A→Bβ,B→Aa,A、B∈VN,a、β∈V∗
則稱該文法是左遞歸的!
一個文法是左遞歸時,不能採用自頂向下分析法。
消除左遞歸的方法:
1) 消除直接左遞歸,把直接左遞歸改寫爲右遞歸,比如對文法
G:S→Sa,S→b
改寫成
G:S→bS′,S′→aS′∣ε
S最後只有一個b,說明S串第一個符號就是b
S能一直遞歸,並提出一個a,則說明S串爲baaaaa…aaa
S -> bS’,後面的aaaa…aaa可以當做S’.然後因爲要求是去除左遞歸,所以這裏就用右遞歸S’->aS’
最後,S’也會迭代完,則S’->ε
一般情況下(多個具有相同左部的左遞歸產生式),假定關於A的全部產生式爲
A→Aa1∣Aa2∣......∣Aan∣β1∣β2∣......∣βn,其中ai(1≤i≤m)不等於空,βj(1≤j≤n)不以A開頭
消除左遞歸後改寫成
A→β1A′∣β2A′∣......∣βnA′
A′→a1A′∣a2A′∣......∣anA′∣ε
和上面的例子同理,左遞歸的變成右遞歸,原來左遞歸後面的終結符放到前面來(1)
右部是終結符的也變成右遞歸,終結符放在前面(2)
2)消除間接左遞歸。先將間接左遞歸變成直接左遞歸,然後按(1)操作
參考:《自己動手寫編譯器、鏈接器》