cue/internal/adt: add builtin and import support
- Builtin
- BuiltinValidator
- import
Change-Id: I826ce644cd8d5000ca7998b8eaf6c96ecef6fe02
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6502
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/testdata/basicrewrite/011_list_arithmetic.txtar b/cue/testdata/basicrewrite/011_list_arithmetic.txtar
index c1b52fd..c1d26a8 100644
--- a/cue/testdata/basicrewrite/011_list_arithmetic.txtar
+++ b/cue/testdata/basicrewrite/011_list_arithmetic.txtar
@@ -11,6 +11,7 @@
mul1_0: list1 * 0
mul1_1: 1 * list1
mul1_2: list1 * 2
+add1_2: list + list1
e: list * -1
-- out/def --
list: [1, 2, 3]
@@ -41,6 +42,7 @@
mul1_0: (〈0;list1〉 * 0)
mul1_1: (1 * 〈0;list1〉)
mul1_2: (〈0;list1〉 * 2)
+ add1_2: (〈0;list〉 + 〈0;list1〉)
e: (〈0;list〉 * -1)
}
-- out/eval --
@@ -78,8 +80,14 @@
0: (int){ 1 }
1: (int){ 1 }
}
+ add1_2: (#list){
+ 0: (int){ 1 }
+ 1: (int){ 2 }
+ 2: (int){ 3 }
+ 3: (int){ 1 }
+ }
e: (_|_){
// [eval] cannot convert negative number to uint64:
- // ./in.cue:9:9
+ // ./in.cue:10:9
}
}
diff --git a/cue/testdata/basicrewrite/014_disjunctions.txtar b/cue/testdata/basicrewrite/014_disjunctions.txtar
index 01e01ae..2172cb7 100644
--- a/cue/testdata/basicrewrite/014_disjunctions.txtar
+++ b/cue/testdata/basicrewrite/014_disjunctions.txtar
@@ -1,5 +1,5 @@
# DO NOT EDIT; generated by go run testdata/gen.go
-#
+
#name: disjunctions
#evalPartial
-- in.cue --
diff --git a/cue/testdata/basicrewrite/017_null.txtar b/cue/testdata/basicrewrite/017_null.txtar
index 755b9f6..c58a9a5 100644
--- a/cue/testdata/basicrewrite/017_null.txtar
+++ b/cue/testdata/basicrewrite/017_null.txtar
@@ -45,7 +45,7 @@
eq2: (bool){ false }
ne1: (bool){ true }
call: (_|_){
- // [eval] cannot call non-function *adt.Null (type nil):
+ // [eval] cannot call non-function *adt.Null (type null):
// ./in.cue:9:7
}
}
diff --git a/cue/testdata/export/020.txtar b/cue/testdata/export/020.txtar
index 660432c..5659d63 100644
--- a/cue/testdata/export/020.txtar
+++ b/cue/testdata/export/020.txtar
@@ -14,3 +14,8 @@
b: 0
-- out/json --
{"a":"","b":0}
+-- out/eval --
+(struct){
+ a: (string){ "" }
+ b: (int){ 0 }
+}
diff --git a/cue/testdata/fulleval/026_dont_convert_incomplete_errors_to_non-incomplete.txtar b/cue/testdata/fulleval/026_dont_convert_incomplete_errors_to_non-incomplete.txtar
index 348ad9a..d41909a 100644
--- a/cue/testdata/fulleval/026_dont_convert_incomplete_errors_to_non-incomplete.txtar
+++ b/cue/testdata/fulleval/026_dont_convert_incomplete_errors_to_non-incomplete.txtar
@@ -45,8 +45,7 @@
-- out/legacy-debug --
<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")}
-- out/eval --
-(_|_){
- // [eval]
+(struct){
n1: (struct){
min: (_|_){
// [cycle] cycle error
@@ -88,11 +87,11 @@
// ./in.cue:13:5
}
s2: (_|_){
- // [eval] cannot call non-function *adt.SelectorExpr (type nil):
+ // [incomplete] incomplete value *adt.ImportReference:
// ./in.cue:14:5
}
s3: (_|_){
- // [eval] cannot call non-function *adt.SelectorExpr (type nil):
+ // [incomplete] incomplete value *adt.ImportReference:
// ./in.cue:15:5
}
str: (string){ string }
diff --git a/cue/testdata/fulleval/027_len_of_incomplete_types.txtar b/cue/testdata/fulleval/027_len_of_incomplete_types.txtar
index 5fe7a7c..bc9111c 100644
--- a/cue/testdata/fulleval/027_len_of_incomplete_types.txtar
+++ b/cue/testdata/fulleval/027_len_of_incomplete_types.txtar
@@ -32,3 +32,27 @@
v10: 0
-- out/legacy-debug --
<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}
+-- out/eval --
+(struct){
+ args: (#list){
+ }
+ v1: (int){ 0 }
+ v2: (int){ 0 }
+ v3: (int){ 0 }
+ v4: (int){ 1 }
+ v5: (_|_){
+ // [incomplete] unresolved disjunction *adt.Disjunction (type struct):
+ // ./in.cue:6:7
+ }
+ v6: (_|_){
+ // [incomplete] unresolved disjunction *adt.Disjunction (type bytes):
+ // ./in.cue:7:7
+ }
+ v7: (int){ 2 }
+ v8: (_|_){
+ // [incomplete] unresolved disjunction *adt.Disjunction (type list):
+ // ./in.cue:9:7
+ }
+ v9: (int){ 4 }
+ v10: (int){ 0 }
+}
diff --git a/cue/testdata/fulleval/032_or_builtin_should_not_fail_on_non-concrete_empty_list.txtar b/cue/testdata/fulleval/032_or_builtin_should_not_fail_on_non-concrete_empty_list.txtar
index b3e4875..1a44031 100644
--- a/cue/testdata/fulleval/032_or_builtin_should_not_fail_on_non-concrete_empty_list.txtar
+++ b/cue/testdata/fulleval/032_or_builtin_should_not_fail_on_non-concrete_empty_list.txtar
@@ -41,3 +41,21 @@
{"foo":{"jobs":{"foo":{}}}}
-- out/legacy-debug --
<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"}}
+-- out/eval --
+(struct){
+ #Workflow: (#struct){
+ jobs: (#struct){
+ }
+ #JobID: (_|_){
+ // [incomplete] empty list in call to or:
+ // ./in.cue:6:10
+ }
+ }
+ foo: (#struct){
+ jobs: (#struct){
+ foo: (#struct){
+ }
+ }
+ #JobID: (string){ "foo" }
+ }
+}
diff --git "a/cue/testdata/fulleval/044_Issue_\043178.txtar" "b/cue/testdata/fulleval/044_Issue_\043178.txtar"
index b34015d..4ad3745 100644
--- "a/cue/testdata/fulleval/044_Issue_\043178.txtar"
+++ "b/cue/testdata/fulleval/044_Issue_\043178.txtar"
@@ -25,16 +25,15 @@
-- out/legacy-debug --
<0>{foo: <1>.Decode (<2>.data), data: bytes, len: int, bar: <3>.EncodedLen (<2>.len)}
-- out/eval --
-(_|_){
- // [eval]
+(struct){
foo: (_|_){
- // [eval] cannot call non-function *adt.SelectorExpr (type nil):
+ // [incomplete] incomplete value *adt.ImportReference:
// ./in.cue:5:7
}
data: (bytes){ bytes }
len: (int){ int }
bar: (_|_){
- // [eval] cannot call non-function *adt.SelectorExpr (type nil):
+ // [incomplete] incomplete value *adt.ImportReference:
// ./in.cue:9:6
}
}
diff --git a/cue/testdata/fulleval/049_alias_reuse_in_nested_scope.txtar b/cue/testdata/fulleval/049_alias_reuse_in_nested_scope.txtar
index cf00d94..240a8b2 100644
--- a/cue/testdata/fulleval/049_alias_reuse_in_nested_scope.txtar
+++ b/cue/testdata/fulleval/049_alias_reuse_in_nested_scope.txtar
@@ -64,3 +64,31 @@
{"b":{"foo":"key","a":{"foo":{}}}}
-- out/legacy-debug --
<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, }}}}
+-- out/eval --
+(struct){
+ #Foo: (#struct){
+ connection: (#struct){
+ }
+ }
+ #A: (#struct){
+ foo: (string){ "key" }
+ a: (#struct){
+ foo: (#struct){
+ }
+ }
+ }
+ #B: (#struct){
+ foo: (string){ string }
+ a: (#struct){
+ foo: (#struct){
+ }
+ }
+ }
+ b: (#struct){
+ foo: (string){ "key" }
+ a: (#struct){
+ foo: (#struct){
+ }
+ }
+ }
+}
diff --git a/cue/testdata/references/labels.txtar b/cue/testdata/references/labels.txtar
new file mode 100644
index 0000000..829c79d
--- /dev/null
+++ b/cue/testdata/references/labels.txtar
@@ -0,0 +1,179 @@
+-- in.cue --
+// Label aliases
+
+// direct
+a: [X=string]: X
+a: bar: {}
+
+// in struct
+b: [X=string]: {X}
+b: bar: {}
+
+// in structs
+c: [X=string]: X
+c: [Y=string]: {{{Y}}}
+c: bar: {}
+
+// in sub field
+d: [X=string]: name: X
+d: bar: {}
+
+// nested
+e: [X=string]: [Y=string]: X + Y
+e: foo: bar: {}
+
+// Field aliases
+
+bar: 3
+f1: C="foo\(bar)": {
+ name: "xx"
+ foo: C.name
+}
+
+// nested
+f1: D="foo\(bar)": E="foo\(bar)baz": {
+ name: "xx"
+ a: D["foo\(bar)baz"].name
+ b: E.name
+}
+
+// Combo
+
+c1: C="foo\(bar)": [D=string]: {
+ name: D
+ foo: C.x.name
+}
+c1: foo3: x: _
+
+
+// TODO: support. Also not yet supported in old implementation.
+// c10: {
+// C=[string]: {
+// name: "x"
+// foo: C.name
+// }
+// }
+// c2: c1 & { x: _ }
+
+-- out/eval --
+(struct){
+ a: (struct){
+ bar: (string){ "bar" }
+ }
+ b: (struct){
+ bar: (string){ "bar" }
+ }
+ c: (struct){
+ bar: (string){ "bar" }
+ }
+ d: (struct){
+ bar: (struct){
+ name: (string){ "bar" }
+ }
+ }
+ e: (struct){
+ foo: (struct){
+ bar: (string){ "foobar" }
+ }
+ }
+ bar: (int){ 3 }
+ f1: (struct){
+ foo3: (struct){
+ name: (string){ "xx" }
+ foo: (string){ "xx" }
+ foo3baz: (struct){
+ name: (string){ "xx" }
+ a: (string){ "xx" }
+ b: (string){ "xx" }
+ }
+ }
+ }
+ c1: (struct){
+ foo3: (struct){
+ x: (struct){
+ name: (string){ "x" }
+ foo: (string){ "x" }
+ }
+ }
+ }
+}
+-- out/compile --
+--- in.cue
+{
+ a: {
+ [string]: 〈0;-〉
+ }
+ a: {
+ bar: {}
+ }
+ b: {
+ [string]: {
+ 〈1;-〉
+ }
+ }
+ b: {
+ bar: {}
+ }
+ c: {
+ [string]: 〈0;-〉
+ }
+ c: {
+ [string]: {
+ {
+ {
+ 〈3;-〉
+ }
+ }
+ }
+ }
+ c: {
+ bar: {}
+ }
+ d: {
+ [string]: {
+ name: 〈1;-〉
+ }
+ }
+ d: {
+ bar: {}
+ }
+ e: {
+ [string]: {
+ [string]: (〈1;-〉 + 〈0;-〉)
+ }
+ }
+ e: {
+ foo: {
+ bar: {}
+ }
+ }
+ bar: 3
+ f1: {
+ "foo\(〈1;bar〉)": {
+ name: "xx"
+ foo: 〈1;("foo\(〈1;bar〉)")〉.name
+ }
+ }
+ f1: {
+ "foo\(〈1;bar〉)": {
+ "foo\(〈2;bar〉)baz": {
+ name: "xx"
+ a: 〈2;("foo\(〈1;bar〉)")〉["foo\(〈3;bar〉)baz"].name
+ b: 〈1;("foo\(〈2;bar〉)baz")〉.name
+ }
+ }
+ }
+ c1: {
+ "foo\(〈1;bar〉)": {
+ [string]: {
+ name: 〈1;-〉
+ foo: 〈2;("foo\(〈1;bar〉)")〉.x.name
+ }
+ }
+ }
+ c1: {
+ foo3: {
+ x: _
+ }
+ }
+}
diff --git a/cue/testdata/references/labelstop.txtar b/cue/testdata/references/labelstop.txtar
new file mode 100644
index 0000000..b936ab0
--- /dev/null
+++ b/cue/testdata/references/labelstop.txtar
@@ -0,0 +1,21 @@
+TODO: add maching when bulk optional fields are allowed
+alongside other fields.
+-- in.cue --
+{[X=string]: baz: X}
+bar: {}
+-- out/eval --
+(struct){
+ bar: (struct){
+ baz: (string){ "bar" }
+ }
+}
+-- out/compile --
+--- in.cue
+{
+ {
+ [string]: {
+ baz: 〈1;-〉
+ }
+ }
+ bar: {}
+}
diff --git a/cue/testdata/resolve/048_builtins.txtar b/cue/testdata/resolve/048_builtins.txtar
index 45da3b7..bcb6fab 100644
--- a/cue/testdata/resolve/048_builtins.txtar
+++ b/cue/testdata/resolve/048_builtins.txtar
@@ -44,3 +44,42 @@
}
-- out/legacy-debug --
<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"}}
+-- out/eval --
+(_|_){
+ // [eval]
+ a1: (struct){
+ a: (string){ &(=~"oo", =~"fo") }
+ b: (string){ =~"oo" }
+ c: (string){ =~"fo" }
+ }
+ a2: (struct){
+ a: (string){ "foo" }
+ b: (string){ =~"oo" }
+ c: (string){ =~"fo" }
+ }
+ a3: (_|_){
+ // [eval]
+ a: (_|_){
+ // [eval] invalid value *adt.Vertex (out of bound *adt.BoundValue)
+ }
+ b: (string){ =~"oo" }
+ c: (string){ =~"fo" }
+ }
+ o1: (struct){
+ a: (string){ |((string){ string }, (string){ "bar" }) }
+ b: (string){ string }
+ c: (string){ "bar" }
+ }
+ o2: (struct){
+ a: (string){ "foo" }
+ b: (string){ string }
+ c: (string){ "bar" }
+ }
+ o3: (struct){
+ a: (_|_){
+ // [incomplete] empty disjunction
+ }
+ b: (string){ "baz" }
+ c: (string){ "bar" }
+ }
+}
diff --git a/internal/core/adt/adt.go b/internal/core/adt/adt.go
index 8ae9338..120aee9 100644
--- a/internal/core/adt/adt.go
+++ b/internal/core/adt/adt.go
@@ -90,12 +90,16 @@
return x.Value.Concreteness()
}
+func (x *NodeLink) Concreteness() Concreteness { return Concrete }
func (x *ListMarker) Concreteness() Concreteness { return Concrete }
func (x *StructMarker) Concreteness() Concreteness { return Concrete }
-func (*Conjunction) Concreteness() Concreteness { return Constraint }
-func (*Disjunction) Concreteness() Concreteness { return Constraint }
-func (*BoundValue) Concreteness() Concreteness { return Constraint }
+func (*Conjunction) Concreteness() Concreteness { return Constraint }
+func (*Disjunction) Concreteness() Concreteness { return Constraint }
+func (*BoundValue) Concreteness() Concreteness { return Constraint }
+
+// Constraint only applies if Builtin is used as constraint.
+func (*Builtin) Concreteness() Concreteness { return Constraint }
func (*BuiltinValidator) Concreteness() Concreteness { return Constraint }
// Value and Expr
@@ -132,9 +136,11 @@
func (*Disjunction) expr() {}
func (*BoundValue) expr() {}
func (*BuiltinValidator) expr() {}
+func (*Builtin) expr() {}
// Expr and Resolver
+func (*NodeLink) expr() {}
func (*FieldReference) expr() {}
func (*LabelReference) expr() {}
func (*DynamicReference) expr() {}
@@ -207,6 +213,8 @@
func (*BoundValue) elemNode() {}
func (*BuiltinValidator) declNode() {}
func (*BuiltinValidator) elemNode() {}
+func (*NodeLink) declNode() {}
+func (*NodeLink) elemNode() {}
func (*FieldReference) declNode() {}
func (*FieldReference) elemNode() {}
func (*LabelReference) declNode() {}
@@ -231,6 +239,8 @@
func (*BinaryExpr) elemNode() {}
func (*CallExpr) declNode() {}
func (*CallExpr) elemNode() {}
+func (*Builtin) declNode() {}
+func (*Builtin) elemNode() {}
func (*DisjunctionExpr) declNode() {}
func (*DisjunctionExpr) elemNode() {}
@@ -249,6 +259,7 @@
func (*Conjunction) node() {}
func (*Disjunction) node() {}
func (*BoundValue) node() {}
+func (*Builtin) node() {}
func (*BuiltinValidator) node() {}
func (*Bottom) node() {}
func (*Null) node() {}
@@ -261,6 +272,7 @@
func (*StructLit) node() {}
func (*ListLit) node() {}
func (*BoundExpr) node() {}
+func (*NodeLink) node() {}
func (*FieldReference) node() {}
func (*LabelReference) node() {}
func (*DynamicReference) node() {}
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index 8f9e07e..3718304 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -60,6 +60,10 @@
// StringIndexer allows for converting string labels to and from a
// canonical numeric representation.
StringIndexer
+
+ // LoadImport loads a unique Vertex associated with a given import path. It
+ // returns an error if no import for this package could be found.
+ LoadImport(importPath string) (*Vertex, errors.Error)
}
type Config struct {
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index 2346519..4836405 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -17,6 +17,7 @@
import (
"bytes"
"fmt"
+ "io"
"regexp"
"github.com/cockroachdb/apd/v2"
@@ -396,6 +397,20 @@
}
}
+// A NodeLink is used during computation to refer to an existing Vertex.
+// It is used to signal a potential cycle or reference.
+// Note that a NodeLink may be used as a value. This should be taken into
+// account.
+type NodeLink struct {
+ Node *Vertex
+}
+
+func (x *NodeLink) Kind() Kind {
+ return x.Node.Kind()
+}
+func (x *NodeLink) Source() ast.Node { return x.Node.Source() }
+func (x *NodeLink) resolve(c *OpContext) *Vertex { return x.Node }
+
// A FieldReference represents a lexical reference to a field.
//
// a
@@ -504,18 +519,11 @@
return x.Src
}
-// TODO: imports
-// func (x *ImportReference) resolve(c *context, e *environment) (arc, *Bottom) {
-// return c.r.lookupImport(e, x.importPath)
-// }
-
-// func (x *ImportReference) eval(c *context, e *environment) envVal {
-// arc, err := lookup(e, e.node, x.label)
-// if err != nil {
-// return err
-// }
-// return envVal{e, arc.eval()}
-// }
+func (x *ImportReference) resolve(ctx *OpContext) *Vertex {
+ path := x.ImportPath.StringValue(ctx)
+ v, _ := ctx.Runtime.LoadImport(path)
+ return v
+}
// A LetReference evaluates a let expression in its original environment.
//
@@ -878,20 +886,122 @@
}
func (x *CallExpr) evaluate(c *OpContext) Value {
- c.addErrf(0, pos(x), "cannot call non-function %s (type %s)",
- x.Fun, "nil")
- return nil
+ fun := c.value(x.Fun)
+ args := []Value{}
+ for _, a := range x.Args {
+ expr := c.value(a)
+ args = append(args, expr)
+ }
+ if c.HasErr() {
+ return nil
+ }
+ b, _ := fun.(*Builtin)
+ if b == nil {
+ c.addErrf(0, pos(x.Fun), "cannot call non-function %s (type %s)",
+ c.Str(x.Fun), kind(fun))
+ return nil
+ }
+ result := b.call(c, x.Src, args)
+ if result == nil {
+ return nil
+ }
+ return c.eval(result)
}
+// A Builtin is a value representing a native function call.
+type Builtin struct {
+ // TODO: make these values for better type checking.
+ Params []Kind
+ Result Kind
+ Func func(c *OpContext, args []Value) Expr
+
+ Package Feature
+ Name string
+ // REMOVE: for legacy
+ Const string
+}
+
+func (x *Builtin) WriteName(w io.Writer, c *OpContext) {
+ _, _ = fmt.Fprintf(w, "%s.%s", x.Package.StringValue(c), x.Name)
+}
+
+// Kind here represents the case where Builtin is used as a Validator.
+func (x *Builtin) Kind() Kind {
+ if len(x.Params) == 0 {
+ return BottomKind
+ }
+ return x.Params[0]
+}
+
+func (x *Builtin) validate(c *OpContext, v Value) *Bottom {
+ if x.Result != BoolKind {
+ return c.NewErrf(
+ "invalid validator %s: not a bool return", x.Name)
+ }
+ if len(x.Params) != 1 {
+ return c.NewErrf(
+ "invalid validator %s: may only have one validator to be used without call", x.Name)
+ }
+ return validateWithBuiltin(c, nil, x, []Value{v})
+}
+
+func bottom(v Value) *Bottom {
+ if x, ok := v.(*Vertex); ok {
+ v = x.Value
+ }
+ b, _ := v.(*Bottom)
+ return b
+}
+
+func (x *Builtin) call(c *OpContext, call *ast.CallExpr, args []Value) Expr {
+ if len(x.Params)-1 == len(args) && x.Result == BoolKind {
+ // We have a custom builtin
+ return &BuiltinValidator{call, x, args}
+ }
+ switch {
+ case len(x.Params) < len(args):
+ c.addErrf(0, call.Rparen,
+ "too many arguments in call to %s (have %d, want %d)",
+ call.Fun, len(args), len(x.Params))
+ return nil
+ case len(x.Params) > len(args):
+ c.addErrf(0, call.Rparen,
+ "not enough arguments in call to %s (have %d, want %d)",
+ call.Fun, len(args), len(x.Params))
+ return nil
+ }
+ for i, a := range args {
+ if x.Params[i] != BottomKind {
+ if b := bottom(a); b != nil {
+ return b
+ }
+ if k := kind(a); x.Params[i]&k == BottomKind {
+ code := EvalError
+ b, _ := args[i].(*Bottom)
+ if b != nil {
+ code = b.Code
+ }
+ c.addErrf(code, pos(a),
+ "cannot use %s (type %s) as %s in argument %d to %s",
+ a, k, x.Params[i], i+1, call.Fun)
+ return nil
+ }
+ }
+ }
+ return x.Func(c, args)
+}
+
+func (x *Builtin) Source() ast.Node { return nil }
+
// A BuiltinValidator is a Value that results from evaluation a partial call
// to a builtin (using CallExpr).
//
// strings.MinRunes(4)
//
type BuiltinValidator struct {
- Src *ast.CallExpr
- Fun Expr
- Args []Value // any but the first value
+ Src *ast.CallExpr
+ Builtin *Builtin
+ Args []Value // any but the first value
}
func (x *BuiltinValidator) Source() ast.Node {
@@ -900,10 +1010,50 @@
}
return x.Src
}
-func (x *BuiltinValidator) Kind() Kind { return TopKind }
+
+func (x *BuiltinValidator) Kind() Kind {
+ return x.Builtin.Params[0]
+}
func (x *BuiltinValidator) validate(c *OpContext, v Value) *Bottom {
- return nil
+ args := make([]Value, len(x.Args)+1)
+ args[0] = v
+ copy(args[1:], x.Args)
+ return validateWithBuiltin(c, x.Src, x.Builtin, args)
+}
+
+func validateWithBuiltin(c *OpContext, src *ast.CallExpr, b *Builtin, args []Value) *Bottom {
+ res := b.call(c, src, args)
+ switch v := res.(type) {
+ case nil:
+ return nil
+
+ case *Bottom:
+ return v
+
+ case *Bool:
+ if v.B {
+ return nil
+ }
+
+ default:
+ return c.NewErrf("invalid validator %s.%s", b.Package.StringValue(c), b.Name)
+ }
+
+ // failed:
+ var buf bytes.Buffer
+ b.WriteName(&buf, c)
+ if len(args) > 1 {
+ buf.WriteString("(")
+ for i, a := range args[1:] {
+ if i > 0 {
+ _, _ = buf.WriteString(", ")
+ }
+ buf.WriteString(c.Str(a))
+ }
+ buf.WriteString(")")
+ }
+ return c.NewErrf("invalid value %s (does not satisfy %s)", c.Str(args[0]), buf.String())
}
// A Disjunction represents a disjunction, where each disjunct may or may not
diff --git a/internal/core/compile/builtin.go b/internal/core/compile/builtin.go
new file mode 100644
index 0000000..8934019
--- /dev/null
+++ b/internal/core/compile/builtin.go
@@ -0,0 +1,137 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compile
+
+import (
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/internal/core/adt"
+)
+
+// This file contains predeclared builtins.
+
+const supportedByLen = adt.StructKind | adt.BytesKind | adt.StringKind | adt.ListKind
+
+var lenBuiltin = &adt.Builtin{
+ Name: "len",
+ Params: []adt.Kind{supportedByLen},
+ Result: adt.IntKind,
+ Func: func(c *adt.OpContext, args []adt.Value) adt.Expr {
+ v := args[0]
+ if x, ok := v.(*adt.Vertex); ok {
+ switch x.Value.(type) {
+ case nil:
+ // This should not happen, but be defensive.
+ return c.NewErrf("unevaluated vertex")
+ case *adt.ListMarker:
+ return c.NewInt64(int64(len(x.Elems())), v)
+
+ case *adt.StructMarker:
+ n := 0
+ v, _ := v.(*adt.Vertex)
+ for _, a := range v.Arcs {
+ if a.Label.IsRegular() {
+ n++
+ }
+ }
+ return c.NewInt64(int64(n), v)
+
+ default:
+ v = x.Value
+ }
+ }
+
+ switch x := v.(type) {
+ case *adt.Bytes:
+ return c.NewInt64(int64(len(x.B)), v)
+ case *adt.String:
+ return c.NewInt64(int64(len(x.Str)), v)
+ default:
+ k := x.Kind()
+ if k&supportedByLen == adt.BottomKind {
+ return c.NewErrf("invalid argument type %v", k)
+ }
+ b := c.NewErrf("incomplete argument %s (type %v)", c.Str(v), k)
+ b.Code = adt.IncompleteError
+ return b
+ }
+ },
+}
+
+var closeBuiltin = &adt.Builtin{
+ Name: "close",
+ Params: []adt.Kind{adt.StructKind},
+ Result: adt.StructKind,
+ Func: func(c *adt.OpContext, args []adt.Value) adt.Expr {
+ s, ok := args[0].(*adt.Vertex)
+ if !ok {
+ return c.NewErrf("struct argument must be concrete")
+ }
+ if s.IsClosed(c) {
+ return s
+ }
+ v := *s
+ v.Value = &adt.StructMarker{NeedClose: true}
+ return &v
+ },
+}
+
+var andBuiltin = &adt.Builtin{
+ Name: "and",
+ Params: []adt.Kind{adt.ListKind},
+ Result: adt.IntKind,
+ Func: func(c *adt.OpContext, args []adt.Value) adt.Expr {
+ list := c.Elems(args[0])
+ if len(list) == 0 {
+ return &adt.Top{}
+ }
+ a := []adt.Value{}
+ for _, c := range list {
+ a = append(a, c.Value)
+ }
+ return &adt.Conjunction{Values: a}
+ },
+}
+
+var orBuiltin = &adt.Builtin{
+ Name: "or",
+ Params: []adt.Kind{adt.ListKind},
+ Result: adt.IntKind,
+ Func: func(c *adt.OpContext, args []adt.Value) adt.Expr {
+ d := []adt.Disjunct{}
+ for _, c := range c.Elems(args[0]) {
+ d = append(d, adt.Disjunct{Val: c, Default: 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.
+ return &adt.Bottom{
+ Code: adt.IncompleteError,
+ Err: errors.Newf(c.Pos(), "empty list in call to or"),
+ }
+ }
+ v := &adt.Vertex{}
+ // TODO: make a Disjunction.
+ v.AddConjunct(adt.MakeConjunct(nil,
+ &adt.DisjunctionExpr{Values: d, HasDefaults: false},
+ ))
+ c.Unify(c, v, adt.Finalized)
+ return v
+ },
+}
diff --git a/internal/core/compile/compile.go b/internal/core/compile/compile.go
index eacd993..f965240 100644
--- a/internal/core/compile/compile.go
+++ b/internal/core/compile/compile.go
@@ -37,7 +37,7 @@
//
// Files may return a completed parse even if it has errors.
func Files(cfg *Config, r adt.Runtime, files ...*ast.File) (*adt.Vertex, errors.Error) {
- c := &compiler{index: r}
+ c := newCompiler(cfg, r)
v := c.compileFiles(files)
@@ -47,7 +47,19 @@
return v, nil
}
+func newCompiler(cfg *Config, r adt.Runtime) *compiler {
+ c := &compiler{
+ index: r,
+ }
+ if cfg != nil {
+ c.Config = *cfg
+ }
+ return c
+}
+
type compiler struct {
+ Config
+
index adt.StringIndexer
stack []frame
@@ -192,6 +204,10 @@
return res
}
+func (c *compiler) compileExpr(x ast.Expr) adt.Expr {
+ return c.expr(x)
+}
+
// resolve assumes that all existing resolutions are legal. Validation should
// be done in a separate step if required.
//
diff --git a/internal/core/compile/predeclared.go b/internal/core/compile/predeclared.go
index 6c54d85..28d6b59 100644
--- a/internal/core/compile/predeclared.go
+++ b/internal/core/compile/predeclared.go
@@ -42,14 +42,14 @@
case "number", "__number":
return &adt.BasicType{Src: n, K: adt.NumKind}
- // case "len", "__len":
- // return lenBuiltin
- // case "close", "__close":
- // return closeBuiltin
- // case "and", "__and":
- // return andBuiltin
- // case "or", "__or":
- // return orBuiltin
+ case "len", "__len":
+ return lenBuiltin
+ case "close", "__close":
+ return closeBuiltin
+ case "and", "__and":
+ return andBuiltin
+ case "or", "__or":
+ return orBuiltin
}
if r, ok := predefinedRanges[n.Name]; ok {
diff --git a/internal/core/debug/compact.go b/internal/core/debug/compact.go
index 67a4da9..722a480 100644
--- a/internal/core/debug/compact.go
+++ b/internal/core/debug/compact.go
@@ -169,6 +169,16 @@
fmt.Fprint(w, x.Op)
w.node(x.Value)
+ case *adt.NodeLink:
+ w.string(openTuple)
+ for i, f := range x.Node.Path() {
+ if i > 0 {
+ w.string(".")
+ }
+ w.label(f)
+ }
+ w.string(closeTuple)
+
case *adt.FieldReference:
w.label(x.Label)
@@ -253,8 +263,15 @@
}
w.string(")")
+ case *adt.Builtin:
+ if x.Package != 0 {
+ w.label(x.Package)
+ w.string(".")
+ }
+ w.string(x.Name)
+
case *adt.BuiltinValidator:
- w.node(x.Fun)
+ w.node(x.Builtin)
w.string("(")
for i, a := range x.Args {
if i > 0 {
diff --git a/internal/core/debug/debug.go b/internal/core/debug/debug.go
index 6209b83..b61d58f 100644
--- a/internal/core/debug/debug.go
+++ b/internal/core/debug/debug.go
@@ -67,6 +67,12 @@
index adt.StringIndexer
indent string
cfg *Config
+
+ // modes:
+ // - show vertex
+ // - show original conjuncts
+ // - show unevaluated
+ // - auto
}
func (w *printer) string(s string) {
@@ -298,6 +304,16 @@
fmt.Fprint(w, x.Op)
w.node(x.Value)
+ case *adt.NodeLink:
+ w.string(openTuple)
+ for i, f := range x.Node.Path() {
+ if i > 0 {
+ w.string(".")
+ }
+ w.label(f)
+ }
+ w.string(closeTuple)
+
case *adt.FieldReference:
w.string(openTuple)
w.string(strconv.Itoa(int(x.UpCount)))
@@ -396,8 +412,15 @@
}
w.string(")")
+ case *adt.Builtin:
+ if x.Package != 0 {
+ w.label(x.Package)
+ w.string(".")
+ }
+ w.string(x.Name)
+
case *adt.BuiltinValidator:
- w.node(x.Fun)
+ w.node(x.Builtin)
w.string("(")
for i, a := range x.Args {
if i > 0 {
diff --git a/internal/core/eval/eval_test.go b/internal/core/eval/eval_test.go
index 9dea9a5..b8cb2bf 100644
--- a/internal/core/eval/eval_test.go
+++ b/internal/core/eval/eval_test.go
@@ -92,16 +92,8 @@
"cycle/025_cannot_resolve_references_that_would_be_ambiguous": "cycle",
- "export/020": "builtin",
- "resolve/034_closing_structs": "builtin",
- "resolve/048_builtins": "builtin",
-
- "fulleval/027_len_of_incomplete_types": "builtin",
-
- "fulleval/032_or_builtin_should_not_fail_on_non-concrete_empty_list": "builtin",
-
- "fulleval/049_alias_reuse_in_nested_scope": "builtin",
- "fulleval/053_issue312": "builtin",
+ "resolve/034_closing_structs": "close()",
+ "fulleval/053_issue312": "close()",
}
// TestX is for debugging. Do not delete.
diff --git a/internal/core/runtime/index.go b/internal/core/runtime/index.go
index 4907368..569e8ad 100644
--- a/internal/core/runtime/index.go
+++ b/internal/core/runtime/index.go
@@ -32,14 +32,6 @@
typeCache sync.Map // map[reflect.Type]evaluated
}
-// work around golang-ci linter bug: fields are used.
-func init() {
- var i index
- i.mutex.Lock()
- i.mutex.Unlock()
- i.typeCache.Load(1)
-}
-
// sharedIndex is used for indexing builtins and any other labels common to
// all instances.
var sharedIndex = newSharedIndex()
diff --git a/internal/core/runtime/runtime.go b/internal/core/runtime/runtime.go
index ecdaa0a..c2f7d83 100644
--- a/internal/core/runtime/runtime.go
+++ b/internal/core/runtime/runtime.go
@@ -14,6 +14,11 @@
package runtime
+import (
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/internal/core/adt"
+)
+
// A Runtime maintains data structures for indexing and resuse for evaluation.
type Runtime struct {
*index
@@ -25,3 +30,7 @@
index: newIndex(sharedIndex),
}
}
+
+func (x *Runtime) LoadImport(importPath string) (*adt.Vertex, errors.Error) {
+ return nil, nil
+}