0
0
mirror of https://github.com/vim/vim.git synced 2025-09-25 03:54:15 -04:00

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 <errael@raelity.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Ernie Rael
2024-01-13 11:47:33 +01:00
committed by Christian Brabandt
parent d8cb1ddab7
commit e79e207760
12 changed files with 303 additions and 61 deletions

View 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 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} foldlevel({lnum}) Number fold level at {lnum}
foldtext() String line displayed for closed fold foldtext() String line displayed for closed fold
foldtextresult({lnum}) String text for closed fold at {lnum} 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 foreground() Number bring the Vim window to the foreground
fullcommand({name} [, {vim9}]) String get full command from {name} fullcommand({name} [, {vim9}]) String get full command from {name}
funcref({name} [, {arglist}] [, {dict}]) funcref({name} [, {arglist}] [, {dict}])
@@ -2995,6 +2997,45 @@ foldtextresult({lnum}) *foldtextresult()*
Can also be used as a |method|: > Can also be used as a |method|: >
GetLnum()->foldtextresult() 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()*
foreground() Move the Vim window to the foreground. Useful when sent from foreground() Move the Vim window to the foreground. Useful when sent from

View File

@@ -5167,6 +5167,7 @@ E738 eval.txt /*E738*
E739 builtin.txt /*E739* E739 builtin.txt /*E739*
E74 message.txt /*E74* E74 message.txt /*E74*
E740 userfunc.txt /*E740* E740 userfunc.txt /*E740*
E741 builtin.txt /*E741*
E741 eval.txt /*E741* E741 eval.txt /*E741*
E742 userfunc.txt /*E742* E742 userfunc.txt /*E742*
E743 eval.txt /*E743* E743 eval.txt /*E743*
@@ -7148,6 +7149,7 @@ foldtextresult() builtin.txt /*foldtextresult()*
font-sizes gui_x11.txt /*font-sizes* font-sizes gui_x11.txt /*font-sizes*
fontset mbyte.txt /*fontset* fontset mbyte.txt /*fontset*
forced-motion motion.txt /*forced-motion* forced-motion motion.txt /*forced-motion*
foreach() builtin.txt /*foreach()*
foreground() builtin.txt /*foreground()* foreground() builtin.txt /*foreground()*
fork os_unix.txt /*fork* fork os_unix.txt /*fork*
form.vim syntax.txt /*form.vim* form.vim syntax.txt /*form.vim*

View 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 VIM USER MANUAL - by Bram Moolenaar
@@ -798,6 +798,7 @@ List manipulation: *list-functions*
filter() remove selected items from a List filter() remove selected items from a List
map() change each List item map() change each List item
mapnew() make a new List with changed items mapnew() make a new List with changed items
foreach() apply function to List items
reduce() reduce a List to a value reduce() reduce a List to a value
slice() take a slice of a List slice() take a slice of a List
sort() sort a List sort() sort a List
@@ -829,6 +830,7 @@ Dictionary manipulation: *dict-functions*
filter() remove selected entries from a Dictionary filter() remove selected entries from a Dictionary
map() change each Dictionary entry map() change each Dictionary entry
mapnew() make a new Dictionary with changed items mapnew() make a new Dictionary with changed items
foreach() apply function to Dictionary items
keys() get List of Dictionary keys keys() get List of Dictionary keys
values() get List of Dictionary values values() get List of Dictionary values
items() get List of Dictionary key-value pairs items() get List of Dictionary key-value pairs

View File

@@ -641,25 +641,28 @@ blob_filter_map(
if (filter_map_one(&tv, expr, filtermap, fc, &newtv, &rem) == FAIL if (filter_map_one(&tv, expr, filtermap, fc, &newtv, &rem) == FAIL
|| did_emsg) || did_emsg)
break; break;
if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL) if (filtermap != FILTERMAP_FOREACH)
{ {
clear_tv(&newtv); if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL)
emsg(_(e_invalid_operation_for_blob)); {
break; clear_tv(&newtv);
} emsg(_(e_invalid_operation_for_blob));
if (filtermap != FILTERMAP_FILTER) break;
{ }
if (newtv.vval.v_number != val) if (filtermap != FILTERMAP_FILTER)
blob_set(b_ret, i, newtv.vval.v_number); {
} if (newtv.vval.v_number != val)
else if (rem) blob_set(b_ret, i, newtv.vval.v_number);
{ }
char_u *p = (char_u *)blob_arg->bv_ga.ga_data; else if (rem)
{
char_u *p = (char_u *)blob_arg->bv_ga.ga_data;
mch_memmove(p + i, p + i + 1, mch_memmove(p + i, p + i + 1,
(size_t)b->bv_ga.ga_len - i - 1); (size_t)b->bv_ga.ga_len - i - 1);
--b->bv_ga.ga_len; --b->bv_ga.ga_len;
--i; --i;
}
} }
++idx; ++idx;
} }

View File

@@ -1329,8 +1329,8 @@ dict_extend_func(
} }
/* /*
* Implementation of map() and filter() for a Dict. Apply "expr" to every * Implementation of map(), filter(), foreach() for a Dict. Apply "expr" to
* item in Dict "d" and return the result in "rettv". * every item in Dict "d" and return the result in "rettv".
*/ */
void void
dict_filter_map( dict_filter_map(
@@ -1392,7 +1392,6 @@ dict_filter_map(
arg_errmsg, TRUE))) arg_errmsg, TRUE)))
break; break;
set_vim_var_string(VV_KEY, di->di_key, -1); 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); r = filter_map_one(&di->di_tv, expr, filtermap, fc, &newtv, &rem);
clear_tv(get_vim_var_tv(VV_KEY)); clear_tv(get_vim_var_tv(VV_KEY));
if (r == FAIL || did_emsg) if (r == FAIL || did_emsg)

