LL和递归下降解析器之间的区别?

Nol*_*rin 72 grammar parsing recursive-descent context-free-grammar ll-grammar

我最近正在努力教自己解析器(语言/无上下文语法)是如何工作的,除了一件事以外,大部分解析器似乎都有意义.我特别关注LL(k)语法,其中两个主要算法似乎是LL解析器(使用堆栈/解析表)和递归下降解析器(简单地使用递归).

据我所知,递归下降算法适用于所有LL(k)语法,可能更多,而LL解析器适用于所有LL(k)语法.然而,递归下降解析器显然要比LL解析器简单得多(正如LL一个比LR一个简单).

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

Dan*_*wak 94

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

另一方面,你的直觉是正确的,即递归下降解析器可以处理比LL更大的语法类.递归下降可以处理任何LL(*)(即无限前瞻)语法以及一小组模糊语法.这是因为递归下降实际上是PEG或Parser Expression Grammar的直接编码实现.具体来说,析取运算符(a | b)不是可交换的,意味着a | b不相等b | a.递归下降解析器将按顺序尝试每个替代方案.因此,如果a输入相匹配,它会succede即使b 匹配的输入.这样可以else简单地通过正确排序析取来处理经典的"最长匹配"歧义,例如悬挂问题.

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

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

令人感兴趣的是,它也值得研究递归上升,它是一种在递归下降之后直接编码的解析算法,但能够处理任何LALR语法.我还将深入研究解析器组合器,它是一种将递归下降解析器组合在一起的功能性方法.

  • 预测集:它实际上取决于所使用的策略.如果您*独占*依赖于那些预测集并且不执行回溯,则语法类精确地为LL(k),其中k是用于计算预测集的前瞻量.但是,通过在RD中内联预测集而不完全消除回溯,您可以获得预测解析的许多好处.这允许解析器接受通常由RD处理的所有语法,但具有更快的平均情况.不幸的是,这种解析器的最坏情况仍然是指数级的. (6认同)
  • 大多数递归下降解析器(甚至是手写的解析器)将使用内联预测集来限制*替换,限制回溯而不限制灵活性.最终的结果是一个解析器,除了大多数病态语法之外几乎都是线性时间,并且仍然接受整个PEG类. (5认同)
  • 好东西.但是有一点挑剔:"大多数非常重要的用例都是在某种解析器生成器中实现的......".这不是真的.最广泛使用的编译器和IDE(C#,VB,Visual C++和GCC就是很好的例子)使用手写解析器.这些可以说是一些最重要的用途. (4认同)
  • @DanielSpiewak我知道你在几年前发布了这条评论,但即使在那时它也是错的;)GCC使用bison进行C-parser直到3.x,但是尽可能在2008年改用手写的递归下降解析器告诉你这个http://gcc.gnu.org/wiki/New_C_Parser (3认同)
  • 我非常希望得到的回应!:)感谢那里的所有信息(包括最后一点,我甚至都不知道).在我理解你在这个答案中提出的所有概念之前,可能需要更多的阅读,但你肯定已经回答了我的问题并指出了正确的方向,需要进一步研究.我现在模糊的主要问题是PEG如何与递归下降解析器相关,以及解析器组合器如何组合各种解析器.如果你能澄清其中一个/两个,我将非常感激. (2认同)
  • @DanielSpiewak"LL通常是一种比递归下降更有效的解析技术." 真的吗?解析LL语法时,递归下降永远不需要回溯,对吧?(我维护一个递归下降解析器;它只在语法明确非LL的地方回溯.) (2认同)