Skip to content

Instantly share code, notes, and snippets.

@acook
Last active December 24, 2015 21:29
Show Gist options
  • Save acook/6865766 to your computer and use it in GitHub Desktop.
Save acook/6865766 to your computer and use it in GitHub Desktop.
Allows you to swap the values of local variables with a method. Yep, all this for just that. Actually, you can drop all the caller_binding stuff and it'll work the same, you'll just have to pass in the binding explicitly then.
if defined?(Rubinius) then
require_relative 'rbx_caller_binding'
else
require_relative 'mri_caller_binding'
end
# Modified from http://rubychallenger.blogspot.com/2011/07/caller-binding.html
module CallerBinding
module ObjectExtensions
def caller_binding skip = 1
require 'continuation' if RUBY_VERSION >= '1.9.0' && !defined? Continuation
cc = nil # must be present to work within lambda
count = 0 # counter of returns
set_trace_func lambda { |event, file, lineno, id, binding, klass|
# First return gets to the caller of this method
# (which already know its own binding).
# Second return gets to the caller of the caller.
# That's we want!
if count == (skip + 1)
set_trace_func nil
# Will return the binding to the callcc below.
cc.call binding
elsif event == "return"
count += 1
end
}
# First time it'll set the cc and return nil to the caller.
# So it's important to the caller to return again
# if it gets nil, then we get the second return.
# Second time it'll return the binding.
result = callcc { |cont| cc = cont }
return result.is_a?(Binding) ? result : raise(ArgumentError, "Can't get binding of main object. Got #{result.class} instead of Binding.")
end
end
end
class ::Object
include CallerBinding::ObjectExtensions
extend CallerBinding::ObjectExtensions
end
# Extracted from https://github.com/banister/binding_of_caller/blob/master/lib/binding_of_caller/rubinius.rb
module CallerBinding
module BindingExtensions
# Retrieve the binding of the nth caller of the current frame.
# @return [Binding]
def caller_of(n)
location = Rubinius::VM.backtrace(1 + n, true).first
raise RuntimeError, "Invalid frame, gone beyond end of stack!" if location.nil?
setup_binding_from_location(location)
end
# The description of the frame.
# @return [String]
def frame_description
@frame_description
end
# Return bindings for all caller frames.
# @return [Array<Binding>]
def callers
Rubinius::VM.backtrace(1, true).map &(method(:setup_binding_from_location).
to_proc)
end
# Number of parent frames available at the point of call.
# @return [Fixnum]
def frame_count
Rubinius::VM.backtrace(1).count
end
# The type of the frame.
# @return [Symbol]
def frame_type
if compiled_code.for_module_body?
:class
elsif compiled_code.for_eval?
:eval
elsif compiled_code.is_block?
:block
else
:method
end
end
protected
def setup_binding_from_location(location)
binding = Binding.setup location.variables,
location.variables.method,
location.constant_scope,
location.variables.self,
location
binding.instance_variable_set :@frame_description,
location.describe.gsub("{ } in", "block in")
binding
end
end
module ObjectExtensions
def caller_binding skip = 1
binding.caller_of((skip + 1))
end
end
end
class ::Binding
include CallerBinding::BindingExtensions
extend CallerBinding::BindingExtensions
end
class ::Object
include CallerBinding::ObjectExtensions
extend CallerBinding::ObjectExtensions
end
# Original idea from http://onestepback.org/index.cgi/Tech/Ruby/RubyBindings.rdoc
# Additional refactoring by https://github.com/workmad3
require_relative 'caller_binding'
def swap var_a, var_b, binding_of_caller = nil
binding_of_caller ||= caller_binding
eval "#{var_a},#{var_b} = #{var_b},#{var_a}", binding_of_caller
end
require_relative 'swap'
def demonstrate!
a = Object.new
b = lambda{ {lol: 1} }
swap :a, :b
p a # => #<Proc:0x15c@/home/acook/projects/swap/usage.rb:5 (lambda)>
p a.class # => Proc
p a.call # => {:lol=>1}
p b # => #<Object:0x1c4>
p b.class # => Object
end
demonstrate!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment