Ruby

RubyVM::ASTさわってみた

RubyVM::AST [Experimental]

Ruby 2.6では RubyVM::AST モジュールが導入されました。

このモジュールには、文字列をパースしてAST(抽象構文木)のNodeを返すparseメソッド、ファイルをパースするparse_fileメソッドが実装されています。 RubyVM::AST::Node も導入されました。このクラスのインスタンスから位置情報や子ノードを取得することができます。この機能はexperimentalであり、互換性は保証されていません。
https://www.ruby-lang.org/ja/news/2018/05/31/ruby-2-6-0-preview2-released/

API

RubyVM::AST

= RubyVM::AST

(from ruby core)
------------------------------------------------------------------------
= Class methods:

  parse, parse_file

RubyVM::AST::Node

= RubyVM::AST::Node < Object

(from ruby core)
------------------------------------------------------------------------
= Instance methods:

  children, first_column, first_lineno, inspect, last_column,
  last_lineno, type

RubyVM::AST.parse

Rubyのコードの文字列を渡すと、文字列をパースしてASTを返す。とりあえず無をparseしてみる。

root = RubyVM::AST.parse('')
# => #<RubyVM::AST::Node(NODE_SCOPE(0) 1:0, 2:0): >

root.type
# => "NODE_SCOPE"

[root.first_lineno, root.first_column, root.last_lineno, root.last_column]
# => [1, 0, 2, 0]

root.children
# => [nil, #<RubyVM::AST::Node(NODE_BEGIN(16) 2:0, 2:0): >]

root.children.class
# => Array

root.children.last.type
# => "NODE_BEGIN

root.children.last.children
# => [nil]

パースできない文字列を渡すとどうなるか

エラーが出て実行がとまる

RubyVM::AST.parse('^^')
# no file name:1: syntax error, unexpected '^'

rescueできない

begin
  RubyVM::AST.parse('^^')
rescue Exception
  puts "捕まえた〜!"
  p $!
end
# no file name:1: syntax error, unexpected '^'

RubyVM::AST::Node

#type

この辺で定義されてるenum node_typeを文字列で返す。
https://github.com/ruby/ruby/blob/v2_6_0_preview2/node.h#L22-L225

#children

抽象構文木の葉を返す。末端までたどって表示させてみると節点に演算子(NODE_OPCALL)が来ているのがわかる。

def walk(node, level = 0)
  print " " * (level * 2)
  p node
  return unless node&.children&.empty?&.!
  walk(node.children.first, level + 1)
  walk(node.children.last, level + 1)
end

walk(RubyVM::AST.parse('1 + 1'))
# #<RubyVM::AST::Node(NODE_SCOPE(0) 1:0, 1:5): >
#   nil
#   #<RubyVM::AST::Node(NODE_OPCALL(36) 1:0, 1:5): >
#     #<RubyVM::AST::Node(NODE_LIT(59) 1:0, 1:1): >
#     #<RubyVM::AST::Node(NODE_ARRAY(42) 1:4, 1:5): >
#       #<RubyVM::AST::Node(NODE_LIT(59) 1:4, 1:5): >
#       nil

RubyVM::AST::Nodeをnewできるか

newできない

RubyVM::AST::Node.new
Traceback (most recent call last):
        2: from /home/sei/local/bin/irb:11:in `<main>'
        1: from (irb):1
NoMethodError (undefined method `new' for RubyVM::AST::Node:Class)

自分でASTを組み立ててYARVにコンパイルして実行できるか

YARVのInstructionSequenceに変換するAPIは公開されていないので出来なさそう。