commit a4006360f1d43211ec9abf5b869f49e082ab82e7
parent d9153d620ade3c7590d4fb99e6e8a3c827f2a299
Author: zeertzjq <zeertzjq@outlook.com>
Date: Tue, 7 Oct 2025 10:20:48 +0800
Merge pull request #36052 from janlazo/vim-8.1.1978
vim-patch:8.1.{1978,1981}
Diffstat:
9 files changed, 865 insertions(+), 841 deletions(-)
diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh
@@ -250,6 +250,10 @@ preprocess_patch() {
LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/evalfunc\.c/\1\/eval\/funcs.c/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
+ # Rename list.c to eval/list.c
+ LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/list\.c/\1\/eval\/list.c/g' \
+ "$file" > "$file".tmp && mv "$file".tmp "$file"
+
# Rename evalvars.c to eval/vars.c
LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/evalvars\.c/\1\/eval\/vars.c/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
diff --git a/src/gen/gen_eval.lua b/src/gen/gen_eval.lua
@@ -23,6 +23,7 @@ hashpipe:write([[
#include "nvim/eval/deprecated.h"
#include "nvim/eval/fs.h"
#include "nvim/eval/funcs.h"
+#include "nvim/eval/list.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/vars.h"
#include "nvim/eval/window.h"
diff --git a/src/nvim/errors.h b/src/nvim/errors.h
@@ -1,5 +1,7 @@
#pragma once
+#include <inttypes.h>
+
#include "nvim/gettext_defs.h"
#include "nvim/macros_defs.h"
@@ -101,6 +103,7 @@ EXTERN const char e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: \"
EXTERN const char e_dictkey_len[] INIT(= N_("E716: Key not present in Dictionary: \"%.*s\""));
EXTERN const char e_listreq[] INIT(= N_("E714: List required"));
EXTERN const char e_listblobreq[] INIT(= N_("E897: List or Blob required"));
+EXTERN const char e_listblobarg[] INIT(= N_("E899: Argument of %s must be a List or Blob"));
EXTERN const char e_listdictarg[] INIT(= N_("E712: Argument of %s must be a List or Dictionary"));
EXTERN const char e_listdictblobarg[] INIT(= N_("E896: Argument of %s must be a List, Dictionary or Blob"));
EXTERN const char e_readerrf[] INIT(= N_("E47: Error while reading errorfile"));
@@ -141,6 +144,7 @@ EXTERN const char e_au_recursive[] INIT(= N_("E952: Autocommand caused recursive
EXTERN const char e_menu_only_exists_in_another_mode[]
INIT(= N_("E328: Menu only exists in another mode"));
EXTERN const char e_autocmd_close[] INIT(= N_("E813: Cannot close autocmd window"));
+EXTERN const char e_list_index_out_of_range_nr[] INIT(= N_("E684: List index out of range: %" PRId64));
EXTERN const char e_listarg[] INIT(= N_("E686: Argument of %s must be a List"));
EXTERN const char e_unsupportedoption[] INIT(= N_("E519: Option not supported"));
EXTERN const char e_fnametoolong[] INIT(= N_("E856: Filename too long"));
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
@@ -108,8 +108,6 @@ static const char e_dot_can_only_be_used_on_dictionary_str[]
= N_("E1203: Dot can only be used on a dictionary: %s");
static const char e_empty_function_name[]
= N_("E1192: Empty function name");
-static const char e_argument_of_str_must_be_list_string_dictionary_or_blob[]
- = N_("E1250: Argument of %s must be a List, String, Dictionary or Blob");
static const char e_cannot_use_partial_here[]
= N_("E1265: Cannot use a partial here");
@@ -299,14 +297,6 @@ static ScopeDictDictItem vimvars_var;
static partial_T *vvlua_partial;
-/// Enum used by filter(), map(), mapnew() and foreach()
-typedef enum {
- FILTERMAP_FILTER,
- FILTERMAP_MAP,
- FILTERMAP_MAPNEW,
- FILTERMAP_FOREACH,
-} filtermap_T;
-
#include "eval.c.generated.h"
static uint64_t last_timer_id = 1;
@@ -5060,402 +5050,6 @@ static int eval_env_var(char **arg, typval_T *rettv, int evaluate)
return OK;
}
-/// Implementation of map(), filter(), foreach() for a Dict. Apply "expr" to
-/// every item in Dict "d" and return the result in "rettv".
-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;
- }
-
- dict_T *d_ret = NULL;
-
- if (filtermap == FILTERMAP_MAPNEW) {
- tv_dict_alloc_ret(rettv);
- d_ret = rettv->vval.v_dict;
- }
-
- const VarLockStatus prev_lock = d->dv_lock;
- if (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;
- }
- set_vim_var_string(VV_KEY, di->di_key, -1);
- typval_T newtv;
- bool rem;
- int r = filter_map_one(&di->di_tv, expr, filtermap, &newtv, &rem);
- tv_clear(get_vim_var_tv(VV_KEY));
- 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(), filter(), foreach() for a Blob.
-static void filter_map_blob(blob_T *blob_arg, filtermap_T filtermap, typval_T *expr,
- const char *arg_errmsg, typval_T *rettv)
-{
- if (filtermap == FILTERMAP_MAPNEW) {
- rettv->v_type = VAR_BLOB;
- rettv->vval.v_blob = NULL;
- }
- blob_T *b = blob_arg;
- if (b == NULL
- || (filtermap == FILTERMAP_FILTER
- && value_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE))) {
- return;
- }
-
- blob_T *b_ret = b;
-
- if (filtermap == FILTERMAP_MAPNEW) {
- tv_blob_copy(b, rettv);
- b_ret = rettv->vval.v_blob;
- }
-
- // set_vim_var_nr() doesn't set the type
- set_vim_var_type(VV_KEY, VAR_NUMBER);
-
- const VarLockStatus prev_lock = b->bv_lock;
- if (b->bv_lock == 0) {
- b->bv_lock = VAR_LOCKED;
- }
-
- 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,
- };
- set_vim_var_nr(VV_KEY, idx);
- typval_T newtv;
- bool rem;
- if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
- || did_emsg) {
- break;
- }
- if (filtermap != FILTERMAP_FOREACH) {
- 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++;
- }
-
- b->bv_lock = prev_lock;
-}
-
-/// Implementation of map(), filter(), foreach() 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;
-
- // set_vim_var_nr() doesn't set the type
- set_vim_var_type(VV_KEY, 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 = xmemdupz(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;
- }
- if (filtermap == FILTERMAP_MAP || filtermap == FILTERMAP_MAPNEW) {
- 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 (filtermap == FILTERMAP_FOREACH || !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(), filter(), foreach() for a List. Apply "expr" to
-/// every item in List "l" and return the result in "rettv".
-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;
- }
-
- list_T *l_ret = NULL;
-
- if (filtermap == FILTERMAP_MAPNEW) {
- tv_list_alloc_ret(rettv, kListLenUnknown);
- l_ret = rettv->vval.v_list;
- }
- // set_vim_var_nr() doesn't set the type
- set_vim_var_type(VV_KEY, VAR_NUMBER);
-
- const VarLockStatus prev_lock = tv_list_locked(l);
- if (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;
- }
- set_vim_var_nr(VV_KEY, 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(), filter() and foreach().
-static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
-{
- const char *const func_name = (filtermap == FILTERMAP_MAP
- ? "map()"
- : (filtermap == FILTERMAP_MAPNEW
- ? "mapnew()"
- : (filtermap == FILTERMAP_FILTER
- ? "filter()"
- : "foreach()")));
- const char *const arg_errmsg = (filtermap == FILTERMAP_MAP
- ? N_("map() argument")
- : (filtermap == FILTERMAP_MAPNEW
- ? N_("mapnew() argument")
- : (filtermap == FILTERMAP_FILTER
- ? N_("filter() argument")
- : N_("foreach() argument"))));
-
- // map(), filter(), foreach() return the first argument, also on failure.
- if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING) {
- tv_copy(&argvars[0], rettv);
- }
-
- 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);
- return;
- }
-
- typval_T *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) {
- return;
- }
-
- 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.
- int save_did_emsg = did_emsg;
- did_emsg = false;
-
- if (argvars[0].v_type == VAR_DICT) {
- filter_map_dict(argvars[0].vval.v_dict, filtermap, func_name,
- arg_errmsg, expr, rettv);
- } else if (argvars[0].v_type == VAR_BLOB) {
- filter_map_blob(argvars[0].vval.v_blob, filtermap, expr, arg_errmsg, rettv);
- } else if (argvars[0].v_type == VAR_STRING) {
- filter_map_string(tv_get_string(&argvars[0]), filtermap, expr, rettv);
- } else {
- assert(argvars[0].v_type == VAR_LIST);
- filter_map_list(argvars[0].vval.v_list, filtermap, func_name,
- arg_errmsg, expr, rettv);
- }
-
- restore_vimvar(VV_KEY, &save_key);
- restore_vimvar(VV_VAL, &save_val);
-
- did_emsg |= save_did_emsg;
-}
-
-/// Handle one item for map(), filter(), foreach().
-/// Sets v:val to "tv". Caller must set v:key.
-///
-/// @param tv original value
-/// @param expr callback
-/// @param newtv for map() an mapnew(): new value
-/// @param remp for filter(): remove flag
-static int filter_map_one(typval_T *tv, typval_T *expr, const filtermap_T filtermap,
- typval_T *newtv, bool *remp)
- FUNC_ATTR_NONNULL_ALL
-{
- typval_T argv[3];
- int retval = FAIL;
-
- tv_copy(tv, get_vim_var_tv(VV_VAL));
-
- newtv->v_type = VAR_UNKNOWN;
- if (filtermap == FILTERMAP_FOREACH && expr->v_type == VAR_STRING) {
- // foreach() is not limited to an expression
- do_cmdline_cmd(expr->vval.v_string);
- if (!did_emsg) {
- retval = OK;
- }
- goto theend;
- }
-
- argv[0] = *get_vim_var_tv(VV_KEY);
- argv[1] = *get_vim_var_tv(VV_VAL);
- if (eval_expr_typval(expr, false, argv, 2, newtv) == FAIL) {
- goto theend;
- }
- if (filtermap == FILTERMAP_FILTER) {
- bool error = false;
-
- // filter(): when expr is zero remove the item
- *remp = (tv_get_number_chk(newtv, &error) == 0);
- tv_clear(newtv);
- // On type error, nothing has been removed; return FAIL to stop the
- // loop. The error message was given by tv_get_number_chk().
- if (error) {
- goto theend;
- }
- } else if (filtermap == FILTERMAP_FOREACH) {
- tv_clear(newtv);
- }
- retval = OK;
-theend:
- tv_clear(get_vim_var_tv(VV_VAL));
- return retval;
-}
-
-/// "filter()" function
-void f_filter(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- filter_map(argvars, rettv, FILTERMAP_FILTER);
-}
-
-/// "map()" function
-void f_map(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- filter_map(argvars, rettv, FILTERMAP_MAP);
-}
-
-/// "mapnew()" function
-void f_mapnew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- filter_map(argvars, rettv, FILTERMAP_MAPNEW);
-}
-
-/// "foreach()" function
-void f_foreach(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- filter_map(argvars, rettv, FILTERMAP_FOREACH);
-}
-
/// Builds a process argument vector from a Vimscript object (typval_T).
///
/// @param[in] cmd_tv Vimscript object
diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c
@@ -15,9 +15,6 @@
#include "eval/executor.c.generated.h"
-char *e_list_index_out_of_range_nr
- = N_("E684: List index out of range: %" PRId64);
-
/// Handle "blob1 += blob2".
/// Returns OK or FAIL.
static int tv_op_blob(typval_T *tv1, const typval_T *tv2, const char *op)
diff --git a/src/nvim/eval/executor.h b/src/nvim/eval/executor.h
@@ -2,6 +2,4 @@
#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
-extern char *e_list_index_out_of_range_nr;
-
#include "eval/executor.h.generated.h"
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
@@ -157,10 +157,7 @@ PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH
PRAGMA_DIAG_POP
PRAGMA_DIAG_POP
-static const char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob");
static const char *e_invalwindow = N_("E957: Invalid window number");
-static const char e_argument_of_str_must_be_list_string_or_dictionary[]
- = N_("E706: Argument of %s must be a List, String or Dictionary");
static const char e_invalid_submatch_number_nr[]
= N_("E935: Invalid submatch number: %d");
static const char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value");
@@ -398,34 +395,6 @@ static void f_abs(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
-/// "add(list, item)" function
-static void f_add(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = 1; // Default: failed.
- if (argvars[0].v_type == VAR_LIST) {
- list_T *const l = argvars[0].vval.v_list;
- if (!value_check_lock(tv_list_locked(l), N_("add() argument"),
- TV_TRANSLATE)) {
- tv_list_append_tv(l, &argvars[1]);
- tv_copy(&argvars[0], rettv);
- }
- } else if (argvars[0].v_type == VAR_BLOB) {
- blob_T *const b = argvars[0].vval.v_blob;
- if (b != NULL
- && !value_check_lock(b->bv_lock, N_("add() argument"), TV_TRANSLATE)) {
- bool error = false;
- const varnumber_T n = tv_get_number_chk(&argvars[1], &error);
-
- if (!error) {
- ga_append(&b->bv_ga, (uint8_t)n);
- tv_copy(&argvars[0], rettv);
- }
- }
- } else {
- emsg(_(e_listblobreq));
- }
-}
-
/// "and(expr, expr)" function
static void f_and(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -853,125 +822,6 @@ static void f_copy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
var_item_copy(NULL, &argvars[0], rettv, false, 0);
}
-/// Count the number of times "needle" occurs in string "haystack".
-///
-/// @param ic ignore case
-static varnumber_T count_string(const char *haystack, const char *needle, bool ic)
-{
- varnumber_T n = 0;
- const char *p = haystack;
-
- if (p == NULL || needle == NULL || *needle == NUL) {
- return 0;
- }
-
- if (ic) {
- const size_t len = strlen(needle);
-
- while (*p != NUL) {
- if (mb_strnicmp(p, needle, len) == 0) {
- n++;
- p += len;
- } else {
- MB_PTR_ADV(p);
- }
- }
- } else {
- const char *next;
- while ((next = strstr(p, needle)) != NULL) {
- n++;
- p = next + strlen(needle);
- }
- }
-
- return n;
-}
-
-/// Count the number of times item "needle" occurs in List "l" starting at index "idx".
-///
-/// @param ic ignore case
-static varnumber_T count_list(list_T *l, typval_T *needle, int64_t idx, bool ic)
-{
- if (tv_list_len(l) == 0) {
- return 0;
- }
-
- listitem_T *li = tv_list_find(l, (int)idx);
- if (li == NULL) {
- semsg(_(e_list_index_out_of_range_nr), idx);
- return 0;
- }
-
- varnumber_T n = 0;
-
- for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
- if (tv_equal(TV_LIST_ITEM_TV(li), needle, ic)) {
- n++;
- }
- }
-
- return n;
-}
-
-/// Count the number of times item "needle" occurs in Dict "d".
-///
-/// @param ic ignore case
-static varnumber_T count_dict(dict_T *d, typval_T *needle, bool ic)
-{
- if (d == NULL) {
- return 0;
- }
-
- varnumber_T n = 0;
-
- TV_DICT_ITER(d, di, {
- if (tv_equal(&di->di_tv, needle, ic)) {
- n++;
- }
- });
-
- return n;
-}
-
-/// "count()" function
-static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- varnumber_T n = 0;
- int ic = 0;
- bool error = false;
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- ic = (int)tv_get_number_chk(&argvars[2], &error);
- }
-
- if (!error && argvars[0].v_type == VAR_STRING) {
- n = count_string(argvars[0].vval.v_string, tv_get_string_chk(&argvars[1]), ic);
- } else if (!error && argvars[0].v_type == VAR_LIST) {
- int64_t idx = 0;
- if (argvars[2].v_type != VAR_UNKNOWN
- && argvars[3].v_type != VAR_UNKNOWN) {
- idx = (int64_t)tv_get_number_chk(&argvars[3], &error);
- }
- if (!error) {
- n = count_list(argvars[0].vval.v_list, &argvars[1], idx, ic);
- }
- } else if (!error && argvars[0].v_type == VAR_DICT) {
- dict_T *d = argvars[0].vval.v_dict;
-
- if (d != NULL) {
- if (argvars[2].v_type != VAR_UNKNOWN
- && argvars[3].v_type != VAR_UNKNOWN) {
- emsg(_(e_invarg));
- } else {
- n = count_dict(argvars[0].vval.v_dict, &argvars[1], ic);
- }
- }
- } else if (!error) {
- semsg(_(e_argument_of_str_must_be_list_string_or_dictionary), "count()");
- }
- rettv->vval.v_number = n;
-}
-
/// "ctxget([{index}])" function
static void f_ctxget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -1757,161 +1607,6 @@ static void f_flattennew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
flatten_common(argvars, rettv, true);
}
-/// extend() a List. Append List argvars[1] to List argvars[0] before index
-/// argvars[3] and return the resulting list in "rettv".
-///
-/// @param is_new true for extendnew()
-static void extend_list(typval_T *argvars, const char *arg_errmsg, bool is_new, typval_T *rettv)
-{
- bool error = false;
-
- list_T *l1 = argvars[0].vval.v_list;
- list_T *const l2 = argvars[1].vval.v_list;
-
- if (!is_new && value_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {
- return;
- }
-
- if (is_new) {
- l1 = tv_list_copy(NULL, l1, false, get_copyID());
- if (l1 == NULL) {
- return;
- }
- }
-
- listitem_T *item;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- int before = (int)tv_get_number_chk(&argvars[2], &error);
- if (error) {
- return; // Type error; errmsg already given.
- }
-
- if (before == tv_list_len(l1)) {
- item = NULL;
- } else {
- item = tv_list_find(l1, before);
- if (item == NULL) {
- semsg(_(e_list_index_out_of_range_nr), (int64_t)before);
- return;
- }
- }
- } else {
- item = NULL;
- }
- tv_list_extend(l1, l2, item);
-
- if (is_new) {
- *rettv = (typval_T){
- .v_type = VAR_LIST,
- .v_lock = VAR_UNLOCKED,
- .vval.v_list = l1,
- };
- } else {
- tv_copy(&argvars[0], rettv);
- }
-}
-
-/// extend() a Dict. Append Dict argvars[1] to Dict argvars[0] and return the
-/// resulting Dict in "rettv".
-///
-/// @param is_new true for extendnew()
-static void extend_dict(typval_T *argvars, const char *arg_errmsg, bool is_new, typval_T *rettv)
-{
- dict_T *d1 = argvars[0].vval.v_dict;
- if (d1 == NULL) {
- const bool locked = value_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE);
- (void)locked;
- assert(locked == true);
- return;
- }
- dict_T *const d2 = argvars[1].vval.v_dict;
- if (d2 == NULL) {
- // Do nothing
- tv_copy(&argvars[0], rettv);
- return;
- }
-
- if (!is_new && value_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) {
- return;
- }
-
- if (is_new) {
- d1 = tv_dict_copy(NULL, d1, false, get_copyID());
- if (d1 == NULL) {
- return;
- }
- }
-
- const char *action = "force";
- // Check the third argument.
- if (argvars[2].v_type != VAR_UNKNOWN) {
- const char *const av[] = { "keep", "force", "error" };
-
- action = tv_get_string_chk(&argvars[2]);
- if (action == NULL) {
- if (is_new) {
- tv_dict_unref(d1);
- }
- return; // Type error; error message already given.
- }
- size_t i;
- for (i = 0; i < ARRAY_SIZE(av); i++) {
- if (strcmp(action, av[i]) == 0) {
- break;
- }
- }
- if (i == 3) {
- if (is_new) {
- tv_dict_unref(d1);
- }
- semsg(_(e_invarg2), action);
- return;
- }
- }
-
- tv_dict_extend(d1, d2, action);
-
- if (is_new) {
- *rettv = (typval_T){
- .v_type = VAR_DICT,
- .v_lock = VAR_UNLOCKED,
- .vval.v_dict = d1,
- };
- } else {
- tv_copy(&argvars[0], rettv);
- }
-}
-
-/// "extend()" or "extendnew()" function.
-///
-/// @param is_new true for extendnew()
-static void extend(typval_T *argvars, typval_T *rettv, char *arg_errmsg, bool is_new)
-{
- if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
- extend_list(argvars, arg_errmsg, is_new, rettv);
- } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT) {
- extend_dict(argvars, arg_errmsg, is_new, rettv);
- } else {
- semsg(_(e_listdictarg), is_new ? "extendnew()" : "extend()");
- }
-}
-
-/// "extend(list, list [, idx])" function
-/// "extend(dict, dict [, action])" function
-static void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- char *errmsg = N_("extend() argument");
- extend(argvars, rettv, errmsg, false);
-}
-
-/// "extendnew(list, list [, idx])" function
-/// "extendnew(dict, dict [, action])" function
-static void f_extendnew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- char *errmsg = N_("extendnew() argument");
- extend(argvars, rettv, errmsg, true);
-}
-
/// "feedkeys()" function
static void f_feedkeys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -3535,81 +3230,6 @@ static void f_inputsecret(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
inputsecret_flag = false;
}
-/// "insert()" function
-static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- bool error = false;
-
- if (argvars[0].v_type == VAR_BLOB) {
- blob_T *const b = argvars[0].vval.v_blob;
-
- if (b == NULL
- || value_check_lock(b->bv_lock, N_("insert() argument"),
- TV_TRANSLATE)) {
- return;
- }
-
- int before = 0;
- const int len = tv_blob_len(b);
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- before = (int)tv_get_number_chk(&argvars[2], &error);
- if (error) {
- return; // type error; errmsg already given
- }
- if (before < 0 || before > len) {
- semsg(_(e_invarg2), tv_get_string(&argvars[2]));
- return;
- }
- }
- const int val = (int)tv_get_number_chk(&argvars[1], &error);
- if (error) {
- return;
- }
- if (val < 0 || val > 255) {
- semsg(_(e_invarg2), tv_get_string(&argvars[1]));
- return;
- }
-
- ga_grow(&b->bv_ga, 1);
- uint8_t *const p = (uint8_t *)b->bv_ga.ga_data;
- memmove(p + before + 1, p + before, (size_t)(len - before));
- *(p + before) = (uint8_t)val;
- b->bv_ga.ga_len++;
-
- tv_copy(&argvars[0], rettv);
- } else if (argvars[0].v_type != VAR_LIST) {
- semsg(_(e_listblobarg), "insert()");
- } else {
- list_T *l = argvars[0].vval.v_list;
- if (value_check_lock(tv_list_locked(l), N_("insert() argument"), TV_TRANSLATE)) {
- return;
- }
-
- int64_t before = 0;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- before = tv_get_number_chk(&argvars[2], &error);
- }
- if (error) {
- // type error; errmsg already given
- return;
- }
-
- listitem_T *item = NULL;
- if (before != tv_list_len(l)) {
- item = tv_list_find(l, (int)before);
- if (item == NULL) {
- semsg(_(e_list_index_out_of_range_nr), before);
- l = NULL;
- }
- }
- if (l != NULL) {
- tv_list_insert_tv(l, &argvars[1], item);
- tv_copy(&argvars[0], rettv);
- }
- }
-}
-
/// "interrupt()" function
static void f_interrupt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -5656,22 +5276,6 @@ static void f_reltimestr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
-/// "remove()" function
-static void f_remove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *const arg_errmsg = N_("remove() argument");
-
- if (argvars[0].v_type == VAR_DICT) {
- tv_dict_remove(argvars, rettv, arg_errmsg);
- } else if (argvars[0].v_type == VAR_BLOB) {
- tv_blob_remove(argvars, rettv, arg_errmsg);
- } else if (argvars[0].v_type == VAR_LIST) {
- tv_list_remove(argvars, rettv, arg_errmsg);
- } else {
- semsg(_(e_listdictblobarg), "remove()");
- }
-}
-
/// "repeat()" function
static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -5740,40 +5344,6 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
-/// "reverse({list})" function
-static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- if (tv_check_for_string_or_list_or_blob_arg(argvars, 0) == FAIL) {
- return;
- }
-
- if (argvars[0].v_type == VAR_BLOB) {
- blob_T *const b = argvars[0].vval.v_blob;
- const int len = tv_blob_len(b);
-
- for (int i = 0; i < len / 2; i++) {
- const uint8_t tmp = tv_blob_get(b, i);
- tv_blob_set(b, i, tv_blob_get(b, len - i - 1));
- tv_blob_set(b, len - i - 1, tmp);
- }
- tv_blob_set_ret(rettv, b);
- } else if (argvars[0].v_type == VAR_STRING) {
- rettv->v_type = VAR_STRING;
- if (argvars[0].vval.v_string != NULL) {
- rettv->vval.v_string = reverse_text(argvars[0].vval.v_string);
- } else {
- rettv->vval.v_string = NULL;
- }
- } else if (argvars[0].v_type == VAR_LIST) {
- list_T *const l = argvars[0].vval.v_list;
- if (!value_check_lock(tv_list_locked(l), N_("reverse() argument"),
- TV_TRANSLATE)) {
- tv_list_reverse(l);
- tv_list_set_ret(rettv, l);
- }
- }
-}
-
/// Implementation of reduce() for list "argvars[0]", using the function "expr"
/// starting with the optional initial value argvars[2] and return the result in
/// "rettv".
diff --git a/src/nvim/eval/list.c b/src/nvim/eval/list.c
@@ -0,0 +1,851 @@
+// eval/list.c: List support and container (List, Dict, Blob) functions.
+
+#include "nvim/errors.h"
+#include "nvim/eval.h"
+#include "nvim/eval/list.h"
+#include "nvim/eval/typval.h"
+#include "nvim/eval/vars.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/garray.h"
+#include "nvim/globals.h"
+#include "nvim/mbyte.h"
+#include "nvim/strings.h"
+#include "nvim/vim_defs.h"
+
+/// Enum used by filter(), map(), mapnew() and foreach()
+typedef enum {
+ FILTERMAP_FILTER,
+ FILTERMAP_MAP,
+ FILTERMAP_MAPNEW,
+ FILTERMAP_FOREACH,
+} filtermap_T;
+
+#include "eval/list.c.generated.h"
+
+static const char e_argument_of_str_must_be_list_string_or_dictionary[]
+ = N_("E706: Argument of %s must be a List, String or Dictionary");
+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");
+
+/// Handle one item for map(), filter(), foreach().
+/// Sets v:val to "tv". Caller must set v:key.
+///
+/// @param tv original value
+/// @param expr callback
+/// @param newtv for map() an mapnew(): new value
+/// @param remp for filter(): remove flag
+static int filter_map_one(typval_T *tv, typval_T *expr, const filtermap_T filtermap,
+ typval_T *newtv, bool *remp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ typval_T argv[3];
+ int retval = FAIL;
+
+ tv_copy(tv, get_vim_var_tv(VV_VAL));
+
+ newtv->v_type = VAR_UNKNOWN;
+ if (filtermap == FILTERMAP_FOREACH && expr->v_type == VAR_STRING) {
+ // foreach() is not limited to an expression
+ do_cmdline_cmd(expr->vval.v_string);
+ if (!did_emsg) {
+ retval = OK;
+ }
+ goto theend;
+ }
+
+ argv[0] = *get_vim_var_tv(VV_KEY);
+ argv[1] = *get_vim_var_tv(VV_VAL);
+ if (eval_expr_typval(expr, false, argv, 2, newtv) == FAIL) {
+ goto theend;
+ }
+ if (filtermap == FILTERMAP_FILTER) {
+ bool error = false;
+
+ // filter(): when expr is zero remove the item
+ *remp = (tv_get_number_chk(newtv, &error) == 0);
+ tv_clear(newtv);
+ // On type error, nothing has been removed; return FAIL to stop the
+ // loop. The error message was given by tv_get_number_chk().
+ if (error) {
+ goto theend;
+ }
+ } else if (filtermap == FILTERMAP_FOREACH) {
+ tv_clear(newtv);
+ }
+ retval = OK;
+theend:
+ tv_clear(get_vim_var_tv(VV_VAL));
+ return retval;
+}
+
+/// Implementation of map(), filter(), foreach() for a Dict. Apply "expr" to
+/// every item in Dict "d" and return the result in "rettv".
+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;
+ }
+
+ dict_T *d_ret = NULL;
+
+ if (filtermap == FILTERMAP_MAPNEW) {
+ tv_dict_alloc_ret(rettv);
+ d_ret = rettv->vval.v_dict;
+ }
+
+ const VarLockStatus prev_lock = d->dv_lock;
+ if (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;
+ }
+ set_vim_var_string(VV_KEY, di->di_key, -1);
+ typval_T newtv;
+ bool rem;
+ int r = filter_map_one(&di->di_tv, expr, filtermap, &newtv, &rem);
+ tv_clear(get_vim_var_tv(VV_KEY));
+ 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(), filter(), foreach() for a Blob.
+static void filter_map_blob(blob_T *blob_arg, filtermap_T filtermap, typval_T *expr,
+ const char *arg_errmsg, typval_T *rettv)
+{
+ if (filtermap == FILTERMAP_MAPNEW) {
+ rettv->v_type = VAR_BLOB;
+ rettv->vval.v_blob = NULL;
+ }
+ blob_T *b = blob_arg;
+ if (b == NULL
+ || (filtermap == FILTERMAP_FILTER
+ && value_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE))) {
+ return;
+ }
+
+ blob_T *b_ret = b;
+
+ if (filtermap == FILTERMAP_MAPNEW) {
+ tv_blob_copy(b, rettv);
+ b_ret = rettv->vval.v_blob;
+ }
+
+ // set_vim_var_nr() doesn't set the type
+ set_vim_var_type(VV_KEY, VAR_NUMBER);
+
+ const VarLockStatus prev_lock = b->bv_lock;
+ if (b->bv_lock == 0) {
+ b->bv_lock = VAR_LOCKED;
+ }
+
+ 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,
+ };
+ set_vim_var_nr(VV_KEY, idx);
+ typval_T newtv;
+ bool rem;
+ if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
+ || did_emsg) {
+ break;
+ }
+ if (filtermap != FILTERMAP_FOREACH) {
+ 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++;
+ }
+
+ b->bv_lock = prev_lock;
+}
+
+/// Implementation of map(), filter(), foreach() 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;
+
+ // set_vim_var_nr() doesn't set the type
+ set_vim_var_type(VV_KEY, 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 = xmemdupz(p, (size_t)len),
+ };
+
+ set_vim_var_nr(VV_KEY, 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;
+ }
+ if (filtermap == FILTERMAP_MAP || filtermap == FILTERMAP_MAPNEW) {
+ 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 (filtermap == FILTERMAP_FOREACH || !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(), filter(), foreach() for a List. Apply "expr" to
+/// every item in List "l" and return the result in "rettv".
+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;
+ }
+
+ list_T *l_ret = NULL;
+
+ if (filtermap == FILTERMAP_MAPNEW) {
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ l_ret = rettv->vval.v_list;
+ }
+ // set_vim_var_nr() doesn't set the type
+ set_vim_var_type(VV_KEY, VAR_NUMBER);
+
+ const VarLockStatus prev_lock = tv_list_locked(l);
+ if (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;
+ }
+ set_vim_var_nr(VV_KEY, 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(), filter() and foreach().
+static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
+{
+ const char *const func_name = (filtermap == FILTERMAP_MAP
+ ? "map()"
+ : (filtermap == FILTERMAP_MAPNEW
+ ? "mapnew()"
+ : (filtermap == FILTERMAP_FILTER
+ ? "filter()"
+ : "foreach()")));
+ const char *const arg_errmsg = (filtermap == FILTERMAP_MAP
+ ? N_("map() argument")
+ : (filtermap == FILTERMAP_MAPNEW
+ ? N_("mapnew() argument")
+ : (filtermap == FILTERMAP_FILTER
+ ? N_("filter() argument")
+ : N_("foreach() argument"))));
+
+ // map(), filter(), foreach() return the first argument, also on failure.
+ if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING) {
+ tv_copy(&argvars[0], rettv);
+ }
+
+ 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);
+ return;
+ }
+
+ typval_T *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) {
+ return;
+ }
+
+ 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.
+ int save_did_emsg = did_emsg;
+ did_emsg = false;
+
+ if (argvars[0].v_type == VAR_DICT) {
+ filter_map_dict(argvars[0].vval.v_dict, filtermap, func_name,
+ arg_errmsg, expr, rettv);
+ } else if (argvars[0].v_type == VAR_BLOB) {
+ filter_map_blob(argvars[0].vval.v_blob, filtermap, expr, arg_errmsg, rettv);
+ } else if (argvars[0].v_type == VAR_STRING) {
+ filter_map_string(tv_get_string(&argvars[0]), filtermap, expr, rettv);
+ } else {
+ assert(argvars[0].v_type == VAR_LIST);
+ filter_map_list(argvars[0].vval.v_list, filtermap, func_name,
+ arg_errmsg, expr, rettv);
+ }
+
+ restore_vimvar(VV_KEY, &save_key);
+ restore_vimvar(VV_VAL, &save_val);
+
+ did_emsg |= save_did_emsg;
+}
+
+/// "filter()" function
+void f_filter(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ filter_map(argvars, rettv, FILTERMAP_FILTER);
+}
+
+/// "map()" function
+void f_map(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ filter_map(argvars, rettv, FILTERMAP_MAP);
+}
+
+/// "mapnew()" function
+void f_mapnew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ filter_map(argvars, rettv, FILTERMAP_MAPNEW);
+}
+
+/// "foreach()" function
+void f_foreach(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ filter_map(argvars, rettv, FILTERMAP_FOREACH);
+}
+
+/// "add(list, item)" function
+void f_add(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = 1; // Default: failed.
+ if (argvars[0].v_type == VAR_LIST) {
+ list_T *const l = argvars[0].vval.v_list;
+ if (!value_check_lock(tv_list_locked(l), N_("add() argument"),
+ TV_TRANSLATE)) {
+ tv_list_append_tv(l, &argvars[1]);
+ tv_copy(&argvars[0], rettv);
+ }
+ } else if (argvars[0].v_type == VAR_BLOB) {
+ blob_T *const b = argvars[0].vval.v_blob;
+ if (b != NULL
+ && !value_check_lock(b->bv_lock, N_("add() argument"), TV_TRANSLATE)) {
+ bool error = false;
+ const varnumber_T n = tv_get_number_chk(&argvars[1], &error);
+
+ if (!error) {
+ ga_append(&b->bv_ga, (uint8_t)n);
+ tv_copy(&argvars[0], rettv);
+ }
+ }
+ } else {
+ emsg(_(e_listblobreq));
+ }
+}
+
+/// Count the number of times "needle" occurs in string "haystack".
+///
+/// @param ic ignore case
+static varnumber_T count_string(const char *haystack, const char *needle, bool ic)
+{
+ varnumber_T n = 0;
+ const char *p = haystack;
+
+ if (p == NULL || needle == NULL || *needle == NUL) {
+ return 0;
+ }
+
+ if (ic) {
+ const size_t len = strlen(needle);
+
+ while (*p != NUL) {
+ if (mb_strnicmp(p, needle, len) == 0) {
+ n++;
+ p += len;
+ } else {
+ MB_PTR_ADV(p);
+ }
+ }
+ } else {
+ const char *next;
+ while ((next = strstr(p, needle)) != NULL) {
+ n++;
+ p = next + strlen(needle);
+ }
+ }
+
+ return n;
+}
+
+/// Count the number of times item "needle" occurs in List "l" starting at index "idx".
+///
+/// @param ic ignore case
+static varnumber_T count_list(list_T *l, typval_T *needle, int64_t idx, bool ic)
+{
+ if (tv_list_len(l) == 0) {
+ return 0;
+ }
+
+ listitem_T *li = tv_list_find(l, (int)idx);
+ if (li == NULL) {
+ semsg(_(e_list_index_out_of_range_nr), idx);
+ return 0;
+ }
+
+ varnumber_T n = 0;
+
+ for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ if (tv_equal(TV_LIST_ITEM_TV(li), needle, ic)) {
+ n++;
+ }
+ }
+
+ return n;
+}
+
+/// Count the number of times item "needle" occurs in Dict "d".
+///
+/// @param ic ignore case
+static varnumber_T count_dict(dict_T *d, typval_T *needle, bool ic)
+{
+ if (d == NULL) {
+ return 0;
+ }
+
+ varnumber_T n = 0;
+
+ TV_DICT_ITER(d, di, {
+ if (tv_equal(&di->di_tv, needle, ic)) {
+ n++;
+ }
+ });
+
+ return n;
+}
+
+/// "count()" function
+void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ varnumber_T n = 0;
+ int ic = 0;
+ bool error = false;
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ ic = (int)tv_get_number_chk(&argvars[2], &error);
+ }
+
+ if (!error && argvars[0].v_type == VAR_STRING) {
+ n = count_string(argvars[0].vval.v_string, tv_get_string_chk(&argvars[1]), ic);
+ } else if (!error && argvars[0].v_type == VAR_LIST) {
+ int64_t idx = 0;
+ if (argvars[2].v_type != VAR_UNKNOWN
+ && argvars[3].v_type != VAR_UNKNOWN) {
+ idx = (int64_t)tv_get_number_chk(&argvars[3], &error);
+ }
+ if (!error) {
+ n = count_list(argvars[0].vval.v_list, &argvars[1], idx, ic);
+ }
+ } else if (!error && argvars[0].v_type == VAR_DICT) {
+ dict_T *d = argvars[0].vval.v_dict;
+
+ if (d != NULL) {
+ if (argvars[2].v_type != VAR_UNKNOWN
+ && argvars[3].v_type != VAR_UNKNOWN) {
+ emsg(_(e_invarg));
+ } else {
+ n = count_dict(argvars[0].vval.v_dict, &argvars[1], ic);
+ }
+ }
+ } else if (!error) {
+ semsg(_(e_argument_of_str_must_be_list_string_or_dictionary), "count()");
+ }
+ rettv->vval.v_number = n;
+}
+
+/// extend() a Dict. Append Dict argvars[1] to Dict argvars[0] and return the
+/// resulting Dict in "rettv".
+///
+/// @param is_new true for extendnew()
+static void extend_dict(typval_T *argvars, const char *arg_errmsg, bool is_new, typval_T *rettv)
+{
+ dict_T *d1 = argvars[0].vval.v_dict;
+ if (d1 == NULL) {
+ const bool locked = value_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE);
+ (void)locked;
+ assert(locked == true);
+ return;
+ }
+ dict_T *const d2 = argvars[1].vval.v_dict;
+ if (d2 == NULL) {
+ // Do nothing
+ tv_copy(&argvars[0], rettv);
+ return;
+ }
+
+ if (!is_new && value_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) {
+ return;
+ }
+
+ if (is_new) {
+ d1 = tv_dict_copy(NULL, d1, false, get_copyID());
+ if (d1 == NULL) {
+ return;
+ }
+ }
+
+ const char *action = "force";
+ // Check the third argument.
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ const char *const av[] = { "keep", "force", "error" };
+
+ action = tv_get_string_chk(&argvars[2]);
+ if (action == NULL) {
+ if (is_new) {
+ tv_dict_unref(d1);
+ }
+ return; // Type error; error message already given.
+ }
+ size_t i;
+ for (i = 0; i < ARRAY_SIZE(av); i++) {
+ if (strcmp(action, av[i]) == 0) {
+ break;
+ }
+ }
+ if (i == 3) {
+ if (is_new) {
+ tv_dict_unref(d1);
+ }
+ semsg(_(e_invarg2), action);
+ return;
+ }
+ }
+
+ tv_dict_extend(d1, d2, action);
+
+ if (is_new) {
+ *rettv = (typval_T){
+ .v_type = VAR_DICT,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_dict = d1,
+ };
+ } else {
+ tv_copy(&argvars[0], rettv);
+ }
+}
+
+/// extend() a List. Append List argvars[1] to List argvars[0] before index
+/// argvars[3] and return the resulting list in "rettv".
+///
+/// @param is_new true for extendnew()
+static void extend_list(typval_T *argvars, const char *arg_errmsg, bool is_new, typval_T *rettv)
+{
+ bool error = false;
+
+ list_T *l1 = argvars[0].vval.v_list;
+ list_T *const l2 = argvars[1].vval.v_list;
+
+ if (!is_new && value_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {
+ return;
+ }
+
+ if (is_new) {
+ l1 = tv_list_copy(NULL, l1, false, get_copyID());
+ if (l1 == NULL) {
+ return;
+ }
+ }
+
+ listitem_T *item;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ int before = (int)tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ return; // Type error; errmsg already given.
+ }
+
+ if (before == tv_list_len(l1)) {
+ item = NULL;
+ } else {
+ item = tv_list_find(l1, before);
+ if (item == NULL) {
+ semsg(_(e_list_index_out_of_range_nr), (int64_t)before);
+ return;
+ }
+ }
+ } else {
+ item = NULL;
+ }
+ tv_list_extend(l1, l2, item);
+
+ if (is_new) {
+ *rettv = (typval_T){
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_list = l1,
+ };
+ } else {
+ tv_copy(&argvars[0], rettv);
+ }
+}
+
+/// "extend()" or "extendnew()" function.
+///
+/// @param is_new true for extendnew()
+static void extend(typval_T *argvars, typval_T *rettv, char *arg_errmsg, bool is_new)
+{
+ if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
+ extend_list(argvars, arg_errmsg, is_new, rettv);
+ } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT) {
+ extend_dict(argvars, arg_errmsg, is_new, rettv);
+ } else {
+ semsg(_(e_listdictarg), is_new ? "extendnew()" : "extend()");
+ }
+}
+
+/// "extend(list, list [, idx])" function
+/// "extend(dict, dict [, action])" function
+void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char *errmsg = N_("extend() argument");
+ extend(argvars, rettv, errmsg, false);
+}
+
+/// "extendnew(list, list [, idx])" function
+/// "extendnew(dict, dict [, action])" function
+void f_extendnew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char *errmsg = N_("extendnew() argument");
+ extend(argvars, rettv, errmsg, true);
+}
+
+/// "insert()" function
+void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ bool error = false;
+
+ if (argvars[0].v_type == VAR_BLOB) {
+ blob_T *const b = argvars[0].vval.v_blob;
+
+ if (b == NULL
+ || value_check_lock(b->bv_lock, N_("insert() argument"),
+ TV_TRANSLATE)) {
+ return;
+ }
+
+ int before = 0;
+ const int len = tv_blob_len(b);
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ before = (int)tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ return; // type error; errmsg already given
+ }
+ if (before < 0 || before > len) {
+ semsg(_(e_invarg2), tv_get_string(&argvars[2]));
+ return;
+ }
+ }
+ const int val = (int)tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ return;
+ }
+ if (val < 0 || val > 255) {
+ semsg(_(e_invarg2), tv_get_string(&argvars[1]));
+ return;
+ }
+
+ ga_grow(&b->bv_ga, 1);
+ uint8_t *const p = (uint8_t *)b->bv_ga.ga_data;
+ memmove(p + before + 1, p + before, (size_t)(len - before));
+ *(p + before) = (uint8_t)val;
+ b->bv_ga.ga_len++;
+
+ tv_copy(&argvars[0], rettv);
+ } else if (argvars[0].v_type != VAR_LIST) {
+ semsg(_(e_listblobarg), "insert()");
+ } else {
+ list_T *l = argvars[0].vval.v_list;
+ if (value_check_lock(tv_list_locked(l), N_("insert() argument"), TV_TRANSLATE)) {
+ return;
+ }
+
+ int64_t before = 0;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ before = tv_get_number_chk(&argvars[2], &error);
+ }
+ if (error) {
+ // type error; errmsg already given
+ return;
+ }
+
+ listitem_T *item = NULL;
+ if (before != tv_list_len(l)) {
+ item = tv_list_find(l, (int)before);
+ if (item == NULL) {
+ semsg(_(e_list_index_out_of_range_nr), before);
+ l = NULL;
+ }
+ }
+ if (l != NULL) {
+ tv_list_insert_tv(l, &argvars[1], item);
+ tv_copy(&argvars[0], rettv);
+ }
+ }
+}
+
+/// "remove()" function
+void f_remove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *const arg_errmsg = N_("remove() argument");
+
+ if (argvars[0].v_type == VAR_DICT) {
+ tv_dict_remove(argvars, rettv, arg_errmsg);
+ } else if (argvars[0].v_type == VAR_BLOB) {
+ tv_blob_remove(argvars, rettv, arg_errmsg);
+ } else if (argvars[0].v_type == VAR_LIST) {
+ tv_list_remove(argvars, rettv, arg_errmsg);
+ } else {
+ semsg(_(e_listdictblobarg), "remove()");
+ }
+}
+
+/// "reverse({list})" function
+void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ if (tv_check_for_string_or_list_or_blob_arg(argvars, 0) == FAIL) {
+ return;
+ }
+
+ if (argvars[0].v_type == VAR_BLOB) {
+ blob_T *const b = argvars[0].vval.v_blob;
+ const int len = tv_blob_len(b);
+
+ for (int i = 0; i < len / 2; i++) {
+ const uint8_t tmp = tv_blob_get(b, i);
+ tv_blob_set(b, i, tv_blob_get(b, len - i - 1));
+ tv_blob_set(b, len - i - 1, tmp);
+ }
+ tv_blob_set_ret(rettv, b);
+ } else if (argvars[0].v_type == VAR_STRING) {
+ rettv->v_type = VAR_STRING;
+ if (argvars[0].vval.v_string != NULL) {
+ rettv->vval.v_string = reverse_text(argvars[0].vval.v_string);
+ } else {
+ rettv->vval.v_string = NULL;
+ }
+ } else if (argvars[0].v_type == VAR_LIST) {
+ list_T *const l = argvars[0].vval.v_list;
+ if (!value_check_lock(tv_list_locked(l), N_("reverse() argument"),
+ TV_TRANSLATE)) {
+ tv_list_reverse(l);
+ tv_list_set_ret(rettv, l);
+ }
+ }
+}
diff --git a/src/nvim/eval/list.h b/src/nvim/eval/list.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include "nvim/eval/typval_defs.h"
+
+#include "eval/list.h.generated.h"