递归下降分析
设有文法G,
G: S → cAd
A → ab
那么,识别S的递归下降函数的伪代码为:
//识别 S
void S()
{ match(c);
A();
match(d);
}
//识别 A
void A()
{
match(a);
match(b);
}
EBNF表示法
为了避免重复扫描词法分析输出的单词序列(提高效率),需要先将文法G采用EBNF表示法,然后写出递归下降分析程序
- EBNF使用[ ]表示选择(可有可无的部分)
如:A → a[b]
代码实现为:void A() { match(a); if(token==b){ match(b); } }
- EBNF使用{ }表示重复,{ }中的内容可以重复0~n次
A → Aα|β(左递归) => A → β{α}
A → αA|β(右递归) => A → {α}β
如:exp→ term{addop term}
addop→ +|-
代码实现为:void exp() { term(); while (token=="+" || token=="-") { match(token); term(); } }
消除左递归、提取左公因子
-
左递归
如果一个文法中有一个非终结符号A使得对某个串α存在一个推导A → Aα,那么这个文法就是左递归的。递归分为立即左递归和非立即左递归:立即左递归:
1)A → β
2)A → Aα
非立即左递归:
1)A→aB
2)A→Bb
3)B→Ac
4)B→d -
消除
- 立即左递归:假设当前规则开始符号为A,且涉及左递归,则将本规则内的A替换为A’,并将规则右侧的A’移动到末尾;同时,将其他以A为开始符号,但不涉及左递归的规则末尾追加A’。最后添加一条规则:A’ → ε,作为递归终止条件。
将A → Aα | β 转换为
A → β A’
A’ → α A’ | ε - 非立即左递归:先将非立即左递归化为立即左递归,再按照上述方式消除即可。
对于非立即左递归:
1)A→aB
2)A→Bb
3)B→Ac
4)B→d
先将其变为立即左递归
1)B→aBc
2)B→Bbc
3)B→d
可化简为:B→aBc | Bbc | d
然后按照上面的规则进行转换即可
1)B→aBcB’ |dB’
2)B’→bcB’ |ε
最后进行整合
1)A→aB
2)A→Bb
3)B→(aBc|d)B’
4)B’→bcB’|ε
- 立即左递归:假设当前规则开始符号为A,且涉及左递归,则将本规则内的A替换为A’,并将规则右侧的A’移动到末尾;同时,将其他以A为开始符号,但不涉及左递归的规则末尾追加A’。最后添加一条规则:A’ → ε,作为递归终止条件。
-
提取左公因子
假定有如下规则:
S → aB1|aB2|aB3|aB4|…|aBn|y
显然前n项拥有一个共同的左公因子:a,所以可以把它提取出来。
提取规则:将规则S中公因子之后的变化的部分用一条新规则S’代表,其他保持不变,变化的部分在S’中用或连接。即:
S → aS’|y
S’ → B1|B2|B3|B4|…|Bn
自上而下分析
LL(1)分析
第1个“L”指的是由左向右地处理输入;
第2个“L”指的是它为输入串找出一个最左推导;
括号中的数字1意味着它使用输入单词序列中的一个单词预测分析的动作。
- 给定一个简单的文法G(E)
E → TE’
E’ → +TE’ | ε
T → FT’
T’ → *FT’ | ε
F → (E) | i
求First集
- 若X是终结符或ε,则First(X) ={X}
- 若X是非终结符,则对于X对应的每个产生式 X→A1A2…An,
①First(X)包括 First(A1)-{ε}
②若对于任意 i < n,均有 ε∈First(Ai) ,那么 First(X)也包括First(Ai+1)-{ε}
③若对于任意 i ≤ n,均有 ε∈First(Ai),则First(X)也包括ε
- 例:求文法G(E)的First集
分析:不妨先调整下文法的顺序,使右侧直接面临终结符的文法靠前
E’ → +TE’ | ε
T’ → *FT’ | ε
F → (E) | i
T → FT’
E → TE’First 内容 来源 E (,i First(T) E’ +,ε 直接读取 T (,i First(F) T’ *,ε 直接读取 F (,i 直接读取
求Follow集
Follow 集仅针对非终结符,并且不包含ε
求解Follow(A) ,应重复以下步骤,直到所有的Follow集合不再变化为止:
- 若A是开始符号(出现在任意一条规则的左侧),则Follow(A)包含$
- 若存在产生式B→αAβ(或B→Aβ),则Follow(A)包含First(β)-{ε}
- 若存在产生式B→αAβ(或B→Aβ或B→αAε),且ε∈First(β),则
Follow(A)包含Follow(B)
解释: ∀c∈Follow(B),∃ 句型 …Bc…,当β取ε时,存在推导B→αA,故存在句型 …αAc…,显然c∈Follow(A)。
考虑到A还可以出现在其他产生式右侧,如 D→θA,Follow(A)的范围不会小于Follow(B)。
例:求文法G(E)的Follow集(需要用到先前的First集)
① E → TE’
② E’ → +TE’ | ε
③ T → FT’
④ T’ → *FT’ | ε
⑤ F → (E) | i
First | 内容 | 来源 |
---|---|---|
E | $,) | 开始符号、⑤ |
E’ | $,) | 开始符号、①Follow(E) |
T | $,+,) | 开始符号、①②First(E’)-{ε}、①②Follow(E’) |
T’ | $,+,) | 开始符号、③Follow(T) |
F | $, * ,+,) | 开始符号、③④First(T’)-{ε}、③Follow(T)、④Follow(T‘) |
显然,应该重点关注导致First(X)-{ε}出现以及可以直接获取终结符的产生式;此外,即使某些产生式左侧不属于开始符号,可能因为包含其它式的Follow集而获得$符。
构造LL(1)分析表
分析表以非终结符作为行索引,以终结符作为列索引,分析表的元素位置存储产生式。
过程描述: 把每一个非终结符的每一个候选产生式放到分析表的正确位置上,使得当栈顶出现该非终结符、面临某些终结符的时候,能够选择正确的候选产生式进行扩展”。
如何正确放置产生式?
- 对于产生式 A→α,且 ε 不在 First(α) 中
- 该式应该被放在A所在的行中
- 该式应该被放在First(α)所在的列中
- 对于产生式 A→ε
- 该式应该被放在A所在的行中
- 该式应该被放在Follow(A)所在的列中
- 注意分析表中没有 ε 列
构造G的分析表M[A, a], 确定每个产生式A→α在表中的位置
视频演示
- 对文法G的每个产生式A→α执行第2步和第3步;
- 对每个终结符 a∈FIRST(α),把A→α加至M[A, a]中;
- 若ε∈FIRST(α),则对任何b∈FOLLOW(A)把A→α加至M[A, b]中。
如果所有产生式都已扫描完毕,执行第4步。 - 把所有未填入产生式的位置标记为“出错”
二义性及其消除
如果G是左递归或二义的,那么,M至少含有一个多重定义入口。因此,消除左递归和提取左因子将有助于获得无多重定义的分析表M。
可以证明,一个文法G的预测分析表M不含多重定义入口,当且仅当该文法为LL(1)的。
LL(1)文法不是二义的。
- if-else语句的二义性:
设有如下文法(将里面的单词视为一个终结符)
G(S):
S → if C then S | if C then S else S | a
C → b
提取左因子之后,改写成:
G(S):
S → if C then S S’ | a
S’→ else S | ε
C → b
我们得到的LL(1)分析表M如下:
显然,M[S’, else]存在二义性 - 二义性的消除
以上图为例,我们人为地去掉一条M[S’, else]中的一条产生式,就能消除二义性。如果保留 S’→else S 就体现了最近匹配原则(编译器一般都是这么做的), 保留S’→ε 则体现了最远匹配原则。
自下而上分析
前缀:从符号串的尾部删去若干(包括0个)个符号之后所余下的部分
活前缀:分析栈中存储的符号串,不含有句柄之后的任何符号
后缀:从符号串的首部删去若干(包括0个)个符号之后所余下的部分
例如,对于 W=androidstudio,W的前缀有androidstudio、androidstudi、androidstud、androidstu、…、ε;W的后缀有ndroidstudio、droidstudio、roidstudio、…、ε 。
可归前缀:若文法G存在推导 αAω => αβω,则可由 αβω规约到αAω,此时称αβ的前缀为文法G的活前缀,αβ称为G的可归前缀。
句柄:如果符号栈的内容是当前句型的可归前缀,那么栈顶已经形成当前句型的句柄,进行归约,否则继续移进。在语法树结构下,最左端的两层子树末端是句柄。
自下而上分析法从输入单词序列开始,自左至右逐步进行归约,试图将其归约为文法的开始符号。自左至右规约是规范推导的逆过程,所以称之为规范规约,规范规约的每一步是从当前的规范句型中将句柄归约为相应的非终结符
规范规约
规范规约定义:假定α是文法G的一个句子,当序列αn,αn-1,αn-2,…,α1,α0满足
① αn = α
② α0为文法开始符号S
③ 对于任意 0<i≤n,αi-1是将αi的句柄替换为产生式左部的符号得到的
则称该序列为α的一个规范规约
演示
对于G[S]:S→aAcBe,A→b,A→Ab,B→d
当输入串为abbcde时,我们进行了如下规约:
句型 | 规约规则 | 句柄 | 语法树 |
---|---|---|---|
abbcde | A→b | b | ![]() |
aAbcde | A→Ab | Ab | ![]() |
aAcde | B→d | d | ![]() |
aAcBe | S→aAcBe | aAcBe | ![]() |
S | 规约完毕 | - | S |
一个显而易见的问题是,按照上述方法,我们必须要构建出语法树才能找到句柄,但语法树应该是语法分析的输出结果,因此在实际分析时,不可能通过语法树找句柄。
LR(k)分析法
L是指自左(Left)向右分析输入单词序列
R是指分析过程都是构造最右(Right)推导的逆过程(规范归约)
括号中的 k 是指在决定当前分析动作时向右看的单词个数为 k
- 优点
- 应用面广:能识别所有采用上下文无关文法描述的语法结构
- 有效实现:是无回溯的移进——归约方法
- 容易查错:能及时发现语法错误,准确指出错误位置
- 核心:LR分析表
LR分析表
LR分析表由两张子表构成
- ACTION[s ,a]以状态序号为行,终结符为列。表示当状态s面临输入符号a时,应采取的动作(移进、规约、报错、接受)
- 移进(shift):s1表示将 下一个符号 和 状态1移进分析栈。
- 规约(reduce):r1表示用第一条产生式(假定为A→β)进行规约,规约时将分析栈中的 β 及其对应的状态全部弹出(设Stop始终表示分析栈栈顶的状态),同时将 A 和 状态GOTO(Stop,A)放入分析栈。
- 接受(accept):acc 表示分析成功,分析器停止工作。
- 报错:所有空白的格子都用于报错
- GOTO[s, X]以状态序号为行,非终结符为列。表示当状态s面对文法符号X时,下一个状态应该是什么
LR分析过程
设初始分析栈内容如下:
输入串:a1a2…an$ ;Stop=S0
状态栈 | 符号栈 |
---|---|
S0 | $ |
设当前分析栈状态为
输入串:aiai+1…an$ ; Stop = Sm
状态栈 | 符号栈 |
---|---|
Sm | Xm |
… | … |
S2 | X2 |
S1 | X1 |
S0 | $ |
我们总是根据 栈顶的状态和当前的输入符号,从ACTION(Sm,ai)中确定下一步动作:
-
若ACTION(Sm,ai)表示移进,且下一状态为 Snew,我们把Snew和ai入栈,分析栈状态变为:
输入串:ai+1ai+2…an$; Stop = Snew状态栈 符号栈 Snew ai Sm Xm … … S2 X2 S1 X1 S0 $ -
若ACTION(Sm,ai)表示按 A → Xm-r+1Xm-r+2…Xm进行规约(产生式右部长度为r),则分析栈状态变为:
输入串:aiai+1…an$ (输入串没有发生变化) ; Stop = Sm-r+1= GOTO(Sm-r , A)状态栈 符号栈 Sm-r+1 = GOTO(Sm-r , A) A Sm-r Xm-r … … S2 X2 S1 X1 S0 $ -
若ACTION(Sm,ai)表示接受,宣布分析成功
-
若ACTION(Sm,ai)表示出错,停止分析,按出错处理。
LR分析示例
LR分析过程满足两条性质:
- 任何时候符号栈里的内容和输入串剩下的部分拼接起来一定是个规范句型
- 一旦符号栈栈顶出现了句柄,必然进行规约
其余内容请直接观看视频。
LR(0)文法
构造LR(0)分析表
活前缀:活前缀中不含句柄之后的字符,因此只要保证分析栈中的符号始终是活前缀,我们的分析就不会出错。
构造识别活前缀的DFA:
- 文法的拓广:将G(S)拓广为G’(S’),引进的S’是G(S)中未出现过的非终结符,在G’(S’)中添加规则 S’→S,S’是G’(S’)的开始符号,不会出现在其他产生式右部。
- LR(0)项目
在每个产生式的右部添加一个圆点,圆点左侧的部分是产生式中我们已经看见的
例如, A→XYZ 有4个项目:
①A→·XYZ ②A→X·YZ ③A→XY·Z ④A→XYZ·
其中④称为规约项目;
S’ 对应的规约项目称为接受项目。
假定Y是终结符,则②称为移进项目;
假定Y是非终结符,则②称为待约项目。 - 构造步骤:
- 构造出识别文法活前缀的DFA
- 将DFA确定化
用得到的的DFA构造LR(0)分析表:
- LR(0)文法:如果G的拓广文法G’的活前缀识别自动机的每个状态(项目集)不存在 ①同时含有移进和规约项目 ②含有多个规约项目 的情况,则G无冲突,符合LR(0)文法。
- LR(0)分析表构造算法:
- 初始化:令每个项目集Ik的下标k作为分析器的状态,包含项目S’→•S的集合Ik的下标k为分析器的初态。
- 构造LR(0)分析表的ACTION和GOTO子表
GO(I1,a)=I2 表示当处于项目集I1时,如果读取到a,会转移到项目集I2- ACTION表
- 移进:若项目A→α•aβ属于Ik且GO(Ik, a)=Ij,a为终结符,则置ACTION[k, a] 为“sj”
- 规约:假定产生式A→α是文法G’ 的第j个产生式,若项目A→α• 属于Ik,则将ACTION表第k行的 每一列 都置为 “rj”
- 接受:若项目S’→S•属于Ik,则置ACTION[k,$]为“acc”
- GOTO表
- 若GO(Ik, A)=Ij,A为非终结符,则置GOTO[k, A]=j
- 凡不能用以上规则填入的空白格均置上“报错标志”
- ACTION表
注意
LR(0)文法的要求过于严苛,稍微具有实际意义的文法就很可能不符合LR(0)的要求。
SLR(1)冲突解决法
释义:S-simple;(1)最多向前看一个单词
- 假定一个LR(0)规范族含有如下的一个项目集(状态)I={X→α•bβ,A1→α•,A2→α•}
FOLLOW(A1)和FOLLOW(A2)的交集为Φ,且不包含b
当状态I面临任何输入符号a 时,可以:- 若a=b,则移进;
- 若a∈FOLLOW(A1),用产生式A1→α进行归约(X→A1•bβ);
- 若a∈FOLLOW(A2),用产生式A2→α进行归约(X→A2•bβ);
- 此外,报错。
- (一般化表述)假定LR(0)规范族的一个项目集 I = {X1→α•a1β1,X2→α•a2β2,…,Xm→α•amβm,A1→α•,A2→α•,…,An→α• }
如果集合{a1,…,am},FOLLOW(A1),…,FOLLOW(An)两两不相交(包括不得有两个FOLLOW集合有$),则当状态I面临任何输入符号a 时:- 若a=ai,i=1,2,…,m,则用产生式Xi→α•aiβi移进(Xi→αai•βi);
- 若a∈FOLLOW(Ai),i=1,2,…,n,则用产生式Ai→α进行归约(Xi→Ai•aiβi);
- 此外,报错。
构造SLR(1)分析表
- 初始化:令每个项目集Ik的下标k作为分析器的状态,包含项目S’→•S的集合Ik的下标k为分析器的初态。
- 构造SLR(1)分析表的ACTION和GOTO子表(注意对比LR(0)分析表的构造)
GO(I1,a)=I2 表示当处于项目集I1时,如果读取到a,会转移到项目集I2- ACTION表
- 移进:若项目A→α•aβ属于Ik且GO(Ik, a)=Ij,a为终结符,则置ACTION[k, a] 为“sj”
- 规约:假定产生式A→α是文法G’ 的第j个产生式,若项目 A→α• 属于Ik,那么,对 任何 终结符a∈FOLLOW(A),置ACTION[k,a]为 “rj”
- 接受:若项目S’→S•属于Ik,则置ACTION[k,$]为“acc”
- GOTO表
- 若GO(Ik, A)=Ij,A为非终结符,则置GOTO[k, A]=j
- 凡不能用以上规则填入的空白格均置上“报错标志”
- ACTION表
SLR(1)分析表构造示例
请直接观看视频
注意
在有些情况下,当状态i显现于栈顶时,当前单词是a,栈里的活前缀βα未必允许把α归约为A,因为可能根本就不存在一个形如“βAa"的规范句型,在这种情况下,用“A→α”归约不一定合适。
产生这个问题的根本原因是Follow集合提供的信息太泛。
LR(1)冲突解决法
LR(k)项目
定义:扩展LR(0)项目,附带有k个终结符 [A→α•β, a1a2…ak]
a1a2…ak 称为向前搜索符串(或展望串)。
归约项目 [A→α•,a1a2…ak]的意义:当它所属的状态呈现在栈顶且后续的k个输入符号为 a1a2…ak 时,才可以把栈顶上的α归约为 A
对于任何移进或待约项目[A→α•β, a1a2…ak], β≠ε,搜索符串 a1a2…ak没有直接作用。
一般情况下,我们只对k≤1的情况感兴趣
LR(1)项目集规范族
闭包函数CLOSURE
假定I是文法G’的任一项目集,定义和构造I的闭包CLOSURE(I)如下:
- I的任何项目都属于CLOSURE(I)。
- 若项目[A→α•Bβ, a]属于CLOSURE(I),B→ξ 是一个产生式,那么,对于FIRST(βa) 中的每个终结符b,如果[B→•ξ, b]原来不在CLOSURE(I)中,则把它加进去。
例:对于文法G[S]:(0)S’→S (1)S→aAcBe (2)A→b (3)A→Ab (4)B→d,求出LR(1)项目[S→a•AcBe , $]的闭包CLOSURE([S→a•AcBe , $])
解:此处a对应α,Α对应B,cBe对应β, $对应a,因此FIRST(βa)=FIRST(cBe $)={c}
因此需要向闭包中添加[A→•b,c], [A→•Ab,c]
最终求得CLOSURE([S→a•AcBe , $]) = { [S→a•AcBe , $], [A→•b,c], [A→•Ab,c] } - 重复执行步骤2,直至CLOSURE(I)不再增大为止。
转换函数GO
令I是一个项目集,X是一个文法符号,函数GO(I,X)定义为:
GO(I,X)=CLOSURE(J),其中 J={ 任何形如[ A→αX•β, a ]的项目 | [ A→α•Xβ, a ]∈I }
构造LR(1)分析表
- 初始化:令每个项目集Ik的下标k作为分析器的状态,包含项目[S’→•S, #]的集合Ik的下标k为分析器的初态。
- 构造LR(1)分析表的ACTION和GOTO子表(注意对比SLR(1)分析表的构造)
GO(I1,a)=I2 表示当处于项目集I1时,如果读取到a,会转移到项目集I2- ACTION表
- 移进:若项目A→[α•aβ, b] 属于Ik且GO(Ik, a)=Ij,a为终结符,则置ACTION[k, a] 为“sj”
- 规约:假定产生式A→α是文法G’ 的第j个产生式,若项目 A→[α•, a] 属于Ik,置ACTION[k,a]为 “rj”
- 接受:若项目S’→[S•, $ ]属于Ik,则置ACTION[k,$]为“acc”
- GOTO表
- 若GO(Ik, A)=Ij,A为非终结符,则置GOTO[k, A]=j
- 凡不能用以上规则填入的空白格均置上“报错标志”
- ACTION表
LR(1)的状态数远多于SLR(1)
LR(1)分析表构造示例
请直接观看视频