pkg/list: adding aggregation for decimal lists

this commit adds the aggregation functions avg, max, min, product, sum
on lists containing only numbers. it also adds a new type to builtin.go
and gen.go to have a direct transformation of cue.Value to
[]*internal.Decimal

Issue #78

Change-Id: I94640726b93f8bb23f43cf85330e2e9056cbcf70
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3103
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin.go b/cue/builtin.go
index d6716eb..d7fea05 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -574,6 +574,26 @@
 	return v
 }
 
+func (c *callCtxt) decimalList(i int) (a []*apd.Decimal) {
+	arg := c.args[i]
+	x := newValueRoot(c.ctx, arg)
+	v, err := x.List()
+	if err != nil {
+		c.invalidArgType(c.args[i], i, "list", err)
+		return nil
+	}
+	for j := 0; v.Next(); j++ {
+		num, err := v.Value().getNum(numKind)
+		if err != nil {
+			c.errf(c.src, err, "invalid list element %d in argument %d to %s: %v",
+				j, i, c.name(), err)
+			break
+		}
+		a = append(a, &num.v)
+	}
+	return a
+}
+
 func (c *callCtxt) strList(i int) (a []string) {
 	arg := c.args[i]
 	x := newValueRoot(c.ctx, arg)
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index 73d3fd7..6e63183 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -135,6 +135,51 @@
 		test("strconv", `strconv.FormatFloat(3.02, 1.0, 4, 64)`),
 		`_|_(cannot use 1.0 (type float) as int in argument 2 to strconv.FormatFloat)`,
 	}, {
+		test("list", `list.Avg([1, 2, 3, 4])`),
+		`2.5`,
+	}, {
+		test("list", `list.Avg([])`),
+		`_|_(error in call to list.Avg: empty list)`,
+	}, {
+		test("list", `list.Avg("foo")`),
+		`_|_(cannot use "foo" (type string) as list in argument 1 to list.Avg)`,
+	}, {
+		test("list", `list.Max([1, 2, 3, 4])`),
+		`4`,
+	}, {
+		test("list", `list.Max([])`),
+		`_|_(error in call to list.Max: empty list)`,
+	}, {
+		test("list", `list.Max("foo")`),
+		`_|_(cannot use "foo" (type string) as list in argument 1 to list.Max)`,
+	}, {
+		test("list", `list.Min([1, 2, 3, 4])`),
+		`1`,
+	}, {
+		test("list", `list.Min([])`),
+		`_|_(error in call to list.Min: empty list)`,
+	}, {
+		test("list", `list.Min("foo")`),
+		`_|_(cannot use "foo" (type string) as list in argument 1 to list.Min)`,
+	}, {
+		test("list", `list.Product([1, 2, 3, 4])`),
+		`24`,
+	}, {
+		test("list", `list.Product([])`),
+		`1`,
+	}, {
+		test("list", `list.Product("foo")`),
+		`_|_(cannot use "foo" (type string) as list in argument 1 to list.Product)`,
+	}, {
+		test("list", `list.Sum([1, 2, 3, 4])`),
+		`10`,
+	}, {
+		test("list", `list.Sum([])`),
+		`0`,
+	}, {
+		test("list", `list.Sum("foo")`),
+		`_|_(cannot use "foo" (type string) as list in argument 1 to list.Sum)`,
+	}, {
 		// Panics
 		test("math", `math.Jacobi(1000, 2000)`),
 		`_|_(error in call to math.Jacobi: big: invalid 2nd argument to Int.Jacobi: need odd integer but got 2000)`,
diff --git a/cue/builtins.go b/cue/builtins.go
index 2e6ef2a..efc4ffd 100644
--- a/cue/builtins.go
+++ b/cue/builtins.go
@@ -717,6 +717,118 @@
 					return false
 				}()
 			},
