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 '))
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    878,
+/**/
     877,
 /**/
     876,