]> granicus.if.org Git - vim/commitdiff
patch 8.2.3871: list.c contains code for dict and blob v8.2.3871
authorYegappan Lakshmanan <yegappan@yahoo.com>
Wed, 22 Dec 2021 18:19:26 +0000 (18:19 +0000)
committerBram Moolenaar <Bram@vim.org>
Wed, 22 Dec 2021 18:19:26 +0000 (18:19 +0000)
Problem:    List.c contains code for dict and blob.
Solution:   Refactor to put code where it belongs. (Yegappan Lakshmanan,
            closes #9386)

13 files changed:
src/blob.c
src/dict.c
src/list.c
src/proto/blob.pro
src/proto/dict.pro
src/proto/list.pro
src/proto/strings.pro
src/strings.c
src/structs.h
src/testdir/test_filter_map.vim
src/testdir/test_listdict.vim
src/testdir/test_sort.vim
src/version.c

index 5658370527f551fa20ff47410715b98c36b10d9c..1387a8e6531dd2d729d7b75d32e4c585fa8582b5 100644 (file)
@@ -410,78 +410,306 @@ blob_set_range(blob_T *dest, long n1, long n2, typval_T *src)
 }
 
 /*
- * "remove({blob})" function
+ * "add(blob, item)" function
+ */
+    void
+blob_add(typval_T *argvars, typval_T *rettv)
+{
+    blob_T     *b = argvars[0].vval.v_blob;
+    int                error = FALSE;
+    varnumber_T n;
+
+    if (b == NULL)
+    {
+       if (in_vim9script())
+           emsg(_(e_cannot_add_to_null_blob));
+       return;
+    }
+
+    if (value_check_lock(b->bv_lock, (char_u *)N_("add() argument"), TRUE))
+       return;
+
+    n = tv_get_number_chk(&argvars[1], &error);
+    if (error)
+       return;
+
+    ga_append(&b->bv_ga, (int)n);
+    copy_tv(&argvars[0], rettv);
+}
+
+/*
+ * "remove({blob}, {idx} [, {end}])" function
  */
     void
 blob_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg)
 {
     blob_T     *b = argvars[0].vval.v_blob;
+    blob_T     *newblob;
     int                error = FALSE;
     long       idx;
     long       end;
+    int                len;
+    char_u     *p;
 
     if (b != NULL && value_check_lock(b->bv_lock, arg_errmsg, TRUE))
        return;
 
     idx = (long)tv_get_number_chk(&argvars[1], &error);
-    if (!error)
+    if (error)
+       return;
+
+    len = blob_len(b);
+
+    if (idx < 0)
+       // count from the end
+       idx = len + idx;
+    if (idx < 0 || idx >= len)
+    {
+       semsg(_(e_blobidx), idx);
+       return;
+    }
+    if (argvars[2].v_type == VAR_UNKNOWN)
     {
-       int     len = blob_len(b);
-       char_u  *p;
+       // Remove one item, return its value.
+       p = (char_u *)b->bv_ga.ga_data;
+       rettv->vval.v_number = (varnumber_T) *(p + idx);
+       mch_memmove(p + idx, p + idx + 1, (size_t)len - idx - 1);
+       --b->bv_ga.ga_len;
+       return;
+    }
 
-       if (idx < 0)
-           // count from the end
-           idx = len + idx;
-       if (idx < 0 || idx >= len)
-       {
-           semsg(_(e_blobidx), idx);
+    // Remove range of items, return blob with values.
+    end = (long)tv_get_number_chk(&argvars[2], &error);
+    if (error)
+       return;
+    if (end < 0)
+       // count from the end
+       end = len + end;
+    if (end >= len || idx > end)
+    {
+       semsg(_(e_blobidx), end);
+       return;
+    }
+    newblob = blob_alloc();
+    if (newblob == NULL)
+       return;
+    newblob->bv_ga.ga_len = end - idx + 1;
+    if (ga_grow(&newblob->bv_ga, end - idx + 1) == FAIL)
+    {
+       vim_free(newblob);
+       return;
+    }
+    p = (char_u *)b->bv_ga.ga_data;
+    mch_memmove((char_u *)newblob->bv_ga.ga_data, p + idx,
+           (size_t)(end - idx + 1));
+    ++newblob->bv_refcount;
+    rettv->v_type = VAR_BLOB;
+    rettv->vval.v_blob = newblob;
+
+    if (len - end - 1 > 0)
+       mch_memmove(p + idx, p + end + 1, (size_t)(len - end - 1));
+    b->bv_ga.ga_len -= end - idx + 1;
+}
+
+/*
+ * Implementation of map() and filter() for a Blob.  Apply "expr" to every
+ * number in Blob "blob_arg" and return the result in "rettv".
+ */
+    void
+blob_filter_map(
+       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 (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)
+       {
+           clear_tv(&newtv);
+           emsg(_(e_invalblob));
+           break;
        }
-       if (argvars[2].v_type == VAR_UNKNOWN)
+       if (filtermap != FILTERMAP_FILTER)
        {
-           // Remove one item, return its value.
-           p = (char_u *)b->bv_ga.ga_data;
-           rettv->vval.v_number = (varnumber_T) *(p + idx);
-           mch_memmove(p + idx, p + idx + 1, (size_t)len - idx - 1);
+           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;
        }
-       else
+       ++idx;
+    }
+}
+
+/*
+ * "insert(blob, {item} [, {idx}])" function
+ */
+    void
+blob_insert_func(typval_T *argvars, typval_T *rettv)
+{
+    blob_T     *b = argvars[0].vval.v_blob;
+    long       before = 0;
+    int                error = FALSE;
+    int                val, len;
+    char_u     *p;
+
+    if (b == NULL)
+    {
+       if (in_vim9script())
+           emsg(_(e_cannot_add_to_null_blob));
+       return;
+    }
+
+    if (value_check_lock(b->bv_lock, (char_u *)N_("insert() argument"), TRUE))
+       return;
+
+    len = blob_len(b);
+    if (argvars[2].v_type != VAR_UNKNOWN)
+    {
+       before = (long)tv_get_number_chk(&argvars[2], &error);
+       if (error)
+           return;             // type error; errmsg already given
+       if (before < 0 || before > len)
        {
-           blob_T  *blob;
-
-           // Remove range of items, return blob with values.
-           end = (long)tv_get_number_chk(&argvars[2], &error);
-           if (error)
-               return;
-           if (end < 0)
-               // count from the end
-               end = len + end;
-           if (end >= len || idx > end)
-           {
-               semsg(_(e_blobidx), end);
-               return;
-           }
-           blob = blob_alloc();
-           if (blob == NULL)
-               return;
-           blob->bv_ga.ga_len = end - idx + 1;
-           if (ga_grow(&blob->bv_ga, end - idx + 1) == FAIL)
-           {
-               vim_free(blob);
-               return;
-           }
-           p = (char_u *)b->bv_ga.ga_data;
-           mch_memmove((char_u *)blob->bv_ga.ga_data, p + idx,
-                                                 (size_t)(end - idx + 1));
-           ++blob->bv_refcount;
-           rettv->v_type = VAR_BLOB;
-           rettv->vval.v_blob = blob;
+           semsg(_(e_invarg2), tv_get_string(&argvars[2]));
+           return;
+       }
+    }
+    val = tv_get_number_chk(&argvars[1], &error);
+    if (error)
+       return;
+    if (val < 0 || val > 255)
+    {
+       semsg(_(e_invarg2), tv_get_string(&argvars[1]));
+       return;
+    }
+
+    if (ga_grow(&b->bv_ga, 1) == FAIL)
+       return;
+    p = (char_u *)b->bv_ga.ga_data;
+    mch_memmove(p + before + 1, p + before, (size_t)len - before);
+    *(p + before) = val;
+    ++b->bv_ga.ga_len;
+
+    copy_tv(&argvars[0], rettv);
+}
+
+/*
+ * reduce() Blob argvars[0] using the function 'funcname' with arguments in
+ * 'funcexe' starting with the initial value argvars[2] and return the result
+ * in 'rettv'.
+ */
+    void
+blob_reduce(
+       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 (len - end - 1 > 0)
-               mch_memmove(p + idx, p + end + 1, (size_t)(len - end - 1));
-           b->bv_ga.ga_len -= end - idx + 1;
+    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;
+    }
+}
+
+/*
+ * "reverse({blob})" function
+ */
+    void
+blob_reverse(blob_T *b, typval_T *rettv)
+{
+    int        i, len = blob_len(b);
+
+    for (i = 0; i < len / 2; i++)
+    {
+       int tmp = blob_get(b, i);
+
+       blob_set(b, i, blob_get(b, len - i - 1));
+       blob_set(b, len - i - 1, tmp);
     }
+    rettv_blob_set(rettv, b);
 }
 
 /*
index ea43d0be1cb44eee3548f6f5243dc3d47557c41c..f2989fff21e47decf4a23ab560d434021e3fb0a0 100644 (file)
@@ -896,13 +896,11 @@ eval_dict(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int literal)
     int                vim9script = in_vim9script();
     int                had_comma;
 
-    /*
-     * First check if it's not a curly-braces thing: {expr}.
-     * Must do this without evaluating, otherwise a function may be called
-     * twice.  Unfortunately this means we need to call eval1() twice for the
-     * first item.
-     * But {} is an empty Dictionary.
-     */
+    // First check if it's not a curly-braces thing: {expr}.
+    // Must do this without evaluating, otherwise a function may be called
+    // twice.  Unfortunately this means we need to call eval1() twice for the
+    // first item.
+    // But {} is an empty Dictionary.
     if (!vim9script
            && *curly_expr != '}'
            && eval1(&curly_expr, &tv, NULL) == OK
@@ -1183,6 +1181,251 @@ dict_equal(
     return TRUE;
 }
 
+/*
+ * Count the number of times item "needle" occurs in Dict "d". Case is ignored
+ * if "ic" is TRUE.
+ */
+    long
+dict_count(dict_T *d, typval_T *needle, int ic)
+{
+    int                todo;
+    hashitem_T *hi;
+    long       n = 0;
+
+    if (d == NULL)
+       return 0;
+
+    todo = (int)d->dv_hashtab.ht_used;
+    for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi)
+    {
+       if (!HASHITEM_EMPTY(hi))
+       {
+           --todo;
+           if (tv_equal(&HI2DI(hi)->di_tv, needle, ic, FALSE))
+               ++n;
+       }
+    }
+
+    return n;
+}
+
+/*
+ * extend() a Dict. Append Dict argvars[1] to Dict argvars[0] and return the
+ * resulting Dict in "rettv".  "is_new" is TRUE for extendnew().
+ */
+    void
+dict_extend_func(
+       typval_T        *argvars,
+       type_T          *type,
+       char            *func_name,
+       char_u          *arg_errmsg,
+       int             is_new,
+       typval_T        *rettv)
+{
+    dict_T     *d1, *d2;
+    char_u     *action;
+    int        i;
+
+    d1 = argvars[0].vval.v_dict;
+    if (d1 == NULL)
+    {
+       emsg(_(e_cannot_extend_null_dict));
+       return;
+    }
+    d2 = argvars[1].vval.v_dict;
+    if ((is_new || !value_check_lock(d1->dv_lock, arg_errmsg, TRUE))
+           && d2 != NULL)
+    {
+       if (is_new)
+       {
+           d1 = dict_copy(d1, FALSE, get_copyID());
+           if (d1 == NULL)
+               return;
+       }
+
+       // Check the third argument.
+       if (argvars[2].v_type != VAR_UNKNOWN)
+       {
+           static char *(av[]) = {"keep", "force", "error"};
+
+           action = tv_get_string_chk(&argvars[2]);
+           if (action == NULL)
+               return;
+           for (i = 0; i < 3; ++i)
+               if (STRCMP(action, av[i]) == 0)
+                   break;
+           if (i == 3)
+           {
+               semsg(_(e_invarg2), action);
+               return;
+           }
+       }
+       else
+           action = (char_u *)"force";
+
+       if (type != NULL && check_typval_arg_type(type, &argvars[1],
+                   func_name, 2) == FAIL)
+           return;
+       dict_extend(d1, d2, action, func_name);
+
+       if (is_new)
+       {
+           rettv->v_type = VAR_DICT;
+           rettv->vval.v_dict = d1;
+           rettv->v_lock = FALSE;
+       }
+       else
+           copy_tv(&argvars[0], rettv);
+    }
+}
+
+/*
+ * Implementation of map() and filter() for a Dict.  Apply "expr" to every
+ * item in Dict "d" and return the result in "rettv".
+ */
+    void
+dict_filter_map(
+       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;
+    dictitem_T *di;
+    int                todo;
+    int                rem;
+
+    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;
+
+    prev_lock = d->dv_lock;
+
+    if (filtermap == FILTERMAP_MAPNEW)
+    {
+       if (rettv_dict_alloc(rettv) == FAIL)
+           return;
+       d_ret = rettv->vval.v_dict;
+    }
+
+    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 (!HASHITEM_EMPTY(hi))
+       {
+           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);
+           }
+       }
+    }
+    hash_unlock(ht);
+    d->dv_lock = prev_lock;
+}
+
+/*
+ * "remove({dict})" function
+ */
+    void
+dict_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg)
+{
+    dict_T     *d;
+    char_u     *key;
+    dictitem_T *di;
+
+    if (argvars[2].v_type != VAR_UNKNOWN)
+    {
+       semsg(_(e_too_many_arguments_for_function_str), "remove()");
+       return;
+    }
+
+    d = argvars[0].vval.v_dict;
+    if (d == NULL || value_check_lock(d->dv_lock, arg_errmsg, TRUE))
+       return;
+
+    key = tv_get_string_chk(&argvars[1]);
+    if (key == NULL)
+       return;
+
+    di = dict_find(d, key, -1);
+    if (di == NULL)
+    {
+       semsg(_(e_dictkey), key);
+       return;
+    }
+
+    if (var_check_fixed(di->di_flags, arg_errmsg, TRUE)
+           || var_check_ro(di->di_flags, arg_errmsg, TRUE))
+       return;
+
+    *rettv = di->di_tv;
+    init_tv(&di->di_tv);
+    dictitem_remove(d, di);
+}
+
 /*
  * Turn a dict into a list:
  * "what" == 0: list of keys
@@ -1338,36 +1581,4 @@ f_has_key(typval_T *argvars, typval_T *rettv)
                                      tv_get_string(&argvars[1]), -1) != NULL;
 }
 
-/*
- * "remove({dict})" function
- */
-    void
-dict_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg)
-{
-    dict_T     *d;
-    char_u     *key;
-    dictitem_T *di;
-
-    if (argvars[2].v_type != VAR_UNKNOWN)
-       semsg(_(e_too_many_arguments_for_function_str), "remove()");
-    else if ((d = argvars[0].vval.v_dict) != NULL
-           && !value_check_lock(d->dv_lock, arg_errmsg, TRUE))
-    {
-       key = tv_get_string_chk(&argvars[1]);
-       if (key != NULL)
-       {
-           di = dict_find(d, key, -1);
-           if (di == NULL)
-               semsg(_(e_dictkey), key);
-           else if (!var_check_fixed(di->di_flags, arg_errmsg, TRUE)
-                       && !var_check_ro(di->di_flags, arg_errmsg, TRUE))
-           {
-               *rettv = di->di_tv;
-               init_tv(&di->di_tv);
-               dictitem_remove(d, di);
-           }
-       }
-    }
-}
-
 #endif // defined(FEAT_EVAL)
