86

我最近试图自学解析器(用于语言/上下文无关语法)是如何工作的,除了一件事之外,其中大部分似乎都是有意义的。我特别关注LL(k) 语法,其中两个主要算法似乎是LL 解析器(使用堆栈/解析表)和递归下降解析器(仅使用递归)。

据我所知,递归下降算法适用于所有 LL(k) 语法,甚至可能更多,而 LL 解析器适用于所有 LL(k) 语法。然而,递归下降解析器显然比 LL 解析器要简单得多(就像 LL 解析器比 LR 解析器更简单一样)。

所以我的问题是,使用任何一种算法时可能会遇到哪些优势/问题?为什么有人会选择 LL 而不是递归下降,因为它适用于同一组语法并且实现起来更棘手?

4

1 回答 1

114

LL 通常是比递归下降更有效的解析技术。事实上,在最坏的情况下,一个朴素的递归下降解析器实际上是O(k^n)(其中n是输入大小)。一些技术,如 memoization(产生Packrat解析器)可以改进这一点,并扩展解析器接受的语法类别,但总是需要权衡空间。LL 解析器(据我所知)总是线性时间。

另一方面,您的直觉是正确的,递归下降解析器可以处理比 LL 更多的语法。递归下降可以处理任何 LL(*) 的语法(即无限前瞻)以及一小组模棱两可的语法。这是因为递归下降实际上是 PEG 或Parser Expression Grammar(s)的直接编码实现。具体来说,析取运算符 ( a | b) 不可交换,即a | b不等于b | a。递归下降解析器将按顺序尝试每个备选方案。因此,如果a匹配输入,即使匹配输入也会b 成功。这允许经典的“最长匹配”歧义,如悬空else只需正确排序析取即可处理问题。

综上所述,可以使用递归下降实现 LL(k) 解析器,使其在线性时间内运行。这是通过基本上内联预测集来完成的,以便每个解析例程在恒定时间内确定给定输入的适当生成。不幸的是,这种技术消除了整个语法类别的处理。一旦我们进入预测解析,像悬空这样的问题else就不再那么容易解决了。

至于为什么选择 LL 而不是递归下降,主要是效率和可维护性的问题。递归下降解析器明显更容易实现,但它们通常更难维护,因为它们所代表的语法不以任何声明形式存在。大多数重要的解析器用例都使用解析器生成器,例如 ANTLR 或 Bison。使用这些工具,算法是直接编码的递归下降还是表驱动的 LL(k) 并不重要。

作为一个有趣的问题,还值得研究recursive-ascent,它是一种按照递归下降的方式直接编码的解析算法,但能够处理任何 LALR 语法。我还将深入研究解析器组合器,这是一种将递归下降解析器组合在一起的功能性方式。

于 2009-06-25T15:45:22.570 回答