diff options
-rw-r--r-- | runtime/doc/builtin.txt | 7 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/vimfn.lua | 7 | ||||
-rw-r--r-- | src/nvim/eval.c | 3 | ||||
-rw-r--r-- | src/nvim/eval.lua | 7 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 54 | ||||
-rw-r--r-- | test/old/testdir/test_listdict.vim | 29 |
6 files changed, 88 insertions, 19 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 0b24ed8d85..6303d73db5 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -5514,9 +5514,9 @@ readfile({fname} [, {type} [, {max}]]) *readfile()* 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. + |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 @@ -5527,6 +5527,7 @@ reduce({object}, {func} [, {initial}]) *reduce()* *E99 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 }) < reg_executing() *reg_executing()* diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 4b90a9e3c0..4fe9093666 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -6551,9 +6551,9 @@ function vim.fn.readdir(directory, expr) end function vim.fn.readfile(fname, type, max) end --- {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. +--- |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 @@ -6564,6 +6564,7 @@ function vim.fn.readfile(fname, type, max) end --- 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 }) --- < --- --- @param object any diff --git a/src/nvim/eval.c b/src/nvim/eval.c index dab0896d75..b73744f21b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -108,7 +108,7 @@ static const char e_dot_can_only_be_used_on_dictionary_str[] = N_("E1203: Dot can only be used on a dictionary: %s"); static const char e_empty_function_name[] = N_("E1192: Empty function name"); -static char e_argument_of_str_must_be_list_string_dictionary_or_blob[] +static const char e_argument_of_str_must_be_list_string_dictionary_or_blob[] = N_("E1250: Argument of %s must be a List, String, Dictionary or Blob"); static char * const namespace_char = "abglstvw"; @@ -5206,7 +5206,6 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap int len; for (const char *p = tv_get_string(&argvars[0]); *p != NUL; p += len) { len = utfc_ptr2len(p); - typval_T tv = { .v_type = VAR_STRING, .v_lock = VAR_UNLOCKED, diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 4dbd63d131..62d97a2931 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -7876,9 +7876,9 @@ M.funcs = { tags = { 'E998' }, desc = [=[ {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. + |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 @@ -7889,6 +7889,7 @@ M.funcs = { 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 }) < ]=], name = 'reduce', diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 0506c08b07..bf11fdf029 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -150,8 +150,12 @@ static const char *e_invalwindow = N_("E957: Invalid window number"); static const char e_invalid_submatch_number_nr[] = N_("E935: Invalid submatch number: %d"); static const char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value"); +static const char e_string_list_or_blob_required[] + = N_("E1098: String, List or Blob required"); static const char e_missing_function_argument[] = N_("E1132: Missing function argument"); +static const char e_string_expected_for_argument_nr[] + = N_("E1253: String expected for argument %d"); /// Dummy va_list for passing to vim_snprintf /// @@ -6168,11 +6172,16 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } /// "reduce(list, { accumulator, element -> value } [, initial])" function +/// "reduce(blob, { accumulator, element -> value } [, initial])" function +/// "reduce(string, { accumulator, element -> value } [, initial])" function static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) { - emsg(_(e_listblobreq)); - return; + const int called_emsg_start = called_emsg; + + if (argvars[0].v_type != VAR_STRING + && argvars[0].v_type != VAR_LIST + && argvars[0].v_type != VAR_BLOB) { + emsg(_(e_string_list_or_blob_required)); } const char *func_name; @@ -6217,7 +6226,6 @@ static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (l != NULL) { const VarLockStatus prev_locked = tv_list_locked(l); - const int called_emsg_start = called_emsg; tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { @@ -6232,6 +6240,44 @@ static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } tv_list_set_lock(l, prev_locked); } + } else if (argvars[0].v_type == VAR_STRING) { + const char *p = tv_get_string(&argvars[0]); + int len; + + if (argvars[2].v_type == VAR_UNKNOWN) { + if (*p == NUL) { + semsg(_(e_reduceempty), "String"); + return; + } + len = utfc_ptr2len(p); + *rettv = (typval_T){ + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = xstrnsave(p, (size_t)len), + }; + p += len; + } else if (argvars[2].v_type != VAR_STRING) { + semsg(_(e_string_expected_for_argument_nr), 3); + return; + } else { + tv_copy(&argvars[2], rettv); + } + + for (; *p != NUL; p += len) { + argv[0] = *rettv; + len = utfc_ptr2len(p); + argv[1] = (typval_T){ + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = xstrnsave(p, (size_t)len), + }; + const int r = call_func(func_name, -1, rettv, 2, argv, &funcexe); + tv_clear(&argv[0]); + tv_clear(&argv[1]); + if (r == FAIL || called_emsg != called_emsg_start) { + break; + } + } } else { const blob_T *const b = argvars[0].vval.v_blob; int i; diff --git a/test/old/testdir/test_listdict.vim b/test/old/testdir/test_listdict.vim index d6309bad1e..be090c81d3 100644 --- a/test/old/testdir/test_listdict.vim +++ b/test/old/testdir/test_listdict.vim @@ -1,4 +1,5 @@ " Tests for the List and Dict types +scriptencoding utf-8 source vim9.vim @@ -900,7 +901,7 @@ func Test_reverse_sort_uniq() call assert_fails("call sort([1, 2], function('min'))", "E118:") endfunc -" reduce a list or a blob +" 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)) @@ -923,6 +924,16 @@ func Test_reduce() 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, v:_null_string)) END call CheckLegacyAndVim9Success(lines) @@ -931,13 +942,23 @@ func Test_reduce() 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(v:_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)", '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({}, { 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) |