diff --git a/cue/builtin.go b/cue/builtin.go index d6716ebc2..d7fea0531 100644 --- a/cue/builtin.go +++ b/cue/builtin.go @@ -574,6 +574,26 @@ func (c *callCtxt) iter(i int) (a Iterator) { 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 73d3fd752..6e631831f 100644 --- a/cue/builtin_test.go +++ b/cue/builtin_test.go @@ -134,6 +134,51 @@ func TestBuiltins(t *testing.T) { // Find a better alternative, as this call should go. 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)`), diff --git a/cue/builtins.go b/cue/builtins.go index 2e6ef2a3a..efc4ffd8c 100644 --- a/cue/builtins.go +++ b/cue/builtins.go @@ -717,6 +717,118 @@ var builtinPackages = map[string]*builtinPkg{ 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 @@ var builtinPackages = map[string]*builtinPkg{ 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 cd30512b1..7f607ebdc 100644 --- a/cue/gen.go +++ b/cue/gen.go @@ -399,6 +399,8 @@ func (g *generator) goKind(expr ast.Expr) string { return "bigRat" case "internal.Decimal": return "decimal" + case "[]*internal.Decimal": + return "decimalList" case "cue.Struct": return "structVal" case "cue.Value": @@ -440,6 +442,9 @@ func (g *generator) goToCUE(expr ast.Expr) (cueKind string, omitCheck bool) { 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 a7cdc4271..f4e0ab79b 100644 --- a/internal/internal.go +++ b/internal/internal.go @@ -69,6 +69,9 @@ var GetRuntime func(instance interface{}) interface{} // 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 000000000..4ce4ba3f6 --- /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 +}