# HG changeset patch # User Christian Brabandt # Date 1705143606 -3600 # Node ID da670b1549b322056cd0715909b930f7ad55b6e9 # Parent 93171f4925c5dc40efd955ca4883c42981369574 patch 9.1.0027: Vim is missing a foreach() func Commit: https://github.com/vim/vim/commit/e79e2077607e8f829ba823308c91104a795736ba Author: Ernie Rael Date: Sat Jan 13 11:47:33 2024 +0100 patch 9.1.0027: Vim is missing a foreach() func 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 Signed-off-by: Christian Brabandt diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -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 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}) *foldtextresu 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 diff --git a/runtime/doc/tags b/runtime/doc/tags --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -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 /*foldtextr 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* diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -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-functi 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 diff --git a/src/blob.c b/src/blob.c --- a/src/blob.c +++ b/src/blob.c @@ -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) - { - clear_tv(&newtv); - emsg(_(e_invalid_operation_for_blob)); - break; - } - if (filtermap != FILTERMAP_FILTER) + if (filtermap != FILTERMAP_FOREACH) { - 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; } diff --git a/src/dict.c b/src/dict.c --- a/src/dict.c +++ b/src/dict.c @@ -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) diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -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, argc { 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 *de 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_ 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, diff --git a/src/list.c b/src/list.c --- a/src/list.c +++ b/src/list.c @@ -2325,7 +2325,7 @@ f_uniq(typval_T *argvars, typval_T *rett } /* - * 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) - { - clear_tv(&newtv); - break; + 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; } - // 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; + 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 * 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); @@ -2630,6 +2650,15 @@ f_mapnew(typval_T *argvars, typval_T *re } /* + * "foreach()" function + */ + void +f_foreach(typval_T *argvars, typval_T *rettv) +{ + filter_map(argvars, rettv, FILTERMAP_FOREACH); +} + +/* * "add(list, item)" function */ static void diff --git a/src/proto/list.pro b/src/proto/list.pro --- a/src/proto/list.pro +++ b/src/proto/list.pro @@ -56,6 +56,7 @@ int filter_map_one(typval_T *tv, typval_ 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); diff --git a/src/strings.c b/src/strings.c --- a/src/strings.c +++ b/src/strings.c @@ -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); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -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() diff --git a/src/testdir/test_filter_map.vim b/src/testdir/test_filter_map.vim --- a/src/testdir/test_filter_map.vim +++ b/src/testdir/test_filter_map.vim @@ -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 + 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 + 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 .. "]"')) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -705,6 +705,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 27, +/**/ 26, /**/ 25,