qt4cg / qtspecs

QT4 specifications
https://qt4cg.org/
Other
28 stars 15 forks source link

Dangling else syntax ambiguity #1309

Closed michaelhkay closed 2 months ago

michaelhkay commented 3 months ago

I think this is ambiguous, or at any rate, involves arbitrary lookahead:

if (a = b) then if (c = d) {23} else {}

You only get a successful parse if you associate the "else" with the first "if", but you can't do that until you know that there isn't going to be a second "else".

Again, it's the fact that an expression can now begin with a left curly brace that's the culprit.

michaelhkay commented 3 months ago

A more complex example:

if (a = b) 
then for $i in (1 to 5)
         return if ($i gt 3) {$i}
else {}

One possible remedy is to require that the expression within braces after then and else is not omitted. I believe that {} is the only example of a construct that is both a valid expression and a valid EnclosedExpr; any other expression starting with { would give a syntax error if used after a braced else, which makes it much harder to construct an example that parses successfully, but not as the user intended. We would still need a rule that the else belongs with the nearest if, but the error messages might be clearer.

michaelhkay commented 3 months ago

Another remedy, which also addresses #1241, would be to disallow {} as an empty map constructor. Instead this would have to be written as map{}.

ndw commented 3 months ago

Are we still confident that the problem we were trying to solve is worth the extra complexity?

michaelhkay commented 3 months ago

@ndw Are you suggesting getting rid of the braced if-then-else entirely? Or the bare-brace map constructor? Or something else?

ndw commented 3 months ago

I'm asking us to consider whether, on balance, it's worth trying to find a way to add additional rules and complexity to make this work, or whether we should consider backing out either or both of the braced if-then-else and bare-brace map constructors.

I fear that having drifted into grammatical ambiguity, we're going to enter a viscious cycle of "invent a fix for the current case, time passes, find a new ambiguity, invent a new fix, time passes, find a new ambiguity, repeat until exhaustion."

Deciding to back out the changes now might be a bitter pill, but if we wait a year, it will be that much harder to swallow.

ChristianGruen commented 3 months ago

I fear that having drifted into grammatical ambiguity, we're going to enter a viscious cycle of "invent a fix for the current case, time passes, find a new ambiguity, invent a new fix, time passes, find a new ambiguity, repeat until exhaustion."

I have similar concerns.

The designers of the 5 or even 10 most popular programming languages seem to have chosen to accept the existence of the dangling-else ambiguity. Maybe we should reflect again why we believe it is a no-go for our language, or if this restriction is not too paternalistic (it’s always up to the coder, or/and the underlying style/formatting convention, to enforce else branches or use appropriate indentation).

michaelhkay commented 2 months ago

We could place constraints on the mixing of braced and unbraced "if" expressions - for example disallowing use of a braced if expression within an unbraced if expression.

ChristianGruen commented 2 months ago

We could place constraints on the mixing of braced and unbraced "if" expressions - for example disallowing use of a braced if expression within an unbraced if expression.

Again: Wouldn’t it be much more intuitive to keep it simple, and follow the path that other major languages have chosen?

michaelhkay commented 2 months ago

You mean, associate the "else" with the innermost "if".

We could do that. I just hate perpetuating a design error that was recognized and fixed 55 years ago in Algol 68.

michaelhkay commented 2 months ago

I hit another syntax ambiguity with bare-braces:

Consider

for $n in $x/element return {}

Almost hit this one in real life - see K2-ForExprWithout-15, which Saxon is currently failing to parse. The actual test does

for $n in $arg/element return $n

which can be parsed with a little bit of lookahead, but if the $ is changed to { then it becomes troublesome.

michaelhkay commented 2 months ago

I'm wondering if there is a solution that restricts the use of the bare-brace map constructor to contexts where there is no risk of ambiguity, e.g. immediately following something like :=, (, or ,.

Something like

StandaloneExpr ::= ExprSingle | BareBraceMap

with StandaloneExpr replacing ExprSingle in selected productions such as Expr, VarValue, LetBinding, Argument, and SquareArrayConstructor.

So basically the rule-of-thumb becomes that you can omit the keyword map on a map constructor only in contexts where there are no other operators involved, and in particular when the opening brace is preceded by (, [, :=, or ,.

ChristianGruen commented 2 months ago

So basically the rule-of-thumb becomes that you can omit the keyword map on a map constructor only in contexts where there are no other operators involved, and in particular when the opening brace is preceded by (, [, :=, or ,.

I would tend to hate that, being used to the comfort of fully composable constructs… But as usual it’s easy to complain.

Related: It was reported back to us that curly braces preceded by curly braces can become cryptic:

declare function local:f() { { 1: 2 } };
michaelhkay commented 2 months ago

that curly braces preceded by curly braces

Which was why I didn't include EnclosedExpr as one of the places where a StandaloneExpr would be allowed.

Note that the proposal doesn't really depart from the existing principle that to compose expressions in an arbitrary way you sometimes need to use parentheses. All it does is to move the bare-brace map constructor to a much lower operator precedence level.

michaelhkay commented 2 months ago

Another example: for $x in element where {} return $x -- here the ambiguity can't be resolved by any amount of lookahead.

ChristianGruen commented 2 months ago

Node constructors followed by unbraced QNames… The first thing (pardon) I would send to Limbo if we dropped any features …which we won’t do.

ChristianGruen commented 2 months ago

I’m trying to get the new rules right: Is it correct that {} instance of map(*) is now expected to raise a parsing error?

I observed that the change also affects XQuery Update and Full Text. I assume we won’t have an official document that illustrates whether Saxon and BaseX will behave identically?

michaelhkay commented 2 months ago

Is it correct that {} instance of map(*) is now expected to raise a parsing error?

Yes. We could try and tweak it so that operators are permitted after a bare-brace constructor but not before, but I'm not happy with the asymmetry.

I observed that the change also affects XQuery Update and Full Text

Yes. I'm not sure what to do about this. It's not specific to this issue. Ideally we would publish revisions of the UPD and FT grammars to show how they integrate with the XQ4 grammar, but it's a significant task.

ChristianGruen commented 2 months ago

but it's a significant task.

I guess so. @rhdunn will probably be the first to be confronted with the challenge in practice ;·)