Here is my solution.
It uses RubyNode (which is available as gem now, see
http://rubynode.rubyforge.org/) to access the block's body node and then
transforms that body node into the s-expression.
It is pretty similar to Ryan's ParseTree solution, but supports some
additional node types and has some more tests.
Dominik
require "rubynode"
class Node2Sexp
# (transformed) nodes are arrays, that look like:
# [:type, attribute hash or array of nodes]
def to_sexp(node)
node && send("#{node.first}_to_sexp", node.last)
end
# fixed argument lists are represented as :array nodes, e.g.
# [:array, [argnode1, argnode2, ...]]
def process_args(args_node)
return [] unless args_node
if args_node.first == :array
args_node.last.map { |node| to_sexp(node) }
else
raise "variable arguments not allowed"
end
end
# :call nodes: method call with explicit receiver:
# nil.foo => [:call, {:args=>false, :mid=>:foo, :recv=>[:nil, {}]}]
# nil == nil =>
# [:call, {:args=>[:array, [[:nil, {}]]], :mid=>:==, :recv=>[:nil, {}]}]
def call_to_sexp(hash)
[hash[:mid], to_sexp(hash[:recv]), *process_args(hash[:args])]
end
# :fcall nodes: function call (no explicit receiver):
# foo() => [:fcall, {:args=>false, :mid=>:foo}]
# foo(nil) => [:fcall, {:args=>[:array, [[:nil, {}]]], :mid=>:foo]
def fcall_to_sexp(hash)
[hash[:mid], *process_args(hash[:args])]
end
# :vcall nodes: function call that looks like variable
# foo => [:vcall, {:mid=>:foo}]
alias vcall_to_sexp fcall_to_sexp
# :lit nodes: literals
# 1 => [:lit, {:lit=>1}]
# :abc => [:lit, {:lit=>:abc}]
def lit_to_sexp(hash)
hash[:lit]
end
# :str nodes: strings without interpolation
# "abc" => [:str, {:lit=>"abc"}]
alias str_to_sexp lit_to_sexp
def nil_to_sexp(hash) nil end
def false_to_sexp(hash) false end
def true_to_sexp(hash) true end
end
def sxp(&block)
body = block.body_node
return nil unless body
Node2Sexp.new.to_sexp(body.transform)
end
if $0 == __FILE__ then
require 'test/unit'
class TestQuiz < Test::Unit::TestCase
def test_sxp_nested_calls
assert_equal [:max, [:count, :name]], sxp{max(count(:name))}
end
def test_sxp_vcall
assert_equal [:abc], sxp{abc}
end
def test_sxp_call_plus_eval
assert_equal [:count, [:+, 3, 7]], sxp{count(3+7)}
end
def test_sxp_call_with_multiple_args
assert_equal [:count, 3, 7], sxp{count(3,7)}
end
def test_sxp_binarymsg_mixed_1
assert_equal [:+, 3, :symbol], sxp{3+:symbol}
end
def test_sxp_binarymsg_mixed_call
assert_equal [:+, 3, [:count, :field]], sxp{3+count(:field)}
end
def test_sxp_binarymsg_mixed_2
assert_equal [:/, 7, :field], sxp{7/:field}
end
def test_sxp_binarymsg_mixed_3
assert_equal [:>, :field, 5], sxp{:field > 5}
end
def test_sxp_lits
assert_equal 8, sxp{8}
end
def test_sxp_true_false_nil
assert_equal [:+, true, false], sxp{true+false}
assert_equal nil, sxp{nil}
end
def test_sxp_empty
assert_equal nil, sxp{}
end
def test_sxp_binarymsg_syms
assert_equal [:==, :field1, :field2], sxp{:field1 == :field2 }
end
def test_sxp_from_sander_dot_land_at_gmail_com
assert_equal [:==,[:^, 2, 3], [:^, 1, 1]], sxp{ 2^3 == 1^1}
assert_equal [:==, [:+, 3.0, 0.1415], 3], sxp{3.0 + 0.1415 == 3}
assert_equal([:|,
[:==, [:+, :hello, :world], :helloworld],
[:==, [:+, [:+, "hello", " "], "world"], "hello
world"]] ,
sxp {
(:hello + :world == :helloworld) |
('hello' + ' ' + 'world' == 'hello world')
})
assert_equal [:==, [:+, [:abs, [:factorial, 3]], [:*, [:factorial,
4], 42]],
[:+, [:+, 4000000, [:**, 2, 32]], [:%, 2.7, 1.1]]],
sxp{ 3.factorial.abs + 4.factorial * 42 == 4_000_000 + 2**32 + 2.7
% 1.1 }
end
def test_ihavenocluewhy
assert_equal 11, 5 + 6
assert_raise(TypeError) { 7 / :field }
assert_raise(NoMethodError) { 7+count(:field) }
assert_raise(NoMethodError) { :field > 5 }
end
end
end