编译原理——自下而上的语法分析方法(LR分析法)
LR分析法及分析程序自动构造概述上下文无关文法的LR分析法LR:自左至右扫描,最右推导的逆过程(也就是最左归约)LR方法:在归约的过程中,一方面记住移入和归约的整个符号串,另一方面通过产生式推测未来可能碰到的输入符号优缺点:优点:文法范围广,识别能力强,可以识别出错位置缺点:工作量大,需要构造这种分析程序的产生器产生器作用:应用产生器产生一大类上下文无关文法的LR分析程序对二义性文法或难分析的特殊
自下而上的语法分析(LR分析法)
概述
- 上下文无关文法的LR分析法
- LR:自左至右扫描,最右推导的逆过程(也就是最左归约)
LR方法:
-
在归约的过程中,一方面记住移入和归约的整个符号串,另一方面通过产生式推测未来可能碰到的输入符号
-
优缺点:
- 优点:文法范围广,识别能力强,可以识别出错位置
- 缺点:工作量大,需要构造这种分析程序的产生器
-
产生器作用:
- 应用产生器产生一大类上下文无关文法的LR分析程序
- 对二义性文法或难分析的特殊方法,施加一些限制使之能用LR分析法
-
LR分析器:
包括两部分:总控程序,分析表
总控程序:控制程序的运行,查分析表的内存做简单动作
产生器的任务就是产生分析表 -
分析表:
- 一个文法的LR分析器对应四种不同分析表,所以分析表都恰好识别产生的所有语句
- LR(0),SLR,LR(1),LALR四种分析表
LR分析法
- LR分析法的基本思想
- 规范归约时,当一串貌似句柄的符号串呈现于栈顶,LR分析法根据三方面来寻找句柄:历史,展望,现实
- 下推栈:
- 分析表:
- 总控程序的动作:
解释:移进动作就是将在两个栈的栈顶(si,a)si表示状态,a表示栈顶的字符,因为a是终结符,所以去该状态列对应的终结符行,相交成的动作,需要先将下一个字符移入,然后将某一个状态加入状态栈中栈顶变为(sj,b)
LR分析器
分析过程:
0状态遇到i,跳到5状态
5状态遇到*,按照6产生式归约
F入栈,0状态遇到F非终结符,入栈状态3
LR文法:
- 定义:如果可以根据某一文法可以构造一张分析表,使得该表中每一个元素至多只有一种明确的动作,这个文法就是LR文法
LR(0)项目集族和LR(0)分析表的构造:
-
基本思想:
- 只根据历史来识别呈现于栈顶的句柄
-
LR(0)是其他分析法的基础
-
构造表的策略:
- 构造文法G的优先自动机,他能识别文法中的所有活前缀
-
活前缀:
- 字的前缀是指字的任意首部
- 例如:字ABC的前缀有空,A,AB,ABC
- 活前缀概念:
- 指规范句型的前缀,这种前缀不含句柄之后的任何符号
- 规范句型:通过规范推导和归约得到的句型
- 例子:
例如:ab[2]b[3]cd[4]e[1]
先去识别ab得到[2],通过2号产生式得到对b归约,aAb[3]cd[4]e[1],通过[3],aAcd[4]e[1],通过[4],aAcBe[1],通过[1],的得到S
- 字的前缀是指字的任意首部
-
活前缀的两种类型:
- 归态活前缀:活前缀的尾部正好为句柄尾部,可以进行归约 前文中的ab[2]就是
- 非归态活前缀:句柄未形成,需要继续移进若干符号之后才能形成句柄
-
构造自动机识别活前缀:
- 对于一个文法,我们可以构造一个自动机来识别它的所有活前缀
- 由于产生式的右部符号串就是句柄,若这些符号串以进栈,则表示它已处于归态活前缀,若只有部分进栈则属于非归态活前缀。要想要知道活前缀有没有大部分入栈,可以为每一个产生式构造一个自动机,由它的状态来记住当前情况,这个状态称为项目,这些自动机的全体是能识别所有活前缀的优先自动机
-
项目:
-
由项目构造NFA的方法:
文法拓展的必要性:通过对文法的扩展,不会出现单单只识别了一个开始符号就确定是这个句子的情况。例如S->bS’A | asd,这时候,当我们,读入了b,进入bS‘A状态,然后需要读入S’,这时我们返回去,通过读入asd进入了终态,这时候,我们结束,但是也可以继续,这时候机器就不知道怎么选择了
-
例子:
列出所有项目
对于识别空串就是,小圆点到达了一个非终结符前面,可以通过读入这个非终结符的首符集来进入这个非终结符。 -
LR(0)项目集族的机器构造法:
-
例子:
解释:(这里使用了*表示点)
首先找到开始符号S‘的e闭包状态集S’->E,E->*aA,E->bB,为状态集0
找状态集族里的状态集0,发现可以通过E到达状态S‘->E ,可以通过a到达状态E->aA,可以通过b到达状态E->bB,
对三个终态集求闭包
以此类推 -
构造LR(0)分析表:
解释:
这里的go函数表示的就是当栈顶符号为Ik,读头下符号为a,那么就将a入栈,并入栈一个识别了a的状态
当小点出现在一个产生式的最后位置,那就表示对于读头下任何符号,都可以使用归约,将符号栈中已有的部分符号按照某个产生式进行归约,
SLR语法分析方法:
-
当使用LR(0)分析法,出现归约和归约之间的冲突,移进和归约之间的冲突,可以尝试使用SLR分析法,可以解决LR(0)分析法的部分问题。
-
例如在一个项目中A->a* ;B->b*;这时候,到这个状态按照LR(0)有两种归约方法,因此发生了冲突。
-
在归约时除了考虑历史情况之外,还考虑一些现实
-
消除冲突
-
构造SLR分析表:构造LR(0)项目集族,使用SLR分析法填表。
2):对于读头下面是follow集中的字符,才使用归约操作
为什么不能解决移进-归约:信息不足,因为当follow(A)存在a字符,可以对A进行归约也可以对X进行移进操作。 -
例子:
上图为产生移进-归约冲突,提示:为什么LR(0)不能避免归约-移进操作,LR(0)分析法,对于可以进行归约操作的字符串,将所有字符都作为归约操作,这样对于可以移进操作的字符,就会出现重定义。为什么不能避免归约-归约冲突:因为如果一个字符串有两个选择进行归约,同样因为将所有的读入字符都可以进行归约,这样会导致可以对所有的字符都可以进行两个选择进行归约。
为什么SLR文法可能不会产生移进-归约冲突:因为当归约项的follow集中不包含移进项的字符,就可以明确下一个动作就是归约
例如I1状态,其中按照SLR分析法,有归约动作我们需要求FOLLOW(S’)={#}可以归约操作,对于+可以进行移进操作,当时这时候+不属于FOLLOW(S’)所以没有发生冲突。
可以看到这张表中,有些带有归约动作的行,不是如LR(0)文法,一行都是归约动作,可以看3状态T->F*;按照SLR规则我们需要找到T的follow()集合{*,+,),#},当读头下出现这四个符号时,表示可以对T->F归约。 -
一个非SLR的例子:
对于状态2:follow®中存在=,这就造成了移进和归约的冲突
为什么会出现这种情况:当follow集中有某个字符,当读头下是这个符号时;但是不能将栈中符号串进行归约,也就是上图中的项目2,R的follow集中有{=}但是,不能将L归约成R,原因是,follow集中的符号是忽略了前提的,它只是告诉我们后面可能会出现的符号,但是当某些前提达不到时,其实后面跟随的符号是永远不会出现的;就像题中的R的follow集,存在"=“但是只有S‘->S;S->L=R;L->*R时,R后面才可能出现”=",所以当R前面是" * “时,后面读入的符号才可能是”=";否则,如果将这题中的L归约成R,就可能造成,两条路,向一条走的可以走通,但是我们走向了另外一条,导致了错误,程序认为该句子不能识别。
如上图对于一个字符串i=进行归约,按照上图步骤推出错误,所以当我们推导i=R(这里的R只是一个R只是代指一个R可以推出的字符串)也是会推出错误,但是这样的字符串可以通过S->L=R归约得到,就是因为将i归约成了L又错误的归约成了R,导致了错误。
规范LR分析表构造:
SLR的缺陷:
对于项目集2,产生了移进归约冲突,因为当读头下为=,可以进行移进,当时follow®中也存在=,这是为什么,因为follow()集指的是,这个符号后面可能出现的符号,这时忽略了前提条件的。例如对于这个文法,在2项目的情况下,读头下是=,但是如果我们将L归约成了R,那也就是说我们认为R后面可以跟着=,但是我们根据文法推导得到,要出现R=的情况,只有在R前面是*的时候才成立,也就是R‘->R->L=R->*R=R,但是我们显然知道项目2是通过S‘->S->R->L得到的,也就是栈中没有 * ,所以在项目2中没有L归约成R的可能性。我们应该关系的是这样的规范推导后面跟着的符号是什么,在S‘->S->R->L也就是first(#),因为S‘后面跟着的就是#好,也就是说,当读头后面跟着的是#的时候,才可以将L归约成R,这样一路归约下去,到R’。
规范LR:
这个也就是表示栈里面放的是α,读头下面是β,预计当我们读入β后,
这个也就是表示栈里面
在这种情况下,我们在x项目下,我们可以知道的是我们栈中有ae,遇到了c可以进行移进操作也就是S->aec,也可以使用归约操作A->e,因为A的follow集中有=,产生冲突了,那就是因为,follow集只考虑了会出现了,但是没有考虑在规范推导中会出现的,我们进行规范推导,发现当ae在栈中,只有读头下面是first(d)时,才可以通过将e归约成A,然后读入d,将栈归约成S,再归约成S‘,而此时的读头下面是c,所以当我们将e归约成了A,栈中变成了aA,读头下面是c,则出现了错误,因为当栈中是aA时,之后读头下面是d,才能读入。
follow(A)={c,d},当A后面出现c,那正确的条件是栈中有a;当A后面跟d,那正确的条件是栈中有b,这两种中,只有aAd是规范推导
规范LR项目集族的构造过程:
(表示点号)对于{(A->α * Bβ,a)}项目集,(A->α * Bβ,a)表示栈中有了α,我们需要读入B,再读入β,这时候进行归约,只有当读入下面是a的时候才能归约;我们先使用LR(0),将空串产生式加入项目集{(A->α * Bβ,a),(B-> γ,first(βa))},这里的first(βa)是因为,我们需要找到规范推导后,可以进行γ归约成B的条件,也就是读头下面是first(βa)。
因为还是针对同一个需要被归约的式子,搜索符只是作为一个归约是读头的判断条件而已。
解释:从(S’-> * S,#)产生(S-> * L=R,#),当读头下面是#对L=R进行归约,再产生(L-> (点) *R,first(=R#))……
此处的图在《编译原理及编译程序构造第三版》的p121页
LR(1)分析表:
只有对后文中的first()才进行归约。
1),对于一个项目k,如果k读入一个终结符到j项目,将action[k,a]置为读入并转到状态j
2),对于一个归约状态k,只有读头下是a的时候,才会归约,也就是将action[k,a]置为归约产生式的编号
LALR分析法:
基本思想:
合并同心:
假设有同心项目m和n,则,m和n通过识别X到达了m’和n’状态,我们就可以将两个项目合并,假设去除n项目那一栏,然后将原来指向n项目的指向m项目,原来指向n’项目的指向m’项目
!移进和移进合并,因为当前项目是同心的,所以当这个项目读一个非终结符之后,得到的项目也是同心的,则我们去掉一个项目,让其他本来指向去除项目的关系,指向对应的同心项目
归约和出错合并,因为规定做归约的原因,所以放松了报错条件,因为可能遇到了报错的那种情况,但是我们去做了归约处理,但是报错能力没有减弱,时间推后了
在这种情况下,第一个项目,当读头下是d归约为A,是e归约成B,当两个项目合并后,因为两个搜索符都一样,当出现这种情况,就不知道怎么进行归约了,出现了归约归约冲突。所以LALR文法是LR(1)的子集
构造算法:
比LR(1)多个一个合并同心的操作,action表的构造一致
构造goto表,需要将删去的那些项目,读入一个X填写的值,置为还存在的那个项目集读入一个X填写的值一致
定义
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)