TinySCHEME のソースを読む -3 TinySCHEME の処理エンジン
TinySCHEME のソースを読む -1 セルの構造
TinySCHEME のソースを読む -2 シンボル、環境
TinySCHEME のソースを読む -3 TinySCHEME の処理エンジン
TinySCHEME のソースを読む -4 トップレベルからの処理の流れ
TinySCHEME のソースを読む -5 define の例
tsdbg TinyScheme 用デバッグ extension
TinySCHEME の処理ロジック
TinySCHEME は再帰というものを一切用いず、独自の処理ロジックを持つ。
Eval_Cycle
main() から初期化処理を経ると、TinySCHEME の中心的な処理エンジンである Eval_Cycle() が呼ばれる。
この関数は無限ループになっており、オペレーションコード sc->op に従って処理を呼び出す構造になっている。
4305 行目から Eval_Cycle() のソースが定義されている。以下は Eval_Cycle() の宣言である:
static void Eval_Cycle(scheme *sc, enum scheme_opcodes op);
この関数は、まず sc->op を引数 op で初期化する。
次に for(;;) でループに入り、sc->op を評価する。そして、sc->op 毎に定義された処理(以後オペレーションと呼ぶ)、へ飛ぶ。
各オペレーションは、それぞれ決まった処理をこなし、T か NIL を返す。
オペレーションから T が返る場合、Eval_Cycle() はループに戻り、処理を続ける。この場合、オペレーションは sc->op を更新していることが多い。
オペレーションから NIL が返った場合、Eval_Cycle() はループを抜け、終了する。
sc->op は、4308 行目で定義された pcd の初期化に使われる:
op_code_info *pcd = dispatch_table + sc->op;
op_code_info の定義は以下である:
typedef pointer (*dispatch_func) (scheme *, enum scheme_opcodes); typedef struct { dispatch_func func; char *name; ... // 略 } op_code_info;
4367 行目で、オペレーション func() を呼び出す:
if (pcd->func(sc, (enum scheme_opcodes)sc->op) == sc->NIL) {
func() を呼び出すまでに行っている処理があるが、これは引数のエラーチェックである。
オペレーションで使われる特別な関数 / マクロ
オペレーションは特別な関数やマクロを使って処理の継続や終了を行う。
s_goto()
s_goto() は、引数で渡されるオペレーションに強制的にジャンプするものである。
このマクロは 2319 行目に定義がある:
#define BEGIN do { #define END } while (0) #define s_goto(sc, a) BEGIN \ sc->op = (int)(a); \ return sc->T; END
sc->op を更新し、sc->T を返すことで、Eval_Cycle() に戻り、次の sc->op の処理へ飛ぶ。
s_save()
s_save() はある時点での状態を保存するものである。
s_save() は 2438 行目で定義されている関数である:
static void s_save(scheme *sc, enum scheme_opcodes op, pointer args, pointer code) { sc->dump = cons(sc, sc->envir, cons(sc, (code), sc->dump)); sc->dump = cons(sc, (args), sc->dump); sc->dump = cons(sc, mk_integer(sc, (long)(op)), sc->dump); }
TinySCHEME は dump というセルを使い、スタックフレームのようなものを実現している。
dump には、op コード、引数を表す args、s_save() が呼ばれた時点の環境 envir、処理する手続きを表す code、そして、以前の dump がリストとして含まれる。
dump に積まれた情報は、s_return() で復元される。
s_return()
s_return() はオペレーションを終了する場合に呼び出される。
直前の s_save() で保存されたデータを復元する処理を行う。
2323 行目と 2427 行目に定義されている。
#define s_return(sc, a) return _s_return(sc, a) static pointer _s_return(scheme *sc, pointer a) { sc->value = (a); if (sc->dump == sc->NIL) return sc->NIL; sc->op = ivalue(car(sc->dump)); sc->args = cadr(sc->dump); sc->envir = caddr(sc->dump); sc->code = cadddr(sc->dump); sc->dump = cddddr(sc->dump); return sc->T; }
ここでは、s_save() で保存されたデータ以外に、sc->value という変数が出てくる。
これはオペレーションの戻り値ないし処理結果という認識で良い。
s_return() は、見て分かる通り、dump にデータが積まれていればそれを復元し sc->T を返すので、結果的に Eval_Cycle に戻り、復元した sc->op へ飛ぶ。
dump が無い場合は NIL を返すため、Eval_Cycle() は終了する。
s_return() には return というキーワードが含まれているが、感覚的には return and call としたほうが、しっくりくる。
s_return() は、自分を呼び出した元に戻るわけでは無い。
s_save() によって登録ないし予約された処理を、これから呼び出すのである。
syntax
syntax は if や define, lambda などを表すセルで、4379 行目 assign_syntax() で作られる。
static void assign_syntax(scheme *sc, char *name) { pointer x; x = oblist_add_by_name(sc, name); typeflag(x) |= T_SYNTAX; }
syntax は、car 部に syntax 名 ( string )、cdr に NIL を指した、コンスセル一つだけで表現される。このコンスセルには T_SYMBOL | T_SYNTAX フラグが付く。
各 syntax のオペレーションコードは、4405 行目 syntaxnum() で返される。