pkg/list: add sort functionality

Unfortunately, quite a few reordering in tests.

Change-Id: I205adc611051e042be98b8d96d3322aee1f064a9
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4481
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index f68db8b..0ec2863 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -240,6 +240,28 @@
 		test("list", `list.Slice([1, 2, 3, 4], 1, 5)`),
 		`_|_(error in call to list.Slice: slice bounds out of range)`,
 	}, {
+		test("list", `list.Sort([], list.Ascending)`),
+		`[]`,
+	}, {
+		test("list", `list.Sort([2, 3, 1, 4], {x:_, y:_, less: (x<y)})`),
+		`[1,2,3,4]`,
+	}, {
+		test("list", `list.SortStable([{a:2,v:1}, {a:1,v:2}, {a:1,v:3}], {
+			x:_,
+			y:_,
+			less: (x.a < y.a)
+		})`),
+		`[{a: 1, v: 2},{a: 1, v: 3},{a: 2, v: 1}]`,
+	}, {
+		test("list", `list.Sort([{a:1}, {b:2}], list.Ascending)`),
+		`_|_(error in call to list.Sort: conflicting values close(T, close(T)) and {b: 2} (mismatched types number|string and struct))`,
+	}, {
+		test("list", `list.SortStrings(["b", "a"])`),
+		`["a","b"]`,
+	}, {
+		test("list", `list.SortStrings([1, 2])`),
+		`_|_(invalid list element 0 in argument 0 to list.SortStrings: cannot use value 1 (type int) as string)`,
+	}, {
 		test("list", `list.Sum([1, 2, 3, 4])`),
 		`10`,
 	}, {
diff --git a/cue/builtins.go b/cue/builtins.go
index 9668515..5635d40 100644
--- a/cue/builtins.go
+++ b/cue/builtins.go
@@ -1080,7 +1080,96 @@
 					}()
 				}
 			},
+		}, {
+			Name:   "Sort",
+			Params: []kind{listKind, topKind},
+			Result: listKind,
+			Func: func(c *callCtxt) {
+				list, cmp := c.list(0), c.value(1)
+				if c.do() {
+					c.ret, c.err = func() (interface{}, error) {
+						s := valueSorter{list, cmp, nil}
+
+						sort.Sort(&s)
+						return s.ret()
+					}()
+				}
+			},
+		}, {
+			Name:   "SortStable",
+			Params: []kind{listKind, topKind},
+			Result: listKind,
+			Func: func(c *callCtxt) {
+				list, cmp := c.list(0), c.value(1)
+				if c.do() {
+					c.ret, c.err = func() (interface{}, error) {
+						s := valueSorter{list, cmp, nil}
+						sort.Stable(&s)
+						return s.ret()
+					}()
+				}
+			},
+		}, {
+			Name:   "SortStrings",
+			Params: []kind{listKind},
+			Result: listKind,
+			Func: func(c *callCtxt) {
+				a := c.strList(0)
+				if c.do() {
+					c.ret = func() interface{} {
+						sort.Strings(a)
+						return a
+					}()
+				}
+			},
+		}, {
+			Name:   "IsSorted",
+			Params: []kind{listKind, topKind},
+			Result: boolKind,
+			Func: func(c *callCtxt) {
+				list, cmp := c.list(0), c.value(1)
+				if c.do() {
+					c.ret = func() interface{} {
+						s := valueSorter{list, cmp, nil}
+						return sort.IsSorted(&s)
+					}()
+				}
+			},
+		}, {
+			Name:   "IsSortedStrings",
+			Params: []kind{listKind},
+			Result: boolKind,
+			Func: func(c *callCtxt) {
+				a := c.strList(0)
+				if c.do() {
+					c.ret = func() interface{} {
+						return sort.StringsAreSorted(a)
+					}()
+				}
+			},
 		}},
