cue/ast: first step to new comprehension format.

Change-Id: Idbeff0071b9bb8d70c13f8600df94e25ce09204f
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2952
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/import.go b/cmd/cue/cmd/import.go
index adb58c2..4a601f8 100644
--- a/cmd/cue/cmd/import.go
+++ b/cmd/cue/cmd/import.go
@@ -666,7 +666,7 @@
 
 	ast.Walk(expr, func(n ast.Node) bool {
 		switch n.(type) {
-		case *ast.ComprehensionDecl:
+		case *ast.Comprehension:
 			return false
 		}
 		return true
diff --git a/cmd/cue/cmd/trim.go b/cmd/cue/cmd/trim.go
index 0e13c26..081ac71 100644
--- a/cmd/cue/cmd/trim.go
+++ b/cmd/cue/cmd/trim.go
@@ -218,7 +218,7 @@
 				t.markAlwaysGen(x.Value, false)
 			}
 
-		case *ast.ListComprehension, *ast.ComprehensionDecl:
+		case *ast.ListComprehension, *ast.Comprehension:
 			t.markAlwaysGen(x, true)
 		}
 	})
diff --git a/cue/ast.go b/cue/ast.go
index b8163ee..17605d2 100644
--- a/cue/ast.go
+++ b/cue/ast.go
@@ -262,7 +262,7 @@
 			case *ast.Field, *ast.Alias:
 				v1.walk(e)
 
-			case *ast.ComprehensionDecl:
+			case *ast.Comprehension:
 				v1.walk(x)
 			}
 		}
@@ -303,17 +303,21 @@
 	case *ast.Ellipsis:
 		return v.errf(n, "ellipsis (...) only allowed at end of list or struct")
 
-	case *ast.ComprehensionDecl:
+	case *ast.Comprehension:
+		st, ok := n.Value.(*ast.StructLit)
+		if !ok || len(st.Elts) != 1 {
+			return v.errf(n, "invalid comprehension: %v", n)
+		}
+		field := st.Elts[0].(*ast.Field)
 		yielder := &yield{
-			baseValue: newExpr(n.Field.Value),
-			opt:       n.Field.Optional != token.NoPos,
-			def:       n.Field.Token == token.ISA,
+			baseValue: newExpr(n.Value),
+			opt:       field.Optional != token.NoPos,
+			def:       field.Token == token.ISA,
 		}
 		fc := &fieldComprehension{
 			baseValue: newDecl(n),
 			clauses:   wrapClauses(v, yielder, n.Clauses),
 		}
-		field := n.Field
 		switch x := field.Label.(type) {
 		case *ast.Interpolation:
 			v.sel = "?"
diff --git a/cue/ast/ast.go b/cue/ast/ast.go
index deb5915..e558b79 100644
--- a/cue/ast/ast.go
+++ b/cue/ast/ast.go
@@ -63,7 +63,7 @@
 func (*ListLit) exprNode()       {}
 func (*Ellipsis) exprNode()      {}
 
-// func (*StructComprehension) exprNode() {}
+// func (*Comprehension) exprNode() {}
 func (*ListComprehension) exprNode() {}
 func (*ParenExpr) exprNode()         {}
 func (*SelectorExpr) exprNode()      {}
@@ -80,13 +80,13 @@
 	declNode()
 }
 
-func (*Field) declNode()             {}
-func (*ComprehensionDecl) declNode() {}
-func (*ImportDecl) declNode()        {}
-func (*BadDecl) declNode()           {}
-func (*EmbedDecl) declNode()         {}
-func (*Alias) declNode()             {}
-func (*Ellipsis) declNode()          {}
+func (*Field) declNode()         {}
+func (*Comprehension) declNode() {}
+func (*ImportDecl) declNode()    {}
+func (*BadDecl) declNode()       {}
+func (*EmbedDecl) declNode()     {}
+func (*Alias) declNode()         {}
+func (*Ellipsis) declNode()      {}
 
 // Not technically declarations, but appearing at the same level.
 func (*Package) declNode()      {}
@@ -317,20 +317,16 @@
 func (a *Alias) Pos() token.Pos { return a.Ident.Pos() }
 func (a *Alias) End() token.Pos { return a.Expr.End() }
 
