1、第五章 自上而下语法分析,语法分析是继词法分析之后编译过程的第2阶段。它的主要任务是对词法分析的输出结果单词序列进行分析,识别合法的语法单位。 语法分析最常用的方法有:优先方法、递归下降法、LL方法和LR方法。 所谓自上而下分析是指从树的根结点开始,为给定的输入串构造一棵语法树。 本章中,我们通过讨论一个一般的非确定的自上而下分析器来讨论上下无关语言的自上而下分析器的设计。,5.1 非确定的下推自动机,下面所要构造的非确定的自上而下分析器属于一般的下推自动机(PDA)类。 所谓一个下推或栈自动机(Stack Automaton),非形式地说,应包含: 一个输入符号串; 一个读头,它从左至右移动
2、,每次读进一个输入符号; 一个有穷状态自动机,用于控制整个系统的操作; 一个后进先出下推栈,,下推自动机的非空移动:,5.1.1 PDA形式定义,形式上说,一个PDA是一个七元组:(Q,H,q0,Z0,F) Q 是状态的有穷集,它的每个元素称为一个状态; 是有穷的字母表,它的每个元素是一个输入符号; H 是有穷的下推栈字符表,它的每个元素称为一个栈符号。 q0Q 是该PDA的初态; Z0H 是下推栈的初始符号; F Q 是一个终态集(或接收状态集);它的每个元素称为终态;(可空)。,5.1.1 PDA形式定义, 是描述PDA动作与状态变化的。PDA的动作可用定义式来表示为: (q,a,z)=(
3、p1,h1),(p2,h2)(pn,hn) 它的含义为: 在控制器当前状态为q,下推栈顶符号为z ,输入符号为a的情况下,把控制器的状态改为pi,用hi 替换栈顶的z,并让读头右移一格。,5.1.2 PDA的 构形和移动,PDA的一个构形是一个三元组:(q,w,h) 其中,qQ;w*是尚待扫描的输入串,包括读头当前所指的符号;hH*是栈的内容。 PDA的一次移动可看作是从一种构形变换成另一种构形的一种方式。反过来,构形又为定义PDA的移动提供了一种更简单的手段。我们称(q,ax,Zh)(p,x,hh)是一次可能的移动,当且仅当(p,h)(q,a,Z) 。 常用+表示由一次或多次移动组成的序列。
4、用*表示由零次或多次移动组成的序列。注意“零”次移动并不改变当前的构形。,5.1.2 PDA的 构形和移动,PDA M 所识别的语言L(M)可表示为: L(M)= *(P0,,Z0)*(P,)(空栈接收)或者 L(M)= *(P0,,Z0)*(P,h) pF(终态接收),例5.1 考虑下表定义的两状态PDA,其的两个状态分别是p和Q,(p,a,Z)=(p1,h1),(p2,h2),输入符号是0和1,栈符号是R,B和G。该PDA识别由符号0和1组成的所有回文(Palindrome) 。,这个自动机是非确定的,因为在行3和行6包含了可供选择的移动;也因为无输入符号(如在行7)时照样可进行移动,而且
5、此时存在相应的选择。该PDA的开始状态时p,初始栈内容时R。它停止于空栈。用该PDA识别输入串001100,其识别过程如下:,(p,001100,R) (p,01100,BR) 由行1或 (Q,001100,) 由行7(阻塞) (p,01100,BR) (p,1100,BBR) 由行3a或 (Q,1100,R) 由行3b (Q, 1100,R) (Q,1100,) 由行10(阻塞) (p,1100,BBR) (p,100,GBBR) 由行5 (p, 100,GBBR) (p,00,GGBBR) 由行6a,或 (Q,00,BBR) 由行6b (p,00,GGBBR) (p,0,BGGBBR) 由
6、行4 (p,0,BGGBBR) (p,BBGGBBR) 由行3a(阻塞)或 (Q,GGBBR) 由行3b(阻塞) (Q,00,BBR) (Q,0,BR) 由行8 (Q,0,BR) (Q,R) 由行8 (Q,R) (Q,) 由行10(接收),5.1.3 上下文无关语言与PDA,联系PDA和上下文无关语言的一个重要定理是: 定理5.1 对每一个上下文无关语言L,存在一个恰好识别L的非确定的PDA M,反之亦然。 这个定理在编译系统中的实际重要性在于:现有的大多数高级程序设计语言都可用上下文无关文法描述。因此定理5.1隐含了:识别这个语言的机械识别器必是PDA。,5.1.3 上下文无关语言与PDA,
7、定理5.1包含两方面含义: 给定一个上下文无关语言,存在一个识别它的PDA M; 反过来,给定一个PDA M,可以根据它构造出一个等价的上下文无关文法。前者对编译程序的构造很有吸引力,但后者则不然。,算法5.1 从CFG到NDPDA 给定 CFG G=(N,P,S) 可以构造 一个相应的非确定的PDA M:M=(Q,H,q0, Z0,F) 它只有一个状态q和下面的转换规则: 对P中每一个形如Aw的产生式,(q,A)包含(q,w); 对每个a,(p,a,a)包含(q,) 且 Q=q = H=N q0=q Z0=S F为终态集(可空)。这个PDA停止于空栈。,例5.2 考虑文法S0S1|c该文法描
8、述语言0*c1*,其中0的个数和1的个数相等。转换规则是: 1.(q,0,0)=(q,) 2.(q,1,1)=(q,) 3.(q,c,c)=(q,) 4.(q,S)=(q,0S1) ,(q,c)(其中可与任何合法输入符号匹配) 其中规则1、2、3根据前面的规则构造,4根据规则 构造。,使用例5.2分析句子,给定输入串00c11,所构造的PDA用下面的移动序列来接收它(注意,我们可从构形中省掉状态,因为它总是相同的):(q,00c11,S)4a(q,00c11,0S1)1(q,0c11,S1)4a(q,0c11,0S11)1(q,c11,S11)4b(q,c11,c11)3(q,11,11)2(
9、q,1,1)2(q,) (接收),输入串,栈和句型,由算法5.1构造的非确定的PDA的一个有趣特性是由下面的定理表示出来的。 定理5.2 令(q,y,h)是某个文法G相关的NDPDA的任意构形,其中输入串是xy,如果(q,xy,S)*(q,y,h) 那么xh是G的一个最左句型,换言之,S*xh(S是G的开始符号)。 上述定理反过来也成立:给定G中的任何句型xh,如果x是一个终结符串,而且h中至多最左符号是终结符,那么,(q,y,h)是该NDPDA的一个构形,而且 (q,xy,S)*(q,y,h),5.2 消除左递归方法 5.2.1 文法的左递归性,文法的左递归性属文法递归性的一种,在一文法中,
10、所有形如 AxAy x,y (N) *,AN称为递归产生式(或自嵌入产生式)。 若其中x=,则有AAy称之为直接左递归产生式。,5.2.1 文法的左递归性,若其中y=,则有AxA称之为直接右递归产生式。 若一文法中至少含有一条递归产生式,或在用该文法推导符号串的过程中,存在AA或AA或AA形式的推导,则称该文法是(直接)递归的。,非确定的自顶向下分析存在的问题,非确定的自上而下的分析法:对任何输入串W试图用一切可能的办法,从文法的开始符号出发,自上而下地为它建立一棵语法树。或者说,为输入串寻找一个最左推导,如果试探成功,则W为相应文法的句子,否则W就不是文法的句子。这种分析过程本质上是一种穷举
11、试探过程,反复使用不同的规则,谋求匹配输入串的过程。,回溯现象侯选式:我们把文法中每个非终结符A的右部(可能有多个)称为A的侯选式。当某非终结符对应多个侯选式时,如果其中有几个侯选式的右部左端第一个符号相同,那么就会使得语法分析程序无法根据当前的输入符号准确地决定选用哪个侯选式来替换A,只能采用试探的办法,任选某个侯选式去试探一次。如果不能导致最终地匹配,只得再换另一个侯选式去试探,这就造成了回溯现象。所以这种带回溯的自顶向下分析法,实际上是采用了一种穷尽一切可能选择的试探法。,回溯现象:在回溯之前,编译程序实际已经做了大量薄记工作,包括语义翻译工作在内。在回溯时,要清除这些内容,然后开始重新
12、薄记,这就降低了分析效率。 左递归现象:回溯使语法分析器的动作不稳定,左递归会使分析程序进入到无穷的循环之中,这些问题都和描述语言的文法有直接关系。,5.2.2 用扩展的BNF表示法消除左递归,在前面,文法的产生式都是采用巴科斯范式(BNF)描述的,它使得文法更严谨、简洁和清晰。为了消除文法的左递归,需对巴科斯范式进行扩展,增加以下元符号: 花括号 :表示符号串x出现零次或多次。:n表示符号串x能重复出现的最大次数,m表示符号串x能重复出现的最小次数。 方括号 方括号用来表示可选项。x = x或,表示符号串x可出现一次或不出现。可以用来定义某些高级语言中的“条件语句”。 圆括号( )利用圆括号
13、可提出一个非终结符的多个产生式右部的公共因子。例如,Axy|xw|xz 可写成 Ax(y|w|z),利用下面的两条规则,可把包含直接左递归的产生式转换成用扩展BNF表示法表示的产生式。 提公因子每当一条产生式中有公因子可提的时候,就把它提出来,若原产生式是 Ax|xy则可写成 Ax(y|),这里把当作最后一个候选式。 若 Ax|y|z|Av是一组产生式,且它只有一个直接左递归的右部位于最后,则可把这组产生式变换成如下形式:A(x|y|z)v 也就是说,使用上述规则,可把产生式改写成相对于某个非终结符而言,至多只含一个直接左递归的右部;然后,利用上述规则消除这个直接左递归。,5.2.3 直接改写
14、文法,设产生式U Uxy此产生式称为直接左递归形式。其中,x和y是两个符号串,y的首字符不是U。 产生式为直接左递归形式,可直接改写为一个等价的非直接左递归形式U yUU xU 其中U是新引进的非终结符号。显然,这种形式与原形式是等价的,即从A推出的符号串是相同的。,直接左递归更一般的形式U Ux1Ux2Uxmy1y2yn其中,xi (i=1, 2, , m) yi (i=1, 2, , n), yj的头字符都不是U, xi 都不是,则有U y1U y2UynUU x1U x2UxmU,消除左递归举例。 例如 :有文法:SSa 可改写为:SbSSb SaS|表达式文法: 消除左递归后:EE+T
15、|T ETE E+TE|TT*F|F TFT T*FT|F(E)|i F(E)|i,消除间接左递归1重新改写文法2先将间接左递归变为直接左递归,然后再按上述方法消除。 例(1)AaB 用(1)(2)去替换(3)中的A可得:(2)ABb (1)BaBc(3)BAc (2)BBbc(4)Bd (3)Bd消除左递归后可得:B(d|aBc)BBbcB|最终文法为:(1)AaB (2)ABb (3)B(d|aBc)B(4)BbcB|,5.2.4 消除所有左递归的算法,若一个文法不含形如A+A的推导,也不含有以为右部的产生式,那么,执行下面的算法将保证消除该文法中的所有左递归: 将文法G的所有非终结符整理
16、成某一顺序U1,U2,Un。 for i:= 1 to n dobeginfor j:= 1 to i1 dobegin 把产生式Ui Uj替换成Ui 12m(其中Uj 12m 是该文法中关于Uj的所有产生式)end; 消除Ui产生式中的直接左递归end; 化简改写之后的文法,删除多余产生式。,5.3 LL(k)文法,LL(k)文法是上下文无关文法的一个真子集。同时,LL(k)文法也是允许采用确定的自上而下分析技术的最大一类文法。 LL(K)的含义:自左至右扫描(输入串),自上而下进行最左推导。K的含义是指在分析的过程中至多向前查看K个符号,一般情况下,K1。 给定一文法,判断它是否是一个LL
17、(k)文法并不是很容易的事。我们将给出一个判断条件。根据这个条件就能推断一个给定的文法是否是LL(k)文法。此外,由于分析表是分析器的核心,因此,我们将给出构造LL(k)分析器的分析表的方法。,LL(1)文法的引入,为了保证能够进行确定的自顶向下的分析,文法应该满足在分析的过程中,每次对产生式的选择都是唯一的。 S-文法:(举例)每个产生式右边都以终结符开始;同一个非终结符的各个侯选式的首终结符不同; Q-文法:(举例)每个产生式右边都为或以终结符开始;具有相同左部的产生式具有不相交的可选集; LL(1)文法:一个上下文无关文法称为是LL(1)文法,当且仅当同一非终结符的各个产生式的可选集互不
18、相交。,5.3.1 LL(1)文法的判断条件,按照前面所述方法改造,消除左递归后的文法不一定就是一个LL(1)文法,还必须借助于某种判断条件才能确定。为此,我们将引进三个集合:FIRST,FOLLOW和SELECT。 先定义集合FIRST和FOLLOW。 设是文法G的一个符号串,(VNVT)*,定义FIRST() = a a, aVT, (VNVT)*特别,若有 ,则FIRST()。即,FIRST()是从可推导出的所有首终结符或可能的。 设S是文法的识别符号,U VN ,定义FOLLOW(U) = bS xUby,bVT,x, y(VNVT)*若S xU,则规定“$”FOLLOW(U)。即,F
19、OLLOW(U)是文法的所有句型中紧接在U之后出现的终结符或$($不是文法符号,而是一个特定的结束符)。,再来看看SELECT集合。 假定U是文法G的任一产生式,其中UVN,(VNVT)*,集合SELECT(U)的构造如下:利用上面的三个集合可建立LL(1)文法的判断条件如下:令G是一个CFG,当且仅当对于G中每个具有相同左部的产生式(即一个非终结符的所有产生式)U1|1|n都有 SELECT (Ui)SELECT(Uj)= (ij, i, j=1, 2, , n),则文法G是一个LL(1)文法。,5.3.2 集合FIRST、FOLLOW与SELECT的构造,1.FIRST()的求法:设A是一
20、产生式, (1)若=a,且aVt,则FIRST()=a; (2)若=X,X是非终结符(XVn),且X=*b,则把终结符b加入到FIRST()中; (3)若=X1X2Xk,X1,X2,Xk都是非终结符,而X1X2Xi =* ,则把FIRST(Xi+1Xk)加入到FIRST()中,其中1ik-1;如果X1X2Xk ,则把FIRST()加入到FIRST()中。,2.FOLLOW(A)的求法: (1)若有产生式BAa,a是终结符,把a加入到FOLLOW(A)中; (2)若有产生式BAX,X是非终结符,则把FIRST(X)加入到FOLLOW(A)中; (3)若BaA,或BaA,但=* 则把FOLLOW(
21、B)加入到FOLLOW(A)中; (4)若A是文法的开始符号,则把结束符$加入到FOLLOW(A)中。,3. SELECT可选集的求法:设A是文法G的任意产生式,它的编号为i,则它的可选集SELECT(i)定义如下: (1)如果,且是不可空的,则SELECT(i)=FIRST(); (2)如果,但是可空的,则SELECT(i)=FIRST() FOLLOW(A); (3)如果=,即产生式为A则SELECT(i)=FOLLOW(A)。,例5.9 设文法G32 S: (1) S A (2) A BA (3) A iBA (4) A (5) B CB (6) B +CB B (8) C ) A* (
22、9) C (SELECT (AiBA)SELECT(A)= FIRST(iBA)(FIRST()FOLLOW(A)= i*, $, =, SELECT (B +CB)SELECT(B) = FIRST(+CB)(FIRST()FOLLOW(B)= +i, *, $, =, SELECT (C )A*)SELECT(C ( )= FIRST( )A*)FIRST ( ( )= ) ( =。 此文法G是一个LL(1)文法,需要注意的是,对于LL(1)文法,利用SELECT集,我们还可以得到十分有助于句子分析的结果: 在分析过程中的某一步,当非终结符U正处于栈顶时,若aSELECT (U)是当前的输
23、入符号,则可立即选用产生式U;若aSELECT (U)是当前的输入符号,则可选用产生式U。 由于LL(1)文法的任何一对具有相同左部的产生式的SELECT集是不相交的,因此,根据现行的非终结符(已在栈顶)和当前的输入符号就能唯一地确定分析过程中的下一步动作,也就是说,我们完全可以为LL(1)文法构造一个确定的分析器。,5.4 确定的LL(1)分析器的构造,主要由三个部分组成: (1)LL(1)(预测)分析表 (2)分析栈 (3)分析器总控程序,5.4.1 LL(1)分析表的构造 预测分析表是根据文法产生式的可选集(SELECT)构造的,可用一个二维数组 MU,a来描述。 行:文法中所有非终结符
24、VN。列:文法中所有终结符VT及文法结束符号$。矩阵元素:当前行非终结符VN面临列终结符VT时,继续推导所应采用的产生式(产生式标号) 方法: 如果 a SELECT(U ) 则 MU,a = U MU,a中也可能存放一个ERROR,表示此时输入串中存在语法错误。,例5.11 对于例5.9中的文法G S,构造分析表。 对于产生式S A, FIRST(A)=(, ), MS, ( = S A,MS, ) = SA。 对于产生式ABA, FIRST(B)=(, ), MA, C = A BA,MA, ) = A BA。 对于产生式A iBA, FIRST (iBA) = i, MA, i= A i
25、BA。 对于产生式A , FOLLOW(A)=* , $, MA, * = A , MA, $=A 。,对于产生式BCB, FIRST(C) = (, ), MB, C = B CB,MB, ) = BCB。 对于产生式B +CB, FIRST(+CB) = +, MB, + = B +CB。 对于产生式B , FOLLOW(B) = i, *, $, MB, i = B ,MB, * = B ,MB, $ = B。 对于产生式C )A*, FIRST ( )A*) = ) , MC, ) = C )A*。 对于产生式C (, FIRST ( ) ) = ( , MC, ( = C (。,分析
26、表M,5.4.2 LL(1)分析器的总控算法,LL(1)分析器就是带有LL(1)分析表的一个PDA,也称预测分析程序。下面给出LL(1)分析器的总控算法: 最初,分析器把文法的开始符号S置于栈顶(假定输入串以$结束); 若栈顶为一终结符,而且与当前输入符号匹配,则读头前进一位置(扫描下一输入符号),并逐出栈顶符号。否则报错。(匹配动作) 若栈顶符号是一非终结符U,且当前的输入符号为a,则查看分析表M,若MU,a置有关于U的产生式Uw,则先从栈中逐出U再把w 反序进栈;若w=,则不推进任何信息进栈,仅逐出栈顶符号;若MU,a为空白,则调用出错处理子程序。(应用动作) 重复步骤、,直至栈变为空。
27、该分析器停止于空栈。(接收),例5.14 按照LL(1)分析器总控算法,对文法G32S的符号串 ( i (进行分析,分析过程见下表。分析结果表明该符号是文法G32S的一个句子。,LL(1)分析法的优缺点: 预测分析法是确定的自顶向下分析,不会产生回溯现象。 分析时间大约正比于程序长度。 具有良好的诊断和错误恢复能力。 预测分析表较其它分析方法中的分析表相对较小,节省空间。 只适用于一类可用预测分析文法描述的语言的语法分析。存在对非LL(1)文法的改造问题。,5.5 LL(k)文法的几个结论,可以证明: LL(k)文法是不含左递归的; LL(k)文法是无二义性的; 一文法G是LL(1),当且仅当
28、对于G的每一非终结符U的任何两个不同产生式A|,下面的条件成立: FIRST()FIRST()= ; ,中至多只有一个能推出空串; 若 ,则FIRST()FOLLOW(A)= 。,5.6 递归下降分析程序及其设计,递归下降法的实现思想:递归下降法又称为递归子程序法,是比较简单、直观,易于构造的一种语法分析方法。它的实现思想是对应文法中每个非终结符编写一个递归(子程序)过程,每个过程的功能是分析与识别由该非终结符推出的串,当某非终结符的产生式有多个侯选式时能够按LL(1)形式可唯一地确定选用某个侯选式进行推导。,递归子程序的设计方法,一般情况下,用非终结符表示过程的名字,过程体则按产生式右部符号
29、串顺序编写。每匹配一个终结符,则再读入下一个符号,对于产生式右部的每一个非终结符,则调用相应的过程。当一个非终结符对应多个侯选式时,则过程体将按可选集来决定选用哪个侯选式。在递归下降法中,递归子程序数等于文法中的非终结符个数。,构造递归下降分析程序时,每个函数名是相应的非终结符,函数体则是根据规则右部符号串的结构编写。(1)当遇到终结符a时,则编写语句:if (当前读入的输入符号=a),读入下一个符号;(2)当遇到非终结符A时,则编写语句调用相应的函数A();(3)当遇到A时,则编写语句:if(当前读入的符号FOLLOW(A),ERROR( );(4)当某个非终结符的规则有多个侯选式时,按LL
30、(1)文法的条件唯一地选择一个侯选式进行推导。,5.6.1 框图设计,例5.15 设文法G S:S (A)aAbA eAdSAA dA此文法的递归下降程序框图设计如下所示。图中省略了递归入口RI和递归出口RO部分,如果采用低级语言编程,只需要在程序框图的开头和末尾加上这两个部分。,5.6.2 程序设计,例5.16 例5.15中的文法G S:子程序P(S):READ(ch)if ch = ( thenbeginREAD(ch); P(A);if ch = ) then goto Lelse errorend,else if ch a then errorelse beginREAD(ch); P
31、(A);if ch = b then goto Lelse errorendL: READ(ch);return,子程序P(A):if ch = e thenbeginREAD(ch); goto Lendif ch d then errorP(S);L: P(A);return,子程序P(A):L: if ch = d thenbeginread(ch); goto Lendelse if ch = b then goto Lelse if ch = ) then goto Lelse errorL: return,问题:为什么要编成递归子程序?因为文法具有递归性。前面已经讲过,自顶向下分析
32、不能处理左递归文法,若有左递归,则应改写文法,但是消除了左递归并不等于消除了文法中的所有递归性质,此时文法有右递归性或自嵌入性。如在文法中可能有如下规则:UUUU此仍为递归规则,所以要编成递归子程序。,递归下降法的优缺点:,优点: 最主要一点是编写速度快; 由于分析器和文法的紧密对应性,容易保证语法分析器的正确性。至少使得错误都变得简单和易于发现。 缺点:在句法分析期间高深度的递归调用影响了分析器的效率,许多时间要花费在递归子程序的连接上。,5.7 带回溯的自上而下分析法,不带回溯的自上而下分析法,并不适用于所有的上下文无关文法,必须对文法加以限制;而带回溯的自上而下分析法,对上下文无关文法具有通用性,几乎没有限制,但速度慢是这种分析法的致命弱点。,