3

I keep seeing this snippet everywhere, and it works! why?

while gets
    print if /start/../end/
end

How does ruby evaluate /start/ without an Lvalue? I would expect that we would first have to store the value of 'gets' somewhere and then do

gets_result =~ /start/.. gets_result =~ /end/

So why does the snippet work?

Let me clear this up.

How does ruby know to compare the regular expression against gets In the snippet above at no point do I specify to ruby that the reg exp is to be compared to gets but it just knows. Question is how?

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
code shogan
  • 839
  • 2
  • 9
  • 25

3 Answers3

4

Kernel#gets not only returns the next line, but also assign the value to $_.

Kernel#print print $_ if there's no argument.

The flip-flop operator (/start/../end/) also operate on $_.

falsetru
  • 357,413
  • 63
  • 732
  • 636
2

Nice question.

Remember: When the Range operator (.. or ...) is used in a conditional statement, it does something totally unexpected: it doesn't create a Range object. Instead, it acts as a "flip-flop" operator.

The below code actually

while gets
    print if /start/../end/
end

by default is

while gets
    # gets_input I put just to make the code more expressive
    # actually the input taken using gets method applied here implicitly.
    print if  /start/ =~ gets_input .. /end/ =~ gets_input
end

Let me proof you. I took the help of Ruby Tracer class.

trace = TracePoint.new do |tp|
  p [tp.lineno, tp.event, tp.defined_class,tp.method_id]
end
trace.enable do
  while gets
    # when you type start in your console, 11 will be output.
    print 11 if /start/../end/
  end
end

Let me run this code, to show you my above code as a proof and also the Ruby Flip-Flop feature :

(arup~>Ruby)$ ruby test.rb
test.rb:6: warning: regex literal in condition
test.rb:6: warning: regex literal in condition
[4, :b_call, nil, nil]
[5, :line, nil, nil]
[5, :c_call, Kernel, :gets]
[5, :c_call, ARGF.class, :gets]
end # I presses **end** here.
[5, :c_return, ARGF.class, :gets]
[5, :c_return, Kernel, :gets]
[6, :line, nil, nil]
# Regexp#=~ call begin happened for /end/ =~ gets_input
[6, :c_call, Regexp, :=~]
# Regexp#=~ call end happened for /end/ =~ gets_input
[6, :c_return, Regexp, :=~] 
[5, :c_call, Kernel, :gets]
[5, :c_call, ARGF.class, :gets]
start # I presses **start** here.
[5, :c_return, ARGF.class, :gets]
[5, :c_return, Kernel, :gets]
[6, :line, nil, nil]
# Regexp#=~ call begin happened for /start/ =~ gets_input
[6, :c_call, Regexp, :=~] 
# Regexp#=~ call end happened for /start/ =~ gets_input
[6, :c_return, Regexp, :=~]
# Regexp#=~ call begin happened for /end/ =~ gets_input
[6, :c_call, Regexp, :=~] 
# Regexp#=~ call end happened for /end/ =~ gets_input
[6, :c_return, Regexp, :=~] 
[6, :c_call, Kernel, :print]
[6, :c_call, IO, :write]
[6, :c_call, Fixnum, :to_s]
[6, :c_return, Fixnum, :to_s]
# As there is a match so **if** clause true, thus 11 printed
11[6, :c_return, IO, :write] 
[6, :c_return, Kernel, :print]
[5, :c_call, Kernel, :gets]
[5, :c_call, ARGF.class, :gets]
end  
[5, :c_return, ARGF.class, :gets]
[5, :c_return, Kernel, :gets]
[6, :line, nil, nil]
[6, :c_call, Regexp, :=~]
[6, :c_return, Regexp, :=~]
[6, :c_call, Kernel, :print]
[6, :c_call, IO, :write]
[6, :c_call, Fixnum, :to_s]
[6, :c_return, Fixnum, :to_s]
11[6, :c_return, IO, :write]
[6, :c_return, Kernel, :print]
[5, :c_call, Kernel, :gets]
[5, :c_call, ARGF.class, :gets]
Arup Rakshit
  • 116,827
  • 30
  • 260
  • 317
0

In this case, .. is a flip-flop operator. When given 'start', the condition evaluates to true, and continues being true until end is given.

Zach Kemp
  • 11,736
  • 1
  • 32
  • 46