cue: compile builtins at init time

Also allow any CUE as literal value for builtins.
This, in turn, is necessary to define some useful core
types (like time.Time) as well as to give better support
for the scripting layer, allowing better abstractions
for tools.

Updates #24

Change-Id: Icdcf7dfe7330bcc0efef437689503a84bcf3567c
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/1721
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/ast.go b/cue/ast.go
index 5e02b9e..4c5ece5 100644
--- a/cue/ast.go
+++ b/cue/ast.go
@@ -127,14 +127,13 @@
 
 func (v *astVisitor) loadImport(imp *ast.ImportSpec) evaluated {
 	ctx := v.ctx()
-	val := lookupBuiltinPkg(ctx, imp)
-	if !isBottom(val) {
-		return val
-	}
 	path, err := literal.Unquote(imp.Path.Value)
 	if err != nil {
 		return ctx.mkErr(newNode(imp), "illformed import spec")
 	}
+	if p := getBuiltinPkg(ctx, path); p != nil {
+		return p
+	}
 	bimp := v.inst.LookupImport(path)
 	if bimp == nil {
 		return ctx.mkErr(newNode(imp), "package %q not found", path)
diff --git a/cue/build.go b/cue/build.go
index 921116b..a6d8601 100644
--- a/cue/build.go
+++ b/cue/build.go
@@ -69,19 +69,39 @@
 	labels   []string
 
 	loaded map[*build.Instance]*Instance
+
+	offset label
+	parent *index
+	freeze bool
 }
 
-// newIndex creates a new index.
-func newIndex(f *token.FileSet) *index {
+// sharedIndex is used for indexing builtins and any other labels common to
+// all instances.
+var sharedIndex = newSharedIndex(token.NewFileSet())
+
+func newSharedIndex(f *token.FileSet) *index {
 	i := &index{
 		fset:     f,
 		labelMap: map[string]label{"": 0},
 		labels:   []string{""},
-		loaded:   map[*build.Instance]*Instance{},
 	}
 	return i
 }
 
+// newIndex creates a new index.
+func newIndex(f *token.FileSet) *index {
+	parent := sharedIndex
+	i := &index{
+		fset:     f,
+		labelMap: map[string]label{},
+		loaded:   map[*build.Instance]*Instance{},
+		offset:   label(len(parent.labels)) + parent.offset,
+		parent:   parent,
+	}
+	parent.freeze = true
+	return i
+}
+
 func (idx *index) strLabel(str string) label {
 	return idx.label(str, false)
 }
@@ -97,10 +117,23 @@
 	return 0, false
 }
 
+func (idx *index) findLabel(s string) (f label, ok bool) {
+	for x := idx; x != nil; x = x.parent {
+		f, ok = x.labelMap[s]
+		if ok {
+			break
+		}
+	}
+	return f, ok
+}
+
 func (idx *index) label(s string, isIdent bool) label {
-	f, ok := idx.labelMap[s]
+	f, ok := idx.findLabel(s)
 	if !ok {
-		f = label(len(idx.labelMap))
+		if idx.freeze {
+			panic("adding label to frozen index")
+		}
+		f = label(len(idx.labelMap)) + idx.offset
 		idx.labelMap[s] = f
 		idx.labels = append(idx.labels, s)
 	}
@@ -112,7 +145,10 @@
 }
 
 func (idx *index) labelStr(l label) string {
-	return idx.labels[l>>1]
+	l >>= 1
+	for ; l < idx.offset; idx = idx.parent {
+	}
+	return idx.labels[l-idx.offset]
 }
 
 func (idx *index) loadInstance(p *build.Instance) *Instance {
@@ -189,13 +225,12 @@
 			continue // quietly ignore the error
 		}
 		name := path.Base(id)
-		if _, ok := builtins[id]; ok {
-		} else if imp := p.LookupImport(id); imp != nil {
+		if imp := p.LookupImport(id); imp != nil {
 			name = imp.PkgName
 			if spec.Name != nil {
 				name = spec.Name.Name
 			}
-		} else {
+		} else if _, ok := builtins[id]; !ok {
 			// continue
 			return idx.mkErr(newNode(spec), "package %q not found", id)
 		}
diff --git a/cue/builtin.go b/cue/builtin.go
index 39c3fad..550a4c5 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -27,7 +27,7 @@
 	"strings"
 
 	"cuelang.org/go/cue/ast"
-	"cuelang.org/go/cue/literal"
+	"cuelang.org/go/cue/parser"
 	"github.com/cockroachdb/apd"
 )
 
@@ -56,7 +56,34 @@
 	Result kind
 	Func   func(c *callCtxt)
 	// Const  interface{}
-	Const evaluated
+	Const string
+}
+
+func mustCompileBuiltins(ctx *context, b []*builtin) *structLit {
+	obj := &structLit{}
+	for _, b := range b {
+		f := ctx.label(b.Name, false) // never starts with _
+		// n := &node{baseValue: newBase(imp.Path)}
+		var v evaluated = b
+		if b.Const != "" {
+			v = mustParseConstBuiltin(ctx, b.Name, b.Const)
+		}
+		obj.arcs = append(obj.arcs, arc{feature: f, v: v})
+	}
+	sort.Sort(obj)
+	return obj
+}
+
+// newConstBuiltin parses and creates any CUE expression that does not have
+// fields.
+func mustParseConstBuiltin(ctx *context, name, val string) evaluated {
+	expr, err := parser.ParseExpr(ctx.index.fset, "<builtin:"+name+">", val)
+	if err != nil {
+		panic(err)
+	}
+	v := newVisitor(ctx.index, nil, nil, nil)
+	value := v.walk(expr)
+	return value.evalPartial(ctx)
 }
 
 var _ caller = &builtin{}
