粗浅的中文翻译版本,如有不当之处,请指出,感谢:)
我决定踏上编译器编写之旅。过去我写过一些汇编器,也为无类型语言写过一个简单编译器。但是我从来没有写过可以编译自身的编译器(a compiler that can compile itself)。这就是我此行的目的地。
作为这个过程的一部分,我将把我的工作写下来,这样其他人就可以跟着做了。这也有助于我理清思路和想法。希望你和我会发现这很有用!
以下是我这次旅行的目标:
- 写一个可以编译自身的编译器。我认为如果编译器能够编译自己,它就可以称自己为“真正的”编译器。
- 瞄准至少一个真正的硬件平台。我见过一些为假想的机器生成代码的编译器。我希望我的编译器能在真正的硬件上工作。还有,如果可能的话,我希望写的编译器可以支持不同硬件平台的多个后端。
- 实用先于研究。在编译器领域有很多研究。在这个旅程中,我想从零开始,所以我倾向于采用实用的方法,而不是偏重理论的方法。也就是说,有时候我需要引入(并实现)一些基于理论的东西。
- 遵循 KISS 原则:keep it simple, stupid!我肯定会在这里使用 Ken Thompson 的原则:“当有疑问时,使用暴力。”
- 采取许多小步骤来达到最终目标。我会把旅程分成许多简单的步骤,而不是大步跨越。这将使编译器的每一个新增部分都变得简单易懂。
目标语言的选择是很困难的。如果我选择像 Python、Go 等这样的高级语言,那么我就必须实现一大堆库和类,因为它们是语言内置的。
我可以为 Lisp 这样的语言写一个编译器,但这可以很容易做到。
相反,我又回到了老本行,我打算为 C 语言的一个子集写一个编译器,足以让编译器编译自己。
C 语言只是汇编语言的升级版(对于 C 语言的某个子集,而不是 C18),这使得将 C 代码编译成汇编的任务变得更加容易。哦,我也喜欢 C。
编译器的工作是将一种语言(通常是高级语言)中的输入翻译成不同的输出语言(通常是比输入更低级别的语言)。主要步骤如下:
-
通过词法分析来识别词汇元素。在一些语言中,
=
和==
是不同的,所以不能只读一个=
。我们称这些词汇元素为标记(token)。 -
解析输入,即识别输入的语法和结构元素,并确保它们符合语言的语法。例如,你的语言可能有这样的决策结构:
if (x < 23) { print("x is smaller than 23\n"); }
但是在另一种语言中你可能会写成:
if (x < 23): print("x is smaller than 23\n")
这也是编译器可以检测语法错误的地方,比如第二个 print 语句末尾缺少分号。
-
对输入进行语义分析,即理解输入的含义。这实际上不同于识别语法和结构。例如,在英语中,句子的形式可能是
<主语><动词><形容词><宾语>
。以下两个句子结构相同,但含义完全不同:David ate lovely bananas. Jennifer hates green tomatoes.
-
将输入的含义翻译成不同的语言。在这里,我们将输入一部分一部分地转换为较低级别的语言。
网上有很多编译器资源。下面是我会看的。
如果你想从一些关于编译器的书籍、论文和工具开始,我强烈推荐以下列表:
当我要构建自己的编译器时,我计划看看其他编译器的想法,并可能会借用他们的一些代码。以下是我正在看的:
- SubC by Nils M Holm
- Swieros C Compiler by Robert Swierczek
- fbcc by Fabrice Bellard
- tcc, also by Fabrice Bellard and others
- catc by Yuichiro Nakada
- amacc by Jim Huang
- Small C by Ron Cain, James E. Hendrix, derivatives by others
特别地,我将使用 SubC 编译器中的许多想法和一些代码。
假设你想参加这次旅行,以下是你需要的。我将使用 Linux 开发环境,所以下载并设置你最喜欢的Linux 系统:我使用的是 Lubuntu 18.04。
我将以两个硬件平台为目标:Intel x86-64 和 32 位 ARM。我将使用运行 Lubuntu 18.04 的 PC 作为 Intel 目标,使用运行 Raspbian 的树莓派作为 ARM 目标。
在 Intel 平台上,我们需要一个现有的 C 编译器。因此,安装这个软件包(我给出 Ubuntu/Debian 命令):
$ sudo apt-get install build-essential
如果一个普通 Linux 系统需要更多的工具,请告诉我。
最后,克隆这个 Github 仓库。
在编译器编写旅程的下一部分中,我们将从扫描输入文件的代码开始,并查找作为语言词汇元素的标记。