From: Dominik Bathon Date: 2006-09-25T23:15:47+09:00 Subject: [SOLUTION] Code to S-Exp (#95) 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