+		cue: `{
+	Comparer :: {
+		T ::  _
+		less: bool
+		x:    T
+		y:    T
+	}
+	Ascending :: {
+		T ::  number | string
+		less: true && x < y
+		x:    T
+		y:    T
+		Comparer
+	}
+	Descending :: {
+		T ::  number | string
+		less: x > y
+		x:    T
+		y:    T
+		Comparer
+	}
+}`,
 	},
 	"math": &builtinPkg{
 		native: []*builtin{{
diff --git a/cue/builtinutil.go b/cue/builtinutil.go
new file mode 100644
index 0000000..d3c471e
--- /dev/null
+++ b/cue/builtinutil.go
@@ -0,0 +1,57 @@
+// Copyright 2019 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 cue
+
+// TODO: this code could be generated, but currently isn't.
+
+type valueSorter struct {
+	a   []Value
+	cmp Value
+	err error
+}
+
+func (s *valueSorter) ret() ([]Value, error) {
+	if s.err != nil {
+		return nil, s.err
+	}
+	// The input slice is already a copy and that we can modify it safely.
+	return s.a, nil
+}
+
+func (s *valueSorter) Len() int      { return len(s.a) }
+func (s *valueSorter) Swap(i, j int) { s.a[i], s.a[j] = s.a[j], s.a[i] }
+func (s *valueSorter) Less(i, j int) bool {
+	x := fill(s.cmp, s.a[i], "x")
+	x = fill(x, s.a[j], "y")
+	isLess, err := x.Lookup("less").Bool()
+	if err != nil && s.err == nil {
+		s.err = err
+		return true
+	}
+	return isLess
+}
+
+// fill creates a new value with the old value unified with the given value.
+// TODO: consider making this a method on Value.
+func fill(v Value, x interface{}, path ...string) Value {
+	ctx := v.ctx()
+	root := v.path.val()
+	for i := len(path) - 1; i >= 0; i-- {
+		x = map[string]interface{}{path[i]: x}
+	}
+	value := convert(ctx, root, true, x)
+	eval := binOp(ctx, baseValue{}, opUnify, root, value)
+	return newValueRoot(ctx, eval)
+}
diff --git a/cue/export_test.go b/cue/export_test.go
index 5dcf8d6..4771064 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -784,11 +784,11 @@
 			T :: {
 				[string]: int64
 			}
-			X :: {
+			x: {
 				[string]: int64
 				x:        int64
 			}
-			x: {
+			X :: {
 				[string]: int64
 				x:        int64
 			}
@@ -809,10 +809,10 @@
 		{
 			T :: {
 			}
+			x: x: int64
 			X :: {
 				x: int64
 			}
-			x: x: int64
 		}`),
 	}, {
 		eval: true,
diff --git a/cue/go_test.go b/cue/go_test.go
index b311a97..621dfa8 100644
--- a/cue/go_test.go
+++ b/cue/go_test.go
@@ -170,13 +170,13 @@
 			T time.Time
 			G func()
 		}{},
-		`(*null | <0>{A: ((int & >=-32768 & int & <=32767) & (>=0 & <100)), ` +
+		`(*null | <0>{T: _, ` +
+			`A: ((int & >=-32768 & int & <=32767) & (>=0 & <100)), ` +
 			`C: string, ` +
 			`D: bool, ` +
 			`F: float, ` +
 			`b: null, ` +
-			`L?: (*null | bytes), ` +
-			`T: _})`,
+			`L?: (*null | bytes)})`,
 	}, {
 		struct {
 			A int `cue:"<"` // invalid
@@ -191,7 +191,7 @@
 			T string `cue:""` // allowed
 			h int
 		}{},
-		"<0>{D?: number, T: string, P?: (*null | number), I?: _}",
+		"<0>{T: string, D?: number, P?: (*null | number), I?: _}",
 	}, {
 		struct {
 			A int8 `cue:"C-B"`
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 6637245..c4871fc 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -518,9 +518,9 @@
 			x: x + 1
 		`,
 		out: `<0>{` +
+			`x: _|_((100 & 101):conflicting values 100 and 101), ` +
 			`a: _|_((210 & 200):conflicting values 210 and 200), ` +
-			`b: _|_((210 & 200):conflicting values 210 and 200), ` +
-			`x: _|_((100 & 101):conflicting values 100 and 101)}`,
+			`b: _|_((210 & 200):conflicting values 210 and 200)}`,
 		// TODO: find a way to mark error in data.
 	}}
 	rewriteHelper(t, testCases, evalPartial)
@@ -557,7 +557,7 @@
 			x: a & b
 			y: b & c
 			`,
-		out: `<0>{a: "a", b: "a", c: (*"a" | *"b"), x: "a", y: (*"a" | *"b")}`,
+		out: `<0>{x: "a", y: (*"a" | *"b"), a: "a", b: "a", c: (*"a" | *"b")}`,
 	}}
 	rewriteHelper(t, testCases, evalFull)
 }
@@ -577,7 +577,7 @@
 		out: "<0>{a: 3, b: <1>{c: <2>{d: 3}}, c: <3>{c: 2}, d: <4>{d: 2}}",
 	}, {
 		in:  "`foo-bar`: 3\n x: `foo-bar`,",
-		out: `<0>{foo-bar: 3, x: 3}`,
+		out: `<0>{x: 3, foo-bar: 3}`,
 	}, {
 		desc: "resolution of quoted identifiers",
 		in: `
@@ -1541,7 +1541,7 @@
 			w: v & { b: 100 }
 			wp: v & { b: 100 }
 			`,
-		out: `<0>{a: <1>{b: int}, c: <2>{b: 100, d: (<3>.a.b + 3)}, x: <4>{b: int, c: (<5>.b + 5)}, y: <6>{b: 100, c: 105}, v: <7>{b: int, c: (<3>.v.b + 5)}, w: <8>{b: 100, c: (<3>.v.b + 5)}, wp: <9>{b: 100, c: (<3>.v.b + 5)}}`,
+		out: `<0>{x: <1>{b: int, c: (<2>.b + 5)}, y: <3>{b: 100, c: 105}, a: <4>{b: int}, c: <5>{b: 100, d: (<6>.a.b + 3)}, v: <7>{b: int, c: (<6>.v.b + 5)}, w: <8>{b: 100, c: (<6>.v.b + 5)}, wp: <9>{b: 100, c: (<6>.v.b + 5)}}`,
 		// TODO(#152): should be
 		// out: `<0>{a: <1>{b: int}, c: <2>{b: 100, d: (<3>.a.b + 3)}, x: <4>{b: int, c: (<5>.b + 5)}, y: <6>{b: 100, c: 105}, v: <7>{b: int, c: (<8>.b + 5)}, w: <9>{b: 100, c: 105}, wp: <10>{b: 100, c: 105}}`,
 	}, {
@@ -1639,7 +1639,7 @@
 				d: 4, // Combines constraints S.A, S.B, T.A, and T.B
 			}
 		}`,
-		out: "<0>{S: <1>{A: <2>{a: 1}, B: <3>{a: 1, b: 2}}, T: <4>{A: <5>{a: 1, c: 3}, B: <6>{a: 1, b: 2, c: 3, d: 4}}}",
+		out: "<0>{T: <1>{A: <2>{a: 1, c: 3}, B: <3>{a: 1, b: 2, c: 3, d: 4}}, S: <4>{A: <5>{a: 1}, B: <6>{a: 1, b: 2}}}",
 	}, {
 		desc: "field templates",
 		in: `
@@ -1659,7 +1659,7 @@
 				bar: _
 			}
 			`,
-		out: `<0>{a: <1>{[]: <2>(name: string)->int, k: 1}, b: <3>{[]: <4>(X: string)->(<5>{x: 0, y: (*1 | int)} & <6>{}), v: <7>{x: 0, y: (*1 | int)}, w: <8>{x: 0, y: (*1 | int)}}, c: <9>{[]: <10>(Name: string)-><11>{name: <10>.Name, y: 1}, foo: <12>{name: "foo", y: 1}, bar: <13>{name: "bar", y: 1}}}`,
+		out: `<0>{a: <1>{[]: <2>(name: string)->int, k: 1}, b: <3>{[]: <4>(X: string)->(<5>{x: 0, y: (*1 | int)} & <6>{}), v: <7>{x: 0, y: (*1 | int)}, w: <8>{x: 0, y: (*1 | int)}}, c: <9>{[]: <10>(Name: string)-><11>{y: 1, name: <10>.Name}, foo: <12>{y: 1, name: "foo"}, bar: <13>{y: 1, name: "bar"}}}`,
 	}, {
 		desc: "range unification",
 		in: `
@@ -2060,7 +2060,7 @@
 				bar: _
 			}
 			`,
-		out: `<0>{a: <1>{[]: <2>(name: string)->int, k: 1}, b: <3>{[]: <4>(X: string)->(<5>{x: 0, y: (*1 | int)} & <6>{}), v: <7>{x: 0, y: 1}, w: <8>{x: 0, y: 0}}, c: <9>{[]: <10>(Name: string)-><11>{name: <10>.Name, y: 1}, foo: <12>{name: "foo", y: 1}, bar: <13>{name: "bar", y: 1}}}`,
+		out: `<0>{a: <1>{[]: <2>(name: string)->int, k: 1}, b: <3>{[]: <4>(X: string)->(<5>{x: 0, y: (*1 | int)} & <6>{}), v: <7>{x: 0, y: 1}, w: <8>{x: 0, y: 0}}, c: <9>{[]: <10>(Name: string)-><11>{y: 1, name: <10>.Name}, foo: <12>{y: 1, name: "foo"}, bar: <13>{y: 1, name: "bar"}}}`,
 	}, {
 		desc: "field comprehension",
 		in: `
@@ -2773,7 +2773,7 @@
 		s: t & {
 			ok :: false
 		}`,
-		out: `<0>{t: <1>{ok :: true, x: int}, s: <2>{ok :: false}}`,
+		out: `<0>{t: <1>{x: int, ok :: true}, s: <2>{ok :: false}}`,
 	}, {
 		desc: "cross-dependent comprehension",
 		// TODO(eval): fix: c should ultimately be allowed the struct. Current
@@ -2793,7 +2793,7 @@
 		`,
 		// c should not be allowed, as it would break commutativiy.
 		// See comments above.
-		out: `<0>{a :: <1>C{b: bool if <2>.b yield <3>C{c: 4}}, x: _|_(4:field "c" not allowed in closed struct), y: _|_(4:field "c" not allowed in closed struct)}`,
+		out: `<0>{x: _|_(4:field "c" not allowed in closed struct), y: _|_(4:field "c" not allowed in closed struct), a :: <1>C{b: bool if <2>.b yield <3>C{c: 4}}}`,
 	}, {
 		desc: "optional expanded before lookup",
 		in: `
diff --git a/pkg/list/sort.cue b/pkg/list/sort.cue
new file mode 100644
index 0000000..4ea3e78
--- /dev/null
+++ b/pkg/list/sort.cue
@@ -0,0 +1,48 @@
+// Copyright 2019 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 list
+
+// A Comparer specifies whether one value is strictly less than another value.
+Comparer :: {
+	T ::  _
+	x:    T
+	y:    T
+	less: bool // true if x < y
+}
+
+// Ascending defines a Comparer to sort comparable values in increasing order.
+//
+// Example:
+//     list.Sort(a, list.Ascending)
+Ascending :: {
+	Comparer
+	T :: number | string
+	x:   T
+	y:   T
+	// TODO: the following will be fixed when removing old-school templating.
+	less: true && (x < y)
+}
+
+// Descending defines a Comparer to sort comparable values in decreasing order.
+//
+// Example:
+//     list.Sort(a, list.Descending)
+Descending :: {
+	Comparer
+	T ::  number | string
+	x:    T
+	y:    T
+	less: (x > y)
+}
diff --git a/pkg/list/sort.go b/pkg/list/sort.go
new file mode 100644
index 0000000..991bbc4
--- /dev/null
+++ b/pkg/list/sort.go
@@ -0,0 +1,96 @@
+// Copyright 2018 The 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.
+
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package list
+
+import (
+	"sort"
+
+	"cuelang.org/go/cue"
+)
+
+// valueSorter defines a sort.Interface; implemented in cue/builtinutil.go.
+type valueSorter struct {
+	a   []cue.Value
+	cmp cue.Value
+	err error
+}
+
+func (s *valueSorter) ret() ([]cue.Value, error) {
+	panic("implemented in cue/builtinutil.go")
+}
+
+func (s *valueSorter) Len() int           { panic("implemented in cue/builtinutil.go") }
+func (s *valueSorter) Swap(i, j int)      { panic("implemented in cue/builtinutil.go") }
+func (s *valueSorter) Less(i, j int) bool { panic("implemented in cue/builtinutil.go") }
+
+// Sort sorts data. It does O(n*log(n)) comparisons.
+// The sort is not guaranteed to be stable.
+//
+// cmp is a struct of the form {T :: _, x: T, y: T, less: bool}, where
+// less should reflect x < y.
+//
+// Example:
+//
+//    Sort([2, 3, 1], list.Ascending)
+//
+//    Sort{{a: 2}, {a: 3}, {a: 1}, {a: {}, b: {}, less: x.a < y.a}}
+//
+func Sort(list []cue.Value, cmp cue.Value) (sorted []cue.Value, err error) {
+	s := valueSorter{list, cmp, nil}
+	// The input slice is already a copy and that we can modify it safely.
+	sort.Sort(&s)
+	return s.ret()
+}
+
+// SortStable sorts data while keeping the original order of equal elements.
+// It does O(n*log(n)) comparisons.
+//
+// cmp is a struct of the form {T :: _, a: T, b: T, less: bool}, where
+// less should reflect a < b.
+//
+// Example:
+//
+//    Sort([2, 3, 1], list.Ascending)
+//
+//    Sort{{a: 2}, {a: 3}, {a: 1}, {a: {}, b: {}, less: x.a < y.a}}
+//
+func SortStable(list []cue.Value, cmp cue.Value) (sorted []cue.Value, err error) {
+	s := valueSorter{list, cmp, nil}
+	sort.Stable(&s)
+	return s.ret()
+}
+
+// Strings sorts a list of strings in increasing order.
+func SortStrings(a []string) []string {
+	sort.Strings(a)
+	return a
+}
+
+// IsSorted tests whether a list is sorted.
+//
+// See Sort for an example comparator.
+func IsSorted(list []cue.Value, cmp cue.Value) bool {
+	s := valueSorter{list, cmp, nil}
+	return sort.IsSorted(&s)
+}
+
+// IsSortedStrings tests whether a list is a sorted lists of strings.
+func IsSortedStrings(a []string) bool {
+	return sort.StringsAreSorted(a)
+}