编写编译器......什么是对的,什么是错的?

时间:2009-11-06 03:03:33

标签: compiler-construction resources bison flex-lexer

好的,在我寻找编写编译器的必要内容的过程中,我遇到了一些障碍。似乎我发现的每一项技术或工具都在某些地方存在争议。

我现在使用Bison和Flex,但我觉得这种方法已经过时了。这是真的?这是一种很好的向前兼容的方式来继续编写一个完整的编程语言吗?

在不同概念和工具的海洋中(ANTLR,LL(k),GLR,LALR,LLVM,Flex,Bison)编写编译器的当前趋势和最佳实践是什么?龙书是否过时了?

8 个答案:

答案 0 :(得分:30)

除非你想编写一个真正简单的编译器,否则你的注意力是错误的。

编写编译器只是编写解析器的一小部分。有一个解析器就像 当问题是攀登珠穆朗玛峰时,爬上喜马拉雅山的山麓。你到了山麓的顶部,向上看......只有2万英尺的距离,你只做了真正容易的部分。而且你会注意到,到达山麓顶部所需的技术远比你需要的技术更容易。

(仅供参考:目前最好的解析技术是GLR,很容易 在不破解语法的情况下接受含糊不清的语法。 GLR甚至可以轻松解析C ++, 这违反了C ++难以解析的民间定理。民间定理 来自试图使用YACC和ANTLR来解析它的人。)

要构建编译器,您需要许多机器:

  • AST building
  • 符号表构造
  • 控制流量分析
  • 数据流分析
  • 基本上表示程序代码作为数据流计算(SSA或三元组)
  • 目标机器的模型
  • 将程序代码映射到机器指令的方法
  • 注册分配
  • 优化:持续传播,循环展开,......

我们甚至没有接近全球流量分析,全局优化或特殊处理 用于涉及SIMD指令或缓存优化的现代指令集。 ... 这个清单一直在继续。 Dragon书籍对基本主题进行了很好的介绍,但没有解决任何高级主题。你会想要Cooper的“工程编译器”和Muchnick的“高级编译器设计”作为参考,如果你在开始之前已经很好地浏览它们会很好。

构建现代编译器是一项非常出色的工程技术。

答案 1 :(得分:11)

虽然经过深入研究,但解析是编译中最不重要的部分。 (例外:你正在设计自己的具体语法,并且不断改进和改变语言。)

Yacc,Bison和朋友的设计是为了拥有64K内存的机器时代。它们非常适合在内存有限的机器上快速运行。但是,将语法强制为LALR(1)形式所需的人类工程量在今天是荒谬的。 Ira Baxter说GLR可能是最好,最灵活的解析技术,但PEG(Parsing Expression Grammars)也很好。在这两种情况下,人类工程学都比旧工具领先了几年。

解雇解析之后,我现在将开始另一项技术食品斗争:-) 编译主要包括将程序从一个表单重写到另一个表单,直到最终到达汇编代码或机器代码。对于这类问题,你真的不想使用C或C ++:

  问:(当被问及Dave Hanson与Chris Fraser在lcc上发表他惊人的书时)“你和Chris已经花了十年的时间来构建可能是有史以来最精心设计的编译器之一。你从经验中学到了什么?“

     

答:“好吧,C是编写编译器的糟糕语言。”

我建议您尝试一种流行的函数式语言,如Haskell或Standard ML。在这个领域工作的人普遍认为编译器是功能语言的“杀手级应用”。代数数据类型和模式匹配是为将抽象语法写入中间代码到机器代码而量身定制的。看看这些技术的强大功能的好地方是Andrew Appel的书 Compiling With Continuations 。 (Appel的编译器教科书也是一个很好的阅读和非常优雅的设计,但他并不总是解释为什么设计是这样的。)

答案 2 :(得分:7)

为了构建编译器,我强烈建议站在巨人的肩膀上。有很多好东西可以放在一起制作编译器。我一直在为C / C ++编写兼职编译器。它使用GLR进行解析,构建AST,使用SSA作为其中间形式,进行过程间优化,并为X86,ARM,MIPS,PowerPC,Sparc等生成代码。

