分析器的选择

时间:2014-03-01 00:40:25

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

好的,我理解这个问题可能听起来很基于意见,但是由于我有几个特定的​​选择标准,我认为它会很适合SO。所以,我在这里......

我过去曾经使用编译器/解释器构建很多(显然主要是作为一种爱好)并且由于某种原因我坚持使用Lex / Yacc(或Flex / Bison,我对他们如何现在打电话给他们......哈哈)。

然而,由于我发现自己目前正在玩另一个业余爱好者翻译项目,我认为我应该尝试一些不同的东西,以避免我不喜欢Lex / Yacc。

所以,即:

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

3 个答案:

答案 0 :(得分:24)

自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是更好,更好的答案。

答案 1 :(得分:8)

我只想回答最后一个问题,稍加编辑:

  

至少就Lex / Yacc而言,解析错误消息或多或少是神秘的(语法错误...... Yuhuu!),很少有人帮助诊断问题。 (好吧,除非你是开发翻译的人......哈哈)。那么,是否比 更好地使用 Lex / Yacc进行错误报告?

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

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

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

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

%type TOK_YY_ID "identifier"

这只会带你到目前为止。在许多情况下,了解“期望”的内容足以理解语法错误,但有时更明确一些是有用的。在这种情况下,实际定义error规则很有用。获得这些权利更像是一门艺术,而不是一门科学,但所有错误报告/恢复方法都是如此;关键是要尽可能具体地说明错误的语法是什么样的,而不是更具体的必要。

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

答案 2 :(得分:2)

有趣的问题 - 不确定我对你的实际问题有一个很好的答案,但是我的“评论”对于评论来说有点太长了......

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

我承认,我从来没有使用Lex / Yacc或Flex / Bison做任何真实的事情。我有时看过它,但我发现很难使用这些工具,你要么最终采用生成的代码并修改它(使用自动生成的代码的坏主意),或者错误处理错误,并且难以调试代码那个。但是,我只花了大约两个小时试图找到因过早地“吃”分号而导致的错误,从而导致解析器在令牌流中丢失......