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)
}