diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index b4fca3b015..2993e59b63 100755 --- 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 index 7ed68b3811..62bcde6c64 100644 --- 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 index 16d7ea930f..2ac837faae 100644 --- a/src/nvim/errors.h +++ b/src/nvim/errors.h @@ -1,5 +1,7 @@ #pragma once +#include + #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 index f4345fbe59..b7737f74d3 100644 --- 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 index 9b70d42e70..75765e5c84 100644 --- 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 index 88ab2e7257..099ca5313d 100644 --- 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 index c805a2ff9e..84d09982e6 100644 --- 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 new file mode 100644 index 0000000000..201e5ed7d2 --- /dev/null +++ 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 new file mode 100644 index 0000000000..7753e613d3 --- /dev/null +++ b/src/nvim/eval/list.h @@ -0,0 +1,5 @@ +#pragma once + +#include "nvim/eval/typval_defs.h" + +#include "eval/list.h.generated.h"