vim-patch:7.4.1989

Problem:    filter() and map() only accept a string argument.
Solution:   Implement using a Funcref argument (Yasuhiro Matsumoto, Ken
            Takata)

b33c7eb5b8
This commit is contained in:
Michael Ennen
2016-12-14 00:20:48 -07:00
parent b0fc6108c9
commit bb2afeb026
7 changed files with 192 additions and 42 deletions

View File

@@ -3373,31 +3373,47 @@ filewritable({file}) *filewritable()*
directory, and we can write to it, the result is 2. directory, and we can write to it, the result is 2.
filter({expr}, {string}) *filter()* filter({expr1}, {expr2}) *filter()*
{expr} must be a |List| or a |Dictionary|. {expr1} must be a |List| or a |Dictionary|.
For each item in {expr} evaluate {string} and when the result For each item in {expr1} evaluate {expr2} and when the result
is zero remove the item from the |List| or |Dictionary|. is zero remove the item from the |List| or |Dictionary|.
Inside {string} |v:val| has the value 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.
For a |Dictionary| |v:key| has the key of the current item. For a |Dictionary| |v:key| has the key of the current item.
Examples: > Examples: >
:call filter(mylist, 'v:val !~ "OLD"') call filter(mylist, 'v:val !~ "OLD"')
< Removes the items where "OLD" appears. > < 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. > < 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|. < 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 used as an expression again. Often it is good to use a
|literal-string| to avoid having to double backslashes. |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 The operation is done in-place. If you want a |List| or
|Dictionary| to remain unmodified make a copy first: > |Dictionary| to remain unmodified make a copy first: >
:let l = filter(copy(mylist), 'v:val =~ "KEEP"') :let l = filter(copy(mylist), 'v:val =~ "KEEP"')
< Returns {expr}, the |List| or |Dictionary| that was filtered. < Returns {expr1}, the |List| or |Dictionary| that was filtered.
When an error is encountered while evaluating {string} no When an error is encountered while evaluating {expr2} no
further items in {expr} are processed. 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()* finddir({name}[, {path}[, {count}]]) *finddir()*
@@ -4948,29 +4964,43 @@ log10({expr}) *log10()*
< -2.0 < -2.0
map({expr}, {string}) *map()* map({expr1}, {expr2}) *map()*
{expr} must be a |List| or a |Dictionary|. {expr1} must be a |List| or a |Dictionary|.
Replace each item in {expr} with the result of evaluating Replace each item in {expr1} with the result of evaluating
{string}. {expr2}. {expr2} must be a |string| or |Funcref|.
Inside {string} |v:val| has the value of the current item.
For a |Dictionary| |v:key| has the key of the current item If {expr2} is a |string|, inside {expr2} |v:val| has the value
and for a |List| |v:key| has the index of the current item. 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: > Example: >
:call map(mylist, '"> " . v:val . " <"') :call map(mylist, '"> " . v:val . " <"')
< This puts "> " before and " <" after each item in "mylist". < 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 used as an expression again. Often it is good to use a
|literal-string| to avoid having to double backslashes. You |literal-string| to avoid having to double backslashes. You
still have to double ' quotes 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 The operation is done in-place. If you want a |List| or
|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 {expr}, the |List| or |Dictionary| that was filtered. < Returns {expr1}, the |List| or |Dictionary| that was filtered.
When an error is encountered while evaluating {string} no When an error is encountered while evaluating {expr2} no
further items in {expr} are processed. 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()* maparg({name}[, {mode} [, {abbr} [, {dict}]]]) *maparg()*

View File

