cue/ast: convenience constructors for common types

In line with NewIdent

Updated code to use them, providing evidence of
their usefulness.

Change-Id: I0ccea6cb7df852c6ee56436bd20e008267a14aee
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3242
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/fix.go b/cmd/cue/cmd/fix.go
index a3fd8a2..5406384 100644
--- a/cmd/cue/cmd/fix.go
+++ b/cmd/cue/cmd/fix.go
@@ -119,17 +119,11 @@
 				astutil.CopyMeta(lo, x.Low)
 			}
 			if hi == nil { // a[i:]
-				hi = &ast.CallExpr{
-					Fun:  ast.NewIdent("len"),
-					Args: []ast.Expr{x.X},
-				}
+				hi = ast.NewCall(ast.NewIdent("len"), x.X)
 				astutil.CopyMeta(lo, x.High)
 			}
 			if pkg := c.Import("list"); pkg != nil {
-				c.Replace(&ast.CallExpr{
-					Fun:  &ast.SelectorExpr{X: pkg, Sel: ast.NewIdent("Slice")},
-					Args: []ast.Expr{x.X, lo, hi},
-				})
+				c.Replace(ast.NewCall(ast.NewSel(pkg, "Slice"), x.X, lo, hi))
 			}
 		}
 		return true
diff --git a/cmd/cue/cmd/import.go b/cmd/cue/cmd/import.go
index fcc0d29..697d1ff 100644
--- a/cmd/cue/cmd/import.go
+++ b/cmd/cue/cmd/import.go
@@ -489,11 +489,8 @@
 					ident = nil
 				}
 
-				path := fmt.Sprintf(`"encoding/%s"`, short)
-				imports.Specs = append(imports.Specs, &ast.ImportSpec{
-					Name: ident,
-					Path: &ast.BasicLit{Kind: token.STRING, Value: path},
-				})
+				imports.Specs = append(imports.Specs,
+					ast.NewImport(ident, "encoding/"+short))
 			}
 		}
 		f.Decls = append([]ast.Decl{imports}, f.Decls...)
@@ -709,15 +706,9 @@
 			// found a replacable string
 			dataField := h.uniqueName(name, "_", "cue_")
 
-			f.Value = &ast.CallExpr{
-				Fun: &ast.SelectorExpr{
-					X:   h.altNames[enc.typ],
-					Sel: ast.NewIdent("Marshal"),
-				},
-				Args: []ast.Expr{
-					ast.NewIdent(dataField),
-				},
-			}
+			f.Value = ast.NewCall(
+				ast.NewSel(h.altNames[enc.typ], "Marshal"),
+				ast.NewIdent(dataField))
 
 			obj.Elts = append(obj.Elts, nil)
 			copy(obj.Elts[i+1:], obj.Elts[i:])
diff --git a/cue/ast/ast.go b/cue/ast/ast.go
index 8e7ec79..fb93b3f 100644
--- a/cue/ast/ast.go
+++ b/cue/ast/ast.go
@@ -17,6 +17,7 @@
 package ast // import "cuelang.org/go/cue/ast"
 
 import (
+	"strconv"
 	"strings"
 
 	"cuelang.org/go/cue/token"
@@ -409,6 +410,19 @@
 	Value    string      // literal string; e.g. 42, 0x7f, 3.14, 1_234_567, 1e-9, 2.4i, 'a', '\x7f', "foo", or '\m\n\o'
 }
 
+// NewString creates a new BasicLit with a string value without position.
+// It quotes the given string.
+// Useful for ASTs generated by code other than the Go
+func NewString(str string) *BasicLit {
+	// TODO: use CUE quoting.
+	str = strconv.Quote(str)
+	return &BasicLit{Kind: token.STRING, ValuePos: token.NoPos, Value: str}
+}
+
+// TODO:
+// - use CUE-specific quoting (hoist functionality in export)
+// - NewBytes
+
 // A Interpolation node represents a string or bytes interpolation.
 type Interpolation struct {
 	label
@@ -481,6 +495,15 @@
 	Sel *Ident // field selector
 }
 
