【问题标题】:How would I go about writing an interpreter in C? [closed]我将如何用 C 编写解释器? [关闭]
【发布时间】:2011-10-16 18:41:50
【问题描述】:

我想要一些参考资料或提示,可能是一两本电子书。我不想编写编译器,只是在寻找一个我可以跟随并随时修改的教程。感谢您的理解!

顺便说一句:它必须是 C。

如果有更多的回复将不胜感激。

【问题讨论】:

  • 深入研究已经存在的项目可能会让人不知所措。好问题。
  • 谢谢!我试过这样做,我发现从头开始编写代码更容易,所以你知道什么是什么。
  • 关于您的编辑:a) 请不要将两个不相关的问题放在同一个问题中,并且 b) 使用 GNU readline(或 BSD 编辑行)库。
  • 请务必查看其他解释器/编译器书籍/教程问题!对于这样一个多方面的话题,您通常需要探索材料,直到您发现它的“角度”。然后您就可以开始整合来自任何来源的信息了。
  • 我推荐使用 C 语言的小型 BASIC 解释器,代码行数很少,但易于扩展:my basic

标签: c interpreter


【解决方案1】:

开始编写解释器的一个好方法是编写一个简单的机器模拟器。这是一种您可以为其编写解释器的简单语言:

该语言有一个堆栈和 6 条指令:

push <num> # 将一个数字压入堆栈

pop # 弹出栈中的第一个数字

add # 弹出栈顶 2 项并将它们的总和压入栈中。 (请记住,您可以添加负数,因此您也可以使用减法)。您还可以使用其他一些指令来创建循环。

ifeq <address> #检查栈顶,如果为0则继续,否则跳转到<address>,其中<address>是行号

jump <address>#跳转到某行号

print # 打印栈顶的值

dup # 将堆栈顶部的副本推回堆栈。

一旦您编写了一个可以接受这些指令并执行它们的程序,您实际上就创建了一个非常简单的基于堆栈的虚拟机。由于这是一种非常低级的语言,因此您无需了解 AST 是什么、如何将语法解析为 AST 并将其翻译成机器代码等。这对于教程项目来说太复杂了。从这个开始,一旦你创建了这个小虚拟机,你就可以开始考虑如何将一些常见的结构转换到这台机器上。例如您可能想考虑如何将 C if/else 语句或 while 循环翻译成这种语言。

编辑:

从下面的 cmets 看来,您似乎需要更多的 C 经验才能完成这项任务。

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

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

那么最好多了解一下 string.h 库 - strcmp, strdup - 几个有用的字符串函数。

简而言之,与 python 相比,C 的学习曲线要​​高得多,只是因为它是一种较低级别的语言,而且你必须管理自己的内存,所以在尝试编写一个 C 之前,最好先学习一些关于 C 的基本知识。解释器,即使你已经知道如何在 python 中编写一个。

【讨论】:

  • ifeq 是什么?我不明白...
  • 还有复合语句吗?
  • 最后,我不明白如何解释文字指令。在python中,我可以将单词存储在一个数组中,它们是否具有不同的长度并不重要。但是,在 C 中,它不允许您这样做。我如何在 C 中解释这个?
  • 这是我正在寻找教程的那种东西。
  • ifeq 代表 if equals,它基本上告诉您查看堆栈顶部并在堆栈顶部不为零时跳转到某个位置。它是一种常见的汇编语言符号。这是一种汇编语言,0 通常表示某事相等。该语言仅处理堆栈,因此本身没有复合语句,您必须将“复合语句”翻译成该语言才能执行它。
【解决方案2】:

解释器和编译器之间的唯一区别在于,不是从 AST 生成代码,而是在 VM 中执行它。一旦你理解了这一点,几乎任何编译器书籍,甚至是Red Dragon Book第一版不是第二版!),就足够了。 p>

【讨论】:

  • 1:什么是 AST? 2:虚拟机的定义是什么?我有一个解释表达式的后缀计算器。那是虚拟机吗? 3:非常感谢:)
  • 1. Abstract Syntax Tree 2. 一个automaton,逐个操作解释代码操作。后缀计算器可以看作是虚拟机的一种简单形式。
  • 虽然这是您设计真实语言时应该考虑的方式,但我认为这个答案对于初学者来说可能有点过头了。可能有一种完全命令式且不需要大量解析的解释性语言(例如,一种虚构的汇编语言)。实际上,对于有兴趣编写解释器的人来说,我会考虑为简单的现实世界 cpu 使用 VM 或模拟器。
  • 无耻插件:这是一个 lexer, parser and interpreter 用于极简命令式语言,全部在 1.4k LoC 中。它可能具有很好的教育价值。
【解决方案3】:

我看到这个回复有点晚了,但是由于当我搜索编写解释器时这个线程出现在结果列表中的第二位,并且没有人提到任何非常具体的内容,我将提供以下示例:

免责声明:这只是我匆忙写的一些简单代码,以便为下面的解释奠定基础,因此并不完美,但它可以编译和运行,并且似乎给出了预期的答案。

从下往上阅读以下 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 可用于保存局部变量,而另一个映射可用于全局变量...

当然可以编写一个从代码中构建抽象语法树的编译器。然后遍历这棵树以生成在虚拟机(如 Java 和 .Net)上执行的机器代码或某种字节代码。这比天真地逐行解析并执行它们提供了更好的性能,但在我看来,这并不是在编写解释器。那就是编写编译器及其目标虚拟机。

如果有人想学习编写解释器,他们应该尝试制作最基本的简单实用的工作解释器。

【讨论】:

  • 嗨@Visitor Iterator。我希望你能读懂我。这个例子是我找到的最短的例子,清晰且有效。请问有没有推荐的在线参考或书籍?
  • 嗨@Massimo 幸运的是我做到了:) 是的。我知道这个链接compilers.iecc.com/crenshaw 和本书的开头amazon.com/Crafting-Compiler-Charles-N-Fischer/dp/0805321667 采用了类似的方法,但用于编写编译器。 (我只读了前几章)。对于解释器,您只需评估立即遇到的不同标记。在我看来,这实际上使很多事情变得更容易。 (不需要存储和遍历抽象语法树并发出机器代码)。希望对您有用。
猜你喜欢
  • 2011-11-14
  • 2010-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-11
相关资源
最近更新 更多