自下而上的语法分析(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->b
    B,
    对三个终态集求闭包
    以此类推

  • 构造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填写的值一致

在这里插入图片描述

定义

在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