連鎖性言語を作る10
オレ言語のコードをRubyコードに変換してevalすれば、VMで実行するのと比べてディスパッチが無くなって速くなる。毎回evalするとRubyのコンパイルが発生してオーバーヘッドがあるので、evalでProcを生成して、実行時はそれを呼ぶだけ、という形にしておくのが望ましい。これはテキストを編集してうんぬん、というだけなので簡単であり省略。
新たなチャレンジとして、オレ言語→Rubyトランスレータなどがよいかもしれない。変換したコードをrbファイルで出力しておいて、それをrequireして呼び出すことで実行できる、みたいな。Rubyを呼び出すことはできていたが、Rubyから呼ぶインターフェイスは今まで無かったのでそのへんを構築するという課題も解消する。んじゃこれいってみよう。
というところまで考えて、ふと思った。
Rubyコードに変換してevalするだけってのは、本当に簡単なのか?印象としては簡単っぽいけども、やったことがあればまだしも、やったことの無いことに対して簡単なはずだから省略ってのはちょっとアレなのではないか。簡単ならすぐできるわけだし、とりあえずやっておけばいいんじゃないか。と。
やってみよう。
チャンレジ
結論から言うと少し悩んだ(だめじゃん)。
基本的にはCodeGeneratorでテキストを編集して、VMのインスタンスでinstance_evalしてProcを生成、AST配列のインスタンス変数に保持する。のだが、従来のコードではAST配列に入ったオブジェクトをスタックにpushしたりしていたわけで、これをテキストにどう展開すればいいのか。シンボルや文字列や数字なら簡単だが、Quotationだったりすると真面目に展開処理を書きたくない。っていうかAST配列の中にあるのにわざわざ展開するか?みたいな。しかしAST配列はVMインスタンスに持ってるわけではないし、実行スコープはVMインスタンスなのでアクセスできない。困った。
という感じで悩んだ結果、生成するProcの引数としてASTを渡すことにした。CodeGeneratorのコードはこんな感じ。
module CodeGenerator def self.generate(ast, vm) str = "->ast{\n" ast.each.with_index do |d, i| case d when :swap str += "@stack[-2], @stack[-1] = @stack[-1], @stack[-2]\n" when :drop str += "@stack.pop\n" when :dup str += "@stack << @stack[-1]\n" when Symbol str += "@words[:\"#{ast[i]}\"].call\n" when OreSymbol str += "@stack << ast[#{i}].to_sym\n" else str += "@stack << ast[#{i}]\n" end end str += "}" ast.instance_variable_set(:@bytecode, vm.instance_eval(str)) end end
んー、CodeGenerator.generateとProcの引数がどっちもastなせいで非常に混乱するコードになってしまった。これはダメな例だな。うん。つってもどっちも同じオブジェクトをアクセスするはずなので混乱する必要は無い。
んでVM側はこう。
def run(ast) unless ast.instance_variable_defined?(:@bytecode) CodeGenerator.generate(ast, self) end ast.instance_variable_get(:@bytecode).call(ast) end
astを引数にProcを呼び出す。
例えばオレ言語で書いたこんなコードは、
[ ] :to_s 1 send
こんなRubyコードに変換される。
->ast{ @stack << ast[0] @stack << ast[1].to_sym @stack << ast[2] @words[:"send"].call }