]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.0027: Vim is missing a foreach() func v9.1.0027
authorErnie Rael <errael@raelity.com>
Sat, 13 Jan 2024 10:47:33 +0000 (11:47 +0100)
committerChristian Brabandt <cb@256bit.org>
Sat, 13 Jan 2024 10:47:33 +0000 (11:47 +0100)
Problem:  Vim is missing a foreach() func
Solution: Implement foreach({expr1}, {expr2}) function,
          which applies {expr2} for each item in {expr1}
          without changing it (Ernie Rael)

closes: #12166

Signed-off-by: Ernie Rael <errael@raelity.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
12 files changed:
runtime/doc/builtin.txt
runtime/doc/tags
runtime/doc/usr_41.txt
src/blob.c
src/dict.c
src/evalfunc.c
src/list.c
src/proto/list.pro
src/strings.c
src/structs.h
src/testdir/test_filter_map.vim
src/version.c

index 9ac79c611b86275b7738dfe470d3bd5e22ebea0a..4bbf5066fa344d62f9040f858e9af24cf8a0e773 100644 (file)
@@ -1,4 +1,4 @@
-*builtin.txt*  For Vim version 9.1.  Last change: 2024 Jan 05
+*builtin.txt*  For Vim version 9.1.  Last change: 2024 Jan 13
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -198,6 +198,8 @@ foldclosedend({lnum})               Number  last line of fold at {lnum} if closed
 foldlevel({lnum})              Number  fold level at {lnum}
 foldtext()                     String  line displayed for closed fold
 foldtextresult({lnum})         String  text for closed fold at {lnum}
+foreach({expr1}, {expr2})      List/Dict/Blob/String
+                                       for each item in {expr1} call {expr2}
 foreground()                   Number  bring the Vim window to the foreground
 fullcommand({name} [, {vim9}]) String  get full command from {name}
 funcref({name} [, {arglist}] [, {dict}])
@@ -2995,6 +2997,45 @@ foldtextresult({lnum})                                   *foldtextresult()*
 
                Can also be used as a |method|: >
                        GetLnum()->foldtextresult()
