pkg/list: adding flatten function as a builtin

this commit adds a new builtin to the list package for generating a
flat list from a nested lists by expanding nested lists in place.

for instance

    list.Flatten([1, [[2, 3], []], [4]])

results in

    [1, 2, 3, 4]

Change-Id: Ia462f7f2db504fd49601a3c77c0fae9387c038c4
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3460
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index 34979a8..0610c90 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -156,6 +156,12 @@
 		test("list", `list.Drop([1, 2, 3, 4], -1)`),
 		`_|_(error in call to list.Drop: negative index)`,
 	}, {
+		test("list", `list.Flatten([1, [[2, 3], []], [4]])`),
+		`[1,2,3,4]`,
+	}, {
+		test("list", `list.Flatten("foo")`),
+		`_|_(error in call to list.Flatten: cannot use value "foo" (type string) as list)`,
+	}, {
 		test("list", `list.Max([1, 2, 3, 4])`),
 		`4`,
 	}, {
diff --git a/cue/builtins.go b/cue/builtins.go
index a055e9a..037fa33 100644
--- a/cue/builtins.go
+++ b/cue/builtins.go
@@ -681,6 +681,37 @@
 				}()
 			},
 		}, {
+			Name:   "Flatten",
+			Params: []kind{topKind},
+			Result: listKind,
+			Func: func(c *callCtxt) {
+				xs := c.value(0)
+				c.ret, c.err = func() (interface{}, error) {
+					var flatten func(Value) ([]Value, error)
+					flatten = func(xs Value) ([]Value, error) {
+						var res []Value
+						iter, err := xs.List()
+						if err != nil {
+							return nil, err
+						}
+						for iter.Next() {
+							val := iter.Value()
+							if val.Kind() == ListKind {
+								vals, err := flatten(val)
+								if err != nil {
+									return nil, err
+								}
+								res = append(res, vals...)
+							} else {
+								res = append(res, val)
+							}
+						}
+						return res, nil
+					}
+					return flatten(xs)
+				}()
+			},
+		}, {
 			Name:   "Take",
 			Params: []kind{listKind, intKind},
 			Result: listKind,
diff --git a/pkg/list/list.go b/pkg/list/list.go
index 9249f3f..fa8c869 100644
--- a/pkg/list/list.go
+++ b/pkg/list/list.go
@@ -45,6 +45,42 @@
 	return x[n:], nil
 }
 
+// Flatten reports a flattend sequence of the list x by expanding any elements
+// that are lists.
+//
+// For instance:
+//
+//    Flatten([1, [[2, 3], []], [4]])
+//
+// results in
+//
+//    [1, 2, 3, 4]
+//
+func Flatten(xs cue.Value) ([]cue.Value, error) {
+	var flatten func(cue.Value) ([]cue.Value, error)
+	flatten = func(xs cue.Value) ([]cue.Value, error) {
+		var res []cue.Value
+		iter, err := xs.List()
+		if err != nil {
+			return nil, err
+		}
+		for iter.Next() {
+			val := iter.Value()
+			if val.Kind() == cue.ListKind {
+				vals, err := flatten(val)
+				if err != nil {
+					return nil, err
+				}
+				res = append(res, vals...)
+			} else {
+				res = append(res, val)
+			}
+		}
+		return res, nil
+	}
+	return flatten(xs)
+}
+
 // Take reports the prefix of length n of list x, or x itself if n > len(x).
 //
 // For instance: