mirror of
https://github.com/vim/vim.git
synced 2025-09-28 04:24:06 -04:00
patch 7.4.1989
Problem: filter() and map() only accept a string argument. Solution: Implement using a Funcref argument (Yasuhiro Matsumoto, Ken Takata)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
*eval.txt* For Vim version 7.4. Last change: 2016 Jul 02
|
||||
*eval.txt* For Vim version 7.4. Last change: 2016 Jul 04
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@@ -3521,31 +3521,46 @@ filewritable({file}) *filewritable()*
|
||||
directory, and we can write to it, the result is 2.
|
||||
|
||||
|
||||
filter({expr}, {string}) *filter()*
|
||||
{expr} must be a |List| or a |Dictionary|.
|
||||
For each item in {expr} evaluate {string} and when the result
|
||||
filter({expr1}, {expr2}) *filter()*
|
||||
{expr1} must be a |List| or a |Dictionary|.
|
||||
For each item in {expr1} evaluate {expr2} and when the result
|
||||
is zero remove the item from the |List| or |Dictionary|.
|
||||
Inside {string} |v:val| has the value of the current item.
|
||||
For a |Dictionary| |v:key| has the key of the current item.
|
||||
{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
|
||||
of the current item.
|
||||
Examples: >
|
||||
:call filter(mylist, 'v:val !~ "OLD"')
|
||||
call filter(mylist, 'v:val !~ "OLD"')
|
||||
< Removes the items where "OLD" appears. >
|
||||
:call filter(mydict, 'v:key >= 8')
|
||||
call filter(mydict, 'v:key >= 8')
|
||||
< Removes the items with a key below 8. >
|
||||
:call filter(var, 0)
|
||||
call filter(var, 0)
|
||||
< Removes all the items, thus clears the |List| or |Dictionary|.
|
||||
|
||||
Note that {string} is the result of expression and is then
|
||||
Note that {expr2} is the result of expression and is then
|
||||
used as an expression again. Often it is good to use a
|
||||
|literal-string| to avoid having to double backslashes.
|
||||
|
||||
If {expr2} is a |Funcref| it must take two arguments:
|
||||
1. the key or the index of the current item.
|
||||
2. the value of the current item.
|
||||
The function must return TRUE if the item should be kept.
|
||||
Example that keeps the odd items of a list: >
|
||||
func Odd(idx, val)
|
||||
return a:idx % 2 == 1
|
||||
endfunc
|
||||
call filter(mylist, function('Odd'))
|
||||
<
|
||||
The operation is done in-place. If you want a |List| or
|
||||
|Dictionary| to remain unmodified make a copy first: >
|
||||
:let l = filter(copy(mylist), 'v:val =~ "KEEP"')
|
||||
|
||||
< Returns {expr}, the |List| or |Dictionary| that was filtered.
|
||||
When an error is encountered while evaluating {string} no
|
||||
further items in {expr} are processed.
|
||||
< 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.
|
||||
|
||||
|
||||
finddir({name}[, {path}[, {count}]]) *finddir()*
|
||||
@@ -5036,29 +5051,43 @@ luaeval({expr}[, {expr}]) *luaeval()*
|
||||
See |lua-luaeval| for more details.
|
||||
{only available when compiled with the |+lua| feature}
|
||||
|
||||
map({expr}, {string}) *map()*
|
||||
{expr} must be a |List| or a |Dictionary|.
|
||||
Replace each item in {expr} with the result of evaluating
|
||||
{string}.
|
||||
Inside {string} |v:val| has the value of the current item.
|
||||
For a |Dictionary| |v:key| has the key of the current item
|
||||
and for a |List| |v:key| has the index of the current item.
|
||||
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
|
||||
of the current item and for a |List| |v:key| has the index of
|
||||
the current item.
|
||||
Example: >
|
||||
:call map(mylist, '"> " . v:val . " <"')
|
||||
< This puts "> " before and " <" after each item in "mylist".
|
||||
|
||||
Note that {string} is the result of an expression and is then
|
||||
Note that {expr2} is the result of an expression and is then
|
||||
used as an expression again. Often it is good to use a
|
||||
|literal-string| to avoid having to double backslashes. You
|
||||
still have to double ' quotes
|
||||
|
||||
If {expr2} is a |Funcref| it is called with two arguments:
|
||||
1. The key or the index of the current item.
|
||||
2. the value of the current item.
|
||||
The function must return the new value of the item. Example
|
||||
that changes each value by "key-value": >
|
||||
func KeyValue(key, val)
|
||||
return a:key . '-' . a:val
|
||||
endfunc
|
||||
call map(myDict, function('KeyValue'))
|
||||
<
|
||||
The operation is done in-place. If you want a |List| or
|
||||
|Dictionary| to remain unmodified make a copy first: >
|
||||
:let tlist = map(copy(mylist), ' v:val . "\t"')
|
||||
|
||||
< Returns {expr}, the |List| or |Dictionary| that was filtered.
|
||||
When an error is encountered while evaluating {string} no
|
||||
further items in {expr} are processed.
|
||||
< 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.
|
||||
|
||||
|
||||
maparg({name}[, {mode} [, {abbr} [, {dict}]]]) *maparg()*
|
||||
|
@@ -2031,6 +2031,7 @@ test_arglist \
|
||||
test_farsi \
|
||||
test_feedkeys \
|
||||
test_file_perm \
|
||||
test_filter_map \
|
||||
test_fnamemodify \
|
||||
test_glob2regpat \
|
||||
test_goto \
|
||||
|
42
src/eval.c
42
src/eval.c
@@ -6375,7 +6375,8 @@ tv_equal(
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* For VAR_FUNC and VAR_PARTIAL only compare the function name. */
|
||||
/* For VAR_FUNC and VAR_PARTIAL compare the function name, bound dict and
|
||||
* arguments. */
|
||||
if ((tv1->v_type == VAR_FUNC
|
||||
|| (tv1->v_type == VAR_PARTIAL && tv1->vval.v_partial != NULL))
|
||||
&& (tv2->v_type == VAR_FUNC
|
||||
@@ -11852,7 +11853,7 @@ findfilendir(
|
||||
}
|
||||
|
||||
static void filter_map(typval_T *argvars, typval_T *rettv, int map);
|
||||
static int filter_map_one(typval_T *tv, char_u *expr, int map, int *remp);
|
||||
static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp);
|
||||
|
||||
/*
|
||||
* Implementation of map() and filter().
|
||||
@@ -11860,8 +11861,7 @@ static int filter_map_one(typval_T *tv, char_u *expr, int map, int *remp);
|
||||
static void
|
||||
filter_map(typval_T *argvars, typval_T *rettv, int map)
|
||||
{
|
||||
char_u buf[NUMBUFLEN];
|
||||
char_u *expr;
|
||||
typval_T *expr;
|
||||
listitem_T *li, *nli;
|
||||
list_T *l = NULL;
|
||||
dictitem_T *di;
|
||||
@@ -11896,14 +11896,13 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
|
||||
return;
|
||||
}
|
||||
|
||||
expr = get_tv_string_buf_chk(&argvars[1], buf);
|
||||
expr = &argvars[1];
|
||||
/* On type errors, the preceding call has already displayed an error
|
||||
* message. Avoid a misleading error message for an empty string that
|
||||
* was not passed as argument. */
|
||||
if (expr != NULL)
|
||||
if (expr->v_type != VAR_UNKNOWN)
|
||||
{
|
||||
prepare_vimvar(VV_VAL, &save_val);
|
||||
expr = skipwhite(expr);
|
||||
|
||||
/* We reset "did_emsg" to be able to detect whether an error
|
||||
* occurred during evaluation of the expression. */
|
||||
@@ -11975,22 +11974,45 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
|
||||
}
|
||||
|
||||
static int
|
||||
filter_map_one(typval_T *tv, char_u *expr, int map, int *remp)
|
||||
filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp)
|
||||
{
|
||||
typval_T rettv;
|
||||
typval_T argv[3];
|
||||
char_u *s;
|
||||
int retval = FAIL;
|
||||
int dummy;
|
||||
|
||||
copy_tv(tv, &vimvars[VV_VAL].vv_tv);
|
||||
s = expr;
|
||||
argv[0] = vimvars[VV_KEY].vv_tv;
|
||||
argv[1] = vimvars[VV_VAL].vv_tv;
|
||||
s = expr->vval.v_string;
|
||||
if (expr->v_type == VAR_FUNC)
|
||||
{
|
||||
if (call_func(s, (int)STRLEN(s),
|
||||
&rettv, 2, argv, 0L, 0L, &dummy, TRUE, NULL, NULL) == FAIL)
|
||||
goto theend;
|
||||
}
|
||||
else if (expr->v_type == VAR_PARTIAL)
|
||||
{
|
||||
partial_T *partial = expr->vval.v_partial;
|
||||
|
||||
s = partial->pt_name;
|
||||
if (call_func(s, (int)STRLEN(s),
|
||||
&rettv, 2, argv, 0L, 0L, &dummy, TRUE, partial, NULL)
|
||||
== FAIL)
|
||||
goto theend;
|
||||
}
|
||||
else
|
||||
{
|
||||
s = skipwhite(s);
|
||||
if (eval1(&s, &rettv, TRUE) == FAIL)
|
||||
goto theend;
|
||||
if (*s != NUL) /* check for trailing chars after expr */
|
||||
{
|
||||
EMSG2(_(e_invexpr2), s);
|
||||
clear_tv(&rettv);
|
||||
goto theend;
|
||||
}
|
||||
}
|
||||
if (map)
|
||||
{
|
||||
/* map(): replace the list item value */
|
||||
|
@@ -12,6 +12,7 @@ source test_expand_dllpath.vim
|
||||
source test_feedkeys.vim
|
||||
source test_fnamemodify.vim
|
||||
source test_file_perm.vim
|
||||
source test_filter_map.vim
|
||||
source test_glob2regpat.vim
|
||||
source test_goto.vim
|
||||
source test_help_tagjump.vim
|
||||
|
77
src/testdir/test_filter_map.vim
Normal file
77
src/testdir/test_filter_map.vim
Normal file
@@ -0,0 +1,77 @@
|
||||
" Test filter() and map()
|
||||
|
||||
" list with expression string
|
||||
func Test_filter_map_list_expr_string()
|
||||
" filter()
|
||||
call assert_equal([2, 3, 4], filter([1, 2, 3, 4], 'v:val > 1'))
|
||||
call assert_equal([3, 4], filter([1, 2, 3, 4], 'v:key > 1'))
|
||||
|
||||
" map()
|
||||
call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], 'v:val * 2'))
|
||||
call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2'))
|
||||
endfunc
|
||||
|
||||
" dict with expression string
|
||||
func Test_filter_map_dict_expr_string()
|
||||
let dict = {"foo": 1, "bar": 2, "baz": 3}
|
||||
|
||||
" filter()
|
||||
call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), 'v:val > 1'))
|
||||
call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), 'v:key > "bar"'))
|
||||
|
||||
" map()
|
||||
call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), 'v:val * 2'))
|
||||
call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]'))
|
||||
endfunc
|
||||
|
||||
" list with funcref
|
||||
func Test_filter_map_list_expr_funcref()
|
||||
" filter()
|
||||
func! s:filter1(index, val) abort
|
||||
return a:val > 1
|
||||
endfunc
|
||||
call assert_equal([2, 3, 4], filter([1, 2, 3, 4], function('s:filter1')))
|
||||
|
||||
func! s:filter2(index, val) abort
|
||||
return a:index > 1
|
||||
endfunc
|
||||
call assert_equal([3, 4], filter([1, 2, 3, 4], function('s:filter2')))
|
||||
|
||||
" map()
|
||||
func! s:filter3(index, val) abort
|
||||
return a:val * 2
|
||||
endfunc
|
||||
call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], function('s:filter3')))
|
||||
|
||||
func! s:filter4(index, val) abort
|
||||
return a:index * 2
|
||||
endfunc
|
||||
call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4')))
|
||||
endfunc
|
||||
|
||||
" dict with funcref
|
||||
func Test_filter_map_dict_expr_funcref()
|
||||
let dict = {"foo": 1, "bar": 2, "baz": 3}
|
||||
|
||||
" filter()
|
||||
func! s:filter1(key, val) abort
|
||||
return a:val > 1
|
||||
endfunc
|
||||
call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), function('s:filter1')))
|
||||
|
||||
func! s:filter2(key, val) abort
|
||||
return a:key > "bar"
|
||||
endfunc
|
||||
call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), function('s:filter2')))
|
||||
|
||||
" map()
|
||||
func! s:filter3(key, val) abort
|
||||
return a:val * 2
|
||||
endfunc
|
||||
call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), function('s:filter3')))
|
||||
|
||||
func! s:filter4(key, val) abort
|
||||
return a:key[0]
|
||||
endfunc
|
||||
call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
|
||||
endfunc
|
@@ -14,6 +14,14 @@ func MySort(up, one, two)
|
||||
return a:one < a:two ? 1 : -1
|
||||
endfunc
|
||||
|
||||
func MyMap(sub, index, val)
|
||||
return a:val - a:sub
|
||||
endfunc
|
||||
|
||||
func MyFilter(threshold, index, val)
|
||||
return a:val > a:threshold
|
||||
endfunc
|
||||
|
||||
func Test_partial_args()
|
||||
let Cb = function('MyFunc', ["foo", "bar"])
|
||||
|
||||
@@ -36,6 +44,16 @@ func Test_partial_args()
|
||||
call assert_equal([1, 2, 3], sort([3, 1, 2], Sort))
|
||||
let Sort = function('MySort', [0])
|
||||
call assert_equal([3, 2, 1], sort([3, 1, 2], Sort))
|
||||
|
||||
let Map = function('MyMap', [2])
|
||||
call assert_equal([-1, 0, 1], map([1, 2, 3], Map))
|
||||
let Map = function('MyMap', [3])
|
||||
call assert_equal([-2, -1, 0], map([1, 2, 3], Map))
|
||||
|
||||
let Filter = function('MyFilter', [1])
|
||||
call assert_equal([2, 3], filter([1, 2, 3], Filter))
|
||||
let Filter = function('MyFilter', [2])
|
||||
call assert_equal([3], filter([1, 2, 3], Filter))
|
||||
endfunc
|
||||
|
||||
func MyDictFunc(arg1, arg2) dict
|
||||
@@ -60,6 +78,9 @@ func Test_partial_dict()
|
||||
call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy"))
|
||||
call assert_fails('Cb("fff")', 'E492:')
|
||||
|
||||
let Cb = function('MyDictFunc', dict)
|
||||
call assert_equal({"foo": "hello/foo/1", "bar": "hello/bar/2"}, map({"foo": 1, "bar": 2}, Cb))
|
||||
|
||||
let dict = {"tr": function('tr', ['hello', 'h', 'H'])}
|
||||
call assert_equal("Hello", dict.tr())
|
||||
endfunc
|
||||
|
@@ -758,6 +758,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
1989,
|
||||
/**/
|
||||
1988,
|
||||
/**/
|
||||
|
Reference in New Issue
Block a user