+
+foreach({expr1}, {expr2})                                      *foreach()*
+               {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
+               For each item in {expr1} execute {expr2}. {expr1} is not
+               modified; its values may be, as with |:lockvar| 1. *E741*
+               See |map()| and |filter()| to modify {expr1}.
+
+               {expr2} must be a |string| or |Funcref|.
+
+               If {expr2} is a |string|, inside {expr2} |v:val| has the value
+               of the current item.  For a |Dictionary| |v:key| has the key
+               of the current item and for a |List| |v:key| has the index of
+               the current item.  For a |Blob| |v:key| has the index of the
+               current byte. For a |String| |v:key| has the index of the
+               current character.
+               Examples: >
+                       call foreach(mylist, 'used[v:val] = true')
+<              This records the items that are in the {expr1} list.
+
+               Note that {expr2} is the result of expression and is then used
+               as a command.  Often it is good to use a |literal-string| to
+               avoid having to double backslashes.
+
+               If {expr2} is a |Funcref| it must take two arguments:
+                       1. the key or the index of the current item.
+                       2. the value of the current item.
+               With a legacy script lambda you don't get an error if it only
+               accepts one argument, but with a Vim9 lambda you get "E1106:
+               One argument too many", the number of arguments must match.
+               If the function returns a value, it is ignored.
+
+               Returns {expr1} in all cases.
+               When an error is encountered while executing {expr2} no
+               further items in {expr1} are processed.
+               When {expr2} is a Funcref errors inside a function are ignored,
+               unless it was defined with the "abort" flag.
+
+               Can also be used as a |method|: >
+                       mylist->foreach(expr2)
 <
                                                        *foreground()*
 foreground()   Move the Vim window to the foreground.  Useful when sent from
index b3691bb93f2d1552b97132ae1232ce64a59e438f..40b90c085007171e8f478209089841912a1317bf 100644 (file)
@@ -5167,6 +5167,7 @@ E738      eval.txt        /*E738*
 E739   builtin.txt     /*E739*
 E74    message.txt     /*E74*
 E740   userfunc.txt    /*E740*
+E741   builtin.txt     /*E741*
 E741   eval.txt        /*E741*
 E742   userfunc.txt    /*E742*
 E743   eval.txt        /*E743*
@@ -7148,6 +7149,7 @@ foldtextresult()  builtin.txt     /*foldtextresult()*
 font-sizes     gui_x11.txt     /*font-sizes*
 fontset        mbyte.txt       /*fontset*
 forced-motion  motion.txt      /*forced-motion*
+foreach()      builtin.txt     /*foreach()*
 foreground()   builtin.txt     /*foreground()*
 fork   os_unix.txt     /*fork*
 form.vim       syntax.txt      /*form.vim*
index 2286d48516a05e7408c774a8f073ba9f3225065e..0990a06513da7958367fbf4d1b432fc2f00b44c4 100644 (file)
@@ -1,4 +1,4 @@
-*usr_41.txt*   For Vim version 9.1.  Last change: 2023 May 06
+*usr_41.txt*   For Vim version 9.1.  Last change: 2024 Jan 13
 
                     VIM USER MANUAL - by Bram Moolenaar
 
@@ -798,6 +798,7 @@ List manipulation:                                  *list-functions*
        filter()                remove selected items from a List
        map()                   change each List item
        mapnew()                make a new List with changed items
+       foreach()               apply function to List items
        reduce()                reduce a List to a value
        slice()                 take a slice of a List
        sort()                  sort a List
@@ -829,6 +830,7 @@ Dictionary manipulation:                            *dict-functions*
        filter()                remove selected entries from a Dictionary
        map()                   change each Dictionary entry
        mapnew()                make a new Dictionary with changed items
+       foreach()               apply function to Dictionary items
        keys()                  get List of Dictionary keys
        values()                get List of Dictionary values
        items()                 get List of Dictionary key-value pairs
index c5d7eaed64721ffa405153d6af6e23f773fe5c1f..9cdd504b6e213ccad3608ce055963a4a3ee97bca 100644 (file)
@@ -641,25 +641,28 @@ blob_filter_map(
        if (filter_map_one(&tv, expr, filtermap, fc, &newtv, &rem) == FAIL
                || did_emsg)
            break;
-       if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL)
+       if (filtermap != FILTERMAP_FOREACH)
        {
-           clear_tv(&newtv);
-           emsg(_(e_invalid_operation_for_blob));
-           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;
+           if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL)
+           {
+               clear_tv(&newtv);
+               emsg(_(e_invalid_operation_for_blob));
+               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;
+               mch_memmove(p + i, p + i + 1,
+                           (size_t)b->bv_ga.ga_len - i - 1);
+               --b->bv_ga.ga_len;
+               --i;
+           }
        }
        ++idx;
     }
index cb22b42c91086e82f7668889e914b80728cc6385..508d00cdf7c402afb1e4e2f16f0c7374c2f23528 100644 (file)
@@ -1329,8 +1329,8 @@ dict_extend_func(
 }
 
 /*
- * Implementation of map() and filter() for a Dict.  Apply "expr" to every
- * item in Dict "d" and return the result in "rettv".
+ * Implementation of map(), filter(), foreach() for a Dict.  Apply "expr" to
+ * every item in Dict "d" and return the result in "rettv".
  */
     void
 dict_filter_map(
@@ -1392,7 +1392,6 @@ dict_filter_map(
                            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, fc, &newtv, &rem);
            clear_tv(get_vim_var_tv(VV_KEY));
            if (r == FAIL || did_emsg)
index e37b3a412a8a56d3c34cd9b8965bf1bf019b9ecc..9b3bdf756d63b043e0af0f936b952600de683427 100644 (file)
@@ -607,10 +607,11 @@ arg_list_or_dict_or_blob_or_string_mod(
 }
 
 /*
- * Check second argument of map() or filter().
+ * Check second argument of map(), filter(), foreach().
  */
     static int
-check_map_filter_arg2(type_T *type, argcontext_T *context, int is_map)
+check_map_filter_arg2(type_T *type, argcontext_T *context,
+                                                       filtermap_T filtermap)
 {
     type_T *expected_member = NULL;
     type_T *(args[2]);
@@ -663,12 +664,14 @@ check_map_filter_arg2(type_T *type, argcontext_T *context, int is_map)
     {
        where_T where = WHERE_INIT;
 
-       if (is_map)
+       if (filtermap == FILTERMAP_MAP)
            t_func_exp.tt_member = expected_member == NULL
                                        || type_any_or_unknown(type->tt_member)
                                ? &t_any : expected_member;
-       else
+       else if (filtermap == FILTERMAP_FILTER)
            t_func_exp.tt_member = &t_bool;
+       else // filtermap == FILTERMAP_FOREACH
+           t_func_exp.tt_member = &t_unknown;
        if (args[0] == NULL)
            args[0] = &t_unknown;
        if (type->tt_argcount == -1)
@@ -693,7 +696,7 @@ arg_filter_func(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
        return OK;
 
     if (type->tt_type == VAR_FUNC)
-       return check_map_filter_arg2(type, context, FALSE);
+       return check_map_filter_arg2(type, context, FILTERMAP_FILTER);
     semsg(_(e_string_or_function_required_for_argument_nr), 2);
     return FAIL;
 }
@@ -710,7 +713,24 @@ arg_map_func(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
        return OK;
 
     if (type->tt_type == VAR_FUNC)
-       return check_map_filter_arg2(type, context, TRUE);
+       return check_map_filter_arg2(type, context, FILTERMAP_MAP);
+    semsg(_(e_string_or_function_required_for_argument_nr), 2);
+    return FAIL;
+}
+
+/*
+ * Check second argument of foreach(), the function.
+ */
+    static int
+arg_foreach_func(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
+{
+    if (type->tt_type == VAR_STRING
+           || type->tt_type == VAR_PARTIAL
+           || type_any_or_unknown(type))
+       return OK;
+
+    if (type->tt_type == VAR_FUNC)
+       return check_map_filter_arg2(type, context, FILTERMAP_FOREACH);
     semsg(_(e_string_or_function_required_for_argument_nr), 2);
     return FAIL;
 }
@@ -1173,6 +1193,7 @@ static argcheck_T arg1_len[] = {arg_len1};
 static argcheck_T arg3_libcall[] = {arg_string, arg_string, arg_string_or_nr};
 static argcheck_T arg14_maparg[] = {arg_string, arg_string, arg_bool, arg_bool};
 static argcheck_T arg2_filter[] = {arg_list_or_dict_or_blob_or_string_mod, arg_filter_func};
+static argcheck_T arg2_foreach[] = {arg_list_or_dict_or_blob_or_string, arg_foreach_func};
 static argcheck_T arg2_instanceof[] = {arg_object, varargs_class, NULL };
 static argcheck_T arg2_map[] = {arg_list_or_dict_or_blob_or_string_mod, arg_map_func};
 static argcheck_T arg2_mapnew[] = {arg_list_or_dict_or_blob_or_string, arg_any};
@@ -2013,6 +2034,8 @@ static funcentry_T global_functions[] =
                        ret_string,         f_foldtext},
     {"foldtextresult", 1, 1, FEARG_1,      arg1_lnum,
                        ret_string,         f_foldtextresult},
+    {"foreach",                2, 2, FEARG_1,      arg2_foreach,
+                       ret_first_arg,      f_foreach},
     {"foreground",     0, 0, 0,            NULL,
                        ret_void,           f_foreground},
     {"fullcommand",    1, 2, FEARG_1,      arg2_string_bool,
index b50cb03c98560b1546b68ac92110c7a55f52370d..e9f1ae320678d017d92eed7dbd88a7ff92e76981 100644 (file)
@@ -2325,7 +2325,7 @@ f_uniq(typval_T *argvars, typval_T *rettv)
 }
 
 /*
- * Handle one item for map() and filter().
+ * Handle one item for map(), filter(), foreach().
  * Sets v:val to "tv".  Caller must set v:key.
  */
     int
@@ -2341,6 +2341,17 @@ filter_map_one(
     int                retval = FAIL;
 
     copy_tv(tv, get_vim_var_tv(VV_VAL));
+
+    newtv->v_type = VAR_UNKNOWN;
+    if (filtermap == FILTERMAP_FOREACH && expr->v_type == VAR_STRING)
+    {
+       // foreach() is not limited to an expression
+       do_cmdline_cmd(expr->vval.v_string);
+       if (!did_emsg)
+           retval = OK;
+       goto theend;
+    }
+
     argv[0] = *get_vim_var_tv(VV_KEY);
     argv[1] = *get_vim_var_tv(VV_VAL);
     if (eval_expr_typval(expr, FALSE, argv, 2, fc, newtv) == FAIL)
@@ -2360,6 +2371,8 @@ filter_map_one(
        if (error)
            goto theend;
     }
+    else if (filtermap == FILTERMAP_FOREACH)
+       clear_tv(newtv);
     retval = OK;
 theend:
     clear_tv(get_vim_var_tv(VV_VAL));
@@ -2367,8 +2380,8 @@ theend:
 }
 
 /*
- * Implementation of map() and filter() for a List.  Apply "expr" to every item
- * in List "l" and return the result in "rettv".
+ * Implementation of map(), filter(), foreach() for a List.  Apply "expr" to
+ * every item in List "l" and return the result in "rettv".
  */
     static void
 list_filter_map(
@@ -2421,7 +2434,8 @@ list_filter_map(
        int             stride = l->lv_u.nonmat.lv_stride;
 
        // List from range(): loop over the numbers
-       if (filtermap != FILTERMAP_MAPNEW)
+       // NOTE: foreach() returns the range_list_item
+       if (filtermap != FILTERMAP_MAPNEW && filtermap != FILTERMAP_FOREACH)
        {
            l->lv_first = NULL;
            l->lv_u.mat.lv_last = NULL;
@@ -2444,27 +2458,30 @@ list_filter_map(
                clear_tv(&newtv);
                break;
            }
-           if (filtermap != FILTERMAP_FILTER)
+           if (filtermap != FILTERMAP_FOREACH)
            {
-               if (filtermap == FILTERMAP_MAP && argtype != NULL
+               if (filtermap != FILTERMAP_FILTER)
+               {
+                   if (filtermap == FILTERMAP_MAP && argtype != NULL
                        && check_typval_arg_type(
-                           argtype->tt_member, &newtv,
-                           func_name, 0) == FAIL)
+                                                argtype->tt_member, &newtv,
+                                                func_name, 0) == FAIL)
+                   {
+                       clear_tv(&newtv);
+                       break;
+                   }
+                   // map(), mapnew(): always append the new value to the
+                   // list
+                   if (list_append_tv_move(filtermap == FILTERMAP_MAP
+                                           ? l : l_ret, &newtv) == FAIL)
+                       break;
+               }
+               else if (!rem)
                {
-                   clear_tv(&newtv);
-                   break;
+                   // filter(): append the list item value when not rem
+                   if (list_append_tv_move(l, &tv) == FAIL)
+                       break;
                }
-               // map(), mapnew(): always append the new value to the
-               // list
-               if (list_append_tv_move(filtermap == FILTERMAP_MAP
-                           ? l : l_ret, &newtv) == FAIL)
-                   break;
-           }
-           else if (!rem)
-           {
-               // filter(): append the list item value when not rem
-               if (list_append_tv_move(l, &tv) == FAIL)
-                   break;
            }
 
            val += stride;
@@ -2508,7 +2525,7 @@ list_filter_map(
                    break;
            }
            else if (filtermap == FILTERMAP_FILTER && rem)
-               listitem_remove(l, li);
+                   listitem_remove(l, li);
            ++idx;
        }
     }
@@ -2519,7 +2536,7 @@ list_filter_map(
 }
 
 /*
- * Implementation of map() and filter().
+ * Implementation of map(), filter() and foreach().
  */
     static void
 filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
@@ -2527,16 +2544,19 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
     typval_T   *expr;
     char       *func_name = filtermap == FILTERMAP_MAP ? "map()"
                                  : filtermap == FILTERMAP_MAPNEW ? "mapnew()"
-                                 : "filter()";
+                                 : filtermap == FILTERMAP_FILTER ? "filter()"
+                                 : "foreach()";
     char_u     *arg_errmsg = (char_u *)(filtermap == FILTERMAP_MAP
                                                         ? N_("map() argument")
                                       : filtermap == FILTERMAP_MAPNEW
                                                      ? N_("mapnew() argument")
-                                                   : N_("filter() argument"));
+                                      : filtermap == FILTERMAP_FILTER
+                                                     ? N_("filter() argument")
+                                                  : N_("foreach() argument"));
     int                save_did_emsg;
     type_T     *type = NULL;
 
-    // map() and filter() return the first argument, also on failure.
+    // map(), filter(), foreach() return the first argument, also on failure.
     if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING)
        copy_tv(&argvars[0], rettv);
 
@@ -2629,6 +2649,15 @@ f_mapnew(typval_T *argvars, typval_T *rettv)
     filter_map(argvars, rettv, FILTERMAP_MAPNEW);
 }
 
+/*
+ * "foreach()" function
+ */
+    void
+f_foreach(typval_T *argvars, typval_T *rettv)
+{
+    filter_map(argvars, rettv, FILTERMAP_FOREACH);
+}
+
 /*
  * "add(list, item)" function
  */
index 5abe03c096092dfc5a1ec3c54bbbb4e1155f48e9..0b58c692a4a5a551d3545e84da3383ce26cead8e 100644 (file)
@@ -56,6 +56,7 @@ int filter_map_one(typval_T *tv, typval_T *expr, filtermap_T filtermap, funccall
 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);
+void f_foreach(typval_T *argvars, typval_T *rettv);
 void f_add(typval_T *argvars, typval_T *rettv);
 void f_count(typval_T *argvars, typval_T *rettv);
 void f_extend(typval_T *argvars, typval_T *rettv);
index 0aeea4bce9f14f7172d8ea29092f00d83e494c15..b38c3d0bd7ef89bd1a82a57bf2cde89a200198b7 100644 (file)
@@ -942,7 +942,6 @@ string_filter_map(
            break;
        len = (int)STRLEN(tv.vval.v_string);
 
-       newtv.v_type = VAR_UNKNOWN;
        set_vim_var_nr(VV_KEY, idx);
        if (filter_map_one(&tv, expr, filtermap, fc, &newtv, &rem) == FAIL
                || did_emsg)
@@ -951,7 +950,7 @@ string_filter_map(
            clear_tv(&tv);
            break;
        }
-       else if (filtermap != FILTERMAP_FILTER)
+       if (filtermap == FILTERMAP_MAP || filtermap == FILTERMAP_MAPNEW)
        {
            if (newtv.v_type != VAR_STRING)
            {
@@ -963,7 +962,7 @@ string_filter_map(
            else
                ga_concat(&ga, newtv.vval.v_string);
        }
-       else if (!rem)
+       else if (filtermap == FILTERMAP_FOREACH || !rem)
            ga_concat(&ga, tv.vval.v_string);
 
        clear_tv(&newtv);
index 6d77deb45462836295814d84c9f547cd37da3a7d..85f440d35d64c1f0f80972422ec03838be66056d 100644 (file)
@@ -4879,11 +4879,12 @@ typedef struct {
     hashtab_T  sve_hashtab;
 } save_v_event_T;
 
-// Enum used by filter(), map() and mapnew()
+// Enum used by filter(), map(), mapnew() and foreach()
 typedef enum {
     FILTERMAP_FILTER,
     FILTERMAP_MAP,
-    FILTERMAP_MAPNEW
+    FILTERMAP_MAPNEW,
+    FILTERMAP_FOREACH
 } filtermap_T;
 
 // Structure used by switch_win() to pass values to restore_win()
index 6a07bec21517df999660332a3aed91a55a4669f8..37ebe847b1aae626925f039edb4bfa1f799a2239 100644 (file)
@@ -14,6 +14,18 @@ func Test_filter_map_list_expr_string()
   call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2'))
   call assert_equal([9, 9, 9, 9], map([1, 2, 3, 4], 9))
   call assert_equal([7, 7, 7], map([1, 2, 3], ' 7 '))
+
+  " foreach()
+  let list01 = [1, 2, 3, 4]
+  let list02 = []
+  call assert_equal([1, 2, 3, 4], foreach(list01, 'call add(list02, v:val * 2)'))
+  call assert_equal([2, 4, 6, 8], list02)
+  let list02 = []
+  call assert_equal([1, 2, 3, 4], foreach(list01, 'call add(list02, v:key * 2)'))
+  call assert_equal([0, 2, 4, 6], list02)
+  let list02 = []
+  call assert_equal([1, 2, 3, 4], foreach(list01, 'call add(list02, 9)'))
+  call assert_equal([9, 9, 9, 9], list02)
 endfunc
 
 " dict with expression string
@@ -29,6 +41,14 @@ func Test_filter_map_dict_expr_string()
   call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), 'v:val * 2'))
   call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]'))
   call assert_equal({"foo": 9, "bar": 9, "baz": 9}, map(copy(dict), 9))
