2012-01-07 11 views
17

本「Modern Compiler Design」は、コンパイラに関する素晴らしい本です。そのソースコードでは、私を悩ましているものは、ASTまたは抽象構文木です。たとえば、次のような構文解析を行う括弧で囲まれた式パーサを記述したいとします。((2+3)*4) * 2!この本では、次のようなASTがあると言います。再帰的降下パーサー

 ((2+3)*4) * 2 
     / |  \ 
     (2+3) *4 * 2 
     / | \ 
    (2+3) * 4 
    /| \ 
    2 + 3 

私はツリーをメモリに保存するか、再帰呼び出しを使用する必要があります。注:私はそれをメモリに保存しないと、どのようにマシンコードに変換できますか?

パーサコード:

int parse(Expression &expr) 
{ 
    if(token.class=='D') 
    { 
    expr.type='D'; 
    expr.value=token.val-'0'; 
    get_next_token(); 
    return 1; 
    } 
    if(token.class=='(') 
    { 
    expr.type='P'; 
    get_next_token(); 
    parse(&expr->left); 
    parse_operator(&expr->op); 
    parse(&expr->right); 
    if(token.class!=')') 
     Error("missing)"); 
    get_next_token(); 
    return 1; 
    } 
    return 0; 
} 

文法は以下のとおりです。

expr -> expr | (expr op expr) 
digit -> 0|1|2....|9 
op -> +|* 

答えて

18

あなたは、メモリ内のツリーを保存することもできますし、直接必要な出力コードを生成することができます。中間形式を格納することは、通常、出力を生成する前に、より高いレベルでコード上で何らかの処理を行うことができるように行われる。

たとえば、式に変数が含まれていないことがわかり、その結果が固定数になっていることがわかります。一度に1つのノードだけを見ることは可能ですが、これは不可能です。 "2 *"を見た後で、何かの倍を計算するためのマシンコードを生成すると、プログラムは "3"を計算してから、他の部分が例えば "3"のときにこのコードは無駄になります。そのたびの倍数 "6"をロードするだけで同等ですが、より短く高速です。

マシンコードを生成したい場合は、コードが生成されるマシンの種類をまず知る必要があります。最も単純なモデルはスタックベースのアプローチです。この場合、レジスタ割り当てロジックは不要で、中間表現なしで直接マシンコードにコンパイルするのは簡単です。整数、4つの演算、単項否定と変数を扱うこの小さな例を考えてみましょう。データ構造はまったく使われていないことに気づくでしょう:ソースコード文字が読み込まれ、機械命令が出力に書き出されます...あなたが出力

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 

として取得する入力として1+y*(-3+x)でコンパイラを実行しているたとえば

#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') { 
     // Number 
     compileLiteral(s); 
    } else if ((*s >= 'a' && *s <= 'z') || 
       (*s >= 'A' && *s <= 'Z') || 
       (*s == '_')) { 
     // Variable 
     compileSymbol(s); 
    } else if (*s == '-') { 
     // Unary negation 
     s++; 
     compileTerm(s); 
     printf(" neg eax\n"); 
    } else if (*s == '(') { 
     // Parenthesized sub-expression 
     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; 
} 

しかし書き込みコンパイラのこのアプローチは、最適化コンパイラにうまくスケールしません。

出力段に「ピープホール」オプティマイザを追加することで最適化することは可能ですが、多くの有用な最適化はより高い視点からのコードを見るだけで可能です。

また、ベアマシンコードの生成でも、どのレジスタをwhatに割り当てるかを決定したり、特定のコードパターンに便利なアセンブラ実装を決定したりするなど、より多くのコードを見ることで利益を得ることができます。

例えば同じ式はだから私はメモリ内のツリーを保存するか、単に再帰呼び出しを使用する必要があります

mov eax, dword ptr x 
sub eax, 3 
imul dword ptr y 
inc eax 
2

あなたはダイクストラのShunting-yard algorithmとASTを作成することができます。

解析中に即時結果を計算しない限り、ある時点では式全体またはASTがメモリに格納されます。これは、リテラルまたはコンパイル時定数を含む(サブ)式では動作しますが、実行時に計算される変数では機能しません。

4

10回のうち9回は、レキシングと解析が完了した後に行っている作業のためにASTをメモリに保存します。

あなたは多くのことを行うことができますASTたら:

  • することなど、いくつかの他の出力、に変換し
  • (おそらく、おそらく独自のカスタム・スタックを使用して、再帰を使用して)それを直接評価します別の言語のコードまたは他のタイプの翻訳を使用することができます。
  • 優先命令にコンパイル
  • を設定するなど
0

に最適化コンパイラでコンパイルすることができ、

パーサーで再帰呼び出しを使用してツリーをメモリに構築します。

もちろん、処理するためにツリーをメモリに保持したいとします。

メモリのコードの表現が最適化コンパイラによって保持されます(変換されます)。

0

質問に対する答えは、コンパイラ、インタープリタ、または何か(中間言語を囲んでいるインタプリタ)を必要とするかどうかによって異なります。インタプリタを必要とするならば、再帰的降下パーサは同時に式を評価するので、メモリに保持する必要はありません。コンパイラが必要な場合は、例のような定数式を最適化できますが、ほとんどの式は変数で動作し、リニア形式に変換する前に中間ステップとしてツリー形式に変換する必要があります。

ハイブリッドコンパイラ/インタプリタは通常、式をコンパイルしますが、必ずしもコンパイルする必要はありません。インタプリタをソースコードで単純に囲むように実行可能ファイルを出力するプログラムを作成するための安価な方法です。 Matlabはこの手法を使用しています。実際にはコンパイルされたコードですが、インタラクティブバージョンとの整合性に問題がありました。しかし、私は表現のための解析木を生成することの難しさが問題を決定するのを許さないでしょう。

関連する問題