编译器如何编译自己?

我正在网站http://coffeescript.org/上研究CoffeeScript,它有文本

CoffeeScript编译器本身是用CoffeeScript编写的

编译器如何编译自己,或者这个语句是什么意思?

编译器的第一版不能用特定的编程语言机器生成; 你的困惑是可以理解的。 具有更多语言特性的编译器的更高版本(源代码在第一版新语言中重写)可以由第一编译器构build。 那个版本可以编译下一个编译器,依此类推。 这是一个例子:

  1. 第一个CoffeeScript编译器是用Ruby编写的,生成CoffeeScript的第一个版本
  2. CS编译器的源代码在CoffeeScript 1中被重写
  3. 原始的CS编译器将新代码(用CS 1编写)编译到编译器的版本2中
  4. 对编译器源代码进行更改以添加新的语言function
  5. 第二个CS编译器(第一个用CS写的)将修订后的新源代码编译到编译器的第三版中
  6. 对每个迭代重复步骤4和5

注意:我不确定CoffeeScript版本是如何编号的,这只是一个例子。

这个过程通常被称为自举 。 另一个引导编译器的例子是rustc , Rust语言的编译器。

Ken Thompson是UNIX创始人之一的Ken Thompson在“ 反思信任信任”一文中写了一个关于C编译器如何自行编译的迷人(易读)的概述。 类似的概念可以适用于CoffeeScript或任何其他语言。

编译自己的代码的编译器的思想与quine类似:源代码在执行时产生原始源代码。 这是 CoffeeScript quine的一个例子 。 汤普森给了这个C奎因的例子:

 char s[] = { '\t', '0', '\n', '}', ';', '\n', '\n', '/', '*', '\n', … 213 lines omitted … 0 }; /* * The string s is a representation of the body * of this program from '0' * to the end. */ main() { int i; printf("char\ts[] = {\n"); for(i = 0; s[i]; i++) printf("\t%d,\n", s[i]); printf("%s", s); } 

接下来,您可能会想知道编译器是如何被教导的,像'\n'这样的转义序列代表了ASCII码10.答案是,在C编译器的某个地方,有一个解释字符文字的例程,包含一些像这样的条件来识别反斜杠序列:

 … c = next(); if (c != '\\') return c; /* A normal character */ c = next(); if (c == '\\') return '\\'; /* Two backslashes in the code means one backslash */ if (c == 'r') return '\r'; /* '\r' is a carriage return */ … 

所以,我们可以在上面的代码中添加一个条件…

 if (c == 'n') return 10; /* '\n' is a newline */ 

…产生一个知道'\n'表示ASCII 10的编译器。有趣的是,编译器以及由它编译的所有后续编译器 “知道”该映射,因此在下一代源代码中,可以更改最后一个进入

 if (c == 'n') return '\n'; 

…它会做正确的事情! 10来自编译器,不再需要在编译器的源代码中明确定义。 1

这是用C代码实现的C语言function的一个例子。 现在,为每一种语言function重复该过程,并且您有一个“自托pipe”编译器:用C语言编写的C编译器。


1本文所描述的情节扭曲是因为编译器可以被“教”这样的事实,也可能被错误地教导以难以检测的方式产生特洛伊的可执行文件,并且这种破坏行为可以持续在受污染的编译器生成的所有编译器中。

你已经得到了很好的答案,但是我想给你提供一个不同的观点,希望对你有所启发。 我们首先确定两个我们都可以同意的事实:

  1. CoffeeScript编译器是一个可以编译用CoffeeScript编写的程序的程序。
  2. CoffeeScript编译器是用CoffeeScript编写的程序。

我相信你可以同意#1和#2是真的。 现在,看看这两个陈述。 你现在看到,CoffeeScript编译器能够编译CoffeeScript编译器是完全正常的吗?

编译器不关心它编译的内容。 只要它是用CoffeeScript编写的程序,就可以编译它。 而CoffeeScript编译器本身恰好是这样一个程序。 CoffeeScript编译器不关心它正在编译的CoffeeScript编译器本身。 它看到的只是一些CoffeeScript代码。 期。

编译器如何编译自己,或者这个语句是什么意思?

是的,这正是这个说法的意思,我希望你现在可以看到这个说法是真实的。

编译器如何编译自己,或者这个语句是什么意思?

这就是这个意思。 首先,有些事情要考虑。 有四个对象我们需要看看:

  • 任何CoffeScript程序的源代码
  • 任何CoffeScript程序的(生成的)程序集
  • CoffeScript编译器的源代码
  • CoffeScript编译器的(生成的)程序集

