On Ranges as conditions

Reading a few tutorials I found this piece of code:

  while input = gets
      input = input.chomp
      puts input + " triggered" if input =~ /start/ .. input =~ /end/
  end

I understand what it does. If I type start, end or anything in between
it prints
"triggered" after the input. Now, if I'm out of the start .. end block,
the if
fails.

What I don't understand is how it does it. For instance, if I use the
interpreter to do something like this:

  irb(main):038:0> input = 'fooo'
  => "fooo"
  irb(main):039:0> puts input + " triggered" if input =~ /start/ .. input
=~ /end/
  => nil
  irb(main):040:0> input = 'start'
  => "start"
  irb(main):041:0> puts input + " triggered" if input =~ /start/ .. input
=~ /end/
  start triggered
  => nil
  irb(main):042:0> input = 'bar'
  => "bar"
  irb(main):043:0> puts input + " triggered" if input =~ /start/ .. input
=~ /end/
  => nil

it doesn't work as I'd expect. I thought the if clause there would
somehow set a
global to say "the first regex was matched" or something like that. But it
doesn't seem to be the way it works. It only seems to work inside of a while
loop. I thought it might have something to do with the $_ global. But I
made a
test in the interpreter which ruled out that theory:

  irb(main):045:0> $_ = 'foo'
  => "foo"
  irb(main):046:0> input = 'foo'
  => "foo"
  irb(main):047:0> puts input + " triggered" if input =~ /start/ .. input
=~ /end/
  => nil
  irb(main):048:0> $_ = input = 'start'
  => "start"
  irb(main):049:0> puts input + " triggered" if input =~ /start/ .. input
=~ /end/
  start triggered
  => nil
  irb(main):050:0> $_ = input = 'foo'
  => "foo"
  irb(main):051:0> puts input + " triggered" if input =~ /start/ .. input
=~ /end/
  => nil
  irb(main):052:0>

Can anyone help me find out what's going on?

As far as I can tell, from reading the 1.8 parse.c code, the problem
is that conditional ranges really aren't Range instances but are
really a kind of syntactic sugar. Every time you retype the source
line into irb, you get an entirely new parse, and a new conditional
range in the initial state.

···

On Sat, Oct 3, 2009 at 1:15 PM, Rafael Cunha de Almeida <none@example.org> wrote:

Reading a few tutorials I found this piece of code:

   while input = gets
       input = input\.chomp
       puts input \+ &quot; triggered&quot; if input =\~ /start/ \.\. input =\~ /end/
   end

I understand what it does. If I type start, end or anything in between
it prints
"triggered" after the input. Now, if I'm out of the start .. end block,
the if
fails.

What I don't understand is how it does it. For instance, if I use the
interpreter to do something like this:

   irb\(main\):038:0&gt; input = &#39;fooo&#39;
   =&gt; &quot;fooo&quot;
   irb\(main\):039:0&gt; puts input \+ &quot; triggered&quot; if input =\~ /start/ \.\. input

=~ /end/
=> nil
irb(main):040:0> input = 'start'
=> "start"
irb(main):041:0> puts input + " triggered" if input =~ /start/ .. input
=~ /end/
start triggered
=> nil
irb(main):042:0> input = 'bar'
=> "bar"
irb(main):043:0> puts input + " triggered" if input =~ /start/ .. input
=~ /end/
=> nil

it doesn't work as I'd expect. I thought the if clause there would
somehow set a
global to say "the first regex was matched" or something like that. But it
doesn't seem to be the way it works. It only seems to work inside of a while
loop. I thought it might have something to do with the $_ global. But I
made a
test in the interpreter which ruled out that theory:

   irb\(main\):045:0&gt; $\_ = &#39;foo&#39;
   =&gt; &quot;foo&quot;
   irb\(main\):046:0&gt; input = &#39;foo&#39;
   =&gt; &quot;foo&quot;
   irb\(main\):047:0&gt; puts input \+ &quot; triggered&quot; if input =\~ /start/ \.\. input

=~ /end/
=> nil
irb(main):048:0> $_ = input = 'start'
=> "start"
irb(main):049:0> puts input + " triggered" if input =~ /start/ .. input
=~ /end/
start triggered
=> nil
irb(main):050:0> $_ = input = 'foo'
=> "foo"
irb(main):051:0> puts input + " triggered" if input =~ /start/ .. input
=~ /end/
=> nil
irb(main):052:0>

Can anyone help me find out what's going on?

--
Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale

It isn't a range, it's a "flip-flop" which happens to use the same operator ('..') as range construction.

While useful in some sed/awk like scripts it's not a widely used feature.

-- MarkusQ

# (this entire response can be directly placed into a script -- not the
above quote, though)

···

On Sat, Oct 3, 2009 at 12:15 PM, Rafael Cunha de Almeida <none@example.org>wrote:

