連鎖性言語を作る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
}

トランスレータを考える

今回の経験をベースに考えてみると、意外にトランスレータは厄介なことがわかる。ようするに、コードは展開できるがデータをどうするかという話だ。オレ言語のコードをそのまま吐き出してパースしてASTを作って展開したコードに渡せばいい、といえば確かにそうなのだが、せっかくトランスレータ作るのになんでパースしてんの、って話でもあるし、もうちょっといい感じにするにはどうしたらいいのかな。
ということでしばし考えつつこれは今後のネタに。