diff --git a/src/nvim/eval.c b/src/nvim/eval.c index fb12214e59..7b2a20e6fb 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -4664,7 +4664,7 @@ static inline bool set_ref_dict(dict_T *dict, int copyID) return false; } -/// Get the key for *{key: val} into "tv" and advance "arg". +/// Get the key for #{key: val} into "tv" and advance "arg". /// /// @return FAIL when there is no valid key. static int get_literal_key(char **arg, typval_T *tv) @@ -4684,7 +4684,7 @@ static int get_literal_key(char **arg, typval_T *tv) } /// Allocate a variable for a Dictionary and fill it from "*arg". -/// "literal" is true for *{key: val} +/// "literal" is true for #{key: val} /// /// @return OK or FAIL. Returns NOTDONE for {expr}. static int dict_get_tv(char **arg, typval_T *rettv, int evaluate, bool literal) @@ -5589,58 +5589,6 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const cmd_silent = cmd_silent_save; } -/// Turn a dictionary into a list -/// -/// @param[in] tv Dictionary to convert. Is checked for actually being -/// a dictionary, will give an error if not. -/// @param[out] rettv Location where result will be saved. -/// @param[in] what What to save in rettv. -void dict_list(typval_T *const tv, typval_T *const rettv, const DictListType what) -{ - if (tv->v_type != VAR_DICT) { - emsg(_(e_dictreq)); - return; - } - if (tv->vval.v_dict == NULL) { - return; - } - - tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); - - TV_DICT_ITER(tv->vval.v_dict, di, { - typval_T tv_item = { .v_lock = VAR_UNLOCKED }; - - switch (what) { - case kDictListKeys: - tv_item.v_type = VAR_STRING; - tv_item.vval.v_string = (char *)vim_strsave(di->di_key); - break; - case kDictListValues: - tv_copy(&di->di_tv, &tv_item); - break; - case kDictListItems: { - // items() - list_T *const sub_l = tv_list_alloc(2); - tv_item.v_type = VAR_LIST; - tv_item.vval.v_list = sub_l; - tv_list_ref(sub_l); - - tv_list_append_owned_tv(sub_l, (typval_T) { - .v_type = VAR_STRING, - .v_lock = VAR_UNLOCKED, - .vval.v_string = xstrdup((const char *)di->di_key), - }); - - tv_list_append_tv(sub_l, &di->di_tv); - - break; - } - } - - tv_list_append_owned_tv(rettv->vval.v_list, tv_item); - }); -} - /// Builds a process argument vector from a VimL object (typval_T). /// /// @param[in] cmd_tv VimL object diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index dac6bb6e34..7d5404d782 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -4200,22 +4200,6 @@ static bool has_wsl(void) return has_wsl == kTrue; } -/// "has_key()" function -static void f_has_key(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_DICT) { - emsg(_(e_dictreq)); - return; - } - if (argvars[0].vval.v_dict == NULL) { - return; - } - - rettv->vval.v_number = tv_dict_find(argvars[0].vval.v_dict, - tv_get_string(&argvars[1]), - -1) != NULL; -} - /// `haslocaldir([{win}[, {tab}]])` function /// /// Returns `1` if the scope object has a local directory, `0` otherwise. If a @@ -4770,12 +4754,6 @@ static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr) vim_vsnprintf_typval(rettv->vval.v_string, len + 1, "%p", dummy_ap, argvars); } -/// "items(dict)" function -static void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_list(argvars, rettv, 2); -} - /// "jobpid(id)" function static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -5208,30 +5186,6 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_list = rv; } -/// "join()" function -static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_LIST) { - emsg(_(e_listreq)); - return; - } - const char *const sep = (argvars[1].v_type == VAR_UNKNOWN - ? " " - : tv_get_string_chk(&argvars[1])); - - rettv->v_type = VAR_STRING; - - if (sep != NULL) { - garray_T ga; - ga_init(&ga, (int)sizeof(char), 80); - tv_list_join(&ga, argvars[0].vval.v_list, sep); - ga_append(&ga, NUL); - rettv->vval.v_string = ga.ga_data; - } else { - rettv->vval.v_string = NULL; - } -} - /// json_decode() function static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -5273,12 +5227,6 @@ static void f_json_encode(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = encode_tv2json(&argvars[0], NULL); } -/// "keys()" function -static void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_list(argvars, rettv, 0); -} - /// "last_buffer_nr()" function. static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -5435,35 +5383,6 @@ static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "list2str()" function -static void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - garray_T ga; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (argvars[0].v_type != VAR_LIST) { - emsg(_(e_invarg)); - return; - } - - list_T *const l = argvars[0].vval.v_list; - if (l == NULL) { - return; // empty list results in empty string - } - - ga_init(&ga, 1, 80); - char buf[MB_MAXBYTES + 1]; - - TV_LIST_ITER_CONST(l, li, { - buf[utf_char2bytes(tv_get_number(TV_LIST_ITEM_TV(li)), (char *)buf)] = NUL; - ga_concat(&ga, (char *)buf); - }); - ga_append(&ga, NUL); - - rettv->vval.v_string = ga.ga_data; -} - /// "localtime()" function static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -6849,134 +6768,16 @@ static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "remove()" function static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - listitem_T *item, *item2; - listitem_T *li; - long idx; - long end; - dict_T *d; - dictitem_T *di; const char *const arg_errmsg = N_("remove() argument"); if (argvars[0].v_type == VAR_DICT) { - if (argvars[2].v_type != VAR_UNKNOWN) { - semsg(_(e_toomanyarg), "remove()"); - } else if ((d = argvars[0].vval.v_dict) != NULL - && !var_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) { - const char *key = tv_get_string_chk(&argvars[1]); - if (key != NULL) { - di = tv_dict_find(d, key, -1); - if (di == NULL) { - semsg(_(e_dictkey), key); - } else if (!var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE) - && !var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) { - *rettv = di->di_tv; - di->di_tv = TV_INITIAL_VALUE; - tv_dict_item_remove(d, di); - if (tv_dict_is_watched(d)) { - tv_dict_watcher_notify(d, key, NULL, rettv); - } - } - } - } + tv_dict_remove(argvars, rettv, arg_errmsg); } else if (argvars[0].v_type == VAR_BLOB) { - blob_T *const b = argvars[0].vval.v_blob; - - if (b != NULL && var_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE)) { - return; - } - - bool error = false; - idx = (long)tv_get_number_chk(&argvars[1], &error); - - if (!error) { - const int len = tv_blob_len(b); - - if (idx < 0) { - // count from the end - idx = len + idx; - } - if (idx < 0 || idx >= len) { - semsg(_(e_blobidx), (int64_t)idx); - return; - } - if (argvars[2].v_type == VAR_UNKNOWN) { - // Remove one item, return its value. - char_u *const p = (char_u *)b->bv_ga.ga_data; - rettv->vval.v_number = (varnumber_T)(*(p + idx)); - memmove(p + idx, p + idx + 1, (size_t)len - idx - 1); - b->bv_ga.ga_len--; - } else { - // Remove range of items, return blob with values. - end = (long)tv_get_number_chk(&argvars[2], &error); - if (error) { - return; - } - if (end < 0) { - // count from the end - end = len + end; - } - if (end >= len || idx > end) { - semsg(_(e_blobidx), (int64_t)end); - return; - } - blob_T *const blob = tv_blob_alloc(); - blob->bv_ga.ga_len = end - idx + 1; - ga_grow(&blob->bv_ga, end - idx + 1); - - char_u *const p = (char_u *)b->bv_ga.ga_data; - memmove((char_u *)blob->bv_ga.ga_data, p + idx, - (size_t)(end - idx + 1)); - tv_blob_set_ret(rettv, blob); - - if (len - end - 1 > 0) { - memmove(p + idx, p + end + 1, (size_t)(len - end - 1)); - } - b->bv_ga.ga_len -= end - idx + 1; - } - } - } else if (argvars[0].v_type != VAR_LIST) { + 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()"); - } else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - arg_errmsg, TV_TRANSLATE)) { - bool error = false; - - idx = tv_get_number_chk(&argvars[1], &error); - if (error) { - // Type error: do nothing, errmsg already given. - } else if ((item = tv_list_find(l, idx)) == NULL) { - semsg(_(e_listidx), (int64_t)idx); - } else { - if (argvars[2].v_type == VAR_UNKNOWN) { - // Remove one item, return its value. - tv_list_drop_items(l, item, item); - *rettv = *TV_LIST_ITEM_TV(item); - xfree(item); - } else { - // Remove range of items, return list with values. - end = tv_get_number_chk(&argvars[2], &error); - if (error) { - // Type error: do nothing. - } else if ((item2 = tv_list_find(l, end)) == NULL) { - semsg(_(e_listidx), (int64_t)end); - } else { - int cnt = 0; - - for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { - cnt++; - if (li == item2) { - break; - } - } - if (li == NULL) { // Didn't find "item2" after "item". - emsg(_(e_invrange)); - } else { - tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt), - cnt); - } - } - } - } } } @@ -8867,341 +8668,6 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; } -/// struct storing information about current sort -typedef struct { - int item_compare_ic; - bool item_compare_lc; - bool item_compare_numeric; - bool item_compare_numbers; - bool item_compare_float; - const char *item_compare_func; - partial_T *item_compare_partial; - dict_T *item_compare_selfdict; - bool item_compare_func_err; -} sortinfo_T; -static sortinfo_T *sortinfo = NULL; - -#define ITEM_COMPARE_FAIL 999 - -/// Compare functions for f_sort() and f_uniq() below. -static int item_compare(const void *s1, const void *s2, bool keep_zero) -{ - ListSortItem *const si1 = (ListSortItem *)s1; - ListSortItem *const si2 = (ListSortItem *)s2; - - typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item); - typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item); - - int res; - - if (sortinfo->item_compare_numbers) { - const varnumber_T v1 = tv_get_number(tv1); - const varnumber_T v2 = tv_get_number(tv2); - - res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; - goto item_compare_end; - } - - if (sortinfo->item_compare_float) { - const float_T v1 = tv_get_float(tv1); - const float_T v2 = tv_get_float(tv2); - - res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; - goto item_compare_end; - } - - char *tofree1 = NULL; - char *tofree2 = NULL; - char *p1; - char *p2; - - // encode_tv2string() puts quotes around a string and allocates memory. Don't - // do that for string variables. Use a single quote when comparing with - // a non-string to do what the docs promise. - if (tv1->v_type == VAR_STRING) { - if (tv2->v_type != VAR_STRING || sortinfo->item_compare_numeric) { - p1 = "'"; - } else { - p1 = tv1->vval.v_string; - } - } else { - tofree1 = p1 = encode_tv2string(tv1, NULL); - } - if (tv2->v_type == VAR_STRING) { - if (tv1->v_type != VAR_STRING || sortinfo->item_compare_numeric) { - p2 = "'"; - } else { - p2 = tv2->vval.v_string; - } - } else { - tofree2 = p2 = encode_tv2string(tv2, NULL); - } - if (p1 == NULL) { - p1 = ""; - } - if (p2 == NULL) { - p2 = ""; - } - if (!sortinfo->item_compare_numeric) { - if (sortinfo->item_compare_lc) { - res = strcoll(p1, p2); - } else { - res = sortinfo->item_compare_ic ? STRICMP(p1, p2): STRCMP(p1, p2); - } - } else { - double n1, n2; - n1 = strtod(p1, &p1); - n2 = strtod(p2, &p2); - res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1; - } - - xfree(tofree1); - xfree(tofree2); - -item_compare_end: - // When the result would be zero, compare the item indexes. Makes the - // sort stable. - if (res == 0 && !keep_zero) { - // WARNING: When using uniq si1 and si2 are actually listitem_T **, no - // indexes are there. - res = si1->idx > si2->idx ? 1 : -1; - } - return res; -} - -static int item_compare_keeping_zero(const void *s1, const void *s2) -{ - return item_compare(s1, s2, true); -} - -static int item_compare_not_keeping_zero(const void *s1, const void *s2) -{ - return item_compare(s1, s2, false); -} - -static int item_compare2(const void *s1, const void *s2, bool keep_zero) -{ - ListSortItem *si1, *si2; - int res; - typval_T rettv; - typval_T argv[3]; - const char *func_name; - partial_T *partial = sortinfo->item_compare_partial; - - // shortcut after failure in previous call; compare all items equal - if (sortinfo->item_compare_func_err) { - return 0; - } - - si1 = (ListSortItem *)s1; - si2 = (ListSortItem *)s2; - - if (partial == NULL) { - func_name = sortinfo->item_compare_func; - } else { - func_name = (const char *)partial_name(partial); - } - - // Copy the values. This is needed to be able to set v_lock to VAR_FIXED - // in the copy without changing the original list items. - tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]); - tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]); - - rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this - funcexe_T funcexe = FUNCEXE_INIT; - funcexe.evaluate = true; - funcexe.partial = partial; - funcexe.selfdict = sortinfo->item_compare_selfdict; - res = call_func(func_name, -1, &rettv, 2, argv, &funcexe); - tv_clear(&argv[0]); - tv_clear(&argv[1]); - - if (res == FAIL) { - res = ITEM_COMPARE_FAIL; - } else { - res = tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); - if (res > 0) { - res = 1; - } else if (res < 0) { - res = -1; - } - } - if (sortinfo->item_compare_func_err) { - res = ITEM_COMPARE_FAIL; // return value has wrong type - } - tv_clear(&rettv); - - // When the result would be zero, compare the pointers themselves. Makes - // the sort stable. - if (res == 0 && !keep_zero) { - // WARNING: When using uniq si1 and si2 are actually listitem_T **, no - // indexes are there. - res = si1->idx > si2->idx ? 1 : -1; - } - - return res; -} - -static int item_compare2_keeping_zero(const void *s1, const void *s2) -{ - return item_compare2(s1, s2, true); -} - -static int item_compare2_not_keeping_zero(const void *s1, const void *s2) -{ - return item_compare2(s1, s2, false); -} - -/// "sort({list})" function -static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) -{ - ListSortItem *ptrs; - long len; - long i; - - // Pointer to current info struct used in compare function. Save and restore - // the current one for nested calls. - sortinfo_T info; - sortinfo_T *old_sortinfo = sortinfo; - sortinfo = &info; - - const char *const arg_errmsg = (sort - ? N_("sort() argument") - : N_("uniq() argument")); - - if (argvars[0].v_type != VAR_LIST) { - semsg(_(e_listarg), sort ? "sort()" : "uniq()"); - } else { - list_T *const l = argvars[0].vval.v_list; - if (var_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { - goto theend; - } - tv_list_set_ret(rettv, l); - - len = tv_list_len(l); - if (len <= 1) { - goto theend; // short list sorts pretty quickly - } - - info.item_compare_ic = false; - info.item_compare_lc = false; - info.item_compare_numeric = false; - info.item_compare_numbers = false; - info.item_compare_float = false; - info.item_compare_func = NULL; - info.item_compare_partial = NULL; - info.item_compare_selfdict = NULL; - - if (argvars[1].v_type != VAR_UNKNOWN) { - // optional second argument: {func} - if (argvars[1].v_type == VAR_FUNC) { - info.item_compare_func = (const char *)argvars[1].vval.v_string; - } else if (argvars[1].v_type == VAR_PARTIAL) { - info.item_compare_partial = argvars[1].vval.v_partial; - } else { - bool error = false; - - i = tv_get_number_chk(&argvars[1], &error); - if (error) { - goto theend; // type error; errmsg already given - } - if (i == 1) { - info.item_compare_ic = true; - } else if (argvars[1].v_type != VAR_NUMBER) { - info.item_compare_func = tv_get_string(&argvars[1]); - } else if (i != 0) { - emsg(_(e_invarg)); - goto theend; - } - if (info.item_compare_func != NULL) { - if (*info.item_compare_func == NUL) { - // empty string means default sort - info.item_compare_func = NULL; - } else if (strcmp(info.item_compare_func, "n") == 0) { - info.item_compare_func = NULL; - info.item_compare_numeric = true; - } else if (strcmp(info.item_compare_func, "N") == 0) { - info.item_compare_func = NULL; - info.item_compare_numbers = true; - } else if (strcmp(info.item_compare_func, "f") == 0) { - info.item_compare_func = NULL; - info.item_compare_float = true; - } else if (strcmp(info.item_compare_func, "i") == 0) { - info.item_compare_func = NULL; - info.item_compare_ic = true; - } else if (strcmp(info.item_compare_func, "l") == 0) { - info.item_compare_func = NULL; - info.item_compare_lc = true; - } - } - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - // optional third argument: {dict} - if (argvars[2].v_type != VAR_DICT) { - emsg(_(e_dictreq)); - goto theend; - } - info.item_compare_selfdict = argvars[2].vval.v_dict; - } - } - - // Make an array with each entry pointing to an item in the List. - ptrs = xmalloc((size_t)(len * sizeof(ListSortItem))); - - if (sort) { - info.item_compare_func_err = false; - tv_list_item_sort(l, ptrs, - ((info.item_compare_func == NULL - && info.item_compare_partial == NULL) - ? item_compare_not_keeping_zero - : item_compare2_not_keeping_zero), - &info.item_compare_func_err); - if (info.item_compare_func_err) { - emsg(_("E702: Sort compare function failed")); - } - } else { - ListSorter item_compare_func_ptr; - - // f_uniq(): ptrs will be a stack of items to remove. - info.item_compare_func_err = false; - if (info.item_compare_func != NULL - || info.item_compare_partial != NULL) { - item_compare_func_ptr = item_compare2_keeping_zero; - } else { - item_compare_func_ptr = item_compare_keeping_zero; - } - - int idx = 0; - for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) - ; li != NULL;) { - listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); - if (item_compare_func_ptr(&prev_li, &li) == 0) { - if (info.item_compare_func_err) { // -V547 - emsg(_("E882: Uniq compare function failed")); - break; - } - li = tv_list_item_remove(l, li); - } else { - idx++; - li = TV_LIST_ITEM_NEXT(l, li); - } - } - } - - xfree(ptrs); - } - -theend: - sortinfo = old_sortinfo; -} - -/// "sort"({list})" function -static void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - do_sort_uniq(argvars, rettv, true); -} - /// "stdioopen()" function static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -9237,12 +8703,6 @@ static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; } -/// "uniq({list})" function -static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - do_sort_uniq(argvars, rettv, false); -} - /// "reltimefloat()" function static void f_reltimefloat(typval_T *argvars, typval_T *rettv, FunPtr fptr) FUNC_ATTR_NONNULL_ALL @@ -10801,12 +10261,6 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); } -/// "values(dict)" function -static void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_list(argvars, rettv, 1); -} - /// "virtcol(string)" function static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) { diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 63458e7e69..fd57b45e86 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -830,6 +830,454 @@ int tv_list_join(garray_T *const gap, list_T *const l, const char *const sep) return retval; } +/// "join()" function +void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_LIST) { + emsg(_(e_listreq)); + return; + } + const char *const sep = (argvars[1].v_type == VAR_UNKNOWN + ? " " + : tv_get_string_chk(&argvars[1])); + + rettv->v_type = VAR_STRING; + + if (sep != NULL) { + garray_T ga; + ga_init(&ga, (int)sizeof(char), 80); + tv_list_join(&ga, argvars[0].vval.v_list, sep); + ga_append(&ga, NUL); + rettv->vval.v_string = ga.ga_data; + } else { + rettv->vval.v_string = NULL; + } +} + +/// "list2str()" function +void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + garray_T ga; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (argvars[0].v_type != VAR_LIST) { + emsg(_(e_invarg)); + return; + } + + list_T *const l = argvars[0].vval.v_list; + if (l == NULL) { + return; // empty list results in empty string + } + + ga_init(&ga, 1, 80); + char buf[MB_MAXBYTES + 1]; + + TV_LIST_ITER_CONST(l, li, { + buf[utf_char2bytes((int)tv_get_number(TV_LIST_ITEM_TV(li)), (char *)buf)] = NUL; + ga_concat(&ga, (char *)buf); + }); + ga_append(&ga, NUL); + + rettv->vval.v_string = ga.ga_data; +} + +/// "remove({list})" function +void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) +{ + list_T *l; + bool error = false; + + if (var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + arg_errmsg, TV_TRANSLATE)) { + return; + } + + long idx = tv_get_number_chk(&argvars[1], &error); + + listitem_T *item; + + if (error) { + // Type error: do nothing, errmsg already given. + } else if ((item = tv_list_find(l, (int)idx)) == NULL) { + semsg(_(e_listidx), (int64_t)idx); + } else { + if (argvars[2].v_type == VAR_UNKNOWN) { + // Remove one item, return its value. + tv_list_drop_items(l, item, item); + *rettv = *TV_LIST_ITEM_TV(item); + xfree(item); + } else { + listitem_T *item2; + // Remove range of items, return list with values. + long end = tv_get_number_chk(&argvars[2], &error); + if (error) { + // Type error: do nothing. + } else if ((item2 = tv_list_find(l, (int)end)) == NULL) { + semsg(_(e_listidx), (int64_t)end); + } else { + int cnt = 0; + + listitem_T *li; + for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + cnt++; + if (li == item2) { + break; + } + } + if (li == NULL) { // Didn't find "item2" after "item". + emsg(_(e_invrange)); + } else { + tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt), + cnt); + } + } + } + } +} + +/// struct storing information about current sort +typedef struct { + int item_compare_ic; + bool item_compare_lc; + bool item_compare_numeric; + bool item_compare_numbers; + bool item_compare_float; + const char *item_compare_func; + partial_T *item_compare_partial; + dict_T *item_compare_selfdict; + bool item_compare_func_err; +} sortinfo_T; +static sortinfo_T *sortinfo = NULL; + +#define ITEM_COMPARE_FAIL 999 + +/// Compare functions for f_sort() and f_uniq() below. +static int item_compare(const void *s1, const void *s2, bool keep_zero) +{ + ListSortItem *const si1 = (ListSortItem *)s1; + ListSortItem *const si2 = (ListSortItem *)s2; + + typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item); + typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item); + + int res; + + if (sortinfo->item_compare_numbers) { + const varnumber_T v1 = tv_get_number(tv1); + const varnumber_T v2 = tv_get_number(tv2); + + res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; + goto item_compare_end; + } + + if (sortinfo->item_compare_float) { + const float_T v1 = tv_get_float(tv1); + const float_T v2 = tv_get_float(tv2); + + res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1; + goto item_compare_end; + } + + char *tofree1 = NULL; + char *tofree2 = NULL; + char *p1; + char *p2; + + // encode_tv2string() puts quotes around a string and allocates memory. Don't + // do that for string variables. Use a single quote when comparing with + // a non-string to do what the docs promise. + if (tv1->v_type == VAR_STRING) { + if (tv2->v_type != VAR_STRING || sortinfo->item_compare_numeric) { + p1 = "'"; + } else { + p1 = tv1->vval.v_string; + } + } else { + tofree1 = p1 = encode_tv2string(tv1, NULL); + } + if (tv2->v_type == VAR_STRING) { + if (tv1->v_type != VAR_STRING || sortinfo->item_compare_numeric) { + p2 = "'"; + } else { + p2 = tv2->vval.v_string; + } + } else { + tofree2 = p2 = encode_tv2string(tv2, NULL); + } + if (p1 == NULL) { + p1 = ""; + } + if (p2 == NULL) { + p2 = ""; + } + if (!sortinfo->item_compare_numeric) { + if (sortinfo->item_compare_lc) { + res = strcoll(p1, p2); + } else { + res = sortinfo->item_compare_ic ? STRICMP(p1, p2): STRCMP(p1, p2); + } + } else { + double n1, n2; + n1 = strtod(p1, &p1); + n2 = strtod(p2, &p2); + res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1; + } + + xfree(tofree1); + xfree(tofree2); + +item_compare_end: + // When the result would be zero, compare the item indexes. Makes the + // sort stable. + if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. + res = si1->idx > si2->idx ? 1 : -1; + } + return res; +} + +static int item_compare_keeping_zero(const void *s1, const void *s2) +{ + return item_compare(s1, s2, true); +} + +static int item_compare_not_keeping_zero(const void *s1, const void *s2) +{ + return item_compare(s1, s2, false); +} + +static int item_compare2(const void *s1, const void *s2, bool keep_zero) +{ + ListSortItem *si1, *si2; + int res; + typval_T rettv; + typval_T argv[3]; + const char *func_name; + partial_T *partial = sortinfo->item_compare_partial; + + // shortcut after failure in previous call; compare all items equal + if (sortinfo->item_compare_func_err) { + return 0; + } + + si1 = (ListSortItem *)s1; + si2 = (ListSortItem *)s2; + + if (partial == NULL) { + func_name = sortinfo->item_compare_func; + } else { + func_name = (const char *)partial_name(partial); + } + + // Copy the values. This is needed to be able to set v_lock to VAR_FIXED + // in the copy without changing the original list items. + tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]); + tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]); + + rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.evaluate = true; + funcexe.partial = partial; + funcexe.selfdict = sortinfo->item_compare_selfdict; + res = call_func(func_name, -1, &rettv, 2, argv, &funcexe); + tv_clear(&argv[0]); + tv_clear(&argv[1]); + + if (res == FAIL) { + res = ITEM_COMPARE_FAIL; + } else { + res = (int)tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); + if (res > 0) { + res = 1; + } else if (res < 0) { + res = -1; + } + } + if (sortinfo->item_compare_func_err) { + res = ITEM_COMPARE_FAIL; // return value has wrong type + } + tv_clear(&rettv); + + // When the result would be zero, compare the pointers themselves. Makes + // the sort stable. + if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. + res = si1->idx > si2->idx ? 1 : -1; + } + + return res; +} + +static int item_compare2_keeping_zero(const void *s1, const void *s2) +{ + return item_compare2(s1, s2, true); +} + +static int item_compare2_not_keeping_zero(const void *s1, const void *s2) +{ + return item_compare2(s1, s2, false); +} + +/// "sort({list})" function +static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) +{ + ListSortItem *ptrs; + long len; + long i; + + // Pointer to current info struct used in compare function. Save and restore + // the current one for nested calls. + sortinfo_T info; + sortinfo_T *old_sortinfo = sortinfo; + sortinfo = &info; + + const char *const arg_errmsg = (sort + ? N_("sort() argument") + : N_("uniq() argument")); + + if (argvars[0].v_type != VAR_LIST) { + semsg(_(e_listarg), sort ? "sort()" : "uniq()"); + } else { + list_T *const l = argvars[0].vval.v_list; + if (var_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { + goto theend; + } + tv_list_set_ret(rettv, l); + + len = tv_list_len(l); + if (len <= 1) { + goto theend; // short list sorts pretty quickly + } + + info.item_compare_ic = false; + info.item_compare_lc = false; + info.item_compare_numeric = false; + info.item_compare_numbers = false; + info.item_compare_float = false; + info.item_compare_func = NULL; + info.item_compare_partial = NULL; + info.item_compare_selfdict = NULL; + + if (argvars[1].v_type != VAR_UNKNOWN) { + // optional second argument: {func} + if (argvars[1].v_type == VAR_FUNC) { + info.item_compare_func = (const char *)argvars[1].vval.v_string; + } else if (argvars[1].v_type == VAR_PARTIAL) { + info.item_compare_partial = argvars[1].vval.v_partial; + } else { + bool error = false; + + i = tv_get_number_chk(&argvars[1], &error); + if (error) { + goto theend; // type error; errmsg already given + } + if (i == 1) { + info.item_compare_ic = true; + } else if (argvars[1].v_type != VAR_NUMBER) { + info.item_compare_func = tv_get_string(&argvars[1]); + } else if (i != 0) { + emsg(_(e_invarg)); + goto theend; + } + if (info.item_compare_func != NULL) { + if (*info.item_compare_func == NUL) { + // empty string means default sort + info.item_compare_func = NULL; + } else if (strcmp(info.item_compare_func, "n") == 0) { + info.item_compare_func = NULL; + info.item_compare_numeric = true; + } else if (strcmp(info.item_compare_func, "N") == 0) { + info.item_compare_func = NULL; + info.item_compare_numbers = true; + } else if (strcmp(info.item_compare_func, "f") == 0) { + info.item_compare_func = NULL; + info.item_compare_float = true; + } else if (strcmp(info.item_compare_func, "i") == 0) { + info.item_compare_func = NULL; + info.item_compare_ic = true; + } else if (strcmp(info.item_compare_func, "l") == 0) { + info.item_compare_func = NULL; + info.item_compare_lc = true; + } + } + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + // optional third argument: {dict} + if (argvars[2].v_type != VAR_DICT) { + emsg(_(e_dictreq)); + goto theend; + } + info.item_compare_selfdict = argvars[2].vval.v_dict; + } + } + + // Make an array with each entry pointing to an item in the List. + ptrs = xmalloc((size_t)((unsigned)len * sizeof(ListSortItem))); + + if (sort) { + info.item_compare_func_err = false; + tv_list_item_sort(l, ptrs, + ((info.item_compare_func == NULL + && info.item_compare_partial == NULL) + ? item_compare_not_keeping_zero + : item_compare2_not_keeping_zero), + &info.item_compare_func_err); + if (info.item_compare_func_err) { + emsg(_("E702: Sort compare function failed")); + } + } else { + ListSorter item_compare_func_ptr; + + // f_uniq(): ptrs will be a stack of items to remove. + info.item_compare_func_err = false; + if (info.item_compare_func != NULL + || info.item_compare_partial != NULL) { + item_compare_func_ptr = item_compare2_keeping_zero; + } else { + item_compare_func_ptr = item_compare_keeping_zero; + } + + int idx = 0; + for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) + ; li != NULL;) { + listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); + if (item_compare_func_ptr(&prev_li, &li) == 0) { + if (info.item_compare_func_err) { // -V547 + emsg(_("E882: Uniq compare function failed")); + break; + } + li = tv_list_item_remove(l, li); + } else { + idx++; + li = TV_LIST_ITEM_NEXT(l, li); + } + } + } + + xfree(ptrs); + } + +theend: + sortinfo = old_sortinfo; +} + +/// "sort"({list})" function +void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + do_sort_uniq(argvars, rettv, true); +} + +/// "uniq({list})" function +void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + do_sort_uniq(argvars, rettv, false); +} + /// Check whether two lists are equal /// /// @param[in] l1 First list to compare. @@ -2200,6 +2648,66 @@ bool tv_blob_equal(const blob_T *const b1, const blob_T *const b2) return true; } +/// "remove({blob})" function +void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) +{ + blob_T *const b = argvars[0].vval.v_blob; + + if (b != NULL && var_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE)) { + return; + } + + bool error = false; + long idx = tv_get_number_chk(&argvars[1], &error); + + if (!error) { + const int len = tv_blob_len(b); + + if (idx < 0) { + // count from the end + idx = len + idx; + } + if (idx < 0 || idx >= len) { + semsg(_(e_blobidx), (int64_t)idx); + return; + } + if (argvars[2].v_type == VAR_UNKNOWN) { + // Remove one item, return its value. + char_u *const p = (char_u *)b->bv_ga.ga_data; + rettv->vval.v_number = (varnumber_T)(*(p + idx)); + memmove(p + idx, p + idx + 1, (size_t)(len - idx - 1)); + b->bv_ga.ga_len--; + } else { + // Remove range of items, return blob with values. + long end = tv_get_number_chk(&argvars[2], &error); + if (error) { + return; + } + if (end < 0) { + // count from the end + end = len + end; + } + if (end >= len || idx > end) { + semsg(_(e_blobidx), (int64_t)end); + return; + } + blob_T *const blob = tv_blob_alloc(); + blob->bv_ga.ga_len = (int)(end - idx + 1); + ga_grow(&blob->bv_ga, (int)(end - idx + 1)); + + char_u *const p = (char_u *)b->bv_ga.ga_data; + memmove((char_u *)blob->bv_ga.ga_data, p + idx, + (size_t)(end - idx + 1)); + tv_blob_set_ret(rettv, blob); + + if (len - end - 1 > 0) { + memmove(p + idx, p + end + 1, (size_t)(len - end - 1)); + } + b->bv_ga.ga_len -= (int)(end - idx + 1); + } + } +} + //{{{1 Generic typval operations //{{{2 Init/alloc/clear //{{{3 Alloc @@ -2244,6 +2752,118 @@ void tv_dict_alloc_ret(typval_T *const ret_tv) tv_dict_set_ret(ret_tv, d); } +/// Turn a dictionary into a list +/// +/// @param[in] tv Dictionary to convert. Is checked for actually being +/// a dictionary, will give an error if not. +/// @param[out] rettv Location where result will be saved. +/// @param[in] what What to save in rettv. +static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictListType what) +{ + if (tv->v_type != VAR_DICT) { + emsg(_(e_dictreq)); + return; + } + if (tv->vval.v_dict == NULL) { + return; + } + + tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); + + TV_DICT_ITER(tv->vval.v_dict, di, { + typval_T tv_item = { .v_lock = VAR_UNLOCKED }; + + switch (what) { + case kDictListKeys: + tv_item.v_type = VAR_STRING; + tv_item.vval.v_string = (char *)vim_strsave(di->di_key); + break; + case kDictListValues: + tv_copy(&di->di_tv, &tv_item); + break; + case kDictListItems: { + // items() + list_T *const sub_l = tv_list_alloc(2); + tv_item.v_type = VAR_LIST; + tv_item.vval.v_list = sub_l; + tv_list_ref(sub_l); + + tv_list_append_owned_tv(sub_l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = xstrdup((const char *)di->di_key), + }); + + tv_list_append_tv(sub_l, &di->di_tv); + + break; + } + } + + tv_list_append_owned_tv(rettv->vval.v_list, tv_item); + }); +} + +/// "items(dict)" function +void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_list(argvars, rettv, 2); +} + +/// "keys()" function +void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_list(argvars, rettv, 0); +} + +/// "values(dict)" function +void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_list(argvars, rettv, 1); +} + +/// "has_key()" function +void f_has_key(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_DICT) { + emsg(_(e_dictreq)); + return; + } + if (argvars[0].vval.v_dict == NULL) { + return; + } + + rettv->vval.v_number = tv_dict_find(argvars[0].vval.v_dict, + tv_get_string(&argvars[1]), + -1) != NULL; +} + +/// "remove({dict})" function +void tv_dict_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) +{ + dict_T *d; + if (argvars[2].v_type != VAR_UNKNOWN) { + semsg(_(e_toomanyarg), "remove()"); + } else if ((d = argvars[0].vval.v_dict) != NULL + && !var_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) { + const char *key = tv_get_string_chk(&argvars[1]); + if (key != NULL) { + dictitem_T *di = tv_dict_find(d, key, -1); + if (di == NULL) { + semsg(_(e_dictkey), key); + } else if (!var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE) + && !var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) { + *rettv = di->di_tv; + di->di_tv = TV_INITIAL_VALUE; + tv_dict_item_remove(d, di); + if (tv_dict_is_watched(d)) { + tv_dict_watcher_notify(d, key, NULL, rettv); + } + } + } + } +} + /// Allocate an empty blob for a return value. /// /// Also sets reference count.