]> granicus.if.org Git - vim/commitdiff
patch 8.2.3849: functions implementing reduce and map are too long v8.2.3849
authorYegappan Lakshmanan <yegappan@yahoo.com>
Sun, 19 Dec 2021 10:35:15 +0000 (10:35 +0000)
committerBram Moolenaar <Bram@vim.org>
Sun, 19 Dec 2021 10:35:15 +0000 (10:35 +0000)
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)

runtime/doc/eval.txt
src/list.c
src/testdir/test_listdict.vim
src/version.c

index d54523f9652c9d3a098663c7bca74a46b9a2ec4f..db8e1002dcec217b56e6775173f76836cefa6046 100644 (file)
@@ -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
index 484be83311f4670a2c0f44c36d1efc3e01fb7cab..94b5a0b5d7d087d5b4580d4190bbd427bd621e6a 100644 (file)
@@ -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);
@@ -3251,19 +3307,176 @@ 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)
index c7673132595333f205e8de8a1e8fe7ca8c6eff9d..de63c799984eb8d3b9def215ab33eb48eba69845 100644 (file)
@@ -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:')
+  call assert_fails("call reduce([1], { a, v -> a10}, '')", 'E121:')
 
   let g:lut = [1, 2, 3, 4]
   func EvilRemove()
index d99e18c8adcf8f76f246996c0496bb730dfa4344..372b76050f9e47be5becf296b10b757e709669d6 100644 (file)
@@ -749,6 +749,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3849,
 /**/
     3848,
 /**/