changeset 26676:b856b797c5d1 v8.2.3867

patch 8.2.3867: implementation of some list functions too complicated Commit: https://github.com/vim/vim/commit/d92813a59877c707e4b64bea6d786aad152acb45 Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Tue Dec 21 13:19:42 2021 +0000 patch 8.2.3867: implementation of some list functions too complicated Problem: Implementation of some list functions too complicated. Solution: Refactor do_sort_uniq(), f_count() and extend() (Yegappan Lakshmanan, closes #9378)
author Bram Moolenaar <Bram@vim.org>
date Tue, 21 Dec 2021 14:30:03 +0100
parents 3a473d09904c
children 51db19c22522
files src/list.c src/version.c
diffstat 2 files changed, 514 insertions(+), 400 deletions(-) [+]
line wrap: on
line diff
--- a/src/list.c
+++ b/src/list.c
@@ -1987,18 +1987,229 @@ item_compare2(const void *s1, const void
 }
 
 /*
+ * sort() List "l"
+ */
+    static void
+do_sort(list_T *l, sortinfo_T *info)
+{
+    long	len;
+    sortItem_T	*ptrs;
+    long	i = 0;
+    listitem_T	*li;
+
+    len = list_len(l);
+
+    // Make an array with each entry pointing to an item in the List.
+    ptrs = ALLOC_MULT(sortItem_T, len);
+    if (ptrs == NULL)
+	return;
+
+    // sort(): ptrs will be the list to sort
+    FOR_ALL_LIST_ITEMS(l, li)
+    {
+	ptrs[i].item = li;
+	ptrs[i].idx = i;
+	++i;
+    }
+
+    info->item_compare_func_err = FALSE;
+    info->item_compare_keep_zero = FALSE;
+    // test the compare function
+    if ((info->item_compare_func != NULL
+		|| info->item_compare_partial != NULL)
+	    && item_compare2((void *)&ptrs[0], (void *)&ptrs[1])
+	    == ITEM_COMPARE_FAIL)
+	emsg(_("E702: Sort compare function failed"));
+    else
+    {
+	// Sort the array with item pointers.
+	qsort((void *)ptrs, (size_t)len, sizeof(sortItem_T),
+		info->item_compare_func == NULL
+		&& info->item_compare_partial == NULL
+		? item_compare : item_compare2);
+
+	if (!info->item_compare_func_err)
+	{
+	    // Clear the List and append the items in sorted order.
+	    l->lv_first = l->lv_u.mat.lv_last
+		= l->lv_u.mat.lv_idx_item = NULL;
+	    l->lv_len = 0;
+	    for (i = 0; i < len; ++i)
+		list_append(l, ptrs[i].item);
+	}
+    }
+
+    vim_free(ptrs);
+}
+
+/*
+ * uniq() List "l"
+ */
+    static void
+do_uniq(list_T *l, sortinfo_T *info)
+{
+    long	len;
+    sortItem_T	*ptrs;
+    long	i = 0;
+    listitem_T	*li;
+    int	(*item_compare_func_ptr)(const void *, const void *);
+
+    len = list_len(l);
+
+    // Make an array with each entry pointing to an item in the List.
+    ptrs = ALLOC_MULT(sortItem_T, len);
+    if (ptrs == NULL)
+	return;
+
+    // f_uniq(): ptrs will be a stack of items to remove
+    info->item_compare_func_err = FALSE;
+    info->item_compare_keep_zero = TRUE;
+    item_compare_func_ptr = info->item_compare_func != NULL
+	|| info->item_compare_partial != NULL
+	? item_compare2 : item_compare;
+
+    for (li = l->lv_first; li != NULL && li->li_next != NULL;
+	    li = li->li_next)
+    {
+	if (item_compare_func_ptr((void *)&li, (void *)&li->li_next)
+		== 0)
+	    ptrs[i++].item = li;
+	if (info->item_compare_func_err)
+	{
+	    emsg(_("E882: Uniq compare function failed"));
+	    break;
+	}
+    }
+
+    if (!info->item_compare_func_err)
+    {
+	while (--i >= 0)
+	{
+	    li = ptrs[i].item->li_next;
+	    ptrs[i].item->li_next = li->li_next;
+	    if (li->li_next != NULL)
+		li->li_next->li_prev = ptrs[i].item;
+	    else
+		l->lv_u.mat.lv_last = ptrs[i].item;
+	    list_fix_watch(l, li);
+	    listitem_free(l, li);
+	    l->lv_len--;
+	}
+    }
+
+    vim_free(ptrs);
+}
+
+/*
+ * Parse the optional arguments to sort() and uniq() and return the values in
+ * 'info'.
+ */
+    static int
+parse_sort_uniq_args(typval_T *argvars, sortinfo_T *info)
+{
+    info->item_compare_ic = FALSE;
+    info->item_compare_lc = FALSE;
+    info->item_compare_numeric = FALSE;
+    info->item_compare_numbers = FALSE;
+#ifdef FEAT_FLOAT
+    info->item_compare_float = FALSE;
+#endif
+    info->item_compare_func = NULL;
+    info->item_compare_partial = NULL;
+    info->item_compare_selfdict = NULL;
+
+    if (argvars[1].v_type == VAR_UNKNOWN)
+	return OK;
+
+    // optional second argument: {func}
+    if (argvars[1].v_type == VAR_FUNC)
+	info->item_compare_func = argvars[1].vval.v_string;
+    else if (argvars[1].v_type == VAR_PARTIAL)
+	info->item_compare_partial = argvars[1].vval.v_partial;
+    else
+    {
+	int	    error = FALSE;
+	int	    nr = 0;
+
+	if (argvars[1].v_type == VAR_NUMBER)
+	{
+	    nr = tv_get_number_chk(&argvars[1], &error);
+	    if (error)
+		return FAIL;
+	    if (nr == 1)
+		info->item_compare_ic = TRUE;
+	}
+	if (nr != 1)
+	{
+	    if (argvars[1].v_type != VAR_NUMBER)
+		info->item_compare_func = tv_get_string(&argvars[1]);
+	    else if (nr != 0)
+	    {
+		emsg(_(e_invarg));
+		return FAIL;
+	    }
+	}
+	if (info->item_compare_func != NULL)
+	{
+	    if (*info->item_compare_func == NUL)
+	    {
+		// empty string means default sort
+		info->item_compare_func = NULL;
+	    }
+	    else if (STRCMP(info->item_compare_func, "n") == 0)
+	    {
+		info->item_compare_func = NULL;
+		info->item_compare_numeric = TRUE;
+	    }
+	    else if (STRCMP(info->item_compare_func, "N") == 0)
+	    {
+		info->item_compare_func = NULL;
+		info->item_compare_numbers = TRUE;
+	    }
+#ifdef FEAT_FLOAT
+	    else if (STRCMP(info->item_compare_func, "f") == 0)
+	    {
+		info->item_compare_func = NULL;
+		info->item_compare_float = TRUE;
+	    }
+#endif
+	    else if (STRCMP(info->item_compare_func, "i") == 0)
+	    {
+		info->item_compare_func = NULL;
+		info->item_compare_ic = TRUE;
+	    }
+	    else if (STRCMP(info->item_compare_func, "l") == 0)
+	    {
+		info->item_compare_func = NULL;
+		info->item_compare_lc = TRUE;
+	    }
+	}
+    }
+
+    if (argvars[2].v_type != VAR_UNKNOWN)
+    {
+	// optional third argument: {dict}
+	if (argvars[2].v_type != VAR_DICT)
+	{
+	    emsg(_(e_dictreq));
+	    return FAIL;
+	}
+	info->item_compare_selfdict = argvars[2].vval.v_dict;
+    }
+
+    return OK;
+}
+
+/*
  * "sort()" or "uniq()" function
  */
     static void
 do_sort_uniq(typval_T *argvars, typval_T *rettv, int sort)
 {
     list_T	*l;
-    listitem_T	*li;
-    sortItem_T	*ptrs;
     sortinfo_T	*old_sortinfo;
     sortinfo_T	info;
     long	len;
-    long	i;
 
     if (in_vim9script()
 	    && (check_for_list_arg(argvars, 0) == FAIL
@@ -2006,204 +2217,39 @@ do_sort_uniq(typval_T *argvars, typval_T
 		    && check_for_opt_dict_arg(argvars, 2) == FAIL)))
 	return;
 
+    if (argvars[0].v_type != VAR_LIST)
+    {
+	semsg(_(e_listarg), sort ? "sort()" : "uniq()");
+	return;
+    }
+
     // Pointer to current info struct used in compare function. Save and
     // restore the current one for nested calls.
     old_sortinfo = sortinfo;
     sortinfo = &info;
 
-    if (argvars[0].v_type != VAR_LIST)
-	semsg(_(e_listarg), sort ? "sort()" : "uniq()");
+    l = argvars[0].vval.v_list;
+    if (l != NULL && value_check_lock(l->lv_lock,
+		(char_u *)(sort ? N_("sort() argument") : N_("uniq() argument")),
+		TRUE))
+	goto theend;
+    rettv_list_set(rettv, l);
+    if (l == NULL)
+	goto theend;
+    CHECK_LIST_MATERIALIZE(l);
+
+    len = list_len(l);
+    if (len <= 1)
+	goto theend;	// short list sorts pretty quickly
+
+    if (parse_sort_uniq_args(argvars, &info) == FAIL)
+	goto theend;
+
+    if (sort)
+	do_sort(l, &info);
     else
-    {
-	l = argvars[0].vval.v_list;
-	if (l != NULL && value_check_lock(l->lv_lock,
-	     (char_u *)(sort ? N_("sort() argument") : N_("uniq() argument")),
-									TRUE))
-	    goto theend;
-	rettv_list_set(rettv, l);
-	if (l == NULL)
-	    goto theend;
-	CHECK_LIST_MATERIALIZE(l);
-
-	len = list_len(l);
-	if (len <= 1)
-	    goto theend;	// short list sorts pretty quickly
-
-	info.item_compare_ic = FALSE;
-	info.item_compare_lc = FALSE;
-	info.item_compare_numeric = FALSE;
-	info.item_compare_numbers = FALSE;
-#ifdef FEAT_FLOAT
-	info.item_compare_float = FALSE;
-#endif
-	info.item_compare_func = NULL;
-	info.item_compare_partial = NULL;
-	info.item_compare_selfdict = NULL;
-	if (argvars[1].v_type != VAR_UNKNOWN)
-	{
-	    // optional second argument: {func}
-	    if (argvars[1].v_type == VAR_FUNC)
-		info.item_compare_func = argvars[1].vval.v_string;
-	    else if (argvars[1].v_type == VAR_PARTIAL)
-		info.item_compare_partial = argvars[1].vval.v_partial;
-	    else
-	    {
-		int	    error = FALSE;
-		int	    nr = 0;
-
-		if (argvars[1].v_type == VAR_NUMBER)
-		{
-		    nr = tv_get_number_chk(&argvars[1], &error);
-		    if (error)
-			goto theend;	// type error; errmsg already given
-		    if (nr == 1)
-			info.item_compare_ic = TRUE;
-		}
-		if (nr != 1)
-		{
-		    if (argvars[1].v_type != VAR_NUMBER)
-			info.item_compare_func = tv_get_string(&argvars[1]);
-		    else if (nr != 0)
-		    {
-			emsg(_(e_invarg));
-			goto theend;
-		    }
-		}
-		if (info.item_compare_func != NULL)
-		{
-		    if (*info.item_compare_func == NUL)
-		    {
-			// empty string means default sort
-			info.item_compare_func = NULL;
-		    }
-		    else if (STRCMP(info.item_compare_func, "n") == 0)
-		    {
-			info.item_compare_func = NULL;
-			info.item_compare_numeric = TRUE;
-		    }
-		    else if (STRCMP(info.item_compare_func, "N") == 0)
-		    {
-			info.item_compare_func = NULL;
-			info.item_compare_numbers = TRUE;
-		    }
-#ifdef FEAT_FLOAT
-		    else if (STRCMP(info.item_compare_func, "f") == 0)
-		    {
-			info.item_compare_func = NULL;
-			info.item_compare_float = TRUE;
-		    }
-#endif
-		    else if (STRCMP(info.item_compare_func, "i") == 0)
-		    {
-			info.item_compare_func = NULL;
-			info.item_compare_ic = TRUE;
-		    }
-		    else if (STRCMP(info.item_compare_func, "l") == 0)
-		    {
-			info.item_compare_func = NULL;
-			info.item_compare_lc = TRUE;
-		    }
-		}
-	    }
-
-	    if (argvars[2].v_type != VAR_UNKNOWN)
-	    {
-		// optional third argument: {dict}
-		if (argvars[2].v_type != VAR_DICT)
-		{
-		    emsg(_(e_dictreq));
-		    goto theend;
-		}
-		info.item_compare_selfdict = argvars[2].vval.v_dict;
-	    }
-	}
-
-	// Make an array with each entry pointing to an item in the List.
-	ptrs = ALLOC_MULT(sortItem_T, len);
-	if (ptrs == NULL)
-	    goto theend;
-
-	i = 0;
-	if (sort)
-	{
-	    // sort(): ptrs will be the list to sort
-	    FOR_ALL_LIST_ITEMS(l, li)
-	    {
-		ptrs[i].item = li;
-		ptrs[i].idx = i;
-		++i;
-	    }
-
-	    info.item_compare_func_err = FALSE;
-	    info.item_compare_keep_zero = FALSE;
-	    // test the compare function
-	    if ((info.item_compare_func != NULL
-					 || info.item_compare_partial != NULL)
-		    && item_compare2((void *)&ptrs[0], (void *)&ptrs[1])
-							 == ITEM_COMPARE_FAIL)
-		emsg(_("E702: Sort compare function failed"));
-	    else
-	    {
-		// Sort the array with item pointers.
-		qsort((void *)ptrs, (size_t)len, sizeof(sortItem_T),
-		    info.item_compare_func == NULL
-					  && info.item_compare_partial == NULL
-					       ? item_compare : item_compare2);
-
-		if (!info.item_compare_func_err)
-		{
-		    // Clear the List and append the items in sorted order.
-		    l->lv_first = l->lv_u.mat.lv_last
-					      = l->lv_u.mat.lv_idx_item = NULL;
-		    l->lv_len = 0;
-		    for (i = 0; i < len; ++i)
-			list_append(l, ptrs[i].item);
-		}
-	    }
-	}
-	else
-	{
-	    int	(*item_compare_func_ptr)(const void *, const void *);
-
-	    // f_uniq(): ptrs will be a stack of items to remove
-	    info.item_compare_func_err = FALSE;
-	    info.item_compare_keep_zero = TRUE;
-	    item_compare_func_ptr = info.item_compare_func != NULL
-					  || info.item_compare_partial != NULL
-					       ? item_compare2 : item_compare;
-
-	    for (li = l->lv_first; li != NULL && li->li_next != NULL;
-							     li = li->li_next)
-	    {
-		if (item_compare_func_ptr((void *)&li, (void *)&li->li_next)
-									 == 0)
-		    ptrs[i++].item = li;
-		if (info.item_compare_func_err)
-		{
-		    emsg(_("E882: Uniq compare function failed"));
-		    break;
-		}
-	    }
-
-	    if (!info.item_compare_func_err)
-	    {
-		while (--i >= 0)
-		{
-		    li = ptrs[i].item->li_next;
-		    ptrs[i].item->li_next = li->li_next;
-		    if (li->li_next != NULL)
-			li->li_next->li_prev = ptrs[i].item;
-		    else
-			l->lv_u.mat.lv_last = ptrs[i].item;
-		    list_fix_watch(l, li);
-		    listitem_free(l, li);
-		    l->lv_len--;
-		}
-	    }
-	}
-
-	vim_free(ptrs);
-    }
+	do_uniq(l, &info);
+
 theend:
     sortinfo = old_sortinfo;
 }
