#-- # Binding-of-Caller, Copyright (c) 2003 Florian Gross #++ #begin require 'nano/continuation/self/create' #rescue LoadError # class Continuation; end # :nodoc: # for RDoc # def Continuation.create(*args, &block) # :nodoc: # cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?} # result ||= args # return *[cc, *result] # end #end class Binding; end # for RDoc # This method returns the binding of the method that called your # method. It will raise an Exception when you're not inside a method. # # It's used like this: # def inc_counter(amount = 1) # Binding.of_caller do |binding| # # Create a lambda that will increase the variable 'counter' # # in the caller of this method when called. # inc = eval("lambda { |arg| counter += arg }", binding) # # We can refer to amount from inside this block safely. # inc.call(amount) # end # # No other statements can go here. Put them inside the block. # end # counter = 0 # 2.times { inc_counter } # counter # => 2 # # Binding.of_caller must be the last statement in the method. # This means that you will have to put everything you want to # do after the call to Binding.of_caller into the block of it. # This should be no problem however, because Ruby has closures. # If you don't do this an Exception will be raised. Because of # the way that Binding.of_caller is implemented it has to be # done this way. # # ++Binding-of-Caller, Copyright (c) 2003 Florian Gross++ # def Binding.of_caller(&block) old_critical = Thread.critical Thread.critical = true count = 0 cc, result, error, extra_data = Continuation.create(nil, nil) error.call if error tracer = lambda do |*args| type, context, extra_data = args[0], args[4], args if type == "return" count += 1 # First this method and then calling one will return -- # the trace event of the second event gets the context # of the method which called the method that called this # method. if count == 2 # It would be nice if we could restore the trace_func # that was set before we swapped in our own one, but # this is impossible without overloading set_trace_func # in current Ruby. set_trace_func(nil) cc.call(eval("binding", context), nil, extra_data) end elsif type == "line" then nil elsif type == "c-return" and extra_data[3] == :set_trace_func then nil else set_trace_func(nil) error_msg = "Binding.of_caller used in non-method context or " + "trailing statements of method using it aren't in the block." cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil) end end unless result set_trace_func(tracer) return nil else Thread.critical = old_critical case block.arity when 1 then yield(result) else yield(result, extra_data) end end end # _____ _ # |_ _|__ ___| |_ # | |/ _ \/ __| __| # | | __/\__ \ |_ # |_|\___||___/\__| # =begin test require 'test/unit' class TC_Binding < Test::Unit::TestCase def inc_counter(amount = 1) Binding.of_caller do |binding| inc = eval("lambda { |arg| counter += arg }", binding) inc.call(amount) end end def test_of_caller counter = 0 2.times { inc_counter } assert_equal( 2, counter ) end end =end