0
0
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:
Bram Moolenaar
2016-07-04 22:29:49 +02:00
parent ab9c89b68d
commit b33c7eb5b8
7 changed files with 192 additions and 39 deletions

View File

@@ -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()*

View File

@@ -2031,6 +2031,7 @@ test_arglist \
test_farsi \
test_feedkeys \
test_file_perm \
test_filter_map \
test_fnamemodify \
test_glob2regpat \
test_goto \

View File

@@ -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 */

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -758,6 +758,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1989,
/**/
1988,
/**/