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

Consider function, if/else, and match expressions as values rather than an expression #212

Open
SheepTester opened this issue Jul 5, 2021 · 0 comments
Labels
enhancement New feature or request
Milestone

Comments

@SheepTester
Copy link
Member

In the syntax, values are generally more tautly contained, like numbers or record literals, where you can't possibly think of ripping the value up into smaller pieces. Expressions, however, usually consist of other expressions that could be "ripped up"; for example, 3 + 3 in some contexts (like next to a multiplication * operator) can be rebracketed if no parentheses are used. Expressions can be used as a value by putting them in parentheses, which in effect keeps the expression contained.

As a rule of thumb, you need parentheses if you want to use an expression in multiplication, but you don't for a value:

let prod1 = 3 * "wow" // No parentheses needed around a string; it's a value!
let prod1 = 3 * 1 + 1 // `1 + 1` isn't a proper grouping here because multiplication binds tighter (per PEMDAS); it's an expression!

For reference, here are the expressions and values in the Python and JS implementations:

N-lang/python/syntax.lark

Lines 140 to 145 in 61bb3e4

?expression: ifelse_expr
| boolean_expression
| function_def
| anonymous_func
| function_callback_pipe
| match

N-lang/python/syntax.lark

Lines 174 to 188 in 61bb3e4

value: NUMBER
| BOOLEAN
| STRING
| NAME
| "(" expression ")"
| UNIT
| function_callback
| char
| tupleval
| listval
| recordval
| impn
| HEX
| BINARY
| OCTAL

expression -> tupleExpression {% id %}
| returnExpression {% id %}
| "imp" _ identifier {% from(ast.ImportFile) %}
| "imp" _ string {% from(ast.ImportFile) %}

value -> identifier {% id %}
| %number {% from(ast.Number) %}
| %float {% from(ast.Float) %}
| string {% id %}
| %char {% from(ast.Char) %}
| "(" _ ")" {% from(ast.Unit) %}
| "(" _ expression _ ")" {% includeBrackets %}
| ("[" _) ((noCommaExpression (_ "," _)):* noCommaExpression ((_ ","):? _)):? "]" {% from(ast.List) %}
| ("{" _) ((recordEntry blockSeparator):* recordEntry (blockSeparator | _spaces)):? "}" {% from(ast.Record) %}

(Function expressions are listed as pipeRhs in the JS impl. at the same level as booleanExpression, so it and if expressions are effectively expressions)

There's notable differences between the implementations so we'll have to write tests documenting the correct behaviour and then fix each implementation accordingly. (The Python branch is no longer the de facto implementation per #117 and might be incorrect.) That's a separate issue though!

Anyways, it would be nice if we could use function expressions, if/else expressions, and match expressions without parentheses. After all, they look fairly self-contained.

let wow = 3 * if true { 9 } else { 6 }

That can't be rebracketed as 3 * if because (3 * if) true { 9 } else { 6 } isn't valid syntax.

Parentheses are quite annoying, so it would be nice to not have to use them.

This should be a fairly simple change, just moving those expressions from expression to value. Assuming the tests are complete, they shouldn't fail more than they did before with this change.

  • For function expressions, ignoring type errors, 3 * [] -> () {} * 3 isn't syntactically ambiguous, even if the [] could be an empty list and the {} could be an empty record. -> is only used in function expressions and type annotations (and I guess match expressions?), and type annotations are fairly isolated from expressions. Thus, the -> is fairly indicative of a function expression, so there's no way to ambiguously reanalyse un-parenthesised function expressions

  • For if/else expressions, we don't allow values right next to each other with only spaces in between. For example, wow { a: 1 } sheep { b: 2 } isn't valid syntax. Even with newlines in between, because we have a limited subset of expressions that are allowed as statements, they're still invalid syntax. So, 3 * if condition { a } else { b } * 3 isn't ambiguous. Even if it got rebracketed to 3 * if and { b } * 3 (again, ignoring type errors), the middle condition { a } else is a series of values without anything in between them, so that's invalid syntax. Even with newlines, they aren't valid statements on their own:

    // Not ambiguous
    let wow = 3 * if
    condition
    { a }
    else
    { b } * 3

    Also, if and else are keywords, so they're quite certainly for an if/else.

  • Similarly for match expressions, 3 * match(value) { ... } * 3 could be seen as 3 * match(value) and { ... } * 3, but they're next to each other, so that's invalid syntax. Additionally, match is also a keyword

@SheepTester SheepTester added the enhancement New feature or request label Jul 5, 2021
@SheepTester SheepTester added this to the N Next milestone Jul 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant