编译原理之LR(1)文法

LR(0)分析方法是一种自底向上分析方法,自底向上分析方法是一种移进归约过程,当分析的栈顶符号串形成句柄或可归约串时就采取归约动作。若是限定采用规范规约,那么自底向上分析法的关键问题是在分析过程中如何确定句柄。LR 分析法正是给出一种能根据当前分析栈中的符号串(通常以状态表示)和向右顺序查看输人串的k(k≥0)个符号就可唯一地 确定分析器的动作是移进还是归约和用哪个产生式归约,因而也就能唯一地确定句柄。LR分析法的归约过程是规范推导的逆过程,所以LR分析过程是种规范归约过程。

LR(k)分析方法是1965年Knuth提出的,括号中的k表示向右查看输入事符号的个数。这种方法比起自顶向下的LL(k)分析方法和自底向上的优先分析方法对文法的限制要少得多,也就是说,对于大多数用无二义性上下文无关文法描述的语有都可以用相定的LR分析器进行识别,而且这种方法还具有分析速度快,能准确即时地指出出错位置的特点。的构造工作量相当大,k愈大,构造愈复它的主要缺点是对于一个实用语言文法的分析器译程序,当采用LR分析器时都是借助于美国杂,实现比较困难。因此,目前许多实用的编能接受一个用BNF描述的满足 LR类中LALR(1)Bell实验室推出的yacc来实现的。

对于LR文法,我们可以自动构造相应的LR分析表。为了构造LR分析表,我们需要定义一个重要概念——文法的规范句型“活前缀”。

这种句柄之后不含任何符号的前缀称为活前缀。

在LR分析工作过程中的任何时候,栈里的文法符号(自栈底而上)X1X2…Xm应该构成活前缀,把输入串的剩余部分配上之后即应成为规范句型(如果整个输入串确实构成一个句子)。因此,只要输入串的已扫描部分保持可归约成一个活前缀,那就意味着所扫描过的部分没有错误。

对于一个文法G,我们可以构造一个有限自动机,它能识别G的所有活前缀,然后把这个自动机转变成LR分析表,按照该LR分析表进行LR分析,就能保证在分析的过程中,如果分析的句子是正确的,栈里的文法符号(自栈底而上)始终构成活前缀。

假若一个文法G的拓广文法的活前缀识别自动机中的每个状态(项目集)不存在下述情况:(1)既含移进项目又含归约项目;(2)含有多个归约项目,则称G是一个LR(0)文法。该自动机的状态集合即为该文法的LR(0)项目集规范族。

构造识别文法活前缀DFA有3种方法:

(1)根据形式定义求出活前缀的正则表达式,然后由此正则表达式构造NFA再确定为DFA;

(2)求出文法的所有项目,按一定规则构造识别活前缀的NFA再确定化为DFA;

(3)使用闭包函数(CLOSURE)和转向函数(GO(I,X))构造文法G’的LR(0)的项目集规范族,再由转换函数建立状态之间的连接关系来得到识别活前缀的DFA。

符号串的前缀是指该符号串的任意首部,包括空串ε。例如,对于符号串abc,其前缀有ε,a,ab,abc。如果输入串没有错误的话,一个规范句型的活前缀是该句型的一个前缀,但它不含句柄之后的任何符号。之所以称为活前缀,是因为在该前缀后联接尚未输入的符号串可以构成一个规范句型。

活前缀与句柄的关系如下:

(1)活前缀已含有句柄的全部符号,表明产生式A→β的右部β已出现在栈顶。

(2)活前缀只含句柄的一部分符号,表明A→β1β2的右部子串β1已出现在栈顶,期待从输入串中看到β2推出的符号。

(3)活前缀不含有句柄的任何符号,此时期望A→β的右部所推出的符号串。

