Merge pull request #16553 from seandewar/vim-8.2.0878

vim-patch:8.2.{0882,1051,1083}: port `reduce()` function
This commit is contained in:
Sean Dewar 2022-02-08 17:05:27 +00:00 committed by GitHub
commit 46c93b4304
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 162 additions and 0 deletions

View File

@ -340,6 +340,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
@ -5772,6 +5774,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

@ -282,6 +282,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_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
/// ///
@ -8054,6 +8055,102 @@ 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 initial;
typval_T argv[3];
if (argvars[0].v_type == VAR_LIST) {
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);
initial = *TV_LIST_ITEM_TV(first);
li = TV_LIST_ITEM_NEXT(l, first);
} else {
initial = argvars[2];
li = tv_list_first(l);
}
tv_copy(&initial, rettv);
if (l != NULL) {
const VarLockStatus prev_locked = tv_list_locked(l);
const int called_emsg_start = called_emsg;
tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here
for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
argv[0] = *rettv;
argv[1] = *TV_LIST_ITEM_TV(li);
rettv->v_type = VAR_UNKNOWN;
const int r = call_func(func_name, -1, rettv, 2, argv, &funcexe);
tv_clear(&argv[0]);
if (r == FAIL || called_emsg != called_emsg_start) {
break;
}
}
tv_list_set_lock(l, prev_locked);
}
} 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;
}
initial.v_type = VAR_NUMBER;
initial.vval.v_number = tv_blob_get(b, 0);
i = 1;
} else if (argvars[2].v_type != VAR_NUMBER) {
emsg(_(e_number_exp));
return;
} else {
initial = argvars[2];
i = 0;
}
tv_copy(&initial, rettv);
for (; i < tv_blob_len(b); i++) {
argv[0] = *rettv;
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;
}
}
}
}
#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,49 @@ 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:')
let g:lut = [1, 2, 3, 4]
func EvilRemove()
call remove(g:lut, 1)
return 1
endfunc
call assert_fails("call reduce(g:lut, { acc, val -> EvilRemove() }, 1)", 'E742:')
unlet g:lut
delfunc EvilRemove
call assert_equal(42, reduce(v:_null_list, function('add'), 42))
call assert_equal(42, reduce(v:_null_blob, function('add'), 42))
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 '))