秘密?我借用了几个来源的代码。

  • 来自clang的预处理器和错误报告
  • Elkhound和Elsa编译器生成器和C / C ++编译器
  • 用于优化和代码生成的LLVM系统

兼职工作我已经能够组建一个非常有用的工具系统。如果我试图从头开始,我现在几乎没有完成解析器。 ; - )

http://ellcc.org

答案 3 :(得分:4)

我会假设你和我处于同一个位置:你想编写一个有趣的编译器,并至少学习它的每一个阶段。因此,您不希望仅为现有编译器编写插件。并且您希望避免使用太多现有的编译器模块,除非您可以准确理解他们正在做什么。在我的情况下,我使用bison,这是一个轻微的例外,因为它至少做了一些我认为理所当然的事情(我在大学里学过语法等,但那是很长一段时间前)。另一方面,解析器生成器很常见,它是一个值得关注的编译器阶段:bison可能会阻止我编写很多解析代码但是它给了我一个编写解析器动作代码的更改。

与某些建议相反,我会说你可以在不了解输入和目标语言的所有内容的情况下开始使用。除了一些例外情况,语言功能以后难以添加。我发现的一个例外是控制流:如果你将大多数后来的操作写在树形式上,那么很难满足breakcontinue和{{}之类的语句1}}(甚至是结构化形式)。所以我建议在做太多的事情之前从树翻译成CFG。

  1. 为输入的某个相当稳定的子集编写解析器。
  2. 添加构建其有用的内存中表示形式的操作(通常是树),然后将其打印出来。
  3. 让它以看起来有点像目标语言的形式打印出来。在我的情况下,我打印树节点为“x = y + z;”节点为“ADD x,y,z”; “if(c){...}”变成“bz c label1”,然后翻译“...”然后“label1:”。
  4. 在中间添加可选阶段。这些可以是优化和/或检查阶段。您可能需要一个准备表示以便轻松生成代码的程序:我有一个阶段通过添加临时变量来减少过于复杂的表达式。 (这实际上是输出所必需的,因为“ADD”指令只能用于简单的输入。)
  5. 返回并改进其中的任何部分。例如。在解析器操作中加入一些检查,以便在该阶段检测到错误(例如,使用未声明的变量)。
  6. 如果采用迭代方法,很容易完成大部分工作。

答案 4 :(得分:2)

我无法对各种方法进行比较,但ANTLR小组涵盖范围广range of rich target languages

包括目前大多数常见的。 ANTLR还支持各种输出语言。我们计划解决类似CSS的语言

答案 5 :(得分:1)

Flex和Bison并没有什么问题,但如果您正在寻找更新的东西(面向对象),您可以考虑boost's Spirit library

答案 6 :(得分:1)

有人认真询问龙书是否过时了?这是开创性的工作人员。我不能告诉你我从前两章中学到了多少(因为我已经忘记了它... ba-dum-bum)。

每项技术(除了goto语句之外)都有批评者和支持者。不要喋喋不休地“做出正确的工具选择”,并全神贯注地学习概念并以有意义的方式实施它们。我的意思是,即使你选择了世界上最完美的最好的工具,你也会认为你会像FORTRAN那样建立被爱,崇拜和尊重的东西......我的意思是我们喜欢它......对吧?

当然不是男人......这么多的学习来自犯错误。这就是你最了解的地方。

你可以做到!

答案 7 :(得分:1)

这是1)一个像Java或C ++这样的大型现有语言在一个极端,还是2)一种没有花哨的数据类型的小语言?

如果为1,你最好快速掌握Ira提到的所有技术。

如果是2,如果你只是编写一个递归下降的解析器,你可以立即执行它,并且a)在解析时将其转换为你最喜欢的语言(YFL),或者b)构建一个符号表和解析树,然后走那个生成YFL。如果您不想生成YFL,只需编写一个遍历解析树的解释器。

如果您的目标是学习所有棘手的技术,那么就这样做。如果没有,快速和肮脏是要走的路。如果是后者,请不要担心优化!!

顺便说一句,如果你想真正快速而又肮脏,而且你有C或C ++,并且你不会为编写宏而感到自豪,创建语言的一种简单方法就是编写一组宏。这样您就可以创建自己的语句,同时利用基础语言的数据类型,表达式语法,效率和运行时库。