@@ -88,16 +115,10 @@
 }
 
 func (x *builtin) kind() kind {
-	if x.Const != nil {
-		return x.Const.kind()
-	}
 	return lambdaKind
 }
 
 func (x *builtin) evalPartial(ctx *context) evaluated {
-	if x.Const != nil {
-		return x.Const
-	}
 	return x
 }
 
@@ -150,12 +171,14 @@
 	ret  interface{}
 }
 
-var builtins map[string][]*builtin
+var builtins = map[string]*structLit{}
 
 func initBuiltins(pkgs map[string][]*builtin) {
-	builtins = pkgs
+	ctx := sharedIndex.newContext()
 	for k, b := range pkgs {
-		builtins["-/"+path.Base(k)] = b
+		e := mustCompileBuiltins(ctx, b)
+		builtins[k] = e
+		builtins["-/"+path.Base(k)] = e
 	}
 }
 
@@ -168,17 +191,7 @@
 	if !ok {
 		return nil
 	}
-
-	// TODO(perf): store in index
-
-	obj := &structLit{}
-	for _, b := range p {
-		f := ctx.label(b.Name, false) // never starts with _
-		// n := &node{baseValue: newBase(imp.Path)}
-		obj.arcs = append(obj.arcs, arc{feature: f, v: b})
-	}
-	sort.Sort(obj)
-	return obj
+	return p
 }
 
 // do returns whether the call should be done.
@@ -347,20 +360,6 @@
 	return a
 }
 
