【问题标题】:What advantages do LL parsers have over LR parsers?LL 解析器比 LR 解析器有什么优势?
【发布时间】:2010-11-03 22:36:19
【问题描述】:

LL 解析器与 LR 解析器相比有哪些优势以保证它们在 today's parser generator tools 中的相对受欢迎程度?

根据Wikipedia,LR 解析似乎比 LL 更有优势:

LR 解析比 LL 解析可以处理更大范围的语言,并且在错误报告方面也更好,即当输入不符合语法时,它会尽快检测到语法错误。这与 LL(k)(或者更糟糕的是,LL(*) 解析器)形成对比,后者可能会由于回溯而将错误检测推迟到语法的不同分支,这通常会使错误更难在具有长公共前缀的析取中定位.

注意:这不是家庭作业。当我发现 Antlr 是一个 LL 解析器生成器时,我感到很惊讶(尽管它的名称中有“LR”!)。

【问题讨论】:

  • 谈论解析器(而不是语法):LL(*) 可以用简单的递归方式编写。这是我书中的 +1。
  • @pst:没错,我只是希望“因为它们更容易实现”不是主要优势。 :)
  • 请注意,ANTLR 中的“LR”仅代表“语言识别”,与它接受的语法类别无关。
  • 其实在 Terrence Parr 的心目中,它代表的是Anti-LR。至少我记得有一次采访他,他承认这个内涵并非偶然。他说他专门创建了 ANTLR,因为他觉得几乎完全专注于 LR 解析是一个很大的错误,他厌倦了告诉所有人 LR 很烂,没有人在听,所以他决定创建世界上最好的解析器生成器,粉碎所有其他人,只留下 LL 站着……或类似的东西。
  • @Jörg:我刚刚给他发了电子邮件,邀请他参加这次讨论。他可能很忙,但也许他还会加入!

标签: parsing parser-generator lalr ll lr


【解决方案1】:

如果您想要解析树/森林并且不介意黑盒,GLR 非常棒。它可以让你输入任何你想要的CFG,代价是通过详尽的测试在解析时检查歧义,而不是静态地解决LR/LALR冲突。有人说这是一个很好的权衡。 Ira Baxter 的 DMS 工具或 Elkhound 具有免费的 C++ 语法,对这类问题很有用。 ANTLR 对于一大类语言应用程序也很有用,但它使用自上而下的方法,生成称为 LL(*) 的递归下降解析器,它允许语义谓词。我将在没有证据的情况下声明谓词允许您解析 CFG 之外的上下文相关语言。程序员喜欢在语法中插入动作,喜欢良好的错误处理,喜欢单步调试。 LL在这三个方面都很擅长。 LL 是我们手工完成的,因此更容易理解。不要相信wikipedia nonsense about LR being better at handling errors。也就是说,如果你用 ANTLR 回溯很多,LL(*) 的错误确实更糟(PEG 有这个问题)。

重新回溯。 GLR 也进行推测(即回溯),就像 PEG、ANTLR 和任何其他非确定性策略一样。在任何不确定的 LR 状态下,GLR “分叉”子解析器以尝试任何可行的路径。无论如何,LL 有很好的错误处理上下文。 LR 知道它匹配表达式,LL 知道它是赋值或IF-conditional 中的表达式; LR 知道它可能在其中一个,但不确定 - 而不确定性正是它的力量所在。

GLR 是O(n^3) 最坏的情况。 packrat/PEG 是O(n) 最坏的情况。由于循环前瞻 DFA,ANTLR 是 O(n^2),但实际上是 O(n)。真的没关系。 GLR 足够快。

ANTLRAN其他T工具,用于Lang R识别不是反LR,但我也喜欢那个;)

坦率地说,和很多 80 后的年轻程序员一样,我不了解 LALR,也不喜欢黑匣子(现在我挖掘 GLR 引擎的美,但仍然更喜欢 LL)。我构建了一个基于商业 LL(k) 的编译器,并决定构建一个工具来生成我手工构建的内容。 ANTLR 并不适合所有人,像 C++ 这样的边缘情况可能会更好地使用 GLR 处理,但很多人发现 ANTLR 适合他们的舒适区。自 2008 年 1 月以来,在 ANTLRWorks 中,ANTLR 的二进制 jar 和源 zip 总共有 134,000 次下载(根据 Google Analytics)。请参阅 LL(*) 上的 our paper,其中包含大量经验数据。

【讨论】:

  • 我当然可以欣赏逐行调试的能力!我还可以理解“上下文”的概念(也就是说,知道要解析的表达式包含在 if 语句中)。感谢您的回答,它肯定说明了 LL 解析器的一些优势!
  • @Terence:“黑匣子”有什么特点?所有解析器生成器和支持机制对用户来说都是谜团,这是正确的;大多数用户不想知道理论或齿轮。我购买了您的 ANTLR 超越 CFG 的“缺席证明”;但任何允许语义谓词的语法驱动引擎都是如此。实际上,它们都可以(至少是我遇到或建造的所有:),包括 DMS。单步是工具问题,而不是解析器生成器技术。错误恢复的质量具有相同的属性。
  • All:我同意 ANTLR 和大多数解析器生成器实际上都适合为“适度”语法构建解析器。当语法变得“强硬”时,区别就开始出现了。如果您可以控制语法并且可以更改它以消除棘手的情况,那么任何解析器生成器都可以。如果你不是,那么解析器生成器的强度真的很重要。在任何一种情况下,支持该工具的工程都确实有帮助。尽管我对 GLR 有偏见(在实践中它们也是 O(n)!),但我会第一个承认 Terence 在使 ANTLR 有效方面做得相当不错。
  • @Ira:我只是说 GLR 是一个黑盒子,因为它可以工作但不透明。另一个极端是递归下降解析器(由 ANTLR 生成),它不是黑匣子,因为它是程序员手动构建的并且可以单步调试。事实上,这并不完全正确。 LL(*) 允许循环 DFA 向前扫描,我为这些决策生成状态机(黑盒),但这只是用于替代生产预测而不是解析。
  • 更新。即将发表的关于自适应 LL()、ALL() 的 OOPSLA '14 论文,它采用任何语法,但有一个例外:没有 间接 左递归。处理e : e '*' e | INT ; 规则文件antlr.org/papers/allstar-techreport.pdf ALL(*) 是我解析的最后一句话。 25年,我终于破解了它。将 C11 规范开箱即用地完成了左递归,尽管在不调整语法的情况下进行解析的线性速度很慢。 Java 语法解析器 12,920 个文件,3.6M 行,大小 123M 的 Java 库在
【解决方案2】:

如果您必须编写代码,递归下降 (LL) 是您可以实际执行的操作;人们实际上无法手动构建 L(AL)R 解析器。

鉴于现代解析器生成器将为您处理所有解析器构造,并且空间不是什么大问题,我更喜欢 LR 解析器,因为您不必与语法作斗争以使它们对您有效特定的解析器生成器(没有“删除所有左递归”的愚蠢)。

事实上,我更喜欢GLR parsers,它几乎可以用上下文无关语法解析任何东西。不用担心左递归。没有转移/减少冲突的担忧。没有前瞻限制。

如果您想查看一个 GLR 解析引擎可以处理的语言范围(包括著名的难以解析的 LL/LALR 语言 C++),您可以查看 @ 987654322@.