index 57a1a52746505a09e1358bd178dd12587efc10b9..61a26a35ddc2ebc436449730eda2a6c0bce4b13c 100644 (file)
@@ -313,28 +313,6 @@ listitem_alloc(void)
     return ALLOC_ONE(listitem_T);
 }
 
-/*
- * Make a typval_T of the first character of "input" and store it in "output".
- * Return OK or FAIL.
- */
-    static int
-tv_get_first_char(char_u *input, typval_T *output)
-{
-    char_u     buf[MB_MAXBYTES + 1];
-    int                len;
-
-    if (input == NULL || output == NULL)
-       return FAIL;
-
-    len = has_mbyte ? mb_ptr2len(input) : 1;
-    STRNCPY(buf, input, len);
-    buf[len] = NUL;
-    output->v_type = VAR_STRING;
-    output->vval.v_string = vim_strsave(buf);
-
-    return output->vval.v_string == NULL ? FAIL : OK;
-}
-
 /*
  * Free a list item, unless it was allocated together with the list itself.
  * Does not clear the value.  Does not notify watchers.
@@ -876,9 +854,7 @@ list_assign_range(
     long       idx;
     type_T     *member_type = NULL;
 
-    /*
-     * Check whether any of the list items is locked before making any changes.
-     */
+    // Check whether any of the list items is locked before making any changes.
     idx = idx1;
     dest_li = first_li;
     for (src_li = src->lv_first; src_li != NULL && dest_li != NULL; )
