]> granicus.if.org Git - vim/commitdiff
patch 7.4.1989 v7.4.1989
authorBram Moolenaar <Bram@vim.org>
Mon, 4 Jul 2016 20:29:49 +0000 (22:29 +0200)
committerBram Moolenaar <Bram@vim.org>
Mon, 4 Jul 2016 20:29:49 +0000 (22:29 +0200)
Problem:    filter() and map() only accept a string argument.
Solution:   Implement using a Funcref argument (Yasuhiro Matsumoto, Ken
            Takata)

runtime/doc/eval.txt
src/Makefile
src/eval.c
src/testdir/test_alot.vim
src/testdir/test_filter_map.vim [new file with mode: 0644]
src/testdir/test_partial.vim
src/version.c

index bf33b48d98e4b5b90465a2368b565f6306b2e356..66dbd9a1883c8f4cf148f595b1a0e64331bf67fc 100644 (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()*
index 3aeac7dce03f7d8176b0d72750bf96f49f99a793..4deb4a2bffa55e93ba408824f306657d612dbf99 100644 (file)
@@ -2031,6 +2031,7 @@ test_arglist \
        test_farsi \
        test_feedkeys \
        test_file_perm \
+       test_filter_map \
        test_fnamemodify \
        test_glob2regpat \
        test_goto \
index 41abe37d9fe6797ff7e3faff50bfa5989ae00fcc..4a1ad4f73a4d58ba66c3021372cb5659044c5b3e 100644 (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,21 +11974,44 @@ 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;
-    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)
     {
index 76134fb6678a4781810245d620c7bb87ddbfd438..e8546efe8ae7abee841ce4f29b15dcb3fe910ac2 100644 (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
diff --git a/src/testdir/test_filter_map.vim b/src/testdir/test_filter_map.vim
new file mode 100644 (file)
index 0000000..6bb063c
--- /dev/null
@@ -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
index 6012507bea9057e7d74b65bcc963941492edd751..b43f34902c9786cfeeb586bc28e19828d22c01f7 100644 (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
index a89b83e1c556bd7311dcf574218e306f02a6541a..7588c9cf1798fc4650e519faa095324c59dcd2d1 100644 (file)
@@ -758,6 +758,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1989,
 /**/
     1988,
 /**/