cue: hoist functionality to cue/internal/runtime
This will allow this code to be shared between new and old
implementation.
This makes cue use the new internal representation for labels.
Change-Id: I574f1401f9c55c0e151445d1f89312d06be49818
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6508
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/internal/core/runtime/errors.go b/internal/core/runtime/errors.go
new file mode 100644
index 0000000..e98cdda
--- /dev/null
+++ b/internal/core/runtime/errors.go
@@ -0,0 +1,52 @@
+// 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 (
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/token"
+)
+
+var _ errors.Error = &nodeError{}
+
+// A nodeError is an error associated with processing an AST node.
+type nodeError struct {
+ path []string // optional
+ n ast.Node
+
+ errors.Message
+}
+
+func (n *nodeError) Error() string {
+ return errors.String(n)
+}
+
+func nodeErrorf(n ast.Node, format string, args ...interface{}) *nodeError {
+ return &nodeError{
+ n: n,
+ Message: errors.NewMessage(format, args),
+ }
+}
+
+func (e *nodeError) Position() token.Pos {
+ return e.n.Pos()
+}
+
+func (e *nodeError) InputPositions() []token.Pos { return nil }
+
+func (e *nodeError) Path() []string {
+ return e.path
+}
diff --git a/internal/core/runtime/index.go b/internal/core/runtime/index.go
index 8af0f41..11c342c 100644
--- a/internal/core/runtime/index.go
+++ b/internal/core/runtime/index.go
@@ -19,81 +19,158 @@
"sync"
"cuelang.org/go/cue/ast"
+ "cuelang.org/go/internal"
"cuelang.org/go/internal/core/adt"
)
-// index maps conversions from label names to internal codes.
+// 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
+// All instances belonging to the same package should share this Index.
+//
+// INDEX IS A TRANSITIONAL TYPE TO BRIDGE THE OLD AND NEW
+// IMPLEMENTATIONS. USE RUNTIME.
+type Index struct {
+ labelMap map[string]int64
labels []string
- offset int
- parent *index
+ // Change this to Instance at some point.
+ // From *structLit/*Vertex -> Instance
+ imports map[interface{}]interface{}
+ importsByPath map[string]interface{}
+ // imports map[string]*adt.Vertex
- mutex sync.Mutex
+ offset int64
+ parent *Index
+
+ // mutex sync.Mutex
typeCache sync.Map // map[reflect.Type]evaluated
}
-// sharedIndex is used for indexing builtins and any other labels common to
+// SharedIndex is used for indexing builtins and any other labels common to
// all instances.
-var sharedIndex = newSharedIndex()
+var SharedIndex = newSharedIndex()
-func newSharedIndex() *index {
- i := &index{
- labelMap: map[string]int{"": 0},
- labels: []string{"_"},
+var SharedIndexNew = newSharedIndex()
+
+func newSharedIndex() *Index {
+ i := &Index{
+ labelMap: map[string]int64{"": 0},
+ labels: []string{""},
+ imports: map[interface{}]interface{}{},
+ importsByPath: map[string]interface{}{},
}
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,
+// NewIndex creates a new index.
+func NewIndex(parent *Index) *Index {
+ i := &Index{
+ labelMap: map[string]int64{},
+ imports: map[interface{}]interface{}{},
+ importsByPath: map[string]interface{}{},
+ offset: int64(len(parent.labels)) + parent.offset,
+ parent: parent,
}
return i
}
-func (x *index) IndexToString(i int64) string {
- for ; int(i) < x.offset; x = x.parent {
+func (x *Index) IndexToString(i int64) string {
+ for ; i < x.offset; x = x.parent {
}
- return x.labels[int(i)-x.offset]
+ return x.labels[i-x.offset]
}
-func (x *index) StringToIndex(s string) int64 {
+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
+ index := int64(len(x.labelMap)) + x.offset
x.labelMap[s] = index
x.labels = append(x.labels, s)
return int64(index)
}
-func (x *index) StoreType(t reflect.Type, src ast.Expr, expr adt.Expr) {
- if expr == nil {
- x.typeCache.Store(t, src)
- } else {
- x.typeCache.Store(t, expr)
+func (x *Index) HasLabel(s string) (ok bool) {
+ for c := x; c != nil; c = c.parent {
+ _, ok = c.labelMap[s]
+ if ok {
+ break
+ }
+ }
+ return ok
+}
+
+func (x *Index) StoreType(t reflect.Type, v interface{}) {
+ x.typeCache.Store(t, v)
+}
+
+func (x *Index) LoadType(t reflect.Type) (v interface{}, ok bool) {
+ v, ok = x.typeCache.Load(t)
+ return v, ok
+}
+
+func (x *Index) StrLabel(str string) adt.Feature {
+ return x.Label(str, false)
+}
+
+func (x *Index) NodeLabel(n ast.Node) (f adt.Feature, ok bool) {
+ switch label := n.(type) {
+ case *ast.BasicLit:
+ name, _, err := ast.LabelName(label)
+ return x.Label(name, false), err == nil
+ case *ast.Ident:
+ name, err := ast.ParseIdent(label)
+ return x.Label(name, true), err == nil
+ }
+ return 0, false
+}
+
+func (x *Index) Label(s string, isIdent bool) adt.Feature {
+ index := x.StringToIndex(s)
+ typ := adt.StringLabel
+ if isIdent {
+ switch {
+ case internal.IsDef(s) && internal.IsHidden(s):
+ typ = adt.HiddenDefinitionLabel
+ case internal.IsDef(s):
+ typ = adt.DefinitionLabel
+ case internal.IsHidden(s):
+ typ = adt.HiddenLabel
+ }
+ }
+ f, _ := adt.MakeLabel(nil, index, typ)
+ return f
+}
+
+func (idx *Index) LabelStr(l adt.Feature) string {
+ index := int64(l.Index())
+ return idx.IndexToString(index)
+}
+
+func (x *Index) AddInst(path string, key, p interface{}) {
+ if key == nil {
+ panic("key must not be nil")
+ }
+ x.imports[key] = p
+ if path != "" {
+ x.importsByPath[path] = key
}
}
-func (x *index) LoadType(t reflect.Type) (src ast.Expr, expr adt.Expr, ok bool) {
- v, ok := x.typeCache.Load(t)
- if ok {
- switch x := v.(type) {
- case ast.Expr:
- return x, nil, true
- case adt.Expr:
- src, _ = x.Source().(ast.Expr)
- return src, x, true
- }
+func (x *Index) GetImportFromNode(key interface{}) interface{} {
+ imp := x.imports[key]
+ if imp == nil && x.parent != nil {
+ return x.parent.GetImportFromNode(key)
}
- return nil, nil, false
+ return imp
+}
+
+func (x *Index) GetImportFromPath(id string) interface{} {
+ key := x.importsByPath[id]
+ if key == nil && x.parent != nil {
+ return x.parent.GetImportFromPath(id)
+ }
+ return key
}
diff --git a/internal/core/runtime/resolve.go b/internal/core/runtime/resolve.go
new file mode 100644
index 0000000..80ba789
--- /dev/null
+++ b/internal/core/runtime/resolve.go
@@ -0,0 +1,165 @@
+// 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 (
+ "path"
+ "strconv"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/build"
+ "cuelang.org/go/cue/errors"
+)
+
+func lineStr(idx *Index, n ast.Node) string {
+ return n.Pos().String()
+}
+
+func ResolveFiles(
+ idx *Index,
+ p *build.Instance,
+ isBuiltin func(s string) bool,
+) errors.Error {
+ // Link top-level declarations. As top-level entries get unified, an entry
+ // may be linked to any top-level entry of any of the files.
+ allFields := map[string]ast.Node{}
+ for _, file := range p.Files {
+ for _, d := range file.Decls {
+ if f, ok := d.(*ast.Field); ok && f.Value != nil {
+ if ident, ok := f.Label.(*ast.Ident); ok {
+ allFields[ident.Name] = f.Value
+ }
+ }
+ }
+ }
+ for _, f := range p.Files {
+ if err := ResolveFile(idx, f, p, allFields, isBuiltin); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func ResolveFile(
+ idx *Index,
+ f *ast.File,
+ p *build.Instance,
+ allFields map[string]ast.Node,
+ isBuiltin func(s string) bool,
+) errors.Error {
+ unresolved := map[string][]*ast.Ident{}
+ for _, u := range f.Unresolved {
+ unresolved[u.Name] = append(unresolved[u.Name], u)
+ }
+ fields := map[string]ast.Node{}
+ for _, d := range f.Decls {
+ if f, ok := d.(*ast.Field); ok && f.Value != nil {
+ if ident, ok := f.Label.(*ast.Ident); ok {
+ fields[ident.Name] = d
+ }
+ }
+ }
+ var errs errors.Error
+
+ specs := []*ast.ImportSpec{}
+
+ for _, spec := range f.Imports {
+ id, err := strconv.Unquote(spec.Path.Value)
+ if err != nil {
+ continue // quietly ignore the error
+ }
+ name := path.Base(id)
+ if imp := p.LookupImport(id); imp != nil {
+ name = imp.PkgName
+ } else if !isBuiltin(id) {
+ errs = errors.Append(errs,
+ nodeErrorf(spec, "package %q not found", id))
+ continue
+ }
+ if spec.Name != nil {
+ name = spec.Name.Name
+ }
+ if n, ok := fields[name]; ok {
+ errs = errors.Append(errs, nodeErrorf(spec,
+ "%s redeclared as imported package name\n"+
+ "\tprevious declaration at %v", name, lineStr(idx, n)))
+ continue
+ }
+ fields[name] = spec
+ used := false
+ for _, u := range unresolved[name] {
+ used = true
+ u.Node = spec
+ }
+ if !used {
+ specs = append(specs, spec)
+ }
+ }
+
+ // Verify each import is used.
+ if len(specs) > 0 {
+ // Find references to imports. This assumes that identifiers in labels
+ // are not resolved or that such errors are caught elsewhere.
+ ast.Walk(f, nil, func(n ast.Node) {
+ if x, ok := n.(*ast.Ident); ok {
+ // As we also visit labels, most nodes will be nil.
+ if x.Node == nil {
+ return
+ }
+ for i, s := range specs {
+ if s == x.Node {
+ specs[i] = nil
+ return
+ }
+ }
+ }
+ })
+
+ // Add errors for unused imports.
+ for _, spec := range specs {
+ if spec == nil {
+ continue
+ }
+ if spec.Name == nil {
+ errs = errors.Append(errs, nodeErrorf(spec,
+ "imported and not used: %s", spec.Path.Value))
+ } else {
+ errs = errors.Append(errs, nodeErrorf(spec,
+ "imported and not used: %s as %s", spec.Path.Value, spec.Name))
+ }
+ }
+ }
+
+ k := 0
+ for _, u := range f.Unresolved {
+ if u.Node != nil {
+ continue
+ }
+ if n, ok := allFields[u.Name]; ok {
+ u.Node = n
+ u.Scope = f
+ continue
+ }
+ f.Unresolved[k] = u
+ k++
+ }
+ f.Unresolved = f.Unresolved[:k]
+ // TODO: also need to resolve types.
+ // if len(f.Unresolved) > 0 {
+ // n := f.Unresolved[0]
+ // return ctx.mkErr(newBase(n), "unresolved reference %s", n.Name)
+ // }
+ return errs
+}
diff --git a/internal/core/runtime/resolve_test.go b/internal/core/runtime/resolve_test.go
new file mode 100644
index 0000000..42ec1dd
--- /dev/null
+++ b/internal/core/runtime/resolve_test.go
@@ -0,0 +1,63 @@
+// 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 (
+ "testing"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/build"
+)
+
+// TestPartiallyResolved tests that the resolve will detect the usage of
+// imports that are referenced by previously resolved nodes.
+func TestPartiallyResolved(t *testing.T) {
+ const importPath = "acme.com/foo"
+ spec1 := &ast.ImportSpec{
+ Path: ast.NewString(importPath),
+ }
+ spec2 := &ast.ImportSpec{
+ Name: ast.NewIdent("bar"),
+ Path: ast.NewString(importPath),
+ }
+
+ isBuiltin := func(s string) bool { return false }
+
+ f := &ast.File{
+ Decls: []ast.Decl{
+ &ast.ImportDecl{Specs: []*ast.ImportSpec{spec1, spec2}},
+ &ast.Field{
+ Label: ast.NewIdent("X"),
+ Value: &ast.Ident{Name: "foo", Node: spec1},
+ },
+ &ast.Alias{
+ Ident: ast.NewIdent("Y"),
+ Expr: &ast.Ident{Name: "bar", Node: spec2},
+ },
+ },
+ Imports: []*ast.ImportSpec{spec1, spec2},
+ }
+
+ err := ResolveFile(nil, f, &build.Instance{
+ Imports: []*build.Instance{{
+ ImportPath: importPath,
+ PkgName: "foo",
+ }},
+ }, map[string]ast.Node{}, isBuiltin)
+
+ if err != nil {
+ t.Errorf("exected no error, found %v", err)
+ }
+}
diff --git a/internal/core/runtime/runtime.go b/internal/core/runtime/runtime.go
index c2f7d83..61ae9a1 100644
--- a/internal/core/runtime/runtime.go
+++ b/internal/core/runtime/runtime.go
@@ -15,22 +15,59 @@
package runtime
import (
+ "reflect"
+
+ "cuelang.org/go/cue/ast"
"cuelang.org/go/cue/errors"
"cuelang.org/go/internal/core/adt"
)
// A Runtime maintains data structures for indexing and resuse for evaluation.
type Runtime struct {
- *index
+ index *Index
}
// New creates a new Runtime.
func New() *Runtime {
return &Runtime{
- index: newIndex(sharedIndex),
+ index: NewIndex(SharedIndexNew),
}
}
+func (x *Runtime) IndexToString(i int64) string {
+ return x.index.IndexToString(i)
+}
+
+func (x *Runtime) StringToIndex(s string) int64 {
+ return x.index.StringToIndex(s)
+}
+
func (x *Runtime) LoadImport(importPath string) (*adt.Vertex, errors.Error) {
- return nil, nil
+ v := x.index.GetImportFromPath(importPath)
+ if v == nil {
+ return nil, nil
+ }
+ return v.(*adt.Vertex), nil
+}
+
+func (x *Runtime) StoreType(t reflect.Type, src ast.Expr, expr adt.Expr) {
+ if expr == nil {
+ x.index.StoreType(t, src)
+ } else {
+ x.index.StoreType(t, expr)
+ }
+}
+
+func (x *Runtime) LoadType(t reflect.Type) (src ast.Expr, expr adt.Expr, ok bool) {
+ v, ok := x.index.LoadType(t)
+ if ok {
+ switch x := v.(type) {
+ case ast.Expr:
+ return x, nil, true
+ case adt.Expr:
+ src, _ = x.Source().(ast.Expr)
+ return src, x, true
+ }
+ }
+ return nil, nil, false
}