81 lines
2.7 KiB
Ruby
81 lines
2.7 KiB
Ruby
|
class Continuation # :nodoc:
|
||
|
def self.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.
|
||
|
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
|