From: Brian Candler Date: 2012-11-04T20:59:11+09:00 Subject: Re: bug?: local variable created in if modifier not available in modified expression Igor Pirnovar wrote in post #1082740: > You are misleading us here with {{ def bar; "Method bar"; end }} before > the assignment in {{ bar if bar = 123 }}, because parser now thinks that > it sees the method bar. On the contrary, it is you who are misleading people with your inaccurate information. In your defence, I will say that ruby is a language which is not formally specified or documented, and therefore it is easy to come up with misinterpretations of what you see. However, there are people on this list who will try to enlighten you, if you will take the trouble to listen. > Try it with a new variable, that is not shadowed > by another token like in your case the bar method, and you will see, > that interpreter exits with the error! For instance: > > p "v5:#{v5 if ((v5='created')!='')}" #=> FAILS with: > # t.rb:28:in `
': undefined local variable \ > # or method `v5' for main:Object (NameError) Indeed it does, and the error message says "undefined local variable or method" Ruby has interpreted the first v5 as a method call, because no local variable assignment statement exists lexically earlier in the scope. So it has tried to call method v5(), and failed. However it cannot know whether the programmer intended v5 to be a variable or method. It could be that you intended to use a variable, but mistyped the name: v5 = nil puts vv5 # Oops, I mistyped the variable name vv6 = nil # Oops, I mistyped the variable name at assignment time puts v6 Or it could be that you intended to call a method but mistyped it: def v7; "ok"; end puts vv7 # Oops, I mistyped the method name So the best that Ruby can say is "undefined local variable or method". But what it actually means is: v5 is *definitely* not a local variable at this point, so I tried invoking it as a method, but there is no method called v5 at this point in time. Note: whether v5 exists as a method or not can only be known at runtime, because methods are created dynamically as the program runs. > Precedence may not be important, though I am not sure it is not, since > the run-time must wait for the condition to be evaluated, therefore it > will execute the assignment inside the 'if' expression in accordance > with precedence, namely prior 'if' expression itself is evaluated, hence > allowing the evaluation of the expression in front of the conditional, > the run-time was waiting for. Wrong. The resolution of the variable name / method call ambiguity is not done at run-time. It is done at *parse* time, when the source file is being read in and the AST is being built, before the AST is executed. If you don't believe me, then try making a source file which cannot be parsed, like this: puts "hello" class Foo # missing end You will see something like this: syntax error, unexpected $end But notice that the word "hello" does not appear on your screen. This is because execution cannot start until the file has been completely parsed from top to bottom. Balancing class/end and def/end is a parse-time activity. Now, it is during this *parsing* phase that the decision is made as to whether a bareword 'foo' is to be interpreted as a method call or as a local variable reference. And this happens before even one statement from this file has been executed. > Also, the fact that {{ puts var||='n/a' if var=123 }} works as expected, > and {{ puts var if var=123 }} does not, proves that evaluation here is > right-to-left regardless of the fact that parser works in the opposite > direction, after all, if parser would react to the first assignment it > encountered in 'strictly' left-to-right fashion the result should be > 'n/a' and not 123 that was evaluated last. var||='n/a' is an assignment to var and therefore springs a local variable into existence at that point lexically. You can consider it as a shortcut for var = var || 'n/a', although it is not exactly like that. The execution sequence obviously requires the 'if' condition to be evaluated before the LHS can be conditionally executed. if var = 123 # assigns 123 to var, evaluates as true, hence LHS is run puts var||='n/a' # prints 123, because var has already been assigned to So evaluation is right-before-left, at the time where the AST is executed. This still does not change the fact that it is parsed left-to-right. Hence, if you have not assigned to var previously, then puts var if var=123 is parsed as puts var() if var=123 It treats the first bareword 'var' to be resolved as a method call; it will fail unless a method called 'var' has been defined. The final point you need to be clear on: whether an assignment is *executed* or not makes no difference. It's simply whether it is parsed as one. So for example: if false baz = 123 end puts baz # prints nil - does not give an error Regards, Brian. -- Posted via http://www.ruby-forum.com/.