讲稿第5章 自顶向下语法分析Word文档格式.docx
《讲稿第5章 自顶向下语法分析Word文档格式.docx》由会员分享,可在线阅读,更多相关《讲稿第5章 自顶向下语法分析Word文档格式.docx(17页珍藏版)》请在冰豆网上搜索。
令S为根结点S.
2.用S的右部,符号串去匹配输入串
完成一步推导SaAb
检查a-a匹配
A是非终结符,将匹配任务交给A
3.选用A的右部符号串匹配输入串
A有两个右部,选第一个
完成进一步推导Acd
检查,c-c匹配,b-d不匹配(失败)
但是还不能冒然宣布αL(G[S])
4.回溯即砍掉A的子树改选A的第二右部
Ac检查c-c匹配b-b匹配
建立语法树,末端结点为acb与输入acb相匹配,建立了推导序列
SaAbacb
∴acbL(G[S])
例5.2G[S]:
SSa|b
所能产生的语言L={ban|n>
=0},输入串baaaa#应该是文法的句子,但用自顶向下分析时可以看出当输入符号为b时,为与b匹配则应选用Sb来推导,但这样就推不出后面部分,而若用SSa推导则会出现下图的情况,无法确定到什么时候才用Sb替换,这样一来在处理S的过程中,在当前输入符号尚未得到匹配就又进入递归调用处理S的过程,这样会造成死循环。
二、自顶向下分析存在的主要问题
1、这是一种自上而下的试探法,有时免不了要走回头路——回溯。
该方法实现时就是模拟收养儿子的过程。
2、左递归文法会造成死循环。
三、问题的解决
(1)左递归问题:
改写产生式,变左递归为右递归。
①直接左递归:
直接见前面产生式的左递归,如
PPα|β,P∈VN,α、β∈(VT∪VN)*
这种直接左递归可以通过直接扫描产生式发现,也容易解决——改写产生式即可。
改写为:
PβP’
P’αP’|
如:
PPα1∣Pα2∣…∣Pαm∣β1∣β2∣…∣βn,其中P∈VN,αi,βj∈(VT∪VN)*且α≠є,β不以P开头,改写成:
Pβ1P'
|β2P'
|…|βnP'
P'
α1P'
|α2P'
|…|αmP'
|ε
例:
文法G[E]:
EE+T|T改写成ETE'
E'
+TE'
TT*F|F改写成TFT'
T'
*FT'
②间接左递归:
通过改写产生式我们可以消除直接左递归,但不能完全消除左递归,因为还存在着间接左递归的问题。
例如文法G[P]:
PQc|c
QRb|b
RPa|a
表面上并看不出它的左递归性,但是仔细观察时不难发现P、Q、R都是左递归的,因为
PQcRbcPabc,即P+Pabc;
Q+Qcab,R+Rbca亦然。
对这种左递归的消除就稍微麻烦一些。
首先,从产生式中找这种形式的左递归。
其思想也就是通过推导来寻找左递归、并进一步修改产生式。
为此,可以设计算法实现(见教材P89):
例子见P90的文法。
(2)回溯问题:
若走了一大段错误路径,不得不推倒重来时,实际上已经在错误的路上做了很多费时而又麻烦的语义工作——如中间代码产生以及表格登记等。
仔细分析一下引起回溯的原因,不难发现主要是出在父亲收养儿子的盲目性。
就是说,面对一个输入符号a,父亲不能明确的指派该由哪个候选来匹配。
于是我们也就有了相应的解决办法:
如果对某个非终结符P,它可能有若干个候选式α1,α2,…αm,对每个候选式我们可以求出它的终结首符号集FIRST集,定义如下(见教材P76):
FIRST()={a|*a,a∈VT,,∈V*}
若*ε,则规定ε∈FRIST()。
换句话说,FIRST(α):
从α可能推导出的所有开头终结符号或ε。
如果非终结符A的所有候选的First集两两不交,即:
First(αi)∩First(αj)=φ,(对任意i≠j)
那么当要A去匹配输入串时,它就可以根据输入串第一个字符a,准确地指派某个候选去执行这一任务,这个候选就是那个First集含a的αi,这样就无须去进行试探了。
当然,程序语言的文法中大都存在这样的非终结符:
它的所有候选的终结首符号集并非两两不相交的。
<
条件语句>
IF<
条件>
THEN<
语句>
ELSE<
∣IF<
那么现在的关键又是解决候选首符集两两不交的问题了。
解决的办法就是提取公共左因子,引入新的非终结符,改写产生式,直到其所有候选的终结首符集两两不相交。
例如,若关于A的产生式为:
Aδβ1|δβ2|…δβn,提取左因子:
Aδ(β1|β2|…βn),引入非终结符A’
AδA’,A’β1|β2|…βn
通过反复提取公因子,我们就能把每个非终极符的所有候选首符集变成两两不相交的。
归纳起来消除回溯的办法是:
1、提取左因子;
2、求每个候选的First集使其两两不相交。
5.3递归子程序分析法(15分钟)
由以上讨论,我们就可以构造一个不带回溯的自上而下的分析程序了,该程序由一组递归过程组成,每个过程对应文法的一个非终结符。
由于文法递归,所以相应的子程序也就递归。
递归子程序由此得名。
例文法:
G[E]:
EaA
Aa|bAc
对于EaA
viodE
{
if(ch==’a’)
GETCH;
//读下一字符
A;
}
elseERROR;
}
对于Aa|bAc
viodA
if(ch==’a’)GETCH;
elseif(ch==’b’)
GETCH;
A;
if(ch==’c’)GETCH;
elseERROR;
}
}
优点:
❑构造方法非常简单
❑程序结构清晰
缺点:
❑递归调用较多,占用内存多、速度慢
❑如果所采用的高级语言不允许递归,则不能使用此方法
5.4预测分析方法(100分钟)
回想一下前面介绍过的自上而下分析的基本思想,关键是对于某个非终结符A和当前输入符号a,如何去选择A的候选。
该方法就是由此而来。
对于一个消除了左递归且经过提取最左公因子后的候选首符集两两不相交的文法,我们就有可能根据其产生式把所有的对偶(A,a)构造出来,这些对偶构成一张预测分析表。
例如文法G[E]:
ETE'
E'
|
TFT'
T'
F(E)|i
有如下预测分析表
其中#为输入串的结束标志,对偶可分析为:
光有这样一个表还不够,无法记录当前分析到了哪一步,为此还需要一个栈来存放文法符号。
显然,开始时栈底应为,这样我们就可以由一个符号栈和一个分析表来控制分析工作了。
为了较易说明问题,我们用一个工作单元a表示当前输入符号,用X表示符号栈的栈顶符号,那么我们的预测分析程序的总控程序任何时候的操作无非是如下三种动作之一:
①若X=a=‘#’,表示分析成功,终止;
②若X=a≠’#’,则把X由栈中托出,让a指向下一个输入符号;
③若X为非终结符,则查看分析表M[X,a]。
若有关于X的产生式,则先令X出栈,再将该产生式右部倒序装入栈中(若右部符号为ε,则意味着什么也不进栈);
若无产生式则调用出错诊断程序。
表驱动预测分析程序模型图
根据以上说法,分析表M中的任何M[X,a]都不能是多重定义的,这就要求文法G应是无二义的并且不含左递归。
5.4.1LL
(1)文法
LL
(1)文法就是分析表不含多重定义入口的文法。
LL的含义
第一个L-自左向右扫描分析输入符号串
第二个L-从识别符号开始生成句子的最左推导
LL
(1):
向前看一个输入符号,便能唯一确定当前应选择的规则
LL(k):
向前看k个输入符号,才能唯一确定当前应选择的规则
显然若Aα,而a∈First(α),那么M[A,a]放Aα是再合适不过的了,这儿就涉及到如何求First集First(α)={a∣α*a…,a∈VT}的问题了。
5.4.2FIRST集的构造
First集的定义5.2节已经给出了,这里介绍一下计算FIRST集的算法:
以文法G[E]为例:
(1)若α=aα′,且a∈VT,则a∈FIRST(α);
例:
FIRST(i)={i}
FIRST(+TE'
)={+}
(2)若α=Xα′,X∈VN,且有产生式Xb…,则把b加入到FIRST(α)中;
FIRST(FT'
)={(,i}
(3)若α=X1X2…Xnα′,其中Xi∈VN,1≤i≤n;
①将FIRST(X1)中的一切非ε的终结符加进FIRST(α);
②若ε∈FIRST(X1),则将FIRST(X2)中的一切非ε的终结符加进FIRST(α);
③若ε∈FIRST(X1)且ε∈FIRST(X2),则将FIRST(X3)中的一切非ε的终结符加进FIRST(α);
④依此类推,若对于一切1≤i≤n,ε∈FIRST(Xi),则将ε加进FIRST(α)。
注意:
要顺序往下做,直到每个First集不再增大为止。
一旦不满足条件,过程就要中断进行。
FIRST(FT'
)=FIRST(F)-{ε}={(,i}
各个非终结符及终结符串的FIRST集为:
FIRST(F)={(,i}
FIRST(T’)={*,ε}
FIRST(T)=FIRST(F)-{ε}={(,i}
FIRST(E’)={+,ε}
FIRST(E)=FIRST(T)-{ε}={(,i}
FIRST(TE’)=FIRST(T)-{ε}={(,i}
FIRST(+TE’)={+}
FIRST(ε)={ε}
FIRST(FT’)=FIRST(F)-{ε}={(,i}
FIRST(*FT’)={*}
FIRST((E))={(}
FIRST(i)={i}
构造了每个文法符号的First集之后,我们也就能够对文法的任何符号串α构造出First(α)了。
那么是否问题全解决了呢?
不是的!
从教材P77例5.3可以看出,当某一非终结符的产生式中含有ε时,它的非空产生式右部的首符号集两两不相交,并与推导过程中紧跟非终结符右边可能出现的终结符集也不相交,则仍可以构造确定的自顶向下分析。
为此我们定义一个文法符号的后继符号的集合。
5.4.3FOLLOW集的构造
对于文法G的非终结符的后继符号集称为FOLLOW集,定义如下(见教材P77):
FOLLOW(A)={a|S*…Aa…,a∈VT}
若S*…A,则规定#∈FOLLOW(A),其中“#”是输入串的结束符。
FOLLOW(A):
是所有句型中紧接A之后的终结符号或#。
有E*T+TE'
,则+∈FOLLOW(T)
计算FOLLOW集的算法:
(1)若为开始符号,则把“#”加入FOLLOW(A)中;
#∈FOLLOW(E)
(2)若BA(≠),则把FIRST()-{}加入FOLLOW(A)中;
由F(E)可知,)∈FOLLOW(E)
(3)若BA或BA,且*,则把FOLLOW(B)加入FOLLOW(A)中。
因为有当形如:
D1B1
BA
的产生式时,A、B、D∈VN,、1、、1∈V*,在推导过程中可能出现句型序列如:
S*...1B1......1A1......1A1...
由FOLLOW集的定义可知FIRST
(1)∈FOLLOW(B)必有FIRST
(1)∈FOLLOW(A),所以有FOLLOW(B)⊆FOLLOW(A)。
由ETE'
,应把FOLLOW(E)加入∈FOLLOW(E'
)
由E'
+TE'
且E'
ε,应把FOLLOW(E'
)加入FOLLOW(T)
FOLLOW集合中不能有ε
求G[E]的FOLLOW集:
FOLLOW(E)={#,)}
∵E是开始符号∴#∈FOLLOW(E)
又F(E)∴)∈FOLLOW(E)
FOLLOW(E’)={#,)}
∵ETE’∴FOLLOW(E)加入FOLLOW(E’)
FOLLOW(T)={+,),#}
∵E’+TE’∴FIRST(E’)-{ε}加入FOLLOW(T)
又E’ε,∴FOLLOW(E’)加入FOLLOW(T)
FOLLOW(T’)=FOLLOW(T)={+,),#}
∵TFT’∴FOLLOW(T)加入FOLLOW(T’)
FOLLOW(F)={*,+,),#}
∵TFT’∴FOLLOW(F)=FIRST(T’)-{ε}
又T’ε∴FOLLOW(T)加入FOLLOW(F)
实际上要完成预测分析表的构造,首先要判断文法是否是LL
(1)文法。
LL
(1)文法的判别则需要计算SELECT集。
5.4.4SELECT集
给定上下文无关文法的产生式Aα(A∈VN,α∈V*),定义SELECT集合为(见教材P78):
(1)若α*,则SELECT(Aα)=FIRST(α)
(2)若α*,则SELECT(Aα)=FIRST(α)-{}∪FOLLOW(A)
5.4.5LL
(1)文法的判别
一个上下文无关文法是LL
(1)文法的充要条件是(见教材P78),对每个非终结符A的两个不同的产生式Aα,Aβ,满足:
SELECT(Aα)∩SELECT(Aβ)=Φ
(其中α和β不能同时推导出)。
例G[E]:
(1)ETE’
(2)E’+TE’(3)E’
(4)TFT’(5)T’*FT’(6)T’
(7)F(E)(8)Fi
E’+TE’|FIRST(+TE’)={+}
FOLLOW(E′)={),#}
SELECT
(2)∩SELECT(3)=Φ
T’*FT’|FIRST(*FT’)={*}
FOLLOW(T′)={+,),#}
SELECT(5)∩SELECT(6)=Φ
F(E)|iFIRST((E))={(}
FIRST(i)={i}
SELECT(7)∩SELECT(8)=Φ
所以G[E]是LL
(1)的。
需要指出的是:
并不是任何一个文法都能通过消除左递归、提取公因子等手段改写成LL
(1)文法的。
5.4.6预测分析表的构造及分析
预测分析表用矩阵M(或称二维数组)表示。
矩阵的元素M[A,a]中的下标A表示非终结符,a为终结符或#。
若aSELECT(A),则把A放入[A,a]中。
如SELECT(E’)={),#},则有M[E’,)]=E’,M[E’,#]=E’。
把所有无定义的[A,a]标上“出错标志”。
为了使表简化,其产生式的左部及可以不写入表中(即表内只写产生式的右部),表内的空白处为出错。
5.4.5中例子的预测分析表为
i
+
*
(
#
E
TE’
E’
+TE’
T
FT’
T’
*FT’
F
(E)
总控程序算法描述(自学了解):
push(#);
push(S);
//把#和开始符号S依次压进栈
a=getsym();
//读入第一个符号给a
flag=1;
whileflag{
X=pop();
//从栈中弹出X
if(X∈VT)
if(X==a)a=getsym();
//读入下一个符号给a
elseerror();
elseif(X==‘#’)
if(X==a)flag=0;
//分析成功
elseif(M[X,a]==XX1X2…Xn)//X∈Vn,查分析表
{X=pop();
push(Xn…X2X1);
//若X1X2…Xn=ε,则不进栈}
}//endofwhile
下面用预测分析程序、栈和预测分析表对输入串i+i*i#进行分析,给出栈的变化过程。
分析成功,所以i+i*i#是文法的句子。
❑效率高于递归子程序法
❑对文法的限制较多,要求文法必须为LL
(1)文法
5.5预测分析中的错误处理(15分钟,自学了解)
(1)在预测分析过程中,发现了下列两种情况,则说明遇到了语法错误:
1、栈顶的终结符与当前输入符不匹配;
2、非终结符A处于栈顶,面临的输入符为a,但分析表M的M[A,a]为空;
(2)“应急”恢复策略
发现错误后,要尽快从错误中恢复过来,使分析能够继续下去。
基本的做法是跳过输入串中的一些符号直至遇到“同步符号”为止。
显然,这种做法的效果依赖于同步符号集的选择。
(3)同步符号的选择
1、把FOLLOW(A)中的所有符号作为A的同步符号。
跳过输入串中的一些符号直至遇到这些“同步符号”,把A从栈中弹出,可使分析继续。
2、把FIRST(A)中的符号加到A的同步符号集,当FIRST(A)中的符号在输入中出现时,可根据A恢复分析。
3、如果一个非终结符产生空串,那么,推导ε的产生式可以作为缺省的情况,这样做可以推迟某些错误检查,但不能导致放弃一个错误。
这种方法减少了在错误恢复期间必须考虑的非终结符数。
4、如果不能匹配栈顶的终结符,一种简单的做法就是弹出这个终结符,并发出消息说明已经插入了这个终结符,继续进行语法分析。
这样做的结果就是一个单词符号的同步符号集包含所有其他单词符号。
第5章小结(5分钟)
v自上而下语法分析的基本思想
v消除左递归、提取左公因子
vFirst集、Follow集和Select集的计算
vLL
(1)文法的判别
v预测分析表的构造
vLL
(1)分析过程
第5章作业(1分钟)
P100
2
(1)
(2)(3)
3(补充:
分析符号串bef是否为文法的句子)
7
(1)
(2)