#--
# Binding-of-Caller, Copyright (c) 2003 Florian Gross
#++

#begin
  require 'facets/core/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