+
+  " foreach()
+  let dict01 = {}
+  call assert_equal(dict, foreach(copy(dict), 'let dict01[v:key] = v:val * 2'))
+  call assert_equal({"foo": 2, "bar": 4, "baz": 6}, dict01)
+  let dict01 = {}
+  call assert_equal(dict, foreach(copy(dict), 'let dict01[v:key] = v:key[0]'))
+  call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, dict01)
 endfunc
 
 " list with funcref
@@ -54,6 +74,16 @@ func Test_filter_map_list_expr_funcref()
     return a:index * 2
   endfunc
   call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4')))
+
+  " foreach()
+  func! s:foreach1(index, val) abort
+    call add(g:test_variable, a:val + 1)
+    return [ 11, 12, 13, 14 ]
+  endfunc
+  let g:test_variable = []
+  call assert_equal([0, 1, 2, 3, 4], foreach(range(5), function('s:foreach1')))
+  call assert_equal([1, 2, 3, 4, 5], g:test_variable)
+  call remove(g:, 'test_variable')
 endfunc
 
 func Test_filter_map_nested()
@@ -90,11 +120,46 @@ func Test_filter_map_dict_expr_funcref()
     return a:key[0]
   endfunc
   call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
+
+  " foreach()
+  func! s:foreach1(key, val) abort
+    call extend(g:test_variable, {a:key: a:val * 2})
+    return [ 11, 12, 13, 14 ]
+  endfunc
+  let g:test_variable = {}
+  call assert_equal(dict, foreach(copy(dict), function('s:foreach1')))
+  call assert_equal({"foo": 2, "bar": 4, "baz": 6}, g:test_variable)
+  call remove(g:, 'test_variable')
+endfunc
+
+func Test_map_filter_locked()
+  let list01 = [1, 2, 3, 4]
+  lockvar 1 list01
+  call assert_fails('call filter(list01, "v:val > 1")', 'E741:')
+  call assert_equal([2, 4, 6, 8], map(list01, 'v:val * 2'))
+  call assert_equal([1, 2, 3, 4], map(list01, 'v:val / 2'))
+  call assert_equal([2, 4, 6, 8], mapnew(list01, 'v:val * 2'))
+  let g:test_variable = []
+  call assert_equal([1, 2, 3, 4], foreach(list01, 'call add(g:test_variable, v:val * 2)'))
+  call remove(g:, 'test_variable')
+  call assert_fails('call filter(list01, "v:val > 1")', 'E741:')
+  unlockvar 1 list01
+  lockvar! list01
+  call assert_fails('call filter(list01, "v:val > 1")', 'E741:')
+  call assert_fails('call map(list01, "v:val * 2")', 'E741:')
+  call assert_equal([2, 4, 6, 8], mapnew(list01, 'v:val * 2'))
+  let g:test_variable = []
+  call assert_equal([1, 2, 3, 4], foreach(list01, 'call add(g:test_variable, v:val * 2)'))
+  call assert_fails('call foreach(list01, "let list01[0] = -1")', 'E741:')
+  call assert_fails('call filter(list01, "v:val > 1")', 'E741:')
+  call remove(g:, 'test_variable')
+  unlockvar! list01
 endfunc
 
 func Test_map_filter_fails()
   call assert_fails('call map([1], "42 +")', 'E15:')
   call assert_fails('call filter([1], "42 +")', 'E15:')
