vim-patch:partial:8.2.3849: functions implementing reduce and map are too long

Problem:    Functions implementing reduce and map are too long.
Solution:   Use a function for each type of value.  Add a few more test cases
            and add to the help. (Yegappan Lakshmanan, closes vim/vim#9370)

389b72196e

Partial port as this doesn't include handling for non-materialized List.

Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
This commit is contained in:
zeertzjq
2023-08-17 14:12:24 +08:00
parent c5576fcc80
commit b193674b4a
6 changed files with 418 additions and 346 deletions

View File

@@ -1635,8 +1635,8 @@ filter({expr1}, {expr2}) *filter()*
of the current item. For a |Dictionary| |v:key| has the key of the current item. For a |Dictionary| |v:key| has the key
of the current item and for a |List| |v:key| has the index of of the current item and for a |List| |v:key| has the index of
the current item. For a |Blob| |v:key| has the index of the the current item. For a |Blob| |v:key| has the index of the
current byte. current byte. For a |String| |v:key| has the index of the
current character.
Examples: >vim Examples: >vim
call filter(mylist, 'v:val !~ "OLD"') call filter(mylist, 'v:val !~ "OLD"')
< Removes the items where "OLD" appears. >vim < Removes the items where "OLD" appears. >vim
@@ -4080,7 +4080,8 @@ map({expr1}, {expr2}) *map()*
of the current item. For a |Dictionary| |v:key| has the key of the current item. For a |Dictionary| |v:key| has the key
of the current item and for a |List| |v:key| has the index of of the current item and for a |List| |v:key| has the index of
the current item. For a |Blob| |v:key| has the index of the the current item. For a |Blob| |v:key| has the index of the
current byte. current byte. For a |String| |v:key| has the index of the
current character.
Example: >vim Example: >vim
call map(mylist, '"> " .. v:val .. " <"') call map(mylist, '"> " .. v:val .. " <"')
< This puts "> " before and " <" after each item in "mylist". < This puts "> " before and " <" after each item in "mylist".
@@ -5514,9 +5515,9 @@ readfile({fname} [, {type} [, {max}]]) *readfile()*
reduce({object}, {func} [, {initial}]) *reduce()* *E998* reduce({object}, {func} [, {initial}]) *reduce()* *E998*
{func} is called for every item in {object}, which can be a {func} is called for every item in {object}, which can be a
|String|, |List| or a |Blob|. {func} is called with two arguments: |String|, |List| or a |Blob|. {func} is called with two
the result so far and current item. After processing all arguments: the result so far and current item. After
items the result is returned. processing all items the result is returned.
{initial} is the initial result. When omitted, the first item {initial} is the initial result. When omitted, the first item
in {object} is used and {func} is first called for the second in {object} is used and {func} is first called for the second

View File

@@ -2017,8 +2017,8 @@ function vim.fn.filewritable(file) end
--- of the current item. For a |Dictionary| |v:key| has the key --- of the current item. For a |Dictionary| |v:key| has the key
--- of the current item and for a |List| |v:key| has the index of --- of the current item and for a |List| |v:key| has the index of
--- the current item. For a |Blob| |v:key| has the index of the --- the current item. For a |Blob| |v:key| has the index of the
--- current byte. --- current byte. For a |String| |v:key| has the index of the
--- --- current character.
--- Examples: >vim --- Examples: >vim
--- call filter(mylist, 'v:val !~ "OLD"') --- call filter(mylist, 'v:val !~ "OLD"')
--- <Removes the items where "OLD" appears. >vim --- <Removes the items where "OLD" appears. >vim
@@ -4937,7 +4937,8 @@ function vim.fn.log10(expr) end
--- of the current item. For a |Dictionary| |v:key| has the key --- of the current item. For a |Dictionary| |v:key| has the key
--- of the current item and for a |List| |v:key| has the index of --- of the current item and for a |List| |v:key| has the index of
--- the current item. For a |Blob| |v:key| has the index of the --- the current item. For a |Blob| |v:key| has the index of the
--- current byte. --- current byte. For a |String| |v:key| has the index of the
--- current character.
--- Example: >vim --- Example: >vim
--- call map(mylist, '"> " .. v:val .. " <"') --- call map(mylist, '"> " .. v:val .. " <"')
--- <This puts "> " before and " <" after each item in "mylist". --- <This puts "> " before and " <" after each item in "mylist".
@@ -6551,9 +6552,9 @@ function vim.fn.readdir(directory, expr) end
function vim.fn.readfile(fname, type, max) end function vim.fn.readfile(fname, type, max) end
--- {func} is called for every item in {object}, which can be a --- {func} is called for every item in {object}, which can be a
--- |String|, |List| or a |Blob|. {func} is called with two arguments: --- |String|, |List| or a |Blob|. {func} is called with two
--- the result so far and current item. After processing all --- arguments: the result so far and current item. After
--- items the result is returned. --- processing all items the result is returned.
--- ---
--- {initial} is the initial result. When omitted, the first item --- {initial} is the initial result. When omitted, the first item
--- in {object} is used and {func} is first called for the second --- in {object} is used and {func} is first called for the second

View File

@@ -5026,79 +5026,20 @@ void assert_error(garray_T *gap)
tv_list_append_string(vimvars[VV_ERRORS].vv_list, gap->ga_data, (ptrdiff_t)gap->ga_len); tv_list_append_string(vimvars[VV_ERRORS].vv_list, gap->ga_data, (ptrdiff_t)gap->ga_len);
} }
/// Implementation of map() and filter(). /// Implementation of map() and filter() for a Dict.
static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap) static void filter_map_dict(dict_T *d, filtermap_T filtermap, const char *func_name,
const char *arg_errmsg, typval_T *expr, typval_T *rettv)
{ {
list_T *l = NULL;
dict_T *d = NULL;
blob_T *b = NULL;
int rem = false;
const char *const ermsg = (filtermap == FILTERMAP_MAP ? "map()"
: filtermap == FILTERMAP_MAPNEW ? "mapnew()" : "filter()");
const char *const arg_errmsg = (filtermap == FILTERMAP_MAP
? N_("map() argument")
: (filtermap == FILTERMAP_MAPNEW
? N_("mapnew() argument")
: N_("filter() argument")));
// map() and filter() return the first argument, also on failure.
if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING) {
tv_copy(&argvars[0], rettv);
}
if (argvars[0].v_type == VAR_BLOB) {
if (filtermap == FILTERMAP_MAPNEW) {
rettv->v_type = VAR_BLOB;
rettv->vval.v_blob = NULL;
}
if ((b = argvars[0].vval.v_blob) == NULL) {
return;
}
} else if (argvars[0].v_type == VAR_LIST) {
if (filtermap == FILTERMAP_MAPNEW) {
rettv->v_type = VAR_LIST;
rettv->vval.v_list = NULL;
}
if ((l = argvars[0].vval.v_list) == NULL
|| (filtermap == FILTERMAP_FILTER
&& value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) {
return;
}
} else if (argvars[0].v_type == VAR_DICT) {
if (filtermap == FILTERMAP_MAPNEW) { if (filtermap == FILTERMAP_MAPNEW) {
rettv->v_type = VAR_DICT; rettv->v_type = VAR_DICT;
rettv->vval.v_dict = NULL; rettv->vval.v_dict = NULL;
} }
if ((d = argvars[0].vval.v_dict) == NULL if (d == NULL
|| (filtermap == FILTERMAP_FILTER || (filtermap == FILTERMAP_FILTER
&& value_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { && value_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) {
return; return;
} }
} else if (argvars[0].v_type == VAR_STRING) {
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
} else {
semsg(_(e_argument_of_str_must_be_list_string_dictionary_or_blob), ermsg);
return;
}
typval_T *expr = &argvars[1];
// On type errors, the preceding call has already displayed an error
// message. Avoid a misleading error message for an empty string that
// was not passed as argument.
if (expr->v_type != VAR_UNKNOWN) {
int idx = 0;
typval_T save_val;
prepare_vimvar(VV_VAL, &save_val);
// We reset "did_emsg" to be able to detect whether an error
// occurred during evaluation of the expression.
int save_did_emsg = did_emsg;
did_emsg = false;
typval_T save_key;
prepare_vimvar(VV_KEY, &save_key);
if (argvars[0].v_type == VAR_DICT) {
const VarLockStatus prev_lock = d->dv_lock; const VarLockStatus prev_lock = d->dv_lock;
dict_T *d_ret = NULL; dict_T *d_ret = NULL;
@@ -5112,14 +5053,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
if (filtermap != FILTERMAP_FILTER && d->dv_lock == VAR_UNLOCKED) { if (filtermap != FILTERMAP_FILTER && d->dv_lock == VAR_UNLOCKED) {
d->dv_lock = VAR_LOCKED; d->dv_lock = VAR_LOCKED;
} }
hashtab_T *ht = &d->dv_hashtab; hash_lock(&d->dv_hashtab);
hash_lock(ht); TV_DICT_ITER(d, di, {
int todo = (int)ht->ht_used;
for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
todo--;
dictitem_T *di = TV_DICT_HI2DI(hi);
if (filtermap == FILTERMAP_MAP if (filtermap == FILTERMAP_MAP
&& (value_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE) && (value_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE)
|| var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) { || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) {
@@ -5128,6 +5063,7 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
vimvars[VV_KEY].vv_str = xstrdup(di->di_key); vimvars[VV_KEY].vv_str = xstrdup(di->di_key);
typval_T newtv; typval_T newtv;
bool rem;
int r = filter_map_one(&di->di_tv, expr, filtermap, &newtv, &rem); int r = filter_map_one(&di->di_tv, expr, filtermap, &newtv, &rem);
tv_clear(&vimvars[VV_KEY].vv_tv); tv_clear(&vimvars[VV_KEY].vv_tv);
if (r == FAIL || did_emsg) { if (r == FAIL || did_emsg) {
@@ -5154,11 +5090,24 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
} }
tv_dict_item_remove(d, di); tv_dict_item_remove(d, di);
} }
} });
} hash_unlock(&d->dv_hashtab);
hash_unlock(ht);
d->dv_lock = prev_lock; d->dv_lock = prev_lock;
} else if (argvars[0].v_type == VAR_BLOB) { }
/// Implementation of map() and filter() for a Blob.
static void filter_map_blob(blob_T *blob_arg, filtermap_T filtermap, typval_T *expr,
typval_T *rettv)
{
if (filtermap == FILTERMAP_MAPNEW) {
rettv->v_type = VAR_BLOB;
rettv->vval.v_blob = NULL;
}
blob_T *b;
if ((b = blob_arg) == NULL) {
return;
}
blob_T *b_ret = b; blob_T *b_ret = b;
if (filtermap == FILTERMAP_MAPNEW) { if (filtermap == FILTERMAP_MAPNEW) {
@@ -5168,7 +5117,7 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
vimvars[VV_KEY].vv_type = VAR_NUMBER; vimvars[VV_KEY].vv_type = VAR_NUMBER;
for (int i = 0; i < b->bv_ga.ga_len; i++) { for (int i = 0, idx = 0; i < b->bv_ga.ga_len; i++) {
const varnumber_T val = tv_blob_get(b, i); const varnumber_T val = tv_blob_get(b, i);
typval_T tv = { typval_T tv = {
.v_type = VAR_NUMBER, .v_type = VAR_NUMBER,
@@ -5177,6 +5126,7 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
}; };
vimvars[VV_KEY].vv_nr = idx; vimvars[VV_KEY].vv_nr = idx;
typval_T newtv; typval_T newtv;
bool rem;
if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
|| did_emsg) { || did_emsg) {
break; break;
@@ -5191,20 +5141,29 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
tv_blob_set(b_ret, i, (uint8_t)newtv.vval.v_number); tv_blob_set(b_ret, i, (uint8_t)newtv.vval.v_number);
} }
} else if (rem) { } else if (rem) {
char *const p = argvars[0].vval.v_blob->bv_ga.ga_data; char *const p = (char *)blob_arg->bv_ga.ga_data;
memmove(p + i, p + i + 1, (size_t)(b->bv_ga.ga_len - i - 1)); memmove(p + i, p + i + 1, (size_t)(b->bv_ga.ga_len - i - 1));
b->bv_ga.ga_len--; b->bv_ga.ga_len--;
i--; i--;
} }
idx++; idx++;
} }
} else if (argvars[0].v_type == VAR_STRING) { }
/// Implementation of map() and filter() for a String.
static void filter_map_string(const char *str, filtermap_T filtermap, typval_T *expr,
typval_T *rettv)
{
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
vimvars[VV_KEY].vv_type = VAR_NUMBER; vimvars[VV_KEY].vv_type = VAR_NUMBER;
garray_T ga; garray_T ga;
ga_init(&ga, (int)sizeof(char), 80); ga_init(&ga, (int)sizeof(char), 80);
int len; int len = 0;
for (const char *p = tv_get_string(&argvars[0]); *p != NUL; p += len) { int idx = 0;
for (const char *p = str; *p != NUL; p += len) {
len = utfc_ptr2len(p); len = utfc_ptr2len(p);
typval_T tv = { typval_T tv = {
.v_type = VAR_STRING, .v_type = VAR_STRING,
@@ -5214,6 +5173,7 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
vimvars[VV_KEY].vv_nr = idx; vimvars[VV_KEY].vv_nr = idx;
typval_T newtv; typval_T newtv;
bool rem;
if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
|| did_emsg) { || did_emsg) {
tv_clear(&newtv); tv_clear(&newtv);
@@ -5239,8 +5199,21 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
} }
ga_append(&ga, NUL); ga_append(&ga, NUL);
rettv->vval.v_string = ga.ga_data; rettv->vval.v_string = ga.ga_data;
} else { }
assert(argvars[0].v_type == VAR_LIST);
/// Implementation of map() and filter() for a List.
static void filter_map_list(list_T *l, filtermap_T filtermap, const char *func_name,
const char *arg_errmsg, typval_T *expr, typval_T *rettv)
{
if (filtermap == FILTERMAP_MAPNEW) {
rettv->v_type = VAR_LIST;
rettv->vval.v_list = NULL;
}
if (l == NULL
|| (filtermap == FILTERMAP_FILTER
&& value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) {
return;
}
const VarLockStatus prev_lock = tv_list_locked(l); const VarLockStatus prev_lock = tv_list_locked(l);
list_T *l_ret = NULL; list_T *l_ret = NULL;
@@ -5255,14 +5228,16 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
if (filtermap != FILTERMAP_FILTER && tv_list_locked(l) == VAR_UNLOCKED) { if (filtermap != FILTERMAP_FILTER && tv_list_locked(l) == VAR_UNLOCKED) {
tv_list_set_lock(l, VAR_LOCKED); tv_list_set_lock(l, VAR_LOCKED);
} }
int idx = 0;
for (listitem_T *li = tv_list_first(l); li != NULL;) { for (listitem_T *li = tv_list_first(l); li != NULL;) {
if (filtermap == FILTERMAP_MAP if (filtermap == FILTERMAP_MAP
&& value_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, && value_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, TV_TRANSLATE)) {
TV_TRANSLATE)) {
break; break;
} }
vimvars[VV_KEY].vv_nr = idx; vimvars[VV_KEY].vv_nr = idx;
typval_T newtv; typval_T newtv;
bool rem;
if (filter_map_one(TV_LIST_ITEM_TV(li), expr, filtermap, &newtv, &rem) == FAIL) { if (filter_map_one(TV_LIST_ITEM_TV(li), expr, filtermap, &newtv, &rem) == FAIL) {
break; break;
} }
@@ -5286,9 +5261,65 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
} }
idx++; idx++;
} }
tv_list_set_lock(l, prev_lock); tv_list_set_lock(l, prev_lock);
} }
/// Implementation of map() and filter().
static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
{
const char *const func_name = (filtermap == FILTERMAP_MAP
? "map()"
: (filtermap == FILTERMAP_MAPNEW
? "mapnew()"
: "filter()"));
const char *const arg_errmsg = (filtermap == FILTERMAP_MAP
? N_("map() argument")
: (filtermap == FILTERMAP_MAPNEW
? N_("mapnew() argument")
: N_("filter() argument")));
// map() and filter() return the first argument, also on failure.
if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING) {
tv_copy(&argvars[0], rettv);
}
if (argvars[0].v_type != VAR_BLOB
&& argvars[0].v_type != VAR_LIST
&& argvars[0].v_type != VAR_DICT
&& argvars[0].v_type != VAR_STRING) {
semsg(_(e_argument_of_str_must_be_list_string_dictionary_or_blob), func_name);
return;
}
typval_T *expr = &argvars[1];
// On type errors, the preceding call has already displayed an error
// message. Avoid a misleading error message for an empty string that
// was not passed as argument.
if (expr->v_type != VAR_UNKNOWN) {
typval_T save_val;
prepare_vimvar(VV_VAL, &save_val);
// We reset "did_emsg" to be able to detect whether an error
// occurred during evaluation of the expression.
int save_did_emsg = did_emsg;
did_emsg = false;
typval_T save_key;
prepare_vimvar(VV_KEY, &save_key);
if (argvars[0].v_type == VAR_DICT) {
filter_map_dict(argvars[0].vval.v_dict, filtermap, func_name,
arg_errmsg, expr, rettv);
} else if (argvars[0].v_type == VAR_BLOB) {
filter_map_blob(argvars[0].vval.v_blob, filtermap, expr, rettv);
} else if (argvars[0].v_type == VAR_STRING) {
filter_map_string(tv_get_string(&argvars[0]), filtermap, expr, rettv);
} else {
assert(argvars[0].v_type == VAR_LIST);
filter_map_list(argvars[0].vval.v_list, filtermap, func_name,
arg_errmsg, expr, rettv);
}
restore_vimvar(VV_KEY, &save_key); restore_vimvar(VV_KEY, &save_key);
restore_vimvar(VV_VAL, &save_val); restore_vimvar(VV_VAL, &save_val);
@@ -5304,7 +5335,7 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
/// @param newtv for map() an mapnew(): new value /// @param newtv for map() an mapnew(): new value
/// @param remp for filter(): remove flag /// @param remp for filter(): remove flag
static int filter_map_one(typval_T *tv, typval_T *expr, const filtermap_T filtermap, static int filter_map_one(typval_T *tv, typval_T *expr, const filtermap_T filtermap,
typval_T *newtv, int *remp) typval_T *newtv, bool *remp)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{ {
typval_T argv[3]; typval_T argv[3];

View File

@@ -2551,8 +2551,8 @@ M.funcs = {
of the current item. For a |Dictionary| |v:key| has the key of the current item. For a |Dictionary| |v:key| has the key
of the current item and for a |List| |v:key| has the index of of the current item and for a |List| |v:key| has the index of
the current item. For a |Blob| |v:key| has the index of the the current item. For a |Blob| |v:key| has the index of the
current byte. current byte. For a |String| |v:key| has the index of the
current character.
Examples: >vim Examples: >vim
call filter(mylist, 'v:val !~ "OLD"') call filter(mylist, 'v:val !~ "OLD"')
<Removes the items where "OLD" appears. >vim <Removes the items where "OLD" appears. >vim
@@ -6068,7 +6068,8 @@ M.funcs = {
of the current item. For a |Dictionary| |v:key| has the key of the current item. For a |Dictionary| |v:key| has the key
of the current item and for a |List| |v:key| has the index of of the current item and for a |List| |v:key| has the index of
the current item. For a |Blob| |v:key| has the index of the the current item. For a |Blob| |v:key| has the index of the
current byte. current byte. For a |String| |v:key| has the index of the
current character.
Example: >vim Example: >vim
call map(mylist, '"> " .. v:val .. " <"') call map(mylist, '"> " .. v:val .. " <"')
<This puts "> " before and " <" after each item in "mylist". <This puts "> " before and " <" after each item in "mylist".
@@ -7876,9 +7877,9 @@ M.funcs = {
tags = { 'E998' }, tags = { 'E998' },
desc = [=[ desc = [=[
{func} is called for every item in {object}, which can be a {func} is called for every item in {object}, which can be a
|String|, |List| or a |Blob|. {func} is called with two arguments: |String|, |List| or a |Blob|. {func} is called with two
the result so far and current item. After processing all arguments: the result so far and current item. After
items the result is returned. processing all items the result is returned.
{initial} is the initial result. When omitted, the first item {initial} is the initial result. When omitted, the first item
in {object} is used and {func} is first called for the second in {object} is used and {func} is first called for the second

View File

@@ -6171,44 +6171,15 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
} }
} }
/// "reduce(list, { accumulator, element -> value } [, initial])" function /// reduce() on a List
/// "reduce(blob, { accumulator, element -> value } [, initial])" function static void reduce_list(typval_T *argvars, const char *func_name, funcexe_T *funcexe,
/// "reduce(string, { accumulator, element -> value } [, initial])" function typval_T *rettv)
static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{ {
list_T *const l = argvars[0].vval.v_list;
const int called_emsg_start = called_emsg; const int called_emsg_start = called_emsg;
if (argvars[0].v_type != VAR_STRING
&& argvars[0].v_type != VAR_LIST
&& argvars[0].v_type != VAR_BLOB) {
emsg(_(e_string_list_or_blob_required));
}
const char *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 = tv_get_string(&argvars[1]);
}
if (func_name == NULL || *func_name == NUL) {
emsg(_(e_missing_function_argument));
return;
}
funcexe_T funcexe = FUNCEXE_INIT;
funcexe.fe_evaluate = true;
funcexe.fe_partial = partial;
typval_T initial; typval_T initial;
typval_T argv[3]; const listitem_T *li = NULL;
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 (argvars[2].v_type == VAR_UNKNOWN) {
if (tv_list_len(l) == 0) { if (tv_list_len(l) == 0) {
semsg(_(e_reduceempty), "List"); semsg(_(e_reduceempty), "List");
@@ -6224,15 +6195,19 @@ static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tv_copy(&initial, rettv); tv_copy(&initial, rettv);
if (l != NULL) { if (l == NULL) {
return;
}
const VarLockStatus prev_locked = tv_list_locked(l); const VarLockStatus prev_locked = tv_list_locked(l);
tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here
for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
typval_T argv[3];
argv[0] = *rettv; argv[0] = *rettv;
argv[1] = *TV_LIST_ITEM_TV(li); argv[1] = *TV_LIST_ITEM_TV(li);
rettv->v_type = VAR_UNKNOWN; rettv->v_type = VAR_UNKNOWN;
const int r = call_func(func_name, -1, rettv, 2, argv, &funcexe); const int r = call_func(func_name, -1, rettv, 2, argv, funcexe);
tv_clear(&argv[0]); tv_clear(&argv[0]);
if (r == FAIL || called_emsg != called_emsg_start) { if (r == FAIL || called_emsg != called_emsg_start) {
break; break;
@@ -6240,9 +6215,14 @@ static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
} }
tv_list_set_lock(l, prev_locked); tv_list_set_lock(l, prev_locked);
} }
} else if (argvars[0].v_type == VAR_STRING) {
/// reduce() on a String
static void reduce_string(typval_T *argvars, const char *func_name, funcexe_T *funcexe,
typval_T *rettv)
{
const char *p = tv_get_string(&argvars[0]); const char *p = tv_get_string(&argvars[0]);
int len; int len;
const int called_emsg_start = called_emsg;
if (argvars[2].v_type == VAR_UNKNOWN) { if (argvars[2].v_type == VAR_UNKNOWN) {
if (*p == NUL) { if (*p == NUL) {
@@ -6264,6 +6244,7 @@ static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
} }
for (; *p != NUL; p += len) { for (; *p != NUL; p += len) {
typval_T argv[3];
argv[0] = *rettv; argv[0] = *rettv;
len = utfc_ptr2len(p); len = utfc_ptr2len(p);
argv[1] = (typval_T){ argv[1] = (typval_T){
@@ -6271,24 +6252,34 @@ static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
.v_lock = VAR_UNLOCKED, .v_lock = VAR_UNLOCKED,
.vval.v_string = xstrnsave(p, (size_t)len), .vval.v_string = xstrnsave(p, (size_t)len),
}; };
const int r = call_func(func_name, -1, rettv, 2, argv, &funcexe); const int r = call_func(func_name, -1, rettv, 2, argv, funcexe);
tv_clear(&argv[0]); tv_clear(&argv[0]);
tv_clear(&argv[1]); tv_clear(&argv[1]);
if (r == FAIL || called_emsg != called_emsg_start) { if (r == FAIL || called_emsg != called_emsg_start) {
break; break;
} }
} }
} else { }
const blob_T *const b = argvars[0].vval.v_blob;
int i;
/// reduce() on a Blob
static void reduce_blob(typval_T *argvars, const char *func_name, funcexe_T *funcexe,
typval_T *rettv)
{
const blob_T *const b = argvars[0].vval.v_blob;
const int called_emsg_start = called_emsg;
typval_T initial;
int i;
if (argvars[2].v_type == VAR_UNKNOWN) { if (argvars[2].v_type == VAR_UNKNOWN) {
if (tv_blob_len(b) == 0) { if (tv_blob_len(b) == 0) {
semsg(_(e_reduceempty), "Blob"); semsg(_(e_reduceempty), "Blob");
return; return;
} }
initial.v_type = VAR_NUMBER; initial = (typval_T){
initial.vval.v_number = tv_blob_get(b, 0); .v_type = VAR_NUMBER,
.v_lock = VAR_UNLOCKED,
.vval.v_number = tv_blob_get(b, 0),
};
i = 1; i = 1;
} else if (argvars[2].v_type != VAR_NUMBER) { } else if (argvars[2].v_type != VAR_NUMBER) {
emsg(_(e_number_exp)); emsg(_(e_number_exp));
@@ -6300,14 +6291,58 @@ static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tv_copy(&initial, rettv); tv_copy(&initial, rettv);
for (; i < tv_blob_len(b); i++) { for (; i < tv_blob_len(b); i++) {
typval_T argv[3];
argv[0] = *rettv; argv[0] = *rettv;
argv[1].v_type = VAR_NUMBER; argv[1] = (typval_T){
argv[1].vval.v_number = tv_blob_get(b, i); .v_type = VAR_NUMBER,
if (call_func(func_name, -1, rettv, 2, argv, &funcexe) == FAIL) { .v_lock = VAR_UNLOCKED,
.vval.v_number = tv_blob_get(b, i),
};
const int r = call_func(func_name, -1, rettv, 2, argv, funcexe);
if (r == FAIL || called_emsg != called_emsg_start) {
return; return;
} }
} }
} }
/// "reduce(list, { accumulator, element -> value } [, initial])" function
/// "reduce(blob, { accumulator, element -> value } [, initial])" function
/// "reduce(string, { accumulator, element -> value } [, initial])" function
static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
if (argvars[0].v_type != VAR_STRING
&& argvars[0].v_type != VAR_LIST
&& argvars[0].v_type != VAR_BLOB) {
emsg(_(e_string_list_or_blob_required));
return;
}
const char *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 = tv_get_string(&argvars[1]);
}
if (func_name == NULL || *func_name == NUL) {
emsg(_(e_missing_function_argument));
return;
}
funcexe_T funcexe = FUNCEXE_INIT;
funcexe.fe_evaluate = true;
funcexe.fe_partial = partial;
if (argvars[0].v_type == VAR_LIST) {
reduce_list(argvars, func_name, &funcexe, rettv);
} else if (argvars[0].v_type == VAR_STRING) {
reduce_string(argvars, func_name, &funcexe, rettv);
} else {
reduce_blob(argvars, func_name, &funcexe, rettv);
}
} }
#define SP_NOMOVE 0x01 ///< don't move cursor #define SP_NOMOVE 0x01 ///< don't move cursor

View File

@@ -958,6 +958,9 @@ func Test_reduce()
call assert_fails("call reduce('', { acc, val -> acc + val }, {})", 'E1253:') call assert_fails("call reduce('', { acc, val -> acc + val }, {})", 'E1253:')
call assert_fails("call reduce('', { acc, val -> acc + val }, 0.1)", 'E1253:') call assert_fails("call reduce('', { acc, val -> acc + val }, 0.1)", 'E1253:')
call assert_fails("call reduce('', { acc, val -> acc + val }, function('tr'))", 'E1253:') call assert_fails("call reduce('', { acc, val -> acc + val }, function('tr'))", 'E1253:')
call assert_fails("call reduce('abc', { a, v -> a10}, '')", 'E121:')
call assert_fails("call reduce(0z01, { a, v -> a10}, 1)", 'E121:')
call assert_fails("call reduce([1], { a, v -> a10}, '')", 'E121:')
let g:lut = [1, 2, 3, 4] let g:lut = [1, 2, 3, 4]
func EvilRemove() func EvilRemove()