syntax-rulesが動いた日
オレ処理系の[Nendo]についての開発メモ。
chibi-scheme 0.3のer-macro-transformerとsyntax-rulesをそのままNendoに移植を試みていたのだが、ついに成功した。
make-syntactic-closureの実装方法は別段難しくなかった。 トップレベルにbindされた変数なら、その識別子をシンボルで返し、そうでなければ(gensym)したシンボルを返せばいいだけだった。 Rubyで書いた関数なので、これだけでは意味がわからんだろうけど、雰囲気だけでも伝わるかと思うので、コードを貼っておく。
def _make_MIMARKsyntactic_MIMARKclosure( mac_env, use_env, identifier )
if _pair_QUMARK( identifier )
raise RuntimeError, "Error: make-syntactic-closure requires symbol only..."
else
if mac_env.to_arr.include?( identifier )
identifier
else
sym = toRubySymbol( identifier ) + _gensym( ).to_s
sym.intern
end
end
end
苦労した主な敗因は、chibi-schemeのsyntax-rulesの定義を自分がよく理解しないままポーティングしようとしていたことだ。 こんな複雑な手続きが、一発で動くほど甘い世界ではない。 結局、デバッグコード挿入しつつ少しづつ原因を追いこんでいった。
Nendoにポーティングする時に考慮する必要があったのは以下の点。
* … というシンボルをあらかじめグローバルに定義しておく必要があった
syntax-rulesを評価する直前に、次のような定義を置いた。
(define ... '...)
* NendoのScheme非互換仕様に対しての手当が必要だった
マクロ展開時 v.1 や v.2 のようなローカル変数を抑制する必要があった。 Nendoには instance.method の様な表記をRubyのメソッド呼び出しとして特別扱いしている。 (これは、Schemeとは非互換な部分である)
Nendoでは syntax-rulesで展開したコード中の v.1 などの形式が vというインスタンスの1というメソッド呼び出しになってしまう。 まあ、数値リテラルのメソッドなんてものは無いので、v.1のような表記は特別に:”v.1”というシンボルとして扱えばいいのだけど、そういう例外も気持ち悪いので特例は入れないつもり。 結局 v__1 や v__2 のような変数名を生成するように変更した。
(rename (string->symbol (string-append "v." (number->string count)))))
↓
(rename (string->symbol (string-append "v__" (number->string count)))))
* chibi-scheme 0.3のany関数にバグがあった。
chibi-scheme 0.3のsyntax-rulesの定義中で何回か any が使用されているが、実はchibi-schemeのanyにはバグがある。(これは、chibi-schemeのMLでバグ報告しないといけない) これを見つけるのに苦労したのだよ。 次のような場面で使われている。
(define-syntax syntax-rules
(er-macro-transformer
.
.
(cond
((any (lambda (v) (compare t (car v))) vars)
=> (lambda (cell)
(if (<= (cdr cell) dim)
t
(error "too few ...'s"))))
(else
(list _rename (list _quote t)))))
.
.
anyで見つかったcellを使って、(cdr cell)とかしちゃっているが、これは間違い。 本来のanyは条件にマッチした値を返すわけではなく、リスト中に条件を満たすものが1件でもあれば #t を返す。 要はchibi-schemeのanyはfindの動作をしちゃっているようだ。
証拠を示そう。 SRFI 1: List Libraryのanyの定義 Note the difference between find and any – find returns the element that satisfied the predicate; any returns the true value that the predicate produced.
(any integer? '(a 3 b 2.7)) => #t
(any integer? '(a 3.1 b 2.7)) => #f
とうわけで、このように修正した。 誤り:
((any (lambda (v) (compare t (car v))) vars)
正解:
((find (lambda (v) (compare t (car v))) vars)
参考:
$ chibi-scheme
> (any even? '(1 2 3 4))
2
$ gosh
gosh> (any even? '(1 2 3 4))
#t
$ nendo
nendo> (any even? '(1 2 3 4))
#t
nendo> (use srfi-1)
20000
nendo> (any even? '(1 2 3 4))
#t
さて、これでsrfi-2とかGaucheのutil.listがポーティングできるぞー。 Nendoでできる世界がさらに広がること間違いナシ。 高い壁をひとつ越えた気がする。すごい充実感。