Last active
December 24, 2015 21:29
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if defined?(Rubinius) then | |
require_relative 'rbx_caller_binding' | |
else | |
require_relative 'mri_caller_binding' | |
end | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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