View 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 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 *expected_member = NULL;
type_T *(args[2]); 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; where_T where = WHERE_INIT;
if (is_map) if (filtermap == FILTERMAP_MAP)
t_func_exp.tt_member = expected_member == NULL t_func_exp.tt_member = expected_member == NULL
|| type_any_or_unknown(type->tt_member) || type_any_or_unknown(type->tt_member)
? &t_any : expected_member; ? &t_any : expected_member;
else else if (filtermap == FILTERMAP_FILTER)
t_func_exp.tt_member = &t_bool; t_func_exp.tt_member = &t_bool;
else // filtermap == FILTERMAP_FOREACH
t_func_exp.tt_member = &t_unknown;
if (args[0] == NULL) if (args[0] == NULL)
args[0] = &t_unknown; args[0] = &t_unknown;
if (type->tt_argcount == -1) 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; return OK;
if (type->tt_type == VAR_FUNC) 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); semsg(_(e_string_or_function_required_for_argument_nr), 2);
return FAIL; return FAIL;
} }
@@ -710,7 +713,24 @@ arg_map_func(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
return OK; return OK;
if (type->tt_type == VAR_FUNC) 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); semsg(_(e_string_or_function_required_for_argument_nr), 2);
return FAIL; 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 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 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_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_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_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}; 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}, ret_string, f_foldtext},
{"foldtextresult", 1, 1, FEARG_1, arg1_lnum, {"foldtextresult", 1, 1, FEARG_1, arg1_lnum,
ret_string, f_foldtextresult}, ret_string, f_foldtextresult},
{"foreach", 2, 2, FEARG_1, arg2_foreach,
ret_first_arg, f_foreach},
{"foreground", 0, 0, 0, NULL, {"foreground", 0, 0, 0, NULL,
ret_void, f_foreground}, ret_void, f_foreground},
{"fullcommand", 1, 2, FEARG_1, arg2_string_bool, {"fullcommand", 1, 2, FEARG_1, arg2_string_bool,

View 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. * Sets v:val to "tv". Caller must set v:key.
*/ */
int int
@@ -2341,6 +2341,17 @@ filter_map_one(
int retval = FAIL; int retval = FAIL;
copy_tv(tv, get_vim_var_tv(VV_VAL)); 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[0] = *get_vim_var_tv(VV_KEY);
argv[1] = *get_vim_var_tv(VV_VAL); argv[1] = *get_vim_var_tv(VV_VAL);
if (eval_expr_typval(expr, FALSE, argv, 2, fc, newtv) == FAIL) if (eval_expr_typval(expr, FALSE, argv, 2, fc, newtv) == FAIL)
@@ -2360,6 +2371,8 @@ filter_map_one(
if (error) if (error)
goto theend; goto theend;
} }
else if (filtermap == FILTERMAP_FOREACH)
clear_tv(newtv);
retval = OK; retval = OK;
theend: theend:
clear_tv(get_vim_var_tv(VV_VAL)); 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 * Implementation of map(), filter(), foreach() for a List. Apply "expr" to
* in List "l" and return the result in "rettv". * every item in List "l" and return the result in "rettv".
*/ */
static void static void
list_filter_map( list_filter_map(
@@ -2421,7 +2434,8 @@ list_filter_map(
int stride = l->lv_u.nonmat.lv_stride; int stride = l->lv_u.nonmat.lv_stride;
// List from range(): loop over the numbers // 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_first = NULL;
l->lv_u.mat.lv_last = NULL; l->lv_u.mat.lv_last = NULL;
@@ -2444,27 +2458,30 @@ list_filter_map(
clear_tv(&newtv); clear_tv(&newtv);
break; break;
} }
if (filtermap != FILTERMAP_FILTER) if (filtermap != FILTERMAP_FOREACH)
{ {
if (filtermap == FILTERMAP_MAP && argtype != NULL if (filtermap != FILTERMAP_FILTER)
&& check_typval_arg_type(
argtype->tt_member, &newtv,
func_name, 0) == FAIL)
{ {
clear_tv(&newtv); if (filtermap == FILTERMAP_MAP && argtype != NULL
break; && check_typval_arg_type(
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)
{
// 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; val += stride;
@@ -2508,7 +2525,7 @@ list_filter_map(
break; break;
} }
else if (filtermap == FILTERMAP_FILTER && rem) else if (filtermap == FILTERMAP_FILTER && rem)
listitem_remove(l, li); listitem_remove(l, li);
++idx; ++idx;
} }
} }
@@ -2519,7 +2536,7 @@ list_filter_map(
} }
/* /*
* Implementation of map() and filter(). * Implementation of map(), filter() and foreach().
*/ */
static void static void
filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap) 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; typval_T *expr;
char *func_name = filtermap == FILTERMAP_MAP ? "map()" char *func_name = filtermap == FILTERMAP_MAP ? "map()"
: filtermap == FILTERMAP_MAPNEW ? "mapnew()" : filtermap == FILTERMAP_MAPNEW ? "mapnew()"
: "filter()"; : filtermap == FILTERMAP_FILTER ? "filter()"
: "foreach()";
char_u *arg_errmsg = (char_u *)(filtermap == FILTERMAP_MAP char_u *arg_errmsg = (char_u *)(filtermap == FILTERMAP_MAP
? N_("map() argument") ? N_("map() argument")
: filtermap == FILTERMAP_MAPNEW : filtermap == FILTERMAP_MAPNEW
? N_("mapnew() argument") ? N_("mapnew() argument")
: N_("filter() argument")); : filtermap == FILTERMAP_FILTER
? N_("filter() argument")
: N_("foreach() argument"));
int save_did_emsg; int save_did_emsg;
type_T *type = NULL; 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) if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING)
copy_tv(&argvars[0], rettv); copy_tv(&argvars[0], rettv);
@@ -2629,6 +2649,15 @@ f_mapnew(typval_T *argvars, typval_T *rettv)
filter_map(argvars, rettv, FILTERMAP_MAPNEW); 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 * "add(list, item)" function
*/ */

View 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_filter(typval_T *argvars, typval_T *rettv);
void f_map(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_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_add(typval_T *argvars, typval_T *rettv);
void f_count(typval_T *argvars, typval_T *rettv); void f_count(typval_T *argvars, typval_T *rettv);
void f_extend(typval_T *argvars, typval_T *rettv); void f_extend(typval_T *argvars, typval_T *rettv);

View File

@@ -942,7 +942,6 @@ string_filter_map(
break; break;
len = (int)STRLEN(tv.vval.v_string); len = (int)STRLEN(tv.vval.v_string);
newtv.v_type = VAR_UNKNOWN;
set_vim_var_nr(VV_KEY, idx); set_vim_var_nr(VV_KEY, idx);
if (filter_map_one(&tv, expr, filtermap, fc, &newtv, &rem) == FAIL if (filter_map_one(&tv, expr, filtermap, fc, &newtv, &rem) == FAIL
|| did_emsg) || did_emsg)
@@ -951,7 +950,7 @@ string_filter_map(
clear_tv(&tv); clear_tv(&tv);
break; break;
} }
else if (filtermap != FILTERMAP_FILTER) if (filtermap == FILTERMAP_MAP || filtermap == FILTERMAP_MAPNEW)
{ {
if (newtv.v_type != VAR_STRING) if (newtv.v_type != VAR_STRING)
{ {
@@ -963,7 +962,7 @@ string_filter_map(
else else
ga_concat(&ga, newtv.vval.v_string); ga_concat(&ga, newtv.vval.v_string);
} }
else if (!rem) else if (filtermap == FILTERMAP_FOREACH || !rem)
ga_concat(&ga, tv.vval.v_string); ga_concat(&ga, tv.vval.v_string);
clear_tv(&newtv); clear_tv(&newtv);

View File

@@ -4879,11 +4879,12 @@ typedef struct {
hashtab_T sve_hashtab; hashtab_T sve_hashtab;
} save_v_event_T; } save_v_event_T;
// Enum used by filter(), map() and mapnew() // Enum used by filter(), map(), mapnew() and foreach()
typedef enum { typedef enum {
FILTERMAP_FILTER, FILTERMAP_FILTER,
FILTERMAP_MAP, FILTERMAP_MAP,
FILTERMAP_MAPNEW FILTERMAP_MAPNEW,
FILTERMAP_FOREACH
} filtermap_T; } filtermap_T;
// Structure used by switch_win() to pass values to restore_win() // Structure used by switch_win() to pass values to restore_win()

View 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([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([9, 9, 9, 9], map([1, 2, 3, 4], 9))
call assert_equal([7, 7, 7], map([1, 2, 3], ' 7 ')) 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 endfunc
" dict with expression string " 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": 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": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]'))
call assert_equal({"foo": 9, "bar": 9, "baz": 9}, map(copy(dict), 9)) 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 endfunc
" list with funcref " list with funcref
@@ -54,6 +74,16 @@ func Test_filter_map_list_expr_funcref()
return a:index * 2 return a:index * 2
endfunc endfunc
call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4'))) 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 endfunc
func Test_filter_map_nested() func Test_filter_map_nested()
@@ -90,11 +120,46 @@ func Test_filter_map_dict_expr_funcref()
return a:key[0] return a:key[0]
endfunc endfunc
call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4'))) 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 endfunc
func Test_map_filter_fails() func Test_map_filter_fails()
call assert_fails('call map([1], "42 +")', 'E15:') call assert_fails('call map([1], "42 +")', 'E15:')
call assert_fails('call filter([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([1, 2, 3], '{}')", 'E728:')
call assert_fails("let l = filter({'k' : 10}, '{}')", 'E728:') call assert_fails("let l = filter({'k' : 10}, '{}')", 'E728:')
call assert_fails("let l = filter([1, 2], {})", 'E731:') 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_fails("let l = filter([1, 2], function('min'))", 'E118:')
call assert_equal([1, 2, 3], filter([1, 2, 3], test_null_partial())) 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("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 endfunc
func Test_map_and_modify() func Test_map_and_modify()
@@ -123,7 +190,7 @@ endfunc
func Test_filter_and_modify() func Test_filter_and_modify()
let l = [0] 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:') call assert_fails('call filter(l, "remove(l, 0)")', 'E741:')
let d = #{a: 0, b: 0, c: 0} 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:') call assert_fails('call filter(b, "remove(b, 0)")', 'E741:')
endfunc 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() func Test_mapnew_dict()
let din = #{one: 1, two: 2} let din = #{one: 1, two: 2}
let dout = mapnew(din, {k, v -> string(v)}) let dout = mapnew(din, {k, v -> string(v)})
@@ -160,6 +239,36 @@ func Test_mapnew_blob()
call assert_equal(0z129956, bout) call assert_equal(0z129956, bout)
endfunc 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 " Test for using map(), filter() and mapnew() with a string
func Test_filter_map_string() func Test_filter_map_string()
" filter() " filter()
@@ -219,6 +328,37 @@ func Test_filter_map_string()
END END
call v9.CheckLegacyAndVim9Success(lines) 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 let lines =<< trim END
#" map() and filter() #" map() and filter()
call assert_equal('[あ][⁈][a][😊][⁉][💕][💕][b][💕]', map(filter('あx⁈ax😊x⁉💕💕b💕x', '"x" != v:val'), '"[" .. v:val .. "]"')) call assert_equal('[あ][⁈][a][😊][⁉][💕][💕][b][💕]', map(filter('あx⁈ax😊x⁉💕💕b💕x', '"x" != v:val'), '"[" .. v:val .. "]"'))

View File

@@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
27,
/**/ /**/
26, 26,
/**/ /**/