问题
I am trying to write a compact line as below, the code is an extract from a script that reads STDIN by using the dynamically scoped special variable $*IN. Can you please advise how to write this line correctly?
This works
for $*IN.lines() {
last when "" ;
say "VERBOSE \"$_ is the string\"";
$i=$i+1;
}
does not work
.say "VERBOSE \"$_ is the string\"" for $*IN.lines() last when "";
error output:
===SORRY!=== Error while compiling /usr/share/asterisk/agi-bin/agi-t1.p6
Two terms in a row
at /usr/share/asterisk/agi-bin/agi-t1.p6:5
------> .say⏏ "Verbose \"$_\"" for $*IN.lines() last
expecting any of:
infix
infix stopper
statement end
statement modifier
statement modifier loop
回答1:
A generic explanation of the error message
===SORRY!=== Error while compiling ...
When you see a SORRY!
, then you know the compiler is talking to you about a problem that happened during compilation, even before there was an attempt to run your code.
Two terms in a row
This is the compiler's English summary of about what stopped it compiling your code. We'll return to it later.
The ------>
is the compiler's way of saying that it was confused by what you've written and it's going to display some of your code after the ------>
.
The ⏏
, a character whose Unicode name is EJECT SYMBOL
, is inserted into the display of your code. The point it's inserted should help in interpreting the error message.
In this case it points between .say
and "VERBOSE..."
. The compiler thinks those are two terms in a row.
What is a term?
Consider the following code:
start-time + 42
sum(start-time, 42)
Terms are somewhat like terms in mathematics. Both the example expressions include the terms start-time
and 42
. The overall expression start-time + 42
is also a term. The expression sum(start-time, 42)
might be called the sum()
term or sum(...)
term.
Terms are also somewhat like nouns or noun phrases in natural language. start-time
and 42
are like nouns, hence terms. start-time + 42
and sum(...)
are like noun phrases, each of which is also a term.
(Btw, terms, in the sense relevant to this question, are not related to parsing "terminals" which are sometimes called "terms".)
By now you might be able to guess what happens if you try to compile the example code this section began with:
Two terms in a row across lines (missing semicolon or comma?)
The first line (start-time + 42
) is a term. sum(start-time, 42)
is another term. And there's nothing between them except a line end. Perl 6 clearly doesn't like two terms in a row without something that's not a term in between them and whitespace and line ends don't count.
How can one avoid the "Two terms in a row" error?
Operators like the infix +
, postcircumfix ()
, and infix ,
that I used in the above examples can be used in operator positions (before, after, in between, or around, terms) to form expressions. (And the overall expressions are then themselves terms as explained above.)
Keywords like for
or last
, used in keyword position, are also not terms (unless you are crazy enough to redefine them as terms, in which case you'll likely get the weird compilation errors you deserve. :)) But, like operators, they must be placed in the right position or the compiler might think they're terms. If you write last
in the wrong place, the compiler might think last
is a term.
The problem with your code
The compiler considers .say
(note the space at the end) to be a term, equivalent to .say()
. So it interprets what you wrote as .say() "VERBOSE..."
which is two terms in a row.
(I recommend you just accept that this is so but if you wish to dig into the minutia of method calling syntax to fully understand why invocant.foo ( arrgh, arrgh, ... ) ;
is also "Two terms in a row", see my answer covering various syntaxes related to routine calls.)
Let's fix your code by changing the .say
to say
(without the .
):
say "VERBOSE \"$_ is the string\"" for $*IN.lines() last when "";
The compiler returns another "Two terms in a row" error but now it points between $*IN.lines()
and last
.
The for
keyword and its iteration argument have to be either at the start of a statement or at the end of a statement. You've used them at the end.
But that means that what comes after the for
is a term (its iteration argument).
$*IN.lines()
can work as a term.
You could even have it be part of an expression, eg. for flat $*IN.lines(), $*FOO.lines()
to loop over both input lines and lines from some other handle. (The flat
creates a single list for the for
to loop over by flattening the two individual lists, one from $*IN.lines()
, the other from $*FOO.lines()
.)
But you didn't build an expression, you just immediately followed $*IN.lines()
with last
.
Because there isn't a last
infix operator and last
must be the first word in a statement for it to be the last
keyword, the compiler interprets the last
as a term -- and so it sees "Two terms in a row".
You need the last
to be a statement keyword and it needs to be in the context of the for
loop. But you already have a statement in the context of the for
loop, namely the say ...
expression/term. You need some brackets or similar to allow you to write multiple statements. Here's one way:
{ last when ""; say "VERBOSE \"$_ is the string\"" } for $*IN.lines();
Now your code works.
Final tweaks
I might as well throw in a couple final tweaks:
( last when ''; say qq[VERBOSE "$_ is the string"]; $i++ ) for lines ;
I've switched from
{...}
to(...)
. It's not more compact but it shows that you can use parens rather than braces when thefor
is written as a statement modifier (i.e. at the end of the statement rather than at the start). The{...}
create a lexical scope whereas(...)
does not; there are times where one or the other is just what you need.You don't need the
$*IN.
because there's alines
sub that's equivalent to$*IN.lines()
.I've dropped the
()
afterlines
because they're not needed if there are no arguments afterlines
(or$*IN.lines
) and before the end of the statement.I've used
''
instead of""
because I think it's a good habit to use non-interpolating quotes if you don't need interpolating ones.I've used
qq[...]
because that means you don't have to escape the"
in the string.I've used
$i++
rather than$i=$i+1
because it achieves the same effect and I think it reads better.
来源:https://stackoverflow.com/questions/50775130/two-terms-in-a-row-error