+// NewSel creates a sequence of selectors.
+// Useful for ASTs generated by code other than the Go
+func NewSel(x Expr, sel ...string) Expr {
+	for _, s := range sel {
+		x = &SelectorExpr{X: x, Sel: NewIdent(s)}
+	}
+	return x
+}
+
 // An IndexExpr node represents an expression followed by an index.
 type IndexExpr struct {
 	comments
@@ -509,6 +532,12 @@
 	Rparen token.Pos // position of ")"
 }
 
+// NewCall creates a new CallExpr.
+// Useful for ASTs generated by code other than the Go
+func NewCall(fun Expr, args ...Expr) *CallExpr {
+	return &CallExpr{Fun: fun, Args: args}
+}
+
 // A UnaryExpr node represents a unary expression.
 type UnaryExpr struct {
 	comments
@@ -633,6 +662,12 @@
 	EndPos token.Pos // end of spec (overrides Path.Pos if nonzero)
 }
 
+func NewImport(name *Ident, importPath string) *ImportSpec {
+	importPath = strconv.Quote(importPath)
+	path := &BasicLit{Kind: token.STRING, Value: importPath}
+	return &ImportSpec{Name: name, Path: path}
+}
+
 // Pos and End implementations for spec nodes.
 
 func (s *ImportSpec) Pos() token.Pos { return getPos(s) }
diff --git a/cue/ast_test.go b/cue/ast_test.go
index 96b4b63..3550a1e 100644
--- a/cue/ast_test.go
+++ b/cue/ast_test.go
@@ -451,7 +451,7 @@
 }
 
 func TestShadowing(t *testing.T) {
-	spec := &ast.ImportSpec{Path: mustParseExpr(`"list"`).(*ast.BasicLit)}
+	spec := ast.NewImport(nil, "list")
 	testCases := []struct {
 		file *ast.File
 		want string
@@ -460,15 +460,8 @@
 			&ast.ImportDecl{Specs: []*ast.ImportSpec{spec}},
 			&ast.Field{
 				Label: mustParseExpr(`list`).(*ast.Ident),
-				Value: &ast.CallExpr{
-					Fun: &ast.SelectorExpr{
-						X: &ast.Ident{
-							Name: "list",
-							Node: spec,
-						},
-						Sel: ast.NewIdent("Min"),
-					},
-				},
+				Value: ast.NewCall(
+					ast.NewSel(&ast.Ident{Name: "list", Node: spec}, "Min")),
 			},
 		}},
 		want: "import listx \"list\", list: listx.Min()",
diff --git a/cue/build_test.go b/cue/build_test.go
index 804e7ea..912b452 100644
--- a/cue/build_test.go
+++ b/cue/build_test.go
@@ -29,12 +29,12 @@
 		expr ast.Expr
 		out  string
 	}{{
-		expr: &ast.BasicLit{Kind: token.STRING, Value: `"Hello"`},
+		expr: ast.NewString("Hello"),
 		out:  `"Hello"`,
 	}, {
 		expr: &ast.ListLit{Elts: []ast.Expr{
-			&ast.BasicLit{Kind: token.STRING, Value: `"Hello"`},
-			&ast.BasicLit{Kind: token.STRING, Value: `"World"`},
+			ast.NewString("Hello"),
+			ast.NewString("World"),
 		}},
 		out: `["Hello","World"]`,
 	}}
diff --git a/cue/export.go b/cue/export.go
index 8035c94..9fea698 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -72,10 +72,7 @@
 				Expr:  ast.NewIdent(info.short),
 			})
 		}