-// lookupBuiltinPkg returns the builtin package for the given path if it exists.
-func lookupBuiltinPkg(ctx *context, imp *ast.ImportSpec) evaluated {
-	path, err := literal.Unquote(imp.Path.Value)
-	if err != nil {
-		return ctx.mkErr(newNode(imp), "illformed import spec")
-	}
-
-	p := getBuiltinPkg(ctx, path)
-	if p == nil {
-		return ctx.mkErr(newNode(imp), "package %q not found", path)
-	}
-	return p
-}
-
 func convert(ctx *context, src source, x interface{}) evaluated {
 	switch v := x.(type) {
 	case evaluated:
diff --git a/cue/builtins.go b/cue/builtins.go
index 51162d5..1955814 100644
--- a/cue/builtins.go
+++ b/cue/builtins.go
@@ -54,10 +54,10 @@
 	"": []*builtin{{}},
 	"crypto/md5": []*builtin{{
 		Name:  "Size",
-		Const: intFromGo("16"),
+		Const: "16",
 	}, {
 		Name:  "BlockSize",
-		Const: intFromGo("64"),
+		Const: "64",
 	}, {
 		Name:   "Sum",
 		Params: []kind{stringKind},
@@ -71,10 +71,10 @@
 	}},
 	"crypto/sha1": []*builtin{{
 		Name:  "Size",
-		Const: intFromGo("20"),
+		Const: "20",
 	}, {
 		Name:  "BlockSize",
-		Const: intFromGo("64"),
+		Const: "64",
 	}, {
 		Name:   "Sum",
 		Params: []kind{stringKind},
@@ -88,13 +88,13 @@
 	}},
 	"crypto/sha256": []*builtin{{
 		Name:  "Size",
-		Const: intFromGo("32"),
+		Const: "32",
 	}, {
 		Name:  "Size224",
-		Const: intFromGo("28"),
+		Const: "28",
 	}, {
 		Name:  "BlockSize",
-		Const: intFromGo("64"),
+		Const: "64",
 	}, {
 		Name:   "Sum256",
 		Params: []kind{stringKind},
@@ -118,19 +118,19 @@
 	}},
 	"crypto/sha512": []*builtin{{
 		Name:  "Size",
-		Const: intFromGo("64"),
+		Const: "64",
 	}, {
 		Name:  "Size224",
-		Const: intFromGo("28"),
+		Const: "28",
 	}, {
 		Name:  "Size256",
-		Const: intFromGo("32"),
+		Const: "32",
 	}, {
 		Name:  "Size384",
-		Const: intFromGo("48"),
+		Const: "48",
 	}, {
 		Name:  "BlockSize",
-		Const: intFromGo("128"),
+		Const: "128",
 	}, {
 		Name:   "Sum512",
 		Params: []kind{stringKind},
@@ -449,40 +449,40 @@
 	"list": []*builtin{{}},
 	"math": []*builtin{{
 		Name:  "MaxExp",
-		Const: intFromGo("2147483647"),
+		Const: "2147483647",
 	}, {
 		Name:  "MinExp",
-		Const: intFromGo("-2147483648"),
+		Const: "-2147483648",
 	}, {
 		Name:  "MaxPrec",
-		Const: intFromGo("4294967295"),
+		Const: "4294967295",
 	}, {
 		Name:  "ToNearestEven",
-		Const: intFromGo("0"),
+		Const: "0",
 	}, {
 		Name:  "ToNearestAway",
-		Const: intFromGo("1"),
+		Const: "1",
 	}, {
 		Name:  "ToZero",
-		Const: intFromGo("2"),
+		Const: "2",
 	}, {
 		Name:  "AwayFromZero",
-		Const: intFromGo("3"),
+		Const: "3",
 	}, {
 		Name:  "ToNegativeInf",
-		Const: intFromGo("4"),
+		Const: "4",
 	}, {
 		Name:  "ToPositiveInf",
-		Const: intFromGo("5"),
+		Const: "5",
 	}, {
 		Name:  "Below",
-		Const: intFromGo("-1"),
+		Const: "-1",
 	}, {
 		Name:  "Exact",
-		Const: intFromGo("0"),
+		Const: "0",
 	}, {
 		Name:  "Above",
-		Const: intFromGo("1"),
+		Const: "1",
 	}, {
 		Name:   "Jacobi",
 		Params: []kind{intKind, intKind},
@@ -495,7 +495,7 @@
 		},
 	}, {
 		Name:  "MaxBase",
-		Const: intFromGo("62"),
+		Const: "62",
 	}, {
 		Name:   "Floor",
 		Params: []kind{numKind},
@@ -638,37 +638,37 @@
 		},
 	}, {
 		Name:  "E",
-		Const: floatFromGo("2.71828182845904523536028747135266249775724709369995957496696763"),
+		Const: "2.71828182845904523536028747135266249775724709369995957496696763",
 	}, {
 		Name:  "Pi",
-		Const: floatFromGo("3.14159265358979323846264338327950288419716939937510582097494459"),
+		Const: "3.14159265358979323846264338327950288419716939937510582097494459",
 	}, {
 		Name:  "Phi",
-		Const: floatFromGo("1.61803398874989484820458683436563811772030917980576286213544861"),
+		Const: "1.61803398874989484820458683436563811772030917980576286213544861",
 	}, {
 		Name:  "Sqrt2",
-		Const: floatFromGo("1.41421356237309504880168872420969807856967187537694807317667974"),
+		Const: "1.41421356237309504880168872420969807856967187537694807317667974",
 	}, {
 		Name:  "SqrtE",
-		Const: floatFromGo("1.64872127070012814684865078781416357165377610071014801157507931"),
+		Const: "1.64872127070012814684865078781416357165377610071014801157507931",
 	}, {
 		Name:  "SqrtPi",
-		Const: floatFromGo("1.77245385090551602729816748334114518279754945612238712821380779"),
+		Const: "1.77245385090551602729816748334114518279754945612238712821380779",
 	}, {
 		Name:  "SqrtPhi",
-		Const: floatFromGo("1.27201964951406896425242246173749149171560804184009624861664038"),
+		Const: "1.27201964951406896425242246173749149171560804184009624861664038",
 	}, {
 		Name:  "Ln2",
-		Const: floatFromGo("0.693147180559945309417232121458176568075500134360255254120680009"),
+		Const: "0.693147180559945309417232121458176568075500134360255254120680009",
 	}, {
 		Name:  "Log2E",
-		Const: floatFromGo("1.442695040888963407359924681001892137426645954152985934135449408"),
+		Const: "1.442695040888963407359924681001892137426645954152985934135449408",
 	}, {
 		Name:  "Ln10",
-		Const: floatFromGo("2.3025850929940456840179914546843642076011014886287729760333278"),
+		Const: "2.3025850929940456840179914546843642076011014886287729760333278",
 	}, {
 		Name:  "Log10E",
-		Const: floatFromGo("0.43429448190325182765112891891660508229439700580366656611445378"),
+		Const: "0.43429448190325182765112891891660508229439700580366656611445378",
 	}, {
 		Name:   "Copysign",
 		Params: []kind{numKind, numKind},
@@ -1319,7 +1319,7 @@
 		},
 	}, {
 		Name:  "IntSize",
-		Const: intFromGo("64"),
+		Const: "64",
 	}, {
 		Name:   "ParseUint",
 		Params: []kind{stringKind, intKind, intKind},
diff --git a/cue/export.go b/cue/export.go
index e5111d2..3622e4c 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -56,7 +56,7 @@
 	s = strings.ToUpper(s)
 	lab := s
 	for {
-		if _, ok := p.ctx.labelMap[lab]; !ok {
+		if _, ok := p.ctx.findLabel(lab); !ok {
 			p.ctx.label(lab, true)
 			break
 		}
diff --git a/cue/gen.go b/cue/gen.go
index 7effb9a..3dd656a 100644
--- a/cue/gen.go
+++ b/cue/gen.go
@@ -228,24 +228,21 @@
 	name := spec.Names[0].Name
 	value := ""
 	switch v := g.toValue(spec.Values[0]); v.Kind() {
-	case constant.Bool:
-		value = fmt.Sprintf("&boolList{b: %q}", v.String())
-	case constant.String:
-		value = fmt.Sprintf("&stringLit{str: %q}", v.ExactString())
-	case constant.Int:
-		value = fmt.Sprintf("intFromGo(%q)", v.ExactString())
+	case constant.Bool, constant.Int, constant.String:
+		// TODO: convert octal numbers
+		value = v.ExactString()
 	case constant.Float:
 		var rat big.Rat
 		rat.SetString(v.ExactString())
 		var float big.Float
 		float.SetRat(&rat)
-		value = fmt.Sprintf("floatFromGo(%q)", float.Text('g', -1))
+		value = float.Text('g', -1)
 	default:
 		fmt.Printf("Dropped entry %s.%s (%T: %v)\n", g.defaultPkg, name, v.Kind(), v.ExactString())
 		return
 	}
 	g.sep()
-	fmt.Fprintf(g.w, "Name: %q,\n Const: %s,\n", name, value)
+	fmt.Fprintf(g.w, "Name: %q,\n Const: %q,\n", name, value)
 }
 
 func (g *generator) toValue(x ast.Expr) constant.Value {
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 1fe8383..7b50370 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -1584,6 +1584,8 @@
 		`,
 		out: `<0>{a: <1>{b: <2>{c: 4, d: 5}, c: 4, d: 5}}`,
 	}, {
+		// TODO: rename EE and FF to E and F to check correct ordering.
+
 		desc: "nested templates in one field",
 		in: `
 			a <A> b <B>: {
@@ -1592,9 +1594,15 @@
 			}
 			a "A" b "B": _
 			a "C" b "D": _
-			a "E" b "F": { c: "bar" }
+			a "EE" b "FF": { c: "bar" }
 		`,
-		out: `<0>{a: <1>{<>: <2>(A: string)-><3>{b: <4>{<>: <5>(B: string)-><6>{name: <2>.A, kind: <5>.B}, }}, A: <7>{b: <8>{<>: <9>(B: string)-><10>{name: <11>.A, kind: <9>.B}, B: <12>{name: "A", kind: "B"}}}, C: <13>{b: <14>{<>: <15>(B: string)-><16>{name: <17>.A, kind: <15>.B}, D: <18>{name: "C", kind: "D"}}}, E: <19>{b: <20>{<>: <21>(B: string)-><22>{name: <23>.A, kind: <21>.B}, F: <24>{name: "E", kind: "F", c: "bar"}}}}}`,
+		out: `<0>{a: <1>{<>: <2>(A: string)-><3>{b: <4>{<>: <5>(B: string)-><6>{name: <2>.A, kind: <5>.B}, }}, ` +
+			`A: <7>{b: <8>{<>: <9>(B: string)-><10>{name: <11>.A, kind: <9>.B}, ` +
+			`B: <12>{name: "A", kind: "B"}}}, ` +
+			`C: <13>{b: <14>{<>: <15>(B: string)-><16>{name: <17>.A, kind: <15>.B}, ` +
+			`D: <18>{name: "C", kind: "D"}}}, ` +
+			`EE: <19>{b: <20>{<>: <21>(B: string)-><22>{name: <23>.A, kind: <21>.B}, ` +
+			`FF: <24>{name: "EE", kind: "FF", c: "bar"}}}}}`,
 	}, {
 		desc: "template unification within one struct",
 		in: `
@@ -1607,9 +1615,9 @@
 			a "E": { c: "bar" }
 		`,
 		out: `<0>{a: <1>{<>: <2>(A: string)->(<3>{name: <2>.A} & <4>{kind: <2>.A}), ` +
-			`A: <5>{name: "A", kind: "A"}, ` +
-			`C: <6>{name: "C", kind: "C"}, ` +
-			`E: <7>{name: "E", kind: "E", c: "bar"}}}`,
+			`E: <5>{name: "E", kind: "E", c: "bar"}, ` +
+			`A: <6>{name: "A", kind: "A"}, ` +
+			`C: <7>{name: "C", kind: "C"}}}`,
 	}, {
 		desc: "field comprehensions with multiple keys",
 		in: `
@@ -1624,13 +1632,21 @@
 				{a: "C", b: "D" },
 				{a: "E", b: "F" },
 			]`,
-		out: `<0>{a: <1>{` +
-			`A: <2>{b: <3>{B: <4>{a: "A", b: "B"}}}, ` +
-			`C: <5>{b: <6>{D: <7>{a: "C", b: "D"}}}, ` +
-			`E: <8>{b: <9>{F: <10>{a: "E", b: "F"}}}}, ` +
-			`A: <11>{B: <12>{a: "A", b: "B"}}, ` +
-			`C: <13>{D: <14>{a: "C", b: "D"}}, ` +
-			`E: <15>{F: <16>{a: "E", b: "F"}}}`,
+		out: `<0>{E: <1>{F: <2>{a: "E", b: "F"}}, ` +
+			`a: <3>{` +
+			`E: <4>{b: <5>{F: <6>{a: "E", b: "F"}}}, ` +
+			`A: <7>{b: <8>{B: <9>{a: "A", b: "B"}}}, ` +
+			`C: <10>{b: <11>{D: <12>{a: "C", b: "D"}}}}, ` +
+			`A: <13>{B: <14>{a: "A", b: "B"}}, ` +
+			`C: <15>{D: <16>{a: "C", b: "D"}}}`,
+		// TODO: this order would be desirable.
+		// out: `<0>{a: <1>{` +
+		// 	`A: <2>{b: <3>{B: <4>{a: "A", b: "B"}}}, ` +
+		// 	`C: <5>{b: <6>{D: <7>{a: "C", b: "D"}}}, ` +
+		// 	`E: <8>{b: <9>{F: <10>{a: "E", b: "F"}}}}, ` +
+		// 	`A: <11>{B: <12>{a: "A", b: "B"}}, ` +
+		// 	`C: <13>{D: <14>{a: "C", b: "D"}}, ` +
+		// 	`E: <15>{F: <16>{a: "E", b: "F"}}}`,
 	}, {
 		desc: "field comprehensions with templates",
 		in: `
diff --git a/cue/value.go b/cue/value.go
index c1f442e..f15d7cf 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -19,7 +19,6 @@
 	"math/big"
 	"sort"
 	"strconv"
-	"strings"
 	"time"
 
 	"cuelang.org/go/cue/ast"
@@ -372,32 +371,6 @@
 	return int(v)
 }
 
-func intFromGo(s string) *numLit {
-	num := &numLit{}
-	num.k = intKind
-	var ok bool
-	switch {
-	case strings.HasPrefix(s, "0x"), strings.HasPrefix(s, "0X"):
-		_, ok = num.v.Coeff.SetString(s, 16)
-	case strings.HasPrefix(s, "0"):
-		_, ok = num.v.Coeff.SetString(s, 16)
-	default:
-		_, cond, err := num.v.SetString(s)
-		ok = cond == 0 && err == nil
-	}
-	if !ok {
-		panic(fmt.Sprintf("could not parse number %q", s))
-	}
-	return num
-}
-
-func floatFromGo(s string) *numLit {
-	num := &numLit{}
-	num.k = floatKind
-	num.v.SetString(s)
-	return num
-}
-
 type durationLit struct {
 	baseValue
 	d time.Duration
@@ -768,6 +741,10 @@
 type arc struct {
 	feature  label
 	optional bool
+	// TODO: add index to preserve approximate order within a struct and use
+	// topological sort to compute new struct order when unifying. This could
+	// also be achieved by not sorting labels on features and doing
+	// a linear search in fields.
 
 	v     value
 	cache evaluated // also used as newValue during unification.
diff --git a/doc/ref/spec.md b/doc/ref/spec.md
index 4f5783b..6ea7707 100644
--- a/doc/ref/spec.md
+++ b/doc/ref/spec.md
@@ -1108,12 +1108,12 @@
 
 An identifier or string label may be followed by a question mark `?`
 to indicate a field is optional.
+The question mark is not part of the field name.
 Constraints defined by an optional field should only be applied when
 a field is present.
-Fields with such markers may be omitted from output and should not cause
+A field with such a marker may be omitted from output and should not cause
 an error when emitting a concrete configuration, even if its value is
 not concrete or bottom.
-The question mark is not part of the field name.
 The result of unifying two fields only has an optional marker
 if both fields have such a marker.