vim-patch:partial:9.1.0027: Vim is missing a foreach() func (#27037)

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: vim/vim#12166

e79e207760

Partial port as this doesn't handle non-materialized range() lists.

vim-patch:c92b8bed1fa6

runtime(help): delete duplicate help tag E741 (vim/vim#13861)

c92b8bed1f

Co-authored-by: Ernie Rael <errael@raelity.com>
This commit is contained in:
zeertzjq 2024-01-16 11:30:35 +08:00 committed by GitHub
parent 267e90f31d
commit 46a7c1b319
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 317 additions and 26 deletions

View File

@ -1866,6 +1866,41 @@ foldtextresult({lnum}) *foldtextresult()*
line, "'m" mark m, etc. line, "'m" mark m, etc.
Useful when exporting folded text, e.g., to HTML. Useful when exporting folded text, e.g., to HTML.
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, 'let used[v:val] = v: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 lambda you don't get an error if it only accepts one
argument.
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.
fullcommand({name}) *fullcommand()* fullcommand({name}) *fullcommand()*
Get the full command name from a short abbreviated command Get the full command name from a short abbreviated command

View File

@ -665,6 +665,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
@ -696,6 +697,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

@ -2308,6 +2308,45 @@ function vim.fn.foldtext() end
--- @return string --- @return string
function vim.fn.foldtextresult(lnum) end function vim.fn.foldtextresult(lnum) end
--- {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, 'let used[v:val] = v: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 lambda you don't get an error if it only accepts one
--- argument.
--- 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.
---
--- @param expr1 any
--- @param expr2 any
--- @return any
function vim.fn.foreach(expr1, expr2) end
--- Get the full command name from a short abbreviated command --- Get the full command name from a short abbreviated command
--- name; see |20.2| for details on command abbreviations. --- name; see |20.2| for details on command abbreviations.
--- ---

View File

@ -308,11 +308,12 @@ static partial_T *vvlua_partial;
/// v: hashtab /// v: hashtab
#define vimvarht vimvardict.dv_hashtab #define vimvarht vimvardict.dv_hashtab
/// 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;
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
@ -5098,7 +5099,8 @@ void assert_error(garray_T *gap)
tv_list_append_string(vimvars[VV_ERRORS].vv_list, gap->ga_data, (ptrdiff_t)gap->ga_len); tv_list_append_string(vimvars[VV_ERRORS].vv_list, gap->ga_data, (ptrdiff_t)gap->ga_len);
} }
/// Implementation of map() and filter() for a Dict. /// Implementation of map(), filter(), foreach() for a Dict. Apply "expr" to
/// every item in Dict "d" and return the result in "rettv".
static void filter_map_dict(dict_T *d, filtermap_T filtermap, const char *func_name, static void filter_map_dict(dict_T *d, filtermap_T filtermap, const char *func_name,
const char *arg_errmsg, typval_T *expr, typval_T *rettv) const char *arg_errmsg, typval_T *expr, typval_T *rettv)
{ {
@ -5166,7 +5168,7 @@ static void filter_map_dict(dict_T *d, filtermap_T filtermap, const char *func_n
d->dv_lock = prev_lock; d->dv_lock = prev_lock;
} }
/// Implementation of map() and filter() for a Blob. /// Implementation of map(), filter(), foreach() for a Blob.
static void filter_map_blob(blob_T *blob_arg, filtermap_T filtermap, typval_T *expr, static void filter_map_blob(blob_T *blob_arg, filtermap_T filtermap, typval_T *expr,
const char *arg_errmsg, typval_T *rettv) const char *arg_errmsg, typval_T *rettv)
{ {
@ -5209,20 +5211,22 @@ static void filter_map_blob(blob_T *blob_arg, filtermap_T filtermap, typval_T *e
|| did_emsg) { || did_emsg) {
break; break;
} }
if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL) { if (filtermap != FILTERMAP_FOREACH) {
tv_clear(&newtv); if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL) {
emsg(_(e_invalblob)); tv_clear(&newtv);
break; emsg(_(e_invalblob));
} break;
if (filtermap != FILTERMAP_FILTER) { }
if (newtv.vval.v_number != val) { if (filtermap != FILTERMAP_FILTER) {
tv_blob_set(b_ret, i, (uint8_t)newtv.vval.v_number); if (newtv.vval.v_number != val) {
tv_blob_set(b_ret, i, (uint8_t)newtv.vval.v_number);
}
} else if (rem) {
char *const p = (char *)blob_arg->bv_ga.ga_data;
memmove(p + i, p + i + 1, (size_t)(b->bv_ga.ga_len - i - 1));
b->bv_ga.ga_len--;
i--;
} }
} else if (rem) {
char *const p = (char *)blob_arg->bv_ga.ga_data;
memmove(p + i, p + i + 1, (size_t)(b->bv_ga.ga_len - i - 1));
b->bv_ga.ga_len--;
i--;
} }
idx++; idx++;
} }
@ -5230,7 +5234,7 @@ static void filter_map_blob(blob_T *blob_arg, filtermap_T filtermap, typval_T *e
b->bv_lock = prev_lock; b->bv_lock = prev_lock;
} }
/// Implementation of map() and filter() for a String. /// Implementation of map(), filter(), foreach() for a String.
static void filter_map_string(const char *str, filtermap_T filtermap, typval_T *expr, static void filter_map_string(const char *str, filtermap_T filtermap, typval_T *expr,
typval_T *rettv) typval_T *rettv)
{ {
@ -5259,7 +5263,8 @@ static void filter_map_string(const char *str, filtermap_T filtermap, typval_T *
tv_clear(&newtv); tv_clear(&newtv);
tv_clear(&tv); tv_clear(&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) {
tv_clear(&newtv); tv_clear(&newtv);
tv_clear(&tv); tv_clear(&tv);
@ -5268,7 +5273,7 @@ static void filter_map_string(const char *str, filtermap_T filtermap, typval_T *
} 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);
} }
@ -5281,7 +5286,8 @@ static void filter_map_string(const char *str, filtermap_T filtermap, typval_T *
rettv->vval.v_string = ga.ga_data; rettv->vval.v_string = ga.ga_data;
} }
/// Implementation of map() and filter() for a List. /// Implementation of map(), filter(), foreach() for a List. Apply "expr" to
/// every item in List "l" and return the result in "rettv".
static void filter_map_list(list_T *l, filtermap_T filtermap, const char *func_name, static void filter_map_list(list_T *l, filtermap_T filtermap, const char *func_name,
const char *arg_errmsg, typval_T *expr, typval_T *rettv) const char *arg_errmsg, typval_T *expr, typval_T *rettv)
{ {
@ -5345,21 +5351,25 @@ static void filter_map_list(list_T *l, filtermap_T filtermap, const char *func_n
tv_list_set_lock(l, prev_lock); tv_list_set_lock(l, prev_lock);
} }
/// Implementation of map() and filter(). /// Implementation of map(), filter() and foreach().
static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap) static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
{ {
const char *const func_name = (filtermap == FILTERMAP_MAP const char *const func_name = (filtermap == FILTERMAP_MAP
? "map()" ? "map()"
: (filtermap == FILTERMAP_MAPNEW : (filtermap == FILTERMAP_MAPNEW
? "mapnew()" ? "mapnew()"
: "filter()")); : (filtermap == FILTERMAP_FILTER
? "filter()"
: "foreach()")));
const char *const arg_errmsg = (filtermap == FILTERMAP_MAP const char *const arg_errmsg = (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"))));
// 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) {
tv_copy(&argvars[0], rettv); tv_copy(&argvars[0], rettv);
} }
@ -5407,7 +5417,7 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
} }
} }
/// 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.
/// ///
/// @param tv original value /// @param tv original value
@ -5422,6 +5432,17 @@ static int filter_map_one(typval_T *tv, typval_T *expr, const filtermap_T filter
int retval = FAIL; int retval = FAIL;
tv_copy(tv, &vimvars[VV_VAL].vv_tv); tv_copy(tv, &vimvars[VV_VAL].vv_tv);
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] = vimvars[VV_KEY].vv_tv; argv[0] = vimvars[VV_KEY].vv_tv;
argv[1] = vimvars[VV_VAL].vv_tv; argv[1] = vimvars[VV_VAL].vv_tv;
if (eval_expr_typval(expr, false, argv, 2, newtv) == FAIL) { if (eval_expr_typval(expr, false, argv, 2, newtv) == FAIL) {
@ -5438,6 +5459,8 @@ static int filter_map_one(typval_T *tv, typval_T *expr, const filtermap_T filter
if (error) { if (error) {
goto theend; goto theend;
} }
} else if (filtermap == FILTERMAP_FOREACH) {
tv_clear(newtv);
} }
retval = OK; retval = OK;
theend: theend:
@ -5463,6 +5486,12 @@ void f_mapnew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
filter_map(argvars, rettv, FILTERMAP_MAPNEW); filter_map(argvars, rettv, FILTERMAP_MAPNEW);
} }
/// "foreach()" function
void f_foreach(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
filter_map(argvars, rettv, FILTERMAP_FOREACH);
}
/// "function()" function /// "function()" function
/// "funcref()" function /// "funcref()" function
void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref) void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref)