@@ -2835,6 +2881,105 @@ f_add(typval_T *argvars, typval_T *rettv
 }
 
 /*
+ * 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)
+{
+    long	n = 0;
+    listitem_T	*li;
+
+    if (l == NULL)
+	return 0;
+
+    CHECK_LIST_MATERIALIZE(l);
+
+    if (list_len(l) == 0)
+	return 0;
+
+    li = list_find(l, idx);
+    if (li == NULL)
+    {
+	semsg(_(e_listidx), idx);
+	return 0;
+    }
+
+    for ( ; li != NULL; li = li->li_next)
+	if (tv_equal(&li->li_tv, needle, ic, FALSE))
+	    ++n;
+
+    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
  */
     void
@@ -2854,95 +2999,26 @@ f_count(typval_T *argvars, typval_T *ret
     if (argvars[2].v_type != VAR_UNKNOWN)
 	ic = (int)tv_get_bool_chk(&argvars[2], &error);
 
-    if (argvars[0].v_type == VAR_STRING)
-    {
-	char_u *expr = tv_get_string_chk(&argvars[1]);
-	char_u *p = argvars[0].vval.v_string;
-	char_u *next;
-
-	if (!error && expr != NULL && *expr != NUL && p != NULL)
-	{
-	    if (ic)
-	    {
-		size_t len = STRLEN(expr);
-
-		while (*p != NUL)
-		{
-		    if (MB_STRNICMP(p, expr, len) == 0)
-		    {
-			++n;
-			p += len;
-		    }
-		    else
-			MB_PTR_ADV(p);
-		}
-	    }
-	    else
-		while ((next = (char_u *)strstr((char *)p, (char *)expr))
-								       != NULL)
-		{
-		    ++n;
-		    p = next + STRLEN(expr);
-		}
-	}
-
-    }
-    else if (argvars[0].v_type == VAR_LIST)
+    if (!error && argvars[0].v_type == VAR_STRING)
+	n = count_string(argvars[0].vval.v_string,
+					tv_get_string_chk(&argvars[1]), ic);
+    else if (!error && argvars[0].v_type == VAR_LIST)
     {
-	listitem_T	*li;
-	list_T		*l;
-	long		idx;
-
-	if ((l = argvars[0].vval.v_list) != NULL)
-	{
-	    CHECK_LIST_MATERIALIZE(l);
-	    li = l->lv_first;
-	    if (argvars[2].v_type != VAR_UNKNOWN)
-	    {
-		if (argvars[3].v_type != VAR_UNKNOWN)
-		{
-		    idx = (long)tv_get_number_chk(&argvars[3], &error);
-		    if (!error)
-		    {
-			li = list_find(l, idx);
-			if (li == NULL)
-			    semsg(_(e_listidx), idx);
-		    }
-		}
-		if (error)
-		    li = NULL;
-	    }
-
-	    for ( ; li != NULL; li = li->li_next)
-		if (tv_equal(&li->li_tv, &argvars[1], ic, FALSE))
-		    ++n;
-	}
+	long idx = 0;
+
+	if (argvars[2].v_type != VAR_UNKNOWN
+		&& 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);
     }
-    else if (argvars[0].v_type == VAR_DICT)
+    else if (!error && argvars[0].v_type == VAR_DICT)
     {
-	int		todo;
-	dict_T		*d;
-	hashitem_T	*hi;
-
-	if ((d = argvars[0].vval.v_dict) != NULL)
-	{
-	    if (argvars[2].v_type != VAR_UNKNOWN)
-	    {
-		if (argvars[3].v_type != VAR_UNKNOWN)
-		    emsg(_(e_invarg));
-	    }
-
-	    todo = error ? 0 : (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, &argvars[1], ic, FALSE))
-			++n;
-		}
-	    }
-	}
+	if (argvars[2].v_type != VAR_UNKNOWN
+		&& argvars[3].v_type != VAR_UNKNOWN)
+	    emsg(_(e_invarg));
+	else
+	    n = count_dict(argvars[0].vval.v_dict, &argvars[1], ic);
     }
     else
 	semsg(_(e_listdictarg), "count()");
