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/cue/build.go b/cue/build.go
index 634cdb1..846c48f 100644
--- a/cue/build.go
+++ b/cue/build.go
@@ -15,8 +15,6 @@
package cue
import (
- "path"
- "strconv"
"sync"
"cuelang.org/go/cue/ast"
@@ -25,6 +23,7 @@
"cuelang.org/go/cue/errors"
"cuelang.org/go/cue/token"
"cuelang.org/go/internal"
+ "cuelang.org/go/internal/core/runtime"
)
// A Runtime is used for creating CUE interpretations.
@@ -193,121 +192,25 @@
//
// All instances belonging to the same package should share this index.
type index struct {
- labelMap map[string]label
- labels []string
+ *runtime.Index
- loaded map[*build.Instance]*Instance
- imports map[value]*Instance // key is always a *structLit
- importsByPath map[string]*Instance
-
- offset label
- parent *index
-
- mutex sync.Mutex
- typeCache sync.Map // map[reflect.Type]evaluated
+ loaded map[*build.Instance]*Instance
+ mutex sync.Mutex
}
-// work around golang-ci linter bug: fields are used.
-func init() {
- var i index
- i.mutex.Lock()
- i.mutex.Unlock()
- i.typeCache.Load(1)
-}
-
-const sharedOffset = 0x40000000
-
// sharedIndex is used for indexing builtins and any other labels common to
// all instances.
-var sharedIndex = newSharedIndex()
-
-func newSharedIndex() *index {
- // TODO: nasty hack to indicate FileSet of shared index. Remove the whole
- // FileSet idea from the API. Just take the hit of the extra pointers for
- // positions in the ast, and then optimize the storage in an abstract
- // machine implementation for storing graphs.
- i := &index{
- labelMap: map[string]label{"": 0},
- labels: []string{""},
- imports: map[value]*Instance{},
- importsByPath: map[string]*Instance{},
- }
- return i
+var sharedIndex = &index{
+ Index: runtime.SharedIndex,
+ loaded: map[*build.Instance]*Instance{},
}
// newIndex creates a new index.
func newIndex(parent *index) *index {
- i := &index{
- labelMap: map[string]label{},
- loaded: map[*build.Instance]*Instance{},
- imports: map[value]*Instance{},
- importsByPath: map[string]*Instance{},
- offset: label(len(parent.labels)) + parent.offset,
- parent: parent,
+ return &index{
+ Index: runtime.NewIndex(parent.Index),
+ loaded: map[*build.Instance]*Instance{},
}
- return i
-}
-
-func (idx *index) StrLabel(str string) label {
- return idx.Label(str, false)
-}
-
-func (idx *index) NodeLabel(n ast.Node) (f label, ok bool) {
- switch x := n.(type) {
- case *ast.BasicLit:
- name, _, err := ast.LabelName(x)
- return idx.Label(name, false), err == nil
- case *ast.Ident:
- name, err := ast.ParseIdent(x)
- return idx.Label(name, true), err == nil
- }
- return 0, false
-}
-
-func (idx *index) HasLabel(s string) (ok bool) {
- for x := idx; x != nil; x = x.parent {
- _, ok = x.labelMap[s]
- if ok {
- break
- }
- }
- return ok
-}
-
-func (idx *index) findLabel(s string) (f label, ok bool) {
- for x := idx; x != nil; x = x.parent {
- f, ok = x.labelMap[s]
- if ok {
- break
- }
- }
- return f, ok
-}
-
-func (idx *index) Label(s string, isIdent bool) label {
- f, ok := idx.findLabel(s)
- if !ok {
- f = label(len(idx.labelMap)) + idx.offset
- idx.labelMap[s] = f
- idx.labels = append(idx.labels, s)
- }
- f <<= labelShift
- if isIdent {
- if internal.IsDef(s) {
- f |= definition
- }
- if internal.IsHidden(s) {
- f |= hidden
- }
- }
- return f
-}
-
-func (idx *index) LabelStr(l label) string {
- l >>= labelShift
- for ; l < idx.offset; idx = idx.parent {
- }
- return idx.labels[l-idx.offset]
}
func isBuiltin(s string) bool {
@@ -331,7 +234,7 @@
if inst.Err == nil {
// inst.instance.index.state = s
// inst.instance.inst = p
- inst.Err = resolveFiles(idx, p, isBuiltin)
+ inst.Err = runtime.ResolveFiles(idx.Index, p, isBuiltin)
for _, f := range files {
err := inst.insertFile(f)
inst.Err = errors.Append(inst.Err, err)
@@ -342,144 +245,3 @@
inst.complete = true
return inst
}
-
-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/cue/build_test.go b/cue/build_test.go
index 51309a5..7817ec7 100644
--- a/cue/build_test.go
+++ b/cue/build_test.go
@@ -53,45 +53,6 @@
}
}
-// 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),
- }
-
- 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)
- }
-}
-
func TestBuild(t *testing.T) {
files := func(s ...string) []string { return s }
insts := func(i ...*bimport) []*bimport { return i }
diff --git a/cue/debug.go b/cue/debug.go
index 3de2dcf..12fbe73 100644
--- a/cue/debug.go
+++ b/cue/debug.go
@@ -143,8 +143,8 @@
}
str := p.ctx.LabelStr(f)
- if strings.HasPrefix(str, "#") && f&definition == 0 ||
- strings.HasPrefix(str, "_") && f&hidden == 0 ||
+ if strings.HasPrefix(str, "#") && !f.IsDef() ||
+ strings.HasPrefix(str, "_") && !f.IsHidden() ||
!ast.IsValidIdent(str) {
return strconv.Quote(str)
}
@@ -381,7 +381,7 @@
if x.optional {
p.write("?")
}
- if x.definition && x.feature&definition == 0 {
+ if x.definition && !x.feature.IsDef() {
p.write(" :: ")
} else {
p.write(": ")
diff --git a/cue/export.go b/cue/export.go
index ea5e9d0..f86592d 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -146,8 +146,8 @@
func (p *exporter) label(f label) ast.Label {
str := p.ctx.LabelStr(f)
- if strings.HasPrefix(str, "#") && f&definition == 0 ||
- strings.HasPrefix(str, "_") && f&hidden == 0 ||
+ if strings.HasPrefix(str, "#") && !f.IsDef() ||
+ strings.HasPrefix(str, "_") && !f.IsHidden() ||
!ast.IsValidIdent(str) {
return ast.NewLit(token.STRING, strconv.Quote(str))
}
@@ -878,7 +878,7 @@
f.Token = token.ISA
}
}
- if a.feature&hidden != 0 && p.mode.concrete && p.mode.omitHidden {
+ if a.feature.IsHidden() && p.mode.concrete && p.mode.omitHidden {
continue
}
oldInDef := p.inDef
diff --git a/cue/go.go b/cue/go.go
index 20dee9a..2be1ae0 100644
--- a/cue/go.go
+++ b/cue/go.go
@@ -508,7 +508,7 @@
}
func goTypeToValueRec(ctx *context, allowNullDefault bool, t reflect.Type) (e value) {
- if e, ok := ctx.typeCache.Load(t); ok {
+ if e, ok := ctx.LoadType(t); ok {
return e.(value)
}
@@ -578,7 +578,7 @@
// resolve field tags to allow field tags to refer to the struct fields.
tags := map[label]string{}
obj := newStruct(baseValue{})
- ctx.typeCache.Store(t, obj)
+ ctx.StoreType(t, obj)
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
@@ -674,7 +674,7 @@
store:
// TODO: store error if not nil?
if e != nil {
- ctx.typeCache.Store(t, e)
+ ctx.StoreType(t, e)
}
return e
}
diff --git a/cue/instance.go b/cue/instance.go
index 0ec3dee..4258436 100644
--- a/cue/instance.go
+++ b/cue/instance.go
@@ -20,6 +20,7 @@
"cuelang.org/go/cue/errors"
"cuelang.org/go/cue/token"
"cuelang.org/go/internal"
+ "cuelang.org/go/internal/core/runtime"
)
// An Instance defines a single configuration based on a collection of
@@ -48,23 +49,25 @@
}
func (x *index) addInst(p *Instance) *Instance {
- if p.rootStruct == nil {
- panic("struct must not be nil")
- }
+ x.Index.AddInst(p.ImportPath, p.rootStruct, p)
p.index = x
- x.imports[p.rootValue] = p
- if p.ImportPath != "" {
- x.importsByPath[p.ImportPath] = p
- }
return p
}
func (x *index) getImportFromNode(v value) *Instance {
- imp := x.imports[v]
- if imp == nil && x.parent != nil {
- return x.parent.getImportFromNode(v)
+ p := x.Index.GetImportFromNode(v)
+ if p == nil {
+ return nil
}
- return imp
+ return p.(*Instance)
+}
+
+func (x *index) getImportFromPath(id string) *Instance {
+ node := x.Index.GetImportFromPath(id)
+ if node == nil {
+ return nil
+ }
+ return x.Index.GetImportFromNode(node).(*Instance)
}
func init() {
@@ -251,7 +254,7 @@
}
i.scope = v.obj
- if err := resolveFiles(idx, p, isBuiltin); err != nil {
+ if err := runtime.ResolveFiles(idx.Index, p, isBuiltin); err != nil {
i.setError(err)
return i
}
diff --git a/cue/marshal.go b/cue/marshal.go
index a1e3509..4ae5cb7 100644
--- a/cue/marshal.go
+++ b/cue/marshal.go
@@ -19,6 +19,7 @@
"compress/gzip"
"encoding/gob"
"path/filepath"
+ "strings"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/build"
@@ -183,8 +184,8 @@
p := len(staged) - 1
for _, imp := range imports {
- i := ctx.importsByPath[imp]
- if i == nil {
+ i := ctx.getImportFromPath(imp)
+ if i == nil || !strings.Contains(imp, ".") {
continue // a builtin package.
}
stageInstance(i)
diff --git a/cue/types.go b/cue/types.go
index e1f2ce2..bdbcbe0 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -286,7 +286,7 @@
// IsHidden reports if a field is hidden from the data model.
func (i *Iterator) IsHidden() bool {
- return i.f&hidden != 0
+ return i.f.IsHidden()
}
// IsOptional reports if a field is optional.
@@ -564,7 +564,7 @@
a = append(a, strconv.FormatInt(int64(v.index), 10))
case structKind:
f := idx.LabelStr(v.arc.feature)
- if v.arc.feature&(hidden|definition) == 0 {
+ if l := v.arc.feature; !l.IsDef() && !l.IsHidden() {
if !isIdent(f) && !isNumber(f) {
f = quote(f, '"')
}
@@ -1341,7 +1341,7 @@
break
}
}
- needFilter = needFilter || f&hidden != 0
+ needFilter = needFilter || f.IsHidden()
}
if needFilter {
@@ -1351,7 +1351,7 @@
if a.definition && (o.omitDefinitions || o.concrete) {
continue
}
- if a.feature&hidden != 0 && o.omitHidden {
+ if a.feature.IsHidden() && o.omitHidden {
continue
}
if o.omitOptional && a.optional {
@@ -1441,7 +1441,7 @@
v := Value{ctx.index, &valueData{s.v.path, uint32(i), a}}
str := ctx.LabelStr(a.feature)
- return FieldInfo{str, i, v, a.definition, a.optional, a.feature&hidden != 0}
+ return FieldInfo{str, i, v, a.definition, a.optional, a.feature.IsHidden()}
}
// FieldByName looks up a field for the given name. If isIdent is true, it will
@@ -1518,7 +1518,7 @@
f := v.ctx().Label(name, true)
for i, a := range o.arcs {
if a.feature == f {
- if f&hidden != 0 || !a.definition || a.optional {
+ if f.IsHidden() || !a.definition || a.optional {
break
}
return newChildValue(&o, i)
diff --git a/cue/value.go b/cue/value.go
index fd5ada1..14ebb70 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -801,8 +801,7 @@
if o == nil {
return false
}
-
- if f&(hidden|definition) != 0 {
+ if f.IsDef() || f.IsHidden() {
return false
}
@@ -975,7 +974,7 @@
func (x *structLit) allows(ctx *context, f label) bool {
return !x.closeStatus.isClosed() ||
- f&hidden != 0 ||
+ f.IsHidden() ||
x.optionals.allows(ctx, f)
}
@@ -1206,7 +1205,7 @@
return v, nil
}
- if x.arcs[i].feature&(hidden|definition) == 0 {
+ if f := x.arcs[i].feature; !f.IsHidden() && !f.IsDef() {
name := ctx.LabelStr(x.arcs[i].feature)
arg := &stringLit{x.baseValue, name, nil}
@@ -1226,13 +1225,6 @@
// A label is a canonicalized feature name.
type label = adt.Feature
-const (
- hidden label = 0x01 // only set iff identifier starting with _ or #_
- definition label = 0x02 // only set iff identifier starting with #
-
- labelShift = 2
-)
-
// An arc holds the label-value pair.
//
// A fully evaluated arc has either a node or a value. An unevaluated arc,
@@ -1917,7 +1909,7 @@
ctx.LabelStr(a.feature),
nil,
}
- if a.definition || a.optional || a.feature&hidden != 0 {
+ if a.definition || a.optional || a.feature.IsHidden() {
continue
}
val := src.at(ctx, i)
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
}