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() で返される。