To: vim_dev@googlegroups.com Subject: Patch 8.2.3848 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.3848 Problem: Cannot use reduce() for a string. Solution: Make reduce() work with a string. (Naruhiko Nishino, closes #9366) Files: runtime/doc/eval.txt, src/errors.h, src/evalfunc.c, src/list.c, src/typval.c, src/proto/typval.pro, src/testdir/test_listdict.vim, src/testdir/test_vim9_builtin.vim *** ../vim-8.2.3847/runtime/doc/eval.txt 2021-12-15 19:13:58.229251291 +0000 --- runtime/doc/eval.txt 2021-12-18 17:54:45.246094476 +0000 *************** *** 8875,8883 **** reduce({object}, {func} [, {initial}]) *reduce()* *E998* {func} is called for every item in {object}, which can be a ! |List| or a |Blob|. {func} is called with two arguments: the ! result so far and current item. After processing all items ! the result is returned. {initial} is the initial result. When omitted, the first item in {object} is used and {func} is first called for the second --- 8959,8967 ---- reduce({object}, {func} [, {initial}]) *reduce()* *E998* {func} is called for every item in {object}, which can be a ! |String|, |List| or a |Blob|. {func} is called with two arguments: ! the result so far and current item. After processing all ! items the result is returned. {initial} is the initial result. When omitted, the first item in {object} is used and {func} is first called for the second *************** *** 8888,8893 **** --- 8972,8978 ---- echo reduce([1, 3, 5], { acc, val -> acc + val }) echo reduce(['x', 'y'], { acc, val -> acc .. val }, 'a') echo reduce(0z1122, { acc, val -> 2 * acc + val }) + echo reduce('xyz', { acc, val -> acc .. ',' .. val }) < Can also be used as a |method|: > echo mylist->reduce({ acc, val -> acc + val }, 0) *** ../vim-8.2.3847/src/errors.h 2021-12-17 20:15:30.448830724 +0000 --- src/errors.h 2021-12-18 18:18:38.443857994 +0000 *************** *** 843,846 **** EXTERN char e_argument_of_str_must_be_list_string_dictionary_or_blob[] INIT(= N_("E1250: Argument of %s must be a List, String, Dictionary or Blob")); EXTERN char e_list_dict_blob_or_string_required_for_argument_nr[] ! INIT(= N_("E1228: List, Dictionary, Blob or String required for argument %d")); --- 843,850 ---- EXTERN char e_argument_of_str_must_be_list_string_dictionary_or_blob[] INIT(= N_("E1250: Argument of %s must be a List, String, Dictionary or Blob")); EXTERN char e_list_dict_blob_or_string_required_for_argument_nr[] ! INIT(= N_("E1251: List, Dictionary, Blob or String required for argument %d")); ! EXTERN char e_string_list_or_blob_required_for_argument_nr[] ! INIT(= N_("E1252: String, List or Blob required for argument %d")); ! EXTERN char e_string_expected_for_argument_nr[] ! INIT(= N_("E1253: String expected for argument %d")); *** ../vim-8.2.3847/src/evalfunc.c 2021-12-16 20:56:52.952098567 +0000 --- src/evalfunc.c 2021-12-18 17:54:45.250094470 +0000 *************** *** 465,470 **** --- 465,485 ---- } /* + * Check "type" is a list of 'any' or a blob or a string. + */ + static int + arg_string_list_or_blob(type_T *type, argcontext_T *context) + { + if (type->tt_type == VAR_ANY + || type->tt_type == VAR_LIST + || type->tt_type == VAR_BLOB + || type->tt_type == VAR_STRING) + return OK; + arg_type_mismatch(&t_list_any, type, context->arg_idx + 1); + return FAIL; + } + + /* * Check "type" is a job. */ static int *************** *** 817,823 **** static argcheck_T arg25_matchadd[] = {arg_string, arg_string, arg_number, arg_number, arg_dict_any}; static argcheck_T arg25_matchaddpos[] = {arg_string, arg_list_any, arg_number, arg_number, arg_dict_any}; static argcheck_T arg119_printf[] = {arg_string_or_nr, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; ! static argcheck_T arg23_reduce[] = {arg_list_or_blob, NULL, NULL}; static argcheck_T arg24_remote_expr[] = {arg_string, arg_string, arg_string, arg_number}; static argcheck_T arg23_remove[] = {arg_list_or_dict_or_blob, arg_remove2, arg_number}; static argcheck_T arg2_repeat[] = {arg_repeat1, arg_number}; --- 832,838 ---- static argcheck_T arg25_matchadd[] = {arg_string, arg_string, arg_number, arg_number, arg_dict_any}; static argcheck_T arg25_matchaddpos[] = {arg_string, arg_list_any, arg_number, arg_number, arg_dict_any}; static argcheck_T arg119_printf[] = {arg_string_or_nr, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; ! static argcheck_T arg23_reduce[] = {arg_string_list_or_blob, NULL, NULL}; static argcheck_T arg24_remote_expr[] = {arg_string, arg_string, arg_string, arg_number}; static argcheck_T arg23_remove[] = {arg_list_or_dict_or_blob, arg_remove2, arg_number}; static argcheck_T arg2_repeat[] = {arg_repeat1, arg_number}; *** ../vim-8.2.3847/src/list.c 2021-12-16 08:21:05.302828003 +0000 --- src/list.c 2021-12-18 18:05:34.921139121 +0000 *************** *** 314,319 **** --- 314,341 ---- } /* + * Make a typval_T of the first character of "input" and store it in "output". + * Return OK or FAIL. + */ + static int + tv_get_first_char(char_u *input, typval_T *output) + { + char_u buf[MB_MAXBYTES + 1]; + int len; + + if (input == NULL || output == NULL) + return FAIL; + + len = has_mbyte ? mb_ptr2len(input) : 1; + STRNCPY(buf, input, len); + buf[len] = NUL; + output->v_type = VAR_STRING; + output->vval.v_string = vim_strsave(buf); + + return output->vval.v_string == NULL ? FAIL : OK; + } + + /* * Free a list item, unless it was allocated together with the list itself. * Does not clear the value. Does not notify watchers. */ *************** *** 2492,2498 **** char_u *p; typval_T tv; garray_T ga; - char_u buf[MB_MAXBYTES + 1]; int len; // set_vim_var_nr() doesn't set the type --- 2514,2519 ---- *************** *** 2503,2518 **** { typval_T newtv; ! if (has_mbyte) ! len = mb_ptr2len(p); ! else ! len = 1; ! ! STRNCPY(buf, p, len); ! buf[len] = NUL; ! ! tv.v_type = VAR_STRING; ! tv.vval.v_string = vim_strsave(buf); set_vim_var_nr(VV_KEY, idx); if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL --- 2524,2532 ---- { typval_T newtv; ! if (tv_get_first_char(p, &tv) == FAIL) ! break; ! len = STRLEN(tv.vval.v_string); set_vim_var_nr(VV_KEY, idx); if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL *************** *** 3248,3259 **** partial_T *partial = NULL; funcexe_T funcexe; typval_T argv[3]; ! if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) ! { ! emsg(_(e_listblobreq)); return; ! } if (argvars[1].v_type == VAR_FUNC) func_name = argvars[1].vval.v_string; --- 3262,3278 ---- partial_T *partial = NULL; funcexe_T funcexe; typval_T argv[3]; + int r; + int called_emsg_start = called_emsg; ! if (in_vim9script() ! && check_for_string_or_list_or_blob_arg(argvars, 0) == FAIL) return; ! ! if (argvars[0].v_type != VAR_STRING ! && argvars[0].v_type != VAR_LIST ! && argvars[0].v_type != VAR_BLOB) ! semsg(_(e_string_list_or_blob_required), "reduce()"); if (argvars[1].v_type == VAR_FUNC) func_name = argvars[1].vval.v_string; *************** *** 3278,3285 **** { list_T *l = argvars[0].vval.v_list; listitem_T *li = NULL; - int r; - int called_emsg_start = called_emsg; if (l != NULL) CHECK_LIST_MATERIALIZE(l); --- 3297,3302 ---- *************** *** 3319,3324 **** --- 3336,3378 ---- l->lv_lock = prev_locked; } } + else if (argvars[0].v_type == VAR_STRING) + { + char_u *p = tv_get_string(&argvars[0]); + int len; + + if (argvars[2].v_type == VAR_UNKNOWN) + { + if (*p == NUL) + { + semsg(_(e_reduceempty), "String"); + return; + } + if (tv_get_first_char(p, rettv) == FAIL) + return; + p += STRLEN(rettv->vval.v_string); + } + else if (argvars[2].v_type != VAR_STRING) + { + semsg(_(e_string_expected_for_argument_nr), 3); + return; + } + else + copy_tv(&argvars[2], rettv); + + for ( ; *p != NUL; p += len) + { + argv[0] = *rettv; + if (tv_get_first_char(p, &argv[1]) == FAIL) + break; + len = STRLEN(argv[1].vval.v_string); + r = call_func(func_name, -1, rettv, 2, argv, &funcexe); + clear_tv(&argv[0]); + clear_tv(&argv[1]); + if (r == FAIL || called_emsg != called_emsg_start) + break; + } + } else { blob_T *b = argvars[0].vval.v_blob; *** ../vim-8.2.3847/src/typval.c 2021-12-17 18:01:27.269790317 +0000 --- src/typval.c 2021-12-18 18:09:37.928749058 +0000 *************** *** 663,668 **** --- 663,685 ---- } /* + * Give an error and return FAIL unless "args[idx]" is a string, a list or a + * blob. + */ + int + check_for_string_or_list_or_blob_arg(typval_T *args, int idx) + { + if (args[idx].v_type != VAR_STRING + && args[idx].v_type != VAR_LIST + && args[idx].v_type != VAR_BLOB) + { + semsg(_(e_string_list_or_blob_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; + } + + /* * Check for an optional string or list argument at 'idx' */ int *************** *** 697,706 **** && args[idx].v_type != VAR_NUMBER && args[idx].v_type != VAR_LIST) { ! if (idx >= 0) ! semsg(_(e_string_number_or_list_required_for_argument_nr), idx + 1); ! else ! emsg(_(e_stringreq)); return FAIL; } return OK; --- 714,720 ---- && args[idx].v_type != VAR_NUMBER && args[idx].v_type != VAR_LIST) { ! semsg(_(e_string_number_or_list_required_for_argument_nr), idx + 1); return FAIL; } return OK; *************** *** 742,751 **** { if (args[idx].v_type != VAR_LIST && args[idx].v_type != VAR_BLOB) { ! if (idx >= 0) ! semsg(_(e_list_or_blob_required_for_argument_nr), idx + 1); ! else ! emsg(_(e_listreq)); return FAIL; } return OK; --- 756,762 ---- { if (args[idx].v_type != VAR_LIST && args[idx].v_type != VAR_BLOB) { ! semsg(_(e_list_or_blob_required_for_argument_nr), idx + 1); return FAIL; } return OK; *** ../vim-8.2.3847/src/proto/typval.pro 2021-12-16 08:21:05.302828003 +0000 --- src/proto/typval.pro 2021-12-18 17:54:45.250094470 +0000 *************** *** 34,39 **** --- 34,40 ---- int check_for_opt_string_or_number_arg(typval_T *args, int idx); int check_for_string_or_blob_arg(typval_T *args, int idx); int check_for_string_or_list_arg(typval_T *args, int idx); + int check_for_string_or_list_or_blob_arg(typval_T *args, int idx); int check_for_opt_string_or_list_arg(typval_T *args, int idx); int check_for_string_or_dict_arg(typval_T *args, int idx); int check_for_string_or_number_or_list_arg(typval_T *args, int idx); *** ../vim-8.2.3847/src/testdir/test_listdict.vim 2021-12-17 20:36:11.200135980 +0000 --- src/testdir/test_listdict.vim 2021-12-18 18:20:11.103703258 +0000 *************** *** 1,4 **** --- 1,5 ---- " Tests for the List and Dict types + scriptencoding utf-8 source vim9.vim *************** *** 936,942 **** call assert_fails("call sort([1, 2], function('min'))", "E118:") endfunc ! " reduce a list or a blob func Test_reduce() let lines =<< trim END call assert_equal(1, reduce([], LSTART acc, val LMIDDLE acc + val LEND, 1)) --- 937,943 ---- call assert_fails("call sort([1, 2], function('min'))", "E118:") endfunc ! " reduce a list, blob or string func Test_reduce() let lines =<< trim END call assert_equal(1, reduce([], LSTART acc, val LMIDDLE acc + val LEND, 1)) *************** *** 959,964 **** --- 960,975 ---- call assert_equal(0xff, reduce(0zff, LSTART acc, val LMIDDLE acc + val LEND)) call assert_equal(2 * (2 * 0xaf + 0xbf) + 0xcf, reduce(0zAFBFCF, LSTART acc, val LMIDDLE 2 * acc + val LEND)) + + call assert_equal('x,y,z', 'xyz'->reduce(LSTART acc, val LMIDDLE acc .. ',' .. val LEND)) + call assert_equal('', ''->reduce(LSTART acc, val LMIDDLE acc .. ',' .. val LEND, '')) + call assert_equal('あ,い,う,え,お,😊,💕', 'あいうえお😊💕'->reduce(LSTART acc, val LMIDDLE acc .. ',' .. val LEND)) + call assert_equal('😊,あ,い,う,え,お,💕', 'あいうえお💕'->reduce(LSTART acc, val LMIDDLE acc .. ',' .. val LEND, '😊')) + call assert_equal('ऊ,ॠ,ॡ', reduce('ऊॠॡ', LSTART acc, val LMIDDLE acc .. ',' .. val LEND)) + call assert_equal('c,à,t', reduce('càt', LSTART acc, val LMIDDLE acc .. ',' .. val LEND)) + call assert_equal('Å,s,t,r,ö,m', reduce('Åström', LSTART acc, val LMIDDLE acc .. ',' .. val LEND)) + call assert_equal('Å,s,t,r,ö,m', reduce('Åström', LSTART acc, val LMIDDLE acc .. ',' .. val LEND)) + call assert_equal(',a,b,c', reduce('abc', LSTART acc, val LMIDDLE acc .. ',' .. val LEND, test_null_string())) END call CheckLegacyAndVim9Success(lines) *************** *** 967,979 **** call assert_fails("call reduce([], { acc, val -> acc + val })", 'E998: Reduce of an empty List with no initial value') call assert_fails("call reduce(0z, { acc, val -> acc + val })", 'E998: Reduce of an empty Blob with no initial value') ! call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E897:') ! call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E897:') ! call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E897:') call assert_fails("call reduce([1, 2], 'Xdoes_not_exist')", 'E117:') call assert_fails("echo reduce(0z01, { acc, val -> 2 * acc + val }, '')", 'E39:') let g:lut = [1, 2, 3, 4] func EvilRemove() call remove(g:lut, 1) --- 978,1000 ---- call assert_fails("call reduce([], { acc, val -> acc + val })", 'E998: Reduce of an empty List with no initial value') call assert_fails("call reduce(0z, { acc, val -> acc + val })", 'E998: Reduce of an empty Blob with no initial value') + call assert_fails("call reduce('', { acc, val -> acc + val })", 'E998: Reduce of an empty String with no initial value') + call assert_fails("call reduce(test_null_string(), { acc, val -> acc + val })", 'E998: Reduce of an empty String with no initial value') ! call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E1098:') ! call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E1098:') call assert_fails("call reduce([1, 2], 'Xdoes_not_exist')", 'E117:') call assert_fails("echo reduce(0z01, { acc, val -> 2 * acc + val }, '')", 'E39:') + call assert_fails("vim9 reduce(0, (acc, val) => (acc .. val), '')", 'E1252:') + call assert_fails("vim9 reduce({}, (acc, val) => (acc .. val), '')", 'E1252:') + call assert_fails("vim9 reduce(0.1, (acc, val) => (acc .. val), '')", 'E1252:') + call assert_fails("vim9 reduce(function('tr'), (acc, val) => (acc .. val), '')", 'E1252:') + call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E1253:') + call assert_fails("call reduce('', { acc, val -> acc + val }, {})", 'E1253:') + call assert_fails("call reduce('', { acc, val -> acc + val }, 0.1)", 'E1253:') + call assert_fails("call reduce('', { acc, val -> acc + val }, function('tr'))", 'E1253:') + let g:lut = [1, 2, 3, 4] func EvilRemove() call remove(g:lut, 1) *** ../vim-8.2.3847/src/testdir/test_vim9_builtin.vim 2021-12-18 12:40:48.950087774 +0000 --- src/testdir/test_vim9_builtin.vim 2021-12-18 18:32:28.575403640 +0000 *************** *** 1232,1238 **** enddef def Test_filter() ! CheckDefAndScriptFailure2(['filter(1.1, "1")'], 'E1013: Argument 1: type mismatch, expected list but got float', 'E1228: List, Dictionary, Blob or String required for argument 1') assert_equal([], filter([1, 2, 3], '0')) assert_equal([1, 2, 3], filter([1, 2, 3], '1')) assert_equal({b: 20}, filter({a: 10, b: 20}, 'v:val == 20')) --- 1232,1238 ---- enddef def Test_filter() ! CheckDefAndScriptFailure2(['filter(1.1, "1")'], 'E1013: Argument 1: type mismatch, expected list but got float', 'E1251: List, Dictionary, Blob or String required for argument 1') assert_equal([], filter([1, 2, 3], '0')) assert_equal([1, 2, 3], filter([1, 2, 3], '1')) assert_equal({b: 20}, filter({a: 10, b: 20}, 'v:val == 20')) *************** *** 2028,2036 **** def Test_map() if has('channel') ! CheckDefAndScriptFailure2(['map(test_null_channel(), "1")'], 'E1013: Argument 1: type mismatch, expected list but got channel', 'E1228: List, Dictionary, Blob or String required for argument 1') endif ! CheckDefAndScriptFailure2(['map(1, "1")'], 'E1013: Argument 1: type mismatch, expected list but got number', 'E1228: List, Dictionary, Blob or String required for argument 1') enddef def Test_map_failure() --- 2028,2036 ---- def Test_map() if has('channel') ! CheckDefAndScriptFailure2(['map(test_null_channel(), "1")'], 'E1013: Argument 1: type mismatch, expected list but got channel', 'E1251: List, Dictionary, Blob or String required for argument 1') endif ! CheckDefAndScriptFailure2(['map(1, "1")'], 'E1013: Argument 1: type mismatch, expected list but got number', 'E1251: List, Dictionary, Blob or String required for argument 1') enddef def Test_map_failure() *************** *** 2147,2155 **** def Test_mapnew() if has('channel') ! CheckDefAndScriptFailure2(['mapnew(test_null_job(), "1")'], 'E1013: Argument 1: type mismatch, expected list but got job', 'E1228: List, Dictionary, Blob or String required for argument 1') endif ! CheckDefAndScriptFailure2(['mapnew(1, "1")'], 'E1013: Argument 1: type mismatch, expected list but got number', 'E1228: List, Dictionary, Blob or String required for argument 1') enddef def Test_mapset() --- 2147,2155 ---- def Test_mapnew() if has('channel') ! CheckDefAndScriptFailure2(['mapnew(test_null_job(), "1")'], 'E1013: Argument 1: type mismatch, expected list but got job', 'E1251: List, Dictionary, Blob or String required for argument 1') endif ! CheckDefAndScriptFailure2(['mapnew(1, "1")'], 'E1013: Argument 1: type mismatch, expected list but got number', 'E1251: List, Dictionary, Blob or String required for argument 1') enddef def Test_mapset() *************** *** 2682,2688 **** enddef def Test_reduce() ! CheckDefAndScriptFailure2(['reduce({a: 10}, "1")'], 'E1013: Argument 1: type mismatch, expected list but got dict', 'E897: List or Blob required') assert_equal(6, [1, 2, 3]->reduce((r, c) => r + c, 0)) assert_equal(11, 0z0506->reduce((r, c) => r + c, 0)) enddef --- 2682,2688 ---- enddef def Test_reduce() ! CheckDefAndScriptFailure2(['reduce({a: 10}, "1")'], 'E1013: Argument 1: type mismatch, expected list but got dict', 'E1252: String, List or Blob required for argument 1') assert_equal(6, [1, 2, 3]->reduce((r, c) => r + c, 0)) assert_equal(11, 0z0506->reduce((r, c) => r + c, 0)) enddef *** ../vim-8.2.3847/src/version.c 2021-12-18 16:54:28.363950392 +0000 --- src/version.c 2021-12-18 18:12:48.008438406 +0000 *************** *** 751,752 **** --- 751,754 ---- { /* Add new patch number below this line */ + /**/ + 3848, /**/ -- hundred-and-one symptoms of being an internet addict: 68. Your cat always puts viruses on your dogs homepage /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///