我喜欢一些参考或提示,可能是一两本电子书。我不打算编写一个编译器,只是寻找一个我可以遵循的教程并随时修改。谢谢你的理解!
BTW:必须是C。
任何更多的回复将不胜感激。
答案 0 :(得分:24)
开始编写解释器的一个好方法是编写一个简单的机器模拟器。这是一种简单的语言,您可以为其编写解释器:
该语言有一个堆栈和6条指令:
push <num>
#将数字推送到堆栈
pop
#弹出堆栈上的第一个数字
add
#弹出堆栈中的前2项并将其总和推到堆栈。 (记住你可以添加负数,所以你也有减法)。您还可以使用其中一些其他指令创建循环。
ifeq <address>
#检查堆栈的顶部,如果它为0,则继续,否则,跳转到<address>
,其中<address>
是行号
jump <address>
#跳转到行号
print
#打印堆栈顶部的值
dup
#将堆栈顶部的内容复制回堆栈。
一旦你编写了一个可以接受这些指令并执行它们的程序,你基本上就创建了一个非常简单的基于堆栈的虚拟机。由于这是一种非常低级的语言,因此您不需要了解AST是什么,如何将语法解析为AST,并将其转换为机器代码等。这对于教程项目来说太复杂了。从这开始,一旦你创建了这个小VM,你就可以开始思考如何将一些常见的构造转换成这台机器。例如您可能想要考虑如何将C if / else语句或while循环转换为此语言。
编辑:
从下面的评论中,您可能需要更多使用C的经验才能解决此问题。
我建议首先了解以下主题:
然后,了解一下string.h库也是一件好事 - strcmp,strdup - 一些有用的字符串函数。
简而言之,C语言与python相比具有更高的学习曲线,因为它是一种较低级别的语言而且你必须管理自己的内存,所以在尝试编写一个C之前先学习一些关于C的基本知识是很好的。解释器,即使你已经知道如何在python中编写一个。
答案 1 :(得分:15)
解释器和编译器之间的唯一区别在于,不是从AST生成代码,而是在VM中执行它。一旦你理解了这一点,几乎任何编译器书,即使Red Dragon Book(第一个版本,不第二个!)就足够了。
答案 2 :(得分:4)
我看到这是一个迟到的回复,但是因为当我搜索编写解释器时,这个帖子出现在结果列表的第二位,并且没有人提到任何非常具体的内容我将提供以下示例:
免责声明:这只是我编写的一些简单的代码,以便为下面的解释奠定基础,因此并不完美,但它编译并运行,似乎给出了预期的答案。 / em>的
从下到上阅读以下C代码:
#include <stdio.h>
#include <stdlib.h>
double expression(void);
double vars[26]; // variables
char get(void) { char c = getchar(); return c; } // get one byte
char peek(void) { char c = getchar(); ungetc(c, stdin); return c; } // peek at next byte
double number(void) { double d; scanf("%lf", &d); return d; } // read one double
void expect(char c) { // expect char c from stream
char d = get();
if (c != d) {
fprintf(stderr, "Error: Expected %c but got %c.\n", c, d);
}
}
double factor(void) { // read a factor
double f;
char c = peek();
if (c == '(') { // an expression inside parantesis?
expect('(');
f = expression();
expect(')');
} else if (c >= 'A' && c <= 'Z') { // a variable ?
expect(c);
f = vars[c - 'A'];
} else { // or, a number?
f = number();
}
return f;
}
double term(void) { // read a term
double t = factor();
while (peek() == '*' || peek() == '/') { // * or / more factors
char c = get();
if (c == '*') {
t = t * factor();
} else {
t = t / factor();
}
}
return t;
}
double expression(void) { // read an expression
double e = term();
while (peek() == '+' || peek() == '-') { // + or - more terms
char c = get();
if (c == '+') {
e = e + term();
} else {
e = e - term();
}
}
return e;
}
double statement(void) { // read a statement
double ret;
char c = peek();
if (c >= 'A' && c <= 'Z') { // variable ?
expect(c);
if (peek() == '=') { // assignment ?
expect('=');
double val = expression();
vars[c - 'A'] = val;
ret = val;
} else {
ungetc(c, stdin);
ret = expression();
}
} else {
ret = expression();
}
expect('\n');
return ret;
}
int main(void) {
printf("> "); fflush(stdout);
for (;;) {
double v = statement();
printf(" = %lf\n> ", v); fflush(stdout);
}
return EXIT_SUCCESS;
}
对于支持一个字母变量的基本数学表达式,这是一个简单的recursive descend parser。运行它并键入一些语句会产生以下结果:
> (1+2)*3
= 9.000000
> A=1
= 1.000000
> B=2
= 2.000000
> C=3
= 3.000000
> (A+B)*C
= 9.000000
您可以更改get(),peek()和number()以从文件或代码行列表中读取。你还应该创建一个函数来读取标识符(基本上是单词)。然后展开statement()函数,以便能够改变它下一个运行的行以进行分支。最后,将所需的分支操作添加到语句函数中,如
if "condition" then
"statements"
else
"statements"
endif.
while "condition" do
"statements"
endwhile
function fac(x)
if x = 0 then
return 1
else
return x*fac(x-1)
endif
endfunction
显然,您可以根据需要决定语法。您需要考虑定义函数的方法以及如何处理参数/参数变量,局部变量和全局变量。如果是更好的数组和数据结构。引用/指针。输入输出? 为了处理递归函数调用,您可能需要使用堆栈。
在我看来,用C ++和STL更容易做到这一切。例如,一个std :: map可用于保存局部变量,另一个map可用于全局变量......
当然可以编写一个从代码中构建抽象语法树的编译器。然后遍历此树以生成机器代码或在虚拟机(如Java和.Net)上执行的某种字节代码。这比通过逐行解析和执行它们提供了更好的性能,但在我看来,这不是写一个解释器。那就是编写一个编译器和它的目标虚拟机。
如果有人想学习编写口译员,他们应该尝试制作最基本的简单实用的口译员。