概述
我对编译器的工作原理已经感兴趣很久了。神秘的指令和奇异的行为总是会令我迷惑不已。也从未真正理解优化是如何进行的,以及编译器又是如何知道我到底做错了什么。
当我决定学习如何编写编译器的时候,我发现有许多关于这个领域的术语和缩写。什么是 SLR 或 LALR 解析器?什么是该死的词位(lexeme)或有限自动机(finite automata)?什么是递归下降分析(recursive-descent parsing)?什么是 AST?
最开始的时候,这真是铺天盖地。
我发现,最大的障碍是网络上的在线教程、指南和其他学习材料并没有教你如何编写编译器或解释器。更正一下,它们没有教你如何从零开始,而只提供了一个概览,自顶向下的依赖已有工具来完成这个任务。由于构建一个编译器确实是一个庞大的任务,在一个学期的时间里想要涵盖所有的内容放几乎是不可能的。对此我也无法指责任何人。
即便如此,我觉得这些工具跳过了一些我觉得是有价值的、希望了解更多的课程和信息。
因此,我希望通过我个人学习如何编写编译器的旅程,为大家带来一个指引。
务必记得,我不是一个专家程序员,我也没有把所有的东西琢磨清楚。只是实在忍不住想分享给大家,我发现的如此好的内容,希望对大家寻找自己的学习道路有所帮助。毕竟,这也是你在这里的原因,不是吗?
实至名归
计算器是最初的工作成果,而关于这个项目所有的代码都是手工编写的,但不可否认其与 Go 和 Go 标准库中解释器包的相似之处。我在学习如何编写编译器时,每当卡住了,我常常会借鉴它们的工作。对于任何与他们共同的工作成果相似的任何代码所带来的荣耀都应当归功于 Go 开发者。你可以在golang.org找到 Go 编译器的源代码。
先决条件
这个系列文章并不十分适合初级程序员。它假设你已经有一些编程经验,并且已经熟悉 Go 语言。因此,目标读者至少应当是中级以上的程序员。
虽然我认为不少初级程序员也可以跟得上文章,不过我不会去解释那些不与编译直接相关的 Go 及其他任何代码。我会提供一些工具函数,不过由于它们与编译并没有明确的关系,我不会对它们进行解释。
工具
编译器的设计是语言设计的一部分,至少有着关联。这是个广泛的话题,而对于编译器的每一个步骤都可以是一个课程。
已经有许多工具可以用于加速处理。Flex 和 Bison 多半是最常用的开源工具。Flex 基于叫做 Lex 的程序,这是 Lexical Analysis(词法分析)的简称。Bison 基于 YACC,是 Yet Anther Compiler Compiler(另一个编译器的编译器)的缩写。
提供一系列的规则,你可以创建一个 GCC 的前端,或生成一个你自己的编译器。当然,如果你明白你在干什么的话。如果你不清楚,那就准备好受打击吧。
事实上,我发现跳过这些工具来学习基础的内容会另人更加开心。我认为通过手工的方式来构建整个编译器栈对于长期来说是更好的方案,不过这仅仅是我个人的观点。你还是得自己拿主意。
对于我来说,使用这些工具的问题在于,在编译的各个阶段之间,最终会变得支离破碎,导致失控。同时心头总会萦绕着一个问题:“这到底是怎么工作的?”
解决方案
这也就是为什么在这个系列里,我会涵盖每个独立的组件,并且从一点一滴开始编写代码。这可能会花更长的时间,不过我相信到最后你会有一个深刻的理解。如果在我们完成之后,你决定用前面提到的工具,你也会感到更加轻松,并且做出更加明确的决定。
开始进入第二部分,准备好下地干活吧!
链接: