From 3051e8bbb11f1446729fc964083a62f475938207 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 6 Oct 2025 00:08:57 -0400 Subject: [PATCH] vim-patch:8.1.1981: the evalfunc.c file is too big Problem: The evalfunc.c file is too big. Solution: Move undo functions to undo.c. Move cmdline functions to ex_getln.c. Move some container functions to list.c. https://github.com/vim/vim/commit/08c308aeb5e7dfa18fa61f261b0bff79517a4883 Co-authored-by: Bram Moolenaar --- src/nvim/errors.h | 4 + src/nvim/eval/executor.c | 3 - src/nvim/eval/executor.h | 2 - src/nvim/eval/funcs.c | 430 -------------------------------------- src/nvim/eval/list.c | 432 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 436 insertions(+), 435 deletions(-) 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/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 index ed33e7df29..201e5ed7d2 100644 --- a/src/nvim/eval/list.c +++ b/src/nvim/eval/list.c @@ -1,3 +1,5 @@ +// eval/list.c: List support and container (List, Dict, Blob) functions. + #include "nvim/errors.h" #include "nvim/eval.h" #include "nvim/eval/list.h" @@ -7,6 +9,7 @@ #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() @@ -19,6 +22,8 @@ typedef enum { #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"); @@ -417,3 +422,430 @@ 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); + } + } +}