View File

@ -2923,6 +2923,48 @@ M.funcs = {
returns = 'string', returns = 'string',
signature = 'foldtextresult({lnum})', signature = 'foldtextresult({lnum})',
}, },
foreach = {
args = 2,
base = 1,
desc = [=[
{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, 'let used[v:val] = v: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 lambda you don't get an error if it only accepts one
argument.
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.
]=],
name = 'foreach',
params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
signature = 'foreach({expr1}, {expr2})',
},
foreground = { foreground = {
args = 0, args = 0,
params = {}, params = {},

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:')
@ -108,6 +173,8 @@ func Test_map_filter_fails()
" Nvim doesn't have null partials " Nvim doesn't have null partials
" 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()
@ -125,7 +192,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}
@ -135,6 +202,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)})
@ -162,6 +241,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 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 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()
@ -221,6 +330,37 @@ func Test_filter_map_string()
END END
call CheckLegacyAndVim9Success(lines) call 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 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 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

@ -2,6 +2,10 @@
" Use a different file name for each run. " Use a different file name for each run.
let s:sequence = 1 let s:sequence = 1
func CheckDefSuccess(lines)
return
endfunc
func CheckDefFailure(lines, error, lnum = -3) func CheckDefFailure(lines, error, lnum = -3)
return return
endfunc endfunc