LR,SLR和LALR解析器之间有什么区别?


103

LR,SLR和LALR解析器之间的实际区别是什么?我知道SLR和LALR是LR解析器的类型,但是就它们的解析表而言,实际区别是什么?

以及如何显示语法是LR,SLR还是LALR?对于LL语法,我们只需要显示解析表的任何单元格都不应包含多个生产规则。LALR,SLR和LR是否有类似规则?

例如,如何显示语法

S --> Aa | bAc | dc | bda
A --> d

是LALR(1)但不是SLR(1)?


编辑(ybungalobill):对于LALR和LR之间的区别,我没有得到满意的答案。因此,LALR的表较小,但只能识别LR语法的一个子集。有人可以详细说明一下LALR和LR之间的区别吗?LALR(1)和LR(1)就足够回答了。他们都使用1个令牌前瞻和双方都表驱动!它们有何不同?


好吧,即使我正在寻找一个正确的答案,LALR(1)只是对LR(1)的略微修改,表的大小减小了,以便我们可以最大程度地减少内存使用量……
vikkyhacks

Answers:


64

SLR,LALR和LR解析器都可以使用完全相同的表驱动机制来实现。

从根本上讲,解析算法将收集下一个输入令牌T,并查询当前状态S(以及相关的前瞻,GOTO和归约表)以决定要执行的操作:

  • SHIFT:如果当前表对令牌T进行SHIFT,则将对(S,T)推送到解析堆栈上,则根据GOTO表对当前令牌所说的内容(例如GOTO(T))更改状态),则提取另一个输入令牌T',然后重复该过程
  • 减少:每个状态都有0、1或该状态可能发生的许多可能的减少。如果解析器是LR或LALR,则根据前瞻集检查令牌,以获取该状态的所有有效缩减。如果令牌与针对语法规则G = R1 R2 .. Rn的缩减的先行集匹配,则会发生堆栈缩减和移位:调用G的语义操作,堆栈弹出n次(从Rn),对( S,G)被推入堆栈,新状态S'设置为GOTO(G),并且该循环以相同的令牌T重复。如果解析器是SLR解析器,则最多有一个归约规则状态,因此可以盲目执行还原操作,而无需搜索查看适用的还原。这是单反解析器很有必要知道,如果有是否减少;这很容易判断每个州是否明确记录了与之关联的减少数量,并且实际上L(AL)R版本需要该计数。
  • 错误:如果无法进行SHIFT或REDUCE,则声明语法错误。

那么,如果他们都使用相同的机器,那有什么意义呢?

SLR声称的价值在于其实现的简便性。您不必遍历所有可能的减少量检查前瞻集,因为最多只有一个,并且如果该状态没有SHIFT退出,则这是唯一可行的操作。可以将哪种减少量专门附加到状态上,因此SLR解析机制不必寻找它。在实践中,L(AL)R解析器处理大量有用的语言,并且实施起来几乎不需要额外的工作,除了作为学术练习之外,没有人实现SLR。

LALR和LR之间的区别与表生成器有关。LR解析器生成器跟踪来自特定状态的所有可能的缩减及其精确的超前集合。您最终得到的状态是,每个归约都与其从其左上下文中设置的确切提前行相关联。这往往会建立大量状态。如果用于缩减的GOTO表和观察头集兼容并且不冲突,则LALR解析器生成器愿意组合状态。这样产生的状态数量要少得多,但代价是无法区分LR可以区分的某些符号序列。因此,与LALR解析器相比,LR解析器可以解析更大的语言集,但是解析器表却大得多。在实践中,可以找到与目标语言足够接近的LALR语法,因此状态机的大小值得优化。

所以:所有三个使用相同的机器。在您可以忽略一小部分机械的意义上说,单反是“容易的”,但这并不值得麻烦。LR解析了更广泛的语言集,但是状态表往往很大。这使LALR成为实际选择。

综上所述,值得一提的是,GLR解析器可以使用更复杂的机制但使用完全相同的表(包括LALR使用的较小版本)来解析任何上下文无关的语言。这意味着GLR绝对比LR,LALR和SLR更强大。如果您可以编写标准的BNF语法,则GLR会根据它进行语法分析。机制上的差异是,当GOTO表和/或超前集之间存在冲突时,GLR愿意尝试多个解析。(GLR如何有效地做到这一点是个天才[不是我的],但是不适合本SO帖子)。

对我来说,这是一个非常有用的事实。我构建了程序分析器,代码转换器和解析器是必需的,但“毫无趣味”;有趣的工作是您对解析后的结果所做的工作,因此重点是进行后解析工作。使用GLR意味着我可以相对容易地构建有效的语法,与破解语法以使用LALR可用形式相比。在尝试处理非学术语言(例如C ++或Fortran)时,这一点非常重要,因为在这些语言中,您实际上需要成千上万条规则来很好地处理整个语言,并且您不想花一生的时间来尝试破解语法规则,满足LALR(甚至LR)的限制。

