From: Ryan Davis Date: 2006-09-25T07:23:40+09:00 Subject: Re: [QUIZ][SOLUTION] Code to S-Exp (#95) I only spent about 30 minutes on this and it does a minimal job of passing the current tests (plus the ones added by Sander). It'll break when other ruby expression types (if/case/blocks) are thrown at it, but those are really easy to cover if need be. That said, I thought this was a good example of the power of ParseTree. This requires the latest release of ParseTree. The rest came from ZenHacks/ruby2ruby but I extracted the crucial bits and put them in here directly. #!/usr/local/bin/ruby -w # Your mission: Create a function named sxp() that can take a block # (not a string), and create an s-expression representing the code in # the block. require 'rubygems' require 'parse_tree' require 'sexp_processor' ############################################################ # From unreleased ruby2ruby: class ProcStoreTmp @@n = 0 def self.name @@n += 1 return :"myproc#{@@n}" end end class Method def with_class_and_method_name if self.inspect =~ // then klass = eval $1 # cheap method = $2.intern raise "Couldn't determine class from #{self.inspect}" if klass.nil? return yield(klass, method) else raise "Can't parse signature: #{self.inspect}" end end def to_sexp with_class_and_method_name do |klass, method| ParseTree.new(false).parse_tree_for_method(klass, method) end end end class Proc def to_method name = ProcStoreTmp.name ProcStoreTmp.send(:define_method, name, self) ProcStoreTmp.new.method(name) end def to_sexp body = self.to_method.to_sexp[2][1..-1] [:proc, *body] end end # END unreleased ruby2ruby: ############################################################ class Quiz < SexpProcessor def initialize super self.auto_shift_type = true self.strict = false self.expected = Object end def process_proc(exp) return * _list(exp) end def process_fcall(exp) [exp.shift, process(exp.shift)] end def process_call(exp) lhs = process(exp.shift) name = exp.shift rhs = process(exp.shift) [name, lhs, rhs].compact end def process_array(exp) return * _list(exp) end def process_lit(exp) exp.shift end def process_str(exp) exp.shift end def _list(exp) result = [] until exp.empty? do result << process(exp.shift) end result end end def sxp(&block) Quiz.new.process(block.to_sexp) 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_call_plus_eval 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_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