现在,显然你可以使用CoffeScript编译器生成的程序集(可执行文件)来编译任意CoffeScript程序,并生成该程序的程序集。

现在,CoffeScript编译器本身就是一个任意的CoffeScript程序,因此它可以由CoffeScript编译器编译。

看来你的困惑源于这样一个事实,即当你创build自己的新语言时,你没有一个编译器,但你可以用它来编译你的编译器。 这肯定看起来像鸡蛋问题 ,对吧?

介绍称为自举的过程。

  1. 你用已经存在的语言编写一个编译器(在CoffeScript的情况下,原始编译器是用Ruby编写的),它可以编译新语言的一个子集
  2. 你编写一个编译器,可以用新的语言编译新语言的一个子集。 您只能使用上述步骤中编译器的语言function进行编译。
  3. 您可以使用第1步中的编译器编译第2步中的编译器。这会为您提供一个最初以新语言的子集编写的程序集,并且可以编译新语言的一个子集。

现在你需要添加新的function。 假设你只while循环”的时候实现了,而且还需要for循环”。 这不是一个问题,因为你可以重写任何for -loop的方式,这是一个while -loop。 这意味着你只能在编译器的源代码中使用while循环,因为你手头的汇编只能编译这些代码。 但是你可以在你的编译器中创build函数,并且可以编译和编译。 然后你使用你已有的程序集,并编译新的编译器版本。 现在你有一个编译器的程序集,也可以parsing和编译for循环! 你现在可以回到你的编译器的源代码文件,并且重写任何你不希望进入循环的循环。

冲洗并重复,直到编译器编译所需的所有语言function。

while显然只是例子,但这适用于任何你想要的新语言function。 然后你现在就处于CoffeScript的状态:编译器编译自己。

那里有很多文献。 信任信任的思考是对这个话题感兴趣的经典人物 ,至less应该阅读一次。

一个小的但重要的澄清

这里的术语编译器掩盖了涉及两个文件的事实。 一个是可执行文件,它以CoffeScript编写的input文件为输出文件,生成另一个可执行文件,可链接的目标文件或共享库。 另一个是CoffeeScript源文件,它恰好描述了编译CoffeeScript的过程。

将第一个文件应用到第二个文件中,生成第三个文件,这个文件能够执行与第一个文件相同的编译行为(如果第二个文件定义了第一个文件没有实现的function,则可能更多),因此可能会replace第一个文件如此渴望。

  1. CoffeeScript编译器最初是用Ruby编写的。
  2. CoffeeScript编译器在CoffeeScript中被重写。

由于CoffeeScript编译器的Ruby版本已经存在,所以它被用来创buildCoffeeScript编译器的CoffeeScript版本。

在这里输入图像说明 这被称为自承载编译器 。

这是非常普遍的,通常是由于作者希望用自己的语言来维持这种语言的发展。

这不是编译器的问题,而是语言的expression问题,因为编译器就是用某种语言编写的程序。

当我们说“一种语言被编写/实现”时,我们实际上是指该语言的编译器或解释器被实现。 有编程语言可以编写实现该语言的程序(编译器/解释器是相同的语言)。 这些语言被称为通用语言 。

为了能够理解这一点,考虑一下金属车床。 这是一个用来塑造金属的工具。 用这个工具就可以创build另一个相同的工具。 因此,该工具是一种通用机器。 当然,第一个是使用其他手段(其他工具)创build的,可能质量较差。 但是第一个被用来build立更高精度的新的。

3D打印机几乎是一台通用机器。 您可以使用3D打印机打印整个3D打印机(无法构build融化塑料的提示)。

通过归纳certificate

感应步骤

编译器的第n + 1版本是用X编写的。

因此它可以由编译器的第n个版本编译(也写在X中)。

基本情况

但是用X编写的第一个版本的编译器必须由用X以外的语言编写的X编译器编译。这一步被称为bootstrapping编译器。

编译器采用高级规范,将其转化为低级实现,例如可以在硬件上执行。 因此除了目标语言的语义之外,规范的格式与实际执行之间没有关系。

交叉编译器从一个系统转移到另一个系统,跨语言编译器将一种语言规范编译成另一种语言规范。

基本上编译是一种正义的翻译,而语言的层次通常是较高层次的语言,而语言层次较多,但是有很多变体。

Bootstrapping编译器当然是最令人困惑的,因为它们编译的是他们编写的语言。不要忘记bootstrapping中的第一步,它至less需要一个可执行的最小版本。 许多引导编译器首先处理编程语言的最小特性,并且只要可以使用先前的特性表示新特性就可以添加更多复杂的语言特性。 如果情况并非如此,则需要事先使用另一种语言开发“编译器”的那一部分。