举一个著名的例子,C ++被认为很难解析……被从事LALR解析的人所接受。使用GLR机制,可以使用C ++参考手册后面提供的规则直接解析C ++。(我恰好有一个这样的解析器,它不仅处理香草C ++,而且还处理各种供应商方言。只有在实践中才有可能,因为我们使用的是GLR解析器IMHO。)

[2011年11月,EDIT:我们扩展了解析器,以处理所有C ++ 11。GLR使这一操作变得容易得多。编辑2014年8月:现在处理所有C ++ 17。一切都没有破裂或恶化,GLR仍然是猫的叫声。]


AFAIK C ++无法用LR解析,因为它需要无限的前瞻性。因此,我想不出任何可以使用LR解析它的技巧。LRE解析器听起来也很有前途。
Yakov Galka

5
GCC用于使用Bison == LALR解析C ++。您总是可以在解析器中增加额外的黏性,以处理使您心痛的情况(超前,是这种类型的名称)。问题是“黑客有多痛苦?” 对于GCC来说,这是很痛苦的,但是他们使它起作用了。这并不意味着建议这样做,这是我关于使用GLR的观点。
伊拉·巴克斯特

我不明白使用GLR如何帮助您使用C ++。如果您不知道某个东西是否是类型名称,那么您就是不知道如何解析x * y;-使用GLR会如何帮助呢?
user541686

2
关键是,GLR解析器将生成两个解析(作为集成解析“树”中的“模糊子树”(实际上是DAG)。稍后,您可以通过引入其他解析来解析要保留的子树。上下文信息。我们的C ++解析器非常简单地针对此问题:它并没有试图解决问题,这意味着我们不必通过解析来纠缠符号表的构造,因此我们的解析器和C ++的符号表的构造都是单独清洁的,因此每个都需要建造和维护
Ira Baxter

18

LALR解析器在LR语法中合并相似的状态,以生成与等效SLR语法大小完全相同的解析器状态表,该表通常比纯LR解析表小一个数量级。但是,对于过于复杂以至于无法用于LALR的LR语法,这些合并状态会导致解析器冲突,或产生无法完全识别原始LR语法的解析器。

顺便说一句,我在这里的 MLR(k)解析表算法中提到了一些相关信息。

附录

简短的答案是LALR解析表较小,但解析器机制相同。如果生成所有LR状态,并且具有很多冗余(几乎相同)状态,则给定的LALR语法将产生更大的解析表。

LALR表较小,因为相似(冗余)状态被合并在一起,从而有效地丢弃了分离状态编码的上下文/超前信息。优点是对于相同的语法,您可以获得更小的解析表。

缺点是并非所有的LR语法都可以被编码为LALR表,因为更复杂的语法具有更复杂的前瞻性,从而导致两个或更多状态,而不是单个合并状态。

主要区别在于,生成LR表的算法在从状态到状态的转换之间携带更多信息,而LALR算法则没有。因此,LALR算法无法判断给定的合并状态是否应真正保留为两个或多个单独的状态。


3
+1我喜欢Honalee的想法。我的G / L(AL)R解析器生成器中包含这样的种子。它产生了最小的LALR机器,然后我打算分割存在冲突的状态,但是我从未经历过。这看起来像是生成最小大小的“ LR”(如解析表集)的好方法。尽管它无法解析GLR,但是它可能会减少GLR必须进行的并行解析的数量,这将很有用。
艾拉·巴克斯特

12

另一个答案(YAA)。

像Ira Baxter所说的,SLR(1),LALR(1)和LR(1)的解析算法是相同的,
但是由于解析器生成算法的原因,解析器表可能会有所不同。

SLR解析器生成器创建LR(0)状态机,并根据语法(FIRST和FOLLOW集)计算前瞻。这是一种简化的方法,可能会报告在LR(0)状态机中实际上不存在的冲突。

LALR解析器生成器创建LR(0)状态机,并从LR(0)状态机(通过终端转换)计算前瞻。这是一种正确的方法,但是偶尔会报告在LR(1)状态机中不存在的冲突。

规范的LR解析器生成器将计算LR(1)状态机,而前瞻已经是LR(1)状态机的一部分。这些解析器表可能非常大。

最小LR解析器生成器计算LR(1)状态机,但在此过程中合并兼容状态,然后从最小LR(1)状态机计算前瞻。这些解析器表的大小相同或略大于LALR解析器表,从而提供了最佳解决方案。

LRSTAR 10.0可以在C ++中生成LALR(1),LR(1),CLR(1)或LR(*)解析器,无论语法需要什么。请参阅此图,其中显示了LR解析器之间的差异。

[全部披露:LRSTAR是我的产品]


5

假设没有前瞻性的解析器很高兴地为语法分析字符串。

使用给定的示例,它遇到一个字符串dc,它是做什么的?是否将其简化为S,因为dc此语法生成的是有效字符串?或者也许我们正在尝试解析,bdc因为即使这是可以接受的字符串?

作为人类,我们知道答案很简单,我们只需要记住是否已经解析过即可b。但是计算机很愚蠢:)

