为什么有些编译器更喜欢手工制作的解析器而非解析器生成器?

时间:2013-03-28 02:29:30

标签: parsing compiler-construction vala

根据Vala文档:“在0.3.1之前,Vala的解析器是经典的flex扫描器和Bison LALR解析器组合。但是从commit eba85a开始,解析器是一个手工制作的递归下降解析器。” 我的问题是:为什么?

问题可以解决任何不使用解析器生成器的编译器。从解析器生成器到手工解析器的这种转变的优缺点是什么?将解析器生成器(Bison,ANTLR)用于编译器有什么缺点?

作为旁注:我对Vala特别感兴趣,因为我喜欢使用具有现代功能和清晰语法的语言,但可编译为“本机”和“非托管”高级语言(在Vala的情况下为C) 。到目前为止我只发现了Vala。我想通过使Vala(或类似语言)可编译为C ++(由Qt libs支持)来获得乐趣。但是因为我不想发明全新的语言,所以我想考虑一些现有的语法。显然,手工制作的解析器没有我可能重用的书面形式语法。你对这个想法的评论是受欢迎的(整个想法是愚蠢的吗?)。

5 个答案:

答案 0 :(得分:24)

在我的职业生涯中,我已经编写了六个手工制作的解析器(在大多数情况下是递归下降解析器AKA自上而下解析器),并且看过解析器生成器生成的解析器,我必须承认我对解析器生成器有偏见。

以下是每种方法的优缺点。

分析器生成器

优点:

  • 快速找到一个有效的解析器(至少如果你不知道如何手动编码)。

缺点:

  • 生成的代码很难理解和调试。
  • 难以实现正确的错误处理。生成器将为语法正确的代码创建一个正确的解析器,但会阻塞不正确的代码,并且在大多数情况下将无法提供正确的错误消息。
  • 解析器生成器中的错误可能会暂停您的项目。你需要在其他人的代码中修复bug(如果源代码可用),等待作者修复它或解决bug(如果可能的话)。

手工制作的递归下降解析器

优点:

  • 生成的代码很容易理解。递归解析器通常具有对应于每个语言构造的一个函数,例如, parse用于解析'while'语句,parseDeclaration用于解析声明等等。理解和调试解析器很简单。
  • 很容易提供有意义的错误消息,从错误中恢复并以在特定情况下最有意义的方式继续解析。

缺点:

  • 手动编写解析器需要一些时间,特别是如果您没有这方面的经验。

  • 解析器可能有点慢。这适用于所有递归解析器,而不仅仅是手写的解析器。具有对应于每个语言构造的一个函数来解析简单的数字文字,解析器可以从例如几个开始进行十几个或更多个嵌套调用。 parseExpression通过parseAddition,parseMultiplication等parseLiteral。函数调用在像C这样的语言中相对便宜,但仍然需要很长时间。

加速递归解析器的一个解决方案是用自下而上的子解析器替换部分递归解析器,这通常要快得多。这种子解析器的自然候选者是具有几个优先级的几乎统一的语法(即二进制和一元表达式)的表达式。表达式的自下而上解析器通常也很容易处理代码,它通常只是一个循环从词法分析器获取输入标记,一堆值和运算符标记的运算符优先级查找表。

答案 1 :(得分:11)

LR(1)和LALR(1)解析器真的非常烦人,原因有两个:

  1. 解析器生成器不能很好地生成有用的错误消息。
  2. 某些歧义,比如C风格的if-else块,使得编写语法非常痛苦。
  3. 另一方面,LL(1)语法在这两方面都要好得多。 LL(1)语法的结构使得它们很容易编码为递归下降解析器,因此处理解析器生成器并不是真正的胜利。

    另外,对于Vala,解析器和编译器本身作为库提供,因此您可以使用Vala编译器库为Vala编译器构建自定义后端,并获取所有解析和类型检查等等。免费。

答案 2 :(得分:2)

我知道这不会是明确的,如果你的问题不是特别与Vala有关,我也不会打扰,但因为他们是...

当时我没有太多参与该项目,所以我对某些细节并不清楚,但我记得Vala切换时的重要原因是dogfooding。我不确定这是主要动机,但我确实记得这是一个因素。

