ruiさんのminilispの評価器のところを勝手に解説します。
対象読者はタイムインターメディアの社内勉強会のメンバーですが誰かの役に立つかもしれないと思い公開します。
https://github.com/rui314/minilisp/blob/nogc/minilisp.c#L352 からがevaluatorです。
static Obj *eval(Obj *env, Obj *obj);
evalを構成する関数の中でevalを使うためにプロトタイプ宣言しておきます。
static void add_variable(Obj *env, Obj *sym, Obj *val) {
env->vars = acons(sym, val, env->vars);
}
envは環境フレームで、上位の環境(up
)と、自分自身が持っている変数(vars
)との組になっています。
add_variable
はvars
に変数を追加します。vars
はalistになっています。
変数の追加は単に今までのvars
にaconsした結果を新しいvars
にするだけです。
// Returns a newly created environment frame.
static Obj *push_env(Obj *env, Obj *vars, Obj *values) {
if (list_length(vars) != list_length(values))
error("Cannot apply function: number of argument does not match");
Obj *map = Nil;
for (Obj *p = vars, *q = values; p != Nil; p = p->cdr, q = q->cdr) {
Obj *sym = p->car;
Obj *val = q->car;
map = acons(sym, val, map);
}
return make_env(map, env);
}
これは環境フレームを作る関数ですが、関数呼び出し処理の一部です。ちょっとここで環境フレームとスコープについての解説。
環境フレームが作られる時というのはつまりスコープが作られる時ということになります。馴染みのあるschemeの例だとlambdaとlet系の構文がスコープを作っています。letは意味的には
(let ((x a)
(y b))
body...)
;;↓
((lambda (x y) body...) a b)
のように解釈できます。xとyが定義される環境は関数が実際に呼び出されて、関数のbody部分に来たときに新しく発生しているわけです。つまり、lambdaこそがスコープを作る構文ということになります。
これを踏まえて、push_envが何をしているか見ていきます。まず引数のvars
というのは関数のパラメータで(xとy)、values
は実引数です(aとb)。最初のエラーチェックは関数適用ができるかどうかの引数の個数チェックです。
次のfor文はvars
とvalues
をzipしてalistを作っています(変数map)。
最後に上位環境とこのmapを使って、新しい環境を作れば、めでたく関数本体を実行するときに使う環境が完成です。
続く