我怎么去用C写一个翻译?

时间:2011-07-31 04:00:05

标签: c interpreter

我喜欢一些参考或提示,可能是一两本电子书。我不打算编写一个编译器,只是寻找一个我可以遵循的教程并随时修改。谢谢你的理解!

BTW:必须是C。

任何更多的回复将不胜感激。

3 个答案:

答案 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的经验才能解决此问题。

我建议首先了解以下主题:

  • scanf,printf,putchar,getchar - 基本C IO功能
  • struct - C
  • 中的基本数据结构
  • malloc - 如何分配内存,以及堆栈内存和堆内存之间的区别
  • 链表 - 以及如何实现堆栈,然后可能是二叉树(你需要 首先了解结构和malloc)

然后,了解一下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)上执行的某种字节代码。这比通过逐行解析和执行它们提供了更好的性能,但在我看来,这不是写一个解释器。那就是编写一个编译器和它的目标虚拟机。

如果有人想学习编写口译员,他们应该尝试制作最基本的简单实用的口译员。