第0部分:介绍

我决定踏上编写编译器的旅程。在过去,我曾经编写过一些汇编器,并为一种无类型语言编写过一个简单的编译器。但我从未编写过一个能够编译自身的编译器。这就是我在这个旅程中的目标。

作为这个过程的一部分,我打算记录我的工作,以便其他人也能够跟随。这也将帮助我澄清我的思路和想法。希望你和我都会找到这个过程有用!

旅程的目标

以下是我对这个旅程的目标和非目标:

  • 编写一个自举编译器。我认为如果编译器能够编译自己,那么它就可以称为一个真正的编译器。
  • 针对至少一个真实的硬件平台。我见过一些为假设机器生成代码的编译器。我希望我的编译器可以在真实的硬件上运行。而且,如果可能的话,我希望编写编译器以支持为不同硬件平台生成代码的多个后端。
  • 实践优先于研究。在编译器领域有很多研究。我想从零开始这个旅程,所以我会采用实用的方法而不是理论重的方法。也就是说,在某些情况下,我将需要引入(并实现)一些基于理论的东西。
  • 遵循KISS原则:保持简单,愚蠢!我肯定会在这里使用肯·汤普森(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,同样是由Fabrice Bellard和其他人编写的
  • 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存储库的副本。

下一步

在我们编写编译器的旅程的下一部分中,我们将从扫描输入文件并找到语言的token的代码开始。下一步