diff options
Diffstat (limited to 'src/nvim/eval/typval.c')
-rw-r--r-- | src/nvim/eval/typval.c | 560 |
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))); |