patch 7.4.1989
Problem: filter() and map() only accept a string argument.
Solution: Implement using a Funcref argument (Yasuhiro Matsumoto, Ken
Takata)
diff --git a/src/Makefile b/src/Makefile
index 3aeac7d..4deb4a2 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -2031,6 +2031,7 @@
test_farsi \
test_feedkeys \
test_file_perm \
+ test_filter_map \
test_fnamemodify \
test_glob2regpat \
test_goto \
diff --git a/src/eval.c b/src/eval.c
index 41abe37..4a1ad4f 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -6375,7 +6375,8 @@
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 @@
}
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 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 @@
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,21 +11974,44 @@
}
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;
- if (eval1(&s, &rettv, TRUE) == FAIL)
- goto theend;
- if (*s != NUL) /* check for trailing chars after 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)
{
- EMSG2(_(e_invexpr2), s);
- clear_tv(&rettv);
- goto theend;
+ 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);
+ goto theend;
+ }
}
if (map)
{
diff --git a/src/testdir/test_alot.vim b/src/testdir/test_alot.vim
index 76134fb..e8546ef 100644
--- a/src/testdir/test_alot.vim
+++ b/src/testdir/test_alot.vim
@@ -12,6 +12,7 @@
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
diff --git a/src/testdir/test_filter_map.vim b/src/testdir/test_filter_map.vim
new file mode 100644
index 0000000..6bb063c
--- /dev/null
+++ b/src/testdir/test_filter_map.vim
@@ -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
diff --git a/src/testdir/test_partial.vim b/src/testdir/test_partial.vim
index 6012507..b43f349 100644
--- a/src/testdir/test_partial.vim
+++ b/src/testdir/test_partial.vim
@@ -14,6 +14,14 @@
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 @@
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 @@
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
diff --git a/src/version.c b/src/version.c
index a89b83e..7588c9c 100644
--- a/src/version.c
+++ b/src/version.c
@@ -759,6 +759,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1989,
+/**/
1988,
/**/
1987,