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