【问题标题】:Choice of Parser Generator [closed]解析器生成器的选择[关闭]
【发布时间】:2014-03-01 00:40:24
【问题描述】:

好的,我知道这个问题可能听起来很基于意见,但是由于我有几个特定的​​选择标准,我认为它非常适合 SO。所以,我来了……

我过去曾多次使用编译器/解释器构建(显然主要是作为一种爱好),出于某种原因,我坚持使用 Lex/Yacc(或 Flex/Bison,我对它们如何使用感到很困惑)现在打电话给他们...大声笑)。

但是,由于我发现自己目前正在玩另一个业余口译项目,我想我应该尝试一些不同的东西,也许是为了避免我不喜欢 Lex/Yacc。

所以,即:

  • C++ 友好(优于 C 友好)
  • 良好的文档(最好有一些已经实现的现有语法 + 关于如何编译/使用它们的说明 - 听起来很明显,对吧?)
  • 可能是 LALR、LL(*)、递归下降,我真的不在乎注意:输入您喜欢哪种类型以及哪种类型的实现不过会很棒;老实说,我从来没有真正理解过它们的优缺点,尽管我确实知道它们指的是什么)
  • 将 Lexer 部分和 Parser 语法合并到一个文件中 一点也不差;从来没有真正明白为什么它必须一分为二。
  • 最后但并非最不重要的一点:我一直遇到...问题。我的意思是 - 至少就 Lex/Yacc 而言,解析错误消息或多或少是神秘的(Syntax Error... Yuhuu!)而且它们很少有助于诊断问题。 (好吧,除非你是开发解释器的人......哈哈)。那么,关于错误报告,还有什么比 Lex/Yacc 更好的吗?

好的,我希望这不是太冗长。我全是耳朵! :-)

【问题讨论】:

  • 你应该在软件推荐上询问这个问题,stackexchange.com 上的一个新网站。
  • 供参考:解析表达式语法是同时指定词法分析器和解析器的另一种好方法。如果你不能使用像 ANTLR 这样的东西,那会很方便。 PEG 启用上下文相关的词法分析器规则。 PEG 的至少一个值得注意的 C++ 实现是 Dr. Hirsch 的 PEGTL,这是一种在 C++11 中获得 MIT 许可的无依赖的仅标头 PEG 词法分析器/解析器实现。词法分析器和解析器使用相同的语言描述,并且全部使用纯 C++ 表达式编写,无需单独运行工具。

标签: c++ parsing bison lex parser-generator


【解决方案1】:

自 1969 年以来,我一直在构建解析器生成器和解析器。

递归下降、YACC 和 JavaCC 是你听到的典型答案。

这些是您祖父的解析器生成器,它们会受到语法的限制。总是,(尤其是在 Stack Overflow 上),一些可怜的灵魂会问“我如何解决这个移位/减少”问题(对于像 YACC 这样的 LR 解析器生成器)或“我如何消除左递归”(对于递归下降或 LL 解析器生成器,比如JavaCC)。更糟糕的是,它们无法处理真正具有句法歧义的语法,就像在大多数复杂语言中一样。

GLR(和 GLL)解析器允许您编写上下文无关的语法……并解析它们,无需大惊小怪。这是真正的生产力提升。这是有代价的:你可能会得到模棱两可的解析,但有办法处理它。 (见discussion of C++ parsing troubles that neither YACC nor JavaCC can handle by themselves)。

Bison(广泛使用)有一个GLR option;用它!最近的多语言程序操作工具似乎都使用 GLL 或 GLR。我们的 DMS 软件再造工具包使用 GLR 并解析 C++(MS 和 GNU 变体中的完整 C++14!)、Java、COBOL 和大量其他复杂语言; GLR 是我职业生涯中做出的最佳技术选择之一。 Stratego 使用 GLR。我认为 RascalMPL 使用 GLL。 Scott McPeak 的 Elkhound GLR 解析器生成器是基于 C++ 的,并且我很确定会生成 C++ 代码(OP 要求提供基于 C++ 的答案)。

这些天的热门话题是 PEG 和 ANTLR4。这些比 LL 或 LR 解析器更好,但在试图塑造语法时仍然会让人感到悲伤。 (对于 PEG,您必须订购产品,假设您可以找到这样的订单,以处理具有优先级的模棱两可的规则。使用 ANTLR4,您仍然需要指定前瞻来解决歧义;我不知道它如何处理无限前瞻)。 AFAIK,没有人使用这两种技术构建实用的 C++ 解析器,所以他们没有辜负他们的声誉。

我认为 GLR 和 GLL 是更好的答案。

【讨论】:

  • 好吧,想象一下我认为我±20年的经验听起来相当不错!哈哈。非常感谢您的意见!我一定会看看 GLR。至于 ANTLR4,是的,我已经尝试过了,不过(并不是说它不容易使用)似乎我对 Bison 感觉最舒服...... :-)
  • 您好,我刚刚阅读了您的帖子,您能否向以前从未在该领域做过任何事情的人提供有关从何处开始编写解析器/词法分析器的建议?在什么情况下我可以实现一些具有挑战性的东西,可能会遇到很多错误,但要了解事情是如何运作的?也许您可以建议使用特定的工具/语法(EBNF 或 PEG)来实现一种真实的语言,因为根据您的经验,这个假设的练习是一个很好的练习?或者做一些练习来真正理解 LL 和 LR,除了 EBNF 和 PEG 宗教的东西。
  • 简短的回答:选择其中任何一个任务并执行它。在你的情况下,我会选择我最喜欢的语言,然后为它构建一个词法分析器;你会发现这比你想象的更难(细节方面)和更容易(机械)。注意“空白”。如果你克服了这个问题,我会选择 ANTLR 并尝试编写一个解析器。这些将提供良好的基础经验和基础。
  • @user2485710 Levine/Mason/Brown Lex&Yacc(pdf 链接!)这本书是一本不错的介绍。它包括诸如完整的 SQL 解析器之类的实际示例,并演示了许多在一个地方很难找到的交易技巧。我发现它是进入当代野牛的绝佳跳板。
  • 感谢您分享所有这些。这应该是最佳答案。
【解决方案2】:

我只回答最后一个问题,稍作修改:

至少就 Lex/Yacc 而言,解析错误消息或多或少是神秘的(语法错误...... Yuhuu!)并且它们很少帮助诊断问题。 (好吧,除非你是开发解释器的人......哈哈)。那么,在错误报告方面,有没有 更好的使用方式 Lex/Yacc?

好吧,从使用现代版本的 bison 开始,它具有相当完整的在线 manual(并且很可能与可执行文件一起安装,具体取决于您安装 bison 的方式)。特别是,从这些声明开始:

%define parse.error verbose
%define parse.lac full

这至少会将神秘的“语法错误”错误替换为“预期的”令牌类型列表。

然后确保您的令牌类型具有有意义的名称,因为它们将作为错误消息的一部分呈现给用户。如果您习惯使用IDENTIFIER 作为终端,那么您可能没问题,但是“预期 TOK_YY_ID”消息有点令人讨厌。您可以在type 声明中为终端声明一个可读的:

%type TOK_YY_ID "identifier"

这只会带您到此为止。在许多情况下,知道什么是“预期的”就足以理解语法错误,但有时更明确是有用的。在这种情况下,实际定义error 规则很有用。正确处理这些问题与其说是科学,不如说是一门艺术,但所有错误报告/恢复方法都是如此;关键是要尽可能具体地说明错误的语法是什么样的,而不是比必要的更具体。

一种有趣的错误报告方法是使用当前解析器状态和前瞻标记(两者在错误报告时都可见)来查找自定义错误消息(如果存在)。我认为这种方法长期以来一直是编译器民间传说的一部分,而且我敢肯定,几十年来我已经看过几篇关于它的文章。这是Russ Cox 的一篇相对较新的文章。

【讨论】:

  • 非常感谢您的意见!我一定会看看你的建议! ;-)
【解决方案3】:

有趣的问题 - 不确定我对你的实际问题有很好的答案,但我的“评论”有点太长了,无法发表评论......

我正在使用 Pascal 编译器,如果我自己可以这么说的话,我已经用大约 1100 行代码编写了 Lexer、Tokenizer 和 Parser(包括生成 AST 以进入 LLVM 的代码生成器),非常“漂亮”的 C++ 代码 - 全部手动完成。它对生成良好的错误消息更加友好,并且有所帮助。缺少一些位,在我的编译器完成之前我还有很多工作要做,但我可以编译一些相当复杂的代码。

我承认,我从未将 Lex/Yacc 或 Flex/Bison 用于任何真正的东西。我有时看过它,但我发现很难使用这些工具,你要么最终获取生成的代码并修改它(自动生成代码的坏主意),要么错误处理不好,并且难以在上面调试代码那个。但是后来,我只花了大约两个小时试图找到一个由于过早“吃”分号而导致的错误,进而导致解析器在令牌流中迷失......

【讨论】:

  • 好吧,说我没有想过(像一百万次)自己从头开始编写实际的词法分析器/解析器,这是一个谎言。我就是这样......重新发明轮子类型的编码器(我们正在谈论编译器构造,所以这应该很明显,嗯?哈哈)。但是,我仍然相信解析器生成器(除了错误消息,如果我不得不挑出他们的一个主要缺陷)确实可以使解析部分(这不是编译器的核心部分,恕我直言) 好简单。这或多或少就像立即将您的想法从纸上转换为可解析的语法... :-)
  • 旁注: 正如您可能已经从我上一条评论中猜到的那样,我最感兴趣的是语言(+编译器)构造(我的意思是:不为现有语言),因此能够非常“直观地”构建我自己的 EBNF 语法,大大提高了生产力。
  • 抱歉,帮不上忙。我确实有一个快速的谷歌搜索我的一位同事写的东西,但没有发现任何特别好的东西。他一直在研究用 ML 编写的东西,几乎可以满足您的要求,包括具有用于以图形方式显示语言语法的内置文档。
  • 我也对编译器感兴趣,但更多的是实用的方式——在工作中,我们有编译器技术(尤其是我使用 OpenCL),我想更多地了解我们的编译器是如何工作的有效 - 所以编写我自己的编译器是了解其核心组件 LLVM 是如何工作的一种方式。
  • 但请注意,Pascal 经过精心设计,易于自上而下解析(并且在一般过程中),从头到尾。难怪手工编写解析器很容易。
猜你喜欢
  • 2011-09-06
  • 1970-01-01
  • 1970-01-01
  • 2011-10-23
  • 2013-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多