@@ -5285,7 +5285,8 @@ tv_equal (
return TRUE; 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 if ((tv1->v_type == VAR_FUNC
|| (tv1->v_type == VAR_PARTIAL && tv1->vval.v_partial != NULL)) || (tv1->v_type == VAR_PARTIAL && tv1->vval.v_partial != NULL))
&& (tv2->v_type == VAR_FUNC && (tv2->v_type == VAR_FUNC
@@ -9306,8 +9307,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
*/ */
static void filter_map(typval_T *argvars, typval_T *rettv, int map) static void filter_map(typval_T *argvars, typval_T *rettv, int map)
{ {
char_u buf[NUMBUFLEN]; typval_T *expr;
char_u *expr;
listitem_T *li, *nli; listitem_T *li, *nli;
list_T *l = NULL; list_T *l = NULL;
dictitem_T *di; dictitem_T *di;
@@ -9339,16 +9339,15 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
return; return;
} }
expr = get_tv_string_buf_chk(&argvars[1], buf); expr = &argvars[1];
/* On type errors, the preceding call has already displayed an error // On type errors, the preceding call has already displayed an error
* message. Avoid a misleading error message for an empty string that // message. Avoid a misleading error message for an empty string that
* was not passed as argument. */ // was not passed as argument.
if (expr != NULL) { if (expr->v_type != VAR_UNKNOWN) {
prepare_vimvar(VV_VAL, &save_val); prepare_vimvar(VV_VAL, &save_val);
expr = skipwhite(expr);
/* We reset "did_emsg" to be able to detect whether an error // We reset "did_emsg" to be able to detect whether an error
* occurred during evaluation of the expression. */ // occurred during evaluation of the expression.
save_did_emsg = did_emsg; save_did_emsg = did_emsg;
did_emsg = FALSE; did_emsg = FALSE;
@@ -9412,21 +9411,42 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
copy_tv(&argvars[0], rettv); copy_tv(&argvars[0], rettv);
} }
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)
{ {
typval_T rettv; typval_T rettv;
typeval_T argv[3];
char_u *s; char_u *s;
int retval = FAIL; int retval = FAIL;
int dummy;
copy_tv(tv, &vimvars[VV_VAL].vv_tv); copy_tv(tv, &vimvars[VV_VAL].vv_tv);
s = expr; argv[0] = vimvars[VV_KEY].vv_tv;
if (eval1(&s, &rettv, TRUE) == FAIL) 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; goto theend;
if (*s != NUL) { /* check for trailing chars after expr */ }
} 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); EMSG2(_(e_invexpr2), s);
clear_tv(&rettv);
goto theend; goto theend;
} }
}
if (map) { if (map) {
/* map(): replace the list item value */ /* map(): replace the list item value */
clear_tv(tv); clear_tv(tv);

View File

@@ -34,6 +34,7 @@ NEW_TESTS ?= \
test_cscope.res \ test_cscope.res \
test_digraph.res \ test_digraph.res \
test_diffmode.res \ test_diffmode.res \
test_filter_map.res \
test_gn.res \ test_gn.res \
test_hardcopy.res \ test_hardcopy.res \
test_help_tagjump.res \ test_help_tagjump.res \

View File

@@ -9,6 +9,7 @@ source test_ex_undo.vim
source test_expr.vim source test_expr.vim
source test_expr_utf8.vim source test_expr_utf8.vim
source test_feedkeys.vim source test_feedkeys.vim
source test_filter_map.vim
source test_goto.vim source test_goto.vim
source test_jumps.vim source test_jumps.vim
source test_match.vim source test_match.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 return a:one < a:two ? 1 : -1
endfunc 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() func Test_partial_args()
let Cb = function('MyFunc', ["foo", "bar"]) 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)) call assert_equal([1, 2, 3], sort([3, 1, 2], Sort))
let Sort = function('MySort', [0]) let Sort = function('MySort', [0])
call assert_equal([3, 2, 1], sort([3, 1, 2], Sort)) 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 endfunc
func MyDictFunc(arg1, arg2) dict func MyDictFunc(arg1, arg2) dict
@@ -59,6 +77,9 @@ func Test_partial_dict()
call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy")) call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy"))
call assert_fails('Cb("fff")', 'E492:') 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'])} let dict = {"tr": function('tr', ['hello', 'h', 'H'])}
call assert_equal("Hello", dict.tr()) call assert_equal("Hello", dict.tr())
endfunc endfunc

View File

@@ -451,7 +451,7 @@ static int included_patches[] = {
// 1992, // 1992,
// 1991, // 1991,
1990, 1990,
// 1989, 1989,
// 1988 NA // 1988 NA
// 1987 NA // 1987 NA
// 1986, // 1986,