1、编译原理,第五章 语法分析自下而上分析,SLR 分析,LR(0) 方法对文法的要求严格,现实中很难保证文法的符合LR (0) 通过对非终结符跟随符号的展望可以解决部分 LR(0) 分析冲突,假定一个LR(0)规范族中含有如下的一个项目集(状态):IXb,A,B。 若FOLLOW(A)和FOLLOW(B)的交集为,且不包含b,那么,当状态I面临任何输入符号a时,可以: 1. 若a=b,则移进; 2. 若aFOLLOW(A),用产生式A进行归约; 3. 若aFOLLOW(B),用产生式B进行归约; 4. 此外,报错。,假定LR(0)规范族的一个项目集 I=A1a11,A2a22,Amamm,B1,
2、B2,Bn 如果集合a1,am,FOLLOW(B1),FOLLOW(Bn)两两不相交(包括不得有两个FOLLOW集合有#),则: 1. 若a是某个ai,i=1,2,m,则移进; 2. 若aFOLLOW(Bi),i=1,2,n,则用产生式Bi进行归约; 3. 此外,报错。 冲突性动作的这种解决办法叫做SLR(1)解决办法。,SLR 分析的特点,SLR 的 DFA 与 LR(0) 完全相同! 不同在于构造分析表的方法不同对于 LR(0) 存在冲突的状态,SLR 可以通过计算 Follow 集来判断移进还是规约,以及用哪条产生式规约,SLR(1) 与 LR(0),LR(0)只看分析栈的内容,不考虑当
3、前输入符SLR(1)考虑输入符,用 Follow 集来解决冲突,因此SLR(1)要比LR(0)分析能力强,构造SLR(1)分析表方法: 首先把G拓广为G,对G构造LR(0)项目集规范族C和活前缀识别自动机的状态转换函数GO. 然后使用G和GO,按下面的算法构造SLR分析表: 令每个项目集Ik的下标k作为分析器的状态,包含项目SS的集合Ik的下标k为分析器的初态。,分析表的ACTION和GOTO子表构造方法: 1. 若项目Aa属于Ik且GO(Ik,a)=Ij,a为终结符,则置ACTIONk,a为 “sj”; 2. 若项目A属于Ik,那么,对任何终结符a,aFOLLOW(A),置ACTIONk,a
4、为 “rj”;其中,假定A为文法G的第j个产生式; 3. 若项目SS属于Ik,则置ACTIONk,#为“acc”; 4. 若GO(Ik,A)Ij,A为非终结符,则置GOTOk,A=j; 5. 分析表中凡不能用规则1至4填入信息的空白格均置上“出错标志”。,按上述方法构造出的ACTION与GOTO表如果不含多重入口,则称该文法为SLR(1)文法。 使用SLR表的分析器叫做一个SLR分析器。 每个SLR(1)文法都是无二义的。但也存在许多无二义文法不是SLR(1)的.,例5.11 考察下面的拓广文法: (0) SE (1) EE+T (2) ET (3) TT*F (4) TF (5) F(E)
5、(6) Fi,这个文法的LR(0)项目集规范族为:,I0: SE EE+TET TT*FTT*FTFF (E) Fi,I1: SE EE+T,I2: ET TT*F,I3: TF,I4: F(E) EE+TETTT*FTFF (E)Fi,I5 : Fi,I6: EE+TTT*FTFF(E)Fi,I7: TT*FF(E)Fi,I8: F(E)EE+T,I9: EE+T TT*F,I10: TT*F,I11: F(E),I1、I2和I9都含有“移进归约”冲突。 FOLLOW(E)#, ), + FOLLOW(S)=# FOLLOW(T)=*,#,),+,I2: ET TT*F,I1: SE EE+
6、T,I9: EE+T TT*F,其分析表如下:,非SLR文法示例:考虑如下文法: (0) SS (1) SL=R (2) SR (3) L*R (4) Li (5) RL,计算FOLLOW集合所得到的超前符号集合可能大于实际能出现的超前符号集。,这个文法的LR(0)项目集规范族为:,I0:SS SL=RSRS*RLiRL,I1:SS,I2:SL=RRL,I3:SR,I4:L*RRLL*RLi,I5:Li,I6:SL=RRLL*RLi,I7:L*R,I9:SL=R,I8:RL,I2含有“移进归约”冲突。 FOLLOW(R)#, =,,I2:SL=RRL,如下文法: (0) SS (1) SL=R
7、 (2) SR (3) L*R (4) Li (5) RL 当状态2显现于栈顶而且面临输入符号为=时,实际上不能用对栈顶L进行归约。,不含“ R= ”为前缀的规范句型 有含“ *R= ”为前缀的规范句型,I2:SL=RRL,SLR在方法中,如果项目集Ii含项目A.而且下一输入符号aFOLLOW(A),则状态i面临a时,可选用“用A归约”动作。 但在有些情况下,当状态i显现于栈顶时,栈里的活前缀未必允许把归约为A,因为可能根本就不存在一个形如“Aa”的规范句型。因此,在这种情况下,用“ A ”归约不一定合适。 FOLLOW集合提供的信息太泛!,LR(k) 分析法,Donald E. KnuthL
8、R(k)分析器(第6章)和属性文法(14章) 的发明者最强大的无回溯自底向上分析器,LR(1) 分析法,LR(0) 方法不依赖输入流,直接判定归约,容易出现冲突。SLR(1) 方法简单的把非终结符的Follow 集做为可归约的依据,并不精确。,SLR(1) 文法的局限性,判断是否能用A-将#a归约成#Aa 前提条件不仅仅是要求a Follow(A) 还必须要求 Aa 是某规范句型的前缀。,LR(1) 项目,在之前的方法LR(0) 或SLR(1) 中,只要某状态中含有归约项目A,那么当栈顶出现串时,我们就能用A进行归约 现在我们想使每个状态含有较多的“展望”信息,这将有助于克服动作冲突和无效归约
9、,5.3.4 规范LR分析表的构造,我们需要重新定义项目,使得每个项目都附带有k个终结符。每个项目的一般形式是A, a1a2ak ,这样的一个项目称为一个LR(k)项目。项目中的 a1a2ak 称为它的向前搜索符串(或展望串)。 向前搜索符串仅对归约项目A,a1a2ak有意义。对于任何移进或待约项目A, a1a2ak, ,搜索符串 a1a2ak 没有作用。,归约项目A, a1a2ak意味着:当它所属的状态呈现在栈顶且后续的k个输入符号为 a1a2ak 时,才可以把栈顶上的归约为A。 我们只对k1的情形感兴趣,向前搜索(展望)一个符号就多半可以确定“移进”或“归约”。,LR(1) 项目,在原来的
10、每个LR(0)项目 A 中放置一向前搜索符号 a: A , a, 称为LR(1)项目。 要求每个LR(1)项目对相应的活前缀是有效的,以保证每一步分析都能得到规范句型的活前缀。,LR(1) 项目,一个 LR(1)项目定义为: A , a, a VT, 称为超前搜索符。 当 时,a对分析的进行无作用 当 = 时,a 明确指出当项目A- , a是栈顶状态的一个项目时,仅在当前输入符为 a 时进行归约。,回顾我们在SLR(1)中碰到的问题,S V = E S E V E V id E V,I0 : S S S V = E S E V E V id E V,I2 : S V = E E V ,V,第一
11、项目使得action2, = 为 s6,第二项目使得 action2, = 为按 EV 归约,因为=属于E的Follow集,=是E的一个后继符:S V = E E = E,回顾我们在SLR(1)中碰到的问题,S V = E S E V E V id E V,I0 : S S S V = E S E V E V id E V,I2 : S V = E E V ,V,当我们在I2如果用E V 进行规约, 我们得到的句型就会是 E = 但是文法中不存在以E=开始的右句型,有效项目,LR(1) 项目 A , a 对活前缀 有效:如果存在着推导S *rm Aw rm w,其中: = ; a是w的第一个符
12、号,或者w是且a是#,有效项目,例 S BB B bB | a从最右推导S * bbBba bbbBba看出:BbB, b 对活前缀 = bbb 是有效的有效项目避免产生无效规约,因为我们根据有效项目进行的规约得到的是规范句型的前缀,我们可以通过闭包运算求出对某个活前缀有效的所有项目(项目集),为构造有效的LR(1)项目集族我们需要两个函数CLOSURE和GO。,LR(1)项目的闭包运算,设I是一个LR(1)项目集, Closure(I)定义为: I的任何项目 Closure(I); 若项目AB,a Closure(I), B是一个产生式,则对于任何终结符 bFirst(a), B, b Cl
13、osure(I); 重复步骤2,直至 Closure(I) 不再增大,AB, a对活前缀是有效的,则对于每个形如B的产生式, 对任何bFIRST(a),B, b对也是有效的。 证明:若项目AB, a对有效,则有规范推导,令I是一个项目集,X是一个文法符号,函数GO(I,X)定义为: GO(I,X)CLOSURE(J) 其中J任何形如AX, a的项目 | AX, aI,状态转移函数 Go,文法G的LR(1)项目集族C的构造算法:BEGINC:=CLOSURE(SS,#);REPEATFOR C中每个项目集I和G的每个符号X DOIF GO(I,X)非空且不属于C,THEN 把GO(I,X)加入C
14、中UNTIL C不再增大 END,例子 S BB B bB | a,S S, # I0 S BB, # B bB, b/a B a, b/a,例子 S BB B bB | a,S S, # I0 S BB, # B bB, b/a B a, b/a,S S, # I1,S BB, # B bB, # B a, # I2,S,B,B bB, b/a B bB, b/a B a, b/a I3,B a, b/a I4,a,b,例子 S BB B bB | a,构造LR(1)分析表的算法。 令每个Ik的下标k为分析表的状态,令含有SS, #的Ik的k为分析器的初态。,动作ACTION和状态转换GOT
15、O构造如下: 1. 若项目Aa, b属于Ik且GO(Ik, a)Ij, a为终结符,则置ACTIONk, a为 “sj”。 2. 若项目A,a属于Ik,则置ACTIONk, a为 “rj”;其中假定A为文法G的第j个产生式。 3. 若项目SS, #属于Ik,则置ACTIONk, #为 “acc”。 4. 若GO(Ik,A)Ij,则置GOTOk, A=j。 5. 分析表中凡不能用规则1至4填入信息的空白栏均填上“出错标志”。,按上述算法构造的分析表,若不存在多重定义的入口(即,动作冲突)的情形,则称它是文法G的一张规范的LR(1)分析表。 使用这种分析表的分析器叫做一个规范的LR分析器。 具有规
16、范的LR(1)分析表的文法称为一个LR(1)文法。 LR(1)状态比SLR多, LR(0)SLR LR(1) 无二义文法,例5.13 (5.10)的拓广文法G( S) (0) SS (1) SBB (2) BaB (3) Bb,LR(1)的项目集C和函数GO,I0: SS, #SBB, #BaB, a/bB b, a/b,I1: SS , #,I2: SB B, #BaB, #B b, #,I3: BaB, a/bBaB, a/bB b, a/b,I4: B b , a/b,I5: SBB, #,I6: BaB, #BaB, #B b, #,I7: B b , #,I8: B aB, a/b,
17、I9: B aB, #,I0: SS, #SBB, #BaB, a BaB, bB b, aB b, b,LR(1)分析表为:,例: 按上表对aabab进行分析 步骤 状态 符号 输入串0 0 # aabab#1 03 #a abab#2 033 #aa bab#3 0334 #aab ab#4 0338 #aaB ab#5 038 #aB ab#6 02 #B ab#7 026 #Ba b#8 0267 #Baa #9 0269 #BaB #10 025 #BB #11 01 #S # acc,例: 按上表对abab进行分析 步骤 状态 符号 输入串0 0 # abab#1 03 #a ba
18、b#2 034 #ab ab#3 038 #aB ab#4 02 #B ab#5 026 #Ba b#6 0267 #Bab #7 0269 #BaB #8 025 #BB #9 01 #S # acc,LALR(1) 分析,LR(1) 的超前搜索符解决了 SLR(1) 分析中所不能解决的冲突,使 LR(1) 分析比 SLR(1) 分析的能力有明显的提高 绝大多数的编程语言语法分析,都能用LR(1)完成,S V = E S E V E V id E V,I0 : S S, # S V = E, # S E, # V E, =/# V id, =/# E V, #,I2 : S V = E, #
19、 E V , #,V,回到前面的例子 用LR(1)解决SLR中的冲突,在I2时,只有当超前搜索符是#时, 我们才能用第二个项目进行规约,LR(1) 分析的缺点,从前面的介绍可知,每个LR(1)项目均由两部分组成 一是 LR(0)项目, 称为LR(1)项目的核心;一是超前搜索符号 对于移进项目, 搜索符集无作用; 对于归约项目,它指明了在扫描到不同的符号时用不同的产生式进行归约,LR(1) 分析的缺点,由于带上了不同的超前搜索符,往往一个LR(0)项目对应多个LR(1)的项目,它们只有超前搜索符不同因此,LR(1)的项目集数目比起LR(0)的项目集数目要大大增加,LR(1) 分析的缺点,缺点:分
20、析表状态数过大,使分析的效率降低像早期的Algol语言的 SLR(1) 分析表只要几百个状态,而其 LR(1) 分析表却要几千个状态,LALR(1) 分析,为解决二者之间的能力与效率之矛盾, LALR(1) 分析是最佳方案。LALR(1)方法的分析能力介于 LR(1) 与 SLR(1)之间,是目前最流行的分析技术。,LALR(1) 分析,LALR(1) 分析 (LookAhead-LR,即超前之意) 是由 F.DeRemer 提出的。 其基本思想就是将LR(1)项目集中的同心集合并,将其压缩为较小的 DFA,若压缩过程并未带来新的冲突,则分析表可大大地简化(状态数与SLR(1), LR(0)的
21、DFA相同)。,相关的术语,假设 A, b 是 LR(1) 项目,则称其中的 LR(0) 项目部分 A 为该项目的核心(core),即除去搜索符号的部分。,相关的术语,除去搜索符号串之后,如果两个LR(1)项目集是完全相同的,则称它们为同心集合并同心集就是将其中对应的LR(1)项目的向前搜索符号集合并到一起,合并同心集,例:假设在LR(1)状态机中有状态S1和S2: S1 = A , a1 ,B , b1 , S2 = A , a2 ,B , b2 Core(S1)= A , B , Core(S2)= A , B , SameCoreState( S1 )= S 1, S2 Merge(S1
22、, S2) = A, a1, a2 , B, b1 ,b2 ,同心合并可能导致冲突,我们知道,一个LR(1)文法的LR(1)项目集族是不存在冲突的,那么经过合并之后,会不会出现新的冲突呢? 有可能! 并不会导致“移进归约”冲突 但可能导致新的“归约归约”冲突,同心合并可能导致冲突,同心集的合并不会引起新的移进归约冲突,如果存在这种冲突,那么合并后的项目集中有两个项目:A , a B a, bB a, c A , d这样一来就说明了原来的LR(1)项目集中就已有“移进归约”冲突了,显然与我们的假设相矛盾,同心合并可能导致冲突,同心集合并可能导致新的“归约归约”冲突 例如,考虑文法G: (0) S
23、S (1) SaAdbBdaBebAe (2) Ac (3) Bc,同心合并可能导致冲突,对ac有效的项目集,A c , d B c , e,对bc有效的项目集,A c , e B c , d,合并同心集后,A c , d/e B c , d/e,当面临d,e时,用Ac还是Bc来归约?,该文法是LR(1)的, 但不是LALR(1)的!,LALR 分析表的构造,基本思想:先构造LR(1)项目集规范族,再合并同心集。要求: LR(1) 项目集族不存在冲突; 合并后的集族不含“归约归约”冲突。,例 :S BB,B bB | a,S S, # I0 S BB, # B bB, b/a B a, b/a
24、,S S, # I1,S BB, # B bB, # B a, # I2,S,B,B bB, b/a/# B bB, b/a/# B a, b/a/# I36,B a, b/a/# I47,a,a,b,b,S BB, # I5,B bB, b/a/# I89,B,b,B,a,例 :S BB,B bB | a,遇到错误时,对于正确的输入串,LR 和 LALR 分析器工作基本一致 但有错误时,LALR 可能比 LR 多做些不必要的规约,但是不会移进更多的符号 这就保证了能准确地指出错误的位置,这一点与 LR(1) 是等效的,实用性的体现,对于常见的编程语言文法,LR(1) 常常有高达几万个状态LA
25、LR(1) 通过合并同心集,大大减少了状态数,提高了编译器的运行效率,减小了运行时的存储空间,二义文法的应用,我们知道二义文法产生的分析表都含有多重入口,因此给我们的分析带来很多麻烦。 人们已经证明:任何二义文法都不是LR文法,因而也决不是SLR或LALR文法。,二义文法的应用,但是二义文法也不是一无是处,某些二义文法还是非常有用的。 例如:G1: EE+EE*E(E)i G2: EE+TTTT*FF F(E)i,二义文法的应用,G1与G2相比,有两个明显的好处 如果要改变算符的优先关系或结合规则,G1不需做任何变动; G1的分析表状态数比G2少,因为后者的单非产生式(T-F)要占用不少状态;
26、,二义文法的应用,如何使用 LR 分析法的基本思想,附加一些条件,来分析二义文法所定义的语言以文法 G1 为例来说明问题。它的LR(0) 项目集规范族如下图所示:,I0:EEEE+EEE*EE(E)Ei,I1:EEEE+EEE*E,I4:EE+EEE+EEE*EE(E)Ei,I3:Ei,I2:E(E)EE+EEE*EE(E)Ei,I7:EE+EEE+EEE*E,I5:EE*EEE+EEE*EE(E)Ei,I8:EE*EEE+EEE*E,I6:E(E)EE+EEE*E,I9:E(E),E,+,i,(,(,i,E,*,i,E,+,E,*,i,(,*,),+,*,+,二义文法的应用,从图中可以看到,
27、在状态 I1 时,EE 要求接受,而EE +E, EE*E要求移进 这种冲突可以使用 SLR方法 予以解决查看FOLLOW(E)。,I1: EEEE+EEE*E,二义文法的应用,再看状态I7EE+E要求规约,而 EE + E, EE * E 要求移进 + 或*这个冲突却不是SLR方法能够解决的。 ( ? ),I7: EE+EEE+EEE*E,拓广文法G: (0) EE (1) EE+E (2) EE*E (3) E(E) (4) Ei,考虑输入串i+i*i,在处理了i+i之后,分析器进入状态I7,这时分析栈的内容为0E1+4E7,剩余输入串为*i#。 此时就产生了问题:到底是执行r1还是s5呢
28、?,二义性的解决,这种冲突只有借助于其他条件才能得到解决,也就是要使用算符优先级和结合规则的有关信息 规定:* 优先级高于 +,两者都是左结合 根据此约定消除分析表中的冲突项,改造分析表,面临+,归约 面临,移进 面临 ) 和#,归约例如:栈中 输入id + id + id 规约id + id id 移进,I7: EE+EEE+EEE*E,改造分析表,面临+,归约 面临* ,归约 面临 ) 和#,归约例如:栈中 输入id * id + id 规约id * id id 规约,I8: EE*EEE +EEE *E,if-then-else,Stmt if Expr then Stmt Stmt i
29、f Expr then Stmt else Stmt 语义上规定else要和最近的then匹配 if A then if B then C else D 我们可以通过强制规定,碰到移进归约冲突则移进,就能满足语义要求,几点结论,(1)四种LR类文法之间的关系LR(0) SLR(1) LALR(1) LR(1)对于给定的文法G,可以通过如下图所示的算法判断G属于哪类LR文法:,四种LR文法的判断,几点结论,(2)任何LR(k)或LL(k)文法都是无二义性(3)任何二义性的文法都不可能是LR(k)或LL(k)文法,但可借助于其它因素,如算符的优先级和结合规则来构造无冲突的分析表,LR(0):局限性大,但其构造方法是其他构造方法的基础; SLR:虽然不是对所有文法都存在,但这种分析表较易实现又较有使用价值; LR(1):分析能力最强,能适用于一大类文法,但是,实现代价过高(表过大); LALR:能力介于SLR和LR之间,实现效率较高,最适用。,LR(0)、SLR(1)、LR(1)、LALR(1)分析表比较,作业,P1331,2,3 P1345 (1),(2),(3), (4) P1348(选作),