@@ -896,9 +872,7 @@ list_assign_range(
                                           && dest->lv_type->tt_member != NULL)
        member_type = dest->lv_type->tt_member;
 
-    /*
-     * Assign the List values to the list items.
-     */
+    // Assign the List values to the list items.
     idx = idx1;
     dest_li = first_li;
     for (src_li = src->lv_first; src_li != NULL; )
@@ -1725,6 +1699,10 @@ f_list2str(typval_T *argvars, typval_T *rettv)
     rettv->vval.v_string = ga.ga_data;
 }
 
+/*
+ * Remove item argvars[1] from List argvars[0]. If argvars[2] is supplied, then
+ * remove the range of items from argvars[1] to argvars[2] (inclusive).
+ */
     static void
 list_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg)
 {
@@ -1733,6 +1711,9 @@ list_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg)
     listitem_T *li;
     int                error = FALSE;
     long       idx;
+    long       end;
+    int                cnt = 0;
+    list_T     *rl;
 
     if ((l = argvars[0].vval.v_list) == NULL
                             || value_check_lock(l->lv_lock, arg_errmsg, TRUE))
@@ -1740,75 +1721,76 @@ list_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg)
 
     idx = (long)tv_get_number_chk(&argvars[1], &error);
     if (error)
-       ;               // type error: do nothing, errmsg already given
-    else if ((item = list_find(l, idx)) == NULL)
+       return;         // type error: do nothing, errmsg already given
+
+    if ((item = list_find(l, idx)) == NULL)
+    {
        semsg(_(e_listidx), idx);
-    else
+       return;
+    }
+
+    if (argvars[2].v_type == VAR_UNKNOWN)
     {
-       if (argvars[2].v_type == VAR_UNKNOWN)
-       {
-           // Remove one item, return its value.
-           vimlist_remove(l, item, item);
-           *rettv = item->li_tv;
-           list_free_item(l, item);
-       }
-       else
-       {
-           // Remove range of items, return list with values.
-           long end = (long)tv_get_number_chk(&argvars[2], &error);
+       // Remove one item, return its value.
+       vimlist_remove(l, item, item);
+       *rettv = item->li_tv;
+       list_free_item(l, item);
+       return;
+    }
 
-           if (error)
-               ;               // type error: do nothing
-           else if ((item2 = list_find(l, end)) == NULL)
-               semsg(_(e_listidx), end);
-           else
-           {
-               int         cnt = 0;
+    // Remove range of items, return list with values.
+    end = (long)tv_get_number_chk(&argvars[2], &error);
+    if (error)
+       return;         // type error: do nothing
 
-               for (li = item; li != NULL; li = li->li_next)
-               {
-                   ++cnt;
-                   if (li == item2)
-                       break;
-               }
-               if (li == NULL)  // didn't find "item2" after "item"
-                   emsg(_(e_invalid_range));
-               else
-               {
-                   vimlist_remove(l, item, item2);
-                   if (rettv_list_alloc(rettv) == OK)
-                   {
-                       list_T *rl = rettv->vval.v_list;
-
-                       if (l->lv_with_items > 0)
-                       {
-                           // need to copy the list items and move the value
-                           while (item != NULL)
-                           {
-                               li = listitem_alloc();
-                               if (li == NULL)
-                                   return;
-                               li->li_tv = item->li_tv;
-                               init_tv(&item->li_tv);
-                               list_append(rl, li);
-                               if (item == item2)
-                                   break;
-                               item = item->li_next;
-                           }
-                       }
-                       else
-                       {
-                           rl->lv_first = item;
-                           rl->lv_u.mat.lv_last = item2;
-                           item->li_prev = NULL;
-                           item2->li_next = NULL;
-                           rl->lv_len = cnt;
-                       }
-                   }
-               }
-           }
+    if ((item2 = list_find(l, end)) == NULL)
+    {
+       semsg(_(e_listidx), end);
+       return;
+    }
+
+    for (li = item; li != NULL; li = li->li_next)
+    {
+       ++cnt;
+       if (li == item2)
+           break;
+    }
+    if (li == NULL)  // didn't find "item2" after "item"
+    {
+       emsg(_(e_invalid_range));
+       return;
+    }
+
+    vimlist_remove(l, item, item2);
+    if (rettv_list_alloc(rettv) != OK)
+       return;
+
+    rl = rettv->vval.v_list;
+
+    if (l->lv_with_items > 0)
+    {
+       // need to copy the list items and move the value
+       while (item != NULL)
+       {
+           li = listitem_alloc();
+           if (li == NULL)
+               return;
+           li->li_tv = item->li_tv;
+           init_tv(&item->li_tv);
+           list_append(rl, li);
+           if (item == item2)
+               break;
+           item = item->li_next;
        }
     }
+    else
+    {
+       rl->lv_first = item;
+       rl->lv_u.mat.lv_last = item2;
+       item->li_prev = NULL;
+       item2->li_next = NULL;
+       rl->lv_len = cnt;
+    }
 }
 
 static int item_compare(const void *s1, const void *s2);
@@ -2101,8 +2083,8 @@ do_uniq(list_T *l, sortinfo_T *info)
 }
 
 /*
- * Parse the optional arguments to sort() and uniq() and return the values in
- * 'info'.
+ * Parse the optional arguments supplied to the sort() or uniq() function and
+ * return the values in "info".
  */
     static int
 parse_sort_uniq_args(typval_T *argvars, sortinfo_T *info)
@@ -2272,17 +2254,11 @@ f_uniq(typval_T *argvars, typval_T *rettv)
     do_sort_uniq(argvars, rettv, FALSE);
 }
 
-typedef enum {
-    FILTERMAP_FILTER,
-    FILTERMAP_MAP,
-    FILTERMAP_MAPNEW
-} filtermap_T;
-
 /*
  * Handle one item for map() and filter().
  * Sets v:val to "tv".  Caller must set v:key.
  */
-    static int
+    int
 filter_map_one(
        typval_T        *tv,        // original value
        typval_T        *expr,      // callback
@@ -2320,254 +2296,11 @@ theend:
 }
 
 /*
- * Implementation of map() and filter() for a Dict.
- */
-    static void
-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;
-    dictitem_T *di;
-    int                todo;
-    int                rem;
-
-    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;
-
-    prev_lock = d->dv_lock;
-
-    if (filtermap == FILTERMAP_MAPNEW)
-    {
-       if (rettv_dict_alloc(rettv) == FAIL)
-           return;
-       d_ret = rettv->vval.v_dict;
-    }
-
-    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 (!HASHITEM_EMPTY(hi))
-       {
-           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);
-           }
-       }
-    }
-    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 (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)
-       {
-           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 *)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;
-    }
-}
-
-/*
- * 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)
-    {
-       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)
-       {
-           clear_tv(&newtv);
-           clear_tv(&tv);
-           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;
-}
-
-/*
- * Implementation of map() and filter() for a List.
+ * Implementation of map() and filter() for a List.  Apply "expr" to every item
+ * in List "l" and return the result in "rettv".
  */
     static void
-filter_map_list(
+list_filter_map(
        list_T          *l,
        filtermap_T     filtermap,
        type_T          *argtype,
@@ -2775,15 +2508,15 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
        did_emsg = FALSE;
 
        if (argvars[0].v_type == VAR_DICT)
-           filter_map_dict(argvars[0].vval.v_dict, filtermap, type, func_name,
+           dict_filter_map(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);
+           blob_filter_map(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,
+           string_filter_map(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,
+           list_filter_map(argvars[0].vval.v_list, filtermap, type, func_name,
                    arg_errmsg, expr, rettv);
 
        restore_vimvar(VV_KEY, &save_key);
@@ -2826,6 +2559,27 @@ f_mapnew(typval_T *argvars, typval_T *rettv)
 
 /*
  * "add(list, item)" function
+ */
+    static void
+list_add(typval_T *argvars, typval_T *rettv)
+{
+    list_T     *l = argvars[0].vval.v_list;
+
+    if (l == NULL)
+    {
+       if (in_vim9script())
+           emsg(_(e_cannot_add_to_null_list));
+    }
+    else if (!value_check_lock(l->lv_lock,
+               (char_u *)N_("add() argument"), TRUE)
+           && list_append_tv(l, &argvars[1]) == OK)
+    {
+       copy_tv(&argvars[0], rettv);
+    }
+}
+
+/*
+ * "add(object, item)" function
  */
     void
 f_add(typval_T *argvars, typval_T *rettv)
@@ -2839,103 +2593,30 @@ f_add(typval_T *argvars, typval_T *rettv)
        return;
 
     if (argvars[0].v_type == VAR_LIST)
-    {
-       list_T  *l = argvars[0].vval.v_list;
-
-       if (l == NULL)
-       {
-           if (in_vim9script())
-               emsg(_(e_cannot_add_to_null_list));
-       }
-       else if (!value_check_lock(l->lv_lock,
-                                         (char_u *)N_("add() argument"), TRUE)
-               && list_append_tv(l, &argvars[1]) == OK)
-       {
-           copy_tv(&argvars[0], rettv);
-       }
-    }
+       list_add(argvars, rettv);
     else if (argvars[0].v_type == VAR_BLOB)
-    {
-       blob_T  *b = argvars[0].vval.v_blob;
-
-       if (b == NULL)
-       {
-           if (in_vim9script())
-               emsg(_(e_cannot_add_to_null_blob));
-       }
-       else if (!value_check_lock(b->bv_lock,
-                                        (char_u *)N_("add() argument"), TRUE))
-       {
-           int         error = FALSE;
-           varnumber_T n = tv_get_number_chk(&argvars[1], &error);
-
-           if (!error)
-           {
-               ga_append(&b->bv_ga, (int)n);
-               copy_tv(&argvars[0], rettv);
-           }
-       }
-    }
+       blob_add(argvars, rettv);
     else
        emsg(_(e_listblobreq));
 }
 
 /*
- * Count the number of times "needle" occurs in string "haystack". Case is
- * ignored if "ic" is TRUE.
+ * Count the number of times item "needle" occurs in List "l" starting at index
+ * "idx". Case is ignored if "ic" is TRUE.
  */
     static long
-count_string(char_u *haystack, char_u *needle, int ic)
+list_count(list_T *l, typval_T *needle, long idx, int ic)
 {
     long       n = 0;
-    char_u     *p = haystack;
-    char_u     *next;
+    listitem_T *li;
 
-    if (p == NULL || needle == NULL || *needle == NUL)
+    if (l == NULL)
        return 0;
 
-    if (ic)
-    {
-       size_t len = STRLEN(needle);
+    CHECK_LIST_MATERIALIZE(l);
 
-       while (*p != NUL)
-       {
-           if (MB_STRNICMP(p, needle, len) == 0)
-           {
-               ++n;
-               p += len;
-           }
-           else
-               MB_PTR_ADV(p);
-       }
-    }
-    else
-       while ((next = (char_u *)strstr((char *)p, (char *)needle)) != NULL)
-       {
-           ++n;
-           p = next + STRLEN(needle);
-       }
-
-    return n;
-}
-
-/*
- * Count the number of times item "needle" occurs in List "l" starting at index
- * "idx". Case is ignored if "ic" is TRUE.
- */
-    static long
-count_list(list_T *l, typval_T *needle, long idx, int ic)
-{
-    long       n = 0;
-    listitem_T *li;
-
-    if (l == NULL)
-       return 0;
-
-    CHECK_LIST_MATERIALIZE(l);
-
-    if (list_len(l) == 0)
-       return 0;
+    if (list_len(l) == 0)
+       return 0;
 
     li = list_find(l, idx);
     if (li == NULL)
@@ -2951,34 +2632,6 @@ count_list(list_T *l, typval_T *needle, long idx, int ic)
     return n;
 }
 
-/*
- * Count the number of times item "needle" occurs in Dict "d". Case is ignored
- * if "ic" is TRUE.
- */
-    static long
-count_dict(dict_T *d, typval_T *needle, int ic)
-{
-    int                todo;
-    hashitem_T *hi;
-    long       n = 0;
-
-    if (d == NULL)
-       return 0;
-
-    todo = (int)d->dv_hashtab.ht_used;
-    for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi)
-    {
-       if (!HASHITEM_EMPTY(hi))
-       {
-           --todo;
-           if (tv_equal(&HI2DI(hi)->di_tv, needle, ic, FALSE))
-               ++n;
-       }
-    }
-
-    return n;
-}
-
 /*
  * "count()" function
  */
@@ -3000,7 +2653,7 @@ f_count(typval_T *argvars, typval_T *rettv)
        ic = (int)tv_get_bool_chk(&argvars[2], &error);
 
     if (!error && argvars[0].v_type == VAR_STRING)
-       n = count_string(argvars[0].vval.v_string,
+       n = string_count(argvars[0].vval.v_string,
                                        tv_get_string_chk(&argvars[1]), ic);
     else if (!error && argvars[0].v_type == VAR_LIST)
     {
@@ -3010,7 +2663,7 @@ f_count(typval_T *argvars, typval_T *rettv)
                && argvars[3].v_type != VAR_UNKNOWN)
            idx = (long)tv_get_number_chk(&argvars[3], &error);
        if (!error)
-           n = count_list(argvars[0].vval.v_list, &argvars[1], idx, ic);
+           n = list_count(argvars[0].vval.v_list, &argvars[1], idx, ic);
     }
     else if (!error && argvars[0].v_type == VAR_DICT)
     {
@@ -3018,7 +2671,7 @@ f_count(typval_T *argvars, typval_T *rettv)
                && argvars[3].v_type != VAR_UNKNOWN)
            emsg(_(e_invarg));
        else
-           n = count_dict(argvars[0].vval.v_dict, &argvars[1], ic);
+           n = dict_count(argvars[0].vval.v_dict, &argvars[1], ic);
     }
     else
        semsg(_(e_listdictarg), "count()");
@@ -3031,7 +2684,7 @@ f_count(typval_T *argvars, typval_T *rettv)
  * extendnew().
  */
     static void
-extend_list(
+list_extend_func(
        typval_T        *argvars,
        type_T          *type,
        char            *func_name,
@@ -3097,76 +2750,6 @@ extend_list(
     }
 }
 
-/*
- * extend() a Dict. Append Dict argvars[1] to Dict argvars[0] and return the
- * resulting Dict in "rettv".  "is_new" is TRUE for extendnew().
- */
-    static void
-extend_dict(
-       typval_T        *argvars,
-       type_T          *type,
-       char            *func_name,
-       char_u          *arg_errmsg,
-       int             is_new,
-       typval_T        *rettv)
-{
-    dict_T     *d1, *d2;
-    char_u     *action;
-    int        i;
-
-    d1 = argvars[0].vval.v_dict;
-    if (d1 == NULL)
-    {
-       emsg(_(e_cannot_extend_null_dict));
-       return;
-    }
-    d2 = argvars[1].vval.v_dict;
-    if ((is_new || !value_check_lock(d1->dv_lock, arg_errmsg, TRUE))
-           && d2 != NULL)
-    {
-       if (is_new)
-       {
-           d1 = dict_copy(d1, FALSE, get_copyID());
-           if (d1 == NULL)
-               return;
-       }
-
-       // Check the third argument.
-       if (argvars[2].v_type != VAR_UNKNOWN)
-       {
-           static char *(av[]) = {"keep", "force", "error"};
-
-           action = tv_get_string_chk(&argvars[2]);
-           if (action == NULL)
-               return;
-           for (i = 0; i < 3; ++i)
-               if (STRCMP(action, av[i]) == 0)
-                   break;
-           if (i == 3)
-           {
-               semsg(_(e_invarg2), action);
-               return;
-           }
-       }
-       else
-           action = (char_u *)"force";
-
-       if (type != NULL && check_typval_arg_type(type, &argvars[1],
-                   func_name, 2) == FAIL)
-           return;
-       dict_extend(d1, d2, action, func_name);
-
-       if (is_new)
-       {
-           rettv->v_type = VAR_DICT;
-           rettv->vval.v_dict = d1;
-           rettv->v_lock = FALSE;
-       }
-       else
-           copy_tv(&argvars[0], rettv);
-    }
-}
-
 /*
  * "extend()" or "extendnew()" function.  "is_new" is TRUE for extendnew().
  */
@@ -3185,9 +2768,9 @@ extend(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg, int is_new)
     }
 
     if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST)
-       extend_list(argvars, type, func_name, arg_errmsg, is_new, rettv);
+       list_extend_func(argvars, type, func_name, arg_errmsg, is_new, rettv);
     else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT)
-       extend_dict(argvars, type, func_name, arg_errmsg, is_new, rettv);
+       dict_extend_func(argvars, type, func_name, arg_errmsg, is_new, rettv);
     else
        semsg(_(e_listdictarg), func_name);
 
@@ -3219,16 +2802,53 @@ f_extendnew(typval_T *argvars, typval_T *rettv)
     extend(argvars, rettv, errmsg, TRUE);
 }
 
+    static void
+list_insert_func(typval_T *argvars, typval_T *rettv)
+{
+    list_T     *l = argvars[0].vval.v_list;
+    long       before = 0;
+    listitem_T *item;
+    int                error = FALSE;
+
+    if (l == NULL)
+    {
+       if (in_vim9script())
+           emsg(_(e_cannot_add_to_null_list));
+       return;
+    }
+
+    if (value_check_lock(l->lv_lock, (char_u *)N_("insert() argument"), TRUE))
+       return;
+
+    if (argvars[2].v_type != VAR_UNKNOWN)
+       before = (long)tv_get_number_chk(&argvars[2], &error);
+    if (error)
+       return;         // type error; errmsg already given
+
+    if (before == l->lv_len)
+       item = NULL;
+    else
+    {
+       item = list_find(l, before);
+       if (item == NULL)
+       {
+           semsg(_(e_listidx), before);
+           l = NULL;
+       }
+    }
+    if (l != NULL)
+    {
+       (void)list_insert_tv(l, &argvars[1], item);
+       copy_tv(&argvars[0], rettv);
+    }
+}
+
 /*
  * "insert()" function
  */
     void
 f_insert(typval_T *argvars, typval_T *rettv)
 {
-    long       before = 0;
-    listitem_T *item;
-    int                error = FALSE;
-
     if (in_vim9script()
            && (check_for_list_or_blob_arg(argvars, 0) == FAIL
                || (argvars[0].v_type == VAR_BLOB
@@ -3237,88 +2857,11 @@ f_insert(typval_T *argvars, typval_T *rettv)
        return;
 
     if (argvars[0].v_type == VAR_BLOB)
-    {
-       blob_T  *b = argvars[0].vval.v_blob;
-
-       if (b == NULL)
-       {
-           if (in_vim9script())
-               emsg(_(e_cannot_add_to_null_blob));
-       }
-       else if (!value_check_lock(b->bv_lock,
-                                    (char_u *)N_("insert() argument"), TRUE))
-       {
-           int         val, len;
-           char_u      *p;
-
-           len = blob_len(b);
-           if (argvars[2].v_type != VAR_UNKNOWN)
-           {
-               before = (long)tv_get_number_chk(&argvars[2], &error);
-               if (error)
-                   return;             // type error; errmsg already given
-               if (before < 0 || before > len)
-               {
-                   semsg(_(e_invarg2), tv_get_string(&argvars[2]));
-                   return;
-               }
-           }
-           val = tv_get_number_chk(&argvars[1], &error);
-           if (error)
-               return;
-           if (val < 0 || val > 255)
-           {
-               semsg(_(e_invarg2), tv_get_string(&argvars[1]));
-               return;
-           }
-
-           if (ga_grow(&b->bv_ga, 1) == FAIL)
-               return;
-           p = (char_u *)b->bv_ga.ga_data;
-           mch_memmove(p + before + 1, p + before, (size_t)len - before);
-           *(p + before) = val;
-           ++b->bv_ga.ga_len;
-
-           copy_tv(&argvars[0], rettv);
-       }
-    }
+       blob_insert_func(argvars, rettv);
     else if (argvars[0].v_type != VAR_LIST)
        semsg(_(e_listblobarg), "insert()");
     else
-    {
-       list_T  *l = argvars[0].vval.v_list;
-
-       if (l == NULL)
-       {
-           if (in_vim9script())
-               emsg(_(e_cannot_add_to_null_list));
-       }
-       else if (!value_check_lock(l->lv_lock,
-                                    (char_u *)N_("insert() argument"), TRUE))
-       {
-           if (argvars[2].v_type != VAR_UNKNOWN)
-               before = (long)tv_get_number_chk(&argvars[2], &error);
-           if (error)
-               return;         // type error; errmsg already given
-
-           if (before == l->lv_len)
-               item = NULL;
-           else
-           {
-               item = list_find(l, before);
-               if (item == NULL)
-               {
-                   semsg(_(e_listidx), before);
-                   l = NULL;
-               }
-           }
-           if (l != NULL)
-           {
-               (void)list_insert_tv(l, &argvars[1], item);
-               copy_tv(&argvars[0], rettv);
-           }
-       }
-    }
+       list_insert_func(argvars, rettv);
 }
 
 /*
@@ -3349,66 +2892,54 @@ f_remove(typval_T *argvars, typval_T *rettv)
        semsg(_(e_listdictblobarg), "remove()");
 }
 
+    static void
+list_reverse(list_T *l, typval_T *rettv)
+{
+    listitem_T *li, *ni;
+
+    rettv_list_set(rettv, l);
+    if (l != NULL
+           && !value_check_lock(l->lv_lock,
+               (char_u *)N_("reverse() argument"), TRUE))
+    {
+       if (l->lv_first == &range_list_item)
+       {
+           varnumber_T new_start = l->lv_u.nonmat.lv_start
+               + (l->lv_len - 1) * l->lv_u.nonmat.lv_stride;
+           l->lv_u.nonmat.lv_end = new_start
+               - (l->lv_u.nonmat.lv_end - l->lv_u.nonmat.lv_start);
+           l->lv_u.nonmat.lv_start = new_start;
+           l->lv_u.nonmat.lv_stride = -l->lv_u.nonmat.lv_stride;
+           return;
+       }
+       li = l->lv_u.mat.lv_last;
+       l->lv_first = l->lv_u.mat.lv_last = NULL;
+       l->lv_len = 0;
+       while (li != NULL)
+       {
+           ni = li->li_prev;
+           list_append(l, li);
+           li = ni;
+       }
+       l->lv_u.mat.lv_idx = l->lv_len - l->lv_u.mat.lv_idx - 1;
+    }
+}
+
 /*
  * "reverse({list})" function
  */
     void
 f_reverse(typval_T *argvars, typval_T *rettv)
 {
-    list_T     *l;
-    listitem_T *li, *ni;
-
     if (in_vim9script() && check_for_list_or_blob_arg(argvars, 0) == FAIL)
        return;
 
     if (argvars[0].v_type == VAR_BLOB)
-    {
-       blob_T  *b = argvars[0].vval.v_blob;
-       int     i, len = blob_len(b);
-
-       for (i = 0; i < len / 2; i++)
-       {
-           int tmp = blob_get(b, i);
-
-           blob_set(b, i, blob_get(b, len - i - 1));
-           blob_set(b, len - i - 1, tmp);
-       }
-       rettv_blob_set(rettv, b);
-       return;
-    }
-
-    if (argvars[0].v_type != VAR_LIST)
+       blob_reverse(argvars[0].vval.v_blob, rettv);
+    else if (argvars[0].v_type != VAR_LIST)
        semsg(_(e_listblobarg), "reverse()");
     else
-    {
-       l = argvars[0].vval.v_list;
-       rettv_list_set(rettv, l);
-       if (l != NULL
-           && !value_check_lock(l->lv_lock,
-                                   (char_u *)N_("reverse() argument"), TRUE))
-       {
-           if (l->lv_first == &range_list_item)
-           {
-               varnumber_T new_start = l->lv_u.nonmat.lv_start
-                                 + (l->lv_len - 1) * l->lv_u.nonmat.lv_stride;
-               l->lv_u.nonmat.lv_end = new_start
-                          - (l->lv_u.nonmat.lv_end - l->lv_u.nonmat.lv_start);
-               l->lv_u.nonmat.lv_start = new_start;
-               l->lv_u.nonmat.lv_stride = -l->lv_u.nonmat.lv_stride;
-               return;
-           }
-           li = l->lv_u.mat.lv_last;
-           l->lv_first = l->lv_u.mat.lv_last = NULL;
-           l->lv_len = 0;
-           while (li != NULL)
-           {
-               ni = li->li_prev;
-               list_append(l, li);
-               li = ni;
-           }
-           l->lv_u.mat.lv_idx = l->lv_len - l->lv_u.mat.lv_idx - 1;
-       }
-    }
+       list_reverse(argvars[0].vval.v_list, rettv);
 }
 
 /*
@@ -3417,7 +2948,7 @@ f_reverse(typval_T *argvars, typval_T *rettv)
  * in 'rettv'.
  */
     static void
-reduce_list(
+list_reduce(
        typval_T        *argvars,
        char_u          *func_name,
        funcexe_T       *funcexe,
@@ -3470,114 +3001,6 @@ reduce_list(
     l->lv_lock = prev_locked;
 }
 
-/*
- * reduce() String argvars[0] using the function 'funcname' with arguments in
- * 'funcexe' starting with the initial value argvars[2] and return the result
- * in 'rettv'.
- */
-    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() Blob argvars[0] using the function 'funcname' with arguments in
- * 'funcexe' starting with the initial value argvars[2] and return the result
- * in 'rettv'.
- */
-    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
  * "reduce(blob, { accumulator, element -> value } [, initial])"
@@ -3622,11 +3045,11 @@ f_reduce(typval_T *argvars, typval_T *rettv)
     funcexe.fe_partial = partial;
 
     if (argvars[0].v_type == VAR_LIST)
-       reduce_list(argvars, func_name, &funcexe, rettv);
+       list_reduce(argvars, func_name, &funcexe, rettv);
     else if (argvars[0].v_type == VAR_STRING)
-       reduce_string(argvars, func_name, &funcexe, rettv);
+       string_reduce(argvars, func_name, &funcexe, rettv);
     else
-       reduce_blob(argvars, func_name, &funcexe, rettv);
+       blob_reduce(argvars, func_name, &funcexe, rettv);
 }
 
 #endif // defined(FEAT_EVAL)
index dbc8d1490c4fd812481531e698fce09195bf2526..0b1c81458ea5f1384f5118dc0f5d7acc667346ea 100644 (file)
@@ -18,7 +18,12 @@ int blob_slice_or_index(blob_T *blob, int is_range, varnumber_T n1, varnumber_T
 int check_blob_index(long bloblen, varnumber_T n1, int quiet);
 int check_blob_range(long bloblen, varnumber_T n1, varnumber_T n2, int quiet);
 int blob_set_range(blob_T *dest, long n1, long n2, typval_T *src);
+void blob_add(typval_T *argvars, typval_T *rettv);
 void blob_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg);
+void blob_filter_map(blob_T *blob_arg, filtermap_T filtermap, typval_T *expr, typval_T *rettv);
+void blob_insert_func(typval_T *argvars, typval_T *rettv);
+void blob_reduce(typval_T *argvars, char_u *func_name, funcexe_T *funcexe, typval_T *rettv);
+void blob_reverse(blob_T *b, typval_T *rettv);
 void f_blob2list(typval_T *argvars, typval_T *rettv);
 void f_list2blob(typval_T *argvars, typval_T *rettv);
 /* vim: set ft=c : */
index 3089c1fff7b5350ea99f5691584cfe582accce4e..d6cf15226307629a39390bc39cfc6c1ea93fb99a 100644 (file)
@@ -39,10 +39,13 @@ int eval_dict(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int literal);
 void dict_extend(dict_T *d1, dict_T *d2, char_u *action, char *func_name);
 dictitem_T *dict_lookup(hashitem_T *hi);
 int dict_equal(dict_T *d1, dict_T *d2, int ic, int recursive);
+long dict_count(dict_T *d, typval_T *needle, int ic);
+void dict_extend_func(typval_T *argvars, type_T *type, char *func_name, char_u *arg_errmsg, int is_new, typval_T *rettv);
+void dict_filter_map(dict_T *d, filtermap_T filtermap, type_T *argtype, char *func_name, char_u *arg_errmsg, typval_T *expr, typval_T *rettv);
+void dict_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg);
 void f_items(typval_T *argvars, typval_T *rettv);
 void f_keys(typval_T *argvars, typval_T *rettv);
 void f_values(typval_T *argvars, typval_T *rettv);
 void dict_set_items_ro(dict_T *di);
 void f_has_key(typval_T *argvars, typval_T *rettv);
-void dict_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg);
 /* vim: set ft=c : */
index 97e6c4730df30c067eaff626ba8588fe14a961b4..5b7414bce2006294b4b806a5917f03a513d5c7a2 100644 (file)
@@ -50,6 +50,7 @@ void init_static_list(staticList10_T *sl);
 void f_list2str(typval_T *argvars, typval_T *rettv);
 void f_sort(typval_T *argvars, typval_T *rettv);
 void f_uniq(typval_T *argvars, typval_T *rettv);
+int filter_map_one(typval_T *tv, typval_T *expr, filtermap_T filtermap, typval_T *newtv, int *remp);
 void f_filter(typval_T *argvars, typval_T *rettv);
 void f_map(typval_T *argvars, typval_T *rettv);
 void f_mapnew(typval_T *argvars, typval_T *rettv);
index 4dba9b1bdaaa2ba21d5cc9ddc82486e0ace70e0d..778ec902bc69c1ee958cdefe16b026749d1c6ca5 100644 (file)
@@ -21,6 +21,9 @@ void sort_strings(char_u **files, int count);
 int has_non_ascii(char_u *s);
 char_u *concat_str(char_u *str1, char_u *str2);
 char_u *string_quote(char_u *str, int function);
+long string_count(char_u *haystack, char_u *needle, int ic);
+void string_filter_map(char_u *str, filtermap_T filtermap, typval_T *expr, typval_T *rettv);
+void string_reduce(typval_T *argvars, char_u *func_name, funcexe_T *funcexe, typval_T *rettv);
 void f_byteidx(typval_T *argvars, typval_T *rettv);
 void f_byteidxcomp(typval_T *argvars, typval_T *rettv);
 void f_charidx(typval_T *argvars, typval_T *rettv);
index 847d74379741332ef3de75e8901404e579f8d7d0..6a6b0f43c0800acaf01a1e5a216f3356cd79f7f6 100644 (file)
@@ -764,7 +764,6 @@ concat_str(char_u *str1, char_u *str2)
 }
 
 #if defined(FEAT_EVAL) || defined(PROTO)
-
 /*
  * Return string "str" in ' quotes, doubling ' characters.
  * If "str" is NULL an empty string is assumed.
@@ -809,6 +808,185 @@ string_quote(char_u *str, int function)
     return s;
 }
 
+/*
+ * Count the number of times "needle" occurs in string "haystack". Case is
+ * ignored if "ic" is TRUE.
+ */
+    long
+string_count(char_u *haystack, char_u *needle, int ic)
+{
+    long       n = 0;
+    char_u     *p = haystack;
+    char_u     *next;
+
+    if (p == NULL || needle == NULL || *needle == NUL)
+       return 0;
+
+    if (ic)
+    {
+       size_t len = STRLEN(needle);
+
+       while (*p != NUL)
+       {
+           if (MB_STRNICMP(p, needle, len) == 0)
+           {
+               ++n;
+               p += len;
+           }
+           else
+               MB_PTR_ADV(p);
+       }
+    }
+    else
+       while ((next = (char_u *)strstr((char *)p, (char *)needle)) != NULL)
+       {
+           ++n;
+           p = next + STRLEN(needle);
+       }
+
+    return n;
+}
+
+/*
+ * Make a typval_T of the first character of "input" and store it in "output".
+ * Return OK or FAIL.
+ */
+    static int
+copy_first_char_to_tv(char_u *input, typval_T *output)
+{
+    char_u     buf[MB_MAXBYTES + 1];
+    int                len;
+
+    if (input == NULL || output == NULL)
+       return FAIL;
+
+    len = has_mbyte ? mb_ptr2len(input) : 1;
+    STRNCPY(buf, input, len);
+    buf[len] = NUL;
+    output->v_type = VAR_STRING;
+    output->vval.v_string = vim_strsave(buf);
+
+    return output->vval.v_string == NULL ? FAIL : OK;
+}
+
+/*
+ * Implementation of map() and filter() for a String. Apply "expr" to every
+ * character in string "str" and return the result in "rettv".
+ */
+    void
+string_filter_map(
+       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)
+    {
+       typval_T newtv;
+
+       if (copy_first_char_to_tv(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)
+       {
+           clear_tv(&newtv);
+           clear_tv(&tv);
+           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;
+}
+
+/*
+ * reduce() String argvars[0] using the function 'funcname' with arguments in
+ * 'funcexe' starting with the initial value argvars[2] and return the result
+ * in 'rettv'.
+ */
+    void
+string_reduce(
+       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 (copy_first_char_to_tv(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 (copy_first_char_to_tv(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;
+    }
+}
+
     static void
 byteidx(typval_T *argvars, typval_T *rettv, int comp UNUSED)
 {
@@ -1686,9 +1864,6 @@ f_trim(typval_T *argvars, typval_T *rettv)
     rettv->vval.v_string = vim_strnsave(head, tail - head);
 }
 
-#endif
-
-#if defined(FEAT_EVAL)
 static char *e_printf = N_("E766: Insufficient arguments for printf()");
 
 /*
@@ -1766,6 +1941,7 @@ tv_float(typval_T *tvs, int *idxp)
     return f;
 }
 # endif
+
 #endif
 
 #ifdef FEAT_FLOAT
index 9aad4152e498c9634dd6af9a83b220707f9f092a..7023ac3d342d23bda1b0ec10fd8a7407c0b039cd 100644 (file)
@@ -4492,3 +4492,11 @@ typedef struct {
     int                sve_did_save;
     hashtab_T  sve_hashtab;
 } save_v_event_T;
+
+// Enum used by filter(), map() and mapnew()
+typedef enum {
+    FILTERMAP_FILTER,
+    FILTERMAP_MAP,
+    FILTERMAP_MAPNEW
+} filtermap_T;
+
index ab7e1960af1f478712ef077d1e6afd323e9b96b6..1767a99669122e68d5cb688fb2a66c2be7e46197 100644 (file)
@@ -182,6 +182,7 @@ func Test_filter_map_string()
     call assert_equal('', map('abc', LSTART i, x LMIDDLE '' LEND))
     call assert_equal('', map('', "v:val == 'a'"))
     call assert_equal('', map(test_null_string(), "v:val == 'a'"))
+    call assert_fails('echo map("abc", "10")', 'E928:')
   END
   call CheckLegacyAndVim9Success(lines)
 
index 2fea643189b614994ce3c1b18d166b0e0fce1ca6..e7b6588786fd514934ed66694cd0d2986a7fc41f 100644 (file)
@@ -783,6 +783,32 @@ func Test_dict_lock_map()
   call CheckScriptFailure(lines, 'E741:')
 endfunc
 
+" Lock one item in a list
+func Test_list_item_lock_map()
+  let lines =<< trim END
+      VAR l = [99, 100, 101]
+      lockvar l[1]
+      call assert_fails("echo map(l, 'v:val + 200')", 'E741:')
+      call assert_equal([299, 100, 101], l)
+  END
+  " This won't work in a :def function
+  call CheckTransLegacySuccess(lines)
+  call CheckTransVim9Success(lines)
+endfunc
+
+" Lock one item in a dict
+func Test_dict_item_lock_map()
+  let lines =<< trim END
+      VAR d = {'a': 99, 'b': 100, 'c': 101}
+      lockvar d.b
+      call assert_fails("echo map(d, 'v:val + 200')", 'E741:')
+      call assert_equal({'a': 299, 'b': 100, 'c': 101}, d)
+  END
+  " This won't work in a :def function
+  call CheckTransLegacySuccess(lines)
+  call CheckTransVim9Success(lines)
+endfunc
+
 " No extend() after lock on dict item
 func Test_dict_lock_extend()
   let d = {'a': 99, 'b': 100}
index 82f70fff12e0588cce72d7f2d06541a164432954..783ca9ed7a352a4fed660595d9d503d3e1e84969 100644 (file)
@@ -1548,4 +1548,20 @@ func Test_sort_with_marks()
   close!
 endfunc
 
+" Test for sort() using a dict function
+func Test_sort_using_dict_func()
+  func DictSort(a, b) dict
+    if self.order == 'reverse'
+      return a:b - a:a
+    else
+      return a:a - a:b
+    endif
+  endfunc
+  let d = #{order: ''}
+  call assert_equal([1, 2, 3], sort([2, 1, 3], 'DictSort', d))
+  let d = #{order: 'reverse'}
+  call assert_equal([3, 2, 1], sort([2, 1, 3], 'DictSort', d))
+  delfunc DictSort
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
index 50902d622247dc135efaea8c75e7caf6c041f939..3ce7063d4d4dc161c1d02653495e1efd0eab6eac 100644 (file)
@@ -749,6 +749,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3871,
 /**/
     3870,
 /**/