在文法G的每个产生式的右部(候选式)的任何位置上添加一个圆点,所构成的每个产生式称为LR(0)项目。如产生式A® xyz有如下项目:A®.xyz,A®x.yz,A®xy.z,A®xyz.。为刻划分析过程中的文法的每一个产生式的右部符号已有多大一部分被识别(出现在栈顶),可以用这种标有圆点的产生式来确定。

(1)A→β.刻划产生式A→β的右部β已出现在栈顶。

(2)A→β1.β2 刻划A→β1β2的右部子串β1已出现在栈顶,期待从输入串中看到β2推出的符号。

(3)A→.β 刻划没有句柄的任何符号在栈顶,此时期望A→β的右部所推出的符号串。

(4)对于A→ε的LR(0)项目只有A→.。

   设文法G=(VT,VN,S,P)是一个上下文无关文法,若存在一个规范推导SAw12w(其中A12P),则称项目A12对活前缀=1是有效的,即LR(0) 有效项目。

从直观意义上讲,一个LR(0)项目指明了在分析过程中的某一步我们看到产生式的多大部分被识别,LR(0)项目中的圆点可看成是分析栈栈顶与输入串的分界线,圆点左边为已进入分析栈的部分,右边是当前输入或继续扫描的符号串。

不同的LR(0)项目,反映了分析栈顶的不同情况。我们根据LR(0)项目的作用不同,将其分为四类:

(1)归约项目:

表现形式:A→a.

这类LR(0)项目表示句柄a恰好包含在栈中,即当前栈顶的部分内容构成了所期望的句柄,应按A→a进行归约。

(2)接受项目:

表现形式:→a.

其中是文法惟一的开始符号。这类LR(0)项目实际是特殊的归约项目,表示分析栈中内容恰好为a,用→a进行归约,则整个分析成功。

(3)移进项目:

表现形式:A→a.(bVT)

这类LR(0)项目表示分析栈中是不完全包含句柄的活前缀,为构成恰好有句柄的活前级,需将b移进分析栈。

(4)待约项目:

表现形式:A→α.Bβ (BVN)

这类LR(0)项目表示分析栈中是不完全包含句柄的活前缀,为构成恰好有句柄的活前缀,应把当前输入字符串中的相应内容先归约到B。

在给出LR(0)项目的定义和分类之后,我们从这些LR(0)项目出发,来构造能识别文法所有前缀的有限自动机。其步骤是:首先构造能识别文法所有活前缀的非确定的有限自动机,再将其确定化和最小化,最终得到所需的确定的有限自动机。

由文法G的LR(0)项目构造识别文法G的所有活前缀的非确定有限自动机的方法:

(1)规定含有文法开始符号的产生式(设→A)的第一个LR(0)项目(即→.A)为NFA的惟一初态。

(2)令所有LR(0)项目分别对应NFA的一个状态且LR(0)项目为归约项目的对应状态为终态。

(3)若状态i和状态j出自同一文法G的产生式且两个状态LR(0)项目的圆点只相差一个位置,即:

若i为X→X1X2·…Xi-1·Xi…Xn, j为 X→X1X2…Xi·Xi+1…Xn,则从状态i引一条标记为Xi的弧到状态j。

(4)若状态i为待约项目(设X→α·Aβ),则从状态i引ε弧到所有A→·r的状态。

为了使“接受”状态易于识别,我们通常将文法G进行拓广。

假定文法G是一个以S为开始符号的文法,我们构造一个,它包含了整个G,但它引进了一个不出现在G中的非终结符,并加进一个新产生式→S,以→S为开始符号。那么,我们称是G的拓广文法。

这样,便会有一个仅含项目→S的状态,这就是惟一的“接受”态。

如果I是文法G'的一个项目集,定义和构造I的闭包CLOSURE(I)如下:

(1)
I的项目都在CLOSURE(I)中。

(2)
若A→a.Bb属于CLOSURE(I),则每一形如B→.g的项目也属于CLOSURE(I)。

(3)
重复(2)直到CLOSURE(I)不再扩大。

定义转换函数如下:

GO(I,X)= CLOSURE(J)

