cue/parser: parse new comprehension syntax
Change-Id: I3e75bc9abb45c45b25889675b72504377c28f1e1
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3180
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/ast_test.go b/cue/ast_test.go
index 0719f1b..03cdbe4 100644
--- a/cue/ast_test.go
+++ b/cue/ast_test.go
@@ -257,7 +257,7 @@
in: `
a: int @b('' ,b) // invalid
`,
- out: "attribute missing ')':\n test:2:16\nmissing ',' in file:\n test:3:3\n<0>{}",
+ out: "attribute missing ')':\n test:2:16\nmissing ',' in struct literal:\n test:3:3\n<0>{}",
}}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
diff --git a/cue/parser/error_test.go b/cue/parser/error_test.go
index 887d855..d25dbc0 100644
--- a/cue/parser/error_test.go
+++ b/cue/parser/error_test.go
@@ -177,9 +177,11 @@
}
for _, fi := range list {
name := fi.Name()
- if !fi.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".src") {
- checkErrors(t, filepath.Join(testdata, name), nil)
- }
+ t.Run(name, func(t *testing.T) {
+ if !fi.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".src") {
+ checkErrors(t, filepath.Join(testdata, name), nil)
+ }
+ })
}
}
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index 7a6b9ef..e03341d 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -646,7 +646,13 @@
defer p.closeList()
for p.tok != token.RBRACE && p.tok != token.ELLIPSIS && p.tok != token.EOF {
- list = append(list, p.parseField())
+ switch p.tok {
+ case token.FOR, token.IF, token.LET:
+ list = append(list, p.parseComprehension())
+
+ default:
+ list = append(list, p.parseField())
+ }
}
if p.tok == token.ELLIPSIS {
@@ -656,6 +662,70 @@
return
}
+func (p *parser) parseComprehension() (decl ast.Decl) {
+ if p.trace {
+ defer un(trace(p, "Field"))
+ }
+
+ c := p.openComments()
+ defer func() { c.closeNode(p, decl) }()
+
+ tok := p.tok
+ pos := p.pos
+ clauses, fc := p.parseComprehensionClauses(true)
+ if fc != nil {
+ lab := &ast.Ident{
+ NamePos: pos,
+ Name: tok.String(),
+ }
+ opt := token.NoPos
+ tok = p.tok
+ pos = p.pos
+ if p.tok == token.OPTION {
+ opt = pos
+ p.next()
+ tok = p.tok
+ pos = p.pos
+ }
+ p.next()
+ rhs := p.parseRHS()
+
+ // Field or alias.
+ switch tok {
+ case token.BIND:
+ decl = &ast.Alias{Ident: lab, Equal: pos, Expr: rhs}
+ case token.ISA, token.COLON:
+ decl = &ast.Field{
+ Label: lab,
+ Optional: opt,
+ TokenPos: pos,
+ Token: tok,
+ Value: rhs,
+ Attrs: p.parseAttributes(),
+ }
+ default:
+ decl = &ast.BadDecl{From: lab.Pos(), To: rhs.End()}
+ }
+ fc.closeNode(p, decl)
+ if p.atComma("struct literal", token.RBRACE) { // TODO: may be EOF
+ p.next()
+ }
+
+ return decl
+ }
+
+ expr := p.parseStruct()
+
+ if p.atComma("struct literal", token.RBRACE) { // TODO: may be EOF
+ p.next()
+ }
+
+ return &ast.Comprehension{
+ Clauses: clauses,
+ Value: expr,
+ }
+}
+
func (p *parser) parseField() (decl ast.Decl) {
if p.trace {
defer un(trace(p, "Field"))
@@ -682,7 +752,7 @@
expr = p.parseExpr()
}
e := &ast.EmbedDecl{Expr: expr}
- if p.atComma("file", token.RBRACE) {
+ if p.atComma("struct literal", token.RBRACE) {
p.next()
}
return e
@@ -755,16 +825,10 @@
p.next() // : or ::
m.Value = p.parseRHS()
- p.openList()
- for p.tok == token.ATTRIBUTE {
+ if attrs := p.parseAttributes(); attrs != nil {
allowComprehension = false
- c := p.openComments()
- a := &ast.Attribute{At: p.pos, Text: p.lit}
- p.next()
- c.closeNode(p, a)
- m.Attrs = append(m.Attrs, a)
+ m.Attrs = attrs
}
- p.closeList()
decl = this
switch p.tok {
@@ -776,7 +840,7 @@
if !allowComprehension {
p.errf(p.pos, "comprehension not allowed for this field")
}
- clauses := p.parseComprehensionClauses()
+ clauses, _ := p.parseComprehensionClauses(false)
return &ast.Comprehension{
Clauses: clauses,
Value: &ast.StructLit{
@@ -792,6 +856,19 @@
return decl
}
+func (p *parser) parseAttributes() (attrs []*ast.Attribute) {
+ p.openList()
+ for p.tok == token.ATTRIBUTE {
+ c := p.openComments()
+ a := &ast.Attribute{At: p.pos, Text: p.lit}
+ p.next()
+ c.closeNode(p, a)
+ attrs = append(attrs, a)
+ }
+ p.closeList()
+ return attrs
+}
+
func (p *parser) parseLabel(f *ast.Field) (expr ast.Expr, ok bool) {
switch p.tok {
case token.IDENT:
@@ -886,17 +963,26 @@
return elts
}
-func (p *parser) parseComprehensionClauses() (clauses []ast.Clause) {
+// parseComprehensionClauses parses either new-style (first==true)
+// or old-style (first==false).
+// Should we now disallow keywords as identifiers? If not, we need to
+// return a list of discovered labels as the alternative.
+func (p *parser) parseComprehensionClauses(first bool) (clauses []ast.Clause, c *commentState) {
// TODO: reuse Template spec, which is possible if it doesn't check the
// first is an identifier.
+
for {
- if p.tok == token.COMMA {
- p.next()
- }
switch p.tok {
case token.FOR:
c := p.openComments()
forPos := p.expect(token.FOR)
+ if first {
+ switch p.tok {
+ case token.COLON, token.ISA, token.BIND, token.OPTION:
+ return nil, c
+ }
+ }
+
var key, value *ast.Ident
var colon token.Pos
value = p.parseIdent()
@@ -918,15 +1004,28 @@
case token.IF:
c := p.openComments()
+ ifPos := p.expect(token.IF)
+ if first {
+ switch p.tok {
+ case token.COLON, token.ISA, token.BIND, token.OPTION:
+ return nil, c
+ }
+ }
+
clauses = append(clauses, c.closeClause(p, &ast.IfClause{
- If: p.expect(token.IF),
+ If: ifPos,
Condition: p.parseExpr(),
}))
- // TODO: case LET:
+ // case token.LET:
default:
- return clauses
+ return clauses, nil
}
+ if p.tok == token.COMMA {
+ p.next()
+ }
+
+ first = false
}
}
@@ -942,7 +1041,7 @@
elts := p.parseListElements()
- if clauses := p.parseComprehensionClauses(); clauses != nil {
+ if clauses, _ := p.parseComprehensionClauses(false); clauses != nil {
var expr ast.Expr
if len(elts) != 1 {
p.errf(lbrack.Add(1), "list comprehension must have exactly one element")
diff --git a/cue/parser/parser_test.go b/cue/parser/parser_test.go
index 38f4114..ef26627 100644
--- a/cue/parser/parser_test.go
+++ b/cue/parser/parser_test.go
@@ -229,7 +229,11 @@
"field comprehensions",
`{
y: { a: 1, b: 2}
- a: { "\(k)": v for k, v in y if v > 2 }
+ a: {
+ for k, v in y if v > 2 {
+ "\(k)": v
+ }
+ }
}`,
`{y: {a: 1, b: 2}, a: {for k: v in y if v>2 {"\(k)": v}}}`,
}, {