To: vim_dev@googlegroups.com Subject: Patch 7.4.1989 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 7.4.1989 Problem: filter() and map() only accept a string argument. Solution: Implement using a Funcref argument (Yasuhiro Matsumoto, Ken Takata) Files: runtime/doc/eval.txt, src/Makefile, src/eval.c, src/testdir/test_alot.vim, src/testdir/test_filter_map.vim, src/testdir/test_partial.vim *** ../vim-7.4.1988/runtime/doc/eval.txt 2016-07-01 18:16:47.481936426 +0200 --- runtime/doc/eval.txt 2016-07-04 22:16:37.435329754 +0200 *************** *** 3478,3508 **** 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 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. Examples: > ! :call filter(mylist, 'v:val !~ "OLD"') < Removes the items where "OLD" appears. > ! :call filter(mydict, 'v:key >= 8') < Removes the items with a key below 8. > ! :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 used as an expression again. Often it is good to use a |literal-string| to avoid having to double backslashes. 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. finddir({name}[, {path}[, {count}]]) *finddir()* --- 3521,3566 ---- directory, and we can write to it, the result is 2. ! 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|. ! {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"') < Removes the items where "OLD" appears. > ! call filter(mydict, 'v:key >= 8') < Removes the items with a key below 8. > ! call filter(var, 0) < Removes all the items, thus clears the |List| or |Dictionary|. ! 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 {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()* *************** *** 4908,4936 **** 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. 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 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 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. maparg({name}[, {mode} [, {abbr} [, {dict}]]]) *maparg()* --- 5051,5093 ---- See |lua-luaeval| for more details. {only available when compiled with the |+lua| feature} ! 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 {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 {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()* *** ../vim-7.4.1988/src/Makefile 2016-07-02 20:27:29.953436359 +0200 --- src/Makefile 2016-07-04 21:41:54.026484719 +0200 *************** *** 2031,2036 **** --- 2031,2037 ---- test_farsi \ test_feedkeys \ test_file_perm \ + test_filter_map \ test_fnamemodify \ test_glob2regpat \ test_goto \ *** ../vim-7.4.1988/src/eval.c 2016-07-01 18:16:47.485936367 +0200 --- src/eval.c 2016-07-04 22:03:47.570793571 +0200 *************** *** 6375,6381 **** return TRUE; } ! /* For VAR_FUNC and VAR_PARTIAL only compare the function name. */ if ((tv1->v_type == VAR_FUNC || (tv1->v_type == VAR_PARTIAL && tv1->vval.v_partial != NULL)) && (tv2->v_type == VAR_FUNC --- 6375,6382 ---- return TRUE; } ! /* 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,11858 **** } 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); /* * Implementation of map() and filter(). --- 11853,11859 ---- } static void filter_map(typval_T *argvars, typval_T *rettv, int map); ! static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp); /* * Implementation of map() and filter(). *************** *** 11860,11867 **** static void filter_map(typval_T *argvars, typval_T *rettv, int map) { ! char_u buf[NUMBUFLEN]; ! char_u *expr; listitem_T *li, *nli; list_T *l = NULL; dictitem_T *di; --- 11861,11867 ---- static void filter_map(typval_T *argvars, typval_T *rettv, int map) { ! typval_T *expr; listitem_T *li, *nli; list_T *l = NULL; dictitem_T *di; *************** *** 11896,11909 **** return; } ! expr = get_tv_string_buf_chk(&argvars[1], buf); /* 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) { 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. */ --- 11896,11908 ---- return; } ! 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->v_type != VAR_UNKNOWN) { prepare_vimvar(VV_VAL, &save_val); /* We reset "did_emsg" to be able to detect whether an error * occurred during evaluation of the expression. */ *************** *** 11975,11995 **** } static int ! filter_map_one(typval_T *tv, char_u *expr, int map, int *remp) { typval_T rettv; char_u *s; int retval = FAIL; 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 */ ! { ! EMSG2(_(e_invexpr2), s); ! clear_tv(&rettv); ! goto theend; } if (map) { --- 11974,12017 ---- } static int ! 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); ! 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); ! goto theend; ! } } if (map) { *** ../vim-7.4.1988/src/testdir/test_alot.vim 2016-07-01 14:04:36.422846118 +0200 --- src/testdir/test_alot.vim 2016-07-04 21:41:54.038484538 +0200 *************** *** 12,17 **** --- 12,18 ---- 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 *** ../vim-7.4.1988/src/testdir/test_filter_map.vim 2016-07-04 22:27:37.193518879 +0200 --- src/testdir/test_filter_map.vim 2016-07-04 21:41:54.038484538 +0200 *************** *** 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 *** ../vim-7.4.1988/src/testdir/test_partial.vim 2016-06-02 17:46:16.248499026 +0200 --- src/testdir/test_partial.vim 2016-07-04 21:41:54.038484538 +0200 *************** *** 14,19 **** --- 14,27 ---- 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,41 **** --- 44,59 ---- 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,65 **** --- 78,86 ---- 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 *** ../vim-7.4.1988/src/version.c 2016-07-03 17:47:21.862812533 +0200 --- src/version.c 2016-07-04 21:52:06.049277372 +0200 *************** *** 760,761 **** --- 760,763 ---- { /* Add new patch number below this line */ + /**/ + 1989, /**/ -- hundred-and-one symptoms of being an internet addict: 201. When somebody asks you where you are, you tell them in which chat room. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///