summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2020-11-09 18:31:39 +0100
committerBram Moolenaar <Bram@vim.org>2020-11-09 18:31:39 +0100
commitea696852e7abcdebaf7f17a7f23dc90df1f5e2ed (patch)
treeeedb0a14d24d7ec03f0caa021a1aafea78a1d83b
parent8cebd43e9774d2624af43ee5b86939886f2ba490 (diff)
downloadvim-git-8.2.1969.tar.gz
patch 8.2.1969: Vim9: map() may change the list or dict item typev8.2.1969
Problem: Vim9: map() may change the list or dict item type. Solution: Add mapnew().
-rw-r--r--runtime/doc/eval.txt32
-rw-r--r--runtime/doc/usr_41.txt2
-rw-r--r--src/evalfunc.c17
-rw-r--r--src/list.c181
-rw-r--r--src/proto/list.pro1
-rw-r--r--src/testdir/test_filter_map.vim21
-rw-r--r--src/version.c2
7 files changed, 204 insertions, 52 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index b573a11bf..c08b75bb8 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2669,8 +2669,9 @@ maparg({name} [, {mode} [, {abbr} [, {dict}]]])
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}]]) *mapcheck()*
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
index 387232b52..ea9cfbbec 100644
--- 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-functions*
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
index 6a43120d4..b5f1c0051 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -479,7 +479,6 @@ ret_job(int argcount UNUSED, type_T **argtypes UNUSED)
{
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 **argtypes)
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
index af12aea79..866de1871 100644
--- a/src/list.c
+++ b/src/list.c
@@ -1903,38 +1903,42 @@ f_uniq(typval_T *argvars, typval_T *rettv)
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 *rettv, int map)
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 *rettv, int map)
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 (map && d->dv_lock == 0)
+ 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 *rettv, int map)
{
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 *rettv, int map)
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 *rettv, int map)
}
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 *rettv, int map)
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 *rettv)
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
index 5a2feea2e..26990509f 100644
--- a/src/proto/list.pro
+++ b/src/proto/list.pro
@@ -48,6 +48,7 @@ void f_sort(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_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
index e6587c23b..e88f755b9 100644
--- 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
index 40f385164..f452d96dd 100644
--- 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,