Need help with with Binding.of_caller

Hi,

(hoping that Florian is reading)

I am just in the process of patching ri18n[1] interpolation functionality. Here is what I a trying to do:

ri18n offers a way to interpolate strings. This is for translation purposes. The current version of the interpolation method looks like this:

class String
   def interpolate(caller)
     caller.instance_eval('"' << self.gsub('"', '\"') << '"')
   end
...
end

original version is here:

http://svn.berlios.de/viewcvs/ri18n/trunk/lib/ri18n/standard_exts.rb?view=markup

This will correctly interpolate the following:

@color = 'red'
_i('#{@color} whine')

but not this:

color = 'red'
_i('#{color} whine')

So I thought I might try to use the Binding.of_caller technique. I came as far as this:

class String
   def interpolate(caller) # the param is not needed anymore
     Binding.of_caller do |binding|
       eval('"' << self.gsub('"', '\"') << '"', binding)
     end
   end
...
end

which will do exactly the opposite. Now I am able to eval local variables, but not the instance vars.

this doesn't work:

@color = 'red'
_i('#{@color} whine')

but this does:

color = 'red'
_i('#{color} whine')

What do I need to do to capture the whole environment of the originating _i() call. Is it at all possible?

Sascha Ebach

[1] http://ri18n.berlios.de/

Sascha Ebach wrote:

(hoping that Florian is reading)

If in doubt CC, but I try to keep up with ruby-talk. (Not true for the Rails list, though -- I tried creating a gmane RSS filter for my identity so I wouldn't miss anything, but so far it seems not to have worked.)

So I thought I might try to use the Binding.of_caller technique. I came as far as this:

class String
  def interpolate(caller) # the param is not needed anymore
    Binding.of_caller do |binding|
      eval('"' << self.gsub('"', '\"') << '"', binding)
    end
  end
...
end

which will do exactly the opposite. Now I am able to eval local variables, but not the instance vars.

Trust me: I'm as shocked as you are.

I think I actually stumbled upon this behavior before but quickly displaced the knowledge from my memory.

I think this is related to Ruby not setting the .self field of the BLOCK struct for trace_func bindings. From what I see binding() has some extra logic which might do exactly this.

Don't take my guess at this too literally -- the code for this is fairly complex and I will not claim to understand how all parts of it work together, but I think this might be a bug or a known limitation.

You're only chance of really finding out is if you get lucky and Nobu or ts or someone else with deeper Ruby knowledge accidently reads all this.

Which would actually be cool because then I would know if this is by design or not. Let's wait a bit and see if anyone bites. I will want to eventually document this so an explanation would be very cool.

What do I need to do to capture the whole environment of the originating _i() call. Is it at all possible?

It's a good question and I would like to say that coding interpolation is evil, but it might make sense here.

Perhaps you can make interpolate a method on Kernel -- then you would be able to do interpolate(@foo) and the self of the interpolate method and the one of the caller would always be the same thus avoiding the whole situation. Does this sound reasonable?

Thanks for the fast response Florian.

Florian Groß wrote:

Perhaps you can make interpolate a method on Kernel -- then you would be able to do interpolate(@foo) and the self of the interpolate method and the one of the caller would always be the same thus avoiding the whole situation. Does this sound reasonable?

I am not sure anymore if it is worth going through all the trouble. The whole solution is a little evally. First it is a little hard to understand, like you say yourself. Next there is the question of performance. As I understand eval is not very fast. And this is a method which is potentially being called very often. A Binding.of_caller solution would certainly be the most elegant, but faster and more reliable would be if the method would just do some stringfy thing.

_i('%s whine', @color)

or maybe to further simplify

_i(':color_of whine', :color_of => @color)

which should be easier to work with (but less flexible) for the translators.

_Since I am not the author of the ri18n, he seems to be on a vacation at the moment, I have to wait and talk to him first. But I am going to suggest using the above instead of the eval magic. Unless of course some of the real wizards come up with a magical and fast solution. Chances are slim I guess.

Let's see.

Sascha Ebach