380 likes | 484 Views
4.2.4 LR 分析法. 迄今为止我们所学的分析方法对文法都有一定的要求 , 即有局限性 ; 1965 年 D.Knuth 提出了分析效率极高的 LR(k) 分析技术; LR 分析 : 自左至右扫描的自底向上的分析; 在分析的每一步,只须根据分析栈中的已移进的和已归约出的符号,并至多向前扫描 k 个字符就能确定应采取什么分析动作( 移进、归约、接受、报错 ); LR 分析对文法要求很少 , 效率极高 , 且能及时发现错误 , 是目前最广泛使用的方法 ; 一般用 CFG 描述的语言均可用 LR 分析法. LR 分析综述. 计算机理论研究已证明了如下结论 :
E N D
4.2.4 LR分析法 • 迄今为止我们所学的分析方法对文法都有一定的要求,即有局限性; • 1965年D.Knuth提出了分析效率极高的LR(k)分析技术; • LR分析: 自左至右扫描的自底向上的分析; • 在分析的每一步,只须根据分析栈中的已移进的和已归约出的符号,并至多向前扫描k个字符就能确定应采取什么分析动作(移进、归约、接受、报错); • LR分析对文法要求很少,效率极高,且能及时发现错误,是目前最广泛使用的方法;一般用CFG描述的语言均可用LR分析法
LR分析综述 • 计算机理论研究已证明了如下结论: • LR(k)文法是无二义性文法; • LR(k)文法与LR(1)文法等价. • 由于常见的程序设计语言均能由LR(1)文法产生,因此我们只讨论k=0,1两种情况; • 本节中,我们将先介绍LR分析器的逻辑结构及工作原理,再分别介绍几种LR分析器(即LR(0),SLR(1),LR(1)和LALR(1))的构造; • LR(0)简单,能力低; SLR(1)能力强于LR(0); LR(1)能力强,但分析表大; • LALR(1)能力介于SLR(1)与LR(1)之间,表大小与SLR(1)相同,是最常用的分析方法
LR分析器的逻辑结构及工作原理 • 分析器自左至右地扫描输入串的各符号,并根据当前分析栈的内容及正扫描的符号按分析表的指示完成相应的动作。 • 分析栈记录了已分析的内容及当前的状态; • 开始时,栈内放入#及开始状态S0,随着分析的深入,栈内容总是刻划了当前的状态,及分析的“历史”。 a1 a2 … ai …an# 分 析 栈 总控程序 分析表 Sm Xm … S1 X1 S0 # 分析栈的内容
LR分析表 • LR分析表是LR分析器的核心,它由分析动作(ACTION)表和状态转移(GOTO)表两个子表组成; • ACTION[Sm,ai]指明当栈顶为Sm,输入符为ai时应完成的分析动作; • GOTO[Sm,Xi]指明当分析栈移进一个输入符号或按某产生式进行归约后所要转移到的下一状态. ACTION表 GOTO表
LR分析器的工作过程 S0S1S2…Sm # X1X2…Xm aiai+1ai+2…an# 1.分析开始时,首先将初始状态S0及#压入栈; 2.设在分析的某一步,分析栈和余留输入串处于格局: 用Sm,ai查ACTION表,并根据指示完成相应的动作,分析动作有移进,归约,报错,接受四种: (1)移进 句柄尚未在栈顶形成,正期待继续移进输入符号以形成句柄,故将ai压入栈: S0S1S2…Sm # X1X2…Xmai ai+1ai+2…an# 再用Sm,ai查GOTO表,设得Sm+1,将Sm+1压入栈: S0S1S2…SmSm+1 # X1X2…Xmai ai+1ai+2…an#
(2)归约rj其中rj表示按P的第j产生式AXm-r+1Xm-r+2Xm进行归约;这表明栈顶部的符号串Xm-r+1Xm-r+2Xm是当前句型的句柄.归约的方法是,将栈顶符号串Xm-r+1Xm-r+2Xm(r个符号)从栈顶退出,再将A压入栈,此时的格局为(2)归约rj其中rj表示按P的第j产生式AXm-r+1Xm-r+2Xm进行归约;这表明栈顶部的符号串Xm-r+1Xm-r+2Xm是当前句型的句柄.归约的方法是,将栈顶符号串Xm-r+1Xm-r+2Xm(r个符号)从栈顶退出,再将A压入栈,此时的格局为 S0S1S2…Sm-r # X1X2…Xm-rA aiai+1ai+2…an# 再以(Sm-r,A)查GOTO表,得Sk,将Sk压入栈,得到格局: S0S1S2…Sm-rSk # X1X2…Xm-rA aiai+1ai+2…an# 注意,完成归约动作时,输入串指针并未移动. (3)若ACTION(Sm,ai)=“acc”,则表明当前输入串已被成功地分析完毕,结束; (4)若ACTION(Sm,ai)=“ERROR”,则表明当前输入串有语法错误,转入出错处理程序; 3. 重复步骤2.的工作,直到分析的某步,栈顶出现“acc”为止.此时的格局应为: S0SZ # # Z 其中,Z为文法 的开始符号, SZ是使ACTION(SZ,#)=“acc”的唯一状态. LR分析器的工作过程(续)
a a b b , , # # L E 0 0 3 S S 4 1 2 1 1 ac 2 2 S 5 r2 3 3 r3 r3 4 4 r4 r4 5 5 3 S 4 S 6 2 6 6 r1 LR分析实例 • 设已给文法G[L]: 1. LE,L 2. LE 3. Ea 4. Eb • 文法相应的分析表为 ACTION表 GOTO表
状态 ACTION GOTO a b , # L E 0 s3 s4 1 2 1 ac 2 s5 r2 3 r3 r3 4 r4 r4 5 s3 s4 6 2 6 r1 分析表的合并 • 从分析表的功能及实例中,我们可看出,GOTO表中所有关于终结符的状态转换都与相应的ACTION表相关,因此我们可将其合并到ACTION表中,只将关于VN符的状态转换保留在GOTO表中: 在实际的分析表中,大多数表项内容为ERROR,若略去不填,则分析表是一稀疏表,可采用紧凑的格式存储。
步骤 状态 栈中符号 余留符号 分析动作 下一状态 1 0 # a,b,a# s3 3 2 03 #a ,b,a# r3 GOTO[0,E]=2 3 02 #E ,b,a# s5 5 4 025 #E, b,a# s4 4 5 0254 #E,b ,a# r4 GOTO[5,E]=2 6 0252 #E,E ,a# s5 5 7 02525 #E,E, a# s3 3 8 025253 #E,E,a # r3 GOTO[5,E]=2 9 025252 #E,E,E # r2 GOTO[5,L]=6 10 025256 #E,E,L # r1 GOTO[5,L]=6 11 0256 #E,L # r1 GOTO[0,L]=1 12 01 #L # 符号串“a,b,a”的LR分析过程
LR分析器的总控程序 • parser( ){ • 初始化; • while((item=ACTION[TopStat][InpSym]))!=Acc){ • if(item==ERROR) error( ); • if(item==sj){push(j);advance( );} • elsereduce(i);/* item== ri*/ • } • accept( ); • }
二、LR(0)分析表的构造 • LR(0)分析就是k=0时的LR(k)分析.即在分析的每一步,根据当前的栈顶状态确定下一步动作. • 为了给出构造LR(0)分析表的算法,我们首先引入一些重要的概念和术语 • 规范句型的活前缀(viable prefix) 从前面例子的分析过程可知,将栈内符号与未扫描的输入串拼接起来,可得一规范句型.即栈内符号串总是规范句型的前缀,且不含句柄右侧的符号. 原因:句柄一旦在栈顶形成,就不再移进新符号,而是要进行归约了. 我们把具有上述性质的符号串称为规范句型的活前缀. 规范句型的活前缀有两个要点: (1)它是规范句型的前缀; (2)它不含句柄右侧符号
规范句型的活前缀(续) • 在前面的例子中的第五行: #E,b ,a#中,b是当前句型的句柄, “”, “E” , ”E ,” , ”E , b”都是~. • LR分析过程实际上就是一个逐步产生(识别)~的过程. • 在分析的每一步,栈内符号总是~,且与此时栈顶符号相关. • 提示: 若能构造一个识别所有活前缀的自动机,则构造分析表就不难了.
2. LR(0)项目集 • 由~不含句柄右侧符号这一性质可知,~与当前句柄的关系只能是下述三种情况之一: • 句柄全部在~中(句柄是~的后缀); • ~只含句柄的部分符号(~的后缀是句柄的前缀); • ~不含任何句柄符号. • 对于(1), 应进行归约: A, 记为A; • 对于(2),应移进(句柄的后半部分),A12; • 对于(3),期望移进一产生式的右部: A • 我们把右部添加了一个“”的产生式,称为LR(0)项目
LR(0)项目集(续) • 同一产生式(设右部有n个符号)对应了若干(n+1)个LR(0)项目,每个项目反映了栈顶所处的不同状态. • 若A∈P,则对应了唯一的LR(0)项目A→; • 所有的LR(0)项目是构造识别活前缀的自动机的基础。 • 例 SA | B AaAb | c BaBb |c为识别方便,我们引入新开始符S’及产生式S’ S,得到拓广的方法G’. ⒈s’ S ⒉ S’S ⒊S A ⒋S A ⒌A aAb ⒍ A aAb ⒎ A aAb ⒏ A aAb ⒐A c ⒑A c ⒒S B ⒓S B ⒔ BaBb ⒕ B aBb ⒖ B aBb ⒗ B aBb ⒘B d ⒙ B d • 上面的LR(0)项目可用一对整数(m,n)表示,其中,m表示第m产生式,n表示圆点在该产生式的位置; • 如项目1.可用(0,0)表示,项目14.可用(5,1)表示等等.
LR(0)项目集(续) • 由于不同的项目反映了分析过程的不同情况,因此,我们可根据其不同作用将其分类. • 对于形如A→的项目,此时应进行归约,因此称为归约项目;如前例中的2,4,8,10,12,16,18(蓝色项目)等; • 对于形如AX, ( XVT,可以是空串)的项目,则有待于移进一个VT符号X到栈中,因此称为移进项目,如前例中的5,7,9,13,15,17(红色项目)等; • 对于形如AX, ( XVN, 可以是空串)我们期待移进若干符号之后并将其归约为X,因此称为待约项目,如前例中的1,3,6,14(绿色项目)等.
3.识别所有规范句型全部活前缀的DFA • 该DFA的每个状态由若干LR(0)项目组成的集合表示; • 首先,初态I0含有项目S’→●S,期待将要扫描一个符号串恰好匹配S.由于S是VN符,不可能从输入串中读出,因此应把可能构成S的产生式相应的的项目S→●A及S→●B列入项目集中; • 同理,由A,B为VN符,应将A→●aAb, A→●c, B→●aBb, B→●d放入项目集中.因此,I0由如下项目组成: S’→●S, S→●A, S→●B, A→●aAb, A→●c, B→●aBb, B→●d • 其中, S’→●S称为基本项目.从S’→●S出发构造整个项目集的过程为求基本项目的闭包过程,即整个项目集称为基本项目集的闭包CLOSURE({S’→●S}).
求项目集闭包的算法 • 设I为一项目集,构造I 的项目集闭包CLOSURE(I)的算法如下: 1.I CLOSURE( I ); 2. 若A→XCLOSURE(I), XVN, 则任何X-产生式 X→P, XCLOSURE(I); 3.重复上述过程,直到CLOSURE(I)不再增大.
构造DFA的方法 • 有了初态I0,为求下一状态的方法是: • 设I为一状态,X∈V,且A → X I,则当分析器识别出X后,将进入下一状态Ii且必含有项目A →X (称为项目A → X 的后继),对于整个项目集而言,关于X的后继可能有多个,将其合并在一起构成集合J (即下一状态Ii的基本项目),再通过求闭包得出Ii全部项目: Ii=CLOSURE( J ).为指明Ii是I的后继,记GO(I,X)= Ii; • 例如,对于上例,我们有: I1=GO(I0,S)=CLOSURE({S’ →S·})={S’ →S·}; I2=GO(I0,A)=CLOSURE({S→A·})={S→A·}; I3=GO(I0,B)=CLOSURE({S→B·})={S→B·}; I4=GO(I0,a)=CLOSURE({A→a·Ab,B→a·Bb})={A→a·Ab,B→a·Bb, A→· aAb, B→ · aBb, A→·c,B→·d}; … (参见P157)
构造DFA的方法(续) • 重复上述构造新状态的过程,我们可得到全部的状态,其集合称为方法G的LR(0)项目集规范族,记为C(上例中,C={I0,I1,…,I10}); • 至此,我们所要构造的识别方法G[S]全部活前缀的DFA为: M=(C,V,GO,I0,C);其中, • 状态集及终态集为项目集规范族C; • 字母表为V=VN∪VT∪{S’} • 初态为I0; • 转换函数为GO;
S I1: S’→S· I0: S’→·S S→·A S→·B A→·aAb A→·c B→·aBb B→·d A I8: A→aAb· I2: S→A· B b I3: S→B· A a I7: A→aA·b I4: A→a·Ab A→·aAb A→·c B→a·Bb B→·aBb B→·d B I9: B→aB·b d c c I5: A→c· b d I6: A→d· I10: B→aBb· a 图4-16识别G[S]全部活前缀的DFA
LR(0)分析表的构造 • 有了识别文法G的全部活前缀的DFA,就可构造相应的LR(0)分析表. • 应指出,每个项目集代表了分析过程中的一个状态,且其每个项目与分析动作相关.因此要求每个项目集的诸项目是相容的,即在同一项目集中,不应出现: 1.移进项目与归约项目并存; 2.多个归约项目并存. • 若文法G满足上述条件,即不含上述冲突项目,则称G为LR(0)文法. • 显然,只有当一文法是LR(0)文法时,才能构造出无冲突动作的分析表来.
构造LR(0)分析表的算法 我们用序号i表示状态Ii,则填写LR(0)分析表的方法如下: (1)对于每个项目集Ii中形如A→X项目,若GO(Ii,X)= Ij,则,若X∈VT,则置ACTION[i,X]=sj,否则(X∈VN),置GOTO[i,X]=j; (2)若归约项目A→属于Ii,且A→是P中第j产生式,则对于aVT{#},置ACTION[i,a]=rj (3)若接受项目S’ →S·属于Ii,则置ACTION[i,#]=“acc”; (4)在表中其它未填入信息的栏目中均填“ERR”. 在存储ACTION表时,可用正负值分别表示归约和移进.
ACTION GOTO a b c d # S A B 0 s4 s5 s6 1 2 3 1 acc 2 r1 r1 r1 r1 r1 3 r2 r2 r2 r2 r2 4 s4 s5 s6 7 9 5 r4 r4 r4 r4 r4 6 r6 r6 r6 r6 r6 7 s8 8 r3 r3 r3 r3 r3 9 s10 10 r5 r5 r5 r5 r5 表4-13G[S]的LR(0)分析表
三、SLR(1)分析表的构造 • 须指出,常见程序设计语言都不是LR(0)的,所以LR(0)分析表缺乏实用性.例如,典型的分程序结构: B’ →B B →bD;Se D →D;d|d S →s;S |s • 识别上述文法活前缀的DFA及相应的LR(0)分析表见教材中P160.在状态I8中,出现了“移进-归约”冲突.另外,甚至常见的文法G[E]也不是LR(0)文法. • 实际上,常见的程序设计语言相应的分析表冲突项目是很少的,对分析表做一定的修改,便可消除“移进-归约”或“归约-归约”冲突.
SLR(1)分析表的构造(续) • 考虑LR(0)分析表的造表算法规则(2),对于归约项目A→·,不管下一输入符号是谁,均进行归约.这显然是一个武断的决定. • 若Ii中同时含有B→·b及C→·两类项目时,上述填表方法必然得到冲突的分析表. • 一般地,Ii={A1→·a11,…,Am→·amm,B1→·,…,Bn→·} • 如果能根据下一输入符号a对上述冲突加以区分,则冲突可解决. • 当集合FOLLOW(Bk)(1≦k≦n)与{a1,a2,…,am}两两互不相交时,则可按下述方法解决冲突: • aVT{#}, IFa{a1,a2,…,am}THENACTION[i,a]=sj ELSEIFaFOLLOW(Bj)THENACTION[i,a]=rBj ELSEACTION[i,a]=“ERR”
SLR(1)分析表的构造(续) • 上述方法就是SLR(1)规则(Simple LR(1)).按照SLR(1)规则,只须将LR(0)分析表的填表规则(2)修改为(注:其它规则不变!): (2‘)若归约项目A→属于Ii,且A→是P中第j产生式,则对于aFOLLOW(A),置ACTION[i,a]=rj • 对于给定的文法G,若其相应的SLR(1)分析表无冲突项,则称G是SLR(1)文法. • 例 分程序文法G[B]中,I8={Ss;S, Ss}含有冲突,但FOLLOW(S)={e} ≠{;},故冲突可解决. • G[B]的SLR(1)分析表见P161表4-15.这里略去.
四、LR(1)分析表的构造 • SLR(1)分析表简单实用,但并不能完全解决问题; • 例如,文法 S’→S S→CbBA A→Aab|ab B→C|Db C→a D→a相应的DFA见书中P162图4-18.其中项目集 • I10={S→CbBA·,A→A·ab}中存在“移进-归约”冲突,由FOLLOW(S)={#} ≠{a}, 冲突是可解决的.但项目集I8={C→a·,D→a·}中, FOLLOW(C)={a,b}, FOLLOW(D)={b}, SLR(1)方法不能解决此冲突. • 从SLR(1)解决问题的方法看,对于归约项目A→,只要是FOLLOW(A)中的符号均可按此产生式进行归约,这也有一定的片面性,因为没有考虑所在的“环境”. • 当在栈顶形成时(设此时栈内容为#,输入符为a),若强行将归约为A(栈内容: #A),但#Aa又不是任何规范句型的前缀时,这个归约就是无效的.
例如,对于上述文法的规范句型Cbabab,当分析达到格局例如,对于上述文法的规范句型Cbabab,当分析达到格局 I0I2I4I8 bab # C b a 时,由于输入符b∈FOLLOW(C), 我们若用C来归约,得到 I0I2I4I6 bab # C b C 但CbC不是任何规范前缀!因此在移进b之前,分析将报错. 因此,将#a归约成#Aa…的前提条件不仅仅是要求a∈FOLLOW(A),还必须要求Aa是某规范句型的前缀. 为了确保上述条件,应在原来的每个LR(0)项目[A]中放置一向前搜索符号a: [A,a],称为LR(1)项目. 为使分析的每一步都能得到规范句型的活前缀,还应要求每个LR(1)项目对相应的活前缀是有效的. LR(1)分析表的构造(续)
对活前缀有效的LR(1)项目 定义LR(1)项目[A,a]对活前缀=有效,iff存在规范推导 S Ay yyVT*且满足条件: (1)当y≠时,a∈FIRST(y); (2)当y=时,a=#. • 例如,对于上例中文法,有 可知[B→D·b,a] 对前缀= =CbD有效 S CbBACbBabCbDbab Ay 可知[D→a·,b] 对前缀= =Cba有效 SCbDbabCbabab A = y
求LR(1)项目集闭包算法 • 类似于LR(0)分析,识别文法全部活前缀的DFA的状态是由LR(1)项目集表示的.每个项目集由若干对相应的活前缀有效的LR(1)项目组成. • 对每个LR(1)项目集I,相应的闭包CLOSURE(I)的定义为 • ICLOSURE(I); • 设项目[A B , a] CLOSURE(I),并设它对活前缀 =有效,则对所有形如B的产生式,及每个bFIRST(a),项目[B , b]也对有效(证明见后),即将[B , b] =>CLOSURE(I); • 重复(2),直到CLOSURE(I)不再增大.
新增项目对活前缀仍有效的证明 • 事实上, 当[A B,a]对=有效时,由定义,有: SAyByyVT* 且aFIRST(y) {#} 即SAyBaVT*{#} 无论是否为,可知a总能推出终结符号串: ab’ 由aVT*{#}, FIRST(a)=FIRST(a),从而有规范推导: S B b’ b’即[B , b]对活前缀=有效.
GO函数及DFA的构造方法 • 为求GO(I,X), 其中I为LR(1)项目集,X∈V,类似于LR(0)方法,有: GO(I,X)=CLOSURE(J) 其中, J={[ AX , a ] | [ AX , a ] I} • 注意,每个LR(1)项目与其后继项目有相同的搜索符号 • 采用与LR(0)类似的方法,可构造出文法G的LR(1)项目集族C及状态转换图(DFA).
I12: A→ab·, #/a I1: S’ →S ·, # I2: S →C·bBA, # b S I11: A→a·b, #/a C b I0: S’ → ·S, # S→·CbBA, # C → ·a, b a I4:S→Cb·BA, # B→·C, a B →·Db, a C → ·a, a D →·a, b I5:S→CbB·A, # A→·Aab, #/a A →·ab, #/a B a a A I3: C→a ·, b I10: S→CbBA·, # A →A·ab #/a D I8: C→a ·, a D →a ·, b I7: B→D ·b, a a b I13: A→Aa·b, #/a C I6: B→C ·, # I9: B→Db ·, a b I14: A→Aab·, #/a 图4-19文法G[S]的LR(1)项目集及DFA
LR(1)分析表的构造 • 利用LR(1)项目集族C和GO函数构造LR(1)分析表的方法: • 对于每个项目集Ii中的项目[AX , a],若GO(Ii,X)=Ij则,IF X∈VTTHENACTION(i,X)=sjELSE (X∈VN) GOTO(i,X)=j; • 若归约项目[A , a] ∈Ii, A是第j产生式,则 ACTION(i,a)=rj; • 若[S’ →S· , #] ∈Ii ,则ACTION(i , #)=“acc”; • 其它: ERROR. • 对于一文法G而言,若按上述方法所构造的分析表不含多重定义的元素,则称此分析表为LR(1)分析表.凡具有LR(1)分析表的文法称为LR(1)文法
状态 ACTION GOTO a b c S A B C D 0 s3 1 2 1 acc 2 s4 3 r6 4 s8 5 6 7 5 s11 10 6 r4 7 s9 8 r6 r7 9 r5 10 s13 r1 11 s12 12 r3 r3 13 s14 14 r2 r2 分析表实例
五、LALR(1)分析简介 • 从前面的介绍可知,每个LR(1)项目均由两部分组成,一是LR(0)项目,称为LR(1)项目的核;一是向前搜索符号集. • 对于移进项目,搜索符集无作用;对于归约项目,它指明了在扫描到哪些符号时进行归约. • 上述原理解决了SLR(1)分析中所不能解决的一些冲突,使LR(1)分析比SLR(1)分析的能力有明显的提高. • LR(1)也有缺点:分析表状态数过大,使分析的效率降低. • 为解决二者之间的能力与效率之矛盾,目前最流行的LALR(1)分析是最佳方案.
LALR(1)分析简介(续) • LALR(1)分析(Lookahead-LR)是由F.DeRemer提出的.其基本思想就是将LR(1)项目集中的同心集合并,将其压缩为较小的DFA,若压缩过程并未带来新的冲突,则分析表可大大地简化(状态数与SLR(1),LR(0)的DFA相同). • 书中给出了文法G[E]的例子.合并前该文法有22个状态,合并后只有12个. • LALR(1)方法的分析能力介于LR(1)与SLR(1)之间.是目前最流行的分析技术. • 目前已有许多自动构造LALR(1)分析表的工具,最典型的当属YACC.其相关内容将在<编译原理课程设计>中给大家作详细介绍.
关于各类文法的一些结论 • 任何LR(K),LL(K)及简单优先文法类都是无二义性文法; • 任何二义性的文法不可能性是LR(K)文法,但借助其它方法,可对某些二义性文法建立无冲突的LR(K)分析表; • 每个SLR(K)文法必是LR(K)的,存在LR(1)文法,对任何K,它不是SLR(K)的. • 各文法类之间的关系见P175图4-24