From fa4cff101bad0b58fd6486bd5b0ec291ec144501 Mon Sep 17 00:00:00 2001 From: ckganesan Date: Fri, 23 Feb 2024 22:11:28 +0530 Subject: [PATCH 1/2] Enhanced min and max built-in functions to Support Arrays of Integers and Floats --- builtin/builtin.go | 51 ++++++++++++++++++++----------- builtin/builtin_test.go | 6 ++++ builtin/lib.go | 66 +++++++++++++++++++++++++++++++++-------- expr_test.go | 16 ++++++++++ testdata/examples.txt | 2 +- 5 files changed, 109 insertions(+), 32 deletions(-) diff --git a/builtin/builtin.go b/builtin/builtin.go index 4aad6aa9c..04e54b7d4 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: + 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: + 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 3a2850071..55d2191c0 100644 --- a/builtin/builtin_test.go +++ b/builtin/builtin_test.go @@ -77,8 +77,12 @@ 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}, {`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 +201,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 00b4c9210..9f60649d2 100644 --- a/builtin/lib.go +++ b/builtin/lib.go @@ -254,22 +254,62 @@ func String(arg any) any { return fmt.Sprintf("%v", arg) } -func Max(args ...any) (any, error) { - var max any - for _, arg := range args { - if max == nil || runtime.Less(max, arg) { - max = arg +func minMaxFunc(name string, fn func(any, any) bool, args []any) (any, error) { + switch len(args) { + case 1: + rv := reflect.ValueOf(args[0]) + switch rv.Kind() { + case reflect.Array, reflect.Slice: + switch v := args[0].(type) { + case []float32, []float64, []uint, []uint8, []uint16, []uint32, []uint64, []int, []int8, []int16, []int32, []int64: + rv := reflect.ValueOf(v) + if rv.Len() == 0 { + panic(fmt.Sprintf("not enough arguments to call %s", name)) + } + val := rv.Index(0).Interface() + for i := 1; i < rv.Len(); i++ { + elem := rv.Index(i).Interface() + if fn(val, elem) { + val = elem + } + } + return val, nil + case []any: + var val any + for _, iv := range v { + switch iv.(type) { + case uint, uint8, uint16, uint32, uint64, + int, int8, int16, int32, int64, + float32, float64: + if val == nil || fn(val, iv) { + val = iv + } + default: + return nil, fmt.Errorf("invalid argument for %s (type %T)", name, iv) + } + } + return val, nil + default: + panic(fmt.Sprintf("invalid argument for %s (type %T)", name, v)) + } + default: + return args[0], nil + } + default: + var val any + for _, arg := range args { + if val == nil || fn(val, arg) { + val = arg + } } + return val, nil } - return max, nil +} + +func Max(args ...any) (any, error) { + return minMaxFunc("max", runtime.Less, args) } func Min(args ...any) (any, error) { - var min any - for _, arg := range args { - if min == nil || runtime.More(min, arg) { - min = arg - } - } - return min, nil + return minMaxFunc("min", runtime.More, args) } diff --git a/expr_test.go b/expr_test.go index ed08cae53..74975362b 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 9abe85327..712aa91c0 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)) From 42739d37a9208808617355fcdc22c851e47475fc Mon Sep 17 00:00:00 2001 From: ckganesan Date: Sat, 24 Feb 2024 20:27:09 +0530 Subject: [PATCH 2/2] Enhanced min and max built-in functions to Support Arrays of Integers and Floats --- builtin/builtin.go | 4 +- builtin/builtin_test.go | 2 + builtin/lib.go | 85 +++++++++++++++++------------------------ 3 files changed, 38 insertions(+), 53 deletions(-) diff --git a/builtin/builtin.go b/builtin/builtin.go index 04e54b7d4..fc48e111a 100644 --- a/builtin/builtin.go +++ b/builtin/builtin.go @@ -405,7 +405,7 @@ var Builtins = []*Function{ default: for _, arg := range args { switch kind(arg) { - case reflect.Interface: + 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: @@ -431,7 +431,7 @@ var Builtins = []*Function{ default: for _, arg := range args { switch kind(arg) { - case reflect.Interface: + 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: diff --git a/builtin/builtin_test.go b/builtin/builtin_test.go index 55d2191c0..bc1a2e149 100644 --- a/builtin/builtin_test.go +++ b/builtin/builtin_test.go @@ -79,6 +79,8 @@ func TestBuiltin(t *testing.T) { {`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}, diff --git a/builtin/lib.go b/builtin/lib.go index 9f60649d2..b08c2ed2b 100644 --- a/builtin/lib.go +++ b/builtin/lib.go @@ -254,62 +254,45 @@ func String(arg any) any { return fmt.Sprintf("%v", arg) } +func Max(args ...any) (any, error) { + return minMaxFunc("max", runtime.Less, args) +} + +func Min(args ...any) (any, error) { + return minMaxFunc("min", runtime.More, args) +} + func minMaxFunc(name string, fn func(any, any) bool, args []any) (any, error) { - switch len(args) { - case 1: - rv := reflect.ValueOf(args[0]) - switch rv.Kind() { - case reflect.Array, reflect.Slice: - switch v := args[0].(type) { - case []float32, []float64, []uint, []uint8, []uint16, []uint32, []uint64, []int, []int8, []int16, []int32, []int64: - rv := reflect.ValueOf(v) - if rv.Len() == 0 { - panic(fmt.Sprintf("not enough arguments to call %s", name)) - } - val := rv.Index(0).Interface() - for i := 1; i < rv.Len(); i++ { - elem := rv.Index(i).Interface() - if fn(val, elem) { - val = elem - } - } - return val, nil - case []any: - var val any - for _, iv := range v { - switch iv.(type) { - case uint, uint8, uint16, uint32, uint64, - int, int8, int16, int32, int64, - float32, float64: - if val == nil || fn(val, iv) { - val = iv - } - default: - return nil, fmt.Errorf("invalid argument for %s (type %T)", name, iv) - } + var val any + for _, arg := range args { + 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 } - return val, nil - default: - panic(fmt.Sprintf("invalid argument for %s (type %T)", name, v)) } + 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: - return args[0], nil - } - default: - var val any - for _, arg := range args { - if val == nil || fn(val, arg) { - val = arg + 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 val, nil } -} - -func Max(args ...any) (any, error) { - return minMaxFunc("max", runtime.Less, args) -} - -func Min(args ...any) (any, error) { - return minMaxFunc("min", runtime.More, args) + return val, nil }