Patch 8.2.1969

6 views
Skip to first unread message

Bram Moolenaar

unread,
Nov 9, 2020, 12:32:23 PM11/9/20
to vim...@googlegroups.com

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 -- Br...@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 ///
Reply all
Reply to author
Forward
0 new messages