This question already has answers here:
Why does white-space affect ruby function calls?
(2 answers)
Closed 6 years ago.
How can I remove the "warning: `*' interpreted as argument prefix" from the following code?
hash = {"a" => 1,
"b" => 2,
"s" => 3,}
if "string".start_with? *hash.keys then
puts "ok"
else
puts "ng"
end
When I run the code above, I get:
$ ruby -w /tmp/a.rb
/tmp/a.rb:5: warning: `*' interpreted as argument prefix
ok
What is the best way to fix this warning?
I've tried to put parenthesis around hash like this:
hash = {"a" => 1,
"b" => 2,
"s" => 3,}
if "string".start_with? (*hash.keys) then
puts "ok"
else
puts "ng"
end
then you get:
$ ruby -w /tmp/a.rb
/tmp/a.rb:5: syntax error, unexpected *
if "string".start_with? (*hash.keys) then
^
/tmp/a.rb:5: syntax error, unexpected ')', expecting '='
if "string".start_with? (*hash.keys) then
^
/tmp/a.rb:7: syntax error, unexpected keyword_else, expecting end-of-input
And this is the problem described in Why does white-space affect ruby function calls?, and clearly not the way to fix the warning I'm trying to fix.
My ruby version is:
$ ruby --version
ruby 2.3.3p222 (2016-11-21) [x86_64-linux-gnu]
If you're going to use method-calling-parentheses then you must avoid putting a space between the method name and the opening parentheses:
if "string".start_with?(*hash.keys)
puts "ok"
else
puts "ng"
end
Also, then is rather archaic so we'll pretend that was never there. If there is a space between the method name and the opening parentheses then your parentheses are interpreted as expression-grouping-parentheses and that's where your syntax error comes from.
Once you add the method-calling-parentheses you remove any possible hint of ambiguity as to what your * is supposed to mean and the warning should go away.
BTW, the warning you're getting in this case is rather, um, silly. On second thought, the warning isn't so silly because Ruby can be whitespace sensitive in surprising ways. This:
o.m *x
can be interpreted as:
o.m(*x)
or as:
o.m() * x
but these:
o.m * x
o.m*x
o.m* x
can be interpreted in the same ways. Of course, all three of those are interpreted as o.m() * x and only o.m *x is seen as o.m(*x). Sane whitespace usage would suggest that o.m *x is obviously a splat whereas o.m * x is obviously a multiplication but a couple days on SO should convince you that whitespace usage is hardly sane or consistent.
That said, -w's output in the Real World tends to be so voluminous and noisy that -w is nearly useless.
Related
I ran the following from a bash shell:
echo 'hello world' | ruby -ne 'puts $_ if /hello/'
I thought it was a typo at first, but it outputted hello world surprisingly.
I meant to type:
echo 'hello world' | ruby -ne 'puts $_ if /hello/ === $_'
Can anyone give an explanation, or point to documentation, to why we get this implicit comparison to $_?
I'd also like to note:
echo 'hello world' | ruby -ne 'puts $_ if /test/'
Won't output anything.
The Ruby parser has a special case for regular expression literals in conditionals. Normally (i.e. without using the e, n or p command line options) this code:
if /foo/
puts "TRUE!"
end
produces:
$ ruby regex-in-conditional1.rb
regex-in-conditional1.rb:1: warning: regex literal in condition
Assigning something that matches the regex to $_ first, like this:
$_ = 'foo'
if /foo/
puts "TRUE!"
end
produces:
$ ruby regex-in-conditional2.rb
regex-in-conditional2.rb:2: warning: regex literal in condition
TRUE!
This is a (poorly documented) exception to the normal rules for Ruby conditionals, where anything that’s not false or nil evaluates as truthy.
This only applies to regex literals, the following behaves as you might expect for a conditional:
regex = /foo/
if regex
puts "TRUE!"
end
output:
$ ruby regex-in-conditional3.rb
TRUE!
This is handled in the parser. Searching the MRI code for the text of the warning produces a single match in parse.y:
case NODE_DREGX:
case NODE_DREGX_ONCE:
warning_unless_e_option(parser, node, "regex literal in condition");
return NEW_MATCH2(node, NEW_GVAR(rb_intern("$_")));
I don’t know Bison, so I can’t explain exactly what is going on here, but there are some clues you can deduce. The warning_unless_e_option function simply suppresses the warning if the -e option has been set, as this feature is discouraged in normal code but can be useful in expressions from the command line (this explains why you don’t see the warning in your code). The next line seems to be constructing a parse subtree which is a regular expression match between the regex and the $_ global variable, which contains “[t]he last input line of string by gets or readline”. These nodes will then be compiled into the usually regular expression method call.
That shows what is happening, I’ll just finish with a quote from the Kernel#gets documentation which may explain why this is such an obscure feature
The style of programming using $_ as an implicit parameter is gradually losing favor in the Ruby community.
After digging through the Ruby source (MRI), I think I found an explanation.
The code:
pp RubyVM::InstructionSequence.compile('puts "hello world" if /hello/').to_a
produces the following output:
...
[:trace, 1],
[:putobject, /hello/],
[:getspecial, 0, 0],
[:opt_regexpmatch2],
...
The instructions seem to be calling opt_regexpmatch2 with two arguments, the first argument being the regex /hello/ and the second being a return value from getspecial
getspecial can be found in insns.def
/**
#c variable
#e Get value of special local variable ($~, $_, ..).
#j 特殊なローカル変数($~, $_, ...)の値を得る。
*/
DEFINE_INSN
getspecial
(rb_num_t key, rb_num_t type)
()
(VALUE val)
{
val = vm_getspecial(th, GET_LEP(), key, type);
}
Note that our instructions are most likely telling the VM to bring back the value of $_. $_ is automatically set for us when we run ruby with the correct options, e.g., -n
Now that we have our two arguments, we call opt_regexpmatch2
/**
#c optimize
#e optimized regexp match 2
#j 最適化された正規表現マッチ 2
*/
DEFINE_INSN
opt_regexpmatch2
(CALL_INFO ci)
(VALUE obj2, VALUE obj1)
(VALUE val)
{
if (CLASS_OF(obj2) == rb_cString &&
BASIC_OP_UNREDEFINED_P(BOP_MATCH, STRING_REDEFINED_OP_FLAG)) {
val = rb_reg_match(obj1, obj2);
}
else {
PUSH(obj2);
PUSH(obj1);
CALL_SIMPLE_METHOD(obj2);
}
}
At the end of the day
if /hello/' is equivalent to if $_ =~ /hello/ -- $_ will be nil unless we run ruby with the correct options.
Let's say I have a literal Fixnum 1420028751000 and I want to convert using this:
Time.at(1420028751000 / 1000) # => 2014-12-31 20:25:51 +0800
I can put spaces also, let's say I am beginner and my coding is bad:
Time.at(1420028751000 / 1000)
Time.at(1420028751000 /1000)
All these work fine and give me correct result.
However, once I introduced variable:
a = 1420028751000
Time.at(a/1000) # => Works!
Time.at(a / 1000) # => Works!
Time.at(a /1000) # => Strange thing happen
My question is, what is so unique about the /1000 that make it not working when introducing an variable in Ruby?
Ruby's parser is interpreting /1000 as the beginning of a literal regexp, since a is a token which may be a method. That is, imagine that a is a method:
def a(arg); end
Time.at(a /1000)
Ruby will interpret this as "a invoked with an incomplete regexp as the argument". To "complete" this call, Ruby is expecting you might want to do something like:
Time.at( a(/1000/) )
Ruby (ruby 2.0.0p195 (2013-05-14) [x64-mingw32]) is parsing a sequence "ident-space-slash-number" as something starting a regular epxression:
irb(main):030:0> x = 10
=> 10
irb(main):031:0> x /2
irb(main):032:0/ /
SyntaxError: (irb):32: unterminated regexp meets end of file
(Line #32 is just there to make irb finish the parsing. At the end of line #31, the first / is already treated as a regexp delimiter.)
With different spacing or different operators it works as expected:
irb(main):033:0> x / 2
=> 5
irb(main):034:0> x/ 2
=> 5
irb(main):035:0> x/2
=> 5
irb(main):036:0> x *2
=> 20
Is this a bug? Based on what assumption would the parser see a reqular expression in that case?
First of all, run irb with warnings enabled to have a better understanding of what is going on (unrelevant warnings are omitted):
$ irb -w
irb:001> x = 0
=> 0
irb:002> x /2
irb:003/ /
(irb):2: warning: `/' after local variable or literal is interpreted as binary operator
(irb):2: warning: even though it seems like regexp literal
SyntaxError: (irb):3: unterminated regexp meets end of file
On line 2 the Ruby lexer detects that x is a local variable so it assumes that the following / is a binary operator not the beginning of a regexp. On line 3 raises an error because a / by itself is an incomplete regexp.
This happens because IRB uses a lexer to know if the expression you entered is complete, and therefore can be sent to Ruby for execution, or if you need to provide more input to complete the expression. The IRB's lexer can't detect what x is, so it assumes that it is a method and tries to interpret the rest of the line (/2) as the argument to x, since it is an unterminated regexp IRB ask you to complete it on line 3, thus the code sent by IRB to the Ruby parser is invalid as explained above.
For comparison consider what happens when x is actually a method:
$ irb -w
irb:001> def x; end
=> :x
irb:002> x /2
irb:003/ /
(irb):2: warning: ambiguous first argument; put parentheses or even spaces
ArgumentError: wrong number of arguments (1 for 0)
from (irb):1:in `x'
from (irb):2
In this case both Ruby and IRB agree on the way the expression have to be parsed and you got an error because you are trying to pass an argument (namely /2\n/) to the x method which expects none.
To the point: it is a bug or not? Maybe it is a bug or maybe it is just a compromise to keep the IRB's lexer simple, I can't really tell.
As you defined x = 10. It is sure to Ruby parser that, x is a local variable. But now when you write x /2, Ruby hopes, there is a method called x also and x /2 treated as x(/2). But it is /2 syntactically incorrect, so Ruby seems you are going to give the method x a regexp object. But in the last line when you wrote /, Still the same confusion Ruby parser is having. Thus it complains, your regexp literals is unterminated.
This is partly to compliment Arup's answer
Take a look at the Regexp class documentation. The opening paragraph says:
... Regexps are created using the /.../ and %r{...} literals, and by the Regexp::new constructor...
So as Arup said, the ruby parser interprets the /2 portion of the line as the beginning of the creation of a Regexp literal, very much in the same way as "2 would be interpreted as the beginning of the creation of a String literal.
I am completing Neo's Ruby Koans (http://rubykoans.com/). In about_methods.rb, the koan instructs to correct the following, currently broken, eval:
# (NOTE: We are Using eval below because the example code is
# considered to be syntactically invalid).
def test_sometimes_missing_parentheses_are_ambiguous
eval "assert_equal (5), my_global_method (2, 3)" # ENABLE CHECK
#
# Ruby doesn't know if you mean:
#
# assert_equal(5, my_global_method(2), 3)
# or
# assert_equal(5, my_global_method(2, 3))
#
# Rewrite the eval string to continue.
#
end
my_global_method is
def my_global_method(a,b)
a + b
end
How do I need to alter the eval to pass this test?
Edit: RubyKoans: broken koan? asked whether this code was broken or not, and while it indicates that the question is working as intended, no answer to the koan is supplied.
The error is as follows:
(eval):1: syntax error, unexpected tINTEGER, expecting keyword_do or '{' or '(' (SyntaxError)
assert_equal 5, my_global_method 2, 3
Change it to:
eval "assert_equal 5, my_global_method(2, 3)"
You want your eval line to look something more like this:
eval "assert_equal 5, my_global_method(2, 3)" # ENABLE CHECK
Your error is telling you that it can't understand the parameters being passed to it, as it is missing parentheses. They put that code in incorrectly to demonstrate the need for parentheses when calling a method with several parameters.
Why do I get this?
p {a:3}
# => syntax error, unexpected tINTEGER, expecting tSTRING_CONTENT or tSTRING_DBEG or tSTRING_DVAR or tSTRING_END
# => p {a:3}
^
Ruby has a few oddities in its parsing engine. One is that certain things require parentheses around them.
For instance, this should work.
p({a:3})
Or this
hash = { a: 3 }
p hash
As the other answer pointed out. The reason for this is that the interpreter processes as below.
# Input
p { a: 3 }
# What the interpreter sees
p do
a: 3
end
The Kernel#p doesn't support blocks, so you must use the parentheses.