Ruby1.9.1 : Override string method

The following script doesn’t work…
Do you have any idea of what is wrong?
Is it a ruby bug? (ruby version: ruby 1.9.1p243 (2009-07-16 revision
24175) [i386-darwin9])

require ‘time’

Time.parse(“1999-10-31 16:46:50”) # this works

class String
alias_method :old_sub!, :sub!
def sub!(*args, &block)
old_sub!(*args, &block)
end
end

Time.parse(“1999-10-31 16:46:50”) # this crashes

=>

ArgumentError: argument out of range

from /opt/local/lib/ruby1.9/1.9.1/time.rb:202:in `local’

from /opt/local/lib/ruby1.9/1.9.1/time.rb:202:in `make_time’

from /opt/local/lib/ruby1.9/1.9.1/time.rb:261:in `parse’

from (irb):10

from /opt/local/bin/irb1.9:12:in `’

Thomas S. wrote:

The following script doesn’t work…
Do you have any idea of what is wrong?

There are loads of sub! calls in date/format.rb, but I have no idea why
delegating sub! in this way would break it.

I note that the same error occurs in 1.8.6 too:
ArgumentError: argument out of range
from /usr/lib/ruby/1.8/time.rb:184:in local' from /usr/lib/ruby/1.8/time.rb:184:inmake_time’
from /usr/lib/ruby/1.8/time.rb:243:in `parse’
from (irb):19

I copied time.rb to my local directory and modified it to puts the
arguments to local. I see that it is calling

Time.local(1999, 10, 31, 16, 46, 50, 0)

before the sub! change, but

Time.local(2009,0,1,0,0,0,0)

after it. Most bizarre.

Hi –

On Mon, 2 Nov 2009, Brian C. wrote:

from /usr/lib/ruby/1.8/time.rb:184:in `local’

Time.local(2009,0,1,0,0,0,0)

after it. Most bizarre.

I believe it’s about the $1, $2… variables:

“abc”.sub!(/(.)/, “z”)
p $1 # nil
“abc”.old_sub!(/(.)/, “z”)
p $1 # “a”

When sub! calls old_sub!, the $n variables inside sub! are not set,
but the ones in old_sub! are. When the date parser hits _parse_iso (or
whichever of that family of methods it hits), there are calls to sub!
followed by usage of the $n variables, which are actually not set
because they’re two methods removed.

A more generic example:

def y(str)
/(.)/.match(str)
p $1
end

def x(str)
y(str)
p $1
end

x(“abc”)

=> “a”
nil

David


The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)

Interesting! I wasn’t aware of that.

Is there any way to make it work?

It is feasible in your generic example:

def y(str, b)
eval “/(.)/.match(str)”, b
p $1
end

def x(str)
y(str, binding)
p $1
end

x(“abc”)

=>

nil

“a”

But in my case, I need to find the caller’s binding :

class String
alias_method :old_sub!, :sub!
def sub!(*args, &block)
eval “old_sub!(*args, &block)”, caller_binding
end
end

But I havn’t been able to retrieve it :frowning: Do you have any idea?

Thomas

On Mon, Nov 2, 2009 at 9:31 AM, Thomas S. [email protected] wrote:

Interesting! I wasn’t aware of that.

Is there any way to make it work?

It is not possible to make it work. I have written on this previously,
but it’s a particular ugly wart of Ruby that the $_ and $~ variables
(and related vars like $1, $&, etc) are “special”, and only certain
core class methods are able to modify them (in the caller’s scope)
while no Ruby code can. It is for this reason that you can’t alias and
wrap any of those methods without breaking them.

To be honest, Ruby would be far better off if $_ and $~ went away,
since I believe they’re overreaching by modifying the caller’s scope
without your knowledge.

  • Charlie

A more generic example:

So the $~ variables are both thread-local and scoped to the enclosing
method? That’s definitely new to me. Thanks for the explanation.

Just ran into this in the wild
ruby - Programmatically Alias Method that uses $& Global Variable - Stack Overflow.
I’m guessing things haven’t changed in the last 6 years and this is
still not possible.

Charlie, do you have a link to a copy of your writings on this topic? I
couldn’t find them after a little googling.

It would be interesting to see what features we could add to get rid of
most common useage patterns of special global variables

It would be great to do something like

“foo”.gsub(/(f)(oo)/) { |_, matchdata| matchdata[1].upcase }

So we don’t have to use $1 etc.