Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow parenthesized element expressions. #780

Open
lrhn opened this issue Jan 15, 2020 · 6 comments
Open

Allow parenthesized element expressions. #780

lrhn opened this issue Jan 15, 2020 · 6 comments
Labels
request Requests to resolve a particular developer problem small-feature A small feature which is relatively cheap to implement.

Comments

@lrhn
Copy link
Member

lrhn commented Jan 15, 2020

Currently list literal element expressions are either if ... or for ... or plain expressions. We do not allow parentheses around an if ... element, so the following:

var l = [ if (cond1) (if (cond2) 0) else if (cond3) 1 ];

is not allowed. That is inconvenient because we also do not have a "no element" element that is briefer than ...[], so the way to write the above logic today is:

var l = [ if (cond1) if (cond2) 0 else ...[] else if (cond3) 1 ];

We should consider allowing parentheses around all element expressions. The only place it actually matters is delimiting an if with no else, but that is an annoying surprise in the current grammar.

The change would be adding the following elements production:

<element> ::=  `(' <element> `)' | ... 

which is ambiguous with parenthesized expressions, but since any ambiguous element/expression means the same thing whether it's an element or an expression, it doesn't matter how we resolve the ambiguity.

Optionally, also allow multiple values, comma separated, inside an "elements parenthesis":

var l = [if (cond1) (value1, value2, value3)];

which is currently not something we can write shorter than:

var l = [if (cond1) ...[value1, value2, value3]];

The spread-literal does work, so it's not strictly necessary, but could be convenient. Effectively we would instead change the grammar to:

<element> ::= `(' <elements> `)' | ...

(where the <elements> production allows trailing commas).
That grammar is no more ambiguous than the one above, any presence of a comma will definitely disambiguate towards the element production.

(Edit: Added multiple-values option and grammar).

@lrhn lrhn added the request Requests to resolve a particular developer problem label Jan 15, 2020
@munificent
Copy link
Member

That's an interesting edge case I hadn't considered. I think we could probably allow this.

so the way to write the above logic today is:

var l = [ if (cond1) if (cond2) 0 else ...[] else if (cond3) 1 ];

The other, simpler, option is:

var l = [ if (cond1) ...[if (cond2) 0] else if (cond3) 1 ];

Basically, ...[ and ] are the grouping characters for elements.

@eernstg
Copy link
Member

eernstg commented Jan 16, 2020

It should just work: the following rule can be used in Dart.g without breaking anything in language_2 (language looks good as well, but I haven't tracked exactly which tests are failing because //# something: syntax-error has been replaced by something that the test runner doesn't recognize as a syntax error for the spec_parser):

element
    : expressionElement
    | mapEntry
    | spreadElement
    | ifElement
    | forElement
    | '(' element ')'
    ;

@munificent
Copy link
Member

munificent commented Jan 16, 2020

It should just work: the following rule can be used in Dart.g without breaking anything in language_2

Good to know.

I'm also slightly worried about it preventing other future syntax changes. For example, I could imagine wanting to support (for (var x = 0; x < 100; x++) x) expression syntax as a way to create a lazy Iterable, similar to generator expressions in Python.

@lrhn lrhn added the small-feature A small feature which is relatively cheap to implement. label Jul 8, 2020
@lrhn
Copy link
Member Author

lrhn commented Mar 1, 2021

Why not:

var l = [ if (cond1) { if (cond2) 0 } else if (cond3) 1 ];

Because { if (cond2) 0 } is a set literal. They're "rare" (for now), but ambiguities tend to cost, both in implementation and user mental model, because they are exceptions to rules (so now you need to remember both the rule and the exception and when it applies, instead of just the rule).

Parentheses are usually used for grouping expressions and braces for statements. "Collection elements" feels closer to expressions than statements - they introduce something into the place where they occur.

@lrhn
Copy link
Member Author

lrhn commented Mar 1, 2021

Wrong?

We like braces for grouping statements. We like parentheses for grouping sub-expressions. We don't generally group multiple expressions, those mainly exist lists (parameter lists, collection literals), and they are comma separated instead and the braces are delimiters, not grouping/precedence-operators.

The problem we're trying to solve here is precedence among collection elements.
Collection elements are ... something in-between statements and expressions. I think of them as closer to expressions because they have a value. Some have more than one value, but that's just the a list-monadic expression. Statements, on the other hand, do not. Even if I didn't read it as a set literal, I wouldn't think [for (var x in xs) { x }] as evaluating {x} for its value, that's not what braces read like to me. So, I'd prefer [for (var x in xs) (x)] (which already works as written, it just doesn't allow x to be replaced by a for or if element).

If the goal was to allow multiple elements inside an if or for, something different, I might actually prefer braces.
So, [ for (int i=0; i<10; i++) {i, i*3} ] could look better to me for that goal - not sure it does, neither that or using (i, i*3) are perfect, but both are reasonable choices. Using braces still has the set-literal issue, though.
For now I'll just use ...[i, i*3] instead, and hope the compiler will be smart enough to recognize that.

I know it's all "look and feel" arguments, so clearly subjective. 😞

(And, whoops, interpolations use ${...} as an expression delimiter. Probably just because they stand out more, which is also important. Something like "foo bar $(baz) qux (or fifi)" is too easy to read as the parentheses being part of the text.)

@lrhn
Copy link
Member Author

lrhn commented Mar 2, 2021

I agree that some Unix shell (or Perl, I think awk used $(...)) was likely the origin of $-interpolation. It just "looked right" to the original designers (and I don't disagree, I think it reads better than $(foo) would, because braces don't usually occur in text. Braces look awful in JSON literals, though, so maybe we should have allowed both).

C# also uses braces, just without the $ (or rather, with the $ outside the string: $"ab{foo}cd").

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request Requests to resolve a particular developer problem small-feature A small feature which is relatively cheap to implement.
Projects
None yet
Development

No branches or pull requests

3 participants