1
0
forked from aniani/vim

patch 8.2.3849: functions implementing reduce and map are too long

Problem:    Functions implementing reduce and map are too long.
Solution:   Use a function for each type of value.  Add a few more test cases
            and add to the help. (Yegappan Lakshmanan, closes #9370)
This commit is contained in:
Yegappan Lakshmanan
2021-12-19 10:35:15 +00:00
committed by Bram Moolenaar
parent 0ccb5842f5
commit 389b72196e
4 changed files with 583 additions and 474 deletions

View File

@@ -4893,7 +4893,8 @@ filter({expr1}, {expr2}) *filter()*
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. For a |Blob| |v:key| has the index of the
current byte.
current byte. For a |String| |v:key| has the index of the
current character.
Examples: >
call filter(mylist, 'v:val !~ "OLD"')
< Removes the items where "OLD" appears. >
@@ -7588,7 +7589,8 @@ map({expr1}, {expr2}) *map()*
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. For a |Blob| |v:key| has the index of the
current byte.
current byte. For a |String| |v:key| has the index of the
current character.
Example: >
:call map(mylist, '"> " . v:val . " <"')
< This puts "> " before and " <" after each item in "mylist".
@@ -8959,9 +8961,9 @@ readfile({fname} [, {type} [, {max}]])
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.
|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

View File

@@ -2274,122 +2274,42 @@ theend:
}
/*
* Implementation of map() and filter().
* Implementation of map() and filter() for a Dict.
*/
static void
filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
filter_map_dict(
dict_T *d,
filtermap_T filtermap,
type_T *argtype,
char *func_name,
char_u *arg_errmsg,
typval_T *expr,
typval_T *rettv)
{
typval_T *expr;
listitem_T *li, *nli;
list_T *l = NULL;
dictitem_T *di;
int prev_lock;
dict_T *d_ret = NULL;
hashtab_T *ht;
hashitem_T *hi;
dict_T *d = NULL;
blob_T *b = NULL;
int rem;
dictitem_T *di;
int todo;
char *func_name = filtermap == FILTERMAP_MAP ? "map()"
: filtermap == FILTERMAP_MAPNEW ? "mapnew()"
: "filter()";
char_u *arg_errmsg = (char_u *)(filtermap == FILTERMAP_MAP
? N_("map() argument")
: filtermap == FILTERMAP_MAPNEW
? N_("mapnew() argument")
: N_("filter() argument"));
int save_did_emsg;
int idx = 0;
type_T *type = NULL;
garray_T type_list;
int rem;
// map() and filter() return the first argument, also on failure.
if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING)
copy_tv(&argvars[0], rettv);
if (in_vim9script()
&& (check_for_list_or_dict_or_blob_or_string_arg(argvars, 0)
== FAIL))
return;
if (filtermap == FILTERMAP_MAP && in_vim9script())
{
// Check that map() does not change the type of the dict.
ga_init2(&type_list, sizeof(type_T *), 10);
type = typval2type(argvars, get_copyID(), &type_list, TRUE);
}
if (argvars[0].v_type == VAR_BLOB)
{
if (filtermap == FILTERMAP_MAPNEW)
{
rettv->v_type = VAR_BLOB;
rettv->vval.v_blob = NULL;
}
if ((b = argvars[0].vval.v_blob) == NULL)
goto theend;
}
else if (argvars[0].v_type == VAR_LIST)
{
if (filtermap == FILTERMAP_MAPNEW)
{
rettv->v_type = VAR_LIST;
rettv->vval.v_list = NULL;
}
if ((l = argvars[0].vval.v_list) == NULL
|| (filtermap == FILTERMAP_FILTER
&& value_check_lock(l->lv_lock, arg_errmsg, TRUE)))
goto theend;
}
else if (argvars[0].v_type == VAR_DICT)
{
if (filtermap == FILTERMAP_MAPNEW)
{
rettv->v_type = VAR_DICT;
rettv->vval.v_dict = NULL;
}
if ((d = argvars[0].vval.v_dict) == NULL
if (d == NULL
|| (filtermap == FILTERMAP_FILTER
&& value_check_lock(d->dv_lock, arg_errmsg, TRUE)))
goto theend;
}
else if (argvars[0].v_type == VAR_STRING)
{
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
}
else
{
semsg(_(e_argument_of_str_must_be_list_string_dictionary_or_blob),
func_name);
goto theend;
}
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)
{
typval_T save_val;
typval_T save_key;
prepare_vimvar(VV_VAL, &save_val);
prepare_vimvar(VV_KEY, &save_key);
// We reset "did_emsg" to be able to detect whether an error
// occurred during evaluation of the expression.
save_did_emsg = did_emsg;
did_emsg = FALSE;
if (argvars[0].v_type == VAR_DICT)
{
int prev_lock = d->dv_lock;
dict_T *d_ret = NULL;
prev_lock = d->dv_lock;
if (filtermap == FILTERMAP_MAPNEW)
{
if (rettv_dict_alloc(rettv) == FAIL)
goto theend;
return;
d_ret = rettv->vval.v_dict;
}
@@ -2425,8 +2345,8 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
}
if (filtermap == FILTERMAP_MAP)
{
if (type != NULL && check_typval_arg_type(
type->tt_member, &newtv,
if (argtype != NULL && check_typval_arg_type(
argtype->tt_member, &newtv,
func_name, 0) == FAIL)
{
clear_tv(&newtv);
@@ -2458,17 +2378,38 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
hash_unlock(ht);
d->dv_lock = prev_lock;
}
else if (argvars[0].v_type == VAR_BLOB)
/*
* Implementation of map() and filter() for a Blob.
*/
static void
filter_map_blob(
blob_T *blob_arg,
filtermap_T filtermap,
typval_T *expr,
typval_T *rettv)
{
blob_T *b;
int i;
typval_T tv;
varnumber_T val;
blob_T *b_ret = b;
blob_T *b_ret;
int idx = 0;
int rem;
if (filtermap == FILTERMAP_MAPNEW)
{
rettv->v_type = VAR_BLOB;
rettv->vval.v_blob = NULL;
}
if ((b = blob_arg) == NULL)
return;
b_ret = b;
if (filtermap == FILTERMAP_MAPNEW)
{
if (blob_copy(b, rettv) == FAIL)
goto theend;
return;
b_ret = rettv->vval.v_blob;
}
@@ -2499,7 +2440,7 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
}
else if (rem)
{
char_u *p = (char_u *)argvars[0].vval.v_blob->bv_ga.ga_data;
char_u *p = (char_u *)blob_arg->bv_ga.ga_data;
mch_memmove(p + i, p + i + 1,
(size_t)b->bv_ga.ga_len - i - 1);
@@ -2509,24 +2450,38 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
++idx;
}
}
else if (argvars[0].v_type == VAR_STRING)
/*
* Implementation of map() and filter() for a String.
*/
static void
filter_map_string(
char_u *str,
filtermap_T filtermap,
typval_T *expr,
typval_T *rettv)
{
char_u *p;
typval_T tv;
garray_T ga;
int len;
int len = 0;
int idx = 0;
int rem;
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
// set_vim_var_nr() doesn't set the type
set_vim_var_type(VV_KEY, VAR_NUMBER);
ga_init2(&ga, (int)sizeof(char), 80);
for (p = tv_get_string(&argvars[0]); *p != NUL; p += len)
for (p = str; *p != NUL; p += len)
{
typval_T newtv;
if (tv_get_first_char(p, &tv) == FAIL)
break;
len = STRLEN(tv.vval.v_string);
len = (int)STRLEN(tv.vval.v_string);
set_vim_var_nr(VV_KEY, idx);
if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
@@ -2561,15 +2516,42 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
ga_append(&ga, NUL);
rettv->vval.v_string = ga.ga_data;
}
else // argvars[0].v_type == VAR_LIST
/*
* Implementation of map() and filter() for a List.
*/
static void
filter_map_list(
list_T *l,
filtermap_T filtermap,
type_T *argtype,
char *func_name,
char_u *arg_errmsg,
typval_T *expr,
typval_T *rettv)
{
int prev_lock = l->lv_lock;
int prev_lock;
list_T *l_ret = NULL;
int idx = 0;
int rem;
listitem_T *li, *nli;
if (filtermap == FILTERMAP_MAPNEW)
{
rettv->v_type = VAR_LIST;
rettv->vval.v_list = NULL;
}
if (l == NULL
|| (filtermap == FILTERMAP_FILTER
&& value_check_lock(l->lv_lock, arg_errmsg, TRUE)))
return;
prev_lock = l->lv_lock;
if (filtermap == FILTERMAP_MAPNEW)
{
if (rettv_list_alloc(rettv) == FAIL)
goto theend;
return;
l_ret = rettv->vval.v_list;
}
// set_vim_var_nr() doesn't set the type
@@ -2612,9 +2594,9 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
}
if (filtermap != FILTERMAP_FILTER)
{
if (filtermap == FILTERMAP_MAP && type != NULL
if (filtermap == FILTERMAP_MAP && argtype != NULL
&& check_typval_arg_type(
type->tt_member, &newtv,
argtype->tt_member, &newtv,
func_name, 0) == FAIL)
{
clear_tv(&newtv);
@@ -2658,8 +2640,8 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
}
if (filtermap == FILTERMAP_MAP)
{
if (type != NULL && check_typval_arg_type(
type->tt_member, &newtv, func_name, 0) == FAIL)
if (argtype != NULL && check_typval_arg_type(
argtype->tt_member, &newtv, func_name, 0) == FAIL)
{
clear_tv(&newtv);
break;
@@ -2684,6 +2666,80 @@ filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
l->lv_lock = prev_lock;
}
/*
* Implementation of map() and filter().
*/
static void
filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
{
typval_T *expr;
char *func_name = filtermap == FILTERMAP_MAP ? "map()"
: filtermap == FILTERMAP_MAPNEW ? "mapnew()"
: "filter()";
char_u *arg_errmsg = (char_u *)(filtermap == FILTERMAP_MAP
? N_("map() argument")
: filtermap == FILTERMAP_MAPNEW
? N_("mapnew() argument")
: N_("filter() argument"));
int save_did_emsg;
type_T *type = NULL;
garray_T type_list;
// map() and filter() return the first argument, also on failure.
if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING)
copy_tv(&argvars[0], rettv);
if (in_vim9script()
&& (check_for_list_or_dict_or_blob_or_string_arg(argvars, 0)
== FAIL))
return;
if (filtermap == FILTERMAP_MAP && in_vim9script())
{
// Check that map() does not change the type of the dict.
ga_init2(&type_list, sizeof(type_T *), 10);
type = typval2type(argvars, get_copyID(), &type_list, TRUE);
}
if (argvars[0].v_type != VAR_BLOB
&& argvars[0].v_type != VAR_LIST
&& argvars[0].v_type != VAR_DICT
&& argvars[0].v_type != VAR_STRING)
{
semsg(_(e_argument_of_str_must_be_list_string_dictionary_or_blob),
func_name);
goto theend;
}
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)
{
typval_T save_val;
typval_T save_key;
prepare_vimvar(VV_VAL, &save_val);
prepare_vimvar(VV_KEY, &save_key);
// We reset "did_emsg" to be able to detect whether an error
// occurred during evaluation of the expression.
save_did_emsg = did_emsg;
did_emsg = FALSE;
if (argvars[0].v_type == VAR_DICT)
filter_map_dict(argvars[0].vval.v_dict, filtermap, type, func_name,
arg_errmsg, expr, rettv);
else if (argvars[0].v_type == VAR_BLOB)
filter_map_blob(argvars[0].vval.v_blob, filtermap, expr, rettv);
else if (argvars[0].v_type == VAR_STRING)
filter_map_string(tv_get_string(&argvars[0]), filtermap, expr,
rettv);
else // argvars[0].v_type == VAR_LIST
filter_map_list(argvars[0].vval.v_list, filtermap, type, func_name,
arg_errmsg, expr, rettv);
restore_vimvar(VV_KEY, &save_key);
restore_vimvar(VV_VAL, &save_val);
@@ -3252,51 +3308,22 @@ f_reverse(typval_T *argvars, typval_T *rettv)
}
/*
* "reduce(list, { accumulator, element -> value } [, initial])" function
* reduce() on a List
*/
void
f_reduce(typval_T *argvars, typval_T *rettv)
{
typval_T initial;
char_u *func_name;
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;
else if (argvars[1].v_type == VAR_PARTIAL)
{
partial = argvars[1].vval.v_partial;
func_name = partial_name(partial);
}
else
func_name = tv_get_string(&argvars[1]);
if (func_name == NULL || *func_name == NUL)
{
emsg(_(e_missing_function_argument));
return;
}
CLEAR_FIELD(funcexe);
funcexe.fe_evaluate = TRUE;
funcexe.fe_partial = partial;
if (argvars[0].v_type == VAR_LIST)
static void
reduce_list(
typval_T *argvars,
char_u *func_name,
funcexe_T *funcexe,
typval_T *rettv)
{
list_T *l = argvars[0].vval.v_list;
listitem_T *li = NULL;
typval_T initial;
typval_T argv[3];
int r;
int called_emsg_start = called_emsg;
int prev_locked;
if (l != NULL)
CHECK_LIST_MATERIALIZE(l);
@@ -3318,9 +3345,10 @@ f_reduce(typval_T *argvars, typval_T *rettv)
}
copy_tv(&initial, rettv);
if (l != NULL)
{
int prev_locked = l->lv_lock;
if (l == NULL)
return;
prev_locked = l->lv_lock;
l->lv_lock = VAR_FIXED; // disallow the list changing here
for ( ; li != NULL; li = li->li_next)
@@ -3328,18 +3356,29 @@ f_reduce(typval_T *argvars, typval_T *rettv)
argv[0] = *rettv;
argv[1] = li->li_tv;
rettv->v_type = VAR_UNKNOWN;
r = call_func(func_name, -1, rettv, 2, argv, &funcexe);
r = call_func(func_name, -1, rettv, 2, argv, funcexe);
clear_tv(&argv[0]);
if (r == FAIL || called_emsg != called_emsg_start)
break;
}
l->lv_lock = prev_locked;
}
}
else if (argvars[0].v_type == VAR_STRING)
/*
* reduce() on a String
*/
static void
reduce_string(
typval_T *argvars,
char_u *func_name,
funcexe_T *funcexe,
typval_T *rettv)
{
char_u *p = tv_get_string(&argvars[0]);
int len;
typval_T argv[3];
int r;
int called_emsg_start = called_emsg;
if (argvars[2].v_type == VAR_UNKNOWN)
{
@@ -3365,17 +3404,30 @@ f_reduce(typval_T *argvars, typval_T *rettv)
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);
len = (int)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;
return;
}
}
else
/*
* reduce() on a Blob
*/
static void
reduce_blob(
typval_T *argvars,
char_u *func_name,
funcexe_T *funcexe,
typval_T *rettv)
{
blob_T *b = argvars[0].vval.v_blob;
int called_emsg_start = called_emsg;
int r;
typval_T initial;
typval_T argv[3];
int i;
if (argvars[2].v_type == VAR_UNKNOWN)
@@ -3401,18 +3453,68 @@ f_reduce(typval_T *argvars, typval_T *rettv)
}
copy_tv(&initial, rettv);
if (b != NULL)
{
if (b == NULL)
return;
for ( ; i < b->bv_ga.ga_len; i++)
{
argv[0] = *rettv;
argv[1].v_type = VAR_NUMBER;
argv[1].vval.v_number = blob_get(b, i);
if (call_func(func_name, -1, rettv, 2, argv, &funcexe) == FAIL)
r = call_func(func_name, -1, rettv, 2, argv, funcexe);
clear_tv(&argv[0]);
if (r == FAIL || called_emsg != called_emsg_start)
return;
}
}
/*
* "reduce(list, { accumulator, element -> value } [, initial])" function
*/
void
f_reduce(typval_T *argvars, typval_T *rettv)
{
char_u *func_name;
partial_T *partial = NULL;
funcexe_T funcexe;
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()");
return;
}
if (argvars[1].v_type == VAR_FUNC)
func_name = argvars[1].vval.v_string;
else if (argvars[1].v_type == VAR_PARTIAL)
{
partial = argvars[1].vval.v_partial;
func_name = partial_name(partial);
}
else
func_name = tv_get_string(&argvars[1]);
if (func_name == NULL || *func_name == NUL)
{
emsg(_(e_missing_function_argument));
return;
}
CLEAR_FIELD(funcexe);
funcexe.fe_evaluate = TRUE;
funcexe.fe_partial = partial;
if (argvars[0].v_type == VAR_LIST)
reduce_list(argvars, func_name, &funcexe, rettv);
else if (argvars[0].v_type == VAR_STRING)
reduce_string(argvars, func_name, &funcexe, rettv);
else
reduce_blob(argvars, func_name, &funcexe, rettv);
}
#endif // defined(FEAT_EVAL)

View File

@@ -994,6 +994,9 @@ func Test_reduce()
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:')
call assert_fails("call reduce('abc', { a, v -> a10}, '')", 'E121:')
call assert_fails("call reduce(0z01, { a, v -> a10}, 1)", 'E121:')
call assert_fails("call reduce([1], { a, v -> a10}, '')", 'E121:')
let g:lut = [1, 2, 3, 4]
func EvilRemove()

View File

@@ -749,6 +749,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
3849,
/**/
3848,
/**/