diff --git a/builtin/builtin.go b/builtin/builtin.go index 4aad6aa9..fc48e111 100644 --- a/builtin/builtin.go +++ b/builtin/builtin.go @@ -394,38 +394,53 @@ var Builtins = []*Function{ Name: "max", Func: Max, Validate: func(args []reflect.Type) (reflect.Type, error) { - if len(args) == 0 { + switch len(args) { + case 0: return anyType, fmt.Errorf("not enough arguments to call max") - } - for _, arg := range args { - switch kind(arg) { - case reflect.Interface: + case 1: + if kindName := kind(args[0]); kindName == reflect.Array || kindName == reflect.Slice { return anyType, nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: - default: - return anyType, fmt.Errorf("invalid argument for max (type %s)", arg) } + fallthrough + default: + for _, arg := range args { + switch kind(arg) { + case reflect.Interface, reflect.Array, reflect.Slice: + return anyType, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: + default: + return anyType, fmt.Errorf("invalid argument for max (type %s)", arg) + } + } + return args[0], nil } - return args[0], nil }, }, { Name: "min", Func: Min, Validate: func(args []reflect.Type) (reflect.Type, error) { - if len(args) == 0 { + switch len(args) { + case 0: return anyType, fmt.Errorf("not enough arguments to call min") - } - for _, arg := range args { - switch kind(arg) { - case reflect.Interface: + case 1: + if kindName := kind(args[0]); kindName == reflect.Array || kindName == reflect.Slice { return anyType, nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: - default: - return anyType, fmt.Errorf("invalid argument for min (type %s)", arg) } + fallthrough + default: + for _, arg := range args { + switch kind(arg) { + case reflect.Interface, reflect.Array, reflect.Slice: + return anyType, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: + default: + return anyType, fmt.Errorf("invalid argument for min (type %s)", arg) + } + } + return args[0], nil + } - return args[0], nil }, }, { diff --git a/builtin/builtin_test.go b/builtin/builtin_test.go index 3a285007..bc1a2e14 100644 --- a/builtin/builtin_test.go +++ b/builtin/builtin_test.go @@ -77,8 +77,14 @@ func TestBuiltin(t *testing.T) { {`hasSuffix("foo,bar,baz", "baz")`, true}, {`max(1, 2, 3)`, 3}, {`max(1.5, 2.5, 3.5)`, 3.5}, + {`max([1, 2, 3])`, 3}, + {`max([1.5, 2.5, 3.5])`, 3.5}, + {`max([1, 2, 4, 10], 20, [29, 23, -19])`, 29}, + {`min([1, 2, 4, 10], 20, [29, 23, -19])`, -19}, {`min(1, 2, 3)`, 1}, {`min(1.5, 2.5, 3.5)`, 1.5}, + {`min([1, 2, 3])`, 1}, + {`min([1.5, 2.5, 3.5])`, 1.5}, {`sum(1..9)`, 45}, {`sum([.5, 1.5, 2.5])`, 4.5}, {`sum([])`, 0}, @@ -197,8 +203,10 @@ func TestBuiltin_errors(t *testing.T) { {`trim()`, `not enough arguments to call trim`}, {`max()`, `not enough arguments to call max`}, {`max(1, "2")`, `invalid argument for max (type string)`}, + {`max([1, "2"])`, `invalid argument for max (type string)`}, {`min()`, `not enough arguments to call min`}, {`min(1, "2")`, `invalid argument for min (type string)`}, + {`min([1, "2"])`, `invalid argument for min (type string)`}, {`duration("error")`, `invalid duration`}, {`date("error")`, `invalid date`}, {`get()`, `invalid number of arguments (expected 2, got 0)`}, diff --git a/builtin/lib.go b/builtin/lib.go index 00b4c921..b08c2ed2 100644 --- a/builtin/lib.go +++ b/builtin/lib.go @@ -255,21 +255,44 @@ func String(arg any) any { } func Max(args ...any) (any, error) { - var max any - for _, arg := range args { - if max == nil || runtime.Less(max, arg) { - max = arg - } - } - return max, nil + return minMaxFunc("max", runtime.Less, args) } func Min(args ...any) (any, error) { - var min any + return minMaxFunc("min", runtime.More, args) +} + +func minMaxFunc(name string, fn func(any, any) bool, args []any) (any, error) { + var val any for _, arg := range args { - if min == nil || runtime.More(min, arg) { - min = arg + switch v := arg.(type) { + case []float32, []float64, []uint, []uint8, []uint16, []uint32, []uint64, []int, []int8, []int16, []int32, []int64: + rv := reflect.ValueOf(v) + if rv.Len() == 0 { + return nil, fmt.Errorf("not enough arguments to call %s", name) + } + arg = rv.Index(0).Interface() + for i := 1; i < rv.Len(); i++ { + elem := rv.Index(i).Interface() + if fn(arg, elem) { + arg = elem + } + } + case []any: + var err error + if arg, err = minMaxFunc(name, fn, v); err != nil { + return nil, err + } + case float32, float64, uint, uint8, uint16, uint32, uint64, int, int8, int16, int32, int64: + default: + if len(args) == 1 { + return arg, nil + } + return nil, fmt.Errorf("invalid argument for %s (type %T)", name, v) + } + if val == nil || fn(val, arg) { + val = arg } } - return min, nil + return val, nil } diff --git a/expr_test.go b/expr_test.go index ed08cae5..74975362 100644 --- a/expr_test.go +++ b/expr_test.go @@ -853,6 +853,22 @@ func TestExpr(t *testing.T) { `len({a: 1, b: 2, c: 2})`, 3, }, + { + `max([1, 2, 3])`, + 3, + }, + { + `max(1, 2, 3)`, + 3, + }, + { + `min([1, 2, 3])`, + 1, + }, + { + `min(1, 2, 3)`, + 1, + }, { `{foo: 0, bar: 1}`, map[string]any{"foo": 0, "bar": 1}, diff --git a/testdata/examples.txt b/testdata/examples.txt index 9abe8532..712aa91c 100644 --- a/testdata/examples.txt +++ b/testdata/examples.txt @@ -13822,7 +13822,7 @@ min(ok ? array : false) min(ok ? f64 : i) min(ok ? half : ok) min(ok ? i : f32) -min(ok ? list : score) +min(ok ? array : score) min(ok ? true : div) min(reduce(array, #)) min(reduce(array, 0.5))