Reading a few tutorials I found this piece of code:

       while input = gets
           input = input.chomp
           puts input + " triggered" if input =~ /start/ .. input =~ /end/
       end

I understand what it does. If I type start, end or anything in between
it prints
"triggered" after the input. Now, if I'm out of the start .. end block,
the if
fails.

What I don't understand is how it does it. For instance, if I use the
interpreter to do something like this:

       irb(main):038:0> input = 'fooo'
       => "fooo"
       irb(main):039:0> puts input + " triggered" if input =~ /start/ ..
input
=~ /end/
       => nil
       irb(main):040:0> input = 'start'
       => "start"
       irb(main):041:0> puts input + " triggered" if input =~ /start/ ..
input
=~ /end/
       start triggered
       => nil
       irb(main):042:0> input = 'bar'
       => "bar"
       irb(main):043:0> puts input + " triggered" if input =~ /start/ ..
input
=~ /end/
       => nil

it doesn't work as I'd expect. I thought the if clause there would
somehow set a
global to say "the first regex was matched" or something like that. But it
doesn't seem to be the way it works. It only seems to work inside of a
while
loop. I thought it might have something to do with the $_ global. But I
made a
test in the interpreter which ruled out that theory:

       irb(main):045:0> $_ = 'foo'
       => "foo"
       irb(main):046:0> input = 'foo'
       => "foo"
       irb(main):047:0> puts input + " triggered" if input =~ /start/ ..
input
=~ /end/
       => nil
       irb(main):048:0> $_ = input = 'start'
       => "start"
       irb(main):049:0> puts input + " triggered" if input =~ /start/ ..
input
=~ /end/
       start triggered
       => nil
       irb(main):050:0> $_ = input = 'foo'
       => "foo"
       irb(main):051:0> puts input + " triggered" if input =~ /start/ ..
input
=~ /end/
       => nil
       irb(main):052:0>

Can anyone help me find out what's going on?

#
# The first condition turns it on (causes it to evaluate to true)
# the second turns it off (causes it to evaluate back to false).
# In this example, you would get the output
#
# start triggered
# instruction2 triggered
# instruction3 triggered
# end triggered
# start triggered
# instruction6 triggered
# instruction7 triggered
# instruction8 triggered
# end triggered
#
# Give it a try

$stdin = DATA

while input = gets
  input = input.chomp
  puts input + " triggered" if input =~ /start/ .. input =~ /end/
end

__END__
instruction0
instruction1
start
instruction2
instruction3
end
instruction4
instruction5
start
instruction6
instruction7
instruction8
end
instruction9

Pretty cool! Thanks for pointing this syntax out! I don't recall seeing
it in Pickaxe.

One question:

irb(main):013:0> a=%w{foo start bar end baz start foo bar end baz}
=> ["foo", "start", "bar", "end", "baz", "start", "foo", "bar", "end",
"baz"]
irb(main):014:0> a.each {|i| puts i + (if i =~ /start/ .. i =~ /end/ then
" triggered" else "" end) }
foo
start triggered
bar triggered
end triggered
baz
start triggered
foo triggered
bar triggered
end triggered
baz
=> ["foo", "start", "bar", "end", "baz", "start", "foo", "bar", "end",
"baz"]
irb(main):015:0> a.each {|i| puts i + (if i =~ /start/ ... i =~ /end/
then " triggered" else "" end) }
foo
start triggered
bar triggered
end triggered
baz
start triggered
foo triggered
bar triggered
end triggered
baz

Shouldn't the last version (using ... instead of ..) logically omit the
end line from the triggered section?

--Ken

···

On Sun, 04 Oct 2009 04:06:56 +0900, Rick DeNatale wrote:

On Sat, Oct 3, 2009 at 1:15 PM, Rafael Cunha de Almeida > <none@example.org> wrote:

Reading a few tutorials I found this piece of code:

   while input = gets
       input = input\.chomp
       puts input \+ &quot; triggered&quot; if input =\~ /start/ \.\. input
       =\~ /end/
   end

I understand what it does. If I type start, end or anything in between
it prints
"triggered" after the input. Now, if I'm out of the start .. end block,
the if
fails.

What I don't understand is how it does it. For instance, if I use the
interpreter to do something like this:

   irb\(main\):038:0&gt; input = &#39;fooo&#39;
   =&gt; &quot;fooo&quot;
   irb\(main\):039:0&gt; puts input \+ &quot; triggered&quot; if input =\~
   /start/ \.\. input

=~ /end/
=> nil
irb(main):040:0> input = 'start'
=> "start"
irb(main):041:0> puts input + " triggered" if input =~
/start/ .. input
=~ /end/
start triggered
=> nil
irb(main):042:0> input = 'bar'
=> "bar"
irb(main):043:0> puts input + " triggered" if input =~
/start/ .. input
=~ /end/
=> nil

