Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Bug when rerouting String#gsub with a block using $1?

16 views
Skip to first unread message

Florian Gross

unread,
Aug 17, 2003, 6:38:52 PM8/17/03
to
Moin!

This code:

class String
alias :old_gsub :gsub
def gsub(*args, &block)
old_gsub(*args, &block)
end
end

"hello world".gsub(/(\w+)/) { print $1; $1 }; print "\n"

produces this output for me:

nilnil

(I'm using ruby 1.8.0 (2003-08-04) [i386-mswin32] and I'm told that it
does the same in the 1.8.0 final, 1.7.3 and 1.6.8 on Linux.)

Is this behaviour by design or is this a bug? If it's not a bug: Why
is $1 changed to nil in this case?

(This behaviour is causing an annoying bug in my new Ruby
implementation of Perl 6's Junctions and thus effectively replacing
irb's prompt with "()::>")

Thanks for any answers and effort to clarify this issue!

Regards,
Florian Gross


Mauricio Fernández

unread,
Aug 17, 2003, 6:57:30 PM8/17/03
to
On Mon, Aug 18, 2003 at 07:38:52AM +0900, Florian Gross wrote:
> Moin!
>
> This code:
>
> class String
> alias :old_gsub :gsub
> def gsub(*args, &block)
> old_gsub(*args, &block)
> end
> end
>
> "hello world".gsub(/(\w+)/) { print $1; $1 }; print "\n"
>
> produces this output for me:
>
> nilnil
>
> (I'm using ruby 1.8.0 (2003-08-04) [i386-mswin32] and I'm told that it
> does the same in the 1.8.0 final, 1.7.3 and 1.6.8 on Linux.)
>
> Is this behaviour by design or is this a bug? If it's not a bug: Why
> is $1 changed to nil in this case?

It seems it is by design:

batsman@tux-chan:/tmp$ expand -t2 a.rb
def foo
puts "Match: #{$1.inspect}"
end

"abcd" =~ /(b)/
foo
puts "Match: #{$1.inspect}"

batsman@tux-chan:/tmp$ ruby a.rb
Match: nil
Match: "b"

So $1 is method-scoped.

Now, is there any way to propagate $1??
We need it if gsub and friends are to be wrapped transparently.

--
_ _
| |__ __ _| |_ ___ _ __ ___ __ _ _ __
| '_ \ / _` | __/ __| '_ ` _ \ / _` | '_ \
| |_) | (_| | |_\__ \ | | | | | (_| | | | |
|_.__/ \__,_|\__|___/_| |_| |_|\__,_|_| |_|
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

One tree to rule them all,
One tree to find them,
One tree to bring them all,
and to itself bind them.
-- Gavin Koch <ga...@cygnus.com>

Yukihiro Matsumoto

unread,
Aug 18, 2003, 1:43:00 AM8/18/03
to
Hi,

In message "Re: Bug when rerouting String#gsub with a block using $1?"


on 03/08/18, Mauricio Fernández <batsm...@yahoo.com> writes:

|Now, is there any way to propagate $1??
|We need it if gsub and friends are to be wrapped transparently.

Explicitly? You can pass the match data and assign it to $~.

matz.

Mauricio Fernández

unread,
Aug 18, 2003, 3:17:49 AM8/18/03
to
On Mon, Aug 18, 2003 at 02:43:00PM +0900, Yukihiro Matsumoto wrote:
> |Now, is there any way to propagate $1??
> |We need it if gsub and friends are to be wrapped transparently.
>
> Explicitly? You can pass the match data and assign it to $~.

But this doesn't solve the problem, does it?

I don't see how $~ would help in

class String
alias :old_gsub :gsub
def gsub(*args, &block)
old_gsub(*args, &block)
end
end

"hello world".gsub(/(\w+)/) { print $1; $1 }; print "\n"

Is this just impossible to do in Ruby?

At any rate the behavior of the block is quite strange w.r.t. the binding
of $1. It is very different from that of other variables/globals in the
closure: $1 references the $1 in gsub, instead of the one in old_gsub
or the outer one.

However

batsman@tux-chan:/tmp$ expand -t2 b.rb

def foo
"foo" =~ /(foo)/
yield
end

def bar
"bar" =~ /(bar)/
foo { puts "foo: " + $1.inspect }
yield
end

bar {puts "bar: " + $1.inspect}

puts "1 world".gsub(/(1)/) { $1 + " is one" }

batsman@tux-chan:/tmp$ ruby b.rb
foo: "bar"
bar: nil
1 is one world

So gsub is indeed one special case in that $1 is bound to the "inner $1"
instead of the outer. And there's AFAIK no way to wrap gsub without
breaking it because of that.


--
_ _
| |__ __ _| |_ ___ _ __ ___ __ _ _ __
| '_ \ / _` | __/ __| '_ ` _ \ / _` | '_ \
| |_) | (_| | |_\__ \ | | | | | (_| | | | |
|_.__/ \__,_|\__|___/_| |_| |_|\__,_|_| |_|
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

Turn right here. No! NO! The OTHER right!

Yukihiro Matsumoto

unread,
Aug 18, 2003, 3:47:47 AM8/18/03
to
Hi,

In message "Re: Bug when rerouting String#gsub with a block using $1?"
on 03/08/18, Mauricio Fernández <batsm...@yahoo.com> writes:

| "hello world".gsub(/(\w+)/) { print $1; $1 }; print "\n"
|
|Is this just impossible to do in Ruby?

In pure Ruby, yes.

Ah, wait. If you don't need thread safety, you can do it as:


"hello world".gsub(/(\w+)/) { print $1; $1 }; print "\n"

class String
alias :old_gsub :gsub
def gsub(*args, &block)

if block
old_gsub(*args) {
$match = $~
eval("$~ = $match", block) # the trick here.
yield $&
}
else
old_gsub(*args, &block)
end


end
end
"hello world".gsub(/(\w+)/) { print $1; $1 }; print "\n"

matz.

Mauricio Fernández

unread,
Aug 18, 2003, 3:58:20 AM8/18/03
to
On Mon, Aug 18, 2003 at 04:47:47PM +0900, Yukihiro Matsumoto wrote:
> Hi,
>
> In message "Re: Bug when rerouting String#gsub with a block using $1?"
> on 03/08/18, Mauricio Fernández <batsm...@yahoo.com> writes:
>
> | "hello world".gsub(/(\w+)/) { print $1; $1 }; print "\n"
> |
> |Is this just impossible to do in Ruby?
>
> In pure Ruby, yes.

ouch. We'll then need C to make real Junctions then (not so bad since
we'd do it anyway for speed).

> Ah, wait. If you don't need thread safety, you can do it as:

Thank you for your quick responses.

--
_ _
| |__ __ _| |_ ___ _ __ ___ __ _ _ __
| '_ \ / _` | __/ __| '_ ` _ \ / _` | '_ \
| |_) | (_| | |_\__ \ | | | | | (_| | | | |
|_.__/ \__,_|\__|___/_| |_| |_|\__,_|_| |_|
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

Steal my cash, car and TV - but leave the computer!
-- Soenke Lange <soe...@escher.north.de>

Dan Doel

unread,
Aug 18, 2003, 4:33:05 AM8/18/03
to
What about:

p "hello world".gsub(/(\w+)/) { puts $1; $1 }
puts

class String
alias :old_gsub :gsub
def gsub(*args, &block)
if block

pattern = args[0]
old_gsub(pattern) { |match|
eval "#{pattern.inspect} =~ \"#{match}\"", block
yield match


}
else
old_gsub(*args, &block)
end
end
end

p "hello world".gsub(/(\w+)/) { puts $1; $1 }

Or does this miss something that gsub does?

- Dan


nobu....@softhome.net

unread,
Aug 18, 2003, 8:21:46 AM8/18/03
to
Hi,

At Mon, 18 Aug 2003 16:47:47 +0900,


Yukihiro Matsumoto wrote:
> Ah, wait. If you don't need thread safety, you can do it as:

You forget the trick that you'd written ago.

> "hello world".gsub(/(\w+)/) { print $1; $1 }; print "\n"
> class String
> alias :old_gsub :gsub
> def gsub(*args, &block)
> if block
> old_gsub(*args) {

eval("proc{|m|$~ = m}", block).call($~)


> yield $&
> }
> else
> old_gsub(*args, &block)
> end
> end
> end
> "hello world".gsub(/(\w+)/) { print $1; $1 }; print "\n"

--
Nobu Nakada

Florian Gross

unread,
Aug 18, 2003, 10:47:59 AM8/18/03
to
Yukihiro Matsumoto wrote:

> Hi,

Moin!

> In message "Re: Bug when rerouting String#gsub with a block using $1?"
> on 03/08/18, Mauricio Fernández <batsm...@yahoo.com> writes:
>
> |Is this just impossible to do in Ruby?
>
> In pure Ruby, yes.
>
> Ah, wait. If you don't need thread safety, you can do it as:
>

> [code snippet snipped]

That's a nice hack, thank you! And I think Nobu Nakada's change even
makes it thread-safe, but are you sure that the incosistent behavior
of $1 in blocks passed to gsub is needed? IMHO this is a confusing
trap and thus a source of unnecessary debugging sessions for users.

That aside: Are there other methods like sub, sub!, gsub and gsub!
which have this special behavior?

> matz.

Regards and thank you for designing a wonderful language,
Florian Gross


Yukihiro Matsumoto

unread,
Aug 18, 2003, 2:04:55 PM8/18/03
to
Hi,

In message "Re: Bug when rerouting String#gsub with a block using $1?"

on 03/08/18, Florian Gross <fl...@ccan.de> writes:

|That's a nice hack, thank you! And I think Nobu Nakada's change even
|makes it thread-safe, but are you sure that the incosistent behavior
|of $1 in blocks passed to gsub is needed? IMHO this is a confusing
|trap and thus a source of unnecessary debugging sessions for users.

Then don't use ugly dollar variables. But perhaps gsub should have
passed the match data to the block for convenience.

|That aside: Are there other methods like sub, sub!, gsub and gsub!
|which have this special behavior?

"gets" modifies $_ in local scope. $_ and $~ (and $1 etc) are treated
specially.

matz.

Florian Gross

unread,
Aug 18, 2003, 3:45:33 PM8/18/03
to
Yukihiro Matsumoto wrote:

> Hi,

Moin!

> In message "Re: Bug when rerouting String#gsub with a block using $1?"


> on 03/08/18, Florian Gross <fl...@ccan.de> writes:
>
> |IMHO this is a confusing
> |trap and thus a source of unnecessary debugging sessions for users.
>
> Then don't use ugly dollar variables.

I'm not using them myself which is the problem. irb uses them and I
don't really want to break irb. So unfortunately not using them is no
solution in this case, because others will use them as long as they
are there.

> But perhaps gsub should have passed the match data to the block for
> convenience.

I agree. Currently there's no other way than using $~ in the block of
a gsub do get more MatchData than $~[0]. I think that you could just
replace the current behavior (passing $~[0]) so that $~ gets passed.
The only case where this would be incompatible is fixed by giving the
passed value #to_str as a synonym for #to_s.

> |That aside: Are there other methods like sub, sub!, gsub and gsub!
> |which have this special behavior?
>
> "gets" modifies $_ in local scope. $_ and $~ (and $1 etc) are treated
> specially.

Ah, thanks, I don't think I'll have to special case gets because it
doesn't take a block however which is a Good Thing.

So this problem is really just about sub, sub!, gsub and gsub!. Would
changing those cases so that the $1 vars are taken out of the scope,
that belongs to the closure, be hardly possible at the implementation
level or are there other reasons for not changing this?

Thanks for your attention!

> matz.

Regards,
Florian Gross

0 new messages