コンパイラとVMを作成するよりも簡単なのは、インタプリタを登録してトランポリンすることです。コンパイラではなくインタプリタがあるので(私が思うに)、末尾呼び出しを適切にサポートするには、いくつかの簡単な変換だけが必要です。
最初にすべてを継続渡しスタイルで作成する必要があります。これは、C /C++で考えて実行するのは奇妙かもしれません。Dan FriedmanのParentheCチュートリアルでは、高レベルの再帰プログラムをCに機械変換可能な形式に変換する手順を説明します。
最後に、基本的に単純なVMを実装します。ここでは、通常の関数呼び出しを使用してevalやapplyProcなどを実行する代わりに、グローバル変数を設定して引数を渡しgoto
、次の引数にaを実行します(またはトップレベルを使用します)。ループおよびプログラムカウンター)..。
return applyProc(rator, rand)
になります
reg_rator = rator
reg_rand = rand
reg_pc = applyProc
return
つまり、通常は相互に再帰的に呼び出すすべての関数は、再帰しないコードのブロックである疑似アセンブリに縮小されます。トップレベルのループがプログラムを制御します。
for(;;) {
switch(reg_pc) {
case EVAL:
eval();
break;
case APPLY_PROC:
applyProc();
break;
...
}
}
編集: JavaScriptで書かれた趣味のSchemeインタープリターでも同じプロセスを実行しました。多くの匿名の手順を利用しましたが、具体的な参考になるかもしれません。2011-03-13( 30707a0432563ce1632a )から2011-03-15(5dd3b521dac582507086)までのFoxSchemeのコミット履歴を見てください。
Edit ^ 2:スタックにない場合でも、非末尾再帰はメモリを消費します。