RubyNode

RubyNode is a library that allows read only access to Ruby’s internal NODE structure. It can retrieve the node trees of methods and procs and it can use Ruby’s parser to parse Ruby source code strings to node trees.

It provides the class RubyNode, which wraps an internal NODE. Trees of RubyNode instances can also be transformed into trees of arrays and hashes (similar to s-expressions), which are easy to manipulate and work with.

Sections: Requirements, Download, Installation, Usage, Feedback, Thanks, License.

Requirements

RubyNode is tested with Ruby 1.8.4, 1.8.5 and 1.8.6, but it should also work with other 1.8 versions. RubyNode worked (and still does work) with older Ruby 1.9 snapshots (possibly some node types are not fully supported), but it currently does not work with the latest 1.9 snapshots and releases.

Download

Installation

RubyNode generates some of its C source code from Ruby’s source code, because the node types and other details differ between Ruby versions.

For the official releases of Ruby 1.8.4, 1.8.5 and 1.8.6 the needed source files are included in the package. To compile RubyNode for any other Ruby version, you will need that version’s source tar ball extracted somewhere.

So, for Ruby 1.8.4, 1.8.5 and 1.8.6 just run (as root):

gem install rubynode

Or if you do not use the gem:

ruby setup.rb

That command will compile the C extension and install all files to their default location (to customize the non-gem installation, please see "ruby setup.rb --help")

For other Ruby versions you need to provide the (absolute) path to the source directory for that Ruby version in the enviroment variable RUBY_SOURCE_DIR. Example (as root):

RUBY_SOURCE_DIR="/path/to/ruby_source" gem install rubynode

Or if you do not use the gem:

RUBY_SOURCE_DIR="/path/to/ruby_source" ruby setup.rb

Usage

Just require "rubynode" and use it. Here is a short irb sessions that shows some of the things you can do with RubyNode:

>> require "rubynode" 
=> true

Body node of a method:

>> def plus_1(x)
>>   x + 1
>> end
=> nil
>> pp method(:plus_1).body_node.transform
[:scope,
 {:next=>
   [:block,
    [[:args, {:rest=>-1, :cnt=>1, :opt=>false}],
     [:call,
      {:args=>[:array, [[:lit, {:lit=>1}]]],
       :mid=>:+,
       :recv=>[:lvar, {:vid=>:x, :cnt=>2}]}]]],
  :rval=>[:cref, {:next=>false, :clss=>Object}],
  :tbl=>[:x]}]
=> nil

Body node and var node of a proc:

>> add_2 = proc { |x| x + 2 }
=> #<Proc:0xb7f24c00@(irb):6>
>> pp add_2.body_node.transform
[:call,
 {:args=>[:array, [[:lit, {:lit=>2}]]], :mid=>:+, :recv=>[:dvar, {:vid=>:x}]}]
=> nil
>> pp add_2.var_node.transform
[:dasgn_curr, {:vid=>:x, :value=>false}]
=> nil

Parse a string to nodes:

>> pp "3.times { puts 'Ruby' }".parse_to_nodes.transform
[:iter,
 {:var=>false,
  :iter=>[:call, {:args=>false, :mid=>:times, :recv=>[:lit, {:lit=>3}]}],
  :body=>[:fcall, {:args=>[:array, [[:str, {:lit=>"Ruby"}]]], :mid=>:puts}]}]
=> nil

For more details see API.

Have fun!

Feedback

If you find a bug, think that something doesn’t work as it should or have other suggestions, then please don’t hesitate to contact me and tell me about it.

Thanks

I would like to thank Paul Brannan for writing Nodewrap, which inspired me to write RubyNode and also gave me some ideas and code.

License

Copyright 2006-2007 Dominik Bathon.

RubyNode is licensed under the same terms as Ruby.