+		}, {
+			Name:   "Avg",
+			Params: []kind{listKind},
+			Result: numKind,
+			Func: func(c *callCtxt) {
+				xs := c.decimalList(0)
+				if c.do() {
+					c.ret, c.err = func() (interface{}, error) {
+						if 0 == len(xs) {
+							return nil, fmt.Errorf("empty list")
+						}
+
+						s := apd.New(0, 0)
+						for _, x := range xs {
+							_, err := internal.BaseContext.Add(s, x, s)
+							if err != nil {
+								return nil, err
+							}
+						}
+
+						var d apd.Decimal
+						l := apd.New(int64(len(xs)), 0)
+						_, err := internal.BaseContext.Quo(&d, s, l)
+						if err != nil {
+							return nil, err
+						}
+						return &d, nil
+					}()
+				}
+			},
+		}, {
+			Name:   "Max",
+			Params: []kind{listKind},
+			Result: numKind,
+			Func: func(c *callCtxt) {
+				xs := c.decimalList(0)
+				if c.do() {
+					c.ret, c.err = func() (interface{}, error) {
+						if 0 == len(xs) {
+							return nil, fmt.Errorf("empty list")
+						}
+
+						max := xs[0]
+						for _, x := range xs[1:] {
+							if -1 == max.Cmp(x) {
+								max = x
+							}
+						}
+						return max, nil
+					}()
+				}
+			},
+		}, {
+			Name:   "Min",
+			Params: []kind{listKind},
+			Result: numKind,
+			Func: func(c *callCtxt) {
+				xs := c.decimalList(0)
+				if c.do() {
+					c.ret, c.err = func() (interface{}, error) {
+						if 0 == len(xs) {
+							return nil, fmt.Errorf("empty list")
+						}
+
+						min := xs[0]
+						for _, x := range xs[1:] {
+							if +1 == min.Cmp(x) {
+								min = x
+							}
+						}
+						return min, nil
+					}()
+				}
+			},
+		}, {
+			Name:   "Product",
+			Params: []kind{listKind},
+			Result: numKind,
+			Func: func(c *callCtxt) {
+				xs := c.decimalList(0)
+				if c.do() {
+					c.ret, c.err = func() (interface{}, error) {
+						d := apd.New(1, 0)
+						for _, x := range xs {
+							_, err := internal.BaseContext.Mul(d, x, d)
+							if err != nil {
+								return nil, err
+							}
+						}
+						return d, nil
+					}()
+				}
+			},
+		}, {
+			Name:   "Sum",
+			Params: []kind{listKind},
+			Result: numKind,
+			Func: func(c *callCtxt) {
+				xs := c.decimalList(0)
+				if c.do() {
+					c.ret, c.err = func() (interface{}, error) {
+						d := apd.New(0, 0)
+						for _, x := range xs {
+							_, err := internal.BaseContext.Add(d, x, d)
+							if err != nil {
+								return nil, err
+							}
+						}
+						return d, nil
+					}()
+				}
+			},
 		}},
 	},
 	"math": &builtinPkg{
@@ -2027,6 +2139,7 @@
 			Func: func(c *callCtxt) {
 				s, min := c.string(0), c.int(1)
 				c.ret = func() interface{} {
+
 					return len([]rune(s)) >= min
 				}()
 			},
diff --git a/cue/gen.go b/cue/gen.go
index cd30512..7f607eb 100644
--- a/cue/gen.go
+++ b/cue/gen.go
@@ -399,6 +399,8 @@
 		return "bigRat"
 	case "internal.Decimal":
 		return "decimal"
+	case "[]*internal.Decimal":
+		return "decimalList"
 	case "cue.Struct":
 		return "structVal"
 	case "cue.Value":
@@ -440,6 +442,9 @@
 		cueKind += "numKind"
 	case "list":
 		cueKind += "listKind"
+	case "decimalList":
+		omitCheck = false
+		cueKind += "listKind"
 	case "strList":
 		omitCheck = false
 		cueKind += "listKind"
diff --git a/internal/internal.go b/internal/internal.go
index a7cdc42..f4e0ab7 100644
--- a/internal/internal.go
+++ b/internal/internal.go
@@ -69,6 +69,9 @@
 // keys.
 var CheckAndForkRuntime func(runtime, value interface{}) interface{}
 
+// BaseContext is used as CUEs default context for arbitrary-precision decimals
+var BaseContext = apd.BaseContext.WithPrecision(24)
+
 // ListEllipsis reports the list type and remaining elements of a list. If we
 // ever relax the usage of ellipsis, this function will likely change. Using
 // this function will ensure keeping correct behavior or causing a compiler
diff --git a/pkg/list/math.go b/pkg/list/math.go
new file mode 100644
index 0000000..4ce4ba3
--- /dev/null
+++ b/pkg/list/math.go
@@ -0,0 +1,99 @@
+// 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.
+
+package list
+
+import (
+	"fmt"
+
+	"cuelang.org/go/internal"
+	"github.com/cockroachdb/apd/v2"
+)
+
+// Avg returns the average value of a non empty list xs.
+func Avg(xs []*internal.Decimal) (*internal.Decimal, error) {
+	if 0 == len(xs) {
+		return nil, fmt.Errorf("empty list")
+	}
+
+	s := apd.New(0, 0)
+	for _, x := range xs {
+		_, err := internal.BaseContext.Add(s, x, s)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	var d apd.Decimal
+	l := apd.New(int64(len(xs)), 0)
+	_, err := internal.BaseContext.Quo(&d, s, l)
+	if err != nil {
+		return nil, err
+	}
+	return &d, nil
+}
+
+// Max returns the maximum value of a non empty list xs.
+func Max(xs []*internal.Decimal) (*internal.Decimal, error) {
+	if 0 == len(xs) {
+		return nil, fmt.Errorf("empty list")
+	}
+
+	max := xs[0]
+	for _, x := range xs[1:] {
+		if -1 == max.Cmp(x) {
+			max = x
+		}
+	}
+	return max, nil
+}
+
+// Min returns the minimum value of a non empty list xs.
+func Min(xs []*internal.Decimal) (*internal.Decimal, error) {
+	if 0 == len(xs) {
+		return nil, fmt.Errorf("empty list")
+	}
+
+	min := xs[0]
+	for _, x := range xs[1:] {
+		if +1 == min.Cmp(x) {
+			min = x
+		}
+	}
+	return min, nil
+}
+
+// Product returns the product of a non empty list xs.
+func Product(xs []*internal.Decimal) (*internal.Decimal, error) {
+	d := apd.New(1, 0)
+	for _, x := range xs {
+		_, err := internal.BaseContext.Mul(d, x, d)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return d, nil
+}
+
+// Sum returns the sum of a list non empty xs.
+func Sum(xs []*internal.Decimal) (*internal.Decimal, error) {
+	d := apd.New(0, 0)
+	for _, x := range xs {
+		_, err := internal.BaseContext.Add(d, x, d)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return d, nil
+}