mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge pull request #24749 from zeertzjq/vim-8.2.3848
vim-patch:8.2.{3848,partial:3849}: reduce() on a string
This commit is contained in:
commit
200dafb8a5
14
runtime/doc/builtin.txt
generated
14
runtime/doc/builtin.txt
generated
@ -1635,8 +1635,8 @@ filter({expr1}, {expr2}) *filter()*
|
|||||||
of the current item. For a |Dictionary| |v:key| has the key
|
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
|
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
|
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: >vim
|
Examples: >vim
|
||||||
call filter(mylist, 'v:val !~ "OLD"')
|
call filter(mylist, 'v:val !~ "OLD"')
|
||||||
< Removes the items where "OLD" appears. >vim
|
< Removes the items where "OLD" appears. >vim
|
||||||
@ -4080,7 +4080,8 @@ map({expr1}, {expr2}) *map()*
|
|||||||
of the current item. For a |Dictionary| |v:key| has the key
|
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
|
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
|
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: >vim
|
Example: >vim
|
||||||
call map(mylist, '"> " .. v:val .. " <"')
|
call map(mylist, '"> " .. v:val .. " <"')
|
||||||
< This puts "> " before and " <" after each item in "mylist".
|
< This puts "> " before and " <" after each item in "mylist".
|
||||||
@ -5514,9 +5515,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
|
||||||
result so far and current item. After processing all items
|
arguments: the result so far and current item. After
|
||||||
the result is returned.
|
processing all 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 +5528,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()*
|
||||||
|
14
runtime/lua/vim/_meta/vimfn.lua
generated
14
runtime/lua/vim/_meta/vimfn.lua
generated
@ -2017,8 +2017,8 @@ function vim.fn.filewritable(file) end
|
|||||||
--- of the current item. For a |Dictionary| |v:key| has the key
|
--- 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
|
--- 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
|
--- 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: >vim
|
--- Examples: >vim
|
||||||
--- call filter(mylist, 'v:val !~ "OLD"')
|
--- call filter(mylist, 'v:val !~ "OLD"')
|
||||||
--- <Removes the items where "OLD" appears. >vim
|
--- <Removes the items where "OLD" appears. >vim
|
||||||
@ -4937,7 +4937,8 @@ function vim.fn.log10(expr) end
|
|||||||
--- of the current item. For a |Dictionary| |v:key| has the key
|
--- 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
|
--- 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
|
--- 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: >vim
|
--- Example: >vim
|
||||||
--- call map(mylist, '"> " .. v:val .. " <"')
|
--- call map(mylist, '"> " .. v:val .. " <"')
|
||||||
--- <This puts "> " before and " <" after each item in "mylist".
|
--- <This puts "> " before and " <" after each item in "mylist".
|
||||||
@ -6551,9 +6552,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
|
||||||
--- result so far and current item. After processing all items
|
--- arguments: the result so far and current item. After
|
||||||
--- the result is returned.
|
--- processing all 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 +6565,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
|
||||||
|
484
src/nvim/eval.c
484
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");
|
= 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";
|
||||||
@ -5026,15 +5026,253 @@ void assert_error(garray_T *gap)
|
|||||||
tv_list_append_string(vimvars[VV_ERRORS].vv_list, gap->ga_data, (ptrdiff_t)gap->ga_len);
|
tv_list_append_string(vimvars[VV_ERRORS].vv_list, gap->ga_data, (ptrdiff_t)gap->ga_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implementation of map() and filter() for a Dict.
|
||||||
|
static void filter_map_dict(dict_T *d, filtermap_T filtermap, const char *func_name,
|
||||||
|
const char *arg_errmsg, typval_T *expr, typval_T *rettv)
|
||||||
|
{
|
||||||
|
if (filtermap == FILTERMAP_MAPNEW) {
|
||||||
|
rettv->v_type = VAR_DICT;
|
||||||
|
rettv->vval.v_dict = NULL;
|
||||||
|
}
|
||||||
|
if (d == NULL
|
||||||
|
|| (filtermap == FILTERMAP_FILTER
|
||||||
|
&& value_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VarLockStatus prev_lock = d->dv_lock;
|
||||||
|
dict_T *d_ret = NULL;
|
||||||
|
|
||||||
|
if (filtermap == FILTERMAP_MAPNEW) {
|
||||||
|
tv_dict_alloc_ret(rettv);
|
||||||
|
d_ret = rettv->vval.v_dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
vimvars[VV_KEY].vv_type = VAR_STRING;
|
||||||
|
|
||||||
|
if (filtermap != FILTERMAP_FILTER && d->dv_lock == VAR_UNLOCKED) {
|
||||||
|
d->dv_lock = VAR_LOCKED;
|
||||||
|
}
|
||||||
|
hash_lock(&d->dv_hashtab);
|
||||||
|
TV_DICT_ITER(d, di, {
|
||||||
|
if (filtermap == FILTERMAP_MAP
|
||||||
|
&& (value_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE)
|
||||||
|
|| var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
vimvars[VV_KEY].vv_str = xstrdup(di->di_key);
|
||||||
|
typval_T newtv;
|
||||||
|
bool rem;
|
||||||
|
int r = filter_map_one(&di->di_tv, expr, filtermap, &newtv, &rem);
|
||||||
|
tv_clear(&vimvars[VV_KEY].vv_tv);
|
||||||
|
if (r == FAIL || did_emsg) {
|
||||||
|
tv_clear(&newtv);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (filtermap == FILTERMAP_MAP) {
|
||||||
|
// map(): replace the dict item value
|
||||||
|
tv_clear(&di->di_tv);
|
||||||
|
newtv.v_lock = VAR_UNLOCKED;
|
||||||
|
di->di_tv = newtv;
|
||||||
|
} else if (filtermap == FILTERMAP_MAPNEW) {
|
||||||
|
// mapnew(): add the item value to the new dict
|
||||||
|
r = tv_dict_add_tv(d_ret, di->di_key, strlen(di->di_key), &newtv);
|
||||||
|
tv_clear(&newtv);
|
||||||
|
if (r == FAIL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (filtermap == FILTERMAP_FILTER && rem) {
|
||||||
|
// filter(false): remove the item from the dict
|
||||||
|
if (var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE)
|
||||||
|
|| var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tv_dict_item_remove(d, di);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
hash_unlock(&d->dv_hashtab);
|
||||||
|
d->dv_lock = prev_lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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)
|
||||||
|
{
|
||||||
|
if (filtermap == FILTERMAP_MAPNEW) {
|
||||||
|
rettv->v_type = VAR_BLOB;
|
||||||
|
rettv->vval.v_blob = NULL;
|
||||||
|
}
|
||||||
|
blob_T *b;
|
||||||
|
if ((b = blob_arg) == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
blob_T *b_ret = b;
|
||||||
|
|
||||||
|
if (filtermap == FILTERMAP_MAPNEW) {
|
||||||
|
tv_blob_copy(b, rettv);
|
||||||
|
b_ret = rettv->vval.v_blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
vimvars[VV_KEY].vv_type = VAR_NUMBER;
|
||||||
|
|
||||||
|
for (int i = 0, idx = 0; i < b->bv_ga.ga_len; i++) {
|
||||||
|
const varnumber_T val = tv_blob_get(b, i);
|
||||||
|
typval_T tv = {
|
||||||
|
.v_type = VAR_NUMBER,
|
||||||
|
.v_lock = VAR_UNLOCKED,
|
||||||
|
.vval.v_number = val,
|
||||||
|
};
|
||||||
|
vimvars[VV_KEY].vv_nr = idx;
|
||||||
|
typval_T newtv;
|
||||||
|
bool rem;
|
||||||
|
if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
|
||||||
|
|| did_emsg) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL) {
|
||||||
|
tv_clear(&newtv);
|
||||||
|
emsg(_(e_invalblob));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (filtermap != FILTERMAP_FILTER) {
|
||||||
|
if (newtv.vval.v_number != val) {
|
||||||
|
tv_blob_set(b_ret, i, (uint8_t)newtv.vval.v_number);
|
||||||
|
}
|
||||||
|
} else if (rem) {
|
||||||
|
char *const p = (char *)blob_arg->bv_ga.ga_data;
|
||||||
|
memmove(p + i, p + i + 1, (size_t)(b->bv_ga.ga_len - i - 1));
|
||||||
|
b->bv_ga.ga_len--;
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementation of map() and filter() for a String.
|
||||||
|
static void filter_map_string(const char *str, filtermap_T filtermap, typval_T *expr,
|
||||||
|
typval_T *rettv)
|
||||||
|
{
|
||||||
|
rettv->v_type = VAR_STRING;
|
||||||
|
rettv->vval.v_string = NULL;
|
||||||
|
|
||||||
|
vimvars[VV_KEY].vv_type = VAR_NUMBER;
|
||||||
|
|
||||||
|
garray_T ga;
|
||||||
|
ga_init(&ga, (int)sizeof(char), 80);
|
||||||
|
int len = 0;
|
||||||
|
int idx = 0;
|
||||||
|
for (const char *p = str; *p != NUL; p += len) {
|
||||||
|
len = utfc_ptr2len(p);
|
||||||
|
typval_T tv = {
|
||||||
|
.v_type = VAR_STRING,
|
||||||
|
.v_lock = VAR_UNLOCKED,
|
||||||
|
.vval.v_string = xstrnsave(p, (size_t)len),
|
||||||
|
};
|
||||||
|
|
||||||
|
vimvars[VV_KEY].vv_nr = idx;
|
||||||
|
typval_T newtv;
|
||||||
|
bool rem;
|
||||||
|
if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
|
||||||
|
|| did_emsg) {
|
||||||
|
tv_clear(&newtv);
|
||||||
|
tv_clear(&tv);
|
||||||
|
break;
|
||||||
|
} else if (filtermap != FILTERMAP_FILTER) {
|
||||||
|
if (newtv.v_type != VAR_STRING) {
|
||||||
|
tv_clear(&newtv);
|
||||||
|
tv_clear(&tv);
|
||||||
|
emsg(_(e_stringreq));
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
ga_concat(&ga, newtv.vval.v_string);
|
||||||
|
}
|
||||||
|
} else if (!rem) {
|
||||||
|
ga_concat(&ga, tv.vval.v_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
tv_clear(&newtv);
|
||||||
|
tv_clear(&tv);
|
||||||
|
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
ga_append(&ga, NUL);
|
||||||
|
rettv->vval.v_string = ga.ga_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementation of map() and filter() for a List.
|
||||||
|
static void filter_map_list(list_T *l, filtermap_T filtermap, const char *func_name,
|
||||||
|
const char *arg_errmsg, typval_T *expr, typval_T *rettv)
|
||||||
|
{
|
||||||
|
if (filtermap == FILTERMAP_MAPNEW) {
|
||||||
|
rettv->v_type = VAR_LIST;
|
||||||
|
rettv->vval.v_list = NULL;
|
||||||
|
}
|
||||||
|
if (l == NULL
|
||||||
|
|| (filtermap == FILTERMAP_FILTER
|
||||||
|
&& value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VarLockStatus prev_lock = tv_list_locked(l);
|
||||||
|
list_T *l_ret = NULL;
|
||||||
|
|
||||||
|
if (filtermap == FILTERMAP_MAPNEW) {
|
||||||
|
tv_list_alloc_ret(rettv, kListLenUnknown);
|
||||||
|
l_ret = rettv->vval.v_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
vimvars[VV_KEY].vv_type = VAR_NUMBER;
|
||||||
|
|
||||||
|
if (filtermap != FILTERMAP_FILTER && tv_list_locked(l) == VAR_UNLOCKED) {
|
||||||
|
tv_list_set_lock(l, VAR_LOCKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
for (listitem_T *li = tv_list_first(l); li != NULL;) {
|
||||||
|
if (filtermap == FILTERMAP_MAP
|
||||||
|
&& value_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, TV_TRANSLATE)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
vimvars[VV_KEY].vv_nr = idx;
|
||||||
|
typval_T newtv;
|
||||||
|
bool rem;
|
||||||
|
if (filter_map_one(TV_LIST_ITEM_TV(li), expr, filtermap, &newtv, &rem) == FAIL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (did_emsg) {
|
||||||
|
tv_clear(&newtv);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (filtermap == FILTERMAP_MAP) {
|
||||||
|
// map(): replace the list item value
|
||||||
|
tv_clear(TV_LIST_ITEM_TV(li));
|
||||||
|
newtv.v_lock = VAR_UNLOCKED;
|
||||||
|
*TV_LIST_ITEM_TV(li) = newtv;
|
||||||
|
} else if (filtermap == FILTERMAP_MAPNEW) {
|
||||||
|
// mapnew(): append the list item value
|
||||||
|
tv_list_append_owned_tv(l_ret, newtv);
|
||||||
|
}
|
||||||
|
if (filtermap == FILTERMAP_FILTER && rem) {
|
||||||
|
li = tv_list_item_remove(l, li);
|
||||||
|
} else {
|
||||||
|
li = TV_LIST_ITEM_NEXT(l, li);
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
tv_list_set_lock(l, prev_lock);
|
||||||
|
}
|
||||||
|
|
||||||
/// Implementation of map() and filter().
|
/// Implementation of map() and filter().
|
||||||
static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
|
static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
|
||||||
{
|
{
|
||||||
list_T *l = NULL;
|
const char *const func_name = (filtermap == FILTERMAP_MAP
|
||||||
dict_T *d = NULL;
|
? "map()"
|
||||||
blob_T *b = NULL;
|
: (filtermap == FILTERMAP_MAPNEW
|
||||||
int rem = false;
|
? "mapnew()"
|
||||||
const char *const ermsg = (filtermap == FILTERMAP_MAP ? "map()"
|
: "filter()"));
|
||||||
: filtermap == FILTERMAP_MAPNEW ? "mapnew()" : "filter()");
|
|
||||||
const char *const arg_errmsg = (filtermap == FILTERMAP_MAP
|
const char *const arg_errmsg = (filtermap == FILTERMAP_MAP
|
||||||
? N_("map() argument")
|
? N_("map() argument")
|
||||||
: (filtermap == FILTERMAP_MAPNEW
|
: (filtermap == FILTERMAP_MAPNEW
|
||||||
@ -5046,39 +5284,11 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
|
|||||||
tv_copy(&argvars[0], rettv);
|
tv_copy(&argvars[0], rettv);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argvars[0].v_type == VAR_BLOB) {
|
if (argvars[0].v_type != VAR_BLOB
|
||||||
if (filtermap == FILTERMAP_MAPNEW) {
|
&& argvars[0].v_type != VAR_LIST
|
||||||
rettv->v_type = VAR_BLOB;
|
&& argvars[0].v_type != VAR_DICT
|
||||||
rettv->vval.v_blob = NULL;
|
&& argvars[0].v_type != VAR_STRING) {
|
||||||
}
|
semsg(_(e_argument_of_str_must_be_list_string_dictionary_or_blob), func_name);
|
||||||
if ((b = argvars[0].vval.v_blob) == NULL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} 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(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} 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
|
|
||||||
|| (filtermap == FILTERMAP_FILTER
|
|
||||||
&& value_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} 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), ermsg);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5087,7 +5297,6 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
|
|||||||
// message. Avoid a misleading error message for an empty string that
|
// message. Avoid a misleading error message for an empty string that
|
||||||
// was not passed as argument.
|
// was not passed as argument.
|
||||||
if (expr->v_type != VAR_UNKNOWN) {
|
if (expr->v_type != VAR_UNKNOWN) {
|
||||||
int idx = 0;
|
|
||||||
typval_T save_val;
|
typval_T save_val;
|
||||||
prepare_vimvar(VV_VAL, &save_val);
|
prepare_vimvar(VV_VAL, &save_val);
|
||||||
|
|
||||||
@ -5099,195 +5308,16 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
|
|||||||
typval_T save_key;
|
typval_T save_key;
|
||||||
prepare_vimvar(VV_KEY, &save_key);
|
prepare_vimvar(VV_KEY, &save_key);
|
||||||
if (argvars[0].v_type == VAR_DICT) {
|
if (argvars[0].v_type == VAR_DICT) {
|
||||||
const VarLockStatus prev_lock = d->dv_lock;
|
filter_map_dict(argvars[0].vval.v_dict, filtermap, func_name,
|
||||||
dict_T *d_ret = NULL;
|
arg_errmsg, expr, rettv);
|
||||||
|
|
||||||
if (filtermap == FILTERMAP_MAPNEW) {
|
|
||||||
tv_dict_alloc_ret(rettv);
|
|
||||||
d_ret = rettv->vval.v_dict;
|
|
||||||
}
|
|
||||||
|
|
||||||
vimvars[VV_KEY].vv_type = VAR_STRING;
|
|
||||||
|
|
||||||
if (filtermap != FILTERMAP_FILTER && d->dv_lock == VAR_UNLOCKED) {
|
|
||||||
d->dv_lock = VAR_LOCKED;
|
|
||||||
}
|
|
||||||
hashtab_T *ht = &d->dv_hashtab;
|
|
||||||
hash_lock(ht);
|
|
||||||
int todo = (int)ht->ht_used;
|
|
||||||
for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) {
|
|
||||||
if (!HASHITEM_EMPTY(hi)) {
|
|
||||||
todo--;
|
|
||||||
|
|
||||||
dictitem_T *di = TV_DICT_HI2DI(hi);
|
|
||||||
if (filtermap == FILTERMAP_MAP
|
|
||||||
&& (value_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE)
|
|
||||||
|| var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
vimvars[VV_KEY].vv_str = xstrdup(di->di_key);
|
|
||||||
typval_T newtv;
|
|
||||||
int r = filter_map_one(&di->di_tv, expr, filtermap, &newtv, &rem);
|
|
||||||
tv_clear(&vimvars[VV_KEY].vv_tv);
|
|
||||||
if (r == FAIL || did_emsg) {
|
|
||||||
tv_clear(&newtv);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (filtermap == FILTERMAP_MAP) {
|
|
||||||
// map(): replace the dict item value
|
|
||||||
tv_clear(&di->di_tv);
|
|
||||||
newtv.v_lock = VAR_UNLOCKED;
|
|
||||||
di->di_tv = newtv;
|
|
||||||
} else if (filtermap == FILTERMAP_MAPNEW) {
|
|
||||||
// mapnew(): add the item value to the new dict
|
|
||||||
r = tv_dict_add_tv(d_ret, di->di_key, strlen(di->di_key), &newtv);
|
|
||||||
tv_clear(&newtv);
|
|
||||||
if (r == FAIL) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (filtermap == FILTERMAP_FILTER && rem) {
|
|
||||||
// filter(false): remove the item from the dict
|
|
||||||
if (var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE)
|
|
||||||
|| var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
tv_dict_item_remove(d, di);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hash_unlock(ht);
|
|
||||||
d->dv_lock = prev_lock;
|
|
||||||
} else if (argvars[0].v_type == VAR_BLOB) {
|
} else if (argvars[0].v_type == VAR_BLOB) {
|
||||||
blob_T *b_ret = b;
|
filter_map_blob(argvars[0].vval.v_blob, filtermap, expr, rettv);
|
||||||
|
|
||||||
if (filtermap == FILTERMAP_MAPNEW) {
|
|
||||||
tv_blob_copy(b, rettv);
|
|
||||||
b_ret = rettv->vval.v_blob;
|
|
||||||
}
|
|
||||||
|
|
||||||
vimvars[VV_KEY].vv_type = VAR_NUMBER;
|
|
||||||
|
|
||||||
for (int i = 0; i < b->bv_ga.ga_len; i++) {
|
|
||||||
const varnumber_T val = tv_blob_get(b, i);
|
|
||||||
typval_T tv = {
|
|
||||||
.v_type = VAR_NUMBER,
|
|
||||||
.v_lock = VAR_UNLOCKED,
|
|
||||||
.vval.v_number = val,
|
|
||||||
};
|
|
||||||
vimvars[VV_KEY].vv_nr = idx;
|
|
||||||
typval_T newtv;
|
|
||||||
if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
|
|
||||||
|| did_emsg) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL) {
|
|
||||||
tv_clear(&newtv);
|
|
||||||
emsg(_(e_invalblob));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (filtermap != FILTERMAP_FILTER) {
|
|
||||||
if (newtv.vval.v_number != val) {
|
|
||||||
tv_blob_set(b_ret, i, (uint8_t)newtv.vval.v_number);
|
|
||||||
}
|
|
||||||
} else if (rem) {
|
|
||||||
char *const p = argvars[0].vval.v_blob->bv_ga.ga_data;
|
|
||||||
memmove(p + i, p + i + 1, (size_t)(b->bv_ga.ga_len - i - 1));
|
|
||||||
b->bv_ga.ga_len--;
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
} else if (argvars[0].v_type == VAR_STRING) {
|
} else if (argvars[0].v_type == VAR_STRING) {
|
||||||
vimvars[VV_KEY].vv_type = VAR_NUMBER;
|
filter_map_string(tv_get_string(&argvars[0]), filtermap, expr, rettv);
|
||||||
|
|
||||||
garray_T ga;
|
|
||||||
ga_init(&ga, (int)sizeof(char), 80);
|
|
||||||
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,
|
|
||||||
.vval.v_string = xstrnsave(p, (size_t)len),
|
|
||||||
};
|
|
||||||
|
|
||||||
vimvars[VV_KEY].vv_nr = idx;
|
|
||||||
typval_T newtv;
|
|
||||||
if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
|
|
||||||
|| did_emsg) {
|
|
||||||
tv_clear(&newtv);
|
|
||||||
tv_clear(&tv);
|
|
||||||
break;
|
|
||||||
} else if (filtermap != FILTERMAP_FILTER) {
|
|
||||||
if (newtv.v_type != VAR_STRING) {
|
|
||||||
tv_clear(&newtv);
|
|
||||||
tv_clear(&tv);
|
|
||||||
emsg(_(e_stringreq));
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
ga_concat(&ga, newtv.vval.v_string);
|
|
||||||
}
|
|
||||||
} else if (!rem) {
|
|
||||||
ga_concat(&ga, tv.vval.v_string);
|
|
||||||
}
|
|
||||||
|
|
||||||
tv_clear(&newtv);
|
|
||||||
tv_clear(&tv);
|
|
||||||
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
ga_append(&ga, NUL);
|
|
||||||
rettv->vval.v_string = ga.ga_data;
|
|
||||||
} else {
|
} else {
|
||||||
assert(argvars[0].v_type == VAR_LIST);
|
assert(argvars[0].v_type == VAR_LIST);
|
||||||
|
filter_map_list(argvars[0].vval.v_list, filtermap, func_name,
|
||||||
const VarLockStatus prev_lock = tv_list_locked(l);
|
arg_errmsg, expr, rettv);
|
||||||
list_T *l_ret = NULL;
|
|
||||||
|
|
||||||
if (filtermap == FILTERMAP_MAPNEW) {
|
|
||||||
tv_list_alloc_ret(rettv, kListLenUnknown);
|
|
||||||
l_ret = rettv->vval.v_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
vimvars[VV_KEY].vv_type = VAR_NUMBER;
|
|
||||||
|
|
||||||
if (filtermap != FILTERMAP_FILTER && tv_list_locked(l) == VAR_UNLOCKED) {
|
|
||||||
tv_list_set_lock(l, VAR_LOCKED);
|
|
||||||
}
|
|
||||||
for (listitem_T *li = tv_list_first(l); li != NULL;) {
|
|
||||||
if (filtermap == FILTERMAP_MAP
|
|
||||||
&& value_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg,
|
|
||||||
TV_TRANSLATE)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
vimvars[VV_KEY].vv_nr = idx;
|
|
||||||
typval_T newtv;
|
|
||||||
if (filter_map_one(TV_LIST_ITEM_TV(li), expr, filtermap, &newtv, &rem) == FAIL) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (did_emsg) {
|
|
||||||
tv_clear(&newtv);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (filtermap == FILTERMAP_MAP) {
|
|
||||||
// map(): replace the list item value
|
|
||||||
tv_clear(TV_LIST_ITEM_TV(li));
|
|
||||||
newtv.v_lock = VAR_UNLOCKED;
|
|
||||||
*TV_LIST_ITEM_TV(li) = newtv;
|
|
||||||
} else if (filtermap == FILTERMAP_MAPNEW) {
|
|
||||||
// mapnew(): append the list item value
|
|
||||||
tv_list_append_owned_tv(l_ret, newtv);
|
|
||||||
}
|
|
||||||
if (filtermap == FILTERMAP_FILTER && rem) {
|
|
||||||
li = tv_list_item_remove(l, li);
|
|
||||||
} else {
|
|
||||||
li = TV_LIST_ITEM_NEXT(l, li);
|
|
||||||
}
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
tv_list_set_lock(l, prev_lock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
restore_vimvar(VV_KEY, &save_key);
|
restore_vimvar(VV_KEY, &save_key);
|
||||||
@ -5305,7 +5335,7 @@ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap
|
|||||||
/// @param newtv for map() an mapnew(): new value
|
/// @param newtv for map() an mapnew(): new value
|
||||||
/// @param remp for filter(): remove flag
|
/// @param remp for filter(): remove flag
|
||||||
static int filter_map_one(typval_T *tv, typval_T *expr, const filtermap_T filtermap,
|
static int filter_map_one(typval_T *tv, typval_T *expr, const filtermap_T filtermap,
|
||||||
typval_T *newtv, int *remp)
|
typval_T *newtv, bool *remp)
|
||||||
FUNC_ATTR_NONNULL_ALL
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
typval_T argv[3];
|
typval_T argv[3];
|
||||||
|
@ -2551,8 +2551,8 @@ M.funcs = {
|
|||||||
of the current item. For a |Dictionary| |v:key| has the key
|
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
|
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
|
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: >vim
|
Examples: >vim
|
||||||
call filter(mylist, 'v:val !~ "OLD"')
|
call filter(mylist, 'v:val !~ "OLD"')
|
||||||
<Removes the items where "OLD" appears. >vim
|
<Removes the items where "OLD" appears. >vim
|
||||||
@ -6068,7 +6068,8 @@ M.funcs = {
|
|||||||
of the current item. For a |Dictionary| |v:key| has the key
|
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
|
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
|
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: >vim
|
Example: >vim
|
||||||
call map(mylist, '"> " .. v:val .. " <"')
|
call map(mylist, '"> " .. v:val .. " <"')
|
||||||
<This puts "> " before and " <" after each item in "mylist".
|
<This puts "> " before and " <" after each item in "mylist".
|
||||||
@ -7876,9 +7877,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
|
||||||
result so far and current item. After processing all items
|
arguments: the result so far and current item. After
|
||||||
the result is returned.
|
processing all 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 +7890,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',
|
||||||
|
@ -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
|
||||||
///
|
///
|
||||||
@ -6167,11 +6171,149 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// reduce() on a List
|
||||||
|
static void reduce_list(typval_T *argvars, const char *func_name, funcexe_T *funcexe,
|
||||||
|
typval_T *rettv)
|
||||||
|
{
|
||||||
|
list_T *const l = argvars[0].vval.v_list;
|
||||||
|
const int called_emsg_start = called_emsg;
|
||||||
|
|
||||||
|
typval_T initial;
|
||||||
|
const listitem_T *li = NULL;
|
||||||
|
if (argvars[2].v_type == VAR_UNKNOWN) {
|
||||||
|
if (tv_list_len(l) == 0) {
|
||||||
|
semsg(_(e_reduceempty), "List");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const listitem_T *const first = tv_list_first(l);
|
||||||
|
initial = *TV_LIST_ITEM_TV(first);
|
||||||
|
li = TV_LIST_ITEM_NEXT(l, first);
|
||||||
|
} else {
|
||||||
|
initial = argvars[2];
|
||||||
|
li = tv_list_first(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
tv_copy(&initial, rettv);
|
||||||
|
|
||||||
|
if (l == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VarLockStatus prev_locked = tv_list_locked(l);
|
||||||
|
|
||||||
|
tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here
|
||||||
|
for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
|
||||||
|
typval_T argv[3];
|
||||||
|
argv[0] = *rettv;
|
||||||
|
argv[1] = *TV_LIST_ITEM_TV(li);
|
||||||
|
rettv->v_type = VAR_UNKNOWN;
|
||||||
|
const int r = call_func(func_name, -1, rettv, 2, argv, funcexe);
|
||||||
|
tv_clear(&argv[0]);
|
||||||
|
if (r == FAIL || called_emsg != called_emsg_start) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tv_list_set_lock(l, prev_locked);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// reduce() on a String
|
||||||
|
static void reduce_string(typval_T *argvars, const char *func_name, funcexe_T *funcexe,
|
||||||
|
typval_T *rettv)
|
||||||
|
{
|
||||||
|
const char *p = tv_get_string(&argvars[0]);
|
||||||
|
int len;
|
||||||
|
const int called_emsg_start = called_emsg;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
typval_T argv[3];
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// reduce() on a Blob
|
||||||
|
static void reduce_blob(typval_T *argvars, const char *func_name, funcexe_T *funcexe,
|
||||||
|
typval_T *rettv)
|
||||||
|
{
|
||||||
|
const blob_T *const b = argvars[0].vval.v_blob;
|
||||||
|
const int called_emsg_start = called_emsg;
|
||||||
|
|
||||||
|
typval_T initial;
|
||||||
|
int i;
|
||||||
|
if (argvars[2].v_type == VAR_UNKNOWN) {
|
||||||
|
if (tv_blob_len(b) == 0) {
|
||||||
|
semsg(_(e_reduceempty), "Blob");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initial = (typval_T){
|
||||||
|
.v_type = VAR_NUMBER,
|
||||||
|
.v_lock = VAR_UNLOCKED,
|
||||||
|
.vval.v_number = tv_blob_get(b, 0),
|
||||||
|
};
|
||||||
|
i = 1;
|
||||||
|
} else if (argvars[2].v_type != VAR_NUMBER) {
|
||||||
|
emsg(_(e_number_exp));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
initial = argvars[2];
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tv_copy(&initial, rettv);
|
||||||
|
for (; i < tv_blob_len(b); i++) {
|
||||||
|
typval_T argv[3];
|
||||||
|
argv[0] = *rettv;
|
||||||
|
argv[1] = (typval_T){
|
||||||
|
.v_type = VAR_NUMBER,
|
||||||
|
.v_lock = VAR_UNLOCKED,
|
||||||
|
.vval.v_number = tv_blob_get(b, i),
|
||||||
|
};
|
||||||
|
const int r = call_func(func_name, -1, rettv, 2, argv, funcexe);
|
||||||
|
if (r == FAIL || called_emsg != called_emsg_start) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// "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) {
|
if (argvars[0].v_type != VAR_STRING
|
||||||
emsg(_(e_listblobreq));
|
&& argvars[0].v_type != VAR_LIST
|
||||||
|
&& argvars[0].v_type != VAR_BLOB) {
|
||||||
|
emsg(_(e_string_list_or_blob_required));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6194,73 +6336,12 @@ static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|||||||
funcexe.fe_evaluate = true;
|
funcexe.fe_evaluate = true;
|
||||||
funcexe.fe_partial = partial;
|
funcexe.fe_partial = partial;
|
||||||
|
|
||||||
typval_T initial;
|
|
||||||
typval_T argv[3];
|
|
||||||
if (argvars[0].v_type == VAR_LIST) {
|
if (argvars[0].v_type == VAR_LIST) {
|
||||||
list_T *const l = argvars[0].vval.v_list;
|
reduce_list(argvars, func_name, &funcexe, rettv);
|
||||||
const listitem_T *li;
|
} else if (argvars[0].v_type == VAR_STRING) {
|
||||||
|
reduce_string(argvars, func_name, &funcexe, rettv);
|
||||||
if (argvars[2].v_type == VAR_UNKNOWN) {
|
|
||||||
if (tv_list_len(l) == 0) {
|
|
||||||
semsg(_(e_reduceempty), "List");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const listitem_T *const first = tv_list_first(l);
|
|
||||||
initial = *TV_LIST_ITEM_TV(first);
|
|
||||||
li = TV_LIST_ITEM_NEXT(l, first);
|
|
||||||
} else {
|
|
||||||
initial = argvars[2];
|
|
||||||
li = tv_list_first(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
tv_copy(&initial, rettv);
|
|
||||||
|
|
||||||
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)) {
|
|
||||||
argv[0] = *rettv;
|
|
||||||
argv[1] = *TV_LIST_ITEM_TV(li);
|
|
||||||
rettv->v_type = VAR_UNKNOWN;
|
|
||||||
const int r = call_func(func_name, -1, rettv, 2, argv, &funcexe);
|
|
||||||
tv_clear(&argv[0]);
|
|
||||||
if (r == FAIL || called_emsg != called_emsg_start) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tv_list_set_lock(l, prev_locked);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const blob_T *const b = argvars[0].vval.v_blob;
|
reduce_blob(argvars, func_name, &funcexe, rettv);
|
||||||
int i;
|
|
||||||
|
|
||||||
if (argvars[2].v_type == VAR_UNKNOWN) {
|
|
||||||
if (tv_blob_len(b) == 0) {
|
|
||||||
semsg(_(e_reduceempty), "Blob");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
initial.v_type = VAR_NUMBER;
|
|
||||||
initial.vval.v_number = tv_blob_get(b, 0);
|
|
||||||
i = 1;
|
|
||||||
} else if (argvars[2].v_type != VAR_NUMBER) {
|
|
||||||
emsg(_(e_number_exp));
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
initial = argvars[2];
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
tv_copy(&initial, rettv);
|
|
||||||
for (; i < tv_blob_len(b); i++) {
|
|
||||||
argv[0] = *rettv;
|
|
||||||
argv[1].v_type = VAR_NUMBER;
|
|
||||||
argv[1].vval.v_number = tv_blob_get(b, i);
|
|
||||||
if (call_func(func_name, -1, rettv, 2, argv, &funcexe) == FAIL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,26 @@ 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:')
|
||||||
|
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]
|
let g:lut = [1, 2, 3, 4]
|
||||||
func EvilRemove()
|
func EvilRemove()
|
||||||
call remove(g:lut, 1)
|
call remove(g:lut, 1)
|
||||||
|
Loading…
Reference in New Issue
Block a user