SECDV Schemeの実装 - ランタイムライブラリの構成

ヒープはとりあえず適当にfakeして(= Scheme上のオブジェクトで同等機能を実装することにして)ランタイムライブラリの実装に進む。ヒープ実装はバグりやすいので、一旦他の部分の品質が担保できる状態になってからネットリと進めるのが良い気がしている。
ランタイムライブラリは基本的に退屈なパートで、かつ、品質に対する要求も高いので可能な限り再利用性とテスト可能性に配慮して作る。

ランタイムライブラリの移植層、つまり、図中のHeap + Coreがyuniが想定する必要最低限のScheme手続きということになる。ヒープを実装する場合は、Heap + Coreに含まれる手続きを全て提供しなければならない。
... 一般的なScheme処理系と比べてもちょっと多い気がするが、Scheme標準の構文をほぼ全て実装できる内容とするためにかなりリッチになってしまった。例えば、R7RSに含まれるcase-lambdaを実装するためには、lengthとか比較演算が必要( https://github.com/okuoku/yuni/blob/cce868a69f3e265134eac73a90b977c3197ac05f/lib-r7c/r7c-report/misc/case-lambda.sls#L23 )になる。
(ほぼと付いているのは、parametizeを含めていないため。R7RSに含まれる参照実装ではdynamic-windを使って実装しているが、処理系のプリミティブとして実装する手もあるし、そもそもyuniでは必要としていないので省略した。あとdefine-record-typeも含めていない。)
今回、lengthや比較演算はfixnum専用のものを別途定義してそちらを要求することにした。長さがbignum表現されるようなリストを少なくとも構文では使わないし、Scheme側でbignumを実装しようとすると不都合なため。この手のScheme規格から見たサブセットは先頭に $ を付けて区別している。(simple-structのようなyuni独自仕様は特にそのような変更をしていない)
$boolean=?のような比較は2要素の比較だけをサポートする(3要素以上はScheme側で実装すれば十分なため)。$make-vector等のmake系は、初期値の設定機能を省略している。つまり、基本的にHeap + Coreには可変長引数の手続きが含まれないように配慮している。例外はapplyやerror等で、これらはあんまり真面目に考察していない。特にerrorやraise系は分離した方が良いような気もしているが。。
Heap + Coreが必ずしも構文の実装に必要なものだけというわけではなく、前回( http://d.hatena.ne.jp/mjt/20170530/p1 )書いたようなScheme型を実現するために必要最低限のものを含んでいる。例えばbytevectorは構文には不要だがヒープがbytevectorを提供しないわけには行かないので含めている。
意外と芋蔓式に入るものが多い。caseの実装にはmemvが必要になり、memvの実装にはeqv?が必要となり、eqv?の実装のためにはboolean=?のような手続きも必要となる。逆に、R7RSでは文字列に対するeqv?を規定していない(!?)ので、ここにはstring=?を含めていない。

最小化へのアイデア

今回はとにかく作業を急いでいるのでHeap + Coreの内容を洗練させることにはあんまり頭が向いていないが、Scheme標準構文をもうちょっと細分化することでHeap + Coreの内容は減らすことができる。
case-lambdaが無ければlengthや比較演算を省略することができる。実際r7c-basicに相当するような項目をcase-lambdaを使って実装することは無いのでcase-lambdaは分離してしまっても良いような気がする。
guardが無ければcall/cc系を省略できる。例外に関してはあまり考察が進んでいない。今回はScheme-on-Schemeで検証を進めているため、ホストScheme側の例外とどうやって接続するかという問題を解決する必要がある。これはcall/ccそのものにも言える。
完全な継続を実装していればvalues系は不要になる。しかし、SECDVではvaluesは専用の考察を持っているのでCoreに含めている。この仕様によりcall/ccの実装をサボってもとりあえず多値が使用できるというメリットは有る。
not手続きやcaarのような手続きは構文には不要だが他とのバランスで含めている。notはunlessの参照実装で一応使用しているが、本来は(eqv? #f x)で代用できる。
逆に、Heap + Coreが比較的リッチであることを利用して、SECDV VMバイトコードで直接使えるようにしてしまっても良いかもしれない。どうせこれらの手続きはネイティブコードで実装されることになるので、ランタイムシステムだけでなくコンパイラもHeap + Coreが常に使用できることを期待してしまって良いような気はする。