cue/ast: add NewBinExpr helper

Change-Id: I2f067d5a1b087d43a0c2f95f6054fcfed977fb7b
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4603
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/get_go.go b/cmd/cue/cmd/get_go.go
index 877a2ab..be8ea78 100644
--- a/cmd/cue/cmd/get_go.go
+++ b/cmd/cue/cmd/get_go.go
@@ -627,7 +627,7 @@
 				for _, v := range enums[1:] {
 					y := e.ident(v)
 					cueast.SetRelPos(y, cuetoken.Newline)
-					x = &cueast.BinaryExpr{X: x, Op: cuetoken.OR, Y: y}
+					x = cueast.NewBinExpr(cuetoken.OR, x, y)
 				}
 				a = append(a, e.def(nil, enumName, x, true))
 			}
@@ -678,11 +678,7 @@
 					switch s {
 					case "byte", "string", "error":
 					default:
-						cv = &cueast.BinaryExpr{
-							X:  e.makeType(typ),
-							Op: cuetoken.AND,
-							Y:  cv,
-						}
+						cv = cueast.NewBinExpr(cuetoken.AND, e.makeType(typ), cv)
 					}
 				}
 
diff --git a/cue/ast/ast.go b/cue/ast/ast.go
index 9a4441e..b95714c 100644
--- a/cue/ast/ast.go
+++ b/cue/ast/ast.go
@@ -609,6 +609,21 @@
 	expr
 }
 
+// NewBinExpr creates for list of expressions of length 2 or greater a chained
+// binary expression of the form (((x1 op x2) op x3) ...). For lists of lenght
+// 1 it returns the expression itself. It panics for empty lists.
+// Useful for ASTs generated by code other than the CUE parser.
+func NewBinExpr(op token.Token, operands ...Expr) Expr {
+	if len(operands) == 0 {
+		panic("must specify at least one expression")
+	}
+	expr := operands[0]
+	for _, e := range operands[1:] {
+		expr = &BinaryExpr{X: expr, Op: op, Y: e}
+	}
+	return expr
+}
+
 // token.Pos and End implementations for expression/type nodes.
 
 func (x *BadExpr) Pos() token.Pos        { return x.From }
diff --git a/cue/export.go b/cue/export.go
index 2f43291..4677550 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -456,10 +456,7 @@
 			s := &ast.StructLit{}
 			return p.closeOrOpen(s, p.embedding(s, x))
 		}
-		return &ast.BinaryExpr{
-			X:  p.expr(x.left),
-			Op: opMap[x.op], Y: p.expr(x.right),
-		}
+		return ast.NewBinExpr(opMap[x.op], p.expr(x.left), p.expr(x.right))
 
 	case *bound:
 		return &ast.UnaryExpr{Op: opMap[x.op], X: p.expr(x.value)}
@@ -491,7 +488,7 @@
 		}
 		bin := expr(x.values[0])
 		for _, v := range x.values[1:] {
-			bin = &ast.BinaryExpr{X: bin, Op: token.OR, Y: expr(v)}
+			bin = ast.NewBinExpr(token.OR, bin, expr(v))
 		}
 		return bin
 
@@ -631,15 +628,14 @@
 		if !ok || ln > len(x.elem.arcs) {
 			list.Elts = append(list.Elts, &ast.Ellipsis{Type: p.expr(x.typ)})
 			if !open && !isTop(x.typ) {
-				expr = &ast.BinaryExpr{
-					X: &ast.BinaryExpr{
-						X:  p.expr(x.len),
-						Op: token.MUL,
-						Y:  ast.NewList(p.expr(x.typ)),
-					},
-					Op: token.AND,
-					Y:  list,
-				}
+				expr = ast.NewBinExpr(
+					token.AND,
+					ast.NewBinExpr(
+						token.MUL,
+						p.expr(x.len),
+						ast.NewList(p.expr(x.typ))),
+					list,
+				)
 
 			}
 		}
@@ -1159,9 +1155,5 @@
 	if b == nil {
 		return a
 	}
-	return &ast.BinaryExpr{
-		X:  a,
-		Op: opMap[op],
-		Y:  b,
-	}
+	return ast.NewBinExpr(opMap[op], a, b)
 }