cue: add builtins and and or
These allow computing disjunctions and
conjunctions, for instance resulting from
comprehensions.
Change-Id: I1db6566c9114d70017c923e93f1c1d4091085db9
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/1920
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/ast.go b/cue/ast.go
index 6d6e8fa..3ff7483 100644
--- a/cue/ast.go
+++ b/cue/ast.go
@@ -345,6 +345,10 @@
case "len":
return lenBuiltin
+ case "and":
+ return andBuiltin
+ case "or":
+ return orBuiltin
}
if r, ok := predefinedRanges[n.Name]; ok {
return r
diff --git a/cue/builtin.go b/cue/builtin.go
index 02750f0..fa7e927 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -24,6 +24,7 @@
"path"
"sort"
+ "cuelang.org/go/cue/errors"
"cuelang.org/go/cue/parser"
)
@@ -131,6 +132,41 @@
},
}
+var andBuiltin = &builtin{
+ Name: "and",
+ Params: []kind{listKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ iter := c.list(0)
+ if !iter.Next() {
+ c.ret = &top{baseValue{c.src}}
+ return
+ }
+ u := iter.Value().path.v
+ for iter.Next() {
+ u = mkBin(c.ctx, c.src.Pos(), opUnify, u, iter.Value().path.v)
+ }
+ c.ret = u
+ },
+}
+
+var orBuiltin = &builtin{
+ Name: "or",
+ Params: []kind{stringKind | bytesKind | listKind | structKind},
+ Result: intKind,
+ Func: func(c *callCtxt) {
+ iter := c.list(0)
+ d := []dValue{}
+ for iter.Next() {
+ d = append(d, dValue{iter.Value().path.v, false})
+ }
+ c.ret = &disjunction{baseValue{c.src}, d}
+ if len(d) == 0 {
+ c.ret = errors.New("empty or")
+ }
+ },
+}
+
func (x *builtin) kind() kind {
return lambdaKind
}
@@ -176,6 +212,9 @@
}
}()
x.Func(&call)
+ if e, ok := call.ret.(value); ok {
+ return e
+ }
return convert(ctx, x, call.ret)
}
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index 59c3e19..3b617ab 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -113,6 +113,21 @@
}, {
testExpr(`len('f\x20\x20')`),
`3`,
+ }, {
+ testExpr(`and([string, "foo"])`),
+ `"foo"`,
+ }, {
+ testExpr(`and([string, =~"fo"]) & "foo"`),
+ `"foo"`,
+ }, {
+ testExpr(`and([])`),
+ `_`,
+ }, {
+ testExpr(`or([1, 2, 3]) & 2`),
+ `2`,
+ }, {
+ testExpr(`or([])`),
+ `_|_(builtin:or:empty or)`,
}}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 1e58e59..6a0db16 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -1311,6 +1311,32 @@
out: `<0>{obj: <1>{<>: <2>(Name: string)-><3>{a: (*"dummy" | string) if true yield ("sub"): <4>{as: <3>.a}}, ` +
`foo: <5>{a: "bar", sub: <6>{as: "bar"}}}}`,
}, {
+ 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"):"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: _|_((builtin:or ([<7>.b,<7>.c]) & "foo"):empty disjunction: failed to unify: baz != foo), b: "baz", c: "bar"}}`,
+ }, {
desc: "self-reference cycles conflicts with strings",
in: `
a: {
diff --git a/doc/ref/spec.md b/doc/ref/spec.md
index 7cacb59..2dbc8ac 100644
--- a/doc/ref/spec.md
+++ b/doc/ref/spec.md
@@ -2251,6 +2251,28 @@
len({a:1, b:2}) 2
```
+### `and`
+
+The built-in function `and` takes a list and returns the result of applying
+the `&` operator to all elements in the list.
+It returns top for the empty list.
+
+Expression: Result
+and([a, b]) a & b
+and([a]) a
+and([]) _
+
+### `or`
+
+The built-in function `or` takes a list and returns the result of applying
+the `|` operator to all elements in the list.
+It returns bottom for the empty list.
+
+Expression: Result
+and([a, b]) a | b
+and([a]) a
+and([]) _|_
+
## Cycles