On Wed, 27 Sep 2006 17:25:12 +0200, Ken Bloom <kbloom / gmail.com> wrote:

> Is there a way to make your code pass this test (even though it's not
> strictly turning the parse tree into an s-expression anymore?)
>     def test_varaible
> 	var=:field
> 	assert_equal [:count, :field], sxp{ count(var) }
>      end

Yes that is possible by using eval and the block as binding. I implemented  
it for lvars and dvars, but it would be possible to do the same for  
instance variables, class variables, ...

I also implemented :not nodes as you suggested in the other mail.

Below is the diff and also the complete solution again.

Dominik


--- sexp_1.rb   2006-09-27 19:30:05.000000000 +0200
+++ sexp.rb     2006-09-27 19:52:02.000000000 +0200
@@ -2,6 +2,10 @@
  require "rubynode"

  class Node2Sexp
+  def initialize(binding)
+    @binding = binding
+  end
+
    # (transformed) nodes are arrays, that look like:
    # [:type, attribute hash or array of nodes]
    def to_sexp(node)
@@ -52,12 +56,33 @@
    def nil_to_sexp(hash) nil end
    def false_to_sexp(hash) false end
    def true_to_sexp(hash) true end
+
+  # :lvar nodes: local variables
+  # var => [:lvar, {:cnt=>3, :vid=>:var}] # cnt is the index in the lvar  
table
+  def lvar_to_sexp(hash)
+    eval(hash[:vid].to_s, @binding)
+  end
+  # :dvar nodes: block local variables
+  # var => [:dvar, {:vid=>:var}]
+  alias dvar_to_sexp lvar_to_sexp
+
+  # :not nodes: boolean negation
+  # not :field => [:not, {:body=>[:lit, {:lit=>:field}]}]
+  # !:field => [:not, {:body=>[:lit, {:lit=>:field}]}]
+  def not_to_sexp(hash)
+    body = to_sexp(hash[:body])
+    if Array === body && body[0] == :== && body.size == 3
+      [:"!=", body[1], body[2]]
+    else
+      [:not, body]
+    end
+  end
  end

  def sxp(&block)
    body = block.body_node
    return nil unless body
-  Node2Sexp.new.to_sexp(body.transform)
+  Node2Sexp.new(block).to_sexp(body.transform)
  end

  if $0 == __FILE__ then
@@ -113,6 +138,20 @@
        assert_equal [:==, :field1, :field2], sxp{:field1 == :field2 }
      end

+    def test_sxp_variables
+      lvar = :field # local variable
+      assert_equal [:count, :field], sxp{ count(lvar) }
+      proc {
+        dvar = :field2 # dynavar (block local variable)
+        assert_equal [:==, :field, :field2], sxp{ lvar == dvar }
+      }.call
+    end
+
+    def test_sxp_not
+      assert_equal [:not, :field], sxp{ not :field }
+      assert_equal [:"!=", :a, :b], sxp{ :a != :b }
+    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}

==================================

require "rubynode"

class Node2Sexp
   def initialize(binding)
     @binding = binding
   end

   # (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

   # :lvar nodes: local variables
   # var => [:lvar, {:cnt=>3, :vid=>:var}] # cnt is the index in the lvar  
table
   def lvar_to_sexp(hash)
     eval(hash[:vid].to_s, @binding)
   end
   # :dvar nodes: block local variables
   # var => [:dvar, {:vid=>:var}]
   alias dvar_to_sexp lvar_to_sexp

   # :not nodes: boolean negation
   # not :field => [:not, {:body=>[:lit, {:lit=>:field}]}]
   # !:field => [:not, {:body=>[:lit, {:lit=>:field}]}]
   def not_to_sexp(hash)
     body = to_sexp(hash[:body])
     if Array === body && body[0] == :== && body.size == 3
       [:"!=", body[1], body[2]]
     else
       [:not, body]
     end
   end
end

def sxp(&block)
   body = block.body_node
   return nil unless body
   Node2Sexp.new(block).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_variables
       lvar = :field # local variable
       assert_equal [:count, :field], sxp{ count(lvar) }
       proc {
         dvar = :field2 # dynavar (block local variable)
         assert_equal [:==, :field, :field2], sxp{ lvar == dvar }
       }.call
     end

     def test_sxp_not
       assert_equal [:not, :field], sxp{ not :field }
       assert_equal [:"!=", :a, :b], sxp{ :a != :b }
     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