To: vim_dev@googlegroups.com Subject: Patch 8.2.1969 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.1969 Problem: Vim9: map() may change the list or dict item type. Solution: Add mapnew(). Files: runtime/doc/eval.txt, runtime/doc/usr_41.txt, src/evalfunc.c, src/list.c, src/proto/list.pro, src/testdir/test_filter_map.vim *** ../vim-8.2.1968/runtime/doc/eval.txt 2020-11-01 13:57:37.551988657 +0100 --- runtime/doc/eval.txt 2020-11-09 18:27:17.973436627 +0100 *************** *** 2635,2642 **** 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 match({expr}, {pat} [, {start} [, {count}]]) Number position where {pat} matches in {expr} matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]]) --- 2669,2677 ---- rhs of mapping {name} in mode {mode} mapcheck({name} [, {mode} [, {abbr}]]) String check for mappings matching {name} ! 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}]]]) *************** *** 6922,6930 **** < {only available when compiled with the |+lua| feature} map({expr1}, {expr2}) *map()* ! {expr1} must be a |List| or a |Dictionary|. Replace each item in {expr1} with the result of evaluating ! {expr2}. {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 --- 6988,7001 ---- < {only available when compiled with the |+lua| feature} map({expr1}, {expr2}) *map()* ! {expr1} must be a |List|, |Blob| or |Dictionary|. Replace each item in {expr1} with the result of evaluating ! {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 *************** *** 6959,6969 **** |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. Can also be used as a |method|: > mylist->map(expr2) --- 7030,7040 ---- |Dictionary| to remain unmodified make a copy first: > :let tlist = map(copy(mylist), ' v:val . "\t"') ! < 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) *************** *** 7072,7078 **** GetKey()->mapcheck('n') ! 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* --- 7143,7155 ---- GetKey()->mapcheck('n') ! 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* *** ../vim-8.2.1968/runtime/doc/usr_41.txt 2020-09-22 20:33:30.437223175 +0200 --- runtime/doc/usr_41.txt 2020-11-09 12:54:27.258287675 +0100 *************** *** 636,641 **** --- 644,650 ---- 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 *************** *** 661,666 **** --- 670,676 ---- 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 *** ../vim-8.2.1968/src/evalfunc.c 2020-11-08 12:49:43.120372854 +0100 --- src/evalfunc.c 2020-11-09 18:27:51.937350831 +0100 *************** *** 479,485 **** { return &t_job; } - static type_T * ret_first_arg(int argcount, type_T **argtypes) { --- 479,484 ---- *************** *** 487,492 **** --- 486,503 ---- 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,1125 **** #endif }, {"map", 2, 2, FEARG_1, NULL, ! ret_any, f_map}, {"maparg", 1, 4, FEARG_1, NULL, ret_maparg, f_maparg}, {"mapcheck", 1, 3, FEARG_1, NULL, ret_string, f_mapcheck}, {"mapset", 3, 3, FEARG_1, NULL, ret_void, f_mapset}, {"match", 2, 4, FEARG_1, NULL, --- 1126,1138 ---- #endif }, {"map", 2, 2, FEARG_1, NULL, ! 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, *** ../vim-8.2.1968/src/list.c 2020-11-04 11:36:31.412190527 +0100 --- src/list.c 2020-11-09 18:11:04.635604330 +0100 *************** *** 1903,1940 **** do_sort_uniq(argvars, rettv, FALSE); } /* * Handle one item for map() and filter(). */ static int ! filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) { - 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) goto theend; ! if (map) ! { ! // map(): replace the list item value ! clear_tv(tv); ! rettv.v_lock = 0; ! *tv = rettv; ! } ! else { int error = FALSE; // filter(): when expr is zero remove the item if (in_vim9script()) ! *remp = !tv2bool(&rettv); else ! *remp = (tv_get_number_chk(&rettv, &error) == 0); ! clear_tv(&rettv); // 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) --- 1903,1944 ---- 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, // 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 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, newtv) == FAIL) goto theend; ! if (filtermap == FILTERMAP_FILTER) { int error = FALSE; // filter(): when expr is zero remove the item if (in_vim9script()) ! *remp = !tv2bool(newtv); else ! *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,1956 **** * Implementation of map() and filter(). */ static void ! filter_map(typval_T *argvars, typval_T *rettv, int map) { typval_T *expr; listitem_T *li, *nli; --- 1954,1960 ---- * Implementation of map() and filter(). */ static void ! filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap) { typval_T *expr; listitem_T *li, *nli; *************** *** 1962,1991 **** 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")); int save_did_emsg; int idx = 0; ! // Always return the first argument, also on failure. ! copy_tv(&argvars[0], rettv); if (argvars[0].v_type == VAR_BLOB) { if ((b = argvars[0].vval.v_blob) == NULL) return; } else if (argvars[0].v_type == VAR_LIST) { if ((l = argvars[0].vval.v_list) == NULL ! || (!map && value_check_lock(l->lv_lock, arg_errmsg, TRUE))) return; } else if (argvars[0].v_type == VAR_DICT) { if ((d = argvars[0].vval.v_dict) == NULL ! || (!map && value_check_lock(d->dv_lock, arg_errmsg, TRUE))) return; } else --- 1966,2018 ---- blob_T *b = NULL; int rem; int todo; ! 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; ! // 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 ! || (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 ! || (filtermap == FILTERMAP_FILTER ! && value_check_lock(d->dv_lock, arg_errmsg, TRUE))) return; } else *************** *** 2014,2021 **** if (argvars[0].v_type == VAR_DICT) { int prev_lock = d->dv_lock; ! if (map && d->dv_lock == 0) d->dv_lock = VAR_LOCKED; ht = &d->dv_hashtab; hash_lock(ht); --- 2041,2056 ---- if (argvars[0].v_type == VAR_DICT) { int prev_lock = d->dv_lock; + dict_T *d_ret = NULL; ! 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,2045 **** { if (!HASHITEM_EMPTY(hi)) { ! int r; --todo; di = HI2DI(hi); ! if (map && (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); clear_tv(get_vim_var_tv(VV_KEY)); if (r == FAIL || did_emsg) break; ! if (!map && rem) { if (var_check_fixed(di->di_flags, arg_errmsg, TRUE) || var_check_ro(di->di_flags, arg_errmsg, TRUE)) break; --- 2059,2102 ---- { if (!HASHITEM_EMPTY(hi)) { ! int r; ! typval_T newtv; --todo; di = HI2DI(hi); ! 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, filtermap, ! &newtv, &rem); clear_tv(get_vim_var_tv(VV_KEY)); if (r == FAIL || did_emsg) + { + clear_tv(&newtv); break; ! } ! 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,2081 **** int i; typval_T tv; varnumber_T val; // 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++) { 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) break; ! if (tv.v_type != VAR_NUMBER) { emsg(_(e_invalblob)); break; } ! if (map) { ! if (tv.vval.v_number != val) ! blob_set(b, i, tv.vval.v_number); } else if (rem) { --- 2112,2150 ---- 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, filtermap, &newtv, &rem) == FAIL ! || did_emsg) break; ! if (newtv.v_type != VAR_NUMBER) { + clear_tv(&newtv); emsg(_(e_invalblob)); break; } ! if (filtermap != FILTERMAP_FILTER) { ! if (newtv.vval.v_number != val) ! blob_set(b_ret, i, newtv.vval.v_number); } else if (rem) { *************** *** 2091,2114 **** } else // argvars[0].v_type == VAR_LIST { ! int prev_lock = l->lv_lock; // 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) 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)) 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) break; ! if (!map && rem) listitem_remove(l, li); ++idx; } --- 2160,2206 ---- } else // argvars[0].v_type == VAR_LIST { ! 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 (filtermap != FILTERMAP_FILTER && l->lv_lock == 0) l->lv_lock = VAR_LOCKED; for (li = l->lv_first; li != NULL; li = nli) { ! 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, filtermap, ! &newtv, &rem) == FAIL || did_emsg) break; ! 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,2134 **** void f_filter(typval_T *argvars, typval_T *rettv) { ! filter_map(argvars, rettv, FALSE); } /* --- 2220,2226 ---- void f_filter(typval_T *argvars, typval_T *rettv) { ! filter_map(argvars, rettv, FILTERMAP_FILTER); } /* *************** *** 2137,2143 **** void f_map(typval_T *argvars, typval_T *rettv) { ! filter_map(argvars, rettv, TRUE); } /* --- 2229,2244 ---- void f_map(typval_T *argvars, typval_T *rettv) { ! filter_map(argvars, rettv, FILTERMAP_MAP); ! } ! ! /* ! * "mapnew()" function ! */ ! void ! f_mapnew(typval_T *argvars, typval_T *rettv) ! { ! filter_map(argvars, rettv, FILTERMAP_MAPNEW); } /* *** ../vim-8.2.1968/src/proto/list.pro 2020-08-15 22:14:49.055890417 +0200 --- src/proto/list.pro 2020-11-09 12:45:56.079722242 +0100 *************** *** 48,53 **** --- 48,54 ---- 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); *** ../vim-8.2.1968/src/testdir/test_filter_map.vim 2020-10-15 22:29:13.566726912 +0200 --- src/testdir/test_filter_map.vim 2020-11-09 18:15:15.067132031 +0100 *************** *** 118,121 **** --- 118,142 ---- 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 *** ../vim-8.2.1968/src/version.c 2020-11-08 12:49:43.120372854 +0100 --- src/version.c 2020-11-09 12:19:49.495595596 +0100 *************** *** 752,753 **** --- 752,755 ---- { /* Add new patch number below this line */ + /**/ + 1969, /**/ -- hundred-and-one symptoms of being an internet addict: 221. Your wife melts your keyboard in the oven. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///