set!の実装につまづく(3) =>解決
私がRubyで書いているLisp方言、 [Nendo]について。
先日の記事 ([kiyoka.2010_02_22]『set!の実装につまづく(2)』)の続き。
問題のバグを修正し、各々のset!に対応するRubyコードを正常に出力できるようになった。 shiroさんのコメントにあったように、LispのコードをRubyに変換する処理において、サブフォームに再起して行く際にローカル変数のリストを渡していく方法で簡単に実装できた。 set!の第一引数の変数名がローカル変数リストにヒットすればRubyのローカル変数を使用し、ヒットしなければRubyのインスタンス変数(Lispのグローバル変数の代替品)を使う実装にした。 このバグ修正をする前作業として、インデント付きのRubyコード生成を生成するようにしたので見やすくなった。 そのままコードを貼りつけて解説する。
– グローバル変数の更新 Nendo:
(define a-global-var 1)
(set! a-global-var 10)
生成コード(Ruby):
@_a_global_var =
1
@_a_global_var =
10
– ローカル変数の更新 Nendo:
(let ((a-local-var 2))
a-local-var)
生成コード(Ruby):
begin
___lambda = lambda { |_a_local_var|
begin
_a_local_var
rescue => __e ; __e.set_backtrace( *"(stdin):126"* + __e.backtrace ) ; raise __e
end
} ; ___lambda.call(
2
)
end
– グローバル変数とローカル変数の混在 Nendo:
(define a-global-var 1)
(let ((a-local-var 3))
(set! a-global-var 20)
(set! a-local-var 4))
生成コード(Ruby):
@_a_global_var =
1
begin
___lambda = lambda { |_a_local_var|
@_a_global_var =
20
_a_local_var =
4
} ; ___lambda.call(
3
)
end
明日は、内部defineをletrecに変換するコンパイルフェーズを[Nendo]自身で書いてみよう。
ところで、実際にSchemeの仕様書を読みながら実装してみると、Schemeの仕様があまり何も規定していない実装自由度の高い仕様であることが分かる。 なので、どのような用途を狙うかによって、処理系ごとの規模の振れ幅は大きいだろうなと思う。 例えば、小さなフットプリントを狙うのか、処理系のソースコード規模最小限を狙うのか、高速な処理系を狙うのかなどで全く違った実装になる。 Schemeの処理系の数が余りにも多いのも頷ける。(tinyschemeとかchibischime、GaucheとかMoshとか色々ある)