由于SLR(1)解析器具有比LR(0)更多的功能来执行超前查找,因此我们知道,在这种情况下,任何数量的超前查找都无法告诉我们该怎么做。相反,我们需要回顾过去。这样就可以规范的LR解析器来解救。它记住了过去的背景。

它记住此上下文的方式是它自律,每当遇到a时b,它就会开始走上通往阅读的道路bdc,这是一种可能。因此,当它看到一个时,d便知道它是否已经在行走。因此,CLR(1)解析器可以做SLR(1)解析器不能做的事情!

但是现在,由于我们必须定义很多路径,因此机器的状态变得非常大!

因此,我们合并了相同的路径,但正如预期的那样,可能会引起混乱的问题。但是,我们愿意冒险以减小尺寸为代价。

这是您的LALR(1)解析器。


现在如何算法化。

在为以上语言绘制配置集时,您会看到两种状态的移位减少冲突。要删除它们,您可能需要考虑SLR(1),它会根据后续情况做出决策,但是您会发现它仍然无法实现。因此,您将再次绘制配置集,但这次有一个限制,即每当您计算闭包时,所添加的其他生产必须严格遵循。请参阅任何教科书,以了解这些内容。


这是不准确的

4

用SLR vs LR生成的解析器表之间的基本区别在于,减少操作基于SLR表的Follows设置。这可能过于严格,最终会导致减少班次的冲突。

另一方面,LR解析器仅根据实际上可以跟随被缩减的非终端的终端集来减少决策。这组终端通常是此类非终端的“跟随”组的适当子集,因此与换档操作发生冲突的机会较小。

出于这个原因,LR解析器更强大。LR解析表可能非常大。

LALR解析器从构建LR解析表的想法开始,但是结合生成的状态的方式大大减少了表的大小。缺点是,对于某些语法,如果不使用LR表,可能会产生冲突的可能性很小。

LALR解析器的功能比LR解析器稍差一些,但仍比SLR解析器强大。由于这个原因,YACC和其他此类解析器生成器倾向于使用LALR。

PS为简洁起见,上面的SLR,LALR和LR的确表示SLR(1),LALR(1)和LR(1),因此暗示一个令牌先行。


4

SLR解析器识别LALR(1)解析器可以识别的语法的适当子集,而后者又可以识别LR(1)解析器可以识别的语法的适当子集。

这些语法中的每一个都构造为状态机,每个状态在解析输入时代表语法的一组生产规则(以及每个语法中的位置)。

不是SLR的LALR(1)语法的Dragon Book示例是这样的:

S → L = R | R
L → * R | id
R → L

这是该语法的状态之一:

S → L•= R
R → L•

所述指示在每个所述可能制作的解析器的位置。直到达到末尾并尝试减少时,它才知道实际在哪个生产中。

在这里,解析器可以移动=或减小R → L

单反(又名LR(0))解析器将决定它是否可以通过检查下一个输入符号是在减少后续集R(即集,可以按照语法所有终端R)。由于=也位于此集合中,因此SLR解析器遇到了shift-reduce冲突。

但是,LALR(1)解析器将使用所有可以遵循R的特定生成方式$(即输入的结束)的所有终端的集合。因此,没有冲突。

正如以前的评论者所指出的,LALR(1)解析器具有与SLR解析器相同的状态数。前瞻传播算法用于将前瞻从相应的LR(1)状态添加到SLR状态产生。生成的LALR(1)解析器可以引入LR(1)解析器中不存在的reduce-reduce冲突,但不能引入shift-reduce冲突。

在您的示例中,以下LALR(1)状态在SLR实现中导致移位减少冲突:

S → b d•a / $
A → d• / c

之后的符号/是LALR(1)解析器中每个生成的跟随集。在SLR中,follow(A包括a,也可以移动。



-2

一个简单的答案是,所有LR(1)语法都是LALR(1)语法。与LALR(1)相比,LR(1)在关联的有限状态机中具有更多的状态(状态的两倍以上)。这就是LALR(1)语法比LR(1)语法需要更多代码来检测语法错误的主要原因。关于这两种语法,要知道的另一件事是,在LR(1)语法中,减少/减少冲突可能更少。但是在LALR(1)中,减少/减少冲突的可能性更大。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.