GCC和Clangparsing器是否真的是手写的?

看来GCC和LLVM-Clang使用手写recursion下降parsing器 ,而不是机器生成的,基于Bison-Flex的自底向上parsing。

请问有人可以证实这是事实吗? 如果是这样,为什么主stream编译器框架使用手写parsing器?

更新 : 这里有关这个话题的有趣的博客

是:

有一个民间定理说C很难parsing,C ++实际上是不可能的。

这不是真的。

真正的是,使用LALR(1)parsing器很难parsingC和C ++,而不会破解parsing机器并在符号表数据中缠结。 海湾合作委员会实际上用来parsing他们,使用YACC和这样的额外hackery,是的,这是丑陋的。 现在GCC使用手写parsing器,但仍然与符号表hackery。 铿锵人从来没有尝试过使用自动parsing器生成器; AFAIK Clangparsing器一直是手工编码的recursion下降。

什么是真正的,C和C ++是相对容易parsing与更强大的自动生成的分析器,如GLR分析器 ,你不需要任何黑客。 Elsa C ++parsing器就是这样一个例子。 我们的C ++前端是另一个(就像我们所有的“编译器”前端一样,GLR是非常棒的parsing技术)。

我们的C ++前端没有GCC那么快,肯定比Elsa慢; 我们已经没有多less精力来仔细调整它,因为我们还有其他更紧迫的问题(它已被用于数百万行C ++代码中)。 Elsa可能比GCC慢,只是因为它更一般。 鉴于目前处理器的速度,这些差异在实践中可能并不重要。

但今天广泛分发的“真正的编译器”,其根源在于10年或20年以前的编译器。 效率低下更重要,没有人听说过GLRparsing器,所以人们做了他们知道该怎么做的事情。 铿锵肯定是最近的,但是民间定理长期以来一直保持着“说服力”。

你不必这样做了。 您可以非常合理地使用GLR和其他类似的parsing器作为前端,并提高编译器的可维护性。

什么真实的,是得到一个符合你友好的邻居编译器行为的语法是困难的。 尽pipe几乎所有的C ++编译器都实现了原始标准的大部分,但是他们也往往会有很多黑暗的angular落扩展,例如MS编译器中的DLL规范等。如果你有一个强大的parsing引擎,你可以花时间去试图获得最终的语法要符合现实,而不是试图弯曲你的语法来匹配parsing器生成器的限制。

编辑2012年11月:自写这个答案以来,我们改进了我们的C ++前端,以处理完整的C ++ 11,包括ANSI,GNU和MS变体方言。 虽然有很多额外的东西,我们不必改变我们的parsing引擎; 我们只是修改了语法规则。 我们必须改变语义分析; C ++ 11在语义上非常复杂,而且这项工作使得parsing器能够运行。

编辑2015年2月:…现在处理完整的C ++ 14。 (请参阅从c ++代码中获取可读的AST代码,以便parsing简单的代码,以及C ++的臭名昭着的“最令人头疼的parsing”)。

编辑2017年4月:现在处理(草稿)C ++ 17。

Clang的parsing器是一个手写的recursion下降parsing器,还有其他几个开源和商业C和C ++前端。

Clang使用recursion下降parsing器有几个原因:

  • 性能 :手写的parsing器使我们能够编写一个快速parsing器,根据需要优化热path,并始终控制性能。 拥有一个快速的parsing器使得Clang可以用于其他开发工具,通常不使用“真正的”parsing器,例如,IDE中的语法高亮和代码完成。
  • 诊断和错误恢复 :因为您完全掌握了一个手写recursion下降parsing器,所以很容易添加特殊情况来检测常见问题并提供出色的诊断和错误恢复(例如,请参阅http://clang.llvm .org / features.html#expressivediags )使用自动生成的parsing器,您仅限于生成器的function。
  • 简单 :recursion下降分析器很容易编写,理解和debugging。 您不需要成为parsing专家,也不需要学习新的工具来扩展/改进parsing器(这对开源项目来说尤其重要),但仍然可以获得很好的结果。

总的来说,对于一个C ++编译器来说,它并不重要:C ++的parsing部分并不重要,但它仍然是一个比较容易的部分,所以简单一些就够了。 语义分析—尤其是名称查找,初始化,重载parsing和模板实例化—比parsing要复杂得多。 如果需要certificate,请查看代码的分发情况,并在Clang的“Sema”组件(用于语义分析)与其“Parse”组件(用于parsing)中提交。

gcc的parsing器是手写的。 。 我怀疑clang是一样的。 这可能是由于几个原因:

  • 性能 :您为特定任务手动优化的东西几乎总是比一般解决scheme执行得更好。 抽象通常会有性能问题
  • 时机 :至less在海合会的情况下,海合会早于大量的免费开发工具(1987年问世)。 当时没有yacc等的免费版本,我想这对于FSF的人来说是优先考虑的。

这可能不是“这里没有发明”综合征的情况,而是更多的是“没有什么特别优化我们所需要的,所以我们写了我们自己的”。

奇怪的答案在那里!

C / C ++语法不是上下文无关的。 由于Foo *条,它们是上下文敏感的; 歧义。 我们必须build立一个typedef的列表来知道Foo是否是一个types。

Ira Baxter:我没有看到你的GLR事情的重点。 为什么要构build一个包含歧义的parsing树。 parsing意味着解决歧义,构build语法树。 第二遍你解决了这些歧义,所以这并不难。 对我来说,这更丑陋

Yacc是LR(1)parsing器生成器(或LALR(1)),但它可以很容易地修改为上下文相关。 它里面没有什么难看的东西。 Yacc / Bison已经被创build来帮助parsingC语言,所以可能它不是生成C语法分析器的最丑陋的工具。

在GCC 3.x之前,Cparsing器是由yacc / bison生成的,在parsing过程中生成了typedefs表。 用“in parse”typedefs表格构build,C语法成为本地上下文无关的,此外“本地LR(1)”。

现在,在Gcc 4.x中,它是一个recursion下降parsing器。 它与Gcc 3.x中的parsing器完全相同,它仍然是LR(1),并且具有相同的语法规则。 不同之处在于yaccparsing器已被手改写,shift / reduce现在隐藏在调用堆栈中,并且没有“state454:if(nextsym =='(')goto state398”,就像在gcc 3.x中一样yaccparsing器,因此更容易打补丁,处理错误和打印更好的消息,并且在parsing过程中执行一些下一个编译步骤。以gcc noob的“易于阅读”代码的代价为代价。

他们为什么从yacc切换到recursion下降? 因为避免yaccparsingC ++是非常必要的,并且因为GCC梦想成为多语言编译器,即它可以编译的不同语言之间共享最多的代码。 这就是C ++和C语法分析器以相同的方式编写的原因。

C ++比C更难parsing,因为它不是C(本地)LR(1),它甚至不是LR(k)。 看看func<4 > 2> ,它是一个用4> 2实例化的模板函数,即func<4 > 2>必须被读为func<1> 。 这绝对不是LR(1)。 现在考虑func<4 > 2 > 1 > 3 > 3 > 8 > 9 > 8 > 7 > 8> 。 (parse_template_parameter是不明确的parsing器函数,如果parse_template_parameter(17tokens)失败,请再次尝试parse_template_parameter(15tokens),parse_template_parameter(13tokens)…直到有用)。

我不知道为什么不能添加到yacc / bisonrecursion子语法中,也许这将成为gcc / GNU分析器开发的下一步?