Saturday, July 30, 2011

Caller binding

One of most useful feature not present in Ruby is to get the binding of the caller of current method, to do something with its local variables.

There is an implementation in the Extensions gem, but it must be the last method call in the method, and we must use the binding within a block.

There is another implementation here, but it depends on tracing along all the code execution, compromising the performance.

However, in this answer in StackOverflow, Taisuke Yamada implemented an version of ppp.rb (what is it?) which inspired me to implement my own version of a caller_binding method. Enjoy!

#!/usr/bin/ruby
#
# (c) Sony Fermino dos Santos, 2011
# License: Public domain
# Implementation: 2011-07-30
#
# Published at:
# http://rubychallenger.blogspot.com/2011/07/caller-binding.html
#
# Inspired on:
# http://stackoverflow.com/questions/1356749/can-you-eval-code-in-the-context-of-a-caller-in-ruby/6109886#6109886
#
# How to use:
# return unless bnd = caller_binding
# After that line, bnd will contain the binding of caller

require 'continuation' if RUBY_VERSION >= '1.9.0'

def caller_binding
  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 == 2
      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.
  return callcc { |cont| cc = cont }
end

# Example of use:

def var_dump *vars
  return unless bnd = caller_binding
  vars.each do |s|
    value = eval s.to_s, bnd
    puts "#{s} = #{value.inspect}"
  end
end

def test
  a = 1
  s = "hello"
  var_dump :s, :a
end

test

Friday, July 29, 2011

How to return from inside eval

I have a code which I need to use within eval. Sometimes I need to get out from the middle of eval code, but how?

In PHP I can use the return keyword, but it doesn't work on Ruby:

# expected to see 1, 2 and 5; not 3 nor 4; and no errors
eval "puts 1; puts 2; return; puts 3; puts 4"
  # => Error: unexpected return
puts 5

I tried with return, end, exit, break, and I couldn't get success. exit doesn't raise errors, but then I don't get the 5.

After many tries and a question in StackOverflow, I found a solution which fits best into my problem:

lambda do
  eval "puts 1; puts 2; return; puts 3; puts 4"
end.call
puts 5

This way the intuitive return keyword can be used inside eval to get out from it successfully.

You can find alternative ways at StackOverflow.