Ribbon: 名前空間の実装

とりあえずToDoで最も優先すべきなのは自分自身でbootstrapできるようにすることだろう。そうしないと、CIにGaucheが必要でWindows対応がちょっと面倒なことになる。
対応方針
... できれば元々のRibbitからの改造は必要最低限で済ませたい。。それでいて、かつ、実行時のインパクトを最小にしようとするならば:
- オフラインコンパイルではVMでの実行直前にシンボルをリネームする
- オンラインコンパイルではコンパイル中にシンボルをリネームする
を、それぞれ実装するしか無いかな。。
シンボルのリネーム
Ribbitにおけるシンボルオブジェクトは:
#(データ 名前)
のように、シンボルをevalした際に得られるデータと名前を格納したオブジェクトとして表現される。Schemeの標準の範囲内では、シンボルは常に intern されるので、 string->symbol
したシンボルと quote
したシンボルは eq?
的な意味で一致しなければならない。
(eq? (string->symbol "test") 'test) ;; => 真
これを実現するために、Ribbonではstring hashtableをVMに持たせて "名前" => シンボル が1対1で対応するようにしている。 このハッシュテーブルこそが名前空間と考えられる 。

rename hookの導入
オンラインコンパイル用のhookをとりあえず入れた。
このhookでは、グローバル名前空間のシンボルをライブラリ名前空間のシンボルに交換することになる。今は単にno-opだけど。
EDIT: 場所間違えてた

VM命令のトラバース
オフラインコンパイル用には、VM命令に含まれているシンボルをリネームする必要がある。 このためには、VMに与える全ての命令を列挙しなければならない。
VM命令はribで表現され、それぞれのフィールドは以下のように解釈される。... 要するに先頭がOPコード、末尾が次の命令 または 0(命令列の終端を示す)となる。
0 | 1 | 2 |
---|---|---|
JMP(0) | getref | 0/<next> |
SET(1) | getref | <next> |
GET(2) | getref | <next> |
CONST(3) | procedure | <next> |
IF(4) | <next> | <next> |
ENTER(5) | number | <next> |
CONSTOBJ(6) | quote | <next> |
HALT(otherwise) | don't care | don't care |
(ENTER、CONSTOBJ命令はribbonの独自拡張で本来のribbitには存在しない)
真ん中の解釈は命令による。
<next>は次の命令を表わす。よって常にribとなり、その内容は上記の表のいずれかになる。
getrefはグローバル変数参照の場合はシンボルであり、そうでない場合は数値となる。今回の目的のためにはgetrefで使用されたシンボルをリネームする必要がある。
number および quote、don't careはSchemeオブジェクトで、ribではなく、ribを含むこともない。
procedureは手続きのテンプレートをあらわすribであり、以下のような構造をしている。
0 | 1 | 2 |
---|---|---|
code | nil | procedure-type(1) |
真ん中は常に空リスト(nil)となり、本来は専用のライブラリ手続き close
によって埋められる。
codeは更にribであり、以下のような構造をしている。
0 | 1 | 2 |
---|---|---|
argc | 0(未使用) | <next> |
argcはlambdaが取る引数の数になるが、Ribbonでは可変長引数に対応しており、 可変長引数の場合は負値となる 。
コンパイルされたプログラムは procedure となる。
... 以上の情報で、コンパイルされたプログラムの全てのribと参照しているグローバル変数を列挙することができる。

ribcode
とりあえず全ての命令をパースしてribを含まない形にエンコード/それをデコードしてみて機能が完全であることを確認した。デコーダはSchemeとCの両方で実装していて、Scheme側をCにほぼコピペできる形で書いている:
これで長さ == 3のvectorをribと見做すヒューリスティックが不要になったので、vectorのテストが通るようになった。(以前のコードだと、長さ3のvectorをリテラルとして書くとribに化けてしまっていた)

とりあえずスクリプトについて実装
まぁ事前コンパイルぶんはbootstrapには要らないから無しで良いか。。
renameのロジックはuninternedなシンボルが作成できることに依存している。つまり、ライブラリの import
export
ではグローバルなシンボルテーブルではなくinternされていないシンボルを直接やりとりできるようにしている。uninternedなシンボルは全てのSchemeで使えるわけではないので、本物のRibbitVMでないと動作しない。というわけでテストからはSchemeで実装したVMからbootstrapする系は取り除いた。