+  call assert_fails('call foreach([1], "let a = }")', 'E15:')
   call assert_fails("let l = filter([1, 2, 3], '{}')", 'E728:')
   call assert_fails("let l = filter({'k' : 10}, '{}')", 'E728:')
   call assert_fails("let l = filter([1, 2], {})", 'E731:')
@@ -106,6 +171,8 @@ func Test_map_filter_fails()
   call assert_fails("let l = filter([1, 2], function('min'))", 'E118:')
   call assert_equal([1, 2, 3], filter([1, 2, 3], test_null_partial()))
   call assert_fails("let l = filter([1, 2], {a, b, c -> 1})", 'E119:')
+  call assert_fails('call foreach([1], "xyzzy")', 'E492:')
+  call assert_fails('call foreach([1], "let a = foo")', 'E121:')
 endfunc
 
 func Test_map_and_modify()
@@ -123,7 +190,7 @@ endfunc
 
 func Test_filter_and_modify()
   let l = [0]
-  " cannot change the list halfway a map()
+  " cannot change the list halfway thru filter()
   call assert_fails('call filter(l, "remove(l, 0)")', 'E741:')
 
   let d = #{a: 0, b: 0, c: 0}
@@ -133,6 +200,18 @@ func Test_filter_and_modify()
   call assert_fails('call filter(b, "remove(b, 0)")', 'E741:')
 endfunc
 
