Code as Objects

The Meta Object Runtime Execution in the name means a program can read its own code. Metorex returns a method's body, a block's statements, or a parsed string to the program as syntax tree nodes. Each node is a hash with a "type" key and the fields for that type.

A Method's Body#

get_source returns the method. body returns its statements as nodes.

class Invoice
  def total(price, quantity)
    price * quantity + 5
  end
end
The syntax tree of price * quantity + 5 BinaryOp+ BinaryOp* IntLiteral5 Identifierprice Identifierquantity
The tree Invoice#total's body returns. * sits below + because it binds tighter.

A program can walk those nodes like any other hashes:

method = Invoice.new.get_source(:total)
puts "#{method.name} takes #{method.arity} arguments"

def show(node, depth = 0)
  detail = case node["type"]
           when "BinaryOp" then node["op"]
           when "Identifier" then node["name"]
           when "IntLiteral" then node["value"]
           end
  puts "#{"  " * depth}#{node["type"]} #{detail}"
  show(node["left"], depth + 1) if node["left"]
  show(node["right"], depth + 1) if node["right"]
end

show(method.body.first)
total takes 2 arguments
BinaryOp +
  BinaryOp *
    Identifier price
    Identifier quantity
  IntLiteral 5

A Block's Statements#

A block captured with &block answers statements the same way, and it still runs:

def capture(&block) = block

rule = capture { |order| order * 2 }
p rule.arity
p rule.statements.first
p rule.call(21)
1
{"type" => "BinaryOp", "op" => "*", "left" => {"type" => "Identifier", "name" => "order"}, "right" => {"type" => "IntLiteral", "value" => 2}}
42

Parsing a String#

parse turns source text into the same nodes without running it:

tree = parse("1 + 2 * 3")
p tree.first["op"]
p tree.first["right"]
"+"
{"type" => "BinaryOp", "op" => "*", "left" => {"type" => "IntLiteral", "value" => 2}, "right" => {"type" => "IntLiteral", "value" => 3}}