summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYegappan Lakshmanan <yegappan@yahoo.com>2021-12-19 10:35:15 +0000
committerBram Moolenaar <Bram@vim.org>2021-12-19 10:35:15 +0000
commit389b72196e6aaeafe3f907c73d271f2c6b931140 (patch)
tree4afb251968ba13c94cc8cbaa8daf7cf8a3279518
parent0ccb5842f5fb103763d106c7aa364d758343c35a (diff)
patch 8.2.3849: functions implementing reduce and map are too longv8.2.3849
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 #9370)
-rw-r--r--runtime/doc/eval.txt12
-rw-r--r--src/list.c1038
-rw-r--r--src/testdir/test_listdict.vim3
-rw-r--r--src/version.c2
4 files changed, 582 insertions, 473 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index d54523f965..db8e1002dc 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -4893,7 +4893,8 @@ filter({expr1}, {expr2}) *filter()*
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
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: >
call filter(mylist, 'v:val !~ "OLD"')
< Removes the items where "OLD" appears. >
@@ -7588,7 +7589,8 @@ map({expr1}, {expr2}) *map()*
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
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: >
:call map(mylist, '"> " . v:val . " <"')
< This puts "> " before and " <" after each item in "mylist".
@@ -8959,9 +8961,9 @@ readfile({fname} [, {type} [, {max}]])
reduce({object}, {func} [, {initial}]) *reduce()* *E998*
{func} is called for every item in {object}, which can be a
- |String|, |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.
+ |String|, |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
diff --git a/src/list.c b/src/list.c
index 484be83311..94b5a0b5d7 100644
--- a/src/list.c
+++ b/src/list.c
@@ -2274,415 +2274,471 @@ theend:
}
/*
- * 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)
-{
- typval_T *expr;
- listitem_T *li, *nli;
- list_T *l = NULL;
- dictitem_T *di;
+filter_map_dict(
+ dict_T *d,
+ filtermap_T filtermap,
+ type_T *argtype,
+ char *func_name,
+ char_u *arg_errmsg,
+ typval_T *expr,
+ typval_T *rettv)
+{
+ int prev_lock;
+ dict_T *d_ret = NULL;
hashtab_T *ht;
hashitem_T *hi;
- dict_T *d = NULL;
- blob_T *b = NULL;
- int rem;
+ dictitem_T *di;
int todo;
- char *func_name = filtermap == FILTERMAP_MAP ? "map()"
- : filtermap == FILTERMAP_MAPNEW ? "mapnew()"
- : "filter()";
- char_u *arg_errmsg = (char_u *)(filtermap == FILTERMAP_MAP
- ? N_("map() argument")
- : filtermap == FILTERMAP_MAPNEW
- ? N_("mapnew() argument")
- : N_("filter() argument"));
- int save_did_emsg;
- int idx = 0;
- type_T *type = NULL;
- garray_T type_list;
-
- // map() and filter() return the first argument, also on failure.
- if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING)
- copy_tv(&argvars[0], rettv);
+ int rem;
- if (in_vim9script()
- && (check_for_list_or_dict_or_blob_or_string_arg(argvars, 0)
- == FAIL))
+ if (filtermap == FILTERMAP_MAPNEW)
+ {
+ rettv->v_type = VAR_DICT;
+ rettv->vval.v_dict = NULL;
+ }
+ if (d == NULL
+ || (filtermap == FILTERMAP_FILTER
+ && value_check_lock(d->dv_lock, arg_errmsg, TRUE)))
return;
- if (filtermap == FILTERMAP_MAP && in_vim9script())
+ prev_lock = d->dv_lock;
+
+ if (filtermap == FILTERMAP_MAPNEW)
{
- // Check that map() does not change the type of the dict.
- ga_init2(&type_list, sizeof(type_T *), 10);
- type = typval2type(argvars, get_copyID(), &type_list, TRUE);
+ if (rettv_dict_alloc(rettv) == FAIL)
+ return;
+ d_ret = rettv->vval.v_dict;
}
- if (argvars[0].v_type == VAR_BLOB)
+ if (filtermap != FILTERMAP_FILTER && d->dv_lock == 0)
+ d->dv_lock = VAR_LOCKED;
+ ht = &d->dv_hashtab;
+ hash_lock(ht);
+ todo = (int)ht->ht_used;
+ for (hi = ht->ht_array; todo > 0; ++hi)
{
- if (filtermap == FILTERMAP_MAPNEW)
+ if (!HASHITEM_EMPTY(hi))
{
- rettv->v_type = VAR_BLOB;
- rettv->vval.v_blob = NULL;
+ int r;
+ typval_T newtv;
+
+ --todo;
+ di = HI2DI(hi);
+ if (filtermap == FILTERMAP_MAP
+ && (value_check_lock(di->di_tv.v_lock,
+ arg_errmsg, TRUE)
+ || var_check_ro(di->di_flags,
+ arg_errmsg, TRUE)))
+ break;
+ set_vim_var_string(VV_KEY, di->di_key, -1);
+ newtv.v_type = VAR_UNKNOWN;
+ r = filter_map_one(&di->di_tv, expr, filtermap,
+ &newtv, &rem);
+ clear_tv(get_vim_var_tv(VV_KEY));
+ if (r == FAIL || did_emsg)
+ {
+ clear_tv(&newtv);
+ break;
+ }
+ if (filtermap == FILTERMAP_MAP)
+ {
+ if (argtype != NULL && check_typval_arg_type(
+ argtype->tt_member, &newtv,
+ func_name, 0) == FAIL)
+ {
+ clear_tv(&newtv);
+ break;
+ }
+ // map(): replace the dict item value
+ clear_tv(&di->di_tv);
+ newtv.v_lock = 0;
+ di->di_tv = newtv;
+ }
+ else if (filtermap == FILTERMAP_MAPNEW)
+ {
+ // mapnew(): add the item value to the new dict
+ r = dict_add_tv(d_ret, (char *)di->di_key, &newtv);
+ clear_tv(&newtv);
+ if (r == FAIL)
+ break;
+ }
+ else if (filtermap == FILTERMAP_FILTER && rem)
+ {
+ // filter(false): remove the item from the dict
+ if (var_check_fixed(di->di_flags, arg_errmsg, TRUE)
+ || var_check_ro(di->di_flags, arg_errmsg, TRUE))
+ break;
+ dictitem_remove(d, di);
+ }
}
- if ((b = argvars[0].vval.v_blob) == NULL)
- goto theend;
}
- else if (argvars[0].v_type == VAR_LIST)
+ hash_unlock(ht);
+ d->dv_lock = prev_lock;
+}
+
+/*
+ * 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)
+{
+ blob_T *b;
+ int i;
+ typval_T tv;
+ varnumber_T val;
+ blob_T *b_ret;
+ int idx = 0;
+ int rem;
+
+ if (filtermap == FILTERMAP_MAPNEW)
+ {
+ rettv->v_type = VAR_BLOB;
+ rettv->vval.v_blob = NULL;
+ }
+ if ((b = blob_arg) == NULL)
+ return;
+
+ b_ret = b;
+ if (filtermap == FILTERMAP_MAPNEW)
{
- if (filtermap == FILTERMAP_MAPNEW)
+ if (blob_copy(b, rettv) == FAIL)
+ return;
+ b_ret = rettv->vval.v_blob;
+ }
+
+ // set_vim_var_nr() doesn't set the type
+ set_vim_var_type(VV_KEY, VAR_NUMBER);
+
+ for (i = 0; i < b->bv_ga.ga_len; i++)
+ {
+ typval_T newtv;
+
+ tv.v_type = VAR_NUMBER;
+ val = blob_get(b, i);
+ tv.vval.v_number = val;
+ set_vim_var_nr(VV_KEY, idx);
+ if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
+ || did_emsg)
+ break;
+ if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL)
{
- rettv->v_type = VAR_LIST;
- rettv->vval.v_list = NULL;
+ clear_tv(&newtv);
+ emsg(_(e_invalblob));
+ break;
}
- if ((l = argvars[0].vval.v_list) == NULL
- || (filtermap == FILTERMAP_FILTER
- && value_check_lock(l->lv_lock, arg_errmsg, TRUE)))
- goto theend;
+ if (filtermap != FILTERMAP_FILTER)
+ {
+ if (newtv.vval.v_number != val)
+ blob_set(b_ret, i, newtv.vval.v_number);
+ }
+ else if (rem)
+ {
+ char_u *p = (char_u *)blob_arg->bv_ga.ga_data;
+
+ mch_memmove(p + i, p + i + 1,
+ (size_t)b->bv_ga.ga_len - i - 1);
+ --b->bv_ga.ga_len;
+ --i;
+ }
+ ++idx;
}
- else if (argvars[0].v_type == VAR_DICT)
+}
+
+/*
+ * Implementation of map() and filter() for a String.
+ */
+ static void
+filter_map_string(
+ char_u *str,
+ filtermap_T filtermap,
+ typval_T *expr,
+ typval_T *rettv)
+{
+ char_u *p;
+ typval_T tv;
+ garray_T ga;
+ int len = 0;
+ int idx = 0;
+ int rem;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ // set_vim_var_nr() doesn't set the type
+ set_vim_var_type(VV_KEY, VAR_NUMBER);
+
+ ga_init2(&ga, (int)sizeof(char), 80);
+ for (p = str; *p != NUL; p += len)
{
- if (filtermap == FILTERMAP_MAPNEW)
+ typval_T newtv;
+
+ if (tv_get_first_char(p, &tv) == FAIL)
+ break;
+ len = (int)STRLEN(tv.vval.v_string);
+
+ set_vim_var_nr(VV_KEY, idx);
+ if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
+ || did_emsg)
+ break;
+ if (did_emsg)
{
- rettv->v_type = VAR_DICT;
- rettv->vval.v_dict = NULL;
+ clear_tv(&newtv);
+ clear_tv(&tv);
+ break;
}
- if ((d = argvars[0].vval.v_dict) == NULL
- || (filtermap == FILTERMAP_FILTER
- && value_check_lock(d->dv_lock, arg_errmsg, TRUE)))
- goto theend;
+ else if (filtermap != FILTERMAP_FILTER)
+ {
+ if (newtv.v_type != VAR_STRING)
+ {
+ clear_tv(&newtv);
+ clear_tv(&tv);
+ emsg(_(e_stringreq));
+ break;
+ }
+ else
+ ga_concat(&ga, newtv.vval.v_string);
+ }
+ else if (!rem)
+ ga_concat(&ga, tv.vval.v_string);
+
+ clear_tv(&newtv);
+ clear_tv(&tv);
+
+ ++idx;
}
- else if (argvars[0].v_type == VAR_STRING)
+ ga_append(&ga, NUL);
+ rettv->vval.v_string = ga.ga_data;
+}
+
+/*
+ * Implementation of map() and filter() for a List.
+ */
+ static void
+filter_map_list(
+ list_T *l,
+ filtermap_T filtermap,
+ type_T *argtype,
+ char *func_name,
+ char_u *arg_errmsg,
+ typval_T *expr,
+ typval_T *rettv)
+{
+ int prev_lock;
+ list_T *l_ret = NULL;
+ int idx = 0;
+ int rem;
+ listitem_T *li, *nli;
+
+ if (filtermap == FILTERMAP_MAPNEW)
{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
+ rettv->v_type = VAR_LIST;
+ rettv->vval.v_list = NULL;
}
- else
+ if (l == NULL
+ || (filtermap == FILTERMAP_FILTER
+ && value_check_lock(l->lv_lock, arg_errmsg, TRUE)))
+ return;
+
+ prev_lock = l->lv_lock;
+
+ if (filtermap == FILTERMAP_MAPNEW)
{
- semsg(_(e_argument_of_str_must_be_list_string_dictionary_or_blob),
- func_name);
- goto theend;
+ if (rettv_list_alloc(rettv) == FAIL)
+ return;
+ l_ret = rettv->vval.v_list;
}
+ // set_vim_var_nr() doesn't set the type
+ set_vim_var_type(VV_KEY, VAR_NUMBER);
- 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;
- typval_T save_key;
+ if (filtermap != FILTERMAP_FILTER && l->lv_lock == 0)
+ l->lv_lock = VAR_LOCKED;
- prepare_vimvar(VV_VAL, &save_val);
- prepare_vimvar(VV_KEY, &save_key);
+ if (l->lv_first == &range_list_item)
+ {
+ varnumber_T val = l->lv_u.nonmat.lv_start;
+ int len = l->lv_len;
+ int stride = l->lv_u.nonmat.lv_stride;
- // We reset "did_emsg" to be able to detect whether an error
- // occurred during evaluation of the expression.
- save_did_emsg = did_emsg;
- did_emsg = FALSE;
+ // List from range(): loop over the numbers
+ if (filtermap != FILTERMAP_MAPNEW)
+ {
+ l->lv_first = NULL;
+ l->lv_u.mat.lv_last = NULL;
+ l->lv_len = 0;
+ l->lv_u.mat.lv_idx_item = NULL;
+ }
- if (argvars[0].v_type == VAR_DICT)
+ for (idx = 0; idx < len; ++idx)
{
- int prev_lock = d->dv_lock;
- dict_T *d_ret = NULL;
+ typval_T tv;
+ typval_T newtv;
- if (filtermap == FILTERMAP_MAPNEW)
+ tv.v_type = VAR_NUMBER;
+ tv.v_lock = 0;
+ tv.vval.v_number = val;
+ set_vim_var_nr(VV_KEY, idx);
+ if (filter_map_one(&tv, expr, filtermap, &newtv, &rem)
+ == FAIL)
+ break;
+ if (did_emsg)
{
- if (rettv_dict_alloc(rettv) == FAIL)
- goto theend;
- d_ret = rettv->vval.v_dict;
+ clear_tv(&newtv);
+ break;
}
-
- if (filtermap != FILTERMAP_FILTER && d->dv_lock == 0)
- d->dv_lock = VAR_LOCKED;
- ht = &d->dv_hashtab;
- hash_lock(ht);
- todo = (int)ht->ht_used;
- for (hi = ht->ht_array; todo > 0; ++hi)
+ if (filtermap != FILTERMAP_FILTER)
{
- if (!HASHITEM_EMPTY(hi))
+ if (filtermap == FILTERMAP_MAP && argtype != NULL
+ && check_typval_arg_type(
+ argtype->tt_member, &newtv,
+ func_name, 0) == FAIL)
{
- int r;
- typval_T newtv;
-
- --todo;
- di = HI2DI(hi);
- if (filtermap == FILTERMAP_MAP
- && (value_check_lock(di->di_tv.v_lock,
- arg_errmsg, TRUE)
- || var_check_ro(di->di_flags,
- arg_errmsg, TRUE)))
- break;
- set_vim_var_string(VV_KEY, di->di_key, -1);
- newtv.v_type = VAR_UNKNOWN;
- r = filter_map_one(&di->di_tv, expr, filtermap,
- &newtv, &rem);
- clear_tv(get_vim_var_tv(VV_KEY));
- if (r == FAIL || did_emsg)
- {
- clear_tv(&newtv);
- break;
- }
- if (filtermap == FILTERMAP_MAP)
- {
- if (type != NULL && check_typval_arg_type(
- type->tt_member, &newtv,
- func_name, 0) == FAIL)
- {
- clear_tv(&newtv);
- break;
- }
- // map(): replace the dict item value
- clear_tv(&di->di_tv);
- newtv.v_lock = 0;
- di->di_tv = newtv;
- }
- else if (filtermap == FILTERMAP_MAPNEW)
- {
- // mapnew(): add the item value to the new dict
- r = dict_add_tv(d_ret, (char *)di->di_key, &newtv);
- clear_tv(&newtv);
- if (r == FAIL)
- break;
- }
- else if (filtermap == FILTERMAP_FILTER && rem)
- {
- // filter(false): remove the item from the dict
- if (var_check_fixed(di->di_flags, arg_errmsg, TRUE)
- || var_check_ro(di->di_flags, arg_errmsg, TRUE))
- break;
- dictitem_remove(d, di);
- }
+ clear_tv(&newtv);
+ break;
}
+ // map(), mapnew(): always append the new value to the
+ // list
+ if (list_append_tv_move(filtermap == FILTERMAP_MAP
+ ? l : l_ret, &newtv) == FAIL)
+ break;
+ }
+ else if (!rem)
+ {
+ // filter(): append the list item value when not rem
+ if (list_append_tv_move(l, &tv) == FAIL)
+ break;
}
- hash_unlock(ht);
- d->dv_lock = prev_lock;
+
+ val += stride;
}
- else if (argvars[0].v_type == VAR_BLOB)
+ }
+ else
+ {
+ // Materialized list: loop over the items
+ for (li = l->lv_first; li != NULL; li = nli)
{
- int i;
- typval_T tv;
- varnumber_T val;
- blob_T *b_ret = b;
+ typval_T newtv;
- if (filtermap == FILTERMAP_MAPNEW)
+ if (filtermap == FILTERMAP_MAP && value_check_lock(
+ li->li_tv.v_lock, arg_errmsg, TRUE))
+ break;
+ nli = li->li_next;
+ set_vim_var_nr(VV_KEY, idx);
+ if (filter_map_one(&li->li_tv, expr, filtermap,
+ &newtv, &rem) == FAIL)
+ break;
+ if (did_emsg)
{
- if (blob_copy(b, rettv) == FAIL)
- goto theend;
- b_ret = rettv->vval.v_blob;
+ clear_tv(&newtv);
+ break;
}
-
- // set_vim_var_nr() doesn't set the type
- set_vim_var_type(VV_KEY, VAR_NUMBER);
-
- for (i = 0; i < b->bv_ga.ga_len; i++)
+ if (filtermap == FILTERMAP_MAP)
{
- typval_T newtv;
-
- tv.v_type = VAR_NUMBER;
- val = blob_get(b, i);
- tv.vval.v_number = val;
- set_vim_var_nr(VV_KEY, idx);
- if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
- || did_emsg)
- break;
- if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL)
+ if (argtype != NULL && check_typval_arg_type(
+ argtype->tt_member, &newtv, func_name, 0) == FAIL)
{
clear_tv(&newtv);
- emsg(_(e_invalblob));
break;
}
- if (filtermap != FILTERMAP_FILTER)
- {
- if (newtv.vval.v_number != val)
- blob_set(b_ret, i, newtv.vval.v_number);
- }
- else if (rem)
- {
- char_u *p = (char_u *)argvars[0].vval.v_blob->bv_ga.ga_data;
-
- mch_memmove(p + i, p + i + 1,
- (size_t)b->bv_ga.ga_len - i - 1);
- --b->bv_ga.ga_len;
- --i;
- }
- ++idx;
+ // map(): replace the list item value
+ clear_tv(&li->li_tv);
+ newtv.v_lock = 0;
+ li->li_tv = newtv;
}
- }
- else if (argvars[0].v_type == VAR_STRING)
- {
- char_u *p;
- typval_T tv;
- garray_T ga;
- int len;
-
- // set_vim_var_nr() doesn't set the type
- set_vim_var_type(VV_KEY, VAR_NUMBER);
-
- ga_init2(&ga, (int)sizeof(char), 80);
- for (p = tv_get_string(&argvars[0]); *p != NUL; p += len)
+ else if (filtermap == FILTERMAP_MAPNEW)
{
- typval_T newtv;
-
- if (tv_get_first_char(p, &tv) == FAIL)
- break;
- len = STRLEN(tv.vval.v_string);
-
- set_vim_var_nr(VV_KEY, idx);
- if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
- || did_emsg)
- break;
- if (did_emsg)
- {
- clear_tv(&newtv);
- clear_tv(&tv);
+ // mapnew(): append the list item value
+ if (list_append_tv_move(l_ret, &newtv) == FAIL)
break;
- }
- else if (filtermap != FILTERMAP_FILTER)
- {
- if (newtv.v_type != VAR_STRING)
- {
- clear_tv(&newtv);
- clear_tv(&tv);
- emsg(_(e_stringreq));
- break;
- }
- else
- ga_concat(&ga, newtv.vval.v_string);
- }
- else if (!rem)
- ga_concat(&ga, tv.vval.v_string);
-
- clear_tv(&newtv);
- clear_tv(&tv);
-
- ++idx;
}
- ga_append(&ga, NUL);
- rettv->vval.v_string = ga.ga_data;
+ else if (filtermap == FILTERMAP_FILTER && rem)
+ listitem_remove(l, li);
+ ++idx;
}
- else // argvars[0].v_type == VAR_LIST
- {
- int prev_lock = l->lv_lock;
- list_T *l_ret = NULL;
+ }
- if (filtermap == FILTERMAP_MAPNEW)
- {
- if (rettv_list_alloc(rettv) == FAIL)
- goto theend;
- l_ret = rettv->vval.v_list;
- }
- // set_vim_var_nr() doesn't set the type
- set_vim_var_type(VV_KEY, VAR_NUMBER);
+ l->lv_lock = prev_lock;
+}
- if (filtermap != FILTERMAP_FILTER && l->lv_lock == 0)
- l->lv_lock = VAR_LOCKED;
+/*
+ * Implementation of map() and filter().
+ */
+ static void
+filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
+{
+ typval_T *expr;
+ char *func_name = filtermap == FILTERMAP_MAP ? "map()"
+ : filtermap == FILTERMAP_MAPNEW ? "mapnew()"
+ : "filter()";
+ char_u *arg_errmsg = (char_u *)(filtermap == FILTERMAP_MAP
+ ? N_("map() argument")
+ : filtermap == FILTERMAP_MAPNEW
+ ? N_("mapnew() argument")
+ : N_("filter() argument"));
+ int save_did_emsg;
+ type_T *type = NULL;
+ garray_T type_list;
- if (l->lv_first == &range_list_item)
- {
- varnumber_T val = l->lv_u.nonmat.lv_start;
- int len = l->lv_len;
- int stride = l->lv_u.nonmat.lv_stride;
+ // map() and filter() return the first argument, also on failure.
+ if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING)
+ copy_tv(&argvars[0], rettv);
- // List from range(): loop over the numbers
- if (filtermap != FILTERMAP_MAPNEW)
- {
- l->lv_first = NULL;
- l->lv_u.mat.lv_last = NULL;
- l->lv_len = 0;
- l->lv_u.mat.lv_idx_item = NULL;
- }
+ if (in_vim9script()
+ && (check_for_list_or_dict_or_blob_or_string_arg(argvars, 0)
+ == FAIL))
+ return;
- for (idx = 0; idx < len; ++idx)
- {
- typval_T tv;
- typval_T newtv;
-
- tv.v_type = VAR_NUMBER;
- tv.v_lock = 0;
- tv.vval.v_number = val;
- set_vim_var_nr(VV_KEY, idx);
- if (filter_map_one(&tv, expr, filtermap, &newtv, &rem)
- == FAIL)
- break;
- if (did_emsg)
- {
- clear_tv(&newtv);
- break;
- }
- if (filtermap != FILTERMAP_FILTER)
- {
- if (filtermap == FILTERMAP_MAP && type != NULL
- && check_typval_arg_type(
- type->tt_member, &newtv,
- func_name, 0) == FAIL)
- {
- clear_tv(&newtv);
- break;
- }
- // map(), mapnew(): always append the new value to the
- // list
- if (list_append_tv_move(filtermap == FILTERMAP_MAP
- ? l : l_ret, &newtv) == FAIL)
- break;
- }
- else if (!rem)
- {
- // filter(): append the list item value when not rem
- if (list_append_tv_move(l, &tv) == FAIL)
- break;
- }
+ if (filtermap == FILTERMAP_MAP && in_vim9script())
+ {
+ // Check that map() does not change the type of the dict.
+ ga_init2(&type_list, sizeof(type_T *), 10);
+ type = typval2type(argvars, get_copyID(), &type_list, TRUE);
+ }
- val += stride;
- }
- }
- else
- {
- // Materialized list: loop over the items
- for (li = l->lv_first; li != NULL; li = nli)
- {
- typval_T newtv;
+ 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);
+ goto theend;
+ }
- if (filtermap == FILTERMAP_MAP && value_check_lock(
- li->li_tv.v_lock, arg_errmsg, TRUE))
- break;
- nli = li->li_next;
- set_vim_var_nr(VV_KEY, idx);
- if (filter_map_one(&li->li_tv, expr, filtermap,
- &newtv, &rem) == FAIL)
- break;
- if (did_emsg)
- {
- clear_tv(&newtv);
- break;
- }
- if (filtermap == FILTERMAP_MAP)
- {
- if (type != NULL && check_typval_arg_type(
- type->tt_member, &newtv, func_name, 0) == FAIL)
- {
- clear_tv(&newtv);
- break;
- }
- // map(): replace the list item value
- clear_tv(&li->li_tv);
- newtv.v_lock = 0;
- li->li_tv = newtv;
- }
- else if (filtermap == FILTERMAP_MAPNEW)
- {
- // mapnew(): append the list item value
- if (list_append_tv_move(l_ret, &newtv) == FAIL)
- break;
- }
- else if (filtermap == FILTERMAP_FILTER && rem)
- listitem_remove(l, li);
- ++idx;
- }
- }
+ 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;
+ typval_T save_key;
- l->lv_lock = prev_lock;
- }
+ prepare_vimvar(VV_VAL, &save_val);
+ prepare_vimvar(VV_KEY, &save_key);
+
+ // We reset "did_emsg" to be able to detect whether an error
+ // occurred during evaluation of the expression.
+ save_did_emsg = did_emsg;
+ did_emsg = FALSE;
+
+ if (argvars[0].v_type == VAR_DICT)
+ filter_map_dict(argvars[0].vval.v_dict, filtermap, type, 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 // argvars[0].v_type == VAR_LIST
+ filter_map_list(argvars[0].vval.v_list, filtermap, type, func_name,
+ arg_errmsg, expr, rettv);
restore_vimvar(VV_KEY, &save_key);
restore_vimvar(VV_VAL, &save_val);
@@ -3252,18 +3308,175 @@ f_reverse(typval_T *argvars, typval_T *rettv)
}
/*
+ * reduce() on a List
+ */
+ static void
+reduce_list(
+ typval_T *argvars,
+ char_u *func_name,
+ funcexe_T *funcexe,
+ typval_T *rettv)
+{
+ list_T *l = argvars[0].vval.v_list;
+ listitem_T *li = NULL;
+ typval_T initial;
+ typval_T argv[3];
+ int r;
+ int called_emsg_start = called_emsg;
+ int prev_locked;
+
+ if (l != NULL)
+ CHECK_LIST_MATERIALIZE(l);
+ if (argvars[2].v_type == VAR_UNKNOWN)
+ {
+ if (l == NULL || l->lv_first == NULL)
+ {
+ semsg(_(e_reduceempty), "List");
+ return;
+ }
+ initial = l->lv_first->li_tv;
+ li = l->lv_first->li_next;
+ }
+ else
+ {
+ initial = argvars[2];
+ if (l != NULL)
+ li = l->lv_first;
+ }
+ copy_tv(&initial, rettv);
+
+ if (l == NULL)
+ return;
+
+ prev_locked = l->lv_lock;
+
+ l->lv_lock = VAR_FIXED; // disallow the list changing here
+ for ( ; li != NULL; li = li->li_next)
+ {
+ argv[0] = *rettv;
+ argv[1] = li->li_tv;
+ rettv->v_type = VAR_UNKNOWN;
+ r = call_func(func_name, -1, rettv, 2, argv, funcexe);
+ clear_tv(&argv[0]);
+ if (r == FAIL || called_emsg != called_emsg_start)
+ break;
+ }
+ l->lv_lock = prev_locked;
+}
+
+/*
+ * reduce() on a String
+ */
+ static void
+reduce_string(
+ typval_T *argvars,
+ char_u *func_name,
+ funcexe_T *funcexe,
+ typval_T *rettv)
+{
+ char_u *p = tv_get_string(&argvars[0]);
+ int len;
+ typval_T argv[3];
+ int r;
+ int called_emsg_start = called_emsg;
+
+ if (argvars[2].v_type == VAR_UNKNOWN)
+ {
+ if (*p == NUL)
+ {
+ semsg(_(e_reduceempty), "String");
+ return;
+ }
+ if (tv_get_first_char(p, rettv) == FAIL)
+ return;
+ p += STRLEN(rettv->vval.v_string);
+ }
+ else if (argvars[2].v_type != VAR_STRING)
+ {
+ semsg(_(e_string_expected_for_argument_nr), 3);
+ return;
+ }
+ else
+ copy_tv(&argvars[2], rettv);
+
+ for ( ; *p != NUL; p += len)
+ {
+ argv[0] = *rettv;
+ if (tv_get_first_char(p, &argv[1]) == FAIL)
+ break;
+ len = (int)STRLEN(argv[1].vval.v_string);
+ r = call_func(func_name, -1, rettv, 2, argv, funcexe);
+ clear_tv(&argv[0]);
+ clear_tv(&argv[1]);
+ if (r == FAIL || called_emsg != called_emsg_start)
+ return;
+ }
+}
+
+/*
+ * reduce() on a Blob
+ */
+ static void
+reduce_blob(
+ typval_T *argvars,
+ char_u *func_name,
+ funcexe_T *funcexe,
+ typval_T *rettv)
+{
+ blob_T *b = argvars[0].vval.v_blob;
+ int called_emsg_start = called_emsg;
+ int r;
+ typval_T initial;
+ typval_T argv[3];
+ int i;
+
+ if (argvars[2].v_type == VAR_UNKNOWN)
+ {
+ if (b == NULL || b->bv_ga.ga_len == 0)
+ {
+ semsg(_(e_reduceempty), "Blob");
+ return;
+ }
+ initial.v_type = VAR_NUMBER;
+ initial.vval.v_number = blob_get(b, 0);
+ i = 1;
+ }
+ else if (argvars[2].v_type != VAR_NUMBER)
+ {
+ emsg(_(e_number_expected));
+ return;
+ }
+ else
+ {
+ initial = argvars[2];
+ i = 0;
+ }
+
+ copy_tv(&initial, rettv);
+ if (b == NULL)
+ return;
+
+ for ( ; i < b->bv_ga.ga_len; i++)
+ {
+ argv[0] = *rettv;
+ argv[1].v_type = VAR_NUMBER;
+ argv[1].vval.v_number = blob_get(b, i);
+ r = call_func(func_name, -1, rettv, 2, argv, funcexe);
+ clear_tv(&argv[0]);
+ if (r == FAIL || called_emsg != called_emsg_start)
+ return;
+ }
+}
+
+/*
* "reduce(list, { accumulator, element -> value } [, initial])" function
*/
void
f_reduce(typval_T *argvars, typval_T *rettv)
{
- typval_T initial;
char_u *func_name;
partial_T *partial = NULL;
funcexe_T funcexe;
- typval_T argv[3];
- int r;
- int called_emsg_start = called_emsg;
if (in_vim9script()
&& check_for_string_or_list_or_blob_arg(argvars, 0) == FAIL)
@@ -3272,7 +3485,10 @@ f_reduce(typval_T *argvars, typval_T *rettv)
if (argvars[0].v_type != VAR_STRING
&& argvars[0].v_type != VAR_LIST
&& argvars[0].v_type != VAR_BLOB)
+ {
semsg(_(e_string_list_or_blob_required), "reduce()");
+ return;
+ }
if (argvars[1].v_type == VAR_FUNC)
func_name = argvars[1].vval.v_string;
@@ -3294,125 +3510,11 @@ f_reduce(typval_T *argvars, typval_T *rettv)
funcexe.fe_partial = partial;
if (argvars[0].v_type == VAR_LIST)
- {
- list_T *l = argvars[0].vval.v_list;
- listitem_T *li = NULL;
-
- if (l != NULL)
- CHECK_LIST_MATERIALIZE(l);
- if (argvars[2].v_type == VAR_UNKNOWN)
- {
- if (l == NULL || l->lv_first == NULL)
- {
- semsg(_(e_reduceempty), "List");
- return;
- }
- initial = l->lv_first->li_tv;
- li = l->lv_first->li_next;
- }
- else
- {
- initial = argvars[2];
- if (l != NULL)
- li = l->lv_first;
- }
- copy_tv(&initial, rettv);
-
- if (l != NULL)
- {
- int prev_locked = l->lv_lock;
-
- l->lv_lock = VAR_FIXED; // disallow the list changing here
- for ( ; li != NULL; li = li->li_next)
- {
- argv[0] = *rettv;
- argv[1] = li->li_tv;
- rettv->v_type = VAR_UNKNOWN;
- r = call_func(func_name, -1, rettv, 2, argv, &funcexe);
- clear_tv(&argv[0]);
- if (r == FAIL || called_emsg != called_emsg_start)
- break;
- }
- l->lv_lock = prev_locked;
- }
- }
+ reduce_list(argvars, func_name, &funcexe, rettv);
else if (argvars[0].v_type == VAR_STRING)
- {
- char_u *p = tv_get_string(&argvars[0]);
- int len;
-
- if (argvars[2].v_type == VAR_UNKNOWN)
- {
- if (*p == NUL)
- {
- semsg(_(e_reduceempty), "String");
- return;
- }
- if (tv_get_first_char(p, rettv) == FAIL)
- return;
- p += STRLEN(rettv->vval.v_string);
- }
- else if (argvars[2].v_type != VAR_STRING)
- {
- semsg(_(e_string_expected_for_argument_nr), 3);
- return;
- }
- else
- copy_tv(&argvars[2], rettv);
-
- for ( ; *p != NUL; p += len)
- {
- argv[0] = *rettv;
- if (tv_get_first_char(p, &argv[1]) == FAIL)
- break;
- len = STRLEN(argv[1].vval.v_string);
- r = call_func(func_name, -1, rettv, 2, argv, &funcexe);
- clear_tv(&argv[0]);
- clear_tv(&argv[1]);
- if (r == FAIL || called_emsg != called_emsg_start)
- break;
- }
- }
+ reduce_string(argvars, func_name, &funcexe, 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;
- }
- initial.v_type = VAR_NUMBER;
- initial.vval.v_number = blob_get(b, 0);
- i = 1;
- }
- else if (argvars[2].v_type != VAR_NUMBER)
- {
- emsg(_(e_number_expected));
- return;
- }
- else
- {
- initial = argvars[2];
- i = 0;
- }
-
- copy_tv(&initial, rettv);
- if (b != NULL)
- {
- for ( ; i < b->bv_ga.ga_len; i++)
- {
- argv[0] = *rettv;
- 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;
- }
- }
- }
+ reduce_blob(argvars, func_name, &funcexe, rettv);
}
#endif // defined(FEAT_EVAL)
diff --git a/src/testdir/test_listdict.vim b/src/testdir/test_listdict.vim
index c767313259..de63c79998 100644
--- a/src/testdir/test_listdict.vim
+++ b/src/testdir/test_listdict.vim
@@ -994,6 +994,9 @@ func Test_reduce()
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 }, function('tr'))", 'E1253:')
+ call assert_fails("call reduce('abc', { a, v -> a10}, '')", 'E121:')
+ call assert_fails("call reduce(0z01, { a, v -> a10}, 1)", 'E121:')