Mercurial > vim
changeset 20649:1fa0ace0ba65 v8.2.0878
patch 8.2.0878: no reduce() function
Commit: https://github.com/vim/vim/commit/85629985b71035608a37ba3bde86968481490d46
Author: Bram Moolenaar <Bram@vim.org>
Date: Mon Jun 1 18:39:20 2020 +0200
patch 8.2.0878: no reduce() function
Problem: No reduce() function.
Solution: Add a reduce() function. (closes https://github.com/vim/vim/issues/5481)
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Mon, 01 Jun 2020 18:45:03 +0200 |
parents | 78db823f74d0 |
children | 92b4c98fc228 |
files | runtime/doc/eval.txt src/evalfunc.c src/globals.h src/list.c src/proto/list.pro src/testdir/test_listdict.vim src/version.c |
diffstat | 7 files changed, 163 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2679,6 +2679,8 @@ readdir({dir} [, {expr}]) List file name readdirex({dir} [, {expr}]) List file info in {dir} selected by {expr} readfile({fname} [, {type} [, {max}]]) List get list of lines from file {fname} +reduce({object}, {func} [, {initial}]) + any reduce {object} using {func} reg_executing() String get the executing register name reg_recording() String get the recording register name reltime([{start} [, {end}]]) List get time value @@ -7965,6 +7967,26 @@ readfile({fname} [, {type} [, {max}]]) Can also be used as a |method|: > GetFileName()->readfile() +reduce({object}, {func} [, {initial}]) *reduce()* *E998* + {func} is called for every item in {object}, which can be a + |List| or a |Blob|. {func} is called with two arguments: the + result so far and current item. After processing all items + the result is returned. + + {initial} is the initial result. When omitted, the first item + in {object} is used and {func} is first called for the second + item. If {initial} is not given and {object} is empty no + result can be computed, an E998 error is given. + + Examples: > + echo reduce([1, 3, 5], { acc, val -> acc + val }) + echo reduce(['x', 'y'], { acc, val -> acc .. val }, 'a') + echo reduce(0z1122, { acc, val -> 2 * acc + val }) +< + Can also be used as a |method|: > + echo mylist->reduce({ acc, val -> acc + val }, 0) + + reg_executing() *reg_executing()* Returns the single letter name of the register being executed. Returns an empty string when no register is being executed.
--- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -769,6 +769,7 @@ static funcentry_T global_functions[] = {"readdir", 1, 2, FEARG_1, ret_list_string, f_readdir}, {"readdirex", 1, 2, FEARG_1, ret_list_dict_any, f_readdirex}, {"readfile", 1, 3, FEARG_1, ret_any, f_readfile}, + {"reduce", 2, 3, FEARG_1, ret_any, f_reduce}, {"reg_executing", 0, 0, 0, ret_string, f_reg_executing}, {"reg_recording", 0, 0, 0, ret_string, f_reg_recording}, {"reltime", 0, 2, FEARG_1, ret_list_any, f_reltime},
--- a/src/globals.h +++ b/src/globals.h @@ -1690,6 +1690,7 @@ EXTERN char e_inval_string[] INIT(= N_(" EXTERN char e_const_option[] INIT(= N_("E996: Cannot lock an option")); EXTERN char e_unknown_option[] INIT(= N_("E113: Unknown option: %s")); EXTERN char e_letunexp[] INIT(= N_("E18: Unexpected characters in :let")); +EXTERN char e_reduceempty[] INIT(= N_("E998: Reduce of an empty %s with no initial value")); #endif #ifdef FEAT_QUICKFIX EXTERN char e_readerrf[] INIT(= N_("E47: Error while reading errorfile"));
--- a/src/list.c +++ b/src/list.c @@ -2305,4 +2305,109 @@ f_reverse(typval_T *argvars, typval_T *r } } +/* + * "reduce(list, { accumlator, element -> value } [, initial])" function + */ + void +f_reduce(typval_T *argvars, typval_T *rettv) +{ + typval_T accum; + char_u *func_name; + partial_T *partial = NULL; + funcexe_T funcexe; + typval_T argv[3]; + + if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) + { + emsg(_(e_listblobreq)); + return; + } + + if (argvars[1].v_type == VAR_FUNC) + func_name = argvars[1].vval.v_string; + else if (argvars[1].v_type == VAR_PARTIAL) + { + partial = argvars[1].vval.v_partial; + func_name = partial_name(partial); + } + else + func_name = tv_get_string(&argvars[1]); + if (*func_name == NUL) + return; // type error or empty name + + vim_memset(&funcexe, 0, sizeof(funcexe)); + funcexe.evaluate = TRUE; + funcexe.partial = partial; + + if (argvars[0].v_type == VAR_LIST) + { + list_T *l = argvars[0].vval.v_list; + listitem_T *li = NULL; + + CHECK_LIST_MATERIALIZE(l); + if (argvars[2].v_type == VAR_UNKNOWN) + { + if (l == NULL || l->lv_first == NULL) + { + semsg(_(e_reduceempty), "List"); + return; + } + accum = l->lv_first->li_tv; + li = l->lv_first->li_next; + } + else + { + accum = argvars[2]; + if (l != NULL) + li = l->lv_first; + } + + copy_tv(&accum, rettv); + for ( ; li != NULL; li = li->li_next) + { + argv[0] = accum; + argv[1] = li->li_tv; + if (call_func(func_name, -1, rettv, 2, argv, &funcexe) == FAIL) + return; + accum = *rettv; + } + } + else + { + blob_T *b = argvars[0].vval.v_blob; + int i; + + if (argvars[2].v_type == VAR_UNKNOWN) + { + if (b == NULL || b->bv_ga.ga_len == 0) + { + semsg(_(e_reduceempty), "Blob"); + return; + } + accum.v_type = VAR_NUMBER; + accum.vval.v_number = blob_get(b, 0); + i = 1; + } + else + { + accum = argvars[2]; + i = 0; + } + + copy_tv(&accum, rettv); + if (b != NULL) + { + for ( ; i < b->bv_ga.ga_len; i++) + { + argv[0] = accum; + argv[1].v_type = VAR_NUMBER; + argv[1].vval.v_number = blob_get(b, i); + if (call_func(func_name, -1, rettv, 2, argv, &funcexe) == FAIL) + return; + accum = *rettv; + } + } + } +} + #endif // defined(FEAT_EVAL)
--- a/src/proto/list.pro +++ b/src/proto/list.pro @@ -51,4 +51,5 @@ void f_extend(typval_T *argvars, typval_ void f_insert(typval_T *argvars, typval_T *rettv); void f_remove(typval_T *argvars, typval_T *rettv); void f_reverse(typval_T *argvars, typval_T *rettv); +void f_reduce(typval_T *argvars, typval_T *rettv); /* vim: set ft=c : */
--- a/src/testdir/test_listdict.vim +++ b/src/testdir/test_listdict.vim @@ -680,6 +680,37 @@ func Test_reverse_sort_uniq() call assert_fails("call sort([1, 2], function('min'))", "E702:") endfunc +" reduce a list or a blob +func Test_reduce() + call assert_equal(1, reduce([], { acc, val -> acc + val }, 1)) + call assert_equal(10, reduce([1, 3, 5], { acc, val -> acc + val }, 1)) + call assert_equal(2 * (2 * ((2 * 1) + 2) + 3) + 4, reduce([2, 3, 4], { acc, val -> 2 * acc + val }, 1)) + call assert_equal('a x y z', ['x', 'y', 'z']->reduce({ acc, val -> acc .. ' ' .. val}, 'a')) + call assert_equal(#{ x: 1, y: 1, z: 1 }, ['x', 'y', 'z']->reduce({ acc, val -> extend(acc, { val: 1 }) }, {})) + call assert_equal([0, 1, 2, 3], reduce([1, 2, 3], function('add'), [0])) + + let l = ['x', 'y', 'z'] + call assert_equal(42, reduce(l, function('get'), #{ x: #{ y: #{ z: 42 } } })) + call assert_equal(['x', 'y', 'z'], l) + + call assert_equal(1, reduce([1], { acc, val -> acc + val })) + call assert_equal('x y z', reduce(['x', 'y', 'z'], { acc, val -> acc .. ' ' .. val })) + call assert_equal(120, range(1, 5)->reduce({ acc, val -> acc * val })) + call assert_fails("call reduce([], { acc, val -> acc + val })", 'E998: Reduce of an empty List with no initial value') + + call assert_equal(1, reduce(0z, { acc, val -> acc + val }, 1)) + call assert_equal(1 + 0xaf + 0xbf + 0xcf, reduce(0zAFBFCF, { acc, val -> acc + val }, 1)) + call assert_equal(2 * (2 * 1 + 0xaf) + 0xbf, 0zAFBF->reduce({ acc, val -> 2 * acc + val }, 1)) + + call assert_equal(0xff, reduce(0zff, { acc, val -> acc + val })) + call assert_equal(2 * (2 * 0xaf + 0xbf) + 0xcf, reduce(0zAFBFCF, { acc, val -> 2 * acc + val })) + call assert_fails("call reduce(0z, { acc, val -> acc + val })", 'E998: Reduce of an empty Blob with no initial value') + + call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E897:') + call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E897:') + call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E897:') +endfunc + " splitting a string to a List using split() func Test_str_split() call assert_equal(['aa', 'bb'], split(' aa bb '))