it doesn't work as I'd expect. I thought the if clause there would
somehow set a
global to say "the first regex was matched" or something like that. But
it doesn't seem to be the way it works. It only seems to work inside of
a while loop. I thought it might have something to do with the $_
global. But I made a
test in the interpreter which ruled out that theory:

   irb\(main\):045:0&gt; $\_ = &#39;foo&#39;
   =&gt; &quot;foo&quot;
   irb\(main\):046:0&gt; input = &#39;foo&#39;
   =&gt; &quot;foo&quot;
   irb\(main\):047:0&gt; puts input \+ &quot; triggered&quot; if input =\~
   /start/ \.\. input

=~ /end/
=> nil
irb(main):048:0> $_ = input = 'start' => "start"
irb(main):049:0> puts input + " triggered" if input =~
/start/ .. input
=~ /end/
start triggered
=> nil
irb(main):050:0> $_ = input = 'foo'
=> "foo"
irb(main):051:0> puts input + " triggered" if input =~
/start/ .. input
=~ /end/
=> nil
irb(main):052:0>

Can anyone help me find out what's going on?

As far as I can tell, from reading the 1.8 parse.c code, the problem is
that conditional ranges really aren't Range instances but are really a
kind of syntactic sugar. Every time you retype the source line into
irb, you get an entirely new parse, and a new conditional range in the
initial state.

--
Chanoch (Ken) Bloom. PhD candidate. Linguistic Cognition Laboratory.
Department of Computer Science. Illinois Institute of Technology.
http://www.iit.edu/~kbloom1/

Pretty cool! Thanks for pointing this syntax out! I don't recall seeing
it in Pickaxe.

It's definitely in the first (2001) edition as "Ranges as conditions":
_A range used in a boolean expression acts as a flip-flop.
_ It has two states, set and unset, and is initially unset.
_ On each call, the range cycles through the state machine ...
_ The range returns true if it is in the set state at the end of the call,
_ and false otherwise.
_The two-dot form of a range behaves slightly differently than the
three-dot form.
_ When the two-dot form first makes the transition from unset to set,
_it immediately evaluates the end condition and makes the transition
accordingly.
_This means that if expr1 and expr2 both evaluate to true on the same call,
_the two-dot form will finish the call in the unset state.
_However, it still returns true for this call.
.
*But* - as Rick DeNatale and Markus Roberts posted - in these expressions
the ".." and "..." don't create ranges but create "flip-flop" expressions,
so I think it's misleading to call these "ranges",
and I think the explanations in "The Ruby Programming Language" are better
because they make that clear.

One question:

irb(main):013:0> a=%w{foo start bar end baz start foo bar end baz}
=> ["foo", "start", "bar", "end", "baz", "start", "foo", "bar", "end",
"baz"]
irb(main):014:0> a.each {|i| puts i + (if i =~ /start/ .. i =~ /end/ then
" triggered" else "" end) }
foo
start triggered
bar triggered
end triggered
baz
start triggered
foo triggered
bar triggered
end triggered
baz
=> ["foo", "start", "bar", "end", "baz", "start", "foo", "bar", "end",
"baz"]
irb(main):015:0> a.each {|i| puts i + (if i =~ /start/ ... i =~ /end/
then " triggered" else "" end) }
foo
start triggered
bar triggered
end triggered
baz
start triggered
foo triggered
bar triggered
end triggered
baz

Shouldn't the last version (using ... instead of ..) logically omit the
end line from the triggered section?

If I understand your question correctly, according to "Programming Ruby"
and "The Ruby Programming Language" the ".." and "..." refer to
*when* the flip-flop expressions are or are not evaluated,
as the quote above from Pickaxe ("Programming Ruby") says.
Does the following (irb) code explain this?

An = [1, 2, 3, 5, 2.0, 3.0, 5.0] #=> [1, 2, 3, 5, 2.0, 3.0, 5.0]
def ffn(b, e, dots = "..")
  aa =
  ffop = "x==#{b} #{dots} x>=#{e}"
  expr = "An.each { |x| aa << x if #{ffop} }"
  puts "expr #=> " + expr
  eval expr
  aa
end
#=> nil
ffn 2, 3, ".."
# expr #=> An.each { |x| aa << x if x==2 .. x>=3 }
#=> [2, 3, 2.0, 3.0]
ffn 2, 3, "..."
# expr #=> An.each { |x| aa << x if x==2 ... x>=3 }
#=> [2, 3, 2.0, 3.0]
fn 2, 2, ".."
# expr #=> An.each { |x| aa << x if x==2 .. x>=2 }
#=> [2, 2.0]
ffn 2, 2, "..."
# expr #=> An.each { |x| aa << x if x==2 ... x>=2 }
#=> [2, 3, 2.0, 3.0]

···

On Wed, Oct 7, 2009 at 1:08 PM, Ken Bloom <kbloom@gmail.com> wrote: