pkg/list: add builtins for list concatenation and repetition

These will be used to rewrite from the current use of
list operators.

Change-Id: I2a6461ac7d45649950160bb835cecf2c408593b4
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8064
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/pkg/list/list.go b/pkg/list/list.go
index be8493b..dadc398 100644
--- a/pkg/list/list.go
+++ b/pkg/list/list.go
@@ -123,6 +123,47 @@
 	return flattenN(xs, depth)
 }
 
+// Repeat returns a new list consisting of count copies of list x.
+//
+// For instance:
+//
+//    Repeat([1, 2], 2)
+//
+// results in
+//
+//    [1, 2, 1, 2]
+//
+func Repeat(x []cue.Value, count int) ([]cue.Value, error) {
+	if count < 0 {
+		return nil, fmt.Errorf("negative count")
+	}
+	var a []cue.Value
+	for i := 0; i < count; i++ {
+		a = append(a, x...)
+	}
+	return a, nil
+}
+
+// Concat takes a list of lists and concatenates them.
+//
+// Concat([a, b, c]) is equivalent to
+//
+//     [ for x in a {x}, for x in b {x}, for x in c {x} ]
+//
+func Concat(a []cue.Value) ([]cue.Value, error) {
+	var res []cue.Value
+	for _, e := range a {
+		iter, err := e.List()
+		if err != nil {
+			return nil, err
+		}
+		for iter.Next() {
+			res = append(res, iter.Value())
+		}
+	}
+	return res, nil
+}
+
 // Take reports the prefix of length n of list x, or x itself if n > len(x).
 //
 // For instance:
diff --git a/pkg/list/pkg.go b/pkg/list/pkg.go
index c5f74e3..f62ae69 100644
--- a/pkg/list/pkg.go
+++ b/pkg/list/pkg.go
@@ -44,6 +44,31 @@
 			}
 		},
 	}, {
+		Name: "Repeat",
+		Params: []internal.Param{
+			{Kind: adt.ListKind},
+			{Kind: adt.IntKind},
+		},
+		Result: adt.ListKind,
+		Func: func(c *internal.CallCtxt) {
+			x, count := c.List(0), c.Int(1)
+			if c.Do() {
+				c.Ret, c.Err = Repeat(x, count)
+			}
+		},
+	}, {
+		Name: "Concat",
+		Params: []internal.Param{
+			{Kind: adt.ListKind},
+		},
+		Result: adt.ListKind,
+		Func: func(c *internal.CallCtxt) {
+			a := c.List(0)
+			if c.Do() {
+				c.Ret, c.Err = Concat(a)
+			}
+		},
+	}, {
 		Name: "Take",
 		Params: []internal.Param{
 			{Kind: adt.ListKind},
diff --git a/pkg/list/testdata/list.txtar b/pkg/list/testdata/list.txtar
new file mode 100644
index 0000000..6291774
--- /dev/null
+++ b/pkg/list/testdata/list.txtar
@@ -0,0 +1,118 @@
+-- in.cue --
+import "list"
+
+repeat: {
+    [string]: {x: _, n: int, v: list.Repeat(x, n)}
+
+    t1: {x: [], n: 0}
+    t2: {x: [1], n: 0}
+
+    t3: {x: [1], n: 1}
+    t4: {x: [1, 2], n: 1}
+
+    t5: {x: [], n: 3}
+    t6: {x: [1, 2], n: 3}
+    t7: {x: [1, 2, 3, 4], n: 3}
+
+    t8: {x: [1],  n: -1}
+}
+concat: {
+    [string]: {x: _, v: list.Concat(x)}
+
+    t1: x: []
+    t2: x: [[]]
+
+    t3: x: [[1]]
+    t4: x: [[1, 2]]
+    t5: x: [[1], [2]]
+
+    t6: x: [[1, 2], [3, 4]]
+
+    t7: x: 1
+    t8: x: [1, [2]]
+}
+-- out/list --
+Errors:
+error in call to list.Concat: cannot use value 1 (type int) as list
+error in call to list.Repeat: negative count
+concat.t7.v: cannot use 1 (type int) as list in argument 1 to list.Concat:
+    ./in.cue:30:12
+
+Result:
+repeat: {
+	t1: {
+		x: []
+		n: 0
+		v: []
+	}
+	t2: {
+		x: [1]
+		n: 0
+		v: []
+	}
+	t3: {
+		x: [1]
+		n: 1
+		v: [1]
+	}
+	t4: {
+		x: [1, 2]
+		n: 1
+		v: [1, 2]
+	}
+	t5: {
+		x: []
+		n: 3
+		v: []
+	}
+	t6: {
+		x: [1, 2]
+		n: 3
+		v: [1, 2, 1, 2, 1, 2]
+	}
+	t7: {
+		x: [1, 2, 3, 4]
+		n: 3
+		v: [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
+	}
+	t8: {
+		x: [1]
+		n: -1
+		v: _|_ // error in call to list.Repeat: negative count (and 1 more errors)
+	}
+}
+concat: {
+	t1: {
+		x: []
+		v: []
+	}
+	t2: {
+		x: [[]]
+		v: []
+	}
+	t3: {
+		x: [[1]]
+		v: [1]
+	}
+	t4: {
+		x: [[1, 2]]
+		v: [1, 2]
+	}
+	t5: {
+		x: [[1], [2]]
+		v: [1, 2]
+	}
+	t6: {
+		x: [[1, 2], [3, 4]]
+		v: [1, 2, 3, 4]
+	}
+	t7: {
+		x: 1
+		v: _|_ // concat.t7.v: cannot use 1 (type int) as list in argument 1 to list.Concat
+	}
+	t8: {
+		x: [1, [2]]
+		v: _|_ // error in call to list.Concat: cannot use value 1 (type int) as list (and 1 more errors)
+	}
+}
+