-		importDecl.Specs = append(importDecl.Specs, &ast.ImportSpec{
-			Name: ident,
-			Path: &ast.BasicLit{Kind: token.STRING, Value: quote(k, '"')},
-		})
+		importDecl.Specs = append(importDecl.Specs, ast.NewImport(ident, k))
 	}
 
 	if obj, ok := value.(*ast.StructLit); ok {
@@ -221,10 +218,7 @@
 		return s
 	}
 	if isClosed && !p.inDef {
-		return &ast.CallExpr{
-			Fun:  ast.NewIdent("close"),
-			Args: []ast.Expr{s},
-		}
+		return ast.NewCall(ast.NewIdent("close"), s)
 	}
 	if !isClosed && p.inDef && !hasTemplate(s) {
 		s.Elts = append(s.Elts, &ast.Ellipsis{})
@@ -255,14 +249,13 @@
 	// TODO: also add position information.
 	switch x := v.(type) {
 	case *builtin:
-		name := ast.NewIdent(x.Name)
 		if x.pkg == 0 {
-			return name
+			return ast.NewIdent(x.Name)
 		}
 		pkg := p.ctx.labelStr(x.pkg)
 		inst := builtins[pkg]
 		short := p.shortName(inst, "", pkg)
-		return &ast.SelectorExpr{X: ast.NewIdent(short), Sel: name}
+		return ast.NewSel(ast.NewIdent(short), x.Name)
 
 	case *nodeRef:
 		if x.short == 0 {
@@ -278,7 +271,7 @@
 	case *selectorExpr:
 		n := p.expr(x.x)
 		if n != nil {
-			return &ast.SelectorExpr{X: n, Sel: p.identifier(x.feature)}
+			return ast.NewSel(n, p.ctx.labelStr(x.feature))
 		}
 		ident := p.identifier(x.feature)
 		node, ok := x.x.(*nodeRef)
@@ -338,7 +331,7 @@
 		return call
 
 	case *customValidator:
-		call := &ast.CallExpr{Fun: p.expr(x.call)}
+		call := ast.NewCall(p.expr(x.call))
 		for _, a := range x.args {
 			call.Args = append(call.Args, p.expr(a))
 		}
diff --git a/cue/format/format_test.go b/cue/format/format_test.go
index ffab0f0..7c5ab8d 100644
--- a/cue/format/format_test.go
+++ b/cue/format/format_test.go
@@ -245,6 +245,7 @@
 			&ast.Package{Name: ast.NewIdent("foo")},
 			&ast.EmbedDecl{
 				Expr: &ast.BasicLit{
+					Kind:     token.INT,
 					ValuePos: token.NoSpace.Pos(),
 					Value:    "1",
 				},
diff --git a/encoding/protobuf/parse.go b/encoding/protobuf/parse.go
index dc1a230..76183c7 100644
--- a/encoding/protobuf/parse.go
+++ b/encoding/protobuf/parse.go
@@ -154,9 +154,7 @@
 	p.sorted = imported
 
 	for _, v := range imported {
-		spec := &ast.ImportSpec{
-			Path: &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(v)},
-		}
+		spec := ast.NewImport(nil, v)
 		imports.Specs = append(imports.Specs, spec)
 		p.file.Imports = append(p.file.Imports, spec)
 	}
@@ -293,13 +291,8 @@
 
 func (p *protoConverter) toExpr(pos scanner.Position, name string) (expr ast.Expr) {
 	a := strings.Split(name, ".")
-	for i, s := range a {
-		if i == 0 {
-			expr = &ast.Ident{NamePos: p.toCUEPos(pos), Name: s}
-			continue
-		}
-		expr = &ast.SelectorExpr{X: expr, Sel: ast.NewIdent(s)}
-	}
+	expr = &ast.Ident{NamePos: p.toCUEPos(pos), Name: a[0]}
+	expr = ast.NewSel(expr, a[1:]...)
 	return expr
 }
 
diff --git a/internal/internal_test.go b/internal/internal_test.go
index c56ce82..a3e8e73 100644
--- a/internal/internal_test.go
+++ b/internal/internal_test.go
@@ -19,7 +19,6 @@
 
 	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/format"
-	"cuelang.org/go/cue/token"
 	"cuelang.org/go/internal"
 	"github.com/stretchr/testify/assert"
 )
@@ -30,11 +29,11 @@
 		out string
 		ok  bool
 	}{{
-		in:  &ast.BasicLit{Kind: token.STRING, Value: `"foo-bar"`},
+		in:  ast.NewString("foo-bar"),
 		out: "foo-bar",
 		ok:  true,
 	}, {
-		in:  &ast.BasicLit{Kind: token.STRING, Value: `"foo bar"`},
+		in:  ast.NewString("foo bar"),
 		out: "foo bar",
 		ok:  true,
 	}, {