Ruby parsing ambiguities
I was reading Perl Cannot Be Parsed: A Formal Proof on PerlMonks over breakfast this morning (this may in itself cause you to worry about me), which introduced me to a clever, ambiguous snippet of Perl constructed by Randall Schwartz:
whatever / 25 ; # / ; die "this dies!";
How this is parsed depends on what whatever
is: if
it’s a function that takes an argument, the slashes delimit a
regular expression, and the following statement kills the
program.
If, on the other hand, whatever
is a function
without any arguments, its return value is divided by 25, and the
rest of the line is a comment.
Since it’s possible to define whatever
dynamically,
the snippet can’t be parsed without running the program up to that
point. Ergo, Perl cannot be parsed statically.
But can we do the same in Ruby? Initially, it seems possible. The disambiguation of slashes has an additional nicety in Matz’s Ruby interpreter: if there’s a space after the opening slash, it will always be interpreted as a division operator. This works, though:
whatever /25#/; raise 'this dies!'
The meaning of the line depends on whether whatever
is a method or a variable:
whatever = 100 whatever /25#/; raise 'this dies!' # completes
def whatever(re) end whatever /25#/; raise 'this dies!' # dies
So it looks like we can perform the Perl trick and use code to
define what whatever
is, thereby creating Ruby code
that can’t be parsed. But, in fact, it doesn’t work, because Ruby’s
cleverer than that. And by clever, I mean evil. Ruby looks at
everything in the current context—even unreachable code—to
determine what the symbol whatever
refers to. So this
works as you’d expect:
if false def whatever() 200 end else whatever = 100 end whatever # => 100
But reverse the logic, and something strange happens:
if true def whatever() 200 end else whatever = 100 end whatever # => nil
Even though the method is defined, and the variable isn’t, the
parser has ‘seen’ the variable, and the meaning of the symbol is
changed into a variable. The message :whatever
is
never sent, because it refers to a variable. But that variable
isn’t defined! Instead of an error, though, we get nil.
If you’re thinking of getting round it by setting up the
definitions in eval
, I’ve bad news: that doesn’t work
for variables:
eval "def whatever() 200 end" whatever # => 200
eval "whatever = 100" whatever # => NameError: undefined local variable or method `whatever' for main:Object
So there you go. Ruby’s syntax may have some apparent ambiguities, but they can be resolved statically. At least, unless there are any others I don’t know about …
Incidentally, it’s testament to the hard work that the JRuby developers have put in that it has exactly the same behaviour.