880 likes | 1.18k Views
第七章 LR 分析法. 教学目的: 让学生了解 LR 分析方法的基本思想,掌握 LR(0) 、 SLR(1) 、 LR(1) 、 LALR(1) 分析法。. 教学重点 : LR ( 0 )分析、 LR ( l )分析、 SLR ( 1 )分析和 LALR ( 1 )分析;构造 LR 分析的分析表。. 课时分配: 8 学时. 本章知识点 ( 内容 ). LR 分析概述. LR ( 0 )分析. SLR ( 1 )分析. LR ( 1 )分析. LALR ( 1 )分析. 二义文法在 LR 分析中的应用. 7.1 LR(Left-Right) 分析概述.
E N D
第七章 LR分析法 • 教学目的:让学生了解LR分析方法的基本思想,掌握LR(0) 、SLR(1) 、LR(1)、LALR(1) 分析法。 • 教学重点:LR(0)分析、LR(l)分析、SLR(1)分析和LALR(1)分析;构造LR分析的分析表。 • 课时分配:8学时
本章知识点(内容) LR分析概述 LR(0)分析 SLR(1)分析 LR(1)分析 LALR(1)分析 二义文法在LR分析中的应用
7.1 LR(Left-Right)分析概述 算符优先分析法存在的问题 强调算符之间的优先关系的唯一性,这使得它的适应面比较窄;算法在发现最左素短语的尾时,需要返回来寻找对应的最左素短语头。 • LR分析法: • [1] 对文法限制少;[2] 适用范围广;[3] 分析速度快; [4]报错准确。[5] 易于实现自动生成。由于构造分析器的工作量很大,不大可能手工构造;如用软件工具Yacc-Yet Another Compiler Compiler,Bell,这些软件工具叫LR生成器。
一、LR(k)分析法 L :从左到右扫描输入符号, R :最右推导对应的最左归约, k :超前读入k个符号,用以确定归约所用的规则。 LR分析法在自左至右扫描输入串时就能发现其中的任何错误.并能准确地指出出错地点。 大多数用上下文无关文法描述的程序语言都可用LR分析器予以识别。 主要缺点是,用手工构造分析程序则工作量相当大。因此,必须求助于自动产生这种分析程序的产生器。
二、LR分析法分类: LR(0)表构造法。这种方法的局限性极大、但它是建立其它较一般的LR分析法的基础。 SIR表构造法。虽然,有一些文法构造不出SLR分析表,但是,这是一种比较容易实现又极有使用价值的方法。 规范LR表构造法。这种分析表能力最强,能够适用一大类文法,但实现代价过高,或者说,分析表的体积非常大。 向前LR表构造法(简称LAIR)。这种分析表的能力介于SIR和规范LR之间,稍加努力,就可以高效地实现。
规范归约(最左归约一最右推导的逆过程)的关键问题是寻找句柄。 LR方法的基本思想是:在规范归约的过程中,一方面要记住已移进和归约出的整个字符串,也就是说要记住历史;一方面能够根据所用的产生式的推测未来可能碰到的输入符号,也就是说能够对未来进行展望。这样,当一串貌似句柄的字符串出现在分析栈的顶部时,我们希望能够根据历史和展望以及现实的输入符号这三部分的材料,决定出现在栈顶的这一串符号是否就是我们要找的句柄。
三、LR分析器的逻辑结构 一个LR分析器包括两部分:一个总控(驱动)程序和一张分析表。注意:所有LR分析器的总控程序都是一样的,只是分析表各有不同。因此产生器的主要任务就是产生分析表。LR分析器的核心部分是一张分析表。 采用下推自动机这种数据模型。包括以下几个部分:1.输入带。2.分析栈:包括状态栈和文法符号栈两部分。3.LR 分析表:包括动作表和状态转移表两张表。
输入缓冲区 a1 … ai … an # 状态栈 符号栈 LR总控程序 Sm Sm-1 … … … S1 S0 Xm Xm-1 … … … X1 # 输出规则 序列 分析表 动作表 action 转移表 goto
分析表包括两部分: 一部分是(ACTION)表,另一部分是"状态转换"(GOTO)表。它们都是二维数组。 ACTION[S,a]中规定了当状态S面临输入符号a时应采取什么动作。 GOTO[S,X]规定了状态S面对文法符号X(终结符或非终结符)时下一状态是什么。 显然,GOTO[S,X]定义了一个以文法符号为字母表的DFA。 【例】演示
每一项ACTION[S,aJ所规定的动作是下述4种可能之--:每一项ACTION[S,aJ所规定的动作是下述4种可能之--: (1)移进:把(S,a)的下一状态S’ =ACTION[S,a]和输入符号a推进栈(对终结符,GOTO[S,a]的值已放入ACTION[S,a]中),下一个·输入符号变成现行输入符号。 (2)归约:指用某一产生式A进行归约。假若 的长度为 ,归约的动作是去掉栈顶的个项,然后把(Sm- ,A)的下一状态和文法符号A推进栈。归约动作不改变现行输入符号。执行归约的动作意味着呈现于栈顶的符号串是一个相对于A的句柄。 (3)接受:宣布分析成功,停止分析器的工作。 (4)报错:报告发现源程序含有错误,调用出错处理程序。
对于--个文法,如果能构造一张分析表,使得它的每个入口均是唯一确定的,则把这个文法称为LR文法。对于一个LR文法,当分析器对输入串进行自左至右扫描时,一旦句柄呈现于栈顶,就能及时对它实行归约。对于--个文法,如果能构造一张分析表,使得它的每个入口均是唯一确定的,则把这个文法称为LR文法。对于一个LR文法,当分析器对输入串进行自左至右扫描时,一旦句柄呈现于栈顶,就能及时对它实行归约。 一个LR分析器有时需要“展望”和实际检查未来的k个输入符号才能决定应采取什么样的“移进一归约”决策。一般而言,一个文法如果能用一个每步顶多向前检查K个输入符号的LR分析器进行分析,则这个文法就称为LR(k)文法。 对于一个文法,如果它的任何"移进一归约"分析器都存在尽管栈的内容和符号都已了解,但无法确定是“移进”还是“归约”;或者,无法从几种可能的规约中确定其一的情形,那么这个文法就是非LR(1)的。
7.2 LR(0)分析法 【例】已知文法G[S],分析符号串abbcde是否是G[S]的句子 。(1) S → aAcBe[1] (2) A → b[2] (3) A → Ab[3] (4) B → d[4]【解】这里每个产生式的右部字符串末尾的编号是为了方便查看在最右推导中是选择哪个产生式推导的,并不是产生式的一部分。 句子的最右推导过程为:S =>aAcBe[1] =>aAcd[4]e[1]=>aAb[3]cd[4]e[1]=>ab[2]b[3]cd[4]e[1]
由于最右推导就是规范推导,因此句型 aAcBe、aAcde、aAbcde、abbcde为规范句型。 规范句型的这种前部分符号串称为可归前缀。 【例如】 符号串aAcBe是规范句型aAcBe的可归前缀, aAcd是规范句型aAcd[4]e[1]的可归前缀, aAb是规范句型aAb[3]cd[4]e[1]的可归前缀, ab是规范句型ab[2]b[3]cd[4]e[1]的可归前缀。 我们把形成可归前缀之前包括可归前缀在内的所有规范句型的前缀都称为活前缀。所谓前缀就是符号串的任意首部。活前缀就是可归前缀的任意首部。
【例如】可归前缀ab的活前缀为ε,a,ab可归前缀aAb的活前缀为ε,a,aA,aAb可归前缀aAcd的活前缀为ε,a,aA,aAc,aAcd可归前缀aAcBe的活前缀为ε,a,aA,aAc,aAcB,aAcBe【例如】可归前缀ab的活前缀为ε,a,ab可归前缀aAb的活前缀为ε,a,aA,aAb可归前缀aAcd的活前缀为ε,a,aA,aAc,aAcd可归前缀aAcBe的活前缀为ε,a,aA,aAc,aAcB,aAcBe
为什么要引进活前缀的概念? 因为规范归约实际上就是规范推导的逆过程。可归前缀就是存放在文法符号栈中的内容,它和输入缓冲器的剩余内容合在一起如果刚好就是规范句型,就能够保证我们的归约是一个规范归约的过程。即栈内符号串总是规范句型的前缀,且不含句柄右侧的符号。原因:句柄一旦在栈顶形成,就不再移进新符号,而是要进行归约了。 我们把具有上述性质的符号串称为规范句型的活前缀。
规范句型活前缀 一、活前缀和可归前缀的形式定义 若S’αAγαβγ,则称αβ为可归前缀; 若有串W是αβ的前缀,则称W是G的一个活前缀(S‘为文法拓广后的开始符,它只出现在规则左部)。可归前缀是包含句柄的活前缀。 二、说明: 规范句型的活前缀有两个要点: (1)它是规范句型的前缀; (2)它不含句柄右侧符号
如何识别文法符号栈中的内容就是活前缀 由于活前缀实际上就是满足一定要求的符号串,因此识别活前缀的工作和识别单词的工作非常类似,所以我们可以采用有穷自动机这种数据模型来实现活前缀的识别。
识别活前缀的有穷自动机的构造 基本思想是我们可以把文法的终结符和非终结符都看成有穷自动机的输入符号,每次把一个符号进栈看成已识别过了该符号,同时状态进行转换,当识别到可归前缀时,相当于在栈中形成句柄,达到了识别句柄的终态。
实现识别活前缀的有穷自动机的构造有两种方法:实现识别活前缀的有穷自动机的构造有两种方法: 方法1: 由文法的产生式直接构造识别活前缀和可归前缀的有穷自动机 方法2: 通过构造文法G的LR(0)的项目集规范族来直接构造识别活前缀的DFA 【说明】由于NFA确定化为DFA的工作量较大,我们考虑直接构造出项目集规范族作为DFA的状态,来构造DFA。
LR(0)项目集规范族的构造 定义:构成识别一个文法活前缀的DFA项目集(状态)的全体,称为这个文法的LR(0)项目集规范族。 (一) LR(0)项目 在每个产生式的右部适当位置添加一个圆点构成项目(item)。每个项目的含义和圆点的位置有关。项目圆点的左部表示分析过程的某个时刻用该产生式归约时句柄已识别的部分,圆点右部表示待识别的部分。
根据圆点所在的位置和圆点后是终结符还是非终结符把项目分为以下几种:1、移进项目,形如 A →a . ab 2、待约项目,形如 A →a . Bb 3、归约项目,形如 A →a . 4、接受项目,形如 S’→S .
【例】产生式S→aAcBe对应的6个项目是: [0]S→.aAcBe [1]S→a.AcBe [2]S→aA.cBe [3]S→aAc.Be [4]S→aAcB.e [5]S→aAcBe.
一个产生式可对应的项目为它的右部符号长度加1,对空产生式一个产生式可对应的项目为它的右部符号长度加1,对空产生式 A-> ε 仅有一个项目 A->. 【例】产生式A->X YZ 对应4个项目 A->.XY Z A->X·Y Z A->X Y·Z A->X YZ·
(二)LR(0)项目集规范族的构造 对于构成识别一个文法活前缀的DFA项目集的全体称为这个文法的LR(0)项目集规范族 (1)首先,为了使"接受"状态易于识别,总是将文法G进行拓广。假定文法G是一个以S为开始符号的文法,我们构造一个G’,它包含了整个G并引进了一个不出现在G中的非终结符S’;同时加进了一个新产生式S‘一>S,而这个S是G’的开始符号,称G’是G的拓广文法。会有一个仅含项目S’->S·的状态,这就是唯一'的"接受"态。
(2)闭包函数CLOSURE(I) 如果I是文法G’的一个项目集,定义和构造I的闭包CLOSURE(I)如下:a)I的项目都在CLOSURE(I)中b)若A→a . Bb属于CLOSURE(I),则每一形如B→. g的项目也属于CLOSURE(I) c)重复b)直到CLOSURE(I)不再扩大。 【说明】求闭包函数的过程实际上就是求所有等价状态的过程。
X (3)转换函数GOTO(I,X)定义转换函数如下:GOTO(I,X)= CLOSURE(J) 其中:I为包含某一项目集的状态,X为一文法符号J={任何形如A→aX . b的项目| A→a . X b属于I}定义两个函数后,就可以通过闭包函数CLOSURE求DFA一个状态的项目集,通过转换函数求DFA一个状态的项目集 【例】 A->a.Xb A->aX.b
构造文法G的LR(0) 项目集规范族的方法: 把文法的所有产生式的项目都引出,每个项目都为NFA的一个状态。其中文法的第一个产生式的第一个项目S’→ . S为文法的初态集的核心项。a)置项目S’→ . S为初态集的核心项后,对核心项求闭包CLOSURE({S’→ . S})得到初态的项目集b)对初态集或其它所构造的项目集应用转换函数GOTO(I,X)= CLOSURE(J)求出新状态J的项目集c)重复b)直到不出现新的项目集为止
【例】文法G[S']: [ 0]S'→E [1]E→aA [2]E→bB [3]A→cA [4]A→d [5]B→cB [6]B→d 其规范项目集族构造如下:
LR(0)分析表的构造 假定C={I。,I1,…,In)。由于我们已经习惯用数字表示状态,因此令每个项目集Ik的下标k作为分析器的状态。特别是令包含项目S’->·S(表示整个句子还未输入)的集合Ik的下标k为分析器的初态。
分析表的ACTION子表和GOTO子表可按如下方法构造:分析表的ACTION子表和GOTO子表可按如下方法构造: (1)若项目A->α . aβ属于Ik 且GO(Ik,a)= Ij,a为终结符,置ACTION[k,a] 为将(j,a)移进栈”,简记为“Sj (2)若项目A->α.属于Ik,则对任何终结符a(或结束符#),置ACTION[k,a] 为用产生式A->a进行归约,简记为“rj”(注意:j是产生式的编号而不是项目集的状态号,即A->a是文法G’的第j个产生式)。
(3)若项目S’->S · 属于Ik (S · 表示整个句子已输入并归约结束),则置ACTION[K,#]为可“接受”,简记为“acc”。 (4)若GO(Ik ,A)=Ij , A为非终结符,则置GOTO[k,A]=j。 (5)分析表中凡不能用规则(1)一(4)填入的空白格均置上“报错标志”。
LR(0)分析算法 1、置输入指针 ip 指向输入串的第一个符号;令 s 是栈顶状态, a 是 ip 所指向的符号;将#压入符号栈,将开始状态0压入状态栈; 2、重复执行如下过程: if(action[s,a]=sj) { 把符号a入符号栈,把状态j入状态栈; 使 ip 指向下一个输入符号。 } else if (action[s,a]=rj)
{从栈顶弹出第j条规则右部串长|β|个符号; 把归约得到的非终结符A压入符号栈; 将goto[s,A]的值j压入状态栈; 并输出规则 A→β。 } else if (action[s,a]=acc) return; else error(); }
【例】文法G[S']: [ 0]S'→E [1]E→aA [2]E→bB [3]A→cA [4]A→d [5]B→cB [6]B→d,对输入串bccd#分析如下:
LR(0)文法 1、如果I 中至少含两个归约项目,则称I 有 归约—归约冲突(Reduce/Reduce Conflict) 2、如果I 中既含归约项目,又含移进项目,则称I 有移进—归约冲突(Shift/Reduce Conflict) 3、如果I 既没有归约—归约冲突,又没有移进—归约冲突,则称I 是相容的(Consistent),否则称I 是不相容的。 4、对文法G,如果I∈C,都是相容的,则称G为LR(0)文法。
【例】有产生式如下 S—>BB B—>aB|b 构造该文法的LR(0)分析表 ①将文法G拓广为文法G' 0)S’一>S 1)S—>BB 2)B—>aB 3)B—>b ②列出LR(0)的所有项目 1、S’—>·S 5、S—>BB· 9、B->.b 2、S'一>S. 6、 B—>·aB 10、B->b. 3、S一>.BB 7、B—>a·B 4、S—>B·B 8、B—>aB.
用e_CLOSURE办法构造文法G'的LR(0)项目集规范族 I0: S'->.S S->.BB B->.aB B->.b I1: S'->S. I2:S—>B·B B一>.aB B—>.b I3: B->a.B B->.aB B->.b I4: B->b. I5: S->BB. I6: B->aB.
状态 ACTlON GOTO a b # S B 0 s3 S4 1 2 1 aCC 2 s3 S4 5 3 S3 s4 6 4 r3 r3 r3 5 r1 r1 rl 6 r2 r2 r2 LR(0)分析表
【练习】设有文法G[S]: S->E E->Aa |bB A->cA |d B->cB |d 构造LR(0)分析表,并利用此分析表判断符号串acccd是否是文法G[S]的句子。
7.3 SLR(1)分析 LR(0)文法是一类非常简单的文法,没有查看下一符号(Token),决定分析动作仅仅根据到目前已经看到的东西,分析能力弱。即使是定义算术表达式这样的简单文法也不是LR(0)。 改进办法: 向前查看下一符号---SLR(1),LR(1),LALR(1)
7.3 SLR(1)分析法 一、LR(0)分析存在的问题 【例】已知文法G,开始符号为S’,判断该文法是否为LR(0) 文法。(0) S’ →S (1) S →rD (2) D→D,i (3) D → i 单击演示
这里仅仅凭LR(0) 项目本身的信息已经无法解决项目之间的冲突,需要向前查看一个符号,再来决定到底是采取移进动作还是归约动作。
二、解决方法 对含有移进--归约和归约--归约冲突的项目集 I={X→a.bb , A→g. ,B→d.}若有:FOLLOW(A) ∩FOLLOW(B) = Φ FOLLOW(A) ∩{b} = Φ FOLLOW(B) ∩{b} = Φ 状态I面临某输入符号a 1) 若a=b,则移进2) 若a∈FOLLOW(A), 则用产生式 A →g 进行归约3) 若a∈FOLLOW(B), 则用产生式 B →d 进行归约4) 此外,报错
若一个文法的LR(0)分析表中所含有的动作冲突都能用上述方法解决,则称这个文法是SLR(1)文法。SLR(1)文法是无二义的。若一个文法的LR(0)分析表中所含有的动作冲突都能用上述方法解决,则称这个文法是SLR(1)文法。SLR(1)文法是无二义的。
SLR(1)分析表构造方法 对任给的一个文法G,我们可用如下的办法构造它的SLR(1)分析表: 首先把G拓广为G’,对G’构造LR(0)项目集规范族和活前缀识别自动机的状态转换函数GO,使用闭包函数CLOUSE和GO函数,然后再按下面的算法构造G’的SLR分析表。
分析表的ACTION子表和GOTO子表构造方法: (1)若项目A->α . aβ属于Ik 且GO(Ik,a)= Ij ,a为终结符,置ACTION[k,a] 为将(j,a)移进栈”,简记为“Sj。 (2)若项目A->α.属于Ik,则对任何终结符a(或结束符#),且a∈FOLLOW(A),置ACTION[k,a] 为用产生式A->a进行归约,简记为“rj”。 (3)若项目S’->S · 属于Ik (S · 表示整个句子已输入并归约结束),则置ACTION[K,#]为可“接受”,简记为“acc”。 (4)若GO(Ik ,A)=Ij , A为非终结符,则置GOTO[k,A]=j;。 (5)分析表中凡不能用规则(1)一(4)填入的空白格均置上“报错标志”。
【练习】判断下列文法是否是LR(0),如不是说明理由,判断是否是SLR(1)文法。【练习】判断下列文法是否是LR(0),如不是说明理由,判断是否是SLR(1)文法。 文法G[S] S->iSeS S->iS S->a