web-dev-qa-db-ja.com

Cでインタプリタを書くにはどうすればよいですか?

いくつかの参考資料やヒント、おそらく電子書籍が1つか2つ欲しいです。私はコンパイラーを書くつもりはありません。ただ、私が従い、進むにつれて修正できるチュートリアルを探しています。ご理解のほどよろしくお願いいたします。

ところで:それはCでなければなりません。

これ以上の返信をいただければ幸いです。

22
tekknolagi

インタプリタの作成を開始するための優れた方法は、単純なマシンシミュレータを作成することです。インタプリタを書くことができる簡単な言語は次のとおりです。

言語にはスタックと6つの命令があります。

Push <num>#スタックに数字をプッシュする

pop#スタックの最初の番号をポップオフします

add#スタックの上位2つのアイテムをポップして、それらの合計をスタックにプッシュします。 (負の数を追加できるので、減算もカバーされていることを忘れないでください)。この命令で他のいくつかの命令を使用してループを作成することで、乗算を取得することもできます。

ifeq <address>#スタックの最上位を調べます。0の場合は続行します。それ以外の場合は、<address>にジャンプします。ここで<address>は行番号です。

jump <address>#行番号にジャンプ

print#スタックの一番上に値を出力します

dup#スタックの一番上にあるもののコピーをスタックにプッシュします。

これらの命令を受け取って実行できるプログラムを作成すると、基本的に非常に単純なスタックベースの仮想マシンが作成されます。これは非常に低レベルの言語であるため、ASTとは何か、文法をASTに解析し、それを機械語に変換する方法などを理解する必要はありません。それも同様です。チュートリアルプロジェクトでは複雑です。これから始めて、この小さなVMを作成したら、いくつかの一般的な構造をこのマシンに変換する方法について考え始めることができます。たとえば、Cをどのように変換するかを考えたい場合があります。/elseステートメントまたはwhileループをこの言語に変換します。

編集:

以下のコメントから、このタスクに取り組む前に、Cについてもう少し経験が必要なようです。

私が提案するのは、最初に次のトピックについて学ぶことです。

  • scanf、printf、putchar、getchar-基本的なC IO関数
  • struct-Cの基本的なデータ構造
  • malloc-メモリの割り当て方法、およびスタックメモリとヒープメモリの違い
  • リンクリスト-そしてスタックを実装する方法、そしておそらくバイナリツリー(最初に構造体とmallocを理解する必要があります)

次に、string.hライブラリ(strcmp、strdup)についてもう少し学ぶと便利です。便利な文字列関数がいくつかあります。

要するに、CはPythonに比べてはるかに高い学習曲線を持っています。これは、Cが低レベルの言語であり、独自のメモリを管理する必要があるためです。したがって、インタプリタを作成する前に、まずCに関するいくつかの基本的なことを学ぶことをお勧めします。 Pythonで作成する方法をすでに知っている場合。

24
Charles Ma

インタプリタとコンパイラのonlyの違いは、ASTからコードを生成する代わりに、代わりにVMでコードを実行することです。 。これを理解すると、ほとんどanyのコンパイラブック、さらには Red Dragon Book最初のエディション、notsecond!)で十分です。

これは少し遅い返信だと思いますが、通訳を書くために検索したときにこのスレッドが結果リストの2番目に表示され、誰も非常に具体的なことを言及していないので、次の例を示します。

免責事項:これは、以下の説明の基礎を築くために急いで書いた単純なコードであるため、完全ではありませんが、コンパイルして実行し、期待どおりの結果が得られるようです。答え。

次の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;
}

これは単純です 再帰下降パーサー 1文字の変数をサポートする基本的な数式の場合。それを実行していくつかのステートメントを入力すると、次の結果が得られます。

> (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でこれらすべてを行うのが簡単でしょう。たとえば、1つのstd :: mapを使用してローカル変数を保持し、別のマップをグローバル変数に使用できます。

もちろん、コードから抽象構文木を構築するコンパイラーを作成することは可能です。次に、このツリーを移動して、仮想マシンで実行されるマシンコードまたはある種のバイトコード(Java and .Netなど)を生成します。これにより、行ごとに単純に解析するよりもパフォーマンスが向上します。私の意見では、インタープリターを作成しているのではなく、コンパイラーとそのターゲット仮想マシンの両方を作成しています。

誰かが通訳を書くことを学びたいのなら、彼らは最も基本的なシンプルで実用的な通訳を作ってみるべきです。

4