+func Test_foreach_and_modify()
+  let l = [0]
+  " cannot change the list halfway thru foreach()
+  call assert_fails('call foreach(l, "let a = remove(l, 0)")', 'E741:')
+
+  let d = #{a: 0, b: 0, c: 0}
+  call assert_fails('call foreach(d, "let a = remove(d, v:key)")', 'E741:')
+
+  let b = 0z1234
+  call assert_fails('call foreach(b, "let a = remove(b, 0)")', 'E741:')
+endfunc
+
 func Test_mapnew_dict()
   let din = #{one: 1, two: 2}
   let dout = mapnew(din, {k, v -> string(v)})
@@ -160,6 +239,36 @@ func Test_mapnew_blob()
   call assert_equal(0z129956, bout)
 endfunc
 
+func Test_foreach_blob()
+  let lines =<< trim END
+    LET g:test_variable = []
+    call assert_equal(0z0001020304, foreach(0z0001020304, 'call add(g:test_variable, v:val)'))
+    call assert_equal([0, 1, 2, 3, 4], g:test_variable)
+  END
+  call v9.CheckLegacyAndVim9Success(lines)
+
+  func! s:foreach1(index, val) abort
+    call add(g:test_variable, a:val)
+    return [ 11, 12, 13, 14 ]
+  endfunc
+  let g:test_variable = []
+  call assert_equal(0z0001020304, foreach(0z0001020304, function('s:foreach1')))
+  call assert_equal([0, 1, 2, 3, 4], g:test_variable)
+
+  let lines =<< trim END
+    def Foreach1(_, val: any): list<number>
+      add(g:test_variable, val)
+      return [ 11, 12, 13, 14 ]
+    enddef
+    g:test_variable = []
+    assert_equal(0z0001020304, foreach(0z0001020304, Foreach1))
+    assert_equal([0, 1, 2, 3, 4], g:test_variable)
+  END
+  call v9.CheckDefSuccess(lines)
+
+  call remove(g:, 'test_variable')
+endfunc
+
 " Test for using map(), filter() and mapnew() with a string
 func Test_filter_map_string()
   " filter()
