RubyVM::AST を用いた Ruby 製 Ruby インタプリタを作っている話

Ruby では v2.6 から RubyVM::AST というのが入ることになりまして、Ruby ランタイム上から AST を取得できるようになります。この話は以前に書いたので省略しますが、ASTがとれるということはASTを解釈して実行することもできるわけです。やるしかないですね。

というわけでやったのがこれです。まだ全然途中ですが。

github.com

デモ

if 式を解釈できるようす

# spec/snippets/if.rb
a = true
if a
  puts "truthy"
else
  puts "falsy"
end

b = false
if b
  puts "truthy"
else
  puts "falsy"
end
$ bin/stray spec/snippets/if.rb
truthy
falsy

メソッドを定義したりできてるようす

# spec/snippets/method.rb
def foo
  puts :foo
end

foo

def bar(arg)
  puts arg
end

bar :bar

def blk
  yield 1
end

blk do |arg|
  puts arg
end
$ bin/stray spec/snippets/method.rb
foo
bar
1

進捗

プロジェクトとしての進捗はこんな感じです。今回は紹介程度に留めて、実装時に留意したことなどは別エントリに書きます。インタプリタの実装をするのは初めてだったのでいろんな壁にぶつかりながらやってます。

できること

  • Integer, String, Symbol, Array あたりのリテラル
    • Float とかそういうのはちょっと面倒なんでやりません
  • ローカル変数代入
  • メソッド定義
    • オプショナル引数、キーワード引数はまだ
  • メソッド呼び出し
  • yield
  • if 式
  • ぼっち演算子

できないこと

  • クラス定義
    • サボってます
    • rb_cClass とかの定義を見てそれに合わせないとダメなのはわかってるんだけどオレオレ実装してしまったので修正が面倒……。
    • 当然継承とか include とか extend とか using ( refinements )とか
  • Kernel モジュール、Objectクラスに定義されてるだいたいのもの
    • 実装するのがめんどくさくて puts くらいしかやってない
  • case 式
    • === をちゃんとするのが単純にめんどくさそうでやってない
  • lambda とか

ちょっと外れた話

cruby のソースを読んでたらフォーマットが他と違うところを発見したのでPRしました。

Use nd_X shorthand for annotation by hkdnet · Pull Request #1901 · ruby/ruby · GitHub

また、実装していると NODE_DEFN から nd_mid を取れないことに気づきました。 NODE_DEFNdef foo; end のようなメソッド定義を示すノード種別です。nd_mid はメソッド名に対応するシンボルです。先程の例だと :foo という情報です。これがないとなんのメソッドを定義したのかわかりません。 twitter で y.kaneko さん( @spikeolaf )に質問したら快く修正してくれました。

新しい機能を実際に使ってみた結果をフィードバックをする人はたぶん少ないので意味がありそうですね。