vim-patch:8.2.3867: implementation of some list functions too complicated (#24757)

Problem:    Implementation of some list functions too complicated.
Solution:   Refactor do_sort_uniq(), f_count() and extend() (Yegappan
            Lakshmanan, closes vim/vim#9378)

d92813a598

Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
This commit is contained in:
zeertzjq 2023-08-17 21:56:03 +08:00 committed by GitHub
parent 5a564bf242
commit de6b58f659
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 387 additions and 334 deletions

View File

@ -884,6 +884,86 @@ 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, false)) {
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, false)) {
n++;
}
});
return n;
}
/// "count()" function
static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@ -895,74 +975,26 @@ static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
ic = (int)tv_get_number_chk(&argvars[2], &error);
}
if (argvars[0].v_type == VAR_STRING) {
const char *expr = tv_get_string_chk(&argvars[1]);
const char *p = argvars[0].vval.v_string;
if (!error && expr != NULL && *expr != NUL && p != NULL) {
if (ic) {
const size_t len = strlen(expr);
while (*p != NUL) {
if (mb_strnicmp(p, expr, len) == 0) {
n++;
p += len;
} else {
MB_PTR_ADV(p);
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 = (long)tv_get_number_chk(&argvars[3], &error);
}
}
} else {
char *next;
while ((next = strstr(p, expr)) != NULL) {
n++;
p = next + strlen(expr);
}
}
}
} else if (argvars[0].v_type == VAR_LIST) {
list_T *l = argvars[0].vval.v_list;
if (l != NULL) {
listitem_T *li = tv_list_first(l);
if (argvars[2].v_type != VAR_UNKNOWN) {
if (argvars[3].v_type != VAR_UNKNOWN) {
int64_t idx = tv_get_number_chk(&argvars[3], &error);
if (!error) {
li = tv_list_find(l, (int)idx);
if (li == NULL) {
semsg(_(e_list_index_out_of_range_nr), idx);
n = count_list(argvars[0].vval.v_list, &argvars[1], idx, ic);
}
}
}
if (error) {
li = NULL;
}
}
for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) {
n++;
}
}
}
} else if (argvars[0].v_type == VAR_DICT) {
} 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) {
if (argvars[3].v_type != VAR_UNKNOWN) {
if (argvars[2].v_type != VAR_UNKNOWN
&& argvars[3].v_type != VAR_UNKNOWN) {
emsg(_(e_invarg));
}
}
int todo = error ? 0 : (int)d->dv_hashtab.ht_used;
for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
todo--;
if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) {
n++;
}
}
} else {
n = count_dict(argvars[0].vval.v_dict, &argvars[1], ic);
}
}
} else {
@ -1875,10 +1907,12 @@ static void f_flattennew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
flatten_common(argvars, rettv, true);
}
/// "extend()" or "extendnew()" function. "is_new" is true for extendnew().
static void extend(typval_T *argvars, typval_T *rettv, char *arg_errmsg, bool is_new)
/// 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)
{
if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
bool error = false;
list_T *l1 = argvars[0].vval.v_list;
@ -1922,7 +1956,14 @@ static void extend(typval_T *argvars, typval_T *rettv, char *arg_errmsg, bool is
tv_copy(&argvars[0], rettv);
}
}
} else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT) {
}
/// 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;
dict_T *const d2 = argvars[1].vval.v_dict;
if (d1 == NULL) {
@ -1973,6 +2014,17 @@ static void extend(typval_T *argvars, typval_T *rettv, char *arg_errmsg, bool is
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()");
}

View File

@ -36,6 +36,19 @@
#include "nvim/types.h"
#include "nvim/vim.h"
/// 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;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/typval.c.generated.h"
#endif
@ -1065,18 +1078,6 @@ void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
}
}
/// 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
@ -1252,86 +1253,133 @@ 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)
/// sort() List "l"
static void do_sort(list_T *l, sortinfo_T *info)
{
ListSortItem *ptrs;
long len;
int i;
const int len = tv_list_len(l);
// 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;
// Make an array with each entry pointing to an item in the List.
ListSortItem *ptrs = xmalloc((size_t)((unsigned)len * sizeof(ListSortItem)));
const char *const arg_errmsg = (sort
? N_("sort() argument")
: N_("uniq() argument"));
// f_sort(): ptrs will be the list to sort
int i = 0;
TV_LIST_ITER(l, li, {
ptrs[i].item = li;
ptrs[i].idx = i;
i++;
});
if (argvars[0].v_type != VAR_LIST) {
semsg(_(e_listarg), sort ? "sort()" : "uniq()");
info->item_compare_func_err = false;
ListSorter item_compare_func = ((info->item_compare_func == NULL
&& info->item_compare_partial == NULL)
? item_compare_not_keeping_zero
: item_compare2_not_keeping_zero);
// Sort the array with item pointers.
qsort(ptrs, (size_t)len, sizeof(ListSortItem), item_compare_func);
if (!info->item_compare_func_err) {
// Clear the list and append the items in the sorted order.
l->lv_first = NULL;
l->lv_last = NULL;
l->lv_idx_item = NULL;
l->lv_len = 0;
for (i = 0; i < len; i++) {
tv_list_append(l, ptrs[i].item);
}
}
if (info->item_compare_func_err) {
emsg(_("E702: Sort compare function failed"));
}
xfree(ptrs);
}
/// uniq() List "l"
static void do_uniq(list_T *l, sortinfo_T *info)
{
const int len = tv_list_len(l);
// Make an array with each entry pointing to an item in the List.
ListSortItem *ptrs = xmalloc((size_t)((unsigned)len * sizeof(ListSortItem)));
// f_uniq(): ptrs will be a stack of items to remove.
info->item_compare_func_err = false;
ListSorter item_compare_func = ((info->item_compare_func == NULL
&& info->item_compare_partial == NULL)
? item_compare_keeping_zero
: item_compare2_keeping_zero);
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(&prev_li, &li) == 0) {
li = tv_list_item_remove(l, li);
} else {
list_T *const l = argvars[0].vval.v_list;
if (value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) {
goto theend;
li = TV_LIST_ITEM_NEXT(l, li);
}
if (info->item_compare_func_err) {
emsg(_("E882: Uniq compare function failed"));
break;
}
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;
xfree(ptrs);
}
/// Parse the optional arguments to sort() and uniq() and return the values in "info".
static int parse_sort_uniq_args(typval_T *argvars, sortinfo_T *info)
{
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) {
return OK;
}
if (argvars[1].v_type != VAR_UNKNOWN) {
// optional second argument: {func}
if (argvars[1].v_type == VAR_FUNC) {
info.item_compare_func = argvars[1].vval.v_string;
info->item_compare_func = argvars[1].vval.v_string;
} else if (argvars[1].v_type == VAR_PARTIAL) {
info.item_compare_partial = argvars[1].vval.v_partial;
info->item_compare_partial = argvars[1].vval.v_partial;
} else {
bool error = false;
i = (int)tv_get_number_chk(&argvars[1], &error);
int nr = (int)tv_get_number_chk(&argvars[1], &error);
if (error) {
goto theend; // type error; errmsg already given
return FAIL; // type error; errmsg already given
}
if (i == 1) {
info.item_compare_ic = true;
if (nr == 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) {
info->item_compare_func = tv_get_string(&argvars[1]);
} else if (nr != 0) {
emsg(_(e_invarg));
goto theend;
return FAIL;
}
if (info.item_compare_func != NULL) {
if (*info.item_compare_func == NUL) {
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;
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;
}
}
}
@ -1339,61 +1387,54 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
if (argvars[2].v_type != VAR_UNKNOWN) {
// optional third argument: {dict}
if (tv_check_for_dict_arg(argvars, 2) == FAIL) {
return FAIL;
}
info->item_compare_selfdict = argvars[2].vval.v_dict;
}
return OK;
}
/// "sort()" or "uniq()" function
static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
{
if (argvars[0].v_type != VAR_LIST) {
semsg(_(e_listarg), sort ? "sort()" : "uniq()");
return;
}
// 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"));
list_T *const l = argvars[0].vval.v_list;
if (value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) {
goto theend;
}
info.item_compare_selfdict = argvars[2].vval.v_dict;
}
}
tv_list_set_ret(rettv, l);
// Make an array with each entry pointing to an item in the List.
ptrs = xmalloc((size_t)((unsigned)len * sizeof(ListSortItem)));
const int len = tv_list_len(l);
if (len <= 1) {
goto theend; // short list sorts pretty quickly
}
if (parse_sort_uniq_args(argvars, &info) == FAIL) {
goto theend;
}
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"));
}
do_sort(l, &info);
} 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;
}
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) {
li = tv_list_item_remove(l, li);
} else {
li = TV_LIST_ITEM_NEXT(l, li);
}
if (info.item_compare_func_err) {
emsg(_("E882: Uniq compare function failed"));
break;
}
}
}
xfree(ptrs);
do_uniq(l, &info);
}
theend:
sortinfo = old_sortinfo;
}
/// "sort"({list})" function
/// "sort({list})" function
void f_sort(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
do_sort_uniq(argvars, rettv, true);
@ -1469,46 +1510,6 @@ void tv_list_reverse(list_T *const l)
l->lv_idx = l->lv_len - l->lv_idx - 1;
}
// FIXME Add unit tests for tv_list_item_sort().
/// Sort list using libc qsort
///
/// @param[in,out] l List to sort, will be sorted in-place.
/// @param ptrs Preallocated array of items to sort, must have at least
/// tv_list_len(l) entries. Should not be initialized.
/// @param[in] item_compare_func Function used to compare list items.
/// @param errp Location where information about whether error occurred is
/// saved by item_compare_func. If boolean there appears to be
/// true list will not be modified. Must be initialized to false
/// by the caller.
void tv_list_item_sort(list_T *const l, ListSortItem *const ptrs,
const ListSorter item_compare_func, const bool *errp)
FUNC_ATTR_NONNULL_ARG(3, 4)
{
const int len = tv_list_len(l);
if (len <= 1) {
return;
}
int i = 0;
TV_LIST_ITER(l, li, {
ptrs[i].item = li;
ptrs[i].idx = i;
i++;
});
// Sort the array with item pointers.
qsort(ptrs, (size_t)len, sizeof(ListSortItem), item_compare_func);
if (!(*errp)) {
// Clear the list and append the items in the sorted order.
l->lv_first = NULL;
l->lv_last = NULL;
l->lv_idx_item = NULL;
l->lv_len = 0;
for (i = 0; i < len; i++) {
tv_list_append(l, ptrs[i].item);
}
}
}
//{{{2 Indexing/searching
/// Locate item with a given index in a list and return it