cue/ast: split off Ellipsis from ListLit
- allows reuse of Ellipsis for StructLit
(needed for closed lits)
- allows relaxing positioning of ellipsis in
the future (may be useful for structs and
associative lists)
- simplifies code in general
Change-Id: I18caac6677b6178a9fe4c3db609046ff76eb7b10
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2863
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/trim.go b/cmd/cue/cmd/trim.go
index b19891d..cc79744 100644
--- a/cmd/cue/cmd/trim.go
+++ b/cmd/cue/cmd/trim.go
@@ -208,8 +208,9 @@
}
case *ast.ListLit:
- if x.Type != nil {
- t.markAlwaysGen(x.Type, false)
+ _, e := internal.ListEllipsis(x)
+ if e != nil && e.Type != nil {
+ t.markAlwaysGen(e.Type, false)
}
case *ast.Field:
diff --git a/cue/ast.go b/cue/ast.go
index eb2403d..8e7f5bc 100644
--- a/cue/ast.go
+++ b/cue/ast.go
@@ -248,22 +248,28 @@
object: v.object,
parent: v,
}
+
+ elts, ellipsis := internal.ListEllipsis(n)
+
arcs := []arc{}
- for i, e := range n.Elts {
+ for i, e := range elts {
v1.sel = strconv.Itoa(i)
arcs = append(arcs, arc{feature: label(i), v: v1.walk(e)})
}
s := &structLit{baseValue: newExpr(n), arcs: arcs}
list := &list{baseValue: newExpr(n), elem: s}
list.initLit()
- if n.Ellipsis != token.NoPos || n.Type != nil {
+ if ellipsis != nil {
list.len = newBound(v.ctx(), list.baseValue, opGeq, intKind, list.len)
- if n.Type != nil {
- list.typ = v1.walk(n.Type)
+ if ellipsis.Type != nil {
+ list.typ = v1.walk(ellipsis.Type)
}
}
ret = list
+ case *ast.Ellipsis:
+ return v.errf(n, "ellipsis (...) only allowed at end of list")
+
case *ast.ComprehensionDecl:
yielder := &yield{baseValue: newExpr(n.Field.Value)}
fc := &fieldComprehension{
diff --git a/cue/ast/ast.go b/cue/ast/ast.go
index 3bbc5a6..a36c1d1 100644
--- a/cue/ast/ast.go
+++ b/cue/ast/ast.go
@@ -61,6 +61,7 @@
func (*Interpolation) exprNode() {}
func (*StructLit) exprNode() {}
func (*ListLit) exprNode() {}
+func (*Ellipsis) exprNode() {}
// func (*StructComprehension) exprNode() {}
func (*ListComprehension) exprNode() {}
@@ -392,11 +393,15 @@
// A ListLit node represents a literal list.
type ListLit struct {
comments
- Lbrack token.Pos // position of "["
- Elts []Expr // list of composite elements; or nil
+ Lbrack token.Pos // position of "["
+ Elts []Expr // list of composite elements; or nil
+ Rbrack token.Pos // position of "]"
+}
+
+type Ellipsis struct {
+ comments
Ellipsis token.Pos // open list if set
Type Expr // type for the remaining elements
- Rbrack token.Pos // position of "]"
}
// A ListComprehension node represents as list comprehension.
@@ -501,6 +506,7 @@
}
func (x *ListLit) Pos() token.Pos { return x.Lbrack }
+func (x *Ellipsis) Pos() token.Pos { return x.Ellipsis }
func (x *ListComprehension) Pos() token.Pos { return x.Lbrack }
func (x *ForClause) Pos() token.Pos { return x.For }
func (x *IfClause) Pos() token.Pos { return x.If }
@@ -527,7 +533,13 @@
}
return x.Rbrace.Add(1)
}
-func (x *ListLit) End() token.Pos { return x.Rbrack.Add(1) }
+func (x *ListLit) End() token.Pos { return x.Rbrack.Add(1) }
+func (x *Ellipsis) End() token.Pos {
+ if x.Type != nil {
+ return x.Type.End()
+ }
+ return x.Ellipsis.Add(3) // len("...")
+}
func (x *ListComprehension) End() token.Pos { return x.Rbrack }
func (x *ForClause) End() token.Pos { return x.Source.End() }
func (x *IfClause) End() token.Pos { return x.Condition.End() }
diff --git a/cue/ast/walk.go b/cue/ast/walk.go
index 49e6acd..89e93a6 100644
--- a/cue/ast/walk.go
+++ b/cue/ast/walk.go
@@ -110,6 +110,8 @@
case *ListLit:
walkExprList(v, n.Elts)
+
+ case *Ellipsis:
if n.Type != nil {
walk(v, n.Type)
}
diff --git a/cue/export.go b/cue/export.go
index bb9969c..3e804a3 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -563,7 +563,7 @@
open = true
}
if !ok || ln > len(x.elem.arcs) {
- list.Type = p.expr(x.typ)
+ list.Elts = append(list.Elts, &ast.Ellipsis{Type: p.expr(x.typ)})
if !open && !isTop(x.typ) {
expr = &ast.BinaryExpr{
X: &ast.BinaryExpr{
diff --git a/cue/format/node.go b/cue/format/node.go
index 2ab6395..32076ad 100644
--- a/cue/format/node.go
+++ b/cue/format/node.go
@@ -457,19 +457,17 @@
case *ast.ListLit:
f.print(x.Lbrack, token.LBRACK, indent)
f.walkExprList(x.Elts, 1)
- if x.Ellipsis != token.NoPos || x.Type != nil {
- f.print(x.Ellipsis, token.ELLIPSIS)
- if x.Type != nil && !isTop(x.Type) {
- f.expr(x.Type)
- }
- } else {
- f.print(trailcomma, noblank)
- f.current.pos += 2
- f.visitComments(f.current.pos)
- }
+ f.print(trailcomma, noblank)
+ f.visitComments(f.current.pos)
f.matchUnindent()
f.print(noblank, x.Rbrack, token.RBRACK)
+ case *ast.Ellipsis:
+ f.print(x.Ellipsis, token.ELLIPSIS)
+ if x.Type != nil && !isTop(x.Type) {
+ f.expr(x.Type)
+ }
+
case *ast.ListComprehension:
f.print(x.Lbrack, token.LBRACK, blank, indent)
f.expr(x.Expr)
diff --git a/cue/format/testdata/expressions.golden b/cue/format/testdata/expressions.golden
index be7b001..65aa717 100644
--- a/cue/format/testdata/expressions.golden
+++ b/cue/format/testdata/expressions.golden
@@ -98,6 +98,8 @@
e: 'aa \(aaa) aa'
e: "aa \(aaa)"
+ e: [1, 2,
+ ]
e: [1, 2]
e: [1, 2, 3, 4,
5, 6, 7, 8]
@@ -107,7 +109,7 @@
e: [...]
e: [
...]
- e: [...
+ e: [...,
]
e: [1, 2, ...]
e: [1, 2,
diff --git a/cue/format/testdata/expressions.input b/cue/format/testdata/expressions.input
index f8b61d3..1d656ea 100644
--- a/cue/format/testdata/expressions.input
+++ b/cue/format/testdata/expressions.input
@@ -98,6 +98,8 @@
e: 'aa \(aaa) aa'
e: "aa \(aaa)"
+ e: [1, 2
+ ]
e: [1, 2]
e: [1, 2, 3, 4,
5, 6, 7, 8]
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index 7b1f78b..c331dc5 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -960,13 +960,14 @@
}
}
- ellipsis := token.NoPos
- typ := ast.Expr(nil)
if p.tok == token.ELLIPSIS {
- ellipsis = p.pos
+ ellipsis := &ast.Ellipsis{
+ Ellipsis: p.pos,
+ }
+ elts = append(elts, ellipsis)
p.next()
if p.tok != token.COMMA && p.tok != token.RBRACK {
- typ = p.parseRHS()
+ ellipsis.Type = p.parseRHS()
}
if p.atComma("list literal", token.RBRACK) {
p.next()
@@ -975,11 +976,9 @@
rbrack := p.expectClosing(token.RBRACK, "list literal")
return &ast.ListLit{
- Lbrack: lbrack,
- Elts: elts,
- Ellipsis: ellipsis,
- Type: typ,
- Rbrack: rbrack}
+ Lbrack: lbrack,
+ Elts: elts,
+ Rbrack: rbrack}
}
func (p *parser) parseListElements() (list []ast.Expr) {
diff --git a/cue/parser/print.go b/cue/parser/print.go
index 40a85cf..2a91d64 100644
--- a/cue/parser/print.go
+++ b/cue/parser/print.go
@@ -97,18 +97,16 @@
case *ast.ListLit:
out := "["
out += debugStr(v.Elts)
- if v.Ellipsis != token.NoPos || v.Type != nil {
- if out != "[" {
- out += ", "
- }
- out += "..."
- if v.Type != nil {
- out += debugStr(v.Type)
- }
- }
out += "]"
return out
+ case *ast.Ellipsis:
+ out := "..."
+ if v.Type != nil {
+ out += debugStr(v.Type)
+ }
+ return out
+
case *ast.ListComprehension:
out := "["
out += debugStr(v.Expr)
diff --git a/cue/parser/walk.go b/cue/parser/walk.go
index 82f88c6..eebe315 100644
--- a/cue/parser/walk.go
+++ b/cue/parser/walk.go
@@ -105,6 +105,8 @@
case *ast.ListLit:
walkExprList(v, n.Elts)
+
+ case *ast.Ellipsis:
if n.Type != nil {
walk(v, n.Type)
}
diff --git a/encoding/protobuf/parse.go b/encoding/protobuf/parse.go
index 9647df4..4df5977 100644
--- a/encoding/protobuf/parse.go
+++ b/encoding/protobuf/parse.go
@@ -509,9 +509,8 @@
if x.Repeated {
f.Value = &ast.ListLit{
- Lbrack: p.toCUEPos(x.Position),
- Ellipsis: token.NoSpace.Pos(),
- Type: f.Value,
+ Lbrack: p.toCUEPos(x.Position),
+ Elts: []ast.Expr{&ast.Ellipsis{Type: f.Value}},
}
}
diff --git a/internal/internal.go b/internal/internal.go
index 36cacc8..fa417ab 100644
--- a/internal/internal.go
+++ b/internal/internal.go
@@ -19,7 +19,10 @@
// TODO: refactor packages as to make this package unnecessary.
-import "github.com/cockroachdb/apd/v2"
+import (
+ "cuelang.org/go/cue/ast"
+ "github.com/cockroachdb/apd/v2"
+)
// A Decimal is an arbitrary-precision binary-coded decimal number.
//
@@ -64,3 +67,18 @@
// if it does not, and returns a forked runtime that will discard additional
// keys.
var CheckAndForkRuntime func(runtime, value interface{}) interface{}
+
+// ListEllipsis reports the list type and remaining elements of a list. If we
+// ever relax the usage of ellipsis, this function will likely change. Using
+// this function will ensure keeping correct behavior or causing a compiler
+// failure.
+func ListEllipsis(n *ast.ListLit) (elts []ast.Expr, e *ast.Ellipsis) {
+ elts = n.Elts
+ if n := len(elts); n > 0 {
+ var ok bool
+ if e, ok = elts[n-1].(*ast.Ellipsis); ok {
+ elts = elts[:n-1]
+ }
+ }
+ return elts, e
+}