在论文Reflections on Trusting Trust 中,Unix 的创始人之一 Ken Thompson 写了一篇引人入胜(且易于阅读)的关于 C 编译器如何编译自身的概述。类似的概念可以应用于 CoffeeScript 或任何其他语言。
编译自己的代码的编译器的想法有点类似于quine:源代码在执行时会生成原始源代码作为输出。 Here is one example CoffeeScript quine。 Thompson 举了一个 C quine 的例子:
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 语言功能的一个示例。现在,对每一个语言特性重复这个过程,你就有了一个“自托管”编译器:一个用 C 编写的 C 编译器。
1 论文中描述的情节转折是,由于编译器可以像这样“教导”事实,它也可能被错误教导以一种难以生成的方式生成木马可执行文件检测,并且这种破坏行为可以持续存在于受污染的编译器生成的所有编译器中。