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

Discussion in 'Ruby' started by Florian Gross, Aug 17, 2003.

  1. Moin!

    This code:

    class String
    alias :eek:ld_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
     
    Florian Gross, Aug 17, 2003
    #1
    1. Advertising

  2. On Mon, Aug 18, 2003 at 07:38:52AM +0900, Florian Gross wrote:
    > Moin!
    >
    > This code:
    >
    > class String
    > alias :eek:ld_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 <>
     
    Mauricio Fernández, Aug 17, 2003
    #2
    1. Advertising

  3. 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 :eek:ld_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!
     
    Mauricio Fernández, Aug 18, 2003
    #3
  4. Hi,

    In message "Re: Bug when rerouting String#gsub with a block using $1?"
    on 03/08/18, Mauricio Fernández <> 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 :eek:ld_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.
     
    Yukihiro Matsumoto, Aug 18, 2003
    #4
  5. 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 <> 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 <>
     
    Mauricio Fernández, Aug 18, 2003
    #5
  6. Florian Gross

    Dan Doel Guest

    What about:

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

    class String
    alias :eek:ld_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
     
    Dan Doel, Aug 18, 2003
    #6
  7. Florian Gross

    Guest

    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 :eek:ld_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
     
    , Aug 18, 2003
    #7
  8. 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 <> 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
     
    Florian Gross, Aug 18, 2003
    #8
  9. Hi,

    In message "Re: Bug when rerouting String#gsub with a block using $1?"
    on 03/08/18, Florian Gross <> 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.
     
    Yukihiro Matsumoto, Aug 18, 2003
    #9
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Andrew Connell
    Replies:
    1
    Views:
    549
    Natty Gur
    Oct 21, 2003
  2. Codex Twin
    Replies:
    8
    Views:
    486
    Codex Twin
    Dec 3, 2004
  3. morrell
    Replies:
    1
    Views:
    967
    roy axenov
    Oct 10, 2006
  4. Drifter

    Need Some rerouting code... puhleasse

    Drifter, Nov 19, 2003, in forum: ASP General
    Replies:
    6
    Views:
    142
    Guest
    Nov 19, 2003
  5. aurelianito

    gsub and gsub! are inconsistent

    aurelianito, Nov 8, 2005, in forum: Ruby
    Replies:
    9
    Views:
    164
    Robert Klemme
    Nov 9, 2005
Loading...

Share This Page