Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Two terms in a row" error

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
like image 483
Mahadevan Avatar asked Jun 09 '18 14:06

Mahadevan


1 Answers

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. Raku 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 interpreted as the keyword last, the compiler instead 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\""; $i=$i+1 } 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 the for 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 a lines sub that's equivalent to $*IN.lines().

  • I've dropped the () after lines because they're not needed if there are no arguments after lines (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.

like image 83
raiph Avatar answered Oct 31 '22 22:10

raiph