1、第四章 语法分析,语法分析是编译过程的核心部分,它以词法分析输出的单词串形式的源程序作为输入和分析的对象,它的主要任务是按照程序语言的语法规则,分析源程序的语法结构,即分析如何由这些单词组成各种语法成分,同时进行语法检查,为语义分析和代码生成作准备。执行语法分析任务的程序叫语法分析程序或语法分析器。 语法分析程序以词法分析输出的符号串作为输入,在分析过程中检查这个符号串是否为该程序语言的句子。若是,输出该句子的分析树;若不是,则表示源程序存在语法错误,需要报告错误的性质和位置。例如,对于C程序语句“IF (a10) b=5;”,词法分析识别出了IF、(、标识符、等单词符号,而语法分析则要检查这
2、些单词之间的搭配、结构是否正确。IF后面是否为(,(后面是否为正确的表达式等等。,常用的语法分析方法大体上可以分成自顶向下和自底向上两大类: 自顶向下分析方法:语法分析从顶部(树根、语言的识别符号)到底部(叶子、语言的终结符号)为输入的符号串建立分析树。本章介绍的递归下降分析方法和LL分析方法就属于自顶向下分析方法。 自底向上分析方法:语法分析从底部到顶部为输入的符号串建立分析树。最常见的算符优先分析法和LR分析方法采用的就是自底向上分析方法。 无论采用哪种分析方法,语法分析都是自左向右地读入符号。,4.1自顶向下的分析方法,自顶向下的分析方法就是从文法的开始符号出发,按最左推导方式向下推导,
3、试图推出要分析的输入串。自顶向下分析常用的方法有:递归下降分析( recursive-descent parsing)和LL( 1 )分析(LL(1) parsing)。,一般而言,为了给w寻找一个最左推导,须对每一步直接推导应使用的产生式进行判断,即反复用各候选式进行试探,以便得到正确的选择。例如,算术表达式文法GE:1.ET|EAT2.TF|TMF3.F(E)|i4.A|5.M*| / 给w=i+i*i建立最左推导。 E=T=F=(E) 错误,退回到F,选用其他候选式E=T=F=i 错误,退回到T,选用其他候选式E=T=TMF 错误,退回到E,选用其他候选式E=EAT,4.1自顶向下的分析
4、方法,上述自顶向下的分析过程有如下基本特点: 由于采用了最左推导,故如果文法是左递归的,便会使语法分析过程陷入循环不已的状态。例如E=EAT=EATAT=EATATAT= 采用最左推导以实现对符号串w的匹配,实际上是用文法产生式的诸候选式反复进行试探的过程,这势必会出现大量的回溯,从而导致语法分析效率的大幅下降。 当报告分析失败时,分析程序一般仅能给出输入串w不是句子这一结论,而难于指出出错的具体情况(错误性质和出错位置等) 。因此,欲实现自顶向下的语法分析,其首要任务是改造程序设计语言的文法,以消除其中的左递归和避免回溯的出现。,4.1.1消除文法的左递归,另一种方法是对A引入一个新的非终结
5、符号A,改写为 A A A A | 例如,前面文法的规则:EEAT|T ,TTMF|F,消除左递归: 方法一:ETAT TFMF 方法二:ETE TFT EATE | TMFT | 一般的,设文法中全部A-产生式为 A A 1| A 2| A n |1 | 2 | m 改写为A1A | 2A | m A A 1A| 2A| n A |,对于左递归问题,我们用等价文法来解决,即将文法中的左递归去掉。 (1)消除直接左递归: 对形如A A | 的直接左递归文法规则,用扩充BNF表示来改写规则,即利用元符号“”和“”来改写规则,将规则改写成 A ,4.1.1消除文法的左递归,下面,再给出一种通过将文
6、法G=(Vn, Vt, P, S)表示成矩阵形式而一次消除G的全部左递归的方法。 首先,令Vn=X1,X2,X n,且对G中的每个产生式Xiy1|y2|y m 将y1,y2,y m分成两类,可将其写成Xi = X11i + X22i + X n n i + i 于是,文法G便可写成如下的矩阵方程 (X1 X2 X n) = (X1 X2 X n) 11 12 1n + (1,2, n )21 22 2n n1 n2 n n,(2)消除一般左递归: 如果一个非终结符号A是经两步或多步推导而出现的左递归,则可对相关产生式作代入操作,将A-产生式化成直接左递归的,再按上面的方法将左递归消除。例如,对
7、于文法 S A | yA S 代入后得到(见引理2.1) S S | y 消除直接左递归得到S y SS S |,4.1.1消除文法的左递归,或 X = XA + B 此方程有形如X=BA*的最小解,由于A* =I+AA* 其中 I = 若设 A* = Z = z11 z12 z1nz21 z22 z2n zn1 zn2 z n n 则有 X = BZZ = I + AZ 将上述两矩阵式写成分量式,便得到了一组等价的新的产生式,且消除了原文法的一切左递归。但若新文法中含有无用产生式,则应予以删除。,4.1.1消除文法的左递归,例4.1 考虑文法 S S a| A b| a A Sc 显然,S,
8、A都是左递归的非终结符。 首先写出文法的矩阵表示(S A) = (S A) a c + (a )b 设 a c * = Z = z11 z12 b z21 z22 则有 (S A) = (a ) z11 z12z21 z22z11 z12 = + a c z11 z12z21 z22 b z21 z22 于是得到新的等价文法 Saz11 Aaz12z11az11|cz21| z12az12|cz22z21bz11 z22bz12| 删除无用产生式得 Saz11 z11az11|cz21|z21bz11,4.1.2 回溯的消除及LL(1)文法,下面我们讨论如何消除自顶向下分析过程中的回溯。此问题
9、的解决将导致定义一类很重要的文法-LL(1)文法。 对于给定的文法GS=(V n ,V t ,P ,S)和给定的输入符号串w=a1a2a n (a i V t),为判断w是否为L(G)中的句子,现试图为w建立一个从S出发的最左推导, 设经过若干步推导后得到 S=*w1A A V n, (V n V t)* 其中w1=a1a2a i-1 (1in) ,即w的一个前缀w1已从上面的推导得到匹配,现须对A 继续进行推导,现设G中的全部A-产生式为A y1| y2 | | y m 且对这m个候选式y k(1km),要么全部y k 均不能推导出以a i打头的符号串(此时w不是句子),要么存在唯一的一个y
10、 j,能使y j 推导出以a i打头的符号串,而其余的y k 则不能推出,这样上述推导过程在产生式选择上的试探将可避免。如果文法G中的全部产生式均满足上述要求,则消除回溯的问题自然就解决了。,4.1.2 回溯的消除及LL(1)文法,可见,要实现无回溯的自顶向下分析,对相应文法须有一定的要求。为导出文法应满足的条件,需定义候选式y的终结首符集和非终结符号A的后继终结符号集如下: FIRST (y) = a | y=*a,a V t 当y=*时,约定 FIRST (y) FIRST (y)就是从y可能推导出的句型的所有开头的终结符和可能的 。 FOLLOW(A)= a | S# =*A a, a
11、V t 当S=*A,约定#FOLLOW(A) FOLLOW(A)就是在所有的句型中紧接在A之后的终结符或# 。于是,对于一个已化简的非左递归文法G,在进行自顶向下语法分析时,不出现回溯的充分条件是对于G中的每个产生式A y1| y2 | | y m,其各候选式均应满足: (1)不同的候选式不能推出以同一终结符号打头的符号串,即FIRST (y i) FIRST (y j) = 1i,jm; i j (2)若有y j =,则其余候选式y i所能推导出的符号串不能以FOLLOW(A)中的终结符号开头,即FIRST (y i) FOLLOW(A) = i1,2,m; i j通常,我们将满足上述条件的
12、文法称为LL(1)文法。对于此种文法,可采用从左到右扫视源程序P,并按最左推导的方式求得与P中各符号的匹配,而且每步推导只需向前查看一个输入符号,就可准确的选择所用的产生式。,4.1.2 回溯的消除及LL(1)文法,下面,给出对给定文法G求各个FIRST集FOLLOW集的算法。由于每个产生式的各个候选式均是一个由文法符号所组成的符号串,因此仅须给出对单个文法符号求这两个集合的方法即可。 文法符号的FIRST集构造方法: 对于文法中的符号XVnVt,其FIRST(X)集合可反复应用下列规则计算,直到其FIRST(X)集合不再增大为止: 1)若XVt,则FIRST(X)=X 2)若XVn,且具有形
13、如Xa的产生式(aVt),或具有形如X的产生式,则把a或加进FIRST(X)。 3)若XVn,且X Y1 Y2 Y kP,且Y1Vn,则把FIRST(Y1)加进FIRST(X);若FIRST(Y1),则把FIRST(Y2)加进FIRST(X);若FIRST(Y1)和FIRST(Y2),则把FIRST(Y3)加进FIRST(X)以此类推;但若对一切1ik,均有FIRST (Yi),则把加进FIRST(X),4.1.2 回溯的消除及LL(1)文法,例4.2 设有文法 S abBA SC |B AbAC B |c 求所有非终结符和符号串AABcab的FIRST集合。 解:FIRST(S)=a FIR
14、ST(A)= FIRST(SC) = FIRST(S) =a,FIRST(B)= FIRST (AbA)= FIRST(A) FIRST (bA)=a,bFIRST(C)= FIRST(B) c=a,b,cFIRST (AABcab)= FIRST(A) FIRST(A) FIRST(Bcab)=a FIRST(B)=a,b,4.1.2 回溯的消除及LL(1)文法,对于文法中的符号BVn,其FOLLOW(B)集合可反复应用下列规则计算,直到集合不再增大为止: 1) 对于文法的开始符号,令#FOLLOW(S) 2) 若G中有形如AB 的产生式,且 ,则将FIRST()符号加进FOLLOW(B)。
15、 3) 若G中有形如AB或AB 的产生式,且FIRST(),则FOLLOW(A)中的全部元素均属于FOLLOW(B)。 注意:在FOLLOW集合中无。,4.1.2 回溯的消除及LL(1)文法,例4.3,有文法 ETE,E+TE,E, TFT, T*FT,T,F(E)|i, 求各非终结符号的FOLLOW集。 解:首先,我们需要求出某些符号的FIRST集: FIRST(E)=FIRST(T)=FIRST(F)= ( ,i FIRST(E)= + ,FIRST(T)= * ,接下来,按FOLLOW集定义求各非终结符号的FOLLOW集:FOLLOW(E)= ),#,FOLLOW(E)= FOLLOW(
16、E)= ) ,# FOLLOW(T)= FIRST(E) FOLLOW(E) FOLLOW(E)= + , ) , # FOLLOW(T)= FOLLOW(T)= + , ) , # FOLLOW(F)= FIRST(T) FOLLOW(T) FOLLOW(T) = *,+,) ,# ,前进,4.1.3 递归下降分析法,4.3.1 递归下降分析的基本方法 递归下降分析的概念极为简单,其方法是将文法中的每一个非终结符U的文法规则看作是识别U的一个过程定义,为每个非终结符号构造一个子程序,以完成该非终结符号所对应的语法成分的分析和识别任务。 当U的规则的右部只有一个侯选式,则按从左向右的顺序依次构
17、造规则U的识别过程代码。如果有终结符号,判断能否与输入的符号相等,如果相等,表示识别成功,读入指针指向下一个输入符号;如果不等,则意味着输入串此时有语法错误。如果是非终结符号,则简单调用这个非终结符号的子程序,由这个子程序完成该非终结符号所对应的语法成分的分析和识别任务。 当U的规则右部有多个侯选式时,则根据每个侯选式的第一个符号确定该侯选式分支。,例4.4,考虑文法Z:=(U)|aUb ,U:=dZ|e,为其构造递归下降分析子程序。并对输入串aeb进行语法分析 。 解:文法中有两个非终结符号Z和U,那么我们需要分别编两个过程来完成Z和U规则的识别。对于规则Z:=(U)|aUb,右部有两个候选
18、式,因此,U的识别过程有两个分支,分别根据符号(和a来判别。同理对规则U:=dZ|e设计的过程也分为两个分支。见图4.1(a)和(b)所示。,4.1.3 递归下降分析法,Z:=(U)|aUb,图4.1(a) 非终结符号Z的分析程序,过程U,INPUTSYM=下一个符号,Z,出口,INPUTSYM =e,Y,N,N,Y,语法错误: 输入串少d、e,INPUTSYM =d,INPUTSYM=下一个符号,Y,U:=dZ|e,图4.1(b) 非终结符号U的分析程序,4.1.3 递归下降分析法,每个非终结符号的子程序设计好后,就可以对输入串进行语法分析。假设输入串为aeb,从Z子程序开始识别, INPU
19、TSYM =a,由于INPUTSYM不等于(,等于a,所以选择Z子程序的右边分支,表示选择了Z:=aUb规则。读下一个符号,使INPUTSYM =e,调U子程序,因INPUTSYM=e,所以,读下一个符号,使INPUTSYM =b,表示使用U:=e规则,并返回调用程序Z子程序右边分支U的下方,接着判断INPUTSYM=b,读下一个符号,并退出Z,分析过程结束,从而判定输入串aeb语法分析成功。这个过程相当于构造了如下推导过程:Z=aUb=aeb,4.1.3 递归下降分析法,ET|E+T|E-T ET+T|-T TF|T*F|T/F TF*F| /F,图4.2 E和T的分析程序,4.1.4 预测
20、分析法,LL(1)分析方法也是常见的自顶向下分析,LL(1)分析使用一个下推栈而不是递归调用来完成分析。名称中第一个L表示自左向右顺序扫描输入符号串,第二个L表示分析过程产生一个句子的最左推导。括号中的1表示每进行一步推导,只需要向前查看一个输入符号,便能确定当前所应选用的规则。,LL(1)分析的基本方法 LL(1)分析器由一个总控程序、一张分析表和一个分析栈组成,如图4.4所示。,输入符号串:,分析栈,LL(1) 总控程序,分析表,输出流,图4.4 LL(1)分析器模型,4.1.4 预测分析法,输入符号串:指要分析的输入符号串。为了分析算法的统一,我们需要在输入串的末尾放置一个特殊符号#,这
21、个符号不属于终结符号集。分析表M:是一个二维表,可用一个二维数组MA,a来表示,它概括了文法的全部信息。分析表中的每一行与文法的一个非终结符号关联,即A可以是文法中的一个非终结符号;而每一列则与文法的一个终结符号或#关联,即a是文法的一个终结符号或#。分析表元素MA,a,指出了分析器应采取的动作:或是指出当前推导应使用的产生式,或是指出输入符号串存在语法错误。 对于不同的LL(1)文法,LL(1)的分析算法是相同的,不同的仅仅是分析表。显然,如何根据文法来构造分析表是LL(1)分析的关键。 对于任意给定的已化简的文法G,为了构造分析表,首先我们要求出每个非终结符号的FOLLOW集合和每个后选式
22、的FIRST集合。然后对文法G中的每个产生式A y1| y2 | | y m,按下列规则确定分析表中的元素M: 1) 若a FIRST (y i) ,则置MA,a=“y i” 。 2) 若 FIRST (y j),且a FOLLOW(A) ,则置MA,a=“y j”。 3) 除上述两种情况外其余一切表元素均置为“ERROR”。 分析栈:用来存放一系列文法符号。分析开始时,先将#入栈,然后再将文法的开始符号入栈。输出流:分析过程中使用的产生式序列。,总控程序:分析器对输入串的分析靠总控程序完成.根据分析栈的栈顶符号X和当前的输入符号a,总控程序按照分析表的指示来决定分析器的动作.工作过程如下:
23、第一步 初始化:分析开始时,首先将符号#及文法的开始符号S依次置于分析栈的底部,并把各指示器调整至起始位置,如图4.5所示。然后,反复执行第二步的操作。,输入符号串:,分析栈:,图4.5分析开始时的状况,第二步 假设分析的某一步,分析栈及余留的符号串如图4.6,则根据栈顶的符号X m,采取下列动作:,#X1X2Xm-1Xm,图4.6分析进行中的状况,4.1.4 预测分析法,(1)若X m V n,表示栈顶要推导出扫描的符号,则查分析表的X m行a i列,假设M X m,a i为产生式 X m Y1Y2Y k,则将X m出栈,并将Y1Y2Y k反序入栈,如图4.7; 若M X m,a i为ERR
24、OR,则出错。,#X1X2Xm-1Y k Y k-1Y1,图4.7 反序入栈,(2)若X m=a i#,表示栈顶与扫描的符号匹配,则栈顶符号X m出栈,输入指示器指向下一个符号, 否则(即X m a i)出错。 (3) 若X m=a i=#,表示输入串完全匹配,分析成功。,4.1.4 预测分析法,4.1.4 预测分析法,例4.5考虑算术表达式文法ETE,E+TE,E ,TFT,T *FT,T ,F (E) |i对规则ETE:FIRST(TE)= (,i,那么在分析表的符号E所在的行、符号(和i所在的列对应的位置分别填入TE,见表4.1的E行。 对规则E+TE:FIRST(+TE)=+,所以在分
25、析表E行+列对应的位置填入+TE,见表4.1的E行。 对规则E:FOLLOW(E)=),#,所以在分析表E行)和#列对应的位置填入 ,见表4.1的E行。 对于一个文法,若按上述方法构造的分析表M不含多重定义,则称它是一个LL(1)文法。,返回,表4.1 算术表达式分析表,表4.1 算术表达式分析表,4.1.4 预测分析法,根据表4.1给出的分析表,对符号串i+i*i进行分析。,表4.2 符号串i+i*i的分析过程,表4.2中输出的产生式序列构成对输入符号串的最左推导。按此产生式序列构造输入符号串i+i*i的最左推导过程如下: E = TE = FTE = iTE = iE = i+TE = i
26、+FTE = i+iTE i+i*FTE i+i*iTE i+i*iE i+i*i,4.1.4 预测分析法,4.1.5 某些非LL(1)文法的改造,对于任何LL(1)文法G,我们总能按上述方法为G构造一个预测分析表,且构造出的分析表决不会含有多重定义的元素.然而,对于非LL(1)文法,或因为它们含有左递归,或因为它们存在二义性,归根结底因为它们不满足无回溯条件,则构造出的分析表必然会出现多重定义的元素. 例如, S abBA SC |BAA |B AbAC B |c FIRST(S)=a FOLLOW(S)=a, b, c,# FIRST(A)=a, b, FOLLOW(A)=a, b, c,
27、# FIRST(B)=a, b FOLLOW(B)=a, b, c,# FIRST(C)=a, b, c FOLLOW(C)=a, b, c,# MA, a=A SC, A BAA MA, b=A BAA, A 可见分析表含有多重定义元素,2、解决二义性问题 这个问题有时可通过反复提取公因子解决。 对形如A 1 |2 | | m 的产生式,可改写为A AA 1 |2 | | m 例如,规则:T (E) |a (E)| a 可改成:T (E) |a T,T (E) |,4.1.5 某些非LL(1)文法的改造,1、左递归转成右递归 LL(1)分析不能处理左递归文法,但也不能像递归下降分析那样将左递
28、归改为采用扩充BNF表示。在LL(1)分析中,必须将左递归文法变成右递归文法。 例如,对左递归规则EE+T|T,如果像递归下降分析那样改成 ET+T无法形成逆序入栈,但可改成右递归:令E为新的非终结符号,则等价的右递归规则为: ETE,E+TE| 实际上,在递归下降分析方法中,也可将左递归规则改成右递归进行处理。,4.1.5 某些非LL(1)文法的改造,但并非所有的文法都可用此法解决分析表的多重定义。 考虑有文法GS:SAU|BR , AaAU|b , BaBR|b , Uc , Rd 对于规则SAU|BR,因为FIRST(AU)FIRST(BR),故该文法不是LL(1)文法。 为了能够提取公
29、因子,必须将非终结符号A、B的产生式带入S的产生式中,得到: SaAUU|bU|aBRR|bR 经提取公因子后,得到Sa(AUU|BRR)|b(U|R),令S、S”分别代替括号中的左右部分,得如下等价文法: SaS|bS” , SAUU|BRR , S”U|R , AaAU|b , BaBR|b , Uc ,Rd 显然,对于规则SAUU|BRR,因为FIRST(AUU)FIRST(BRR),故它仍然不是LL(1)文法。且无论重复多少次提取公因子,都不能把它变成LL(1)文法。由于递归下降分析法也存在这类问题,所以,自顶向下分析方法无法对这类文法进行语法分析。,4.1.5 某些非LL(1)文法的
30、改造,我们把能由某一LL(1)文法产生的语言称为LL(1)语言。LL(1)文法具有下列性质: 1)任何LL(1)文法都是无二义的。 2)若文法中有左递归,肯定不是LL(1)文法。但有些左递归文法可转换成右递归文法,从而满足LL(1)文法的要求。 3)存在一种算法,能判定文法是否为LL(1)文法。 4)存在一种算法,能判定任意两个LL(1)文法是否产生相同的语言。 5)不存在这样的算法,判定任意上下文无关语言能否由LL(1)文法产生。 6)非LL(1)语言是存在的。 在LL系列分析方法中,若每一步推导不是向前看一个字符,而是看K个字符才能确定要选用的产生式,则称为LL(K)分析,能满足LL(K)
31、分析条件的文法叫LL(K)文法。,习题,4.1 对下面文法,设计递归下降分析程序。SaAS|(A) , AAb|c4.2 设有文法GZ:Z=(A) , A=a|Bb , B=Aab 若采用递归下降分析方法,对此文法来说,在分析过程中,能否避免回溯?为什么?4.3 若有文法如下,设计递归下降分析程序。|Id=;|+|-|*|/ID|NUM|(),4.4 有文法GA:A:=aABe| B:=Bb|b1)求每个非终结符号的FOLLOW集。2)该文法是LL(1)文法吗?3)构造LL(1)分析表。4.5 若有文法A(A)A|,1)为非终结符A构造First集合和Follow集合。2)说明该文法是LL(1
32、)的文法。 4.6 利用分析表4.1识别以下算术表达式,请写出分析过程:1) i + i * i + i 2) i * (i +i + i ),习题,4.7 考虑下面简化了的C声明文法:;int|float|charID,|ID 1) 在该文法中提取左因子。 2) 为所得的文法的非终结符构造First集合和Follow集合。 3) 说明所得的文法是LL(1)文法。 4) 为所得的文法构造LL(1)分析表。 5) 假设有输入串为:char x,y,z ,写出相对应的LL(1)分析过程。,习题,4.8 修改语法分析程序,使该程序能分析do语句和逻辑表达式,有关文法规则如下::= if_stat| |:=do while ;:= ID=|:=(&|)|!|&、|、!为逻辑运算符。,习题,