internal/legacy/cue: copy relevant cue files for new implementation
Change-Id: I9c2549e0c37fdb4802265ce9c40753b4cc681f29
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6518
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/internal/legacy/cue/alias.go b/internal/legacy/cue/alias.go
new file mode 100644
index 0000000..ee438fc
--- /dev/null
+++ b/internal/legacy/cue/alias.go
@@ -0,0 +1,69 @@
+// 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 cue
+
+import "cuelang.org/go/internal/core/adt"
+
+type (
+ bottom = adt.Bottom
+ source = adt.Node
+ errCode = adt.ErrorCode
+ kind = adt.Kind
+ nullLit = adt.Null
+ boolLit = adt.Bool
+ numLit = adt.Num
+ stringLit = adt.String
+ bytesLit = adt.Bytes
+ context = adt.OpContext
+ structLit = adt.Vertex
+
+ arc = *adt.Vertex
+ value = adt.Expr
+ evaluated = adt.Value
+ label = adt.Feature
+ Op = adt.Op
+
+ listLit = adt.ListLit
+ top = adt.Top
+ basicType = adt.BasicType
+ boundExpr = adt.BoundExpr
+ boundValue = adt.BoundValue
+ selectorExpr = adt.SelectorExpr
+ indexExpr = adt.IndexExpr
+ sliceExpr = adt.SliceExpr
+ interpolation = adt.Interpolation
+ unaryExpr = adt.UnaryExpr
+ binaryExpr = adt.BinaryExpr
+ callExpr = adt.CallExpr
+)
+
+const (
+ topKind = adt.TopKind
+ nullKind = adt.NullKind
+ boolKind = adt.BoolKind
+ numKind = adt.NumKind
+ intKind = adt.IntKind
+ floatKind = adt.FloatKind
+ stringKind = adt.StringKind
+ bytesKind = adt.BytesKind
+ listKind = adt.ListKind
+ structKind = adt.StructKind
+ bottomKind = adt.BottomKind
+
+ NoOp = adt.NoOp
+
+ codeIncomplete = adt.IncompleteError
+ codeNotExist = adt.IncompleteError
+)
diff --git a/internal/legacy/cue/build.go b/internal/legacy/cue/build.go
new file mode 100644
index 0000000..9f86daf
--- /dev/null
+++ b/internal/legacy/cue/build.go
@@ -0,0 +1,248 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cue
+
+import (
+ "sync"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/ast/astutil"
+ "cuelang.org/go/cue/build"
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/token"
+ "cuelang.org/go/internal"
+ "cuelang.org/go/internal/core/runtime"
+)
+
+// A Runtime is used for creating CUE interpretations.
+//
+// Any operation that involves two Values or Instances should originate from
+// the same Runtime.
+//
+// The zero value of a Runtime is ready to use.
+type Runtime struct {
+ ctx *build.Context // TODO: remove
+ idx *index
+}
+
+func init() {
+ internal.GetRuntime = func(instance interface{}) interface{} {
+ switch x := instance.(type) {
+ case Value:
+ return &Runtime{idx: x.idx}
+
+ case *Instance:
+ return &Runtime{idx: x.index}
+
+ default:
+ panic("argument must be Value or *Instance")
+ }
+ }
+
+ internal.CheckAndForkRuntime = func(runtime, value interface{}) interface{} {
+ r := runtime.(*Runtime)
+ idx := value.(Value).ctx().index
+ if idx != r.idx {
+ panic("value not from same runtime")
+ }
+ return &Runtime{idx: newIndex(idx)}
+ }
+}
+
+func dummyLoad(token.Pos, string) *build.Instance { return nil }
+
+func (r *Runtime) index() *index {
+ if r.idx == nil {
+ r.idx = newIndex(sharedIndex)
+ }
+ return r.idx
+}
+
+func (r *Runtime) buildContext() *build.Context {
+ ctx := r.ctx
+ if r.ctx == nil {
+ ctx = build.NewContext()
+ }
+ return ctx
+}
+
+func (r *Runtime) complete(p *build.Instance) (*Instance, error) {
+ idx := r.index()
+ if err := p.Complete(); err != nil {
+ return nil, err
+ }
+ inst := idx.loadInstance(p)
+ inst.ImportPath = p.ImportPath
+ if inst.Err != nil {
+ return nil, inst.Err
+ }
+ return inst, nil
+}
+
+// Compile compiles the given source into an Instance. The source code may be
+// provided as a string, byte slice, io.Reader. The name is used as the file
+// name in position information. The source may import builtin packages. Use
+// Build to allow importing non-builtin packages.
+func (r *Runtime) Compile(filename string, source interface{}) (*Instance, error) {
+ ctx := r.buildContext()
+ p := ctx.NewInstance(filename, dummyLoad)
+ if err := p.AddFile(filename, source); err != nil {
+ return nil, p.Err
+ }
+ return r.complete(p)
+}
+
+// CompileFile compiles the given source file into an Instance. The source may
+// import builtin packages. Use Build to allow importing non-builtin packages.
+func (r *Runtime) CompileFile(file *ast.File) (*Instance, error) {
+ ctx := r.buildContext()
+ p := ctx.NewInstance(file.Filename, dummyLoad)
+ err := p.AddSyntax(file)
+ if err != nil {
+ return nil, err
+ }
+ _, p.PkgName, _ = internal.PackageInfo(file)
+ return r.complete(p)
+}
+
+// CompileExpr compiles the given source expression into an Instance. The source
+// may import builtin packages. Use Build to allow importing non-builtin
+// packages.
+func (r *Runtime) CompileExpr(expr ast.Expr) (*Instance, error) {
+ f, err := astutil.ToFile(expr)
+ if err != nil {
+ return nil, err
+ }
+ return r.CompileFile(f)
+}
+
+// Parse parses a CUE source value into a CUE Instance. The source code may
+// be provided as a string, byte slice, or io.Reader. The name is used as the
+// file name in position information. The source may import builtin packages.
+//
+// Deprecated: use Compile
+func (r *Runtime) Parse(name string, source interface{}) (*Instance, error) {
+ return r.Compile(name, source)
+}
+
+// Build creates an Instance from the given build.Instance. A returned Instance
+// may be incomplete, in which case its Err field is set.
+func (r *Runtime) Build(instance *build.Instance) (*Instance, error) {
+ return r.complete(instance)
+}
+
+// Build creates one Instance for each build.Instance. A returned Instance
+// may be incomplete, in which case its Err field is set.
+//
+// Example:
+// inst := cue.Build(load.Instances(args))
+//
+func Build(instances []*build.Instance) []*Instance {
+ if len(instances) == 0 {
+ panic("cue: list of instances must not be empty")
+ }
+ var r Runtime
+ a, _ := r.build(instances)
+ return a
+}
+
+func (r *Runtime) build(instances []*build.Instance) ([]*Instance, error) {
+ index := r.index()
+
+ loaded := []*Instance{}
+
+ var errs errors.Error
+
+ for _, p := range instances {
+ _ = p.Complete()
+ errs = errors.Append(errs, p.Err)
+
+ i := index.loadInstance(p)
+ errs = errors.Append(errs, i.Err)
+ loaded = append(loaded, i)
+ }
+
+ // TODO: insert imports
+ return loaded, errs
+}
+
+// FromExpr creates an instance from an expression.
+// Any references must be resolved beforehand.
+//
+// Deprecated: use CompileExpr
+func (r *Runtime) FromExpr(expr ast.Expr) (*Instance, error) {
+ return r.CompileFile(&ast.File{
+ Decls: []ast.Decl{&ast.EmbedDecl{Expr: expr}},
+ })
+}
+
+// index maps conversions from label names to internal codes.
+//
+// All instances belonging to the same package should share this index.
+type index struct {
+ *runtime.Index
+
+ loaded map[*build.Instance]*Instance
+ mutex sync.Mutex
+}
+
+// sharedIndex is used for indexing builtins and any other labels common to
+// all instances.
+var sharedIndex = &index{
+ Index: runtime.SharedIndex,
+ loaded: map[*build.Instance]*Instance{},
+}
+
+// newIndex creates a new index.
+func newIndex(parent *index) *index {
+ return &index{
+ Index: runtime.NewIndex(parent.Index),
+ loaded: map[*build.Instance]*Instance{},
+ }
+}
+
+func isBuiltin(s string) bool {
+ _, ok := builtins[s]
+ return ok
+}
+
+func (idx *index) loadInstance(p *build.Instance) *Instance {
+ if inst := idx.loaded[p]; inst != nil {
+ if !inst.complete {
+ // cycles should be detected by the builder and it should not be
+ // possible to construct a build.Instance that has them.
+ panic("cue: cycle")
+ }
+ return inst
+ }
+ errs := runtime.ResolveFiles(idx.Index, p, isBuiltin)
+ files := p.Files
+ inst := newInstance(idx, p)
+ idx.loaded[p] = inst
+
+ if inst.Err == nil {
+ // inst.instance.index.state = s
+ // inst.instance.inst = p
+ inst.Err = errs
+ for _, f := range files {
+ err := inst.insertFile(f)
+ inst.Err = errors.Append(inst.Err, err)
+ }
+ }
+ inst.ImportPath = p.ImportPath
+
+ inst.complete = true
+ return inst
+}
diff --git a/internal/legacy/cue/build_test.go b/internal/legacy/cue/build_test.go
new file mode 100644
index 0000000..7817ec7
--- /dev/null
+++ b/internal/legacy/cue/build_test.go
@@ -0,0 +1,259 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cue
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/build"
+ "cuelang.org/go/cue/token"
+)
+
+func TestFromExpr(t *testing.T) {
+ testCases := []struct {
+ expr ast.Expr
+ out string
+ }{{
+ expr: ast.NewString("Hello"),
+ out: `"Hello"`,
+ }, {
+ expr: ast.NewList(
+ ast.NewString("Hello"),
+ ast.NewString("World"),
+ ),
+ out: `["Hello","World"]`,
+ }}
+ for _, tc := range testCases {
+ t.Run("", func(t *testing.T) {
+ r := &Runtime{}
+ inst, err := r.CompileExpr(tc.expr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := inst.newContext()
+ if got := debugStr(ctx, inst.eval(ctx)); got != tc.out {
+ t.Errorf("\n got: %v; want %v", got, tc.out)
+ }
+ })
+ }
+}
+
+func TestBuild(t *testing.T) {
+ files := func(s ...string) []string { return s }
+ insts := func(i ...*bimport) []*bimport { return i }
+ pkg1 := &bimport{
+ "pkg1",
+ files(`
+ package pkg1
+
+ Object: "World"
+ `),
+ }
+ pkg2 := &bimport{
+ "example.com/foo/pkg2:pkg",
+ files(`
+ package pkg
+
+ Number: 12
+ `),
+ }
+ pkg3 := &bimport{
+ "example.com/foo/v1:pkg3",
+ files(`
+ package pkg3
+
+ List: [1,2,3]
+ `),
+ }
+
+ testCases := []struct {
+ instances []*bimport
+ emit string
+ }{{
+ insts(&bimport{"", files(`test: "ok"`)}),
+ `{test: "ok"}`,
+ }, {
+ insts(&bimport{"",
+ files(
+ `package test
+
+ import "math"
+
+ "Pi: \(math.Pi)!"`)}),
+ `"Pi: 3.14159265358979323846264338327950288419716939937510582097494459!"`,
+ }, {
+ insts(&bimport{"",
+ files(
+ `package test
+
+ import math2 "math"
+
+ "Pi: \(math2.Pi)!"`)}),
+ `"Pi: 3.14159265358979323846264338327950288419716939937510582097494459!"`,
+ }, {
+ insts(pkg1, &bimport{"",
+ files(
+ `package test
+
+ import "pkg1"
+
+ "Hello \(pkg1.Object)!"`),
+ }),
+ `"Hello World!"`,
+ }, {
+ insts(pkg1, &bimport{"",
+ files(
+ `package test
+
+ import "pkg1"
+
+ "Hello \(pkg1.Object)!"`),
+ }),
+ `"Hello World!"`,
+ }, {
+ insts(pkg1, &bimport{"",
+ files(
+ `package test
+
+ import pkg2 "pkg1"
+ #pkg1: pkg2.Object
+
+ "Hello \(#pkg1)!"`),
+ }),
+ `"Hello World!"`,
+ }, {
+ insts(pkg1, pkg2, &bimport{"",
+ files(
+ `package test
+
+ import bar "pkg1"
+ import baz "example.com/foo/pkg2:pkg"
+
+ pkg1: Object: 3
+ "Hello \(pkg1.Object)!"`),
+ }),
+ `imported and not used: "pkg1" as bar (and 1 more errors)`,
+ }, {
+ insts(pkg2, &bimport{"",
+ files(
+ `package test
+
+ import "example.com/foo/pkg2:pkg"
+
+ "Hello \(pkg2.Number)!"`),
+ }),
+ `imported and not used: "example.com/foo/pkg2:pkg" (and 1 more errors)`,
+ // `file0.cue:5:14: unresolved reference pkg2`,
+ }, {
+ insts(pkg2, &bimport{"",
+ files(
+ `package test
+
+ import "example.com/foo/pkg2:pkg"
+
+ "Hello \(pkg.Number)!"`),
+ }),
+ `"Hello 12!"`,
+ }, {
+ insts(pkg3, &bimport{"",
+ files(
+ `package test
+
+ import "example.com/foo/v1:pkg3"
+
+ "Hello \(pkg3.List[1])!"`),
+ }),
+ `"Hello 2!"`,
+ }, {
+ insts(pkg3, &bimport{"",
+ files(
+ `package test
+
+ import "example.com/foo/v1:pkg3"
+
+ pkg3: 3
+
+ "Hello \(pkg3.List[1])!"`),
+ }),
+ `pkg3 redeclared as imported package name
+ previous declaration at file0.cue:5:5`,
+ }}
+ for _, tc := range testCases {
+ t.Run("", func(t *testing.T) {
+ insts := Build(makeInstances(tc.instances))
+ var got string
+ if err := insts[0].Err; err != nil {
+ got = err.Error()
+ } else {
+ got = strings.TrimSpace(fmt.Sprintf("%s\n", insts[0].Value()))
+ }
+ if got != tc.emit {
+ t.Errorf("\n got: %s\nwant: %s", got, tc.emit)
+ }
+ })
+ }
+}
+
+type builder struct {
+ ctxt *build.Context
+ imports map[string]*bimport
+}
+
+func (b *builder) load(pos token.Pos, path string) *build.Instance {
+ bi := b.imports[path]
+ if bi == nil {
+ return nil
+ }
+ return b.build(bi)
+}
+
+type bimport struct {
+ path string // "" means top-level
+ files []string
+}
+
+func makeInstances(insts []*bimport) (instances []*build.Instance) {
+ b := builder{
+ ctxt: build.NewContext(),
+ imports: map[string]*bimport{},
+ }
+ for _, bi := range insts {
+ if bi.path != "" {
+ b.imports[bi.path] = bi
+ }
+ }
+ for _, bi := range insts {
+ if bi.path == "" {
+ instances = append(instances, b.build(bi))
+ }
+ }
+ return
+}
+
+func (b *builder) build(bi *bimport) *build.Instance {
+ path := bi.path
+ if path == "" {
+ path = "dir"
+ }
+ p := b.ctxt.NewInstance(path, b.load)
+ for i, f := range bi.files {
+ _ = p.AddFile(fmt.Sprintf("file%d.cue", i), f)
+ }
+ _ = p.Complete()
+ return p
+}
diff --git a/internal/legacy/cue/builtin.go b/internal/legacy/cue/builtin.go
new file mode 100644
index 0000000..cfd98a6
--- /dev/null
+++ b/internal/legacy/cue/builtin.go
@@ -0,0 +1,656 @@
+// Copyright 2018 The 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.
+
+//go:generate go run gen.go
+//go:generate go run golang.org/x/tools/cmd/goimports -w -local cuelang.org/go builtins.go
+//go:generate gofmt -s -w builtins.go
+
+package cue
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "math/big"
+ "path"
+ "sort"
+ "strings"
+
+ "github.com/cockroachdb/apd/v2"
+
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/parser"
+ "cuelang.org/go/cue/token"
+ "cuelang.org/go/internal"
+)
+
+// A builtin is a builtin function or constant.
+//
+// A function may return and a constant may be any of the following types:
+//
+// error (translates to bottom)
+// nil (translates to null)
+// bool
+// int*
+// uint*
+// float64
+// string
+// *big.Float
+// *big.Int
+//
+// For any of the above, including interface{} and these types recursively:
+// []T
+// map[string]T
+//
+type builtin struct {
+ baseValue
+ Name string
+ pkg label
+ Params []kind
+ Result kind
+ Func func(c *callCtxt)
+ // Const interface{}
+ Const string
+}
+
+type builtinPkg struct {
+ native []*builtin
+ cue string
+}
+
+func mustCompileBuiltins(ctx *context, p *builtinPkg, pkgName string) *structLit {
+ obj := &structLit{}
+ pkgLabel := ctx.Label(pkgName, false)
+ for _, b := range p.native {
+ b.pkg = pkgLabel
+
+ f := ctx.Label(b.Name, false) // never starts with _
+ // n := &node{baseValue: newBase(imp.Path)}
+ var v evaluated = b
+ if b.Const != "" {
+ v = mustParseConstBuiltin(ctx, b.Name, b.Const)
+ }
+ obj.Arcs = append(obj.Arcs, arc{Label: f, v: v})
+ }
+ sort.Sort(obj)
+
+ // Parse builtin CUE
+ if p.cue != "" {
+ expr, err := parser.ParseExpr(pkgName, p.cue)
+ if err != nil {
+ panic(fmt.Errorf("could not parse %v: %v", p.cue, err))
+ }
+ v := newVisitor(ctx.index, nil, nil, nil, false)
+ value := v.walk(expr)
+ pkg := value.evalPartial(ctx).(*structLit)
+ for _, a := range pkg.Arcs {
+ // Discard option status and attributes at top level.
+ // TODO: filter on capitalized fields?
+ obj.insertValue(ctx, a.Label, false, false, a.v, nil, a.docs)
+ }
+ }
+
+ return obj
+}
+
+// newConstBuiltin parses and creates any CUE expression that does not have
+// fields.
+func mustParseConstBuiltin(ctx *context, name, val string) evaluated {
+ expr, err := parser.ParseExpr("<builtin:"+name+">", val)
+ if err != nil {
+ panic(err)
+ }
+ v := newVisitor(ctx.index, nil, nil, nil, false)
+ value := v.walk(expr)
+ return value.evalPartial(ctx)
+}
+
+var _ caller = &builtin{}
+
+var lenBuiltin = &builtin{
+ Name: "len",
+ Params: []kind{stringKind | bytesKind | listKind | structKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ v := c.value(0)
+ switch k := v.IncompleteKind(); k {
+ case StructKind:
+ s, err := v.structValData(c.ctx)
+ if err != nil {
+ c.ret = err
+ break
+ }
+ c.ret = s.Len()
+ case ListKind:
+ i := 0
+ iter, err := v.List()
+ if err != nil {
+ c.ret = err
+ break
+ }
+ for ; iter.Next(); i++ {
+ }
+ c.ret = i
+ case BytesKind:
+ b, err := v.Bytes()
+ if err != nil {
+ c.ret = err
+ break
+ }
+ c.ret = len(b)
+ case StringKind:
+ s, err := v.String()
+ if err != nil {
+ c.ret = err
+ break
+ }
+ c.ret = len(s)
+ default:
+ c.ret = errors.Newf(token.NoPos,
+ "invalid argument type %v", k)
+ }
+ },
+}
+
+var closeBuiltin = &builtin{
+ Name: "close",
+ Params: []kind{structKind},
+ Result: structKind,
+ Func: func(c *callCtxt) {
+ s, ok := c.args[0].(*structLit)
+ if !ok {
+ c.ret = errors.Newf(c.args[0].Pos(), "struct argument must be concrete")
+ return
+ }
+ c.ret = s.close()
+ },
+}
+
+var andBuiltin = &builtin{
+ Name: "and",
+ Params: []kind{listKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ iter := c.iter(0)
+ if !iter.Next() {
+ c.ret = &top{baseValue{c.src}}
+ return
+ }
+ u := iter.Value().v.v
+ for iter.Next() {
+ u = mkBin(c.ctx, c.src.Pos(), opUnify, u, iter.Value().v.v)
+ }
+ c.ret = u
+ },
+}
+
+var orBuiltin = &builtin{
+ Name: "or",
+ Params: []kind{listKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ iter := c.iter(0)
+ d := []dValue{}
+ for iter.Next() {
+ d = append(d, dValue{iter.Value().v.v, false})
+ }
+ c.ret = &disjunction{baseValue{c.src}, d, nil, false}
+ if len(d) == 0 {
+ // TODO(manifest): This should not be unconditionally incomplete,
+ // but it requires results from comprehensions and all to have
+ // some special status. Maybe this can be solved by having results
+ // of list comprehensions be open if they result from iterating over
+ // an open list or struct. This would actually be exactly what
+ // that means. The error here could then only add an incomplete
+ // status if the source is open.
+ c.ret = c.ctx.mkErr(c.src, codeIncomplete, "empty list in call to or")
+ }
+ },
+}
+
+func (x *builtin) representedKind() kind {
+ if x.isValidator() {
+ return x.Params[0]
+ }
+ return x.Kind()
+}
+
+func (x *builtin) Kind() kind {
+ return lambdaKind
+}
+
+func (x *builtin) evalPartial(ctx *context) evaluated {
+ return x
+}
+
+func (x *builtin) subsumesImpl(s *subsumer, v value) bool {
+ if y, ok := v.(*builtin); ok {
+ return x == y
+ }
+ return false
+}
+
+func (x *builtin) name(ctx *context) string {
+ if x.pkg == 0 {
+ return x.Name
+ }
+ return fmt.Sprintf("%s.%s", ctx.LabelStr(x.pkg), x.Name)
+}
+
+func (x *builtin) isValidator() bool {
+ return len(x.Params) == 1 && x.Result == boolKind
+}
+
+func convertBuiltin(v evaluated) evaluated {
+ x, ok := v.(*builtin)
+ if ok && x.isValidator() {
+ return &customValidator{v.base(), x, []evaluated{}}
+ }
+ return v
+}
+
+func (x *builtin) call(ctx *context, src source, args ...evaluated) (ret value) {
+ if x.Func == nil {
+ return ctx.mkErr(x, "builtin %s is not a function", x.name(ctx))
+ }
+ if len(x.Params)-1 == len(args) && x.Result == boolKind {
+ // We have a custom builtin
+ return &customValidator{src.base(), x, args}
+ }
+ switch {
+ case len(x.Params) < len(args):
+ return ctx.mkErr(src, x, "too many arguments in call to %s (have %d, want %d)",
+ x.name(ctx), len(args), len(x.Params))
+ case len(x.Params) > len(args):
+ return ctx.mkErr(src, x, "not enough arguments in call to %s (have %d, want %d)",
+ x.name(ctx), len(args), len(x.Params))
+ }
+ for i, a := range args {
+ if x.Params[i] != bottomKind {
+ if unifyType(x.Params[i], a.Kind()) == bottomKind {
+ const msg = "cannot use %s (type %s) as %s in argument %d to %s"
+ return ctx.mkErr(src, x, msg, ctx.str(a), a.Kind(), x.Params[i], i+1, x.name(ctx))
+ }
+ }
+ }
+ call := callCtxt{src: src, ctx: ctx, builtin: x, args: args}
+ defer func() {
+ var errVal interface{} = call.err
+ if err := recover(); err != nil {
+ errVal = err
+ }
+ ret = processErr(&call, errVal, ret)
+ }()
+ x.Func(&call)
+ switch v := call.ret.(type) {
+ case value:
+ return v
+ case *valueError:
+ return v.err
+ }
+ return convertVal(ctx, x, true, call.ret)
+}
+
+func processErr(call *callCtxt, errVal interface{}, ret value) value {
+ ctx := call.ctx
+ x := call.builtin
+ src := call.src
+ const msg = "error in call to %s: %v"
+ switch err := errVal.(type) {
+ case nil:
+ case *callError:
+ ret = err.b
+ case *json.MarshalerError:
+ if err, ok := err.Err.(*marshalError); ok && err.b != nil {
+ ret = err.b
+ }
+ case *marshalError:
+ ret = err.b
+ ret = ctx.mkErr(src, x, ret, msg, x.name(ctx), err)
+ case *valueError:
+ ret = err.err
+ ret = ctx.mkErr(src, x, ret, msg, x.name(ctx), err)
+ default:
+ if call.err == internal.ErrIncomplete {
+ ret = ctx.mkErr(src, codeIncomplete, "incomplete value")
+ } else {
+ // TODO: store the underlying error explicitly
+ ret = ctx.mkErr(src, x, msg, x.name(ctx), err)
+ }
+ }
+ return ret
+}
+
+// callCtxt is passed to builtin implementations.
+type callCtxt struct {
+ src source
+ ctx *context
+ builtin *builtin
+ args []evaluated
+ err error
+ ret interface{}
+}
+
+func (c *callCtxt) name() string {
+ return c.builtin.name(c.ctx)
+}
+
+var builtins = map[string]*Instance{}
+
+func initBuiltins(pkgs map[string]*builtinPkg) {
+ ctx := sharedIndex.newContext()
+ keys := []string{}
+ for k := range pkgs {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, k := range keys {
+ b := pkgs[k]
+ e := mustCompileBuiltins(ctx, b, k)
+
+ i := sharedIndex.addInst(&Instance{
+ ImportPath: k,
+ PkgName: path.Base(k),
+ rootStruct: e,
+ rootValue: e,
+ })
+
+ builtins[k] = i
+ builtins["-/"+path.Base(k)] = i
+ }
+}
+
+func getBuiltinShorthandPkg(ctx *context, shorthand string) *structLit {
+ return getBuiltinPkg(ctx, "-/"+shorthand)
+}
+
+func getBuiltinPkg(ctx *context, path string) *structLit {
+ p, ok := builtins[path]
+ if !ok {
+ return nil
+ }
+ return p.rootStruct
+}
+
+func init() {
+ internal.UnifyBuiltin = func(val interface{}, kind string) interface{} {
+ v := val.(Value)
+ ctx := v.ctx()
+
+ p := strings.Split(kind, ".")
+ pkg, name := p[0], p[1]
+ s := getBuiltinPkg(ctx, pkg)
+ if s == nil {
+ return v
+ }
+ a := s.Lookup(ctx, ctx.Label(name, false))
+ if a.v == nil {
+ return v
+ }
+
+ return v.Unify(newValueRoot(ctx, a.v.evalPartial(ctx)))
+ }
+}
+
+// do returns whether the call should be done.
+func (c *callCtxt) do() bool {
+ return c.err == nil
+}
+
+type callError struct {
+ b *bottom
+}
+
+func (e *callError) Error() string {
+ return fmt.Sprint(e.b)
+}
+
+func (c *callCtxt) errf(src source, underlying error, format string, args ...interface{}) {
+ a := make([]interface{}, 0, 2+len(args))
+ if err, ok := underlying.(*valueError); ok {
+ a = append(a, err.err)
+ }
+ a = append(a, format)
+ a = append(a, args...)
+ err := c.ctx.mkErr(src, a...)
+ c.err = &callError{err}
+}
+
+func (c *callCtxt) value(i int) Value {
+ v := newValueRoot(c.ctx, c.args[i])
+ v, _ = v.Default()
+ if !v.IsConcrete() {
+ c.errf(c.src, v.toErr(c.ctx.mkErr(c.src, codeIncomplete,
+ "non-concrete value")), "incomplete")
+ }
+ return v
+}
+
+func (c *callCtxt) structVal(i int) *Struct {
+ v := newValueRoot(c.ctx, c.args[i])
+ s, err := v.Struct()
+ if err != nil {
+ c.invalidArgType(c.args[i], i, "struct", err)
+ return nil
+ }
+ return s
+}
+
+func (c *callCtxt) invalidArgType(arg value, i int, typ string, err error) {
+ if err != nil {
+ c.errf(c.src, err, "cannot use %s (type %s) as %s in argument %d to %s: %v",
+ c.ctx.str(arg), arg.Kind(), typ, i, c.name(), err)
+ } else {
+ c.errf(c.src, nil, "cannot use %s (type %s) as %s in argument %d to %s",
+ c.ctx.str(arg), arg.Kind(), typ, i, c.name())
+ }
+}
+
+func (c *callCtxt) int(i int) int { return int(c.intValue(i, 64, "int64")) }
+func (c *callCtxt) int8(i int) int8 { return int8(c.intValue(i, 8, "int8")) }
+func (c *callCtxt) int16(i int) int16 { return int16(c.intValue(i, 16, "int16")) }
+func (c *callCtxt) int32(i int) int32 { return int32(c.intValue(i, 32, "int32")) }
+func (c *callCtxt) rune(i int) rune { return rune(c.intValue(i, 32, "rune")) }
+func (c *callCtxt) int64(i int) int64 { return int64(c.intValue(i, 64, "int64")) }
+
+func (c *callCtxt) intValue(i, bits int, typ string) int64 {
+ arg := c.args[i]
+ x := newValueRoot(c.ctx, arg)
+ n, err := x.Int(nil)
+ if err != nil {
+ c.invalidArgType(arg, i, typ, err)
+ return 0
+ }
+ if n.BitLen() > bits {
+ c.errf(c.src, err, "int %s overflows %s in argument %d in call to %s",
+ n, typ, i, c.name())
+ }
+ res, _ := x.Int64()
+ return res
+}
+
+func (c *callCtxt) uint(i int) uint { return uint(c.uintValue(i, 64, "uint64")) }
+func (c *callCtxt) uint8(i int) uint8 { return uint8(c.uintValue(i, 8, "uint8")) }
+func (c *callCtxt) byte(i int) uint8 { return byte(c.uintValue(i, 8, "byte")) }
+func (c *callCtxt) uint16(i int) uint16 { return uint16(c.uintValue(i, 16, "uint16")) }
+func (c *callCtxt) uint32(i int) uint32 { return uint32(c.uintValue(i, 32, "uint32")) }
+func (c *callCtxt) uint64(i int) uint64 { return uint64(c.uintValue(i, 64, "uint64")) }
+
+func (c *callCtxt) uintValue(i, bits int, typ string) uint64 {
+ x := newValueRoot(c.ctx, c.args[i])
+ n, err := x.Int(nil)
+ if err != nil || n.Sign() < 0 {
+ c.invalidArgType(c.args[i], i, typ, err)
+ return 0
+ }
+ if n.BitLen() > bits {
+ c.errf(c.src, err, "int %s overflows %s in argument %d in call to %s",
+ n, typ, i, c.name())
+ }
+ res, _ := x.Uint64()
+ return res
+}
+
+func (c *callCtxt) decimal(i int) *apd.Decimal {
+ x := newValueRoot(c.ctx, c.args[i])
+ if _, err := x.MantExp(nil); err != nil {
+ c.invalidArgType(c.args[i], i, "Decimal", err)
+ return nil
+ }
+ return &c.args[i].(*numLit).X
+}
+
+func (c *callCtxt) float64(i int) float64 {
+ x := newValueRoot(c.ctx, c.args[i])
+ res, err := x.Float64()
+ if err != nil {
+ c.invalidArgType(c.args[i], i, "float64", err)
+ return 0
+ }
+ return res
+}
+
+func (c *callCtxt) bigInt(i int) *big.Int {
+ x := newValueRoot(c.ctx, c.args[i])
+ n, err := x.Int(nil)
+ if err != nil {
+ c.invalidArgType(c.args[i], i, "int", err)
+ return nil
+ }
+ return n
+}
+
+func (c *callCtxt) bigFloat(i int) *big.Float {
+ x := newValueRoot(c.ctx, c.args[i])
+ var mant big.Int
+ exp, err := x.MantExp(&mant)
+ if err != nil {
+ c.invalidArgType(c.args[i], i, "float", err)
+ return nil
+ }
+ f := &big.Float{}
+ f.SetInt(&mant)
+ if exp != 0 {
+ var g big.Float
+ e := big.NewInt(int64(exp))
+ f.Mul(f, g.SetInt(e.Exp(ten, e, nil)))
+ }
+ return f
+}
+
+func (c *callCtxt) string(i int) string {
+ x := newValueRoot(c.ctx, c.args[i])
+ v, err := x.String()
+ if err != nil {
+ c.invalidArgType(c.args[i], i, "string", err)
+ return ""
+ }
+ return v
+}
+
+func (c *callCtxt) bytes(i int) []byte {
+ x := newValueRoot(c.ctx, c.args[i])
+ v, err := x.Bytes()
+ if err != nil {
+ c.invalidArgType(c.args[i], i, "bytes", err)
+ return nil
+ }
+ return v
+}
+
+func (c *callCtxt) reader(i int) io.Reader {
+ x := newValueRoot(c.ctx, c.args[i])
+ // TODO: optimize for string and bytes cases
+ r, err := x.Reader()
+ if err != nil {
+ c.invalidArgType(c.args[i], i, "bytes|string", err)
+ return nil
+ }
+ return r
+}
+
+func (c *callCtxt) bool(i int) bool {
+ x := newValueRoot(c.ctx, c.args[i])
+ b, err := x.Bool()
+ if err != nil {
+ c.invalidArgType(c.args[i], i, "bool", err)
+ return false
+ }
+ return b
+}
+
+func (c *callCtxt) list(i int) (a []Value) {
+ arg := c.args[i]
+ x := newValueRoot(c.ctx, arg)
+ v, err := x.List()
+ if err != nil {
+ c.invalidArgType(c.args[i], i, "list", err)
+ return a
+ }
+ for v.Next() {
+ a = append(a, v.Value())
+ }
+ return a
+}
+
+func (c *callCtxt) iter(i int) (a Iterator) {
+ arg := c.args[i]
+ x := newValueRoot(c.ctx, arg)
+ v, err := x.List()
+ if err != nil {
+ c.invalidArgType(c.args[i], i, "list", err)
+ return Iterator{ctx: c.ctx}
+ }
+ return v
+}
+
+func (c *callCtxt) decimalList(i int) (a []*apd.Decimal) {
+ arg := c.args[i]
+ x := newValueRoot(c.ctx, arg)
+ v, err := x.List()
+ if err != nil {
+ c.invalidArgType(c.args[i], i, "list", err)
+ return nil
+ }
+ for j := 0; v.Next(); j++ {
+ num, err := v.Value().getNum(numKind)
+ if err != nil {
+ c.errf(c.src, err, "invalid list element %d in argument %d to %s: %v",
+ j, i, c.name(), err)
+ break
+ }
+ a = append(a, &num.X)
+ }
+ return a
+}
+
+func (c *callCtxt) strList(i int) (a []string) {
+ arg := c.args[i]
+ x := newValueRoot(c.ctx, arg)
+ v, err := x.List()
+ if err != nil {
+ c.invalidArgType(c.args[i], i, "list", err)
+ return nil
+ }
+ for j := 0; v.Next(); j++ {
+ str, err := v.Value().String()
+ if err != nil {
+ c.errf(c.src, err, "invalid list element %d in argument %d to %s: %v",
+ j, i, c.name(), err)
+ break
+ }
+ a = append(a, str)
+ }
+ return a
+}
diff --git a/internal/legacy/cue/builtin_test.go b/internal/legacy/cue/builtin_test.go
new file mode 100644
index 0000000..1901d34
--- /dev/null
+++ b/internal/legacy/cue/builtin_test.go
@@ -0,0 +1,678 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cue
+
+import (
+ "fmt"
+ "math/big"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+func TestBuiltins(t *testing.T) {
+ test := func(pkg, expr string) []*bimport {
+ return []*bimport{{"",
+ []string{fmt.Sprintf("import %q\n(%s)", pkg, expr)},
+ }}
+ }
+ testExpr := func(expr string) []*bimport {
+ return []*bimport{{"",
+ []string{fmt.Sprintf("(%s)", expr)},
+ }}
+ }
+ hexToDec := func(s string) string {
+ var x big.Int
+ x.SetString(s, 16)
+ return x.String()
+ }
+ testCases := []struct {
+ instances []*bimport
+ emit string
+ }{{
+ test("math", "math.Pi"),
+ `3.14159265358979323846264338327950288419716939937510582097494459`,
+ }, {
+ test("math", "math.Floor(math.Pi)"),
+ `3`,
+ }, {
+ test("math", "math.Pi(3)"),
+ `_|_(cannot call non-function Pi (type float))`,
+ }, {
+ test("math", "math.Floor(3, 5)"),
+ `_|_(too many arguments in call to math.Floor (have 2, want 1))`,
+ }, {
+ test("math", `math.Floor("foo")`),
+ `_|_(cannot use "foo" (type string) as number in argument 1 to math.Floor)`,
+ }, {
+ test("crypto/sha256", `sha256.Sum256("hash me")`),
+ `'\xeb \x1a\xf5\xaa\xf0\xd6\x06)\xd3Ò¦\x1eFl\xfc\x0f\xed\xb5\x17\xad\xd81\xec\xacR5\xe1Ú©c\xd6'`,
+ }, {
+ test("crypto/md5", `len(md5.Sum("hash me"))`),
+ `16`,
+ }, {
+ test("crypto/sha1", `len(sha1.Sum("hash me"))`),
+ `20`,
+ }, {
+ test("crypto/sha256", `len(sha256.Sum256("hash me"))`),
+ `32`,
+ }, {
+ test("crypto/sha256", `len(sha256.Sum224("hash me"))`),
+ `28`,
+ }, {
+ test("crypto/sha512", `len(sha512.Sum512("hash me"))`),
+ `64`,
+ }, {
+ test("crypto/sha512", `len(sha512.Sum384("hash me"))`),
+ `48`,
+ }, {
+ test("crypto/sha512", `len(sha512.Sum512_224("hash me"))`),
+ `28`,
+ }, {
+ test("crypto/sha512", `len(sha512.Sum512_256("hash me"))`),
+ `32`,
+ }, {
+ test("encoding/base64", `base64.Encode(null, "foo")`),
+ `"Zm9v"`,
+ }, {
+ test("encoding/base64", `base64.Decode(null, base64.Encode(null, "foo"))`),
+ `'foo'`,
+ }, {
+ test("encoding/base64", `base64.Decode(null, "foo")`),
+ `_|_(error in call to encoding/base64.Decode: illegal base64 data at input byte 0)`,
+ }, {
+ test("encoding/base64", `base64.Decode({}, "foo")`),
+ `_|_(error in call to encoding/base64.Decode: base64: unsupported encoding: cannot use value {} (type struct) as null)`,
+ }, {
+ test("encoding/hex", `hex.Encode("foo")`),
+ `"666f6f"`,
+ }, {
+ test("encoding/hex", `hex.Decode(hex.Encode("foo"))`),
+ `'foo'`,
+ }, {
+ test("encoding/hex", `hex.Decode("foo")`),
+ `_|_(error in call to encoding/hex.Decode: encoding/hex: invalid byte: U+006F 'o')`,
+ }, {
+ test("encoding/hex", `hex.Dump('foo')`),
+ `"00000000 66 6f 6f |foo|\n"`,
+ }, {
+ test("encoding/json", `json.Validate("{\"a\":10}", {b:string})`),
+ `true`,
+ }, {
+ test("encoding/json", `json.Validate("{\"a\":10}", {a:<3})`),
+ `_|_(error in call to encoding/json.Validate: a: invalid value 10 (out of bound <3))`,
+ }, {
+ test("encoding/yaml", `yaml.Validate("a: 2\n---\na: 4", {a:<3})`),
+ `_|_(error in call to encoding/yaml.Validate: a: invalid value 4 (out of bound <3))`,
+ }, {
+ test("encoding/yaml", `yaml.Validate("a: 2\n---\na: 4", {a:<5})`),
+ `true`,
+ }, {
+ test("encoding/yaml", `yaml.Validate("a: 2\n", {a:<5, b:int})`),
+ `_|_(error in call to encoding/yaml.Validate: b: incomplete value (int))`,
+ }, {
+ test("encoding/yaml", `yaml.ValidatePartial("a: 2\n---\na: 4", {a:<3})`),
+ `_|_(error in call to encoding/yaml.ValidatePartial: a: invalid value 4 (out of bound <3))`,
+ }, {
+ test("encoding/yaml", `yaml.ValidatePartial("a: 2\n---\na: 4", {a:<5})`),
+ `true`,
+ }, {
+ test("encoding/yaml", `yaml.ValidatePartial("a: 2\n", {a:<5, b:int})`),
+ `true`,
+ }, {
+ test("strconv", `strconv.FormatUint(64, 16)`),
+ `"40"`,
+ }, {
+ // Find a better alternative, as this call should go.
+ test("strconv", `strconv.FormatFloat(3.02, 300, 4, 64)`),
+ `_|_(int 300 overflows byte in argument 1 in call to strconv.FormatFloat)`,
+ }, {
+ // Find a better alternative, as this call should go.
+ test("strconv", `strconv.FormatFloat(3.02, -1, 4, 64)`),
+ `_|_(cannot use -1 (type int) as byte in argument 1 to strconv.FormatFloat)`,
+ }, {
+ // Find a better alternative, as this call should go.
+ test("strconv", `strconv.FormatFloat(3.02, 1.0, 4, 64)`),
+ `_|_(cannot use 1.0 (type float) as int in argument 2 to strconv.FormatFloat)`,
+ }, {
+ test("list", `list.Avg([1, 2, 3, 4])`),
+ `2.5`,
+ }, {
+ test("list", `list.Avg([])`),
+ `_|_(error in call to list.Avg: empty list)`,
+ }, {
+ test("list", `list.Avg("foo")`),
+ `_|_(cannot use "foo" (type string) as list in argument 1 to list.Avg)`,
+ }, {
+ test("list", `list.Drop([1, 2, 3, 4], 0)`),
+ `[1,2,3,4]`,
+ }, {
+ test("list", `list.Drop([1, 2, 3, 4], 2)`),
+ `[3,4]`,
+ }, {
+ test("list", `list.Drop([1, 2, 3, 4], 10)`),
+ `[]`,
+ }, {
+ test("list", `list.Drop([1, 2, 3, 4], -1)`),
+ `_|_(error in call to list.Drop: negative index)`,
+ }, {
+ test("list", `list.FlattenN([1, [[2, 3], []], [4]], -1)`),
+ `[1,2,3,4]`,
+ }, {
+ test("list", `list.FlattenN([1, [[2, 3], []], [4]], 0)`),
+ `[1,[[2,3],[]],[4]]`,
+ }, {
+ test("list", `list.FlattenN([1, [[2, 3], []], [4]], 1)`),
+ `[1,[2,3],[],4]`,
+ }, {
+ test("list", `list.FlattenN([1, [[2, 3], []], [4]], 2)`),
+ `[1,2,3,4]`,
+ }, {
+ test("list", `list.FlattenN([[1, 2] | *[]], -1)`),
+ `[]`,
+ }, {
+ test("list", `list.FlattenN("foo", 1)`),
+ `_|_(error in call to list.FlattenN: cannot use value "foo" (type string) as list)`,
+ }, {
+ test("list", `list.FlattenN([], "foo")`),
+ `_|_(cannot use "foo" (type string) as int in argument 2 to list.FlattenN)`,
+ }, {
+ test("list", `list.Max([1, 2, 3, 4])`),
+ `4`,
+ }, {
+ test("list", `list.Max([])`),
+ `_|_(error in call to list.Max: empty list)`,
+ }, {
+ test("list", `list.Max("foo")`),
+ `_|_(cannot use "foo" (type string) as list in argument 1 to list.Max)`,
+ }, {
+ test("list", `list.Min([1, 2, 3, 4])`),
+ `1`,
+ }, {
+ test("list", `list.Min([])`),
+ `_|_(error in call to list.Min: empty list)`,
+ }, {
+ test("list", `list.Min("foo")`),
+ `_|_(cannot use "foo" (type string) as list in argument 1 to list.Min)`,
+ }, {
+ test("list", `list.Product([1, 2, 3, 4])`),
+ `24`,
+ }, {
+ test("list", `list.Product([])`),
+ `1`,
+ }, {
+ test("list", `list.Product("foo")`),
+ `_|_(cannot use "foo" (type string) as list in argument 1 to list.Product)`,
+ }, {
+ test("list", `list.Range(0, 5, 0)`),
+ `_|_(error in call to list.Range: step must be non zero)`,
+ }, {
+ test("list", `list.Range(5, 0, 1)`),
+ `_|_(error in call to list.Range: end must be greater than start when step is positive)`,
+ }, {
+ test("list", `list.Range(0, 5, -1)`),
+ `_|_(error in call to list.Range: end must be less than start when step is negative)`,
+ }, {
+ test("list", `list.Range(0, 5, 1)`),
+ `[0,1,2,3,4]`,
+ }, {
+ test("list", `list.Range(0, 1, 1)`),
+ `[0]`,
+ }, {
+ test("list", `list.Range(0, 5, 2)`),
+ `[0,2,4]`,
+ }, {
+ test("list", `list.Range(5, 0, -1)`),
+ `[5,4,3,2,1]`,
+ }, {
+ test("list", `list.Range(0, 5, 0.5)`),
+ `[0,0.5,1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5]`,
+ }, {
+ test("list", `list.Slice([1, 2, 3, 4], 1, 3)`),
+ `[2,3]`,
+ }, {
+ test("list", `list.Slice([1, 2, 3, 4], -1, 3)`),
+ `_|_(error in call to list.Slice: negative index)`,
+ }, {
+ test("list", `list.Slice([1, 2, 3, 4], 3, 1)`),
+ `_|_(error in call to list.Slice: invalid index: 3 > 1)`,
+ }, {
+ test("list", `list.Slice([1, 2, 3, 4], 5, 5)`),
+ `_|_(error in call to list.Slice: slice bounds out of range)`,
+ }, {
+ test("list", `list.Slice([1, 2, 3, 4], 1, 5)`),
+ `_|_(error in call to list.Slice: slice bounds out of range)`,
+ }, {
+ test("list", `list.Sort([], list.Ascending)`),
+ `[]`,
+ }, {
+ test("list", `list.Sort([2, 3, 1, 4], {x:_, y:_, less: x<y})`),
+ `[1,2,3,4]`,
+ }, {
+ test("list", `list.SortStable([{a:2,v:1}, {a:1,v:2}, {a:1,v:3}], {
+ x:_,
+ y:_,
+ less: (x.a < y.a)
+ })`),
+ `[{a: 1, v: 2},{a: 1, v: 3},{a: 2, v: 1}]`,
+ }, {
+ test("list", `list.Sort([{a:1}, {b:2}], list.Ascending)`),
+ `_|_(error in call to list.Sort: less: conflicting values close(T, close(T)) and {b: 2} (mismatched types number|string and struct))`,
+ }, {
+ test("list", `list.SortStrings(["b", "a"])`),
+ `["a","b"]`,
+ }, {
+ test("list", `list.SortStrings([1, 2])`),
+ `_|_(invalid list element 0 in argument 0 to list.SortStrings: 0: cannot use value 1 (type int) as string)`,
+ }, {
+ test("list", `list.Sum([1, 2, 3, 4])`),
+ `10`,
+ }, {
+ test("list", `list.Sum([])`),
+ `0`,
+ }, {
+ test("list", `list.Sum("foo")`),
+ `_|_(cannot use "foo" (type string) as list in argument 1 to list.Sum)`,
+ }, {
+ test("list", `list.Take([1, 2, 3, 4], 0)`),
+ `[]`,
+ }, {
+ test("list", `list.Take([1, 2, 3, 4], 2)`),
+ `[1,2]`,
+ }, {
+ test("list", `list.Take([1, 2, 3, 4], 10)`),
+ `[1,2,3,4]`,
+ }, {
+ test("list", `list.Take([1, 2, 3, 4], -1)`),
+ `_|_(error in call to list.Take: negative index)`,
+ }, {
+ test("list", `list.MinItems([1, 2, 3, 4], 2)`),
+ `true`,
+ }, {
+ test("list", `list.MinItems([1, 2, 3, 4], 5)`),
+ `false`,
+ }, {
+ test("list", `list.MaxItems([1, 2, 3, 4], 5)`),
+ `true`,
+ }, {
+ test("list", `list.MaxItems([1, 2, 3, 4], 2)`),
+ `false`,
+ }, {
+ // Panics
+ test("math", `math.Jacobi(1000, 2000)`),
+ `_|_(error in call to math.Jacobi: big: invalid 2nd argument to Int.Jacobi: need odd integer but got 2000)`,
+ }, {
+ test("math", `math.Jacobi(1000, 201)`),
+ `1`,
+ }, {
+ test("math", `math.Asin(2.0e400)`),
+ `_|_(cannot use 2.0e+400 (type float) as float64 in argument 0 to math.Asin: value was rounded up)`,
+ }, {
+ test("math", `math.MultipleOf(4, 2)`), `true`,
+ }, {
+ test("math", `math.MultipleOf(5, 2)`), `false`,
+ }, {
+ test("math", `math.MultipleOf(5, 0)`),
+ `_|_(error in call to math.MultipleOf: division by zero)`,
+ }, {
+ test("math", `math.MultipleOf(100, 1.00001)`), `false`,
+ }, {
+ test("math", `math.MultipleOf(1, 1)`), `true`,
+ }, {
+ test("math", `math.MultipleOf(5, 2.5)`), `true`,
+ }, {
+ test("math", `math.MultipleOf(100e100, 10)`), `true`,
+ }, {
+ test("encoding/csv", `csv.Decode("1,2,3\n4,5,6")`),
+ `[["1","2","3"],["4","5","6"]]`,
+ }, {
+ test("regexp", `regexp.Find(#"f\w\w"#, "afoot")`),
+ `"foo"`,
+ }, {
+ test("regexp", `regexp.Find(#"f\w\w"#, "bar")`),
+ `_|_(error in call to regexp.Find: no match)`,
+ }, {
+ test("regexp", `regexp.FindAll(#"f\w\w"#, "afoot afloat from", 2)`),
+ `["foo","flo"]`,
+ }, {
+ test("regexp", `regexp.FindAll(#"f\w\w"#, "afoot afloat from", 2)`),
+ `["foo","flo"]`,
+ }, {
+ test("regexp", `regexp.FindAll(#"f\w\w"#, "bla bla", -1)`),
+ `_|_(error in call to regexp.FindAll: no match)`,
+ }, {
+ test("regexp", `regexp.FindSubmatch(#"f(\w)(\w)"#, "afloat afoot from")`),
+ `["flo","l","o"]`,
+ }, {
+ test("regexp", `regexp.FindAllSubmatch(#"f(\w)(\w)"#, "afloat afoot from", -1)`),
+ `[["flo","l","o"],["foo","o","o"],["fro","r","o"]]`,
+ }, {
+ test("regexp", `regexp.FindAllSubmatch(#"f(\w)(\w)"#, "aglom", -1)`),
+ `_|_(error in call to regexp.FindAllSubmatch: no match)`,
+ }, {
+ test("regexp", `regexp.FindNamedSubmatch(#"f(?P<A>\w)(?P<B>\w)"#, "afloat afoot from")`),
+ `{A: "l", B: "o"}`,
+ }, {
+ test("regexp", `regexp.FindAllNamedSubmatch(#"f(?P<A>\w)(?P<B>\w)"#, "afloat afoot from", -1)`),
+ `[{A: "l", B: "o"},{A: "o", B: "o"},{A: "r", B: "o"}]`,
+ }, {
+ test("regexp", `regexp.FindAllNamedSubmatch(#"f(?P<A>optional)?"#, "fbla", -1)`),
+ `[{A: ""}]`,
+ }, {
+ test("regexp", `regexp.FindAllNamedSubmatch(#"f(?P<A>\w)(?P<B>\w)"#, "aglom", -1)`),
+ `_|_(error in call to regexp.FindAllNamedSubmatch: no match)`,
+ }, {
+ test("regexp", `regexp.Valid & "valid"`),
+ `"valid"`,
+ }, {
+ test("regexp", `regexp.Valid & "invalid)"`),
+ "_|_(error in call to regexp.Valid: error parsing regexp: unexpected ): `invalid)`)",
+ }, {
+ test("strconv", `strconv.FormatBool(true)`),
+ `"true"`,
+ }, {
+ test("strings", `strings.Join(["Hello", "World!"], " ")`),
+ `"Hello World!"`,
+ }, {
+ test("strings", `strings.Join([1, 2], " ")`),
+ `_|_(invalid list element 0 in argument 0 to strings.Join: 0: cannot use value 1 (type int) as string)`,
+ }, {
+ test("strings", `strings.ByteAt("a", 0)`),
+ strconv.Itoa('a'),
+ }, {
+ test("strings", `strings.ByteSlice("Hello", 2, 5)`),
+ `'llo'`,
+ }, {
+ test("strings", `strings.SliceRunes("✓ Hello", 0, 3)`),
+ `"✓ H"`,
+ }, {
+ test("strings", `strings.Runes("Café")`),
+ strings.Replace(fmt.Sprint([]rune{'C', 'a', 'f', 'é'}), " ", ",", -1),
+ }, {
+ test("math/bits", `bits.Or(0x8, 0x1)`),
+ `9`,
+ }, {
+ testExpr(`len({})`),
+ `0`,
+ }, {
+ testExpr(`len({a: 1, b: 2, {[foo=_]: int}, _c: 3})`),
+ `2`,
+ }, {
+ testExpr(`len([1, 2, 3])`),
+ `3`,
+ }, {
+ testExpr(`len("foo")`),
+ `3`,
+ }, {
+ testExpr(`len('f\x20\x20')`),
+ `3`,
+ }, {
+ testExpr(`and([string, "foo"])`),
+ `"foo"`,
+ }, {
+ testExpr(`and([string, =~"fo"]) & "foo"`),
+ `"foo"`,
+ }, {
+ testExpr(`and([])`),
+ `{}`, // _ & top scope
+ }, {
+ testExpr(`or([1, 2, 3]) & 2`),
+ `2`,
+ }, {
+ testExpr(`or([])`),
+ `_|_(empty list in call to or)`,
+ }, {
+ test("encoding/csv", `csv.Encode([[1,2,3],[4,5],[7,8,9]])`),
+ `"1,2,3\n4,5\n7,8,9\n"`,
+ }, {
+ test("encoding/csv", `csv.Encode([["a", "b"], ["c"]])`),
+ `"a,b\nc\n"`,
+ }, {
+ test("encoding/json", `json.Valid("1")`),
+ `true`,
+ }, {
+ test("encoding/json", `json.Compact("[1, 2]")`),
+ `"[1,2]"`,
+ }, {
+ test("encoding/json", `json.Indent(#"{"a": 1, "b": 2}"#, "", " ")`),
+ `"{\n \"a\": 1,\n \"b\": 2\n}"`,
+ }, {
+ test("encoding/json", `json.Unmarshal("1")`),
+ `1`,
+ }, {
+ test("encoding/json", `json.MarshalStream([{a: 1}, {b: 2}])`),
+ `"{\"a\":1}\n{\"b\":2}\n"`,
+ }, {
+ test("encoding/json", `{
+ x: int
+ y: json.Marshal({a: x})
+ }`),
+ `{x: int, y: Marshal ({a: x})}`,
+ }, {
+ test("encoding/yaml", `yaml.MarshalStream([{a: 1}, {b: 2}])`),
+ `"a: 1\n---\nb: 2\n"`,
+ }, {
+ test("net", `net.FQDN & "foo.bar."`),
+ `"foo.bar."`,
+ }, {
+ test("net", `net.FQDN("foo.bararararararararararararararararararararararararararararararararara")`),
+ `false`,
+ }, {
+ test("net", `net.SplitHostPort("[::%lo0]:80")`),
+ `["::%lo0","80"]`,
+ }, {
+ test("net", `net.JoinHostPort("example.com", "80")`),
+ `"example.com:80"`,
+ }, {
+ test("net", `net.JoinHostPort("2001:db8::1", 80)`),
+ `"[2001:db8::1]:80"`,
+ }, {
+ test("net", `net.JoinHostPort([192,30,4,2], 80)`),
+ `"192.30.4.2:80"`,
+ }, {
+ test("net", `net.JoinHostPort([192,30,4], 80)`),
+ `_|_(error in call to net.JoinHostPort: invalid host [192,30,4])`,
+ }, {
+ test("net", `net.IP("23.23.23.23")`),
+ `true`,
+ }, {
+ test("net", `net.IPv4 & "23.23.23.2333"`),
+ `_|_(invalid value "23.23.23.2333" (does not satisfy net.IPv4()))`,
+ }, {
+ test("net", `net.IP("23.23.23.23")`),
+ `true`,
+ }, {
+ test("net", `net.IP("2001:db8::1")`),
+ `true`,
+ }, {
+ test("net", `net.IPv4("2001:db8::1")`),
+ `false`,
+ }, {
+ test("net", `net.IPv4() & "ff02::1:3"`),
+ `_|_(invalid value "ff02::1:3" (does not satisfy net.IPv4()))`,
+ }, {
+ test("net", `net.LoopbackIP([127, 0, 0, 1])`),
+ `true`,
+ }, {
+ test("net", `net.LoopbackIP("127.0.0.1")`),
+ `true`,
+ }, {
+ test("net", `net.ToIP4("127.0.0.1")`),
+ `[127,0,0,1]`,
+ }, {
+ test("net", `net.ToIP16("127.0.0.1")`),
+ `[0,0,0,0,0,0,0,0,0,0,255,255,127,0,0,1]`,
+ }, {
+ test("strings", `strings.ToCamel("AlphaBeta")`),
+ `"alphaBeta"`,
+ }, {
+ test("strings", `strings.ToTitle("alpha")`),
+ `"Alpha"`,
+ }, {
+ test("strings", `strings.MaxRunes(3) & "foo"`),
+ `"foo"`,
+ }, {
+ test("strings", `strings.MaxRunes(3) & "quux"`),
+ `_|_(invalid value "quux" (does not satisfy strings.MaxRunes(3)))`,
+ }, {
+ test("strings", `strings.MinRunes(1) & "e"`),
+ `"e"`,
+ }, {
+ test("strings", `strings.MaxRunes(0) & "e"`),
+ `_|_(invalid value "e" (does not satisfy strings.MaxRunes(0)))`,
+ }, {
+ test("strings", `strings.MaxRunes(0) & ""`),
+ `""`,
+ }, {
+ test("strings", `strings.MinRunes(3) & "hello"`),
+ `"hello"`,
+ }, {
+ test("strings", `strings.MaxRunes(10) & "hello"`),
+ `"hello"`,
+ }, {
+ test("strings", `strings.MaxRunes(3) & "hello"`),
+ `_|_(invalid value "hello" (does not satisfy strings.MaxRunes(3)))`,
+ }, {
+ test("strings", `strings.MinRunes(10) & "hello"`),
+ `_|_(invalid value "hello" (does not satisfy strings.MinRunes(10)))`,
+ }, {
+ test("struct", `struct.MinFields(0) & ""`),
+ `_|_(conflicting values MinFields (0) and "" (mismatched types struct and string))`,
+ }, {
+ test("struct", `struct.MinFields(0) & {a: 1}`),
+ `{a: 1}`,
+ }, {
+ test("struct", `struct.MinFields(2) & {a: 1}`),
+ `_|_(invalid value {a: 1} (does not satisfy struct.MinFields(2)))`,
+ }, {
+ test("struct", `struct.MaxFields(0) & {a: 1}`),
+ `_|_(invalid value {a: 1} (does not satisfy struct.MaxFields(0)))`,
+ }, {
+ test("struct", `struct.MaxFields(2) & {a: 1}`),
+ `{a: 1}`,
+ }, {
+ test("math", `math.Pow(8, 4)`), `4096`,
+ }, {
+ test("math", `math.Pow10(4)`), `10000`,
+ }, {
+ test("math", `math.Signbit(-4)`), `true`,
+ }, {
+ test("math", `math.Round(2.5)`), `3`,
+ }, {
+ test("math", `math.Round(-2.5)`), `-3`,
+ }, {
+ test("math", `math.RoundToEven(2.5)`), `2`,
+ }, {
+ test("math", `math.RoundToEven(-2.5)`), `-2`,
+ }, {
+ test("math", `math.Abs(2.5)`), `2.5`,
+ }, {
+ test("math", `math.Abs(-2.2)`), `2.2`,
+ }, {
+ test("math", `math.Cbrt(2)`), `1.25992104989487316476721`,
+ }, {
+ test("math", `math.Copysign(5, -2.2)`), `-5`,
+ }, {
+ test("math", `math.Exp(3)`), `20.0855369231876677409285`,
+ }, {
+ test("math", `math.Exp2(3.5)`), `11.3137084989847603904135`,
+ }, {
+ test("math", `math.Log(4)`), `1.38629436111989061883446`,
+ }, {
+ test("math", `math.Log10(4)`), `0.602059991327962390427478`,
+ }, {
+ test("math", `math.Log2(5)`),
+ `2.32192809488736234787032`,
+ }, {
+ test("math", `math.Dim(3, 2.5)`), `0.5`,
+ }, {
+ test("math", `math.Dim(5, 7.2)`), `0`,
+ }, {
+ test("math", `math.Ceil(2.5)`), `3`,
+ }, {
+ test("math", `math.Ceil(-2.2)`), `-2`,
+ }, {
+ test("math", `math.Floor(2.9)`), `2`,
+ }, {
+ test("math", `math.Floor(-2.2)`), `-3`,
+ }, {
+ test("math", `math.Trunc(2.5)`), `2`,
+ }, {
+ test("math", `math.Trunc(-2.9)`), `-2`,
+ }, {
+ test("math/bits", `bits.Lsh(0x8, 4)`), `128`,
+ }, {
+ test("math/bits", `bits.Rsh(0x100, 4)`), `16`,
+ }, {
+ test("math/bits", `bits.At(0x100, 8)`), `1`,
+ }, {
+ test("math/bits", `bits.At(0x100, 9)`), `0`,
+ }, {
+ test("math/bits", `bits.Set(0x100, 7, 1)`), `384`,
+ }, {
+ test("math/bits", `bits.Set(0x100, 8, 0)`), `0`,
+ }, {
+ test("math/bits", `bits.And(0x10000000000000F0E, 0xF0F7)`), `6`,
+ }, {
+ test("math/bits", `bits.Or(0x100000000000000F0, 0x0F)`),
+ hexToDec("100000000000000FF"),
+ }, {
+ test("math/bits", `bits.Xor(0x10000000000000F0F, 0xFF0)`),
+ hexToDec("100000000000000FF"),
+ }, {
+ test("math/bits", `bits.Xor(0xFF0, 0x10000000000000F0F)`),
+ hexToDec("100000000000000FF"),
+ }, {
+ test("math/bits", `bits.Clear(0xF, 0x100000000000008)`), `7`,
+ }, {
+ test("math/bits", `bits.Clear(0x1000000000000000008, 0xF)`),
+ hexToDec("1000000000000000000"),
+ }, {
+ test("text/tabwriter", `tabwriter.Write("""
+ a\tb\tc
+ aaa\tbb\tvv
+ """)`),
+ `"a b c\naaa bb vv"`,
+ }, {
+ test("text/tabwriter", `tabwriter.Write([
+ "a\tb\tc",
+ "aaa\tbb\tvv"])`),
+ `"a b c\naaa bb vv\n"`,
+ }, {
+ test("text/template", `template.Execute("{{.}}-{{.}}", "foo")`),
+ `"foo-foo"`,
+ }, {
+ test("time", `time.Time & "1937-01-01T12:00:27.87+00:20"`),
+ `"1937-01-01T12:00:27.87+00:20"`,
+ }, {
+ test("time", `time.Time & "no time"`),
+ `_|_(error in call to time.Time: invalid time "no time")`,
+ }, {
+ test("time", `time.Unix(1500000000, 123456)`),
+ `"2017-07-14T02:40:00.000123456Z"`,
+ }}
+ for i, tc := range testCases {
+ t.Run(fmt.Sprint(i), func(t *testing.T) {
+ insts := Build(makeInstances(tc.instances))
+ if err := insts[0].Err; err != nil {
+ t.Fatal(err)
+ }
+ got := strings.TrimSpace(fmt.Sprintf("%s\n", insts[0].Value()))
+ if got != tc.emit {
+ t.Errorf("\n got: %s\nwant: %s", got, tc.emit)
+ }
+ })
+ }
+}
diff --git a/internal/legacy/cue/builtins.go b/internal/legacy/cue/builtins.go
new file mode 100644
index 0000000..290ab79
--- /dev/null
+++ b/internal/legacy/cue/builtins.go
@@ -0,0 +1,3782 @@
+// Code generated by go generate. DO NOT EDIT.
+
+package cue
+
+import (
+ "bytes"
+ "crypto/md5"
+ "crypto/sha1"
+ "crypto/sha256"
+ "crypto/sha512"
+ "encoding/base64"
+ "encoding/csv"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "html"
+ "io"
+ "math"
+ "math/big"
+ "math/bits"
+ "net"
+ "path"
+ "regexp"
+ "sort"
+ "strconv"
+ "strings"
+ "text/tabwriter"
+ "text/template"
+ "time"
+ "unicode"
+ "unicode/utf8"
+
+ "github.com/cockroachdb/apd/v2"
+ "golang.org/x/net/idna"
+
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/literal"
+ "cuelang.org/go/cue/parser"
+ "cuelang.org/go/internal"
+ cueyaml "cuelang.org/go/internal/encoding/yaml"
+ "cuelang.org/go/internal/third_party/yaml"
+)
+
+func init() {
+ initBuiltins(builtinPackages)
+}
+
+var _ io.Reader
+
+var roundTruncContext = apd.Context{Rounding: apd.RoundDown}
+
+var roundUpContext = apd.Context{Rounding: apd.RoundHalfUp}
+
+var roundEvenContext = apd.Context{Rounding: apd.RoundHalfEven}
+
+var mulContext = apd.BaseContext.WithPrecision(1)
+
+var apdContext = apd.BaseContext.WithPrecision(24)
+
+var zero = apd.New(0, 0)
+
+var two = apd.New(2, 0)
+
+var idnaProfile = idna.New(
+ idna.ValidateLabels(true),
+ idna.VerifyDNSLength(true),
+ idna.StrictDomainName(true),
+)
+
+func netGetIP(ip Value) (goip net.IP) {
+ switch ip.Kind() {
+ case StringKind:
+ s, err := ip.String()
+ if err != nil {
+ return nil
+ }
+ goip := net.ParseIP(s)
+ if goip == nil {
+ return nil
+ }
+ return goip
+
+ case BytesKind:
+ b, err := ip.Bytes()
+ if err != nil {
+ return nil
+ }
+ goip := net.ParseIP(string(b))
+ if goip == nil {
+ return nil
+ }
+ return goip
+
+ case ListKind:
+ iter, err := ip.List()
+ if err != nil {
+ return nil
+ }
+ for iter.Next() {
+ v, err := iter.Value().Int64()
+ if err != nil {
+ return nil
+ }
+ if v < 0 || 255 < v {
+ return nil
+ }
+ goip = append(goip, byte(v))
+ }
+ return goip
+
+ default:
+
+ return nil
+ }
+}
+
+func netToList(ip net.IP) []uint {
+ a := make([]uint, len(ip))
+ for i, p := range ip {
+ a[i] = uint(p)
+ }
+ return a
+}
+
+var split = path.Split
+
+var pathClean = path.Clean
+
+var pathExt = path.Ext
+
+var pathBase = path.Base
+
+var pathIsAbs = path.IsAbs
+
+var pathDir = path.Dir
+
+var errNoMatch = errors.New("no match")
+
+var errNoNamedGroup = errors.New("no named groups")
+
+func timeFormat(value, layout string) (bool, error) {
+ _, err := time.Parse(layout, value)
+ if err != nil {
+
+ return false, fmt.Errorf("invalid time %q", value)
+ }
+ return true, nil
+}
+
+var builtinPackages = map[string]*builtinPkg{
+ "": {
+ native: []*builtin{},
+ },
+ "crypto/md5": {
+ native: []*builtin{{
+ Name: "Size",
+ Const: "16",
+ }, {
+ Name: "BlockSize",
+ Const: "64",
+ }, {
+ Name: "Sum",
+ Params: []kind{bytesKind | stringKind},
+ Result: bytesKind | stringKind,
+ Func: func(c *callCtxt) {
+ data := c.bytes(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ a := md5.Sum(data)
+ return a[:]
+ }()
+ }
+ },
+ }},
+ },
+ "crypto/sha1": {
+ native: []*builtin{{
+ Name: "Size",
+ Const: "20",
+ }, {
+ Name: "BlockSize",
+ Const: "64",
+ }, {
+ Name: "Sum",
+ Params: []kind{bytesKind | stringKind},
+ Result: bytesKind | stringKind,
+ Func: func(c *callCtxt) {
+ data := c.bytes(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ a := sha1.Sum(data)
+ return a[:]
+ }()
+ }
+ },
+ }},
+ },
+ "crypto/sha256": {
+ native: []*builtin{{
+ Name: "Size",
+ Const: "32",
+ }, {
+ Name: "Size224",
+ Const: "28",
+ }, {
+ Name: "BlockSize",
+ Const: "64",
+ }, {
+ Name: "Sum256",
+ Params: []kind{bytesKind | stringKind},
+ Result: bytesKind | stringKind,
+ Func: func(c *callCtxt) {
+ data := c.bytes(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ a := sha256.Sum256(data)
+ return a[:]
+ }()
+ }
+ },
+ }, {
+ Name: "Sum224",
+ Params: []kind{bytesKind | stringKind},
+ Result: bytesKind | stringKind,
+ Func: func(c *callCtxt) {
+ data := c.bytes(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ a := sha256.Sum224(data)
+ return a[:]
+ }()
+ }
+ },
+ }},
+ },
+ "crypto/sha512": {
+ native: []*builtin{{
+ Name: "Size",
+ Const: "64",
+ }, {
+ Name: "Size224",
+ Const: "28",
+ }, {
+ Name: "Size256",
+ Const: "32",
+ }, {
+ Name: "Size384",
+ Const: "48",
+ }, {
+ Name: "BlockSize",
+ Const: "128",
+ }, {
+ Name: "Sum512",
+ Params: []kind{bytesKind | stringKind},
+ Result: bytesKind | stringKind,
+ Func: func(c *callCtxt) {
+ data := c.bytes(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ a := sha512.Sum512(data)
+ return a[:]
+ }()
+ }
+ },
+ }, {
+ Name: "Sum384",
+ Params: []kind{bytesKind | stringKind},
+ Result: bytesKind | stringKind,
+ Func: func(c *callCtxt) {
+ data := c.bytes(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ a := sha512.Sum384(data)
+ return a[:]
+ }()
+ }
+ },
+ }, {
+ Name: "Sum512_224",
+ Params: []kind{bytesKind | stringKind},
+ Result: bytesKind | stringKind,
+ Func: func(c *callCtxt) {
+ data := c.bytes(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ a := sha512.Sum512_224(data)
+ return a[:]
+ }()
+ }
+ },
+ }, {
+ Name: "Sum512_256",
+ Params: []kind{bytesKind | stringKind},
+ Result: bytesKind | stringKind,
+ Func: func(c *callCtxt) {
+ data := c.bytes(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ a := sha512.Sum512_256(data)
+ return a[:]
+ }()
+ }
+ },
+ }},
+ },
+ "encoding/base64": {
+ native: []*builtin{{
+ Name: "EncodedLen",
+ Params: []kind{topKind, intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ encoding, n := c.value(0), c.int(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if err := encoding.Null(); err != nil {
+ return 0, fmt.Errorf("base64: unsupported encoding: %v", err)
+ }
+ return base64.StdEncoding.EncodedLen(n), nil
+ }()
+ }
+ },
+ }, {
+ Name: "DecodedLen",
+ Params: []kind{topKind, intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ encoding, x := c.value(0), c.int(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if err := encoding.Null(); err != nil {
+ return 0, fmt.Errorf("base64: unsupported encoding: %v", err)
+ }
+ return base64.StdEncoding.DecodedLen(x), nil
+ }()
+ }
+ },
+ }, {
+ Name: "Encode",
+ Params: []kind{topKind, bytesKind | stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ encoding, src := c.value(0), c.bytes(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if err := encoding.Null(); err != nil {
+ return "", fmt.Errorf("base64: unsupported encoding: %v", err)
+ }
+ return base64.StdEncoding.EncodeToString(src), nil
+ }()
+ }
+ },
+ }, {
+ Name: "Decode",
+ Params: []kind{topKind, stringKind},
+ Result: bytesKind | stringKind,
+ Func: func(c *callCtxt) {
+ encoding, s := c.value(0), c.string(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if err := encoding.Null(); err != nil {
+ return nil, fmt.Errorf("base64: unsupported encoding: %v", err)
+ }
+ return base64.StdEncoding.DecodeString(s)
+ }()
+ }
+ },
+ }},
+ },
+ "encoding/csv": {
+ native: []*builtin{{
+ Name: "Encode",
+ Params: []kind{topKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ x := c.value(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ buf := &bytes.Buffer{}
+ w := csv.NewWriter(buf)
+ iter, err := x.List()
+ if err != nil {
+ return "", err
+ }
+ for iter.Next() {
+ row, err := iter.Value().List()
+ if err != nil {
+ return "", err
+ }
+ a := []string{}
+ for row.Next() {
+ col := row.Value()
+ if str, err := col.String(); err == nil {
+ a = append(a, str)
+ } else {
+ b, err := col.MarshalJSON()
+ if err != nil {
+ return "", err
+ }
+ a = append(a, string(b))
+ }
+ }
+ _ = w.Write(a)
+ }
+ w.Flush()
+ return buf.String(), nil
+ }()
+ }
+ },
+ }, {
+ Name: "Decode",
+ Params: []kind{bytesKind | stringKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ r := c.reader(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ return csv.NewReader(r).ReadAll()
+ }()
+ }
+ },
+ }},
+ },
+ "encoding/hex": {
+ native: []*builtin{{
+ Name: "EncodedLen",
+ Params: []kind{intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ n := c.int(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return hex.EncodedLen(n)
+ }()
+ }
+ },
+ }, {
+ Name: "DecodedLen",
+ Params: []kind{intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ x := c.int(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return hex.DecodedLen(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Decode",
+ Params: []kind{stringKind},
+ Result: bytesKind | stringKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ return hex.DecodeString(s)
+ }()
+ }
+ },
+ }, {
+ Name: "Dump",
+ Params: []kind{bytesKind | stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ data := c.bytes(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return hex.Dump(data)
+ }()
+ }
+ },
+ }, {
+ Name: "Encode",
+ Params: []kind{bytesKind | stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ src := c.bytes(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return hex.EncodeToString(src)
+ }()
+ }
+ },
+ }},
+ },
+ "encoding/json": {
+ native: []*builtin{{
+ Name: "Valid",
+ Params: []kind{bytesKind | stringKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ data := c.bytes(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return json.Valid(data)
+ }()
+ }
+ },
+ }, {
+ Name: "Compact",
+ Params: []kind{bytesKind | stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ src := c.bytes(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ dst := bytes.Buffer{}
+ if err := json.Compact(&dst, src); err != nil {
+ return "", err
+ }
+ return dst.String(), nil
+ }()
+ }
+ },
+ }, {
+ Name: "Indent",
+ Params: []kind{bytesKind | stringKind, stringKind, stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ src, prefix, indent := c.bytes(0), c.string(1), c.string(2)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ dst := bytes.Buffer{}
+ if err := json.Indent(&dst, src, prefix, indent); err != nil {
+ return "", err
+ }
+ return dst.String(), nil
+ }()
+ }
+ },
+ }, {
+ Name: "HTMLEscape",
+ Params: []kind{bytesKind | stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ src := c.bytes(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ dst := &bytes.Buffer{}
+ json.HTMLEscape(dst, src)
+ return dst.String()
+ }()
+ }
+ },
+ }, {
+ Name: "Marshal",
+ Params: []kind{topKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ v := c.value(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ b, err := json.Marshal(v)
+ return string(b), err
+ }()
+ }
+ },
+ }, {
+ Name: "MarshalStream",
+ Params: []kind{topKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ v := c.value(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+
+ iter, err := v.List()
+ if err != nil {
+ return "", err
+ }
+ buf := &bytes.Buffer{}
+ for iter.Next() {
+ b, err := json.Marshal(iter.Value())
+ if err != nil {
+ return "", err
+ }
+ buf.Write(b)
+ buf.WriteByte('\n')
+ }
+ return buf.String(), nil
+ }()
+ }
+ },
+ }, {
+ Name: "Unmarshal",
+ Params: []kind{bytesKind | stringKind},
+ Result: topKind,
+ Func: func(c *callCtxt) {
+ b := c.bytes(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if !json.Valid(b) {
+ return nil, fmt.Errorf("json: invalid JSON")
+ }
+ expr, err := parser.ParseExpr("json", b)
+ if err != nil {
+
+ return nil, fmt.Errorf("json: could not parse JSON: %v", err)
+ }
+ return expr, nil
+ }()
+ }
+ },
+ }, {
+ Name: "Validate",
+ Params: []kind{bytesKind | stringKind, topKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ b, v := c.bytes(0), c.value(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if !json.Valid(b) {
+ return false, fmt.Errorf("json: invalid JSON")
+ }
+ r := internal.GetRuntime(v).(*Runtime)
+ inst, err := r.Compile("json.Validate", b)
+ if err != nil {
+ return false, err
+ }
+
+ v = v.Unify(inst.Value())
+ if v.Err() != nil {
+ return false, v.Err()
+ }
+ return true, nil
+ }()
+ }
+ },
+ }},
+ },
+ "encoding/yaml": {
+ native: []*builtin{{
+ Name: "Marshal",
+ Params: []kind{topKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ v := c.value(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if err := v.Validate(Concrete(true)); err != nil {
+ if err := v.Validate(); err != nil {
+ return "", err
+ }
+ return "", internal.ErrIncomplete
+ }
+ n := v.Syntax(Final(), Concrete(true))
+ b, err := cueyaml.Encode(n)
+ return string(b), err
+ }()
+ }
+ },
+ }, {
+ Name: "MarshalStream",
+ Params: []kind{topKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ v := c.value(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+
+ iter, err := v.List()
+ if err != nil {
+ return "", err
+ }
+ buf := &bytes.Buffer{}
+ for i := 0; iter.Next(); i++ {
+ if i > 0 {
+ buf.WriteString("---\n")
+ }
+ v := iter.Value()
+ if err := v.Validate(Concrete(true)); err != nil {
+ if err := v.Validate(); err != nil {
+ return "", err
+ }
+ return "", internal.ErrIncomplete
+ }
+ n := v.Syntax(Final(), Concrete(true))
+ b, err := cueyaml.Encode(n)
+ if err != nil {
+ return "", err
+ }
+ buf.Write(b)
+ }
+ return buf.String(), nil
+ }()
+ }
+ },
+ }, {
+ Name: "Unmarshal",
+ Params: []kind{bytesKind | stringKind},
+ Result: topKind,
+ Func: func(c *callCtxt) {
+ data := c.bytes(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ return yaml.Unmarshal("", data)
+ }()
+ }
+ },
+ }, {
+ Name: "Validate",
+ Params: []kind{bytesKind | stringKind, topKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ b, v := c.bytes(0), c.value(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ d, err := yaml.NewDecoder("yaml.Validate", b)
+ if err != nil {
+ return false, err
+ }
+ r := internal.GetRuntime(v).(*Runtime)
+ for {
+ expr, err := d.Decode()
+ if err != nil {
+ if err == io.EOF {
+ return true, nil
+ }
+ return false, err
+ }
+
+ inst, err := r.CompileExpr(expr)
+ if err != nil {
+ return false, err
+ }
+
+ x := v.Unify(inst.Value())
+ if err := x.Err(); err != nil {
+ return false, err
+ }
+ if err := x.Validate(Concrete(true)); err != nil {
+ return false, err
+ }
+
+ }
+ }()
+ }
+ },
+ }, {
+ Name: "ValidatePartial",
+ Params: []kind{bytesKind | stringKind, topKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ b, v := c.bytes(0), c.value(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ d, err := yaml.NewDecoder("yaml.ValidatePartial", b)
+ if err != nil {
+ return false, err
+ }
+ r := internal.GetRuntime(v).(*Runtime)
+ for {
+ expr, err := d.Decode()
+ if err != nil {
+ if err == io.EOF {
+ return true, nil
+ }
+ return false, err
+ }
+
+ inst, err := r.CompileExpr(expr)
+ if err != nil {
+ return false, err
+ }
+
+ if x := v.Unify(inst.Value()); x.Err() != nil {
+ return false, x.Err()
+ }
+ }
+ }()
+ }
+ },
+ }},
+ },
+ "html": {
+ native: []*builtin{{
+ Name: "Escape",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return html.EscapeString(s)
+ }()
+ }
+ },
+ }, {
+ Name: "Unescape",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return html.UnescapeString(s)
+ }()
+ }
+ },
+ }},
+ },
+ "list": {
+ native: []*builtin{{
+ Name: "Drop",
+ Params: []kind{listKind, intKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ x, n := c.list(0), c.int(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if n < 0 {
+ return nil, fmt.Errorf("negative index")
+ }
+
+ if n > len(x) {
+ return []Value{}, nil
+ }
+
+ return x[n:], nil
+ }()
+ }
+ },
+ }, {
+ Name: "FlattenN",
+ Params: []kind{topKind, intKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ xs, depth := c.value(0), c.int(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var flattenN func(Value, int) ([]Value, error)
+ flattenN = func(xs Value, depth int) ([]Value, error) {
+ var res []Value
+ iter, err := xs.List()
+ if err != nil {
+ return nil, err
+ }
+ for iter.Next() {
+ val, _ := iter.Value().Default()
+ if val.Kind() == ListKind && depth != 0 {
+ d := depth - 1
+ values, err := flattenN(val, d)
+ if err != nil {
+ return nil, err
+ }
+ res = append(res, values...)
+ } else {
+ res = append(res, val)
+ }
+ }
+ return res, nil
+ }
+ return flattenN(xs, depth)
+ }()
+ }
+ },
+ }, {
+ Name: "Take",
+ Params: []kind{listKind, intKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ x, n := c.list(0), c.int(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if n < 0 {
+ return nil, fmt.Errorf("negative index")
+ }
+
+ if n > len(x) {
+ return x, nil
+ }
+
+ return x[:n], nil
+ }()
+ }
+ },
+ }, {
+ Name: "Slice",
+ Params: []kind{listKind, intKind, intKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ x, i, j := c.list(0), c.int(1), c.int(2)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if i < 0 {
+ return nil, fmt.Errorf("negative index")
+ }
+
+ if i > j {
+ return nil, fmt.Errorf("invalid index: %v > %v", i, j)
+ }
+
+ if i > len(x) {
+ return nil, fmt.Errorf("slice bounds out of range")
+ }
+
+ if j > len(x) {
+ return nil, fmt.Errorf("slice bounds out of range")
+ }
+
+ return x[i:j], nil
+ }()
+ }
+ },
+ }, {
+ Name: "MinItems",
+ Params: []kind{listKind, intKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ a, n := c.list(0), c.int(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return len(a) >= n
+ }()
+ }
+ },
+ }, {
+ Name: "MaxItems",
+ Params: []kind{listKind, intKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ a, n := c.list(0), c.int(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return len(a) <= n
+ }()
+ }
+ },
+ }, {
+ Name: "UniqueItems",
+ Params: []kind{listKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ a := c.list(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ b := []string{}
+ for _, v := range a {
+ b = append(b, fmt.Sprint(v))
+ }
+ sort.Strings(b)
+ for i := 1; i < len(b); i++ {
+ if b[i-1] == b[i] {
+ return false
+ }
+ }
+ return true
+ }()
+ }
+ },
+ }, {
+ Name: "Contains",
+ Params: []kind{listKind, topKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ a, v := c.list(0), c.value(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ for _, w := range a {
+ if v.Equals(w) {
+ return true
+ }
+ }
+ return false
+ }()
+ }
+ },
+ }, {
+ Name: "Avg",
+ Params: []kind{listKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ xs := c.decimalList(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if 0 == len(xs) {
+ return nil, fmt.Errorf("empty list")
+ }
+
+ s := apd.New(0, 0)
+ for _, x := range xs {
+ _, err := internal.BaseContext.Add(s, x, s)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ var d apd.Decimal
+ l := apd.New(int64(len(xs)), 0)
+ _, err := internal.BaseContext.Quo(&d, s, l)
+ if err != nil {
+ return nil, err
+ }
+ return &d, nil
+ }()
+ }
+ },
+ }, {
+ Name: "Max",
+ Params: []kind{listKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ xs := c.decimalList(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if 0 == len(xs) {
+ return nil, fmt.Errorf("empty list")
+ }
+
+ max := xs[0]
+ for _, x := range xs[1:] {
+ if -1 == max.Cmp(x) {
+ max = x
+ }
+ }
+ return max, nil
+ }()
+ }
+ },
+ }, {
+ Name: "Min",
+ Params: []kind{listKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ xs := c.decimalList(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if 0 == len(xs) {
+ return nil, fmt.Errorf("empty list")
+ }
+
+ min := xs[0]
+ for _, x := range xs[1:] {
+ if +1 == min.Cmp(x) {
+ min = x
+ }
+ }
+ return min, nil
+ }()
+ }
+ },
+ }, {
+ Name: "Product",
+ Params: []kind{listKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ xs := c.decimalList(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ d := apd.New(1, 0)
+ for _, x := range xs {
+ _, err := internal.BaseContext.Mul(d, x, d)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return d, nil
+ }()
+ }
+ },
+ }, {
+ Name: "Range",
+ Params: []kind{numKind, numKind, numKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ start, limit, step := c.decimal(0), c.decimal(1), c.decimal(2)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if step.IsZero() {
+ return nil, fmt.Errorf("step must be non zero")
+ }
+
+ if !step.Negative && +1 == start.Cmp(limit) {
+ return nil, fmt.Errorf("end must be greater than start when step is positive")
+ }
+
+ if step.Negative && -1 == start.Cmp(limit) {
+ return nil, fmt.Errorf("end must be less than start when step is negative")
+ }
+
+ var vals []*internal.Decimal
+ num := start
+ for {
+ if !step.Negative && -1 != num.Cmp(limit) {
+ break
+ }
+
+ if step.Negative && +1 != num.Cmp(limit) {
+ break
+ }
+
+ vals = append(vals, num)
+ d := apd.New(0, 0)
+ _, err := internal.BaseContext.Add(d, step, num)
+ if err != nil {
+ return nil, err
+ }
+ num = d
+ }
+ return vals, nil
+ }()
+ }
+ },
+ }, {
+ Name: "Sum",
+ Params: []kind{listKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ xs := c.decimalList(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ d := apd.New(0, 0)
+ for _, x := range xs {
+ _, err := internal.BaseContext.Add(d, x, d)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return d, nil
+ }()
+ }
+ },
+ }, {
+ Name: "Sort",
+ Params: []kind{listKind, topKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ list, cmp := c.list(0), c.value(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ s := valueSorter{list, cmp, nil}
+
+ sort.Sort(&s)
+ return s.ret()
+ }()
+ }
+ },
+ }, {
+ Name: "SortStable",
+ Params: []kind{listKind, topKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ list, cmp := c.list(0), c.value(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ s := valueSorter{list, cmp, nil}
+ sort.Stable(&s)
+ return s.ret()
+ }()
+ }
+ },
+ }, {
+ Name: "SortStrings",
+ Params: []kind{listKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ a := c.strList(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ sort.Strings(a)
+ return a
+ }()
+ }
+ },
+ }, {
+ Name: "IsSorted",
+ Params: []kind{listKind, topKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ list, cmp := c.list(0), c.value(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ s := valueSorter{list, cmp, nil}
+ return sort.IsSorted(&s)
+ }()
+ }
+ },
+ }, {
+ Name: "IsSortedStrings",
+ Params: []kind{listKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ a := c.strList(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return sort.StringsAreSorted(a)
+ }()
+ }
+ },
+ }},
+ cue: `{
+ Comparer: {
+ T: _
+ less: bool
+ x: T
+ y: T
+ }
+ Ascending: {
+ T: number | string
+ less: true && x < y
+ x: T
+ y: T
+ Comparer
+ }
+ Descending: {
+ T: number | string
+ less: x > y
+ x: T
+ y: T
+ Comparer
+ }
+}`,
+ },
+ "math": {
+ native: []*builtin{{
+ Name: "MaxExp",
+ Const: "2147483647",
+ }, {
+ Name: "MinExp",
+ Const: "-2147483648",
+ }, {
+ Name: "MaxPrec",
+ Const: "4294967295",
+ }, {
+ Name: "ToNearestEven",
+ Const: "0",
+ }, {
+ Name: "ToNearestAway",
+ Const: "1",
+ }, {
+ Name: "ToZero",
+ Const: "2",
+ }, {
+ Name: "AwayFromZero",
+ Const: "3",
+ }, {
+ Name: "ToNegativeInf",
+ Const: "4",
+ }, {
+ Name: "ToPositiveInf",
+ Const: "5",
+ }, {
+ Name: "Below",
+ Const: "-1",
+ }, {
+ Name: "Exact",
+ Const: "0",
+ }, {
+ Name: "Above",
+ Const: "1",
+ }, {
+ Name: "Jacobi",
+ Params: []kind{intKind, intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ x, y := c.bigInt(0), c.bigInt(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return big.Jacobi(x, y)
+ }()
+ }
+ },
+ }, {
+ Name: "MaxBase",
+ Const: "62",
+ }, {
+ Name: "Floor",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.decimal(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var d internal.Decimal
+ _, err := apdContext.Floor(&d, x)
+ return &d, err
+ }()
+ }
+ },
+ }, {
+ Name: "Ceil",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.decimal(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var d internal.Decimal
+ _, err := apdContext.Ceil(&d, x)
+ return &d, err
+ }()
+ }
+ },
+ }, {
+ Name: "Trunc",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.decimal(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var d internal.Decimal
+ _, err := roundTruncContext.RoundToIntegralExact(&d, x)
+ return &d, err
+ }()
+ }
+ },
+ }, {
+ Name: "Round",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.decimal(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var d internal.Decimal
+ _, err := roundUpContext.RoundToIntegralExact(&d, x)
+ return &d, err
+ }()
+ }
+ },
+ }, {
+ Name: "RoundToEven",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.decimal(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var d internal.Decimal
+ _, err := roundEvenContext.RoundToIntegralExact(&d, x)
+ return &d, err
+ }()
+ }
+ },
+ }, {
+ Name: "MultipleOf",
+ Params: []kind{numKind, numKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ x, y := c.decimal(0), c.decimal(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var d apd.Decimal
+ cond, err := mulContext.Quo(&d, x, y)
+ return !cond.Inexact(), err
+ }()
+ }
+ },
+ }, {
+ Name: "Abs",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.decimal(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var d internal.Decimal
+ _, err := apdContext.Abs(&d, x)
+ return &d, err
+ }()
+ }
+ },
+ }, {
+ Name: "Acosh",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Acosh(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Asin",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Asin(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Acos",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Acos(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Asinh",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Asinh(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Atan",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Atan(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Atan2",
+ Params: []kind{numKind, numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ y, x := c.float64(0), c.float64(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Atan2(y, x)
+ }()
+ }
+ },
+ }, {
+ Name: "Atanh",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Atanh(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Cbrt",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.decimal(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var d internal.Decimal
+ _, err := apdContext.Cbrt(&d, x)
+ return &d, err
+ }()
+ }
+ },
+ }, {
+ Name: "E",
+ Const: "2.71828182845904523536028747135266249775724709369995957496696763",
+ }, {
+ Name: "Pi",
+ Const: "3.14159265358979323846264338327950288419716939937510582097494459",
+ }, {
+ Name: "Phi",
+ Const: "1.61803398874989484820458683436563811772030917980576286213544861",
+ }, {
+ Name: "Sqrt2",
+ Const: "1.41421356237309504880168872420969807856967187537694807317667974",
+ }, {
+ Name: "SqrtE",
+ Const: "1.64872127070012814684865078781416357165377610071014801157507931",
+ }, {
+ Name: "SqrtPi",
+ Const: "1.77245385090551602729816748334114518279754945612238712821380779",
+ }, {
+ Name: "SqrtPhi",
+ Const: "1.27201964951406896425242246173749149171560804184009624861664038",
+ }, {
+ Name: "Ln2",
+ Const: "0.693147180559945309417232121458176568075500134360255254120680009",
+ }, {
+ Name: "Log2E",
+ Const: "1.442695040888963407359924681001892137426645954152985934135449408",
+ }, {
+ Name: "Ln10",
+ Const: "2.3025850929940456840179914546843642076011014886287729760333278",
+ }, {
+ Name: "Log10E",
+ Const: "0.43429448190325182765112891891660508229439700580366656611445378",
+ }, {
+ Name: "Copysign",
+ Params: []kind{numKind, numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x, y := c.decimal(0), c.decimal(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ var d internal.Decimal
+ d.Set(x)
+ d.Negative = y.Negative
+ return &d
+ }()
+ }
+ },
+ }, {
+ Name: "Dim",
+ Params: []kind{numKind, numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x, y := c.decimal(0), c.decimal(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var d internal.Decimal
+ _, err := apdContext.Sub(&d, x, y)
+ if err != nil {
+ return nil, err
+ }
+ if d.Negative {
+ return zero, nil
+ }
+ return &d, nil
+ }()
+ }
+ },
+ }, {
+ Name: "Erf",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Erf(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Erfc",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Erfc(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Erfinv",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Erfinv(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Erfcinv",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Erfcinv(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Exp",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.decimal(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var d internal.Decimal
+ _, err := apdContext.Exp(&d, x)
+ return &d, err
+ }()
+ }
+ },
+ }, {
+ Name: "Exp2",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.decimal(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var d internal.Decimal
+ _, err := apdContext.Pow(&d, two, x)
+ return &d, err
+ }()
+ }
+ },
+ }, {
+ Name: "Expm1",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Expm1(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Gamma",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Gamma(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Hypot",
+ Params: []kind{numKind, numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ p, q := c.float64(0), c.float64(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Hypot(p, q)
+ }()
+ }
+ },
+ }, {
+ Name: "J0",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.J0(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Y0",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Y0(x)
+ }()
+ }
+ },
+ }, {
+ Name: "J1",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.J1(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Y1",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Y1(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Jn",
+ Params: []kind{intKind, numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ n, x := c.int(0), c.float64(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Jn(n, x)
+ }()
+ }
+ },
+ }, {
+ Name: "Yn",
+ Params: []kind{intKind, numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ n, x := c.int(0), c.float64(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Yn(n, x)
+ }()
+ }
+ },
+ }, {
+ Name: "Ldexp",
+ Params: []kind{numKind, intKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ frac, exp := c.float64(0), c.int(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Ldexp(frac, exp)
+ }()
+ }
+ },
+ }, {
+ Name: "Log",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.decimal(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var d internal.Decimal
+ _, err := apdContext.Ln(&d, x)
+ return &d, err
+ }()
+ }
+ },
+ }, {
+ Name: "Log10",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.decimal(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var d internal.Decimal
+ _, err := apdContext.Log10(&d, x)
+ return &d, err
+ }()
+ }
+ },
+ }, {
+ Name: "Log2",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.decimal(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var d, ln2 internal.Decimal
+ _, _ = apdContext.Ln(&ln2, two)
+ _, err := apdContext.Ln(&d, x)
+ if err != nil {
+ return &d, err
+ }
+ _, err = apdContext.Quo(&d, &d, &ln2)
+ return &d, nil
+ }()
+ }
+ },
+ }, {
+ Name: "Log1p",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Log1p(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Logb",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Logb(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Ilogb",
+ Params: []kind{numKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Ilogb(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Mod",
+ Params: []kind{numKind, numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x, y := c.float64(0), c.float64(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Mod(x, y)
+ }()
+ }
+ },
+ }, {
+ Name: "Pow",
+ Params: []kind{numKind, numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x, y := c.decimal(0), c.decimal(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var d internal.Decimal
+ _, err := apdContext.Pow(&d, x, y)
+ return &d, err
+ }()
+ }
+ },
+ }, {
+ Name: "Pow10",
+ Params: []kind{intKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ n := c.int32(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return apd.New(1, n)
+ }()
+ }
+ },
+ }, {
+ Name: "Remainder",
+ Params: []kind{numKind, numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x, y := c.float64(0), c.float64(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Remainder(x, y)
+ }()
+ }
+ },
+ }, {
+ Name: "Signbit",
+ Params: []kind{numKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ x := c.decimal(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return x.Negative
+ }()
+ }
+ },
+ }, {
+ Name: "Cos",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Cos(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Sin",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Sin(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Sinh",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Sinh(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Cosh",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Cosh(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Sqrt",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Sqrt(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Tan",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Tan(x)
+ }()
+ }
+ },
+ }, {
+ Name: "Tanh",
+ Params: []kind{numKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ x := c.float64(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return math.Tanh(x)
+ }()
+ }
+ },
+ }},
+ },
+ "math/bits": {
+ native: []*builtin{{
+ Name: "Lsh",
+ Params: []kind{intKind, intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ x, n := c.bigInt(0), c.uint(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ var z big.Int
+ z.Lsh(x, n)
+ return &z
+ }()
+ }
+ },
+ }, {
+ Name: "Rsh",
+ Params: []kind{intKind, intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ x, n := c.bigInt(0), c.uint(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ var z big.Int
+ z.Rsh(x, n)
+ return &z
+ }()
+ }
+ },
+ }, {
+ Name: "At",
+ Params: []kind{intKind, intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ x, i := c.bigInt(0), c.uint(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if i > math.MaxInt32 {
+ return 0, fmt.Errorf("bit index too large")
+ }
+ return x.Bit(int(i)), nil
+ }()
+ }
+ },
+ }, {
+ Name: "Set",
+ Params: []kind{intKind, intKind, intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ x, i, bit := c.bigInt(0), c.int(1), c.uint(2)
+ if c.do() {
+ c.ret = func() interface{} {
+ var z big.Int
+ z.SetBit(x, i, bit)
+ return &z
+ }()
+ }
+ },
+ }, {
+ Name: "And",
+ Params: []kind{intKind, intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ a, b := c.bigInt(0), c.bigInt(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ var z big.Int
+ z.And(a, b)
+ return &z
+ }()
+ }
+ },
+ }, {
+ Name: "Or",
+ Params: []kind{intKind, intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ a, b := c.bigInt(0), c.bigInt(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ var z big.Int
+ z.Or(a, b)
+ return &z
+ }()
+ }
+ },
+ }, {
+ Name: "Xor",
+ Params: []kind{intKind, intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ a, b := c.bigInt(0), c.bigInt(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ var z big.Int
+ z.Xor(a, b)
+ return &z
+ }()
+ }
+ },
+ }, {
+ Name: "Clear",
+ Params: []kind{intKind, intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ a, b := c.bigInt(0), c.bigInt(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ var z big.Int
+ z.AndNot(a, b)
+ return &z
+ }()
+ }
+ },
+ }, {
+ Name: "OnesCount",
+ Params: []kind{intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ x := c.bigInt(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ var count int
+ for _, w := range x.Bits() {
+ count += bits.OnesCount64(uint64(w))
+ }
+ return count
+ }()
+ }
+ },
+ }, {
+ Name: "Len",
+ Params: []kind{intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ x := c.bigInt(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return x.BitLen()
+ }()
+ }
+ },
+ }},
+ },
+ "net": {
+ native: []*builtin{{
+ Name: "SplitHostPort",
+ Params: []kind{stringKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ host, port, err := net.SplitHostPort(s)
+ if err != nil {
+ return nil, err
+ }
+ return []string{host, port}, nil
+ }()
+ }
+ },
+ }, {
+ Name: "JoinHostPort",
+ Params: []kind{topKind, topKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ host, port := c.value(0), c.value(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ var err error
+ hostStr := ""
+ switch host.Kind() {
+ case ListKind:
+ ipdata := netGetIP(host)
+ if len(ipdata) != 4 && len(ipdata) != 16 {
+ err = fmt.Errorf("invalid host %q", host)
+ }
+ hostStr = ipdata.String()
+ case BytesKind:
+ var b []byte
+ b, err = host.Bytes()
+ hostStr = string(b)
+ default:
+ hostStr, err = host.String()
+ }
+ if err != nil {
+ return "", err
+ }
+
+ portStr := ""
+ switch port.Kind() {
+ case StringKind:
+ portStr, err = port.String()
+ case BytesKind:
+ var b []byte
+ b, err = port.Bytes()
+ portStr = string(b)
+ default:
+ var i int64
+ i, err = port.Int64()
+ portStr = strconv.Itoa(int(i))
+ }
+ if err != nil {
+ return "", err
+ }
+
+ return net.JoinHostPort(hostStr, portStr), nil
+ }()
+ }
+ },
+ }, {
+ Name: "FQDN",
+ Params: []kind{stringKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ for i := 0; i < len(s); i++ {
+ if s[i] >= utf8.RuneSelf {
+ return false
+ }
+ }
+ _, err := idnaProfile.ToASCII(s)
+ return err == nil
+ }()
+ }
+ },
+ }, {
+ Name: "IPv4len",
+ Const: "4",
+ }, {
+ Name: "IPv6len",
+ Const: "16",
+ }, {
+ Name: "ParseIP",
+ Params: []kind{stringKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ goip := net.ParseIP(s)
+ if goip == nil {
+ return nil, fmt.Errorf("invalid IP address %q", s)
+ }
+ return netToList(goip), nil
+ }()
+ }
+ },
+ }, {
+ Name: "IPv4",
+ Params: []kind{topKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ ip := c.value(0)
+ if c.do() {
+ c.ret = func() interface{} {
+
+ return netGetIP(ip).To4() != nil
+ }()
+ }
+ },
+ }, {
+ Name: "IP",
+ Params: []kind{topKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ ip := c.value(0)
+ if c.do() {
+ c.ret = func() interface{} {
+
+ return netGetIP(ip) != nil
+ }()
+ }
+ },
+ }, {
+ Name: "LoopbackIP",
+ Params: []kind{topKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ ip := c.value(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return netGetIP(ip).IsLoopback()
+ }()
+ }
+ },
+ }, {
+ Name: "MulticastIP",
+ Params: []kind{topKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ ip := c.value(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return netGetIP(ip).IsMulticast()
+ }()
+ }
+ },
+ }, {
+ Name: "InterfaceLocalMulticastIP",
+ Params: []kind{topKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ ip := c.value(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return netGetIP(ip).IsInterfaceLocalMulticast()
+ }()
+ }
+ },
+ }, {
+ Name: "LinkLocalMulticastIP",
+ Params: []kind{topKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ ip := c.value(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return netGetIP(ip).IsLinkLocalMulticast()
+ }()
+ }
+ },
+ }, {
+ Name: "LinkLocalUnicastIP",
+ Params: []kind{topKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ ip := c.value(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return netGetIP(ip).IsLinkLocalUnicast()
+ }()
+ }
+ },
+ }, {
+ Name: "GlobalUnicastIP",
+ Params: []kind{topKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ ip := c.value(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return netGetIP(ip).IsGlobalUnicast()
+ }()
+ }
+ },
+ }, {
+ Name: "UnspecifiedIP",
+ Params: []kind{topKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ ip := c.value(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return netGetIP(ip).IsUnspecified()
+ }()
+ }
+ },
+ }, {
+ Name: "ToIP4",
+ Params: []kind{topKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ ip := c.value(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ ipdata := netGetIP(ip)
+ if ipdata == nil {
+ return nil, fmt.Errorf("invalid IP %q", ip)
+ }
+ ipv4 := ipdata.To4()
+ if ipv4 == nil {
+ return nil, fmt.Errorf("cannot convert %q to IPv4", ipdata)
+ }
+ return netToList(ipv4), nil
+ }()
+ }
+ },
+ }, {
+ Name: "ToIP16",
+ Params: []kind{topKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ ip := c.value(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ ipdata := netGetIP(ip)
+ if ipdata == nil {
+ return nil, fmt.Errorf("invalid IP %q", ip)
+ }
+ return netToList(ipdata), nil
+ }()
+ }
+ },
+ }, {
+ Name: "IPString",
+ Params: []kind{topKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ ip := c.value(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ ipdata := netGetIP(ip)
+ if ipdata == nil {
+ return "", fmt.Errorf("invalid IP %q", ip)
+ }
+ return ipdata.String(), nil
+ }()
+ }
+ },
+ }},
+ },
+ "path": {
+ native: []*builtin{{
+ Name: "Split",
+ Params: []kind{stringKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ path := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ file, dir := split(path)
+ return []string{file, dir}
+ }()
+ }
+ },
+ }, {
+ Name: "Match",
+ Params: []kind{stringKind, stringKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ pattern, name := c.string(0), c.string(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ return path.Match(pattern, name)
+ }()
+ }
+ },
+ }, {
+ Name: "Clean",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ path := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return pathClean(path)
+ }()
+ }
+ },
+ }, {
+ Name: "Ext",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ path := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return pathExt(path)
+ }()
+ }
+ },
+ }, {
+ Name: "Base",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ path := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return pathBase(path)
+ }()
+ }
+ },
+ }, {
+ Name: "IsAbs",
+ Params: []kind{stringKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ path := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return pathIsAbs(path)
+ }()
+ }
+ },
+ }, {
+ Name: "Dir",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ path := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return pathDir(path)
+ }()
+ }
+ },
+ }},
+ },
+ "regexp": {
+ native: []*builtin{{
+ Name: "Valid",
+ Params: []kind{stringKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ pattern := c.string(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ _, err := regexp.Compile(pattern)
+ return err == nil, err
+ }()
+ }
+ },
+ }, {
+ Name: "Find",
+ Params: []kind{stringKind, stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ pattern, s := c.string(0), c.string(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ re, err := regexp.Compile(pattern)
+ if err != nil {
+ return "", err
+ }
+ m := re.FindStringIndex(s)
+ if m == nil {
+ return "", errNoMatch
+ }
+ return s[m[0]:m[1]], nil
+ }()
+ }
+ },
+ }, {
+ Name: "FindAll",
+ Params: []kind{stringKind, stringKind, intKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ pattern, s, n := c.string(0), c.string(1), c.int(2)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ re, err := regexp.Compile(pattern)
+ if err != nil {
+ return nil, err
+ }
+ m := re.FindAllString(s, n)
+ if m == nil {
+ return nil, errNoMatch
+ }
+ return m, nil
+ }()
+ }
+ },
+ }, {
+ Name: "FindSubmatch",
+ Params: []kind{stringKind, stringKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ pattern, s := c.string(0), c.string(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ re, err := regexp.Compile(pattern)
+ if err != nil {
+ return nil, err
+ }
+ m := re.FindStringSubmatch(s)
+ if m == nil {
+ return nil, errNoMatch
+ }
+ return m, nil
+ }()
+ }
+ },
+ }, {
+ Name: "FindAllSubmatch",
+ Params: []kind{stringKind, stringKind, intKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ pattern, s, n := c.string(0), c.string(1), c.int(2)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ re, err := regexp.Compile(pattern)
+ if err != nil {
+ return nil, err
+ }
+ m := re.FindAllStringSubmatch(s, n)
+ if m == nil {
+ return nil, errNoMatch
+ }
+ return m, nil
+ }()
+ }
+ },
+ }, {
+ Name: "FindNamedSubmatch",
+ Params: []kind{stringKind, stringKind},
+ Result: structKind,
+ Func: func(c *callCtxt) {
+ pattern, s := c.string(0), c.string(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ re, err := regexp.Compile(pattern)
+ if err != nil {
+ return nil, err
+ }
+ names := re.SubexpNames()
+ if len(names) == 0 {
+ return nil, errNoNamedGroup
+ }
+ m := re.FindStringSubmatch(s)
+ if m == nil {
+ return nil, errNoMatch
+ }
+ r := make(map[string]string, len(names)-1)
+ for k, name := range names {
+ if name != "" {
+ r[name] = m[k]
+ }
+ }
+ return r, nil
+ }()
+ }
+ },
+ }, {
+ Name: "FindAllNamedSubmatch",
+ Params: []kind{stringKind, stringKind, intKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ pattern, s, n := c.string(0), c.string(1), c.int(2)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ re, err := regexp.Compile(pattern)
+ if err != nil {
+ return nil, err
+ }
+ names := re.SubexpNames()
+ if len(names) == 0 {
+ return nil, errNoNamedGroup
+ }
+ m := re.FindAllStringSubmatch(s, n)
+ if m == nil {
+ return nil, errNoMatch
+ }
+ result := make([]map[string]string, len(m))
+ for i, m := range m {
+ r := make(map[string]string, len(names)-1)
+ for k, name := range names {
+ if name != "" {
+ r[name] = m[k]
+ }
+ }
+ result[i] = r
+ }
+ return result, nil
+ }()
+ }
+ },
+ }, {
+ Name: "Match",
+ Params: []kind{stringKind, stringKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ pattern, s := c.string(0), c.string(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ return regexp.MatchString(pattern, s)
+ }()
+ }
+ },
+ }, {
+ Name: "QuoteMeta",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return regexp.QuoteMeta(s)
+ }()
+ }
+ },
+ }},
+ },
+ "strconv": {
+ native: []*builtin{{
+ Name: "Unquote",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ return literal.Unquote(s)
+ }()
+ }
+ },
+ }, {
+ Name: "ParseBool",
+ Params: []kind{stringKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ str := c.string(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ return strconv.ParseBool(str)
+ }()
+ }
+ },
+ }, {
+ Name: "FormatBool",
+ Params: []kind{boolKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ b := c.bool(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strconv.FormatBool(b)
+ }()
+ }
+ },
+ }, {
+ Name: "ParseFloat",
+ Params: []kind{stringKind, intKind},
+ Result: numKind,
+ Func: func(c *callCtxt) {
+ s, bitSize := c.string(0), c.int(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ return strconv.ParseFloat(s, bitSize)
+ }()
+ }
+ },
+ }, {
+ Name: "IntSize",
+ Const: "64",
+ }, {
+ Name: "ParseUint",
+ Params: []kind{stringKind, intKind, intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ s, base, bitSize := c.string(0), c.int(1), c.int(2)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ return strconv.ParseUint(s, base, bitSize)
+ }()
+ }
+ },
+ }, {
+ Name: "ParseInt",
+ Params: []kind{stringKind, intKind, intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ s, base, bitSize := c.string(0), c.int(1), c.int(2)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ return strconv.ParseInt(s, base, bitSize)
+ }()
+ }
+ },
+ }, {
+ Name: "Atoi",
+ Params: []kind{stringKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ return strconv.Atoi(s)
+ }()
+ }
+ },
+ }, {
+ Name: "FormatFloat",
+ Params: []kind{numKind, intKind, intKind, intKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ f, fmt, prec, bitSize := c.float64(0), c.byte(1), c.int(2), c.int(3)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strconv.FormatFloat(f, fmt, prec, bitSize)
+ }()
+ }
+ },
+ }, {
+ Name: "FormatUint",
+ Params: []kind{intKind, intKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ i, base := c.uint64(0), c.int(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strconv.FormatUint(i, base)
+ }()
+ }
+ },
+ }, {
+ Name: "FormatInt",
+ Params: []kind{intKind, intKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ i, base := c.int64(0), c.int(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strconv.FormatInt(i, base)
+ }()
+ }
+ },
+ }, {
+ Name: "Quote",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strconv.Quote(s)
+ }()
+ }
+ },
+ }, {
+ Name: "QuoteToASCII",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strconv.QuoteToASCII(s)
+ }()
+ }
+ },
+ }, {
+ Name: "QuoteToGraphic",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strconv.QuoteToGraphic(s)
+ }()
+ }
+ },
+ }, {
+ Name: "QuoteRune",
+ Params: []kind{intKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ r := c.rune(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strconv.QuoteRune(r)
+ }()
+ }
+ },
+ }, {
+ Name: "QuoteRuneToASCII",
+ Params: []kind{intKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ r := c.rune(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strconv.QuoteRuneToASCII(r)
+ }()
+ }
+ },
+ }, {
+ Name: "QuoteRuneToGraphic",
+ Params: []kind{intKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ r := c.rune(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strconv.QuoteRuneToGraphic(r)
+ }()
+ }
+ },
+ }, {
+ Name: "IsPrint",
+ Params: []kind{intKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ r := c.rune(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strconv.IsPrint(r)
+ }()
+ }
+ },
+ }, {
+ Name: "IsGraphic",
+ Params: []kind{intKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ r := c.rune(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strconv.IsGraphic(r)
+ }()
+ }
+ },
+ }},
+ },
+ "strings": {
+ native: []*builtin{{
+ Name: "ByteAt",
+ Params: []kind{bytesKind | stringKind, intKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ b, i := c.bytes(0), c.int(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if i < 0 || i >= len(b) {
+ return 0, fmt.Errorf("index out of range")
+ }
+ return b[i], nil
+ }()
+ }
+ },
+ }, {
+ Name: "ByteSlice",
+ Params: []kind{bytesKind | stringKind, intKind, intKind},
+ Result: bytesKind | stringKind,
+ Func: func(c *callCtxt) {
+ b, start, end := c.bytes(0), c.int(1), c.int(2)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if start < 0 || start > end || end > len(b) {
+ return nil, fmt.Errorf("index out of range")
+ }
+ return b[start:end], nil
+ }()
+ }
+ },
+ }, {
+ Name: "Runes",
+ Params: []kind{stringKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return []rune(s)
+ }()
+ }
+ },
+ }, {
+ Name: "MinRunes",
+ Params: []kind{stringKind, intKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ s, min := c.string(0), c.int(1)
+ if c.do() {
+ c.ret = func() interface{} {
+
+ return len([]rune(s)) >= min
+ }()
+ }
+ },
+ }, {
+ Name: "MaxRunes",
+ Params: []kind{stringKind, intKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ s, max := c.string(0), c.int(1)
+ if c.do() {
+ c.ret = func() interface{} {
+
+ return len([]rune(s)) <= max
+ }()
+ }
+ },
+ }, {
+ Name: "ToTitle",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+
+ prev := ' '
+ return strings.Map(
+ func(r rune) rune {
+ if unicode.IsSpace(prev) {
+ prev = r
+ return unicode.ToTitle(r)
+ }
+ prev = r
+ return r
+ },
+ s)
+ }()
+ }
+ },
+ }, {
+ Name: "ToCamel",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+
+ prev := ' '
+ return strings.Map(
+ func(r rune) rune {
+ if unicode.IsSpace(prev) {
+ prev = r
+ return unicode.ToLower(r)
+ }
+ prev = r
+ return r
+ },
+ s)
+ }()
+ }
+ },
+ }, {
+ Name: "SliceRunes",
+ Params: []kind{stringKind, intKind, intKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s, start, end := c.string(0), c.int(1), c.int(2)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ runes := []rune(s)
+ if start < 0 || start > end || end > len(runes) {
+ return "", fmt.Errorf("index out of range")
+ }
+ return string(runes[start:end]), nil
+ }()
+ }
+ },
+ }, {
+ Name: "Compare",
+ Params: []kind{stringKind, stringKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ a, b := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.Compare(a, b)
+ }()
+ }
+ },
+ }, {
+ Name: "Count",
+ Params: []kind{stringKind, stringKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ s, substr := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.Count(s, substr)
+ }()
+ }
+ },
+ }, {
+ Name: "Contains",
+ Params: []kind{stringKind, stringKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ s, substr := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.Contains(s, substr)
+ }()
+ }
+ },
+ }, {
+ Name: "ContainsAny",
+ Params: []kind{stringKind, stringKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ s, chars := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.ContainsAny(s, chars)
+ }()
+ }
+ },
+ }, {
+ Name: "LastIndex",
+ Params: []kind{stringKind, stringKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ s, substr := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.LastIndex(s, substr)
+ }()
+ }
+ },
+ }, {
+ Name: "IndexAny",
+ Params: []kind{stringKind, stringKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ s, chars := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.IndexAny(s, chars)
+ }()
+ }
+ },
+ }, {
+ Name: "LastIndexAny",
+ Params: []kind{stringKind, stringKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ s, chars := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.LastIndexAny(s, chars)
+ }()
+ }
+ },
+ }, {
+ Name: "SplitN",
+ Params: []kind{stringKind, stringKind, intKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ s, sep, n := c.string(0), c.string(1), c.int(2)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.SplitN(s, sep, n)
+ }()
+ }
+ },
+ }, {
+ Name: "SplitAfterN",
+ Params: []kind{stringKind, stringKind, intKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ s, sep, n := c.string(0), c.string(1), c.int(2)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.SplitAfterN(s, sep, n)
+ }()
+ }
+ },
+ }, {
+ Name: "Split",
+ Params: []kind{stringKind, stringKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ s, sep := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.Split(s, sep)
+ }()
+ }
+ },
+ }, {
+ Name: "SplitAfter",
+ Params: []kind{stringKind, stringKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ s, sep := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.SplitAfter(s, sep)
+ }()
+ }
+ },
+ }, {
+ Name: "Fields",
+ Params: []kind{stringKind},
+ Result: listKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.Fields(s)
+ }()
+ }
+ },
+ }, {
+ Name: "Join",
+ Params: []kind{listKind, stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ elems, sep := c.strList(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.Join(elems, sep)
+ }()
+ }
+ },
+ }, {
+ Name: "HasPrefix",
+ Params: []kind{stringKind, stringKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ s, prefix := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.HasPrefix(s, prefix)
+ }()
+ }
+ },
+ }, {
+ Name: "HasSuffix",
+ Params: []kind{stringKind, stringKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ s, suffix := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.HasSuffix(s, suffix)
+ }()
+ }
+ },
+ }, {
+ Name: "Repeat",
+ Params: []kind{stringKind, intKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s, count := c.string(0), c.int(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.Repeat(s, count)
+ }()
+ }
+ },
+ }, {
+ Name: "ToUpper",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.ToUpper(s)
+ }()
+ }
+ },
+ }, {
+ Name: "ToLower",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.ToLower(s)
+ }()
+ }
+ },
+ }, {
+ Name: "Trim",
+ Params: []kind{stringKind, stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s, cutset := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.Trim(s, cutset)
+ }()
+ }
+ },
+ }, {
+ Name: "TrimLeft",
+ Params: []kind{stringKind, stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s, cutset := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.TrimLeft(s, cutset)
+ }()
+ }
+ },
+ }, {
+ Name: "TrimRight",
+ Params: []kind{stringKind, stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s, cutset := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.TrimRight(s, cutset)
+ }()
+ }
+ },
+ }, {
+ Name: "TrimSpace",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.TrimSpace(s)
+ }()
+ }
+ },
+ }, {
+ Name: "TrimPrefix",
+ Params: []kind{stringKind, stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s, prefix := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.TrimPrefix(s, prefix)
+ }()
+ }
+ },
+ }, {
+ Name: "TrimSuffix",
+ Params: []kind{stringKind, stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s, suffix := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.TrimSuffix(s, suffix)
+ }()
+ }
+ },
+ }, {
+ Name: "Replace",
+ Params: []kind{stringKind, stringKind, stringKind, intKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s, old, new, n := c.string(0), c.string(1), c.string(2), c.int(3)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.Replace(s, old, new, n)
+ }()
+ }
+ },
+ }, {
+ Name: "Index",
+ Params: []kind{stringKind, stringKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ s, substr := c.string(0), c.string(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ return strings.Index(s, substr)
+ }()
+ }
+ },
+ }},
+ },
+ "struct": {
+ native: []*builtin{{
+ Name: "MinFields",
+ Params: []kind{structKind, intKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ object, n := c.structVal(0), c.int(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ iter := object.Fields(Hidden(false), Optional(false))
+ count := 0
+ for iter.Next() {
+ count++
+ }
+ return count >= n, nil
+ }()
+ }
+ },
+ }, {
+ Name: "MaxFields",
+ Params: []kind{structKind, intKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ object, n := c.structVal(0), c.int(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ iter := object.Fields(Hidden(false), Optional(false))
+ count := 0
+ for iter.Next() {
+ count++
+ }
+ return count <= n, nil
+ }()
+ }
+ },
+ }},
+ },
+ "text/tabwriter": {
+ native: []*builtin{{
+ Name: "Write",
+ Params: []kind{topKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ data := c.value(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ buf := &bytes.Buffer{}
+ tw := tabwriter.NewWriter(buf, 0, 4, 1, ' ', 0)
+
+ write := func(v Value) error {
+ b, err := v.Bytes()
+ if err != nil {
+ return err
+ }
+ _, err = tw.Write(b)
+ if err != nil {
+ return err
+ }
+ return nil
+ }
+
+ switch data.Kind() {
+ case BytesKind, StringKind:
+ if err := write(data); err != nil {
+ return "", err
+ }
+ case ListKind:
+ for i, _ := data.List(); i.Next(); {
+ if err := write(i.Value()); err != nil {
+ return "", err
+ }
+ _, _ = tw.Write([]byte{'\n'})
+ }
+ default:
+ return "", fmt.Errorf("tabwriter.Write: unsupported type %v", data.Kind())
+ }
+
+ err := tw.Flush()
+ return buf.String(), err
+ }()
+ }
+ },
+ }},
+ },
+ "text/template": {
+ native: []*builtin{{
+ Name: "Execute",
+ Params: []kind{stringKind, topKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ templ, data := c.string(0), c.value(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ t, err := template.New("").Parse(templ)
+ if err != nil {
+ return "", err
+ }
+ var x interface{}
+ if err := data.Decode(&x); err != nil {
+ return "", err
+ }
+ buf := &bytes.Buffer{}
+ if err := t.Execute(buf, x); err != nil {
+ return "", err
+ }
+ return buf.String(), nil
+ }()
+ }
+ },
+ }, {
+ Name: "HTMLEscape",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return template.HTMLEscapeString(s)
+ }()
+ }
+ },
+ }, {
+ Name: "JSEscape",
+ Params: []kind{stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret = func() interface{} {
+ return template.JSEscapeString(s)
+ }()
+ }
+ },
+ }},
+ },
+ "time": {
+ native: []*builtin{{
+ Name: "Nanosecond",
+ Const: "1",
+ }, {
+ Name: "Microsecond",
+ Const: "1000",
+ }, {
+ Name: "Millisecond",
+ Const: "1000000",
+ }, {
+ Name: "Second",
+ Const: "1000000000",
+ }, {
+ Name: "Minute",
+ Const: "60000000000",
+ }, {
+ Name: "Hour",
+ Const: "3600000000000",
+ }, {
+ Name: "Duration",
+ Params: []kind{stringKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ if _, err := time.ParseDuration(s); err != nil {
+ return false, err
+ }
+ return true, nil
+ }()
+ }
+ },
+ }, {
+ Name: "ParseDuration",
+ Params: []kind{stringKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ d, err := time.ParseDuration(s)
+ if err != nil {
+ return 0, err
+ }
+ return int64(d), nil
+ }()
+ }
+ },
+ }, {
+ Name: "ANSIC",
+ Const: "\"Mon Jan _2 15:04:05 2006\"",
+ }, {
+ Name: "UnixDate",
+ Const: "\"Mon Jan _2 15:04:05 MST 2006\"",
+ }, {
+ Name: "RubyDate",
+ Const: "\"Mon Jan 02 15:04:05 -0700 2006\"",
+ }, {
+ Name: "RFC822",
+ Const: "\"02 Jan 06 15:04 MST\"",
+ }, {
+ Name: "RFC822Z",
+ Const: "\"02 Jan 06 15:04 -0700\"",
+ }, {
+ Name: "RFC850",
+ Const: "\"Monday, 02-Jan-06 15:04:05 MST\"",
+ }, {
+ Name: "RFC1123",
+ Const: "\"Mon, 02 Jan 2006 15:04:05 MST\"",
+ }, {
+ Name: "RFC1123Z",
+ Const: "\"Mon, 02 Jan 2006 15:04:05 -0700\"",
+ }, {
+ Name: "RFC3339",
+ Const: "\"2006-01-02T15:04:05Z07:00\"",
+ }, {
+ Name: "RFC3339Nano",
+ Const: "\"2006-01-02T15:04:05.999999999Z07:00\"",
+ }, {
+ Name: "RFC3339Date",
+ Const: "\"2006-01-02\"",
+ }, {
+ Name: "Kitchen",
+ Const: "\"3:04PM\"",
+ }, {
+ Name: "Kitchen24",
+ Const: "\"15:04\"",
+ }, {
+ Name: "January",
+ Const: "1",
+ }, {
+ Name: "February",
+ Const: "2",
+ }, {
+ Name: "March",
+ Const: "3",
+ }, {
+ Name: "April",
+ Const: "4",
+ }, {
+ Name: "May",
+ Const: "5",
+ }, {
+ Name: "June",
+ Const: "6",
+ }, {
+ Name: "July",
+ Const: "7",
+ }, {
+ Name: "August",
+ Const: "8",
+ }, {
+ Name: "September",
+ Const: "9",
+ }, {
+ Name: "October",
+ Const: "10",
+ }, {
+ Name: "November",
+ Const: "11",
+ }, {
+ Name: "December",
+ Const: "12",
+ }, {
+ Name: "Sunday",
+ Const: "0",
+ }, {
+ Name: "Monday",
+ Const: "1",
+ }, {
+ Name: "Tuesday",
+ Const: "2",
+ }, {
+ Name: "Wednesday",
+ Const: "3",
+ }, {
+ Name: "Thursday",
+ Const: "4",
+ }, {
+ Name: "Friday",
+ Const: "5",
+ }, {
+ Name: "Saturday",
+ Const: "6",
+ }, {
+ Name: "Time",
+ Params: []kind{stringKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ s := c.string(0)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ return timeFormat(s, time.RFC3339Nano)
+ }()
+ }
+ },
+ }, {
+ Name: "Format",
+ Params: []kind{stringKind, stringKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ value, layout := c.string(0), c.string(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ return timeFormat(value, layout)
+ }()
+ }
+ },
+ }, {
+ Name: "Parse",
+ Params: []kind{stringKind, stringKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ layout, value := c.string(0), c.string(1)
+ if c.do() {
+ c.ret, c.err = func() (interface{}, error) {
+ t, err := time.Parse(layout, value)
+ if err != nil {
+ return "", err
+ }
+ return t.UTC().Format(time.RFC3339Nano), nil
+ }()
+ }
+ },
+ }, {
+ Name: "Unix",
+ Params: []kind{intKind, intKind},
+ Result: stringKind,
+ Func: func(c *callCtxt) {
+ sec, nsec := c.int64(0), c.int64(1)
+ if c.do() {
+ c.ret = func() interface{} {
+ t := time.Unix(sec, nsec)
+ return t.UTC().Format(time.RFC3339Nano)
+ }()
+ }
+ },
+ }},
+ },
+ "tool": {
+ native: []*builtin{},
+ cue: `{
+ Command: {
+ $usage?: string
+ $short?: string
+ $long?: string
+ Tasks
+ }
+ Tasks: Task | {
+ [name=string]: Tasks
+ }
+ Task: {
+ $type: "tool.Task"
+ $id: =~"\\."
+ $after?: Task | [...Task]
+ }
+ Name: =~"^\\PL([-](\\PL|\\PN))*$"
+}`,
+ },
+ "tool/cli": {
+ native: []*builtin{},
+ cue: `{
+ Print: {
+ $id: *"tool/cli.Print" | "print"
+ text: string
+ }
+}`,
+ },
+ "tool/exec": {
+ native: []*builtin{},
+ cue: `{
+ Run: {
+ $id: *"tool/exec.Run" | "exec"
+ cmd: string | [string, ...string]
+ env: {
+ [string]: string | [...=~"="]
+ }
+ stdout: *null | string | bytes
+ stderr: *null | string | bytes
+ stdin: *null | string | bytes
+ success: bool
+ }
+}`,
+ },
+ "tool/file": {
+ native: []*builtin{},
+ cue: `{
+ Read: {
+ $id: "tool/file.Read"
+ filename: !=""
+ contents: *bytes | string
+ }
+ Create: {
+ $id: "tool/file.Create"
+ filename: !=""
+ contents: bytes | string
+ permissions: int | *420
+ }
+ Append: {
+ $id: "tool/file.Append"
+ filename: !=""
+ contents: bytes | string
+ permissions: int | *420
+ }
+ Glob: {
+ $id: "tool/file.Glob"
+ glob: !=""
+ files: [...string]
+ }
+}`,
+ },
+ "tool/http": {
+ native: []*builtin{},
+ cue: `{
+ Get: Do & {
+ method: "GET"
+ }
+ Do: {
+ $id: *"tool/http.Do" | "http"
+ method: string
+ response: {
+ body: *bytes | string
+ header: {
+ [string]: string | [...string]
+ }
+ trailer: {
+ [string]: string | [...string]
+ }
+ status: string
+ statusCode: int
+ }
+ url: string
+ request: {
+ body: *bytes | string
+ header: {
+ [string]: string | [...string]
+ }
+ trailer: {
+ [string]: string | [...string]
+ }
+ }
+ }
+ Post: Do & {
+ method: "POST"
+ }
+ Put: Do & {
+ method: "PUT"
+ }
+ Delete: Do & {
+ method: "DELETE"
+ }
+}`,
+ },
+ "tool/os": {
+ native: []*builtin{},
+ cue: `{
+ Name: !="" & !~"^[$]"
+ Value: bool | number | *string | null
+ Setenv: {
+ $id: "tool/os.Setenv"
+ {[Name]: Value}
+ }
+ Getenv: {
+ $id: "tool/os.Getenv"
+ {[Name]: Value}
+ }
+ Environ: {
+ $id: "tool/os.Environ"
+ {[Name]: Value}
+ }
+ Clearenv: {
+ $id: "tool/os.Clearenv"
+ }
+}`,
+ },
+}
diff --git a/internal/legacy/cue/builtinutil.go b/internal/legacy/cue/builtinutil.go
new file mode 100644
index 0000000..df22d71
--- /dev/null
+++ b/internal/legacy/cue/builtinutil.go
@@ -0,0 +1,57 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cue
+
+// TODO: this code could be generated, but currently isn't.
+
+type valueSorter struct {
+ a []Value
+ cmp Value
+ err error
+}
+
+func (s *valueSorter) ret() ([]Value, error) {
+ if s.err != nil {
+ return nil, s.err
+ }
+ // The input slice is already a copy and that we can modify it safely.
+ return s.a, nil
+}
+
+func (s *valueSorter) Len() int { return len(s.a) }
+func (s *valueSorter) Swap(i, j int) { s.a[i], s.a[j] = s.a[j], s.a[i] }
+func (s *valueSorter) Less(i, j int) bool {
+ x := fill(s.cmp, s.a[i], "x")
+ x = fill(x, s.a[j], "y")
+ isLess, err := x.Lookup("less").Bool()
+ if err != nil && s.err == nil {
+ s.err = err
+ return true
+ }
+ return isLess
+}
+
+// fill creates a new value with the old value unified with the given value.
+// TODO: consider making this a method on Value.
+func fill(v Value, x interface{}, path ...string) Value {
+ ctx := v.ctx()
+ root := v.v.val()
+ for i := len(path) - 1; i >= 0; i-- {
+ x = map[string]interface{}{path[i]: x}
+ }
+ value := convertVal(ctx, root, false, x)
+ eval := binOp(ctx, baseValue{}, opUnify, root, value)
+ return newValueRoot(ctx, eval)
+}
diff --git a/internal/legacy/cue/context.go b/internal/legacy/cue/context.go
new file mode 100644
index 0000000..a63e3e9
--- /dev/null
+++ b/internal/legacy/cue/context.go
@@ -0,0 +1,121 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cue
+
+import (
+ "github.com/cockroachdb/apd/v2"
+)
+
+// context manages evaluation state.
+type context struct {
+ *apd.Context
+
+ *index
+
+ forwardMap []scope // pairs
+ oldSize []int
+
+ // constraints are to be evaluated at the end values to be evaluated later.
+ constraints []*binaryExpr
+ evalStack []bottom
+
+ inDefinition int
+ inSum int
+ cycleErr bool
+
+ // for debug strings
+ nodeRefs map[scope]string
+
+ // tracing
+ trace bool
+ level int
+
+ // TODO: replace with proper structural cycle detection/ occurs check.
+ // See Issue #29.
+ maxDepth int
+}
+
+func (c *context) incEvalDepth() {
+ if len(c.evalStack) > 0 {
+ c.evalStack[len(c.evalStack)-1].exprDepth++
+ }
+}
+
+func (c *context) decEvalDepth() {
+ if len(c.evalStack) > 0 {
+ c.evalStack[len(c.evalStack)-1].exprDepth--
+ }
+}
+
+var baseContext apd.Context
+
+func init() {
+ baseContext = apd.BaseContext
+ baseContext.Precision = 24
+}
+
+// newContext returns a new evaluation context.
+func (idx *index) newContext() *context {
+ c := &context{
+ Context: &baseContext,
+ index: idx,
+ }
+ return c
+}
+
+// delayConstraint schedules constraint to be evaluated and returns ret. If
+// delaying constraints is currently not allowed, it returns an error instead.
+func (c *context) delayConstraint(ret evaluated, constraint *binaryExpr) evaluated {
+ c.cycleErr = true
+ c.constraints = append(c.constraints, constraint)
+ return ret
+}
+
+func (c *context) processDelayedConstraints() evaluated {
+ cons := c.constraints
+ c.constraints = c.constraints[:0]
+ for _, dc := range cons {
+ v := binOp(c, dc, dc.Op, dc.X.evalPartial(c), dc.Y.evalPartial(c))
+ if isBottom(v) {
+ return v
+ }
+ }
+ return nil
+}
+
+func (c *context) deref(f scope) scope {
+outer:
+ for {
+ for i := 0; i < len(c.forwardMap); i += 2 {
+ if c.forwardMap[i] == f {
+ f = c.forwardMap[i+1]
+ continue outer
+ }
+ }
+ return f
+ }
+}
+
+func (c *context) pushForwards(pairs ...scope) *context {
+ c.oldSize = append(c.oldSize, len(c.forwardMap))
+ c.forwardMap = append(c.forwardMap, pairs...)
+ return c
+}
+
+func (c *context) popForwards() {
+ last := len(c.oldSize) - 1
+ c.forwardMap = c.forwardMap[:c.oldSize[last]]
+ c.oldSize = c.oldSize[:last]
+}
diff --git a/internal/legacy/cue/cue.go b/internal/legacy/cue/cue.go
new file mode 100644
index 0000000..2e0a031
--- /dev/null
+++ b/internal/legacy/cue/cue.go
@@ -0,0 +1,17 @@
+// 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 cue is a transition package for supporting the cue.Value API.
+// It aims to be plugin compatible with the old API.
+package cue
diff --git a/internal/legacy/cue/errors.go b/internal/legacy/cue/errors.go
new file mode 100644
index 0000000..467254f
--- /dev/null
+++ b/internal/legacy/cue/errors.go
@@ -0,0 +1,311 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cue
+
+import (
+ "fmt"
+ "reflect"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/token"
+ "cuelang.org/go/internal/core/adt"
+)
+
+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
+}
+
+func (v Value) appendErr(err errors.Error, b *bottom) errors.Error {
+ switch {
+ case len(b.sub) > 0:
+ for _, b := range b.sub {
+ err = v.appendErr(err, b)
+ }
+ fallthrough
+ case b.err != nil:
+ err = errors.Append(err, b.err)
+ default:
+ err = errors.Append(err, &valueError{
+ v: v,
+ err: b,
+ })
+ }
+ return err
+}
+
+func (v Value) toErr(b *bottom) (err errors.Error) {
+ return v.appendErr(nil, b)
+}
+
+var _ errors.Error = &valueError{}
+
+// A valueError is returned as a result of evaluating a value.
+type valueError struct {
+ v Value
+ err *bottom
+}
+
+func (e *valueError) Error() string {
+ return errors.String(e)
+}
+
+func (e *valueError) Position() token.Pos {
+ return e.err.Pos()
+}
+
+func (e *valueError) InputPositions() []token.Pos {
+ return e.err.Positions(e.v.ctx())
+}
+
+func (e *valueError) Msg() (string, []interface{}) {
+ return e.err.Msg()
+}
+
+func (e *valueError) Path() (a []string) {
+ if e.v.v == nil {
+ return nil
+ }
+ a, _ = e.v.v.appendPath(a, e.v.idx)
+ return a
+}
+
+type errCode = adt.ErrorCode
+
+const (
+ codeNone errCode = 0
+ codeFatal = adt.EvalError
+ codeNotExist = adt.NotExistError
+ codeTypeError = adt.EvalError
+ codeIncomplete = adt.IncompleteError
+ codeUser = adt.UserError
+ codeCycle = adt.CycleError
+)
+
+func isIncomplete(v value) bool {
+ if err, ok := v.(*bottom); ok {
+ return err.Code == codeIncomplete || err.Code == codeCycle
+ }
+ return false
+}
+
+func isLiteralBottom(v value) bool {
+ if err, ok := v.(*bottom); ok {
+ return err.Code == codeUser
+ }
+ return false
+}
+
+var errNotExists = &bottom{Code: codeNotExist, format: "undefined value"}
+
+func exists(v value) bool {
+ if err, ok := v.(*bottom); ok {
+ return err.Code != codeNotExist
+ }
+ return true
+}
+
+// bottom is the bottom of the value lattice. It is subsumed by all values.
+type bottom struct {
+ baseValue
+
+ index *index
+ Code errCode
+ exprDepth int
+ pos source
+ format string
+ args []interface{}
+
+ err errors.Error // pass-through from higher-level API
+ sub []*bottom // sub errors
+ value value
+ wrapped *bottom
+}
+
+func (x *bottom) Kind() kind { return bottomKind }
+
+func (x *bottom) Positions(ctx *context) []token.Pos {
+ var a []token.Pos
+ if x.index != nil { // TODO: remove check?
+ a = appendPositions(ctx, nil, x.pos)
+ }
+ if w := x.wrapped; w != nil {
+ a = append(a, w.Positions(ctx)...)
+ }
+ for _, sub := range x.sub {
+ a = append(a, sub.Positions(ctx)...)
+ }
+ return a
+}
+
+func appendPositions(ctx *context, pos []token.Pos, src source) []token.Pos {
+ if len(pos) > 15 {
+ return pos
+ }
+ if src != nil {
+ if p := src.Pos(); p != token.NoPos {
+ pos = append(pos, src.Pos())
+ }
+ if c := src.computed(); c != nil {
+ pos = appendPositions(ctx, pos, c.x)
+ pos = appendPositions(ctx, pos, c.y)
+ }
+ switch x := src.(type) {
+ case evaluated:
+ case value:
+ pos = appendPositions(ctx, pos, x.evalPartial(ctx))
+ }
+ }
+ return pos
+}
+
+func (x *bottom) Msg() (format string, args []interface{}) {
+ ctx := x.index.newContext()
+ // We need to copy to avoid races.
+ args = make([]interface{}, len(x.args))
+ copy(args, x.args)
+ preEvalArgs(ctx, args)
+ return x.format, x.args
+}
+
+func (x *bottom) msg() string {
+ return fmt.Sprint(x)
+}
+
+func (x *bottom) Format(s fmt.State, verb rune) {
+ msg, args := x.Msg()
+ fmt.Fprintf(s, msg, args...)
+}
+
+func cycleError(v evaluated) *bottom {
+ if err, ok := v.(*bottom); ok && err.Code == codeCycle {
+ return err
+ }
+ return nil
+}
+
+func (c *context) mkIncompatible(src source, op op, a, b evaluated) evaluated {
+ if err := firstBottom(a, b); err != nil {
+ return err
+ }
+ e := mkBin(c, src.Pos(), op, a, b)
+ return c.mkErr(e, "invalid operation %s %s %s (mismatched types %s and %s)",
+ c.str(a), op, c.str(b), a.Kind(), b.Kind())
+}
+
+func (idx *index) mkErr(src source, args ...interface{}) *bottom {
+ e := &bottom{index: idx, pos: src}
+ if src != nil {
+ e.baseValue = src.base()
+ }
+ if v, ok := src.(value); ok {
+ e.value = v
+ }
+outer:
+ for i, a := range args {
+ switch x := a.(type) {
+ case errCode:
+ e.Code = x
+ case *bottom:
+ e.wrapped = x
+ case []*bottom:
+ e.sub = x
+ case errors.Error:
+ e.err = x
+ case value:
+ case string:
+ e.format = x
+ e.args = args[i+1:]
+ // Do not expand message so that errors can be localized.
+ for i, a := range e.args {
+ e.args[i] = fixArg(idx, a)
+ }
+ break outer
+ }
+ }
+ if e.Code == codeNone && e.wrapped != nil {
+ e.Code = e.wrapped.Code
+ }
+ return e
+}
+
+func fixArg(idx *index, x interface{}) interface{} {
+ switch x.(type) {
+ case uint, int, string:
+ return x
+ case value:
+ return x
+ }
+ t := reflect.TypeOf(x)
+ // Store all non-ptr types as is, as they cannot change.
+ if k := t.Kind(); k == reflect.String || k <= reflect.Complex128 {
+ return x
+ }
+ return fmt.Sprint(x)
+}
+
+// preEvalArgs is used to expand value arguments just before printing.
+func preEvalArgs(ctx *context, args []interface{}) {
+ for i, a := range args {
+ switch v := a.(type) {
+ case *bottom:
+ args[i] = v.msg()
+ case value:
+ // TODO: convert to Go values so that localization frameworks
+ // can format values accordingly.
+ args[i] = ctx.str(v)
+ }
+ }
+}
+
+func isBottom(n value) bool {
+ return n.Kind() == bottomKind
+}
+
+func firstBottom(v ...value) *bottom {
+ for _, b := range v {
+ if isBottom(b) {
+ return b.(*bottom)
+ }
+ }
+ return nil
+}
diff --git a/internal/legacy/cue/instance.go b/internal/legacy/cue/instance.go
new file mode 100644
index 0000000..a779041
--- /dev/null
+++ b/internal/legacy/cue/instance.go
@@ -0,0 +1,365 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cue
+
+import (
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/build"
+ "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
+// underlying CUE files.
+type Instance struct {
+ *index
+
+ rootStruct *structLit // the struct to insert root values into
+ rootValue value // the value to evaluate: may add comprehensions
+
+ // scope is used as an additional top-level scope between the package scope
+ // and the predeclared identifiers.
+ scope *structLit
+
+ ImportPath string
+ Dir string
+ PkgName string
+ DisplayName string
+
+ Incomplete bool // true if Pkg and all its dependencies are free of errors
+ Err errors.Error // non-nil if the package had errors
+
+ inst *build.Instance
+
+ complete bool // for cycle detection
+}
+
+func (x *index) addInst(p *Instance) *Instance {
+ x.Index.AddInst(p.ImportPath, p.rootStruct, p)
+ p.index = x
+ return p
+}
+
+func (x *index) getImportFromNode(v value) *Instance {
+ p := x.Index.GetImportFromNode(v)
+ if p == nil {
+ return nil
+ }
+ 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() {
+ internal.MakeInstance = func(value interface{}) interface{} {
+ v := value.(Value)
+ x := v.eval(v.ctx())
+ st, ok := x.(*structLit)
+ if !ok {
+ st = &structLit{baseValue: x.base(), emit: x}
+ }
+ return v.ctx().index.addInst(&Instance{
+ rootStruct: st,
+ rootValue: v.v.v,
+ })
+ }
+}
+
+// newInstance creates a new instance. Use Insert to populate the instance.
+func newInstance(x *index, p *build.Instance) *Instance {
+ // TODO: associate root source with structLit.
+ st := &structLit{baseValue: baseValue{nil}}
+ i := &Instance{
+ rootStruct: st,
+ rootValue: st,
+ inst: p,
+ }
+ if p != nil {
+ i.ImportPath = p.ImportPath
+ i.Dir = p.Dir
+ i.PkgName = p.PkgName
+ i.DisplayName = p.ImportPath
+ if p.Err != nil {
+ i.setListOrError(p.Err)
+ }
+ }
+ return x.addInst(i)
+}
+
+func (inst *Instance) setListOrError(err errors.Error) {
+ inst.Incomplete = true
+ inst.Err = errors.Append(inst.Err, err)
+}
+
+func (inst *Instance) setError(err errors.Error) {
+ inst.Incomplete = true
+ inst.Err = errors.Append(inst.Err, err)
+}
+
+func (inst *Instance) eval(ctx *context) evaluated {
+ // TODO: remove manifest here?
+ v := ctx.manifest(inst.rootValue)
+ if s, ok := v.(*structLit); ok && s.emit != nil {
+ e := s.emit.evalPartial(ctx)
+ src := binSrc(token.NoPos, opUnify, v, e)
+ outer:
+ switch e.(type) {
+ case *structLit, *top:
+ v = binOp(ctx, src, opUnifyUnchecked, v, e)
+ if s, ok := v.(*structLit); ok {
+ s.emit = nil
+ }
+
+ default:
+ for _, a := range s.Arcs {
+ if !a.definition {
+ v = binOp(ctx, src, opUnify, v, e)
+ break outer
+ }
+ }
+ return e
+ }
+ }
+ return v
+}
+
+func init() {
+ internal.EvalExpr = func(value, expr interface{}) interface{} {
+ v := value.(Value)
+ e := expr.(ast.Expr)
+ ctx := v.idx.newContext()
+ return newValueRoot(ctx, evalExpr(ctx, v.eval(ctx), e))
+ }
+}
+
+func evalExpr(ctx *context, x value, expr ast.Expr) evaluated {
+ if isBottom(x) {
+ return ctx.mkErr(x, "error evaluating instance: %v", x)
+ }
+ obj, ok := x.(*structLit)
+ if !ok {
+ return ctx.mkErr(x, "instance is not a struct, found %s", x.Kind())
+ }
+ v := newVisitor(ctx.index, nil, nil, obj, true)
+ return v.walk(expr).evalPartial(ctx)
+}
+
+// Doc returns the package comments for this instance.
+func (inst *Instance) Doc() []*ast.CommentGroup {
+ var docs []*ast.CommentGroup
+ if inst.inst == nil {
+ return nil
+ }
+ for _, f := range inst.inst.Files {
+ if c := internal.FileComment(f); c != nil {
+ docs = append(docs, c)
+ }
+ }
+ return docs
+}
+
+// Value returns the root value of the configuration. If the configuration
+// defines in emit value, it will be that value. Otherwise it will be all
+// top-level values.
+func (inst *Instance) Value() Value {
+ ctx := inst.newContext()
+ return newValueRoot(ctx, inst.eval(ctx))
+}
+
+// Eval evaluates an expression within an existing instance.
+//
+// Expressions may refer to builtin packages if they can be uniquely identified.
+func (inst *Instance) Eval(expr ast.Expr) Value {
+ ctx := inst.newContext()
+ result := evalExpr(ctx, inst.eval(ctx), expr)
+ return newValueRoot(ctx, result)
+}
+
+// Merge unifies the given instances into a single one.
+//
+// Errors regarding conflicts are included in the result, but not reported, so
+// that these will only surface during manifestation. This allows
+// non-conflicting parts to be used.
+func Merge(inst ...*Instance) *Instance {
+ switch len(inst) {
+ case 0:
+ return nil
+ case 1:
+ return inst[0]
+ }
+
+ values := []value{}
+ for _, i := range inst {
+ if i.Err != nil {
+ return i
+ }
+ values = append(values, i.rootValue)
+ }
+ merged := &mergedValues{values: values}
+
+ ctx := inst[0].newContext()
+
+ st, ok := ctx.manifest(merged).(*structLit)
+ if !ok {
+ return nil
+ }
+
+ p := ctx.index.addInst(&Instance{
+ rootStruct: st,
+ rootValue: merged,
+ complete: true,
+ })
+ return p
+}
+
+// Build creates a new instance from the build instances, allowing unbound
+// identifier to bind to the top-level field in inst. The top-level fields in
+// inst take precedence over predeclared identifier and builtin functions.
+func (inst *Instance) Build(p *build.Instance) *Instance {
+ p.Complete()
+
+ idx := inst.index
+
+ i := newInstance(idx, p)
+ if i.Err != nil {
+ return i
+ }
+
+ ctx := inst.newContext()
+ val := newValueRoot(ctx, inst.rootValue)
+ v, err := val.structValFull(ctx)
+ if err != nil {
+ i.setError(val.toErr(err))
+ return i
+ }
+ i.scope = v.obj
+
+ if err := runtime.ResolveFiles(idx.Index, p, isBuiltin); err != nil {
+ i.setError(err)
+ return i
+ }
+ for _, f := range p.Files {
+ if err := i.insertFile(f); err != nil {
+ i.setListOrError(err)
+ }
+ }
+ i.complete = true
+
+ return i
+}
+
+func (inst *Instance) value() Value {
+ return newValueRoot(inst.newContext(), inst.rootValue)
+}
+
+// Lookup reports the value at a path starting from the top level struct. The
+// Exists method of the returned value will report false if the path did not
+// exist. The Err method reports if any error occurred during evaluation. The
+// empty path returns the top-level configuration struct. Use LookupDef for definitions or LookupField for
+// any kind of field.
+func (inst *Instance) Lookup(path ...string) Value {
+ return inst.value().Lookup(path...)
+}
+
+// LookupDef reports the definition with the given name within struct v. The
+// Exists method of the returned value will report false if the definition did
+// not exist. The Err method reports if any error occurred during evaluation.
+func (inst *Instance) LookupDef(path string) Value {
+ return inst.value().LookupDef(path)
+}
+
+// LookupField reports a Field at a path starting from v, or an error if the
+// path is not. The empty path returns v itself.
+//
+// It cannot look up hidden or unexported fields.
+//
+// Deprecated: this API does not work with new-style definitions. Use
+// FieldByName defined on inst.Value().
+func (inst *Instance) LookupField(path ...string) (f FieldInfo, err error) {
+ v := inst.value()
+ for _, k := range path {
+ s, err := v.Struct()
+ if err != nil {
+ return f, err
+ }
+
+ f, err = s.FieldByName(k, true)
+ if err != nil {
+ return f, err
+ }
+ if f.IsHidden {
+ return f, errNotFound
+ }
+ v = f.Value
+ }
+ return f, err
+}
+
+// Fill creates a new instance with the values of the old instance unified with
+// the given value. It is not possible to update the emit value.
+//
+// Values may be any Go value that can be converted to CUE, an ast.Expr or
+// a Value. In the latter case, it will panic if the Value is not from the same
+// Runtime.
+func (inst *Instance) Fill(x interface{}, path ...string) (*Instance, error) {
+ ctx := inst.newContext()
+ root := ctx.manifest(inst.rootValue)
+ for i := len(path) - 1; i >= 0; i-- {
+ x = map[string]interface{}{path[i]: x}
+ }
+ value := convertVal(ctx, root, true, x)
+ eval := binOp(ctx, baseValue{}, opUnify, root, value)
+ // TODO: validate recursively?
+ err := inst.Err
+ var st *structLit
+ var stVal evaluated
+ switch x := eval.(type) {
+ case *structLit:
+ st = x
+ stVal = x
+ default:
+ // This should not happen.
+ b := ctx.mkErr(eval, "error filling struct")
+ err = inst.Value().toErr(b)
+ st = &structLit{emit: b}
+ stVal = b
+ case *bottom:
+ err = inst.Value().toErr(x)
+ st = &structLit{emit: x}
+ stVal = x
+ }
+ inst = inst.index.addInst(&Instance{
+ rootStruct: st,
+ rootValue: stVal,
+ inst: nil,
+
+ // Omit ImportPath to indicate this is not an importable package.
+ Dir: inst.Dir,
+ PkgName: inst.PkgName,
+ Incomplete: inst.Incomplete,
+ Err: err,
+
+ complete: err != nil,
+ })
+ return inst, err
+}
diff --git a/internal/legacy/cue/marshal.go b/internal/legacy/cue/marshal.go
new file mode 100644
index 0000000..fd3cbe1
--- /dev/null
+++ b/internal/legacy/cue/marshal.go
@@ -0,0 +1,215 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cue
+
+import (
+ "bytes"
+ "compress/gzip"
+ "encoding/gob"
+ "path/filepath"
+ "strings"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/ast/astutil"
+ "cuelang.org/go/cue/build"
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/format"
+ "cuelang.org/go/cue/token"
+ "cuelang.org/go/internal"
+)
+
+// root.
+type instanceData struct {
+ Root bool
+ Path string
+ Files []fileData
+}
+
+type fileData struct {
+ Name string
+ Data []byte
+}
+
+const version = 1
+
+type unmarshaller struct {
+ ctxt *build.Context
+ imports map[string]*instanceData
+}
+
+func (b *unmarshaller) load(pos token.Pos, path string) *build.Instance {
+ bi := b.imports[path]
+ if bi == nil {
+ return nil
+ }
+ return b.build(bi)
+}
+
+func (b *unmarshaller) build(bi *instanceData) *build.Instance {
+ p := b.ctxt.NewInstance(bi.Path, b.load)
+ p.ImportPath = bi.Path
+ for _, f := range bi.Files {
+ _ = p.AddFile(f.Name, f.Data)
+ }
+ p.Complete()
+ return p
+}
+
+func compileInstances(r *Runtime, data []*instanceData) (instances []*Instance, err error) {
+ b := unmarshaller{
+ ctxt: r.buildContext(),
+ imports: map[string]*instanceData{},
+ }
+ for _, i := range data {
+ if i.Path == "" {
+ if !i.Root {
+ return nil, errors.Newf(token.NoPos,
+ "data contains non-root package without import path")
+ }
+ continue
+ }
+ b.imports[i.Path] = i
+ }
+
+ builds := []*build.Instance{}
+ for _, i := range data {
+ if !i.Root {
+ continue
+ }
+ builds = append(builds, b.build(i))
+ }
+
+ return r.build(builds)
+}
+
+// Unmarshal creates an Instance from bytes generated by the MarshalBinary
+// method of an instance.
+func (r *Runtime) Unmarshal(b []byte) ([]*Instance, error) {
+ if len(b) == 0 {
+ return nil, errors.Newf(token.NoPos, "unmarshal failed: empty buffer")
+ }
+
+ switch b[0] {
+ case version:
+ default:
+ return nil, errors.Newf(token.NoPos,
+ "unmarshal failed: unsupported version %d, regenerate data", b[0])
+ }
+
+ reader, err := gzip.NewReader(bytes.NewReader(b[1:]))
+ if err != nil {
+ return nil, errors.Newf(token.NoPos, "unmarshal failed: %v", err)
+ }
+
+ data := []*instanceData{}
+ err = gob.NewDecoder(reader).Decode(&data)
+ if err != nil {
+ return nil, errors.Newf(token.NoPos, "unmarshal failed: %v", err)
+ }
+
+ return compileInstances(r, data)
+}
+
+// Marshal creates bytes from a group of instances. Imported instances will
+// be included in the emission.
+//
+// The stored instances are functionally the same, but preserving of file
+// information is only done on a best-effort basis.
+func (r *Runtime) Marshal(instances ...*Instance) (b []byte, err error) {
+ ctx := r.index().newContext()
+
+ staged := []instanceData{}
+ done := map[string]int{}
+
+ var errs errors.Error
+
+ var stageInstance func(i *Instance) (pos int)
+ stageInstance = func(i *Instance) (pos int) {
+ if p, ok := done[i.ImportPath]; ok {
+ return p
+ }
+ // TODO: support exporting instance
+ file, _ := exportFile(ctx, nil, i.rootValue, options{raw: true})
+ imports := []string{}
+ for _, i := range internal.Imports(file) {
+ for _, spec := range i.(*ast.ImportDecl).Specs {
+ info, _ := astutil.ParseImportSpec(spec)
+ imports = append(imports, info.ID)
+ }
+ }
+
+ if i.PkgName != "" {
+ pkg := &ast.Package{Name: ast.NewIdent(i.PkgName)}
+ file.Decls = append([]ast.Decl{pkg}, file.Decls...)
+ }
+
+ b, err := format.Node(file)
+ errs = errors.Append(errs, errors.Promote(err, "marshal"))
+
+ filename := "unmarshal"
+ if i.inst != nil && len(i.inst.Files) == 1 {
+ filename = i.inst.Files[0].Filename
+
+ dir := i.Dir
+ if i.inst != nil && i.inst.Root != "" {
+ dir = i.inst.Root
+ }
+ if dir != "" {
+ filename = filepath.FromSlash(filename)
+ filename, _ = filepath.Rel(dir, filename)
+ filename = filepath.ToSlash(filename)
+ }
+ }
+ // TODO: this should probably be changed upstream, but as the path
+ // is for reference purposes only, this is safe.
+ importPath := filepath.ToSlash(i.ImportPath)
+
+ staged = append(staged, instanceData{
+ Path: importPath,
+ Files: []fileData{{filename, b}},
+ })
+
+ p := len(staged) - 1
+
+ for _, imp := range imports {
+ i := ctx.getImportFromPath(imp)
+ if i == nil || !strings.Contains(imp, ".") {
+ continue // a builtin package.
+ }
+ stageInstance(i)
+ }
+
+ return p
+ }
+
+ for _, i := range instances {
+ staged[stageInstance(i)].Root = true
+ }
+
+ buf := &bytes.Buffer{}
+ buf.WriteByte(version)
+
+ zw := gzip.NewWriter(buf)
+ if err := gob.NewEncoder(zw).Encode(staged); err != nil {
+ return nil, err
+ }
+
+ if err := zw.Close(); err != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+
+}
diff --git a/internal/legacy/cue/marshal_test.go b/internal/legacy/cue/marshal_test.go
new file mode 100644
index 0000000..b380b1a
--- /dev/null
+++ b/internal/legacy/cue/marshal_test.go
@@ -0,0 +1,202 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cue
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+func TestMarshalling(t *testing.T) {
+ testCases := []struct {
+ filename string
+ input string
+ pkg string
+ }{{
+ filename: "foo.cue",
+ pkg: "foo",
+ input: `package foo
+
+ A: int
+ B: string
+ `,
+ }, {
+ filename: "bar.cue",
+ pkg: "bar",
+ input: `package bar
+
+ "Hello world!"
+ `,
+ }, {
+ filename: "qux.cue",
+ input: `
+ "Hello world!"
+ `,
+ }, {
+ filename: "baz.cue",
+ pkg: "baz",
+ input: `package baz
+
+ import "strings"
+
+ a: strings.TrimSpace(" Hello world! ")
+ `}}
+ for _, tc := range testCases {
+ t.Run(tc.filename, func(t *testing.T) {
+ r := &Runtime{}
+ inst, err := r.Compile(tc.filename, tc.input)
+ if err != nil {
+ t.Fatal(err)
+ }
+ inst.ImportPath = "test/pkg"
+ want := fmt.Sprint(inst.Value())
+
+ b, err := r.Marshal(inst)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ r2 := &Runtime{}
+ instances, err := r2.Unmarshal(b)
+ if err != nil {
+ t.Fatal(err)
+ }
+ inst = instances[0]
+
+ if inst.ImportPath != "test/pkg" {
+ t.Error("import path was not restored")
+ }
+ got := fmt.Sprint(inst.Value())
+
+ if got != want {
+ t.Errorf("\ngot: %q;\nwant: %q", got, want)
+ }
+ })
+ }
+}
+
+func TestMarshalMultiPackage(t *testing.T) {
+ files := func(s ...string) (a []fileData) {
+ for i, s := range s {
+ a = append(a, fileData{fmt.Sprintf("file%d.cue", i), []byte(s)})
+ }
+ return a
+ }
+ insts := func(i ...*instanceData) []*instanceData { return i }
+ pkg1 := &instanceData{
+ true,
+ "pkg1",
+ files(`
+ package pkg1
+
+ Object: "World"
+ `),
+ }
+ pkg2 := &instanceData{
+ true,
+ "example.com/foo/pkg2",
+ files(`
+ package pkg
+
+ Number: 12
+ `),
+ }
+
+ testCases := []struct {
+ instances []*instanceData
+ emit string
+ }{{
+ insts(&instanceData{true, "", files(`test: "ok"`)}),
+ `{test: "ok"}`,
+ }, {
+ insts(&instanceData{true, "",
+ files(
+ `package test
+
+ import math2 "math"
+
+ "Pi: \(math2.Pi)!"`)}),
+ `"Pi: 3.14159265358979323846264338327950288419716939937510582097494459!"`,
+ }, {
+ insts(pkg1, &instanceData{true, "",
+ files(
+ `package test
+
+ import "pkg1"
+
+ "Hello \(pkg1.Object)!"`),
+ }),
+ `"Hello World!"`,
+ }, {
+ insts(pkg1, &instanceData{true, "",
+ files(
+ `package test
+
+ import pkg2 "pkg1"
+ pkg1: pkg2.Object
+
+ "Hello \(pkg1)!"`),
+ }),
+ `"Hello World!"`,
+ }, {
+ insts(pkg2, &instanceData{true, "",
+ files(
+ `package test
+
+ import "example.com/foo/pkg2"
+
+ "Hello \(pkg.Number)!"`),
+ }),
+ `"Hello 12!"`,
+ }}
+
+ strValue := func(a []*Instance) (ret []string) {
+ for _, i := range a {
+ ret = append(ret, strings.TrimSpace((fmt.Sprint(i.Value()))))
+ }
+ return ret
+ }
+
+ for _, tc := range testCases {
+ t.Run("", func(t *testing.T) {
+ r := &Runtime{}
+
+ insts, err := compileInstances(r, tc.instances)
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := strValue(insts)
+
+ b, err := r.Marshal(insts...)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ r2 := &Runtime{}
+ insts, err = r2.Unmarshal(b)
+ if err != nil {
+ t.Fatal(err)
+ }
+ got := strValue(insts)
+
+ if !cmp.Equal(got, want) {
+ t.Error(cmp.Diff(got, want))
+ }
+ })
+ }
+}
diff --git a/internal/legacy/cue/op.go b/internal/legacy/cue/op.go
new file mode 100644
index 0000000..b313289
--- /dev/null
+++ b/internal/legacy/cue/op.go
@@ -0,0 +1,219 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cue
+
+import (
+ "cuelang.org/go/cue/token"
+ "cuelang.org/go/internal/core/adt"
+)
+
+// Op indicates the operation at the top of an expression tree of the expression
+// use to evaluate a value.
+type Op = adt.Op
+
+// Values of Op.
+const (
+ NoOp = adt.NoOp
+
+ AndOp = adt.AndOp
+ OrOp = adt.OrOp
+
+ SelectorOp = adt.SelectorOp
+ IndexOp = adt.IndexOp
+ SliceOp = adt.SliceOp
+ CallOp = adt.CallOp
+
+ BooleanAndOp = adt.BoolAndOp
+ BooleanOrOp = adt.BoolOrOp
+
+ EqualOp = adt.EqualOp
+ NotOp = adt.NotOp
+ NotEqualOp = adt.NotEqualOp
+ LessThanOp = adt.LessThanOp
+ LessThanEqualOp = adt.LessEqualOp
+ GreaterThanOp = adt.GreaterThanOp
+ GreaterThanEqualOp = adt.GreaterEqualOp
+
+ RegexMatchOp = adt.MatchOp
+ NotRegexMatchOp = adt.NotMatchOp
+
+ AddOp = adt.AddOp
+ SubtractOp = adt.SubtractOp
+ MultiplyOp = adt.MultiplyOp
+ FloatQuotientOp = adt.FloatQuotientOp
+ IntQuotientOp = adt.IntQuotientOp
+ IntRemainderOp = adt.IntRemainderOp
+ IntDivideOp = adt.IntDivideOp
+ IntModuloOp = adt.IntModuloOp
+
+ InterpolationOp = adt.InterpolationOp
+)
+
+var opToOp = map[op]Op{
+ opUnify: AndOp,
+ // TODO(eval): opUnifyUnchecked is not the same as opUnify and should have its own
+ // category, if needed. More likely opUnifyUnchecked, should be
+ // represented as a separate embedding method.
+ opUnifyUnchecked: AndOp,
+ opDisjunction: OrOp,
+ opLand: BooleanAndOp,
+ opLor: BooleanOrOp,
+ opEql: EqualOp,
+ opNot: NotOp,
+ opNeq: NotEqualOp,
+ opLss: LessThanOp,
+ opLeq: LessThanEqualOp,
+ opGtr: GreaterThanOp,
+ opGeq: GreaterThanEqualOp,
+ opMat: RegexMatchOp,
+ opNMat: NotRegexMatchOp,
+ opAdd: AddOp,
+ opSub: SubtractOp,
+ opMul: MultiplyOp,
+ opQuo: FloatQuotientOp,
+ opIQuo: IntQuotientOp,
+ opIRem: IntRemainderOp,
+ opIDiv: IntDivideOp,
+ opIMod: IntModuloOp,
+}
+
+func opIn(op op, anyOf ...op) bool {
+ for _, o := range anyOf {
+ if o == op {
+ return true
+ }
+ }
+ return false
+}
+
+// isCmp reports whether an op is a comparator.
+func (op op) isCmp() bool {
+ return opEql <= op && op <= opGeq
+}
+
+func (op op) unifyType() (unchecked, ok bool) {
+ if op == opUnifyUnchecked {
+ return true, true
+ }
+ return false, op == opUnify
+}
+
+type op uint16
+
+const (
+ opUnknown op = iota
+
+ opUnify
+ opUnifyUnchecked
+ opDisjunction
+
+ opLand
+ opLor
+ opNot
+
+ opEql
+ opNeq
+ opMat
+ opNMat
+
+ opLss
+ opGtr
+ opLeq
+ opGeq
+
+ opAdd
+ opSub
+ opMul
+ opQuo
+ opRem
+
+ opIDiv
+ opIMod
+ opIQuo
+ opIRem
+)
+
+var opStrings = []string{
+ opUnknown: "??",
+
+ opUnify: "&",
+ // opUnifyUnchecked is internal only. Syntactically this is
+ // represented as embedding.
+ opUnifyUnchecked: "&!",
+ opDisjunction: "|",
+
+ opLand: "&&",
+ opLor: "||",
+ opNot: "!",
+
+ opEql: "==",
+ opNeq: "!=",
+ opMat: "=~",
+ opNMat: "!~",
+
+ opLss: "<",
+ opGtr: ">",
+ opLeq: "<=",
+ opGeq: ">=",
+
+ opAdd: "+",
+ opSub: "-",
+ opMul: "*",
+ opQuo: "/",
+
+ opIDiv: "div",
+ opIMod: "mod",
+ opIQuo: "quo",
+ opIRem: "rem",
+}
+
+func (op op) String() string { return opStrings[op] }
+
+var tokenMap = map[token.Token]op{
+ token.OR: opDisjunction, // |
+ token.AND: opUnify, // &
+
+ token.ADD: opAdd, // +
+ token.SUB: opSub, // -
+ token.MUL: opMul, // *
+ token.QUO: opQuo, // /
+
+ token.IDIV: opIDiv, // div
+ token.IMOD: opIMod, // mod
+ token.IQUO: opIQuo, // quo
+ token.IREM: opIRem, // rem
+
+ token.LAND: opLand, // &&
+ token.LOR: opLor, // ||
+
+ token.EQL: opEql, // ==
+ token.LSS: opLss, // <
+ token.GTR: opGtr, // >
+ token.NOT: opNot, // !
+
+ token.NEQ: opNeq, // !=
+ token.LEQ: opLeq, // <=
+ token.GEQ: opGeq, // >=
+ token.MAT: opMat, // =~
+ token.NMAT: opNMat, // !~
+}
+
+var opMap = map[op]token.Token{}
+
+func init() {
+ for t, o := range tokenMap {
+ opMap[o] = t
+ }
+}
diff --git a/internal/legacy/cue/resolve_test.go b/internal/legacy/cue/resolve_test.go
new file mode 100644
index 0000000..e7480da
--- /dev/null
+++ b/internal/legacy/cue/resolve_test.go
@@ -0,0 +1,3052 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cue
+
+import (
+ "flag"
+ "strings"
+ "testing"
+)
+
+var traceOn = flag.Bool("debug", false, "enable tracing")
+
+func compileFileWithErrors(t *testing.T, body string) (*context, *structLit, error) {
+ t.Helper()
+ ctx, inst, err := compileInstance(t, body)
+ return ctx, inst.rootValue.evalPartial(ctx).(*structLit), err
+}
+
+func compileFile(t *testing.T, body string) (*context, *structLit) {
+ t.Helper()
+ ctx, inst, errs := compileInstance(t, body)
+ if errs != nil {
+ t.Fatal(errs)
+ }
+ return ctx, inst.rootValue.evalPartial(ctx).(*structLit)
+}
+
+func compileInstance(t *testing.T, body string) (*context, *Instance, error) {
+ var r Runtime
+ inst, err := r.Compile("test", body)
+
+ if err != nil {
+ x := newInstance(newIndex(sharedIndex), nil)
+ ctx := x.newContext()
+ return ctx, x, err
+ }
+
+ return r.index().newContext(), inst, nil
+}
+
+func rewriteHelper(t *testing.T, cases []testCase, r rewriteMode) {
+ for _, tc := range cases {
+ t.Run(tc.desc, func(t *testing.T) {
+ ctx, obj := compileFile(t, tc.in)
+ ctx.trace = *traceOn
+ root := testResolve(ctx, obj, r)
+
+ got := debugStr(ctx, root)
+
+ // Copy the result
+ if got != tc.out {
+ fn := t.Errorf
+ if tc.skip {
+ fn = t.Skipf
+ }
+ fn("output differs:\ngot %s\nwant %s", got, tc.out)
+ }
+ })
+ }
+}
+
+type testCase struct {
+ desc string
+ in string
+ out string
+ skip bool
+}
+
+func TestBasicRewrite(t *testing.T) {
+ testCases := []testCase{{
+ desc: "errors",
+ in: `
+ a: _|_ & _|_
+ b: null & _|_
+ c: b.a == _|_
+ d: _|_ != b.a
+ e: _|_ == _|_
+ `,
+ out: `<0>{a: _|_(from source), b: _|_(from source), c: true, d: false, e: true}`,
+ }, {
+ desc: "regexp",
+ in: `
+ c1: "a" =~ "a"
+ c2: "foo" =~ "[a-z]{3}"
+ c3: "foo" =~ "[a-z]{4}"
+ c4: "foo" !~ "[a-z]{4}"
+
+ b1: =~ "a"
+ b1: "a"
+ b2: =~ "[a-z]{3}"
+ b2: "foo"
+ b3: =~ "[a-z]{4}"
+ b3: "foo"
+ b4: !~ "[a-z]{4}"
+ b4: "foo"
+
+ s1: != "b" & =~"c" // =~"c"
+ s2: != "b" & =~"[a-z]" // != "b" & =~"[a-z]"
+
+ e1: "foo" =~ 1
+ e2: "foo" !~ true
+ e3: != "a" & <5
+ `,
+ out: `<0>{c1: true, ` +
+ `c2: true, ` +
+ `c3: false, ` +
+ `c4: true, ` +
+
+ `b1: "a", ` +
+ `b2: "foo", ` +
+ `b3: _|_((=~"[a-z]{4}" & "foo"):invalid value "foo" (does not match =~"[a-z]{4}")), ` +
+ `b4: "foo", ` +
+
+ `s1: =~"c", ` +
+ `s2: (!="b" & =~"[a-z]"), ` +
+
+ `e1: _|_(("foo" =~ 1):invalid operation "foo" =~ 1 (mismatched types string and int)), ` +
+ `e2: _|_(("foo" !~ true):invalid operation "foo" !~ true (mismatched types string and bool)), ` +
+ `e3: _|_((!="a" & <5):conflicting values !="a" and <5 (mismatched types string and number))}`,
+ }, {
+ desc: "arithmetic",
+ in: `
+ i1: 1 & int
+ i2: 2 & int
+
+ sum: -1 + +2 // 1
+ div1: 2.0 / 3 * 6 // 4
+ div2: 2 / 3 * 6 // 4
+ div3: 1.00 / 1.00
+ divZero: 1.0 / 0
+ div00: 0 / 0
+ b: 1 != 4
+ add: div1 + 1.0
+
+ idiv00: 0 div 0
+ imod00: 0 mod 0
+ iquo00: 0 quo 0
+ irem00: 0 rem 0
+
+ v1: 1.0T/2.0
+ v2: 2.0 == 2
+ v3: 2.0/3.0
+ v5: i1 div i2
+
+ e0: 2 + "a"
+ // these are now all alloweed
+ // e1: 2.0 / i1
+ // e2: i1 / 2.0
+ // e3: 3.0 % i2
+ // e4: i1 % 2.0
+ e5: 1.0 div 2
+ e6: 2 rem 2.0
+ e7: 2 quo 2.0
+ e8: 1.0 mod 1
+ `,
+ out: `<0>{i1: 1, i2: 2, ` +
+ `sum: 1, ` +
+ `div1: 4.00000000000000000000000, ` +
+ `div2: 4.00000000000000000000000, ` +
+ `div3: 1., ` +
+ `divZero: _|_((1.0 / 0):division by zero), ` +
+ `div00: _|_((0 / 0):division undefined), ` +
+ `b: true, ` +
+ `add: 5.00000000000000000000000, ` +
+ `idiv00: _|_((0 div 0):division by zero), ` +
+ `imod00: _|_((0 mod 0):division by zero), ` +
+ `iquo00: _|_((0 quo 0):division by zero), ` +
+ `irem00: _|_((0 rem 0):division by zero), ` +
+ `v1: 5.0000000000e+11, ` +
+ `v2: true, ` +
+ `v3: 0.666666666666666666666667, ` +
+ `v5: 0, ` +
+
+ `e0: _|_((2 + "a"):invalid operation 2 + "a" (mismatched types int and string)), ` +
+ // `e1: _|_((2.0 / 1):unsupported op /(float, int)), ` +
+ // `e2: _|_((1 / 2.0):unsupported op /(int, float)), ` +
+ // `e3: _|_((3.0 % 2):unsupported op %(float, int)), ` +
+ // `e4: _|_((1 % 2.0):unsupported op %(int, float)), ` +
+ `e5: _|_((1.0 div 2):invalid operation 1.0 div 2 (mismatched types float and int)), ` +
+ `e6: _|_((2 rem 2.0):invalid operation 2 rem 2.0 (mismatched types int and float)), ` +
+ `e7: _|_((2 quo 2.0):invalid operation 2 quo 2.0 (mismatched types int and float)), ` +
+ `e8: _|_((1.0 mod 1):invalid operation 1.0 mod 1 (mismatched types float and int))}`,
+ }, {
+ desc: "integer-specific arithmetic",
+ in: `
+ q1: 5 quo 2 // 2
+ q2: 5 quo -2 // -2
+ q3: -5 quo 2 // -2
+ q4: -5 quo -2 // 2
+ qe1: 2.0 quo 1
+ qe2: 2 quo 1.0
+
+ r1: 5 rem 2 // 1
+ r2: 5 rem -2 // 1
+ r3: -5 rem 2 // -1
+ r4: -5 rem -2 // -1
+ re1: 2.0 rem 1
+ re2: 2 rem 1.0
+
+ d1: 5 div 2 // 2
+ d2: 5 div -2 // -2
+ d3: -5 div 2 // -3
+ d4: -5 div -2 // 3
+ de1: 2.0 div 1
+ de2: 2 div 1.0
+
+ m1: 5 mod 2 // 1
+ m2: 5 mod -2 // 1
+ m3: -5 mod 2 // 1
+ m4: -5 mod -2 // 1
+ me1: 2.0 mod 1
+ me2: 2 mod 1.0
+ `,
+ out: `<0>{q1: 2, q2: -2, q3: -2, q4: 2, ` +
+ `qe1: _|_((2.0 quo 1):invalid operation 2.0 quo 1 (mismatched types float and int)), ` +
+ `qe2: _|_((2 quo 1.0):invalid operation 2 quo 1.0 (mismatched types int and float)), ` +
+ `r1: 1, r2: 1, r3: -1, r4: -1, ` +
+ `re1: _|_((2.0 rem 1):invalid operation 2.0 rem 1 (mismatched types float and int)), ` +
+ `re2: _|_((2 rem 1.0):invalid operation 2 rem 1.0 (mismatched types int and float)), ` +
+ `d1: 2, d2: -2, d3: -3, d4: 3, ` +
+ `de1: _|_((2.0 div 1):invalid operation 2.0 div 1 (mismatched types float and int)), ` +
+ `de2: _|_((2 div 1.0):invalid operation 2 div 1.0 (mismatched types int and float)), ` +
+ `m1: 1, m2: 1, m3: 1, m4: 1, ` +
+ `me1: _|_((2.0 mod 1):invalid operation 2.0 mod 1 (mismatched types float and int)), ` +
+ `me2: _|_((2 mod 1.0):invalid operation 2 mod 1.0 (mismatched types int and float))}`,
+ }, {
+ desc: "booleans",
+ in: `
+ t: true
+ t: !false
+ f: false
+ f: !t
+ e: true
+ e: !true
+ `,
+ out: "<0>{t: true, f: false, e: _|_(true:conflicting values true and false)}",
+ }, {
+ desc: "boolean arithmetic",
+ in: `
+ a: true && true
+ b: true || false
+ c: false == true
+ d: false != true
+ e: true & true
+ f: true & false
+ `,
+ out: "<0>{a: true, b: true, c: false, d: true, e: true, f: _|_(true:conflicting values true and false)}",
+ }, {
+ desc: "basic type",
+ in: `
+ a: 1 & int
+ b: number & 1
+ c: 1.0
+ c: float
+ d: int & float // _|_
+ e: "4" & string
+ f: true
+ f: bool
+ `,
+ out: `<0>{a: 1, b: 1, c: 1.0, d: _|_((int & float):conflicting values int and float (mismatched types int and float)), e: "4", f: true}`, // TODO: eliminate redundancy
+ }, {
+ desc: "strings and bytes",
+ in: `
+ s0: "foo" + "bar"
+ s1: 3 * "abc"
+ s2: "abc" * 2
+
+ b0: 'foo' + 'bar'
+ b1: 3 * 'abc'
+ b2: 'abc' * 2
+
+ // TODO: consider the semantics of this and perhaps allow this.
+ e0: "a" + ''
+ e1: 'b' + "c"
+ `,
+ out: `<0>{` +
+ `s0: "foobar", ` +
+ `s1: "abcabcabc", ` +
+ `s2: "abcabc", ` +
+ `b0: 'foobar', ` +
+ `b1: 'abcabcabc', ` +
+ `b2: 'abcabc', ` +
+
+ `e0: _|_(("a" + ''):invalid operation "a" + '' (mismatched types string and bytes)), ` +
+ `e1: _|_(('b' + "c"):invalid operation 'b' + "c" (mismatched types bytes and string))` +
+ `}`,
+ }, {
+ desc: "escaping",
+
+ in: `
+ a: "foo\nbar",
+ b: a,
+
+ // TODO: mimic http://exploringjs.com/es6/ch_template-literals.html#sec_introduction-template-literals
+ `,
+ out: `<0>{a: "foo\nbar", b: "foo\nbar"}`,
+ // out: `<0>{a: "foo\nbar", b: <0>.a}`,
+ }, {
+ desc: "reference",
+ in: `
+ a: b
+ b: 2
+ d: {
+ d: 3
+ e: d
+ }
+ e: {
+ e: {
+ v: 1
+ }
+ f: {
+ v: e.v
+ }
+ }
+ `,
+ out: "<0>{a: 2, b: 2, d: <1>{d: 3, e: 3}, e: <2>{e: <3>{v: 1}, f: <4>{v: 1}}}",
+ }, {
+ desc: "lists",
+ in: `
+ list: [1,2,3]
+ index: [1,2,3][1]
+ unify: [1,2,3] & [_,2,3]
+ e: [] & 4
+ e2: [3]["d"]
+ e3: [3][-1]
+ e4: [1, 2, ...>=4 & <=5] & [1, 2, 4, 8]
+ e5: [1, 2, 4, 8] & [1, 2, ...>=4 & <=5]
+ `,
+ out: `<0>{list: [1,2,3], index: 2, unify: [1,2,3], e: _|_(([] & 4):conflicting values [] and 4 (mismatched types list and int)), e2: _|_("d":invalid list index "d" (type string)), e3: _|_(-1:invalid list index -1 (index must be non-negative)), e4: [1,2,4,_|_((<=5 & 8):invalid value 8 (out of bound <=5))], e5: [1,2,4,_|_((<=5 & 8):invalid value 8 (out of bound <=5))]}`,
+ }, {
+ desc: "list arithmetic",
+ in: `
+ list: [1,2,3]
+ mul0: list*0
+ mul1: list*1
+ mul2: 2*list
+ list1: [1]
+ mul1_0: list1*0
+ mul1_1: 1*list1
+ mul1_2: list1*2
+ e: list*-1
+ `,
+ out: `<0>{list: [1,2,3], ` +
+ `mul0: [], ` +
+ `mul1: [1,2,3], ` +
+ `mul2: [1,2,3,1,2,3], ` +
+ `list1: [1], ` +
+ `mul1_0: [], ` +
+ `mul1_1: [1], ` +
+ `mul1_2: [1,1], ` +
+ `e: _|_((<1>.list * -1):negative number -1 multiplies list)}`,
+ }, {
+ desc: "selecting",
+ in: `
+ obj: {a: 1, b: 2}
+ index: {a: 1, b: 2}["b"]
+ mulidx: {a: 1, b: {a:1, b: 3}}["b"]["b"]
+ e: {a: 1}[4]
+ f: {a: 1}.b
+ g: {a: 1}["b"]
+ h: [3].b
+ `,
+ out: `<0>{obj: <1>{a: 1, b: 2}, index: 2, mulidx: 3, e: _|_(4:invalid struct index 4 (type int)), f: <2>{a: 1}.b, g: <3>{a: 1}["b"], h: _|_([3]:invalid operation: [3].b (type list does not support selection))}`,
+ }, {
+ desc: "obj unify",
+ in: `
+ o1: {a: 1 } & { b: 2} // {a:1,b:2}
+ o2: {a: 1, b:2 } & { b: 2} // {a:1,b:2}
+ o3: {a: 1 } & { a:1, b: 2} // {a:1,b:2}
+ o4: {a: 1 } & { b: 2} // {a:1,b:2}
+ o4: {a: 1, b:2 } & { b: 2}
+ o4: {a: 1 } & { a:1, b: 2}
+ e: 1 // 1 & {a:3}
+ e: {a:3}
+ `,
+ out: "<0>{o1: <1>{a: 1, b: 2}, o2: <2>{a: 1, b: 2}, o3: <3>{a: 1, b: 2}, o4: <4>{a: 1, b: 2}, e: _|_((1 & <5>{a: 3}):conflicting values 1 and {a: 3} (mismatched types int and struct))}",
+ }, {
+ desc: "disjunctions",
+ in: `
+ o1: 1 | 2 | 3
+ o2: (1 | 2 | 3) & 1
+ o3: 2 & (1 | *2 | 3)
+ o4: (1 | *2 | 3) & (1 | 2 | *3)
+ o5: (1 | *2 | 3) & (3 | *2 | 1)
+ o6: (1 | 2 | 3) & (3 | 1 | 2)
+ o7: (1 | 2 | 3) & (2 | 3)
+ o8: (1 | 2 | 3) & (3 | 2)
+ o9: (2 | 3) & (1 | 2 | 3)
+ o10: (3 | 2) & (1 | *2 | 3)
+
+ m1: (*1 | (*2 | 3)) & (>=2 & <=3)
+ m2: (*1 | (*2 | 3)) & (2 | 3)
+ m3: (*1 | *(*2 | 3)) & (2 | 3)
+ m4: (2 | 3) & (*2 | 3)
+ m5: (*2 | 3) & (2 | 3)
+
+ // (*2 | 3) & (2 | 3)
+ // (2 | 3) & (*2 | 3)
+ // 2&(*2 | 3) | 3&(*2 | 3)
+ // (*1 | (*2 | 3)) & (2 | 3)
+ // *1& (2 | 3) | (*2 | 3)&(2 | 3)
+ // *2&(2 | 3) | 3&(2 | 3)
+
+ // (2 | 3)&(*1 | (*2 | 3))
+ // 2&(*1 | (*2 | 3)) | 3&(*1 | (*2 | 3))
+ // *1&2 | (*2 | 3)&2 | *1&3 | (*2 | 3)&3
+ // (*2 | 3)&2 | (*2 | 3)&3
+ // *2 | 3
+
+
+ // All errors are treated the same as per the unification model.
+ i1: [1, 2][3] | "c"
+ `,
+ out: `<0>{o1: (1 | 2 | 3), o2: 1, o3: 2, o4: (1 | 2 | 3 | *_|_), o5: (1 | *2 | 3), o6: (1 | 2 | 3), o7: (2 | 3), o8: (2 | 3), o9: (2 | 3), o10: (3 | *2), m1: (*2 | 3), m2: (*2 | 3), m3: (*2 | 3), m4: (*2 | 3), m5: (*2 | 3), i1: "c"}`,
+ }, {
+ desc: "types",
+ in: `
+ i: int
+ j: int & 3
+ s: string
+ t: "s" & string
+ e: int & string
+ e2: 1 & string
+ b: !int
+ p: +true
+ m: -false
+ `,
+ out: `<0>{i: int, j: 3, s: string, t: "s", e: _|_((int & string):conflicting values int and string (mismatched types int and string)), e2: _|_((1 & string):conflicting values 1 and string (mismatched types int and string)), b: _|_(!int:invalid operation !int (! int)), p: _|_(+true:invalid operation +true (+ bool)), m: _|_(-false:invalid operation -false (- bool))}`,
+ }, {
+ desc: "comparison",
+ in: `
+ lss: 1 < 2
+ leq: 1 <= 1.0
+ leq: 2.0 <= 3
+ eql: 1 == 1.0
+ neq: 1.0 == 1
+ gtr: !(2 > 3)
+ geq: 2.0 >= 2
+ seq: "a" + "b" == "ab"
+ err: 2 == "s"
+ `,
+ out: `<0>{lss: true, leq: true, eql: true, neq: true, gtr: true, geq: true, seq: true, err: _|_((2 == "s"):invalid operation 2 == "s" (mismatched types int and string))}`,
+ }, {
+ desc: "null",
+ in: `
+ eql: null == null
+ neq: null != null
+ unf: null & null
+
+ // errors
+ eq1: null == 1
+ eq2: 1 == null
+ ne1: "s" != null
+ call: null()
+ `,
+ out: `<0>{eql: true, neq: false, unf: null, eq1: false, eq2: false, ne1: true, call: _|_(null:cannot call non-function null (type null))}`,
+ }, {
+ desc: "self-reference cycles",
+ in: `
+ a: b - 100
+ b: a + 100
+
+ c: [c[1], c[0]]
+ `,
+ out: `<0>{a: (<1>.b - 100), ` +
+ `b: (<1>.a + 100), ` +
+ `c: [<1>.c[1],<1>.c[0]]}`,
+ }, {
+ desc: "resolved self-reference cycles",
+ in: `
+ a: b - 100
+ b: a + 100
+ b: 200
+
+ c: [c[1], a]
+
+ s1: s2 & {a: 1}
+ s2: s3 & {b: 2}
+ s3: s1 & {c: 3}
+ `,
+ out: `<0>{a: 100, b: 200, c: [100,100], s1: <1>{a: 1, b: 2, c: 3}, s2: <2>{a: 1, b: 2, c: 3}, s3: <3>{a: 1, b: 2, c: 3}}`,
+ }, {
+ desc: "resolved self-reference cycles: Issue 19",
+ in: `
+ // CUE knows how to resolve the following:
+ x: y + 100
+ y: x - 100
+ x: 200
+
+ z1: z2 + 1
+ z2: z3 + 2
+ z3: z1 - 3
+ z3: 8
+
+ // TODO: extensive tests with disjunctions.
+ `,
+ out: `<0>{x: 200, y: 100, z1: 11, z2: 10, z3: 8}`,
+ }, {
+ desc: "delayed constraint failure",
+ in: `
+ a: b - 100
+ b: a + 110
+ b: 200
+
+ x: 100
+ x: x + 1
+ `,
+ out: `<0>{` +
+ `x: _|_((100 & 101):conflicting values 100 and 101), ` +
+ `a: _|_((210 & 200):conflicting values 210 and 200), ` +
+ `b: _|_((210 & 200):conflicting values 210 and 200)}`,
+ // TODO: find a way to mark error in data.
+ }}
+ rewriteHelper(t, testCases, evalPartial)
+}
+
+func TestChooseDefault(t *testing.T) {
+ testCases := []testCase{{
+ desc: "pick first",
+ in: `
+ a: *5 | "a" | true
+ b: c: *{
+ a: 2
+ } | {
+ a : 3
+ }
+ `,
+ out: "<0>{a: 5, b: <1>{c: <2>{a: 2}}}",
+ }, {
+ // In this test, default results to bottom, meaning that the non-default
+ // value remains.
+ desc: "simple disambiguation conflict",
+ in: `
+ a: *"a" | "b"
+ b: *"b" | "a"
+ c: a & b
+ `,
+ out: `<0>{a: "a", b: "b", c: ("a" | "b")}`,
+ }, {
+ desc: "associativity of defaults",
+ in: `
+ a: *"a" | ("b" | "c")
+ b: (*"a" | "b") | "c"
+ c: *"a" | (*"b" | "c")
+ x: a & b
+ y: b & c
+ `,
+ out: `<0>{x: "a", y: (*"a" | *"b"), a: "a", b: "a", c: (*"a" | *"b")}`,
+ }}
+ rewriteHelper(t, testCases, evalFull)
+}
+
+func TestResolve(t *testing.T) {
+ testCases := []testCase{{
+ desc: "convert _ to top",
+ in: `a: { [_]: _ }`,
+ out: `<0>{a: <1>{...}}`,
+ }, {
+ in: `
+ a: b.c.d
+ b: c: { d: 3 }
+ c: { c: d.d, }
+ d: { d: 2 }
+ `,
+ out: "<0>{a: 3, b: <1>{c: <2>{d: 3}}, c: <3>{c: 2}, d: <4>{d: 2}}",
+ }, {
+ in: "`foo-bar`: 3\n x: `foo-bar`,",
+ out: `<0>{x: 3, "foo-bar": 3}`,
+ }, {
+ desc: "resolution of quoted identifiers",
+ in: `
+ package foo
+
+` + "`foo-bar`" + `: 2
+"baz": ` + "`foo-bar`" + `
+
+a: {
+ qux: 3
+ ` + "`qux-quux`" + `: qux
+ "qaz": ` + "`qux-quux`" + `
+}`,
+ out: `<0>{"foo-bar": 2, baz: 2, a: <1>{qux: 3, "qux-quux": 3, qaz: 3}}`,
+ }, {
+ in: `
+ a: _
+ b: a
+ a: { d: 1, d: _ }
+ b: _
+ `,
+ out: `<0>{a: <1>{d: 1}, b: <2>{d: 1}}`,
+ }, {
+ desc: "JSON",
+ in: `
+ a="a": 3
+ b: a
+ o: { "a\nb": 2 } // TODO: use $ for root?
+ c: o["a\nb"]
+ `,
+ out: `<0>{a: 3, b: 3, o: <1>{"a\nb": 2}, c: 2}`,
+ }, {
+ desc: "arithmetic",
+ in: `
+ v1: 1.0T/2.0
+ v2: 2.0 == 2
+ n1: 1
+ v5: 2.0 / n1
+ v6: 1.0 / 1.0
+ e2: int & 4.0/2.0
+ `,
+ out: `<0>{v1: 5.0000000000e+11, v2: true, n1: 1, v5: 2.0, v6: 1., e2: _|_((int & (4.0 / 2.0)):conflicting values int and (4.0 / 2.0) (mismatched types int and float))}`,
+ }, {
+ desc: "inequality",
+ in: `
+ a: 1 != 2
+ b: 1 != null
+ c: true == null
+ d: null != {}
+ e: null == []
+ f: 0 == 0.0 // types are unified first TODO: make this consistent
+ `,
+ out: `<0>{a: true, b: true, c: false, d: true, e: false, f: true}`,
+ }, {
+ desc: "attributes",
+ in: `
+ a: { foo: 1 @foo() @baz(1) }
+ b: { foo: 1 @bar() @foo() }
+ c: a & b
+
+ e: a & { foo: 1 @foo(other) }
+ `,
+ out: `<0>{a: <1>{foo: 1 @baz(1) @foo()}, ` +
+ `b: <2>{foo: 1 @bar() @foo()}, ` +
+ `c: <3>{foo: 1 @bar() @baz(1) @foo()}, ` +
+ `e: _|_((<4>.a & <5>{foo: 1 @foo(other)}):conflicting attributes for key "foo")}`,
+ }, {
+ desc: "optional field unification",
+ in: `
+ a: { foo?: string }
+ b: { foo: "foo" }
+ c: a & b
+ d: a & { "foo"?: "bar" }
+
+ g1: 1
+ "g\(1)"?: 1
+ "g\(2)"?: 2
+ `,
+ out: `<0>{a: <1>{foo?: string}, ` +
+ `b: <2>{foo: "foo"}, ` +
+ `c: <3>{foo: "foo"}, ` +
+ `d: <4>{foo?: "bar"}, ` +
+ `g1: 1, ` +
+ `g2?: 2}`,
+ }, {
+ desc: "optional field resolves to incomplete",
+ in: `
+ r: {
+ a?: 3
+ b: a
+ c: r["a"]
+ }
+ `,
+ out: `<0>{r: <1>{a?: 3, b: <2>.a, c: <3>.r["a"]}}`,
+ // TODO(#152): should be
+ // out: `<0>{r: <1>{a?: 3, b: <2>.a, c: <2>["a"]}}`,
+ }, {
+ desc: "bounds",
+ in: `
+ i1: >1 & 5
+ i2: (>=0 & <=10) & 5
+ i3: !=null & []
+ i4: !=2 & !=4
+
+
+ s1: >=0 & <=10 & !=1 // no simplification
+ s2: >=0 & <=10 & !=11 // >=0 & <=10
+ s3: >5 & !=5 // >5
+ s4: <10 & !=10 // <10
+ s5: !=2 & !=2
+
+ // TODO: could change inequality
+ s6: !=2 & >=2
+ s7: >=2 & !=2
+
+ s8: !=5 & >5
+
+ s10: >=0 & <=10 & <12 & >1 // >1 & <=10
+ s11: >0 & >=0 & <=12 & <12 // >0 & <12
+
+ s20: >=10 & <=10 // 10
+
+ s22: >5 & <=6 // no simplification
+ s22a: >5 & (<=6 & int) // 6
+ s22b: (int & >5) & <=6 // 6
+ s22c: >=5 & (<6 & int) // 5
+ s22d: (int & >=5) & <6 // 5
+ s22e: (>=5 & <6) & int // 5
+ s22f: int & (>=5 & <6) // 5
+
+ s23: >0 & <2 // no simplification
+ s23a: (>0 & <2) & int // int & 1
+ s23b: int & (>0 & <2) // int & 1
+ s23c: (int & >0) & <2 // int & 1
+ s23d: >0 & (int & <2) // int & 1
+ s23e: >0.0 & <2.0 // no simplification
+
+ s30: >0 & int
+
+ e1: null & !=null
+ e2: !=null & null
+ e3: >1 & 1
+ e4: <0 & 0
+ e5: >1 & <0
+ e6: >11 & <11
+ e7: >=11 & <11
+ e8: >11 & <=11
+ e9: >"a" & <1
+ `,
+ out: `<0>{i1: 5, i2: 5, i3: [], i4: (!=2 & !=4), ` +
+
+ `s1: (>=0 & <=10 & !=1), ` +
+ `s2: (>=0 & <=10), ` +
+ `s3: >5, ` +
+ `s4: <10, ` +
+ `s5: !=2, ` +
+
+ `s6: (!=2 & >=2), ` +
+ `s7: (>=2 & !=2), ` +
+
+ `s8: >5, ` +
+
+ `s10: (<=10 & >1), ` +
+ `s11: (>0 & <12), ` +
+
+ `s20: 10, ` +
+
+ `s22: (>5 & <=6), ` +
+ `s22a: 6, ` +
+ `s22b: 6, ` +
+ `s22c: 5, ` +
+ `s22d: 5, ` +
+ `s22e: 5, ` +
+ `s22f: 5, ` +
+
+ `s23: (>0 & <2), ` +
+ `s23a: 1, ` +
+ `s23b: 1, ` +
+ `s23c: 1, ` +
+ `s23d: 1, ` +
+ `s23e: (>0.0 & <2.0), ` +
+
+ `s30: int & >0, ` +
+
+ `e1: _|_((!=null & null):invalid value null (excluded by !=null)), ` +
+ `e2: _|_((!=null & null):invalid value null (excluded by !=null)), ` +
+ `e3: _|_((>1 & 1):invalid value 1 (out of bound >1)), ` +
+ `e4: _|_((<0 & 0):invalid value 0 (out of bound <0)), ` +
+ `e5: _|_(conflicting bounds >1 and <0), ` +
+ `e6: _|_(conflicting bounds >11 and <11), ` +
+ `e7: _|_(conflicting bounds >=11 and <11), ` +
+ `e8: _|_(conflicting bounds >11 and <=11), ` +
+ `e9: _|_((>"a" & <1):conflicting values >"a" and <1 (mismatched types string and number))}`,
+ }, {
+ desc: "bound conversions",
+ in: `
+ r0: int & >0.1 & <=1.9
+ r1: int & >0.1 & <1.9
+ r2: int & >=0.1 & <1.9
+ r3: int & >=-1.9 & <=-0.1
+ r4: int & >-1.9 & <=-0.1
+
+ r5: >=1.1 & <=1.1
+ r6: r5 & 1.1
+
+ c1: (1.2 & >1.3) & <2
+ c2: 1.2 & (>1.3 & <2)
+
+ c3: 1.2 & (>=1 & <2)
+ c4: 1.2 & (>=1 & <2 & int)
+ `,
+ out: `<0>{` +
+ `r0: 1, ` +
+ `r1: 1, ` +
+ `r2: 1, ` +
+ `r3: -1, ` +
+ `r4: -1, ` +
+ `r5: 1.1, ` +
+ `r6: 1.1, ` +
+ `c1: _|_((>1.3 & 1.2):invalid value 1.2 (out of bound >1.3)), ` +
+ `c2: _|_((>1.3 & 1.2):invalid value 1.2 (out of bound >1.3)), ` +
+ `c3: 1.2, ` +
+ `c4: _|_((1.2 & ((>=1 & <2) & int)):conflicting values 1.2 and ((>=1 & <2) & int) (mismatched types float and int))}`,
+ }, {
+ desc: "custom validators",
+ in: `
+ import "strings"
+
+ a: strings.ContainsAny("ab")
+ a: "after"
+
+ b: strings.ContainsAny("c")
+ b: "dog"
+
+ c: strings.ContainsAny("d") & strings.ContainsAny("g")
+ c: "dog"
+ `,
+ out: `<0>{` +
+ `a: "after", ` +
+ `b: _|_(strings.ContainsAny ("c"):invalid value "dog" (does not satisfy strings.ContainsAny("c"))), ` +
+ `c: "dog"` +
+ `}`,
+ }, {
+ desc: "null coalescing",
+ in: `
+ a: null
+ b: a.x | "b"
+ c: a["x"] | "c"
+ `,
+ out: `<0>{a: null, b: "b", c: "c"}`,
+ }, {
+ desc: "reference across tuples and back",
+ // Tests that it is okay to partially evaluate structs.
+ in: `
+ a: { c: b.e, d: b.f }
+ b: { e: 3, f: a.c }
+ `,
+ out: "<0>{a: <1>{c: 3, d: 3}, b: <2>{e: 3, f: 3}}",
+ }, {
+ desc: "index",
+ in: `
+ a: [2][0]
+ b: {foo:"bar"}["foo"]
+ c: (*l|{"3":3})["3"]
+ d: (*[]|[1])[0]
+ l: []
+ e1: [2][""]
+ e2: 2[2]
+ e3: [][true]
+ e4: [1,2,3][3]
+ e5: [1,2,3][-1]
+ e6: (*[]|{})[1]
+ def: {
+ a: 1
+ #b: 3
+ }
+ e7: def["b"]
+ `,
+ out: `<0>{a: 2, b: "bar", c: _|_("3":invalid list index "3" (type string)), l: [], d: _|_([]:index 0 out of bounds), e1: _|_("":invalid list index "" (type string)), e2: _|_(2:invalid operation: 2[2] (type int does not support indexing)), e3: _|_(true:invalid list index true (type bool)), e4: _|_([1,2,3]:index 3 out of bounds), e5: _|_(-1:invalid list index -1 (index must be non-negative)), e6: _|_([]:index 1 out of bounds), def: <1>{a: 1, #b: 3}, e7: <2>.def["b"]}`,
+ // }, {
+ // NOTE: string indexing no longer supported.
+ // Keeping it around until this is no longer an experiment.
+ // desc: "string index",
+ // in: `
+ // a0: "abc"[0]
+ // a1: "abc"[1]
+ // a2: "abc"[2]
+ // a3: "abc"[3]
+ // a4: "abc"[-1]
+
+ // b: "zoëven"[2]
+ // `,
+ // out: `<0>{a0: "a", a1: "b", a2: "c", a3: _|_("abc":index 3 out of bounds), a4: _|_(-1:invalid string index -1 (index must be non-negative)), b: "ë"}`,
+ }, {
+ desc: "disjunctions of lists",
+ in: `
+ l: [ int, int ] | [ string, string ]
+
+ l1: [ "a", "b" ]
+ l2: l & [ "c", "d" ]
+ `,
+ out: `<0>{l: ([int,int] | [string,string]), l1: ["a","b"], l2: ["c","d"]}`,
+ }, {
+ desc: "slice",
+ in: `
+ a: [2][0:0]
+ b: [0][1:1]
+ e1: [][1:1]
+ e2: [0][-1:0]
+ e3: [0][1:0]
+ e4: [0][1:2]
+ e5: 4[1:2]
+ e6: [2]["":]
+ e7: [2][:"9"]
+
+ `,
+ out: `<0>{a: [], b: [], e1: _|_(1:slice bounds out of range), e2: _|_([0]:negative slice index), e3: _|_([0]:invalid slice index: 1 > 0), e4: _|_(2:slice bounds out of range), e5: _|_(4:cannot slice 4 (type int)), e6: _|_("":invalid slice index "" (type string)), e7: _|_("9":invalid slice index "9" (type string))}`,
+ // }, {
+ // NOTE: string indexing no longer supported.
+ // Keeping it around until this is no longer an experiment.
+ // desc: "string slice",
+ // in: `
+ // a0: ""[0:0]
+ // a1: ""[:]
+ // a2: ""[0:]
+ // a3: ""[:0]
+ // b0: "abc"[0:0]
+ // b1: "abc"[0:1]
+ // b2: "abc"[0:2]
+ // b3: "abc"[0:3]
+ // b4: "abc"[3:3]
+ // b5: "abc"[1:]
+ // b6: "abc"[:2]
+
+ // // TODO: supported extended graphemes, instead of just runes.
+ // u: "Spaß"[3:4]
+ // `,
+ // out: `<0>{a0: "", a1: "", a2: "", a3: "", b0: "", b1: "a", b2: "ab", b3: "abc", b4: "", b5: "bc", b6: "ab", u: "ß"}`,
+ }, {
+ desc: "list types",
+ in: `
+ l0: 3*[int]
+ l0: [1, 2, 3]
+ l2: [...{ a: int }]
+ l2: [{a: 1}, {a: 2, b: 3}]
+
+ // TODO: work out a decent way to specify length ranges of lists.
+ // l3: <=10*[int]
+ // l3: [1, 2, 3, ...]
+
+ s1: (6*[int])[2:3]
+ s2: [0,2,3][1:2]
+
+ i1: (6*[int])[2]
+ i2: [0,2,3][2]
+
+ t0: [...{a: 8}]
+ t0: [{}]
+ t1: [...]
+ t1: [...int]
+
+ e0: 2*[{}]
+ e0: [{}]
+ e1: [...int]
+ e1: [...float]
+ `,
+ out: `<0>{` +
+ `l0: [1,2,3], ` +
+ `l2: [<1>{a: 1},<2>{a: 2, b: 3}], ` +
+ `s1: [int], ` +
+ `s2: [2], ` +
+ `i1: int, ` +
+ `i2: 3, ` +
+ `t0: [<3>{a: 8}], ` +
+ `t1: [, ...int], ` +
+ `e0: _|_(([<4>{},<4>{}] & [<5>{}]):conflicting list lengths: conflicting values 2 and 1), ` +
+ `e1: [, ..._|_((int & float):conflicting values int and float (mismatched types int and float))]` +
+ `}`,
+ }, {
+ // TODO: consider removing list arithmetic altogether. It is no longer
+ // needed to indicate the allowed capacity of a list and that didn't
+ // work anyway.
+ desc: "list arithmetic",
+ in: `
+ l0: 3*[1, 2, 3]
+ l1: 0*[1, 2, 3]
+ l2: 10*[]
+ l3: <=2*[]
+ l4: <=2*[int]
+ l5: <=2*(int*[int])
+ l6: 3*[...int]
+ l7: 3*[1, ...int]
+ l8: 3*[1, 2, ...int]
+
+ s0: [] + []
+ s1: [1] + []
+ s2: [] + [2]
+ s3: [1] + [2]
+ s4: [1,2] + []
+ s5: [] + [1,2]
+ s6: [1] + [1,2]
+ s7: [1,2] + [1]
+ s8: [1,2] + [1,2]
+ s9: [] + [...]
+ s10: [1] + [...]
+ s11: [] + [2, ...]
+ s12: [1] + [2, ...]
+ s13: [1,2] + [...]
+ s14: [] + [1,2, ...]
+ s15: [1] + [1,2, ...]
+ s16: [1,2] + [1, ...]
+ s17: [1,2] + [1,2, ...]
+
+ s18: [...] + []
+ s19: [1, ...] + []
+ s20: [...] + [2]
+ s21: [1, ...] + [2]
+ s22: [1,2, ...] + []
+ s23: [...] + [1,2]
+ s24: [1, ...] + [1,2]
+ s25: [1,2, ...] + [1]
+ s26: [1,2, ...] + [1,2]
+ s27: [...] + [...]
+ s28: [1, ...] + [...]
+ s29: [...] + [2, ...]
+ s30: [1, ...] + [2, ...]
+ s31: [1,2, ...] + [...]
+ s32: [...] + [1,2, ...]
+ s33: [1, ...] + [1,2, ...]
+ s34: [1,2, ...] + [1, ...]
+ s35: [1,2, ...] + [1,2, ...]
+ `,
+ out: `<0>{l0: [1,2,3,1,2,3,1,2,3], ` +
+ `l1: [], ` +
+ `l2: [], ` +
+ `l3: (<=2 * []), ` +
+ `l4: (<=2 * [int]), ` +
+ `l5: (<=2 * (int * [int])), ` +
+ `l6: [], ` +
+ `l7: [1,1,1], ` +
+ `l8: [1,2,1,2,1,2], ` +
+
+ `s0: [], ` +
+ `s1: [1], ` +
+ `s2: [2], ` +
+ `s3: [1,2], ` +
+ `s4: [1,2], ` +
+ `s5: [1,2], ` +
+ `s6: [1,1,2], ` +
+ `s7: [1,2,1], ` +
+ `s8: [1,2,1,2], ` +
+ `s9: [], ` +
+ `s10: [1], ` +
+ `s11: [2], ` +
+ `s12: [1,2], ` +
+ `s13: [1,2], ` +
+ `s14: [1,2], ` +
+ `s15: [1,1,2], ` +
+ `s16: [1,2,1], ` +
+ `s17: [1,2,1,2], ` +
+
+ `s18: [], ` +
+ `s19: [1], ` +
+ `s20: [2], ` +
+ `s21: [1,2], ` +
+ `s22: [1,2], ` +
+ `s23: [1,2], ` +
+ `s24: [1,1,2], ` +
+ `s25: [1,2,1], ` +
+ `s26: [1,2,1,2], ` +
+ `s27: [], ` +
+ `s28: [1], ` +
+ `s29: [2], ` +
+ `s30: [1,2], ` +
+ `s31: [1,2], ` +
+ `s32: [1,2], ` +
+ `s33: [1,1,2], ` +
+ `s34: [1,2,1], ` +
+ `s35: [1,2,1,2]` +
+
+ `}`,
+ }, {
+ desc: "list equality",
+ in: `
+ eq0: [] == []
+ eq1: [...] == []
+ eq2: [] == [...]
+ eq3: [...] == [...]
+
+ eq4: [1] == [1]
+ eq5: [1, ...] == [1]
+ eq6: [1] == [1, ...]
+ eq7: [1, ...] == [1, ...]
+
+ eq8: [1, 2] == [1, 2]
+ eq9: [1, 2, ...] == [1, 2]
+ eq10: [1, 2] == [1, 2, ...]
+ eq11: [1, 2, ...] == [1, 2, ...]
+
+ ne0: [] != []
+ ne1: [...] != []
+ ne2: [] != [...]
+ ne3: [...] != [...]
+
+ ne4: [1] != [1]
+ ne5: [1, ...] != [1]
+ ne6: [1] != [1, ...]
+ ne7: [1, ...] != [1, ...]
+
+ ne8: [1, 2] != [1, 2]
+ ne9: [1, 2, ...] != [1, 2]
+ ne10: [1, 2] != [1, 2, ...]
+ ne11: [1, 2, ...] != [1, 2, ...]
+
+ feq0: [] == [1]
+ feq1: [...] == [1]
+ feq2: [] == [1, ...]
+ feq3: [...] == [1, ...]
+
+ feq4: [1] == []
+ feq5: [1, ...] == []
+ feq6: [1] == [...]
+ feq7: [1, ...] == [...]
+
+ feq8: [1, 2] == [1]
+ feq9: [1, ...] == [1, 2]
+ feq10: [1, 2] == [1, ...]
+ feq11: [1, ...] == [1, 2, ...]
+
+ fne0: [] != [1]
+ fne1: [...] != [1]
+ fne2: [] != [1, ...]
+ fne3: [1, ...] != [1, ...]
+
+ fne4: [1] != []
+ fne5: [1, ...] != []
+ fne6: [1] != [...]
+ fne7: [1, ...] != [...]
+
+ fne8: [1, 2] != [1]
+ fne9: [1, ...] != [1, 2]
+ fne10: [1, 2] != [1, ...]
+ fne11: [1, ...] != [1, 2, ...]
+ `,
+ out: `<0>{` +
+ `eq0: true, eq1: true, eq2: true, eq3: true, eq4: true, eq5: true, eq6: true, eq7: true, eq8: true, eq9: true, eq10: true, eq11: true, ` +
+ `ne0: true, ne1: true, ne2: true, ne3: true, ne4: false, ne5: false, ne6: false, ne7: false, ne8: false, ne9: false, ne10: false, ne11: false, ` +
+ `feq0: false, feq1: false, feq2: false, feq3: false, feq4: false, feq5: false, feq6: false, feq7: false, feq8: false, feq9: false, feq10: false, feq11: false, ` +
+ `fne0: false, fne1: false, fne2: false, fne3: false, fne4: false, fne5: false, fne6: false, fne7: false, fne8: false, fne9: false, fne10: false, fne11: false}`,
+ }, {
+ desc: "list unification",
+ in: `
+ a: { l: ["foo", v], v: l[1] }
+ b: a & { l: [_, "bar"] }
+ `,
+ out: `<0>{` +
+ `a: <1>{l: ["foo",<2>.v], ` +
+ `v: <2>.l[1]}, ` +
+ `b: <3>{l: ["foo","bar"], v: "bar"}}`,
+ }, {
+ desc: "correct error messages",
+ // Tests that it is okay to partially evaluate structs.
+ in: `
+ a: "a" & 1
+ `,
+ out: `<0>{a: _|_(("a" & 1):conflicting values "a" and 1 (mismatched types string and int))}`,
+ }, {
+ desc: "structs",
+ in: `
+ a: t & { c: 5 } // {c:5,d:15}
+ b: ti & { c: 7 } // {c:7,d:21}
+ t: { c: number, d: c * 3 } // {c:number,d:number*3}
+ ti: t & { c: int }
+ `,
+ out: `<0>{a: <1>{c: 5, d: 15}, t: <2>{c: number, d: (<3>.c * 3)}, b: <4>{c: 7, d: 21}, ti: <5>{c: int, d: (<6>.c * 3)}}`,
+ }, {
+ desc: "definitions",
+ in: `
+ #Foo: {
+ field: int
+ recursive: {
+ field: string
+ }
+ }
+
+ // Allowed
+ #Foo1: { field: int }
+ #Foo1: { field2: string }
+
+ foo: #Foo
+ foo: { feild: 2 }
+
+ foo1: #Foo
+ foo1: {
+ field: 2
+ recursive: {
+ feild: 2 // Not caught as per spec. TODO: change?
+ }
+ }
+
+ #Bar: {
+ field: int
+ {[A=_]: int}
+ }
+ bar: #Bar
+ bar: { feild: 2 }
+
+ #Mixed: string
+ Mixed: string
+
+ mixedRec: { #Mixed: string }
+ mixedRec: { Mixed: string }
+ `,
+ out: `<0>{` +
+ `#Foo: <1>C{field: int, recursive: <2>C{field: string}}, ` +
+ `#Foo1: <3>C{field: int, field2: string}, ` +
+ `foo: _|_(2:field "feild" not allowed in closed struct), ` +
+ `foo1: <4>C{field: 2, recursive: _|_(2:field "feild" not allowed in closed struct)}, ` +
+ `#Bar: <5>{[]: <6>(A: string)->int, field: int}, ` +
+ `bar: <7>{[]: <8>(A: string)->int, field: int, feild: 2}, ` +
+ `#Mixed: string, ` +
+ `Mixed: string, ` +
+ `mixedRec: <9>{#Mixed: string, Mixed: string}}`,
+ }, {
+ desc: "combined definitions",
+ in: `
+ // Allow combining of structs within a definition
+ #D1: {
+ env: a: "A"
+ env: b: "B"
+ #def: {a: "A"}
+ #def: {b: "B"}
+ }
+
+ d1: #D1 & { env: c: "C" }
+
+ #D2: {
+ a: int
+ }
+ #D2: {
+ b: int
+ }
+
+ #D3: {
+ env: a: "A"
+ }
+ #D3: {
+ env: b: "B"
+ }
+
+ #D4: {
+ env: #DC
+ env: b: int
+ }
+
+ #DC: { a: int }
+ `,
+ out: `<0>{` +
+ `#D1: <1>C{env: <2>C{a: "A", b: "B"}, #def: <3>C{a: "A", b: "B"}}, ` +
+ `d1: <4>C{env: _|_("C":field "c" not allowed in closed struct), #def: <5>C{a: "A", b: "B"}}, ` +
+ `#D2: <6>C{a: int, b: int}, ` +
+ `#D3: <7>C{env: <8>C{a: "A", b: "B"}}, ` +
+ `#D4: <9>C{env: _|_(int:field "b" not allowed in closed struct)}, ` +
+ `#DC: <10>C{a: int}` +
+ `}`,
+ }, {
+ desc: "new-style definitions",
+ in: `
+ #Foo: {
+ a: 1
+ b: int
+ }
+ "#Foo": #Foo & {b: 1}
+
+ bulk: {[string]: string} & {
+ #def: 4 // Different namespace, so bulk option does not apply.
+ _hid: 3
+ a: "foo"
+ }
+ `,
+ out: `<0>{` +
+ `"#Foo": <1>C{a: 1, b: 1}, ` +
+ `#Foo: <2>C{a: 1, b: int}, ` +
+ `bulk: <3>{[]: <4>(_: string)->string, a: "foo", #def: 4, _hid: 3}` +
+ `}`,
+ }, {
+ desc: "recursive closing starting at non-definition",
+ in: `
+ z: a: {
+ #B: {
+ c: d: 1
+ c: f: 1
+ }
+ }
+ A: z & { a: { #B: { c: e: 2 } } }
+ `,
+ out: `<0>{z: <1>{a: <2>{#B: <3>C{c: <4>C{d: 1, f: 1}}}}, A: <5>{a: <6>{#B: <7>C{c: _|_(2:field "e" not allowed in closed struct)}}}}`,
+ }, {
+ desc: "non-closed definition carries over closedness to enclosed template",
+ in: `
+ #S: {
+ [string]: { a: int }
+ }
+ a: #S & {
+ v: { b: int }
+ }
+ #Q: {
+ [string]: { a: int } | { b: int }
+ }
+ b: #Q & {
+ w: { c: int }
+ }
+ #R: {
+ [string]: [{ a: int }, { b: int }]
+ }
+ c: #R & {
+ w: [{ d: int }, ...]
+ }
+ `,
+ out: `<0>{` +
+ `#S: <1>{[]: <2>(_: string)-><3>C{a: int}, }, ` +
+ `a: <4>{[]: <5>(_: string)-><6>C{a: int}, v: _|_(int:field "b" not allowed in closed struct)}, ` +
+ `b: <7>{[]: <8>(_: string)->(<9>C{a: int} | <10>C{b: int}), w: _|_(int:empty disjunction: field "c" not allowed in closed struct)}, ` +
+ `#Q: <11>{[]: <12>(_: string)->(<13>C{a: int} | <14>C{b: int}), }, ` +
+ `c: <15>{[]: <16>(_: string)->[<17>C{a: int},<18>C{b: int}], w: [_|_(int:field "d" not allowed in closed struct),<19>C{b: int}]}, ` +
+ `#R: <20>{[]: <21>(_: string)->[<22>C{a: int},<23>C{b: int}], }}`,
+ }, {
+ desc: "definitions with disjunctions",
+ in: `
+ #Foo: {
+ field: int
+
+ { a: 1 } |
+ { b: 2 }
+ }
+
+ foo: #Foo
+ foo: { a: 1 }
+
+ bar: #Foo
+ bar: { c: 2 }
+
+ baz: #Foo
+ baz: { b: 2 }
+ `,
+ out: `<0>{` +
+ `#Foo: (<1>C{field: int, a: 1} | <2>C{field: int, b: 2}), ` +
+ `foo: <3>C{field: int, a: 1}, ` +
+ `bar: _|_(2:empty disjunction: field "c" not allowed in closed struct), ` +
+ `baz: <4>C{field: int, b: 2}}`,
+ }, {
+ desc: "definitions with disjunctions recurisive",
+ in: `
+ #Foo: {
+ x: {
+ field: int
+
+ { a: 1 } |
+ { b: 2 }
+ }
+ x: c: 3
+ }
+ `,
+ out: `<0>{` +
+ `#Foo: <1>C{x: (<2>C{field: int, a: 1, c: 3} | <3>C{field: int, b: 2, c: 3})}` +
+ `}`,
+ }, {
+ desc: "definitions with embedding",
+ in: `
+ #E: {
+ a: { b: int }
+ }
+
+ #S: {
+ #E
+ a: { c: int }
+ b: 3
+ }
+
+ // adding a field to a nested struct that is closed.
+ #e1: #S & { a: d: 4 }
+ // literal struct not closed until after unification.
+ #v1: #S & { a: c: 4 }
+ `,
+ out: `<0>{` +
+ `#E: <1>C{a: <2>C{b: int}}, ` +
+ `#S: <3>C{a: <4>C{b: int, c: int}, b: 3}, ` +
+ `#e1: <5>C{a: _|_(4:field "d" not allowed in closed struct), b: 3}, ` +
+ `#v1: <6>C{a: <7>C{b: int, c: 4}, b: 3}}`,
+ }, {
+ desc: "top-level definition with struct and disjunction",
+ in: `
+ #def: {
+ Type: string
+ Text: string
+ Size: int
+ }
+
+ #def: {
+ Type: "B"
+ Size: 0
+ } | {
+ Type: "A"
+ Size: 1
+ }`,
+ out: `<0>{` +
+ `#def: (<1>C{Size: (0 & int), Type: ("B" & string), Text: string} | ` +
+ `<2>C{Size: (1 & int), Type: ("A" & string), Text: string})` +
+ `}`,
+ }, {
+ desc: "closing structs",
+ in: `
+ op: {x: int} // {x: int}
+ ot: {x: int, ...} // {x: int, ...}
+ cp: close({x: int}) // closed({x: int})
+ ct: close({x: int, ...}) // {x: int, ...}
+
+ opot: op & ot // {x: int, ...}
+ otop: ot & op // {x: int, ...}
+ opcp: op & cp // closed({x: int})
+ cpop: cp & op // closed({x: int})
+ opct: op & ct // {x: int, ...}
+ ctop: ct & op // {x: int, ...}
+ otcp: ot & cp // closed({x: int})
+ cpot: cp & ot // closed({x: int})
+ otct: ot & ct // {x: int, ...}
+ ctot: ct & ot // {x: int, ...}
+ cpct: cp & ct // closed({x: int})
+ ctcp: ct & cp // closed({x: int})
+ ctct: ct & ct // {x: int, ...}
+ `,
+ out: `<0>{` +
+ `op: <1>{x: int}, ` +
+ `ot: <2>{x: int, ...}, ` +
+ `cp: <3>C{x: int}, ` +
+ `ct: <4>{x: int, ...}, ` +
+ `opot: <5>{x: int, ...}, ` +
+ `otop: <6>{x: int, ...}, ` +
+ `opcp: <7>C{x: int}, ` +
+ `cpop: <8>C{x: int}, ` +
+ `opct: <9>{x: int, ...}, ` +
+ `ctop: <10>{x: int, ...}, ` +
+ `otcp: <11>C{x: int}, ` +
+ `cpot: <12>C{x: int}, ` +
+ `otct: <13>{x: int, ...}, ` +
+ `ctot: <14>{x: int, ...}, ` +
+ `cpct: <15>C{x: int}, ` +
+ `ctcp: <16>C{x: int}, ` +
+ `ctct: <17>{x: int, ...}}`,
+ }, {
+ desc: "excluded embedding from closing",
+ in: `
+ #S: {
+ a: { c: int }
+ {
+ c: { d: int }
+ }
+ B = { open: int }
+ b: B
+ }
+ V: #S & {
+ c: e: int
+ b: extra: int
+ }
+ `,
+ out: `<0>{` +
+ `#S: <1>C{` +
+ `a: <2>C{c: int}, ` +
+ `c: <3>{d: int}, ` +
+ `b: <4>{open: int}}, ` +
+ `V: <5>C{` +
+ `a: <6>C{c: int}, ` +
+ `c: <7>{d: int, e: int}, ` +
+ `b: <8>{open: int, extra: int}}}`,
+ }, {
+ desc: "closing with failed optional",
+ in: `
+ #k1: {a: int, b?: int} & #A // closed({a: int})
+ #k2: #A & {a: int, b?: int} // closed({a: int})
+
+ o1: {a?: 3} & {a?: 4} // {a?: _|_}
+
+ // Optional fields with error values can be elimintated when closing
+ #o2: {a?: 3} & {a?: 4} // close({})
+
+ #d1: {a?: 2, b: 4} | {a?: 3, c: 5}
+ v1: #d1 & {a?: 3, b: 4} // close({b: 4})
+
+ #A: {a: int}
+ `,
+ out: `<0>{` +
+ `#k1: <1>C{a: int}, ` +
+ `#A: <2>C{a: int}, ` +
+ `#k2: <3>C{a: int}, ` +
+ `o1: <4>{a?: _|_((3 & 4):conflicting values 3 and 4)}, ` +
+ `#o2: <5>C{a?: _|_((3 & 4):conflicting values 3 and 4)}, ` +
+ `#d1: (<6>C{a?: 2, b: 4} | <7>C{a?: 3, c: 5}), ` +
+ `v1: <8>C{a?: _|_((2 & 3):conflicting values 2 and 3), b: 4}` +
+ `}`,
+ }, {
+ desc: "closing with comprehensions",
+ in: `
+ #A: {f1: int, f2: int}
+
+ for k, v in {f3 : int} {
+ a: #A & { "\(k)": v }
+ }
+
+ #B: {
+ for k, v in {f1: int} {
+ "\(k)": v
+ }
+ }
+
+ #C: {
+ f1: _
+ for k, v in {f1: int} {
+ "\(k)": v
+ }
+ }
+
+ #D: {
+ for k, v in {f1: int} {
+ "\(k)": v
+ }
+ ...
+ }
+
+ #E: #A & {
+ for k, v in { f3: int } {
+ "\(k)": v
+ }
+ }
+ `,
+ out: `<0>{` +
+ `#A: <1>C{f1: int, f2: int}, ` +
+ `a: _|_(<2>.v:field "f3" not allowed in closed struct), ` +
+ `#B: <3>C{f1: int}, ` +
+ `#C: <4>C{f1: int}, ` +
+ `#D: <5>{f1: int, ...}, ` +
+ `#E: _|_(<6>.v:field "f3" not allowed in closed struct)` +
+ `}`,
+ }, {
+ desc: "incomplete comprehensions",
+ in: `
+ A: {
+ for v in src {
+ "\(v)": v
+ }
+ src: _
+ if true {
+ baz: "baz"
+ }
+ }
+ B: A & {
+ src: ["foo", "bar"]
+ }
+ `,
+ out: `<0>{` +
+ `A: <1>{src: _, baz: "baz" <2>for _, v in <3>.src yield <4>{""+<2>.v+"": <2>.v}}, ` +
+ `B: <5>{src: ["foo","bar"], baz: "baz", foo: "foo", bar: "bar"}}`,
+ }, {
+ desc: "reference to root",
+ in: `
+ a: { b: int }
+ c: a & {
+ b: 100
+ d: a.b + 3 // do not resolve as c != a.
+ }
+ x: {
+ b: int
+ c: b + 5
+ }
+ y: x & {
+ b: 100
+ // c should resolve to 105
+ }
+ v: {
+ b: int
+ c: v.b + 5 // reference starting from copied node.
+ }
+ w: v & { b: 100 }
+ wp: v & { b: 100 }
+ `,
+ out: `<0>{x: <1>{b: int, c: (<2>.b + 5)}, y: <3>{b: 100, c: 105}, a: <4>{b: int}, c: <5>{b: 100, d: (<6>.a.b + 3)}, v: <7>{b: int, c: (<6>.v.b + 5)}, w: <8>{b: 100, c: (<6>.v.b + 5)}, wp: <9>{b: 100, c: (<6>.v.b + 5)}}`,
+ // TODO(#152): should be
+ // out: `<0>{a: <1>{b: int}, c: <2>{b: 100, d: (<3>.a.b + 3)}, x: <4>{b: int, c: (<5>.b + 5)}, y: <6>{b: 100, c: 105}, v: <7>{b: int, c: (<8>.b + 5)}, w: <9>{b: 100, c: 105}, wp: <10>{b: 100, c: 105}}`,
+ }, {
+ desc: "references from template to concrete",
+ in: `
+ res: [t]
+ t: [X=string]: {
+ a: c + b.str
+ b: str: string
+ c: "X"
+ }
+ t: x: { b: str: "DDDD" }
+ `,
+ out: `<0>{res: [<1>{[]: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <5>{a: "XDDDD", c: "X", b: <6>{str: "DDDD"}}}], t: <7>{[]: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <8>{a: "XDDDD", c: "X", b: <9>{str: "DDDD"}}}}`,
+ }, {
+ desc: "interpolation",
+ in: `
+ a: "\(4)"
+ b: "one \(a) two \( a + c )"
+ c: "one"
+ d: "\(r)"
+ u: "\(_)"
+ r: _
+ e: "\([])"`,
+ out: `<0>{a: "4", b: "one 4 two 4one", c: "one", d: ""+<1>.r+"", r: _, u: ""+_+"", e: _|_([]:expression in interpolation must evaluate to a number kind or string (found list))}`,
+ }, {
+ desc: "multiline interpolation",
+ in: `
+ a1: """
+ before
+ \(4)
+ after
+ """
+ a2: """
+ before
+ \(4)
+
+ """
+ a3: """
+
+ \(4)
+ after
+ """
+ a4: """
+
+ \(4)
+
+ """
+ m1: """
+ before
+ \(
+ 4)
+ after
+ """
+ m2: """
+ before
+ \(
+ 4)
+
+ """
+ m3: """
+
+ \(
+
+ 4)
+ after
+ """
+ m4: """
+
+ \(
+ 4)
+
+ """
+ `,
+ out: `<0>{` +
+ `a1: "before\n4\nafter", a2: "before\n4\n", a3: "\n4\nafter", a4: "\n4\n", ` +
+ `m1: "before\n4\nafter", m2: "before\n4\n", m3: "\n4\nafter", m4: "\n4\n"` +
+ `}`,
+ }, {
+ desc: "diamond-shaped constraints",
+ in: `
+ S: {
+ A: {
+ a: 1,
+ },
+ B: A & {
+ b: 2,
+ }
+ },
+ T: S & { // S == { A: { a:1 }, B: { a:1, b:2 } }
+ A: {
+ c: 3,
+ },
+ B: { // S.B & A
+ d: 4, // Combines constraints S.A, S.B, T.A, and T.B
+ }
+ }`,
+ out: "<0>{T: <1>{A: <2>{a: 1, c: 3}, B: <3>{a: 1, b: 2, c: 3, d: 4}}, S: <4>{A: <5>{a: 1}, B: <6>{a: 1, b: 2}}}",
+ }, {
+ desc: "field templates",
+ in: `
+ a: {
+ {[name=_]: int}
+ k: 1
+ }
+ b: {
+ {[X=_]: { x: 0, y: *1 | int }}
+ v: {}
+ w: { x: 0 }
+ }
+ b: { [y=_]: {} }
+ c: {
+ {[Name=_]: { name: Name, y: 1 }}
+ foo: {}
+ bar: _
+ }
+ `,
+ out: `<0>{a: <1>{[]: <2>(name: string)->int, k: 1}, b: <3>{[]: <4>(X: string)->(<5>{x: 0, y: (*1 | int)} & <6>{}), v: <7>{x: 0, y: (*1 | int)}, w: <8>{x: 0, y: (*1 | int)}}, c: <9>{[]: <10>(Name: string)-><11>{y: 1, name: <10>.Name}, foo: <12>{y: 1, name: "foo"}, bar: <13>{y: 1, name: "bar"}}}`,
+ }, {
+ desc: "range unification",
+ in: `
+ // with concrete values
+ a1: >=1 & <=5 & 3
+ a2: >=1 & <=5 & 1
+ a3: >=1 & <=5 & 5
+ a4: >=1 & <=5 & 6
+ a5: >=1 & <=5 & 0
+
+ a6: 3 & >=1 & <=5
+ a7: 1 & >=1 & <=5
+ a8: 5 & >=1 & <=5
+ a9: 6 & >=1 & <=5
+ a10: 0 & >=1 & <=5
+
+ // with ranges
+ b1: >=1 & <=5 & >=1 & <=5
+ b2: >=1 & <=5 & >=1 & <=1
+ b3: >=1 & <=5 & >=5 & <=5
+ b4: >=1 & <=5 & >=2 & <=3
+ b5: >=1 & <=5 & >=3 & <=9
+ b6: >=1 & <=5 & >=5 & <=9
+ b7: >=1 & <=5 & >=6 & <=9
+
+ b8: >=1 & <=5 & >=1 & <=5
+ b9: >=1 & <=1 & >=1 & <=5
+ b10: >=5 & <=5 & >=1 & <=5
+ b11: >=2 & <=3 & >=1 & <=5
+ b12: >=3 & <=9 & >=1 & <=5
+ b13: >=5 & <=9 & >=1 & <=5
+ b14: >=6 & <=9 & >=1 & <=5
+
+ // ranges with more general types
+ c1: int & >=1 & <=5
+ c2: >=1 & <=5 & int
+ c3: string & >=1 & <=5
+ c4: >=1 & <=5 & string
+
+ // other types
+ s1: >="d" & <="z" & "e"
+ s2: >="d" & <="z" & "ee"
+
+ n1: number & >=1 & <=2
+ n2: int & >=1.1 & <=1.3
+ n3: >=1.0 & <=3.0 & 2
+ n4: >=0.0 & <=0.1 & 0.09999
+ n5: >=1 & <=5 & 2.5
+ `,
+ out: `<0>{` +
+ `a1: 3, ` +
+ `a2: 1, ` +
+ `a3: 5, ` +
+ `a4: _|_((<=5 & 6):invalid value 6 (out of bound <=5)), ` +
+ `a5: _|_((>=1 & 0):invalid value 0 (out of bound >=1)), ` +
+ `a6: 3, ` +
+ `a7: 1, ` +
+ `a8: 5, ` +
+
+ `a9: _|_((<=5 & 6):invalid value 6 (out of bound <=5)), ` +
+ `a10: _|_((>=1 & 0):invalid value 0 (out of bound >=1)), ` +
+
+ `b1: (>=1 & <=5), ` +
+ `b2: 1, ` +
+ `b3: 5, ` +
+ `b4: (>=2 & <=3), ` +
+ `b5: (>=3 & <=5), ` +
+ `b6: 5, ` +
+ `b7: _|_(conflicting bounds >=6 and <=5), ` +
+ `b8: (>=1 & <=5), ` +
+ `b9: 1, ` +
+ `b10: 5, ` +
+ `b11: (>=2 & <=3), ` +
+ `b12: (>=3 & <=5), ` +
+ `b13: 5, ` +
+ `b14: _|_(conflicting bounds >=6 and <=5), ` +
+ `c1: (int & >=1 & <=5), ` +
+ `c2: (<=5 & int & >=1), ` +
+ `c3: _|_((string & >=1):conflicting values string and >=1 (mismatched types string and number)), ` +
+ `c4: _|_(((>=1 & <=5) & string):conflicting values (>=1 & <=5) and string (mismatched types number and string)), ` +
+ `s1: "e", ` +
+ `s2: "ee", ` +
+ `n1: (>=1 & <=2), ` +
+ `n2: _|_(conflicting bounds int & >=1.1 and <=1.3), ` +
+ `n3: 2, ` +
+ `n4: 0.09999, ` +
+ `n5: 2.5}`,
+ }, {
+ desc: "predefined ranges",
+ in: `
+ k1: int8
+ k1: 44
+
+ k2: int64
+ k2: -8_000_000_000
+
+ e1: int16
+ e1: 100_000
+ `,
+ out: `<0>{k1: 44, k2: -8000000000, ` +
+ `e1: _|_((int & <=32767 & 100000):invalid value 100000 (out of bound int & <=32767))}`,
+ }, {
+ desc: "struct comprehensions",
+ in: `
+ obj: foo: a: "bar"
+ obj: [Name=string]: {
+ a: *"dummy" | string
+ if true {
+ sub: as: a
+ }
+ }
+
+ for k, v in { #def: 1, opt?: 2, _hid: 3, reg: 4 } {
+ "\(k)": v
+ }
+ `,
+ out: `<0>{obj: <1>{[]: <2>(Name: string)-><3>{a: (*"dummy" | string) if true yield <4>{sub: <5>{as: <3>.a}}}, foo: <6>{a: "bar", sub: <7>{as: "bar"}}}, reg: 4}`,
+ }, {
+ desc: "builtins",
+ in: `
+ a1: {
+ a: and([b, c])
+ b: =~"oo"
+ c: =~"fo"
+ }
+ a2: a1 & { a: "foo" }
+ a3: a1 & { a: "bar" }
+
+ o1: {
+ a: or([b, c])
+ b: string
+ c: "bar"
+ }
+ o2: o1 & { a: "foo" }
+ o3: o1 & { a: "foo", b: "baz" }
+ `,
+ out: `<0>{` +
+ `a1: <1>{a: (=~"oo" & =~"fo"), b: =~"oo", c: =~"fo"}, ` +
+ `a2: <2>{a: "foo", b: =~"oo", c: =~"fo"}, ` +
+ `a3: <3>{a: _|_((=~"oo" & "bar"):invalid value "bar" (does not match =~"oo")), b: =~"oo", c: =~"fo"}, ` +
+ `o1: <4>{a: string, b: string, c: "bar"}, ` +
+ `o2: <5>{a: "foo", b: string, c: "bar"}, ` +
+ `o3: <6>{a: _|_(("baz" & "foo"):empty disjunction: conflicting values "baz" and "foo";("bar" & "foo"):empty disjunction: conflicting values "bar" and "foo"), b: "baz", c: "bar"}}`,
+ }, {
+ desc: "self-reference cycles conflicts with strings",
+ in: `
+ a: {
+ x: y+"?"
+ y: x+"!"
+ }
+ a: x: "hey"
+ `,
+ out: `<0>{a: <1>{x: _|_(("hey!?" & "hey"):conflicting values "hey!?" and "hey"), y: "hey!"}}`,
+ }, {
+ desc: "resolved self-reference cycles with disjunctions",
+ in: `
+ a: b&{x:1} | {y:1} // {x:1,y:3,z:2} | {y:1}
+ b: {x:2} | c&{z:2} // {x:2} | {x:1,y:3,z:2}
+ c: a&{y:3} | {z:3} // {x:1,y:3,z:2} | {z:3}
+ `,
+ out: `<0>{a: (<1>{x: 1, y: 3, z: 2} | <2>{y: 1}), b: (<3>{x: 2} | <4>{x: 1, y: 3, z: 2}), c: (<5>{x: 1, y: 3, z: 2} | <6>{z: 3})}`,
+ }, {
+ // We take a very conservative stance on delaying arithmetic
+ // expressions within disjunctions. It should remain resolvable, though,
+ // once the user specifies one.
+ desc: "resolved self-reference cycles with disjunction",
+ in: `
+ // The second disjunct in xa1 is not resolvable and can be
+ // eliminated:
+ // xa4 & 9
+ // (xa2 + 2) & 9
+ // ((xa3 + 2) + 2) & 9
+ // (((6 & xa1-2) + 2) + 2) & 9
+ // ((6 + 2) + 2) & 9 // 6 == xa1-2
+ // 10 & 9 => _|_
+ // The remaining values resolve.
+ xa1: (xa2 & 8) | (xa4 & 9)
+ xa2: xa3 + 2
+ xa3: 6 & xa1-2
+ xa4: xa2 + 2
+
+ // The second disjunct in xb4 can be eliminated as both disjuncts
+ // of xb3 result in an incompatible sum when substituted.
+ xb1: (xb2 & 8) | (xb4 & 9)
+ xb2: xb3 + 2
+ xb3: (6 & (xb1-2)) | (xb4 & 9)
+ xb4: xb2 + 2
+
+ // Another variant with more disjunctions. xc1 remains with two
+ // possibilities. Technically, only the first value is valid.
+ // However, to fully determine that, all options of the remaining
+ // disjunction will have to be evaluated algebraically, which is
+ // not done.
+ xc1: xc2 & 8 | xc4 & 9 | xc5 & 9
+ xc2: xc3 + 2
+ xc3: 6 & xc1-2
+ xc4: xc2 + 1
+ xc5: xc2 + 2
+
+ // The above is resolved by setting xd1 explicitly.
+ xd1: xd2 & 8 | xd4 & 9 | xd5 & 9
+ xd2: xd3 + 2
+ xd3: 6 & xd1-2
+ xd4: xd2 + 1
+ xd5: xd2 + 2
+ xd1: 8
+
+ // The above is resolved by setting xd1 explicitly to the wrong
+ // value, resulting in an error.
+ xe1: xe2 & 8 | xe4 & 9 | xe5 & 9
+ xe2: xe3 + 2
+ xe3: 6 & xe1-2
+ xe4: xe2 + 1
+ xe5: xe2 + 2
+ xe1: 9
+
+ // Only one solution.
+ xf1: xf2 & 8 | xf4 & 9
+ xf2: xf3 + 2
+ xf3: 6 & xf1-2 | xf4 & 9
+ xf4: xf2 + 2
+
+ z1: z2 + 1 | z3 + 5
+ z2: z3 + 2
+ z3: z1 - 3
+ z3: 8
+ `,
+ out: `<0>{` +
+ `xa1: 8, ` +
+ `xa2: 8, ` +
+ `xa4: 10, ` +
+ `xa3: 6, ` +
+
+ `xb1: 8, ` +
+ `xb2: 8, ` +
+ `xb4: 10, ` +
+ `xb3: 6, ` +
+
+ `xc1: ((<1>.xc2 & 8) | (<1>.xc4 & 9) | (<1>.xc5 & 9)), ` +
+ `xc2: (<1>.xc3 + 2), ` +
+ `xc4: (<1>.xc2 + 1), ` +
+ `xc5: (<1>.xc2 + 2), ` +
+ `xc3: (6 & (<1>.xc1 - 2)), ` +
+
+ `xd1: 8, ` +
+ `xd2: 8, ` +
+ `xd4: 9, ` +
+ `xd5: 10, ` +
+ `xd3: 6, ` +
+
+ `xe1: _|_((6 & 7):conflicting values 6 and 7), ` +
+ `xe2: _|_((6 & 7):conflicting values 6 and 7), ` +
+ `xe4: _|_((6 & 7):conflicting values 6 and 7), ` +
+ `xe5: _|_((6 & 7):conflicting values 6 and 7), ` +
+ `xe3: _|_((6 & 7):conflicting values 6 and 7), ` +
+
+ `xf1: 8, ` +
+ `xf2: 8, ` +
+ `xf4: 10, ` +
+ `xf3: 6, ` +
+
+ `z1: ((<1>.z2 + 1) | (<1>.z3 + 5)), ` +
+ `z2: (<1>.z3 + 2), ` +
+ `z3: ((<1>.z1 - 3) & 8)}`,
+ }, {
+ // Defaults should not alter the result of the above disjunctions.
+ // The results may differ, but errors and resolution should be roughly
+ // the same.
+ desc: "resolved self-reference cycles with disjunction with defaults",
+ in: `
+ // The disjunction in xa could be resolved, but as disjunctions
+ // are not resolved for expression, it remains unresolved.
+ xa1: (xa2 & 8) | *(xa4 & 9)
+ xa2: xa3 + 2
+ xa3: 6 & xa1-2
+ xa4: xa2 + 2
+
+ // As xb3 is a disjunction, xb2 cannot be resolved and evaluating
+ // the cycle completely is broken. However, it is not an error
+ // as the user might still resolve the disjunction.
+ xb1: *(xb2 & 8) | (xb4 & 9)
+ xb2: xb3 + 2
+ xb3: *(6 & (xb1-2)) | (xb4 & 9)
+ xb4: xb2 + 2
+
+ // Another variant with more disjunctions. xc1 remains with two
+ // possibilities. Technically, only the first value is valid.
+ // However, to fully determine that, all options of the remaining
+ // disjunction will have to be evaluated algebraically, which is
+ // not done.
+ xc1: *(xc2 & 8) | (xc4 & 9) | (xc5 & 9)
+ xc2: xc3 + 2
+ xc3: 6 & xc1-2
+ xc4: xc2 + 1
+ xc5: xc2 + 2
+
+ // The above is resolved by setting xd1 explicitly.
+ xd1: *(xd2 & 8) | xd4 & 9 | xd5 & 9
+ xd2: xd3 + 2
+ xd3: 6 & xd1-2
+ xd4: xd2 + 1
+ xd5: xd2 + 2
+
+ // The above is resolved by setting xd1 explicitly to the wrong
+ // value, resulting in an error.
+ xe1: *(xe2 & 8) | xe4 & 9 | xe5 & 9
+ xe2: xe3 + 2
+ xe3: 6 & xe1-2
+ xe4: xe2 + 1
+ xe5: xe2 + 2
+ xe1: 9
+
+ z1: *(z2 + 1) | z3 + 5
+ z2: z3 + 2
+ z3: z1 - 3
+ z3: 8
+ `,
+ out: `<0>{` +
+ `xa1: 8, ` +
+ `xa2: 8, ` +
+ `xa4: 10, ` +
+ `xa3: 6, ` +
+
+ `xb1: 8, ` +
+ `xb2: 8, ` +
+ `xb4: 10, ` +
+ `xb3: 6, ` +
+
+ `xc1: (*8 | 9), ` + // not resolved because we use evalPartial
+ `xc2: 8, ` +
+ `xc4: 9, ` +
+ `xc5: 10, ` +
+ `xc3: 6, ` +
+
+ `xd1: (*8 | 9), ` + // TODO: eliminate 9?
+ `xd2: 8, ` +
+ `xd4: 9, ` +
+ `xd5: 10, ` +
+ `xd3: 6, ` +
+
+ `xe1: _|_((6 & 7):conflicting values 6 and 7), ` +
+ `xe2: _|_((6 & 7):conflicting values 6 and 7), ` +
+ `xe4: _|_((6 & 7):conflicting values 6 and 7), ` +
+ `xe5: _|_((6 & 7):conflicting values 6 and 7), ` +
+ `xe3: _|_((6 & 7):conflicting values 6 and 7), ` +
+
+ `z1: (*11 | 13), ` + // 13 is eliminated with evalFull
+ `z2: 10, ` +
+ `z3: 8}`,
+ }}
+ rewriteHelper(t, testCases, evalPartial)
+}
+
+func TestFullEval(t *testing.T) {
+ testCases := []testCase{{
+ desc: "detect conflicting value",
+ in: `
+ a: 8000.9
+ a: 7080 | int`,
+ out: `<0>{a: _|_((8000.9 & (int | int)):conflicting values 8000.9 and int (mismatched types float and int))}`, // TODO: fix repetition
+ }, {
+ desc: "conflicts in optional fields are okay ",
+ in: `
+ d: {a: 1, b?: 3} | {a: 2}
+
+ // the following conjunction should not eliminate any disjuncts
+ c: d & {b?:4}
+ `,
+ out: `<0>{d: (<1>{a: 1, b?: 3} | <2>{a: 2}), c: (<3>{a: 1, b?: (3 & 4)} | <4>{a: 2, b?: 4})}`,
+ }, {
+ desc: "resolve all disjunctions",
+ in: `
+ service: [Name=string]: {
+ name: string | *Name
+ port: int | *7080
+ }
+ service: foo: _
+ service: bar: { port: 8000 }
+ service: baz: { name: "foobar" }
+ `,
+ out: `<0>{service: <1>{[]: <2>(Name: string)-><3>{name: (string | *<2>.Name), port: (int | *7080)}, foo: <4>{name: "foo", port: 7080}, bar: <5>{name: "bar", port: 8000}, baz: <6>{name: "foobar", port: 7080}}}`,
+ }, {
+ desc: "field templates",
+ in: `
+ a: {
+ [name=_]: int
+ k: 1
+ }
+ b: {
+ [X=_]: { x: 0, y: *1 | int }
+ v: {}
+ w: { y: 0 }
+ }
+ b: { [y=_]: {} } // TODO: allow different name
+ c: {
+ [Name=_]: { name: Name, y: 1 }
+ foo: {}
+ bar: _
+ }
+ `,
+ out: `<0>{a: <1>{[]: <2>(name: string)->int, k: 1}, b: <3>{[]: <4>(X: string)->(<5>{x: 0, y: (*1 | int)} & <6>{}), v: <7>{x: 0, y: 1}, w: <8>{x: 0, y: 0}}, c: <9>{[]: <10>(Name: string)-><11>{y: 1, name: <10>.Name}, foo: <12>{y: 1, name: "foo"}, bar: <13>{y: 1, name: "bar"}}}`,
+ }, {
+ desc: "field comprehension",
+ in: `
+ a: {
+ for k, v in b
+ if k < "d"
+ if v > b.a {
+ "\(k)": v
+ }
+ }
+ b: {
+ a: 1
+ b: 2
+ c: 3
+ d: 4
+ }
+ c: {
+ for k, v in b
+ if k < "d"
+ if v > b.a {
+ "\(k)": v
+ }
+ }
+ `,
+ out: `<0>{a: <1>{b: 2, c: 3}, b: <2>{a: 1, b: 2, c: 3, d: 4}, c: <3>{b: 2, c: 3}}`,
+ }, {
+ desc: "conditional field",
+ in: `
+ if b {
+ a: "foo"
+ }
+ b: true
+ c: {
+ a: 3
+ if a > 1 {
+ a: 3
+ }
+ }
+ d: {
+ a: int
+ if a > 1 {
+ a: 3
+ }
+ }
+ `,
+ // NOTE: the node numbers are not correct here, but this is an artifact
+ // of the testing code.
+ out: `<0>{b: true, a: "foo", c: <1>{a: 3}, d: <2>{a: int if (<3>.a > 1) yield <4>{a: 3}}}`,
+ }, {
+ desc: "referencing field in field comprehension",
+ in: `
+ a: { b: c: 4 }
+ a: {
+ b: d: 5
+ for k, v in b {
+ "\(k)": v
+ }
+ }
+ `,
+ out: `<0>{a: <1>{b: <2>{c: 4, d: 5}, c: 4, d: 5}}`,
+ }, {
+ desc: "different labels for templates",
+ in: `
+ a: [X=string]: { name: X }
+ a: [Name=string]: { name: Name }
+ a: foo: {}
+ `,
+ out: `<0>{a: <1>{[]: <2>(X: string)->(<3>{name: <2>.X} & <4>{name: <2>.X}), foo: <5>{name: "foo"}}}`,
+ }, {
+ // TODO: rename EE and FF to E and F to check correct ordering.
+
+ desc: "nested templates in one field",
+ in: `
+ a: [A=string]: b: [B=string]: {
+ name: A
+ kind: B
+ }
+ a: "A": b: "B": _
+ a: "C": b: "D": _
+ a: "EE": b: "FF": { c: "bar" }
+ `,
+ out: `<0>{a: <1>{[]: <2>(A: string)-><3>{b: <4>{[]: <5>(B: string)-><6>{name: <2>.A, kind: <5>.B}, }}, ` +
+ `A: <7>{b: <8>{[]: <9>(B: string)-><10>{name: <11>.A, kind: <9>.B}, ` +
+ `B: <12>{name: "A", kind: "B"}}}, ` +
+ `C: <13>{b: <14>{[]: <15>(B: string)-><16>{name: <17>.A, kind: <15>.B}, ` +
+ `D: <18>{name: "C", kind: "D"}}}, ` +
+ `EE: <19>{b: <20>{[]: <21>(B: string)-><22>{name: <23>.A, kind: <21>.B}, ` +
+ `FF: <24>{name: "EE", kind: "FF", c: "bar"}}}}}`,
+ }, {
+ desc: "template unification within one struct",
+ in: `
+ a: {
+ [A=string]: { name: A }
+ // TODO: allow duplicate alias here
+ [X=string]: { kind: X }
+ }
+ a: "A": _
+ a: "C": _
+ a: "E": { c: "bar" }
+ `,
+ out: `<0>{a: <1>{[]: <2>(A: string)->(<3>{name: <2>.A} & <4>{kind: <2>.A}), ` +
+ `E: <5>{name: "E", kind: "E", c: "bar"}, ` +
+ `A: <6>{name: "A", kind: "A"}, ` +
+ `C: <7>{name: "C", kind: "C"}}}`,
+ }, {
+ desc: "field comprehensions with multiple keys",
+ in: `
+ for x in [
+ {a: "A", b: "B" },
+ {a: "C", b: "D" },
+ {a: "E", b: "F" },
+ ] {
+ a: "\(x.a)": b: "\(x.b)": x
+ }
+
+ for x in [
+ {a: "A", b: "B" },
+ {a: "C", b: "D" },
+ {a: "E", b: "F" },
+ ] {
+ "\(x.a)": "\(x.b)": x
+ }
+ `,
+ out: `<0>{E: <1>{F: <2>{a: "E", b: "F"}}, ` +
+ `a: <3>{` +
+ `E: <4>{b: <5>{F: <6>{a: "E", b: "F"}}}, ` +
+ `A: <7>{b: <8>{B: <9>{a: "A", b: "B"}}}, ` +
+ `C: <10>{b: <11>{D: <12>{a: "C", b: "D"}}}}, ` +
+ `A: <13>{B: <14>{a: "A", b: "B"}}, ` +
+ `C: <15>{D: <16>{a: "C", b: "D"}}}`,
+ // TODO: this order would be desirable.
+ // out: `<0>{a: <1>{` +
+ // `A: <2>{b: <3>{B: <4>{a: "A", b: "B"}}}, ` +
+ // `C: <5>{b: <6>{D: <7>{a: "C", b: "D"}}}, ` +
+ // `E: <8>{b: <9>{F: <10>{a: "E", b: "F"}}}}, ` +
+ // `A: <11>{B: <12>{a: "A", b: "B"}}, ` +
+ // `C: <13>{D: <14>{a: "C", b: "D"}}, ` +
+ // `E: <15>{F: <16>{a: "E", b: "F"}}}`,
+ }, {
+ desc: "field comprehensions with templates",
+ in: `
+ num: 1
+ a: {
+ if num < 5 {
+ [A=string]: [B=string]: {
+ name: A
+ kind: B
+ }
+ }
+ }
+ a: b: c: d: "bar"
+ `,
+ out: `<0>{num: 1, a: <1>{[]: <2>(A: string)-><3>{[]: <4>(B: string)-><5>{name: <2>.A, kind: <4>.B}, }, ` +
+ `b: <6>{[]: <7>(B: string)-><8>{name: <9>.A, kind: <7>.B}, ` +
+ `c: <10>{name: "b", kind: "c", ` +
+ `d: "bar"}}}}`,
+ }, {
+ desc: "disjunctions of lists",
+ in: `
+ l: *[ int, int ] | [ string, string ]
+
+ l1: [ "a", "b" ]
+ l2: l & [ "c", "d" ]
+ `,
+ out: `<0>{l: [int,int], l1: ["a","b"], l2: ["c","d"]}`,
+ }, {
+ desc: "normalization",
+ in: `
+ a: string | string
+ b: *1 | *int
+ c: *1.0 | *float
+ `,
+ out: `<0>{a: string, b: int, c: float}`,
+ }, {
+ desc: "default disambiguation and elimination",
+ in: `
+ a: *1 | int
+ b: *3 | int
+ c: a & b
+ d: b & a
+
+ e: *1 | *1
+ `,
+ out: `<0>{a: 1, b: 3, c: int, d: int, e: 1}`,
+ }, {
+ desc: "list comprehension",
+ in: `
+ a: [ for k, v in b if k < "d" if v > b.a { k }]
+ b: {
+ a: 1
+ b: 2
+ c: 3
+ d: 4
+ }
+ c: [ for _, x in b for _, y in b if x < y { x } ]
+ d: [ for x, _ in a { x } ]
+ `,
+ out: `<0>{a: ["b","c"], b: <1>{a: 1, b: 2, c: 3, d: 4}, c: [1,1,1,2,2,3], d: [0,1]}`,
+ }, {
+ desc: "struct comprehension with template",
+ in: `
+ result: [ for _, v in service { v } ]
+
+ service: [Name=string]: {
+ name: *Name | string
+ type: "service"
+ port: *7080 | int
+ }
+ service: foo: {}
+ service: bar: { port: 8000 }
+ service: baz: { name: "foobar" }
+ `,
+ out: `<0>{result: [` +
+ `<1>{name: "foo", type: "service", port: 7080},` +
+ `<2>{name: "bar", type: "service", port: 8000},` +
+ `<3>{name: "foobar", type: "service", port: 7080}], ` +
+
+ `service: <4>{` +
+ `[]: <5>(Name: string)-><6>{name: (*<5>.Name | string), type: "service", port: (*7080 | int)}, ` +
+ `foo: <7>{name: "foo", type: "service", port: 7080}, ` +
+ `bar: <8>{name: "bar", type: "service", port: 8000}, ` +
+ `baz: <9>{name: "foobar", type: "service", port: 7080}}}`,
+ }, {
+ desc: "resolutions in struct comprehension keys",
+ in: `
+ a: { for _, b in ["c"] { "\(b + ".")": "a" } }
+ `,
+ out: `<0>{a: <1>{"c.": "a"}}`,
+ }, {
+ desc: "recursive evaluation within list",
+ in: `
+ l: [a]
+ a: b & { c: "t" }
+ b: {
+ d: c
+ c: string
+ }
+ l1: [a1]
+ a1: b1 & { c: "t" }
+ b1: {
+ d: "s" + c
+ c: string
+ }
+ `,
+ out: `<0>{` +
+ `l: [<1>{c: "t", d: "t"}], ` +
+ `a: <2>{c: "t", d: "t"}, ` +
+ `b: <3>{c: string, d: string}, ` +
+ `l1: [<4>{c: "t", d: "st"}], ` +
+ `a1: <5>{c: "t", d: "st"}, ` +
+ `b1: <6>{c: string, d: ("s" + <7>.c)}}`,
+ }, {
+ desc: "ips",
+ in: `
+ IP: 4*[ uint8 ]
+
+ Private:
+ *[ 192, 168, uint8, uint8 ] |
+ [ 10, uint8, uint8, uint8] |
+ [ 172, >=16 & <=32, uint8, uint8 ]
+
+ Inst: Private & [ _, 10, ... ]
+
+ MyIP: Inst & [_, _, 10, 10 ]
+ `,
+ out: `<0>{` +
+ `IP: [(int & >=0 & int & <=255),(int & >=0 & int & <=255),(int & >=0 & int & <=255),(int & >=0 & int & <=255)], ` +
+ `Private: [192,168,(int & >=0 & int & <=255),(int & >=0 & int & <=255)], ` +
+ `Inst: [10,10,(int & >=0 & int & <=255),(int & >=0 & int & <=255)], ` +
+ `MyIP: [10,10,10,10]` +
+ `}`,
+ }, {
+ desc: "complex interaction of groundness",
+ in: `
+ res: [ for x in a for y in x { y & { d: "b" } }]
+ res: [ a.b.c & { d: "b" } ]
+
+ a: b: [C=string]: { d: string, s: "a" + d }
+ a: b: c: d: string
+ `,
+ // TODO(perf): unification should catch shared node.
+ out: `<0>{res: [<1>{d: "b", s: "ab"}], ` +
+ `a: <2>{b: <3>{[]: <4>(C: string)-><5>{d: string, s: ("a" + <5>.d)}, c: <6>{d: string, s: ("a" + <7>.d)}}}}`,
+ }, {
+ desc: "complex groundness 2",
+ in: `
+ r1: f1 & { y: "c" }
+
+ f1: { y: string, res: a.b.c & { d: y } }
+
+ a: b: c: { d: string, s: "a" + d }
+ a: b: [C=string]: { d: string, s: "a" + d }
+ a: b: c: d: string
+ `,
+ out: `<0>{r1: <1>{y: "c", res: <2>{d: "c", s: "ac"}}, f1: <3>{y: string, res: <4>{d: string, s: (("a" + <5>.d) & ("a" + <5>.d))}}, a: <6>{b: <7>{[]: <8>(C: string)-><9>{d: string, s: ("a" + <9>.d)}, c: <10>{d: string, s: (("a" + <11>.d) & ("a" + <11>.d))}}}}`,
+ }, {
+ desc: "references from template to concrete",
+ in: `
+ res: [t]
+ t: [X=string]: {
+ a: c + b.str
+ b: str: string
+ c: "X"
+ }
+ t: x: { b: str: "DDDD" }
+ `,
+ out: `<0>{res: [<1>{[]: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <5>{a: "XDDDD", c: "X", b: <6>{str: "DDDD"}}}], ` +
+ `t: <7>{[]: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <8>{a: "XDDDD", c: "X", b: <9>{str: "DDDD"}}}}`,
+ }, {
+ // TODO: A nice property for CUE to have would be that evaluation time
+ // is proportional to the number of output nodes (note that this is
+ // not the same as saying that the running time is O(n)).
+ // We should probably disallow shenanigans like the one below. But until
+ // this is allowed, it should at least be correct. At least we are not
+ // making reentrant coding easy.
+ desc: "reentrance",
+ in: `
+ // This indirection is needed to avoid binding references to fib
+ // within fib to the instantiated version.
+ fibRec: {nn: int, out: (fib & {n: nn}).out}
+ fib: {
+ n: int
+
+ if n >= 2 {
+ out: (fibRec & {nn: n - 2}).out + (fibRec & {nn: n - 1}).out
+ }
+ if n < 2 {
+ out: n
+ }
+ }
+ fib2: (fib & {n: 2}).out
+ fib7: (fib & {n: 7}).out
+ fib12: (fib & {n: 12}).out
+ `,
+ out: `<0>{` +
+ `fibRec: <1>{` +
+ `nn: int, ` +
+ `out: (<2>.fib & <3>{n: <4>.nn}).out}, ` +
+ // NOTE: the node numbers are not correct here, but this is an artifact
+ // of the testing code.
+ `fib: <5>{n: int if (<6>.n >= 2) yield <7>{out: ((<2>.fibRec & <8>{nn: (<6>.n - 2)}).out + (<2>.fibRec & <9>{nn: (<6>.n - 1)}).out)}, if (<6>.n < 2) yield <10>{out: <6>.n}}, ` +
+ `fib2: 1, ` +
+ `fib7: 13, ` +
+ `fib12: 144}`,
+ }, {
+ desc: "Issue #23",
+ in: `
+ x: {a:1}|{a:2}
+ y: x & {a:3}
+ `,
+ out: `<0>{x: (<1>{a: 1} | <2>{a: 2}), y: _|_((1 & 3):empty disjunction: conflicting values 1 and 3;(2 & 3):empty disjunction: conflicting values 2 and 3)}`,
+ }, {
+ desc: "cannot resolve references that would be ambiguous",
+ in: `
+ a1: *0 | 1
+ a1: a3 - a2
+ a2: *0 | 1
+ a2: a3 - a1
+ a3: 1
+
+ b1: (*0 | 1) & b2
+ b2: (0 | *1) & b1
+
+ c1: (*{a:1} | {b:1}) & c2
+ c2: (*{a:2} | {b:2}) & c1
+ `,
+ out: `<0>{` +
+ `a1: ((*0 | 1) & (<1>.a3 - <1>.a2)), ` +
+ `a3: 1, ` +
+ `a2: ((*0 | 1) & (<1>.a3 - <1>.a1)), ` +
+ `b1: (0 | 1), ` +
+ `b2: (0 | 1), ` +
+ `c1: (<2>{a: 1, b: 2} | <3>{a: 2, b: 1}), ` +
+ `c2: (<4>{a: 2, b: 1} | <5>{a: 1, b: 2})}`,
+ }, {
+ desc: "dont convert incomplete errors to non-incomplete",
+ in: `
+ import "strings"
+
+ n1: {min: <max, max: >min}
+ n2: -num
+ n3: +num
+ n4: num + num
+ n5: num - num
+ n6: num * num
+ n7: num / num
+
+ b1: !is
+
+ s1: "\(str)"
+ s2: strings.ContainsAny("dd")
+ s3: strings.ContainsAny(str, "dd")
+
+ str: string
+ num: <4
+ is: bool
+ `,
+ out: `<0>{` +
+ `n1: <1>{min: <<2>.max, max: ><2>.min}, ` +
+ `n2: -<3>.num, num: <4, ` +
+ `n3: +<3>.num, ` +
+ `n4: (<3>.num + <3>.num), ` +
+ `n5: (<3>.num - <3>.num), ` +
+ `n6: (<3>.num * <3>.num), ` +
+ `n7: (<3>.num / <3>.num), ` +
+ `b1: !<3>.is, ` +
+ `is: bool, ` +
+ `s1: ""+<3>.str+"", ` +
+ `str: string, ` +
+ `s2: strings.ContainsAny ("dd"), ` +
+ `s3: <4>.ContainsAny (<3>.str,"dd")}`,
+ }, {
+ desc: "len of incomplete types",
+ in: `
+ args: *[] | [...string]
+ v1: len(args)
+ v2: len([])
+ v3: len({})
+ v4: len({a: 3})
+ v5: len({a: 3} | {a: 4})
+ v6: len('sf' | 'dd')
+ v7: len([2] | *[1, 2])
+ v8: len([2] | [1, 2])
+ v9: len("😂")
+ v10: len("")
+ `,
+ out: `<0>{` +
+ `args: [], ` +
+ `v1: 0, ` +
+ `v2: 0, ` +
+ `v3: 0, ` +
+ `v4: 1, ` +
+ `v5: len ((<1>{a: 3} | <2>{a: 4})), ` +
+ `v6: len (('sf' | 'dd')), ` +
+ `v7: 2, ` +
+ `v8: len (([2] | [1,2])), ` +
+ `v9: 4, ` +
+ `v10: 0}`,
+ }, {
+ desc: "slice rewrite bug",
+ in: `
+ fn: {
+ arg: [...int] & [1]
+ out: arg[1:]
+ }
+ fn1: fn & {arg: [1]}
+ `,
+ out: `<0>{fn: <1>{arg: [1], out: []}, fn1: <2>{arg: [1], out: []}}`,
+ }, {
+ desc: "Issue #94",
+ in: `
+ foo: {
+ opt?: 1
+ "txt": 2
+ #def: 3
+ regular: 4
+ _hidden: 5
+ }
+ comp: { for k, v in foo { "\(k)": v } }
+ select: {
+ opt: foo.opt
+ "txt": foo.txt
+ #def: foo.#def
+ regular: foo.regular
+ _hidden: foo._hidden
+ }
+ index: {
+ opt: foo["opt"]
+ "txt": foo["txt"]
+ #def: foo["#def"]
+ regular: foo["regular"]
+ _hidden: foo["_hidden"]
+ }
+ `,
+ out: `<0>{` +
+ `foo: <1>{opt?: 1, txt: 2, #def: 3, regular: 4, _hidden: 5}, ` +
+ `comp: <2>{txt: 2, regular: 4}, ` +
+ `select: <3>{opt: <4>.foo.opt, txt: 2, #def: 3, regular: 4, _hidden: 5}, ` +
+ `index: <5>{opt: <4>.foo["opt"], txt: 2, #def: <4>.foo["#def"], regular: 4, _hidden: <4>.foo["_hidden"]}}`,
+ }, {
+ desc: "retain references with interleaved embedding",
+ in: `
+ a: d: {
+ #base
+ #info: {...}
+ Y: #info.X
+ }
+
+ #base: {
+ #info: {...}
+ }
+
+ a: [Name=string]: { #info: {
+ X: "foo"
+ }}
+ `,
+ out: `<0>{a: <1>{[]: <2>(Name: string)-><3>{#info: <4>C{X: "foo"}}, d: <5>C{#info: <6>C{X: "foo"}, Y: "foo"}}, #base: <7>C{#info: <8>{...}}}`,
+ }, {
+ desc: "comparison against bottom",
+ in: `
+ a: _|_ == _|_
+ b: err == 1&2 // not a literal error, so not allowed
+ c: err == _|_ // allowed
+ d: err != _|_ // allowed
+ e: err != 1&3
+ // z: err == err // TODO: should infer to be true?
+ f: ({a: 1} & {a: 2}) == _|_
+ g: ({a: 1} & {b: 2}) == _|_
+ h: _|_ == ({a: 1} & {a: 2})
+ i: _|_ == ({a: 1} & {b: 2})
+
+ err: 1 & 2
+ `,
+ out: `<0>{a: true, b: _|_((1 & 2):conflicting values 1 and 2), err: _|_((1 & 2):conflicting values 1 and 2), c: true, d: false, e: _|_((1 & 2):conflicting values 1 and 2), f: true, g: false, h: true, i: false}`,
+ }, {
+ desc: "or builtin should not fail on non-concrete empty list",
+ in: `
+ #Workflow: {
+ jobs: {
+ [jobID=string]: {
+ }
+ }
+ #JobID: or([ for k, _ in jobs { k } ])
+ }
+
+ foo: #Workflow & {
+ jobs: foo: {
+ }
+ }
+ `,
+ out: `<0>{#Workflow: <1>C{jobs: <2>{[]: <3>(jobID: string)-><4>C{}, }, #JobID: or ([ <5>for k, _ in <6>.jobs yield <5>.k ])}, foo: <7>C{jobs: <8>{[]: <9>(jobID: string)-><10>C{}, foo: <11>C{}}, #JobID: "foo"}}`,
+ }, {
+ desc: "Issue #153",
+ in: `
+ Foo: {
+ listOfCloseds: [...#Closed]
+ }
+
+ #Closed: {
+ a: int | *0
+ }
+
+ Junk: {
+ b: 2
+ }
+
+ Foo & {
+ listOfCloseds: [{
+ for k, v in Junk {
+ "\(k)": v
+ }
+ }]
+ }
+ `,
+ out: `<0>{<1>{listOfCloseds: [_|_(<2>.v:field "b" not allowed in closed struct)]}, Foo: <3>{listOfCloseds: []}, #Closed: <4>C{a: 0}, Junk: <5>{b: 2}}`,
+ }, {
+ desc: "label and field aliases",
+ in: `
+ p: [ID=string]: { name: ID }
+ A="foo=bar": "str"
+ a: A
+ B=bb: 4
+ b1: B
+ b1: bb
+ C="\(a)": 5
+ c: C
+ `,
+ out: `<0>{` +
+ `p: <1>{[]: <2>(ID: string)-><3>{name: <2>.ID}, }, ` +
+ `"foo=bar": "str", ` +
+ `a: "str", ` +
+ `bb: 4, ` +
+ `b1: 4, ` +
+ `c: 5, ` +
+ `str: 5}`,
+ }, {
+ desc: "optionals with label filters",
+ in: `
+ #JobID: =~"^[a-zA-Z]*$"
+ #Job: {
+ name: string
+ cmd: string
+ }
+ #Jobs: {
+ [#JobID]: #Job
+ [=~"Test$"]: name: =~"^test" // Must work without ...
+ }
+
+ jobs: foo: name: "allGood"
+ jobs: foo: name: "allGood"
+
+ jobs1: #Jobs
+ jobs1: foo1: {} // faulty
+
+ jobs2: #Jobs
+ jobs2: fooTest: name: "badName" // faulty
+
+ jobs3: #Jobs
+ jobs3: [string]: #Job
+ jobs3: fooTest1: name: "badName" // faulty
+ `,
+ out: `<0>{` +
+ `#JobID: =~"^[a-zA-Z]*$", ` +
+ `#Job: <1>C{name: string, cmd: string}, ` +
+ `#Jobs: <2>C{[=~"^[a-zA-Z]*$"]: <3>(_: string)-><4>.#Job, [=~"Test$"]: <5>(_: string)-><6>C{name: =~"^test"}, }, ` +
+ `jobs: <7>{foo: <8>{name: "allGood"}}, ` +
+ `jobs1: _|_(<9>{}:field "foo1" not allowed in closed struct), ` +
+ `jobs2: <10>C{[=~"^[a-zA-Z]*$"]: <11>(_: string)-><4>.#Job, [=~"Test$"]: <12>(_: string)-><13>C{name: =~"^test"}, fooTest: _|_(string:field "cmd" not allowed in closed struct)}, ` +
+ `jobs3: _|_(<14>{name: "badName"}:field "fooTest1" not allowed in closed struct)}`,
+ }, {
+ desc: "optionals in open structs",
+ in: `
+ A: {
+ [=~"^[a-s]*$"]: int
+ }
+ B: {
+ [=~"^[m-z]*$"]: int
+ }
+ #C: {A & B}
+ c: #C & { aaa: 3 }
+ `,
+ out: `<0>{A: <1>{[=~"^[a-s]*$"]: <2>(_: string)->int, }, B: <3>{[=~"^[m-z]*$"]: <4>(_: string)->int, }, #C: <5>C{[=~"^[a-s]*$"]: <6>(_: string)->int, [=~"^[m-z]*$"]: <7>(_: string)->int, }, c: <8>C{[=~"^[a-s]*$"]: <9>(_: string)->int, [=~"^[m-z]*$"]: <10>(_: string)->int, aaa: 3}}`,
+ }, {
+ desc: "conjunction of optional sets",
+ in: `
+ #A: {
+ [=~"^[a-s]*$"]: int
+ }
+ #B: {
+ [=~"^[m-z]*$"]: int
+ }
+
+ #C: #A & #B
+ c: #C & { aaa: 3 }
+
+ #D: {#A & #B}
+ d: #D & { aaa: 3 }
+ `,
+ out: `<0>{` +
+ `#A: <1>C{[=~"^[a-s]*$"]: <2>(_: string)->int, }, ` +
+ `#B: <3>C{[=~"^[m-z]*$"]: <4>(_: string)->int, }, ` +
+ `#C: <5>C{(C{[=~"^[a-s]*$"]: <6>(_: string)->int} & C{[=~"^[m-z]*$"]: <7>(_: string)->int}), }, ` +
+ `c: _|_(3:field "aaa" not allowed in closed struct), ` +
+ `#D: <8>C{(C{[=~"^[a-s]*$"]: <9>(_: string)->int} & C{[=~"^[m-z]*$"]: <10>(_: string)->int}), }, ` +
+ `d: _|_(3:field "aaa" not allowed in closed struct)` +
+ `}`,
+ }, {
+ desc: "continue recursive closing for optionals",
+ in: `
+ #S: {
+ [string]: { a: int }
+ }
+ a: #S & {
+ v: { b: int }
+ }
+ `,
+ out: `<0>{#S: <1>{[]: <2>(_: string)-><3>C{a: int}, }, a: <4>{[]: <5>(_: string)-><6>C{a: int}, v: _|_(int:field "b" not allowed in closed struct)}}`,
+ }, {
+ desc: "augment closed optionals",
+ in: `
+ #A: {
+ [=~"^[a-s]*$"]: int
+ }
+ #B: {
+ [=~"^[m-z]*?"]: int
+ }
+ #C: {
+ #A & #B
+ [=~"^Q*$"]: int
+ }
+ c: #C & { QQ: 3 }
+ #D: {
+ #A
+ #B
+ }
+ d: #D & { aaa: 4 }
+ `,
+ out: `<0>{` +
+ `#A: <1>C{[=~"^[a-s]*$"]: <2>(_: string)->int, }, ` +
+ `#B: <3>C{[=~"^[m-z]*?"]: <4>(_: string)->int, }, ` +
+ `#C: <5>C{C{[=~"^Q*$"]: <6>(_: string)->int}, C{(C{[=~"^[a-s]*$"]: <7>(_: string)->int} & C{[=~"^[m-z]*?"]: <8>(_: string)->int})}, }, ` +
+ `c: <9>C{C{[=~"^Q*$"]: <10>(_: string)->int}, C{(C{[=~"^[a-s]*$"]: <11>(_: string)->int} & C{[=~"^[m-z]*?"]: <12>(_: string)->int})}, QQ: 3}, ` +
+ `#D: <13>C{[=~"^[a-s]*$"]: <14>(_: string)->int, [=~"^[m-z]*?"]: <15>(_: string)->int, }, ` +
+ `d: <16>C{[=~"^[a-s]*$"]: <17>(_: string)->int, [=~"^[m-z]*?"]: <18>(_: string)->int, aaa: 4}}`,
+ }, {
+ in: `
+ #Task: {
+ {
+ op: "pull"
+ tag: *"latest" | string
+ refToTag: tag
+ tagExpr: tag + "dd"
+ tagInString: "\(tag)"
+ } | {
+ op: "scratch"
+ }
+ }
+
+ foo: #Task & {"op": "pull"}
+ `,
+ out: `<0>{#Task: (<1>C{op: "pull", tag: (*"latest" | string), refToTag: <1>.tag, tagExpr: (<1>.tag + "dd"), tagInString: ""+<1>.tag+""} | <2>C{op: "scratch"}), foo: <3>C{op: "pull", tag: "latest", refToTag: "latest", tagExpr: "latestdd", tagInString: "latest"}}`,
+ }, {
+ in: `
+ t: {
+ #ok: *true | bool
+ if #ok {
+ x: int
+ }
+ }
+ s: t & {
+ #ok: false
+ }`,
+ out: `<0>{t: <1>{x: int, #ok: true}, s: <2>{#ok: false}}`,
+ }, {
+ desc: "cross-dependent comprehension",
+ // TODO(eval): fix: c should ultimately be allowed the struct. Current
+ // semantics require, however, that generated fields are not available
+ // for evaluation. This, however, does not have to hold, for closedness
+ // checks and allowing this would be more intuitive.
+ // Until that time, ensure that the behavior is at commutative.
+ in: `
+ #a: {
+ if b {
+ c: 4
+ }
+ b: bool
+ }
+ x: (#a & { b: true}) & {c: 4 }
+ y: x
+ `,
+ // c should not be allowed, as it would break commutativiy.
+ // See comments above.
+ out: `<0>{x: _|_(4:field "c" not allowed in closed struct), y: _|_(4:field "c" not allowed in closed struct), #a: <1>C{b: bool if <2>.b yield <3>C{c: 4}}}`,
+ }, {
+ desc: "optional expanded before lookup",
+ in: `
+ test: [ID=_]: {
+ name: ID
+ }
+
+ test: A: {
+ field1: "1"
+ field2: "2"
+ }
+
+ B: test.A & {}
+ `,
+ out: `<0>{test: <1>{[]: <2>(ID: string)-><3>{name: <2>.ID}, A: <4>{name: "A", field1: "1", field2: "2"}}, B: <5>{name: "A", field1: "1", field2: "2"}}`,
+ }, {
+ desc: "Issue #178",
+ in: `
+ import "encoding/csv"
+ import "encoding/hex"
+
+ foo: csv.Decode(data)
+ data: bytes
+
+ len: int
+ bar: hex.EncodedLen(len)
+ `,
+ out: `<0>{foo: <1>.Decode (<2>.data), data: bytes, len: int, bar: <3>.EncodedLen (<2>.len)}`,
+ }, {
+ // This resulted in an issue in an older version. Prevent regression.
+ desc: "comprehension and skipped field",
+ in: `
+ for key, value in {x: v: 1} {
+ "\(key)": {
+ v: *{for pod, _ in value.v {}} | {"\(value.v)": 2}
+ _p: 3
+ }
+ }
+ `,
+ out: `<0>{x: <1>{v: <2>{"1": 2}, _p: 3}}`,
+ }, {
+ desc: "non-structural direct cycles",
+ in: `
+ c1: {bar: baz: 2} & c1.bar
+ c2: {bar: 1} & c2.bar
+ `,
+ out: `<0>{` +
+ `c1: <1>{bar: <2>{baz: 2}, baz: 2}, ` +
+ `c2: _|_(conflicting values {bar: 1} and 1 (mismatched types struct and int))}`,
+ }, {
+ desc: "dont bind to string labels",
+ in: `
+ x: 1
+ y: {
+ "x": 2
+ z: x
+ }
+ `,
+ out: `<0>{x: 1, y: <1>{x: 2, z: 1}}`,
+ }, {
+ desc: "dont pass incomplete values to builtins",
+ in: `
+ import "encoding/json"
+
+ input: string
+ foo: json.Marshal(input)
+ `,
+ out: `<0>{input: string, foo: <1>.Marshal (<2>.input)}`,
+ }, {
+ desc: "alias reuse in nested scope",
+ in: `
+ #Foo: {
+ let X = or([ for k, _ in {} { k } ])
+ connection: [X]: X
+ }
+ #A: {
+ foo: "key"
+ let X = foo
+ a: foo: [X]: X
+ }
+ #B: {
+ foo: string
+ let X = foo
+ a: foo: [X]: X
+ }
+ b: #B & { foo: "key" }
+ `,
+ out: `<0>{` +
+ `#Foo: <1>C{connection: <2>C{[or ([ <3>for k, _ in <4>{} yield <3>.k ])]: <5>(_: string)->or ([ <3>for k, _ in <4>{} yield <3>.k ]), }}, ` +
+ `#A: <6>C{foo: "key", a: <7>C{foo: <8>C{["key"]: <9>(_: string)-><10>.foo, }}}, ` +
+ `#B: <11>C{foo: string, a: <12>C{foo: <13>C{[string]: <14>(_: string)-><15>.foo, }}}, ` +
+ `b: <16>C{foo: "key", a: <17>C{foo: <18>C{["key"]: <19>(_: string)-><20>.foo, }}}` +
+ `}`,
+ }, {
+ desc: "json Marshaling detects incomplete",
+ in: `
+ import "encoding/json"
+ a: json.Marshal({ a: string} )
+
+ foo: { a: 3, b: foo.c }
+ b: json.Marshal(foo)
+ `,
+ out: `<0>{` +
+ `a: <1>.Marshal (<2>{a: string}), ` +
+ `foo: <3>{a: 3, b: <4>.foo.c}, ` +
+ `b: <1>.Marshal (<4>.foo)}`,
+ }, {
+ desc: "detectIncompleteYAML",
+ in: `
+ package foobar
+
+ import yaml "encoding/yaml"
+
+ #Spec: {
+ _vars: {something: string}
+ data: {
+ #foo: {
+ use: _vars.something
+ }
+ baz: yaml.Marshal(_vars.something)
+ foobar: yaml.Marshal(#foo)
+ }
+ }
+ Val: #Spec & {
+ _vars: something: "var-string"
+ }
+ `,
+ out: `<0>{#Spec: <1>C{_vars: <2>C{something: string}, data: <3>C{#foo: <4>C{use: string}, baz: <5>.Marshal (<6>._vars.something), foobar: <5>.Marshal (<7>.#foo)}}, Val: <8>C{_vars: <9>C{something: "var-string"}, data: <10>C{#foo: <11>C{use: "var-string"}, baz: "var-string\n", foobar: "use: var-string\n"}}}`,
+ }, {
+ desc: "detectIncompleteJSON",
+ in: `
+ package foobar
+
+ import "encoding/json"
+
+ #Spec: {
+ _vars: {something: string}
+ data: {
+ #foo: {
+ use: _vars.something
+ }
+ baz: json.Marshal(_vars.something)
+ foobar: json.Marshal(#foo)
+ }
+ }
+ Val: #Spec & {
+ _vars: something: "var-string"
+ }
+ `,
+ out: `<0>{#Spec: <1>C{_vars: <2>C{something: string}, data: <3>C{#foo: <4>C{use: string}, baz: <5>.Marshal (<6>._vars.something), foobar: <5>.Marshal (<7>.#foo)}}, Val: <8>C{_vars: <9>C{something: "var-string"}, data: <10>C{#foo: <11>C{use: "var-string"}, baz: "\"var-string\"", foobar: "{\"use\":\"var-string\"}"}}}`,
+ }, {
+ desc: "issue312",
+ in: `
+ for x in [1] {
+ *close({}) | { [_]: null }
+ }
+ `,
+ out: `<0>{ <1>for _, x in [1] yield <2>{}, (*close (<3>{}) | <4>{[]: <5>(_: string)->null, })}`,
+ }, {
+ // TODO(eval): note that this behavior is incompatible with allowing
+ // non-struct as emit values. If we ever want to do this, we need to
+ // do it soon.
+ desc: "issue312",
+ in: `
+ y: *1 | {a: 2}
+ for x in [1] { y }
+ `,
+ out: `<0>{y: 1, a: 2}`,
+ }, {
+ desc: "issue318",
+ in: `
+ #T: {
+ arg: x: string
+ out1: "\(arg.x) \(arg.y)"
+ out2: "\(arg.y)"
+ vx: arg.x
+ vy: arg.y
+ }
+ `,
+ out: `<0>{` +
+ `#T: <1>C{` +
+ `arg: <2>C{x: string}, ` +
+ `out1: _|_(<3>.arg.y:undefined field "y"), ` +
+ `out2: _|_(<3>.arg.y:undefined field "y"), ` +
+ `vx: string, ` +
+ `vy: _|_(<3>.arg.y:undefined field "y")}}`,
+ }, {
+ desc: "issue314",
+ in: `
+ import (
+ "text/template"
+ "encoding/yaml"
+ "encoding/json"
+ )
+
+ x: {
+ s: "myname"
+ #T
+ }
+
+ #T: {
+ s: string
+ out: template.Execute("{{.s}}", {
+ "s": s
+ })
+ }
+
+ #V: {
+ s: string
+ out: json.Marshal({"s": s})
+ }
+
+ #U: {
+ s: string
+ out: yaml.Marshal({"s": s})
+ }`,
+ out: `<0>{` +
+ `x: <1>C{s: "myname", out: "myname"}, ` +
+ `#T: <2>C{s: string, out: <3>.Execute ("{{.s}}",<4>C{s: <5>.s})}, ` +
+ `#V: <6>C{s: string, out: <7>.Marshal (<8>C{s: <9>.s})}, ` +
+ `#U: <10>C{s: string, out: <11>.Marshal (<12>C{s: <13>.s})}}`,
+ }}
+ rewriteHelper(t, testCases, evalFull)
+}
+
+func TestX(t *testing.T) {
+ // Don't remove. For debugging.
+ in := `
+ `
+
+ if strings.TrimSpace(in) == "" {
+ t.Skip()
+ }
+ rewriteHelper(t, []testCase{{in: in}}, evalFull)
+}
diff --git a/internal/legacy/cue/types.go b/internal/legacy/cue/types.go
new file mode 100644
index 0000000..a5deb07
--- /dev/null
+++ b/internal/legacy/cue/types.go
@@ -0,0 +1,2247 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cue
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "math"
+ "math/big"
+ "strconv"
+ "strings"
+
+ "github.com/cockroachdb/apd/v2"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/token"
+ "cuelang.org/go/internal"
+ "cuelang.org/go/internal/core/adt"
+)
+
+// Kind determines the underlying type of a Value.
+type Kind = adt.Kind
+
+const BottomKind Kind = 0
+
+const (
+ // NullKind indicates a null value.
+ NullKind Kind = adt.NullKind
+
+ // BoolKind indicates a boolean value.
+ BoolKind = adt.BoolKind
+
+ // IntKind represents an integral number.
+ IntKind = adt.IntKind
+
+ // FloatKind represents a decimal float point number that cannot be
+ // converted to an integer. The underlying number may still be integral,
+ // but resulting from an operation that enforces the float type.
+ FloatKind = adt.FloatKind
+
+ // StringKind indicates any kind of string.
+ StringKind = adt.StringKind
+
+ // BytesKind is a blob of data.
+ BytesKind = adt.BytesKind
+
+ // StructKind is a kev-value map.
+ StructKind = adt.StructKind
+
+ // ListKind indicates a list of values.
+ ListKind = adt.ListKind
+
+ // _numberKind is used as a implementation detail inside
+ // Kind.String to indicate NumberKind.
+
+ // NumberKind represents any kind of number.
+ NumberKind = IntKind | FloatKind
+
+ TopKind = Kind(adt.TopKind)
+)
+
+// An structValue represents a JSON object.
+//
+// TODO: remove
+type structValue struct {
+ ctx *context
+ path *valueData
+ obj *structLit
+ Arcs arcs
+}
+
+// Len reports the number of fields in this struct.
+func (o *structValue) Len() int {
+ if o.obj == nil {
+ return 0
+ }
+ return len(o.Arcs)
+}
+
+// At reports the key and value of the ith field, i < o.Len().
+func (o *structValue) At(i int) (key string, v Value) {
+ a := o.Arcs[i]
+ v = newChildValue(o, i)
+ return o.ctx.LabelStr(a.Label), v
+}
+
+// Lookup reports the field for the given key. The returned Value is invalid
+// if it does not exist.
+func (o *structValue) Lookup(key string) Value {
+ f := o.ctx.StrLabel(key)
+ i := 0
+ len := o.Len()
+ for ; i < len; i++ {
+ if o.Arcs[i].Label == f {
+ break
+ }
+ }
+ if i == len {
+ // TODO: better message.
+ ctx := o.ctx
+ x := ctx.mkErr(o.obj, codeNotExist, "value %q not found", key)
+ v := x.evalPartial(ctx)
+ return Value{ctx.index, &valueData{o.path.parent, 0, arc{Label: o.path.Label, Value: v, v: x}}}
+ }
+ return newChildValue(o, i)
+}
+
+// MarshalJSON returns a valid JSON encoding or reports an error if any of the
+// fields is invalid.
+func (o *structValue) marshalJSON() (b []byte, err errors.Error) {
+ b = append(b, '{')
+ n := o.Len()
+ for i := 0; i < n; i++ {
+ k, v := o.At(i)
+ s, err := json.Marshal(k)
+ if err != nil {
+ return nil, unwrapJSONError(err)
+ }
+ b = append(b, s...)
+ b = append(b, ':')
+ bb, err := json.Marshal(v)
+ if err != nil {
+ return nil, unwrapJSONError(err)
+ }
+ b = append(b, bb...)
+ if i < n-1 {
+ b = append(b, ',')
+ }
+ }
+ b = append(b, '}')
+ return b, nil
+}
+
+var _ errors.Error = &marshalError{}
+
+type marshalError struct {
+ err errors.Error
+ b *bottom
+}
+
+func toMarshalErr(v Value, b *bottom) error {
+ return &marshalError{v.toErr(b), b}
+}
+
+func marshalErrf(v Value, src source, code errCode, msg string, args ...interface{}) error {
+ arguments := append([]interface{}{code, msg}, args...)
+ b := v.idx.mkErr(src, arguments...)
+ return toMarshalErr(v, b)
+}
+
+func (e *marshalError) Error() string {
+ return fmt.Sprintf("cue: marshal error: %v", e.err)
+}
+
+func (e *marshalError) Path() []string { return e.err.Path() }
+func (e *marshalError) Msg() (string, []interface{}) { return e.err.Msg() }
+func (e *marshalError) Position() token.Pos { return e.err.Position() }
+func (e *marshalError) InputPositions() []token.Pos {
+ return e.err.InputPositions()
+}
+
+func unwrapJSONError(err error) errors.Error {
+ switch x := err.(type) {
+ case *json.MarshalerError:
+ return unwrapJSONError(x.Err)
+ case *marshalError:
+ return x
+ case errors.Error:
+ return &marshalError{x, nil}
+ default:
+ return &marshalError{errors.Wrapf(err, token.NoPos, "json error"), nil}
+ }
+}
+
+// An Iterator iterates over values.
+//
+type Iterator struct {
+ val Value
+ ctx *context
+ iter iterAtter
+ len int
+ p int
+ cur Value
+ f label
+}
+
+// Next advances the iterator to the next value and reports whether there was
+// any. It must be called before the first call to Value or Key.
+func (i *Iterator) Next() bool {
+ if i.p >= i.len {
+ i.cur = Value{}
+ return false
+ }
+ arc := i.iter.iterAt(i.ctx, i.p)
+ i.cur = i.val.makeChild(i.ctx, uint32(i.p), arc)
+ i.f = arc.Label
+ i.p++
+ return true
+}
+
+// Value returns the current value in the list. It will panic if Next advanced
+// past the last entry.
+func (i *Iterator) Value() Value {
+ return i.cur
+}
+
+// Label reports the label of the value if i iterates over struct fields and
+// "" otherwise.
+func (i *Iterator) Label() string {
+ if i.f == 0 {
+ return ""
+ }
+ return i.ctx.LabelStr(i.f)
+}
+
+// IsHidden reports if a field is hidden from the data model.
+func (i *Iterator) IsHidden() bool {
+ return i.f.IsHidden()
+}
+
+// IsOptional reports if a field is optional.
+func (i *Iterator) IsOptional() bool {
+ return i.cur.v.arc.optional
+}
+
+// IsDefinition reports if a field is a definition.
+func (i *Iterator) IsDefinition() bool {
+ return i.cur.v.arc.definition
+}
+
+// marshalJSON iterates over the list and generates JSON output. HasNext
+// will return false after this operation.
+func marshalList(l *Iterator) (b []byte, err errors.Error) {
+ b = append(b, '[')
+ if l.Next() {
+ for i := 0; ; i++ {
+ x, err := json.Marshal(l.Value())
+ if err != nil {
+ return nil, unwrapJSONError(err)
+ }
+ b = append(b, x...)
+ if !l.Next() {
+ break
+ }
+ b = append(b, ',')
+ }
+ }
+ b = append(b, ']')
+ return b, nil
+}
+
+func (v Value) getNum(k kind) (*numLit, errors.Error) {
+ v, _ = v.Default()
+ if err := v.checkKind(v.ctx(), k); err != nil {
+ return nil, v.toErr(err)
+ }
+ n, _ := v.v.Value.(*numLit)
+ return n, nil
+}
+
+// MantExp breaks x into its mantissa and exponent components and returns the
+// exponent. If a non-nil mant argument is provided its value is set to the
+// mantissa of x. The components satisfy x == mant × 10**exp. It returns an
+// error if v is not a number.
+//
+// The components are not normalized. For instance, 2.00 is represented mant ==
+// 200 and exp == -2. Calling MantExp with a nil argument is an efficient way to
+// get the exponent of the receiver.
+func (v Value) MantExp(mant *big.Int) (exp int, err error) {
+ n, err := v.getNum(numKind)
+ if err != nil {
+ return 0, err
+ }
+ if n.X.Form != 0 {
+ return 0, ErrInfinite
+ }
+ if mant != nil {
+ mant.Set(&n.X.Coeff)
+ if n.X.Negative {
+ mant.Neg(mant)
+ }
+ }
+ return int(n.X.Exponent), nil
+}
+
+// AppendInt appends the string representation of x in the given base to buf and
+// returns the extended buffer, or an error if the underlying number was not
+// an integer.
+func (v Value) AppendInt(buf []byte, base int) ([]byte, error) {
+ i, err := v.Int(nil)
+ if err != nil {
+ return nil, err
+ }
+ return i.Append(buf, base), nil
+}
+
+// AppendFloat appends to buf the string form of the floating-point number x.
+// It returns an error if v is not a number.
+func (v Value) AppendFloat(buf []byte, fmt byte, prec int) ([]byte, error) {
+ n, err := v.getNum(numKind)
+ if err != nil {
+ return nil, err
+ }
+ ctx := apd.BaseContext
+ nd := int(apd.NumDigits(&n.X.Coeff)) + int(n.X.Exponent)
+ if n.X.Form == apd.Infinite {
+ if n.X.Negative {
+ buf = append(buf, '-')
+ }
+ return append(buf, string('∞')...), nil
+ }
+ if fmt == 'f' && nd > 0 {
+ ctx.Precision = uint32(nd + prec)
+ } else {
+ ctx.Precision = uint32(prec)
+ }
+ var d apd.Decimal
+ ctx.Round(&d, &n.X)
+ return d.Append(buf, fmt), nil
+}
+
+var (
+ // ErrBelow indicates that a value was rounded down in a conversion.
+ ErrBelow = errors.New("value was rounded down")
+
+ // ErrAbove indicates that a value was rounded up in a conversion.
+ ErrAbove = errors.New("value was rounded up")
+
+ // ErrInfinite indicates that a value is infinite.
+ ErrInfinite = errors.New("infinite")
+)
+
+// Int converts the underlying integral number to an big.Int. It reports an
+// error if the underlying value is not an integer type. If a non-nil *Int
+// argument z is provided, Int stores the result in z instead of allocating a
+// new Int.
+func (v Value) Int(z *big.Int) (*big.Int, error) {
+ n, err := v.getNum(intKind)
+ if err != nil {
+ return nil, err
+ }
+ if z == nil {
+ z = &big.Int{}
+ }
+ if n.X.Exponent != 0 {
+ panic("cue: exponent should always be nil for integer types")
+ }
+ z.Set(&n.X.Coeff)
+ if n.X.Negative {
+ z.Neg(z)
+ }
+ return z, nil
+}
+
+// Int64 converts the underlying integral number to int64. It reports an
+// error if the underlying value is not an integer type or cannot be represented
+// as an int64. The result is (math.MinInt64, ErrAbove) for x < math.MinInt64,
+// and (math.MaxInt64, ErrBelow) for x > math.MaxInt64.
+func (v Value) Int64() (int64, error) {
+ n, err := v.getNum(intKind)
+ if err != nil {
+ return 0, err
+ }
+ if !n.X.Coeff.IsInt64() {
+ if n.X.Negative {
+ return math.MinInt64, ErrAbove
+ }
+ return math.MaxInt64, ErrBelow
+ }
+ i := n.X.Coeff.Int64()
+ if n.X.Negative {
+ i = -i
+ }
+ return i, nil
+}
+
+// Uint64 converts the underlying integral number to uint64. It reports an
+// error if the underlying value is not an integer type or cannot be represented
+// as a uint64. The result is (0, ErrAbove) for x < 0, and
+// (math.MaxUint64, ErrBelow) for x > math.MaxUint64.
+func (v Value) Uint64() (uint64, error) {
+ n, err := v.getNum(intKind)
+ if err != nil {
+ return 0, err
+ }
+ if n.X.Negative {
+ return 0, ErrAbove
+ }
+ if !n.X.Coeff.IsUint64() {
+ return math.MaxUint64, ErrBelow
+ }
+ i := n.X.Coeff.Uint64()
+ return i, nil
+}
+
+// trimZeros trims 0's for better JSON respresentations.
+func trimZeros(s string) string {
+ n1 := len(s)
+ s2 := strings.TrimRight(s, "0")
+ n2 := len(s2)
+ if p := strings.IndexByte(s2, '.'); p != -1 {
+ if p == n2-1 {
+ return s[:len(s2)+1]
+ }
+ return s2
+ }
+ if n1-n2 <= 4 {
+ return s
+ }
+ return fmt.Sprint(s2, "e+", n1-n2)
+}
+
+var (
+ smallestPosFloat64 *apd.Decimal
+ smallestNegFloat64 *apd.Decimal
+ maxPosFloat64 *apd.Decimal
+ maxNegFloat64 *apd.Decimal
+)
+
+func init() {
+ const (
+ // math.SmallestNonzeroFloat64: 1 / 2**(1023 - 1 + 52)
+ smallest = "4.940656458412465441765687928682213723651e-324"
+ // math.MaxFloat64: 2**1023 * (2**53 - 1) / 2**52
+ max = "1.797693134862315708145274237317043567981e+308"
+ )
+ ctx := apd.BaseContext
+ ctx.Precision = 40
+
+ var err error
+ smallestPosFloat64, _, err = ctx.NewFromString(smallest)
+ if err != nil {
+ panic(err)
+ }
+ smallestNegFloat64, _, err = ctx.NewFromString("-" + smallest)
+ if err != nil {
+ panic(err)
+ }
+ maxPosFloat64, _, err = ctx.NewFromString(max)
+ if err != nil {
+ panic(err)
+ }
+ maxNegFloat64, _, err = ctx.NewFromString("-" + max)
+ if err != nil {
+ panic(err)
+ }
+}
+
+// Float64 returns the float64 value nearest to x. It reports an error if v is
+// not a number. If x is too small to be represented by a float64 (|x| <
+// math.SmallestNonzeroFloat64), the result is (0, ErrBelow) or (-0, ErrAbove),
+// respectively, depending on the sign of x. If x is too large to be represented
+// by a float64 (|x| > math.MaxFloat64), the result is (+Inf, ErrAbove) or
+// (-Inf, ErrBelow), depending on the sign of x.
+func (v Value) Float64() (float64, error) {
+ n, err := v.getNum(numKind)
+ if err != nil {
+ return 0, err
+ }
+ if n.X.Negative {
+ if n.X.Cmp(smallestNegFloat64) == 1 {
+ return -0, ErrAbove
+ }
+ if n.X.Cmp(maxNegFloat64) == -1 {
+ return math.Inf(-1), ErrBelow
+ }
+ } else {
+ if n.X.Cmp(smallestPosFloat64) == -1 {
+ return 0, ErrBelow
+ }
+ if n.X.Cmp(maxPosFloat64) == 1 {
+ return math.Inf(1), ErrAbove
+ }
+ }
+ f, _ := n.X.Float64()
+ return f, nil
+}
+
+type valueData struct {
+ parent *valueData
+ index uint32
+ arc
+}
+
+// path returns the path of the value.
+func (v *valueData) appendPath(a []string, idx *index) ([]string, kind) {
+ var k kind
+ if v.parent != nil {
+ a, k = v.parent.appendPath(a, idx)
+ }
+ switch k {
+ case listKind:
+ a = append(a, strconv.FormatInt(int64(v.index), 10))
+ case structKind:
+ label := idx.LabelStr(v.arc.Label)
+ if f := v.arc.Label; !f.IsDef() && !f.IsHidden() {
+ if !ast.IsValidIdent(label) {
+ label = strconv.Quote(label)
+ }
+ }
+ a = append(a, label)
+ }
+ return a, v.arc.Value.Kind()
+}
+
+// Value holds any value, which may be a Boolean, Error, List, Null, Number,
+// Struct, or String.
+type Value struct {
+ idx *index
+ v *valueData
+}
+
+func newErrValue(v Value, b *bottom) Value {
+ ctx := v.ctx()
+ p := v.v
+ if p == nil {
+ return newValueRoot(ctx, b)
+ }
+ return Value{
+ ctx.index,
+ &valueData{p.parent, p.index, arc{
+ Label: p.arc.Label,
+ Value: b,
+ v: b,
+ }},
+ }
+}
+
+func newValueRoot(ctx *context, x value) Value {
+ v := x.evalPartial(ctx)
+ return Value{ctx.index, &valueData{nil, 0, arc{Value: v, v: x}}}
+}
+
+func newChildValue(obj *structValue, i int) Value {
+ a := obj.Arcs[i]
+ for j, b := range obj.obj.Arcs {
+ if b.Label == a.Label {
+ a = obj.obj.iterAt(obj.ctx, j)
+ // TODO: adding more technical debt here. The evaluator should be
+ // rewritten.
+ x := obj.obj
+ ctx := obj.ctx
+ if x.optionals != nil {
+ name := ctx.LabelStr(x.Arcs[i].Label)
+ arg := &stringLit{x.baseValue, name, nil}
+
+ val, _ := x.optionals.constraint(ctx, arg)
+ if val != nil {
+ a.v = mkBin(ctx, x.Pos(), opUnify, a.v, val)
+ }
+ }
+ break
+ }
+ }
+
+ return Value{obj.ctx.index, &valueData{obj.path, uint32(i), a}}
+}
+
+// Dereference reports the value v refers to if v is a reference or v itself
+// otherwise.
+func Dereference(v Value) Value {
+ if v.v == nil {
+ return v
+ }
+
+ ctx := v.ctx()
+ a, n := appendPath(ctx, make([]label, 0, 3), v.v.v)
+
+ if n == nil {
+ return v
+
+ }
+
+ p := locateNode(v.v, n)
+
+ if p == nil {
+
+ imp := ctx.getImportFromNode(n.node)
+ if imp == nil {
+ // TODO(eval): embedded structs are currently represented at the
+ // same level as the enclosing struct. This means that the parent
+ // of an embedded struct skips the struct in which it is embedded.
+ // Treat embedded structs as "anonymous" fields.
+ // See TestPathCorrection.
+ return v
+ }
+ p = &valueData{arc: arc{v: imp.rootValue, Value: imp.rootStruct}}
+ }
+
+ cached := p.Value
+ if cached == nil {
+ cached = p.v.evalPartial(ctx)
+ }
+ s := cached.(*structLit)
+ for _, f := range a {
+ a := s.Lookup(ctx, f)
+ if a.v == nil {
+ return Value{}
+ }
+ p = &valueData{parent: p, arc: a} // index
+ s, _ = a.Value.(*structLit)
+ }
+
+ v = Value{v.idx, p}
+ return v
+}
+
+func appendPath(ctx *context, a []label, v value) (path []label, n *nodeRef) {
+ switch x := v.(type) {
+ case *selectorExpr:
+ a, n = appendPath(ctx, a, x.X)
+ if n == nil {
+ return nil, nil
+ }
+
+ a = append(a, x.Sel)
+
+ case *indexExpr:
+ e := x.Index.evalPartial(ctx)
+ s, ok := e.(*stringLit)
+ if !ok {
+ return nil, nil
+ }
+
+ a, n = appendPath(ctx, a, x.X)
+ if n == nil {
+ return nil, nil
+ }
+
+ a = append(a, ctx.Label(s.Str, false))
+
+ case *nodeRef:
+ n = x
+ }
+ return a, n
+}
+
+func remakeValue(base Value, v value) Value {
+ p := base.v
+ if n, ok := v.(*nodeRef); ok {
+ if q := locateNode(p, n); q != nil {
+ p = q
+ }
+ }
+ path := *p
+ path.v = v
+ path.Value = v.evalPartial(base.ctx())
+ return Value{base.idx, &path}
+}
+
+func locateNode(p *valueData, n *nodeRef) *valueData {
+ // the parent must exist.
+ for ; p != nil && p.Value != n.node.(value); p = p.parent {
+ }
+ return p
+}
+
+func (v Value) ctx() *context {
+ return v.idx.newContext()
+}
+
+func (v Value) makeChild(ctx *context, i uint32, a arc) Value {
+ return Value{v.idx, &valueData{v.v, i, a}}
+}
+
+func (v Value) makeElem(x value) Value {
+ v, e := v.evalFull(x)
+ return Value{v.idx, &valueData{v.v, 0, arc{
+ optional: true,
+ v: x,
+ Value: e,
+ }}}
+}
+
+func (v Value) eval(ctx *context) evaluated {
+ if v.v == nil || v.v.Value == nil {
+ panic("undefined value")
+ }
+ return ctx.manifest(v.v.Value)
+}
+
+func (v Value) evalFull(u value) (Value, evaluated) {
+ ctx := v.ctx()
+ x := u.evalPartial(ctx)
+ if st, ok := x.(*structLit); ok {
+ var err *bottom
+ x, err = st.expandFields(ctx)
+ if err != nil {
+ x = err
+ }
+ if x != st {
+ p := *v.v
+ p.Value = x
+ v.v = &p
+ }
+ }
+ return v, x
+}
+
+// Eval resolves the references of a value and returns the result.
+// This method is not necessary to obtain concrete values.
+func (v Value) Eval() Value {
+ if v.v == nil {
+ return v
+ }
+ return remakeValue(v.evalFull(v.v.v))
+}
+
+// Default reports the default value and whether it existed. It returns the
+// normal value if there is no default.
+func (v Value) Default() (Value, bool) {
+ if v.v == nil {
+ return v, false
+ }
+ v, u := v.evalFull(v.v.v)
+ x := v.ctx().manifest(u)
+ if x != u {
+ return remakeValue(v, x), true
+ }
+ return v, false
+}
+
+// Label reports he label used to obtain this value from the enclosing struct.
+//
+// TODO: get rid of this somehow. Probably by including a FieldInfo struct
+// or the like.
+func (v Value) Label() (string, bool) {
+ if v.v.Label == 0 {
+ return "", false
+ }
+ return v.idx.LabelStr(v.v.Label), true
+}
+
+// Kind returns the kind of value. It returns BottomKind for atomic values that
+// are not concrete. For instance, it will return BottomKind for the bounds
+// >=0.
+func (v Value) Kind() Kind {
+ if v.v == nil {
+ return BottomKind
+ }
+ c := v.v.Value
+ if c == nil {
+ c = v.v.v.evalPartial(v.ctx())
+ }
+ k := c.Kind()
+ if k.isGround() {
+ switch {
+ case k.isAnyOf(nullKind):
+ return NullKind
+ case k.isAnyOf(boolKind):
+ return BoolKind
+ case k&numKind == (intKind):
+ return IntKind
+ case k&numKind == (floatKind):
+ return FloatKind
+ case k.isAnyOf(numKind):
+ return NumberKind
+ case k.isAnyOf(bytesKind):
+ return BytesKind
+ case k.isAnyOf(stringKind):
+ return StringKind
+ case k.isAnyOf(structKind):
+ return StructKind
+ case k.isAnyOf(listKind):
+ return ListKind
+ }
+ }
+ return BottomKind
+}
+
+// IncompleteKind returns a mask of all kinds that this value may be.
+func (v Value) IncompleteKind() Kind {
+ if v.v == nil {
+ return BottomKind
+ }
+ var k kind
+ x := v.v.v.evalPartial(v.ctx())
+ switch x := convertBuiltin(x).(type) {
+ case *builtin:
+ k = x.representedKind()
+ case *customValidator:
+ k = x.Builtin.Params[0]
+ default:
+ k = x.Kind()
+ }
+ vk := BottomKind // Everything is a bottom kind.
+ for i := kind(1); i < nonGround; i <<= 1 {
+ if k&i != 0 {
+ switch i {
+ case nullKind:
+ vk |= NullKind
+ case boolKind:
+ vk |= BoolKind
+ case intKind:
+ vk |= IntKind
+ case floatKind:
+ vk |= FloatKind
+ case stringKind:
+ vk |= StringKind
+ case bytesKind:
+ vk |= BytesKind
+ case structKind:
+ vk |= StructKind
+ case listKind:
+ vk |= ListKind
+ }
+ }
+ }
+ return vk
+}
+
+// MarshalJSON marshalls this value into valid JSON.
+func (v Value) MarshalJSON() (b []byte, err error) {
+ b, err = v.marshalJSON()
+ if err != nil {
+ return nil, unwrapJSONError(err)
+ }
+ return b, nil
+}
+
+func (v Value) marshalJSON() (b []byte, err error) {
+ v, _ = v.Default()
+ if v.v == nil {
+ return json.Marshal(nil)
+ }
+ ctx := v.idx.newContext()
+ x := v.eval(ctx)
+ // TODO: implement marshalles in value.
+ switch k := x.Kind(); k {
+ case nullKind:
+ return json.Marshal(nil)
+ case boolKind:
+ return json.Marshal(x.(*boolLit).B)
+ case intKind, floatKind, numKind:
+ return x.(*numLit).X.MarshalText()
+ case stringKind:
+ return json.Marshal(x.(*stringLit).Str)
+ case bytesKind:
+ return json.Marshal(x.(*bytesLit).B)
+ case listKind:
+ i, _ := v.List()
+ return marshalList(&i)
+ case structKind:
+ obj, err := v.structValData(ctx)
+ st := obj.obj
+ if len(st.comprehensions) > 0 {
+ // This should always evaluate to incomplete. However, fall back
+ // to a bad error message, rather than crashing, in case it doesn't.
+ if err, ok := st.comprehensions[0].comp.evalPartial(ctx).(*bottom); ok {
+ return nil, toMarshalErr(v, err)
+ }
+ }
+
+ if err != nil {
+ return nil, toMarshalErr(v, err)
+ }
+ return obj.marshalJSON()
+ case bottomKind:
+ return nil, toMarshalErr(v, x.(*bottom))
+ default:
+ if k.hasReferences() {
+ return nil, marshalErrf(v, x, codeIncomplete, "value %q contains unresolved references", ctx.str(x))
+ }
+ if !k.isGround() {
+ return nil, marshalErrf(v, x, codeIncomplete, "cannot convert incomplete value %q to JSON", ctx.str(x))
+ }
+ return nil, marshalErrf(v, x, 0, "cannot convert value %q of type %T to JSON", ctx.str(x), x)
+ }
+}
+
+// Syntax converts the possibly partially evaluated value into syntax. This
+// can use used to print the value with package format.
+func (v Value) Syntax(opts ...Option) ast.Node {
+ // TODO: the default should ideally be simplified representation that
+ // exactly represents the value. The latter can currently only be
+ // ensured with Raw().
+ if v.v == nil || v.v.Value == nil {
+ return nil
+ }
+ ctx := v.ctx()
+ o := getOptions(opts)
+ var inst *Instance
+ if !o.final && !o.concrete {
+ inst = v.instance()
+ }
+ if o.raw {
+ n, _ := export(ctx, inst, v.v.v, o)
+ return n
+ }
+ n, _ := export(ctx, inst, v.v.Value, o)
+ return n
+}
+
+// Decode initializes x with Value v. If x is a struct, it will validate the
+// constraints specified in the field tags.
+func (v Value) Decode(x interface{}) error {
+ // TODO: optimize
+ b, err := v.MarshalJSON()
+ if err != nil {
+ return err
+ }
+ return json.Unmarshal(b, x)
+}
+
+// // EncodeJSON generates JSON for the given value.
+// func (v Value) EncodeJSON(w io.Writer, v Value) error {
+// return nil
+// }
+
+// Doc returns all documentation comments associated with the field from which
+// the current value originates.
+func (v Value) Doc() []*ast.CommentGroup {
+ if v.v == nil {
+ return nil
+ }
+ return v.v.docs.appendDocs(nil)
+}
+
+// Split returns a list of values from which v originated such that
+// the unification of all these values equals v and for all returned values.
+// It will also split unchecked unifications (embeddings), so unifying the
+// split values may fail if actually unified.
+// Source returns a non-nil value.
+//
+// Deprecated: use Expr.
+func (v Value) Split() []Value {
+ if v.v == nil {
+ return nil
+ }
+ ctx := v.ctx()
+ a := []Value{}
+ for _, x := range separate(v.v.v) {
+ path := *v.v
+ path.Value = x.evalPartial(ctx)
+ path.v = x
+ a = append(a, Value{v.idx, &path})
+ }
+ return a
+}
+
+func separate(v value) (a []value) {
+ c := v.computed()
+ if c == nil || (c.op != opUnify && c.op != opUnifyUnchecked) {
+ return []value{v}
+ }
+ if c.x != nil {
+ a = append(a, separate(c.x)...)
+ }
+ if c.y != nil {
+ a = append(a, separate(c.y)...)
+ }
+ return a
+}
+
+// Source returns the original node for this value. The return value may not
+// be a syntax.Expr. For instance, a struct kind may be represented by a
+// struct literal, a field comprehension, or a file. It returns nil for
+// computed nodes. Use Split to get all source values that apply to a field.
+func (v Value) Source() ast.Node {
+ if v.v == nil {
+ return nil
+ }
+ return v.v.v.Source()
+}
+
+// Err returns the error represented by v or nil v is not an error.
+func (v Value) Err() error {
+ if err := v.checkKind(v.ctx(), bottomKind); err != nil {
+ return v.toErr(err)
+ }
+ return nil
+}
+
+// Pos returns position information.
+func (v Value) Pos() token.Pos {
+ if v.v == nil || v.Source() == nil {
+ return token.NoPos
+ }
+ pos := v.Source().Pos()
+ return pos
+}
+
+// TODO: IsFinal: this value can never be changed.
+
+// IsClosed reports whether a list of struct is closed. It reports false when
+// when the value is not a list or struct.
+func (v Value) IsClosed() bool {
+ switch v.Kind() {
+ case StructKind:
+ if st, ok := v.v.val().(*structLit); ok {
+ return st.closeStatus.shouldClose()
+ }
+ case ListKind:
+ if l, ok := v.v.val().(*list); ok {
+ if n, ok := l.len.(*numLit); ok {
+ return n.intValue(v.ctx()) == len(l.elem.Arcs)
+ }
+ }
+ }
+ return false
+}
+
+// IsConcrete reports whether the current value is a concrete scalar value
+// (not relying on default values), a terminal error, a list, or a struct.
+// It does not verify that values of lists or structs are concrete themselves.
+// To check whether there is a concrete default, use v.Default().IsConcrete().
+func (v Value) IsConcrete() bool {
+ if v.v == nil {
+ return false // any is neither concrete, not a list or struct.
+ }
+ x := v.v.v.evalPartial(v.ctx())
+
+ // Errors marked as incomplete are treated as not complete.
+ if isIncomplete(x) {
+ return false
+ }
+ // Other errors are considered ground.
+ return x.Kind().isConcrete()
+}
+
+// Deprecated: IsIncomplete
+//
+// It indicates that the value cannot be fully evaluated due to
+// insufficient information.
+func (v Value) IsIncomplete() bool {
+ // TODO: remove
+ x := v.eval(v.ctx())
+ if !x.Kind().isConcrete() {
+ return true
+ }
+ return isIncomplete(x)
+}
+
+// Exists reports whether this value existed in the configuration.
+func (v Value) Exists() bool {
+ if v.v == nil {
+ return false
+ }
+ return exists(v.eval(v.ctx()))
+}
+
+func (v Value) checkKind(ctx *context, want kind) *bottom {
+ if v.v == nil {
+ return errNotExists
+ }
+ // TODO: use checkKind
+ x := v.eval(ctx)
+ if b, ok := x.(*bottom); ok {
+ return b
+ }
+ got := x.Kind()
+ if want != bottomKind {
+ if got&want&concreteKind == bottomKind {
+ return ctx.mkErr(x, "cannot use value %v (type %s) as %s",
+ v.ctx().str(x), got, want)
+ }
+ if !got.isGround() {
+ return ctx.mkErr(x, codeIncomplete,
+ "non-concrete value %v", got)
+ }
+ }
+ return nil
+}
+
+func makeInt(v Value, x int64) Value {
+ return remakeValue(v, newInt(v.v.v.base(), base10).setInt64(x))
+}
+
+// Len returns the number of items of the underlying value.
+// For lists it reports the capacity of the list. For structs it indicates the
+// number of fields, for bytes the number of bytes.
+func (v Value) Len() Value {
+ if v.v != nil {
+ switch x := v.v.v.evalPartial(v.ctx()).(type) {
+ case *list:
+ return remakeValue(v, x.len.evalPartial(v.ctx()))
+ case *bytesLit:
+ return makeInt(v, int64(x.len()))
+ case *stringLit:
+ return makeInt(v, int64(x.len()))
+ }
+ }
+ const msg = "len not supported for type %v"
+ return remakeValue(v, v.ctx().mkErr(v.v.v, msg, v.Kind()))
+}
+
+// Elem returns the value of undefined element types of lists and structs.
+func (v Value) Elem() (Value, bool) {
+ if v.v == nil {
+ return Value{}, false
+ }
+ ctx := v.ctx()
+ switch x := v.v.Value.(type) {
+ case *structLit:
+ t, _ := x.optionals.constraint(ctx, nil)
+ if t == nil {
+ break
+ }
+ return v.makeElem(t), true
+ case *list:
+ return v.makeElem(x.typ), true
+ }
+ return Value{}, false
+}
+
+// BulkOptionals returns all bulk optional fields as key-value pairs.
+// See also Elem and Template.
+func (v Value) BulkOptionals() [][2]Value {
+ x, ok := v.v.Value.(*structLit)
+ if !ok {
+ return nil
+ }
+ return v.appendBulk(nil, x.optionals)
+}
+
+func (v Value) appendBulk(a [][2]Value, x *optionals) [][2]Value {
+ if x == nil {
+ return a
+ }
+ a = v.appendBulk(a, x.left)
+ a = v.appendBulk(a, x.right)
+ for _, set := range x.fields {
+ if set.key != nil {
+ ctx := v.ctx()
+ fn, ok := ctx.manifest(set.value).(*lambdaExpr)
+ if !ok {
+ // create error
+ continue
+ }
+ x := fn.call(ctx, set.value, &basicType{K: stringKind})
+
+ a = append(a, [2]Value{v.makeElem(set.key), v.makeElem(x)})
+ }
+ }
+ return a
+}
+
+// List creates an iterator over the values of a list or reports an error if
+// v is not a list.
+func (v Value) List() (Iterator, error) {
+ v, _ = v.Default()
+ ctx := v.ctx()
+ if err := v.checkKind(ctx, listKind); err != nil {
+ return Iterator{ctx: ctx}, v.toErr(err)
+ }
+ l := v.eval(ctx).(*list)
+ return Iterator{ctx: ctx, val: v, iter: l, len: len(l.elem.Arcs)}, nil
+}
+
+// Null reports an error if v is not null.
+func (v Value) Null() error {
+ v, _ = v.Default()
+ if err := v.checkKind(v.ctx(), nullKind); err != nil {
+ return v.toErr(err)
+ }
+ return nil
+}
+
+// // IsNull reports whether v is null.
+// func (v Value) IsNull() bool {
+// return v.Null() == nil
+// }
+
+// Bool returns the bool value of v or false and an error if v is not a boolean.
+func (v Value) Bool() (bool, error) {
+ v, _ = v.Default()
+ ctx := v.ctx()
+ if err := v.checkKind(ctx, boolKind); err != nil {
+ return false, v.toErr(err)
+ }
+ return v.eval(ctx).(*boolLit).B, nil
+}
+
+// String returns the string value if v is a string or an error otherwise.
+func (v Value) String() (string, error) {
+ v, _ = v.Default()
+ ctx := v.ctx()
+ if err := v.checkKind(ctx, stringKind); err != nil {
+ return "", v.toErr(err)
+ }
+ return v.eval(ctx).(*stringLit).Str, nil
+}
+
+// Bytes returns a byte slice if v represents a list of bytes or an error
+// otherwise.
+func (v Value) Bytes() ([]byte, error) {
+ v, _ = v.Default()
+ ctx := v.ctx()
+ switch x := v.eval(ctx).(type) {
+ case *bytesLit:
+ return append([]byte(nil), x.B...), nil
+ case *stringLit:
+ return []byte(x.Str), nil
+ }
+ return nil, v.toErr(v.checkKind(ctx, bytesKind|stringKind))
+}
+
+// Reader returns a new Reader if v is a string or bytes type and an error
+// otherwise.
+func (v Value) Reader() (io.Reader, error) {
+ v, _ = v.Default()
+ ctx := v.ctx()
+ switch x := v.eval(ctx).(type) {
+ case *bytesLit:
+ return bytes.NewReader(x.B), nil
+ case *stringLit:
+ return strings.NewReader(x.Str), nil
+ }
+ return nil, v.toErr(v.checkKind(ctx, stringKind|bytesKind))
+}
+
+// TODO: distinguish between optional, hidden, etc. Probably the best approach
+// is to mark options in context and have a single function for creating
+// a structVal.
+
+// structVal returns an structVal or an error if v is not a struct.
+func (v Value) structValData(ctx *context) (structValue, *bottom) {
+ return v.structValOpts(ctx, options{
+ omitHidden: true,
+ omitDefinitions: true,
+ omitOptional: true,
+ })
+}
+
+func (v Value) structValFull(ctx *context) (structValue, *bottom) {
+ return v.structValOpts(ctx, options{})
+}
+
+// structVal returns an structVal or an error if v is not a struct.
+func (v Value) structValOpts(ctx *context, o options) (structValue, *bottom) {
+ v, _ = v.Default() // TODO: remove?
+
+ obj, path, err := v.getStruct()
+ if err != nil {
+ return structValue{}, err
+ }
+
+ // check if any fields can be omitted
+ needFilter := false
+ if o.omitHidden || o.omitOptional || o.omitDefinitions {
+ f := label(0)
+ for _, a := range obj.Arcs {
+ f |= a.Label
+ if a.optional && o.omitOptional {
+ needFilter = true
+ break
+ }
+ if a.definition && (o.omitDefinitions || o.concrete) {
+ needFilter = true
+ break
+ }
+ }
+ needFilter = needFilter || f.IsHidden()
+ }
+
+ if needFilter {
+ arcs := make([]arc, len(obj.Arcs))
+ k := 0
+ for _, a := range obj.Arcs {
+ if a.definition && (o.omitDefinitions || o.concrete) {
+ continue
+ }
+ if a.Label.IsHidden() && o.omitHidden {
+ continue
+ }
+ if o.omitOptional && a.optional {
+ continue
+ }
+ arcs[k] = a
+ k++
+ }
+ arcs = arcs[:k]
+ return structValue{ctx, path, obj, arcs}, nil
+ }
+ return structValue{ctx, path, obj, obj.Arcs}, nil
+}
+
+// Struct returns the underlying struct of a value or an error if the value
+// is not a struct.
+func (v Value) Struct() (*Struct, error) {
+ obj, path, err := v.getStruct()
+ if err != nil {
+ return nil, v.toErr(err)
+ }
+ return &Struct{Value{v.idx, path}, obj}, nil
+}
+
+func (v Value) getStruct() (*structLit, *valueData, *bottom) {
+ ctx := v.ctx()
+ if err := v.checkKind(ctx, structKind); err != nil {
+ return nil, nil, err
+ }
+ orig := v.eval(ctx).(*structLit)
+
+ // TODO: This is expansion appropriate?
+ obj, err := orig.expandFields(ctx)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ path := v.v
+ if obj != orig {
+ p := *path
+ p.arc.Value = obj
+ path = &p
+ }
+
+ return obj, path, nil
+}
+
+// Struct represents a CUE struct value.
+type Struct struct {
+ v Value
+ s *structLit
+}
+
+// FieldInfo contains information about a struct field.
+type FieldInfo struct {
+ Name string
+ Pos int
+ Value Value
+
+ IsDefinition bool
+ IsOptional bool
+ IsHidden bool
+}
+
+func (s *Struct) Len() int {
+ return len(s.s.Arcs)
+}
+
+// field reports information about the ith field, i < o.Len().
+func (s *Struct) Field(i int) FieldInfo {
+ ctx := s.v.ctx()
+ a := s.s.Arcs[i]
+ a.Value = s.s.at(ctx, i)
+
+ // TODO: adding more technical debt here. The evaluator should be
+ // rewritten.
+ x := s.s
+ if x.optionals != nil {
+ name := ctx.LabelStr(x.Arcs[i].Label)
+ arg := &stringLit{x.baseValue, name, nil}
+
+ val, _ := x.optionals.constraint(ctx, arg)
+ if val != nil {
+ a.v = mkBin(ctx, x.Pos(), opUnify, a.v, val)
+ }
+ }
+
+ v := Value{ctx.index, &valueData{s.v.v, uint32(i), a}}
+ str := ctx.LabelStr(a.Label)
+ return FieldInfo{str, i, v, a.definition, a.optional, a.Label.IsHidden()}
+}
+
+// FieldByName looks up a field for the given name. If isIdent is true, it will
+// look up a definition or hidden field (starting with `_` or `_#`). Otherwise
+// it interprets name as an arbitrary string for a regular field.
+func (s *Struct) FieldByName(name string, isIdent bool) (FieldInfo, error) {
+ f := s.v.ctx().Label(name, isIdent)
+ for i, a := range s.s.Arcs {
+ if a.Label == f {
+ return s.Field(i), nil
+ }
+ }
+ return FieldInfo{}, errNotFound
+}
+
+// Fields creates an iterator over the Struct's fields.
+func (s *Struct) Fields(opts ...Option) *Iterator {
+ iter, _ := s.v.Fields(opts...)
+ return iter
+}
+
+// Fields creates an iterator over v's fields if v is a struct or an error
+// otherwise.
+func (v Value) Fields(opts ...Option) (*Iterator, error) {
+ o := options{omitDefinitions: true, omitHidden: true, omitOptional: true}
+ o.updateOptions(opts)
+ ctx := v.ctx()
+ obj, err := v.structValOpts(ctx, o)
+ if err != nil {
+ return &Iterator{ctx: ctx}, v.toErr(err)
+ }
+ n := &structLit{
+ obj.obj.baseValue, // baseValue
+ obj.obj.emit, // emit
+ obj.obj.optionals, // template
+ obj.obj.closeStatus, // closeStatus
+ nil, // comprehensions
+ obj.Arcs, // arcs
+ nil, // attributes
+ }
+ return &Iterator{ctx: ctx, val: v, iter: n, len: len(n.Arcs)}, nil
+}
+
+// Lookup reports the value at a path starting from v. The empty path returns v
+// itself. Use LookupDef for definitions or LookupField for any kind of field.
+//
+// The Exists() method can be used to verify if the returned value existed.
+// Lookup cannot be used to look up hidden or optional fields or definitions.
+func (v Value) Lookup(path ...string) Value {
+ ctx := v.ctx()
+ for _, k := range path {
+ // TODO(eval) TODO(error): always search in full data and change error
+ // message if a field is found but is of the incorrect type.
+ obj, err := v.structValData(ctx)
+ if err != nil {
+ // TODO: return a Value at the same location and a new error?
+ return newErrValue(v, err)
+ }
+ v = obj.Lookup(k)
+ }
+ return v
+}
+
+// LookupDef reports the definition with the given name within struct v. The
+// Exists method of the returned value will report false if the definition did
+// not exist. The Err method reports if any error occurred during evaluation.
+func (v Value) LookupDef(name string) Value {
+ ctx := v.ctx()
+ o, err := v.structValFull(ctx)
+ if err != nil {
+ return newErrValue(v, err)
+ }
+
+ f := v.ctx().Label(name, true)
+ for i, a := range o.Arcs {
+ if a.Label == f {
+ if f.IsHidden() || !a.definition || a.optional {
+ break
+ }
+ return newChildValue(&o, i)
+ }
+ }
+ if !strings.HasPrefix(name, "#") {
+ alt := v.LookupDef("#" + name)
+ // Use the original error message if this resulted in an error as well.
+ if alt.Err() == nil {
+ return alt
+ }
+ }
+ return newErrValue(v, ctx.mkErr(v.v.v,
+ "definition %q not found", name))
+}
+
+var errNotFound = errors.Newf(token.NoPos, "field not found")
+
+// FieldByName looks up a field for the given name. If isIdent is true, it will
+// look up a definition or hidden field (starting with `_` or `_#`). Otherwise
+// it interprets name as an arbitrary string for a regular field.
+func (v Value) FieldByName(name string, isIdent bool) (f FieldInfo, err error) {
+ s, err := v.Struct()
+ if err != nil {
+ return f, err
+ }
+ return s.FieldByName(name, isIdent)
+}
+
+// LookupField reports information about a field of v.
+//
+// Deprecated: this API does not work with new-style definitions. Use FieldByName.
+func (v Value) LookupField(name string) (FieldInfo, error) {
+ s, err := v.Struct()
+ if err != nil {
+ // TODO: return a Value at the same location and a new error?
+ return FieldInfo{}, err
+ }
+ f, err := s.FieldByName(name, true)
+ if err != nil {
+ return f, err
+ }
+ if f.IsHidden {
+ return f, errNotFound
+ }
+ return f, err
+}
+
+// TODO: expose this API?
+//
+// // EvalExpr evaluates an expression within the scope of v, which must be
+// // a struct.
+// //
+// // Expressions may refer to builtin packages if they can be uniquely identified.
+// func (v Value) EvalExpr(expr ast.Expr) Value {
+// ctx := v.ctx()
+// result := evalExpr(ctx, v.eval(ctx), expr)
+// return newValueRoot(ctx, result)
+// }
+
+// Fill creates a new value by unifying v with the value of x at the given path.
+//
+// Values may be any Go value that can be converted to CUE, an ast.Expr or
+// a Value. In the latter case, it will panic if the Value is not from the same
+// Runtime.
+//
+// Any reference in v referring to the value at the given path will resolve
+// to x in the newly created value. The resulting value is not validated.
+func (v Value) Fill(x interface{}, path ...string) Value {
+ if v.v == nil {
+ return v
+ }
+ ctx := v.ctx()
+ root := v.v.val()
+ for i := len(path) - 1; i >= 0; i-- {
+ x = map[string]interface{}{path[i]: x}
+ }
+ value := convertVal(ctx, root, true, x)
+ a := v.v.arc
+ a.v = mkBin(ctx, v.Pos(), opUnify, root, value)
+ a.Value = a.v.evalPartial(ctx)
+ // TODO: validate recursively?
+ return Value{v.idx, &valueData{v.v.parent, v.v.index, a}}
+}
+
+// Template returns a function that represents the template definition for a
+// struct in a configuration file. It returns nil if v is not a struct kind or
+// if there is no template associated with the struct.
+//
+// The returned function returns the value that would be unified with field
+// given its name.
+func (v Value) Template() func(label string) Value {
+ // TODO: rename to optional.
+ if v.v == nil {
+ return nil
+ }
+
+ ctx := v.ctx()
+ x, ok := v.v.Value.(*structLit)
+ if !ok || x.optionals.isEmpty() {
+ return nil
+ }
+
+ return func(label string) Value {
+ arg := &stringLit{x.baseValue, label, nil}
+
+ if val, _ := x.optionals.constraint(ctx, arg); val != nil {
+ return remakeValue(v, val)
+ }
+ return Value{}
+ }
+}
+
+// Subsume reports nil when w is an instance of v or an error otherwise.
+//
+// Without options, the entire value is considered for assumption, which means
+// Subsume tests whether v is a backwards compatible (newer) API version of w.
+// Use the Final() to indicate that the subsumed value is data, and that
+//
+// Use the Final option to check subsumption if a w is known to be final,
+// and should assumed to be closed.
+//
+// Options are currently ignored and the function will panic if any are passed.
+//
+// Value v and w must be obtained from the same build.
+// TODO: remove this requirement.
+func (v Value) Subsume(w Value, opts ...Option) error {
+ var mode subsumeMode
+ o := getOptions(opts)
+ if o.final {
+ mode |= subFinal | subChoose
+ }
+ if o.ignoreClosedness {
+ mode |= subSchema
+ }
+ return subsumes(v, w, mode)
+}
+
+// Deprecated: use Subsume.
+//
+// Subsumes reports whether w is an instance of v.
+//
+// Without options, Subsumes checks whether v is a backwards compatbile schema
+// of w.
+//
+// By default, Subsumes tests whether two values are compatib
+// Value v and w must be obtained from the same build.
+// TODO: remove this requirement.
+func (v Value) Subsumes(w Value) bool {
+ return subsumes(v, w, subChoose) == nil
+}
+
+// Unify reports the greatest lower bound of v and w.
+//
+// Value v and w must be obtained from the same build.
+// TODO: remove this requirement.
+func (v Value) Unify(w Value) Value {
+ ctx := v.ctx()
+ if v.v == nil {
+ return w
+ }
+ if w.v == nil {
+ return v
+ }
+ if v.Err() != nil {
+ // TODO: perhaps keep both errors.
+ return v
+ }
+ if w.Err() != nil {
+ return w
+ }
+ a := v.v.v
+ b := w.v.v
+ src := binSrc(token.NoPos, opUnify, a, b)
+ val := mkBin(ctx, src.Pos(), opUnify, a, b)
+ u := remakeValue(v, val)
+ if err := u.Validate(); err != nil {
+ u = newValueRoot(ctx, ctx.mkErr(src, err))
+ }
+ return u
+}
+
+// Equals reports whether two values are equal, ignoring optional fields.
+// The result is undefined for incomplete values.
+func (v Value) Equals(other Value) bool {
+ if v.v == nil || other.v == nil {
+ return false
+ }
+ x := v.v.val()
+ y := other.v.val()
+ return equals(v.ctx(), x, y)
+}
+
+// Format prints a debug version of a value.
+func (v Value) Format(state fmt.State, verb rune) {
+ ctx := v.ctx()
+ if v.v == nil {
+ fmt.Fprint(state, "<nil>")
+ return
+ }
+ switch {
+ case state.Flag('#'):
+ _, _ = io.WriteString(state, ctx.str(v.v.v))
+ case state.Flag('+'):
+ _, _ = io.WriteString(state, debugStr(ctx, v.v.v))
+ default:
+ _, _ = io.WriteString(state, ctx.str(v.v.Value))
+ }
+}
+
+func (v Value) instance() *Instance {
+ if v.v == nil {
+ return nil
+ }
+ return v.ctx().getImportFromNode(v.v.v)
+}
+
+// Reference returns the instance and path referred to by this value such that
+// inst.Lookup(path) resolves to the same value, or no path if this value is not
+// a reference. If a reference contains index selection (foo[bar]), it will
+// only return a reference if the index resolves to a concrete value.
+func (v Value) Reference() (inst *Instance, path []string) {
+ // TODO: don't include references to hidden fields.
+ if v.v == nil {
+ return nil, nil
+ }
+ ctx := v.ctx()
+ var x value
+ var feature string
+ switch sel := v.v.v.(type) {
+ case *selectorExpr:
+ x = sel.X
+ feature = ctx.LabelStr(sel.Sel)
+
+ case *indexExpr:
+ e := sel.Index.evalPartial(ctx)
+ s, ok := e.(*stringLit)
+ if !ok {
+ return nil, nil
+ }
+ x = sel.X
+ feature = s.Str
+
+ default:
+ return nil, nil
+ }
+ imp, a := mkPath(ctx, v.v, x, feature, 0)
+ return imp, a
+}
+
+func mkPath(c *context, up *valueData, x value, feature string, d int) (imp *Instance, a []string) {
+ switch x := x.(type) {
+ case *selectorExpr:
+ imp, a = mkPath(c, up, x.X, c.LabelStr(x.Sel), d+1)
+ if imp == nil {
+ return nil, nil
+ }
+
+ case *indexExpr:
+ e := x.Index.evalPartial(c)
+ s, ok := e.(*stringLit)
+ if !ok {
+ return nil, nil
+ }
+ imp, a = mkPath(c, up, x.X, s.Str, d+1)
+ if imp == nil {
+ return nil, nil
+ }
+
+ case *nodeRef:
+ // the parent must exist.
+ var v value
+ if p := locateNode(up, x); p != nil {
+ v, a = mkFromRoot(c, p, d+2)
+ } else {
+ // Either this references another parent, or it is an embedding.
+ imp = c.getImportFromNode(x.node)
+ if imp != nil {
+ break
+ }
+ // This must be an embedding, go one up.
+ v, a = mkFromRoot(c, up.parent, d+2)
+ }
+ if v == nil {
+ v = x.node
+ }
+ imp = c.getImportFromNode(v)
+ default:
+ return nil, nil
+ }
+ return imp, append(a, feature)
+}
+
+func mkFromRoot(c *context, up *valueData, d int) (root value, a []string) {
+ if up == nil {
+ return nil, make([]string, 0, d)
+ }
+ root, a = mkFromRoot(c, up.parent, d+1)
+ if up.parent != nil {
+ a = append(a, c.LabelStr(up.Label))
+ } else {
+ root = up.v
+ }
+ return root, a
+}
+
+// References reports all references used to evaluate this value. It does not
+// report references for sub fields if v is a struct.
+//
+// Deprecated: can be implemented in terms of Reference and Expr.
+func (v Value) References() [][]string {
+ // TODO: the pathFinder algorithm is quite broken. Using Reference and Expr
+ // will cast a much more accurate net on referenced values.
+ ctx := v.ctx()
+ pf := pathFinder{up: v.v}
+ raw := v.v.v
+ if raw == nil {
+ return nil
+ }
+ rewrite(ctx, raw, pf.find)
+ return pf.paths
+}
+
+type pathFinder struct {
+ paths [][]string
+ stack []label
+ up *valueData
+}
+
+func (p *pathFinder) find(ctx *context, v value) (value, bool) {
+ switch x := v.(type) {
+ case *selectorExpr:
+ i := len(p.stack)
+ p.stack = append(p.stack, x.Sel)
+ rewrite(ctx, x.X, p.find)
+ p.stack = p.stack[:i]
+ return v, false
+
+ case *nodeRef:
+ i := len(p.stack)
+ up := p.up
+ for ; up != nil && up.Value != x.node.(value); up = up.parent {
+ }
+ for ; up != nil && up.Label > 0; up = up.parent {
+ p.stack = append(p.stack, up.Label)
+ }
+ path := make([]string, len(p.stack))
+ for i, v := range p.stack {
+ path[len(path)-1-i] = ctx.LabelStr(v)
+ }
+ p.paths = append(p.paths, path)
+ p.stack = p.stack[:i]
+ return v, false
+
+ case *structLit:
+ // If the stack is empty, we do not descend, as we are not evaluating
+ // sub fields.
+ if len(p.stack) == 0 {
+ return v, false
+ }
+
+ stack := p.stack
+ p.stack = nil
+ for _, a := range x.Arcs {
+ rewrite(ctx, a.v, p.find)
+ }
+ p.stack = stack
+ return v, false
+ }
+ return v, true
+}
+
+type options struct {
+ concrete bool // enforce that values are concrete
+ raw bool // show original values
+ hasHidden bool
+ omitHidden bool
+ omitDefinitions bool
+ omitOptional bool
+ omitAttrs bool
+ resolveReferences bool
+ final bool
+ ignoreClosedness bool // used for comparing APIs
+ docs bool
+ disallowCycles bool // implied by concrete
+}
+
+// An Option defines modes of evaluation.
+type Option option
+
+type option func(p *options)
+
+// Final indicates a value is final. It implicitly closes all structs and lists
+// in a value and selects defaults.
+func Final() Option {
+ return func(o *options) {
+ o.final = true
+ o.omitDefinitions = true
+ o.omitOptional = true
+ o.omitHidden = true
+ }
+}
+
+// Schema specifies the input is a Schema. Used by Subsume.
+func Schema() Option {
+ return func(o *options) {
+ o.ignoreClosedness = true
+ }
+}
+
+// Concrete ensures that all values are concrete.
+//
+// For Validate this means it returns an error if this is not the case.
+// In other cases a non-concrete value will be replaced with an error.
+func Concrete(concrete bool) Option {
+ return func(p *options) {
+ if concrete {
+ p.concrete = true
+ p.final = true
+ if !p.hasHidden {
+ p.omitHidden = true
+ p.omitDefinitions = true
+ }
+ }
+ }
+}
+
+// DisallowCycles forces validation in the precense of cycles, even if
+// non-concrete values are allowed. This is implied by Concrete(true).
+func DisallowCycles(disallow bool) Option {
+ return func(p *options) { p.disallowCycles = disallow }
+}
+
+// ResolveReferences forces the evaluation of references when outputting.
+// This implies the input cannot have cycles.
+func ResolveReferences(resolve bool) Option {
+ return func(p *options) { p.resolveReferences = resolve }
+}
+
+// Raw tells Syntax to generate the value as is without any simplifications.
+func Raw() Option {
+ return func(p *options) { p.raw = true }
+}
+
+// All indicates that all fields and values should be included in processing
+// even if they can be elided or omitted.
+func All() Option {
+ return func(p *options) {
+ p.omitAttrs = false
+ p.omitHidden = false
+ p.omitDefinitions = false
+ p.omitOptional = false
+ }
+}
+
+// Docs indicates whether docs should be included.
+func Docs(include bool) Option {
+ return func(p *options) { p.docs = true }
+}
+
+// Definitions indicates whether definitions should be included.
+//
+// Definitions may still be included for certain functions if they are referred
+// to by other other values.
+func Definitions(include bool) Option {
+ return func(p *options) {
+ p.hasHidden = true
+ p.omitDefinitions = !include
+ }
+}
+
+// Hidden indicates that definitions and hidden fields should be included.
+//
+// Deprecated: Hidden fields are deprecated.
+func Hidden(include bool) Option {
+ return func(p *options) {
+ p.hasHidden = true
+ p.omitHidden = !include
+ p.omitDefinitions = !include
+ }
+}
+
+// Optional indicates that optional fields should be included.
+func Optional(include bool) Option {
+ return func(p *options) { p.omitOptional = !include }
+}
+
+// Attributes indicates that attributes should be included.
+func Attributes(include bool) Option {
+ return func(p *options) { p.omitAttrs = !include }
+}
+
+func getOptions(opts []Option) (o options) {
+ o.updateOptions(opts)
+ return
+}
+
+func (o *options) updateOptions(opts []Option) {
+ for _, fn := range opts {
+ fn(o)
+ }
+}
+
+// Validate reports any errors, recursively. The returned error may represent
+// more than one error, retrievable with errors.Errors, if more than one
+// exists.
+func (v Value) Validate(opts ...Option) error {
+ x := validator{}
+ o := options{}
+ o.updateOptions(opts)
+ // Logically, errors are always permitted in logical fields, so we
+ // force-disable them.
+ // TODO: consider whether we should honor the option to allow checking
+ // optional fields.
+ o.omitOptional = true
+ x.walk(v, o)
+ return errors.Sanitize(x.errs)
+}
+
+type validator struct {
+ errs errors.Error
+ depth int
+}
+
+func (x *validator) before(v Value, o options) bool {
+ if err := v.checkKind(v.ctx(), bottomKind); err != nil {
+ if !o.concrete && isIncomplete(err) {
+ if o.disallowCycles && err.Code == codeCycle {
+ x.errs = errors.Append(x.errs, v.toErr(err))
+ }
+ return false
+ }
+ x.errs = errors.Append(x.errs, v.toErr(err))
+ if len(errors.Errors(x.errs)) > 50 {
+ return false // mostly to avoid some hypothetical cycle issue
+ }
+ }
+ if o.concrete {
+ ctx := v.ctx()
+ if err := isGroundRecursive(ctx, v.eval(ctx)); err != nil {
+ x.errs = errors.Append(x.errs, v.toErr(err))
+ }
+ }
+ return true
+}
+
+func (x *validator) walk(v Value, opts options) {
+ // TODO(#42): we can get rid of the arbitrary evaluation depth once CUE has
+ // proper structural cycle detection. See Issue #42. Currently errors
+ // occurring at a depth > internal.MaxDepth will not be detected.
+ if x.depth > internal.MaxDepth {
+ return
+ }
+ ctx := v.ctx()
+ switch v.Kind() {
+ case StructKind:
+ if !x.before(v, opts) {
+ return
+ }
+ x.depth++
+ obj, err := v.structValOpts(ctx, opts)
+ if err != nil {
+ if !isIncomplete(err) && opts.concrete {
+ x.errs = errors.Append(x.errs, v.toErr(err))
+ }
+ }
+ for i := 0; i < obj.Len(); i++ {
+ _, v := obj.At(i)
+ opts := opts
+ if obj.Arcs[i].definition {
+ opts.concrete = false
+ }
+ x.walk(v, opts)
+ }
+ x.depth--
+
+ case ListKind:
+ if !x.before(v, opts) {
+ return
+ }
+ x.depth++
+ list, _ := v.List()
+ for list.Next() {
+ x.walk(list.Value(), opts)
+ }
+ x.depth--
+
+ default:
+ x.before(v, opts)
+ }
+}
+
+func isGroundRecursive(ctx *context, v value) *bottom {
+ switch x := v.(type) {
+ case *bottom:
+ if isIncomplete(x) {
+ return x
+ }
+ case *list:
+ for i := 0; i < len(x.elem.Arcs); i++ {
+ v := ctx.manifest(x.at(ctx, i))
+ if err := isGroundRecursive(ctx, v); err != nil {
+ return err
+ }
+ }
+ default:
+ if !x.Kind().isGround() {
+ return ctx.mkErr(v, "incomplete value (%v)", ctx.str(v))
+ }
+ }
+ return nil
+}
+
+// Walk descends into all values of v, calling f. If f returns false, Walk
+// will not descent further. It only visits values that are part of the data
+// model, so this excludes optional fields, hidden fields, and definitions.
+func (v Value) Walk(before func(Value) bool, after func(Value)) {
+ ctx := v.ctx()
+ switch v.Kind() {
+ case StructKind:
+ if before != nil && !before(v) {
+ return
+ }
+ obj, _ := v.structValData(ctx)
+ for i := 0; i < obj.Len(); i++ {
+ _, v := obj.At(i)
+ v.Walk(before, after)
+ }
+ case ListKind:
+ if before != nil && !before(v) {
+ return
+ }
+ list, _ := v.List()
+ for list.Next() {
+ list.Value().Walk(before, after)
+ }
+ default:
+ if before != nil {
+ before(v)
+ }
+ }
+ if after != nil {
+ after(v)
+ }
+}
+
+// Attribute returns the attribute data for the given key.
+// The returned attribute will return an error for any of its methods if there
+// is no attribute for the requested key.
+func (v Value) Attribute(key string) Attribute {
+ // look up the attributes
+ if v.v == nil || v.v.attrs == nil {
+ return Attribute{internal.NewNonExisting(key)}
+ }
+ for _, a := range v.v.attrs.attr {
+ if a.key() != key {
+ continue
+ }
+ return Attribute{internal.ParseAttrBody(token.NoPos, a.body())}
+ }
+ return Attribute{internal.NewNonExisting(key)}
+}
+
+// An Attribute contains meta data about a field.
+type Attribute struct {
+ attr internal.Attr
+}
+
+// Err returns the error associated with this Attribute or nil if this
+// attribute is valid.
+func (a *Attribute) Err() error {
+ return a.attr.Err
+}
+
+// String reports the possibly empty string value at the given position or
+// an error the attribute is invalid or if the position does not exist.
+func (a *Attribute) String(pos int) (string, error) {
+ return a.attr.String(pos)
+}
+
+// Int reports the integer at the given position or an error if the attribute is
+// invalid, the position does not exist, or the value at the given position is
+// not an integer.
+func (a *Attribute) Int(pos int) (int64, error) {
+ return a.attr.Int(pos)
+}
+
+// Flag reports whether an entry with the given name exists at position pos or
+// onwards or an error if the attribute is invalid or if the first pos-1 entries
+// are not defined.
+func (a *Attribute) Flag(pos int, key string) (bool, error) {
+ return a.attr.Flag(pos, key)
+}
+
+// Lookup searches for an entry of the form key=value from position pos onwards
+// and reports the value if found. It reports an error if the attribute is
+// invalid or if the first pos-1 entries are not defined.
+func (a *Attribute) Lookup(pos int, key string) (val string, found bool, err error) {
+ return a.attr.Lookup(pos, key)
+}
+
+// Expr reports the operation of the underlying expression and the values it
+// operates on.
+//
+// For unary expressions, it returns the single value of the expression.
+//
+// For binary expressions it returns first the left and right value, in that
+// order. For associative operations however, (for instance '&' and '|'), it may
+// return more than two values, where the operation is to be applied in
+// sequence.
+//
+// For selector and index expressions it returns the subject and then the index.
+// For selectors, the index is the string value of the identifier.
+//
+// For interpolations it returns a sequence of values to be concatenated, some
+// of which will be literal strings and some unevaluated expressions.
+//
+// A builtin call expression returns the value of the builtin followed by the
+// args of the call.
+func (v Value) Expr() (Op, []Value) {
+ // TODO: return v if this is complete? Yes for now
+ if v.v == nil {
+ return NoOp, nil
+ }
+ // TODO: replace appends with []Value{}. For not leave.
+ a := []Value{}
+ op := NoOp
+ switch x := v.v.v.(type) {
+ case *binaryExpr:
+ a = append(a, remakeValue(v, x.X))
+ a = append(a, remakeValue(v, x.Y))
+ op = opToOp[x.Op]
+ case *unaryExpr:
+ a = append(a, remakeValue(v, x.X))
+ op = opToOp[x.Op]
+ case *bound:
+ a = append(a, remakeValue(v, x.Expr))
+ op = opToOp[x.Op]
+ case *unification:
+ // pre-expanded unification
+ for _, conjunct := range x.Values {
+ a = append(a, remakeValue(v, conjunct))
+ }
+ op = AndOp
+ case *disjunction:
+ // Filter defaults that are subsumed by another value.
+ count := 0
+ outer:
+ for _, disjunct := range x.Values {
+ if disjunct.Default {
+ for _, n := range x.Values {
+ s := subsumer{ctx: v.ctx()}
+ if !n.Default && s.subsumes(n.Val, disjunct.Val) {
+ continue outer
+ }
+ }
+ }
+ count++
+ a = append(a, remakeValue(v, disjunct.Val))
+ }
+ if count > 1 {
+ op = OrOp
+ }
+ case *interpolation:
+ for _, p := range x.Parts {
+ a = append(a, remakeValue(v, p))
+ }
+ op = InterpolationOp
+ case *selectorExpr:
+ a = append(a, remakeValue(v, x.X))
+ a = append(a, remakeValue(v, &stringLit{
+ x.baseValue,
+ v.ctx().LabelStr(x.Sel),
+ nil,
+ }))
+ op = SelectorOp
+ case *indexExpr:
+ a = append(a, remakeValue(v, x.X))
+ a = append(a, remakeValue(v, x.Index))
+ op = IndexOp
+ case *sliceExpr:
+ a = append(a, remakeValue(v, x.X))
+ a = append(a, remakeValue(v, x.Lo))
+ a = append(a, remakeValue(v, x.Hi))
+ op = SliceOp
+ case *callExpr:
+ a = append(a, remakeValue(v, x.Fun))
+ for _, arg := range x.Args {
+ a = append(a, remakeValue(v, arg))
+ }
+ op = CallOp
+ case *customValidator:
+ a = append(a, remakeValue(v, x.Builtin))
+ for _, arg := range x.Args {
+ a = append(a, remakeValue(v, arg))
+ }
+ op = CallOp
+ default:
+ a = append(a, v)
+ }
+ return op, a
+}
diff --git a/internal/legacy/cue/types_test.go b/internal/legacy/cue/types_test.go
new file mode 100644
index 0000000..cea8a14
--- /dev/null
+++ b/internal/legacy/cue/types_test.go
@@ -0,0 +1,2783 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cue
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "math"
+ "math/big"
+ "reflect"
+ "strconv"
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/internal"
+)
+
+func getInstance(t *testing.T, body ...string) *Instance {
+ t.Helper()
+
+ insts := Build(makeInstances([]*bimport{{files: body}}))
+ if insts[0].Err != nil {
+ t.Fatalf("unexpected parse error: %v", insts[0].Err)
+ }
+ return insts[0]
+}
+
+func TestValueType(t *testing.T) {
+ testCases := []struct {
+ value string
+ kind Kind
+ incompleteKind Kind
+ json string
+ valid bool
+ concrete bool
+ closed bool
+ // pos token.Pos
+ }{{ // Not a concrete value.
+ value: `v: _`,
+ kind: BottomKind,
+ incompleteKind: TopKind,
+ }, {
+ value: `v: _|_`,
+ kind: BottomKind,
+ incompleteKind: BottomKind,
+ concrete: true,
+ }, {
+ value: `v: 1&2`,
+ kind: BottomKind,
+ incompleteKind: BottomKind,
+ concrete: true,
+ }, {
+ value: `v: b, b: 1&2`,
+ kind: BottomKind,
+ incompleteKind: BottomKind,
+ concrete: true,
+ }, {
+ value: `v: (b[a]), b: 1, a: 1`,
+ kind: BottomKind,
+ incompleteKind: BottomKind,
+ concrete: true,
+ }, { // TODO: should be error{
+ value: `v: (b)
+ b: bool`,
+ kind: BottomKind,
+ incompleteKind: BoolKind,
+ }, {
+ value: `v: ([][b]), b: "d"`,
+ kind: BottomKind,
+ incompleteKind: BottomKind,
+ concrete: true,
+ }, {
+ value: `v: null`,
+ kind: NullKind,
+ incompleteKind: NullKind,
+ concrete: true,
+ }, {
+ value: `v: true`,
+ kind: BoolKind,
+ incompleteKind: BoolKind,
+ concrete: true,
+ }, {
+ value: `v: false`,
+ kind: BoolKind,
+ incompleteKind: BoolKind,
+ concrete: true,
+ }, {
+ value: `v: bool`,
+ kind: BottomKind,
+ incompleteKind: BoolKind,
+ }, {
+ value: `v: 2`,
+ kind: IntKind,
+ incompleteKind: IntKind,
+ concrete: true,
+ }, {
+ value: `v: 2.0`,
+ kind: FloatKind,
+ incompleteKind: FloatKind,
+ concrete: true,
+ }, {
+ value: `v: 2.0Mi`,
+ kind: IntKind,
+ incompleteKind: IntKind,
+ concrete: true,
+ }, {
+ value: `v: 14_000`,
+ kind: IntKind,
+ incompleteKind: IntKind,
+ concrete: true,
+ }, {
+ value: `v: >=0 & <5`,
+ kind: BottomKind,
+ incompleteKind: NumberKind,
+ }, {
+ value: `v: float`,
+ kind: BottomKind,
+ incompleteKind: FloatKind,
+ }, {
+ value: `v: "str"`,
+ kind: StringKind,
+ incompleteKind: StringKind,
+ concrete: true,
+ }, {
+ value: "v: '''\n'''",
+ kind: BytesKind,
+ incompleteKind: BytesKind,
+ concrete: true,
+ }, {
+ value: "v: string",
+ kind: BottomKind,
+ incompleteKind: StringKind,
+ }, {
+ value: `v: {}`,
+ kind: StructKind,
+ incompleteKind: StructKind,
+ concrete: true,
+ }, {
+ value: `v: close({})`,
+ kind: StructKind,
+ incompleteKind: StructKind,
+ concrete: true,
+ closed: true,
+ }, {
+ value: `v: []`,
+ kind: ListKind,
+ incompleteKind: ListKind,
+ concrete: true,
+ closed: true,
+ }, {
+ value: `v: [...int]`,
+ kind: BottomKind,
+ incompleteKind: ListKind,
+ concrete: false,
+ }, {
+ value: `v: {a: int, b: [1][a]}.b`,
+ kind: BottomKind,
+ concrete: false,
+ }, {
+ value: `import "time"
+ v: {a: time.Time}.a`,
+ kind: BottomKind,
+ incompleteKind: StringKind,
+ concrete: false,
+ }, {
+ value: `import "time"
+ v: {a: time.Time & string}.a`,
+ kind: BottomKind,
+ incompleteKind: StringKind,
+ concrete: false,
+ }, {
+ value: `import "strings"
+ v: {a: strings.ContainsAny("D")}.a`,
+ kind: BottomKind,
+ incompleteKind: StringKind,
+ concrete: false,
+ }, {
+ value: `import "struct"
+ v: {a: struct.MaxFields(2) & {}}.a`,
+ kind: StructKind, // Can determine a valid struct already.
+ incompleteKind: StructKind,
+ concrete: true,
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.value, func(t *testing.T) {
+ inst := getInstance(t, tc.value)
+ v := inst.Lookup("v")
+ if got := v.Kind(); got != tc.kind {
+ t.Errorf("Kind: got %x; want %x", int(got), int(tc.kind))
+ }
+ want := tc.incompleteKind | BottomKind
+ if got := v.IncompleteKind(); got != want {
+ t.Errorf("IncompleteKind: got %x; want %x", int(got), int(want))
+ }
+ if got := v.IsConcrete(); got != tc.concrete {
+ t.Errorf("IsConcrete: got %v; want %v", got, tc.concrete)
+ }
+ if got := v.IsClosed(); got != tc.closed {
+ t.Errorf("IsClosed: got %v; want %v", got, tc.closed)
+ }
+ })
+ }
+}
+
+func TestInt(t *testing.T) {
+ testCases := []struct {
+ value string
+ int int64
+ uint uint64
+ base int
+ err string
+ errU string
+ notInt bool
+ }{{
+ value: "1",
+ int: 1,
+ uint: 1,
+ }, {
+ value: "-1",
+ int: -1,
+ uint: 0,
+ errU: ErrAbove.Error(),
+ }, {
+ value: "-111222333444555666777888999000",
+ int: math.MinInt64,
+ uint: 0,
+ err: ErrAbove.Error(),
+ errU: ErrAbove.Error(),
+ }, {
+ value: "111222333444555666777888999000",
+ int: math.MaxInt64,
+ uint: math.MaxUint64,
+ err: ErrBelow.Error(),
+ errU: ErrBelow.Error(),
+ }, {
+ value: "1.0",
+ err: "cannot use value 1.0 (type float) as int",
+ errU: "cannot use value 1.0 (type float) as int",
+ notInt: true,
+ }, {
+ value: "int",
+ err: "non-concrete value int",
+ errU: "non-concrete value int",
+ notInt: true,
+ }, {
+ value: "_|_",
+ err: "from source",
+ errU: "from source",
+ notInt: true,
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.value, func(t *testing.T) {
+ n := getInstance(t, tc.value).Value()
+ base := 10
+ if tc.base > 0 {
+ base = tc.base
+ }
+ b, err := n.AppendInt(nil, base)
+ if checkFailed(t, err, tc.err, "append") {
+ want := tc.value
+ if got := string(b); got != want {
+ t.Errorf("append: got %v; want %v", got, want)
+ }
+ }
+
+ vi, err := n.Int64()
+ checkErr(t, err, tc.err, "Int64")
+ if vi != tc.int {
+ t.Errorf("Int64: got %v; want %v", vi, tc.int)
+ }
+
+ vu, err := n.Uint64()
+ checkErr(t, err, tc.errU, "Uint64")
+ if vu != uint64(tc.uint) {
+ t.Errorf("Uint64: got %v; want %v", vu, tc.uint)
+ }
+ })
+ }
+}
+
+func TestFloat(t *testing.T) {
+ testCases := []struct {
+ value string
+ float string
+ float64 float64
+ mant string
+ exp int
+ fmt byte
+ prec int
+ kind Kind
+ err string
+ }{{
+ value: "1",
+ float: "1",
+ mant: "1",
+ exp: 0,
+ float64: 1,
+ fmt: 'g',
+ kind: IntKind,
+ }, {
+ value: "-1",
+ float: "-1",
+ mant: "-1",
+ exp: 0,
+ float64: -1,
+ fmt: 'g',
+ kind: IntKind,
+ }, {
+ value: "1.0",
+ float: "1.0",
+ mant: "10",
+ exp: -1,
+ float64: 1.0,
+ fmt: 'g',
+ kind: FloatKind,
+ }, {
+ value: "2.6",
+ float: "2.6",
+ mant: "26",
+ exp: -1,
+ float64: 2.6,
+ fmt: 'g',
+ kind: FloatKind,
+ }, {
+ value: "20.600",
+ float: "20.60",
+ mant: "20600",
+ exp: -3,
+ float64: 20.60,
+ prec: 2,
+ fmt: 'f',
+ kind: FloatKind,
+ }, {
+ value: "1/0",
+ float: "",
+ float64: 0,
+ prec: 2,
+ fmt: 'f',
+ err: "division by zero",
+ kind: BottomKind,
+ }, {
+ value: "1.797693134862315708145274237317043567982e+308",
+ float: "1.8e+308",
+ mant: "1797693134862315708145274237317043567982",
+ exp: 269,
+ float64: math.Inf(1),
+ prec: 2,
+ fmt: 'g',
+ err: ErrAbove.Error(),
+ kind: FloatKind,
+ }, {
+ value: "-1.797693134862315708145274237317043567982e+308",
+ float: "-1.8e+308",
+ mant: "-1797693134862315708145274237317043567982",
+ exp: 269,
+ float64: math.Inf(-1),
+ prec: 2,
+ fmt: 'g',
+ kind: FloatKind,
+ err: ErrBelow.Error(),
+ }, {
+ value: "4.940656458412465441765687928682213723650e-324",
+ float: "4.941e-324",
+ mant: "4940656458412465441765687928682213723650",
+ exp: -363,
+ float64: 0,
+ prec: 4,
+ fmt: 'g',
+ kind: FloatKind,
+ err: ErrBelow.Error(),
+ }, {
+ value: "-4.940656458412465441765687928682213723650e-324",
+ float: "-4.940656458412465441765687928682213723650e-324",
+ mant: "-4940656458412465441765687928682213723650",
+ exp: -363,
+ float64: 0,
+ prec: -1,
+ fmt: 'g',
+ kind: FloatKind,
+ err: ErrAbove.Error(),
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.value, func(t *testing.T) {
+ n := getInstance(t, tc.value).Value()
+ if n.Kind() != tc.kind {
+ t.Fatal("Not a number")
+ }
+
+ var mant big.Int
+ exp, err := n.MantExp(&mant)
+ mstr := ""
+ if err == nil {
+ mstr = mant.String()
+ }
+ if exp != tc.exp || mstr != tc.mant {
+ t.Errorf("mantExp: got %s %d; want %s %d", mstr, exp, tc.mant, tc.exp)
+ }
+
+ b, _ := n.AppendFloat(nil, tc.fmt, tc.prec)
+ want := tc.float
+ if got := string(b); got != want {
+ t.Errorf("append: got %v; want %v", got, want)
+ }
+
+ f, err := n.Float64()
+ checkErr(t, err, tc.err, "Float64")
+ if f != tc.float64 {
+ t.Errorf("Float64: got %v; want %v", f, tc.float64)
+ }
+ })
+ }
+}
+
+func TestString(t *testing.T) {
+ testCases := []struct {
+ value string
+ str string
+ err string
+ }{{
+ value: `""`,
+ str: ``,
+ }, {
+ value: `"Hello world!"`,
+ str: `Hello world!`,
+ }, {
+ value: `"Hello \(#world)!"
+ #world: "world"`,
+ str: `Hello world!`,
+ }, {
+ value: `string`,
+ err: "non-concrete value string",
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.value, func(t *testing.T) {
+ str, err := getInstance(t, tc.value).Value().String()
+ checkFatal(t, err, tc.err, "init")
+ if str != tc.str {
+ t.Errorf("String: got %q; want %q", str, tc.str)
+ }
+
+ b, err := getInstance(t, tc.value).Value().Bytes()
+ checkFatal(t, err, tc.err, "init")
+ if got := string(b); got != tc.str {
+ t.Errorf("Bytes: got %q; want %q", got, tc.str)
+ }
+
+ r, err := getInstance(t, tc.value).Value().Reader()
+ checkFatal(t, err, tc.err, "init")
+ b, _ = ioutil.ReadAll(r)
+ if got := string(b); got != tc.str {
+ t.Errorf("Reader: got %q; want %q", got, tc.str)
+ }
+ })
+ }
+}
+
+func TestError(t *testing.T) {
+ testCases := []struct {
+ value string
+ err string
+ }{{
+ value: `_|_`,
+ err: "from source",
+ }, {
+ value: `"Hello world!"`,
+ }, {
+ value: `string`,
+ err: "",
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.value, func(t *testing.T) {
+ err := getInstance(t, tc.value).Value().Err()
+ checkErr(t, err, tc.err, "init")
+ })
+ }
+}
+
+func TestNull(t *testing.T) {
+ testCases := []struct {
+ value string
+ err string
+ }{{
+ value: `v: _|_`,
+ err: "from source",
+ }, {
+ value: `v: "str"`,
+ err: "cannot use value \"str\" (type string) as null",
+ }, {
+ value: `v: null`,
+ }, {
+ value: `v: _`,
+ err: "non-concrete value _",
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.value, func(t *testing.T) {
+ err := getInstance(t, tc.value).Lookup("v").Null()
+ checkErr(t, err, tc.err, "init")
+ })
+ }
+}
+
+func TestBool(t *testing.T) {
+ testCases := []struct {
+ value string
+ bool bool
+ err string
+ }{{
+ value: `_|_`,
+ err: "from source",
+ }, {
+ value: `"str"`,
+ err: "cannot use value \"str\" (type string) as bool",
+ }, {
+ value: `true`,
+ bool: true,
+ }, {
+ value: `false`,
+ }, {
+ value: `bool`,
+ err: "non-concrete value bool",
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.value, func(t *testing.T) {
+ got, err := getInstance(t, tc.value).Value().Bool()
+ if checkErr(t, err, tc.err, "init") {
+ if got != tc.bool {
+ t.Errorf("got %v; want %v", got, tc.bool)
+ }
+ }
+ })
+ }
+}
+
+func TestList(t *testing.T) {
+ testCases := []struct {
+ value string
+ res string
+ err string
+ }{{
+ value: `_|_`,
+ err: "from source",
+ }, {
+ value: `"str"`,
+ err: "cannot use value \"str\" (type string) as list",
+ }, {
+ value: `[]`,
+ res: "[]",
+ }, {
+ value: `[1,2,3]`,
+ res: "[1,2,3,]",
+ }, {
+ value: `>=5*[1,2,3, ...int]`,
+ err: "incomplete",
+ }, {
+ value: `[for x in #y if x > 1 { x }]
+ #y: [1,2,3]`,
+ res: "[2,3,]",
+ }, {
+ value: `[int]`,
+ err: "cannot convert incomplete value",
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.value, func(t *testing.T) {
+ l, err := getInstance(t, tc.value).Value().List()
+ checkFatal(t, err, tc.err, "init")
+
+ buf := []byte{'['}
+ for l.Next() {
+ b, err := l.Value().MarshalJSON()
+ checkFatal(t, err, tc.err, "list.Value")
+ buf = append(buf, b...)
+ buf = append(buf, ',')
+ }
+ buf = append(buf, ']')
+ if got := string(buf); got != tc.res {
+ t.Errorf("got %v; want %v", got, tc.res)
+ }
+ })
+ }
+}
+
+func TestFields(t *testing.T) {
+ testCases := []struct {
+ value string
+ res string
+ err string
+ }{{
+ value: `_|_`,
+ err: "from source",
+ }, {
+ value: `"str"`,
+ err: "cannot use value \"str\" (type string) as struct",
+ }, {
+ value: `{}`,
+ res: "{}",
+ }, {
+ value: `{a:1,b:2,c:3}`,
+ res: "{a:1,b:2,c:3,}",
+ }, {
+ value: `{a:1,"_b":2,c:3,_d:4}`,
+ res: "{a:1,_b:2,c:3,}",
+ }, {
+ value: `{_a:"a"}`,
+ res: "{}",
+ }, {
+ value: `{ for k, v in #y if v > 1 {"\(k)": v} }
+ #y: {a:1,b:2,c:3}`,
+ res: "{b:2,c:3,}",
+ }, {
+ value: `{ #def: 1, _hidden: 2, opt?: 3, reg: 4 }`,
+ res: "{reg:4,}",
+ }, {
+ value: `{a:1,b:2,c:int}`,
+ err: "cannot convert incomplete value",
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.value, func(t *testing.T) {
+ obj := getInstance(t, tc.value).Value()
+
+ iter, err := obj.Fields()
+ checkFatal(t, err, tc.err, "init")
+
+ buf := []byte{'{'}
+ for iter.Next() {
+ buf = append(buf, iter.Label()...)
+ buf = append(buf, ':')
+ b, err := iter.Value().MarshalJSON()
+ checkFatal(t, err, tc.err, "Obj.At")
+ buf = append(buf, b...)
+ buf = append(buf, ',')
+ }
+ buf = append(buf, '}')
+ if got := string(buf); got != tc.res {
+ t.Errorf("got %v; want %v", got, tc.res)
+ }
+
+ iter, _ = obj.Fields()
+ for iter.Next() {
+ want, err := iter.Value().MarshalJSON()
+ checkFatal(t, err, tc.err, "Obj.At2")
+
+ got, err := obj.Lookup(iter.Label()).MarshalJSON()
+ checkFatal(t, err, tc.err, "Obj.At2")
+
+ if !bytes.Equal(got, want) {
+ t.Errorf("Lookup: got %q; want %q", got, want)
+ }
+ }
+ v := obj.Lookup("non-existing")
+ checkErr(t, v.Err(), "not found", "non-existing")
+ })
+ }
+}
+
+func TestAllFields(t *testing.T) {
+ testCases := []struct {
+ value string
+ res string
+ err string
+ }{{
+ value: `{a:1,"_b":2,c:3,_d:4}`,
+ res: "{a:1,_b:2,c:3,_d:4,}",
+ }, {
+ value: `{_a:"a"}`,
+ res: `{_a:"a",}`,
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.value, func(t *testing.T) {
+ obj := getInstance(t, tc.value).Value()
+
+ var iter *Iterator // Verify that the returned iterator is a pointer.
+ iter, err := obj.Fields(All())
+ checkFatal(t, err, tc.err, "init")
+
+ buf := []byte{'{'}
+ for iter.Next() {
+ buf = append(buf, iter.Label()...)
+ buf = append(buf, ':')
+ b, err := iter.Value().MarshalJSON()
+ checkFatal(t, err, tc.err, "Obj.At")
+ buf = append(buf, b...)
+ buf = append(buf, ',')
+ }
+ buf = append(buf, '}')
+ if got := string(buf); got != tc.res {
+ t.Errorf("got %v; want %v", got, tc.res)
+ }
+ })
+ }
+}
+
+func TestLookup(t *testing.T) {
+ var runtime = new(Runtime)
+ inst, err := runtime.Compile("x.cue", `
+#V: {
+ x: int
+}
+#X: {
+ [string]: int64
+} & #V
+v: #X
+`)
+ if err != nil {
+ t.Fatalf("compile: %v", err)
+ }
+ // expr, err := parser.ParseExpr("lookup.cue", `v`, parser.DeclarationErrors, parser.AllErrors)
+ // if err != nil {
+ // log.Fatalf("parseExpr: %v", err)
+ // }
+ // v := inst.Eval(expr)
+ testCases := []struct {
+ ref []string
+ raw string
+ eval string
+ }{{
+ ref: []string{"v", "x"},
+ raw: "(int & <=9223372036854775807 & int & >=-9223372036854775808)",
+ eval: "int64",
+ }}
+ for _, tc := range testCases {
+ v := inst.Lookup(tc.ref...)
+
+ if got := fmt.Sprint(v); got != tc.raw {
+ t.Errorf("got %v; want %v", got, tc.raw)
+ }
+
+ got := fmt.Sprint(internal.DebugStr(v.Eval().Syntax()))
+ if got != tc.eval {
+ t.Errorf("got %v; want %v", got, tc.eval)
+ }
+
+ v = inst.Lookup()
+ for _, ref := range tc.ref {
+ s, err := v.Struct()
+ if err != nil {
+ t.Fatal(err)
+ }
+ fi, err := s.FieldByName(ref, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ v = fi.Value
+ }
+
+ if got := fmt.Sprint(v); got != tc.raw {
+ t.Errorf("got %v; want %v", got, tc.raw)
+ }
+
+ got = fmt.Sprint(internal.DebugStr(v.Eval().Syntax()))
+ if got != tc.eval {
+ t.Errorf("got %v; want %v", got, tc.eval)
+ }
+ }
+}
+
+func compileT(t *testing.T, r *Runtime, s string) *Instance {
+ t.Helper()
+ inst, err := r.Compile("", s)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return inst
+}
+
+func goValue(v Value) interface{} {
+ var x interface{}
+ err := v.Decode(&x)
+ if err != nil {
+ return err
+ }
+ return x
+}
+
+func TestFill(t *testing.T) {
+ r := &Runtime{}
+
+ inst, err := r.CompileExpr(ast.NewStruct("bar", ast.NewString("baz")))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ testCases := []struct {
+ in string
+ x interface{}
+ path string // comma-separated path
+ out string
+ }{{
+ in: `
+ foo: int
+ bar: foo
+ `,
+ x: 3,
+ path: "foo",
+ out: `
+ foo: 3
+ bar: 3
+ `,
+ }, {
+ in: `
+ string
+ `,
+ x: "foo",
+ path: "",
+ out: `
+ "foo"
+ `,
+ }, {
+ in: `
+ foo: _
+ `,
+ x: inst.Value(),
+ path: "foo",
+ out: `
+ {foo: {bar: "baz"}}
+ `,
+ }}
+
+ for _, tc := range testCases {
+ var path []string
+ if tc.path != "" {
+ path = strings.Split(tc.path, ",")
+ }
+
+ v := compileT(t, r, tc.in).Value().Fill(tc.x, path...)
+ w := compileT(t, r, tc.out).Value()
+
+ if !reflect.DeepEqual(goValue(v), goValue(w)) {
+ t.Errorf("\ngot: %s\nwant: %s", v, w)
+ }
+ }
+}
+
+func TestFill2(t *testing.T) {
+ r := &Runtime{}
+
+ root, err := r.Compile("test", `
+ #Provider: {
+ ID: string
+ notConcrete: bool
+ }
+ `)
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ spec := root.LookupDef("#Provider")
+ providerInstance := spec.Fill("12345", "ID")
+ root, err = root.Fill(providerInstance, "providers", "myprovider")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ got := fmt.Sprint(root.Value())
+
+ if got != `{#Provider: C{ID: string, notConcrete: bool}, providers: {myprovider: C{ID: (string & "12345"), notConcrete: bool}}}` {
+ t.Error(got)
+ }
+}
+
+func TestValue_LookupDef(t *testing.T) {
+ r := &Runtime{}
+
+ testCases := []struct {
+ in string
+ def string // comma-separated path
+ exists bool
+ out string
+ }{{
+ in: `#foo: 3`,
+ def: "#foo",
+ out: `3`,
+ }, {
+ in: `_foo: 3`,
+ def: "_foo",
+ out: `_|_(definition "_foo" not found)`,
+ }, {
+ in: `_#foo: 3`,
+ def: "_#foo",
+ out: `_|_(definition "_#foo" not found)`,
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.def, func(t *testing.T) {
+ v := compileT(t, r, tc.in).Value()
+ v = v.LookupDef(tc.def)
+ got := fmt.Sprint(v)
+
+ if got != tc.out {
+ t.Errorf("\ngot: %s\nwant: %s", got, tc.out)
+ }
+ })
+ }
+}
+
+func TestDefaults(t *testing.T) {
+ testCases := []struct {
+ value string
+ def string
+ val string
+ ok bool
+ }{{
+ value: `number | *1`,
+ def: "1",
+ val: "number",
+ ok: true,
+ }, {
+ value: `1 | 2 | *3`,
+ def: "3",
+ val: "1|2|3",
+ ok: true,
+ }, {
+ value: `*{a:1,b:2}|{a:1}|{b:2}`,
+ def: "{a: 1, b: 2}",
+ val: "{a: 1}|{b: 2}",
+ ok: true,
+ }, {
+ value: `{a:1}&{b:2}`,
+ def: `{a: 1, b: 2}`,
+ val: ``,
+ ok: false,
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.value, func(t *testing.T) {
+ v := getInstance(t, "a: "+tc.value).Lookup("a")
+
+ d, ok := v.Default()
+ if ok != tc.ok {
+ t.Errorf("hasDefault: got %v; want %v", ok, tc.ok)
+ }
+
+ if got := fmt.Sprint(d); got != tc.def {
+ t.Errorf("default: got %v; want %v", got, tc.def)
+ }
+
+ op, val := v.Expr()
+ if op != OrOp {
+ return
+ }
+ vars := []string{}
+ for _, v := range val {
+ vars = append(vars, fmt.Sprint(v))
+ }
+ if got := strings.Join(vars, "|"); got != tc.val {
+ t.Errorf("value: got %v; want %v", got, tc.val)
+ }
+ })
+ }
+}
+
+func TestLen(t *testing.T) {
+ testCases := []struct {
+ input string
+ length string
+ }{{
+ input: "[1, 3]",
+ length: "2",
+ }, {
+ input: "[1, 3, ...]",
+ length: "int & >=2",
+ }, {
+ input: `"foo"`,
+ length: "3",
+ }, {
+ input: `'foo'`,
+ length: "3",
+ // TODO: Currently not supported.
+ // }, {
+ // input: "{a:1, b:3, a:1, c?: 3, _hidden: 4}",
+ // length: "2",
+ }, {
+ input: "3",
+ length: "_|_(len not supported for type int)",
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.input, func(t *testing.T) {
+ v := getInstance(t, "a: "+tc.input).Lookup("a")
+
+ length := v.Len()
+ if got := fmt.Sprint(length); got != tc.length {
+ t.Errorf("length: got %v; want %v", got, tc.length)
+ }
+ })
+ }
+}
+
+func TestTemplate(t *testing.T) {
+ testCases := []struct {
+ value string
+ path []string
+ want string
+ }{{
+ value: `
+ a: [Name=string]: Name
+ `,
+ path: []string{"a", ""},
+ want: `"label"`,
+ }, {
+ value: `
+ [Name=string]: { a: Name }
+ `,
+ path: []string{"", "a"},
+ want: `"label"`,
+ }, {
+ value: `
+ [Name=string]: { a: Name }
+ `,
+ path: []string{""},
+ want: `{"a":"label"}`,
+ }, {
+ value: `
+ a: [Foo=string]: [Bar=string]: { b: Foo+Bar }
+ `,
+ path: []string{"a", "", ""},
+ want: `{"b":"labellabel"}`,
+ }, {
+ value: `
+ a: [Foo=string]: b: [Bar=string]: { c: Foo+Bar }
+ a: foo: b: [Bar=string]: { d: Bar }
+ `,
+ path: []string{"a", "foo", "b", ""},
+ want: `{"c":"foolabel","d":"label"}`,
+ }}
+ for _, tc := range testCases {
+ t.Run("", func(t *testing.T) {
+ v := getInstance(t, tc.value).Value()
+ for _, p := range tc.path {
+ if p == "" {
+ v = v.Template()("label")
+ } else {
+ v = v.Lookup(p)
+ }
+ }
+ b, err := v.MarshalJSON()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got := string(b); got != tc.want {
+ t.Errorf("\n got: %q\nwant: %q", got, tc.want)
+ }
+ })
+ }
+}
+
+func TestSubsumes(t *testing.T) {
+ a := []string{"a"}
+ b := []string{"b"}
+ testCases := []struct {
+ value string
+ pathA []string
+ pathB []string
+ want bool
+ }{{
+ value: `4`,
+ want: true,
+ }, {
+ value: `a: string, b: "foo"`,
+ pathA: a,
+ pathB: b,
+ want: true,
+ }, {
+ value: `a: string, b: "foo"`,
+ pathA: b,
+ pathB: a,
+ want: false,
+ }, {
+ value: `a: {a: string, b: 4}, b: {a: "foo", b: 4}`,
+ pathA: a,
+ pathB: b,
+ want: true,
+ }, {
+ value: `a: [string, 4], b: ["foo", 4]`,
+ pathA: a,
+ pathB: b,
+ want: true,
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.value, func(t *testing.T) {
+ v := getInstance(t, tc.value)
+ a := v.Lookup(tc.pathA...)
+ b := v.Lookup(tc.pathB...)
+ got := a.Subsumes(b)
+ if got != tc.want {
+ t.Errorf("got %v (%v); want %v (%v)", got, a, tc.want, b)
+ }
+ })
+ }
+}
+
+func TestUnify(t *testing.T) {
+ a := []string{"a"}
+ b := []string{"b"}
+ testCases := []struct {
+ value string
+ pathA []string
+ pathB []string
+ want string
+ }{{
+ value: `4`,
+ want: `4`,
+ }, {
+ value: `a: string, b: "foo"`,
+ pathA: a,
+ pathB: b,
+ want: `"foo"`,
+ }, {
+ value: `a: string, b: "foo"`,
+ pathA: b,
+ pathB: a,
+ want: `"foo"`,
+ }, {
+ value: `a: {a: string, b: 4}, b: {a: "foo", b: 4}`,
+ pathA: a,
+ pathB: b,
+ want: `{"a":"foo","b":4}`,
+ }, {
+ value: `a: [string, 4], b: ["foo", 4]`,
+ pathA: a,
+ pathB: b,
+ want: `["foo",4]`,
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.value, func(t *testing.T) {
+ v := getInstance(t, tc.value).Value()
+ x := v.Lookup(tc.pathA...)
+ y := v.Lookup(tc.pathB...)
+ b, err := x.Unify(y).MarshalJSON()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got := string(b); got != tc.want {
+ t.Errorf("got %v; want %v", got, tc.want)
+ }
+ })
+ }
+}
+
+func TestEquals(t *testing.T) {
+ testCases := []struct {
+ a, b string
+ want bool
+ }{{
+ `4`, `4`, true,
+ }, {
+ `"str"`, `2`, false,
+ }, {
+ `2`, `3`, false,
+ }, {
+ `[1]`, `[3]`, false,
+ }, {
+ `[{a: 1,...}]`, `[{a: 1,...}]`, true,
+ }, {
+ `[]`, `[]`, true,
+ }, {
+ `{
+ a: b,
+ b: a,
+ }`,
+ `{
+ a: b,
+ b: a,
+ }`,
+ true,
+ }, {
+ `{
+ a: "foo",
+ b: "bar",
+ }`,
+ `{
+ a: "foo",
+ }`,
+ false,
+ }}
+ for _, tc := range testCases {
+ t.Run("", func(t *testing.T) {
+ var r Runtime
+ a, err := r.Compile("a", tc.a)
+ if err != nil {
+ t.Fatal(err)
+ }
+ b, err := r.Compile("b", tc.b)
+ if err != nil {
+ t.Fatal(err)
+ }
+ got := a.Value().Equals(b.Value())
+ if got != tc.want {
+ t.Errorf("got %v; want %v", got, tc.want)
+ }
+ })
+ }
+}
+
+func TestDecode(t *testing.T) {
+ type fields struct {
+ A int `json:"A"`
+ B int `json:"B"`
+ C int `json:"C"`
+ }
+ intList := func(ints ...int) *[]int {
+ ints = append([]int{}, ints...)
+ return &ints
+ }
+ testCases := []struct {
+ value string
+ dst interface{}
+ want interface{}
+ err string
+ }{{
+ value: `_|_`,
+ err: "from source",
+ }, {
+ value: `"str"`,
+ dst: new(string),
+ want: "str",
+ }, {
+ value: `"str"`,
+ dst: new(int),
+ err: "cannot unmarshal string into Go value of type int",
+ }, {
+ value: `{}`,
+ dst: &fields{},
+ want: fields{},
+ }, {
+ value: `{a:1,b:2,c:3}`,
+ dst: &fields{},
+ want: fields{A: 1, B: 2, C: 3},
+ }, {
+ value: `{for k, v in y if v > 1 {"\(k)": v} }
+ y: {a:1,b:2,c:3}`,
+ dst: &fields{},
+ want: fields{B: 2, C: 3},
+ }, {
+ value: `{a:1,b:2,c:int}`,
+ dst: new(fields),
+ err: "cannot convert incomplete value",
+ }, {
+ value: `[]`,
+ dst: intList(),
+ want: *intList(),
+ }, {
+ value: `[1,2,3]`,
+ dst: intList(),
+ want: *intList(1, 2, 3),
+ }, {
+ value: `[for x in #y if x > 1 { x }]
+ #y: [1,2,3]`,
+ dst: intList(),
+ want: *intList(2, 3),
+ }, {
+ value: `[int]`,
+ err: "cannot convert incomplete value",
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.value, func(t *testing.T) {
+ err := getInstance(t, tc.value).Value().Decode(tc.dst)
+ checkFatal(t, err, tc.err, "init")
+
+ got := reflect.ValueOf(tc.dst).Elem().Interface()
+ if !cmp.Equal(got, tc.want) {
+ t.Error(cmp.Diff(got, tc.want))
+ t.Errorf("\n%#v\n%#v", got, tc.want)
+ }
+ })
+ }
+}
+
+func TestValidate(t *testing.T) {
+ testCases := []struct {
+ desc string
+ in string
+ err bool
+ opts []Option
+ }{{
+ desc: "issue #51",
+ in: `
+ a: [string]: foo
+ a: b: {}
+ `,
+ err: true,
+ }, {
+ desc: "concrete",
+ in: `
+ a: 1
+ b: { c: 2, d: 3 }
+ c: d: e: f: 5
+ g?: int
+ `,
+ opts: []Option{Concrete(true)},
+ }, {
+ desc: "definition error",
+ in: `
+ #b: 1 & 2
+ `,
+ opts: []Option{},
+ err: true,
+ }, {
+ desc: "definition error okay if optional",
+ in: `
+ #b?: 1 & 2
+ `,
+ opts: []Option{},
+ }, {
+ desc: "definition with optional",
+ in: `
+ #b: {
+ a: int
+ b?: >=0
+ }
+ `,
+ opts: []Option{Concrete(true)},
+ }, {
+ desc: "disjunction",
+ in: `a: 1 | 2`,
+ }, {
+ desc: "disjunction concrete",
+ in: `a: 1 | 2`,
+ opts: []Option{Concrete(true)},
+ err: true,
+ }, {
+ desc: "incomplete concrete",
+ in: `a: string`,
+ }, {
+ desc: "incomplete",
+ in: `a: string`,
+ opts: []Option{Concrete(true)},
+ err: true,
+ }, {
+ desc: "list",
+ in: `a: [{b: string}, 3]`,
+ }, {
+ desc: "list concrete",
+ in: `a: [{b: string}, 3]`,
+ opts: []Option{Concrete(true)},
+ err: true,
+ }, {
+ desc: "allow cycles",
+ in: `
+ a: b - 100
+ b: a + 100
+ c: [c[1], c[0]]
+ `,
+ }, {
+ desc: "disallow cycles",
+ in: `
+ a: b - 100
+ b: a + 100
+ c: [c[1], c[0]]
+ `,
+ opts: []Option{DisallowCycles(true)},
+ err: true,
+ }, {
+ desc: "builtins are okay",
+ in: `
+ import "time"
+
+ a: { b: time.Duration } | { c: time.Duration }
+ `,
+ }, {
+ desc: "comprehension error",
+ in: `
+ a: { if b == "foo" { field: 2 } }
+ `,
+ err: true,
+ }, {
+ desc: "ignore optional in schema",
+ in: `
+ #Schema1: {
+ a?: int
+ }
+ instance1: #Schema1
+ `,
+ opts: []Option{Concrete(true)},
+ }, {
+ desc: "issue324",
+ in: `
+ import "encoding/yaml"
+
+ x: string
+ a: b: c: *["\(x)"] | _
+ d: yaml.Marshal(a.b)
+ `,
+ }, {
+ desc: "allow non-concrete values for definitions",
+ in: `
+ variables: #variables
+
+ {[!~"^[.]"]: #job}
+
+ #variables: [string]: int | string
+
+ #job: ({a: int} | {b: int}) & {
+ "variables"?: #variables
+ }
+ `,
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ r := Runtime{}
+ inst, err := r.Parse("validate", tc.in)
+ if err == nil {
+ err = inst.Value().Validate(tc.opts...)
+ }
+ if gotErr := err != nil; gotErr != tc.err {
+ t.Errorf("got %v; want %v", err, tc.err)
+ }
+ })
+ }
+}
+
+func TestPath(t *testing.T) {
+ config := `
+ a: b: c: 5
+ b: {
+ b1: 3
+ b2: 4
+ "b 3": 5
+ "4b": 6
+ l: [
+ {a: 2},
+ {c: 2},
+ ]
+ }
+ `
+ mkpath := func(p ...string) []string { return p }
+ testCases := [][]string{
+ mkpath("a", "b", "c"),
+ mkpath("b", "l", "1", "c"),
+ mkpath("b", `"b 3"`),
+ mkpath("b", `"4b"`),
+ }
+ for _, tc := range testCases {
+ r := Runtime{}
+ inst, err := r.Parse("config", config)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Run(strings.Join(tc, "."), func(t *testing.T) {
+ v := inst.Lookup(tc[0])
+ for _, e := range tc[1:] {
+ if '0' <= e[0] && e[0] <= '9' {
+ i, err := strconv.Atoi(e)
+ if err != nil {
+ t.Fatal(err)
+ }
+ iter, err := v.List()
+ if err != nil {
+ t.Fatal(err)
+ }
+ for c := 0; iter.Next(); c++ {
+ if c == i {
+ v = iter.Value()
+ break
+ }
+ }
+ } else if e[0] == '"' {
+ v = v.Lookup(e[1 : len(e)-1])
+ } else {
+ v = v.Lookup(e)
+ }
+ }
+ got, _ := v.v.appendPath(nil, v.idx)
+ if !reflect.DeepEqual(got, tc) {
+ t.Errorf("got %v; want %v", got, tc)
+ }
+ })
+ }
+}
+
+func TestValueLookup(t *testing.T) {
+ config := `
+ a: {
+ a: 0
+ b: 1
+ c: 2
+ }
+ b: {
+ d: a.a
+ e: int
+ }
+ `
+
+ strList := func(s ...string) []string { return s }
+
+ testCases := []struct {
+ config string
+ path []string
+ str string
+ notExists bool
+ }{{
+ config: "_|_",
+ path: strList(""),
+ str: "from source",
+ }, {
+ config: "_|_",
+ path: strList("a"),
+ str: "from source",
+ }, {
+ config: config,
+ path: strList(),
+ str: "{a: {a: 0, b: 1, c: 2}, b: {d: a.a, e: int}",
+ }, {
+ config: config,
+ path: strList("a", "a"),
+ str: "0",
+ }, {
+ config: config,
+ path: strList("a"),
+ str: "{a: 0, b: 1, c: 2}",
+ }, {
+ config: config,
+ path: strList("b", "d"),
+ str: "0",
+ }, {
+ config: config,
+ path: strList("c", "non-existing"),
+ str: "not found",
+ notExists: true,
+ }, {
+ config: config,
+ path: strList("b", "d", "lookup in non-struct"),
+ str: "cannot use value 0 (type int) as struct",
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.str, func(t *testing.T) {
+ v := getInstance(t, tc.config).Value().Lookup(tc.path...)
+ if got := !v.Exists(); got != tc.notExists {
+ t.Errorf("exists: got %v; want %v", got, tc.notExists)
+ }
+
+ got := fmt.Sprint(v)
+ if tc.str == "" {
+ t.Fatalf("str empty, got %q", got)
+ }
+ if !strings.Contains(got, tc.str) {
+ t.Errorf("\n got %v\nwant %v", got, tc.str)
+ }
+ })
+ }
+}
+
+func cmpError(a, b error) bool {
+ if a == nil {
+ return b == nil
+ }
+ if b == nil {
+ return a == nil
+ }
+ return a.Error() == b.Error()
+}
+
+func TestAttributeErr(t *testing.T) {
+ const config = `
+ a: {
+ a: 0 @foo(a,b,c=1)
+ b: 1 @bar(a,b,c,d=1) @foo(a,,d=1)
+ }
+ `
+ testCases := []struct {
+ path string
+ attr string
+ err error
+ }{{
+ path: "a",
+ attr: "foo",
+ err: nil,
+ }, {
+ path: "a",
+ attr: "bar",
+ err: errors.New(`attribute "bar" does not exist`),
+ }, {
+ path: "xx",
+ attr: "bar",
+ err: errors.New(`attribute "bar" does not exist`),
+ }, {
+ path: "e",
+ attr: "bar",
+ err: errors.New(`attribute "bar" does not exist`),
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.path+"-"+tc.attr, func(t *testing.T) {
+ v := getInstance(t, config).Value().Lookup("a", tc.path)
+ a := v.Attribute(tc.attr)
+ err := a.Err()
+ if !cmpError(err, tc.err) {
+ t.Errorf("got %v; want %v", err, tc.err)
+ }
+ })
+ }
+}
+
+func TestAttributeString(t *testing.T) {
+ const config = `
+ a: {
+ a: 0 @foo(a,b,c=1)
+ b: 1 @bar(a,b,c,d=1) @foo(a,,d=1)
+ }
+ `
+ testCases := []struct {
+ path string
+ attr string
+ pos int
+ str string
+ err error
+ }{{
+ path: "a",
+ attr: "foo",
+ pos: 0,
+ str: "a",
+ }, {
+ path: "a",
+ attr: "foo",
+ pos: 2,
+ str: "c=1",
+ }, {
+ path: "b",
+ attr: "bar",
+ pos: 3,
+ str: "d=1",
+ }, {
+ path: "e",
+ attr: "bar",
+ err: errors.New(`attribute "bar" does not exist`),
+ }, {
+ path: "b",
+ attr: "foo",
+ pos: 4,
+ err: errors.New("field does not exist"),
+ }}
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf("%s.%s:%d", tc.path, tc.attr, tc.pos), func(t *testing.T) {
+ v := getInstance(t, config).Value().Lookup("a", tc.path)
+ a := v.Attribute(tc.attr)
+ got, err := a.String(tc.pos)
+ if !cmpError(err, tc.err) {
+ t.Errorf("err: got %v; want %v", err, tc.err)
+ }
+ if got != tc.str {
+ t.Errorf("str: got %v; want %v", got, tc.str)
+ }
+ })
+ }
+}
+
+func TestAttributeInt(t *testing.T) {
+ const config = `
+ a: {
+ a: 0 @foo(1,3,c=1)
+ b: 1 @bar(a,-4,c,d=1) @foo(a,,d=1)
+ }
+ `
+ testCases := []struct {
+ path string
+ attr string
+ pos int
+ val int64
+ err error
+ }{{
+ path: "a",
+ attr: "foo",
+ pos: 0,
+ val: 1,
+ }, {
+ path: "b",
+ attr: "bar",
+ pos: 1,
+ val: -4,
+ }, {
+ path: "e",
+ attr: "bar",
+ err: errors.New(`attribute "bar" does not exist`),
+ }, {
+ path: "b",
+ attr: "foo",
+ pos: 4,
+ err: errors.New("field does not exist"),
+ }, {
+ path: "a",
+ attr: "foo",
+ pos: 2,
+ err: errors.New(`strconv.ParseInt: parsing "c=1": invalid syntax`),
+ }}
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf("%s.%s:%d", tc.path, tc.attr, tc.pos), func(t *testing.T) {
+ v := getInstance(t, config).Value().Lookup("a", tc.path)
+ a := v.Attribute(tc.attr)
+ got, err := a.Int(tc.pos)
+ if !cmpError(err, tc.err) {
+ t.Errorf("err: got %v; want %v", err, tc.err)
+ }
+ if got != tc.val {
+ t.Errorf("val: got %v; want %v", got, tc.val)
+ }
+ })
+ }
+}
+
+func TestAttributeFlag(t *testing.T) {
+ const config = `
+ a: {
+ a: 0 @foo(a,b,c=1)
+ b: 1 @bar(a,b,c,d=1) @foo(a,,d=1)
+ }
+ `
+ testCases := []struct {
+ path string
+ attr string
+ pos int
+ flag string
+ val bool
+ err error
+ }{{
+ path: "a",
+ attr: "foo",
+ pos: 0,
+ flag: "a",
+ val: true,
+ }, {
+ path: "b",
+ attr: "bar",
+ pos: 1,
+ flag: "a",
+ val: false,
+ }, {
+ path: "b",
+ attr: "bar",
+ pos: 0,
+ flag: "c",
+ val: true,
+ }, {
+ path: "e",
+ attr: "bar",
+ err: errors.New(`attribute "bar" does not exist`),
+ }, {
+ path: "b",
+ attr: "foo",
+ pos: 4,
+ err: errors.New("field does not exist"),
+ }}
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf("%s.%s:%d", tc.path, tc.attr, tc.pos), func(t *testing.T) {
+ v := getInstance(t, config).Value().Lookup("a", tc.path)
+ a := v.Attribute(tc.attr)
+ got, err := a.Flag(tc.pos, tc.flag)
+ if !cmpError(err, tc.err) {
+ t.Errorf("err: got %v; want %v", err, tc.err)
+ }
+ if got != tc.val {
+ t.Errorf("val: got %v; want %v", got, tc.val)
+ }
+ })
+ }
+}
+
+func TestAttributeLookup(t *testing.T) {
+ const config = `
+ a: {
+ a: 0 @foo(a,b,c=1)
+ b: 1 @bar(a,b,e=-5,d=1) @foo(a,,d=1)
+ }
+ `
+ testCases := []struct {
+ path string
+ attr string
+ pos int
+ key string
+ val string
+ err error
+ }{{
+ path: "a",
+ attr: "foo",
+ pos: 0,
+ key: "c",
+ val: "1",
+ }, {
+ path: "b",
+ attr: "bar",
+ pos: 1,
+ key: "a",
+ val: "",
+ }, {
+ path: "b",
+ attr: "bar",
+ pos: 0,
+ key: "e",
+ val: "-5",
+ }, {
+ path: "b",
+ attr: "bar",
+ pos: 0,
+ key: "d",
+ val: "1",
+ }, {
+ path: "b",
+ attr: "foo",
+ pos: 2,
+ key: "d",
+ val: "1",
+ }, {
+ path: "b",
+ attr: "foo",
+ pos: 2,
+ key: "f",
+ val: "",
+ }, {
+ path: "e",
+ attr: "bar",
+ err: errors.New(`attribute "bar" does not exist`),
+ }, {
+ path: "b",
+ attr: "foo",
+ pos: 4,
+ err: errors.New("field does not exist"),
+ }}
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf("%s.%s:%d", tc.path, tc.attr, tc.pos), func(t *testing.T) {
+ v := getInstance(t, config).Value().Lookup("a", tc.path)
+ a := v.Attribute(tc.attr)
+ got, _, err := a.Lookup(tc.pos, tc.key)
+ if !cmpError(err, tc.err) {
+ t.Errorf("err: got %v; want %v", err, tc.err)
+ }
+ if got != tc.val {
+ t.Errorf("val: got %v; want %v", got, tc.val)
+ }
+ })
+ }
+}
+
+func TestValueDoc(t *testing.T) {
+ const config = `
+ // foobar defines at least foo.
+ package foobar
+
+ // A Foo fooses stuff.
+ Foo: {
+ // field1 is an int.
+ field1: int
+
+ field2: int
+
+ // duplicate field comment
+ dup3: int
+ }
+
+ // foos are instances of Foo.
+ foos: [string]: Foo
+
+ // My first little foo.
+ foos: MyFoo: {
+ // local field comment.
+ field1: 0
+
+ // Dangling comment.
+
+ // other field comment.
+ field2: 1
+
+ // duplicate field comment
+ dup3: int
+ }
+
+ bar: {
+ // comment from bar on field 1
+ field1: int
+ // comment from bar on field 2
+ field2: int // don't include this
+ }
+
+ baz: bar & {
+ // comment from baz on field 1
+ field1: int
+ field2: int
+ }
+ `
+ config2 := `
+ // Another Foo.
+ Foo: {}
+ `
+ var r Runtime
+ getInst := func(name, body string) *Instance {
+ inst, err := r.Compile("dir/file1.cue", body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return inst
+ }
+
+ inst := getInst("config", config)
+
+ v1 := inst.Value()
+ v2 := getInst("config2", config2).Value()
+ both := v1.Unify(v2)
+
+ testCases := []struct {
+ val Value
+ path string
+ doc string
+ }{{
+ val: v1,
+ path: "foos",
+ doc: "foos are instances of Foo.\n",
+ }, {
+ val: v1,
+ path: "foos MyFoo",
+ doc: "My first little foo.\n",
+ }, {
+ val: v1,
+ path: "foos MyFoo field1",
+ doc: `field1 is an int.
+
+local field comment.
+`,
+ }, {
+ val: v1,
+ path: "foos MyFoo field2",
+ doc: "other field comment.\n",
+ }, {
+ val: v1,
+ path: "foos MyFoo dup3",
+ doc: `duplicate field comment
+
+duplicate field comment
+`,
+ }, {
+ val: v1,
+ path: "bar field1",
+ doc: "comment from bar on field 1\n",
+ }, {
+ val: v1,
+ path: "baz field1",
+ doc: `comment from baz on field 1
+
+comment from bar on field 1
+`,
+ }, {
+ val: v1,
+ path: "baz field2",
+ doc: "comment from bar on field 2\n",
+ }, {
+ val: v2,
+ path: "Foo",
+ doc: `Another Foo.
+`,
+ }, {
+ val: both,
+ path: "Foo",
+ doc: `Another Foo.
+
+A Foo fooses stuff.
+`,
+ }}
+ for _, tc := range testCases {
+ t.Run("field:"+tc.path, func(t *testing.T) {
+ v := tc.val.Lookup(strings.Split(tc.path, " ")...)
+ doc := docStr(v.Doc())
+ if doc != tc.doc {
+ t.Errorf("doc: got:\n%vwant:\n%v", doc, tc.doc)
+ }
+ })
+ }
+ want := "foobar defines at least foo.\n"
+ if got := docStr(inst.Doc()); got != want {
+ t.Errorf("pkg: got:\n%vwant:\n%v", got, want)
+ }
+}
+
+func docStr(docs []*ast.CommentGroup) string {
+ doc := ""
+ for _, d := range docs {
+ if doc != "" {
+ doc += "\n"
+ }
+ doc += d.Text()
+ }
+ return doc
+}
+
+func TestMarshalJSON(t *testing.T) {
+ testCases := []struct {
+ value string
+ json string
+ err string
+ }{{
+ value: `""`,
+ json: `""`,
+ }, {
+ value: `null`,
+ json: `null`,
+ }, {
+ value: `_|_`,
+ err: "from source",
+ }, {
+ value: `(a.b)
+ a: {}`,
+ err: "undefined field",
+ }, {
+ value: `true`,
+ json: `true`,
+ }, {
+ value: `false`,
+ json: `false`,
+ }, {
+ value: `bool`,
+ err: "cannot convert incomplete value",
+ }, {
+ value: `"str"`,
+ json: `"str"`,
+ }, {
+ value: `12_000`,
+ json: `12000`,
+ }, {
+ value: `12.000`,
+ json: `12.000`,
+ }, {
+ value: `12M`,
+ json: `12000000`,
+ }, {
+ value: `3.0e100`,
+ json: `3.0E+100`,
+ }, {
+ value: `0/0`,
+ err: "division undefined",
+ }, {
+ value: `[]`,
+ json: `[]`,
+ }, {
+ value: `[1, 2, 3]`,
+ json: `[1,2,3]`,
+ }, {
+ value: `[int]`,
+ err: `0: cannot convert incomplete value`,
+ }, {
+ value: `(>=3 * [1, 2])`,
+ err: "incomplete error", // TODO: improve error
+ }, {
+ value: `{}`,
+ json: `{}`,
+ }, {
+ value: `{a: 2, b: 3, c: ["A", "B"]}`,
+ json: `{"a":2,"b":3,"c":["A","B"]}`,
+ }, {
+ value: `{a: 2, b: 3, c: [string, "B"]}`,
+ err: `c.0: cannot convert incomplete value`,
+ }, {
+ value: `{a: [{b: [0, {c: string}] }] }`,
+ err: `a.0.b.1.c: cannot convert incomplete value`,
+ }, {
+ value: `{foo?: 1, bar?: 2, baz: 3}`,
+ json: `{"baz":3}`,
+ }, {
+ // Has an unresolved cycle, but should not matter as all fields involved
+ // are optional
+ value: `{foo?: bar, bar?: foo, baz: 3}`,
+ json: `{"baz":3}`,
+ }, {
+ // Issue #107
+ value: `a: 1.0/1`,
+ json: `{"a":1.0}`,
+ }, {
+ // Issue #108
+ value: `
+ a: int
+ a: >0
+ a: <2
+
+ b: int
+ b: >=0.9
+ b: <1.1
+
+ c: int
+ c: >1
+ c: <=2
+
+ d: int
+ d: >=1
+ d: <=1.5
+
+ e: int
+ e: >=1
+ e: <=1.32
+
+ f: >=1.1 & <=1.1
+ `,
+ json: `{"a":1,"b":1,"c":2,"d":1,"e":1,"f":1.1}`,
+ }, {
+ value: `
+ #Task: {
+ {
+ op: "pull"
+ tag: *"latest" | string
+ tagInString: tag + "dd"
+ } | {
+ op: "scratch"
+ }
+ }
+
+ foo: #Task & {"op": "pull"}
+ `,
+ json: `{"foo":{"op":"pull","tag":"latest","tagInString":"latestdd"}}`,
+ }, {
+ // Issue #326
+ value: `x: "\(string)": "v"`,
+ err: `x: incomplete value 'string' in interpolation`,
+ }, {
+ // Issue #326
+ value: `x: "\(bool)": "v"`,
+ err: `x: expression in interpolation must evaluate to a number kind or string (found bool)`,
+ }, {
+ // Issue #326
+ value: `
+ x: {
+ for k, v in y {
+ "\(k)": v
+ }
+ }
+ y: {}
+ `,
+ json: `{"x":{},"y":{}}`,
+ }, {
+ // Issue #326
+ value: `
+ x: {
+ for k, v in y {
+ "\(k)": v
+ }
+ }
+ y: _
+ `,
+ err: `x: incomplete feed source`,
+ }}
+ for i, tc := range testCases {
+ t.Run(fmt.Sprintf("%d/%v", i, tc.value), func(t *testing.T) {
+ inst := getInstance(t, tc.value)
+ b, err := inst.Value().MarshalJSON()
+ checkFatal(t, err, tc.err, "init")
+
+ if got := string(b); got != tc.json {
+ t.Errorf("\n got %v;\nwant %v", got, tc.json)
+ }
+ })
+ }
+}
+
+func TestWalk(t *testing.T) {
+ testCases := []struct {
+ value string
+ out string
+ }{{
+ value: `""`,
+ out: `""`,
+ }, {
+ value: `null`,
+ out: `null`,
+ }, {
+ value: `_|_`,
+ out: "_|_(from source)",
+ }, {
+ value: `(a.b)
+ a: {}`,
+ out: `_|_(undefined field "b")`,
+ }, {
+ value: `true`,
+ out: `true`,
+ }, {
+ value: `false`,
+ out: `false`,
+ }, {
+ value: `bool`,
+ out: "bool",
+ }, {
+ value: `"str"`,
+ out: `"str"`,
+ }, {
+ value: `12_000`,
+ out: `12000`,
+ }, {
+ value: `12.000`,
+ out: `12.000`,
+ }, {
+ value: `12M`,
+ out: `12000000`,
+ }, {
+ value: `3.0e100`,
+ out: `3.0e+100`,
+ }, {
+ value: `[]`,
+ out: `[]`,
+ }, {
+ value: `[1, 2, 3]`,
+ out: `[1,2,3]`,
+ }, {
+ value: `[int]`,
+ out: `[int]`,
+ }, {
+ value: `3 * [1, 2]`,
+ out: `[1,2,1,2,1,2]`,
+ }, {
+ value: `{}`,
+ out: `{}`,
+ }, {
+ value: `{a: 2, b: 3, c: ["A", "B"]}`,
+ out: `{a:2,b:3,c:["A","B"]}`,
+ }}
+ for i, tc := range testCases {
+ t.Run(fmt.Sprintf("%d/%v", i, tc.value), func(t *testing.T) {
+ inst := getInstance(t, tc.value)
+ buf := []byte{}
+ stripComma := func() {
+ if n := len(buf) - 1; buf[n] == ',' {
+ buf = buf[:n]
+ }
+ }
+ inst.Value().Walk(func(v Value) bool {
+ if k, ok := v.Label(); ok {
+ buf = append(buf, k+":"...)
+ }
+ switch v.Kind() {
+ case StructKind:
+ buf = append(buf, '{')
+ case ListKind:
+ buf = append(buf, '[')
+ default:
+ buf = append(buf, fmt.Sprint(v, ",")...)
+ }
+ return true
+ }, func(v Value) {
+ switch v.Kind() {
+ case StructKind:
+ stripComma()
+ buf = append(buf, "},"...)
+ case ListKind:
+ stripComma()
+ buf = append(buf, "],"...)
+ }
+ })
+ stripComma()
+ if got := string(buf); got != tc.out {
+ t.Errorf("\n got %v;\nwant %v", got, tc.out)
+ }
+ })
+ }
+}
+
+func TestTrimZeros(t *testing.T) {
+ testCases := []struct {
+ in string
+ out string
+ }{
+ {"", ""},
+ {"2", "2"},
+ {"2.0", "2.0"},
+ {"2.000000000000", "2.0"},
+ {"2000000000000", "2e+12"},
+ {"2000000", "2e+6"},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.in, func(t *testing.T) {
+ if got := trimZeros(tc.in); got != tc.out {
+ t.Errorf("got %q; want %q", got, tc.out)
+ }
+ })
+ }
+}
+
+func TestReference(t *testing.T) {
+ testCases := []struct {
+ input string
+ want string
+ }{{
+ input: "v: w: x: _|_",
+ want: "",
+ }, {
+ input: "v: w: x: 2",
+ want: "",
+ }, {
+ input: "v: w: x: a, a: 1",
+ want: "a",
+ }, {
+ input: "v: w: x: a.b.c, a: b: c: 1",
+ want: "a b c",
+ }, {
+ input: "v: w: x: w.a.b.c, v: w: a: b: c: 1",
+ want: "v w a b c",
+ }, {
+ input: `v: w: x: w.a.b.c, v: w: a: b: c: 1, #D: 3, opt?: 3, "v\(#D)": 3, X: {a: 3}, X`,
+ want: "v w a b c",
+ }, {
+ input: `v: w: x: w.a[bb]["c"], v: w: a: b: c: 1, bb: "b"`,
+ want: "v w a b c",
+ }, {
+ input: `v: {
+ for t in src {
+ w: "t\(t)": 1
+ w: "\(t)": w["t\(t)"]
+ }
+ },
+ src: ["x", "y"]`,
+ want: "v w tx",
+ }, {
+ input: `
+ v: w: x: a
+ a: 1
+ for i in [] {
+ }
+ `,
+ want: "a",
+ }, {
+ input: `
+ v: w: close({x: a})
+ a: 1
+ `,
+ want: "a",
+ }}
+ for _, tc := range testCases {
+ t.Run("", func(t *testing.T) {
+ var r Runtime
+ inst, _ := r.Compile("in", tc.input) // getInstance(t, tc.input)
+ v := inst.Lookup("v", "w", "x")
+ inst, a := v.Reference()
+ if got := strings.Join(a, " "); got != tc.want {
+ t.Errorf("\n got %v;\nwant %v", got, tc.want)
+ }
+
+ if tc.want != "" {
+ v := inst.Lookup(a...)
+ if x, _ := v.Int64(); x != 1 {
+ t.Errorf("path resolved to %s; want 1", v)
+ }
+ }
+ })
+ }
+}
+
+func TestPathCorrection(t *testing.T) {
+ testCases := []struct {
+ input string
+ lookup func(i *Instance) Value
+ want string
+ skip bool
+ }{{
+ input: `
+ a: b: {
+ c: d: b
+ }
+ `,
+ lookup: func(i *Instance) Value {
+ _, a := i.Lookup("a", "b", "c", "d").Expr()
+ return a[0].Lookup("b", "c", "d")
+ },
+ want: "a.b",
+ }, {
+ input: `
+ a: {
+ c: 3
+ {x: c}
+ }
+ `,
+ lookup: func(i *Instance) Value {
+ _, a := i.Lookup("a").Expr()
+ return a[1].Lookup("x")
+ },
+ want: "a.c",
+ }, {
+ input: `
+ a: b: [...T]
+ a: b: [...T]
+ T: 1
+ `,
+ lookup: func(i *Instance) Value {
+ v, _ := i.Lookup("a", "b").Elem()
+ _, a := v.Expr()
+ return a[0]
+ },
+ want: "T",
+ }, {
+ input: `
+ #S: {
+ b?: [...#T]
+ b?: [...#T]
+ }
+ #T: int
+ `,
+ lookup: func(i *Instance) Value {
+ v := i.LookupDef("#S")
+ f, _ := v.LookupField("b")
+ v, _ = f.Value.Elem()
+ _, a := v.Expr()
+ return a[0]
+ },
+ want: "#T",
+ }, {
+ input: `
+ #a: {
+ #T: {b: 3}
+ close({}) | close({c: #T}) | close({d: string})
+ }
+ `,
+ lookup: func(i *Instance) Value {
+ f, _ := i.LookupField("#a")
+ _, a := f.Value.Expr() // &
+ _, a = a[1].Expr() // |
+ return a[1].Lookup("c")
+ },
+ want: "#a.#T",
+ }, {
+ input: `
+ package foo
+
+ #Struct: {
+ #T: int
+
+ {b?: #T}
+ }`,
+ want: "#Struct.#T",
+ lookup: func(inst *Instance) Value {
+ // Locate Struct
+ i, _ := inst.Value().Fields(Definitions(true))
+ if !i.Next() {
+ t.Fatal("no fields")
+ }
+ // Locate b
+ i, _ = i.Value().Fields(Definitions(true), Optional(true))
+ if !(i.Next() && i.Next()) {
+ t.Fatal("no fields")
+ }
+ v := i.Value()
+ return v
+ },
+ }, {
+ input: `
+ package foo
+
+ #A: #B: #T
+
+ #T: {
+ a: #S.#U
+ #S: #U: {}
+ }
+ `,
+ want: "#T.#S.#U",
+ lookup: func(inst *Instance) Value {
+ f, _ := inst.Value().LookupField("#A")
+ f, _ = f.Value.LookupField("#B")
+ v := f.Value
+ v = Dereference(v)
+ v = v.Lookup("a")
+ return v
+ },
+ }, {
+ input: `
+ package foo
+
+ #A: #B: #T
+
+ #T: {
+ a: [...#S]
+ #S: {}
+ }
+ `,
+ want: "#T.#S",
+ lookup: func(inst *Instance) Value {
+ f, _ := inst.Value().LookupField("#A")
+ f, _ = f.Value.LookupField("#B")
+ v := f.Value
+ v = Dereference(v)
+ v, _ = v.Lookup("a").Elem()
+ return v
+ },
+ }, {
+ input: `
+ #A: {
+ b: #T
+ }
+
+ #T: {
+ a: #S
+ #S: {}
+ }
+ `,
+ want: "#T.#S",
+ lookup: func(inst *Instance) Value {
+ f, _ := inst.Value().LookupField("#A")
+ v := f.Value.Lookup("b")
+ v = Dereference(v)
+ v = v.Lookup("a")
+ return v
+ },
+ }, {
+ // TODO(eval): embedded structs are currently represented at the same
+ // level as the enclosing struct. This means that the parent of an
+ // embedded struct skips the struct in which it is embedded. Treat
+ // embedded structs as "anonymous" fields.
+ // This could perhaps be made fixed with dereferencing as well.
+ skip: true,
+ input: `
+ #Tracing: {
+ #T: { address?: string }
+ #S: { ip?: string }
+
+ close({}) | close({
+ t: #T
+ }) | close({
+ s: S
+ })
+ }
+ #X: {}
+ #X // Disconnect top-level struct from the one visible by close.
+ `,
+ want: "",
+ lookup: func(inst *Instance) Value {
+ f, _ := inst.Value().LookupField("#Tracing")
+ v := f.Value.Eval()
+ _, args := v.Expr()
+ v = args[1].Lookup("t")
+ v = Dereference(v)
+ return v
+ },
+ }}
+ for _, tc := range testCases {
+ if tc.skip {
+ continue
+ }
+ t.Run("", func(t *testing.T) {
+ var r Runtime
+ inst, err := r.Compile("in", tc.input)
+ if err != nil {
+ t.Fatal(err)
+ }
+ v := tc.lookup(inst)
+ gotInst, ref := v.Reference()
+ if gotInst != inst {
+ t.Error("reference not in original instance")
+ }
+ gotPath := strings.Join(ref, ".")
+ if gotPath != tc.want {
+ t.Errorf("got path %s; want %s", gotPath, tc.want)
+ }
+ })
+ }
+}
+
+func TestReferences(t *testing.T) {
+ config1 := `
+ a: {
+ b: 3
+ }
+ c: {
+ d: a.b
+ e: c.d
+ f: a
+ }
+ `
+ config2 := `
+ a: { c: 3 }
+ b: { c: int, d: 4 }
+ r: (a & b).c
+ c: {args: s1 + s2}.args
+ s1: string
+ s2: string
+ d: ({arg: b}).arg.c
+ e: f.arg.c
+ f: {arg: b}
+ `
+ testCases := []struct {
+ config string
+ in string
+ out string
+ }{
+ {config1, "c.d", "a.b"},
+ {config1, "c.e", "c.d"},
+ {config1, "c.f", "a"},
+
+ {config2, "r", "a.c b.c"},
+ {config2, "c", "s1 s2"},
+ // {config2, "d", "b.c"}, // TODO: make this work as well.
+ {config2, "e", "f.arg.c"}, // TODO: should also report b.c.
+ }
+ for _, tc := range testCases {
+ t.Run(tc.in, func(t *testing.T) {
+ ctx, st := compileFile(t, tc.config)
+ v := newValueRoot(ctx, st)
+ for _, k := range strings.Split(tc.in, ".") {
+ obj, err := v.structValFull(ctx)
+ if err != nil {
+ t.Fatal(err)
+ }
+ v = obj.Lookup(k)
+ }
+ got := []string{}
+ for _, r := range v.References() {
+ got = append(got, strings.Join(r, "."))
+ }
+ want := strings.Split(tc.out, " ")
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("got %v; want %v", got, want)
+ }
+ })
+ }
+}
+
+func checkErr(t *testing.T, err error, str, name string) bool {
+ t.Helper()
+ if err == nil {
+ if str != "" {
+ t.Errorf(`err:%s: got ""; want %q`, name, str)
+ }
+ return true
+ }
+ return checkFailed(t, err, str, name)
+}
+
+func checkFatal(t *testing.T, err error, str, name string) {
+ t.Helper()
+ if !checkFailed(t, err, str, name) {
+ t.SkipNow()
+ }
+}
+
+func checkFailed(t *testing.T, err error, str, name string) bool {
+ t.Helper()
+ if err != nil {
+ got := err.Error()
+ if str == "" {
+ t.Fatalf(`err:%s: got %q; want ""`, name, got)
+ }
+ if !strings.Contains(got, str) {
+ t.Errorf(`err:%s: got %q; want %q`, name, got, str)
+ }
+ return false
+ }
+ return true
+}
+
+func TestExpr(t *testing.T) {
+ testCases := []struct {
+ input string
+ want string
+ }{{
+ input: "v: 3",
+ want: "3",
+ }, {
+ input: "v: 3 + 4",
+ want: "+(3 4)",
+ }, {
+ input: "v: !a, a: 3",
+ want: `!(.(<0> "a"))`,
+ }, {
+ input: "v: 1 | 2 | 3 | *4",
+ want: "|(1 2 3 4)",
+ }, {
+ input: "v: 2 & 5",
+ want: "&(2 5)",
+ }, {
+ input: "v: 2 | 5",
+ want: "|(2 5)",
+ }, {
+ input: "v: 2 && 5",
+ want: "&&(2 5)",
+ }, {
+ input: "v: 2 || 5",
+ want: "||(2 5)",
+ }, {
+ input: "v: 2 == 5",
+ want: "==(2 5)",
+ }, {
+ input: "v: !b, b: true",
+ want: `!(.(<0> "b"))`,
+ }, {
+ input: "v: 2 != 5",
+ want: "!=(2 5)",
+ }, {
+ input: "v: <5",
+ want: "<(5)",
+ }, {
+ input: "v: 2 <= 5",
+ want: "<=(2 5)",
+ }, {
+ input: "v: 2 > 5",
+ want: ">(2 5)",
+ }, {
+ input: "v: 2 >= 5",
+ want: ">=(2 5)",
+ }, {
+ input: "v: 2 =~ 5",
+ want: "=~(2 5)",
+ }, {
+ input: "v: 2 !~ 5",
+ want: "!~(2 5)",
+ }, {
+ input: "v: 2 + 5",
+ want: "+(2 5)",
+ }, {
+ input: "v: 2 - 5",
+ want: "-(2 5)",
+ }, {
+ input: "v: 2 * 5",
+ want: "*(2 5)",
+ }, {
+ input: "v: 2 / 5",
+ want: "/(2 5)",
+ }, {
+ input: "v: 2 quo 5",
+ want: "quo(2 5)",
+ }, {
+ input: "v: 2 rem 5",
+ want: "rem(2 5)",
+ }, {
+ input: "v: 2 div 5",
+ want: "div(2 5)",
+ }, {
+ input: "v: 2 mod 5",
+ want: "mod(2 5)",
+ }, {
+ input: "v: a.b, a: b: 4",
+ want: `.(.(<0> "a") "b")`,
+ }, {
+ input: `v: a["b"], a: b: 3 `,
+ want: `[](.(<0> "a") "b")`,
+ }, {
+ input: "v: a[2:5], a: [1, 2, 3, 4, 5]",
+ want: `[:](.(<0> "a") 2 5)`,
+ }, {
+ input: "v: len([])",
+ want: "()(len [])",
+ }, {
+ input: "v: a.b, a: { b: string }",
+ want: `.(.(<0> "a") "b")`,
+ }, {
+ input: `v: "Hello, \(x)! Welcome to \(place)", place: string, x: string`,
+ want: `\()("Hello, " .(<0> "x") "! Welcome to " .(<0> "place") "")`,
+ }, {
+ input: `v: { a, b: 1 }, a: 2`,
+ want: `&(<0>{b: 1} .(<0> "a"))`,
+ }, {
+ input: `v: { {c: a}, b: a }, a: int`,
+ want: `&(<0>{b: <1>.a} <0>{c: <1>.a})`,
+ }, {
+ input: `v: [...number] | *[1, 2, 3]`,
+ want: `([, ...number] | *[1,2,3])`,
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.input, func(t *testing.T) {
+ v := getInstance(t, tc.input).Lookup("v")
+ got := exprStr(v)
+ if got != tc.want {
+ t.Errorf("\n got %v;\nwant %v", got, tc.want)
+ }
+ })
+ }
+}
+func exprStr(v Value) string {
+ op, operands := v.Expr()
+ if op == NoOp {
+ return debugStr(v.ctx(), v.v.v)
+ }
+ s := op.String()
+ s += "("
+ for i, v := range operands {
+ if i > 0 {
+ s += " "
+ }
+ s += exprStr(v)
+ }
+ s += ")"
+ return s
+}
diff --git a/tmp.cue b/tmp.cue
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tmp.cue