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}
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_recorded() String get the last recorded 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|: >
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.

View File

@ -282,6 +282,7 @@ return {
range={args={1, 3}, base=1},
readdir={args={1, 2}, base=1},
readfile={args={1, 3}, base=1},
reduce={args={2, 3}, base=1},
reg_executing={},
reg_recording={},
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_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
///
@ -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_REPEAT 0x02 ///< repeat to find outer pair
#define SP_RETCOUNT 0x04 ///< return matchcount

View File

@ -620,6 +620,49 @@ func Test_reverse_sort_uniq()
call assert_fails('call reverse("")', 'E899:')
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
func Test_str_split()
call assert_equal(['aa', 'bb'], split(' aa bb '))