internal/core/compile: add new compiler
compile: compiler, but still needs support for builtins and imports
debug: debug representation of internal structures
runtime: just string uniquing functionality for now
cuetxtar: added txtar support, including auto --update and --todo
support.
Change-Id: Idadf6045867b55381f0b3e0da3bc772fa40f7a04
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5840
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index c36ea9c..0a607ec 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -251,7 +251,7 @@
// =~"Name$"
//
type BoundValue struct {
- Src ast.Node
+ Src ast.Expr
Op Op
Value Value
K Kind
@@ -467,6 +467,7 @@
// A Conjunction is a conjunction of values that cannot be represented as a
// single value. It is the result of unification.
type Conjunction struct {
+ Src ast.Expr
Values []Value
}
diff --git a/internal/core/compile/compile.go b/internal/core/compile/compile.go
new file mode 100644
index 0000000..018b99b
--- /dev/null
+++ b/internal/core/compile/compile.go
@@ -0,0 +1,843 @@
+// 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 compile
+
+import (
+ "fmt"
+ "strings"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/literal"
+ "cuelang.org/go/cue/token"
+ "cuelang.org/go/internal"
+ "cuelang.org/go/internal/core/adt"
+ "cuelang.org/go/internal/core/runtime"
+ "golang.org/x/xerrors"
+)
+
+// Config configures a compilation.
+type Config struct {
+}
+
+// Files compiles the given files as a single instance. It disregards
+// the package names and it is the responsibility of the user to verify that
+// the packages names are consistent.
+//
+// Files may return a completed parse even if it has errors.
+func Files(cfg *Config, r *runtime.Runtime, files ...*ast.File) (*adt.Vertex, errors.Error) {
+ c := &compiler{index: r}
+
+ v := c.compileFiles(files)
+
+ if c.errs != nil {
+ return v, c.errs
+ }
+ return v, nil
+}
+
+type compiler struct {
+ index adt.StringIndexer
+
+ stack []frame
+ inSelector int
+
+ fileScope map[adt.Feature]bool
+
+ num literal.NumInfo
+
+ errs errors.Error
+}
+
+func (c *compiler) reset() {
+ c.fileScope = nil
+ c.stack = c.stack[:0]
+ c.errs = nil
+}
+
+func (c *compiler) errf(n ast.Node, format string, args ...interface{}) *adt.Bottom {
+ err := &compilerError{
+ n: n,
+ path: c.path(),
+ Message: errors.NewMessage(format, args),
+ }
+ c.errs = errors.Append(c.errs, err)
+ return &adt.Bottom{}
+}
+
+func (c *compiler) path() []string {
+ a := []string{}
+ for _, f := range c.stack {
+ if f.label != nil {
+ a = append(a, f.label.labelString())
+ }
+ }
+ return a
+}
+
+type frame struct {
+ label labeler // path name leading to this frame.
+ scope ast.Node // *ast.File or *ast.Struct
+ field *ast.Field
+ // scope map[ast.Node]bool
+ upCount int32 // 1 for field, 0 for embedding.
+
+ aliases map[string]aliasEntry
+}
+
+type aliasEntry struct {
+ expr adt.Expr
+ source ast.Node
+ used bool
+}
+
+func (c *compiler) insertAlias(id *ast.Ident, a aliasEntry) *adt.Bottom {
+ k := len(c.stack) - 1
+ m := c.stack[k].aliases
+ if m == nil {
+ m = map[string]aliasEntry{}
+ c.stack[k].aliases = m
+ }
+
+ if id == nil || !ast.IsValidIdent(id.Name) {
+ return c.errf(a.source, "invalid identifier name")
+ }
+
+ if e, ok := m[id.Name]; ok {
+ return c.errf(a.source,
+ "alias %q already declared; previous declaration at %s",
+ id.Name, e.source.Pos())
+ }
+
+ m[id.Name] = a
+ return nil
+}
+
+// lookupAlias looks up an alias with the given name at the k'th stack position.
+func (c compiler) lookupAlias(k int, id *ast.Ident) aliasEntry {
+ m := c.stack[k].aliases
+ name := id.Name
+ entry, ok := m[name]
+
+ if !ok {
+ err := c.errf(id, "could not find LetClause associated with identifier %q", name)
+ return aliasEntry{expr: err}
+ }
+
+ entry.used = true
+ m[name] = entry
+ return entry
+}
+
+func (c *compiler) pushScope(n labeler, upCount int32, id ast.Node) *frame {
+ c.stack = append(c.stack, frame{
+ label: n,
+ scope: id,
+ upCount: upCount,
+ })
+ return &c.stack[len(c.stack)-1]
+}
+
+func (c *compiler) popScope() {
+ k := len(c.stack) - 1
+ f := c.stack[k]
+ for k, v := range f.aliases {
+ if !v.used {
+ c.errf(v.source, "unreferenced alias or let clause %s", k)
+ }
+ }
+ c.stack = c.stack[:k]
+}
+
+// entry points // USE CONFIG
+func (c *compiler) compileFiles(a []*ast.File) *adt.Vertex { // Or value?
+ c.fileScope = map[adt.Feature]bool{}
+
+ // Populate file scope to handle unresolved references. Note that we do
+ // not allow aliases to be resolved across file boundaries.
+ for _, f := range a {
+ for _, d := range f.Decls {
+ if f, ok := d.(*ast.Field); ok {
+ if id, ok := f.Label.(*ast.Ident); ok {
+ c.fileScope[c.label(id)] = true
+ }
+ }
+ }
+ }
+
+ // TODO: set doc.
+ res := &adt.Vertex{}
+
+ // env := &adt.Environment{Vertex: nil} // runtime: c.runtime
+
+ 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.MakeConjunct(nil, v))
+ c.popScope()
+ }
+
+ return res
+}
+
+// resolve assumes that all existing resolutions are legal. Validation should
+// be done in a separate step if required.
+//
+// TODO: collect validation pass to verify that all resolutions are
+// legal?
+func (c *compiler) resolve(n *ast.Ident) adt.Expr {
+ // X in import "path/X"
+ // X in import X "path"
+ if imp, ok := n.Node.(*ast.ImportSpec); ok {
+ return &adt.ImportReference{
+ Src: n,
+ ImportPath: c.label(imp.Path),
+ Label: c.label(n),
+ }
+ }
+
+ label := c.label(n)
+
+ // Unresolved field.
+ if n.Node == nil {
+ upCount := int32(0)
+ for _, c := range c.stack {
+ upCount += c.upCount
+ }
+ if c.fileScope[label] {
+ return &adt.FieldReference{
+ Src: n,
+ UpCount: upCount,
+ Label: label,
+ }
+ }
+
+ if p := predeclared(n); p != nil {
+ return p
+ }
+
+ return c.errf(n, "reference %q not found", n.Name)
+ }
+
+ // X in [X=x]: y Scope: Field Node: Expr (x)
+ // X in X=[x]: y Scope: Field Node: Field
+ if f, ok := n.Scope.(*ast.Field); ok {
+ upCount := int32(0)
+
+ k := len(c.stack) - 1
+ for ; k >= 0; k-- {
+ if c.stack[k].field == f {
+ break
+ }
+ upCount += c.stack[k].upCount
+ }
+
+ label := &adt.LabelReference{
+ Src: n,
+ UpCount: upCount,
+ }
+
+ if f, ok := n.Node.(*ast.Field); ok {
+ _ = c.lookupAlias(k, f.Label.(*ast.Alias).Ident) // mark as used
+ return &adt.DynamicReference{
+ Src: n,
+ UpCount: upCount,
+ Label: label,
+ }
+ }
+ return label
+ }
+
+ upCount := int32(0)
+
+ k := len(c.stack) - 1
+ for ; k >= 0; k-- {
+ if c.stack[k].scope == n.Scope {
+ break
+ }
+ upCount += c.stack[k].upCount
+ }
+ if k < 0 {
+ // This is a programmatic error and should never happen if the users
+ // just builds with the cue command or if astutil.Resolve is used
+ // correctly.
+ c.errf(n, "reference %q set to unknown node in AST; "+
+ "this can result from incorrect API usage or a compiler bug",
+ n.Name)
+ }
+
+ switch n.Node.(type) {
+ // Local expressions
+ case *ast.LetClause, *ast.Alias:
+ entry := c.lookupAlias(k, n)
+
+ return &adt.LetReference{
+ Src: n,
+ UpCount: upCount,
+ Label: label,
+ X: entry.expr,
+ }
+ }
+
+ if n.Scope == nil {
+ // Package.
+ // Should have been handled above.
+ panic("unreachable") // Or direct ancestor node?
+ }
+
+ // X=x: y
+ // X=(x): y
+ // X="\(x)": y
+ if f, ok := n.Node.(*ast.Field); ok {
+ a, ok := f.Label.(*ast.Alias)
+ if !ok {
+ return c.errf(n, "illegal reference %s", n.Name)
+ }
+ aliasInfo := c.lookupAlias(k, a.Ident) // marks alias as used.
+ lab, ok := a.Expr.(ast.Label)
+ if !ok {
+ return c.errf(a.Expr, "invalid label expression")
+ }
+ name, _, err := ast.LabelName(lab)
+ switch {
+ case xerrors.Is(err, ast.ErrIsExpression):
+ if aliasInfo.expr == nil {
+ panic("unreachable")
+ }
+ return &adt.DynamicReference{
+ Src: n,
+ UpCount: upCount,
+ Label: aliasInfo.expr,
+ }
+
+ case err != nil:
+ return c.errf(n, "invalid label: %v", err)
+
+ case name != "":
+ label = c.label(lab)
+
+ default:
+ return c.errf(n, "unsupported field alias %q", name)
+ }
+ }
+
+ return &adt.FieldReference{
+ Src: n,
+ UpCount: upCount,
+ Label: label,
+ }
+}
+
+func (c *compiler) addDecls(st *adt.StructLit, a []ast.Decl) {
+ for _, d := range a {
+ if x := c.decl(d); x != nil {
+ st.Decls = append(st.Decls, x)
+ }
+ }
+}
+
+func (c *compiler) decl(d ast.Decl) adt.Decl {
+ switch x := d.(type) {
+ case *ast.BadDecl:
+ return c.errf(d, "")
+
+ case *ast.Field:
+ lab := x.Label
+ if a, ok := lab.(*ast.Alias); ok {
+ if lab, ok = a.Expr.(ast.Label); !ok {
+ return c.errf(a, "alias expression is not a valid label")
+ }
+
+ e := aliasEntry{source: a}
+
+ switch lab.(type) {
+ case *ast.Ident, *ast.BasicLit:
+ // Even though we won't need the alias, we still register it
+ // for duplicate and failed reference detection.
+ default:
+ e.expr = c.expr(a.Expr)
+ }
+
+ if err := c.insertAlias(a.Ident, e); err != nil {
+ return err
+ }
+ }
+
+ value := c.labeledExpr(x, (*fieldLabel)(x), x.Value)
+
+ switch l := lab.(type) {
+ case *ast.Ident, *ast.BasicLit:
+ label := c.label(lab)
+
+ // TODO(legacy): remove: old-school definitions
+ if x.Token == token.ISA && !label.IsDef() {
+ name, isIdent, err := ast.LabelName(lab)
+ if err == nil && isIdent {
+ idx := c.index.StringToIndex(name)
+ label, _ = adt.MakeLabel(x.Pos(), idx, adt.DefinitionLabel)
+ }
+ }
+
+ if x.Optional == token.NoPos {
+ return &adt.Field{
+ Src: x,
+ Label: label,
+ Value: value,
+ }
+ } else {
+ return &adt.OptionalField{
+ Src: x,
+ Label: label,
+ Value: value,
+ }
+ }
+
+ case *ast.ListLit:
+ if len(l.Elts) != 1 {
+ // error
+ return c.errf(x, "list label must have one element")
+ }
+ elem := l.Elts[0]
+ // TODO: record alias for error handling? In principle it is okay
+ // to have duplicates, but we do want it to be used.
+ if a, ok := elem.(*ast.Alias); ok {
+ elem = a.Expr
+ }
+
+ return &adt.BulkOptionalField{
+ Src: x,
+ Filter: c.expr(elem),
+ Value: value,
+ }
+
+ case *ast.Interpolation: // *ast.ParenExpr,
+ if x.Token == token.ISA {
+ c.errf(x, "definitions not supported for interpolations")
+ }
+ return &adt.DynamicField{
+ Src: x,
+ Key: c.expr(l),
+ Value: value,
+ }
+ }
+
+ // An alias reference will have an expression that is looked up in the
+ // environment cash.
+ case *ast.LetClause:
+ // Cache the parsed expression. Creating a unique expression for each
+ // reference allows the computation to be shared given that we don't
+ // have fields for expressions. This, in turn, prevents exponential
+ // blowup. in x2: x1+x1, x3: x2+x2, ... patterns.
+
+ expr := c.labeledExpr(nil, (*letScope)(x), x.Expr)
+
+ a := aliasEntry{source: x, expr: expr}
+
+ if err := c.insertAlias(x.Ident, a); err != nil {
+ return err
+ }
+
+ case *ast.Alias:
+
+ expr := c.labeledExpr(nil, (*deprecatedAliasScope)(x), x.Expr)
+
+ // TODO(legacy): deprecated, remove this use of Alias
+ a := aliasEntry{source: x, expr: expr}
+
+ if err := c.insertAlias(x.Ident, a); err != nil {
+ return err
+ }
+
+ case *ast.CommentGroup:
+ // Nothing to do for a free-floating comment group.
+
+ case *ast.Attribute:
+ // Nothing to do for now for an attribute declaration.
+
+ case *ast.Ellipsis:
+ return &adt.Ellipsis{
+ Src: x,
+ Value: c.expr(x.Type),
+ }
+
+ case *ast.Comprehension:
+ return c.comprehension(x)
+
+ case *ast.EmbedDecl: // Deprecated
+ return c.embed(x.Expr)
+
+ case ast.Expr:
+ return c.embed(x)
+ }
+ return nil
+}
+
+func (c *compiler) elem(n ast.Expr) adt.Elem {
+ switch x := n.(type) {
+ case *ast.Ellipsis:
+ return &adt.Ellipsis{
+ Src: x,
+ Value: c.expr(x.Type),
+ }
+
+ case *ast.Comprehension:
+ return c.comprehension(x)
+
+ case ast.Expr:
+ return c.expr(x)
+ }
+ return nil
+}
+
+func (c *compiler) comprehension(x *ast.Comprehension) adt.Elem {
+ var cur adt.Yielder
+ var first adt.Elem
+ var prev, next *adt.Yielder
+ for _, v := range x.Clauses {
+ switch x := v.(type) {
+ case *ast.ForClause:
+ var key adt.Feature
+ if x.Key != nil {
+ key = c.label(x.Key)
+ }
+ y := &adt.ForClause{
+ Syntax: x,
+ Key: key,
+ Value: c.label(x.Value),
+ Src: c.expr(x.Source),
+ }
+ cur = y
+ c.pushScope((*forScope)(x), 1, v)
+ defer c.popScope()
+ next = &y.Dst
+
+ case *ast.IfClause:
+ y := &adt.IfClause{
+ Src: x,
+ Condition: c.expr(x.Condition),
+ }
+ cur = y
+ next = &y.Dst
+
+ case *ast.LetClause:
+ y := &adt.LetClause{
+ Src: x,
+ Label: c.label(x.Ident),
+ Expr: c.expr(x.Expr),
+ }
+ cur = y
+ c.pushScope((*letScope)(x), 1, v)
+ defer c.popScope()
+ next = &y.Dst
+ }
+
+ if prev != nil {
+ *prev = cur
+ } else {
+ var ok bool
+ if first, ok = cur.(adt.Elem); !ok {
+ return c.errf(x,
+ "first comprehension clause must be 'if' or 'for'")
+ }
+ }
+ prev = next
+ }
+
+ if y, ok := x.Value.(*ast.StructLit); !ok {
+ return c.errf(x.Value,
+ "comprehension value must be struct, found %T", y)
+ }
+
+ y := c.expr(x.Value)
+
+ st, ok := y.(*adt.StructLit)
+ if !ok {
+ // Error must have been generated.
+ return y
+ }
+
+ if prev != nil {
+ *prev = &adt.ValueClause{StructLit: st}
+ } else {
+ return c.errf(x, "comprehension value without clauses")
+ }
+
+ return first
+}
+
+func (c *compiler) embed(expr ast.Expr) adt.Expr {
+ switch n := expr.(type) {
+ case *ast.StructLit:
+ c.pushScope(nil, 1, n)
+ v := &adt.StructLit{Src: n}
+ c.addDecls(v, n.Elts)
+ c.popScope()
+ return v
+ }
+ return c.expr(expr)
+}
+
+func (c *compiler) labeledExpr(f *ast.Field, lab labeler, expr ast.Expr) adt.Expr {
+ k := len(c.stack) - 1
+ if c.stack[k].field != nil {
+ panic("expected nil field")
+ }
+ c.stack[k].label = lab
+ c.stack[k].field = f
+ value := c.expr(expr)
+ c.stack[k].label = nil
+ c.stack[k].field = nil
+ return value
+}
+
+func (c *compiler) expr(expr ast.Expr) adt.Expr {
+ switch n := expr.(type) {
+ case nil:
+ return nil
+ case *ast.Ident:
+ return c.resolve(n)
+
+ case *ast.StructLit:
+ c.pushScope(nil, 1, n)
+ v := &adt.StructLit{Src: n}
+ c.addDecls(v, n.Elts)
+ c.popScope()
+ return v
+
+ case *ast.ListLit:
+ v := &adt.ListLit{Src: n}
+ elts, ellipsis := internal.ListEllipsis(n)
+ for _, d := range elts {
+ elem := c.elem(d)
+
+ switch x := elem.(type) {
+ case nil:
+ case adt.Elem:
+ v.Elems = append(v.Elems, x)
+ default:
+ c.errf(d, "type %T not allowed in ListLit", d)
+ }
+ }
+ if ellipsis != nil {
+ d := &adt.Ellipsis{
+ Src: ellipsis,
+ Value: c.expr(ellipsis.Type),
+ }
+ v.Elems = append(v.Elems, d)
+ }
+ return v
+
+ case *ast.SelectorExpr:
+ c.inSelector++
+ ret := &adt.SelectorExpr{
+ Src: n,
+ X: c.expr(n.X),
+ Sel: c.label(n.Sel)}
+ c.inSelector--
+ return ret
+
+ case *ast.IndexExpr:
+ return &adt.IndexExpr{
+ Src: n,
+ X: c.expr(n.X),
+ Index: c.expr(n.Index),
+ }
+
+ case *ast.SliceExpr:
+ slice := &adt.SliceExpr{Src: n, X: c.expr(n.X)}
+ if n.Low != nil {
+ slice.Lo = c.expr(n.Low)
+ }
+ if n.High != nil {
+ slice.Hi = c.expr(n.High)
+ }
+ return slice
+
+ case *ast.BottomLit:
+ return &adt.Bottom{Src: n}
+
+ case *ast.BadExpr:
+ return c.errf(n, "invalid expression")
+
+ case *ast.BasicLit:
+ return c.parse(n)
+
+ case *ast.Interpolation:
+ if len(n.Elts) == 0 {
+ return c.errf(n, "invalid interpolation")
+ }
+ first, ok1 := n.Elts[0].(*ast.BasicLit)
+ last, ok2 := n.Elts[len(n.Elts)-1].(*ast.BasicLit)
+ if !ok1 || !ok2 {
+ return c.errf(n, "invalid interpolation")
+ }
+ if len(n.Elts) == 1 {
+ return c.expr(n.Elts[0])
+ }
+ lit := &adt.Interpolation{Src: n, K: adt.StringKind}
+ info, prefixLen, _, err := literal.ParseQuotes(first.Value, last.Value)
+ if err != nil {
+ return c.errf(n, "invalid interpolation: %v", err)
+ }
+ prefix := ""
+ for i := 0; i < len(n.Elts); i += 2 {
+ l, ok := n.Elts[i].(*ast.BasicLit)
+ if !ok {
+ return c.errf(n, "invalid interpolation")
+ }
+ s := l.Value
+ if !strings.HasPrefix(s, prefix) {
+ return c.errf(l, "invalid interpolation: unmatched ')'")
+ }
+ s = l.Value[prefixLen:]
+ x := parseString(c, l, info, s)
+ lit.Parts = append(lit.Parts, x)
+ if i+1 < len(n.Elts) {
+ lit.Parts = append(lit.Parts, c.expr(n.Elts[i+1]))
+ }
+ prefix = ")"
+ prefixLen = 1
+ }
+ return lit
+
+ case *ast.ParenExpr:
+ return c.expr(n.X)
+
+ case *ast.CallExpr:
+ call := &adt.CallExpr{Src: n, Fun: c.expr(n.Fun)}
+ for _, a := range n.Args {
+ call.Args = append(call.Args, c.expr(a))
+ }
+ return call
+
+ case *ast.UnaryExpr:
+ switch n.Op {
+ case token.NOT, token.ADD, token.SUB:
+ return &adt.UnaryExpr{
+ Src: n,
+ Op: adt.OpFromToken(n.Op),
+ X: c.expr(n.X),
+ }
+ case token.GEQ, token.GTR, token.LSS, token.LEQ,
+ token.NEQ, token.MAT, token.NMAT:
+ return &adt.BoundExpr{
+ Src: n,
+ Op: adt.OpFromToken(n.Op),
+ Expr: c.expr(n.X),
+ }
+
+ case token.MUL:
+ return c.errf(n, "preference mark not allowed at this position")
+ default:
+ return c.errf(n, "unsupported unary operator %q", n.Op)
+ }
+
+ case *ast.BinaryExpr:
+ switch n.Op {
+ case token.OR:
+ d := &adt.DisjunctionExpr{Src: n}
+ c.addDisjunctionElem(d, n.X, false)
+ c.addDisjunctionElem(d, n.Y, false)
+ return d
+
+ default:
+ // return updateBin(c,
+ return &adt.BinaryExpr{
+ Src: n,
+ Op: adt.OpFromToken(n.Op), // op
+ X: c.expr(n.X), // left
+ Y: c.expr(n.Y), // right
+ } // )
+ }
+
+ default:
+ panic(fmt.Sprintf("unknown expression type %T", n))
+ // return c.errf(n, "unknown expression type %T", n)
+ }
+}
+
+func (c *compiler) addDisjunctionElem(d *adt.DisjunctionExpr, n ast.Expr, mark bool) {
+ switch x := n.(type) {
+ case *ast.BinaryExpr:
+ if x.Op == token.OR {
+ c.addDisjunctionElem(d, x.X, mark)
+ c.addDisjunctionElem(d, x.Y, mark)
+ return
+ }
+ case *ast.UnaryExpr:
+ if x.Op == token.MUL {
+ d.HasDefaults = true
+ c.addDisjunctionElem(d, x.X, true)
+ return
+ }
+ }
+ d.Values = append(d.Values, adt.Disjunct{Val: c.expr(n), Default: mark})
+}
+
+func (c *compiler) parse(l *ast.BasicLit) (n adt.Expr) {
+ s := l.Value
+ if s == "" {
+ return c.errf(l, "invalid literal %q", s)
+ }
+ switch l.Kind {
+ case token.STRING:
+ info, nStart, _, err := literal.ParseQuotes(s, s)
+ if err != nil {
+ return c.errf(l, err.Error())
+ }
+ s := s[nStart:]
+ return parseString(c, l, info, s)
+
+ case token.FLOAT, token.INT:
+ err := literal.ParseNum(s, &c.num)
+ if err != nil {
+ return c.errf(l, "parse error: %v", err)
+ }
+ kind := adt.FloatKind
+ if c.num.IsInt() {
+ kind = adt.IntKind
+ }
+ n := &adt.Num{Src: l, K: kind}
+ if err = c.num.Decimal(&n.X); err != nil {
+ return c.errf(l, "error converting number to decimal: %v", err)
+ }
+ return n
+
+ case token.TRUE:
+ return &adt.Bool{Src: l, B: true}
+
+ case token.FALSE:
+ return &adt.Bool{Src: l, B: false}
+
+ case token.NULL:
+ return &adt.Null{Src: l}
+
+ default:
+ return c.errf(l, "unknown literal type")
+ }
+}
+
+// parseString decodes a string without the starting and ending quotes.
+func parseString(c *compiler, node ast.Expr, q literal.QuoteInfo, s string) (n adt.Expr) {
+ str, err := q.Unquote(s)
+ if err != nil {
+ return c.errf(node, "invalid string: %v", err)
+ }
+ if q.IsDouble() {
+ return &adt.String{Src: node, Str: str, RE: nil}
+ }
+ return &adt.Bytes{Src: node, B: []byte(str), RE: nil}
+}
diff --git a/internal/core/compile/compile_test.go b/internal/core/compile/compile_test.go
new file mode 100644
index 0000000..4f78d33
--- /dev/null
+++ b/internal/core/compile/compile_test.go
@@ -0,0 +1,121 @@
+// 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 compile_test
+
+import (
+ "flag"
+ "fmt"
+ "testing"
+
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/parser"
+ "cuelang.org/go/internal/core/compile"
+ "cuelang.org/go/internal/core/debug"
+ "cuelang.org/go/internal/core/runtime"
+ "cuelang.org/go/internal/cuetxtar"
+ "cuelang.org/go/pkg/strings"
+)
+
+var (
+ update = flag.Bool("update", false, "update the test files")
+ todo = flag.Bool("todo", false, "run tests marked with #todo-compile")
+)
+
+func TestCompile(t *testing.T) {
+ test := cuetxtar.TxTarTest{
+ Root: "../../../cue/testdata/",
+ Name: "compile",
+ Update: *update,
+ Skip: alwaysSkip,
+ ToDo: needFix,
+ }
+
+ if *todo {
+ test.ToDo = nil
+ }
+
+ r := runtime.New()
+
+ test.Run(t, func(t *cuetxtar.Test) {
+ // TODO: use high-level API.
+
+ a := t.ValidInstances()
+
+ v, err := compile.Files(nil, r, a[0].Files...)
+
+ // Write the results.
+ t.WriteErrors(err)
+
+ if v == nil {
+ return
+ }
+
+ for i, f := range a[0].Files {
+ if i > 0 {
+ fmt.Fprintln(t)
+ }
+ fmt.Fprintln(t, "---", t.Rel(f.Filename))
+ debug.WriteNode(t, r, v.Conjuncts[i].Expr(), &debug.Config{
+ Cwd: t.Dir,
+ })
+ }
+ fmt.Fprintln(t)
+ })
+}
+
+var alwaysSkip = map[string]string{
+ "fulleval/031_comparison against bottom": "fix bin op binding in test",
+}
+
+var needFix = map[string]string{
+ "export/020": "builtin",
+ "fulleval/027_len_of_incomplete_types": "builtin",
+ "fulleval/032_or_builtin_should_not_fail_on_non-concrete_empty_list": "builtin",
+ "fulleval/053_issue312": "builtin",
+ "resolve/034_closing_structs": "builtin",
+ "resolve/048_builtins": "builtin",
+
+ "fulleval/026_dont_convert_incomplete_errors_to_non-incomplete": "import",
+ "fulleval/044_Issue_#178": "import",
+ "fulleval/048_dont_pass_incomplete_values_to_builtins": "import",
+ "fulleval/049_alias_reuse_in_nested_scope": "import",
+ "fulleval/050_json_Marshaling_detects_incomplete": "import",
+ "fulleval/051_detectIncompleteYAML": "import",
+ "fulleval/052_detectIncompleteJSON": "import",
+ "fulleval/056_issue314": "import",
+ "resolve/013_custom_validators": "import",
+}
+
+// TestX is for debugging. Do not delete.
+func TestX(t *testing.T) {
+ in := `
+ `
+
+ if strings.TrimSpace(in) == "" {
+ t.Skip()
+ }
+
+ file, err := parser.ParseFile("TestX", in)
+ if err != nil {
+ t.Fatal(err)
+ }
+ r := runtime.New()
+
+ arc, err := compile.Files(nil, r, file)
+ if err != nil {
+ t.Error(errors.Details(err, nil))
+ }
+ t.Error(debug.NodeString(r, arc.Conjuncts[0].Expr(), nil))
+}
diff --git a/internal/core/compile/errors.go b/internal/core/compile/errors.go
new file mode 100644
index 0000000..fb97622
--- /dev/null
+++ b/internal/core/compile/errors.go
@@ -0,0 +1,48 @@
+// 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 compile
+
+import (
+ "strings"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/token"
+)
+
+var _ errors.Error = &compilerError{}
+
+type compilerError struct {
+ n ast.Node
+ path []string
+ errors.Message
+}
+
+func (e *compilerError) Position() token.Pos { return e.n.Pos() }
+func (e *compilerError) InputPositions() []token.Pos { return nil }
+func (e *compilerError) Path() []string { return e.path }
+func (e *compilerError) Error() string {
+ pos := e.n.Pos()
+ // Import cycles deserve special treatment.
+ if pos.IsValid() {
+ // Omit import stack. The full path to the file where the error
+ // is the most important thing.
+ return pos.String() + ": " + e.Message.Error()
+ }
+ if len(e.path) == 0 {
+ return e.Message.Error()
+ }
+ return strings.Join(e.path, ".") + ": " + e.Message.Error()
+}
diff --git a/internal/core/compile/label.go b/internal/core/compile/label.go
new file mode 100644
index 0000000..1c03298
--- /dev/null
+++ b/internal/core/compile/label.go
@@ -0,0 +1,173 @@
+// 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 compile
+
+import (
+ "strings"
+
+ "github.com/cockroachdb/apd/v2"
+ "golang.org/x/text/unicode/norm"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/literal"
+ "cuelang.org/go/cue/token"
+ "cuelang.org/go/internal/core/adt"
+)
+
+// LabelFromNode converts an ADT node to a feature.
+func (c *compiler) label(n ast.Node) adt.Feature {
+ index := c.index
+ switch x := n.(type) {
+ case *ast.Ident:
+ s := x.Name
+ i := index.StringToIndex(x.Name)
+ t := adt.StringLabel
+ switch {
+ case strings.HasPrefix(s, "#_"):
+ t = adt.HiddenDefinitionLabel
+ case strings.HasPrefix(s, "#"):
+ t = adt.DefinitionLabel
+ case strings.HasPrefix(s, "_"):
+ t = adt.HiddenLabel
+ }
+ f, err := adt.MakeLabel(n.Pos(), i, t)
+ if err != nil {
+ c.errf(n, "invalid identifier label: %v", err)
+ return adt.InvalidLabel
+ }
+ return f
+
+ case *ast.BasicLit:
+ switch x.Kind {
+ case token.STRING:
+ const msg = "invalid string label: %v"
+ s, err := literal.Unquote(x.Value)
+ if err != nil {
+ c.errf(n, msg, err)
+ return adt.InvalidLabel
+ }
+
+ i := int64(index.StringToIndex(norm.NFC.String(s)))
+ f, err := adt.MakeLabel(n.Pos(), i, adt.StringLabel)
+ if err != nil {
+ c.errf(n, msg, err)
+ }
+ return f
+
+ case token.INT:
+ const msg = "invalid int label: %v"
+ if err := literal.ParseNum(x.Value, &c.num); err != nil {
+ c.errf(n, msg, err)
+ return adt.InvalidLabel
+ }
+
+ var d apd.Decimal
+ if err := c.num.Decimal(&d); err != nil {
+ c.errf(n, msg, err)
+ return adt.InvalidLabel
+ }
+
+ i, err := d.Int64()
+ if err != nil {
+ c.errf(n, msg, err)
+ return adt.InvalidLabel
+ }
+
+ f, err := adt.MakeLabel(n.Pos(), i, adt.IntLabel)
+ if err != nil {
+ c.errf(n, msg, err)
+ return adt.InvalidLabel
+ }
+ return f
+
+ case token.FLOAT:
+ _ = c.errf(n, "float %s cannot be used as label", x.Value)
+ return adt.InvalidLabel
+
+ default: // keywords (null, true, false, for, in, if, let)
+ i := index.StringToIndex(x.Kind.String())
+ f, err := adt.MakeLabel(n.Pos(), i, adt.StringLabel)
+ if err != nil {
+ c.errf(n, "invalid string label: %v", err)
+ }
+ return f
+ }
+
+ default:
+ c.errf(n, "unsupported label node type %T", n)
+ return adt.InvalidLabel
+ }
+}
+
+// A labeler converts an AST node to a string representation.
+type labeler interface {
+ labelString() string
+}
+
+type fieldLabel ast.Field
+
+func (l *fieldLabel) labelString() string {
+ lab := l.Label
+
+ if a, ok := lab.(*ast.Alias); ok {
+ if x, _ := a.Expr.(ast.Label); x != nil {
+ lab = x
+ }
+ }
+
+ switch x := lab.(type) {
+ case *ast.Ident:
+ return x.Name
+
+ case *ast.BasicLit:
+ if x.Kind == token.STRING {
+ s, err := literal.Unquote(x.Value)
+ if err == nil && ast.IsValidIdent(s) {
+ return s
+ }
+ }
+ return x.Value
+
+ case *ast.ListLit:
+ return "[]" // TODO: more detail
+
+ case *ast.Interpolation:
+ return "?"
+ // case *ast.ParenExpr:
+ }
+ return "<unknown>"
+}
+
+type forScope ast.ForClause
+
+func (l *forScope) labelString() string {
+ // TODO: include more info in square brackets.
+ return "for[]"
+}
+
+type letScope ast.LetClause
+
+func (l *letScope) labelString() string {
+ // TODO: include more info in square brackets.
+ return "let[]"
+}
+
+// TODO(legacy): remove
+type deprecatedAliasScope ast.Alias
+
+func (l *deprecatedAliasScope) labelString() string {
+ // TODO: include more info in square brackets.
+ return "let[]"
+}
diff --git a/internal/core/compile/predeclared.go b/internal/core/compile/predeclared.go
new file mode 100644
index 0000000..fcadc36
--- /dev/null
+++ b/internal/core/compile/predeclared.go
@@ -0,0 +1,138 @@
+// 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 compile
+
+import (
+ "strconv"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/token"
+ "cuelang.org/go/internal/core/adt"
+)
+
+func predeclared(n *ast.Ident) adt.Expr {
+ // TODO: consider supporting GraphQL-style names:
+ // String, Bytes, Boolean, Integer, Number.
+ // These names will not conflict with idiomatic camel-case JSON.
+ switch n.Name {
+ case "_":
+ return &adt.Top{Src: n}
+ case "string", "__string":
+ return &adt.BasicType{Src: n, K: adt.StringKind}
+ case "bytes", "__bytes":
+ return &adt.BasicType{Src: n, K: adt.BytesKind}
+ case "bool", "__bool":
+ return &adt.BasicType{Src: n, K: adt.BoolKind}
+ case "int", "__int":
+ return &adt.BasicType{Src: n, K: adt.IntKind}
+ case "float", "__float":
+ return &adt.BasicType{Src: n, K: adt.FloatKind}
+ case "number", "__number":
+ return &adt.BasicType{Src: n, K: adt.NumKind}
+
+ // case "len", "__len":
+ // return lenBuiltin
+ // case "close", "__close":
+ // return closeBuiltin
+ // case "and", "__and":
+ // return andBuiltin
+ // case "or", "__or":
+ // return orBuiltin
+ }
+
+ if r, ok := predefinedRanges[n.Name]; ok {
+ return r
+ }
+
+ return nil
+}
+
+var predefinedRanges = map[string]adt.Expr{
+ "rune": mkIntRange("0", strconv.Itoa(0x10FFFF)),
+ "int8": mkIntRange("-128", "127"),
+ "int16": mkIntRange("-32768", "32767"),
+ "int32": mkIntRange("-2147483648", "2147483647"),
+ "int64": mkIntRange("-9223372036854775808", "9223372036854775807"),
+ "int128": mkIntRange(
+ "-170141183460469231731687303715884105728",
+ "170141183460469231731687303715884105727"),
+
+ // Do not include an alias for "byte", as it would be too easily confused
+ // with the builtin "bytes".
+ "uint": mkUint(),
+ "uint8": mkIntRange("0", "255"),
+ "uint16": mkIntRange("0", "65535"),
+ "uint32": mkIntRange("0", "4294967295"),
+ "uint64": mkIntRange("0", "18446744073709551615"),
+ "uint128": mkIntRange("0", "340282366920938463463374607431768211455"),
+
+ // 2**127 * (2**24 - 1) / 2**23
+ "float32": mkFloatRange(
+ "-3.40282346638528859811704183484516925440e+38",
+ "3.40282346638528859811704183484516925440e+38",
+ ),
+ // 2**1023 * (2**53 - 1) / 2**52
+ "float64": mkFloatRange(
+ "-1.797693134862315708145274237317043567981e+308",
+ "1.797693134862315708145274237317043567981e+308",
+ ),
+}
+
+// TODO: use an adt.BoundValue here.
+
+func mkUint() adt.Expr {
+ from := newBound(adt.GreaterEqualOp, adt.IntKind, parseInt("0"))
+ ident := ast.NewIdent("__int")
+ src := ast.NewBinExpr(token.AND, ident, from.Src)
+ return &adt.Conjunction{
+ Src: src,
+ Values: []adt.Value{
+ &adt.BasicType{Src: ident, K: adt.IntKind}, from,
+ },
+ }
+}
+
+func mkIntRange(a, b string) adt.Expr {
+ from := newBound(adt.GreaterEqualOp, adt.IntKind, parseInt(a))
+ to := newBound(adt.LessEqualOp, adt.IntKind, parseInt(b))
+ return &adt.BinaryExpr{nil, adt.AndOp, from, to}
+}
+
+func mkFloatRange(a, b string) adt.Expr {
+ from := newBound(adt.GreaterEqualOp, adt.NumKind, parseFloat(a))
+ to := newBound(adt.LessEqualOp, adt.NumKind, parseFloat(b))
+ return &adt.BinaryExpr{nil, adt.AndOp, from, to}
+}
+
+func newBound(op adt.Op, k adt.Kind, v adt.Value) *adt.BoundValue {
+ return &adt.BoundValue{Op: op, Value: v}
+}
+
+func parseInt(s string) *adt.Num {
+ return parseNum(adt.IntKind, s)
+}
+
+func parseFloat(s string) *adt.Num {
+ return parseNum(adt.FloatKind, s)
+}
+
+func parseNum(k adt.Kind, s string) *adt.Num {
+ num := &adt.Num{K: k}
+ _, _, err := num.X.SetString(s)
+ if err != nil {
+ panic(err)
+ }
+ return num
+}
diff --git a/internal/core/debug/debug.go b/internal/core/debug/debug.go
new file mode 100644
index 0000000..c0a5d1d
--- /dev/null
+++ b/internal/core/debug/debug.go
@@ -0,0 +1,450 @@
+// 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 debug prints a given ADT node.
+//
+// Note that the result is not valid CUE, but instead prints the internals
+// of an ADT node in human-readable form. It uses a simple indentation algorithm
+// for improved readability and diffing.
+//
+package debug
+
+import (
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/internal"
+ "cuelang.org/go/internal/core/adt"
+ "golang.org/x/xerrors"
+)
+
+const (
+ openTuple = "\u3008"
+ closeTuple = "\u3009"
+)
+
+type Config struct {
+ Cwd string
+}
+
+func WriteNode(w io.Writer, i adt.StringIndexer, n adt.Node, config *Config) {
+ if config == nil {
+ config = &Config{}
+ }
+ p := printer{Writer: w, index: i, cfg: config}
+ p.node(n)
+}
+
+func NodeString(i adt.StringIndexer, n adt.Node, config *Config) string {
+ if config == nil {
+ config = &Config{}
+ }
+ b := &strings.Builder{}
+ p := printer{Writer: b, index: i, cfg: config}
+ p.node(n)
+ return b.String()
+}
+
+type printer struct {
+ io.Writer
+ index adt.StringIndexer
+ indent string
+ cfg *Config
+}
+
+func (w *printer) string(s string) {
+ s = strings.Replace(s, "\n", "\n"+w.indent, -1)
+ _, _ = io.WriteString(w, s)
+}
+
+func (w *printer) label(f adt.Feature) {
+ w.string(w.labelString(f))
+}
+
+// TODO: fold into label once :: is no longer supported.
+func (w *printer) labelString(f adt.Feature) string {
+ return f.SelectorString(w.index)
+}
+
+func (w *printer) shortError(errs errors.Error) {
+ for {
+ msg, args := errs.Msg()
+ fmt.Fprintf(w, msg, args...)
+
+ err := xerrors.Unwrap(errs)
+ if err == nil {
+ break
+ }
+
+ if errs, _ = err.(errors.Error); errs != nil {
+ w.string(err.Error())
+ break
+ }
+ }
+}
+
+func (w *printer) node(n adt.Node) {
+ switch x := n.(type) {
+ case *adt.Vertex:
+ var kind adt.Kind
+ if x.Value != nil {
+ kind = x.Value.Kind()
+ }
+
+ kindStr := kind.String()
+ kindStr = strings.ReplaceAll(kindStr, "{...}", "struct")
+ kindStr = strings.ReplaceAll(kindStr, "[...]", "list")
+
+ fmt.Fprintf(w, "(%s){", kindStr)
+
+ if x.Value != nil && kind&^(adt.StructKind|adt.ListKind) != 0 {
+ w.string(" ")
+ w.node(x.Value)
+ w.string(" }")
+ return
+ }
+
+ saved := w.indent
+ w.indent += " "
+
+ if b, ok := x.Value.(*adt.Bottom); ok {
+ saved := w.indent
+ w.indent += "// "
+ w.string("\n")
+ w.string(strings.TrimSpace(errors.Details(b.Err, &errors.Config{
+ Cwd: w.cfg.Cwd,
+ ToSlash: true,
+ })))
+ w.indent = saved
+ }
+
+ if len(x.Arcs) > 0 {
+ for _, a := range x.Arcs {
+ w.string("\n")
+ w.label(a.Label)
+ w.string(": ")
+ w.node(a)
+ }
+ }
+
+ if x.Value == nil {
+ w.indent += "// "
+ w.string("// ")
+ for i, c := range x.Conjuncts {
+ if i > 0 {
+ w.string(" & ")
+ }
+ w.node(c.Expr()) // TODO: also include env?
+ }
+ }
+
+ w.indent = saved
+ w.string("\n")
+ w.string("}")
+
+ case *adt.StructMarker:
+ w.string("struct")
+
+ case *adt.ListMarker:
+ w.string("list")
+
+ case *adt.StructLit:
+ if len(x.Decls) == 0 {
+ w.string("{}")
+ break
+ }
+ w.string("{")
+ w.indent += " "
+ for _, d := range x.Decls {
+ w.string("\n")
+ w.node(d)
+ }
+ w.indent = w.indent[:len(w.indent)-2]
+ w.string("\n}")
+
+ case *adt.ListLit:
+ if len(x.Elems) == 0 {
+ w.string("[]")
+ break
+ }
+ w.string("[")
+ w.indent += " "
+ for _, d := range x.Elems {
+ w.string("\n")
+ w.node(d)
+ w.string(",")
+ }
+ w.indent = w.indent[:len(w.indent)-2]
+ w.string("\n]")
+
+ case *adt.Field:
+ s := w.labelString(x.Label)
+ w.string(s)
+ w.string(":")
+ if x.Label.IsDef() && !internal.IsDef(s) {
+ w.string(":")
+ }
+ w.string(" ")
+ w.node(x.Value)
+
+ case *adt.OptionalField:
+ s := w.labelString(x.Label)
+ w.string(s)
+ w.string("?:")
+ if x.Label.IsDef() && !internal.IsDef(s) {
+ w.string(":")
+ }
+ w.string(" ")
+ w.node(x.Value)
+
+ case *adt.BulkOptionalField:
+ w.string("[")
+ w.node(x.Filter)
+ w.string("]: ")
+ w.node(x.Value)
+
+ case *adt.DynamicField:
+ w.node(x.Key)
+ if x.IsOptional() {
+ w.string("?")
+ }
+ w.string(": ")
+ w.node(x.Value)
+
+ case *adt.Ellipsis:
+ w.string("...")
+ if x.Value != nil {
+ w.node(x.Value)
+ }
+
+ case *adt.Bottom:
+ w.string(`_|_`)
+ if x.Err != nil {
+ w.string("(")
+ w.shortError(x.Err)
+ w.string(")")
+ }
+
+ case *adt.Null:
+ w.string("null")
+
+ case *adt.Bool:
+ fmt.Fprint(w, x.B)
+
+ case *adt.Num:
+ fmt.Fprint(w, &x.X)
+
+ case *adt.String:
+ w.string(strconv.Quote(x.Str))
+
+ case *adt.Bytes:
+ b := []byte(strconv.Quote(string(x.B)))
+ b[0] = '\''
+ b[len(b)-1] = '\''
+ w.string(string(b))
+
+ case *adt.Top:
+ w.string("_")
+
+ case *adt.BasicType:
+ fmt.Fprint(w, x.K)
+
+ case *adt.BoundExpr:
+ fmt.Fprint(w, x.Op)
+ w.node(x.Expr)
+
+ case *adt.BoundValue:
+ fmt.Fprint(w, x.Op)
+ w.node(x.Value)
+
+ case *adt.FieldReference:
+ w.string(openTuple)
+ w.string(strconv.Itoa(int(x.UpCount)))
+ w.string(";")
+ w.label(x.Label)
+ w.string(closeTuple)
+
+ case *adt.LabelReference:
+ w.string(openTuple)
+ w.string(strconv.Itoa(int(x.UpCount)))
+ w.string(";-")
+ w.string(closeTuple)
+
+ case *adt.DynamicReference:
+ w.string(openTuple)
+ w.string(strconv.Itoa(int(x.UpCount)))
+ w.string(";(")
+ w.node(x.Label)
+ w.string(")")
+ w.string(closeTuple)
+
+ case *adt.ImportReference:
+ w.string(openTuple + "import;")
+ w.label(x.ImportPath)
+ w.string(closeTuple)
+
+ case *adt.LetReference:
+ w.string(openTuple)
+ w.string(strconv.Itoa(int(x.UpCount)))
+ w.string(";let ")
+ w.label(x.Label)
+ w.string(closeTuple)
+
+ case *adt.SelectorExpr:
+ w.node(x.X)
+ w.string(".")
+ w.label(x.Sel)
+
+ case *adt.IndexExpr:
+ w.node(x.X)
+ w.string("[")
+ w.node(x.Index)
+ w.string("]")
+
+ case *adt.SliceExpr:
+ w.node(x.X)
+ w.string("[")
+ if x.Lo != nil {
+ w.node(x.Lo)
+ }
+ w.string(":")
+ if x.Hi != nil {
+ w.node(x.Hi)
+ }
+ if x.Stride != nil {
+ w.string(":")
+ w.node(x.Stride)
+ }
+ w.string("]")
+
+ case *adt.Interpolation:
+ w.string(`"`)
+ for i := 0; i < len(x.Parts); i += 2 {
+ if s, ok := x.Parts[i].(*adt.String); ok {
+ w.string(s.Str)
+ } else {
+ w.string("<bad string>")
+ }
+ if i+1 < len(x.Parts) {
+ w.string(`\(`)
+ w.node(x.Parts[i+1])
+ w.string(`)`)
+ }
+ }
+ w.string(`"`)
+
+ case *adt.UnaryExpr:
+ fmt.Fprint(w, x.Op)
+ w.node(x.X)
+
+ case *adt.BinaryExpr:
+ w.string("(")
+ w.node(x.X)
+ fmt.Fprint(w, " ", x.Op, " ")
+ w.node(x.Y)
+ w.string(")")
+
+ case *adt.CallExpr:
+ w.node(x.Fun)
+ w.string("(")
+ for i, a := range x.Args {
+ if i > 0 {
+ w.string(", ")
+ }
+ w.node(a)
+ }
+ w.string(")")
+
+ case *adt.BuiltinValidator:
+ w.node(x.Fun)
+ w.string("(")
+ for i, a := range x.Args {
+ if i > 0 {
+ w.string(", ")
+ }
+ w.node(a)
+ }
+ w.string(")")
+
+ case *adt.DisjunctionExpr:
+ w.string("(")
+ for i, a := range x.Values {
+ if i > 0 {
+ w.string("|")
+ }
+ // Disjunct
+ if a.Default {
+ w.string("*")
+ }
+ w.node(a.Val)
+ }
+ w.string(")")
+
+ case *adt.Conjunction:
+ w.string("&(")
+ for i, c := range x.Values {
+ if i > 0 {
+ w.string(", ")
+ }
+ w.node(c)
+ }
+ w.string(")")
+
+ case *adt.Disjunction:
+ w.string("|(")
+ for i, c := range x.Values {
+ if i > 0 {
+ w.string(", ")
+ }
+ if i < x.NumDefaults {
+ w.string("*")
+ }
+ w.node(c)
+ }
+ w.string(")")
+
+ case *adt.ForClause:
+ w.string("for ")
+ w.label(x.Key)
+ w.string(", ")
+ w.label(x.Value)
+ w.string(" in ")
+ w.node(x.Src)
+ w.string(" ")
+ w.node(x.Dst)
+
+ case *adt.IfClause:
+ w.string("if ")
+ w.node(x.Condition)
+ w.string(" ")
+ w.node(x.Dst)
+
+ case *adt.LetClause:
+ w.string("let ")
+ w.label(x.Label)
+ w.string(" = ")
+ w.node(x.Expr)
+ w.string(" ")
+ w.node(x.Dst)
+
+ case *adt.ValueClause:
+ w.node(x.StructLit)
+
+ default:
+ panic(fmt.Sprintf("unknown type %T", x))
+ }
+}
diff --git a/internal/core/runtime/index.go b/internal/core/runtime/index.go
new file mode 100644
index 0000000..4907368
--- /dev/null
+++ b/internal/core/runtime/index.go
@@ -0,0 +1,81 @@
+// 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 (
+ "sync"
+)
+
+// 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
+ labels []string
+
+ offset int
+ 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)
+}
+
+// sharedIndex is used for indexing builtins and any other labels common to
+// all instances.
+var sharedIndex = newSharedIndex()
+
+func newSharedIndex() *index {
+ i := &index{
+ labelMap: map[string]int{"": 0},
+ labels: []string{"_"},
+ }
+ 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,
+ }
+ return i
+}
+
+func (x *index) IndexToString(i int64) string {
+ for ; int(i) < x.offset; x = x.parent {
+ }
+ return x.labels[int(i)-x.offset]
+}
+
+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
+ x.labelMap[s] = index
+ x.labels = append(x.labels, s)
+ return int64(index)
+}
diff --git a/internal/core/runtime/runtime.go b/internal/core/runtime/runtime.go
new file mode 100644
index 0000000..ecdaa0a
--- /dev/null
+++ b/internal/core/runtime/runtime.go
@@ -0,0 +1,27 @@
+// 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
+
+// A Runtime maintains data structures for indexing and resuse for evaluation.
+type Runtime struct {
+ *index
+}
+
+// New creates a new Runtime.
+func New() *Runtime {
+ return &Runtime{
+ index: newIndex(sharedIndex),
+ }
+}
diff --git a/internal/cuetxtar/txtar.go b/internal/cuetxtar/txtar.go
index 943d136..241305f 100644
--- a/internal/cuetxtar/txtar.go
+++ b/internal/cuetxtar/txtar.go
@@ -193,8 +193,9 @@
return nil
}
- p := strings.Index(fullpath, "/testdata/")
- testName := fullpath[p+len("/testdata/") : len(fullpath)-len(".txtar")]
+ str := filepath.ToSlash(fullpath)
+ p := strings.Index(str, "/testdata/")
+ testName := str[p+len("/testdata/") : len(str)-len(".txtar")]
t.Run(testName, func(t *testing.T) {
a, err := txtar.ParseFile(fullpath)