可维护性也是一个问题。该补丁取代了用C / Bison / YACC编写的较大解析器(相对较少的人对后两者有很多经验),在Vala中使用较小的解析器(几乎所有对valac感兴趣的人都知道并且很自在)。 / p>

更好的错误报告也是一个目标,IIRC。

我不知道它是否是一个因素,但手写的解析器是一个递归的下降解析器。我知道ANTLR会生成这些,ANTLR是用Java编写的,这是一个非常重的依赖(是的,我知道它不是运行时依赖,但仍然)。

  

作为旁注:我对Vala特别感兴趣,因为我喜欢使用具有现代功能和清晰语法的语言,但可编译为“本机”和“非托管”高级语言(在Vala的情况下为C) 。到目前为止我只发现了Vala。我想通过使Vala(或类似语言)可编译为C ++(由Qt libs支持)来获得乐趣。但是因为我不想发明全新的语言,所以我想考虑一些现有的语法。显然,手工制作的解析器没有我可能重用的书面形式语法。你对这个想法的评论是受欢迎的(整个想法是愚蠢的吗?)。

很多Vala实际上反映了GObject做出的决定,在C ++ / Qt中,事情可能会或可能不会以相同的方式运作。如果您的目标是使用Qt / C ++替换valac中的GObject / C,那么您可能需要的工作量超出预期。但是,如果您只想从Vala访问C ++和Qt库,那肯定是可行的。实际上,Luca Bruno大约一年前开始研究(参见wip/cpp分支)。由于缺乏时间而不是技术问题,它暂时没有看到活动。

答案 3 :(得分:0)

  

根据Vala文档:“在0.3.1之前,Vala的解析器是   经典的flex扫描仪和Bison LALR解析器组合。但截至   提交eba85a,解析器是一个手工制作的递归下降解析器。“   我的问题是:为什么?

     

我在这里特别询问Vala,尽管问题可以解决   寻址到任何不使用解析器生成器的编译器。什么   从解析器生成器到解析器的这种移动是有利有弊的   手工制作的解析器?使用解析器生成器有什么缺点   (Bison,ANTLR)编译器?

也许程序员发现了解析器生成器没有发现的一些优化途径,这些优化途径需要完全不同的解析算法。或者,也许解析器生成器在C89中生成代码,程序员决定重构C99或C11将提高可读性。

  

作为旁注:我特别感谢Vala,因为我喜欢   拥有具有现代功能和清晰语法的语言的想法   可编译成“原生”和“非管理”的高级语言(C in   Vala的案例。)

快速说明:C不是原生。它的根源在于可移植性,因为它旨在抽象出那些导致程序员在移植时非常悲伤的硬件/操作系统特定细节。例如,想想为每个操作系统和/或文件系统使用完全不同的 fopen 的痛苦;我不仅仅意味着功能不同,而且在输入和输出期望方面也有所不同,例如。不同的参数,不同的回报值。同样,C11引入了便携式线程;使用线程的代码将能够使用相同的C11兼容代码来定位所有实现线程的操作系统。

  

到目前为止我找到了Vala。我想通过制作Vala来获得乐趣   (或类似语言)可编译为C ++(由Qt库支持)。但是由于   我不想发明我正在考虑采用的全新语言   一些现有的语法。显然,手工制作的解析器没有   书面形式语法我可能会重复使用。你对这个想法的评论是   欢迎(整个想法是愚蠢的吗?)。

使用手工制作的解析器轻松生成C ++代码可能是可行的,所以我不会这么快就抛弃这个选项;旧的flex / bison解析器生成器可能更有用,也可能没有用。但是,这不是您唯一的选择。无论如何,我很想广泛地学习the specification

答案 4 :(得分:0)

很奇怪这些作者从野牛到RD。大多数人会走向相反的方向。

我能看到做Vala作者所做的唯一真正原因是更好的错误恢复,或者他们的语法可能不是很干净。

我认为你会发现大多数新语言都是从手写的解析器开始的,因为作者会感受到他们自己的新语言并确切地知道他们想要做什么。在某些情况下,作者还会学习如何编写编译器。 C是一个典型的例子,C ++也是如此。稍后在演化中,可以替换生成的解析器。另一方面,现有标准语言的编译器可以通过解析器生成器更快地开发,甚至可能通过现有语法开发:上市时间是这些项目的关键业务参数。