# HG changeset patch # User Bram Moolenaar # Date 1604943904 -3600 # Node ID 36fc73078bce17862b284c18db8a983e7dbe07e6 # Parent b80694c9bb40ece31358fe2a7d76a5f4076904ad patch 8.2.1969: Vim9: map() may change the list or dict item type Commit: https://github.com/vim/vim/commit/ea696852e7abcdebaf7f17a7f23dc90df1f5e2ed Author: Bram Moolenaar Date: Mon Nov 9 18:31:39 2020 +0100 patch 8.2.1969: Vim9: map() may change the list or dict item type Problem: Vim9: map() may change the list or dict item type. Solution: Add mapnew(). diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2669,8 +2669,9 @@ maparg({name} [, {mode} [, {abbr} [, {di rhs of mapping {name} in mode {mode} mapcheck({name} [, {mode} [, {abbr}]]) String check for mappings matching {name} -mapset({mode}, {abbr}, {dict}) - none restore mapping from |maparg()| result +mapnew({expr1}, {expr2}) List/Dict like |map()| but creates a new List + or Dictionary +mapset({mode}, {abbr}, {dict}) none restore mapping from |maparg()| result match({expr}, {pat} [, {start} [, {count}]]) Number position where {pat} matches in {expr} matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]]) @@ -6987,9 +6988,14 @@ luaeval({expr} [, {expr}]) *luaeval( < {only available when compiled with the |+lua| feature} map({expr1}, {expr2}) *map()* - {expr1} must be a |List| or a |Dictionary|. + {expr1} must be a |List|, |Blob| or |Dictionary|. Replace each item in {expr1} with the result of evaluating - {expr2}. {expr2} must be a |string| or |Funcref|. + {expr2}. For a |Blob| each byte is replaced. + If the item type changes you may want to use |mapnew()| to + create a new List or Dictionary. This is required when using + Vim9 script. + + {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 @@ -7024,11 +7030,11 @@ map({expr1}, {expr2}) *map()* |Dictionary| to remain unmodified make a copy first: > :let tlist = map(copy(mylist), ' v:val . "\t"') -< Returns {expr1}, the |List| or |Dictionary| that was filtered. - When an error is encountered while evaluating {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. +< Returns {expr1}, the |List|, |Blob| or |Dictionary| that was + filtered. When an error is encountered while evaluating + {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->map(expr2) @@ -7137,7 +7143,13 @@ mapcheck({name} [, {mode} [, {abbr}]]) GetKey()->mapcheck('n') -mapset({mode}, {abbr}, {dict}) *mapset()* +mapnew({expr1}, {expr2}) *mapnew()* + Like |map()| but instead of replacing items in {expr1} a new + List or Dictionary is created and returned. {expr1} remains + unchanged. + + +mapset({mode}, {abbr}, {dict}) *mapset()* Restore a mapping from a dictionary returned by |maparg()|. {mode} and {abbr} should be the same as for the call to |maparg()|. *E460* 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 @@ -644,6 +644,7 @@ List manipulation: *list-functions* deepcopy() make a full copy of a List filter() remove selected items from a List map() change each List item + mapnew() make a new List with changed items reduce() reduce a List to a value sort() sort a List reverse() reverse the order of a List @@ -669,6 +670,7 @@ Dictionary manipulation: *dict-functi extend() add entries from one Dictionary to another filter() remove selected entries from a Dictionary map() change each Dictionary entry + mapnew() make a new Dictionary with changed 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/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -479,7 +479,6 @@ ret_job(int argcount UNUSED, type_T **ar { return &t_job; } - static type_T * ret_first_arg(int argcount, type_T **argtypes) { @@ -487,6 +486,18 @@ ret_first_arg(int argcount, type_T **arg return argtypes[0]; return &t_void; } +// for map(): returns first argument but item type may differ + static type_T * +ret_first_cont(int argcount UNUSED, type_T **argtypes) +{ + if (argtypes[0]->tt_type == VAR_LIST) + return &t_list_any; + if (argtypes[0]->tt_type == VAR_DICT) + return &t_dict_any; + if (argtypes[0]->tt_type == VAR_BLOB) + return argtypes[0]; + return &t_any; +} /* * Used for getqflist(): returns list if there is no argument, dict if there is @@ -1115,11 +1126,13 @@ static funcentry_T global_functions[] = #endif }, {"map", 2, 2, FEARG_1, NULL, - ret_any, f_map}, + ret_first_cont, f_map}, {"maparg", 1, 4, FEARG_1, NULL, ret_maparg, f_maparg}, {"mapcheck", 1, 3, FEARG_1, NULL, ret_string, f_mapcheck}, + {"mapnew", 2, 2, FEARG_1, NULL, + ret_first_cont, f_mapnew}, {"mapset", 3, 3, FEARG_1, NULL, ret_void, f_mapset}, {"match", 2, 4, FEARG_1, NULL, diff --git a/src/list.c b/src/list.c --- a/src/list.c +++ b/src/list.c @@ -1903,38 +1903,42 @@ f_uniq(typval_T *argvars, typval_T *rett do_sort_uniq(argvars, rettv, FALSE); } +typedef enum { + FILTERMAP_FILTER, + FILTERMAP_MAP, + FILTERMAP_MAPNEW +} filtermap_T; + /* * Handle one item for map() and filter(). + * Sets v:val to "tv". Caller must set v:key. */ static int -filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) +filter_map_one( + typval_T *tv, // original value + typval_T *expr, // callback + filtermap_T filtermap, + typval_T *newtv, // for map() and mapnew(): new value + int *remp) // for filter(): remove flag { - typval_T rettv; typval_T argv[3]; int retval = FAIL; copy_tv(tv, get_vim_var_tv(VV_VAL)); argv[0] = *get_vim_var_tv(VV_KEY); argv[1] = *get_vim_var_tv(VV_VAL); - if (eval_expr_typval(expr, argv, 2, &rettv) == FAIL) + if (eval_expr_typval(expr, argv, 2, newtv) == FAIL) goto theend; - if (map) - { - // map(): replace the list item value - clear_tv(tv); - rettv.v_lock = 0; - *tv = rettv; - } - else + if (filtermap == FILTERMAP_FILTER) { int error = FALSE; // filter(): when expr is zero remove the item if (in_vim9script()) - *remp = !tv2bool(&rettv); + *remp = !tv2bool(newtv); else - *remp = (tv_get_number_chk(&rettv, &error) == 0); - clear_tv(&rettv); + *remp = (tv_get_number_chk(newtv, &error) == 0); + clear_tv(newtv); // On type error, nothing has been removed; return FAIL to stop the // loop. The error message was given by tv_get_number_chk(). if (error) @@ -1950,7 +1954,7 @@ theend: * Implementation of map() and filter(). */ static void -filter_map(typval_T *argvars, typval_T *rettv, int map) +filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap) { typval_T *expr; listitem_T *li, *nli; @@ -1962,30 +1966,53 @@ filter_map(typval_T *argvars, typval_T * blob_T *b = NULL; int rem; int todo; - char_u *ermsg = (char_u *)(map ? "map()" : "filter()"); - char_u *arg_errmsg = (char_u *)(map ? N_("map() argument") - : N_("filter() argument")); + char_u *ermsg = (char_u *)(filtermap == FILTERMAP_MAP ? "map()" + : filtermap == FILTERMAP_MAPNEW ? "mapnew()" + : "filter()"); + char_u *arg_errmsg = (char_u *)(filtermap == FILTERMAP_MAP + ? N_("map() argument") + : filtermap == FILTERMAP_MAPNEW + ? N_("mapnew() argument") + : N_("filter() argument")); int save_did_emsg; int idx = 0; - // Always return the first argument, also on failure. - copy_tv(&argvars[0], rettv); + // map() and filter() return the first argument, also on failure. + if (filtermap != FILTERMAP_MAPNEW) + copy_tv(&argvars[0], rettv); if (argvars[0].v_type == VAR_BLOB) { + if (filtermap == FILTERMAP_MAPNEW) + { + rettv->v_type = VAR_BLOB; + rettv->vval.v_blob = NULL; + } if ((b = argvars[0].vval.v_blob) == NULL) return; } else if (argvars[0].v_type == VAR_LIST) { + if (filtermap == FILTERMAP_MAPNEW) + { + rettv->v_type = VAR_LIST; + rettv->vval.v_list = NULL; + } if ((l = argvars[0].vval.v_list) == NULL - || (!map && value_check_lock(l->lv_lock, arg_errmsg, TRUE))) + || (filtermap == FILTERMAP_FILTER + && value_check_lock(l->lv_lock, arg_errmsg, TRUE))) return; } else if (argvars[0].v_type == VAR_DICT) { + if (filtermap == FILTERMAP_MAPNEW) + { + rettv->v_type = VAR_DICT; + rettv->vval.v_dict = NULL; + } if ((d = argvars[0].vval.v_dict) == NULL - || (!map && value_check_lock(d->dv_lock, arg_errmsg, TRUE))) + || (filtermap == FILTERMAP_FILTER + && value_check_lock(d->dv_lock, arg_errmsg, TRUE))) return; } else @@ -2014,8 +2041,16 @@ filter_map(typval_T *argvars, typval_T * if (argvars[0].v_type == VAR_DICT) { int prev_lock = d->dv_lock; + dict_T *d_ret = NULL; - if (map && d->dv_lock == 0) + if (filtermap == FILTERMAP_MAPNEW) + { + if (rettv_dict_alloc(rettv) == FAIL) + return; + d_ret = rettv->vval.v_dict; + } + + if (filtermap != FILTERMAP_FILTER && d->dv_lock == 0) d->dv_lock = VAR_LOCKED; ht = &d->dv_hashtab; hash_lock(ht); @@ -2024,22 +2059,44 @@ filter_map(typval_T *argvars, typval_T * { if (!HASHITEM_EMPTY(hi)) { - int r; + int r; + typval_T newtv; --todo; di = HI2DI(hi); - if (map && (value_check_lock(di->di_tv.v_lock, + if (filtermap != FILTERMAP_FILTER + && (value_check_lock(di->di_tv.v_lock, arg_errmsg, TRUE) || var_check_ro(di->di_flags, arg_errmsg, TRUE))) break; set_vim_var_string(VV_KEY, di->di_key, -1); - r = filter_map_one(&di->di_tv, expr, map, &rem); + r = filter_map_one(&di->di_tv, expr, filtermap, + &newtv, &rem); clear_tv(get_vim_var_tv(VV_KEY)); if (r == FAIL || did_emsg) + { + clear_tv(&newtv); break; - if (!map && rem) + } + if (filtermap == FILTERMAP_MAP) + { + // map(): replace the dict item value + clear_tv(&di->di_tv); + newtv.v_lock = 0; + di->di_tv = newtv; + } + else if (filtermap == FILTERMAP_MAPNEW) { + // mapnew(): add the item value to the new dict + r = dict_add_tv(d_ret, (char *)di->di_key, &newtv); + clear_tv(&newtv); + if (r == FAIL) + break; + } + else if (filtermap == FILTERMAP_FILTER && rem) + { + // filter(false): remove the item from the dict if (var_check_fixed(di->di_flags, arg_errmsg, TRUE) || var_check_ro(di->di_flags, arg_errmsg, TRUE)) break; @@ -2055,27 +2112,39 @@ filter_map(typval_T *argvars, typval_T * int i; typval_T tv; varnumber_T val; + blob_T *b_ret = b; + + if (filtermap == FILTERMAP_MAPNEW) + { + if (blob_copy(b, rettv) == FAIL) + return; + b_ret = rettv->vval.v_blob; + } // set_vim_var_nr() doesn't set the type set_vim_var_type(VV_KEY, VAR_NUMBER); for (i = 0; i < b->bv_ga.ga_len; i++) { + typval_T newtv; + tv.v_type = VAR_NUMBER; val = blob_get(b, i); tv.vval.v_number = val; set_vim_var_nr(VV_KEY, idx); - if (filter_map_one(&tv, expr, map, &rem) == FAIL || did_emsg) + if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL + || did_emsg) break; - if (tv.v_type != VAR_NUMBER) + if (newtv.v_type != VAR_NUMBER) { + clear_tv(&newtv); emsg(_(e_invalblob)); break; } - if (map) + if (filtermap != FILTERMAP_FILTER) { - if (tv.vval.v_number != val) - blob_set(b, i, tv.vval.v_number); + if (newtv.vval.v_number != val) + blob_set(b_ret, i, newtv.vval.v_number); } else if (rem) { @@ -2091,24 +2160,47 @@ filter_map(typval_T *argvars, typval_T * } else // argvars[0].v_type == VAR_LIST { - int prev_lock = l->lv_lock; + int prev_lock = l->lv_lock; + list_T *l_ret = NULL; + if (filtermap == FILTERMAP_MAPNEW) + { + if (rettv_list_alloc(rettv) == FAIL) + return; + l_ret = rettv->vval.v_list; + } // set_vim_var_nr() doesn't set the type set_vim_var_type(VV_KEY, VAR_NUMBER); CHECK_LIST_MATERIALIZE(l); - if (map && l->lv_lock == 0) + if (filtermap != FILTERMAP_FILTER && l->lv_lock == 0) l->lv_lock = VAR_LOCKED; for (li = l->lv_first; li != NULL; li = nli) { - if (map && value_check_lock(li->li_tv.v_lock, arg_errmsg, TRUE)) + typval_T newtv; + + if (filtermap != FILTERMAP_FILTER + && value_check_lock(li->li_tv.v_lock, arg_errmsg, TRUE)) break; nli = li->li_next; set_vim_var_nr(VV_KEY, idx); - if (filter_map_one(&li->li_tv, expr, map, &rem) == FAIL - || did_emsg) + if (filter_map_one(&li->li_tv, expr, filtermap, + &newtv, &rem) == FAIL || did_emsg) break; - if (!map && rem) + if (filtermap == FILTERMAP_MAP) + { + // map(): replace the list item value + clear_tv(&li->li_tv); + newtv.v_lock = 0; + li->li_tv = newtv; + } + else if (filtermap == FILTERMAP_MAPNEW) + { + // mapnew(): append the list item value + if (list_append_tv_move(l_ret, &newtv) == FAIL) + break; + } + else if (filtermap == FILTERMAP_FILTER && rem) listitem_remove(l, li); ++idx; } @@ -2128,7 +2220,7 @@ filter_map(typval_T *argvars, typval_T * void f_filter(typval_T *argvars, typval_T *rettv) { - filter_map(argvars, rettv, FALSE); + filter_map(argvars, rettv, FILTERMAP_FILTER); } /* @@ -2137,7 +2229,16 @@ f_filter(typval_T *argvars, typval_T *re void f_map(typval_T *argvars, typval_T *rettv) { - filter_map(argvars, rettv, TRUE); + filter_map(argvars, rettv, FILTERMAP_MAP); +} + +/* + * "mapnew()" function + */ + void +f_mapnew(typval_T *argvars, typval_T *rettv) +{ + filter_map(argvars, rettv, FILTERMAP_MAPNEW); } /* diff --git a/src/proto/list.pro b/src/proto/list.pro --- a/src/proto/list.pro +++ b/src/proto/list.pro @@ -48,6 +48,7 @@ void f_sort(typval_T *argvars, typval_T void f_uniq(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_mapnew(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/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 @@ -118,4 +118,25 @@ func Test_map_and_modify() call assert_fails('echo map(d, {k,v -> remove(d, k)})', 'E741:') endfunc +func Test_mapnew_dict() + let din = #{one: 1, two: 2} + let dout = mapnew(din, {k, v -> string(v)}) + call assert_equal(#{one: 1, two: 2}, din) + call assert_equal(#{one: '1', two: '2'}, dout) +endfunc + +func Test_mapnew_list() + let lin = [1, 2, 3] + let lout = mapnew(lin, {k, v -> string(v)}) + call assert_equal([1, 2, 3], lin) + call assert_equal(['1', '2', '3'], lout) +endfunc + +func Test_mapnew_blob() + let bin = 0z123456 + let bout = mapnew(bin, {k, v -> k == 1 ? 0x99 : v}) + call assert_equal(0z123456, bin) + call assert_equal(0z129956, bout) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1969, +/**/ 1968, /**/ 1967,