【讨论】:

  • 您不必仅仅因为您能够通过解析器定义中的特定命令直接定义优先级而与语法作斗争,而不是因为 LR 方法对语法本身的要求较低。.跨度>
  • @Ira:非常有趣的帖子!我以前什至没有听说过 GLR 解析器。
  • LALR 的存在是因为对于许多实用语法而言,您获得的表比 LR 更小。您当然可以定义 LALR(3) 语法的概念,尽管在实践中没有人这样做。 GLR 的原因是你可以停止思考 K。
  • @Adam:维基百科网站上的参考资料是必读的,但很难找到“免费”;我有很多年前买的硬拷贝。 Adrian Johnstone 似乎在高级 GLR 解析方面做了大量工作,例如:portal.acm.org/citation.cfm?id=1149674。如果您是 ACM 会员,这是免费的。他的网站cs.rhul.ac.uk/research/languages/publications/… 可能有可免费访问的文档。
  • 自顶向下实现的解析器可以将kleene运算符从逻辑右递归转换为循环操作。但是人们仍然可以以复杂的方式编写涉及左递归的规则;我没有手头的例子,但我很高兴知道我只是不担心我如何写我的语法规则。如果您查看人们对自顶向下解析器的抱怨,即使是那些使用 kleene 星号表示法的解析器,您几乎总是会发现他们试图找到一种方法来摆脱一些不方便的左递归。为什么要担心呢?
【解决方案3】:

根据我的个人经验(我在各种情况下都使用过),最实际的区别是,使用 LL(k),您可以以更简单的方式定义语法(因为它是自上而下的)而无需关心很多LR解析器经常发生可能的reduce-reduce或shift-reduce冲突。您唯一需要关心的是必须将左递归转换为右递归。

另一件事是,自上而下的方法通常意味着更高的复杂性(无论是空间还是时间),因为它必须在解析时存储整个树,并且在解决歧义之前它会增长很多。

【讨论】:

  • 我明白了......你说最实际的区别是 LL 解析器避免了 LR 解析器中常见的冲突。这是否仅适用于将其前瞻限制在一个小数字(例如,LR(1) 或 LALR(1))的 LR 解析器?我认为 LR(k) 解析器没有那么多冲突......
【解决方案4】:

我所熟悉的唯一优点是您可以轻松地手动编写 LL 解析器。 LR 解析器更难手动编码(您通常使用解析器生成器)。

【讨论】:

  • 如果你有这样的发电机,生产它们真的很容易。
【解决方案5】:

LL Parsing 的最坏情况复杂度是 O(n^4),而 LR 解析的最坏情况复杂度更好,O(n^3)。

(但没有人会写 O(n^4) 语法。)

https://en.wikipedia.org/wiki/Top-down_parsing

【讨论】:

    【解决方案6】:

    想到的一个原因是,在 LL 范式中开发一种需要任意回溯(C++)的语言要容易得多。

    【讨论】:

    • Cough C++ 不需要任意回溯。 GLR 解析器可以很好地做到这一点。看我的回答。
    • 我的意思是,对于任何有限的 k,C++ 都不是 LL(k) 或 LR(k),因为声明/表达式有歧义。
    • 嗯,以纯粹的方式解析 C++(不是 GCC-resolves-types-during-parsing hack)需要解析器也接受模棱两可的解析。 LL/LR 解析根本不这样做。 GLR 解析器可以。稍后您可以使用符号表信息解决歧义,允许您将解析与名称和类型解析分开,从而为您提供更清晰的解析器。
    • @Zack 和@Ira:多么有趣的对话!
    • @Zack:阅读我的回答。查看链接。 。我们完全避免了这种黑客攻击。我们用它来进行大规模 C++ 系统的修改。
    【解决方案7】:

    根据 Laurence Tratt 的说法,LL 解析器有一个小而重要的利基,如果你需要的话:

    尽可能高的性能和/或尽可能好的错误消息。

    并手动编写递归下降解析器来完成此操作。

    对于一个现实的编程语言语法,通常需要很多人几个月的努力才能击败自动生成的解析器。

    但是:

    LL 解析器在很大程度上没有吸引力,因为缺少左递归 使表达许多标准编程语言结构变得很尴尬。

    因此他的结论是,可以很好地处理左递归的 LR 解析是可行的方法。

    要对此进行更全面的审查,我推荐 Laurence Tratt 的优秀文章 Which Parsing Approach?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-05
      • 2010-09-19
      • 2011-02-10
      相关资源
      最近更新 更多