internal/core/compile: add new compiler

compile: compiler, but still needs support for builtins and imports

debug: debug representation of internal structures

runtime: just string uniquing functionality for now

cuetxtar: added txtar support, including auto --update and --todo
support.

Change-Id: Idadf6045867b55381f0b3e0da3bc772fa40f7a04
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5840
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index c36ea9c..0a607ec 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -251,7 +251,7 @@
 //    =~"Name$"
 //
 type BoundValue struct {
-	Src   ast.Node
+	Src   ast.Expr
 	Op    Op
 	Value Value
 	K     Kind
@@ -467,6 +467,7 @@
 // A Conjunction is a conjunction of values that cannot be represented as a
 // single value. It is the result of unification.
 type Conjunction struct {
+	Src    ast.Expr
 	Values []Value
 }
 
diff --git a/internal/core/compile/compile.go b/internal/core/compile/compile.go
new file mode 100644
index 0000000..018b99b
--- /dev/null
+++ b/internal/core/compile/compile.go
@@ -0,0 +1,843 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compile
+
+import (
+	"fmt"
+	"strings"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/literal"
+	"cuelang.org/go/cue/token"
+	"cuelang.org/go/internal"
+	"cuelang.org/go/internal/core/adt"
+	"cuelang.org/go/internal/core/runtime"
+	"golang.org/x/xerrors"
+)
+
+// Config configures a compilation.
+type Config struct {
+}
+
+// Files compiles the given files as a single instance. It disregards
+// the package names and it is the responsibility of the user to verify that
+// the packages names are consistent.
+//
+// Files may return a completed parse even if it has errors.
+func Files(cfg *Config, r *runtime.Runtime, files ...*ast.File) (*adt.Vertex, errors.Error) {
+	c := &compiler{index: r}
+
+	v := c.compileFiles(files)
+
+	if c.errs != nil {
+		return v, c.errs
+	}
+	return v, nil
+}
+
+type compiler struct {
+	index adt.StringIndexer
+
+	stack      []frame
+	inSelector int
+
+	fileScope map[adt.Feature]bool
+
+	num literal.NumInfo
+
+	errs errors.Error
+}
+
+func (c *compiler) reset() {
+	c.fileScope = nil
+	c.stack = c.stack[:0]
+	c.errs = nil
+}
+
+func (c *compiler) errf(n ast.Node, format string, args ...interface{}) *adt.Bottom {
+	err := &compilerError{
+		n:       n,
+		path:    c.path(),
+		Message: errors.NewMessage(format, args),
+	}
+	c.errs = errors.Append(c.errs, err)
+	return &adt.Bottom{}
+}
+
+func (c *compiler) path() []string {
+	a := []string{}
+	for _, f := range c.stack {
+		if f.label != nil {
+			a = append(a, f.label.labelString())
+		}
+	}
+	return a
+}
+
+type frame struct {
+	label labeler  // path name leading to this frame.
+	scope ast.Node // *ast.File or *ast.Struct
+	field *ast.Field
+	// scope   map[ast.Node]bool
+	upCount int32 // 1 for field, 0 for embedding.
+
+	aliases map[string]aliasEntry
+}
+
+type aliasEntry struct {
+	expr   adt.Expr
+	source ast.Node
+	used   bool
+}
+
+func (c *compiler) insertAlias(id *ast.Ident, a aliasEntry) *adt.Bottom {
+	k := len(c.stack) - 1
+	m := c.stack[k].aliases
+	if m == nil {
+		m = map[string]aliasEntry{}
+		c.stack[k].aliases = m
+	}
+
+	if id == nil || !ast.IsValidIdent(id.Name) {
+		return c.errf(a.source, "invalid identifier name")
+	}
+
+	if e, ok := m[id.Name]; ok {
+		return c.errf(a.source,
+			"alias %q already declared; previous declaration at %s",
+			id.Name, e.source.Pos())
+	}
+
+	m[id.Name] = a
+	return nil
+}
+
+// lookupAlias looks up an alias with the given name at the k'th stack position.
+func (c compiler) lookupAlias(k int, id *ast.Ident) aliasEntry {
+	m := c.stack[k].aliases
+	name := id.Name
+	entry, ok := m[name]
+
+	if !ok {
+		err := c.errf(id, "could not find LetClause associated with identifier %q", name)
+		return aliasEntry{expr: err}
+	}
+
+	entry.used = true
+	m[name] = entry
+	return entry
+}
+
+func (c *compiler) pushScope(n labeler, upCount int32, id ast.Node) *frame {
+	c.stack = append(c.stack, frame{
+		label:   n,
+		scope:   id,
+		upCount: upCount,
+	})
+	return &c.stack[len(c.stack)-1]
+}
+
+func (c *compiler) popScope() {
+	k := len(c.stack) - 1
+	f := c.stack[k]
+	for k, v := range f.aliases {
+		if !v.used {
+			c.errf(v.source, "unreferenced alias or let clause %s", k)
+		}
+	}
+	c.stack = c.stack[:k]
+}
+
+// entry points // USE CONFIG
+func (c *compiler) compileFiles(a []*ast.File) *adt.Vertex { // Or value?
+	c.fileScope = map[adt.Feature]bool{}
+
+	// Populate file scope to handle unresolved references. Note that we do
+	// not allow aliases to be resolved across file boundaries.
+	for _, f := range a {
+		for _, d := range f.Decls {
+			if f, ok := d.(*ast.Field); ok {
+				if id, ok := f.Label.(*ast.Ident); ok {
+					c.fileScope[c.label(id)] = true
+				}
+			}
+		}
+	}
+
+	// TODO: set doc.
+	res := &adt.Vertex{}
+
+	// env := &adt.Environment{Vertex: nil} // runtime: c.runtime
+
+	for _, file := range a {
+		c.pushScope(nil, 0, file) // File scope
+		v := &adt.StructLit{Src: file}
+		c.addDecls(v, file.Decls)
+		res.Conjuncts = append(res.Conjuncts, adt.MakeConjunct(nil, v))
+		c.popScope()
+	}
+
+	return res
+}
+
+// resolve assumes that all existing resolutions are legal. Validation should
+// be done in a separate step if required.
+//
+// TODO: collect validation pass to verify that all resolutions are
+// legal?
+func (c *compiler) resolve(n *ast.Ident) adt.Expr {
+	// X in import "path/X"
+	// X in import X "path"
+	if imp, ok := n.Node.(*ast.ImportSpec); ok {
+		return &adt.ImportReference{
+			Src:        n,
+			ImportPath: c.label(imp.Path),
+			Label:      c.label(n),
+		}
+	}
+
+	label := c.label(n)
+
+	// Unresolved field.
+	if n.Node == nil {
+		upCount := int32(0)
+		for _, c := range c.stack {
+			upCount += c.upCount
+		}
+		if c.fileScope[label] {
+			return &adt.FieldReference{
+				Src:     n,
+				UpCount: upCount,
+				Label:   label,
+			}
+		}
+
+		if p := predeclared(n); p != nil {
+			return p
+		}
+
+		return c.errf(n, "reference %q not found", n.Name)
+	}
+
+	//   X in [X=x]: y  Scope: Field  Node: Expr (x)
+	//   X in X=[x]: y  Scope: Field  Node: Field
+	if f, ok := n.Scope.(*ast.Field); ok {
+		upCount := int32(0)
+
+		k := len(c.stack) - 1
+		for ; k >= 0; k-- {
+			if c.stack[k].field == f {
+				break
+			}
+			upCount += c.stack[k].upCount
+		}
+
+		label := &adt.LabelReference{
+			Src:     n,
+			UpCount: upCount,
+		}
+
+		if f, ok := n.Node.(*ast.Field); ok {
+			_ = c.lookupAlias(k, f.Label.(*ast.Alias).Ident) // mark as used
+			return &adt.DynamicReference{
+				Src:     n,
+				UpCount: upCount,
+				Label:   label,
+			}
+		}
+		return label
+	}
+
+	upCount := int32(0)
+
+	k := len(c.stack) - 1
+	for ; k >= 0; k-- {
+		if c.stack[k].scope == n.Scope {
+			break
+		}
+		upCount += c.stack[k].upCount
+	}
+	if k < 0 {
+		// This is a programmatic error and should never happen if the users
+		// just builds with the cue command or if astutil.Resolve is used
+		// correctly.
+		c.errf(n, "reference %q set to unknown node in AST; "+
+			"this can result from incorrect API usage or a compiler bug",
+			n.Name)
+	}
+
+	switch n.Node.(type) {
+	// Local expressions
+	case *ast.LetClause, *ast.Alias:
+		entry := c.lookupAlias(k, n)
+
+		return &adt.LetReference{
+			Src:     n,
+			UpCount: upCount,
+			Label:   label,
+			X:       entry.expr,
+		}
+	}
+
+	if n.Scope == nil {
+		// Package.
+		// Should have been handled above.
+		panic("unreachable") // Or direct ancestor node?
+	}
+
+	// X=x: y
+	// X=(x): y
+	// X="\(x)": y
+	if f, ok := n.Node.(*ast.Field); ok {
+		a, ok := f.Label.(*ast.Alias)
+		if !ok {
+			return c.errf(n, "illegal reference %s", n.Name)
+		}
+		aliasInfo := c.lookupAlias(k, a.Ident) // marks alias as used.
+		lab, ok := a.Expr.(ast.Label)
+		if !ok {
+			return c.errf(a.Expr, "invalid label expression")
+		}
+		name, _, err := ast.LabelName(lab)
+		switch {
+		case xerrors.Is(err, ast.ErrIsExpression):
+			if aliasInfo.expr == nil {
+				panic("unreachable")
+			}
+			return &adt.DynamicReference{
+				Src:     n,
+				UpCount: upCount,
+				Label:   aliasInfo.expr,
+			}
+
+		case err != nil:
+			return c.errf(n, "invalid label: %v", err)
+
+		case name != "":
+			label = c.label(lab)
+
+		default:
+			return c.errf(n, "unsupported field alias %q", name)
+		}
+	}
+
+	return &adt.FieldReference{
+		Src:     n,
+		UpCount: upCount,
+		Label:   label,
+	}
+}
+
+func (c *compiler) addDecls(st *adt.StructLit, a []ast.Decl) {
+	for _, d := range a {
+		if x := c.decl(d); x != nil {
+			st.Decls = append(st.Decls, x)
+		}
+	}
+}
+
+func (c *compiler) decl(d ast.Decl) adt.Decl {
+	switch x := d.(type) {
+	case *ast.BadDecl:
+		return c.errf(d, "")
+
+	case *ast.Field:
+		lab := x.Label
+		if a, ok := lab.(*ast.Alias); ok {
+			if lab, ok = a.Expr.(ast.Label); !ok {
+				return c.errf(a, "alias expression is not a valid label")
+			}
+
+			e := aliasEntry{source: a}
+
+			switch lab.(type) {
+			case *ast.Ident, *ast.BasicLit:
+				// Even though we won't need the alias, we still register it
+				// for duplicate and failed reference detection.
+			default:
+				e.expr = c.expr(a.Expr)
+			}
+
+			if err := c.insertAlias(a.Ident, e); err != nil {
+				return err
+			}
+		}
+
+		value := c.labeledExpr(x, (*fieldLabel)(x), x.Value)
+
+		switch l := lab.(type) {
+		case *ast.Ident, *ast.BasicLit:
+			label := c.label(lab)
+
+			// TODO(legacy): remove: old-school definitions
+			if x.Token == token.ISA && !label.IsDef() {
+				name, isIdent, err := ast.LabelName(lab)
+				if err == nil && isIdent {
+					idx := c.index.StringToIndex(name)
+					label, _ = adt.MakeLabel(x.Pos(), idx, adt.DefinitionLabel)
+				}
+			}
+
+			if x.Optional == token.NoPos {
+				return &adt.Field{
+					Src:   x,
+					Label: label,
+					Value: value,
+				}
+			} else {
+				return &adt.OptionalField{
+					Src:   x,
+					Label: label,
+					Value: value,
+				}
+			}
+
+		case *ast.ListLit:
+			if len(l.Elts) != 1 {
+				// error
+				return c.errf(x, "list label must have one element")
+			}
+			elem := l.Elts[0]
+			// TODO: record alias for error handling? In principle it is okay
+			// to have duplicates, but we do want it to be used.
+			if a, ok := elem.(*ast.Alias); ok {
+				elem = a.Expr
+			}
+
+			return &adt.BulkOptionalField{
+				Src:    x,
+				Filter: c.expr(elem),
+				Value:  value,
+			}
+
+		case *ast.Interpolation: // *ast.ParenExpr,
+			if x.Token == token.ISA {
+				c.errf(x, "definitions not supported for interpolations")
+			}
+			return &adt.DynamicField{
+				Src:   x,
+				Key:   c.expr(l),
+				Value: value,
+			}
+		}
+
+	// An alias reference will have an expression that is looked up in the
+	// environment cash.
+	case *ast.LetClause:
+		// Cache the parsed expression. Creating a unique expression for each
+		// reference allows the computation to be shared given that we don't
+		// have fields for expressions. This, in turn, prevents exponential
+		// blowup. in x2: x1+x1, x3: x2+x2, ... patterns.
+
+		expr := c.labeledExpr(nil, (*letScope)(x), x.Expr)
+
+		a := aliasEntry{source: x, expr: expr}
+
+		if err := c.insertAlias(x.Ident, a); err != nil {
+			return err
+		}
+
+	case *ast.Alias:
+
+		expr := c.labeledExpr(nil, (*deprecatedAliasScope)(x), x.Expr)
+
+		// TODO(legacy): deprecated, remove this use of Alias
+		a := aliasEntry{source: x, expr: expr}
+
+		if err := c.insertAlias(x.Ident, a); err != nil {
+			return err
+		}
+
+	case *ast.CommentGroup:
+		// Nothing to do for a free-floating comment group.
+
+	case *ast.Attribute:
+		// Nothing to do for now for an attribute declaration.
+
+	case *ast.Ellipsis:
+		return &adt.Ellipsis{
+			Src:   x,
+			Value: c.expr(x.Type),
+		}
+
+	case *ast.Comprehension:
+		return c.comprehension(x)
+
+	case *ast.EmbedDecl: // Deprecated
+		return c.embed(x.Expr)
+
+	case ast.Expr:
+		return c.embed(x)
+	}
+	return nil
+}
+
+func (c *compiler) elem(n ast.Expr) adt.Elem {
+	switch x := n.(type) {
+	case *ast.Ellipsis:
+		return &adt.Ellipsis{
+			Src:   x,
+			Value: c.expr(x.Type),
+		}
+
+	case *ast.Comprehension:
+		return c.comprehension(x)
+
+	case ast.Expr:
+		return c.expr(x)
+	}
+	return nil
+}
+
+func (c *compiler) comprehension(x *ast.Comprehension) adt.Elem {
+	var cur adt.Yielder
+	var first adt.Elem
+	var prev, next *adt.Yielder
+	for _, v := range x.Clauses {
+		switch x := v.(type) {
+		case *ast.ForClause:
+			var key adt.Feature
+			if x.Key != nil {
+				key = c.label(x.Key)
+			}
+			y := &adt.ForClause{
+				Syntax: x,
+				Key:    key,
+				Value:  c.label(x.Value),
+				Src:    c.expr(x.Source),
+			}
+			cur = y
+			c.pushScope((*forScope)(x), 1, v)
+			defer c.popScope()
+			next = &y.Dst
+
+		case *ast.IfClause:
+			y := &adt.IfClause{
+				Src:       x,
+				Condition: c.expr(x.Condition),
+			}
+			cur = y
+			next = &y.Dst
+
+		case *ast.LetClause:
+			y := &adt.LetClause{
+				Src:   x,
+				Label: c.label(x.Ident),
+				Expr:  c.expr(x.Expr),
+			}
+			cur = y
+			c.pushScope((*letScope)(x), 1, v)
+			defer c.popScope()
+			next = &y.Dst
+		}
+
+		if prev != nil {
+			*prev = cur
+		} else {
+			var ok bool
+			if first, ok = cur.(adt.Elem); !ok {
+				return c.errf(x,
+					"first comprehension clause must be 'if' or 'for'")
+			}
+		}
+		prev = next
+	}
+
+	if y, ok := x.Value.(*ast.StructLit); !ok {
+		return c.errf(x.Value,
+			"comprehension value must be struct, found %T", y)
+	}
+
+	y := c.expr(x.Value)
+
+	st, ok := y.(*adt.StructLit)
+	if !ok {
+		// Error must have been generated.
+		return y
+	}
+
+	if prev != nil {
+		*prev = &adt.ValueClause{StructLit: st}
+	} else {
+		return c.errf(x, "comprehension value without clauses")
+	}
+
+	return first
+}
+
+func (c *compiler) embed(expr ast.Expr) adt.Expr {
+	switch n := expr.(type) {
+	case *ast.StructLit:
+		c.pushScope(nil, 1, n)
+		v := &adt.StructLit{Src: n}
+		c.addDecls(v, n.Elts)
+		c.popScope()
+		return v
+	}
+	return c.expr(expr)
+}
+
+func (c *compiler) labeledExpr(f *ast.Field, lab labeler, expr ast.Expr) adt.Expr {
+	k := len(c.stack) - 1
+	if c.stack[k].field != nil {
+		panic("expected nil field")
+	}
+	c.stack[k].label = lab
+	c.stack[k].field = f
+	value := c.expr(expr)
+	c.stack[k].label = nil
+	c.stack[k].field = nil
+	return value
+}
+
+func (c *compiler) expr(expr ast.Expr) adt.Expr {
+	switch n := expr.(type) {
+	case nil:
+		return nil
+	case *ast.Ident:
+		return c.resolve(n)
+
+	case *ast.StructLit:
+		c.pushScope(nil, 1, n)
+		v := &adt.StructLit{Src: n}
+		c.addDecls(v, n.Elts)
+		c.popScope()
+		return v
+
+	case *ast.ListLit:
+		v := &adt.ListLit{Src: n}
+		elts, ellipsis := internal.ListEllipsis(n)
+		for _, d := range elts {
+			elem := c.elem(d)
+
+			switch x := elem.(type) {
+			case nil:
+			case adt.Elem:
+				v.Elems = append(v.Elems, x)
+			default:
+				c.errf(d, "type %T not allowed in ListLit", d)
+			}
+		}
+		if ellipsis != nil {
+			d := &adt.Ellipsis{
+				Src:   ellipsis,
+				Value: c.expr(ellipsis.Type),
+			}
+			v.Elems = append(v.Elems, d)
+		}
+		return v
+
+	case *ast.SelectorExpr:
+		c.inSelector++
+		ret := &adt.SelectorExpr{
+			Src: n,
+			X:   c.expr(n.X),
+			Sel: c.label(n.Sel)}
+		c.inSelector--
+		return ret
+
+	case *ast.IndexExpr:
+		return &adt.IndexExpr{
+			Src:   n,
+			X:     c.expr(n.X),
+			Index: c.expr(n.Index),
+		}
+
+	case *ast.SliceExpr:
+		slice := &adt.SliceExpr{Src: n, X: c.expr(n.X)}
+		if n.Low != nil {
+			slice.Lo = c.expr(n.Low)
+		}
+		if n.High != nil {
+			slice.Hi = c.expr(n.High)
+		}
+		return slice
+
+	case *ast.BottomLit:
+		return &adt.Bottom{Src: n}
+
+	case *ast.BadExpr:
+		return c.errf(n, "invalid expression")
+
+	case *ast.BasicLit:
+		return c.parse(n)
+
+	case *ast.Interpolation:
+		if len(n.Elts) == 0 {
+			return c.errf(n, "invalid interpolation")
+		}
+		first, ok1 := n.Elts[0].(*ast.BasicLit)
+		last, ok2 := n.Elts[len(n.Elts)-1].(*ast.BasicLit)
+		if !ok1 || !ok2 {
+			return c.errf(n, "invalid interpolation")
+		}
+		if len(n.Elts) == 1 {
+			return c.expr(n.Elts[0])
+		}
+		lit := &adt.Interpolation{Src: n, K: adt.StringKind}
+		info, prefixLen, _, err := literal.ParseQuotes(first.Value, last.Value)
+		if err != nil {
+			return c.errf(n, "invalid interpolation: %v", err)
+		}
+		prefix := ""
+		for i := 0; i < len(n.Elts); i += 2 {
+			l, ok := n.Elts[i].(*ast.BasicLit)
+			if !ok {
+				return c.errf(n, "invalid interpolation")
+			}
+			s := l.Value
+			if !strings.HasPrefix(s, prefix) {
+				return c.errf(l, "invalid interpolation: unmatched ')'")
+			}
+			s = l.Value[prefixLen:]
+			x := parseString(c, l, info, s)
+			lit.Parts = append(lit.Parts, x)
+			if i+1 < len(n.Elts) {
+				lit.Parts = append(lit.Parts, c.expr(n.Elts[i+1]))
+			}
+			prefix = ")"
+			prefixLen = 1
+		}
+		return lit
+
+	case *ast.ParenExpr:
+		return c.expr(n.X)
+
+	case *ast.CallExpr:
+		call := &adt.CallExpr{Src: n, Fun: c.expr(n.Fun)}
+		for _, a := range n.Args {
+			call.Args = append(call.Args, c.expr(a))
+		}
+		return call
+
+	case *ast.UnaryExpr:
+		switch n.Op {
+		case token.NOT, token.ADD, token.SUB:
+			return &adt.UnaryExpr{
+				Src: n,
+				Op:  adt.OpFromToken(n.Op),
+				X:   c.expr(n.X),
+			}
+		case token.GEQ, token.GTR, token.LSS, token.LEQ,
+			token.NEQ, token.MAT, token.NMAT:
+			return &adt.BoundExpr{
+				Src:  n,
+				Op:   adt.OpFromToken(n.Op),
+				Expr: c.expr(n.X),
+			}
+
+		case token.MUL:
+			return c.errf(n, "preference mark not allowed at this position")
+		default:
+			return c.errf(n, "unsupported unary operator %q", n.Op)
+		}
+
+	case *ast.BinaryExpr:
+		switch n.Op {
+		case token.OR:
+			d := &adt.DisjunctionExpr{Src: n}
+			c.addDisjunctionElem(d, n.X, false)
+			c.addDisjunctionElem(d, n.Y, false)
+			return d
+
+		default:
+			// return updateBin(c,
+			return &adt.BinaryExpr{
+				Src: n,
+				Op:  adt.OpFromToken(n.Op), // op
+				X:   c.expr(n.X),           // left
+				Y:   c.expr(n.Y),           // right
+			} // )
+		}
+
+	default:
+		panic(fmt.Sprintf("unknown expression type %T", n))
+		// return c.errf(n, "unknown expression type %T", n)
+	}
+}
+
+func (c *compiler) addDisjunctionElem(d *adt.DisjunctionExpr, n ast.Expr, mark bool) {
+	switch x := n.(type) {
+	case *ast.BinaryExpr:
+		if x.Op == token.OR {
+			c.addDisjunctionElem(d, x.X, mark)
+			c.addDisjunctionElem(d, x.Y, mark)
+			return
+		}
+	case *ast.UnaryExpr:
+		if x.Op == token.MUL {
+			d.HasDefaults = true
+			c.addDisjunctionElem(d, x.X, true)
+			return
+		}
+	}
+	d.Values = append(d.Values, adt.Disjunct{Val: c.expr(n), Default: mark})
+}
+
+func (c *compiler) parse(l *ast.BasicLit) (n adt.Expr) {
+	s := l.Value
+	if s == "" {
+		return c.errf(l, "invalid literal %q", s)
+	}
+	switch l.Kind {
+	case token.STRING:
+		info, nStart, _, err := literal.ParseQuotes(s, s)
+		if err != nil {
+			return c.errf(l, err.Error())
+		}
+		s := s[nStart:]
+		return parseString(c, l, info, s)
+
+	case token.FLOAT, token.INT:
+		err := literal.ParseNum(s, &c.num)
+		if err != nil {
+			return c.errf(l, "parse error: %v", err)
+		}
+		kind := adt.FloatKind
+		if c.num.IsInt() {
+			kind = adt.IntKind
+		}
+		n := &adt.Num{Src: l, K: kind}
+		if err = c.num.Decimal(&n.X); err != nil {
+			return c.errf(l, "error converting number to decimal: %v", err)
+		}
+		return n
+
+	case token.TRUE:
+		return &adt.Bool{Src: l, B: true}
+
+	case token.FALSE:
+		return &adt.Bool{Src: l, B: false}
+
+	case token.NULL:
+		return &adt.Null{Src: l}
+
+	default:
+		return c.errf(l, "unknown literal type")
+	}
+}
+
+// parseString decodes a string without the starting and ending quotes.
+func parseString(c *compiler, node ast.Expr, q literal.QuoteInfo, s string) (n adt.Expr) {
+	str, err := q.Unquote(s)
+	if err != nil {
+		return c.errf(node, "invalid string: %v", err)
+	}
+	if q.IsDouble() {
+		return &adt.String{Src: node, Str: str, RE: nil}
+	}
+	return &adt.Bytes{Src: node, B: []byte(str), RE: nil}
+}
diff --git a/internal/core/compile/compile_test.go b/internal/core/compile/compile_test.go
new file mode 100644
index 0000000..4f78d33
--- /dev/null
+++ b/internal/core/compile/compile_test.go
@@ -0,0 +1,121 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compile_test
+
+import (
+	"flag"
+	"fmt"
+	"testing"
+
+	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/parser"
+	"cuelang.org/go/internal/core/compile"
+	"cuelang.org/go/internal/core/debug"
+	"cuelang.org/go/internal/core/runtime"
+	"cuelang.org/go/internal/cuetxtar"
+	"cuelang.org/go/pkg/strings"
+)
+
+var (
+	update = flag.Bool("update", false, "update the test files")
+	todo   = flag.Bool("todo", false, "run tests marked with #todo-compile")
+)
+
+func TestCompile(t *testing.T) {
+	test := cuetxtar.TxTarTest{
+		Root:   "../../../cue/testdata/",
+		Name:   "compile",
+		Update: *update,
+		Skip:   alwaysSkip,
+		ToDo:   needFix,
+	}
+
+	if *todo {
+		test.ToDo = nil
+	}
+
+	r := runtime.New()
+
+	test.Run(t, func(t *cuetxtar.Test) {
+		// TODO: use high-level API.
+
+		a := t.ValidInstances()
+
+		v, err := compile.Files(nil, r, a[0].Files...)
+
+		// Write the results.
+		t.WriteErrors(err)
+
+		if v == nil {
+			return
+		}
+
+		for i, f := range a[0].Files {
+			if i > 0 {
+				fmt.Fprintln(t)
+			}
+			fmt.Fprintln(t, "---", t.Rel(f.Filename))
+			debug.WriteNode(t, r, v.Conjuncts[i].Expr(), &debug.Config{
+				Cwd: t.Dir,
+			})
+		}
+		fmt.Fprintln(t)
+	})
+}
+
+var alwaysSkip = map[string]string{
+	"fulleval/031_comparison against bottom": "fix bin op binding in test",
+}
+
+var needFix = map[string]string{
+	"export/020":                           "builtin",
+	"fulleval/027_len_of_incomplete_types": "builtin",
+	"fulleval/032_or_builtin_should_not_fail_on_non-concrete_empty_list": "builtin",
+	"fulleval/053_issue312":       "builtin",
+	"resolve/034_closing_structs": "builtin",
+	"resolve/048_builtins":        "builtin",
+
+	"fulleval/026_dont_convert_incomplete_errors_to_non-incomplete": "import",
+	"fulleval/044_Issue_#178":                              "import",
+	"fulleval/048_dont_pass_incomplete_values_to_builtins": "import",
+	"fulleval/049_alias_reuse_in_nested_scope":             "import",
+	"fulleval/050_json_Marshaling_detects_incomplete":      "import",
+	"fulleval/051_detectIncompleteYAML":                    "import",
+	"fulleval/052_detectIncompleteJSON":                    "import",
+	"fulleval/056_issue314":                                "import",
+	"resolve/013_custom_validators":                        "import",
+}
+
+// TestX is for debugging. Do not delete.
+func TestX(t *testing.T) {
+	in := `
+	`
+
+	if strings.TrimSpace(in) == "" {
+		t.Skip()
+	}
+
+	file, err := parser.ParseFile("TestX", in)
+	if err != nil {
+		t.Fatal(err)
+	}
+	r := runtime.New()
+
+	arc, err := compile.Files(nil, r, file)
+	if err != nil {
+		t.Error(errors.Details(err, nil))
+	}
+	t.Error(debug.NodeString(r, arc.Conjuncts[0].Expr(), nil))
+}
diff --git a/internal/core/compile/errors.go b/internal/core/compile/errors.go
new file mode 100644
index 0000000..fb97622
--- /dev/null
+++ b/internal/core/compile/errors.go
@@ -0,0 +1,48 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compile
+
+import (
+	"strings"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/token"
+)
+
+var _ errors.Error = &compilerError{}
+
+type compilerError struct {
+	n    ast.Node
+	path []string
+	errors.Message
+}
+
+func (e *compilerError) Position() token.Pos         { return e.n.Pos() }
+func (e *compilerError) InputPositions() []token.Pos { return nil }
+func (e *compilerError) Path() []string              { return e.path }
+func (e *compilerError) Error() string {
+	pos := e.n.Pos()
+	// Import cycles deserve special treatment.
+	if pos.IsValid() {
+		// Omit import stack. The full path to the file where the error
+		// is the most important thing.
+		return pos.String() + ": " + e.Message.Error()
+	}
+	if len(e.path) == 0 {
+		return e.Message.Error()
+	}
+	return strings.Join(e.path, ".") + ": " + e.Message.Error()
+}
diff --git a/internal/core/compile/label.go b/internal/core/compile/label.go
new file mode 100644
index 0000000..1c03298
--- /dev/null
+++ b/internal/core/compile/label.go
@@ -0,0 +1,173 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compile
+
+import (
+	"strings"
+
+	"github.com/cockroachdb/apd/v2"
+	"golang.org/x/text/unicode/norm"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/literal"
+	"cuelang.org/go/cue/token"
+	"cuelang.org/go/internal/core/adt"
+)
+
+// LabelFromNode converts an ADT node to a feature.
+func (c *compiler) label(n ast.Node) adt.Feature {
+	index := c.index
+	switch x := n.(type) {
+	case *ast.Ident:
+		s := x.Name
+		i := index.StringToIndex(x.Name)
+		t := adt.StringLabel
+		switch {
+		case strings.HasPrefix(s, "#_"):
+			t = adt.HiddenDefinitionLabel
+		case strings.HasPrefix(s, "#"):
+			t = adt.DefinitionLabel
+		case strings.HasPrefix(s, "_"):
+			t = adt.HiddenLabel
+		}
+		f, err := adt.MakeLabel(n.Pos(), i, t)
+		if err != nil {
+			c.errf(n, "invalid identifier label: %v", err)
+			return adt.InvalidLabel
+		}
+		return f
+
+	case *ast.BasicLit:
+		switch x.Kind {
+		case token.STRING:
+			const msg = "invalid string label: %v"
+			s, err := literal.Unquote(x.Value)
+			if err != nil {
+				c.errf(n, msg, err)
+				return adt.InvalidLabel
+			}
+
+			i := int64(index.StringToIndex(norm.NFC.String(s)))
+			f, err := adt.MakeLabel(n.Pos(), i, adt.StringLabel)
+			if err != nil {
+				c.errf(n, msg, err)
+			}
+			return f
+
+		case token.INT:
+			const msg = "invalid int label: %v"
+			if err := literal.ParseNum(x.Value, &c.num); err != nil {
+				c.errf(n, msg, err)
+				return adt.InvalidLabel
+			}
+
+			var d apd.Decimal
+			if err := c.num.Decimal(&d); err != nil {
+				c.errf(n, msg, err)
+				return adt.InvalidLabel
+			}
+
+			i, err := d.Int64()
+			if err != nil {
+				c.errf(n, msg, err)
+				return adt.InvalidLabel
+			}
+
+			f, err := adt.MakeLabel(n.Pos(), i, adt.IntLabel)
+			if err != nil {
+				c.errf(n, msg, err)
+				return adt.InvalidLabel
+			}
+			return f
+
+		case token.FLOAT:
+			_ = c.errf(n, "float %s cannot be used as label", x.Value)
+			return adt.InvalidLabel
+
+		default: // keywords (null, true, false, for, in, if, let)
+			i := index.StringToIndex(x.Kind.String())
+			f, err := adt.MakeLabel(n.Pos(), i, adt.StringLabel)
+			if err != nil {
+				c.errf(n, "invalid string label: %v", err)
+			}
+			return f
+		}
+
+	default:
+		c.errf(n, "unsupported label node type %T", n)
+		return adt.InvalidLabel
+	}
+}
+
+// A labeler converts an AST node to a string representation.
+type labeler interface {
+	labelString() string
+}
+
+type fieldLabel ast.Field
+
+func (l *fieldLabel) labelString() string {
+	lab := l.Label
+
+	if a, ok := lab.(*ast.Alias); ok {
+		if x, _ := a.Expr.(ast.Label); x != nil {
+			lab = x
+		}
+	}
+
+	switch x := lab.(type) {
+	case *ast.Ident:
+		return x.Name
+
+	case *ast.BasicLit:
+		if x.Kind == token.STRING {
+			s, err := literal.Unquote(x.Value)
+			if err == nil && ast.IsValidIdent(s) {
+				return s
+			}
+		}
+		return x.Value
+
+	case *ast.ListLit:
+		return "[]" // TODO: more detail
+
+	case *ast.Interpolation:
+		return "?"
+		// case *ast.ParenExpr:
+	}
+	return "<unknown>"
+}
+
+type forScope ast.ForClause
+
+func (l *forScope) labelString() string {
+	// TODO: include more info in square brackets.
+	return "for[]"
+}
+
+type letScope ast.LetClause
+
+func (l *letScope) labelString() string {
+	// TODO: include more info in square brackets.
+	return "let[]"
+}
+
+// TODO(legacy): remove
+type deprecatedAliasScope ast.Alias
+
+func (l *deprecatedAliasScope) labelString() string {
+	// TODO: include more info in square brackets.
+	return "let[]"
+}
diff --git a/internal/core/compile/predeclared.go b/internal/core/compile/predeclared.go
new file mode 100644
index 0000000..fcadc36
--- /dev/null
+++ b/internal/core/compile/predeclared.go
@@ -0,0 +1,138 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compile
+
+import (
+	"strconv"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/token"
+	"cuelang.org/go/internal/core/adt"
+)
+
+func predeclared(n *ast.Ident) adt.Expr {
+	// TODO: consider supporting GraphQL-style names:
+	// String, Bytes, Boolean, Integer, Number.
+	// These names will not conflict with idiomatic camel-case JSON.
+	switch n.Name {
+	case "_":
+		return &adt.Top{Src: n}
+	case "string", "__string":
+		return &adt.BasicType{Src: n, K: adt.StringKind}
+	case "bytes", "__bytes":
+		return &adt.BasicType{Src: n, K: adt.BytesKind}
+	case "bool", "__bool":
+		return &adt.BasicType{Src: n, K: adt.BoolKind}
+	case "int", "__int":
+		return &adt.BasicType{Src: n, K: adt.IntKind}
+	case "float", "__float":
+		return &adt.BasicType{Src: n, K: adt.FloatKind}
+	case "number", "__number":
+		return &adt.BasicType{Src: n, K: adt.NumKind}
+
+		// case "len", "__len":
+		// 	return lenBuiltin
+		// case "close", "__close":
+		// 	return closeBuiltin
+		// case "and", "__and":
+		// 	return andBuiltin
+		// case "or", "__or":
+		// 	return orBuiltin
+	}
+
+	if r, ok := predefinedRanges[n.Name]; ok {
+		return r
+	}
+
+	return nil
+}
+
+var predefinedRanges = map[string]adt.Expr{
+	"rune":  mkIntRange("0", strconv.Itoa(0x10FFFF)),
+	"int8":  mkIntRange("-128", "127"),
+	"int16": mkIntRange("-32768", "32767"),
+	"int32": mkIntRange("-2147483648", "2147483647"),
+	"int64": mkIntRange("-9223372036854775808", "9223372036854775807"),
+	"int128": mkIntRange(
+		"-170141183460469231731687303715884105728",
+		"170141183460469231731687303715884105727"),
+
+	// Do not include an alias for "byte", as it would be too easily confused
+	// with the builtin "bytes".
+	"uint":    mkUint(),
+	"uint8":   mkIntRange("0", "255"),
+	"uint16":  mkIntRange("0", "65535"),
+	"uint32":  mkIntRange("0", "4294967295"),
+	"uint64":  mkIntRange("0", "18446744073709551615"),
+	"uint128": mkIntRange("0", "340282366920938463463374607431768211455"),
+
+	// 2**127 * (2**24 - 1) / 2**23
+	"float32": mkFloatRange(
+		"-3.40282346638528859811704183484516925440e+38",
+		"3.40282346638528859811704183484516925440e+38",
+	),
+	// 2**1023 * (2**53 - 1) / 2**52
+	"float64": mkFloatRange(
+		"-1.797693134862315708145274237317043567981e+308",
+		"1.797693134862315708145274237317043567981e+308",
+	),
+}
+
+// TODO: use an adt.BoundValue here.
+
+func mkUint() adt.Expr {
+	from := newBound(adt.GreaterEqualOp, adt.IntKind, parseInt("0"))
+	ident := ast.NewIdent("__int")
+	src := ast.NewBinExpr(token.AND, ident, from.Src)
+	return &adt.Conjunction{
+		Src: src,
+		Values: []adt.Value{
+			&adt.BasicType{Src: ident, K: adt.IntKind}, from,
+		},
+	}
+}
+
+func mkIntRange(a, b string) adt.Expr {
+	from := newBound(adt.GreaterEqualOp, adt.IntKind, parseInt(a))
+	to := newBound(adt.LessEqualOp, adt.IntKind, parseInt(b))
+	return &adt.BinaryExpr{nil, adt.AndOp, from, to}
+}
+
+func mkFloatRange(a, b string) adt.Expr {
+	from := newBound(adt.GreaterEqualOp, adt.NumKind, parseFloat(a))
+	to := newBound(adt.LessEqualOp, adt.NumKind, parseFloat(b))
+	return &adt.BinaryExpr{nil, adt.AndOp, from, to}
+}
+
+func newBound(op adt.Op, k adt.Kind, v adt.Value) *adt.BoundValue {
+	return &adt.BoundValue{Op: op, Value: v}
+}
+
+func parseInt(s string) *adt.Num {
+	return parseNum(adt.IntKind, s)
+}
+
+func parseFloat(s string) *adt.Num {
+	return parseNum(adt.FloatKind, s)
+}
+
+func parseNum(k adt.Kind, s string) *adt.Num {
+	num := &adt.Num{K: k}
+	_, _, err := num.X.SetString(s)
+	if err != nil {
+		panic(err)
+	}
+	return num
+}
diff --git a/internal/core/debug/debug.go b/internal/core/debug/debug.go
new file mode 100644
index 0000000..c0a5d1d
--- /dev/null
+++ b/internal/core/debug/debug.go
@@ -0,0 +1,450 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package debug prints a given ADT node.
+//
+// Note that the result is not valid CUE, but instead prints the internals
+// of an ADT node in human-readable form. It uses a simple indentation algorithm
+// for improved readability and diffing.
+//
+package debug
+
+import (
+	"fmt"
+	"io"
+	"strconv"
+	"strings"
+
+	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/internal"
+	"cuelang.org/go/internal/core/adt"
+	"golang.org/x/xerrors"
+)
+
+const (
+	openTuple  = "\u3008"
+	closeTuple = "\u3009"
+)
+
+type Config struct {
+	Cwd string
+}
+
+func WriteNode(w io.Writer, i adt.StringIndexer, n adt.Node, config *Config) {
+	if config == nil {
+		config = &Config{}
+	}
+	p := printer{Writer: w, index: i, cfg: config}
+	p.node(n)
+}
+
+func NodeString(i adt.StringIndexer, n adt.Node, config *Config) string {
+	if config == nil {
+		config = &Config{}
+	}
+	b := &strings.Builder{}
+	p := printer{Writer: b, index: i, cfg: config}
+	p.node(n)
+	return b.String()
+}
+
+type printer struct {
+	io.Writer
+	index  adt.StringIndexer
+	indent string
+	cfg    *Config
+}
+
+func (w *printer) string(s string) {
+	s = strings.Replace(s, "\n", "\n"+w.indent, -1)
+	_, _ = io.WriteString(w, s)
+}
+
+func (w *printer) label(f adt.Feature) {
+	w.string(w.labelString(f))
+}
+
+// TODO: fold into label once :: is no longer supported.
+func (w *printer) labelString(f adt.Feature) string {
+	return f.SelectorString(w.index)
+}
+
+func (w *printer) shortError(errs errors.Error) {
+	for {
+		msg, args := errs.Msg()
+		fmt.Fprintf(w, msg, args...)
+
+		err := xerrors.Unwrap(errs)
+		if err == nil {
+			break
+		}
+
+		if errs, _ = err.(errors.Error); errs != nil {
+			w.string(err.Error())
+			break
+		}
+	}
+}
+
+func (w *printer) node(n adt.Node) {
+	switch x := n.(type) {
+	case *adt.Vertex:
+		var kind adt.Kind
+		if x.Value != nil {
+			kind = x.Value.Kind()
+		}
+
+		kindStr := kind.String()
+		kindStr = strings.ReplaceAll(kindStr, "{...}", "struct")
+		kindStr = strings.ReplaceAll(kindStr, "[...]", "list")
+
+		fmt.Fprintf(w, "(%s){", kindStr)
+
+		if x.Value != nil && kind&^(adt.StructKind|adt.ListKind) != 0 {
+			w.string(" ")
+			w.node(x.Value)
+			w.string(" }")
+			return
+		}
+
+		saved := w.indent
+		w.indent += "  "
+
+		if b, ok := x.Value.(*adt.Bottom); ok {
+			saved := w.indent
+			w.indent += "// "
+			w.string("\n")
+			w.string(strings.TrimSpace(errors.Details(b.Err, &errors.Config{
+				Cwd:     w.cfg.Cwd,
+				ToSlash: true,
+			})))
+			w.indent = saved
+		}
+
+		if len(x.Arcs) > 0 {
+			for _, a := range x.Arcs {
+				w.string("\n")
+				w.label(a.Label)
+				w.string(": ")
+				w.node(a)
+			}
+		}
+
+		if x.Value == nil {
+			w.indent += "// "
+			w.string("// ")
+			for i, c := range x.Conjuncts {
+				if i > 0 {
+					w.string(" & ")
+				}
+				w.node(c.Expr()) // TODO: also include env?
+			}
+		}
+
+		w.indent = saved
+		w.string("\n")
+		w.string("}")
+
+	case *adt.StructMarker:
+		w.string("struct")
+
+	case *adt.ListMarker:
+		w.string("list")
+
+	case *adt.StructLit:
+		if len(x.Decls) == 0 {
+			w.string("{}")
+			break
+		}
+		w.string("{")
+		w.indent += "  "
+		for _, d := range x.Decls {
+			w.string("\n")
+			w.node(d)
+		}
+		w.indent = w.indent[:len(w.indent)-2]
+		w.string("\n}")
+
+	case *adt.ListLit:
+		if len(x.Elems) == 0 {
+			w.string("[]")
+			break
+		}
+		w.string("[")
+		w.indent += "  "
+		for _, d := range x.Elems {
+			w.string("\n")
+			w.node(d)
+			w.string(",")
+		}
+		w.indent = w.indent[:len(w.indent)-2]
+		w.string("\n]")
+
+	case *adt.Field:
+		s := w.labelString(x.Label)
+		w.string(s)
+		w.string(":")
+		if x.Label.IsDef() && !internal.IsDef(s) {
+			w.string(":")
+		}
+		w.string(" ")
+		w.node(x.Value)
+
+	case *adt.OptionalField:
+		s := w.labelString(x.Label)
+		w.string(s)
+		w.string("?:")
+		if x.Label.IsDef() && !internal.IsDef(s) {
+			w.string(":")
+		}
+		w.string(" ")
+		w.node(x.Value)
+
+	case *adt.BulkOptionalField:
+		w.string("[")
+		w.node(x.Filter)
+		w.string("]: ")
+		w.node(x.Value)
+
+	case *adt.DynamicField:
+		w.node(x.Key)
+		if x.IsOptional() {
+			w.string("?")
+		}
+		w.string(": ")
+		w.node(x.Value)
+
+	case *adt.Ellipsis:
+		w.string("...")
+		if x.Value != nil {
+			w.node(x.Value)
+		}
+
+	case *adt.Bottom:
+		w.string(`_|_`)
+		if x.Err != nil {
+			w.string("(")
+			w.shortError(x.Err)
+			w.string(")")
+		}
+
+	case *adt.Null:
+		w.string("null")
+
+	case *adt.Bool:
+		fmt.Fprint(w, x.B)
+
+	case *adt.Num:
+		fmt.Fprint(w, &x.X)
+
+	case *adt.String:
+		w.string(strconv.Quote(x.Str))
+
+	case *adt.Bytes:
+		b := []byte(strconv.Quote(string(x.B)))
+		b[0] = '\''
+		b[len(b)-1] = '\''
+		w.string(string(b))
+
+	case *adt.Top:
+		w.string("_")
+
+	case *adt.BasicType:
+		fmt.Fprint(w, x.K)
+
+	case *adt.BoundExpr:
+		fmt.Fprint(w, x.Op)
+		w.node(x.Expr)
+
+	case *adt.BoundValue:
+		fmt.Fprint(w, x.Op)
+		w.node(x.Value)
+
+	case *adt.FieldReference:
+		w.string(openTuple)
+		w.string(strconv.Itoa(int(x.UpCount)))
+		w.string(";")
+		w.label(x.Label)
+		w.string(closeTuple)
+
+	case *adt.LabelReference:
+		w.string(openTuple)
+		w.string(strconv.Itoa(int(x.UpCount)))
+		w.string(";-")
+		w.string(closeTuple)
+
+	case *adt.DynamicReference:
+		w.string(openTuple)
+		w.string(strconv.Itoa(int(x.UpCount)))
+		w.string(";(")
+		w.node(x.Label)
+		w.string(")")
+		w.string(closeTuple)
+
+	case *adt.ImportReference:
+		w.string(openTuple + "import;")
+		w.label(x.ImportPath)
+		w.string(closeTuple)
+
+	case *adt.LetReference:
+		w.string(openTuple)
+		w.string(strconv.Itoa(int(x.UpCount)))
+		w.string(";let ")
+		w.label(x.Label)
+		w.string(closeTuple)
+
+	case *adt.SelectorExpr:
+		w.node(x.X)
+		w.string(".")
+		w.label(x.Sel)
+
+	case *adt.IndexExpr:
+		w.node(x.X)
+		w.string("[")
+		w.node(x.Index)
+		w.string("]")
+
+	case *adt.SliceExpr:
+		w.node(x.X)
+		w.string("[")
+		if x.Lo != nil {
+			w.node(x.Lo)
+		}
+		w.string(":")
+		if x.Hi != nil {
+			w.node(x.Hi)
+		}
+		if x.Stride != nil {
+			w.string(":")
+			w.node(x.Stride)
+		}
+		w.string("]")
+
+	case *adt.Interpolation:
+		w.string(`"`)
+		for i := 0; i < len(x.Parts); i += 2 {
+			if s, ok := x.Parts[i].(*adt.String); ok {
+				w.string(s.Str)
+			} else {
+				w.string("<bad string>")
+			}
+			if i+1 < len(x.Parts) {
+				w.string(`\(`)
+				w.node(x.Parts[i+1])
+				w.string(`)`)
+			}
+		}
+		w.string(`"`)
+
+	case *adt.UnaryExpr:
+		fmt.Fprint(w, x.Op)
+		w.node(x.X)
+
+	case *adt.BinaryExpr:
+		w.string("(")
+		w.node(x.X)
+		fmt.Fprint(w, " ", x.Op, " ")
+		w.node(x.Y)
+		w.string(")")
+
+	case *adt.CallExpr:
+		w.node(x.Fun)
+		w.string("(")
+		for i, a := range x.Args {
+			if i > 0 {
+				w.string(", ")
+			}
+			w.node(a)
+		}
+		w.string(")")
+
+	case *adt.BuiltinValidator:
+		w.node(x.Fun)
+		w.string("(")
+		for i, a := range x.Args {
+			if i > 0 {
+				w.string(", ")
+			}
+			w.node(a)
+		}
+		w.string(")")
+
+	case *adt.DisjunctionExpr:
+		w.string("(")
+		for i, a := range x.Values {
+			if i > 0 {
+				w.string("|")
+			}
+			// Disjunct
+			if a.Default {
+				w.string("*")
+			}
+			w.node(a.Val)
+		}
+		w.string(")")
+
+	case *adt.Conjunction:
+		w.string("&(")
+		for i, c := range x.Values {
+			if i > 0 {
+				w.string(", ")
+			}
+			w.node(c)
+		}
+		w.string(")")
+
+	case *adt.Disjunction:
+		w.string("|(")
+		for i, c := range x.Values {
+			if i > 0 {
+				w.string(", ")
+			}
+			if i < x.NumDefaults {
+				w.string("*")
+			}
+			w.node(c)
+		}
+		w.string(")")
+
+	case *adt.ForClause:
+		w.string("for ")
+		w.label(x.Key)
+		w.string(", ")
+		w.label(x.Value)
+		w.string(" in ")
+		w.node(x.Src)
+		w.string(" ")
+		w.node(x.Dst)
+
+	case *adt.IfClause:
+		w.string("if ")
+		w.node(x.Condition)
+		w.string(" ")
+		w.node(x.Dst)
+
+	case *adt.LetClause:
+		w.string("let ")
+		w.label(x.Label)
+		w.string(" = ")
+		w.node(x.Expr)
+		w.string(" ")
+		w.node(x.Dst)
+
+	case *adt.ValueClause:
+		w.node(x.StructLit)
+
+	default:
+		panic(fmt.Sprintf("unknown type %T", x))
+	}
+}
diff --git a/internal/core/runtime/index.go b/internal/core/runtime/index.go
new file mode 100644
index 0000000..4907368
--- /dev/null
+++ b/internal/core/runtime/index.go
@@ -0,0 +1,81 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package runtime
+
+import (
+	"sync"
+)
+
+// index maps conversions from label names to internal codes.
+//
+// All instances belonging to the same package should share this index.
+type index struct {
+	labelMap map[string]int
+	labels   []string
+
+	offset int
+	parent *index
+
+	mutex     sync.Mutex
+	typeCache sync.Map // map[reflect.Type]evaluated
+}
+
+// work around golang-ci linter bug: fields are used.
+func init() {
+	var i index
+	i.mutex.Lock()
+	i.mutex.Unlock()
+	i.typeCache.Load(1)
+}
+
+// sharedIndex is used for indexing builtins and any other labels common to
+// all instances.
+var sharedIndex = newSharedIndex()
+
+func newSharedIndex() *index {
+	i := &index{
+		labelMap: map[string]int{"": 0},
+		labels:   []string{"_"},
+	}
+	return i
+}
+
+// newIndex creates a new index.
+func newIndex(parent *index) *index {
+	i := &index{
+		labelMap: map[string]int{},
+		offset:   len(parent.labels) + parent.offset,
+		parent:   parent,
+	}
+	return i
+}
+
+func (x *index) IndexToString(i int64) string {
+	for ; int(i) < x.offset; x = x.parent {
+	}
+	return x.labels[int(i)-x.offset]
+}
+
+func (x *index) StringToIndex(s string) int64 {
+	for p := x; p != nil; p = p.parent {
+		if f, ok := p.labelMap[s]; ok {
+			return int64(f)
+		}
+	}
+	index := len(x.labelMap) + x.offset
+	x.labelMap[s] = index
+	x.labels = append(x.labels, s)
+	return int64(index)
+}
diff --git a/internal/core/runtime/runtime.go b/internal/core/runtime/runtime.go
new file mode 100644
index 0000000..ecdaa0a
--- /dev/null
+++ b/internal/core/runtime/runtime.go
@@ -0,0 +1,27 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package runtime
+
+// A Runtime maintains data structures for indexing and resuse for evaluation.
+type Runtime struct {
+	*index
+}
+
+// New creates a new Runtime.
+func New() *Runtime {
+	return &Runtime{
+		index: newIndex(sharedIndex),
+	}
+}
diff --git a/internal/cuetxtar/txtar.go b/internal/cuetxtar/txtar.go
index 943d136..241305f 100644
--- a/internal/cuetxtar/txtar.go
+++ b/internal/cuetxtar/txtar.go
@@ -193,8 +193,9 @@
 			return nil
 		}
 
-		p := strings.Index(fullpath, "/testdata/")
-		testName := fullpath[p+len("/testdata/") : len(fullpath)-len(".txtar")]
+		str := filepath.ToSlash(fullpath)
+		p := strings.Index(str, "/testdata/")
+		testName := str[p+len("/testdata/") : len(str)-len(".txtar")]
 
 		t.Run(testName, func(t *testing.T) {
 			a, err := txtar.ParseFile(fullpath)