aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2017-12-31 01:11:50 +0100
committerGitHub <noreply@github.com>2017-12-31 01:11:50 +0100
commit9ad557fb2d4a1ef3101c7894a1038aa2eb932a48 (patch)
tree0664208e25fd4672c05527e4521299452da44961
parent46f432074e739a0eca9bb204e9c7769935669dbd (diff)
parent8ac7c23b7dd7c435fb80315921e3704c8e0a7448 (diff)
downloadrneovim-9ad557fb2d4a1ef3101c7894a1038aa2eb932a48.tar.gz
rneovim-9ad557fb2d4a1ef3101c7894a1038aa2eb932a48.tar.bz2
rneovim-9ad557fb2d4a1ef3101c7894a1038aa2eb932a48.zip
Merge pull request #7762 from ZyX-I/remove-some-listitems
Remove some tv_list_item_…() functions
-rw-r--r--src/nvim/api/private/helpers.c8
-rw-r--r--src/nvim/eval.c426
-rw-r--r--src/nvim/eval/decode.c40
-rw-r--r--src/nvim/eval/typval.c167
-rw-r--r--src/nvim/eval/typval.h22
-rw-r--r--src/nvim/lua/converter.c38
-rw-r--r--test/unit/eval/helpers.lua18
-rw-r--r--test/unit/eval/typval_spec.lua266
-rw-r--r--test/unit/helpers.lua77
9 files changed, 608 insertions, 454 deletions
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 4492f8bb93..26ad7ac1a6 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -787,16 +787,14 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
for (uint32_t i = 0; i < obj.data.array.size; i++) {
Object item = obj.data.array.items[i];
- listitem_T *li = tv_list_item_alloc();
+ typval_T li_tv;
- if (!object_to_vim(item, TV_LIST_ITEM_TV(li), err)) {
- // cleanup
- tv_list_item_free(li);
+ if (!object_to_vim(item, &li_tv, err)) {
tv_list_free(list);
return false;
}
- tv_list_append(list, li);
+ tv_list_append_owned_tv(list, li_tv);
}
tv_list_ref(list);
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index e6cb8cdec0..33f8ffb738 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -215,6 +215,15 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL};
static int echo_attr = 0; /* attributes used for ":echo" */
+/// Describe data to return from find_some_match()
+typedef enum {
+ kSomeMatch, ///< Data for match().
+ kSomeMatchEnd, ///< Data for matchend().
+ kSomeMatchList, ///< Data for matchlist().
+ kSomeMatchStr, ///< Data for matchstr().
+ kSomeMatchStrPos, ///< Data for matchstrpos().
+} SomeMatchType;
+
/// trans_function_name() flags
typedef enum {
TFN_INT = 1, ///< May use internal function name
@@ -1506,7 +1515,6 @@ ex_let_vars (
)
{
char_u *arg = arg_start;
- int i;
typval_T ltv;
if (*arg != '[') {
@@ -1525,17 +1533,17 @@ ex_let_vars (
}
list_T *const l = tv->vval.v_list;
- i = tv_list_len(l);
- if (semicolon == 0 && var_count < i) {
+ const int len = tv_list_len(l);
+ if (semicolon == 0 && var_count < len) {
EMSG(_("E687: Less targets than List items"));
return FAIL;
}
- if (var_count - semicolon > i) {
+ if (var_count - semicolon > len) {
EMSG(_("E688: More targets than List items"));
return FAIL;
}
- // l may actually be NULL, but it should fail with E688 or even earlier if you
- // try to do ":let [] = v:_null_list".
+ // List l may actually be NULL, but it should fail with E688 or even earlier
+ // if you try to do ":let [] = v:_null_list".
assert(l != NULL);
listitem_T *item = tv_list_first(l);
@@ -1543,11 +1551,11 @@ ex_let_vars (
arg = skipwhite(arg + 1);
arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]",
nextchars);
- item = TV_LIST_ITEM_NEXT(l, item);
if (arg == NULL) {
return FAIL;
}
+ item = TV_LIST_ITEM_NEXT(l, item);
arg = skipwhite(arg);
if (*arg == ';') {
/* Put the rest of the list (may be empty) in the var after ';'.
@@ -1559,7 +1567,7 @@ ex_let_vars (
}
ltv.v_type = VAR_LIST;
- ltv.v_lock = 0;
+ ltv.v_lock = VAR_UNLOCKED;
ltv.vval.v_list = rest_list;
tv_list_ref(rest_list);
@@ -2412,9 +2420,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
if (TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) == NULL) {
// Need to add an empty item.
tv_list_append_number(lp->ll_list, 0);
- assert(TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li));
+ // ll_li may have become invalid after append, don’t use it.
+ lp->ll_li = tv_list_last(lp->ll_list); // Valid again.
+ } else {
+ lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li);
}
- lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li);
lp->ll_n1++;
}
if (ri != NULL) {
@@ -2887,28 +2897,25 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit)
lp->ll_name_len))) {
return FAIL;
} else if (lp->ll_range) {
- listitem_T *li;
- listitem_T *ll_li = lp->ll_li;
- int ll_n1 = lp->ll_n1;
-
- while (ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= ll_n1)) {
- li = TV_LIST_ITEM_NEXT(lp->ll_list, ll_li);
- if (tv_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock,
+ // Delete a range of List items.
+ listitem_T *const first_li = lp->ll_li;
+ listitem_T *last_li = first_li;
+ for (;;) {
+ listitem_T *const li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li);
+ if (tv_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock,
(const char *)lp->ll_name,
lp->ll_name_len)) {
return false;
}
- ll_li = li;
- ll_n1++;
- }
-
- // Delete a range of List items.
- while (lp->ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) {
- li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li);
- tv_list_item_remove(lp->ll_list, lp->ll_li);
lp->ll_li = li;
lp->ll_n1++;
+ if (lp->ll_li == NULL || (!lp->ll_empty2 && lp->ll_n2 < lp->ll_n1)) {
+ break;
+ } else {
+ last_li = lp->ll_li;
+ }
}
+ tv_list_remove_items(lp->ll_list, first_li, last_li);
} else {
if (lp->ll_list != NULL) {
// unlet a List item.
@@ -4522,7 +4529,7 @@ eval_index (
item = tv_list_find(rettv->vval.v_list, n1);
while (n1++ <= n2) {
tv_list_append_tv(l, TV_LIST_ITEM_TV(item));
- item = TV_LIST_ITEM_NEXT(l, item);
+ item = TV_LIST_ITEM_NEXT(rettv->vval.v_list, item);
}
tv_clear(rettv);
rettv->v_type = VAR_LIST;
@@ -4874,8 +4881,6 @@ void partial_unref(partial_T *pt)
static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate)
{
list_T *l = NULL;
- typval_T tv;
- listitem_T *item;
if (evaluate) {
l = tv_list_alloc();
@@ -4883,13 +4888,13 @@ static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate)
*arg = skipwhite(*arg + 1);
while (**arg != ']' && **arg != NUL) {
- if (eval1(arg, &tv, evaluate) == FAIL) /* recursive! */
+ typval_T tv;
+ if (eval1(arg, &tv, evaluate) == FAIL) { // Recursive!
goto failret;
+ }
if (evaluate) {
- item = tv_list_item_alloc();
- *TV_LIST_ITEM_TV(item) = tv;
- TV_LIST_ITEM_TV(item)->v_lock = VAR_UNLOCKED;
- tv_list_append(l, item);
+ tv.v_lock = VAR_UNLOCKED;
+ tv_list_append_owned_tv(l, tv);
}
if (**arg == ']') {
@@ -8469,7 +8474,6 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
static void filter_map(typval_T *argvars, typval_T *rettv, int map)
{
typval_T *expr;
- listitem_T *li, *nli;
list_T *l = NULL;
dictitem_T *di;
hashtab_T *ht;
@@ -8553,20 +8557,21 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
} else {
vimvars[VV_KEY].vv_type = VAR_NUMBER;
- for (li = tv_list_first(l); li != NULL; li = nli) {
+ for (listitem_T *li = tv_list_first(l); li != NULL;) {
if (map
&& tv_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg,
TV_TRANSLATE)) {
break;
}
- nli = TV_LIST_ITEM_NEXT(l, li);
vimvars[VV_KEY].vv_nr = idx;
if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL
|| did_emsg) {
break;
}
if (!map && rem) {
- tv_list_item_remove(l, li);
+ li = tv_list_item_remove(l, li);
+ } else {
+ li = TV_LIST_ITEM_NEXT(l, li);
}
idx++;
}
@@ -11441,40 +11446,38 @@ static void dict_list(typval_T *const tv, typval_T *const rettv,
tv_list_alloc_ret(rettv);
TV_DICT_ITER(tv->vval.v_dict, di, {
- listitem_T *const li = tv_list_item_alloc();
- tv_list_append(rettv->vval.v_list, li);
+ typval_T tv = { .v_lock = VAR_UNLOCKED };
switch (what) {
case kDictListKeys: {
- TV_LIST_ITEM_TV(li)->v_type = VAR_STRING;
- TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED;
- TV_LIST_ITEM_TV(li)->vval.v_string = vim_strsave(di->di_key);
+ tv.v_type = VAR_STRING;
+ tv.vval.v_string = vim_strsave(di->di_key);
break;
}
case kDictListValues: {
- tv_copy(&di->di_tv, TV_LIST_ITEM_TV(li));
+ tv_copy(&di->di_tv, &tv);
break;
}
case kDictListItems: {
// items()
list_T *const sub_l = tv_list_alloc();
- TV_LIST_ITEM_TV(li)->v_type = VAR_LIST;
- TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED;
- TV_LIST_ITEM_TV(li)->vval.v_list = sub_l;
+ tv.v_type = VAR_LIST;
+ tv.vval.v_list = sub_l;
tv_list_ref(sub_l);
- listitem_T *sub_li = tv_list_item_alloc();
- tv_list_append(sub_l, sub_li);
- TV_LIST_ITEM_TV(sub_li)->v_type = VAR_STRING;
- TV_LIST_ITEM_TV(sub_li)->v_lock = VAR_UNLOCKED;
- TV_LIST_ITEM_TV(sub_li)->vval.v_string = vim_strsave(di->di_key);
+ tv_list_append_owned_tv(sub_l, (typval_T) {
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = (char_u *)xstrdup((const char *)di->di_key),
+ });
+
+ tv_list_append_tv(sub_l, &di->di_tv);
- sub_li = tv_list_item_alloc();
- tv_list_append(sub_l, sub_li);
- tv_copy(&di->di_tv, TV_LIST_ITEM_TV(sub_li));
break;
}
}
+
+ tv_list_append_owned_tv(rettv->vval.v_list, tv);
});
}
@@ -12190,7 +12193,8 @@ static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
-static void find_some_match(typval_T *argvars, typval_T *rettv, int type)
+static void find_some_match(typval_T *const argvars, typval_T *const rettv,
+ const SomeMatchType type)
{
char_u *str = NULL;
long len = 0;
@@ -12211,24 +12215,37 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type)
p_cpo = (char_u *)"";
rettv->vval.v_number = -1;
- if (type == 3 || type == 4) {
- // type 3: return empty list when there are no matches.
- // type 4: return ["", -1, -1, -1]
- tv_list_alloc_ret(rettv);
- if (type == 4) {
+ switch (type) {
+ // matchlist(): return empty list when there are no matches.
+ case kSomeMatchList: {
+ tv_list_alloc_ret(rettv);
+ break;
+ }
+ // matchstrpos(): return ["", -1, -1, -1]
+ case kSomeMatchStrPos: {
+ tv_list_alloc_ret(rettv);
tv_list_append_string(rettv->vval.v_list, "", 0);
tv_list_append_number(rettv->vval.v_list, -1);
tv_list_append_number(rettv->vval.v_list, -1);
tv_list_append_number(rettv->vval.v_list, -1);
+ break;
+ }
+ case kSomeMatchStr: {
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ break;
+ }
+ case kSomeMatch:
+ case kSomeMatchEnd: {
+ // Do nothing: zero is default.
+ break;
}
- } else if (type == 2) {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
}
if (argvars[0].v_type == VAR_LIST) {
- if ((l = argvars[0].vval.v_list) == NULL)
+ if ((l = argvars[0].vval.v_list) == NULL) {
goto theend;
+ }
li = tv_list_first(l);
} else {
expr = str = (char_u *)tv_get_string(&argvars[0]);
@@ -12318,63 +12335,73 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type)
}
if (match) {
- if (type == 4) {
- list_T *const ret_l = rettv->vval.v_list;
- listitem_T *li1 = tv_list_first(ret_l);
- listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1);
- listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2);
- listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3);
- xfree(TV_LIST_ITEM_TV(li1)->vval.v_string);
-
- const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]);
- TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz(
- (const char *)regmatch.startp[0], rd);
- TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)(
- regmatch.startp[0] - expr);
- TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)(
- regmatch.endp[0] - expr);
- if (l != NULL) {
- TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx;
+ switch (type) {
+ case kSomeMatchStrPos: {
+ list_T *const ret_l = rettv->vval.v_list;
+ listitem_T *li1 = tv_list_first(ret_l);
+ listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1);
+ listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2);
+ listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3);
+ xfree(TV_LIST_ITEM_TV(li1)->vval.v_string);
+
+ const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]);
+ TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz(
+ (const char *)regmatch.startp[0], rd);
+ TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)(
+ regmatch.startp[0] - expr);
+ TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)(
+ regmatch.endp[0] - expr);
+ if (l != NULL) {
+ TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx;
+ }
+ break;
}
- } else if (type == 3) {
- int i;
-
- /* return list with matched string and submatches */
- for (i = 0; i < NSUBEXP; ++i) {
- if (regmatch.endp[i] == NULL) {
- tv_list_append_string(rettv->vval.v_list, NULL, 0);
- } else {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)regmatch.startp[i],
- (regmatch.endp[i] - regmatch.startp[i]));
+ case kSomeMatchList: {
+ // Return list with matched string and submatches.
+ for (int i = 0; i < NSUBEXP; i++) {
+ if (regmatch.endp[i] == NULL) {
+ tv_list_append_string(rettv->vval.v_list, NULL, 0);
+ } else {
+ tv_list_append_string(rettv->vval.v_list,
+ (const char *)regmatch.startp[i],
+ (regmatch.endp[i] - regmatch.startp[i]));
+ }
}
+ break;
}
- } else if (type == 2) {
- // Return matched string.
- if (l != NULL) {
- tv_copy(TV_LIST_ITEM_TV(li), rettv);
- } else {
- rettv->vval.v_string = (char_u *)xmemdupz(
- (const char *)regmatch.startp[0],
- (size_t)(regmatch.endp[0] - regmatch.startp[0]));
+ case kSomeMatchStr: {
+ // Return matched string.
+ if (l != NULL) {
+ tv_copy(TV_LIST_ITEM_TV(li), rettv);
+ } else {
+ rettv->vval.v_string = (char_u *)xmemdupz(
+ (const char *)regmatch.startp[0],
+ (size_t)(regmatch.endp[0] - regmatch.startp[0]));
+ }
+ break;
}
- } else if (l != NULL) {
- rettv->vval.v_number = idx;
- } else {
- if (type != 0) {
- rettv->vval.v_number =
- (varnumber_T)(regmatch.startp[0] - str);
- } else {
- rettv->vval.v_number =
- (varnumber_T)(regmatch.endp[0] - str);
+ case kSomeMatch:
+ case kSomeMatchEnd: {
+ if (l != NULL) {
+ rettv->vval.v_number = idx;
+ } else {
+ if (type == kSomeMatch) {
+ rettv->vval.v_number =
+ (varnumber_T)(regmatch.startp[0] - str);
+ } else {
+ rettv->vval.v_number =
+ (varnumber_T)(regmatch.endp[0] - str);
+ }
+ rettv->vval.v_number += (varnumber_T)(str - expr);
+ }
+ break;
}
- rettv->vval.v_number += (varnumber_T)(str - expr);
}
}
vim_regfree(regmatch.regprog);
}
- if (type == 4 && l == NULL) {
+ if (type == kSomeMatchStrPos && l == NULL) {
// matchstrpos() without a list: drop the second item
list_T *const ret_l = rettv->vval.v_list;
tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l)));
@@ -12390,7 +12417,7 @@ theend:
*/
static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- find_some_match(argvars, rettv, 1);
+ find_some_match(argvars, rettv, kSomeMatch);
}
/*
@@ -12505,12 +12532,12 @@ static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
tv_list_alloc_ret(rettv);
- int id = tv_get_number(&argvars[0]);
+ const int id = tv_get_number(&argvars[0]);
if (id >= 1 && id <= 3) {
- matchitem_T *m;
+ matchitem_T *const m = (matchitem_T *)get_match(curwin, id);
- if ((m = (matchitem_T *)get_match(curwin, id)) != NULL) {
+ if (m != NULL) {
tv_list_append_string(rettv->vval.v_list,
(const char *)syn_id2name(m->hlg_id), -1);
tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1);
@@ -12535,7 +12562,7 @@ static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- find_some_match(argvars, rettv, 0);
+ find_some_match(argvars, rettv, kSomeMatchEnd);
}
/*
@@ -12543,7 +12570,7 @@ static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- find_some_match(argvars, rettv, 3);
+ find_some_match(argvars, rettv, kSomeMatchList);
}
/*
@@ -12551,13 +12578,13 @@ static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- find_some_match(argvars, rettv, 2);
+ find_some_match(argvars, rettv, kSomeMatchStr);
}
/// "matchstrpos()" function
static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- find_some_match(argvars, rettv, 4);
+ find_some_match(argvars, rettv, kSomeMatchStrPos);
}
/// Get maximal/minimal number value in a list or dictionary
@@ -12762,13 +12789,12 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
goto f_msgpackparse_exit;
}
if (result == MSGPACK_UNPACK_SUCCESS) {
- listitem_T *li = tv_list_item_alloc();
- TV_LIST_ITEM_TV(li)->v_type = VAR_UNKNOWN;
- tv_list_append(ret_list, li);
- if (msgpack_to_vim(unpacked.data, TV_LIST_ITEM_TV(li)) == FAIL) {
+ typval_T tv = { .v_type = VAR_UNKNOWN };
+ if (msgpack_to_vim(unpacked.data, &tv) == FAIL) {
EMSG2(_(e_invarg2), "Failed to convert msgpack string");
goto f_msgpackparse_exit;
}
+ tv_list_append_owned_tv(ret_list, tv);
}
if (result == MSGPACK_UNPACK_CONTINUE) {
if (rlret == OK) {
@@ -12995,9 +13021,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
long prevlen = 0; /* length of data in prev */
long prevsize = 0; /* size of prev buffer */
long maxline = MAXLNUM;
- long cnt = 0;
- char_u *p; /* position in buf */
- char_u *start; /* start of current line */
if (argvars[1].v_type != VAR_UNKNOWN) {
if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
@@ -13009,6 +13032,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
tv_list_alloc_ret(rettv);
+ list_T *const l = rettv->vval.v_list;
// Always open the file in binary mode, library functions have a mind of
// their own about CR-LF conversion.
@@ -13018,19 +13042,20 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
- while (cnt < maxline || maxline < 0) {
+ while (maxline < 0 || tv_list_len(l) < maxline) {
readlen = (int)fread(buf, 1, io_size, fd);
- /* This for loop processes what was read, but is also entered at end
- * of file so that either:
- * - an incomplete line gets written
- * - a "binary" file gets an empty line at the end if it ends in a
- * newline. */
+ // This for loop processes what was read, but is also entered at end
+ // of file so that either:
+ // - an incomplete line gets written
+ // - a "binary" file gets an empty line at the end if it ends in a
+ // newline.
+ char_u *p; // Position in buf.
+ char_u *start; // Start of current line.
for (p = buf, start = buf;
p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary));
- ++p) {
+ p++) {
if (*p == '\n' || readlen <= 0) {
- listitem_T *li;
char_u *s = NULL;
size_t len = p - start;
@@ -13057,22 +13082,32 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
prevlen = prevsize = 0;
}
- li = tv_list_item_alloc();
- TV_LIST_ITEM_TV(li)->v_type = VAR_STRING;
- TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED;
- TV_LIST_ITEM_TV(li)->vval.v_string = s;
- tv_list_append(rettv->vval.v_list, li);
-
- start = p + 1; /* step over newline */
- if ((++cnt >= maxline && maxline >= 0) || readlen <= 0)
+ tv_list_append_owned_tv(l, (typval_T) {
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = s,
+ });
+
+ start = p + 1; // Step over newline.
+ if (maxline < 0) {
+ if (tv_list_len(l) > -maxline) {
+ assert(tv_list_len(l) == 1 + (-maxline));
+ tv_list_item_remove(l, tv_list_first(l));
+ }
+ } else if (tv_list_len(l) >= maxline) {
+ assert(tv_list_len(l) == maxline);
+ break;
+ }
+ if (readlen <= 0) {
break;
- } else if (*p == NUL)
+ }
+ } else if (*p == NUL) {
*p = '\n';
- /* Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this
- * when finding the BF and check the previous two bytes. */
- else if (*p == 0xbf && enc_utf8 && !binary) {
- /* Find the two bytes before the 0xbf. If p is at buf, or buf
- * + 1, these may be in the "prev" string. */
+ // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this
+ // when finding the BF and check the previous two bytes.
+ } else if (*p == 0xbf && !binary) {
+ // Find the two bytes before the 0xbf. If p is at buf, or buf + 1,
+ // these may be in the "prev" string.
char_u back1 = p >= buf + 1 ? p[-1]
: prevlen >= 1 ? prev[prevlen - 1] : NUL;
char_u back2 = p >= buf + 2 ? p[-2]
@@ -13106,8 +13141,9 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
} /* for */
- if ((cnt >= maxline && maxline >= 0) || readlen <= 0)
+ if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) {
break;
+ }
if (start < p) {
/* There's part of a line in buf, store it in "prev". */
if (p - start + prevlen >= prevsize) {
@@ -13131,17 +13167,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
} /* while */
- /*
- * For a negative line count use only the lines at the end of the file,
- * free the rest.
- */
- if (maxline < 0)
- while (cnt > -maxline) {
- tv_list_item_remove(rettv->vval.v_list,
- tv_list_first(rettv->vval.v_list));
- cnt--;
- }
-
xfree(prev);
fclose(fd);
}
@@ -13291,7 +13316,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} else {
if (argvars[2].v_type == VAR_UNKNOWN) {
// Remove one item, return its value.
- tv_list_remove_items(l, item, item);
+ tv_list_drop_items(l, item, item);
*rettv = *TV_LIST_ITEM_TV(item);
xfree(item);
} else {
@@ -14310,11 +14335,7 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// Copy addrs into a linked list.
list_T *l = tv_list_alloc_ret(rettv);
for (size_t i = 0; i < n; i++) {
- listitem_T *li = tv_list_item_alloc();
- TV_LIST_ITEM_TV(li)->v_type = VAR_STRING;
- TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED;
- TV_LIST_ITEM_TV(li)->vval.v_string = (char_u *)addrs[i];
- tv_list_append(l, li);
+ tv_list_append_allocated_string(l, addrs[i]);
}
xfree(addrs);
}
@@ -15120,12 +15141,6 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->v_type = VAR_NUMBER;
}
-/// struct used in the array that's given to qsort()
-typedef struct {
- listitem_T *item;
- int idx;
-} sortItem_T;
-
/// struct storing information about current sort
typedef struct {
int item_compare_ic;
@@ -15146,8 +15161,8 @@ static sortinfo_T *sortinfo = NULL;
*/
static int item_compare(const void *s1, const void *s2, bool keep_zero)
{
- sortItem_T *const si1 = (sortItem_T *)s1;
- sortItem_T *const si2 = (sortItem_T *)s2;
+ 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);
@@ -15241,7 +15256,7 @@ static int item_compare_not_keeping_zero(const void *s1, const void *s2)
static int item_compare2(const void *s1, const void *s2, bool keep_zero)
{
- sortItem_T *si1, *si2;
+ ListSortItem *si1, *si2;
int res;
typval_T rettv;
typval_T argv[3];
@@ -15254,8 +15269,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
return 0;
}
- si1 = (sortItem_T *)s1;
- si2 = (sortItem_T *)s2;
+ si1 = (ListSortItem *)s1;
+ si2 = (ListSortItem *)s2;
if (partial == NULL) {
func_name = sortinfo->item_compare_func;
@@ -15312,7 +15327,7 @@ static int item_compare2_not_keeping_zero(const void *s1, const void *s2)
*/
static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
{
- sortItem_T *ptrs;
+ ListSortItem *ptrs;
long len;
long i;
@@ -15401,43 +15416,22 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
}
}
- /* Make an array with each entry pointing to an item in the List. */
- ptrs = xmalloc((size_t)(len * sizeof (sortItem_T)));
+ // Make an array with each entry pointing to an item in the List.
+ ptrs = xmalloc((size_t)(len * sizeof(ListSortItem)));
- i = 0;
if (sort) {
- // sort(): ptrs will be the list to sort.
- TV_LIST_ITER(l, li, {
- ptrs[i].item = li;
- ptrs[i].idx = i;
- i++;
- });
-
info.item_compare_func_err = false;
- // Test the compare function.
- if ((info.item_compare_func != NULL
- || info.item_compare_partial != NULL)
- && item_compare2_not_keeping_zero(&ptrs[0], &ptrs[1])
- == ITEM_COMPARE_FAIL) {
+ 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 {
- // Sort the array with item pointers.
- qsort(ptrs, (size_t)len, sizeof (sortItem_T),
- (info.item_compare_func == NULL
- && info.item_compare_partial == NULL ?
- item_compare_not_keeping_zero :
- item_compare2_not_keeping_zero));
-
- if (!info.item_compare_func_err) {
- // Clear the list and append the items in the sorted order.
- tv_list_clear(l);
- for (i = 0; i < len; i++) {
- tv_list_append(l, ptrs[i].item);
- }
- }
}
} else {
- int (*item_compare_func_ptr)(const void *, const void *);
+ ListSorter item_compare_func_ptr;
// f_uniq(): ptrs will be a stack of items to remove.
info.item_compare_func_err = false;
@@ -15450,18 +15444,17 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
int idx = 0;
for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l))
- ; li != NULL
- ; li = TV_LIST_ITEM_NEXT(l, li)) {
+ ; 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) {
EMSG(_("E882: Uniq compare function failed"));
break;
}
- tv_list_item_remove(l, li);
- li = tv_list_find(l, idx);
+ li = tv_list_item_remove(l, li);
} else {
idx++;
+ li = TV_LIST_ITEM_NEXT(l, li);
}
}
}
@@ -15619,12 +15612,7 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
for (int i = 0; i < ga.ga_len; i++) {
char *p = ((char **)ga.ga_data)[i];
-
- listitem_T *const li = tv_list_item_alloc();
- TV_LIST_ITEM_TV(li)->v_type = VAR_STRING;
- TV_LIST_ITEM_TV(li)->v_lock = VAR_LOCKED;
- TV_LIST_ITEM_TV(li)->vval.v_string = (char_u *)p;
- tv_list_append(rettv->vval.v_list, li);
+ tv_list_append_allocated_string(rettv->vval.v_list, p);
}
ga_clear(&ga);
}
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c
index d5c65ebe81..af4e055d23 100644
--- a/src/nvim/eval/decode.c
+++ b/src/nvim/eval/decode.c
@@ -127,9 +127,7 @@ static inline int json_decoder_pop(ValuesStackItem obj,
return FAIL;
}
assert(last_container.special_val == NULL);
- listitem_T *obj_li = tv_list_item_alloc();
- *TV_LIST_ITEM_TV(obj_li) = obj.val;
- tv_list_append(last_container.container.vval.v_list, obj_li);
+ tv_list_append_owned_tv(last_container.container.vval.v_list, obj.val);
} else if (last_container.stack_index == kv_size(*stack) - 2) {
if (!obj.didcolon) {
EMSG2(_("E474: Expected colon before dictionary value: %s"),
@@ -154,12 +152,8 @@ static inline int json_decoder_pop(ValuesStackItem obj,
} else {
list_T *const kv_pair = tv_list_alloc();
tv_list_append_list(last_container.special_val, kv_pair);
- listitem_T *const key_li = tv_list_item_alloc();
- *TV_LIST_ITEM_TV(key_li) = key.val;
- tv_list_append(kv_pair, key_li);
- listitem_T *const val_li = tv_list_item_alloc();
- *TV_LIST_ITEM_TV(val_li) = obj.val;
- tv_list_append(kv_pair, val_li);
+ tv_list_append_owned_tv(kv_pair, key.val);
+ tv_list_append_owned_tv(kv_pair, obj.val);
}
} else {
// Object with key only
@@ -1047,10 +1041,10 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
.vval = { .v_list = list },
};
for (size_t i = 0; i < mobj.via.array.size; i++) {
- listitem_T *const li = tv_list_item_alloc();
- TV_LIST_ITEM_TV(li)->v_type = VAR_UNKNOWN;
- tv_list_append(list, li);
- if (msgpack_to_vim(mobj.via.array.ptr[i], TV_LIST_ITEM_TV(li))
+ // Not populated yet, need to create list item to push.
+ tv_list_append_owned_tv(list, (typval_T) { .v_type = VAR_UNKNOWN });
+ if (msgpack_to_vim(mobj.via.array.ptr[i],
+ TV_LIST_ITEM_TV(tv_list_last(list)))
== FAIL) {
return FAIL;
}
@@ -1095,20 +1089,20 @@ msgpack_to_vim_generic_map: {}
for (size_t i = 0; i < mobj.via.map.size; i++) {
list_T *const kv_pair = tv_list_alloc();
tv_list_append_list(list, kv_pair);
- listitem_T *const key_li = tv_list_item_alloc();
- TV_LIST_ITEM_TV(key_li)->v_type = VAR_UNKNOWN;
- tv_list_append(kv_pair, key_li);
- listitem_T *const val_li = tv_list_item_alloc();
- TV_LIST_ITEM_TV(val_li)->v_type = VAR_UNKNOWN;
- tv_list_append(kv_pair, val_li);
- if (msgpack_to_vim(mobj.via.map.ptr[i].key, TV_LIST_ITEM_TV(key_li))
- == FAIL) {
+
+ typval_T key_tv = { .v_type = VAR_UNKNOWN };
+ if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_tv) == FAIL) {
+ tv_clear(&key_tv);
return FAIL;
}
- if (msgpack_to_vim(mobj.via.map.ptr[i].val, TV_LIST_ITEM_TV(val_li))
- == FAIL) {
+ tv_list_append_owned_tv(kv_pair, key_tv);
+
+ typval_T val_tv = { .v_type = VAR_UNKNOWN };
+ if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_tv) == FAIL) {
+ tv_clear(&val_tv);
return FAIL;
}
+ tv_list_append_owned_tv(kv_pair, val_tv);
}
break;
}
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 53c56d0ffd..21bb84a945 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -3,6 +3,7 @@
#include <stdio.h>
#include <stddef.h>
+#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>
@@ -52,35 +53,29 @@ const char *const tv_empty_string = "";
/// and specifically set lv_lock.
///
/// @return [allocated] new list item.
-listitem_T *tv_list_item_alloc(void)
+static listitem_T *tv_list_item_alloc(void)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC
{
return xmalloc(sizeof(listitem_T));
}
-/// Free a list item
-///
-/// Also clears the value. Does not touch watchers.
-///
-/// @param[out] item Item to free.
-void tv_list_item_free(listitem_T *const item)
- FUNC_ATTR_NONNULL_ALL
-{
- tv_clear(TV_LIST_ITEM_TV(item));
- xfree(item);
-}
-
/// Remove a list item from a List and free it
///
/// Also clears the value.
///
/// @param[out] l List to remove item from.
/// @param[in,out] item Item to remove.
-void tv_list_item_remove(list_T *const l, listitem_T *const item)
+///
+/// @return Pointer to the list item just after removed one, NULL if removed
+/// item was the last one.
+listitem_T *tv_list_item_remove(list_T *const l, listitem_T *const item)
FUNC_ATTR_NONNULL_ALL
{
- tv_list_remove_items(l, item, item);
- tv_list_item_free(item);
+ listitem_T *const next_item = TV_LIST_ITEM_NEXT(l, item);
+ tv_list_drop_items(l, item, item);
+ tv_clear(TV_LIST_ITEM_TV(item));
+ xfree(item);
+ return next_item;
}
//{{{2 List watchers
@@ -267,8 +262,8 @@ void tv_list_unref(list_T *const l)
/// @param[out] l List to remove from.
/// @param[in] item First item to remove.
/// @param[in] item2 Last item to remove.
-void tv_list_remove_items(list_T *const l, listitem_T *const item,
- listitem_T *const item2)
+void tv_list_drop_items(list_T *const l, listitem_T *const item,
+ listitem_T *const item2)
FUNC_ATTR_NONNULL_ALL
{
// Notify watchers.
@@ -290,6 +285,23 @@ void tv_list_remove_items(list_T *const l, listitem_T *const item,
l->lv_idx_item = NULL;
}
+/// Like tv_list_drop_items, but also frees all removed items
+void tv_list_remove_items(list_T *const l, listitem_T *const item,
+ listitem_T *const item2)
+ FUNC_ATTR_NONNULL_ALL
+{
+ tv_list_drop_items(l, item, item2);
+ for (listitem_T *li = item;;) {
+ tv_clear(TV_LIST_ITEM_TV(li));
+ listitem_T *const nli = li->li_next;
+ xfree(li);
+ if (li == item2) {
+ break;
+ }
+ li = nli;
+ }
+}
+
/// Move items "item" to "item2" from list "l" to the end of the list "tgt_l"
///
/// @param[out] l List to move from.
@@ -302,7 +314,7 @@ void tv_list_move_items(list_T *const l, listitem_T *const item,
const int cnt)
FUNC_ATTR_NONNULL_ALL
{
- tv_list_remove_items(l, item, item2);
+ tv_list_drop_items(l, item, item2);
item->li_prev = tgt_l->lv_last;
item2->li_next = NULL;
if (tgt_l->lv_last == NULL) {
@@ -393,19 +405,30 @@ void tv_list_append_tv(list_T *const l, typval_T *const tv)
tv_list_append(l, li);
}
+/// Like tv_list_append_tv(), but tv is moved to a list
+///
+/// This means that it is no longer valid to use contents of the typval_T after
+/// function exits.
+void tv_list_append_owned_tv(list_T *const l, typval_T tv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ listitem_T *const li = tv_list_item_alloc();
+ *TV_LIST_ITEM_TV(li) = tv;
+ tv_list_append(l, li);
+}
+
/// Append a list to a list as one item
///
/// @param[out] l List to append to.
/// @param[in,out] itemlist List to append. Reference count is increased.
-void tv_list_append_list(list_T *const list, list_T *const itemlist)
+void tv_list_append_list(list_T *const l, list_T *const itemlist)
FUNC_ATTR_NONNULL_ARG(1)
{
- listitem_T *const li = tv_list_item_alloc();
-
- TV_LIST_ITEM_TV(li)->v_type = VAR_LIST;
- TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED;
- TV_LIST_ITEM_TV(li)->vval.v_list = itemlist;
- tv_list_append(list, li);
+ tv_list_append_owned_tv(l, (typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_list = itemlist,
+ });
tv_list_ref(itemlist);
}
@@ -413,15 +436,14 @@ void tv_list_append_list(list_T *const list, list_T *const itemlist)
///
/// @param[out] l List to append to.
/// @param[in,out] dict Dictionary to append. Reference count is increased.
-void tv_list_append_dict(list_T *const list, dict_T *const dict)
+void tv_list_append_dict(list_T *const l, dict_T *const dict)
FUNC_ATTR_NONNULL_ARG(1)
{
- listitem_T *const li = tv_list_item_alloc();
-
- TV_LIST_ITEM_TV(li)->v_type = VAR_DICT;
- TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED;
- TV_LIST_ITEM_TV(li)->vval.v_dict = dict;
- tv_list_append(list, li);
+ tv_list_append_owned_tv(l, (typval_T) {
+ .v_type = VAR_DICT,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_dict = dict,
+ });
if (dict != NULL) {
dict->dv_refcount++;
}
@@ -438,14 +460,15 @@ void tv_list_append_string(list_T *const l, const char *const str,
const ssize_t len)
FUNC_ATTR_NONNULL_ARG(1)
{
- if (str == NULL) {
- assert(len == 0 || len == -1);
- tv_list_append_allocated_string(l, NULL);
- } else {
- tv_list_append_allocated_string(l, (len >= 0
- ? xmemdupz(str, (size_t)len)
- : xstrdup(str)));
- }
+ tv_list_append_owned_tv(l, (typval_T) {
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = (str == NULL
+ ? NULL
+ : (len >= 0
+ ? xmemdupz(str, (size_t)len)
+ : xstrdup(str))),
+ });
}
/// Append given string to the list
@@ -457,12 +480,11 @@ void tv_list_append_string(list_T *const l, const char *const str,
void tv_list_append_allocated_string(list_T *const l, char *const str)
FUNC_ATTR_NONNULL_ARG(1)
{
- listitem_T *const li = tv_list_item_alloc();
-
- tv_list_append(l, li);
- TV_LIST_ITEM_TV(li)->v_type = VAR_STRING;
- TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED;
- TV_LIST_ITEM_TV(li)->vval.v_string = (char_u *)str;
+ tv_list_append_owned_tv(l, (typval_T) {
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = (char_u *)str,
+ });
}
/// Append number to the list
@@ -472,11 +494,11 @@ void tv_list_append_allocated_string(list_T *const l, char *const str)
/// listitem_T.
void tv_list_append_number(list_T *const l, const varnumber_T n)
{
- listitem_T *const li = tv_list_item_alloc();
- TV_LIST_ITEM_TV(li)->v_type = VAR_NUMBER;
- TV_LIST_ITEM_TV(li)->v_lock = VAR_UNLOCKED;
- TV_LIST_ITEM_TV(li)->vval.v_number = n;
- tv_list_append(l, li);
+ tv_list_append_owned_tv(l, (typval_T) {
+ .v_type = VAR_NUMBER,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_number = n,
+ });
}
//{{{2 Operations on the whole list
@@ -736,6 +758,47 @@ 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,
+ 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
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index 2bce7bd6b2..c9a9a3e7e8 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -296,6 +296,14 @@ typedef struct list_stack_S {
struct list_stack_S *prev;
} list_stack_T;
+/// Structure representing one list item, used for sort array.
+typedef struct {
+ listitem_T *item; ///< Sorted list item.
+ int idx; ///< Sorted list item index.
+} ListSortItem;
+
+typedef int (*ListSorter)(const void *, const void *);
+
// In a hashtab item "hi_key" points to "di_key" in a dictitem.
// This avoids adding a pointer to the hashtab item.
@@ -403,20 +411,6 @@ static inline list_T *tv_list_latest_copy(const list_T *const l)
return l->lv_copylist;
}
-/// Clear the list without freeing anything at all
-///
-/// For use in sort() which saves items to a separate array and readds them back
-/// after sorting via a number of tv_list_append() calls.
-///
-/// @param[out] l List to clear.
-static inline void tv_list_clear(list_T *const l)
-{
- l->lv_first = NULL;
- l->lv_last = NULL;
- l->lv_idx_item = NULL;
- l->lv_len = 0;
-}
-
static inline int tv_list_uidx(const list_T *const l, int n)
REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT;
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 8303ecdd37..61cb428923 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -212,19 +212,27 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
const char *s = lua_tolstring(lstate, -2, &len);
if (cur.special) {
list_T *const kv_pair = tv_list_alloc();
- tv_list_append_list(cur.tv->vval.v_list, kv_pair);
- listitem_T *const key = tv_list_item_alloc();
- *TV_LIST_ITEM_TV(key) = decode_string(s, len, kTrue, false, false);
- tv_list_append(kv_pair, key);
- if (TV_LIST_ITEM_TV(key)->v_type == VAR_UNKNOWN) {
+
+ typval_T s_tv = decode_string(s, len, kTrue, false, false);
+ if (s_tv.v_type == VAR_UNKNOWN) {
ret = false;
tv_list_unref(kv_pair);
continue;
}
- listitem_T *const val = tv_list_item_alloc();
- tv_list_append(kv_pair, val);
+ tv_list_append_owned_tv(kv_pair, s_tv);
+
+ // Value: not populated yet, need to create list item to push.
+ tv_list_append_owned_tv(kv_pair, (typval_T) {
+ .v_type = VAR_UNKNOWN,
+ });
kv_push(stack, cur);
- cur = (TVPopStackItem) { TV_LIST_ITEM_TV(val), false, false, 0 };
+ tv_list_append_list(cur.tv->vval.v_list, kv_pair);
+ cur = (TVPopStackItem) {
+ .tv = TV_LIST_ITEM_TV(tv_list_last(kv_pair)),
+ .container = false,
+ .special = false,
+ .idx = 0,
+ };
} else {
dictitem_T *const di = tv_dict_item_alloc_len(s, len);
if (tv_dict_add(cur.tv->vval.v_dict, di) == FAIL) {
@@ -244,10 +252,18 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
lua_pop(lstate, 2);
continue;
}
- listitem_T *const li = tv_list_item_alloc();
- tv_list_append(cur.tv->vval.v_list, li);
+ // Not populated yet, need to create list item to push.
+ tv_list_append_owned_tv(cur.tv->vval.v_list, (typval_T) {
+ .v_type = VAR_UNKNOWN,
+ });
kv_push(stack, cur);
- cur = (TVPopStackItem) { TV_LIST_ITEM_TV(li), false, false, 0 };
+ // TODO(ZyX-I): Use indexes, here list item *will* be reallocated.
+ cur = (TVPopStackItem) {
+ .tv = TV_LIST_ITEM_TV(tv_list_last(cur.tv->vval.v_list)),
+ .container = false,
+ .special = false,
+ .idx = 0,
+ };
}
}
assert(!cur.container);
diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua
index d7399182f7..6babd4be77 100644
--- a/test/unit/eval/helpers.lua
+++ b/test/unit/eval/helpers.lua
@@ -7,7 +7,7 @@ local ffi = helpers.ffi
local eq = helpers.eq
local eval = cimport('./src/nvim/eval.h', './src/nvim/eval/typval.h',
- './src/nvim/hashtab.h')
+ './src/nvim/hashtab.h', './src/nvim/memory.h')
local null_string = {[true]='NULL string'}
local null_list = {[true]='NULL list'}
@@ -24,10 +24,19 @@ local nil_value = {[true]='nil'}
local lua2typvalt
+local function tv_list_item_alloc()
+ return ffi.cast('listitem_T*', eval.xmalloc(ffi.sizeof('listitem_T')))
+end
+
+local function tv_list_item_free(li)
+ eval.tv_clear(li.li_tv)
+ eval.xfree(li)
+end
+
local function li_alloc(nogc)
- local gcfunc = eval.tv_list_item_free
+ local gcfunc = tv_list_item_free
if nogc then gcfunc = nil end
- local li = ffi.gc(eval.tv_list_item_alloc(), gcfunc)
+ local li = ffi.gc(tv_list_item_alloc(), gcfunc)
li.li_next = nil
li.li_prev = nil
li.li_tv = {v_type=eval.VAR_UNKNOWN, v_lock=eval.VAR_UNLOCKED}
@@ -41,7 +50,7 @@ local function populate_list(l, lua_l, processed)
processed[lua_l] = l
for i = 1, #lua_l do
local item_tv = ffi.gc(lua2typvalt(lua_l[i], processed), nil)
- local item_li = eval.tv_list_item_alloc()
+ local item_li = tv_list_item_alloc()
item_li.li_tv = item_tv
eval.tv_list_append(l, item_li)
end
@@ -533,6 +542,7 @@ return {
typvalt=typvalt,
li_alloc=li_alloc,
+ tv_list_item_free=tv_list_item_free,
dict_iter=dict_iter,
list_iter=list_iter,
diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua
index bec74f05fc..b668144175 100644
--- a/test/unit/eval/typval_spec.lua
+++ b/test/unit/eval/typval_spec.lua
@@ -41,6 +41,7 @@ local tbl2callback = eval_helpers.tbl2callback
local dict_watchers = eval_helpers.dict_watchers
local concat_tables = global_helpers.concat_tables
+local map = global_helpers.map
local lib = cimport('./src/nvim/eval/typval.h', './src/nvim/memory.h',
'./src/nvim/mbyte.h', './src/nvim/garray.h',
@@ -80,8 +81,6 @@ local function get_alloc_rets(exp_log, res)
return exp_log
end
-local to_cstr_nofree = function(v) return lib.xstrdup(v) end
-
local alloc_log = alloc_log_new()
before_each(function()
@@ -121,118 +120,76 @@ end
describe('typval.c', function()
describe('list', function()
describe('item', function()
- describe('alloc()/free()', function()
+ describe('remove()', function()
itp('works', function()
- local li = li_alloc(true)
- neq(nil, li)
- lib.tv_list_item_free(li)
- alloc_log:check({
- a.li(li),
- a.freed(li),
- })
- end)
- itp('also frees the value', function()
- local li
- local s
- local l
- local tv
- li = li_alloc(true)
- li.li_tv.v_type = lib.VAR_NUMBER
- li.li_tv.vval.v_number = 10
- lib.tv_list_item_free(li)
- alloc_log:check({
- a.li(li),
- a.freed(li),
- })
-
- li = li_alloc(true)
- li.li_tv.v_type = lib.VAR_FLOAT
- li.li_tv.vval.v_float = 10.5
- lib.tv_list_item_free(li)
- alloc_log:check({
- a.li(li),
- a.freed(li),
- })
-
- li = li_alloc(true)
- li.li_tv.v_type = lib.VAR_STRING
- li.li_tv.vval.v_string = nil
- lib.tv_list_item_free(li)
+ local l = list(1, 2, 3, 4, 5, 6, 7)
+ neq(nil, l)
+ local lis = list_items(l)
alloc_log:check({
- a.li(li),
- a.freed(alloc_log.null),
- a.freed(li),
+ a.list(l),
+ a.li(lis[1]),
+ a.li(lis[2]),
+ a.li(lis[3]),
+ a.li(lis[4]),
+ a.li(lis[5]),
+ a.li(lis[6]),
+ a.li(lis[7]),
})
- li = li_alloc(true)
- li.li_tv.v_type = lib.VAR_STRING
- s = to_cstr_nofree('test')
- li.li_tv.vval.v_string = s
- lib.tv_list_item_free(li)
+ eq(lis[2], lib.tv_list_item_remove(l, lis[1]))
alloc_log:check({
- a.li(li),
- a.str(s, #('test')),
- a.freed(s),
- a.freed(li),
+ a.freed(table.remove(lis, 1)),
})
+ eq(lis, list_items(l))
- li = li_alloc(true)
- li.li_tv.v_type = lib.VAR_LIST
- l = ffi.gc(list(), nil)
- l.lv_refcount = 2
- li.li_tv.vval.v_list = l
- lib.tv_list_item_free(li)
+ eq(lis[7], lib.tv_list_item_remove(l, lis[6]))
alloc_log:check({
- a.li(li),
- a.list(l),
- a.freed(li),
+ a.freed(table.remove(lis)),
})
- eq(1, l.lv_refcount)
+ eq(lis, list_items(l))
- li = li_alloc(true)
- tv = lua2typvalt({})
- tv.vval.v_dict.dv_refcount = 2
- li.li_tv = tv
- lib.tv_list_item_free(li)
+ eq(lis[4], lib.tv_list_item_remove(l, lis[3]))
alloc_log:check({
- a.li(li),
- a.dict(tv.vval.v_dict),
- a.freed(li),
+ a.freed(table.remove(lis, 3)),
})
- eq(1, tv.vval.v_dict.dv_refcount)
+ eq(lis, list_items(l))
end)
- end)
- describe('remove()', function()
- itp('works', function()
- local l = list(1, 2, 3, 4, 5, 6, 7)
+ itp('also frees the value', function()
+ local l = list('a', 'b', 'c', 'd')
neq(nil, l)
local lis = list_items(l)
alloc_log:check({
a.list(l),
+ a.str(lis[1].li_tv.vval.v_string, 1),
a.li(lis[1]),
+ a.str(lis[2].li_tv.vval.v_string, 1),
a.li(lis[2]),
+ a.str(lis[3].li_tv.vval.v_string, 1),
a.li(lis[3]),
+ a.str(lis[4].li_tv.vval.v_string, 1),
a.li(lis[4]),
- a.li(lis[5]),
- a.li(lis[6]),
- a.li(lis[7]),
})
+ local strings = map(function(li) return li.li_tv.vval.v_string end,
+ lis)
- lib.tv_list_item_remove(l, lis[1])
+ eq(lis[2], lib.tv_list_item_remove(l, lis[1]))
alloc_log:check({
+ a.freed(table.remove(strings, 1)),
a.freed(table.remove(lis, 1)),
})
eq(lis, list_items(l))
- lib.tv_list_item_remove(l, lis[6])
+ eq(lis[3], lib.tv_list_item_remove(l, lis[2]))
alloc_log:check({
- a.freed(table.remove(lis)),
+ a.freed(table.remove(strings, 2)),
+ a.freed(table.remove(lis, 2)),
})
eq(lis, list_items(l))
- lib.tv_list_item_remove(l, lis[3])
+ eq(nil, lib.tv_list_item_remove(l, lis[2]))
alloc_log:check({
- a.freed(table.remove(lis, 3)),
+ a.freed(table.remove(strings, 2)),
+ a.freed(table.remove(lis, 2)),
})
eq(lis, list_items(l))
end)
@@ -257,19 +214,19 @@ describe('typval.c', function()
a.li(lis[7]),
})
- lib.tv_list_item_remove(l, lis[4])
+ eq(lis[5], lib.tv_list_item_remove(l, lis[4]))
alloc_log:check({a.freed(lis[4])})
eq({lis[1], lis[5], lis[7]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item})
- lib.tv_list_item_remove(l, lis[2])
+ eq(lis[3], lib.tv_list_item_remove(l, lis[2]))
alloc_log:check({a.freed(lis[2])})
eq({lis[1], lis[5], lis[7]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item})
- lib.tv_list_item_remove(l, lis[7])
+ eq(nil, lib.tv_list_item_remove(l, lis[7]))
alloc_log:check({a.freed(lis[7])})
eq({lis[1], lis[5], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
- lib.tv_list_item_remove(l, lis[1])
+ eq(lis[3], lib.tv_list_item_remove(l, lis[1]))
alloc_log:check({a.freed(lis[1])})
eq({lis[3], lis[5], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
@@ -449,7 +406,7 @@ describe('typval.c', function()
})
end)
end)
- describe('remove_items()', function()
+ describe('drop_items()', function()
itp('works', function()
local l_tv = lua2typvalt({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13})
local l = l_tv.vval.v_list
@@ -462,21 +419,92 @@ describe('typval.c', function()
}
alloc_log:clear()
- lib.tv_list_remove_items(l, lis[1], lis[3])
+ lib.tv_list_drop_items(l, lis[1], lis[3])
eq({4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, typvalt2lua(l_tv))
eq({lis[4], lis[7], lis[13]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item})
- lib.tv_list_remove_items(l, lis[11], lis[13])
+ lib.tv_list_drop_items(l, lis[11], lis[13])
eq({4, 5, 6, 7, 8, 9, 10}, typvalt2lua(l_tv))
eq({lis[4], lis[7], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
- lib.tv_list_remove_items(l, lis[6], lis[8])
+ lib.tv_list_drop_items(l, lis[6], lis[8])
eq({4, 5, 9, 10}, typvalt2lua(l_tv))
eq({lis[4], lis[9], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
+ lib.tv_list_drop_items(l, lis[4], lis[10])
+ eq(empty_list, typvalt2lua(l_tv))
+ eq({true, true, true}, {lws[1].lw_item == nil, lws[2].lw_item == nil, lws[3].lw_item == nil})
+
+ lib.tv_list_watch_remove(l, lws[1])
+ lib.tv_list_watch_remove(l, lws[2])
+ lib.tv_list_watch_remove(l, lws[3])
+
+ alloc_log:check({})
+ end)
+ end)
+ describe('remove_items()', function()
+ itp('works', function()
+ local l_tv = lua2typvalt({'1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13'})
+ local l = l_tv.vval.v_list
+ local lis = list_items(l)
+ local strings = map(function(li) return li.li_tv.vval.v_string end, lis)
+ -- Three watchers: pointing to first, middle and last elements.
+ local lws = {
+ list_watch(l, lis[1]),
+ list_watch(l, lis[7]),
+ list_watch(l, lis[13]),
+ }
+ alloc_log:clear()
+
+ lib.tv_list_remove_items(l, lis[1], lis[3])
+ eq({'4', '5', '6', '7', '8', '9', '10', '11', '12', '13'}, typvalt2lua(l_tv))
+ eq({lis[4], lis[7], lis[13]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item})
+ alloc_log:check({
+ a.freed(strings[1]),
+ a.freed(lis[1]),
+ a.freed(strings[2]),
+ a.freed(lis[2]),
+ a.freed(strings[3]),
+ a.freed(lis[3]),
+ })
+
+ lib.tv_list_remove_items(l, lis[11], lis[13])
+ eq({'4', '5', '6', '7', '8', '9', '10'}, typvalt2lua(l_tv))
+ eq({lis[4], lis[7], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
+ alloc_log:check({
+ a.freed(strings[11]),
+ a.freed(lis[11]),
+ a.freed(strings[12]),
+ a.freed(lis[12]),
+ a.freed(strings[13]),
+ a.freed(lis[13]),
+ })
+
+ lib.tv_list_remove_items(l, lis[6], lis[8])
+ eq({'4', '5', '9', '10'}, typvalt2lua(l_tv))
+ eq({lis[4], lis[9], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
+ alloc_log:check({
+ a.freed(strings[6]),
+ a.freed(lis[6]),
+ a.freed(strings[7]),
+ a.freed(lis[7]),
+ a.freed(strings[8]),
+ a.freed(lis[8]),
+ })
+
lib.tv_list_remove_items(l, lis[4], lis[10])
eq(empty_list, typvalt2lua(l_tv))
eq({true, true, true}, {lws[1].lw_item == nil, lws[2].lw_item == nil, lws[3].lw_item == nil})
+ alloc_log:check({
+ a.freed(strings[4]),
+ a.freed(lis[4]),
+ a.freed(strings[5]),
+ a.freed(lis[5]),
+ a.freed(strings[9]),
+ a.freed(lis[9]),
+ a.freed(strings[10]),
+ a.freed(lis[10]),
+ })
lib.tv_list_watch_remove(l, lws[1])
lib.tv_list_watch_remove(l, lws[2])
@@ -678,6 +706,66 @@ describe('typval.c', function()
eq({int(-100500), int(100500)}, typvalt2lua(l_tv))
end)
end)
+ describe('tv()', function()
+ itp('works', function()
+ local l_tv = lua2typvalt(empty_list)
+ local l = l_tv.vval.v_list
+
+ local l_l_tv = lua2typvalt(empty_list)
+ alloc_log:clear()
+ local l_l = l_l_tv.vval.v_list
+ eq(1, l_l.lv_refcount)
+ lib.tv_list_append_tv(l, l_l_tv)
+ eq(2, l_l.lv_refcount)
+ eq(l_l, l.lv_first.li_tv.vval.v_list)
+ alloc_log:check({
+ a.li(l.lv_first),
+ })
+
+ local l_s_tv = lua2typvalt('test')
+ alloc_log:check({
+ a.str(l_s_tv.vval.v_string, 'test'),
+ })
+ lib.tv_list_append_tv(l, l_s_tv)
+ alloc_log:check({
+ a.li(l.lv_last),
+ a.str(l.lv_last.li_tv.vval.v_string, 'test'),
+ })
+
+ eq({empty_list, 'test'}, typvalt2lua(l_tv))
+ end)
+ end)
+ describe('owned tv()', function()
+ itp('works', function()
+ local l_tv = lua2typvalt(empty_list)
+ local l = l_tv.vval.v_list
+
+ local l_l_tv = lua2typvalt(empty_list)
+ alloc_log:clear()
+ local l_l = l_l_tv.vval.v_list
+ eq(1, l_l.lv_refcount)
+ lib.tv_list_append_owned_tv(l, l_l_tv)
+ eq(1, l_l.lv_refcount)
+ l_l.lv_refcount = l_l.lv_refcount + 1
+ eq(l_l, l.lv_first.li_tv.vval.v_list)
+ alloc_log:check({
+ a.li(l.lv_first),
+ })
+
+ local l_s_tv = ffi.gc(lua2typvalt('test'), nil)
+ alloc_log:check({
+ a.str(l_s_tv.vval.v_string, 'test'),
+ })
+ lib.tv_list_append_owned_tv(l, l_s_tv)
+ eq(l_s_tv.vval.v_string, l.lv_last.li_tv.vval.v_string)
+ l_s_tv.vval.v_string = nil
+ alloc_log:check({
+ a.li(l.lv_last),
+ })
+
+ eq({empty_list, 'test'}, typvalt2lua(l_tv))
+ end)
+ end)
end)
describe('copy()', function()
local function tv_list_copy(...)
diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua
index b1e709c444..87c838dece 100644
--- a/test/unit/helpers.lua
+++ b/test/unit/helpers.lua
@@ -650,8 +650,6 @@ local function itp_child(wr, func)
collectgarbage('stop')
child_sethook(wr)
err, emsg = pcall(func)
- collectgarbage('restart')
- collectgarbage()
debug.sethook()
end
emsg = tostring(emsg)
@@ -662,14 +660,15 @@ local function itp_child(wr, func)
end
sc.write(wr, ('-\n%05u\n%s'):format(#emsg, emsg))
deinit()
- sc.close(wr)
- sc.exit(1)
else
sc.write(wr, '+\n')
deinit()
- sc.close(wr)
- sc.exit(0)
end
+ collectgarbage('restart')
+ collectgarbage()
+ sc.write(wr, '$\n')
+ sc.close(wr)
+ sc.exit(err and 0 or 1)
end
local function check_child_err(rd)
@@ -690,44 +689,48 @@ local function check_child_err(rd)
break
end
trace[#trace + 1] = traceline
- table.remove(trace, maxtrace + 1)
+ if #trace > maxtrace then
+ table.remove(trace, 1)
+ end
end
local res = sc.read(rd, 2)
- if #res ~= 2 then
- local error
- if #trace == 0 then
- error = '\nTest crashed, no trace available\n'
- else
- error = '\nTest crashed, trace:\n' .. tracehelp
- for i = 1, #trace do
- error = error .. trace[i]
+ if #res == 2 then
+ local err = ''
+ if res ~= '+\n' then
+ eq('-\n', res)
+ local len_s = sc.read(rd, 5)
+ local len = tonumber(len_s)
+ neq(0, len)
+ if os.getenv('NVIM_TEST_TRACE_ON_ERROR') == '1' and #trace ~= 0 then
+ err = '\nTest failed, trace:\n' .. tracehelp
+ for _, traceline in ipairs(trace) do
+ err = err .. traceline
+ end
end
+ err = err .. sc.read(rd, len + 1)
end
- if not did_traceline then
- error = error .. '\nNo end of trace occurred'
- end
- local cc_err, cc_emsg = pcall(check_cores, Paths.test_luajit_prg, true)
- if not cc_err then
- error = error .. '\ncheck_cores failed: ' .. cc_emsg
+ local eres = sc.read(rd, 2)
+ if eres ~= '$\n' then
+ if #trace == 0 then
+ err = '\nTest crashed, no trace available\n'
+ else
+ err = '\nTest crashed, trace:\n' .. tracehelp
+ for i = 1, #trace do
+ err = err .. trace[i]
+ end
+ end
+ if not did_traceline then
+ err = err .. '\nNo end of trace occurred'
+ end
+ local cc_err, cc_emsg = pcall(check_cores, Paths.test_luajit_prg, true)
+ if not cc_err then
+ err = err .. '\ncheck_cores failed: ' .. cc_emsg
+ end
end
- assert.just_fail(error)
- end
- if res == '+\n' then
- return
- end
- eq('-\n', res)
- local len_s = sc.read(rd, 5)
- local len = tonumber(len_s)
- neq(0, len)
- local err = ''
- if os.getenv('NVIM_TEST_TRACE_ON_ERROR') == '1' and #trace ~= 0 then
- err = '\nTest failed, trace:\n' .. tracehelp
- for _, traceline in ipairs(trace) do
- err = err .. traceline
+ if err ~= '' then
+ assert.just_fail(err)
end
end
- err = err .. sc.read(rd, len + 1)
- assert.just_fail(err)
end
local function itp_parent(rd, pid, allow_failure)