1、编译原理与技术,第4章 自顶向下的语法分析,编译原理与技术,2,主要内容,自顶向下语法分析概述 LL(1)文法 递归下降分析技术 预测分析技术 LL(1)分析中的错误处理,编译原理与技术,3,4.1 自顶向下语法分析的一般方法,基本思想: 对任何输入串,试图用一切可能的办法,从文法开始符号出发,自上而下,从左到右地为输入串建立分析树。或者说,为输入串寻找最左推导。 特点: 本质上是一种试探过程,反复使用不同的产生式谋求匹配输入串。,编译原理与技术,4,例4.1:设文法GS:ScAd,Aab|a,输入串为cad,自顶向下进行语法分析,并构造相应语法树。,4.1 自顶向下语法分析的一般方法,先按文
2、法的开始符号产生根结点S,选择S惟一的产生式构造语法树,如图4.1(a)所示。 把S的子结点从左到右与输入串中的字符相比较,第一个子结点c匹配输入串第一个符号。第二个子结点是非终结符A,要选择A的产生式进一步构造。而A有两个候选式,先选择Aab构造语法树,如图4.1(b)所示。,(a) (b) (c) 图4.1 输入串cad的语法分析树,编译原理与技术,5,此时A的最左子结点a匹配输入串第二个符号。而A的第二个子结点b和输入串第三个符号不一致,说明A的这个产生式不能用来产生该输入串。这就需要回到子结点A,选用另一个产生式Aa来构造语法树,如图4.1(c)所示。 这种回头选用其他产生式的过程称为
3、回溯。回溯时,要把由前面产生式Aab得到的子树删除,并重新读入在子结点A处的当时符号a。 用新的产生式构造语法树后,A的子结点为a,与当前符号一致,读入最后一个输入符号d。此时结点A完成匹配,它的右边是结点d,与当前符号一致。这样就完成了输入串的匹配,说明输入串cad是该文法的一个句子。 上面的分析同时也给出了输入串cad的最左推导过程:S cAd cad。,4.1 自顶向下语法分析的一般方法,(a) (b) (c),编译原理与技术,6,这种一般方法存在一些问题: (1) 左递归问题自顶向下分析采取最左推导,文法中含有左递归会使自上而下的分析过程陷入无限循环。因此,必须消除文法的左递归。 (2
4、) 回溯问题反复寻找可正确匹配的产生式时可能需要不断回溯,虚假匹配现象需要使用更复杂的回溯技术。这样将会产生许多额外工作,因此应设法消除回溯。,4.1 自顶向下语法分析的一般方法,编译原理与技术,7,(3) 出错处理分析不成功时,要确定出错的具体位置比较困难。 (4) 效率问题这种带回溯的自顶向下方法实际上是一种穷尽一切可能的试探法,因此效率很低,代价较高,从而该方法只有理论意义,在实际应用中价值不大。,4.1 自顶向下语法分析的一般方法,编译原理与技术,8,4.2 LL(1)文法,要实现无回溯的自顶向下语法分析,对相应文法必须要有一定的限制。首先,文法应该不含左递归,若文法中含有左递归,则需
5、使用文法的等价变换消除左递归。其次,还要消除回溯。通过提取左因子消除某些文法的回溯,为什么?没有左递归和左因子的文法是否一定可以进行确定的自顶向下分析?,编译原理与技术,9,首符集FIRST,4.2 LL(1)文法,例4.2:文法G1S: SpA | qB AcAd | a BdB | b,w1 = pccadd自顶向下的推导过程: S pA pcAd pccAdd pccadd,语法树:,编译原理与技术,10,4.2 LL(1)文法,文法G1S: SpA | qB AcAd | a BdB | b这个文法的特点: 每个产生式的右部都由终结符号开始。 如果两个产生式有相同的左部,那么它们的右部
6、由不同的终结符开始。,对于这样的文法,分析输入串时,可以跟据输入串的当前符号确定选取产生式。比如w1 = pccadd,第一个符号是p,而从开始符号S出发,只有选择产生式SpA推导,才能出现符号p。同样,要出现第二个符号c,必须选择产生式AcAd。这样,虽然具有相同左部的产生式不只一个,但文法的特点决定了每一步推导只能选择惟一的产生式,从而可以避免回溯。,编译原理与技术,11,4.2 LL(1)文法,例4.3:文法G2S: SAa | Bb Aa | cA Bb | dB,w2=ccaa自顶向下的推导过程: S Aa cAa ccAa ccaa,语法树:,编译原理与技术,12,4.2 LL(1
7、)文法,文法G2S: SAa | Bb Aa | cA Bb | dB这个文法的特点: 每个产生式的右部不全是由终结符号开始。 如果两个产生式有相同的左部,那么它们的右部由不同的终结符或非终结符开始。 文法中无空产生式。,对于这样的文法,分析输入串时,也可以跟据输入串的当前符号确定地选取产生式。 比如推导w2=ccaa时,首先考虑开始符号S,它可以推出以A或B开头的符号串。而由以A和B为左部的产生式可知,A只能推出以a, c开头的符号串,B只能推出以b, d开头的符号串。因此,要得到w2的第一个符号c,只有选择产生式SAa,AcA。同样,要出现第二个符号c,仍需选择产生式AcA,没有别的选择。
8、这样,文法的特点决定了每一步推导只能选择确定的产生式,从而可以避免回溯。,编译原理与技术,13,由上面两个例子可知,一个文法在推导过程中是否会产生回溯,与文法中具有相同左部的每个产生式右部所能推出的开头符号有关系。定义4.1: 设G = (VN,VT , P, S)是上下文无关文法,对于V*,V=VNVT,定义首符集FIRST()为FIRST() = a | , aVT, ,V*即FIRST() = a | a, aVT, V*特别地,若 ,则规定FIRST(),即FIRST()是能推导出的所有在开头位置的终结符或空符。,4.2 LL(1)文法,编译原理与技术,14,4.2 LL(1)文法,由
9、此我们可以看出,如果文法中不含空符产生式,并且每个非终结符的所有候选式右部的首符集两两不相交,则推导中就不会产生回溯。而提取左因子正是为了达到这个目的,即反复提取左因子后,就能够把每个非终结符(包括新引进的非终结符)的所有候选首符集变成两两不相交。因此,提取左因子可以使得不含空符产生式的文法消除回溯。,编译原理与技术,15,4.2 LL(1)文法,后继符集FOLLOW,例4.4: 文法G3S: SAa | Bb Aa | cA |Bb | dB,输入串w3 = cca自顶向下的推导过程为: S Aa cAa ccAa cca,G3与例4.3中文法G2惟一不同之处在于,G3中非终结符A的候选式中
10、含有空符产生式。分析时,对于输入串w3的前两个符号cc,可以确定使用产生式AcA,而要得到第三个符号a,按照a所在的首符集我们应该选择产生式Aa,但是显然这种选择是错误的,因为这样得到的是符号串ccaa而不是cca。实际上,这时正确的选择是产生式A,也就是让A自动匹配到空符,就可以得到与输入串匹配的符号串cca。,编译原理与技术,16,4.2 LL(1)文法,这说明只要求文法每个非终结符的所有候选首符集两两不相交是不够的,还需要进一步讨论。观察上面例子可以看出,之所以会出现上述问题,是因为文法中含有空符产生式A,并且推导过程中A后面跟的终结符a恰好也是A的一个右部首符集中的符号。也就是说,a既
11、能紧跟在A的后面出现,也能由A推出。这样,如果遇到当前非终结符是A而输入串中相应符号为a时,就不容易确定该用产生式A将A自动匹配空符得到紧跟其后的a,还是用产生式Aa推出a。,文法G3S: SAa | Bb Aa | cA |Bb | dB,编译原理与技术,17,因此,一个文法能否进行确定的自顶向下语法分析,不仅仅与文法中具有相同左部的产生式右部的FIRST集有关系,若有产生式右部可能推出,则还与其左部非终结符的后继符号集合有关。定义4.2: 设G = (VN,VT , P, S)是上下文无关文法,对于PVN,定义后继符集FOLLOW(P)为FOLLOW(P) = a | S P且aFRIST
12、(), VT*, V+ 即 FOLLOW(P)=a | S Pa , aVT 。特别地,若S P,则规定$FOLLOW(P)。即FOLLOW(P)是推导过程中所有可能紧跟在P之后的终结符或边界符号$($用来界定输入串,表示为:$输入串$)。,4.2 LL(1)文法,编译原理与技术,18,可以看出,开始符号S后面不会跟任何符号,但是有S S,因此FOLLOW(S)=$。非终结符A后面可能不跟任何符号,即S A,也可能跟开始符号S,而S推导的符号串只能以a, d开头,即FIRST(S)= a, d,因此FOLLOW(A)= a, d, $。,4.2 LL(1)文法,例4.5:文法G4S: SaA
13、| dAbAS |,输入串w4 = abd的推导过程为: S aA abAS abS abd,编译原理与技术,19,一般地,文法中含有形如P|,PVN,,V*的产生式时,若, 不能同时推导出空符,不妨设 , ,则当 FIRST()( FIRST()FOLLOW(P) = 时,对于非终结符P可以确定地选取产生式。,4.2 LL(1)文法,比如例4.4中,产生式Aa | cA |,FOLLOW(A)=a,$,FIRST()=FIRST(a | cA)=a,c, FIRST()=FIRST()=,FIRST()( FIRST()FOLLOW(A)= a,ca,$= a 。因此不能确定选取产生式。而例
14、4.5中,产生式AbAS |,FOLLOW(A)=a,d,$,FIRST()=FIRST(bAS)=b,FIRST()=FIRST()=,FIRST()( FIRST()FOLLOW(A)= ba,d,$= 。因此可以确定选取产生式。,编译原理与技术,20,选择集SELECT 定义4.3: 给定不含左递归的上下文无关文法的产生式P , PVN, V*,定义选择集SELECT(P )为若 ,则SELECT(P ) = FIRST( )若 ,则 SELECT(P ) = (FIRST( )- )FOLLOW(P)也就是说,当文法中含有形如P|(PVN, , V*, , 不同时能推出)的产生式时,能
15、够使用确定的自顶向下分析必须使文法满足SELECT(P)SELECT(P)= ,4.2 LL(1)文法,编译原理与技术,21,比如,例4.5中SELECT(SaA) = FIRST(aA) = a,SELECT(Sd) = FIRST(d) = dSELECT(AbAS) = FIRST(bAS) = b,SELECT(A) = (FIRST()- )FOLLOW(A) = a,d,$SELECT(SaA) SELECT(Sd) = ad = SELECT(AbAS) SELECT(A) = ba,d,$ = 因此,文法G4可以进行确定的自顶向下语法分析。,4.2 LL(1)文法,例4.5:文
16、法G4S: SaA | dAbAS |,编译原理与技术,22,LL(1)文法 定义4.4: 文法G是LL(1)文法,当且仅当每个非终结符P的任何两个候选式P|(, V*)满足: 不存在终结符a,使得和推出的符号串都能以a开头。即 FIRST()FIRST() = 若 , ,则所能推出的符号串的开头符号不在FOLLOW(P)中。即 FIRST()FOLLOW(P) = ,4.2 LL(1)文法,编译原理与技术,23,4.2 LL(1)文法,由SELECT集可以得到LL(1)文法的另一个等价定义:定义4.5: 一个上下文无关文法是LL(1)文法,当其仅当对于每个非终结符P的任何两个候选式P | 满
17、足 SELECT( P ) SELECT( P ) = 其中PVN,, V*,且, 不同时推出。,例4.6:下面文法是否是LL(1)文法?(1)G1S: SaA | d AbAS | (2)G2S: SaAS | b AbA |,编译原理与技术,24,4.2 LL(1)文法,解:用定义4.4判断 (1)对于S aA | d ,FIRST(aA) FIRST(d)= ad= ;对于A bAS | ,FIRST(bAS) FIRST()= b= ;FIRST(bAS)=b,FOLLOW(A)= a, d, $,FIRST(bAS) FOLLOW(A)=ba, d, $= ;因此文法G1满足条件,
18、,由定义4.4知该文法是LL(1)文法。 (2)对于S aA | b ,FIRST(aA) FIRST(b)=ab= ,对于A bAS | ,FIRST(bAS) FIRST()=b= ,FIRST(bAS) = b,FOLLOW(A) = a, b, $FIRST(bAS) FOLLOW(A) = ba, b, $ = b。因此文法G2满足条件,但不满足条件,从而不是LL(1)文法。,编译原理与技术,25,4.2 LL(1)文法,用定义4.5判断 (1)SELECT(SaA) = FIRST(aA) = aSELECT(Sd) = FIRST(d) = dSELECT(AbAS) = FIR
19、ST(bAS) = bSELECT(A) = (FIRST()- )FOLLOW(A) = a,d,$所以 SELECT(SaA)SELECT(Sd) = ad = SELECT(AbAS)SELECT(A) = ba,d,$ = 由定义4.5知文法G1是LL(1)文法。 (2)SELECT(SaAS) = FIRST(aAS) = aSELECT(Sb) = FIRST(b) = bSELECT(AbA) = FIRST(bA) = bSELECT(A) = (FIRST()- )FOLLOW(A) = a,b,$所以 SELECT(SaAS)SELECT(Sb) = ab = SELECT
20、(AbA)SELECT(A) = ba,b,$由定义4.5知文法G2不是LL(1)文法。,编译原理与技术,26,4.2 LL(1)文法,LL(1)文法的特点 没有左因子 不是二义的 不含左递归,一个文法中若含有左递归和左因子,则它一定不是LL(1)文法,也就不可能用确定的自顶向下分析法。然而,某些含有左递归和左因子的文法可以通过等价变换,消除左递归和左因子后可能变为LL(1)文法,不过仍需要用LL(1)文法的定义加以判别。也就是说,文法中不含左递归和左因子只是LL(1)文法的必要条件。,编译原理与技术,27,4.3 递归下降分析技术,基本思想 为文法中每个非终结符编写一个递归过程,每个过程的功
21、能是识别由该非终结符推出的串,当某非终结符的产生式有多个候选式时,按LL(1)形式唯一确定选择哪个候选式进行推导,若遇到某候选式为,认为其自动匹配。把这些递归过程组合起来就构成了文法的递归下降分析程序。,编译原理与技术,28,4.3 递归下降分析技术,递归下降分析器的设计 设LL(1)文法G=(VN,VT,P,S), VN=X1,X2,Xn。对G的每个非终结符号Xi,可以按照下面方法设计子程序Pi( ):(1)对于形如Xi 1 | 2 | | m 的产生式,在相应子程序Pi( )中,应该能够判断当前输入符号a属于哪个候选式j 的FIRST集,并转入该候选式相应代码段,继续识别。对候选式的选择可
22、用if语句或case语句实现。,编译原理与技术,29,4.3 递归下降分析技术,(2)对于形如XiY1Y2Yk(YjVNVT)的产生式,相应子程Pi( )是一个依次识别其右部各符号Yj(j=1,2,k)的过程:如果YjVT,则判断当前输入符号是否与Yj匹配;若YjVN则应有调用相应于Yj的子程序的代码。(3)对于形如Xi的产生式,在相应的子程序Pi( )中,应该能够判断当前输入符号a是否属于集合FOLLOW(Xi),从而决定是从Pi( )返回还是报错。(4)在各个子程序Pi( )中,均应含有进行语法检查的代码。,编译原理与技术,30,4.3 递归下降分析技术,例4.8:下面文法产生Pascal
23、类型的子集,我们用dotdot表示“”,以强调这个字符序列作为一个词法单元。type simple | id | array simple of typesimple integer | char | num dotdot num显然该文法是LL(1)文法,为其构造递归下降分析器。,编译原理与技术,31,4.3 递归下降分析技术,使用类Pascal语言为非终结符type设计子程序如下: proccdure type; begincase lookahead ofin integer, char, num: simple( ): beginmatch (); match (id)endarray
24、: beginmatch (array); match ( ); simple( ); match ( ); match (of ); type( )endother error( )end case end;,编译原理与技术,32,4.3 递归下降分析技术,在这段代码中,使用变量lookahead来存放向前查看的单词符号,根据该单词符号的不同而选择不同的动作。具体来说,如果lookaheadFIRST(simple)=integer, char, num,则转入simple子程序;如果当前单词符号为,则调用匹配函数match,检查是否匹配,若匹配则读入下一个单词符号,存放到变量lookahe
25、ad中,然后继续调用匹配函数match,检查当前符号是否与match函数的参数id匹配,若匹配则意味着可以选取产生式type id;如果当前单词为array,则依次执行以下操作:匹配array,匹配 ,调用simple子程序,匹配 ,匹配of,递归调用type子程序;如果lookahead中的单词符号不是上述符号,则调用出错处理函数error。,编译原理与技术,33,4.3 递归下降分析技术,我们用nexttoken作为读取下一个单词符号的函数,则match函数的设计如下:procedure match (t : token);beginif lookahead = t thenlookahe
26、ad := nexttoken( )else error( )end ifend;,编译原理与技术,34,4.3 递归下降分析技术,类似地,给出非终结符simple的子程序如下:procedure simple;begincase lookahead ofinteger: match (integer)char: match (char)num: beginmatch (num); match (dotdot); match (num)endother error( )end case end;,编译原理与技术,35,4.3 递归下降分析技术,这样,我们得到了两个非终结符的子程序,递归下降分析
27、的主程序设计就比较简单了。首先需要读入一个单词符号,然后调用开始符号的子程序,让其自动递归下降进行子过程调用。当所有调用结束,最终回到主程序时,判断是否到达输入串末尾,如果到达,则分析成功,否则出错。我们用函数gettoken读入输入串第一个单词符号,则主程序伪代码如下:beginlookahead =gettoken( );type( );if lookahead =$exit;else error( )end ifend,编译原理与技术,36,4.3 递归下降分析技术,将上面的各程序组合起来,就得到了该类型定义文法的递归下降分析器。现在使用该分析器分析输入串array integer of
28、 char,首先读入第一个单词array,然后调用开始符号type的子程序,依次进行下面操作:匹配array,读取下一符号 ;匹配 ,读取下一单词integer;调用simple的子程序,匹配integer,读取下一符号 。返回type过程,继续进行下面操作:匹配 ,读取下一单词of;匹配of,读取下一单词char;递归调用type过程,因为char在integer, char, num中,所以调用simple过程,匹配char,读取下一符号,即输入串的结束符$。返回最近的type过程,结束操作,返回外层type过程,结束操作,返回主程序。此时变量lookahead中存放的是结束符$,因此该分
29、析过程结束。说明该输入串符合Pascal类型定义。,编译原理与技术,37,4.3 递归下降分析技术,例4.9:为下列表达式文法GE编写递归下降识别程序。E E+T | TT T*F | F F (E) | i,解:步骤1:消除左递归:E TE E +TE | T FT T *FT | F (E) | i步骤2:编写递归下降识别子程序,这里使用C语言形式。见下页图。,编译原理与技术,38,编译原理与技术,39,4.3 递归下降分析技术,步骤3:编写递归下降识别主程序,编译原理与技术,40,4.3 递归下降分析技术,使用该识别程序识别输入串:i * i + i,首先读入符号i,然后调用子程序E,在
30、E中调用子程序T,在T中调用子程序F,匹配i,读入下一符号*,子程序F调用完毕,回到T。在T中继续调用子程序T,匹配*,读入下一符号i。在T中继续调用子程序F,匹配i,读入下一符号+,回到子程序T。在T中递归调用子程序T,不执行任何操作返回T,进而返回T,进而返回E。在E中继续调用子程序E,匹配+,读入下一符号i,调用子程序T。在T中调用子程序F,匹配i,读入下一符号,为输入串结束符$,返回E。在E中递归调用E,不执行任何操作返回E,进而返回E。至此子程序E调用完毕,回到主程序,检查到达输入串末尾,从而识别成功。,编译原理与技术,41,4.3 递归下降分析技术, 用花括号表示闭包运算*。 用n
31、0表示可任意重复0到n次,特别地,n0=0= 。 用方括号表示10,即表示可以出现也可以不出现,等价于 | 。,扩充的巴科斯范式EBNF 例4.10:例4.9的表达式文法用扩充的巴科斯范式表示为: E T+T T F*F F (E) | i 与每个非终结符相对应的递归子程序的伪代码如下:,编译原理与技术,42,编译原理与技术,43,4.3 递归下降分析技术,使用该识别程序识别输入串:i * i + i。首先读入符号i,然后调用子程序E,在E中调用T,在T中调用F,匹配i,读入下一符号*,返回T。在T中匹配*,读入下一符号i;调用F,匹配i,读入下一符号+,返回T,返回E。在E中匹配+,读入下一
32、符号i,调用T。在T中调用F,匹配i,读入下一符号,为输入串结束符$,返回T,返回E,返回主程序。检查到达输入串末尾,从而识别成功。 显然,使用EBNF编写的递归子程序数量较少,分析过程也比较简单。但是需要注意的是,将BNF转化为EBNF通常不是一件很容易的事情。,编译原理与技术,44,4.3 递归下降分析技术,递归下降分析的特点 优点 实现思想简单明了,程序结构和语法规则有直接的对应关系。 由于每个过程表示一个非终结符号的处理,添加语义加工工作比较方便。 递归下降分析需要书写程序的语言支持递归调用,如果递归调用机制是高效的,那么分析程序也是高效的。,编译原理与技术,45,4.3 递归下降分析
33、技术,缺点 递归下降分析对文法要求比较高,必须满足LL(1)文法。当然在某些语言中个别产生式的推导当不满足LL(1)而满足LL(2)时,也可以采用多向前扫描一个符号的办法。 由于递归调用多,所以速度慢,占用空间多。尽管如此,递归下降分析方法仍是许多程序语言(例如PASCAL,C等编译系统)常常采用的语法分析方法。,编译原理与技术,46,4.4 预测分析技术,预测分析程序的工作过程 一个预测分析器由预测分析程序(总控程序)、先进后出栈(STACK)、预测分析表三个部分组成,其中只有预测分析表与文法有关,它是一个二维矩阵,存放非终结符X面临输入符号a时应选择的产生式,一般用MX, a表示,是预测分
34、析程序分析时的主要依据。,编译原理与技术,47,4.4 预测分析技术,图4.2 预测分析器模型,编译原理与技术,48,4.4 预测分析技术,预测分析程序开始时,将$置入STACK栈,将开始符号S置入STACK栈,将第一个输入符号读入a,将栈顶符号读入X。总控程序执行下面四种动作之一。 (1)推导 若XVN,并且MX, a中存有产生式X X1X2Xk,则X 出栈,X1, X2, , Xk反序置入STACK栈(X 时直接将X出栈即可); (2)匹配 若X=a$,则X出栈,把下一输入符号读入a。 (3)接受 若X=a=$,则分析成功,停止分析过程。 (4)出错 若XVT,但Xa,或者若XVN,但MX
35、, a 中存放出错标记error,则报告出错。,编译原理与技术,49,4.4 预测分析技术,预测分析程序工作示意图,编译原理与技术,50,4.4 预测分析技术,算法4.1 非递归的预测分析 输入: 输入串w和文法GS的分析表M 输出: 如果wL(G), 输出w的最左推导,否则报告错误。 $ S进栈, S在栈顶, w$在输入缓冲区, ip指向w$的第一个符号; /准备工作 flag:=true /flag作为控制标记 while flag do令X等于栈顶符号, a等于ip指向的符号;if XVN then doif MX, a = X Y1Y2 Yk then do从栈中弹出X;把Yk,Yk1
36、, , Y1依次压入栈, Y1在栈顶;输出产生式X Y1Y2 Ykelse error ( ) /报告错误else if X = a then doif X=$ then flag:=false /结束循环else 把X从栈顶弹出, ip指向下一符号else error ( ) end while;,编译原理与技术,51,4.4 预测分析技术,预测分析表的构造 构造FIRST集 首先求出文法中每个文法符号的首符集,即为XVTVN构造FIRST(X)。方法如下: 若XVT,则FIRST(X)=X; 若XVN,且有产生式X a,则 FIRST(X)= FIRST(X)a。特别地,若有产生式X,则
37、FIRST(X)= FIRST(X);,编译原理与技术,52,4.4 预测分析技术,若XVN,且有产生式X Y1Y2Yk,(i=1, 2, , k),则 若 1ik,使得FIRST(Yj), (j=1, 2, , i-1),即Y1Y2Yi-1 ,则 FIRST(X)= FIRST(X)(FIRST(Yj)- ); 若 1ik,都有FIRST(Yj),则 FIRST(X)= FIRST(X) 反复利用以上规则,直到FIRST(X)不再增大为止。然后求符号串的首符集FIRST (),算法如下:,编译原理与技术,53,4.4 预测分析技术,算法4.2 求符号串的首符集FIRST () 输入: 文法G
38、, 符号串=X1X2Xn及FIRST(Xi), 其中Xi(VNVT), 1i n 输出: 首符集FIRST () FIRST()=, k=0 for i = 1 to n doif FIRST(Xi) then 置FIRST()=FIRST()(FIRST(Xi)-) 且 k=k+1else 置FIRST()=FIRST()FIRST(Xi) 结束循环 end for; if k=n then 置FIRST()=FIRST(),编译原理与技术,54,4.4 预测分析技术,例4.11:文法GS: S AB | bC A b | B aD | CAD | bD aS | c各非终结符的首符集为:F
39、IRST(D)=a, c FIRST(B)=a, FIRST(A)=b, FIRST(C) = (FIRST(A)-) FIRST(D) b=a, b, c FIRST(S) = (FIRST(A)-) (FIRST(B)-) b = a, b, 符号串AB的首符集为:FIRST(AB)= (FIRST(A)-) (FIRST(B)-) = a, b, 考虑产生式SAB | bC,因为FIRST(AB) FIRST(bC)= a, b, b ,由定义4.6可知,该文法不是LL(1)文法。,编译原理与技术,55,4.4 预测分析技术,构造FOLLOW集 若P是文法开始符号,则FOLLOW(P)=
40、$; 若有产生式PQ,则 FOLLOW(Q)= FOLLOW(Q) ( FIRST()-) 若有产生式PQ,或者有产生式PQ而,即FIRST(),则 FOLLOW(Q)= FOLLOW(Q) FOLLOW(P) 反复使用上面规则,直到每个FOLLOW集不再增大为止。,编译原理与技术,56,4.4 预测分析技术,算法4.3 求非终结符后继集FOLLOW 输入: 文法GS及FIRST(X), 所有X(VNVT) 输出: 所有非终结符的后继集FOLLOW FOLLOW (S)=$ FOLLOW (P)=, 所有PVN, PS for 每一个产生式 doif 该产生式形如PQ then 置FOLLOW
41、(Q)= FOLLOW(Q) ( FIRST()-)if then 置FOLLOW(Q)= FOLLOW(Q) FOLLOW(P)if 该产生式形如PQ then 置FOLLOW(Q)= FOLLOW(Q) FOLLOW(P) end for;,编译原理与技术,57,4.4 预测分析技术,例4.12:仍讨论例4.11文法GS: S AB | bC A b | B aD | CAD | bD aS | c所有非终结符求FOLLOW集过程如下:FOLLOW(S)=$FOLLOW(D)FOLLOW(A)=aa,cFOLLOW(S)FOLLOW(B)=FOLLOW(S)FOLLOW(C)=FOLLOW
42、(S)FOLLOW(D)=FOLLOW(B)FOLLOW(C)从FOLLOW(S)=$开始,不断循环求解直到所有FOLLOW集不再增大,最后可得:FOLLOW(S)= $ FOLLOW(A)= a, c, $FOLLOW(B)= $ FOLLOW(C)= $FOLLOW(D)= $,编译原理与技术,58,4.4 预测分析技术,构造SELECT集 算法4.4 求选择集SELECT 输入: 文法GS及FIRST(X), 所有X(VNVT), FOLLOW(P), 所有PVN 输出: 所有产生式的选择集SELECT for 每一个产生式P , PVN, V* doif then 置SELECT(P
43、) = FIRST( )if then 置SELECT(P ) = (FIRST( )- )FOLLOW(P) end for;,编译原理与技术,59,4.4 预测分析技术,例4.13:例4.11中文法GS: S AB | bC A b | B aD | C AD | bD aS | c对所有产生式求SELECT集的过程为:SELECT(SAB)=FIRST(AB) FOLLOW(S)= b, a, $ SELECT(SbC)=FIRST(bC)=bSELECT(A)=FIRST()FOLLOW(A)= a, c, $ SELECT(Ab)=FIRST(b)=bSELECT(B)=FIRST(
44、)FOLLOW(B)=$SELECT(BaD)=FIRST(aD)=aSELECT(CAD)=FIRST(AD)= a, b, c SELECT(Cb)=FIRST(b)=bSELECT(DaS)=FIRST(aS)=aSELECT(Dc)=FIRST(c)=c,编译原理与技术,60,4.4 预测分析技术,由以上计算结果可得相同左部产生式的SELECT交集为:SELECT(SAB)SELECT(SbC)= b, a, $ b=b SELECT(A)SELECT(Ab)= a, c, $ b=SELECT(B)SELECT(BaD)=$a=SELECT(CAD)SELECT(Cb) b, a,
45、c bb SELECT(DaS)SELECT(Dc)=ac由LL(1)文法定义知该文法不是LL(1)文法,因为关于S和C的相同左部其产生式的SELECT集的交集不为空。,编译原理与技术,61,4.4 预测分析技术,例4.14:文法GE: ETE E +TE | T FT T *FT | F (E) | i求所有非终结符的FIRST集和FOLLOW集,以及所有产生式的SELECT集,并判断是否是LL(1)文法。,编译原理与技术,62,4.4 预测分析技术,所有非终结符的FIRST集:FIRST ( E ) = (, i FIRST ( E ) = +, FIRST ( T ) = (, i FIRST ( T ) = *, FIRST ( F ) = (, i ,文法GE: ETE E +TE | T FT T *FT | F (E) | i,编译原理与技术,63,4.4 预测分析技术,所有非终结符的FOLLOW集:FOLLOW ( E ) = ),$FOLLOW ( E ) = ),$ FOLLOW ( T ) = +, ), $ FOLLOW ( T ) = +, ), $ FOLLOW ( F ) = *, +, ),$ ,文法GE: ETE E +TE | T FT T *FT | F (E) | i,