1
0
forked from aniani/vim

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().
This commit is contained in:
Bram Moolenaar 2020-11-09 18:31:39 +01:00
parent 8cebd43e97
commit ea696852e7
7 changed files with 205 additions and 53 deletions

View File

@ -2669,8 +2669,9 @@ maparg({name} [, {mode} [, {abbr} [, {dict}]]])
rhs of mapping {name} in mode {mode} rhs of mapping {name} in mode {mode}
mapcheck({name} [, {mode} [, {abbr}]]) mapcheck({name} [, {mode} [, {abbr}]])
String check for mappings matching {name} String check for mappings matching {name}
mapset({mode}, {abbr}, {dict}) mapnew({expr1}, {expr2}) List/Dict like |map()| but creates a new List
none restore mapping from |maparg()| result or Dictionary
mapset({mode}, {abbr}, {dict}) none restore mapping from |maparg()| result
match({expr}, {pat} [, {start} [, {count}]]) match({expr}, {pat} [, {start} [, {count}]])
Number position where {pat} matches in {expr} Number position where {pat} matches in {expr}
matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]]) matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]])
@ -6987,9 +6988,14 @@ luaeval({expr} [, {expr}]) *luaeval()*
< {only available when compiled with the |+lua| feature} < {only available when compiled with the |+lua| feature}
map({expr1}, {expr2}) *map()* 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 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 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. For a |Dictionary| |v:key| has the key
@ -7024,11 +7030,11 @@ map({expr1}, {expr2}) *map()*
|Dictionary| to remain unmodified make a copy first: > |Dictionary| to remain unmodified make a copy first: >
:let tlist = map(copy(mylist), ' v:val . "\t"') :let tlist = map(copy(mylist), ' v:val . "\t"')
< Returns {expr1}, the |List| or |Dictionary| that was filtered. < Returns {expr1}, the |List|, |Blob| or |Dictionary| that was
When an error is encountered while evaluating {expr2} no filtered. When an error is encountered while evaluating
further items in {expr1} are processed. When {expr2} is a {expr2} no further items in {expr1} are processed. When
Funcref errors inside a function are ignored, unless it was {expr2} is a Funcref errors inside a function are ignored,
defined with the "abort" flag. unless it was defined with the "abort" flag.
Can also be used as a |method|: > Can also be used as a |method|: >
mylist->map(expr2) mylist->map(expr2)
@ -7137,6 +7143,12 @@ mapcheck({name} [, {mode} [, {abbr}]]) *mapcheck()*
GetKey()->mapcheck('n') 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()* mapset({mode}, {abbr}, {dict}) *mapset()*
Restore a mapping from a dictionary returned by |maparg()|. Restore a mapping from a dictionary returned by |maparg()|.
{mode} and {abbr} should be the same as for the call to {mode} and {abbr} should be the same as for the call to

View File

@ -644,6 +644,7 @@ List manipulation: *list-functions*
deepcopy() make a full copy of a List deepcopy() make a full copy of a List
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
reduce() reduce a List to a value reduce() reduce a List to a value
sort() sort a List sort() sort a List
reverse() reverse the order of a List reverse() reverse the order of a List
@ -669,6 +670,7 @@ Dictionary manipulation: *dict-functions*
extend() add entries from one Dictionary to another extend() add entries from one Dictionary to another
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
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

