feat(eval): partially port v8.2.0878

Problem:    No reduce() function.
Solution:   Add a reduce() function. (closes vim/vim#5481)
85629985b7

Needs CHECK_LIST_MATERIALIZE from v8.2.0751 (and range_list_materialize from
8.2.0149).

Move e_reduceempty to funcs.c, as it's only used there. Make it static.
Use tv_blob_len, tv_list_len == 0 for empty checks.
Replace vim_memset(&funcexe, 0, ...) with FUNCEXE_INIT.
Leave li initially undefined (tv_list_first returns NULL if list is NULL).

This patch has a memory leak fixed by v8.2.0882.
This commit is contained in:
Sean Dewar 2021-12-06 20:50:29 +00:00 committed by zeertzjq
parent 2870311a37
commit d746f5aa41
4 changed files with 138 additions and 0 deletions

View File

@ -330,6 +330,8 @@ range({expr} [, {max} [, {stride}]])
readdir({dir} [, {expr}]) List file names in {dir} selected by {expr} readdir({dir} [, {expr}]) List file names in {dir} selected by {expr}
readfile({fname} [, {type} [, {max}]]) readfile({fname} [, {type} [, {max}]])
List get list of lines from file {fname} 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_executing() String get the executing register name
reg_recorded() String get the last recorded register name reg_recorded() String get the last recorded register name
reg_recording() String get the recording register name reg_recording() String get the recording register name
@ -5589,6 +5591,25 @@ readfile({fname} [, {type} [, {max}]])
Can also be used as a |method|: > Can also be used as a |method|: >
GetFileName()->readfile() 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()* reg_executing() *reg_executing()*
Returns the single letter name of the register being executed. Returns the single letter name of the register being executed.
Returns an empty string when no register is being executed. Returns an empty string when no register is being executed.

View File

@ -276,6 +276,7 @@ return {
range={args={1, 3}, base=1}, range={args={1, 3}, base=1},
readdir={args={1, 2}, base=1}, readdir={args={1, 2}, base=1},
readfile={args={1, 3}, base=1}, readfile={args={1, 3}, base=1},
reduce={args={2, 3}, base=1},
reg_executing={}, reg_executing={},
reg_recording={}, reg_recording={},
reg_recorded={}, reg_recorded={},

View File

@ -100,6 +100,7 @@ PRAGMA_DIAG_POP
static char *e_listarg = N_("E686: Argument of %s must be a List"); static char *e_listarg = N_("E686: Argument of %s must be a List");
static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob"); static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob");
static char *e_invalwindow = N_("E957: Invalid window number"); static char *e_invalwindow = N_("E957: Invalid window number");
static char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value");
/// Dummy va_list for passing to vim_snprintf /// Dummy va_list for passing to vim_snprintf
/// ///
@ -7855,6 +7856,90 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} }
} }
/// "reduce(list, { accumlator, element -> value } [, initial])" function
static void f_reduce(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) {
emsg(_(e_listblobreq));
return;
}
const char_u *func_name;
partial_T *partial = NULL;
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 = (const char_u *)tv_get_string(&argvars[1]);
}
if (*func_name == NUL) {
return; // type error or empty name
}
funcexe_T funcexe = FUNCEXE_INIT;
funcexe.evaluate = true;
funcexe.partial = partial;
typval_T accum;
typval_T argv[3];
if (argvars[0].v_type == VAR_LIST) {
const list_T *const l = argvars[0].vval.v_list;
const listitem_T *li;
if (argvars[2].v_type == VAR_UNKNOWN) {
if (tv_list_len(l) == 0) {
semsg(_(e_reduceempty), "List");
return;
}
const listitem_T *const first = tv_list_first(l);
accum = *TV_LIST_ITEM_TV(first);
li = TV_LIST_ITEM_NEXT(l, first);
} else {
accum = argvars[2];
li = tv_list_first(l);
}
tv_copy(&accum, rettv);
for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
argv[0] = accum;
argv[1] = *TV_LIST_ITEM_TV(li);
if (call_func(func_name, -1, rettv, 2, argv, &funcexe) == FAIL) {
return;
}
accum = *rettv;
}
} else {
const blob_T *const b = argvars[0].vval.v_blob;
int i;
if (argvars[2].v_type == VAR_UNKNOWN) {
if (tv_blob_len(b) == 0) {
semsg(_(e_reduceempty), "Blob");
return;
}
accum.v_type = VAR_NUMBER;
accum.vval.v_number = tv_blob_get(b, 0);
i = 1;
} else {
accum = argvars[2];
i = 0;
}
tv_copy(&accum, rettv);
for (; i < tv_blob_len(b); i++) {
argv[0] = accum;
argv[1].v_type = VAR_NUMBER;
argv[1].vval.v_number = tv_blob_get(b, i);
if (call_func(func_name, -1, rettv, 2, argv, &funcexe) == FAIL) {
return;
}
accum = *rettv;
}
}
}
#define SP_NOMOVE 0x01 ///< don't move cursor #define SP_NOMOVE 0x01 ///< don't move cursor
#define SP_REPEAT 0x02 ///< repeat to find outer pair #define SP_REPEAT 0x02 ///< repeat to find outer pair
#define SP_RETCOUNT 0x04 ///< return matchcount #define SP_RETCOUNT 0x04 ///< return matchcount

View File

@ -620,6 +620,37 @@ func Test_reverse_sort_uniq()
call assert_fails('call reverse("")', 'E899:') call assert_fails('call reverse("")', 'E899:')
endfunc 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 " splitting a string to a List
func Test_str_split() func Test_str_split()
call assert_equal(['aa', 'bb'], split(' aa bb ')) call assert_equal(['aa', 'bb'], split(' aa bb '))