Use the alternative for the design since the original couldn't be parsed without backtracking

This commit is contained in:
Vyacheslav Egorov 2021-06-09 16:25:10 +03:00
parent 4031fbd0d3
commit 6b9427008e

View file

@ -2,34 +2,83 @@
## Summary
Introduce a way to define anonymous functions using a more terse syntax.
Introduce a way to define anonymous functions using a terse syntax.
## Motivation
Current function syntax takes up a lot of space when used for short anonymous that are passed in function calls.
By introducing a shorter syntax, we make code with heavy use of anonymous functions easier to write and arguably, easier to read as well:
By introducing a shorter syntax, we make code with heavy use of anonymous functions easier to write and arguably, easier to read as well.
Existing syntax:
```lua
local a = sort(c, function(a, b) return a.x > b.x end)
local b = filter(map(c, function(a) return a * 2 end), function(a) return a > 0 end)
local b = filter(c, function(a) return a.good end)
local a = sort(c, function(a, b) return a.x > b.x end)
```
Proposed:
This proposal:
```lua
local a = sort(c, (a, b) -> a.x > b.x)
local b = filter(map(c, a -> a * 2), a -> a > 0)
```
Type annotations are supported are also supported, but with improvements to bi-directional type inference, typechecker should be able to deduce types in many cases:
```lua
local a = sort(c, (a: Bar, b: Bar) -> a.x > b.x)
local b = filter(map(c, (a: number) -> a * 2), (a: number) -> a > 0)
local c = filter(c, a -> a.good)
local a = sort(c, do(a, b) -> a.x > b.x)
```
Terse syntax for anonymous functions like this is used in various programming languages like C#, D, Java, JavaScript and Julia (ignoring `=>` vs `->`).
Type annotations are also supported, but with improvements to bi-directional type inference, typechecker should be able to deduce types in many cases:
```lua
local b = filter(map(c, do(a: number) -> a * 2), do(a: number) -> a > 0)
local c = filter(c, do(a): boolean -> a.good)
local a = sort(c, do(a: Bar, b: Bar) -> a.x > b.x)
```
## Design
Syntax of *prefixexp* is extended with additional options:
Syntax of *prefixexp* is extended with an additional option:
Before:
```
prefixexp ::= NAME | `(' expr `)'
```
After:
```
prefixexp ::= NAME [`->' subexpr] | `(' expr `)'
```
Parsing of the this new option is easy, we check a single token for `->` after the NAME.
Syntax of *simpleexp* is extended with additional option:
```
| `do` `(' [parlist] `)' [`:' ReturnType] `->' subexpr
```
We use `do` as an introduction token here to keep the grammar simple without requiring look-ahead and backtracking.
While the `->` token here is not strictly required, it is more consistent with the single argument syntax and improves readability when return type annotations are used.
---
Note that these lambda function expressions support only a single return value using the *subexpr* grammar, compared to *explist* grammar that supports multiple comma-separated expressions.
When we are done with parsing, we re-use the existing AST for function expression, but the single *subexpr* that we have is placed inside AstStatReturn.
## Drawbacks
### Single return value
In Luau, multiple return value support is a first-class citizen, but the syntax becomes ambiguous if we allow that:
```lua
f(a, b -> b.a, b.b, c) -- how many values does the b return or the f accept?
```
As a solution, regular anonymous function definition can be used when multiple return values are required.
### A `function` with extra steps
This feature introduces an unfamiliar syntax of function definition for people coming from Lua and it adds little real benefit when we already have syntax for anonymous functions.
## Alternatives
### Drop the `do` introduction token
Original version of the proposal had the following syntax extension:
Before:
```
prefixexp ::= NAME | `(' expr `)'
@ -40,56 +89,10 @@ funchead ::= `(' [parlist] `)' [`:' ReturnType]
prefixexp ::= NAME [`->' subexpr] | funchead `->' subexpr | `(' expr `)'
```
Note that an anonymous function with a single argument is allowed to have a type annotation only when it's inside `()`.
Terse syntax for anonymous functions like this is used in various programming languages like C#, D, Java, JavaScript and Julia (ignoring `=>` vs `->`).
We extend the syntax of *prefixexp* instead of *simpleexp* because we parse expressions inside parenthesis at this level.
### Parsing ``NAME [`->' subexpr]``
Parsing of the first option is easy, we check a single token for `->` after the NAME.
### Parsing ``funchead `->' subexpr``
Parsing of the function argument list is in conflict with an expression in `` `(' expr `)'``.
To resolve this issue, we are going to perform the switch to potential function arguments after we consume the common `(` token.
At that point, if the next token is a `NAME`, we use a lookahead token to check if it's one of `:`, `,` or `)`.
In case it's `:` or `,` we proceed with function argument parsing.
In case it's `)` we will consume both `NAME` and `)` and try to figure out is we had a reference to a binding or if the next token is `->` or `:` in which case that's an anonymous function and we got one argument name.
---
When we are done with parsing, we re-use the existing AST for function expression, but the single expression that we have is placed inside AstStatReturn.
## Drawbacks
Support for the proposed syntax complicates parse of *prefixexp* and nears a boundary where we almost step into backtracing.
This feature also introduces an unfamiliar syntax of function definition to Luau for little real benefit.
## Alternatives
### Different token instead of `->`
Other programming languages use `=>` token, but in Luau it is not used right now.
### Introduction token
To avoid complications with parsing, we may use an introduction token and get rid of the `->` token.
One option might be the `do` token like in Ruby. Use of existing token preserves backwards compatibility:
The problem with this option is that parsing with limited look-ahead and without backtracing becomes very complicated, and unavoidable in cases like:
```lua
local a = sort(c, do(a, b) a.x > b.x)
local b = filter(map(c, do(a) a * 2), do(a) a > 0)
```
```lua
local a = sort(c, do(a: Foo, b: Foo) a.x > b.x)
local b = filter(map(c, do(a: number) a * 2), do(a: number) a > 0)
```
Syntax of *simpleexp* is extended with a new option:
```
| `do` funchead subexpr
```
where
```
funchead ::= `(' [parlist] `)' [`:' ReturnType]
f((x):number(2))
```
Only after the return type annotation can we see that there is no `->` token and this was a member function call.