1、第四章 语法分析和语法分析程序,对象:单词流形式的源程序 任务:根据语法规则,分析源程序的语法结构,同时进行语法检查 输出:语法树 假定:先不考虑语义问题 常见分析方法:自顶向下()和自底向上() :递归下降法,预测分析法(LL分析法) :优先分析法,LR分析法,4.1 自顶向下的语法分析,:对已给的输入串w,试图自上而下地建立一棵语法树;或者说,从S出发,为w构造一个最左推导.若成功,则wL(G),否则拒绝. 一般说来,在为w寻求最左推导的每一步,都涉及使用何产生式进行替换的问题.最简单的方法是,逐一试探. 遗憾的是,逐一试探也不能完全解决问题.例如,在含有左递归的文法中,就会出现不能终止的
2、替换现象.,例:ET|EAT TF|TMF F(E)|i A+|- M* | / (4.1) 设w=i+i*i,每个产生式从左至右试验.从E出发: ETF(E)iTMFFMF(E)MFiMF i*Fi/FTMFMF TMFMFMF.,自顶向下分析方法的特点,1.若G有左递归,则分析不能正常进行.因此, 分析必须先消除文法的左递归; 2.分析过程是反复进行试探的过程,因此,难免会出现大量的回溯.特别是当wL(G)时,只有在穷举完所有的试探后才能拒绝w.由于回溯,就需将从出错点到迄今为止已做过的大量工作废弃,显然会大大降低分析的效率.特别是在语法分析阶段还往往要进行同步的语义分析和处理,这些工作也
3、就白做了.因此,消除回溯是分析的另一目标. 3.当拒绝w时,只能知道w不是句子,不知出何错及出在何处,4.1.1 消除文法的左递归,设文法是已简化的.若文法含直接左递归式: AA | (V+) , 则类似正规式求解方法,我们有A ( *). 引入新的非终结符A,令其产生*,则有: A A A A| 由于不以A打头,A非左递归. 对P105的文法(4.1),可改写为 ETE EATE| TFT TMFT| F(E)|i A+|- M *|/,一般地,设文法中全部A-产生式为 AA1|A2|An| 1| m,其中, i不以A打头,则消除直接左递归后的产生式为 A1A| mA 及 A 1A| nA
4、上述方法可消除直接左递归,但对于间接左递归的文法来说,需将原文法进行变换后才适用.例如, S Ab|c ASa,可将其变换为S Sab|c,再使用上述方法,得S cS S abS,消除文法左递归的矩阵方法,设文法的非终结符为 X1, X2, , Xn, 其中,Xi的产生式可分为以VN符和VT符打头的两类.类似正规式方法,将|改写为+,有 Xi=X11i+X22i+Xnni+ i 其中, i 是以VT符打头的产生式之和, ki 可以为 这样,文法G可表示为,该方程有解: X=BA* 为得到A*,由,或: X=XA+B,则有: X=BZ Z=I+AZ 其中X的产生式以VT符打头,而Z的产生式以ij
5、V*打头,因此将不含左递归. 注意,由此所得的文法含有无用符号和无用产生式,需化简,消除文法左递归的例子,例4.1 SSa | Ab | a ASc 相应的矩阵为,展开矩阵,得 S aZ11 A aZ12 Z11aZ11 |cZ21| Z12 aZ12 |cZ22 Z21 bZ11 Z22 | bZ12 文法中含有无用产生式,消除之.最后,有 S aZ11 Z11aZ11 |cZ21| Z21 bZ11,搞掂!,4.1.1 回溯的消除及LL(1)文法,为解决回溯问题,我们从句子的最左推导开始讨论. 设G=(VN,VT,P,S) 为一CFG, w=a1a2an是VT上的符号串,现需判明w是否是L
6、(G)中的句子.为此,从S开始进行最左推导.设经若干步推导后我们得到,注意,i=1时,w1=,A=S 由假设可知,到目前为止,w的前缀w1已匹配,现在需对A进行推导.,对于当前输入符号ai,若只有一个 j (称为候选式)使得从j出发可以推导出一个以ai打头的符号串:,而其它的k(kj), k 都推导不出以ai打头的符号串,则选定产生式Aj就是唯一可行的推导,即 wL(G)选Aj正确; wL(G)选任何产生式均不能匹配 显然,若P中产生式能满足上述要求,则回溯可消除,为得到w的剩余部分aiai+1an.由最左推导的定义,考虑A的所有产生式:,消除回溯的条件,由前面的讨论可知,要实现无回溯的分析,
7、文法必须满足一定的条件。为导出这些条件,我们定义候选式的终结首符集FIRST()=a | * a, aVT,V* 并约定 *时,FIRST() 若对于A-产生式的每个候选式i(i=1,2,m)都推不出 , 且 FIRST(i) 互不相交,则当正扫描的当前输入符号为aiFIRST(j)时,唯一可用于推导的产生式只能是Aj. 例如,文法G1S: SAA AaAb | *, A-产生式有两个候选式, 且 FIRST(aAb)=a FIRST(*)=*,两集不相交,设输入串为aa*bb*,其最左推导为 SAA (a) aAbA (a) aaAbbA (*) aa*bbA (*)aa*bb* (#)注:
8、上面第每步推导右侧括号内为当前扫描的输入符号,#为结束符. 我们得到了一个无回溯的条件: FIRST(i) FIRST(j)=,消除回溯的条件,然而还存在另外一种情况,可能存在某个候选式i, i*,即FIRST(i)(这样的是唯一的(?!),此时,为匹配当前扫描的符号a就可能有两种选择,一是存在某j,使j推导出以a打头的符号串,另一种选择是让A推导出i,并让跟随在A后的符号串推导出以a打头的符号串. 若这两种选择都可行,则回溯不可避免. 因此,就必须要求当i *时,跟在A后的符号串不能推导出其它j 所能推导出的首终结符符号串.为此,我们定义可紧跟在A后的所有终结符之集FOLLOW(A)=a |
9、 S#*Aa, aVT#, ,V*其中,当A为一句型的尾符号时,#FOLLOW(A). 现在,我们可把无回溯的另一条件描述为: 若i*,则FOLLOW(A)与其它的j互不相交: FOLLOW(A)FIRST(j)=.,消除回溯的条件,例 SaA | b AcAS | FIRST(cAS)=c FOLLOW(A) =FIRST(S)#=a,b,# 两个集合不相交, 故可进行无回溯的分析.设输入串w=aca#,其推导过程如下:S#aA# 当前输入符a属于FIRST(aA),用SaA推导acAS# 当前输入符c属于FIRST(cAS),用AcAS推导acS# 当前输入符a属于FOLLOW(A),用A
10、推导. acaA# 当前输入符a属于FIRST(aA),用SaA推导aca# 当前输入符#属于FOLLOW(A),用A推导,成功。 最后,我们将消除回溯的条件归纳为,对G中每个AVT,A-产生式中任何两个候选式i,j,均满足:(1)FIRST(i) FIRST(j)= (ij 1i,jm)(2) i,j中,至多有一个能推导出;(3)若j*,则FIRST(i)FOLLOW(A)= (i=1,2,m ij) 注: 条件(2)可省略. 即(1) (2) 我们把满足上述条件的文法称为LL(1)文法,4.1.3 递归下降分析法,递归下降分析法(recursive descent method)的原理是,
11、对于文法的每个非终结符,根据其各候选式的结构,为其建立一个递归的子程序(函数),用于识别该非终结符所表示的语法范畴. 例如,产生式E+TE,相应的子程序(函数)为 expr_prime( ) if(match(PLUS) advance( ); term( ); expr_prime( ); ,程序中,函数match( )的功能是,以其实参与当前正扫描的符号进行匹配,若成功则返回1,否则返回0; advance( )是读下一单词函数,将所读单词赋给变量lookahead; term( )函数是与非终结符T相对应的子程序. 对于文法的每个非终结符,都建立了子程序后,这组子程序组成了所需的自顶向下
12、语法分析程序. 注意,由于文法的递归性,子程序一定有递归.因此,只能使用允许递归的程序设计语言编写. 由于程序有递归,通常,上述方法也称为递归子程序法,递归子程序法(续),例4.2 文法Gstatements:statements exression;statements | expression term expression expression +term expression | term factor term term *factor term | factor num_or_id | (expression) 可以验证, Gstatements满足LL(1)文法条件.故可用递归下
13、降法分析.教材中P112程序4-1,给出了其递归下降语法分析程序.,其中,在文件lex.h里,将分号、加号、乘号、左右括号、输入结束符及运算对象分别命名为SEMI、PLUS、TIMES、LP、RP、EOI及NUM_OR_ID,并指定了其内部码;另外,还对外部变量yytext, yyleng, yylineno进行了说明. 程序4-2对4-1进行了改进,它利用扩充BNF范式对文法进行改写,使相应程序变为非递归的.,4.1.4 预测分析法,预测分析法的分析器由一张预测分析表(LL(1)分析表),一个控制程序(表驱动程序)及一分析栈组成,输入是待分析的符号串(单词流),以# 结尾。 分析表是一二维数
14、组,M:VN(VT#) (PERR), MA,a的值按下述规则确定:对于每个产生式A1|2|m,(1)若aFIRST(i), 则置MA,a=“Ai”; (2)FIRST(i), aFOLLOW(A), 置MA.a=“Ai”, (3)除上述两种情况外,其它元素均填“ERR”. 分析表元素的含义:指明当前应用何产生式进行推导,或指明输入串出现错误,一、分析器的工作原理,分析器对输入串的分析在控制程序的控制下进行,步骤如下:,2.设在分析的某时刻,的分析格局为,此时,根据当前栈顶符号Xm的不同情况,分别作如下处理: (1) XmVT,且Xm=ai,则匹配,将Xm 顶出栈,输入指针+;否则(Xmai)
15、,出错; (2) XmVN 查表MXm,ai,若MXm,ai=“ERR”,则出错;若MXm,ai= “Xm Y1Y2Yk” ,则将Xm 出栈, Y1Y2Yk 按逆序压入栈,得到格局,1.初始化.首先将#及开始符S压入栈,各指针置初值.此时,格局为,(3)若Xm=ai=#,则表明输入串已完全得到匹配,分析成功,结束.,预测分析法实例,考虑文法(4.1).各个 FIRST集与FOLLOW集如右表所示,文法(4.1)相应的LL(1)分析表见右,注:在分析表中我们有意省略了左侧的非终结符(why?).在实际的表存储时,还可用产生式编号表示,对i+i*i进行预测分析的过程,二、构造FIRST集的算法,对
16、于G中的每个文法符号X,为求FIRST(X),反复应用如下规则,直到集合不再增大: (1) if (XVT) FIRST(X)=X; (2)if (XVN) if(XaP aVT) aFIRST(X); if (XP) FIRST(X); (3) if (XY1Y2YkP) if (Y1VN) FIRST(Y1)- FIRST(X); for(1 j k-1)if (YjVNFIRST(Yj)FIRST(Yj)-FIRST(X);if (for(1 j k): FIRST(Yj) FIRST(X); V*,=X1X2Xn,求FIRST()类似于求XY1Y2Yk,略.,构造FOLLOW集的算法,
17、FOLLOW: VNVT#,反复使用如下规则,直到不再增大: 1. # FOLLOW(S); 2. if (ABP) FIRST()-FOLLOW(B); 3. if ( (ABP) ( AB FIRST() ) ) FOLLOW(A)FOLLOW(B); 算法的证明: 对于1.,由定义直接得到;对于2.,由于A是有用符号,则必存在含A的句型: S * A B Ba (a FIRST();对于3., 类似地, S * A B,显然, FIRST()FOLLOW(A),并且, FIRST()FOLLOW(B).证毕/,构造FIRST集和FOLLOW集的例子,我们以文法(4.1)为例,计算相应的F
18、IRST集和FOLLOW集. 1.求所有VN符的FIRST集.利用规则(2),有 FIRST(M)=*,/, FIRST(A)=+,- FIRST(F)=(,i;再利用规则(3),有FIRST(T)=FIRST(M)=*,/, , FIRST(T)=FIRST(F)=(,i, FIRST(E)=FIRST(A) =+,-, FIRST(E)=FIRST(T)=(,i 2.求FOLLOW集 (1)由规则(1),#FOLLOW(E),再由产生式F(E), ) FOLLOW(E), 从而,FOLLOW(E)=),# (2)由规则(3)及产生式ETE可知FOLLOW(E)FOLLOW(E),即有 FO
19、LLOW(E)=),#,求FIRST,FOLLOW集例子(续),(3)由规则(2)及产生式EATE有 FIRST(E)-FOLLOW(T);再由规则(3)及ETE和E*有 FOLLOW(E)FOLLOW(T) 即FOLLOW(T)=+,-),#=+,-,),# (4)由规则(3)有TFT有FOLLOW(T)FOLLOW(T),即FOLLOW(T)=+,-,),# (5)由规则(2)及TMFT,有 FIRST(T)- FOLLOW(F),再由规则(3)及TMFT和T*,有FOLLOW(T) FOLLOW(F),从而, FOLLOW(F)=*, /+,-,),#=+,-,*,/,),# (6)由规
20、则(2)及EATE,T MFT ,有 FIRST(T)FOLLOW(A), FIRST(F) FOLLOW(M),故有 FOLLOW(A)=(,i, FOLLOW(M)=(,i. 最终所得的FIRST集和FOLLOW集结果,见P119,三、预测分析表的构造,对已给的LL(1)文法,在得到各文法符号的FIRST集和FOLLOW集之后,就可容易地构造出预测分析表(也称LL(1)分析表). 在实际的表存储结构中,矩阵中每个元素并非真正存储的是产生式,而是其右部的逆序(也可以是产生式序号),这样便于分析时使用,并节省了内存空间. 造表的算法 在AVN所在行,aVT所在列, MA,a的填写方法如下: (
21、1) if ( AP and aFIRST() )MA,a =A; (2) if ( * ( FIRST() and aFOLLOW(A) ) MA,a=A; (3) Otherwise, MA,a=ERR. 文法(4.1)的LL(1)分析表见P119表4-2,4.1.5 某些非LL(1)文法的改造,对于LL(1)文法而言,我们总能构造出相应的预测分析表,且表中决不会含有多重定义的元素. 然而对于非LL(1)文法,它们不满足LL(1)文法的条件,尽管仍可为其建立预测分析表,但表中必然会出现多重定义的元素(why?) 例如,文法GS: SabB ASC|BAA| BAbA CB| FIRST(S
22、)=a FIRST(A)=a,b, FIRST(B)=a,b FIRST(C)=a,b,c FOLLOW(S)= FOLLOW(A)=FOLLOW(B)= FOLLOW(C)= a,b,c,#, 由造表规则,有 MA,a=ASC,A, 同理, MB,b=ABAA, A . 可见非LL(1)文法所造之表中,必有冲突元素.事实上, 是否有冲突元素也是判别一文法是否是LL(1)文法的方法之一. 对于某些非LL(1)文法而言,通过消除左递归,反复提取左因子等方法,有时是可以将其改造成LL(1)文法的.,某些非LL(1)文法的改造(续),提取左因子 当文法中含有形如 A1|2|m 的产生式时,可将其改写
23、为: AA A1|2|m 若诸候选式1,2,m 中的一部分仍含有左因子 ,则再进行提取工作,如此等等.这样,就可能得到一个LL(1)文法. 例如, EE+T | T T(E) | a(E) | a 经改造后可得文法 ETE E+TE| TaT | (E) T (E) | 这是一个LL(1)文法. 应当指出,并非所有的文法都能改造为LL(1)文法. 例如,文法GS: SAU | BR AaAU | b BaBR | b Uc Rd 文法中S的两个候选式AU及BR的FIRST集相交,G是非LL(1)的.为提取左因子,先将S产生式中的A,B用其右部替换,得: SaAUU|bU|aBRR|bR, 经提
24、取左因子,得 S aS|bS” SAUU|BRR S” U|R A 显然它仍不是LL(1)文法,关于LL(1)的一些结论,能由LL(1)文法产生的语言称为LL(1)语言.它们已被证明具有许多重要特性, 主要有: (1) 任何LL(1)文法都是无二义性的; (2) 左递归文法是非LL(1)的; (3) 存在一种算法,它能判定任意文法是否为LL(1)的; (4) 存在一种算法,它能判定任意两个LL(1)文法是否等价; (5) CFL是否是LL(1)语言是不可判定的; (6) 非LL(1)语言是存在的. 若在分析过程中,每步向前扫描k个符号来确定选用的产生式,此分析方法称为是LL(k)分析.此法极少用,故从略.,