您可以将树形结构存储在内存中,或直接生成所需的输出代码。通常会存储中间表达式以便能够在生成输出之前在较高级别上对代码进行一些处理。
例如,在您的情况下,很容易发现您的表达式不包含变量,因此结果是固定的数字。然而,仅查看一个节点是无法实现这一点的。更明确地说,如果在查看"2*"后,您生成了用于计算某个值的双倍的机器代码,则当另一部分为"3"时,该代码将有点浪费,因为您的程序将计算" 3 ",然后每次都计算其双倍,而只载入"6"即可等效但更短且更快。
如果要生成机器代码,则需要首先知道要为哪种类型的机器生成代码……最简单的模型使用基于堆栈的方法。在这种情况下,您不需要寄存器分配逻辑,并且可以直接编译成机器代码而无需中间表示。考虑这个小例子,它仅处理整数、四个操作、一元否定和变量…您会注意到根本没有使用数据结构:读取源代码字符并将机器指令写入输出……
#include <stdio.h>
#include <stdlib.h>
void error(const char *what) {
fprintf(stderr, "ERROR: %s\n", what);
exit(1);
}
void compileLiteral(const char *& s) {
int v = 0;
while (*s >= '0' && *s <= '9') {
v = v*10 + *s++ - '0';
}
printf(" mov eax, %i\n", v);
}
void compileSymbol(const char *& s) {
printf(" mov eax, dword ptr ");
while ((*s >= 'a' && *s <= 'z') ||
(*s >= 'A' && *s <= 'Z') ||
(*s >= '0' && *s <= '9') ||
(*s == '_')) {
putchar(*s++);
}
printf("\n");
}
void compileExpression(const char *&);
void compileTerm(const char *& s) {
if (*s >= '0' && *s <= '9') {
compileLiteral(s);
} else if ((*s >= 'a' && *s <= 'z') ||
(*s >= 'A' && *s <= 'Z') ||
(*s == '_')) {
compileSymbol(s);
} else if (*s == '-') {
s++;
compileTerm(s);
printf(" neg eax\n");
} else if (*s == '(') {
s++;
compileExpression(s);
if (*s != ')')
error("')' expected");
s++;
} else {
error("Syntax error");
}
}
void compileMulDiv(const char *& s) {
compileTerm(s);
for (;;) {
if (*s == '*') {
s++;
printf(" push eax\n");
compileTerm(s);
printf(" mov ebx, eax\n");
printf(" pop eax\n");
printf(" imul ebx\n");
} else if (*s == '/') {
s++;
printf(" push eax\n");
compileTerm(s);
printf(" mov ebx, eax\n");
printf(" pop eax\n");
printf(" idiv ebx\n");
} else break;
}
}
void compileAddSub(const char *& s) {
compileMulDiv(s);
for (;;) {
if (*s == '+') {
s++;
printf(" push eax\n");
compileMulDiv(s);
printf(" mov ebx, eax\n");
printf(" pop eax\n");
printf(" add eax, ebx\n");
} else if (*s == '-') {
s++;
printf(" push eax\n");
compileMulDiv(s);
printf(" mov ebx, eax\n");
printf(" pop eax\n");
printf(" sub eax, ebx\n");
} else break;
}
}
void compileExpression(const char *& s) {
compileAddSub(s);
}
int main(int argc, const char *argv[]) {
if (argc != 2) error("Syntax: simple-compiler <expr>\n");
compileExpression(argv[1]);
return 0;
}
例如,使用
1+y*(-3+x)
作为输入运行编译器,则输出结果为:
mov eax, 1
push eax
mov eax, dword ptr y
push eax
mov eax, 3
neg eax
push eax
mov eax, dword ptr x
mov ebx, eax
pop eax
add eax, ebx
mov ebx, eax
pop eax
imul ebx
mov ebx, eax
pop eax
add eax, ebx
然而,这种编写编译器的方法在优化编译器方面并不具有可扩展性。
虽然可以通过在输出阶段添加“Peephole”优化器来获得一些优化,但是许多有用的优化仅从更高的角度查看代码时才可能实现。
此外,即使是裸机代码生成也可以通过查看更多代码受益,例如决定分配哪个寄存器或决定哪个可能的汇编程序实现对于特定的代码模式会更方便。
例如,同一表达式可以通过优化编译器编译为:
mov eax, dword ptr x
sub eax, 3
imul dword ptr y
inc eax