LALR和LR解析有什么区别?

时间:2013-10-29 16:09:17

标签: parsing compiler-construction context-free-grammar lalr lr

我理解LR和LALR都是自下而上的解析算法,但两者之间有什么区别?

LR(0),LALR(1)和LR(1)解析之间有什么区别?如何判断语法是LR(0),LALR(1)还是LR(1)?

3 个答案:

答案 0 :(得分:56)

在较高级别,LR(0),LALR(1)和LR(1)之间的差异如下:

  • LALR(1)解析器是"升级后的#34; LR(0)解析器的一个版本,它跟踪更精确的信息以消除歧义语法。 LR(1)解析器是一个功能更强大的解析器,可以跟踪比LALR(1)解析器更精确的信息。

  • LALR(1)解析器是一个大于LR(0)解析器的常数因子,LR(1)解析器通常比LALR(1)解析器指数大。

  • 任何可以用LR(0)解析器解析的语法都可以用LALR(1)解析器解析,任何可以用LALR(1)解析器解析的语法都可以用LR解析( 1)解析器。有一些语法是LALR(1)但不是LR(0)和LR(1)而不是LALR(1)。

更正式地说,LR(k)解析器是一个自下而上的解析器,它通过维护一堆终端和非终端来工作。解析器由有限自动机控制,该自动机基于解析器的当前状态和下一个输入的k个令牌来确定是否将新令牌移位到堆栈上或 reduce 反向应用生产的堆栈顶部符号。

为了跟踪足够的信息以确定是否移位或缩小,LR(k)解析器使每个状态对应于一个配置集,"一组带有以下信息的作品:

  • 到目前为止已经看到了多少产品,
  • 制作完成后需要什么代币( lookahead

这些信息中的第一条用于确定解析器是否需要进行缩减 - 如果当前状态中的所有制作都没有完成,则没有理由进行缩减。在进行减少时使用这些信息中的第二条以确定是否应该执行减少。在决定是否减少时,LR(k)解析器查看输入流的下一个k标记。如果它们与前瞻标记匹配,则解析器将减少,否则解析器不执行任何操作。

当解析器在给定状态下应该执行的操作存在冲突时,LR(k)解析器会出现问题。当解析器处于生产已完成的状态时,会出现一种类型的冲突,即转移/减少冲突,但该生产冲突的先行符号也被另一个未完成的生产用于国家。这意味着解析器无法判断是否执行缩减。第二种类型的冲突是减少/减少冲突,其中解析器知道它必须进行减少,但是可以进行两次或更多次减少,并且无法确定要做什么。< / p>

直观地说,随着k变得越来越大,解析器可以获得越来越精确的信息,以确定何时移位以及何时缩小。例如,如果语法不是LR(0),则解析器可能具有一个状态,在该状态下根本不给它前瞻,它不能确定是移位还是减少。然而,该语法可能仍然是LR(1),因为给予额外的前瞻标记,它可能能够识别它应该明确地移动而不是减少或者肯定减少而不是移位。

LR(k)解析器的问题在于随着k变大,状态数可以指数增加。 LR(k)解析器中的前瞻是通过在解析器中构建越来越多的状态来处理,以对应于生产和前瞻的不同组合,因此可能的前瞻数量增加,状态数量也增加。因此,LR(1)解析器通常太大而不实用,LR(2)或更高在实践中几乎闻所未闻。

LALR(1)被发明为LR(0)解析器的空间效率和LR(1)解析器的表达能力之间的折衷。有几种方法可以考虑LALR(1)解析器是什么。最初,LALR(1)解析器被指定为将LR(1)自动机转换为较小自动机的转换。虽然LR(1)解析器可能具有比LR(0)自动机更多的状态,但唯一的区别是LR(1)解析器可能具有LR(0)自动机中任何特定状态的多个副本,每个都注释有不同的前瞻信息。可以通过从LR(1)解析器开始,然后将具有相同&#34;核心&#34;的所有状态组合在一起来形成LALR(1)解析器。 (一组制作及其位置),然后将所有前瞻信息汇总在一起。这导致解析器具有与LR(0)解析器相同的状态数,但保留有关前瞻的一些信息以帮助避免LR冲突。

LALR(1)语法的另一种观点使用&#34; LALR-by-SLR&#34;方法。 LALR(1)解析器可以通过从语法的LR(0)解析器开始构造,然后为该语言创建一个新语法,该语法使用有关它们对应的LR(0)解析器中的状态的信息来注释非终结符。然后可以使用有关该语法中非终结符的FOLLOW集的信息来计算LR(0)解析器中的前瞻。

最终结果是

  • LR(0)解析器很小,但不是很有表现力。
  • LALR(1)由于前瞻信息,解析器略大,但非常富有表现力。
  • LR(1)解析器很大,但非常富有表现力。

关于你的第二个问题 - 你如何确定语法是LR(1)还是LALR(1) - 标准方法是尝试为LR(1)解析器和LALR(1)构建解析自动机解析器并检查冲突。要构建LR(1)解析器,您需要构建LR(1)配置集,然后检查这些配置集中的任何一个是否具有shift / reduce冲突或减少/减少冲突。要构造LALR(1)解析器,您可以构建LR(1)解析器,然后使用相同的核心压缩配置集,也可以使用基于该语言的LR(0)解析器的LALR-by-SLR方法。大多数编译器教科书都提供了有关如何构建这些配置集的更多详细信息。您还可以查看the lecture notes from a compilers course I taught in Summer 2012,其中包含所有上述解析方法和其他一些解析方法。

希望这有帮助!

答案 1 :(得分:1)

良好的LALR(1)解析器的解析算法在两个方面有所不同:(1)它应该具有shift-reduce动作,这会将状态数减少约30%并使解析器更快,并且(2) )当检测到语法错误时,它必须执行一次或多次缩减,这使得错误恢复更加复杂。

规范LR(1)解析器(1)的解析算法没有移位减少动作,(2)在检测到语法错误时不进行任何减少,这使得错误恢复更简单。

还有另一种情况,称为最小LR(1),它使用与LALR(1)相同的解析算法和错误恢复算法。最小LR(1)解析器提供LR(1)的功率,并且它们的大小几乎与LALR(1)一样小。 LRSTAR Parser Generator为C ++程序员创建了最小的LR(1)解析器。

答案 2 :(得分:0)

LR(0),SLR(1),LALR(1)解析器都具有相同数量的状态。如果语法需要,最小LR(1)解析器将具有更多状态,以避免减少 - 减少冲突。

Canonical LR(1)解析器将有更多的状态,对于中型或大型计算机语言来说太多了。

SLR(1)解析器生成器构建LR(0)状态机,并通过检查语法(可能报告错误的冲突)来确定k = 1前瞻。

LALR(1)解析器生成器构建LR(0)状态机并通过检查LR(0)状态机(这是非常复杂的)来确定k = 1前瞻。

Canonical LR(1)解析器生成器构建LR(1)状态机。

最小LR(1)解析器生成器构建LR(1)状态机并在构建过程中合并兼容状态。