@ -479,7 +479,6 @@ ret_job(int argcount UNUSED, type_T **argtypes UNUSED)
{ {
return &t_job; return &t_job;
} }
static type_T * static type_T *
ret_first_arg(int argcount, type_T **argtypes) ret_first_arg(int argcount, type_T **argtypes)
{ {
@ -487,6 +486,18 @@ ret_first_arg(int argcount, type_T **argtypes)
return argtypes[0]; return argtypes[0];
return &t_void; 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 * Used for getqflist(): returns list if there is no argument, dict if there is
@ -1115,11 +1126,13 @@ static funcentry_T global_functions[] =
#endif #endif
}, },
{"map", 2, 2, FEARG_1, NULL, {"map", 2, 2, FEARG_1, NULL,
ret_any, f_map}, ret_first_cont, f_map},
{"maparg", 1, 4, FEARG_1, NULL, {"maparg", 1, 4, FEARG_1, NULL,
ret_maparg, f_maparg}, ret_maparg, f_maparg},
{"mapcheck", 1, 3, FEARG_1, NULL, {"mapcheck", 1, 3, FEARG_1, NULL,
ret_string, f_mapcheck}, ret_string, f_mapcheck},
{"mapnew", 2, 2, FEARG_1, NULL,
ret_first_cont, f_mapnew},
{"mapset", 3, 3, FEARG_1, NULL, {"mapset", 3, 3, FEARG_1, NULL,
ret_void, f_mapset}, ret_void, f_mapset},
{"match", 2, 4, FEARG_1, NULL, {"match", 2, 4, FEARG_1, NULL,

View File

@ -1903,38 +1903,42 @@ f_uniq(typval_T *argvars, typval_T *rettv)
do_sort_uniq(argvars, rettv, FALSE); do_sort_uniq(argvars, rettv, FALSE);
} }
typedef enum {
FILTERMAP_FILTER,
FILTERMAP_MAP,
FILTERMAP_MAPNEW
} filtermap_T;
/* /*
* Handle one item for map() and filter(). * Handle one item for map() and filter().
* Sets v:val to "tv". Caller must set v:key.
*/ */
static int 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]; typval_T argv[3];
int retval = FAIL; int retval = FAIL;
copy_tv(tv, get_vim_var_tv(VV_VAL)); copy_tv(tv, get_vim_var_tv(VV_VAL));
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, argv, 2, &rettv) == FAIL) if (eval_expr_typval(expr, argv, 2, newtv) == FAIL)
goto theend; goto theend;
if (map) if (filtermap == FILTERMAP_FILTER)
{
// map(): replace the list item value
clear_tv(tv);
rettv.v_lock = 0;
*tv = rettv;
}
else
{ {
int error = FALSE; int error = FALSE;
// filter(): when expr is zero remove the item // filter(): when expr is zero remove the item
if (in_vim9script()) if (in_vim9script())
*remp = !tv2bool(&rettv); *remp = !tv2bool(newtv);
else else
*remp = (tv_get_number_chk(&rettv, &error) == 0); *remp = (tv_get_number_chk(newtv, &error) == 0);
clear_tv(&rettv); clear_tv(newtv);
// On type error, nothing has been removed; return FAIL to stop the // On type error, nothing has been removed; return FAIL to stop the
// loop. The error message was given by tv_get_number_chk(). // loop. The error message was given by tv_get_number_chk().
if (error) if (error)
@ -1950,7 +1954,7 @@ theend:
* Implementation of map() and filter(). * Implementation of map() and filter().
*/ */
static void 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; typval_T *expr;
listitem_T *li, *nli; listitem_T *li, *nli;
@ -1962,30 +1966,53 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
blob_T *b = NULL; blob_T *b = NULL;
int rem; int rem;
int todo; int todo;
char_u *ermsg = (char_u *)(map ? "map()" : "filter()"); char_u *ermsg = (char_u *)(filtermap == FILTERMAP_MAP ? "map()"
char_u *arg_errmsg = (char_u *)(map ? N_("map() argument") : 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")); : N_("filter() argument"));
int save_did_emsg; int save_did_emsg;
int idx = 0; int idx = 0;
// Always return the first argument, also on failure. // map() and filter() return the first argument, also on failure.
if (filtermap != FILTERMAP_MAPNEW)
copy_tv(&argvars[0], rettv); copy_tv(&argvars[0], rettv);
if (argvars[0].v_type == VAR_BLOB) 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) if ((b = argvars[0].vval.v_blob) == NULL)
return; return;
} }
else if (argvars[0].v_type == VAR_LIST) 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 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; return;
} }
else if (argvars[0].v_type == VAR_DICT) 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 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; return;
} }
else else
@ -2014,8 +2041,16 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
if (argvars[0].v_type == VAR_DICT) if (argvars[0].v_type == VAR_DICT)
{ {
int prev_lock = d->dv_lock; 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; d->dv_lock = VAR_LOCKED;
ht = &d->dv_hashtab; ht = &d->dv_hashtab;
hash_lock(ht); hash_lock(ht);
@ -2025,21 +2060,43 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
if (!HASHITEM_EMPTY(hi)) if (!HASHITEM_EMPTY(hi))
{ {
int r; int r;
typval_T newtv;
--todo; --todo;
di = HI2DI(hi); 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) arg_errmsg, TRUE)
|| var_check_ro(di->di_flags, || var_check_ro(di->di_flags,
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);
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)); clear_tv(get_vim_var_tv(VV_KEY));
if (r == FAIL || did_emsg) if (r == FAIL || did_emsg)
break;
if (!map && rem)
{ {
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) if (var_check_fixed(di->di_flags, arg_errmsg, TRUE)
|| var_check_ro(di->di_flags, arg_errmsg, TRUE)) || var_check_ro(di->di_flags, arg_errmsg, TRUE))
break; break;
@ -2055,27 +2112,39 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
int i; int i;
typval_T tv; typval_T tv;
varnumber_T val; 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_nr() doesn't set the type
set_vim_var_type(VV_KEY, VAR_NUMBER); set_vim_var_type(VV_KEY, VAR_NUMBER);
for (i = 0; i < b->bv_ga.ga_len; i++) for (i = 0; i < b->bv_ga.ga_len; i++)
{ {
typval_T newtv;
tv.v_type = VAR_NUMBER; tv.v_type = VAR_NUMBER;
val = blob_get(b, i); val = blob_get(b, i);
tv.vval.v_number = val; tv.vval.v_number = val;
set_vim_var_nr(VV_KEY, idx); 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; break;
if (tv.v_type != VAR_NUMBER) if (newtv.v_type != VAR_NUMBER)
{ {
clear_tv(&newtv);
emsg(_(e_invalblob)); emsg(_(e_invalblob));
break; break;
} }
if (map) if (filtermap != FILTERMAP_FILTER)
{ {
if (tv.vval.v_number != val) if (newtv.vval.v_number != val)
blob_set(b, i, tv.vval.v_number); blob_set(b_ret, i, newtv.vval.v_number);
} }
else if (rem) else if (rem)
{ {
@ -2092,23 +2161,46 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
else // argvars[0].v_type == VAR_LIST 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_nr() doesn't set the type
set_vim_var_type(VV_KEY, VAR_NUMBER); set_vim_var_type(VV_KEY, VAR_NUMBER);
CHECK_LIST_MATERIALIZE(l); CHECK_LIST_MATERIALIZE(l);
if (map && l->lv_lock == 0) if (filtermap != FILTERMAP_FILTER && l->lv_lock == 0)
l->lv_lock = VAR_LOCKED; l->lv_lock = VAR_LOCKED;
for (li = l->lv_first; li != NULL; li = nli) 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; break;
nli = li->li_next; nli = li->li_next;
set_vim_var_nr(VV_KEY, idx); set_vim_var_nr(VV_KEY, idx);
if (filter_map_one(&li->li_tv, expr, map, &rem) == FAIL if (filter_map_one(&li->li_tv, expr, filtermap,
|| did_emsg) &newtv, &rem) == FAIL || did_emsg)
break; 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); listitem_remove(l, li);
++idx; ++idx;
} }
@ -2128,7 +2220,7 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
void void
f_filter(typval_T *argvars, typval_T *rettv) 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 *rettv)
void void
f_map(typval_T *argvars, typval_T *rettv) 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);
} }
/* /*

View File

@ -48,6 +48,7 @@ void f_sort(typval_T *argvars, typval_T *rettv);
void f_uniq(typval_T *argvars, typval_T *rettv); void f_uniq(typval_T *argvars, typval_T *rettv);
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_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

@ -118,4 +118,25 @@ func Test_map_and_modify()
call assert_fails('echo map(d, {k,v -> remove(d, k)})', 'E741:') call assert_fails('echo map(d, {k,v -> remove(d, k)})', 'E741:')
endfunc 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: shiftwidth=2 sts=2 expandtab

View File

@ -750,6 +750,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 */
/**/
1969,
/**/ /**/
1968, 1968,
/**/ /**/