Open chharvey opened 3 years ago
Alternate suggestion for spread syntax: .*
.
A new suggestion for the spread symbol .*
has the familiarity of the bash-like glob selector. x.*
has the same precedence as the dot-accessor (#25), but it’s not technically an accessor; it stands for “select all the elements in x
”. For example, the entries of [!x.*, y ^ z.*, x.*.0, z.* ^ y]
are all ill-formed. The .*
syntax must only be used on compound expressions (awaits, property accesses, and function calls), and it must be the last “operation” in a collection’s entry. The entries of [(!x).*, (y ^ z).*, x.0.*, 42 . *]
are all well-formed (even if type-invalid).
This proposal suggests using the same symbol .*
for all 3 kinds of spreads: tuples, records, and mappings. With the current grammar, this would make an expression such as [x.*, y.*, z.*]
syntactically ambiguous — Is it a tuple literal, record literal, or mapping literal? Therefore we need a new syntax production for a literal containing only spread items (whose compile-time type could not be determined by syntax), and then modify the tuple, record, and mapping literal productions such that they must, respectively, contain at least 1 expression, property, or case.
let cde: [str, str, str] = [c, d, e]; % assume these variables are strings
let abcdefg = [str, str, str, str, str, str, str] = [a, b, cde.*, f, g];
abcdefg == [a, b, c, d, e, f, g];
type Car = [
make: str,
model: str,
color: str,
year: int,
];
let my_car: Car = [
make= 'Mazda',
model= 'Protegé',
color= 'black',
year= 2003,
];
let new_car: Car = [
color= 'red',
my_car.*,
year= 2010,
];
new_car == [
make= 'Mazda',
model= 'Protegé',
color= 'black',
year= 2010,
];
let office: obj = [
jim |-> pam,
dwight |-> angela,
andy |-> erin,
];
let parks: obj = [
leslie |-> ben,
ann |-> chris,
april |-> andy,
];
[office.*, parks.*] == [
jim |-> pam,
dwight |-> angela,
andy |-> erin,
leslie |-> ben,
ann |-> chris,
april |-> andy,
];
let failure: [str, str, str, str, str] = [a, [c, d, e].*]; %> TypeError (count 4 not assignable to count 5)
let failure: [str, str, str] = [a, [c, d, e].*]; %> TypeError (count 4 not assignable to count 3)
let failure: [a: int, b: bool, c: str] = [a= 42, [b= 'bool', c= true].*]; %> TypeError: `true` not assignable to `str`
let failure: [a: int, b: bool, c: str] = [a= 42, [b= true, d= 'string'].*]; %> TypeError: missing property `c`
[1, [b= 2, c= 3].*]; %> TypeError: record spread into tuple
[[b= 2, c= 3].*, 1]; %> TypeError: record spread into tuple
[1, ['b' |-> 2, 'c' |-> 3].*]; %> TypeError: mapping spread into tuple
[['b' |-> 2, 'c' |-> 3].*, 1]; %> TypeError: mapping spread into tuple
[a= 1, [2, 3].*]; %> TypeError: tuple spread into record
[[2, 3].*, a= 1]; %> TypeError: tuple spread into record
[a= 1, ['b' |-> 2, 'c' |-> 3].*]; %> TypeError: mapping spread into record
[['b' |-> 2, 'c' |-> 3].*, a= 1]; %> TypeError: mapping spread into record
['a' |-> 1, [2, 3].*]; %> TypeError: tuple spread into mapping
[[2, 3].*, 'a' |-> 1]; %> TypeError: tuple spread into mapping
['a' |-> 1, [b= 2, c= 3].*]; %> TypeError: record spread into mapping
[[b= 2, c= 3].*, 'a' |-> 1]; %> TypeError: record spread into mapping
[[b= 2, c= 3].*, [1].*]; %> TypeError: tuple spread into record
[['b' |-> 2, 'c' |-> 3].*, [1].*]; %> TypeError: tuple spread into mapping
[[2, 3].*, [a= 1].*]; %> TypeError: record spread into tuple
[['b' |-> 2, 'c' |-> 3].*, [a= 1].*]; %> TypeError: record spread into mapping
[[2, 3].*, ['a' |-> 1].*]; %> TypeError: mapping spread spread into tuple
[[b= 2, c= 3].*, ['a' |-> 1].*]; %> TypeError: mapping spread spread into record
+Spread
+ ::= ExpressionCompound "." "*";
-ListLiteral ::= "[" ","? Expression# ","? "]";
-RecordLiteral ::= "[" ","? Property# ","? "]";
-MappingLiteral ::= "[" ","? Case# ","? "]";
+CollectionSpread ::= "[" ","? Spread# ","? "]";
+ListLiteral ::= "[" ","? (Spread# ",")? Expression ("," (Spread | Expression)#)? ","? "]";
+RecordLiteral ::= "[" ","? (Spread# ",")? Property ("," (Spread | Property)#)? ","? "]";
+MappingLiteral ::= "[" ","? (Spread# ",")? Case ("," (Spread | Case)#)? ","? "]";
ExpressionUnit ::=
| "[" "]"
| IDENTIFIER
| PrimitiveLiteral
| StringTemplate
+ | CollectionSpread
| ListLiteral
| RecordLiteral
| MappingLiteral
| "(" Expression ")"
;
ExpressionCompound ::=
| ExpressionUnit
| ExpressionCompound PropertyAccess
;
+SemanticSpread
+ ::= Expression;
SemanticEmptyCollection ::= ();
-SemanticList ::= SemanticExpression+;
-SemanticRecord ::= SemanticProperty+;
-SemanticMapping ::= SemanticCase+;
+SemanticSpreadCollection ::= SemanticSpread+;
+SemanticList ::= SemanticSpread* SemanticExpression (SemanticSpread | SemanticExpression)*;
+SemanticRecord ::= SemanticSpread* SemanticProperty (SemanticSpread | SemanticProperty)*;
+SemanticMapping ::= SemanticSpread* SemanticCase (SemanticSpread | SemanticCase)*;
+Decorate(Spread ::= ExpressionCompound "." "*") -> SemanticSpread
+ := (SemanticSpread Decorate(ExpressionCompound));
+Decorate(CollectionSpread ::= "[" ","? CollectionSpread__0__List ","? "]") -> SemanticSpreadCollection
+ := (SemanticSpreadCollection
+ ...Decorate(CollectionSpread__0__List)
+ );
+ Decorate(CollectionSpread__0__List ::= Spread) -> Tuple<SemanticSpread>
+ := [Decorate(Spread)];
+ Decorate(CollectionSpread__0__List ::= CollectionSpread__0__List "," Spread) -> Sequence<SemanticSpread>
+ := [
+ ...Decorate(CollectionSpread__0__List),
+ Decorate(Spread),
+ ];
-Decorate(ListLiteral ::= "[" ","? ListLiteral__0__List ","? "]") -> SemanticList
- := (SemanticList
- ...Decorate(ListLiteral__0__List)
- );
+Decorate(ListLiteral ::= "[" ","? Expression ","? "]") -> SemanticList
+ := (SemanticList Decorate(Expression));
+Decorate(ListLiteral ::= "[" ","? Expression "," ListLiteral__0__List ","? "]") -> SemanticList
+ := (SemanticList
+ Decorate(Expression)
+ ...Decorate(ListLiteral__0__List)
+ );
+Decorate(ListLiteral ::= "[" ","? CollectionSpread__0__List "," Expression ","? "]") -> SemanticList
+ := (SemanticList
+ ...Decorate(CollectionSpread__0__List)
+ Decorate(Expression)
+ );
+Decorate(ListLiteral ::= "[" ","? CollectionSpread__0__List "," Expression "," ListLiteral__0__List ","? "]") -> SemanticList
+ := (SemanticList
+ ...Decorate(CollectionSpread__0__List)
+ Decorate(Expression)
+ ...Decorate(ListLiteral__0__List)
+ );
Decorate(ListLiteral__0__List ::= Expression) -> Tuple<SemanticExpression>
:= [Decorate(Expression)];
+ Decorate(ListLiteral__0__List ::= Spread) -> Tuple<SemanticSpread>
+ := [Decorate(Spread)];
- Decorate(ListLiteral__0__List ::= ListLiteral__0__List "," Expression) -> Sequence<SemanticExpression>
+ Decorate(ListLiteral__0__List ::= ListLiteral__0__List "," Expression) -> Sequence<SemanticExpression | SemanticSpread>
:= [
...Decorate(ListLiteral__0__List),
Decorate(Expression),
];
+ Decorate(ListLiteral__0__List ::= ListLiteral__0__List "," Spread) -> Sequence<SemanticExpression | SemanticSpread>
+ := [
+ ...Decorate(ListLiteral__0__List),
+ Decorate(Spread),
+ ];
-Decorate(RecordLiteral ::= "[" ","? RecordLiteral__0__List ","? "]") -> SemanticRecord
- := (SemanticRecord
- ...Decorate(RecordLiteral__0__List)
- );
+Decorate(RecordLiteral ::= "[" ","? Property ","? "]") -> SemanticRecord
+ := (SemanticRecord Decorate(Property));
+Decorate(RecordLiteral ::= "[" ","? Property "," RecordLiteral__0__List ","? "]") -> SemanticRecord
+ := (SemanticRecord
+ Decorate(Property)
+ ...Decorate(RecordLiteral__0__List)
+ );
+Decorate(RecordLiteral ::= "[" ","? CollectionSpread__0__List "," Property ","? "]") -> SemanticRecord
+ := (SemanticRecord
+ ...Decorate(CollectionSpread__0__List)
+ Decorate(Property)
+ );
+Decorate(RecordLiteral ::= "[" ","? CollectionSpread__0__List "," Property "," RecordLiteral__0__List ","? "]") -> SemanticRecord
+ := (SemanticRecord
+ ...Decorate(CollectionSpread__0__List)
+ Decorate(Property)
+ ...Decorate(RecordLiteral__0__List)
+ );
Decorate(RecordLiteral__0__List ::= Property) -> Tuple<SemanticProperty>
:= [Decorate(Property)];
+ Decorate(RecordLiteral__0__List ::= Spread) -> Tuple<SemanticSpread>
+ := [Decorate(Spread)];
- Decorate(RecordLiteral__0__List ::= RecordLiteral__0__List "," Property) -> Sequence<SemanticProperty>
+ Decorate(RecordLiteral__0__List ::= RecordLiteral__0__List "," Property) -> Sequence<SemanticProperty | SemanticSpread>
:= [
...Decorate(RecordLiteral__0__List),
Decorate(Property),
];
+ Decorate(RecordLiteral__0__List ::= RecordLiteral__0__List "," Spread) -> Sequence<SemanticProperty | SemanticSpread>
+ := [
+ ...Decorate(RecordLiteral__0__List),
+ Decorate(Spread),
+ ];
-Decorate(MappingLiteral ::= "[" ","? MappingLiteral__0__List ","? "]") -> SemanticMapping
- :=(SemanticMapping
- ...Decorate(MappingLiteral__0__List)
- );
+Decorate(MappingLiteral ::= "[" ","? Case ","? "]") -> SemanticMapping
+ := (SemanticMapping Decorate(Case));
+Decorate(MappingLiteral ::= "[" ","? Case "," MappingLiteral__0__List ","? "]") -> SemanticMapping
+ := (SemanticMapping
+ Decorate(Case)
+ ...Decorate(MappingLiteral__0__List)
+ );
+Decorate(MappingLiteral ::= "[" ","? CollectionSpread__0__List "," Case ","? "]") -> SemanticMapping
+ := (SemanticMapping
+ ...Decorate(CollectionSpread__0__List)
+ Decorate(Case)
+ );
+Decorate(MappingLiteral ::= "[" ","? CollectionSpread__0__List "," Case "," MappingLiteral__0__List ","? "]") -> SemanticMapping
+ := (SemanticMapping
+ ...Decorate(CollectionSpread__0__List)
+ Decorate(Case)
+ ...Decorate(MappingLiteral__0__List)
+ );
Decorate(MappingLiteral__0__List ::= Case) -> Tuple<SemanticCase>
:= [Decorate(Case)];
+ Decorate(MappingLiteral__0__List ::= Spread) -> Tuple<SemanticSpread>
+ := [Decorate(Spread)];
- Decorate(MappingLiteral__0__List ::= MappingLiteral__0__List "," Case) -> Sequence<SemanticCase>
+ Decorate(MappingLiteral__0__List ::= MappingLiteral__0__List "," Case) -> Sequence<SemanticCase | SemanticSpread>
:= [
...Decorate(MappingLiteral__0__List),
Decorate(Case),
];
+ Decorate(MappingLiteral__0__List ::= MappingLiteral__0__List "," Spread) -> Sequence<SemanticCase | SemanticSpread>
+ := [
+ ...Decorate(MappingLiteral__0__List),
+ Decorate(Spread),
+ ];
let tup: [int, int, int] = [2, 3, 4];
[1, tup.0, tup.1, tup.2, 5]; % [1, tup.*, 5]
Spread syntax is a way of injecting collection entries into other collections.
Discussion
Fixed-Size Spread
Tuple Spread
Tuples may be spread into other tuple literals, wherin the items of the first tuple are injected into the second tuple, in order, where the spread syntax is.
The symbol
#
is called the single-spread symbol. It’s technically not an operator, since it doesn’t produce an expression, and it doesn’t have the same precedence as unary operators. For example,[#a || b, c]
is interpreted as[#(a || b), c]
.Tuples may also be spread into set literals.
Record Spread
Records can be spread into other records, but since records have keys, the rules for record spreading is stricter than for tuple spreading. Record spreading uses the symbol
##
, and duplicate record keys are overwritten in source order. The symbol##
is called the double-spread symbol (again, not an actual operator).Notice that
new_car.color == 'black'
butnew_car.year == 2010
. This is because the spread##my_car
overwrote thecolor = 'red'
, but the new year overwrote the year inmy_car
. Properties later in source order have higher precedence. When multiple records are spread, properties cascde over each other in source order.Variable-Size Spread
List Spread
Lists can be spread into sets only.
Since there is not yet a literal construction syntax for lists, lists cannot be spread into lists.
Hash Spread
Likewise, we cannot yet spread hashes into hashes.
Set Spread
Sets can be spread into other sets.
Sets cannot yet be spread into lists, since there is not yet a list literal syntax.
Map Spread
Maps can be spread into one another with the triple-spread symbol
###
.Maps overwrite cases in the same way that records overwrite properties.
Errors
We get syntax errors by using the wrong arity of spread inside the wrong syntaxes…
and we get type errors by using the wrong arity of spread on the wrong types:
Since the counts of tuples are static (known at compile-time), we get type errors where counts don’t match up.
Similarly for records, since record keys are static.
Specification
Lexicon
TokenWorth
Syntax
Semantics
Decorate
Workaround
If this feature is not implemented, a sufficient workround is to manually list out the entries.