changeset 26684:2126feddeda6 v8.2.3871

patch 8.2.3871: list.c contains code for dict and blob Commit: https://github.com/vim/vim/commit/f973eeb4911de09258e84cb2248dc0f9392824b4 Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Wed Dec 22 18:19:26 2021 +0000 patch 8.2.3871: list.c contains code for dict and blob Problem: List.c contains code for dict and blob. Solution: Refactor to put code where it belongs. (Yegappan Lakshmanan, closes #9386)
author Bram Moolenaar <Bram@vim.org>
date Wed, 22 Dec 2021 19:30:04 +0100
parents d267ab922b57
children c5be20c42849
files 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
diffstat 13 files changed, 973 insertions(+), 870 deletions(-) [+]
line wrap: on
line diff
--- a/src/blob.c
+++ b/src/blob.c
@@ -410,81 +410,309 @@ blob_set_range(blob_T *dest, long n1, lo
 }
 
 /*
- * "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)
+    {
+	// 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;
+    }
+
+    // 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)
     {
-	int	len = blob_len(b);
-	char_u  *p;
+	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 (idx < 0)
-	    // count from the end
-	    idx = len + idx;
-	if (idx < 0 || idx >= len)
+    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)
 	{
-	    semsg(_(e_blobidx), idx);
+	    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;
+    }
+}
+
+/*
+ * "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)
+	{
+	    semsg(_(e_invarg2), tv_get_string(&argvars[2]));
 	    return;
 	}
-	if (argvars[2].v_type == VAR_UNKNOWN)
-	{
-	    // 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;
-	}
-	else
-	{
-	    blob_T  *blob;
+    }
+    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;
 
-	    // 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;
+    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;
 
-	    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;
-	}
+    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);
+}
+
+/*
  * blob2list() function
  */
     void
--- a/src/dict.c
+++ b/src/dict.c
@@ -896,13 +896,11 @@ eval_dict(char_u **arg, typval_T *rettv,
     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
@@ -1184,6 +1182,251 @@ dict_equal(
 }
 
 /*
+ * 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
  * "what" == 1: list of values
@@ -1338,36 +1581,4 @@ f_has_key(typval_T *argvars, typval_T *r
 				      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)
--- a/src/list.c
+++ b/src/list.c
@@ -314,28 +314,6 @@ listitem_alloc(void)
 }
 
 /*
- * 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->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 
     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,74 +1721,75 @@ list_remove(typval_T *argvars, typval_T 
 
     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);
+	return;
+    }
+
+    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);
+	return;
+    }
+
+    // Remove range of items, return list with values.
+    end = (long)tv_get_number_chk(&argvars[2], &error);
+    if (error)
+	return;		// type error: do nothing
+
+    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
     {
-	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);
-
-	    if (error)
-		;		// type error: do nothing
-	    else if ((item2 = list_find(l, end)) == NULL)
-		semsg(_(e_listidx), end);
-	    else
-	    {
-		int	    cnt = 0;
-
-		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;
-			}
-		    }
-		}
-	    }
-	}
+	rl->lv_first = item;
+	rl->lv_u.mat.lv_last = item2;
+	item->li_prev = NULL;
+	item2->li_next = NULL;
+	rl->lv_len = cnt;
     }
 }
 
@@ -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 *rett
     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.
+ * 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_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.
- */
-    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 *
 	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);
@@ -2827,6 +2560,27 @@ f_mapnew(typval_T *argvars, typval_T *re
 /*
  * "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,92 +2593,19 @@ 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.
- */
-    static long
-count_string(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;
-}
-
-/*
  * 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)
+list_count(list_T *l, typval_T *needle, long idx, int ic)
 {
     long	n = 0;
     listitem_T	*li;
@@ -2952,34 +2633,6 @@ count_list(list_T *l, typval_T *needle, 
 }
 
 /*
- * 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
  */
     void
@@ -3000,7 +2653,7 @@ f_count(typval_T *argvars, typval_T *ret
 	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 *ret
 		&& 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 *ret
 		&& 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 *ret
  * extendnew().
  */
     static void
-extend_list(
+list_extend_func(
 	typval_T	*argvars,
 	type_T		*type,
 	char		*func_name,
@@ -3098,76 +2751,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().
  */
     static void
@@ -3185,9 +2768,9 @@ extend(typval_T *argvars, typval_T *rett
     }
 
     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 
     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 *re
 	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 *re
 	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 *r
  * in 'rettv'.
  */
     static void
-reduce_list(
+list_reduce(
 	typval_T	*argvars,
 	char_u		*func_name,
 	funcexe_T	*funcexe,
@@ -3471,114 +3002,6 @@ reduce_list(
 }
 
 /*
- * 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])"
  * "reduce(string, { accumulator, element -> value } [, initial])"
@@ -3622,11 +3045,11 @@ f_reduce(typval_T *argvars, typval_T *re
     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)
--- a/src/proto/blob.pro
+++ b/src/proto/blob.pro
@@ -18,7 +18,12 @@ int blob_slice_or_index(blob_T *blob, in
 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 : */
--- a/src/proto/dict.pro
+++ b/src/proto/dict.pro
@@ -39,10 +39,13 @@ int eval_dict(char_u **arg, typval_T *re
 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 : */
--- a/src/proto/list.pro
+++ b/src/proto/list.pro
@@ -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);
--- a/src/proto/strings.pro
+++ b/src/proto/strings.pro
@@ -21,6 +21,9 @@ void sort_strings(char_u **files, int co
 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);
--- a/src/strings.c
+++ b/src/strings.c
@@ -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 *rett
     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
--- a/src/structs.h
+++ b/src/structs.h
@@ -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;
+
--- a/src/testdir/test_filter_map.vim
+++ b/src/testdir/test_filter_map.vim
@@ -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)
 
--- a/src/testdir/test_listdict.vim
+++ b/src/testdir/test_listdict.vim
@@ -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}
--- a/src/testdir/test_sort.vim
+++ b/src/testdir/test_sort.vim
@@ -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
--- a/src/version.c
+++ b/src/version.c
@@ -750,6 +750,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3871,
+/**/
     3870,
 /**/
     3869,