diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/eval.lua | 1 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 36 | ||||
-rw-r--r-- | src/nvim/eval/typval.c | 51 | ||||
-rw-r--r-- | src/nvim/testdir/test_flatten.vim | 81 |
4 files changed, 169 insertions, 0 deletions
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index d20e381481..023c60f118 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -111,6 +111,7 @@ return { filter={args=2}, finddir={args={1, 3}}, findfile={args={1, 3}}, + flatten={args={1, 2}}, float2nr={args=1}, floor={args=1, func="float_op_wrapper", data="&floor"}, fmod={args=2}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 831167a489..e350d09935 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2160,6 +2160,42 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = cmdstr; } + +/// "flatten(list[, {maxdepth}])" function +static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + list_T *list; + long maxdepth; + bool error = false; + + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "flatten()"); + return; + } + + if (argvars[1].v_type == VAR_UNKNOWN) { + maxdepth = 999999; + } else { + maxdepth = (long)tv_get_number_chk(&argvars[1], &error); + if (error) { + return; + } + if (maxdepth < 0) { + EMSG(_("E900: maxdepth must be non-negative number")); + return; + } + } + + list = argvars[0].vval.v_list; + if (list != NULL + && !tv_check_lock(tv_list_locked(list), + N_("flatten() argument"), + TV_TRANSLATE) + && tv_list_flatten(list, maxdepth) == OK) { + tv_copy(&argvars[0], rettv); + } +} + /* * "extend(list, list [, idx])" function * "extend(dict, dict [, action])" function diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 2394eb8099..ef8e66a992 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -640,6 +640,57 @@ tv_list_copy_error: return NULL; } +/// Flatten "list" in place to depth "maxdepth". +/// Does nothing if "maxdepth" is 0. +/// +/// @param[in,out] list List to flatten +/// @param[in] maxdepth Maximum depth that will be flattened +/// +/// @return OK or FAIL +int tv_list_flatten(list_T *list, long maxdepth) + FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT +{ + listitem_T *item; + listitem_T *to_free; + int n; + if (maxdepth == 0) { + return OK; + } + + n = 0; + item = list->lv_first; + while (item != NULL) { + fast_breakcheck(); + if (got_int) { + return FAIL; + } + if (item->li_tv.v_type == VAR_LIST) { + listitem_T *next = item->li_next; + + tv_list_drop_items(list, item, item); + tv_list_extend(list, item->li_tv.vval.v_list, next); + tv_clear(&item->li_tv); + to_free = item; + + if (item->li_prev == NULL) { + item = list->lv_first; + } else { + item = item->li_prev->li_next; + } + xfree(to_free); + + if (++n >= maxdepth) { + n = 0; + item = next; + } + } else { + n = 0; + item = item->li_next; + } + } + return OK; +} + /// Extend first list with the second /// /// @param[out] l1 List to extend. diff --git a/src/nvim/testdir/test_flatten.vim b/src/nvim/testdir/test_flatten.vim new file mode 100644 index 0000000000..99086611e1 --- /dev/null +++ b/src/nvim/testdir/test_flatten.vim @@ -0,0 +1,81 @@ +" Test for flatting list. +func Test_flatten() + call assert_fails('call flatten(1)', 'E686:') + call assert_fails('call flatten({})', 'E686:') + call assert_fails('call flatten("string")', 'E686:') + call assert_fails('call flatten([], [])', 'E745:') + call assert_fails('call flatten([], -1)', 'E900: maxdepth') + + call assert_equal([], flatten([])) + call assert_equal([], flatten([[]])) + call assert_equal([], flatten([[[]]])) + + call assert_equal([1, 2, 3], flatten([1, 2, 3])) + call assert_equal([1, 2, 3], flatten([[1], 2, 3])) + call assert_equal([1, 2, 3], flatten([1, [2], 3])) + call assert_equal([1, 2, 3], flatten([1, 2, [3]])) + call assert_equal([1, 2, 3], flatten([[1], [2], 3])) + call assert_equal([1, 2, 3], flatten([1, [2], [3]])) + call assert_equal([1, 2, 3], flatten([[1], 2, [3]])) + call assert_equal([1, 2, 3], flatten([[1], [2], [3]])) + + call assert_equal([1, 2, 3], flatten([[1, 2, 3], []])) + call assert_equal([1, 2, 3], flatten([[], [1, 2, 3]])) + call assert_equal([1, 2, 3], flatten([[1, 2], [], [3]])) + call assert_equal([1, 2, 3], flatten([[], [1, 2, 3], []])) + call assert_equal([1, 2, 3, 4], flatten(range(1, 4))) + + " example in the help + call assert_equal([1, 2, 3, 4, 5], flatten([1, [2, [3, 4]], 5])) + call assert_equal([1, 2, [3, 4], 5], flatten([1, [2, [3, 4]], 5], 1)) + + call assert_equal([0, [1], 2, [3], 4], flatten([[0, [1]], 2, [[3], 4]], 1)) + call assert_equal([1, 2, 3], flatten([[[[1]]], [2], [3]], 3)) + call assert_equal([[1], [2], [3]], flatten([[[1], [2], [3]]], 1)) + call assert_equal([[1]], flatten([[1]], 0)) + + " Make it flatten if the given maxdepth is larger than actual depth. + call assert_equal([1, 2, 3], flatten([[1, 2, 3]], 1)) + call assert_equal([1, 2, 3], flatten([[1, 2, 3]], 2)) + + let l:list = [[1], [2], [3]] + call assert_equal([1, 2, 3], flatten(l:list)) + call assert_equal([1, 2, 3], l:list) + + " Tests for checking reference counter works well. + let l:x = {'foo': 'bar'} + call assert_equal([1, 2, l:x, 3], flatten([1, [2, l:x], 3])) + call test_garbagecollect_now() + call assert_equal('bar', l:x.foo) + + let l:list = [[1], [2], [3]] + call assert_equal([1, 2, 3], flatten(l:list)) + call test_garbagecollect_now() + call assert_equal([1, 2, 3], l:list) + + " Tests for checking circular reference list can be flatten. + let l:x = [1] + let l:y = [x] + let l:z = flatten(l:y) + call assert_equal([1], l:z) + call test_garbagecollect_now() + let l:x[0] = 2 + call assert_equal([2], l:x) + call assert_equal([1], l:z) " NOTE: primitive types are copied. + call assert_equal([1], l:y) + + let l:x = [2] + let l:y = [1, [l:x], 3] " [1, [[2]], 3] + let l:z = flatten(l:y, 1) + call assert_equal([1, [2], 3], l:z) + let l:x[0] = 9 + call assert_equal([1, [9], 3], l:z) " Reference to l:x is kept. + call assert_equal([1, [9], 3], l:y) + + let l:x = [1] + let l:y = [2] + call add(x, y) " l:x = [1, [2]] + call add(y, x) " l:y = [2, [1, [...]]] + call assert_equal([1, 2, 1, 2], flatten(l:x, 2)) + call assert_equal([2, l:x], l:y) +endfunc |