cue: introduce Context to replace Runtime

Also:
- move the API to be Value- instead of Instance-based.
- Add Encode method
- factor out build code to internal/core/

Change-Id: Id6a71752b75422b972dca15c7ab9423401cb0287
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9423
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/common.go b/cmd/cue/cmd/common.go
index d6deeec..123c390 100644
--- a/cmd/cue/cmd/common.go
+++ b/cmd/cue/cmd/common.go
@@ -34,6 +34,7 @@
 	"cuelang.org/go/internal"
 	"cuelang.org/go/internal/encoding"
 	"cuelang.org/go/internal/filetypes"
+	"cuelang.org/go/internal/value"
 )
 
 // Disallow
@@ -244,7 +245,7 @@
 	// TODO: use orphanedSchema
 	i.r = &cue.Runtime{}
 	if v := b.encConfig.Schema; v.Exists() {
-		i.r = internal.GetRuntime(v).(*cue.Runtime)
+		i.r = value.ConvertToRuntime(v.Context())
 	}
 
 	return i
@@ -736,7 +737,7 @@
 		inst = cue.Merge(insts...)
 	}
 
-	r := internal.GetRuntime(inst).(*cue.Runtime)
+	r := value.ConvertToRuntime(inst.Value().Context())
 	for _, b := range binst {
 		for _, i := range b.Imports {
 			if _, err := r.Build(i); err != nil {
diff --git a/cue/build.go b/cue/build.go
index e522765..44730dd 100644
--- a/cue/build.go
+++ b/cue/build.go
@@ -16,63 +16,32 @@
 
 import (
 	"cuelang.org/go/cue/ast"
-	"cuelang.org/go/cue/ast/astutil"
 	"cuelang.org/go/cue/build"
 	"cuelang.org/go/cue/errors"
-	"cuelang.org/go/cue/token"
-	"cuelang.org/go/internal"
+	"cuelang.org/go/internal/core/adt"
 	"cuelang.org/go/internal/core/runtime"
 )
 
-// A Runtime is used for creating CUE interpretations.
+// A Runtime is used for creating CUE Values.
 //
 // Any operation that involves two Values or Instances should originate from
 // the same Runtime.
 //
-// The zero value of a Runtime is ready to use.
-type Runtime struct {
-	idx *runtime.Runtime
-}
-
-func init() {
-	internal.GetRuntime = func(instance interface{}) interface{} {
-		switch x := instance.(type) {
-		case Value:
-			return &Runtime{idx: x.idx}
-
-		case *Instance:
-			return &Runtime{idx: x.index}
-
-		default:
-			panic("argument must be Value or *Instance")
-		}
-	}
-
-	internal.CheckAndForkRuntime = func(runtime, value interface{}) interface{} {
-		r := runtime.(*Runtime)
-		idx := value.(Value).idx
-		if idx != r.idx {
-			panic("value not from same runtime")
-		}
-		return &Runtime{idx: newIndex()}
-	}
-}
-
-func dummyLoad(token.Pos, string) *build.Instance { return nil }
+// The zero value of Runtime works for legacy reasons, but
+// should not be used. It may panic at some point.
+//
+// Deprecated: use Context.
+type Runtime runtime.Runtime
 
 func (r *Runtime) index() *runtime.Runtime {
-	if r.idx == nil {
-		r.idx = newIndex()
-	}
-	return r.idx
+	rt := (*runtime.Runtime)(r)
+	rt.Init()
+	return rt
 }
 
-func (r *Runtime) complete(p *build.Instance) (*Instance, error) {
+func (r *Runtime) complete(p *build.Instance, v *adt.Vertex) (*Instance, error) {
 	idx := r.index()
-	if err := p.Complete(); err != nil {
-		return nil, err
-	}
-	inst := loadInstance(idx, p)
+	inst := getImportFromBuild(idx, p, v)
 	inst.ImportPath = p.ImportPath
 	if inst.Err != nil {
 		return nil, inst.Err
@@ -84,60 +53,64 @@
 // provided as a string, byte slice, io.Reader. The name is used as the file
 // name in position information. The source may import builtin packages. Use
 // Build to allow importing non-builtin packages.
+//
+// Deprecated: use Parse or ParseBytes. The use of Instance is being phased out.
 func (r *Runtime) Compile(filename string, source interface{}) (*Instance, error) {
-	ctx := build.NewContext()
-	p := ctx.NewInstance(filename, dummyLoad)
-	if err := p.AddFile(filename, source); err != nil {
-		return nil, p.Err
-	}
-	return r.complete(p)
+	cfg := &runtime.Config{Filename: filename}
+	v, p := r.index().Compile(cfg, source)
+	return r.complete(p, v)
 }
 
 // CompileFile compiles the given source file into an Instance. The source may
 // import builtin packages. Use Build to allow importing non-builtin packages.
+//
+// Deprecated: use BuildFile. The use of Instance is being phased out.
 func (r *Runtime) CompileFile(file *ast.File) (*Instance, error) {
-	ctx := build.NewContext()
-	p := ctx.NewInstance(file.Filename, dummyLoad)
-	err := p.AddSyntax(file)
-	if err != nil {
-		return nil, err
-	}
-	_, p.PkgName, _ = internal.PackageInfo(file)
-	return r.complete(p)
+	v, p := r.index().CompileFile(nil, file)
+	return r.complete(p, v)
 }
 
 // CompileExpr compiles the given source expression into an Instance. The source
 // may import builtin packages. Use Build to allow importing non-builtin
 // packages.
+//
+// Deprecated: use BuildExpr. The use of Instance is being phased out.
 func (r *Runtime) CompileExpr(expr ast.Expr) (*Instance, error) {
-	f, err := astutil.ToFile(expr)
+	v, p, err := r.index().CompileExpr(nil, expr)
 	if err != nil {
 		return nil, err
 	}
-	return r.CompileFile(f)
+	return r.complete(p, v)
 }
 
-// Parse parses a CUE source value into a CUE Instance. The source code may
-// be provided as a string, byte slice, or io.Reader. The name is used as the
-// file name in position information. The source may import builtin packages.
+// Parse parses a CUE source value into a CUE Instance. The source code may be
+// provided as a string, byte slice, or io.Reader. The name is used as the file
+// name in position information. The source may import builtin packages.
 //
-// Deprecated: use Compile
+// Deprecated: use ParseString or ParseBytes.  The use of Instance is being
+// phased out.
 func (r *Runtime) Parse(name string, source interface{}) (*Instance, error) {
 	return r.Compile(name, source)
 }
 
 // Build creates an Instance from the given build.Instance. A returned Instance
 // may be incomplete, in which case its Err field is set.
-func (r *Runtime) Build(instance *build.Instance) (*Instance, error) {
-	return r.complete(instance)
+//
+// Deprecated: use Runtime.BuildInstance. The use of Instance is being phased
+// out.
+func (r *Runtime) Build(p *build.Instance) (*Instance, error) {
+	v, _ := r.index().Build(nil, p)
+	return r.complete(p, v)
 }
 
-// Build creates one Instance for each build.Instance. A returned Instance
-// may be incomplete, in which case its Err field is set.
+// Build creates one Instance for each build.Instance. A returned Instance may
+// be incomplete, in which case its Err field is set.
 //
 // Example:
 //	inst := cue.Build(load.Instances(args))
 //
+// Deprecated: use Runtime.BuildInstances. The use of Instance is being phased
+// out.
 func Build(instances []*build.Instance) []*Instance {
 	if len(instances) == 0 {
 		panic("cue: list of instances must not be empty")
@@ -155,10 +128,8 @@
 	var errs errors.Error
 
 	for _, p := range instances {
-		_ = p.Complete()
-		errs = errors.Append(errs, p.Err)
-
-		i := loadInstance(index, p)
+		v, _ := index.Build(nil, p)
+		i := getImportFromBuild(index, p, v)
 		errs = errors.Append(errs, i.Err)
 		loaded = append(loaded, i)
 	}
@@ -177,16 +148,6 @@
 	})
 }
 
-// newIndex creates a new index.
-func newIndex() *runtime.Runtime {
-	return runtime.New()
-}
-
 func isBuiltin(s string) bool {
 	return runtime.SharedRuntime.IsBuiltinPackage(s)
 }
-
-func loadInstance(idx *runtime.Runtime, p *build.Instance) *Instance {
-	v, _ := idx.Build(p)
-	return getImportFromBuild(idx, p, v)
-}
diff --git a/cue/context.go b/cue/context.go
index 6122fc4..f9726f3 100644
--- a/cue/context.go
+++ b/cue/context.go
@@ -15,12 +15,274 @@
 package cue
 
 import (
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/build"
+	"cuelang.org/go/cue/errors"
 	"cuelang.org/go/internal/core/adt"
+	"cuelang.org/go/internal/core/convert"
 	"cuelang.org/go/internal/core/debug"
 	"cuelang.org/go/internal/core/eval"
 	"cuelang.org/go/internal/core/runtime"
 )
 
+// A Context is used for creating CUE Values.
+//
+// A Context keeps track of loaded instances, indices of internal
+// representations of values, and defines the set of supported builtins. Any
+// operation that involves two Values should originate from the same Context.
+//
+// Use
+//
+//    ctx := cuecontext.New()
+//
+// to create a new Context.
+type Context runtime.Runtime
+
+func (c *Context) index() *runtime.Runtime {
+	rt := (*runtime.Runtime)(c)
+	return rt
+}
+
+func (c *Context) ctx() *adt.OpContext {
+	return newContext(c.index())
+}
+
+// Context reports the Context with which this value was created.
+func (v Value) Context() *Context {
+	return (*Context)(v.idx)
+}
+
+// A BuildOption defines options for the various build-related methods of
+// Context.
+type BuildOption func(o *runtime.Config)
+
+// Scope defines a context in which to resolve unresolved identifiers.
+//
+// Only one scope may be given. It panics if more than one scope is given
+// or if the Context in which scope was created differs from the one where
+// this option is used.
+func Scope(scope Value) BuildOption {
+	return func(o *runtime.Config) {
+		if o.Runtime != scope.idx {
+			panic("incompatible runtime")
+		}
+		if o.Scope != nil {
+			panic("more than one scope is given")
+		}
+		o.Scope = scope.v
+	}
+}
+
+// Filename assigns a filename to parsed content.
+func Filename(filename string) BuildOption {
+	return func(o *runtime.Config) { o.Filename = filename }
+}
+
+func (c *Context) parseOptions(options []BuildOption) (cfg runtime.Config) {
+	cfg.Runtime = (*runtime.Runtime)(c)
+	for _, f := range options {
+		f(&cfg)
+	}
+	return cfg
+}
+
+// BuildInstance creates a Value from the given build.Instance.
+//
+// The returned Value will represent an error, accessible through Err, if any
+// error occurred.
+func (c *Context) BuildInstance(i *build.Instance, options ...BuildOption) Value {
+	cfg := c.parseOptions(options)
+	v, err := c.index().Build(&cfg, i)
+	if err != nil {
+		return c.makeError(err)
+	}
+	return c.make(v)
+}
+
+func (c *Context) makeError(err errors.Error) Value {
+	b := &adt.Bottom{Err: err}
+	node := &adt.Vertex{BaseValue: b}
+	node.UpdateStatus(adt.Finalized)
+	node.AddConjunct(adt.MakeRootConjunct(nil, b))
+	return c.make(node)
+}
+
+// BuildInstances creates a Value for each of the given instances and reports
+// the combined errors or nil if there were no errors.
+func (c *Context) BuildInstances(instances []*build.Instance) ([]Value, error) {
+	var errs errors.Error
+	var a []Value
+	for _, b := range instances {
+		v, err := c.index().Build(nil, b)
+		if err != nil {
+			errs = errors.Append(errs, err)
+		}
+		a = append(a, c.make(v))
+	}
+	return a, errs
+}
+
+// BuildFile creates a Value from f.
+//
+// The returned Value will represent an error, accessible through Err, if any
+// error occurred.
+func (c *Context) BuildFile(f *ast.File, options ...BuildOption) Value {
+	cfg := c.parseOptions(options)
+	return c.compile(c.index().CompileFile(&cfg, f))
+}
+
+func (c *Context) compile(v *adt.Vertex, p *build.Instance) Value {
+	if p.Err != nil {
+		return c.makeError(p.Err)
+	}
+	return c.make(v)
+}
+
+// BuildExpr creates a Value from x.
+//
+// The returned Value will represent an error, accessible through Err, if any
+// error occurred.
+func (c *Context) BuildExpr(x ast.Expr, options ...BuildOption) Value {
+	cfg := c.parseOptions(options)
+	v, p, err := c.index().CompileExpr(&cfg, x)
+	if err != nil {
+		return c.makeError(p.Err)
+	}
+	return c.make(v)
+}
+
+// CompileString parses and build a Value from the given source string.
+//
+// The returned Value will represent an error, accessible through Err, if any
+// error occurred.
+func (c *Context) CompileString(src string, options ...BuildOption) Value {
+	cfg := c.parseOptions(options)
+	return c.compile(c.index().Compile(&cfg, src))
+}
+
+// ParseString parses and build a Value from the given source bytes.
+//
+// The returned Value will represent an error, accessible through Err, if any
+// error occurred.
+func (c *Context) CompileBytes(b []byte, options ...BuildOption) Value {
+	cfg := c.parseOptions(options)
+	return c.compile(c.index().Compile(&cfg, b))
+}
+
+// TODO: fs.FS or custom wrapper?
+// // CompileFile parses and build a Value from the given source bytes.
+// //
+// // The returned Value will represent an error, accessible through Err, if any
+// // error occurred.
+// func (c *Context) CompileFile(f fs.File, options ...BuildOption) Value {
+// 	b, err := io.ReadAll(f)
+// 	if err != nil {
+// 		return c.makeError(errors.Promote(err, "parsing file system file"))
+// 	}
+// 	return c.compile(c.runtime().Compile("", b))
+// }
+
+func (c *Context) make(v *adt.Vertex) Value {
+	return newValueRoot(c.index(), newContext(c.index()), v)
+}
+
+// An EncodeOption defines options for the various encoding-related methods of
+// Context.
+type EncodeOption func(*encodeOptions)
+
+type encodeOptions struct {
+	nilIsTop bool
+}
+
+func (o *encodeOptions) process(option []EncodeOption) {
+	for _, f := range option {
+		f(o)
+	}
+}
+
+// NilIsAny indicates whether a nil value is interpreted as null or _.
+//
+// The default is to interpret nil as _.
+func NilIsAny(isAny bool) EncodeOption {
+	return func(o *encodeOptions) { o.nilIsTop = isAny }
+}
+
+// Encode converts a Go value to a CUE value.
+//
+// The returned Value will represent an error, accessible through Err, if any
+// error occurred.
+func (c *Context) Encode(x interface{}, option ...EncodeOption) Value {
+	var options encodeOptions
+	options.process(option)
+
+	ctx := c.ctx()
+	// TODO: is true the right default?
+	expr := convert.GoValueToValue(ctx, x, options.nilIsTop)
+	n := &adt.Vertex{}
+	n.AddConjunct(adt.MakeRootConjunct(nil, expr))
+	n.Finalize(ctx)
+	return c.make(n)
+}
+
+// Encode converts a Go type to a CUE value.
+//
+// The returned Value will represent an error, accessible through Err, if any
+// error occurred.
+func (c *Context) EncodeType(x interface{}, option ...EncodeOption) Value {
+	switch v := x.(type) {
+	case *adt.Vertex:
+		return c.make(v)
+	}
+
+	ctx := c.ctx()
+	expr, err := convert.GoTypeToExpr(ctx, x)
+	if err != nil {
+		return c.makeError(err)
+	}
+	n := &adt.Vertex{}
+	n.AddConjunct(adt.MakeRootConjunct(nil, expr))
+	n.Finalize(ctx)
+	return c.make(n)
+}
+
+// TODO:
+
+// func (c *Context) NewExpr(op Op, v ...Value) Value {
+// 	return Value{}
+// }
+
+// func (c *Context) NewList(v ...Value) Value {
+// 	return Value{}
+// }
+
+// func (c *Context) NewValue(v ...ValueElem) Value {
+// 	return Value{}
+// }
+
+// func NewAttr(key string, values ...string) *Attribute {
+// 	return &Attribute{}
+// }
+
+// // Clear unloads all previously-loaded imports.
+// func (c *Context) Clear() {
+// }
+
+// // Values created up to the point of the Fork will be valid in both runtimes.
+// func (c *Context) Fork() *Context {
+// 	return nil
+// }
+
+// type ValueElem interface {
+// }
+
+// func NewField(sel Selector, value Value, attrs ...Attribute) ValueElem {
+// 	return nil
+// }
+
+// func NewDocComment(text string) ValueElem {
+// 	return nil
+// }
+
 // newContext returns a new evaluation context.
 func newContext(idx *runtime.Runtime) *adt.OpContext {
 	if idx == nil {
diff --git a/cue/cuecontext/cuecontext.go b/cue/cuecontext/cuecontext.go
new file mode 100644
index 0000000..0608079
--- /dev/null
+++ b/cue/cuecontext/cuecontext.go
@@ -0,0 +1,31 @@
+// Copyright 2021 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 cuecontext
+
+import (
+	"cuelang.org/go/cue"
+	"cuelang.org/go/internal/core/runtime"
+
+	_ "cuelang.org/go/pkg"
+)
+
+// Option controls a build context.
+type Option interface{ buildOption() }
+
+// New creates a new Context.
+func New(options ...Option) *cue.Context {
+	r := runtime.New()
+	return (*cue.Context)(r)
+}
diff --git a/cue/examplecompile_test.go b/cue/examplecompile_test.go
new file mode 100644
index 0000000..6803da3
--- /dev/null
+++ b/cue/examplecompile_test.go
@@ -0,0 +1,55 @@
+// Copyright 2021 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 cue_test
+
+import (
+	"fmt"
+
+	"cuelang.org/go/cue"
+	"cuelang.org/go/cue/cuecontext"
+)
+
+func ExampleContext() {
+	ctx := cuecontext.New()
+
+	v := ctx.CompileString(`
+		a: 2
+		b: 3
+		"a+b": a + b
+	`)
+
+	p("lookups")
+	p("a:     %+v", v.LookupPath(cue.ParsePath("a")))
+	p("b:     %+v", v.LookupPath(cue.ParsePath("b")))
+	p(`"a+b": %+v`, v.LookupPath(cue.ParsePath(`"a+b"`)))
+	p("")
+	p("expressions")
+	p("a + b: %+v", ctx.CompileString("a + b", cue.Scope(v)))
+	p("a * b: %+v", ctx.CompileString("a * b", cue.Scope(v)))
+
+	// Output:
+	// lookups
+	// a:     2
+	// b:     3
+	// "a+b": 5
+
+	// expressions
+	// a + b: 5
+	// a * b: 6
+}
+
+func p(format string, args ...interface{}) {
+	fmt.Printf(format+"\n", args...)
+}
diff --git a/cue/examples_test.go b/cue/examples_test.go
index 2ed3e4e..4063959 100644
--- a/cue/examples_test.go
+++ b/cue/examples_test.go
@@ -20,6 +20,7 @@
 	"os"
 
 	"cuelang.org/go/cue"
+	"cuelang.org/go/cue/cuecontext"
 	"cuelang.org/go/internal/cuetxtar"
 	"github.com/rogpeppe/go-internal/txtar"
 )
@@ -67,8 +68,9 @@
 }
 
 func ExampleValue_Allows() {
+	ctx := cuecontext.New()
+
 	const file = `
--- main.cue --
 a: [1, 2, ...int]
 
 b: #Point
@@ -84,7 +86,7 @@
 #C: [>"m"]: int
 `
 
-	v := load(file).Value()
+	v := ctx.CompileString(file)
 
 	a := v.LookupPath(cue.ParsePath("a"))
 	fmt.Println("a allows:")
diff --git a/cue/go.go b/cue/go.go
index c0d5a63..bd77055 100644
--- a/cue/go.go
+++ b/cue/go.go
@@ -27,7 +27,7 @@
 		ctx := eval.NewContext(r.index(), nil)
 		v := convert.GoValueToValue(ctx, x, nilIsTop)
 		n := adt.ToVertex(v)
-		return Value{r.idx, n}
+		return Value{r.index(), n}
 	}
 
 	internal.FromGoType = func(runtime, x interface{}) interface{} {
@@ -39,7 +39,7 @@
 		}
 		n := &adt.Vertex{}
 		n.AddConjunct(adt.MakeRootConjunct(nil, expr))
-		return Value{r.idx, n}
+		return Value{r.index(), n}
 
 		// return convertType(runtime.(*Runtime), x)
 	}
diff --git a/cue/marshal.go b/cue/marshal.go
index f4f0c6b..f877b77 100644
--- a/cue/marshal.go
+++ b/cue/marshal.go
@@ -140,7 +140,7 @@
 			return p
 		}
 		// TODO: support exporting instance
-		file, _ := export.Def(r.idx, i.inst.ID(), i.root)
+		file, _ := export.Def(r.index(), i.inst.ID(), i.root)
 		imports := []string{}
 		file.VisitImports(func(i *ast.ImportDecl) {
 			for _, spec := range i.Specs {
@@ -190,7 +190,7 @@
 		p := len(staged) - 1
 
 		for _, imp := range imports {
-			i := getImportFromPath(r.idx, imp)
+			i := getImportFromPath(r.index(), imp)
 			if i == nil || !strings.Contains(imp, ".") {
 				continue // a builtin package.
 			}
diff --git a/encoding/gocode/generator.go b/encoding/gocode/generator.go
index 0270e93..a2932fe 100644
--- a/encoding/gocode/generator.go
+++ b/encoding/gocode/generator.go
@@ -26,7 +26,7 @@
 
 	"cuelang.org/go/cue"
 	"cuelang.org/go/cue/errors"
-	"cuelang.org/go/internal"
+	"cuelang.org/go/internal/value"
 )
 
 // Config defines options for generation Go code.
@@ -159,7 +159,7 @@
 		g.decl(iter.Label(), iter.Value())
 	}
 
-	r := internal.GetRuntime(inst).(*cue.Runtime)
+	r := value.ConvertToRuntime(inst.Value().Context())
 	b, err = r.Marshal(inst)
 	g.addErr(err)
 
diff --git a/encoding/gocode/gocodec/codec.go b/encoding/gocode/gocodec/codec.go
index bd4b083..0951959 100644
--- a/encoding/gocode/gocodec/codec.go
+++ b/encoding/gocode/gocodec/codec.go
@@ -24,7 +24,9 @@
 	"sync"
 
 	"cuelang.org/go/cue"
+	"cuelang.org/go/cue/cuecontext"
 	"cuelang.org/go/internal"
+	"cuelang.org/go/internal/value"
 )
 
 // Config has no options yet, but is defined for future extensibility.
@@ -93,7 +95,7 @@
 	return v.Decode(x)
 }
 
-var defaultCodec = New(&cue.Runtime{}, nil)
+var defaultCodec = New(value.ConvertToRuntime(cuecontext.New()), nil)
 
 // Validate calls Validate on a default Codec for the type of x.
 func Validate(x interface{}) error {
@@ -125,7 +127,7 @@
 	c.mutex.RLock()
 	defer c.mutex.RUnlock()
 
-	r := checkAndForkRuntime(c.runtime, v)
+	r := checkAndForkContext(c.runtime, v)
 	w, err := fromGoValue(r, x, false)
 	if err != nil {
 		return err
@@ -151,7 +153,7 @@
 	c.mutex.RLock()
 	defer c.mutex.RUnlock()
 
-	r := checkAndForkRuntime(c.runtime, v)
+	r := checkAndForkContext(c.runtime, v)
 	w, err := fromGoValue(r, x, true)
 	if err != nil {
 		return err
@@ -176,6 +178,10 @@
 	return v, nil
 }
 
-func checkAndForkRuntime(r *cue.Runtime, v cue.Value) *cue.Runtime {
-	return internal.CheckAndForkRuntime(r, v).(*cue.Runtime)
+func checkAndForkContext(r *cue.Runtime, v cue.Value) *cue.Runtime {
+	rr := value.ConvertToRuntime(v.Context())
+	if r != rr {
+		panic("value not from same runtime")
+	}
+	return rr
 }
diff --git a/internal/core/adt/eval_test.go b/internal/core/adt/eval_test.go
index 06143b4..f095be1 100644
--- a/internal/core/adt/eval_test.go
+++ b/internal/core/adt/eval_test.go
@@ -54,7 +54,7 @@
 	test.Run(t, func(t *cuetxtar.Test) {
 		a := t.ValidInstances()
 
-		v, err := r.Build(a[0])
+		v, err := r.Build(nil, a[0])
 		if err != nil {
 			t.WriteErrors(err)
 			return
@@ -117,7 +117,7 @@
 
 	r := runtime.New()
 
-	v, err := r.Build(instance)
+	v, err := r.Build(nil, instance)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/internal/core/compile/compile.go b/internal/core/compile/compile.go
index 432ba2d..1d86bd4 100644
--- a/internal/core/compile/compile.go
+++ b/internal/core/compile/compile.go
@@ -92,6 +92,7 @@
 
 type compiler struct {
 	Config
+	upCountOffset int32 // 1 for files; 0 for expressions
 
 	index adt.StringIndexer
 
@@ -235,6 +236,7 @@
 
 func (c *compiler) compileFiles(a []*ast.File) *adt.Vertex { // Or value?
 	c.fileScope = map[adt.Feature]bool{}
+	c.upCountOffset = 1
 
 	// TODO(resolve): this is also done in the runtime package, do we need both?
 
@@ -262,11 +264,20 @@
 
 	// env := &adt.Environment{Vertex: nil} // runtime: c.runtime
 
+	env := &adt.Environment{}
+	top := env
+
+	for p := c.Config.Scope; p != nil; p = p.Parent {
+		top.Vertex = p
+		top.Up = &adt.Environment{}
+		top = top.Up
+	}
+
 	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.MakeRootConjunct(nil, v))
+		res.Conjuncts = append(res.Conjuncts, adt.MakeRootConjunct(env, v))
 		c.popScope()
 	}
 
@@ -319,6 +330,7 @@
 				Label:   label,
 			}
 		}
+		upCount += c.upCountOffset
 		for p := c.Scope; p != nil; p = p.Parent {
 			for _, a := range p.Arcs {
 				if a.Label == label {
diff --git a/internal/core/export/value_test.go b/internal/core/export/value_test.go
index 2312a5e..c84ba54 100644
--- a/internal/core/export/value_test.go
+++ b/internal/core/export/value_test.go
@@ -49,7 +49,7 @@
 
 		pkgID := a[0].ID()
 
-		v, err := r.Build(a[0])
+		v, err := r.Build(nil, a[0])
 		if err != nil {
 			t.Fatal(err)
 		}
diff --git a/internal/core/runtime/build.go b/internal/core/runtime/build.go
index 0927071..e38101c 100644
--- a/internal/core/runtime/build.go
+++ b/internal/core/runtime/build.go
@@ -21,13 +21,25 @@
 	"cuelang.org/go/cue/ast/astutil"
 	"cuelang.org/go/cue/build"
 	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/token"
+	"cuelang.org/go/internal"
 	"cuelang.org/go/internal/core/adt"
 	"cuelang.org/go/internal/core/compile"
 )
 
+type Config struct {
+	Runtime  *Runtime
+	Filename string
+
+	compile.Config
+}
+
 // Build builds b and all its transitive dependencies, insofar they have not
 // been build yet.
-func (x *Runtime) Build(b *build.Instance) (v *adt.Vertex, errs errors.Error) {
+func (x *Runtime) Build(cfg *Config, b *build.Instance) (v *adt.Vertex, errs errors.Error) {
+	if err := b.Complete(); err != nil {
+		return nil, b.Err
+	}
 	if v := x.getNodeFromInstance(b); v != nil {
 		return v, b.Err
 	}
@@ -45,7 +57,7 @@
 	for _, file := range b.Files {
 		file.VisitImports(func(d *ast.ImportDecl) {
 			for _, s := range d.Specs {
-				errs = errors.Append(errs, x.buildSpec(b, s))
+				errs = errors.Append(errs, x.buildSpec(cfg, b, s))
 			}
 		})
 	}
@@ -53,7 +65,11 @@
 	err := x.ResolveFiles(b)
 	errs = errors.Append(errs, err)
 
-	v, err = compile.Files(nil, x, b.ID(), b.Files...)
+	var cc *compile.Config
+	if cfg != nil {
+		cc = &cfg.Config
+	}
+	v, err = compile.Files(cc, x, b.ID(), b.Files...)
 	errs = errors.Append(errs, err)
 
 	if errs != nil {
@@ -66,7 +82,48 @@
 	return v, errs
 }
 
-func (x *Runtime) buildSpec(b *build.Instance, spec *ast.ImportSpec) (errs errors.Error) {
+func dummyLoad(token.Pos, string) *build.Instance { return nil }
+
+func (r *Runtime) Compile(cfg *Config, source interface{}) (*adt.Vertex, *build.Instance) {
+	ctx := build.NewContext()
+	var filename string
+	if cfg != nil && cfg.Filename != "" {
+		filename = cfg.Filename
+	}
+	p := ctx.NewInstance(filename, dummyLoad)
+	if err := p.AddFile(filename, source); err != nil {
+		return nil, p
+	}
+	v, _ := r.Build(cfg, p)
+	return v, p
+}
+
+func (r *Runtime) CompileFile(cfg *Config, file *ast.File) (*adt.Vertex, *build.Instance) {
+	ctx := build.NewContext()
+	filename := file.Filename
+	if cfg != nil && cfg.Filename != "" {
+		filename = cfg.Filename
+	}
+	p := ctx.NewInstance(filename, dummyLoad)
+	err := p.AddSyntax(file)
+	if err != nil {
+		return nil, p
+	}
+	_, p.PkgName, _ = internal.PackageInfo(file)
+	v, _ := r.Build(cfg, p)
+	return v, p
+}
+
+func (r *Runtime) CompileExpr(cfg *Config, expr ast.Expr) (*adt.Vertex, *build.Instance, error) {
+	f, err := astutil.ToFile(expr)
+	if err != nil {
+		return nil, nil, err
+	}
+	v, p := r.CompileFile(cfg, f)
+	return v, p, p.Err
+}
+
+func (x *Runtime) buildSpec(cfg *Config, b *build.Instance, spec *ast.ImportSpec) (errs errors.Error) {
 	info, err := astutil.ParseImportSpec(spec)
 	if err != nil {
 		return errors.Promote(err, "invalid import path")
@@ -86,7 +143,7 @@
 		return pkg.Err
 	}
 
-	if _, err := x.Build(pkg); err != nil {
+	if _, err := x.Build(cfg, pkg); err != nil {
 		return err
 	}
 
diff --git a/internal/internal.go b/internal/internal.go
index 31112fe..9cd1500 100644
--- a/internal/internal.go
+++ b/internal/internal.go
@@ -67,17 +67,9 @@
 // UnifyBuiltin returns the given Value unified with the given builtin template.
 var UnifyBuiltin func(v interface{}, kind string) interface{}
 
-// GetRuntime reports the runtime for an Instance or Value.
-var GetRuntime func(instance interface{}) interface{}
-
 // MakeInstance makes a new instance from a value.
 var MakeInstance func(value interface{}) (instance interface{})
 
-// CheckAndForkRuntime checks that value is created using runtime, panicking
-// if it does not, and returns a forked runtime that will discard additional
-// keys.
-var CheckAndForkRuntime func(runtime, value interface{}) interface{}
-
 // BaseContext is used as CUEs default context for arbitrary-precision decimals
 var BaseContext = apd.BaseContext.WithPrecision(24)
 
diff --git a/internal/value/value.go b/internal/value/value.go
index 62a127d..f5a4662 100644
--- a/internal/value/value.go
+++ b/internal/value/value.go
@@ -23,6 +23,15 @@
 	"cuelang.org/go/internal/types"
 )
 
+func ConvertToRuntime(c *cue.Context) *cue.Runtime {
+	return (*cue.Runtime)(c)
+}
+
+func ConvertToContext(r *cue.Runtime) *cue.Context {
+	(*runtime.Runtime)(r).Init()
+	return (*cue.Context)(r)
+}
+
 func ToInternal(v cue.Value) (*runtime.Runtime, *adt.Vertex) {
 	var t types.Value
 	v.Core(&t)
diff --git a/pkg/encoding/json/manual.go b/pkg/encoding/json/manual.go
index d5d0f9b..bd1fc06 100644
--- a/pkg/encoding/json/manual.go
+++ b/pkg/encoding/json/manual.go
@@ -24,7 +24,7 @@
 	"cuelang.org/go/cue/errors"
 	"cuelang.org/go/cue/parser"
 	"cuelang.org/go/cue/token"
-	"cuelang.org/go/internal"
+	"cuelang.org/go/internal/value"
 )
 
 // Compact generates the JSON-encoded src with insignificant space characters
@@ -111,7 +111,7 @@
 	if !json.Valid(b) {
 		return false, fmt.Errorf("json: invalid JSON")
 	}
-	r := internal.GetRuntime(v).(*cue.Runtime)
+	r := value.ConvertToRuntime(v.Context())
 	inst, err := r.Compile("json.Validate", b)
 	if err != nil {
 		return false, err
diff --git a/pkg/encoding/yaml/manual.go b/pkg/encoding/yaml/manual.go
index 7e3f5ef..f1fde15 100644
--- a/pkg/encoding/yaml/manual.go
+++ b/pkg/encoding/yaml/manual.go
@@ -23,6 +23,7 @@
 	"cuelang.org/go/internal"
 	cueyaml "cuelang.org/go/internal/encoding/yaml"
 	"cuelang.org/go/internal/third_party/yaml"
+	"cuelang.org/go/internal/value"
 )
 
 // Marshal returns the YAML encoding of v.
@@ -83,7 +84,7 @@
 	if err != nil {
 		return false, err
 	}
-	r := internal.GetRuntime(v).(*cue.Runtime)
+	r := value.ConvertToRuntime(v.Context())
 	for {
 		expr, err := d.Decode()
 		if err != nil {
@@ -127,7 +128,7 @@
 	if err != nil {
 		return false, err
 	}
-	r := internal.GetRuntime(v).(*cue.Runtime)
+	r := value.ConvertToRuntime(v.Context())
 	for {
 		expr, err := d.Decode()
 		if err != nil {
diff --git a/pkg/internal/builtintest/testing.go b/pkg/internal/builtintest/testing.go
index 98eaeaa..238b2ec 100644
--- a/pkg/internal/builtintest/testing.go
+++ b/pkg/internal/builtintest/testing.go
@@ -37,7 +37,7 @@
 	test.Run(t, func(t *cuetxtar.Test) {
 		a := t.ValidInstances()
 
-		v, errs := r.Build(a[0])
+		v, errs := r.Build(nil, a[0])
 		if errs != nil {
 			t.Fatal(errs)
 		}