其中:I为包含某一项目集的状态,X为一文法符号,J={ A→aX .b | A→a.X b∈I}。

圆点不在产生式右部最左边的项目称为核,惟一的例外是S′→.S,因此用GOTO(I,X)状态转换函数得到的J为转向后状态闭包项目集的核。

使用闭包函数(CLOSURE)和转换函数(GO(I,X))构造文法G’的LR(0)的项目集规范族,步骤如下:

(1) 置项目S′→.S为初态集的核,然后对核求闭包CLOSURE({S′→.S})得到初态的闭包项目集。

(2) 对初态集或其他所构造的项目集应用转换函数GO(I,X)=
CLOSURE(J)求出新状态J的闭包项目集。

(3)
重复(2)直到不出现新的项目集为止。

计算LR(0)项目集规范族C={I0,I1
, … In }的算法伪代码如下:

Procedure itemsets(G’);

Begin C := {
CLOSURE ({S’®.S})}

Repeat

For C 中每一项目集I和每一文法符号X

Do if GO(I,X) 非空且不属于C

             Then 把 GO(I,X) 放入C中

Until C 不再增大

End;

一个项目集可能包含多种项目,若移进和归约项目同时存在,则称移进-归约冲突,若

归约和归约项目同时存在,则称归约-归约冲突。下面看一个具体的例子:

我们希望能根据识别文法的活前缀的DFA建立LR分析器,因此,需要研究这个DFA的每个项目集(状态)中的项目的不同作用。

我们说项目A→β1.β2对活前缀αβ1是有效的,其条件是存在规范推导。一般而言,同一项目可能对几个活前缀都是有效的(当一个项目出现在几个不同的集合中时便是这种情形)。若归约项目A→β1.对活前缀是有效的,则它告诉我们应把符号串归约为A,即把活前缀变成αA。若移进项目A→β1.β2对活前缀是有效的,则它告诉我们,句柄尚未形成,因此,下一步动作应是移进。但是,可能存在这样的情形,对同一活前缀,存在若干项目对它都是有效的。而且它们告诉我们应做的事情各不相同,互相冲突。这种冲突通过向前多看几个输入符号,或许能够获得解决。

对于每个活前缀,我们可以构造它的有效项目集。实际上,一个活前缀γ的有效项目集正是从上述的DFA的初态出发,经读出γ后而到达的那个项目集(状态)。换言之,在任何时候,分析栈中的活前缀X1X2…Xm的有效项目集正是栈顶状态Sm所代表的那个集合。这是LR分析理论的一条基本定理。实际上,栈顶的项目集(状态)体现了栈里的一切有用信息——历史。

前面我们已经对LR(0)文法进行了定义,下面我们来看一下LR(0)分析表是如何构造的。

对于LR(0)文法,我们可以直接从它的项目集规范族C和活前缀识别自动机的状态转换函数GO构造出LR分析表。下面是构造LR(0)分析表的算法。

假定C={I0, I1,…,In},令每个项目集Ik的下标k为分析器的一个状态,因此,G’的LR(0)分析表含有状态0,1,…,n。令那个含有项目S’→.S的Ik的下标k为初态。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→α进行规约”,简记为“rj”;其中,假定A→α为文法G’的第j个产生式;

(3)若项目S’→S.属于Ik, 则置ACTION[k,
#]为“接受”,简记为“acc”;

(4)若GO (Ik, A)= Ij, A为非终结符,则置GOTO[k, A]=j;

(5)分析表中凡不能用上述1至4填入信息的空白格均置上“出错标志”。

按上述算法构造的含有ACTION和GOTO两部分的分析表,如果每个入口不含多重定义,则称它为文法G的一张LR(0)分析表。具有LR(0)表的文法G称为一个LR(0)文法,LR(0)文法是无二义的。

例如,文法G(E)的拓广文法如下:

(0)S’→E

(1)E→aA

(2)E→bB

(3)A→cA

(4)A→d

(5)B→cB

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值