]> granicus.if.org Git - vim/commitdiff
patch 8.2.3848: cannot use reduce() for a string v8.2.3848
authorrbtnn <naru123456789@gmail.com>
Sat, 18 Dec 2021 18:33:46 +0000 (18:33 +0000)
committerBram Moolenaar <Bram@vim.org>
Sat, 18 Dec 2021 18:33:46 +0000 (18:33 +0000)
Problem:    Cannot use reduce() for a string.
Solution:   Make reduce() work with a string. (Naruhiko Nishino, closes #9366)

runtime/doc/eval.txt
src/errors.h
src/evalfunc.c
src/list.c
src/proto/typval.pro
src/testdir/test_listdict.vim
src/testdir/test_vim9_builtin.vim
src/typval.c
src/version.c

index 6765bdcf7e6141271b9ab7ae6472744659e8451f..d54523f9652c9d3a098663c7bca74a46b9a2ec4f 100644 (file)
@@ -8959,9 +8959,9 @@ readfile({fname} [, {type} [, {max}]])
 
 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
@@ -8972,6 +8972,7 @@ reduce({object}, {func} [, {initial}])                    *reduce()* *E998*
                        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)
index 2ca71f15245e43f2d68cd3c8e63ed515ac5adb10..3fd8e5a01df3b3e79fc22e51eeb8f4ef151586ca 100644 (file)
@@ -843,4 +843,8 @@ EXTERN char e_highlight_group_name_too_long[]
 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"));
+       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"));
index 12236f73a529a8bff97fb94ba72448401083d35f..e670381859978e17ec42d74be66c315919c757ec 100644 (file)
@@ -464,6 +464,21 @@ arg_list_or_dict_or_blob_or_string(type_T *type, argcontext_T *context)
     return FAIL;
 }
 
+/*
+ * 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.
  */
@@ -817,7 +832,7 @@ static argcheck_T arg2_mapfilter[] = {arg_list_or_dict_or_blob_or_string, NULL};
 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 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};
index fafe4655a66ebe3046bdc5e441b3ca23735f06f5..484be83311f4670a2c0f44c36d1efc3e01fb7cab 100644 (file)
@@ -313,6 +313,28 @@ listitem_alloc(void)
     return ALLOC_ONE(listitem_T);
 }
 
+/*
+ * 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,7 +2514,6 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
            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
@@ -2503,16 +2524,9 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
            {
                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);
+               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,12 +3262,17 @@ f_reduce(typval_T *argvars, typval_T *rettv)
     partial_T   *partial = NULL;
     funcexe_T  funcexe;
     typval_T   argv[3];
+    int                r;
+    int                called_emsg_start = called_emsg;
 
-    if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB)
-    {
-       emsg(_(e_listblobreq));
+    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,8 +3297,6 @@ f_reduce(typval_T *argvars, typval_T *rettv)
     {
        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);
@@ -3319,6 +3336,43 @@ f_reduce(typval_T *argvars, typval_T *rettv)
            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;
index 675ad1248c6b37de4bb3706a75ea7cfa7d0379ea..a62f3dac8ceb21d5cd57e577320a0f4fea18825e 100644 (file)
@@ -34,6 +34,7 @@ int check_for_opt_lnum_arg(typval_T *args, int idx);
 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);
index 30f47adc6b4cb8e31a668ba98a5ea73d30140eab..c7673132595333f205e8de8a1e8fe7ca8c6eff9d 100644 (file)
@@ -1,4 +1,5 @@
 " Tests for the List and Dict types
+scriptencoding utf-8
 
 source vim9.vim
 
@@ -936,7 +937,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))
@@ -959,6 +960,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, test_null_string()))
   END
   call CheckLegacyAndVim9Success(lines)
 
@@ -967,13 +978,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(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)", '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)
index aa5af0d8931f7e97e94e2022fda4f89a2dd1520a..a6d898475464122bb0b2f0cea7c27a4326d21caf 100644 (file)
@@ -1232,7 +1232,7 @@ def Wrong_dict_key_type(items: list<number>): list<number>
 enddef
 
 def Test_filter()
-  CheckDefAndScriptFailure2(['filter(1.1, "1")'], 'E1013: Argument 1: type mismatch, expected list<any> but got float', 'E1228: List, Dictionary, Blob or String required for argument 1')
+  CheckDefAndScriptFailure2(['filter(1.1, "1")'], 'E1013: Argument 1: type mismatch, expected list<any> 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,9 +2028,9 @@ enddef
 
 def Test_map()
   if has('channel')
-    CheckDefAndScriptFailure2(['map(test_null_channel(), "1")'], 'E1013: Argument 1: type mismatch, expected list<any> but got channel', 'E1228: List, Dictionary, Blob or String required for argument 1')
+    CheckDefAndScriptFailure2(['map(test_null_channel(), "1")'], 'E1013: Argument 1: type mismatch, expected list<any> 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<any> but got number', 'E1228: List, Dictionary, Blob or String required for argument 1')
+  CheckDefAndScriptFailure2(['map(1, "1")'], 'E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1251: List, Dictionary, Blob or String required for argument 1')
 enddef
 
 def Test_map_failure()
@@ -2147,9 +2147,9 @@ enddef
 
 def Test_mapnew()
   if has('channel')
-    CheckDefAndScriptFailure2(['mapnew(test_null_job(), "1")'], 'E1013: Argument 1: type mismatch, expected list<any> but got job', 'E1228: List, Dictionary, Blob or String required for argument 1')
+    CheckDefAndScriptFailure2(['mapnew(test_null_job(), "1")'], 'E1013: Argument 1: type mismatch, expected list<any> 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<any> but got number', 'E1228: List, Dictionary, Blob or String required for argument 1')
+  CheckDefAndScriptFailure2(['mapnew(1, "1")'], 'E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1251: List, Dictionary, Blob or String required for argument 1')
 enddef
 
 def Test_mapset()
@@ -2682,7 +2682,7 @@ def Test_readfile()
 enddef
 
 def Test_reduce()
-  CheckDefAndScriptFailure2(['reduce({a: 10}, "1")'], 'E1013: Argument 1: type mismatch, expected list<any> but got dict<number>', 'E897: List or Blob required')
+  CheckDefAndScriptFailure2(['reduce({a: 10}, "1")'], 'E1013: Argument 1: type mismatch, expected list<any> but got dict<number>', '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
index 09381f27927f706f03b23c63e67702b8c089c74a..7716873bbb9b00444676d37ce9171f147b588734 100644 (file)
@@ -662,6 +662,23 @@ check_for_string_or_list_arg(typval_T *args, int idx)
     return OK;
 }
 
+/*
+ * 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'
  */
@@ -697,10 +714,7 @@ check_for_string_or_number_or_list_arg(typval_T *args, int idx)
            && 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));
+       semsg(_(e_string_number_or_list_required_for_argument_nr), idx + 1);
        return FAIL;
     }
     return OK;
@@ -742,10 +756,7 @@ check_for_list_or_blob_arg(typval_T *args, int idx)
 {
     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));
+       semsg(_(e_list_or_blob_required_for_argument_nr), idx + 1);
        return FAIL;
     }
     return OK;
index 8ae0a86f442ea520563f2ae936ebbe285777033d..d99e18c8adcf8f76f246996c0496bb730dfa4344 100644 (file)
@@ -749,6 +749,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3848,
 /**/
     3847,
 /**/