Compiler construction 用自己的语言编写编译器
直观地说,似乎语言Compiler construction 用自己的语言编写编译器,compiler-construction,bootstrapping,Compiler Construction,Bootstrapping,直观地说,似乎语言Foo的编译器本身不能用Foo编写。更具体地说,语言Foo的第一个编译器不能用Foo编写,但任何后续编译器都可以为Foo编写 但这是真的吗?我有一些非常模糊的记忆,读过一种语言,它的第一个编译器是用“自身”编写的。这可能吗?如果可能,如何实现?这称为“引导”。您必须首先用其他语言(通常是Java或C)为您的语言构建编译器(或解释器)。完成后,您可以用Foo语言编写新版本的编译器。您使用第一个引导编译器来编译编译器,然后使用这个编译后的编译器来编译其他所有东西(包括它自己的未来版
Foo
的编译器本身不能用Foo编写。更具体地说,语言Foo
的第一个编译器不能用Foo编写,但任何后续编译器都可以为Foo
编写
但这是真的吗?我有一些非常模糊的记忆,读过一种语言,它的第一个编译器是用“自身”编写的。这可能吗?如果可能,如何实现?这称为“引导”。您必须首先用其他语言(通常是Java或C)为您的语言构建编译器(或解释器)。完成后,您可以用Foo语言编写新版本的编译器。您使用第一个引导编译器来编译编译器,然后使用这个编译后的编译器来编译其他所有东西(包括它自己的未来版本)
大多数语言确实是以这种方式创建的,部分原因是语言设计者喜欢使用他们正在创建的语言,也因为一个非平凡的编译器通常可以作为语言“完整”程度的有用基准
Scala就是一个例子。它的第一个编译器是由Martin Odersky的实验语言Pizza创建的。从2.0版开始,编译器完全用Scala重新编写。从那时起,旧的Pizza编译器可能会被完全抛弃,因为新的Scala编译器可以用于编译自己以备将来的迭代。也许您可以编写一个描述BNF。您不能自己编写编译器,因为您没有任何东西可以编译起始源代码。有两种方法可以解决这个问题 最不受欢迎的是以下内容。您可以用汇编语言为一组最小的语言编写一个最小编译器(恶心),然后使用该编译器实现该语言的额外功能。构建自己的方式,直到拥有一个拥有所有语言特性的编译器为止。这是一个痛苦的过程,通常只有在你别无选择的时候才能完成 首选的方法是使用交叉编译器。您可以更改其他计算机上现有编译器的后端,以创建在目标计算机上运行的输出。然后你有一个很好的完整的编译器,并在目标机器上工作。这方面最流行的是C语言,因为有很多现有的编译器具有可插拔的后端,可以进行交换
一个鲜为人知的事实是GNU C++编译器有一个只使用C子集的实现。原因通常是很容易找到一个C编译器的一个新的目标机器,让您可以建立完整的GNU C++编译器从它。现在你已经开始引导自己在目标机器上拥有一个C++编译器了。
这是一个转储(实际上是很难搜索的主题):这里引用了手册中的一段话,在这一步中,我们开始从其源代码构建GCC编译器。(Linux从头开始是一种安装Linux的方式,它与安装发行版完全不同,因为您必须编译目标系统的每一个二进制文件。) “bootstrap”目标不只是编译GCC,而是多次编译它。它使用的是第一次编译的程序 第二次循环编译自己,第三次循环编译自己。然后比较第二个和第三个 编译以确保它可以完美地复制自己。这也意味着它被正确编译
使用“bootstrap”目标的动机是,用于构建目标系统工具链的编译器可能与目标编译器的版本不完全相同。以这种方式进行操作,在目标系统中肯定会获得一个可以自行编译的编译器。GNU Ada编译器GNAT需要完全构建Ada编译器。将其移植到一个没有现成GNAT二进制文件的平台时,这可能是一件痛苦的事情。我记得听过一篇文章,其中Dick Gabriel谈到用LISP在纸上写一个裸体版本,然后手工将其组装成机器代码,从而引导原始LISP解释器。从那时起,其余的LISP功能都是用LISP编写和解释的。通常,您需要先让编译器的工作部分(如果是灵长类的)正常工作,然后才可以开始考虑让它自托管。在某些语言中,这实际上被认为是一个重要的里程碑 从“mono”中我记得的情况来看,他们可能需要在反射中添加一些东西才能使其正常工作:mono团队一直指出,有些东西根本不可能使用
反射。Emit
;当然,微软团队可能会证明他们错了
这有几个真正的优点:对于初学者来说,这是一个相当好的单元测试!你只有一种语言需要担心(即C专家可能不知道多少C++,但是现在你可以修复C编译器)。但我想知道这里的工作中是否没有多少职业自豪感:他们只是希望它能自我托管
不是一个很好的编译器,但我最近一直在开发一个自托管系统;代码生成器用于生成代码生成器。。。因此,如果模式发生更改,我只需在其自身上运行它:新版本。如果有bug,我只需返回到早期版本并重试。很方便,也很容易维护
更新1 我刚刚在PDC上看了Anders的文章,他(大约一个小时)确实给出了一些更为合理的理由——所有这些都是关于编译器作为服务的。仅供记录。Mono project C#编译器已经“自托管”很长时间了,它的意思是它是用
make bootstrap
...
if (c == 92) { // backslash
c = getc();
if (c == 110) { // n
return 10;
} else if (c == 92) { // another backslash
return 92;
} else {
...
}
}
...
...
if (c == '\\') {
c = getc();
if (c == 'n') {
return '\n';
} else if (c == '\\') {
return '\\';
} else {
...
}
}
...
void compileFunction(char * name, char * filename, char * code) {
if (strcmp("compileFunction", name) == 0 && strcmp("compile.c", filename) == 0) {
code = A;
} else if (strcmp("xxx", name) == 0 && strcmp("yyy.c", filename) == 0) {
code = B;
}
... code to compile the function body from the string in "code" ...
}
ADD
/ \
MPY 3
/ \
5 x
[ADD,[MPY,5,x],3]
ADD[MPY[5,x],3]
<NAME>(<unparse>)=><action>;
(<unparse>)=><action>;
...
(<unparse>)=><action>;
expr_gen(ADD[expr_gen(x),expr_gen(y)])=> x+y;
expr_gen(#node[expr_gen(x),expr_gen(y)])=> #action;
node: ADD, SUB, MPY, DIV;
action: x+y, x-y, x*y, x/y;
(NUMBER(x))=> x;
(SYMBOL(x))=> val:(x);
.MACHOP #opnm register,@indirect offset (index): // Instruction's parameters.
.MORG 36, O(18): $/36; // Align to 36 bit boundary print format: 18 bit octal $/36
O(9): #opcd; // Op code 9 bit octal print out
(4): register; // 4 bit register field appended print
(1): indirect; // 1 bit appended print
(4): index; // 4 bit index register appended print
O(18): if (#opcd&&3==1) offset // immediate mode use value else
else offset/36; // memory address divide by 36
// to get word address.
// Vectored entry opcode table:
#opnm := MOVE, MOVEI, MOVEM, MOVES, MOVS, MOVSI, MOVSM, MOVSS,
MOVN, MOVNI, MOVNM, MOVNS, MOVM, MOVMI, MOVMM, MOVMS,
IMUL, IMULI, IMULM, IMULB, MUL, MULI, MULM, MULB,
...
TDO, TSO, TDOE, TSOE, TDOA, TSOA, TDON, TSON;
// corresponding opcode value:
#opcd := 0O200, 0O201, 0O202, 0O203, 0O204, 0O205, 0O206, 0O207,
0O210, 0O211, 0O212, 0O213, 0O214, 0O215, 0O216, 0O217,
0O220, 0O221, 0O222, 0O223, 0O224, 0O225, 0O226, 0O227,
...
0O670, 0O671, 0O672, 0O673, 0O674, 0O675, 0O676, 0O677;
400020 201082 000005 MOVEI r1,5(r2)
<name> <formula type operator> <expression> ;
/* Character Class Formula class_mask */
bin: '0'|'1'; // 0b00000010
oct: bin|'2'|'3'|'4'|'5'|'6'|'7'; // 0b00000110
dgt: oct|'8'|'9'; // 0b00001110
hex: dgt|'A'|'B'|'C'|'D'|'E'|'F'|'a'|'b'|'c'|'d'|'e'|'f'; // 0b00011110
upr: 'A'|'B'|'C'|'D'|'E'|'F'|'G'|'H'|'I'|'J'|'K'|'L'|'M'|
'N'|'O'|'P'|'Q'|'R'|'S'|'T'|'U'|'V'|'W'|'X'|'Y'|'Z'; // 0b00100000
lwr: 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'|'k'|'l'|'m'|
'n'|'o'|'p'|'q'|'r'|'s'|'t'|'u'|'v'|'w'|'x'|'y'|'z'; // 0b01000000
alpha: upr|lwr; // 0b01100000
alphanum: alpha|dgt; // 0b01101110
test byte ptr [eax+_classmap],dgt
jne <success>
je <failure>
string .. (''' .ANY ''' | '"' $(-"""" .ANY | """""","""") '"') MAKSTR[];
-"""" .ANY
"""""",""""
number .. "0B" bin $bin MAKBIN[] // binary integer
|"0O" oct $oct MAKOCT[] // octal integer
|("0H"|"0X") hex $hex MAKHEX[] // hexadecimal integer
// look for decimal number determining if integer or floating point.
| ('+'|+'-'|--) // only - matters
dgt $dgt // integer part
( +'.' $dgt // fractional part?
((+'E'|'e','E') // exponent part
('+'|+'-'|--) // Only negative matters
dgt(dgt(dgt|--)|--)|--) // 1 2 or 3 digit exponent
MAKFLOAT[] ) // floating point
MAKINT[]; // decimal integer
(a b | c d)\ e
:<node name> creates a node object and pushes it onto the node stack.
.. Token formula create token objects and push them onto
the parse stack.
!<number> pops the top node object and top <number> of parstack
entries into a list representation of the tree. The
tree then pushed onto the parse stack.
+[ ... ]+ creates a list of the parse stack entries created
between them:
'(' +[argument $(',' argument]+ ')'
could parse an argument list. into a list.
Exp = Term $(('+':ADD|'-':SUB) Term!2);
Term = Factor $(('*':MPY|'/':DIV) Factor!2);
Factor = ( number
| id ( '(' +[Exp $(',' Exp)]+ ')' :FUN!2
| --)
| '(' Exp ')" )
(^' Factor:XPO!2 |--);
d^(x+5)^3-a+b*c => ADD[SUB[EXP[EXP[d,ADD[x,5]],3],a],MPY[b,c]]
ADD
/ \
SUB MPY
/ \ / \
EXP a b c
/ \
d EXP
/ \
ADD 3
/ \
x 5
program = $((declaration // A program is a sequence of
// declarations terminated by
|.EOF .STOP) // End Of File finish & stop compile
\ // Backtrack: .EOF failed or
// declaration long-failed.
(ERRORX["?Error?"] // report unknown error
// flagging furthest parse point.
$(-';' (.ANY // find a ';'. skiping .ANY
| .STOP)) // character: .ANY fails on end of file
// so .STOP ends the compile.
// (-';') failing breaks loop.
';')); // Match ';' and continue
declaration = "#" directive // Compiler directive.
| comment // skips comment text
| global DECLAR[*1] // Global linkage
|(id // functions starting with an id:
( formula PARSER[*1] // Parsing formula
| sequencer GENERATOR[*1] // Code generator
| optimizer ISO[*1] // Optimizer
| pseudo_op PRODUCTION[*1] // Pseudo instruction
| emitor_op MACHOP[*1] // Machine instruction
) // All the above start with an identifier
\ (ERRORX["Syntax error."]
garbol); // skip over error.
formula = ("==" syntax :BCKTRAK // backtrack grammar formula
|'=' syntax :SYNTAX // grammar formula.
|':' chclass :CLASS // character class define
|".." token :TOKEN // token formula
)';' !2 // Combine node name with id
// parsed in calling declaration
// formula and tree produced
// by the called syntax, token
// or character class formula.
$(-(.NL |"/*") (.ANY|.STOP)); Comment ; to line separator?
chclass = +[ letter $('|' letter) ]+;// a simple list of character codes
// except
letter = char | number | id; // when including another class
syntax = seq ('|' alt1|'\' alt2 |--);
alt1 = seq:ALT!2 ('|' alt1|--); Non-backtrack alternative sequence.
alt2 = seq:BKTK!2 ('\' alt2|--); backtrack alternative sequence
seq = +[oper $oper]+;
oper = test | action | '(' syntax ')' | comment;
test = string | id ('[' (arg_list| ,NILL) ']':GENCALL!2|.EMPTY);
action = ':' id:NODE!1
| '!' number:MAKTREE!1
| "+[" seq "]+" :MAKLST!1;
// C style comments
comment = "//" $(-.NL .ANY)
| "/*" $(-"*/" .ANY) "*/";