aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/eval/typval.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/eval/typval.c')
-rw-r--r--src/nvim/eval/typval.c560
1 files changed, 435 insertions, 125 deletions
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 4521085519..c8b550f902 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>
@@ -30,6 +31,7 @@
#include "nvim/message.h"
// TODO(ZyX-I): Move line_breakcheck out of misc1
#include "nvim/misc1.h" // For line_breakcheck
+#include "nvim/os/fileio.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/typval.c.generated.h"
@@ -44,6 +46,70 @@ bool tv_in_free_unref_items = false;
const char *const tv_empty_string = "";
//{{{1 Lists
+//{{{2 List log
+#ifdef LOG_LIST_ACTIONS
+ListLog *list_log_first = NULL;
+ListLog *list_log_last = NULL;
+
+/// Write list log to the given file
+///
+/// @param[in] fname File to write log to. Will be appended to if already
+/// present.
+void list_write_log(const char *const fname)
+ FUNC_ATTR_NONNULL_ALL
+{
+ FileDescriptor fp;
+ const int fo_ret = file_open(&fp, fname, kFileCreate|kFileAppend, 0600);
+ if (fo_ret != 0) {
+ emsgf(_("E5142: Failed to open file %s: %s"), fname, os_strerror(fo_ret));
+ return;
+ }
+ for (ListLog *chunk = list_log_first; chunk != NULL;) {
+ for (size_t i = 0; i < chunk->size; i++) {
+ char buf[10 + 1 + ((16 + 3) * 3) + (8 + 2) + 2];
+ // act : hex " c:" len "[]" "\n\0"
+ const ListLogEntry entry = chunk->entries[i];
+ const size_t snp_len = (size_t)snprintf(
+ buf, sizeof(buf),
+ "%-10.10s: l:%016" PRIxPTR "[%08d] 1:%016" PRIxPTR " 2:%016" PRIxPTR
+ "\n",
+ entry.action, entry.l, entry.len, entry.li1, entry.li2);
+ assert(snp_len + 1 == sizeof(buf));
+ const ptrdiff_t fw_ret = file_write(&fp, buf, snp_len);
+ if (fw_ret != (ptrdiff_t)snp_len) {
+ assert(fw_ret < 0);
+ if (i) {
+ memmove(chunk->entries, chunk->entries + i,
+ sizeof(chunk->entries[0]) * (chunk->size - i));
+ chunk->size -= i;
+ }
+ emsgf(_("E5143: Failed to write to file %s: %s"),
+ fname, os_strerror((int)fw_ret));
+ return;
+ }
+ }
+ list_log_first = chunk->next;
+ xfree(chunk);
+ chunk = list_log_first;
+ }
+ const int fc_ret = file_close(&fp, true);
+ if (fc_ret != 0) {
+ emsgf(_("E5144: Failed to close file %s: %s"), fname, os_strerror(fc_ret));
+ }
+}
+
+#ifdef EXITFREE
+/// Free list log
+void list_free_log(void)
+{
+ for (ListLog *chunk = list_log_first; chunk != NULL;) {
+ list_log_first = chunk->next;
+ xfree(chunk);
+ chunk = list_log_first;
+ }
+}
+#endif
+#endif
//{{{2 List item
/// Allocate a list item
@@ -52,35 +118,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(&item->li_tv);
- 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
@@ -137,8 +197,14 @@ void tv_list_watch_fix(list_T *const l, const listitem_T *const item)
///
/// Caller should take care of the reference count.
///
+/// @param[in] len Expected number of items to be populated before list
+/// becomes accessible from VimL. It is still valid to
+/// underpopulate a list, value only controls how many elements
+/// will be allocated in advance. Currently does nothing.
+/// @see ListLenSpecials.
+///
/// @return [allocated] new list.
-list_T *tv_list_alloc(void)
+list_T *tv_list_alloc(const ptrdiff_t len)
FUNC_ATTR_NONNULL_RET
{
list_T *const list = xcalloc(1, sizeof(list_T));
@@ -150,15 +216,59 @@ list_T *tv_list_alloc(void)
list->lv_used_prev = NULL;
list->lv_used_next = gc_first_list;
gc_first_list = list;
+ list_log(list, NULL, (void *)(uintptr_t)len, "alloc");
return list;
}
+/// Initialize a static list with 10 items
+///
+/// @param[out] sl Static list to initialize.
+void tv_list_init_static10(staticList10_T *const sl)
+ FUNC_ATTR_NONNULL_ALL
+{
+#define SL_SIZE ARRAY_SIZE(sl->sl_items)
+ list_T *const l = &sl->sl_list;
+
+ memset(sl, 0, sizeof(staticList10_T));
+ l->lv_first = &sl->sl_items[0];
+ l->lv_last = &sl->sl_items[SL_SIZE - 1];
+ l->lv_refcount = DO_NOT_FREE_CNT;
+ tv_list_set_lock(l, VAR_FIXED);
+ sl->sl_list.lv_len = 10;
+
+ sl->sl_items[0].li_prev = NULL;
+ sl->sl_items[0].li_next = &sl->sl_items[1];
+ sl->sl_items[SL_SIZE - 1].li_prev = &sl->sl_items[SL_SIZE - 2];
+ sl->sl_items[SL_SIZE - 1].li_next = NULL;
+
+ for (size_t i = 1; i < SL_SIZE - 1; i++) {
+ listitem_T *const li = &sl->sl_items[i];
+ li->li_prev = li - 1;
+ li->li_next = li + 1;
+ }
+ list_log((const list_T *)sl, &sl->sl_items[0], &sl->sl_items[SL_SIZE - 1],
+ "s10init");
+#undef SL_SIZE
+}
+
+/// Initialize static list with undefined number of elements
+///
+/// @param[out] l List to initialize.
+void tv_list_init_static(list_T *const l)
+ FUNC_ATTR_NONNULL_ALL
+{
+ memset(l, 0, sizeof(*l));
+ l->lv_refcount = DO_NOT_FREE_CNT;
+ list_log(l, NULL, NULL, "sinit");
+}
+
/// Free items contained in a list
///
/// @param[in,out] l List to clear.
void tv_list_free_contents(list_T *const l)
FUNC_ATTR_NONNULL_ALL
{
+ list_log(l, NULL, NULL, "freecont");
for (listitem_T *item = l->lv_first; item != NULL; item = l->lv_first) {
// Remove the item before deleting it.
l->lv_first = item->li_next;
@@ -188,6 +298,7 @@ void tv_list_free_list(list_T *const l)
if (l->lv_used_next != NULL) {
l->lv_used_next->lv_used_prev = l->lv_used_prev;
}
+ list_log(l, NULL, NULL, "freelist");
xfree(l);
}
@@ -221,17 +332,18 @@ void tv_list_unref(list_T *const l)
//{{{2 Add/remove
-/// Remove items "item" to "item2" from list "l".
+/// Remove items "item" to "item2" from list "l"
///
/// @warning Does not free the listitem or the value!
///
/// @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
{
+ list_log(l, item, item2, "drop");
// Notify watchers.
for (listitem_T *ip = item; ip != item2->li_next; ip = ip->li_next) {
l->lv_len--;
@@ -249,6 +361,51 @@ void tv_list_remove_items(list_T *const l, listitem_T *const item,
item->li_prev->li_next = item2->li_next;
}
l->lv_idx_item = NULL;
+ list_log(l, l->lv_first, l->lv_last, "afterdrop");
+}
+
+/// 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
+{
+ list_log(l, item, item2, "remove");
+ 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.
+/// @param[in] item First item to move.
+/// @param[in] item2 Last item to move.
+/// @param[out] tgt_l List to move to.
+/// @param[in] cnt Number of items moved.
+void tv_list_move_items(list_T *const l, listitem_T *const item,
+ listitem_T *const item2, list_T *const tgt_l,
+ const int cnt)
+ FUNC_ATTR_NONNULL_ALL
+{
+ list_log(l, item, item2, "move");
+ tv_list_drop_items(l, item, item2);
+ item->li_prev = tgt_l->lv_last;
+ item2->li_next = NULL;
+ if (tgt_l->lv_last == NULL) {
+ tgt_l->lv_first = item;
+ } else {
+ tgt_l->lv_last->li_next = item;
+ }
+ tgt_l->lv_last = item2;
+ tgt_l->lv_len += cnt;
+ list_log(tgt_l, tgt_l->lv_first, tgt_l->lv_last, "movetgt");
}
/// Insert list item
@@ -277,6 +434,7 @@ void tv_list_insert(list_T *const l, listitem_T *const ni,
}
item->li_prev = ni;
l->lv_len++;
+ list_log(l, ni, item, "insert");
}
}
@@ -303,6 +461,7 @@ void tv_list_insert_tv(list_T *const l, typval_T *const tv,
void tv_list_append(list_T *const l, listitem_T *const item)
FUNC_ATTR_NONNULL_ALL
{
+ list_log(l, item, NULL, "append");
if (l->lv_last == NULL) {
// empty list
l->lv_first = item;
@@ -326,7 +485,19 @@ void tv_list_append_tv(list_T *const l, typval_T *const tv)
FUNC_ATTR_NONNULL_ALL
{
listitem_T *const li = tv_list_item_alloc();
- tv_copy(tv, &li->li_tv);
+ tv_copy(tv, TV_LIST_ITEM_TV(li));
+ 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);
}
@@ -334,33 +505,29 @@ void tv_list_append_tv(list_T *const l, typval_T *const tv)
///
/// @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();
-
- li->li_tv.v_type = VAR_LIST;
- li->li_tv.v_lock = VAR_UNLOCKED;
- li->li_tv.vval.v_list = itemlist;
- tv_list_append(list, li);
- if (itemlist != NULL) {
- itemlist->lv_refcount++;
- }
+ tv_list_append_owned_tv(l, (typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_list = itemlist,
+ });
+ tv_list_ref(itemlist);
}
/// Append a dictionary to a list
///
/// @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();
-
- li->li_tv.v_type = VAR_DICT;
- li->li_tv.v_lock = VAR_UNLOCKED;
- li->li_tv.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++;
}
@@ -374,17 +541,18 @@ void tv_list_append_dict(list_T *const list, dict_T *const dict)
/// case string is considered to be usual zero-terminated
/// string or NULL “empty” string.
void tv_list_append_string(list_T *const l, const char *const str,
- const ptrdiff_t len)
+ 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
@@ -396,12 +564,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);
- li->li_tv.v_type = VAR_STRING;
- li->li_tv.v_lock = VAR_UNLOCKED;
- li->li_tv.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
@@ -411,11 +578,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();
- li->li_tv.v_type = VAR_NUMBER;
- li->li_tv.v_lock = VAR_UNLOCKED;
- li->li_tv.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
@@ -438,34 +605,36 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig,
return NULL;
}
- list_T *copy = tv_list_alloc();
+ list_T *copy = tv_list_alloc(tv_list_len(orig));
+ tv_list_ref(copy);
if (copyID != 0) {
// Do this before adding the items, because one of the items may
// refer back to this list.
orig->lv_copyID = copyID;
orig->lv_copylist = copy;
}
- listitem_T *item;
- for (item = orig->lv_first; item != NULL && !got_int;
- item = item->li_next) {
+ TV_LIST_ITER(orig, item, {
+ if (got_int) {
+ break;
+ }
listitem_T *const ni = tv_list_item_alloc();
if (deep) {
- if (var_item_copy(conv, &item->li_tv, &ni->li_tv, deep, copyID) == FAIL) {
+ if (var_item_copy(conv, TV_LIST_ITEM_TV(item), TV_LIST_ITEM_TV(ni),
+ deep, copyID) == FAIL) {
xfree(ni);
- break;
+ goto tv_list_copy_error;
}
} else {
- tv_copy(&item->li_tv, &ni->li_tv);
+ tv_copy(TV_LIST_ITEM_TV(item), TV_LIST_ITEM_TV(ni));
}
tv_list_append(copy, ni);
- }
- copy->lv_refcount++;
- if (item != NULL) {
- tv_list_unref(copy);
- copy = NULL;
- }
+ });
return copy;
+
+tv_list_copy_error:
+ tv_list_unref(copy);
+ return NULL;
}
/// Extend first list with the second
@@ -475,17 +644,17 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig,
/// @param[in] bef If not NULL, extends before this item.
void tv_list_extend(list_T *const l1, list_T *const l2,
listitem_T *const bef)
- FUNC_ATTR_NONNULL_ARG(1, 2)
+ FUNC_ATTR_NONNULL_ARG(1)
{
- int todo = l2->lv_len;
+ int todo = tv_list_len(l2);
listitem_T *const befbef = (bef == NULL ? NULL : bef->li_prev);
listitem_T *const saved_next = (befbef == NULL ? NULL : befbef->li_next);
// We also quit the loop when we have inserted the original item count of
// the list, avoid a hang when we extend a list with itself.
- for (listitem_T *item = l2->lv_first
- ; item != NULL && --todo >= 0
+ for (listitem_T *item = tv_list_first(l2)
+ ; item != NULL && todo--
; item = (item == befbef ? saved_next : item->li_next)) {
- tv_list_insert_tv(l1, &item->li_tv, bef);
+ tv_list_insert_tv(l1, TV_LIST_ITEM_TV(item), bef);
}
}
@@ -540,13 +709,15 @@ static int list_join_inner(garray_T *const gap, list_T *const l,
{
size_t sumlen = 0;
bool first = true;
- listitem_T *item;
// Stringify each item in the list.
- for (item = l->lv_first; item != NULL && !got_int; item = item->li_next) {
+ TV_LIST_ITER(l, item, {
+ if (got_int) {
+ break;
+ }
char *s;
size_t len;
- s = encode_tv2echo(&item->li_tv, &len);
+ s = encode_tv2echo(TV_LIST_ITEM_TV(item), &len);
if (s == NULL) {
return FAIL;
}
@@ -557,7 +728,7 @@ static int list_join_inner(garray_T *const gap, list_T *const l,
p->tofree = p->s = (char_u *)s;
line_breakcheck();
- }
+ });
// Allocate result buffer with its total size, avoid re-allocation and
// multiple copy operations. Add 2 for a tailing ']' and NUL.
@@ -591,16 +762,16 @@ static int list_join_inner(garray_T *const gap, list_T *const l,
///
/// @return OK in case of success, FAIL otherwise.
int tv_list_join(garray_T *const gap, list_T *const l, const char *const sep)
- FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_ARG(1)
{
- if (l->lv_len < 1) {
+ if (!tv_list_len(l)) {
return OK;
}
garray_T join_ga;
int retval;
- ga_init(&join_ga, (int)sizeof(Join), l->lv_len);
+ ga_init(&join_ga, (int)sizeof(Join), tv_list_len(l));
retval = list_join_inner(gap, l, sep, &join_ga);
#define FREE_JOIN_TOFREE(join) xfree((join)->tofree)
@@ -632,11 +803,13 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic,
return false;
}
- listitem_T *item1 = l1->lv_first;
- listitem_T *item2 = l2->lv_first;
+ listitem_T *item1 = tv_list_first(l1);
+ listitem_T *item2 = tv_list_first(l2);
for (; item1 != NULL && item2 != NULL
- ; item1 = item1->li_next, item2 = item2->li_next) {
- if (!tv_equal(&item1->li_tv, &item2->li_tv, ic, recursive)) {
+ ; (item1 = TV_LIST_ITEM_NEXT(l1, item1),
+ item2 = TV_LIST_ITEM_NEXT(l2, item2))) {
+ if (!tv_equal(TV_LIST_ITEM_TV(item1), TV_LIST_ITEM_TV(item2), ic,
+ recursive)) {
return false;
}
}
@@ -644,6 +817,74 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic,
return true;
}
+/// Reverse list in-place
+///
+/// @param[in,out] l List to reverse.
+void tv_list_reverse(list_T *const l)
+{
+ if (tv_list_len(l) <= 1) {
+ return;
+ }
+ list_log(l, NULL, NULL, "reverse");
+#define SWAP(a, b) \
+ do { \
+ tmp = a; \
+ a = b; \
+ b = tmp; \
+ } while (0)
+ listitem_T *tmp;
+
+ SWAP(l->lv_first, l->lv_last);
+ for (listitem_T *li = l->lv_first; li != NULL; li = li->li_next) {
+ SWAP(li->li_next, li->li_prev);
+ }
+#undef SWAP
+
+ 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;
+ }
+ list_log(l, NULL, NULL, "sort");
+ 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
@@ -662,13 +903,8 @@ listitem_T *tv_list_find(list_T *const l, int n)
return NULL;
}
- // Negative index is relative to the end.
- if (n < 0) {
- n = l->lv_len + n;
- }
-
- // Check for index out of range.
- if (n < 0 || n >= l->lv_len) {
+ n = tv_list_uidx(l, n);
+ if (n == -1) {
return NULL;
}
@@ -717,6 +953,7 @@ listitem_T *tv_list_find(list_T *const l, int n)
// Cache the used index.
l->lv_idx = idx;
l->lv_idx_item = item;
+ list_log(l, l->lv_idx_item, (void *)(uintptr_t)l->lv_idx, "find");
return item;
}
@@ -740,7 +977,7 @@ varnumber_T tv_list_find_nr(list_T *const l, const int n, bool *const ret_error)
}
return -1;
}
- return tv_get_number_chk(&li->li_tv, ret_error);
+ return tv_get_number_chk(TV_LIST_ITEM_TV(li), ret_error);
}
/// Get list item l[n] as a string
@@ -757,7 +994,7 @@ const char *tv_list_find_str(list_T *const l, const int n)
emsgf(_(e_listidx), (int64_t)n);
return NULL;
}
- return tv_get_string(&li->li_tv);
+ return tv_get_string(TV_LIST_ITEM_TV(li));
}
/// Locate item in a list and return its index
@@ -772,15 +1009,14 @@ long tv_list_idx_of_item(const list_T *const l, const listitem_T *const item)
if (l == NULL) {
return -1;
}
- long idx = 0;
- const listitem_T *li;
- for (li = l->lv_first; li != NULL && li != item; li = li->li_next) {
+ int idx = 0;
+ TV_LIST_ITER_CONST(l, li, {
+ if (li == item) {
+ return idx;
+ }
idx++;
- }
- if (li == NULL) {
- return -1;
- }
- return idx;
+ });
+ return -1;
}
//{{{1 Dictionaries
@@ -824,7 +1060,7 @@ void tv_dict_watcher_add(dict_T *const dict, const char *const key_pattern,
/// @param[in] cb2 Second callback to check.
///
/// @return True if they are equal, false otherwise.
-bool tv_callback_equal(const Callback *const cb1, const Callback *const cb2)
+bool tv_callback_equal(const Callback *cb1, const Callback *cb2)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
if (cb1->type != cb2->type) {
@@ -843,10 +1079,31 @@ bool tv_callback_equal(const Callback *const cb1, const Callback *const cb2)
return true;
}
}
- assert(false);
+ abort();
return false;
}
+/// Unref/free callback
+void callback_free(Callback *callback)
+ FUNC_ATTR_NONNULL_ALL
+{
+ switch (callback->type) {
+ case kCallbackFuncref: {
+ func_unref(callback->data.funcref);
+ xfree(callback->data.funcref);
+ break;
+ }
+ case kCallbackPartial: {
+ partial_unref(callback->data.partial);
+ break;
+ }
+ case kCallbackNone: {
+ break;
+ }
+ }
+ callback->type = kCallbackNone;
+}
+
/// Remove watcher from a dictionary
///
/// @param dict Dictionary to remove watcher from.
@@ -1318,7 +1575,7 @@ int tv_dict_add_list(dict_T *const d, const char *const key,
item->di_tv.v_lock = VAR_UNLOCKED;
item->di_tv.v_type = VAR_LIST;
item->di_tv.vval.v_list = list;
- list->lv_refcount++;
+ tv_list_ref(list);
if (tv_dict_add(d, item) == FAIL) {
tv_dict_item_free(item);
return FAIL;
@@ -1374,6 +1631,29 @@ int tv_dict_add_nr(dict_T *const d, const char *const key,
return OK;
}
+/// Add a special entry to dictionary
+///
+/// @param[out] d Dictionary to add entry to.
+/// @param[in] key Key to add.
+/// @param[in] key_len Key length.
+/// @param[in] val SpecialVarValue to add.
+///
+/// @return OK in case of success, FAIL when key already exists.
+int tv_dict_add_special(dict_T *const d, const char *const key,
+ const size_t key_len, SpecialVarValue val)
+{
+ dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
+
+ item->di_tv.v_lock = VAR_UNLOCKED;
+ item->di_tv.v_type = VAR_SPECIAL;
+ item->di_tv.vval.v_special = val;
+ if (tv_dict_add(d, item) == FAIL) {
+ tv_dict_item_free(item);
+ return FAIL;
+ }
+ return OK;
+}
+
/// Add a string entry to dictionary
///
/// @param[out] d Dictionary to add entry to.
@@ -1387,11 +1667,32 @@ int tv_dict_add_str(dict_T *const d,
const char *const val)
FUNC_ATTR_NONNULL_ALL
{
+ return tv_dict_add_allocated_str(d, key, key_len, xstrdup(val));
+}
+
+/// Add a string entry to dictionary
+///
+/// Unlike tv_dict_add_str() saves val to the new dictionary item in place of
+/// creating a new copy.
+///
+/// @warning String will be freed even in case addition fails.
+///
+/// @param[out] d Dictionary to add entry to.
+/// @param[in] key Key to add.
+/// @param[in] key_len Key length.
+/// @param[in] val String to add.
+///
+/// @return OK in case of success, FAIL when key already exists.
+int tv_dict_add_allocated_str(dict_T *const d,
+ const char *const key, const size_t key_len,
+ char *const val)
+ FUNC_ATTR_NONNULL_ALL
+{
dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
item->di_tv.v_lock = VAR_UNLOCKED;
item->di_tv.v_type = VAR_STRING;
- item->di_tv.vval.v_string = (char_u *)xstrdup(val);
+ item->di_tv.vval.v_string = (char_u *)val;
if (tv_dict_add(d, item) == FAIL) {
tv_dict_item_free(item);
return FAIL;
@@ -1603,16 +1904,20 @@ void tv_dict_set_keys_readonly(dict_T *const dict)
/// Also sets reference count.
///
/// @param[out] ret_tv Structure where list is saved.
+/// @param[in] len Expected number of items to be populated before list
+/// becomes accessible from VimL. It is still valid to
+/// underpopulate a list, value only controls how many elements
+/// will be allocated in advance. @see ListLenSpecials.
///
/// @return [allocated] pointer to the created list.
-list_T *tv_list_alloc_ret(typval_T *const ret_tv)
+list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len)
FUNC_ATTR_NONNULL_ALL
{
- list_T *const l = tv_list_alloc();
+ list_T *const l = tv_list_alloc(len);
ret_tv->vval.v_list = l;
ret_tv->v_type = VAR_LIST;
ret_tv->v_lock = VAR_UNLOCKED;
- l->lv_refcount++;
+ tv_list_ref(l);
return l;
}
@@ -1729,14 +2034,20 @@ static inline void _nothing_conv_func_end(typval_T *const tv, const int copyID)
tv->v_lock = VAR_UNLOCKED; \
} while (0)
+static inline void _nothing_conv_empty_dict(typval_T *const tv,
+ dict_T **const dictp)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(2)
+{
+ tv_dict_unref(*dictp);
+ *dictp = NULL;
+ if (tv != NULL) {
+ tv->v_lock = VAR_UNLOCKED;
+ }
+}
#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \
do { \
assert((void *)&dict != (void *)&TYPVAL_ENCODE_NODICT_VAR); \
- tv_dict_unref((dict_T *)dict); \
- *((dict_T **)&dict) = NULL; \
- if (tv != NULL) { \
- ((typval_T *)tv)->v_lock = VAR_UNLOCKED; \
- } \
+ _nothing_conv_empty_dict(tv, ((dict_T **)&dict)); \
} while (0)
static inline int _nothing_conv_real_list_after_start(
@@ -1933,7 +2244,7 @@ void tv_free(typval_T *tv)
///
/// @param[in] from Location to copy from.
/// @param[out] to Location to copy to.
-void tv_copy(typval_T *const from, typval_T *const to)
+void tv_copy(const typval_T *const from, typval_T *const to)
{
to->v_type = from->v_type;
to->v_lock = VAR_UNLOCKED;
@@ -1961,9 +2272,7 @@ void tv_copy(typval_T *const from, typval_T *const to)
break;
}
case VAR_LIST: {
- if (from->vval.v_list != NULL) {
- to->vval.v_list->lv_refcount++;
- }
+ tv_list_ref(to->vval.v_list);
break;
}
case VAR_DICT: {
@@ -2019,9 +2328,9 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock)
CHANGE_LOCK(lock, l->lv_lock);
if (deep < 0 || deep > 1) {
// Recursive: lock/unlock the items the List contains.
- for (listitem_T *li = l->lv_first; li != NULL; li = li->li_next) {
- tv_item_lock(&li->li_tv, deep - 1, lock);
- }
+ TV_LIST_ITER(l, li, {
+ tv_item_lock(TV_LIST_ITEM_TV(li), deep - 1, lock);
+ });
}
}
break;
@@ -2057,6 +2366,8 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock)
/// Check whether VimL value is locked itself or refers to a locked container
///
+/// @warning Fixed container is not the same as locked.
+///
/// @param[in] tv Value to check.
///
/// @return True if value is locked, false otherwise.
@@ -2065,8 +2376,7 @@ bool tv_islocked(const typval_T *const tv)
{
return ((tv->v_lock == VAR_LOCKED)
|| (tv->v_type == VAR_LIST
- && tv->vval.v_list != NULL
- && (tv->vval.v_list->lv_lock == VAR_LOCKED))
+ && (tv_list_locked(tv->vval.v_list) == VAR_LOCKED))
|| (tv->v_type == VAR_DICT
&& tv->vval.v_dict != NULL
&& (tv->vval.v_dict->dv_lock == VAR_LOCKED)));