doc/ref/spec.md: write out spec for aliases and optional field sets
This is really to complete the previous CL.
I'll probably merge them before submitting.
Change-Id: If818d60b823bd0a61aa7569b8e19a2daafdbb350
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3821
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/doc/ref/spec.md b/doc/ref/spec.md
index a748a57..4666355 100644
--- a/doc/ref/spec.md
+++ b/doc/ref/spec.md
@@ -1024,8 +1024,8 @@
the same label, the result of which is a single field with the same properties
as defined as the unification of two fields resulting from unifying two structs.
-These examples illustrate required fields only. Examples with
-optional fields follow below.
+These examples illustrate required fields only.
+Examples with optional fields follow below.
```
Expression Result (without optional fields)
@@ -1040,9 +1040,19 @@
{a: 1} & {a: 2} _|_
```
-Syntactically, the labels of optional fields are followed by a
-question mark `?`.
+Optional labels are defined in sets with an expression to select all
+labels to which to apply a given constraint.
+Syntactically, the label of an optional field set is an expression in square
+brackets indicating the matching labels.
+The value `string` matches all fields, while a concrete string matches a
+single field.
+As the latter case is common, a concrete label followed by
+a question mark `?` may be used as a shorthand.
+So `foo?: bar` is a shorthand for `["foo"]: bar`.
The question mark is not part of the field name.
+The token `...` may be used as the last declaration in a struct
+and is a shorthand for `[_]: _`.
+
Concrete field labels may be an identifier or string, the latter of which may be
interpolated.
Fields with identifier labels can be referred to within the scope they are
@@ -1062,58 +1072,8 @@
An optional expression limits this to the set of optional fields which
labels match the expression.
-->
-A Bind label, written `<identifier>`, is useful for capturing a label as a value
-and for enforcing constraints on all fields of a struct.
-In a field using a bind label, such as
-```
-{
- <id>: { name: id }
-}
-```
-the label name is bound to the identifier for the scope of the field value, so
-it can be used inside the value to denote the label.
-A bind label matches every field of its enclosing struct, so
-```
-{
- <id>: { name: id }
- a: { value: 1 }
-}
-```
-evaluates to
-```
-{
- a: { name: "a" }
- a: { value: 1 }
-}
-```
-Since identical fields in a struct unify, this is equivalent to
-```
-{
- a: {
- name: "a"
- value: 1
- }
-}
-```
-
-Because bind labels match every field in a struct, they can enforce constraints
-on all fields. The struct
-
-```
-ints: {
- <_>: int
-}
-```
-can only have integer field values:
-
-```
-ints & { a: 1 } // ok
-ints & { b: "two" } // _|_, because int & "two" == _|_.
-```
-
-The token `...` is a shorthand for `<_>: _`.
<!-- NOTE: if we allow ...Expr, as in list, it would mean something different. -->
@@ -1148,25 +1108,13 @@
additionalProperties and additionalItems.
-->
-<!-- TODO: for next round of implementation, replace ExpressionLabel with:
-ExpressionLabel = BindLabel | [ BindLabel ] "[" [ Expression ] "]" .
--->
-
-<!-- TODO: strongly consider relaxing an embedding to be an Expression, instead
-of Operand. This will tie in with using dots instead of spaces on the LHS,
-comprehensions and the ability to generate good error messages, so thread
-carefully.
--->
```
StructLit = "{" { Declaration "," } [ "..." ] "}" .
-Declaration = Field | Alias | Comprehension | Embedding .
-Embedding = Expression .
-Field = Label { Label } Expression .
-Alias = identifier "=" Expression .
-
-Label = [ identifier "=" ] LabelPath ( ":" | "::" ) .
-LabelPath = LabelExpr [ "?" ] | [ LabelExpr ] Filters .
-LabelExpr = identifier | simple_string_lit .
+Declaration = Field | Comprehension | AliasExpr .
+Field = LabelSpec { LabelSpec } Expression .
+LabelSpec = Label ( ":" | "::" ) .
+Label = LabelName [ "?" ] | "[" AliasExpr "]".
+LabelName = identifier | simple_string_lit .
attribute = "@" identifier "(" attr_elems ")" .
attr_elems = attr_elem { "," attr_elem }
@@ -1177,6 +1125,9 @@
attr_char = /* an arbitrary Unicode code point except newline, ',', '"', `'`, '#', '=', '(', and ')' */ .
```
+<!--
+ TODO: Label = LabelName [ "?" ] | "[" AliasExpr "]" | "(" AliasExpr ")"
+-->
```
Expression Result (without optional fields)
@@ -1190,7 +1141,30 @@
g: a & { foo?: number } {}
h: b & { foo?: number } _|_
i: c & { foo: string } { foo: "bar" }
+
+intMap: [string]: int
+intMap: {
+ t1: 43
+ t2: 2.4 // error: 2.4 is not an integer
+}
+
+nameMap: [string]: {
+ firstName: string
+ nickName: *firstName | string
+}
+
+nameMap: hank: { firstName: "Hank" }
```
+The optional field set defined by `nameMap` matches every field,
+in this case just `hank`, and unifies the associated constraint
+with the matched field, resulting in:
+```
+nameMap: hank: {
+ firstName: "Hank"
+ nickName: "Hank"
+}
+```
+
#### Closed structs
@@ -1249,7 +1223,7 @@
} // _|_ feild1 not defined for A
C: close({
- <_>: _
+ [_]: _
})
C2: C & {
@@ -1445,13 +1419,31 @@
#### Aliases
-In addition to fields, a struct literal may also define aliases.
Aliases name values that can be referred to
-within the [scope](#declarations-and-scopes) of their
-definition, but are not part of the struct: aliases are irrelevant to
-the partial ordering of values and are not emitted as part of any
-generated data.
-The name of an alias must be unique within the struct literal.
+within the [scope](#declarations-and-scopes) in which they are declared.
+The name of an alias must be unique within its scope.
+
+```
+AliasExpr = identifier "=" Expression | Expression .
+```
+
+Aliases can appear in several positions:
+
+As a declaration in a struct (`X=expr`):
+
+- binds the value to an identifier without including it in the struct.
+
+In front of a Label (`X=label: value`):
+
+- binds the identifier to the same value as `label` would be bound
+ to if it were a valid identifier.
+- for optional fields (`foo?: bar` and `[foo]: bar`),
+ the bound identifier is only visible within the field value (`value`).
+
+Inside a bracketed label (`[X=expr]: value`):
+
+- binds the identifier to the the concrete label that matches `expr`
+ within the instances of the field value (`value`).
<!-- TODO: explain the difference between aliases and definitions.
Now that you have definitions, are aliases really necessary?
@@ -1459,28 +1451,30 @@
-->
```
-// The empty struct.
-{}
+// An alias declaration
+Alias = 3
+a: Alias // 3
-// A struct with 3 fields and 1 alias.
-{
- alias = 3
+// A field alias
+foo: X // 4
+X="not an identifier": 4
- foo: 2
- bar: "a string"
-
- "not an ident": 4
-}
+// A label alias
+[Y=string]: { name: Y }
+foo: { value: 1 } // outputs: foo: { name: "foo", value: 1 }
```
+<!-- TODO: also allow aliases as lists -->
+
+
#### Shorthand notation for nested structs
A field whose value is a struct with a single field may be written as
-a sequence of the two field names,
+a colon-separated sequence of the two field names,
followed by a colon and the value of that single field.
```
-job myTask replicas: 2
+job: myTask: replicas: 2
```
expands to
```
@@ -1550,9 +1544,6 @@
ListLit = "[" [ ElementList [ "," [ "..." [ Expression ] ] ] "]" .
ElementList = Expression { "," Expression } .
```
-<!---
-KeyedElement = Element .
---->
Lists can be thought of as structs:
@@ -1866,21 +1857,26 @@
PrimaryExpr =
Operand |
PrimaryExpr Selector |
- PrimaryExpr Query |
PrimaryExpr Index |
PrimaryExpr Slice |
PrimaryExpr Arguments .
Selector = "." (identifier | simple_string_lit) .
-Query = "." Filters .
-Filters = Filter { Filter } .
-Filter = "[" [ "?" [ ":" ] ] Expression "]" .
Index = "[" Expression "]" .
-Slice = "[" [ Expression ] ":" [ Expression ] [ ":" [Expression] ] "]" .
Argument = Expression .
Arguments = "(" [ ( Argument { "," Argument } ) [ "," ] ] ")" .
```
<!---
+TODO:
+ PrimaryExpr Query |
+Query = "." Filters .
+Filters = Filter { Filter } .
+Filter = "[" [ "?" ] AliasExpr "]" .
+
+TODO: maybe reintroduce slices, as they are useful in queries, probably this
+time with Python semantics.
+Slice = "[" [ Expression ] ":" [ Expression ] [ ":" [Expression] ] "]" .
+
Argument = Expression | ( identifer ":" Expression ).
// & expression type