手抜きの限りを尽くしてScheme処理系を作りたい(再)
ちょっと前回( https://zenn.dev/okuoku/scraps/98d91b70370703 )スケジュールの都合で何もせずに終わってしまったので再度。。
Schemeサブセットの設計
作業を始める前にSchemeサブセットの仕様を考えておく。
GenericSchemeとFakeScheme
実装を適当に済ませるために、サブセットをGenericSchemeとFakeSchemeの2段階に分ける。GenericSchemeは define-macro
がある処理系、FakeSchemeは無い処理系と要約できる。
実際にSchemeプログラムを実行するためには define-macro
で定義されるマクロを展開(expand)する必要があり、これの実装が地味に面倒なのでC/C++ではFakeSchemeの方だけを実装することにしたい。
で、今回は主に GenericScheme仕様のプログラムをFakeScheme仕様のプログラムに変換するための expander をSchemeで実装する。GenericSchemeは既存のs7とかGaucheのようなScheme実装の厳密なサブセット、FakeSchemeはGenericSchemeの厳密なサブセットになるため、既存のScheme実装をFakeScheme実装と見做してexpanderをテストできる。
GenericSchemeにおける文脈
Schemeプログラム内部には3つの文脈 1) トップレベル 2) lambda 3) シーケンス が存在する。トップレベルはプログラムの開始時点のものであり、 lambda は lambda
とか let
のような予約語で遷移する。シーケンスはただの式。
FIXME: 後でR7RSを確認してScheme標準と用語を揃える
トップレベル には define
define-macro
および通常の式を置くことができる。トップレベルに define
または define-macro
したものはライブラリとしてエクスポートできるという特徴があるため特別扱いする。
lambda には define
および通常の式を置くことができる。以前BiwaSchemeがこの位置に define-macro
を置けなかったためGenericSchemeでは置けないルールにしているが他の大半のScheme実装では問題なく置けるのでこの制約は外してしまっても良いかもしれない。また、 lambda内での define
はlambdaを抜けた際に無効化する必要がある ため、特別扱いしている。更に、 define
以外の式が現われたら以降は通常の式のみ という制約がSchemeの規格として有り、これによりコンパイルのコストを下げられるようになっている。
シーケンス は通常の式のみを置くことができる。