@@ -219,6 +328,37 @@ func Test_filter_map_string()
   END
   call v9.CheckLegacyAndVim9Success(lines)
 
+  " foreach()
+  let lines =<< trim END
+    VAR s = "abc"
+    LET g:test_variable = []
+    call assert_equal(s, foreach(s, 'call add(g:test_variable, v:val)'))
+    call assert_equal(['a', 'b', 'c'], g:test_variable)
+    LET g:test_variable = []
+    LET s = 'あiうえお'
+    call assert_equal(s, foreach(s, 'call add(g:test_variable, v:val)'))
+    call assert_equal(['あ', 'i', 'う', 'え', 'お'], g:test_variable)
+  END
+  call v9.CheckLegacyAndVim9Success(lines)
+  func! s:foreach1(index, val) abort
+    call add(g:test_variable, a:val)
+    return [ 11, 12, 13, 14 ]
+  endfunc
+  let g:test_variable = []
+  call assert_equal('abcd', foreach('abcd', function('s:foreach1')))
+  call assert_equal(['a', 'b', 'c', 'd'], g:test_variable)
+  let lines =<< trim END
+    def Foreach1(_, val: string): list<number>
+      add(g:test_variable, val)
+      return [ 11, 12, 13, 14 ]
+    enddef
+    g:test_variable = []
+    assert_equal('abcd', foreach('abcd', Foreach1))
+    assert_equal(['a', 'b', 'c', 'd'], g:test_variable)
+  END
+  call v9.CheckDefSuccess(lines)
+  call remove(g:, 'test_variable')
+
   let lines =<< trim END
     #" map() and filter()
     call assert_equal('[あ][⁈][a][😊][⁉][💕][💕][b][💕]', map(filter('あx⁈ax😊x⁉💕💕b💕x', '"x" != v:val'), '"[" .. v:val .. "]"'))
index 6216b2f033bb4b616a6bdd7cb158674891332e2b..a17ae156b28397ddefe79aa6d3205871c8fd9e94 100644 (file)
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    27,
 /**/
     26,
 /**/