@@ -2950,6 +3026,148 @@ f_count(typval_T *argvars, typval_T *ret
 }
 
 /*
+ * extend() a List. Append List argvars[1] to List argvars[0] before index
+ * argvars[3] and return the resulting list in "rettv".  "is_new" is TRUE for
+ * extendnew().
+ */
+    static void
+extend_list(
+	typval_T	*argvars,
+	type_T		*type,
+	char		*func_name,
+	char_u		*arg_errmsg,
+	int		is_new,
+	typval_T	*rettv)
+{
+    list_T	*l1, *l2;
+    listitem_T	*item;
+    long	before;
+    int		error = FALSE;
+
+    l1 = argvars[0].vval.v_list;
+    if (l1 == NULL)
+    {
+	emsg(_(e_cannot_extend_null_list));
+	return;
+    }
+    l2 = argvars[1].vval.v_list;
+    if ((is_new || !value_check_lock(l1->lv_lock, arg_errmsg, TRUE))
+	    && l2 != NULL)
+    {
+	if (is_new)
+	{
+	    l1 = list_copy(l1, FALSE, get_copyID());
+	    if (l1 == NULL)
+		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 == l1->lv_len)
+		item = NULL;
+	    else
+	    {
+		item = list_find(l1, before);
+		if (item == NULL)
+		{
+		    semsg(_(e_listidx), before);
+		    return;
+		}
+	    }
+	}
+	else
+	    item = NULL;
+	if (type != NULL && check_typval_arg_type(
+		    type, &argvars[1], func_name, 2) == FAIL)
+	    return;
+	list_extend(l1, l2, item);
+
+	if (is_new)
+	{
+	    rettv->v_type = VAR_LIST;
+	    rettv->vval.v_list = l1;
+	    rettv->v_lock = FALSE;
+	}
+	else
+	    copy_tv(&argvars[0], rettv);
+    }
+}
+
+/*
+ * 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
@@ -2967,126 +3185,12 @@ extend(typval_T *argvars, typval_T *rett
     }
 
     if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST)
-    {
-	list_T		*l1, *l2;
-	listitem_T	*item;
-	long		before;
-	int		error = FALSE;
-
-	l1 = argvars[0].vval.v_list;
-	if (l1 == NULL)
-	{
-	    emsg(_(e_cannot_extend_null_list));
-	    goto theend;
-	}
-	l2 = argvars[1].vval.v_list;
-	if ((is_new || !value_check_lock(l1->lv_lock, arg_errmsg, TRUE))
-								 && l2 != NULL)
-	{
-	    if (is_new)
-	    {
-		l1 = list_copy(l1, FALSE, get_copyID());
-		if (l1 == NULL)
-		    goto theend;
-	    }
-
-	    if (argvars[2].v_type != VAR_UNKNOWN)
-	    {
-		before = (long)tv_get_number_chk(&argvars[2], &error);
-		if (error)
-		    goto theend;	// type error; errmsg already given
-
-		if (before == l1->lv_len)
-		    item = NULL;
-		else
-		{
-		    item = list_find(l1, before);
-		    if (item == NULL)
-		    {
-			semsg(_(e_listidx), before);
-			goto theend;
-		    }
-		}
-	    }
-	    else
-		item = NULL;
-	    if (type != NULL && check_typval_arg_type(
-				      type, &argvars[1], func_name, 2) == FAIL)
-		goto theend;
-	    list_extend(l1, l2, item);
-
-	    if (is_new)
-	    {
-		rettv->v_type = VAR_LIST;
-		rettv->vval.v_list = l1;
-		rettv->v_lock = FALSE;
-	    }
-	    else
-		copy_tv(&argvars[0], rettv);
-	}
-    }
+	extend_list(argvars, type, func_name, arg_errmsg, is_new, rettv);
     else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT)
-    {
-	dict_T	*d1, *d2;
-	char_u	*action;
-	int	i;
-
-	d1 = argvars[0].vval.v_dict;
-	if (d1 == NULL)
-	{
-	    emsg(_(e_cannot_extend_null_dict));
-	    goto theend;
-	}
-	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)
-		    goto theend;
-	    }
-
-	    // 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)
-		    goto theend;	// type error; errmsg already given
-		for (i = 0; i < 3; ++i)
-		    if (STRCMP(action, av[i]) == 0)
-			break;
-		if (i == 3)
-		{
-		    semsg(_(e_invarg2), action);
-		    goto theend;
-		}
-	    }
-	    else
-		action = (char_u *)"force";
-
-	    if (type != NULL && check_typval_arg_type(type, &argvars[1],
-							 func_name, 2) == FAIL)
-		goto theend;
-	    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_dict(argvars, type, func_name, arg_errmsg, is_new, rettv);
     else
 	semsg(_(e_listdictarg), func_name);
 
-theend:
     if (type != NULL)
 	clear_type_list(&type_list);
 }
@@ -3308,7 +3412,9 @@ f_reverse(typval_T *argvars, typval_T *r
 }
 
 /*
- * reduce() on a List
+ * reduce() List 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_list(
@@ -3365,7 +3471,9 @@ reduce_list(
 }
 
 /*
- * reduce() on a String
+ * 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(
@@ -3414,7 +3522,9 @@ reduce_string(
 }
 
 /*
- * reduce() on a Blob
+ * 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(
@@ -3470,6 +3580,8 @@ reduce_blob(
 
 /*
  * "reduce(list, { accumulator, element -> value } [, initial])" function
+ * "reduce(blob, { accumulator, element -> value } [, initial])"
+ * "reduce(string, { accumulator, element -> value } [, initial])"
  */
     void
 f_reduce(typval_T *argvars, typval_T *rettv)
--- 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 */
 /**/
+    3867,
+/**/
     3866,
 /**/
     3865,