Open5

Ribbon: 名前空間の実装

okuokuokuoku

とりあえずToDoで最も優先すべきなのは自分自身でbootstrapできるようにすることだろう。そうしないと、CIにGaucheが必要でWindows対応がちょっと面倒なことになる。

対応方針

... できれば元々のRibbitからの改造は必要最低限で済ませたい。。それでいて、かつ、実行時のインパクトを最小にしようとするならば:

  1. オフラインコンパイルではVMでの実行直前にシンボルをリネームする
  2. オンラインコンパイルではコンパイル中にシンボルをリネームする

を、それぞれ実装するしか無いかな。。

シンボルのリネーム

Ribbitにおけるシンボルオブジェクトは:

#(データ 名前) 

のように、シンボルをevalした際に得られるデータと名前を格納したオブジェクトとして表現される。Schemeの標準の範囲内では、シンボルは常に intern されるので、 string->symbol したシンボルと quote したシンボルは eq? 的な意味で一致しなければならない。

(eq? (string->symbol "test") 'test) ;; => 真

これを実現するために、Ribbonではstring hashtableをVMに持たせて "名前" => シンボル が1対1で対応するようにしている。 このハッシュテーブルこそが名前空間と考えられる

okuokuokuoku

rename hookの導入

オンラインコンパイル用のhookをとりあえず入れた。

https://github.com/okuoku/yuniribbit-proto/commit/e462dbe8ca9ac59e82fa1e90f9778b0f7447646e

このhookでは、グローバル名前空間のシンボルをライブラリ名前空間のシンボルに交換することになる。今は単にno-opだけど。

EDIT: 場所間違えてた

https://github.com/okuoku/yuniribbit-proto/commit/3ebd3f493e3d5c0d131c7c71e6c080b8fe25068a

okuokuokuoku

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と参照しているグローバル変数を列挙することができる。

okuokuokuoku

ribcode

とりあえず全ての命令をパースしてribを含まない形にエンコード/それをデコードしてみて機能が完全であることを確認した。デコーダはSchemeとCの両方で実装していて、Scheme側をCにほぼコピペできる形で書いている:

https://github.com/okuoku/yuniribbit-proto/blob/b94f58d4a750b36bd9c277cc223d25bf668c8486/ribbon/util/ribcode.sls#L188-L207

https://github.com/okuoku/ribbon/blob/1c1aa4cdce7a034edf2681e8ddb8d3bd4ccfcdb6/c-proto/c-proto.c#L2290-L2315

これで長さ == 3のvectorをribと見做すヒューリスティックが不要になったので、vectorのテストが通るようになった。(以前のコードだと、長さ3のvectorをリテラルとして書くとribに化けてしまっていた)

https://github.com/okuoku/ribbon/commit/8a053b0c0f6785618c7b485cd6f2c72229c69b63

okuokuokuoku

とりあえずスクリプトについて実装

https://github.com/okuoku/yuniribbit-proto/commit/3d72183924124e965f0c7116108013f1133d8e24

まぁ事前コンパイルぶんはbootstrapには要らないから無しで良いか。。

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

https://github.com/okuoku/ribbon/commit/b580277176ca8f5137e1d1eed3c71d9391fbcf35