-// A ComprehensionDecl node represents a field comprehension.
-type ComprehensionDecl struct {
+// A Comprehension node represents a comprehension declaration.
+type Comprehension struct {
 	comments
-	Field   *Field
-	Select  token.Pos
-	Clauses []Clause
+	Clauses []Clause // There must be at least one clause.
+	Value   Expr     // Must be a struct
 }
 
-func (x *ComprehensionDecl) Pos() token.Pos { return x.Field.Pos() }
-func (x *ComprehensionDecl) End() token.Pos {
-	if len(x.Clauses) > 0 {
-		return x.Clauses[len(x.Clauses)-1].End()
-	}
-	return x.Select
+func (x *Comprehension) Pos() token.Pos { return x.Clauses[0].Pos() }
+func (x *Comprehension) End() token.Pos {
+	return x.Value.End()
 }
 
 // ----------------------------------------------------------------------------
diff --git a/cue/ast/walk.go b/cue/ast/walk.go
index 8fb1ee9..6e66754 100644
--- a/cue/ast/walk.go
+++ b/cue/ast/walk.go
@@ -169,11 +169,11 @@
 		walk(v, n.Ident)
 		walk(v, n.Expr)
 
-	case *ComprehensionDecl:
-		walk(v, n.Field)
+	case *Comprehension:
 		for _, c := range n.Clauses {
 			walk(v, c)
 		}
+		walk(v, n.Value)
 
 	// Files and packages
 	case *File:
diff --git a/cue/export.go b/cue/export.go
index 8cd1518..1a72d4c 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -678,7 +678,12 @@
 				}
 				var decl ast.Decl = f
 				if len(clauses) > 0 {
-					decl = &ast.ComprehensionDecl{Field: f, Clauses: clauses}
+					decl = &ast.Comprehension{
+						Clauses: clauses,
+						Value: &ast.StructLit{
+							Elts: []ast.Decl{f},
+						},
+					}
 				}
 				obj.Elts = append(obj.Elts, decl)
 				break
diff --git a/cue/format/node.go b/cue/format/node.go
index e2bf474..70f08fd 100644
--- a/cue/format/node.go
+++ b/cue/format/node.go
@@ -223,11 +223,16 @@
 			f.print(formfeed)
 		}
 
-	case *ast.ComprehensionDecl:
-		f.decl(n.Field)
+	case *ast.Comprehension:
+		st, ok := n.Value.(*ast.StructLit)
+		if !ok || len(st.Elts) != 1 {
+			f.print(n.Value, "*bad decl*", declcomma)
+		}
+		field := st.Elts[0]
+		f.decl(field)
 		f.print(blank)
-		if n.Select != token.NoPos {
-			f.print(n.Select, token.ARROW, blank)
+		if n.Clauses[0].Pos().RelPos() >= token.Newline {
+			f.print(token.ARROW, blank)
 		}
 		f.print(indent)
 		f.walkClauseList(n.Clauses)
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index 31eb2e5..7a6b9ef 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -767,10 +767,9 @@
 	p.closeList()
 
 	decl = this
-	var arrow token.Pos
 	switch p.tok {
 	case token.ARROW:
-		arrow = p.expect(token.ARROW)
+		p.expect(token.ARROW)
 		fallthrough
 
 	case token.FOR, token.IF:
@@ -778,10 +777,11 @@
 			p.errf(p.pos, "comprehension not allowed for this field")
 		}
 		clauses := p.parseComprehensionClauses()
-		return &ast.ComprehensionDecl{
-			Field:   this,
-			Select:  arrow,
+		return &ast.Comprehension{
 			Clauses: clauses,
+			Value: &ast.StructLit{
+				Elts: []ast.Decl{this},
+			},
 		}
 	}
 
diff --git a/cue/parser/parser_test.go b/cue/parser/parser_test.go
index a4fd97f..38f4114 100644
--- a/cue/parser/parser_test.go
+++ b/cue/parser/parser_test.go
@@ -231,7 +231,7 @@
 				y: { a: 1, b: 2}
 				a: { "\(k)": v for k, v in y if v > 2 }
 			 }`,
-		`{y: {a: 1, b: 2}, a: {"\(k)": v for k: v in y if v>2 }}`,
+		`{y: {a: 1, b: 2}, a: {for k: v in y if v>2 {"\(k)": v}}}`,
 	}, {
 		"duplicates allowed",
 		`{
diff --git a/cue/parser/print.go b/cue/parser/print.go
index 7fd2dda..f72a636 100644
--- a/cue/parser/print.go
+++ b/cue/parser/print.go
@@ -82,10 +82,9 @@
 		}
 		return out
 
-	case *ast.ComprehensionDecl:
-		out := debugStr(v.Field)
-		out += " "
-		out += debugStr(v.Clauses)
+	case *ast.Comprehension:
+		out := debugStr(v.Clauses)
+		out += debugStr(v.Value)
 		return out
 
 	case *ast.StructLit:
diff --git a/cue/parser/resolve.go b/cue/parser/resolve.go
index fd17c1e..f164fd0 100644
--- a/cue/parser/resolve.go
+++ b/cue/parser/resolve.go
@@ -115,7 +115,7 @@
 	case *ast.StructLit:
 		return newScope(s.file, s, x, x.Elts)
 
-	case *ast.ComprehensionDecl:
+	case *ast.Comprehension:
 		s = scopeClauses(s, x.Clauses)
 
 	case *ast.ListComprehension:
diff --git a/cue/parser/walk.go b/cue/parser/walk.go
index 58b3085..53f68c2 100644
--- a/cue/parser/walk.go
+++ b/cue/parser/walk.go
@@ -164,11 +164,11 @@
 		walk(v, n.Ident)
 		walk(v, n.Expr)
 
-	case *ast.ComprehensionDecl:
-		walk(v, n.Field)
+	case *ast.Comprehension:
 		for _, c := range n.Clauses {
 			walk(v, c)
 		}
+		walk(v, n.Value)
 
 	// Files and packages
 	case *ast.File: