diff --git a/builtin/builtin.go b/builtin/builtin.go index cc6f197c..0107d276 100644 --- a/builtin/builtin.go +++ b/builtin/builtin.go @@ -873,6 +873,37 @@ var Builtins = []*Function{ return arrayType, nil }, }, + { + Name: "flatten", + Safe: func(args ...any) (any, uint, error) { + var size uint + if len(args) != 1 { + return nil, 0, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + v := reflect.ValueOf(deref.Deref(args[0])) + if v.Kind() != reflect.Array && v.Kind() != reflect.Slice { + return nil, size, fmt.Errorf("cannot flatten %s", v.Kind()) + } + ret := flatten(v) + size = uint(len(ret)) + return ret, size, nil + }, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + + for _, arg := range args { + switch kind(deref.Type(arg)) { + case reflect.Interface, reflect.Slice, reflect.Array: + default: + return anyType, fmt.Errorf("cannot flatten %s", arg) + } + } + + return arrayType, nil + }, + }, { Name: "sort", Safe: func(args ...any) (any, uint, error) { diff --git a/builtin/builtin_test.go b/builtin/builtin_test.go index 97f24989..ddfb4a49 100644 --- a/builtin/builtin_test.go +++ b/builtin/builtin_test.go @@ -152,6 +152,9 @@ func TestBuiltin(t *testing.T) { {`reduce([], 5, 0)`, 0}, {`concat(ArrayOfString, ArrayOfInt)`, []any{"foo", "bar", "baz", 1, 2, 3}}, {`concat(PtrArrayWithNil, [nil])`, []any{42, nil}}, + {`flatten([["a", "b"], [1, 2]])`, []any{"a", "b", 1, 2}}, + {`flatten([["a", "b"], [1, 2, [3, 4]]])`, []any{"a", "b", 1, 2, 3, 4}}, + {`flatten([["a", "b"], [1, 2, [3, [[[["c", "d"], "e"]]], 4]]])`, []any{"a", "b", 1, 2, 3, "c", "d", "e", 4}}, } for _, test := range tests { @@ -236,6 +239,8 @@ func TestBuiltin_errors(t *testing.T) { {`now(nil)`, "invalid number of arguments (expected 0, got 1)"}, {`date(nil)`, "interface {} is nil, not string (1:1)"}, {`timezone(nil)`, "cannot use nil as argument (type string) to call timezone (1:10)"}, + {`flatten([1, 2], [3, 4])`, "invalid number of arguments (expected 1, got 2)"}, + {`flatten(1)`, "cannot flatten int"}, } for _, test := range errorTests { t.Run(test.input, func(t *testing.T) { diff --git a/builtin/lib.go b/builtin/lib.go index e3cd61b9..13c7d207 100644 --- a/builtin/lib.go +++ b/builtin/lib.go @@ -359,3 +359,17 @@ func median(args ...any) ([]float64, error) { } return values, nil } + +func flatten(arg reflect.Value) []any { + ret := []any{} + for i := 0; i < arg.Len(); i++ { + v := deref.Value(arg.Index(i)) + if v.Kind() == reflect.Array || v.Kind() == reflect.Slice { + x := flatten(v) + ret = append(ret, x...) + } else { + ret = append(ret, v.Interface()) + } + } + return ret +} diff --git a/docs/language-definition.md b/docs/language-definition.md index c8709aa0..9c277e9a 100644 --- a/docs/language-definition.md +++ b/docs/language-definition.md @@ -689,6 +689,14 @@ Concatenates two or more arrays. concat([1, 2], [3, 4]) == [1, 2, 3, 4] ``` +### flatten(array) {#flatten} + +Flattens given array into one-dimentional array. + +```expr +flatten([1, 2, [3, 4]]) == [1, 2, 3, 4] +``` + ### join(array[, delimiter]) {#join} Joins an array of strings into a single string with the given delimiter.