默认的规则
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)操作
参考:《自己动手写编译器、链接器》