Skip to content

Commit

Permalink
cue: hoist functionality to cue/internal/runtime
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
mpvl committed Jul 19, 2020
1 parent 3efd10d commit b2ea648
Show file tree
Hide file tree
Showing 14 changed files with 484 additions and 371 deletions.
260 changes: 11 additions & 249 deletions cue/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
package cue

import (
"path"
"strconv"
"sync"

"cuelang.org/go/cue/ast"
Expand All @@ -25,6 +23,7 @@ import (
"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.
Expand Down Expand Up @@ -193,121 +192,25 @@ func (r *Runtime) FromExpr(expr ast.Expr) (*Instance, error) {
//
// 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
}

// work around golang-ci linter bug: fields are used.
func init() {
var i index
i.mutex.Lock()
i.mutex.Unlock()
i.typeCache.Load(1)
loaded map[*build.Instance]*Instance
mutex sync.Mutex
}

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 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 &index{
Index: runtime.NewIndex(parent.Index),
loaded: map[*build.Instance]*Instance{},
}
return idx.labels[l-idx.offset]
}

func isBuiltin(s string) bool {
Expand All @@ -331,7 +234,7 @@ func (idx *index) loadInstance(p *build.Instance) *Instance {
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)
Expand All @@ -342,144 +245,3 @@ func (idx *index) loadInstance(p *build.Instance) *Instance {
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
}
39 changes: 0 additions & 39 deletions cue/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,45 +53,6 @@ func TestFromExpr(t *testing.T) {
}
}

// 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 }
Expand Down
6 changes: 3 additions & 3 deletions cue/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ func (p *printer) label(f label) string {
}

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)
}
Expand Down Expand Up @@ -381,7 +381,7 @@ func (p *printer) str(v interface{}) {
if x.optional {
p.write("?")
}
if x.definition && x.feature&definition == 0 {
if x.definition && !x.feature.IsDef() {
p.write(" :: ")
} else {
p.write(": ")
Expand Down
6 changes: 3 additions & 3 deletions cue/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ func (p *exporter) unique(s string) string {

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))
}
Expand Down Expand Up @@ -878,7 +878,7 @@ func (p *exporter) structure(x *structLit, addTempl bool) (ret *ast.StructLit, e
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
Expand Down

0 comments on commit b2ea648

Please sign in to comment.