#!/usr/local/bin/ruby -w # # == extensions/binding.rb # # Adds methods to the builtin Binding class. # require "extensions/_base" require "extensions/continuation" # # Ruby's built-in Binding class doesn't contain any methods. It is merely a "context" object # that can be used in calls to Kernel.eval, like this: # # def example(_binding) # return eval("x", _binding) # end # # x = 55 # current_binding = Kernel.binding # example(current_binding) # -> 55 # # The most useful method introduced to Binding by the _extensions_ package is # Binding.of_caller. It allows you to access the binding of the calling method, thus # enabling you to access local variables in that scope. The other methods are a convenient # object-oriented facade for operations that you can already do with #eval as demonstrated # above. Here is an example that showcases all of the Binding methods included in # _extensions_. # # def example # Binding.of_caller do |b| # puts "x + y = #{b.eval('x + y')}" # puts "x = #{b[:x]}" # puts "Local variables: " + b.local_variables.join(', ') # b[:y] += 1 # puts "Changed value of y in calling context to #{b[:y]}" # puts "Is 'z' defined in calling context? " + (b.defined?(:z) ? 'Yes' : 'No') # end # end # # x = 5 # y = 17 # example # y # -> 18 # # Binding.of_caller was written by Florian Gross. The other methods were written by Tom # Sawyer. # class Binding end # # * Binding.of_caller # ExtensionsProject.implement(Binding, :of_caller, :class) do class Binding # # This method returns the binding of the method that called your method, enabling you to # access its local variables. If you call it without being in a method, it will raise an # Exception. # # === Example # # def inc_counter # Binding.of_caller do |b| # eval("counter += 1", b) # end # # <--- line (A) # end # counter = 0 # inc_counter # inc_counter # counter # -> 2 # # === Warning # # Binding.of_caller must be the _last_ method call in the method. For example, # if you insert some code at line *A* in the example above, an Exception will be raised. # You'll get away with a simple assignment, but anything involving a method call is # trouble. # # === Explanation # # It works by installing a temporary trace_func (see Kernel.set_trace_func). This makes # available -- to the trace function -- the binding of a method after it has returned. # Using a continuation, Binding.of_caller will let _your_ method return, # retrieve the binding, and return to the of_caller call with that binding in # hand. This time it executes the block. # # Because it is actually running Binding.of_caller twice, and returning from # your method twice, any code between the of_caller call and the end of your # method will be run twice. This is obviously not desirable, so an Exception is raised # if any code is found. # # See the thread around ruby-talk:109607 for more discussion. # # === Extra Warning # # If you have a trace function in place, Binding.of_caller will destroy that. # Ruby does not allow you to access the current trace function, so it can't be restored # afterwards. XXX: will this clash with the profiler and/or debugger? # # === Credits # # Binding.of_caller was written by Florian Frank. # def Binding.of_caller(&block) old_critical = Thread.critical Thread.critical = true count = 0 cc, result, error = Continuation.create(nil, nil) error.call if error tracer = lambda do |*args| type, context = args[0], args[4] 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) end elsif type != "line" 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(Exception, error_msg ) }) end end unless result set_trace_func(tracer) return nil else Thread.critical = old_critical yield result end end end # class Binding end # # * Binding#eval # ExtensionsProject.implement(Binding, :eval, :instance) do class Binding # # Evaluates the given string in the context of this binding. # def eval(str) Kernel.eval(str, self) end end end # # * Binding#local_variables # ExtensionsProject.implement(Binding, :local_variables, :instance) do class Binding # # Returns the variables that are local to this binding. # def local_variables self.eval('local_variables') end end end # # * Binding#[] # ExtensionsProject.implement(Binding, :[], :instance) do class Binding # # Returns the value of the given variable in this binding. # def [](variable) self.eval(variable.to_s) end end end # # * Binding#[]= # ExtensionsProject.implement(Binding, :[]=, :instance) do class Binding # # Sets the given variable (in this binding) to the given value. # def []=(variable, value) self.eval("lambda { |v| #{variable} = v }").call(value) end end end # # * Binding#defined? # ExtensionsProject.implement(Binding, :defined?, :instance) do class Binding # # Evaluates defined? in this binding. # def defined?(variable) self.eval("defined?(#{variable})") end end end