patch 8.2.0878: no reduce() function
Problem: No reduce() function.
Solution: Add a reduce() function. (closes #5481)
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 06de421..bfc811b 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -769,6 +769,7 @@
{"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},
diff --git a/src/globals.h b/src/globals.h
index 57577d4..66b204b 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -1690,6 +1690,7 @@
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"));
diff --git a/src/list.c b/src/list.c
index 7c06cfc..40a910b 100644
--- a/src/list.c
+++ b/src/list.c
@@ -2305,4 +2305,109 @@
}
}
+/*
+ * "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)
diff --git a/src/proto/list.pro b/src/proto/list.pro
index c276874..77183af 100644
--- a/src/proto/list.pro
+++ b/src/proto/list.pro
@@ -51,4 +51,5 @@
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 : */
diff --git a/src/testdir/test_listdict.vim b/src/testdir/test_listdict.vim
index 1dea4a8..b1af87e 100644
--- a/src/testdir/test_listdict.vim
+++ b/src/testdir/test_listdict.vim
@@ -680,6 +680,37 @@
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 '))
diff --git a/src/version.c b/src/version.c
index af3aadf..4a27140 100644
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 878,
+/**/
877,
/**/
876,