AST解釈実行
3194 ワード
構文解析と意味解析の結果は抽象構文ツリーASTであり,その後のコンパイル原理にはコード生成と最適化の大きな部分があるが,アクチュエータを1つ作るだけでASTまで解釈して実行することができ,もちろんASTを生成しなくても解析実行は可能であるが,前述した理由に基づいて解析実行の方式を採用することは極めて少ない.
現在の解釈実行言語の多くは、仮想マシンでバイトコードを解釈して実行されているが、その後、ASTの解釈をシリアル化しただけで、実際にrubyは1.9バージョン以前にASTを解釈して実行され、1.9にYARVを統合して解釈バイトコードに変更された.
抽象的に見ると、高級言語のASTはstmtです.list、文法分析の過程はただ人が理解できる言語を便利な解釈器に変えて理解するだけで、具体的な実行過程は人がコードを読むのと何の違いもなくて、1つ1つのstmtが実行して、前にstmtのインタフェース類の様子を話したことがあります:
このRetTypeは後述するが,形式的に解釈器のアーキテクチャは次のように考えられる.
したがって、コア部分は各stmtのexecute部分を実現することにあり、簡単にするために、動的タイプ言語について議論します.すべての変数タイプはObjectを使用しています.ここではexprのインタフェースが必要です.
静的タイプの場合は複雑になりますが、考えにくいことではありません.
すると、前のStmtPrintのexecuteメソッドが簡単に書けます(便宜上、コードは主にjavaのような形式を使用していますが、必ずしもjava構文と一致するとは限りません):
次に、StmtWhileのexecuteメソッドを考慮します.
ここで、ネストされた文のリストが表示され、それに応じてexecute_が再帰的に呼び出されます.stmt_List解釈実行、ここではRetTypeの意味を説明する必要があります.具体的な構文によると、1つの文ブロックが戻るのは一般的に以下のような状況があります.実行終了、break、continue、return、異常です.RetTypeはこのいくつかに対応していますが、明らかに上//...の内容の差は多くありません.
returnと異常は直接上の階に戻ります.while自体は関数やtryの中にあるので、上の階で処理すればいいです.また、returnの戻り値と異常の内容はすでにどこかにあるはずですが、これは上の偽コードには現れていません.それは実行時環境の範疇に属しています.後で話します.
exprのcomputeメソッドの実装は、差が少なくても似ています.まず、各オペランドを計算し、最後に加算などの現在のオブジェクトの演算を行います.
再帰呼び出し要素のcomputeは、もちろんこの再帰は無限ではなく、最後にload操作に停止します.
ここでenvはランタイム環境の抽象であり、簡単に言えばaの内容に基づいて対応するオブジェクトを返すことができる.このload操作は一般的に名前空間によっていくつかに細分化される.例えば、LoadLocal、LoadGlobal、LoadConstは、局所変数領域、グローバル領域、定数領域を表す.
特殊ポイントの、関数呼び出しexpr:
この実現は少し醜いが、実はf.executeに変えることができる(...)もっと直感的で、ここのexecute_stmt_list_with_frmおよびret_value存在スタックフレームには意味が表れるだけで,実際には必ずしもそうではない.
現在の解釈実行言語の多くは、仮想マシンでバイトコードを解釈して実行されているが、その後、ASTの解釈をシリアル化しただけで、実際にrubyは1.9バージョン以前にASTを解釈して実行され、1.9にYARVを統合して解釈バイトコードに変更された.
抽象的に見ると、高級言語のASTはstmtです.list、文法分析の過程はただ人が理解できる言語を便利な解釈器に変えて理解するだけで、具体的な実行過程は人がコードを読むのと何の違いもなくて、1つ1つのstmtが実行して、前にstmtのインタフェース類の様子を話したことがあります:
class Stmt
{
RetType execute();
}
このRetTypeは後述するが,形式的に解釈器のアーキテクチャは次のように考えられる.
RetType execute_stmt_list(StmtList stmt_list)
{
for stmt in stmt_list // ,
{
RetType ret = stmt.execute();
// RetType,
}
}
したがって、コア部分は各stmtのexecute部分を実現することにあり、簡単にするために、動的タイプ言語について議論します.すべての変数タイプはObjectを使用しています.ここではexprのインタフェースが必要です.
class Expr
{
Object compute();
}
静的タイプの場合は複雑になりますが、考えにくいことではありません.
すると、前のStmtPrintのexecuteメソッドが簡単に書けます(便宜上、コードは主にjavaのような形式を使用していますが、必ずしもjava構文と一致するとは限りません):
Object a = this.e.compute();
System.out.println(a);
return RET_TYPE_NORMAL;
次に、StmtWhileのexecuteメソッドを考慮します.
while (this.condition.compute().as_bool()) // ,as_bool Object ,
{
RetType ret = execute_stmt_list(this.stmt_list);
//...
}
ここで、ネストされた文のリストが表示され、それに応じてexecute_が再帰的に呼び出されます.stmt_List解釈実行、ここではRetTypeの意味を説明する必要があります.具体的な構文によると、1つの文ブロックが戻るのは一般的に以下のような状況があります.実行終了、break、continue、return、異常です.RetTypeはこのいくつかに対応していますが、明らかに上//...の内容の差は多くありません.
switch (ret)
{
case RET_TYPE_NORMAL:
case RET_TYPE_CONTINUE:
continue;
case RET_TYPE_BREAK:
break; // while , switch
case RET_TYPE_RETURN:
return RET_TYPE_RETURN;
case RET_TYPE_EXCEPTION:
return RET_TYPE_EXCEPTION;
}
returnと異常は直接上の階に戻ります.while自体は関数やtryの中にあるので、上の階で処理すればいいです.また、returnの戻り値と異常の内容はすでにどこかにあるはずですが、これは上の偽コードには現れていません.それは実行時環境の範疇に属しています.後で話します.
exprのcomputeメソッドの実装は、差が少なくても似ています.まず、各オペランドを計算し、最後に加算などの現在のオブジェクトの演算を行います.
class ExprAdd
{
Expr a;
Expr b;
Object compute()
{
return a.compute().add(b.compute());
}
}
再帰呼び出し要素のcomputeは、もちろんこの再帰は無限ではなく、最後にload操作に停止します.
class ExprLoad
{
LoadArg a;
Object compute()
{
return env.get(a);
}
}
ここでenvはランタイム環境の抽象であり、簡単に言えばaの内容に基づいて対応するオブジェクトを返すことができる.このload操作は一般的に名前空間によっていくつかに細分化される.例えば、LoadLocal、LoadGlobal、LoadConstは、局所変数領域、グローバル領域、定数領域を表す.
特殊ポイントの、関数呼び出しexpr:
class ExprCallFunc
{
Func f;
ExprList arg_list;
Object compute()
{
frm = f.init_frame(compute_expr_list(arg_list)); //
RetType ret = execute_stmt_list_with_frm(f.stmt_list, frm);
assert ret == RET_TYPE_RETURN;
return frm.ret_value;
}
}
この実現は少し醜いが、実はf.executeに変えることができる(...)もっと直感的で、ここのexecute_stmt_list_with_frmおよびret_value存在スタックフレームには意味が表れるだけで,実際には必ずしもそうではない.