vim-patch:8.2.3848: cannot use reduce() for a string

Problem:    Cannot use reduce() for a string.
Solution:   Make reduce() work with a string. (Naruhiko Nishino, closes vim/vim#9366)

0ccb5842f5

Omit tv_get_first_char() as it doesn't really save much code.

Co-authored-by: rbtnn <naru123456789@gmail.com>
This commit is contained in:
zeertzjq 2023-08-17 13:41:43 +08:00
parent 7f51829cf7
commit c5576fcc80
6 changed files with 88 additions and 19 deletions

View File

@ -5514,9 +5514,9 @@ readfile({fname} [, {type} [, {max}]]) *readfile()*
reduce({object}, {func} [, {initial}]) *reduce()* *E998* reduce({object}, {func} [, {initial}]) *reduce()* *E998*
{func} is called for every item in {object}, which can be a {func} is called for every item in {object}, which can be a
|List| or a |Blob|. {func} is called with two arguments: the |String|, |List| or a |Blob|. {func} is called with two arguments:
result so far and current item. After processing all items the result so far and current item. After processing all
the result is returned. items the result is returned.
{initial} is the initial result. When omitted, the first item {initial} is the initial result. When omitted, the first item
in {object} is used and {func} is first called for the second 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([1, 3, 5], { acc, val -> acc + val })
echo reduce(['x', 'y'], { acc, val -> acc .. val }, 'a') echo reduce(['x', 'y'], { acc, val -> acc .. val }, 'a')
echo reduce(0z1122, { acc, val -> 2 * acc + val }) echo reduce(0z1122, { acc, val -> 2 * acc + val })
echo reduce('xyz', { acc, val -> acc .. ',' .. val })
< <
reg_executing() *reg_executing()* reg_executing() *reg_executing()*

View File

@ -6551,9 +6551,9 @@ function vim.fn.readdir(directory, expr) end
function vim.fn.readfile(fname, type, max) end function vim.fn.readfile(fname, type, max) end
--- {func} is called for every item in {object}, which can be a --- {func} is called for every item in {object}, which can be a
--- |List| or a |Blob|. {func} is called with two arguments: the --- |String|, |List| or a |Blob|. {func} is called with two arguments:
--- result so far and current item. After processing all items --- the result so far and current item. After processing all
--- the result is returned. --- items the result is returned.
--- ---
--- {initial} is the initial result. When omitted, the first item --- {initial} is the initial result. When omitted, the first item
--- in {object} is used and {func} is first called for the second --- 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([1, 3, 5], { acc, val -> acc + val })
--- echo reduce(['x', 'y'], { acc, val -> acc .. val }, 'a') --- echo reduce(['x', 'y'], { acc, val -> acc .. val }, 'a')
--- echo reduce(0z1122, { acc, val -> 2 * acc + val }) --- echo reduce(0z1122, { acc, val -> 2 * acc + val })
--- echo reduce('xyz', { acc, val -> acc .. ',' .. val })
--- < --- <
--- ---
--- @param object any --- @param object any

View File

@ -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"); = N_("E1203: Dot can only be used on a dictionary: %s");
static const char e_empty_function_name[] static const char e_empty_function_name[]
= N_("E1192: 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"); = N_("E1250: Argument of %s must be a List, String, Dictionary or Blob");
static char * const namespace_char = "abglstvw"; 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; int len;
for (const char *p = tv_get_string(&argvars[0]); *p != NUL; p += len) { for (const char *p = tv_get_string(&argvars[0]); *p != NUL; p += len) {
len = utfc_ptr2len(p); len = utfc_ptr2len(p);
typval_T tv = { typval_T tv = {
.v_type = VAR_STRING, .v_type = VAR_STRING,
.v_lock = VAR_UNLOCKED, .v_lock = VAR_UNLOCKED,

View File

@ -7876,9 +7876,9 @@ M.funcs = {
tags = { 'E998' }, tags = { 'E998' },
desc = [=[ desc = [=[
{func} is called for every item in {object}, which can be a {func} is called for every item in {object}, which can be a
|List| or a |Blob|. {func} is called with two arguments: the |String|, |List| or a |Blob|. {func} is called with two arguments:
result so far and current item. After processing all items the result so far and current item. After processing all
the result is returned. items the result is returned.
{initial} is the initial result. When omitted, the first item {initial} is the initial result. When omitted, the first item
in {object} is used and {func} is first called for the second 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([1, 3, 5], { acc, val -> acc + val })
echo reduce(['x', 'y'], { acc, val -> acc .. val }, 'a') echo reduce(['x', 'y'], { acc, val -> acc .. val }, 'a')
echo reduce(0z1122, { acc, val -> 2 * acc + val }) echo reduce(0z1122, { acc, val -> 2 * acc + val })
echo reduce('xyz', { acc, val -> acc .. ',' .. val })
< <
]=], ]=],
name = 'reduce', name = 'reduce',

View File

@ -150,8 +150,12 @@ static const char *e_invalwindow = N_("E957: Invalid window number");
static const char e_invalid_submatch_number_nr[] static const char e_invalid_submatch_number_nr[]
= N_("E935: Invalid submatch number: %d"); = 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_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[] static const char e_missing_function_argument[]
= N_("E1132: 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 /// 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(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) 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) { const int called_emsg_start = called_emsg;
emsg(_(e_listblobreq));
return; 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; const char *func_name;
@ -6217,7 +6226,6 @@ static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (l != NULL) { if (l != NULL) {
const VarLockStatus prev_locked = tv_list_locked(l); 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 tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here
for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { 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); 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 { } else {
const blob_T *const b = argvars[0].vval.v_blob; const blob_T *const b = argvars[0].vval.v_blob;
int i; int i;

View File

@ -1,4 +1,5 @@
" Tests for the List and Dict types " Tests for the List and Dict types
scriptencoding utf-8
source vim9.vim source vim9.vim
@ -900,7 +901,7 @@ func Test_reverse_sort_uniq()
call assert_fails("call sort([1, 2], function('min'))", "E118:") call assert_fails("call sort([1, 2], function('min'))", "E118:")
endfunc endfunc
" reduce a list or a blob " reduce a list, blob or string
func Test_reduce() func Test_reduce()
let lines =<< trim END let lines =<< trim END
call assert_equal(1, reduce([], LSTART acc, val LMIDDLE acc + val LEND, 1)) 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(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(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 END
call CheckLegacyAndVim9Success(lines) 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([], { 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(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({}, { acc, val -> acc + val }, 1)", 'E1098:')
call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E897:') call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E1098:')
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("call reduce([1, 2], 'Xdoes_not_exist')", 'E117:')
call assert_fails("echo reduce(0z01, { acc, val -> 2 * acc + val }, '')", 'E39:') 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] let g:lut = [1, 2, 3, 4]
func EvilRemove() func EvilRemove()
call remove(g:lut, 1) call remove(g:lut, 1)