From fb146e80aa1ead96518f38b9684e39249bc83485 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 26 Jul 2016 23:16:23 +0300 Subject: eval: Split eval.c into smaller files --- src/nvim/eval/typval.c | 1171 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1171 insertions(+) create mode 100644 src/nvim/eval/typval.c (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c new file mode 100644 index 0000000000..7726e106a1 --- /dev/null +++ b/src/nvim/eval/typval.c @@ -0,0 +1,1171 @@ +#include +#include +#include + +#include "nvim/eval/typval.h" +#include "nvim/eval/gc.h" +#include "nvim/eval/executor.h" +#include "nvim/eval/encode.h" +#include "nvim/eval/typval_encode.h" +#include "nvim/eval.h" +#include "nvim/types.h" +#include "nvim/assert.h" +#include "nvim/memory.h" +#include "nvim/globals.h" +// TODO(ZyX-I): Move line_breakcheck out of misc1 +#include "nvim/misc1.h" // For line_breakcheck + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/typval.c.generated.h" +#endif + +bool tv_in_free_unref_items = false; + +// TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead + +#define DICT_MAXNEST 100 + +const char *const tv_empty_string = ""; + +//{{{1 Lists +//{{{2 List item + +/// Allocate a list item +/// +/// @warning Allocated item is not initialized, do not forget to initialize it +/// and specifically set lv_lock. +/// +/// @return [allocated] new list item. +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) + FUNC_ATTR_NONNULL_ALL +{ + tv_list_remove_items(l, item, item); + tv_list_item_free(item); +} + +//{{{2 List watchers + +/// Add a watcher to a list +/// +/// @param[out] l List to add watcher to. +/// @param[in] lw Watcher to add. +void tv_list_watch_add(list_T *const l, listwatch_T *const lw) + FUNC_ATTR_NONNULL_ALL +{ + lw->lw_next = l->lv_watch; + l->lv_watch = lw; +} + +/// Remove a watcher from a list +/// +/// Does not give a warning if watcher was not found. +/// +/// @param[out] l List to remove watcher from. +/// @param[in] lwrem Watcher to remove. +void tv_list_watch_remove(list_T *const l, listwatch_T *const lwrem) + FUNC_ATTR_NONNULL_ALL +{ + listwatch_T **lwp = &l->lv_watch; + for (listwatch_T *lw = l->lv_watch; lw != NULL; lw = lw->lw_next) { + if (lw == lwrem) { + *lwp = lw->lw_next; + break; + } + lwp = &lw->lw_next; + } +} + +/// Advance watchers to the next item +/// +/// Used just before removing an item from a list. +/// +/// @param[out] l List from which item is removed. +/// @param[in] item List item being removed. +void tv_list_watch_fix(list_T *const l, const listitem_T *const item) + FUNC_ATTR_NONNULL_ALL +{ + for (listwatch_T *lw = l->lv_watch; lw != NULL; lw = lw->lw_next) { + if (lw->lw_item == item) { + lw->lw_item = item->li_next; + } + } +} + +//{{{2 Lists +//{{{3 Alloc/free + +/// Allocate an empty list +/// +/// Caller should take care of the reference count. +/// +/// @return [allocated] new list. +list_T *tv_list_alloc(void) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC +{ + list_T *const list = xcalloc(1, sizeof(list_T)); + + // Prepend the list to the list of lists for garbage collection. + if (gc_first_list != NULL) { + gc_first_list->lv_used_prev = list; + } + list->lv_used_prev = NULL; + list->lv_used_next = gc_first_list; + gc_first_list = list; + return list; +} + +/// 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 +{ + 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; + tv_clear(&item->li_tv); + xfree(item); + } + l->lv_len = 0; + l->lv_idx_item = NULL; + for (listwatch_T *lw = l->lv_watch; lw != NULL; lw = lw->lw_next) { + lw->lw_item = NULL; + } +} + +/// Free a list itself, ignoring items it contains +/// +/// Ignores the reference count. +/// +/// @param[in,out] l List to free. +void tv_list_free_list(list_T *const l) + FUNC_ATTR_NONNULL_ALL +{ + // Remove the list from the list of lists for garbage collection. + if (l->lv_used_prev == NULL) { + gc_first_list = l->lv_used_next; + } else { + l->lv_used_prev->lv_used_next = l->lv_used_next; + } + if (l->lv_used_next != NULL) { + l->lv_used_next->lv_used_prev = l->lv_used_prev; + } + + xfree(l); +} + +/// Free a list, including all items it points to +/// +/// Ignores the reference count. Does not do anything if +/// tv_in_free_unref_items is true. +/// +/// @param[in,out] l List to free. +void tv_list_free(list_T *const l) + FUNC_ATTR_NONNULL_ALL +{ + if (!tv_in_free_unref_items) { + tv_list_free_contents(l); + tv_list_free_list(l); + } +} + +/// Unreference a list +/// +/// Decrements the reference count and frees when it becomes zero or less. +/// +/// @param[in,out] l List to unreference. +void tv_list_unref(list_T *const l) +{ + if (l != NULL && --l->lv_refcount <= 0) { + tv_list_free(l); + } +} + +//{{{3 Add/remove + +/// 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) +{ + // notify watchers + for (listitem_T *ip = item; ip != NULL; ip = ip->li_next) { + l->lv_len--; + tv_list_watch_fix(l, ip); + if (ip == item2) { + break; + } + } + + if (item2->li_next == NULL) { + l->lv_last = item->li_prev; + } else { + item2->li_next->li_prev = item->li_prev; + } + if (item->li_prev == NULL) { + l->lv_first = item2->li_next; + } else { + item->li_prev->li_next = item2->li_next; + } + l->lv_idx_item = NULL; +} + +/// Insert list item +/// +/// @param[out] l List to insert to. +/// @param[in,out] ni Item to insert. +/// @param[in] item Item to insert before. If NULL, inserts at the end of the +/// list. +void tv_list_insert(list_T *const l, listitem_T *const ni, + listitem_T *const item) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + if (item == NULL) { + // Append new item at end of list. + tv_list_append(l, ni); + } else { + // Insert new item before existing item. + ni->li_prev = item->li_prev; + ni->li_next = item; + if (item->li_prev == NULL) { + l->lv_first = ni; + l->lv_idx++; + } else { + item->li_prev->li_next = ni; + l->lv_idx_item = NULL; + } + item->li_prev = ni; + l->lv_len++; + } +} + +/// Insert VimL value into a list +/// +/// @param[out] l List to insert to. +/// @param[in,out] tv Value to insert. Is copied (@see copy_tv()) to an +/// allocated listitem_T and inserted. +/// @param[in] item Item to insert before. If NULL, inserts at the end of the +/// list. +void tv_list_insert_tv(list_T *const l, typval_T *const tv, + listitem_T *const item) +{ + listitem_T *const ni = tv_list_item_alloc(); + + copy_tv(tv, &ni->li_tv); + tv_list_insert(l, ni, item); +} + +/// Append item to the end of list +/// +/// @param[out] l List to append to. +/// @param[in,out] item Item to append. +void tv_list_append(list_T *const l, listitem_T *const item) + FUNC_ATTR_NONNULL_ALL +{ + if (l->lv_last == NULL) { + // empty list + l->lv_first = item; + l->lv_last = item; + item->li_prev = NULL; + } else { + l->lv_last->li_next = item; + item->li_prev = l->lv_last; + l->lv_last = item; + } + l->lv_len++; + item->li_next = NULL; +} + +/// Append VimL value to the end of list +/// +/// @param[out] l List to append to. +/// @param[in,out] tv Value to append. Is copied (@see copy_tv()) to an +/// allocated listitem_T. +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(); + copy_tv(tv, &li->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) + 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++; + } +} + +/// 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) + 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); + if (dict != NULL) { + dict->dv_refcount++; + } +} + +/// Make a copy of "str" and append it as an item to list "l" +/// +/// @param[out] l List to append to. +/// @param[in] str String to append. +/// @param[in] len Length of the appended string. May be -1, in this +/// 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) + 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))); + } +} + +/// Append given string to the list +/// +/// Unlike list_append_string this function does not copy the string. +/// +/// @param[out] l List to append to. +/// @param[in] str String to append. +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; +} + +/// Append number to the list +/// +/// @param[out] l List to append to. +/// @param[in] n Number to append. Will be recorded in the allocated +/// 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); +} + +//{{{3 Operations on the whole list + +/// Make a copy of list +/// +/// @param[in] conv If non-NULL, then all internal strings will be converted. +/// @param[in] orig Original list to copy. +/// @param[in] deep If false, then shallow copy will be done. +/// @param[in] copyID See var_item_copy(). +/// +/// @return Copied list. May be NULL in case original list is NULL or some +/// failure happens. The refcount of the new list is set to 1. +list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig, + const bool deep, const int copyID) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (orig == NULL) { + return NULL; + } + + list_T *copy = tv_list_alloc(); + 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) { + listitem_T *const ni = tv_list_item_alloc(); + if (deep) { + if (var_item_copy(conv, &item->li_tv, &ni->li_tv, deep, copyID) == FAIL) { + xfree(ni); + break; + } + } else { + copy_tv(&item->li_tv, &ni->li_tv); + } + tv_list_append(copy, ni); + } + copy->lv_refcount++; + if (item != NULL) { + tv_list_unref(copy); + copy = NULL; + } + + return copy; +} + +/// Extend first list with the second +/// +/// @param[out] l1 List to extend. +/// @param[in] l2 List to extend with. +/// @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) +{ + int todo = l2->lv_len; + // 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 + ; item = item->li_next) { + tv_list_insert_tv(l1, &item->li_tv, bef); + } +} + +/// Concatenate lists into a new list +/// +/// @param[in] l1 First list. +/// @param[in] l2 Second list. +/// @param[out] ret_tv Location where new list is saved. +/// +/// @return OK or FAIL. +int tv_list_concat(list_T *const l1, list_T *const l2, typval_T *const tv) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (l1 == NULL || l2 == NULL) { + return FAIL; + } + + // make a copy of the first list. + list_T *const l = tv_list_copy(NULL, l1, false, 0); + if (l == NULL) { + return FAIL; + } + tv->v_type = VAR_LIST; + tv->vval.v_list = l; + + // append all items from the second list + tv_list_extend(l, l2, NULL); + return OK; +} + +typedef struct { + char_u *s; + char_u *tofree; +} Join; + +/// Join list into a string, helper function +/// +/// @param[out] gap Garray where result will be saved. +/// @param[in] l List to join. +/// @param[in] sep Used separator. +/// @param[in] join_gap Garray to keep each list item string. +/// +/// @return OK in case of success, FAIL otherwise. +static int list_join_inner(garray_T *const gap, list_T *const l, + const char *const sep, garray_T *const join_gap) + FUNC_ATTR_NONNULL_ALL +{ + 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) { + char *s; + size_t len; + s = encode_tv2echo(&item->li_tv, &len); + if (s == NULL) { + return FAIL; + } + + sumlen += len; + + Join *const p = GA_APPEND_VIA_PTR(Join, join_gap); + 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. + if (join_gap->ga_len >= 2) { + sumlen += strlen(sep) * (size_t)(join_gap->ga_len - 1); + } + ga_grow(gap, (int)sumlen + 2); + + for (int i = 0; i < join_gap->ga_len && !got_int; i++) { + if (first) { + first = false; + } else { + ga_concat(gap, (const char_u *)sep); + } + const Join *const p = ((const Join *)join_gap->ga_data) + i; + + if (p->s != NULL) { + ga_concat(gap, p->s); + } + line_breakcheck(); + } + + return OK; +} + +/// Join list into a string using given separator +/// +/// @param[out] gap Garray where result will be saved. +/// @param[in] l Joined list. +/// @param[in] sep Separator. +/// +/// @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 +{ + if (l->lv_len < 1) { + return OK; + } + + garray_T join_ga; + int retval; + + ga_init(&join_ga, (int)sizeof(Join), l->lv_len); + retval = list_join_inner(gap, l, sep, &join_ga); + +#define FREE_JOIN_TOFREE(join) xfree((join)->tofree) + GA_DEEP_CLEAR(&join_ga, Join, FREE_JOIN_TOFREE); +#undef FREE_JOIN_TOFREE + + return retval; +} + +/// Chech whether two lists are equal +/// +/// @param[in] l1 First list to compare. +/// @param[in] l2 Second list to compare. +/// @param[in] ic True if case is to be ignored. +/// @param[in] recursive True when used recursively. +bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, + const bool recursive) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (l1 == NULL || l2 == NULL) { + // FIXME? compare empty list with NULL list equal + return false; + } + if (l1 == l2) { + return true; + } + if (tv_list_len(l1) != tv_list_len(l2)) { + return false; + } + + listitem_T *item1 = l1->lv_first; + listitem_T *item2 = l2->lv_first; + for (; item1 != NULL && item2 != NULL + ; item1 = item1->li_next, item2 = item2->li_next) { + if (!tv_equal(&item1->li_tv, &item2->li_tv, ic, recursive)) { + return false; + } + } + assert(item1 == NULL && item2 == NULL); + return true; +} + +//{{{3 Indexing/searching + +/// Locate item with a given index in a list and return it +/// +/// @param[in] l List to index. +/// @param[in] n Index. Negative index is counted from the end, -1 is the last +/// item. +/// +/// @return Item at the given index or NULL if `n` is out of range. +listitem_T *tv_list_find(list_T *const l, int n) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + STATIC_ASSERT(sizeof(n) == sizeof(l->lv_idx), + "n and lv_idx sizes do not match"); + if (l == NULL) { + 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) { + return NULL; + } + + int idx; + listitem_T *item; + + // When there is a cached index may start search from there. + if (l->lv_idx_item != NULL) { + if (n < l->lv_idx / 2) { + // Closest to the start of the list. + item = l->lv_first; + idx = 0; + } else if (n > (l->lv_idx + l->lv_len) / 2) { + // Closest to the end of the list. + item = l->lv_last; + idx = l->lv_len - 1; + } else { + // Closest to the cached index. + item = l->lv_idx_item; + idx = l->lv_idx; + } + } else { + if (n < l->lv_len / 2) { + // Closest to the start of the list. + item = l->lv_first; + idx = 0; + } else { + // Closest to the end of the list. + item = l->lv_last; + idx = l->lv_len - 1; + } + } + + while (n > idx) { + // Search forward. + item = item->li_next; + idx++; + } + while (n < idx) { + // Search backward. + item = item->li_prev; + idx--; + } + + assert(idx == n); + // Cache the used index. + l->lv_idx = idx; + l->lv_idx_item = item; + + return item; +} + +/// Get list item l[n] as a number +/// +/// @param[in] l List to index. +/// @param[in] n Index in a list. +/// @param[out] ret_error Location where 1 will be saved if index was not +/// found. May be NULL. If everything is OK, +/// `*ret_error` is not touched. +/// +/// @return Integer value at the given index or -1. +varnumber_T tv_list_find_nr(list_T *const l, const int n, bool *ret_error) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + const listitem_T *const li = tv_list_find(l, n); + if (li == NULL) { + if (ret_error != NULL) { + *ret_error = true; + } + return -1; + } + return get_tv_number_chk(&li->li_tv, ret_error); +} + +/// Get list item l[n - 1] as a string +/// +/// @param[in] l List to index. +/// @param[in] n Index in a list. +/// +/// @return [allocated] Copy of the list item string value. +char *tv_list_find_str(list_T *l, int n) + FUNC_ATTR_MALLOC +{ + const listitem_T *const li = tv_list_find(l, n - 1); + if (li == NULL) { + EMSGN(_(e_listidx), n); + return NULL; + } + return (char *)get_tv_string(&li->li_tv); +} + +/// Locate item in a list and return its index +/// +/// @param[in] l List to search. +/// @param[in] item Item to search for. +/// +/// @return Index of an item or -1 if item is not in the list. +long tv_list_idx_of_item(const list_T *const l, const listitem_T *const item) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (l == NULL) { + return -1; + } + long idx = 0; + listitem_T *li; + for (li = l->lv_first; li != NULL && li != item; li = li->li_next) { + idx++; + } + if (li == NULL) { + return -1; + } + return idx; +} +//{{{1 Generic typval operations +//{{{2 Init/alloc/clear +//{{{3 Alloc + +/// Allocate an empty list for a return value +/// +/// Also sets reference count. +/// +/// @param[out] ret_tv Structure where list is saved. +/// +/// @return [allocated] pointer to the created list. +list_T *tv_list_alloc_ret(typval_T *const ret_tv) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_MALLOC +{ + list_T *const l = tv_list_alloc(); + ret_tv->vval.v_list = l; + ret_tv->v_type = VAR_LIST; + ret_tv->v_lock = VAR_UNLOCKED; + l->lv_refcount++; + return l; +} + +//{{{3 Clear +#define TYPVAL_ENCODE_ALLOW_SPECIALS false + +#define TYPVAL_ENCODE_CONV_NIL(tv) \ + do { \ + tv->vval.v_special = kSpecialVarFalse; \ + tv->v_lock = VAR_UNLOCKED; \ + } while (0) + +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ + TYPVAL_ENCODE_CONV_NIL(tv) + +#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ + do { \ + (void)num; \ + tv->vval.v_number = 0; \ + tv->v_lock = VAR_UNLOCKED; \ + } while (0) + +#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) + +#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ + do { \ + tv->vval.v_float = 0; \ + tv->v_lock = VAR_UNLOCKED; \ + } while (0) + +#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \ + do { \ + xfree(buf); \ + tv->vval.v_string = NULL; \ + tv->v_lock = VAR_UNLOCKED; \ + } while (0) + +#define TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len) + +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type) + +static inline int _nothing_conv_func_start(typval_T *const tv, + char_u *const fun) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1) +{ + tv->v_lock = VAR_UNLOCKED; + if (tv->v_type == VAR_PARTIAL) { + partial_T *const pt_ = tv->vval.v_partial; + if (pt_ != NULL && pt_->pt_refcount > 1) { + pt_->pt_refcount--; + tv->vval.v_partial = NULL; + return OK; + } + } else { + func_unref(fun); + if ((const char *)fun != tv_empty_string) { + xfree(fun); + } + tv->vval.v_string = NULL; + } + return NOTDONE; +} +#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ + do { \ + if (_nothing_conv_func_start(tv, fun) != NOTDONE) { \ + return OK; \ + } \ + } while (0) + +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len) + +static inline void _nothing_conv_func_end(typval_T *const tv, const int copyID) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL +{ + if (tv->v_type == VAR_PARTIAL) { + partial_T *const pt = tv->vval.v_partial; + if (pt == NULL) { + return; + } + // Dictionary should already be freed by the time. + // If it was not freed then it is a part of the reference cycle. + assert(pt->pt_dict == NULL || pt->pt_dict->dv_copyID == copyID); + pt->pt_dict = NULL; + // As well as all arguments. + pt->pt_argc = 0; + assert(pt->pt_refcount <= 1); + partial_unref(pt); + tv->vval.v_partial = NULL; + assert(tv->v_lock == VAR_UNLOCKED); + } +} +#define TYPVAL_ENCODE_CONV_FUNC_END(tv) _nothing_conv_func_end(tv, copyID) + +#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \ + do { \ + tv_list_unref(tv->vval.v_list); \ + tv->vval.v_list = NULL; \ + tv->v_lock = VAR_UNLOCKED; \ + } while (0) + +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ + do { \ + assert((void *)&dict != (void *)&TYPVAL_ENCODE_NODICT_VAR); \ + dict_unref((dict_T *)dict); \ + *((dict_T **)&dict) = NULL; \ + if (tv != NULL) { \ + ((typval_T *)tv)->v_lock = VAR_UNLOCKED; \ + } \ + } while (0) + +static inline int _nothing_conv_real_list_after_start( + typval_T *const tv, MPConvStackVal *const mpsv) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT +{ + assert(tv != NULL); + tv->v_lock = VAR_UNLOCKED; + if (tv->vval.v_list->lv_refcount > 1) { + tv->vval.v_list->lv_refcount--; + tv->vval.v_list = NULL; + mpsv->data.l.li = NULL; + return OK; + } + return NOTDONE; +} +#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) + +#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) \ + do { \ + if (_nothing_conv_real_list_after_start(tv, &mpsv) != NOTDONE) { \ + goto typval_encode_stop_converting_one_item; \ + } \ + } while (0) + +#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) + +static inline void _nothing_conv_list_end(typval_T *const tv) + FUNC_ATTR_ALWAYS_INLINE +{ + if (tv == NULL) { + return; + } + assert(tv->v_type == VAR_LIST); + list_T *const list = tv->vval.v_list; + tv_list_unref(list); + tv->vval.v_list = NULL; +} +#define TYPVAL_ENCODE_CONV_LIST_END(tv) _nothing_conv_list_end(tv) + +static inline int _nothing_conv_real_dict_after_start( + typval_T *const tv, dict_T **const dictp, const void *const nodictvar, + MPConvStackVal *const mpsv) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (tv != NULL) { + tv->v_lock = VAR_UNLOCKED; + } + if ((const void *)dictp != nodictvar && (*dictp)->dv_refcount > 1) { + (*dictp)->dv_refcount--; + *dictp = NULL; + mpsv->data.d.todo = 0; + return OK; + } + return NOTDONE; +} +#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) + +#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) \ + do { \ + if (_nothing_conv_real_dict_after_start( \ + tv, (dict_T **)&dict, (void *)&TYPVAL_ENCODE_NODICT_VAR, \ + &mpsv) != NOTDONE) { \ + goto typval_encode_stop_converting_one_item; \ + } \ + } while (0) + +#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(tv, dict) +#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) +#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) + +static inline void _nothing_conv_dict_end(typval_T *const tv, + dict_T **const dictp, + const void *const nodictvar) + FUNC_ATTR_ALWAYS_INLINE +{ + if ((const void *)dictp != nodictvar) { + dict_unref(*dictp); + *dictp = NULL; + } +} +#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \ + _nothing_conv_dict_end(tv, (dict_T **)&dict, \ + (void *)&TYPVAL_ENCODE_NODICT_VAR) + +#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) + +#define TYPVAL_ENCODE_SCOPE static +#define TYPVAL_ENCODE_NAME nothing +#define TYPVAL_ENCODE_FIRST_ARG_TYPE const void *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME ignored +#include "nvim/eval/typval_encode.c.h" +#undef TYPVAL_ENCODE_SCOPE +#undef TYPVAL_ENCODE_NAME +#undef TYPVAL_ENCODE_FIRST_ARG_TYPE +#undef TYPVAL_ENCODE_FIRST_ARG_NAME + +#undef TYPVAL_ENCODE_ALLOW_SPECIALS +#undef TYPVAL_ENCODE_CONV_NIL +#undef TYPVAL_ENCODE_CONV_BOOL +#undef TYPVAL_ENCODE_CONV_NUMBER +#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER +#undef TYPVAL_ENCODE_CONV_FLOAT +#undef TYPVAL_ENCODE_CONV_STRING +#undef TYPVAL_ENCODE_CONV_STR_STRING +#undef TYPVAL_ENCODE_CONV_EXT_STRING +#undef TYPVAL_ENCODE_CONV_FUNC_START +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF +#undef TYPVAL_ENCODE_CONV_FUNC_END +#undef TYPVAL_ENCODE_CONV_EMPTY_LIST +#undef TYPVAL_ENCODE_CONV_EMPTY_DICT +#undef TYPVAL_ENCODE_CONV_LIST_START +#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START +#undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS +#undef TYPVAL_ENCODE_CONV_LIST_END +#undef TYPVAL_ENCODE_CONV_DICT_START +#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START +#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK +#undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY +#undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS +#undef TYPVAL_ENCODE_CONV_DICT_END +#undef TYPVAL_ENCODE_CONV_RECURSE + +/// Free memory for a variable value and set the value to NULL or 0 +/// +/// @param[in,out] varp Value to free. +void tv_clear(typval_T *varp) +{ + if (varp != NULL && varp->v_type != VAR_UNKNOWN) { + const int evn_ret = encode_vim_to_nothing(varp, varp, "tv_clear argument"); + (void)evn_ret; + assert(evn_ret == OK); + } +} + +//{{{2 Locks + +/// Lock or unlock an item +/// +/// @param[out] tv Item to (un)lock. +/// @param[in] deep Levels to (un)lock, -1 to (un)lock everything. +/// @param[in] lock True if it is needed to lock an item, false to unlock. +void tv_item_lock(typval_T *const tv, const int deep, const bool lock) +{ + // TODO(ZyX-I): Make this not recursive + static int recurse = 0; + + if (recurse >= DICT_MAXNEST) { + emsgf(_("E743: variable nested too deep for (un)lock")); + return; + } + if (deep == 0) { + return; + } + recurse++; + + // lock/unlock the item itself +#define CHANGE_LOCK(lock, var) \ + do { \ + var = ((VarLockStatus[]) { \ + [VAR_UNLOCKED] = (lock ? VAR_LOCKED : VAR_UNLOCKED), \ + [VAR_LOCKED] = (lock ? VAR_LOCKED : VAR_UNLOCKED), \ + [VAR_FIXED] = VAR_FIXED, \ + })[var]; \ + } while (0) + CHANGE_LOCK(lock, tv->v_lock); + + switch (tv->v_type) { + case VAR_LIST: { + list_T *const l = tv->vval.v_list; + if (l != NULL) { + 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); + } + } + } + break; + } + case VAR_DICT: { + dict_T *const d = tv->vval.v_dict; + if (d != NULL) { + CHANGE_LOCK(lock, d->dv_lock); + if (deep < 0 || deep > 1) { + // recursive: lock/unlock the items the List contains + int todo = (int)d->dv_hashtab.ht_used; + for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + tv_item_lock(&HI2DI(hi)->di_tv, deep - 1, lock); + } + } + } + } + break; + } + case VAR_NUMBER: + case VAR_FLOAT: + case VAR_STRING: + case VAR_FUNC: + case VAR_PARTIAL: + case VAR_SPECIAL: { + break; + } + case VAR_UNKNOWN: { + assert(false); + } + } +#undef CHANGE_LOCK + recurse--; +} + +/// Check whether VimL value is locked itself or refers to a locked container +/// +/// @param[in] tv Value to check. +/// +/// @return True if value is locked, false otherwise. +bool tv_islocked(const typval_T *const tv) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + 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->v_type == VAR_DICT + && tv->vval.v_dict != NULL + && (tv->vval.v_dict->dv_lock & VAR_LOCKED))); +} + +//{{{2 Type checks + +/// Check that given value is a number or string +/// +/// Error messages are compatible with get_tv_number() previously used for the +/// same purpose in buf*() functions. Special values are not accepted (previous +/// behaviour: silently fail to find buffer). +/// +/// @param[in] tv Value to check. +/// +/// @return true if everything is OK, false otherwise. +bool tv_check_str_or_nr(const typval_T *const tv) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + switch (tv->v_type) { + case VAR_NUMBER: + case VAR_STRING: { + return true; + } + case VAR_FLOAT: { + EMSG(_("E805: Expected a Number or a String, Float found")); + return false; + } + case VAR_PARTIAL: + case VAR_FUNC: { + EMSG(_("E703: Expected a Number or a String, Funcref found")); + return false; + } + case VAR_LIST: { + EMSG(_("E745: Expected a Number or a String, List found")); + return false; + } + case VAR_DICT: { + EMSG(_("E728: Expected a Number or a String, Dictionary found")); + return false; + } + case VAR_SPECIAL: { + EMSG(_("E5300: Expected a Number or a String")); + return false; + } + case VAR_UNKNOWN: { + EMSG2(_(e_intern2), "tv_check_str_or_nr(UNKNOWN)"); + return false; + } + } + assert(false); + return false; +} -- cgit From e18a5783080f7c94f408ec5f53dedffdb69789e1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 20 Aug 2016 22:24:34 +0300 Subject: *: Move some dictionary functions to typval.h and use char* Also fixes buffer reusage in setmatches() and complete(). --- src/nvim/eval/typval.c | 788 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 742 insertions(+), 46 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 7726e106a1..bb7baed7d2 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2,6 +2,7 @@ #include #include +#include "nvim/lib/queue.h" #include "nvim/eval/typval.h" #include "nvim/eval/gc.h" #include "nvim/eval/executor.h" @@ -12,6 +13,9 @@ #include "nvim/assert.h" #include "nvim/memory.h" #include "nvim/globals.h" +#include "nvim/hashtab.h" +#include "nvim/vim.h" +#include "nvim/ascii.h" // TODO(ZyX-I): Move line_breakcheck out of misc1 #include "nvim/misc1.h" // For line_breakcheck @@ -115,8 +119,7 @@ void tv_list_watch_fix(list_T *const l, const listitem_T *const item) } } -//{{{2 Lists -//{{{3 Alloc/free +//{{{2 Alloc/free /// Allocate an empty list /// @@ -205,7 +208,7 @@ void tv_list_unref(list_T *const l) } } -//{{{3 Add/remove +//{{{2 Add/remove /// Remove items "item" to "item2" from list "l". /// @@ -406,7 +409,7 @@ void tv_list_append_number(list_T *const l, const varnumber_T n) tv_list_append(l, li); } -//{{{3 Operations on the whole list +//{{{2 Operations on the whole list /// Make a copy of list /// @@ -596,6 +599,8 @@ int tv_list_join(garray_T *const gap, list_T *const l, const char *const sep) /// @param[in] l2 Second list to compare. /// @param[in] ic True if case is to be ignored. /// @param[in] recursive True when used recursively. +/// +/// @return True if lists are equal, false otherwise. bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, const bool recursive) FUNC_ATTR_WARN_UNUSED_RESULT @@ -623,7 +628,7 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, return true; } -//{{{3 Indexing/searching +//{{{2 Indexing/searching /// Locate item with a given index in a list and return it /// @@ -761,6 +766,552 @@ long tv_list_idx_of_item(const list_T *const l, const listitem_T *const item) } return idx; } + +//{{{1 Dictionaries +//{{{2 Dictionary watchers + +/// Perform all necessary cleanup for a `DictWatcher` instance +/// +/// @param watcher Watcher to free. +void tv_dict_watcher_free(DictWatcher *watcher) + FUNC_ATTR_NONNULL_ALL +{ + callback_free(&watcher->callback); + xfree(watcher->key_pattern); + xfree(watcher); +} + +/// Test if `key` matches with with `watcher->key_pattern` +/// +/// @param[in] watcher Watcher to check key pattern from. +/// @param[in] key Key to check. +/// +/// @return true if key matches, false otherwise. +static bool tv_dict_watcher_matches(DictWatcher *watcher, const char *const key) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + // For now only allow very simple globbing in key patterns: a '*' at the end + // of the string means it should match everything up to the '*' instead of the + // whole string. + const size_t len = strlen(watcher->key_pattern); + if (watcher->key_pattern[len - 1] == '*') { + return strncmp(key, watcher->key_pattern, len - 1) == 0; + } else { + return strcmp(key, watcher->key_pattern) == 0; + } +} + +/// Send a change notification to all dictionary watchers that match given key +/// +/// @param[in] dict Dictionary which was modified. +/// @param[in] key Key which was modified. +/// @param[in] newtv New key value. +/// @param[in] oldtv Old key value. +void tv_dict_watcher_notify(dict_T *const dict, const char *const key, + typval_T *const newtv, typval_T *const oldtv) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + typval_T argv[3]; + + argv[0].v_type = VAR_DICT; + argv[0].v_lock = VAR_UNLOCKED; + argv[0].vval.v_dict = dict; + argv[1].v_type = VAR_STRING; + argv[1].v_lock = VAR_UNLOCKED; + argv[1].vval.v_string = (char_u *)xstrdup(key); + argv[2].v_type = VAR_DICT; + argv[2].v_lock = VAR_UNLOCKED; + argv[2].vval.v_dict = tv_dict_alloc(); + argv[2].vval.v_dict->dv_refcount++; + + if (newtv) { + dictitem_T *const v = tv_dict_item_alloc_len(S_LEN("new")); + copy_tv(newtv, &v->di_tv); + tv_dict_add(argv[2].vval.v_dict, v); + } + + if (oldtv) { + dictitem_T *const v = tv_dict_item_alloc_len(S_LEN("old")); + copy_tv(oldtv, &v->di_tv); + tv_dict_add(argv[2].vval.v_dict, v); + } + + typval_T rettv; + + QUEUE *w; + QUEUE_FOREACH(w, &dict->watchers) { + DictWatcher *watcher = tv_dict_watcher_node_data(w); + if (!watcher->busy && tv_dict_watcher_matches(watcher, key)) { + rettv = TV_INITIAL_VALUE; + watcher->busy = true; + callback_call(&watcher->callback, 3, argv, &rettv); + watcher->busy = false; + tv_clear(&rettv); + } + } + + for (size_t i = 1; i < ARRAY_SIZE(argv); i++) { + tv_clear(argv + i); + } +} + +//{{{2 Dictionary item + +/// Allocate a dictionary item +/// +/// @note that the value of the item (->di_tv) still needs to be initialized. +/// +/// @param[in] key Key, is copied to the new item. +/// @param[in] key_len Key length. +/// +/// @return [allocated] new dictionary item. +dictitem_T *tv_dict_item_alloc_len(const char *const key, const size_t key_len) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_MALLOC +{ + dictitem_T *const di = xmalloc(offsetof(dictitem_T, di_key) + key_len + 1); + memcpy(di->di_key, key, key_len); + di->di_key[key_len] = NUL; + di->di_flags = DI_FLAGS_ALLOC; + return di; +} + +/// Allocate a dictionary item +/// +/// @note that the value of the item (->di_tv) still needs to be initialized. +/// +/// @param[in] key Key, is copied to the new item. +/// +/// @return [allocated] new dictionary item. +dictitem_T *tv_dict_item_alloc(const char *const key) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_MALLOC +{ + return tv_dict_item_alloc_len(key, strlen(key)); +} + +/// Free a dictionary item, also clearing the value +/// +/// @param item Item to free. +void tv_dict_item_free(dictitem_T *const item) + FUNC_ATTR_NONNULL_ALL +{ + tv_clear(&item->di_tv); + if (item->di_flags & DI_FLAGS_ALLOC) { + xfree(item); + } +} + +/// Add item to dictionary +/// +/// @param[out] d Dictionary to add to. +/// @param[in] item Item to add. +/// +/// @return FAIL if key already exists. +int tv_dict_add(dict_T *const d, dictitem_T *const item) + FUNC_ATTR_NONNULL_ALL +{ + return hash_add(&d->dv_hashtab, item->di_key); +} + +/// Make a copy of a dictionary item +/// +/// @param[in] di Item to copy. +/// +/// @return [allocated] new dictionary item. +static dictitem_T *tv_dict_item_copy(dictitem_T *const di) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_MALLOC +{ + dictitem_T *const new_di = tv_dict_item_alloc((const char *)di->di_key); + copy_tv(&di->di_tv, &new_di->di_tv); + return new_di; +} + +/// Remove item from dictionary and free it +/// +/// @param dict Dictionary to remove item from. +/// @param item Item to remove. +void tv_dict_item_remove(dict_T *const dict, dictitem_T *const item) + FUNC_ATTR_NONNULL_ALL +{ + hashitem_T *const hi = hash_find(&dict->dv_hashtab, item->di_key); + if (HASHITEM_EMPTY(hi)) { + emsgf(_(e_intern2), "tv_dict_item_remove()"); + } else { + hash_remove(&dict->dv_hashtab, hi); + } + tv_dict_item_free(item); +} + +//{{{2 Alloc/free + +/// Allocate an empty dictionary +/// +/// @return [allocated] new dictionary. +dict_T *tv_dict_alloc(void) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT +{ + dict_T *const d = xmalloc(sizeof(dict_T)); + + // Add the dict to the list of dicts for garbage collection. + if (gc_first_dict != NULL) { + gc_first_dict->dv_used_prev = d; + } + d->dv_used_next = gc_first_dict; + d->dv_used_prev = NULL; + gc_first_dict = d; + + hash_init(&d->dv_hashtab); + d->dv_lock = VAR_UNLOCKED; + d->dv_scope = VAR_NO_SCOPE; + d->dv_refcount = 0; + d->dv_copyID = 0; + QUEUE_INIT(&d->watchers); + + return d; +} + +/// Free items contained in a dictionary +/// +/// @param[in,out] d Dictionary to clear. +void tv_dict_free_contents(dict_T *const d) + FUNC_ATTR_NONNULL_ALL +{ + // Lock the hashtab, we don't want it to resize while freeing items. + hash_lock(&d->dv_hashtab); + assert(d->dv_hashtab.ht_locked > 0); + HASHTAB_ITER(&d->dv_hashtab, hi, { + // Remove the item before deleting it, just in case there is + // something recursive causing trouble. + dictitem_T *const di = TV_DICT_HI2DI(hi); + hash_remove(&d->dv_hashtab, hi); + tv_dict_item_free(di); + }); + + while (!QUEUE_EMPTY(&d->watchers)) { + QUEUE *w = QUEUE_HEAD(&d->watchers); + QUEUE_REMOVE(w); + DictWatcher *watcher = tv_dict_watcher_node_data(w); + tv_dict_watcher_free(watcher); + } + + hash_clear(&d->dv_hashtab); + d->dv_hashtab.ht_locked--; + hash_init(&d->dv_hashtab); +} + +/// Free a dictionary itself, ignoring items it contains +/// +/// Ignores the reference count. +/// +/// @param[in,out] d Dictionary to free. +void tv_dict_free_dict(dict_T *const d) + FUNC_ATTR_NONNULL_ALL +{ + // Remove the dict from the list of dicts for garbage collection. + if (d->dv_used_prev == NULL) { + gc_first_dict = d->dv_used_next; + } else { + d->dv_used_prev->dv_used_next = d->dv_used_next; + } + if (d->dv_used_next != NULL) { + d->dv_used_next->dv_used_prev = d->dv_used_prev; + } + + xfree(d); +} + +/// Free a dictionary, including all items it contains +/// +/// Ignores the reference count. +/// +/// @param d Dictionary to free. +void tv_dict_free(dict_T *const d) + FUNC_ATTR_NONNULL_ALL +{ + if (!tv_in_free_unref_items) { + tv_dict_free_contents(d); + tv_dict_free_dict(d); + } +} + + +/// Unreference a dictionary +/// +/// Decrements the reference count and frees dictionary when it becomes zero. +/// +/// @param[in] d Dictionary to operate on. +void tv_dict_unref(dict_T *const d) +{ + if (d != NULL && --d->dv_refcount <= 0) { + tv_dict_free(d); + } +} + +//{{{2 Indexing/searching + +/// Find item in dictionary +/// +/// @param[in] d Dictionary to check. +/// @param[in] key Dictionary key. +/// @param[in] len Key length. If negative, then strlen(key) is used. +/// +/// @return found item or NULL if nothing was found. +dictitem_T *tv_dict_find(const dict_T *const d, const char *const key, + const ptrdiff_t len) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + hashitem_T *const hi = (len < 0 + ? hash_find(&d->dv_hashtab, (const char_u *)key) + : hash_find_len(&d->dv_hashtab, key, (size_t)len)); + if (HASHITEM_EMPTY(hi)) { + return NULL; + } + return TV_DICT_HI2DI(hi); +} + +/// Get a number item from a dictionary +/// +/// Returns 0 if the entry does not exist. +/// +/// @param[in] d Dictionary to get item from. +/// @param[in] key Key to find in dictionary. +/// +/// @return Dictionary item. +varnumber_T tv_dict_get_number(dict_T *const d, const char *const key) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + dictitem_T *const di = tv_dict_find(d, key, -1); + if (di == NULL) { + return 0; + } + return get_tv_number(&di->di_tv); +} + +/// Get a string item from a dictionary +/// +/// @param[in] d Dictionary to get item from. +/// @param[in] key Dictionary key. +/// @param[in] save If true, returned string will be placed in the allocated +/// memory. +/// +/// @return NULL if key does not exist, empty string in case of type error, +/// string item value otherwise. If returned value is not NULL, it may +/// be allocated depending on `save` argument. +char *tv_dict_get_string(dict_T *const d, const char *const key, + const bool save) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + static char numbuf[NUMBUFLEN]; + const char *const s = tv_dict_get_string_buf(d, key, numbuf); + if (save && s != NULL) { + return xstrdup(s); + } + return (char *)s; +} + +/// Get a string item from a dictionary +/// +/// @param[in] d Dictionary to get item from. +/// @param[in] key Dictionary key. +/// @param[in] numbuf Numbuf for. +/// +/// @return NULL if key does not exist, empty string in case of type error, +/// string item value otherwise. +const char *tv_dict_get_string_buf(dict_T *const d, const char *const key, + char *const numbuf) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + dictitem_T *const di = tv_dict_find(d, key, -1); + if (di == NULL) { + return NULL; + } + return (const char *)get_tv_string_buf(&di->di_tv, (char_u *)numbuf); +} + +//{{{2 Operations on the whole dict + +/// Clear all the keys of a Dictionary. "d" remains a valid empty Dictionary. +/// +/// @param d The Dictionary to clear +void tv_dict_clear(dict_T *const d) + FUNC_ATTR_NONNULL_ALL +{ + hash_lock(&d->dv_hashtab); + assert(d->dv_hashtab.ht_locked > 0); + + HASHTAB_ITER(&d->dv_hashtab, hi, { + tv_dict_item_free(TV_DICT_HI2DI(hi)); + hash_remove(&d->dv_hashtab, hi); + }); + + hash_unlock(&d->dv_hashtab); +} + +/// Extend dictionary with items from another dictionary +/// +/// @param d1 Dictionary to extend. +/// @param[in] d2 Dictionary to extend with. +/// @param[in] action "error", "force", "keep": +/// +/// e*, including "error": duplicate key gives an error. +/// f*, including "force": duplicate d2 keys override d1. +/// other, including "keep": duplicate d2 keys ignored. +void tv_dict_extend(dict_T *const d1, dict_T *const d2, + const char *const action) + FUNC_ATTR_NONNULL_ALL +{ + const bool watched = tv_dict_is_watched(d1); + const char *const arg_errmsg = _("extend() argument"); + const size_t arg_errmsg_len = strlen(arg_errmsg); + + TV_DICT_ITER(d2, di2, { + dictitem_T *const di1 = tv_dict_find(d1, (const char *)di2->di_key, -1); + if (d1->dv_scope != VAR_NO_SCOPE) { + // Disallow replacing a builtin function in l: and g:. + // Check the key to be valid when adding to any scope. + if (d1->dv_scope == VAR_DEF_SCOPE + && di2->di_tv.v_type == VAR_FUNC + && !var_check_func_name((const char *)di2->di_key, di1 == NULL)) { + break; + } + if (!valid_varname((const char *)di2->di_key)) { + break; + } + } + if (di1 == NULL) { + dictitem_T *const new_di = tv_dict_item_copy(di2); + if (tv_dict_add(d1, new_di) == FAIL) { + tv_dict_item_free(new_di); + } else if (watched) { + tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv, + NULL); + } + } else if (*action == 'e') { + emsgf(_("E737: Key already exists: %s"), di2->di_key); + break; + } else if (*action == 'f' && di2 != di1) { + typval_T oldtv; + + if (tv_check_lock(di1->di_tv.v_lock, arg_errmsg, arg_errmsg_len) + || var_check_ro(di1->di_flags, arg_errmsg, arg_errmsg_len)) { + break; + } + + if (watched) { + copy_tv(&di1->di_tv, &oldtv); + } + + tv_clear(&di1->di_tv); + copy_tv(&di2->di_tv, &di1->di_tv); + + if (watched) { + tv_dict_watcher_notify(d1, (const char *)di1->di_key, &di1->di_tv, + &oldtv); + tv_clear(&oldtv); + } + } + }); +} + +/// Compare two dictionaries +/// +/// @param[in] d1 First dictionary. +/// @param[in] d2 Second dictionary. +/// @param[in] ic True if case is to be ignored. +/// @param[in] recursive True when used recursively. +bool tv_dict_equal(dict_T *const d1, dict_T *const d2, + const bool ic, const bool recursive) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (d1 == d2) { + return true; + } + if (d1 == NULL || d2 == NULL) { + return false; + } + if (tv_dict_len(d1) != tv_dict_len(d2)) { + return false; + } + + TV_DICT_ITER(d1, di1, { + dictitem_T *const di2 = tv_dict_find(d2, (const char *)di1->di_key, -1); + if (di2 == NULL) { + return false; + } + if (!tv_equal(&di1->di_tv, &di2->di_tv, ic, recursive)) { + return false; + } + }); + return true; +} + +/// Make a copy of dictionary +/// +/// @param[in] conv If non-NULL, then all internal strings will be converted. +/// @param[in] orig Original dictionary to copy. +/// @param[in] deep If false, then shallow copy will be done. +/// @param[in] copyID See var_item_copy(). +/// +/// @return Copied dictionary. May be NULL in case original dictionary is NULL +/// or some failure happens. The refcount of the new dictionary is set +/// to 1. +dict_T *tv_dict_copy(const vimconv_T *const conv, + dict_T *const orig, + const bool deep, + const int copyID) +{ + if (orig == NULL) { + return NULL; + } + + dict_T *copy = tv_dict_alloc(); + if (copyID != 0) { + orig->dv_copyID = copyID; + orig->dv_copydict = copy; + } + TV_DICT_ITER(orig, di, { + if (got_int) { + break; + } + dictitem_T *new_di; + if (conv == NULL || conv->vc_type == CONV_NONE) { + new_di = tv_dict_item_alloc((const char *)di->di_key); + } else { + size_t len = STRLEN(di->di_key); + char *const key = (char *)string_convert(conv, di->di_key, &len); + if (key == NULL) { + new_di = tv_dict_item_alloc_len((const char *)di->di_key, len); + } else { + new_di = tv_dict_item_alloc_len(key, len); + xfree(key); + } + } + if (deep) { + if (var_item_copy(conv, &di->di_tv, &new_di->di_tv, deep, + copyID) == FAIL) { + xfree(new_di); + break; + } + } else { + copy_tv(&di->di_tv, &new_di->di_tv); + } + if (tv_dict_add(copy, new_di) == FAIL) { + tv_dict_item_free(new_di); + break; + } + }); + + copy->dv_refcount++; + if (got_int) { + tv_dict_unref(copy); + copy = NULL; + } + + return copy; +} + //{{{1 Generic typval operations //{{{2 Init/alloc/clear //{{{3 Alloc @@ -783,6 +1334,21 @@ list_T *tv_list_alloc_ret(typval_T *const ret_tv) return l; } +/// Allocate an empty dictionary for a return value +/// +/// Also sets reference count. +/// +/// @param[out] ret_tv Structure where dictionary is saved. +void tv_dict_alloc_ret(typval_T *const ret_tv) + FUNC_ATTR_NONNULL_ALL +{ + dict_T *const d = tv_dict_alloc(); + ret_tv->vval.v_dict = d; + ret_tv->v_type = VAR_DICT; + ret_tv->v_lock = VAR_UNLOCKED; + d->dv_refcount++; +} + //{{{3 Clear #define TYPVAL_ENCODE_ALLOW_SPECIALS false @@ -884,7 +1450,7 @@ static inline void _nothing_conv_func_end(typval_T *const tv, const int copyID) #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ do { \ assert((void *)&dict != (void *)&TYPVAL_ENCODE_NODICT_VAR); \ - dict_unref((dict_T *)dict); \ + tv_dict_unref((dict_T *)dict); \ *((dict_T **)&dict) = NULL; \ if (tv != NULL) { \ ((typval_T *)tv)->v_lock = VAR_UNLOCKED; \ @@ -966,7 +1532,7 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, FUNC_ATTR_ALWAYS_INLINE { if ((const void *)dictp != nodictvar) { - dict_unref(*dictp); + tv_dict_unref(*dictp); *dictp = NULL; } } @@ -1077,13 +1643,9 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) CHANGE_LOCK(lock, d->dv_lock); if (deep < 0 || deep > 1) { // recursive: lock/unlock the items the List contains - int todo = (int)d->dv_hashtab.ht_used; - for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - tv_item_lock(&HI2DI(hi)->di_tv, deep - 1, lock); - } - } + TV_DICT_ITER(d, di, { + tv_item_lock(&di->di_tv, deep - 1, lock); + }); } } break; @@ -1121,6 +1683,140 @@ bool tv_islocked(const typval_T *const tv) && (tv->vval.v_dict->dv_lock & VAR_LOCKED))); } +/// Return true if typval is locked +/// +/// Also gives an error message when typval is locked. +/// +/// @param[in] lock Lock status. +/// @param[in] name Variable name, used in the error message. +/// @param[in] use_gettext True if variable name also is to be translated. +/// +/// @return true if variable is locked, false otherwise. +bool tv_check_lock(const VarLockStatus lock, const char *const name, + const size_t name_len) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + const char *error_message = NULL; + switch (lock) { + case VAR_UNLOCKED: { + return false; + } + case VAR_LOCKED: { + error_message = N_("E741: Value is locked: %.*s"); + break; + } + case VAR_FIXED: { + error_message = N_("E742: Cannot change value of %.*s"); + break; + } + } + assert(error_message != NULL); + + const char *const unknown_name = _("Unknown"); + + emsgf(_(error_message), (name != NULL ? name_len : strlen(unknown_name)), + (name != NULL ? name : unknown_name)); + + return true; +} + +//{{{2 Comparison + +static int tv_equal_recurse_limit; + +/// Compare two VimL values +/// +/// Like "==", but strings and numbers are different, as well as floats and +/// numbers. +/// +/// @warning Too nested structures may be considered equal even if they are not. +/// +/// @param[in] tv1 First value to compare. +/// @param[in] tv2 Second value to compare. +/// @param[in] ic True if case is to be ignored. +/// @param[in] recursive True when used recursively. +/// +/// @return true if values are equal. +bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, + const bool recursive) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + // TODO(ZyX-I): Make this not recursive + static int recursive_cnt = 0; // Catch recursive loops. + + if (!((tv1->v_type == VAR_FUNC || tv1->v_type == VAR_PARTIAL) + && (tv2->v_type == VAR_FUNC || tv2->v_type == VAR_PARTIAL)) + && tv1->v_type != tv2->v_type) { + return false; + } + + // Catch lists and dicts that have an endless loop by limiting + // recursiveness to a limit. We guess they are equal then. + // A fixed limit has the problem of still taking an awful long time. + // Reduce the limit every time running into it. That should work fine for + // deeply linked structures that are not recursively linked and catch + // recursiveness quickly. + if (!recursive) { + tv_equal_recurse_limit = 1000; + } + if (recursive_cnt >= tv_equal_recurse_limit) { + tv_equal_recurse_limit--; + return true; + } + + switch (tv1->v_type) { + case VAR_LIST: { + recursive_cnt++; + const bool r = tv_list_equal(tv1->vval.v_list, tv2->vval.v_list, ic, + true); + recursive_cnt--; + return r; + } + case VAR_DICT: { + recursive_cnt++; + const bool r = tv_dict_equal(tv1->vval.v_dict, tv2->vval.v_dict, ic, + true); + recursive_cnt--; + return r; + } + case VAR_PARTIAL: + case VAR_FUNC: { + if ((tv1->v_type == VAR_PARTIAL && tv1->vval.v_partial == NULL) + || (tv2->v_type == VAR_PARTIAL && tv2->vval.v_partial == NULL)) { + return false; + } + recursive_cnt++; + const bool r = func_equal(tv1, tv2, ic); + recursive_cnt--; + return r; + } + case VAR_NUMBER: { + return tv1->vval.v_number == tv2->vval.v_number; + } + case VAR_FLOAT: { + return tv1->vval.v_float == tv2->vval.v_float; + } + case VAR_STRING: { + char buf1[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + const char *s1 = (const char *)get_tv_string_buf(tv1, (char_u *)buf1); + const char *s2 = (const char *)get_tv_string_buf(tv2, (char_u *)buf2); + return mb_strcmp_ic((bool)ic, s1, s2) == 0; + } + case VAR_SPECIAL: { + return tv1->vval.v_special == tv2->vval.v_special; + } + case VAR_UNKNOWN: { + // VAR_UNKNOWN can be the result of an invalid expression, let’s say it + // does not equal anything, not even self. + return false; + } + } + + assert(false); + return false; +} + //{{{2 Type checks /// Check that given value is a number or string @@ -1135,37 +1831,37 @@ bool tv_islocked(const typval_T *const tv) bool tv_check_str_or_nr(const typval_T *const tv) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE { - switch (tv->v_type) { - case VAR_NUMBER: - case VAR_STRING: { - return true; - } - case VAR_FLOAT: { - EMSG(_("E805: Expected a Number or a String, Float found")); - return false; - } - case VAR_PARTIAL: - case VAR_FUNC: { - EMSG(_("E703: Expected a Number or a String, Funcref found")); - return false; - } - case VAR_LIST: { - EMSG(_("E745: Expected a Number or a String, List found")); - return false; - } - case VAR_DICT: { - EMSG(_("E728: Expected a Number or a String, Dictionary found")); - return false; - } - case VAR_SPECIAL: { - EMSG(_("E5300: Expected a Number or a String")); - return false; - } - case VAR_UNKNOWN: { - EMSG2(_(e_intern2), "tv_check_str_or_nr(UNKNOWN)"); - return false; - } + switch (tv->v_type) { + case VAR_NUMBER: + case VAR_STRING: { + return true; } - assert(false); - return false; + case VAR_FLOAT: { + emsgf(_("E805: Expected a Number or a String, Float found")); + return false; + } + case VAR_PARTIAL: + case VAR_FUNC: { + emsgf(_("E703: Expected a Number or a String, Funcref found")); + return false; + } + case VAR_LIST: { + emsgf(_("E745: Expected a Number or a String, List found")); + return false; + } + case VAR_DICT: { + emsgf(_("E728: Expected a Number or a String, Dictionary found")); + return false; + } + case VAR_SPECIAL: { + emsgf(_("E5300: Expected a Number or a String")); + return false; + } + case VAR_UNKNOWN: { + emsgf(_(e_intern2), "tv_check_str_or_nr(UNKNOWN)"); + return false; + } + } + assert(false); + return false; } -- cgit From ecff8387f465bbf5d57de09498059ff1de671131 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 20 Aug 2016 23:12:15 +0300 Subject: eval: Move get_dict_callback to typval.c --- src/nvim/eval/typval.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index bb7baed7d2..f4f34e0d92 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1130,6 +1130,42 @@ const char *tv_dict_get_string_buf(dict_T *const d, const char *const key, return (const char *)get_tv_string_buf(&di->di_tv, (char_u *)numbuf); } +/// Get a function from a dictionary +/// +/// @param[in] d Dictionary to get callback from. +/// @param[in] key Dictionary key. +/// @param[in] key_len Key length, may be -1 to use strlen(). +/// @param[out] result The address where a pointer to the wanted callback +/// will be left. +/// +/// @return true/false on success/failure. +bool tv_dict_get_callback(dict_T *const d, + const char *const key, const ptrdiff_t key_len, + Callback *const result) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + result->type = kCallbackNone; + + dictitem_T *const di = tv_dict_find(d, key, key_len); + + if (di == NULL) { + return true; + } + + if (di->di_tv.v_type != VAR_FUNC && di->di_tv.v_type != VAR_STRING + && di->di_tv.v_type != VAR_PARTIAL) { + EMSG(_("Argument is not a function or function name")); + return false; + } + + typval_T tv; + copy_tv(&di->di_tv, &tv); + set_selfdict(&tv, d); + bool res = callback_from_typval(result, &tv); + tv_clear(&tv); + return res; +} + //{{{2 Operations on the whole dict /// Clear all the keys of a Dictionary. "d" remains a valid empty Dictionary. -- cgit From 983a5532ca17060508ea308a9b7069dae4facb59 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 20 Aug 2016 23:14:50 +0300 Subject: eval: Move dict_set_keys_readonly to typval.c --- src/nvim/eval/typval.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index f4f34e0d92..0df818040b 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1135,8 +1135,8 @@ const char *tv_dict_get_string_buf(dict_T *const d, const char *const key, /// @param[in] d Dictionary to get callback from. /// @param[in] key Dictionary key. /// @param[in] key_len Key length, may be -1 to use strlen(). -/// @param[out] result The address where a pointer to the wanted callback -/// will be left. +/// @param[out] result The address where a pointer to the wanted callback +/// will be left. /// /// @return true/false on success/failure. bool tv_dict_get_callback(dict_T *const d, @@ -1348,6 +1348,19 @@ dict_T *tv_dict_copy(const vimconv_T *const conv, return copy; } +/// Set all existing keys in "dict" as read-only. +/// +/// This does not protect against adding new keys to the Dictionary. +/// +/// @param dict The dict whose keys should be frozen. +void tv_dict_set_keys_readonly(dict_T *const dict) + FUNC_ATTR_NONNULL_ALL +{ + TV_DICT_ITER(dict, di, { + di->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX; + }); +} + //{{{1 Generic typval operations //{{{2 Init/alloc/clear //{{{3 Alloc -- cgit From 210342d795a13e88579d3e55ae931463d16f241d Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 11 Jan 2017 23:20:03 +0300 Subject: eval: Move dict_add_list and dict_add_dict to typval.c --- src/nvim/eval/typval.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 0df818040b..f9885926c7 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1166,6 +1166,58 @@ bool tv_dict_get_callback(dict_T *const d, return res; } +//{{{2 dict_add* + +/// Add a list entry to dictionary +/// +/// @param[out] d Dictionary to add entry to. +/// @param[in] key Key to add. +/// @param[in] key_len Key length. +/// @param list List to add. Will have reference count incremented. +/// +/// @return OK in case of success, FAIL when key already exists. +int tv_dict_add_list(dict_T *const d, const char *const key, + const size_t key_len, list_T *const list) + FUNC_ATTR_NONNULL_ALL +{ + dictitem_T *item = tv_dict_item_alloc_len(key, key_len); + + item->di_tv.v_lock = VAR_UNLOCKED; + item->di_tv.v_type = VAR_LIST; + item->di_tv.vval.v_list = list; + if (tv_dict_add(d, item) == FAIL) { + tv_dict_item_free(item); + return FAIL; + } + list->lv_refcount++; + return OK; +} + +/// Add a dictionary entry to dictionary +/// +/// @param[out] d Dictionary to add entry to. +/// @param[in] key Key to add. +/// @param[in] key_len Key length. +/// @param dict Dictionary to add. Will have reference count incremented. +/// +/// @return OK in case of success, FAIL when key already exists. +int tv_dict_add_dict(dict_T *const d, const char *const key, + const size_t key_len, dict_T *const dict) + FUNC_ATTR_NONNULL_ALL +{ + dictitem_T *const item = tv_dict_item_alloc(key); + + item->di_tv.v_lock = VAR_UNLOCKED; + item->di_tv.v_type = VAR_DICT; + item->di_tv.vval.v_dict = dict; + if (tv_dict_add(d, item) == FAIL) { + tv_dict_item_free(item); + return FAIL; + } + dict->dv_refcount++; + return OK; +} + //{{{2 Operations on the whole dict /// Clear all the keys of a Dictionary. "d" remains a valid empty Dictionary. -- cgit From 2dcfc439b2ec2be4d830826061e89860977516be Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 20 Aug 2016 23:56:49 +0300 Subject: eval: Split and move dict_add_nr_str to typval.c Function was split into tv_dict_add_nr() and tv_dict_add_str(). --- src/nvim/eval/typval.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index f9885926c7..62460bcc3a 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1180,7 +1180,7 @@ int tv_dict_add_list(dict_T *const d, const char *const key, const size_t key_len, list_T *const list) FUNC_ATTR_NONNULL_ALL { - dictitem_T *item = tv_dict_item_alloc_len(key, key_len); + 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_LIST; @@ -1218,6 +1218,54 @@ int tv_dict_add_dict(dict_T *const d, const char *const key, return OK; } +/// Add a number 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] nr Number to add. +/// +/// @return OK in case of success, FAIL when key already exists. +int tv_dict_add_nr(dict_T *const d, const char *const key, + const size_t key_len, const varnumber_T nr) +{ + 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_NUMBER; + item->di_tv.vval.v_number = nr; + 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. +/// @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_str(dict_T *const d, + const char *const key, const size_t key_len, + const 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); + if (tv_dict_add(d, item) == FAIL) { + tv_dict_item_free(item); + return FAIL; + } + return OK; +} + //{{{2 Operations on the whole dict /// Clear all the keys of a Dictionary. "d" remains a valid empty Dictionary. -- cgit From 28dafe3ff0b0dc082fb62b2251fd64a167ce7188 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 21 Aug 2016 08:16:47 +0300 Subject: eval,*: Move get_tv_string to typval.c Function was renamed and changed to return `const char *`. --- src/nvim/eval/typval.c | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 62460bcc3a..cbeb2e059c 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -733,7 +733,7 @@ varnumber_T tv_list_find_nr(list_T *const l, const int n, bool *ret_error) /// @param[in] n Index in a list. /// /// @return [allocated] Copy of the list item string value. -char *tv_list_find_str(list_T *l, int n) +const char *tv_list_find_str(list_T *l, int n) FUNC_ATTR_MALLOC { const listitem_T *const li = tv_list_find(l, n - 1); @@ -741,7 +741,7 @@ char *tv_list_find_str(list_T *l, int n) EMSGN(_(e_listidx), n); return NULL; } - return (char *)get_tv_string(&li->li_tv); + return tv_get_string(&li->li_tv); } /// Locate item in a list and return its index @@ -2014,3 +2014,27 @@ bool tv_check_str_or_nr(const typval_T *const tv) assert(false); return false; } + +//{{{2 Get + +/// Get the string value of a variable +/// +/// @warning For number and special values it uses a single, static buffer. It +/// may be used only once, next call to get_tv_string may reuse it. Use +/// get_tv_string_buf() if you need to use tv_get_string() output after +/// calling it again. +/// +/// @note get_tv_string_chk() and get_tv_string_buf_chk() are similar, but +/// return NULL on error. +/// +/// @param[in] varp Varible to get value of. +/// +/// @return Variable value if it is VAR_STRING variable, number converted to +/// a string for VAR_NUMBER, v: variable name for VAR_SPECIAL or empty +/// string. +const char *tv_get_string(const typval_T *const varp) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT +{ + static char_u mybuf[NUMBUFLEN]; + return (const char *)get_tv_string_buf((typval_T *)varp, mybuf); +} -- cgit From 949f09bdbba592a12629c71e20ff7bb49a21db6c Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 21 Aug 2016 08:47:56 +0300 Subject: eval: Move get_tv_string_buf() to eval/typval.c --- src/nvim/eval/typval.c | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index cbeb2e059c..2c2d0ecaab 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1127,7 +1127,7 @@ const char *tv_dict_get_string_buf(dict_T *const d, const char *const key, if (di == NULL) { return NULL; } - return (const char *)get_tv_string_buf(&di->di_tv, (char_u *)numbuf); + return tv_get_string_buf(&di->di_tv, numbuf); } /// Get a function from a dictionary @@ -1948,8 +1948,8 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, case VAR_STRING: { char buf1[NUMBUFLEN]; char buf2[NUMBUFLEN]; - const char *s1 = (const char *)get_tv_string_buf(tv1, (char_u *)buf1); - const char *s2 = (const char *)get_tv_string_buf(tv2, (char_u *)buf2); + const char *s1 = tv_get_string_buf(tv1, buf1); + const char *s2 = tv_get_string_buf(tv2, buf2); return mb_strcmp_ic((bool)ic, s1, s2) == 0; } case VAR_SPECIAL: { @@ -2021,7 +2021,7 @@ bool tv_check_str_or_nr(const typval_T *const tv) /// /// @warning For number and special values it uses a single, static buffer. It /// may be used only once, next call to get_tv_string may reuse it. Use -/// get_tv_string_buf() if you need to use tv_get_string() output after +/// tv_get_string_buf() if you need to use tv_get_string() output after /// calling it again. /// /// @note get_tv_string_chk() and get_tv_string_buf_chk() are similar, but @@ -2035,6 +2035,30 @@ bool tv_check_str_or_nr(const typval_T *const tv) const char *tv_get_string(const typval_T *const varp) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT { - static char_u mybuf[NUMBUFLEN]; - return (const char *)get_tv_string_buf((typval_T *)varp, mybuf); + static char mybuf[NUMBUFLEN]; + return tv_get_string_buf((typval_T *)varp, mybuf); +} + +/// Get the string value of a variable +/// +/// @note get_tv_string_chk() and get_tv_string_buf_chk() are similar, but +/// return NULL on error. +/// +/// @param[in] varp Varible to get value of. +/// @param buf Buffer used to hold numbers and special variables converted to +/// string. When function encounters one of these stringified value +/// will be written to buf and buf will be returned. +/// +/// Buffer must have NUMBUFLEN size. +/// +/// @return Variable value if it is VAR_STRING variable, number converted to +/// a string for VAR_NUMBER, v: variable name for VAR_SPECIAL or empty +/// string. +const char *tv_get_string_buf(const typval_T *const varp, char *const buf) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT +{ + const char *const res = (const char *)get_tv_string_buf_chk( + (typval_T *)varp, (char_u *)buf); + + return res != NULL ? res : ""; } -- cgit From 7ee5cc7429017a4a08a8b62b628bea156fea0008 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 28 Aug 2016 08:09:29 +0300 Subject: eval: Move get_tv_lnum and get_tv_float to eval/typval.h Additionally - Rename former tv_get_float to tv_get_float_chk due to name conflict (former get_tv_float is better suited for being tv_get_float). - Add E907 error to get_tv_float() and test that it is being raised when appropriate. --- src/nvim/eval/typval.c | 94 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 14 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 2c2d0ecaab..6a16b3869a 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -16,6 +16,7 @@ #include "nvim/hashtab.h" #include "nvim/vim.h" #include "nvim/ascii.h" +#include "nvim/pos.h" // TODO(ZyX-I): Move line_breakcheck out of misc1 #include "nvim/misc1.h" // For line_breakcheck @@ -1730,11 +1731,11 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, /// Free memory for a variable value and set the value to NULL or 0 /// -/// @param[in,out] varp Value to free. -void tv_clear(typval_T *varp) +/// @param[in,out] tv Value to free. +void tv_clear(typval_T *tv) { - if (varp != NULL && varp->v_type != VAR_UNKNOWN) { - const int evn_ret = encode_vim_to_nothing(varp, varp, "tv_clear argument"); + if (tv != NULL && tv->v_type != VAR_UNKNOWN) { + const int evn_ret = encode_vim_to_nothing(NULL, tv, "tv_clear argument"); (void)evn_ret; assert(evn_ret == OK); } @@ -2017,7 +2018,72 @@ bool tv_check_str_or_nr(const typval_T *const tv) //{{{2 Get -/// Get the string value of a variable +/// Get the line number from VimL object +/// +/// @param[in] tv Object to get value from. Is expected to be a number or +/// a special string like ".", "$", … (works with current buffer +/// only). +/// +/// @return Line number or -1 or 0. +linenr_T tv_get_lnum(const typval_T *const tv) +{ + linenr_T lnum = get_tv_number_chk(tv, NULL); + if (lnum == 0) { // No valid number, try using same function as line() does. + int fnum; + pos_T *const fp = var2fpos(tv, true, &fnum); + if (fp != NULL) { + lnum = fp->lnum; + } + } + return lnum; +} + +/// Get the floating-point value of a VimL object +/// +/// Raises an error if object is not number or floating-point. +/// +/// @param[in] tv Object to get value of. +/// +/// @return Floating-point value of the variable or zero. +float_T tv_get_float(const typval_T *const tv) +{ + switch (tv->v_type) { + case VAR_NUMBER: { + return (float_T)(tv->vval.v_number); + } + case VAR_FLOAT: { + return tv->vval.v_float; + } + case VAR_PARTIAL: + case VAR_FUNC: { + EMSG(_("E891: Using a Funcref as a Float")); + break; + } + case VAR_STRING: { + EMSG(_("E892: Using a String as a Float")); + break; + } + case VAR_LIST: { + EMSG(_("E893: Using a List as a Float")); + break; + } + case VAR_DICT: { + EMSG(_("E894: Using a Dictionary as a Float")); + break; + } + case VAR_SPECIAL: { + EMSG(_("E907: Using a special value as a Float")); + break; + } + case VAR_UNKNOWN: { + EMSG2(_(e_intern2), "get_tv_float()"); + break; + } + } + return 0; +} + +/// Get the string value of a VimL object /// /// @warning For number and special values it uses a single, static buffer. It /// may be used only once, next call to get_tv_string may reuse it. Use @@ -2027,38 +2093,38 @@ bool tv_check_str_or_nr(const typval_T *const tv) /// @note get_tv_string_chk() and get_tv_string_buf_chk() are similar, but /// return NULL on error. /// -/// @param[in] varp Varible to get value of. +/// @param[in] tv Object to get value of. /// -/// @return Variable value if it is VAR_STRING variable, number converted to +/// @return Object value if it is VAR_STRING object, number converted to /// a string for VAR_NUMBER, v: variable name for VAR_SPECIAL or empty /// string. -const char *tv_get_string(const typval_T *const varp) +const char *tv_get_string(const typval_T *const tv) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT { static char mybuf[NUMBUFLEN]; - return tv_get_string_buf((typval_T *)varp, mybuf); + return tv_get_string_buf((typval_T *)tv, mybuf); } -/// Get the string value of a variable +/// Get the string value of a VimL object /// /// @note get_tv_string_chk() and get_tv_string_buf_chk() are similar, but /// return NULL on error. /// -/// @param[in] varp Varible to get value of. +/// @param[in] tv Object to get value of. /// @param buf Buffer used to hold numbers and special variables converted to /// string. When function encounters one of these stringified value /// will be written to buf and buf will be returned. /// /// Buffer must have NUMBUFLEN size. /// -/// @return Variable value if it is VAR_STRING variable, number converted to +/// @return Object value if it is VAR_STRING object, number converted to /// a string for VAR_NUMBER, v: variable name for VAR_SPECIAL or empty /// string. -const char *tv_get_string_buf(const typval_T *const varp, char *const buf) +const char *tv_get_string_buf(const typval_T *const tv, char *const buf) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT { const char *const res = (const char *)get_tv_string_buf_chk( - (typval_T *)varp, (char_u *)buf); + (typval_T *)tv, (char_u *)buf); return res != NULL ? res : ""; } -- cgit From 1b3e13da5be48739ac4291208e421b68e607cc0f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 28 Aug 2016 08:18:29 +0300 Subject: eval: Refactor get_tv_lnum_buf --- src/nvim/eval/typval.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 6a16b3869a..38032762dc 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2026,6 +2026,7 @@ bool tv_check_str_or_nr(const typval_T *const tv) /// /// @return Line number or -1 or 0. linenr_T tv_get_lnum(const typval_T *const tv) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { linenr_T lnum = get_tv_number_chk(tv, NULL); if (lnum == 0) { // No valid number, try using same function as line() does. @@ -2046,6 +2047,7 @@ linenr_T tv_get_lnum(const typval_T *const tv) /// /// @return Floating-point value of the variable or zero. float_T tv_get_float(const typval_T *const tv) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { switch (tv->v_type) { case VAR_NUMBER: { -- cgit From 233b0c93bba66492d7b8b61f8ac61082f03668a1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 28 Aug 2016 08:55:58 +0300 Subject: eval: Move get_tv_number[_chk] to eval/typval.c --- src/nvim/eval/typval.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 5 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 38032762dc..087e76de10 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -17,6 +17,7 @@ #include "nvim/vim.h" #include "nvim/ascii.h" #include "nvim/pos.h" +#include "nvim/charset.h" // TODO(ZyX-I): Move line_breakcheck out of misc1 #include "nvim/misc1.h" // For line_breakcheck @@ -725,7 +726,7 @@ varnumber_T tv_list_find_nr(list_T *const l, const int n, bool *ret_error) } return -1; } - return get_tv_number_chk(&li->li_tv, ret_error); + return tv_get_number_chk(&li->li_tv, ret_error); } /// Get list item l[n - 1] as a string @@ -1087,7 +1088,7 @@ varnumber_T tv_dict_get_number(dict_T *const d, const char *const key) if (di == NULL) { return 0; } - return get_tv_number(&di->di_tv); + return tv_get_number(&di->di_tv); } /// Get a string item from a dictionary @@ -1971,7 +1972,7 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, /// Check that given value is a number or string /// -/// Error messages are compatible with get_tv_number() previously used for the +/// Error messages are compatible with tv_get_number() previously used for the /// same purpose in buf*() functions. Special values are not accepted (previous /// behaviour: silently fail to find buffer). /// @@ -2016,8 +2017,127 @@ bool tv_check_str_or_nr(const typval_T *const tv) return false; } +#define FUNC_ERROR "E703: Using a Funcref as a Number" + +static const char *const num_errors[] = { + [VAR_PARTIAL]=N_(FUNC_ERROR), + [VAR_FUNC]=N_(FUNC_ERROR), + [VAR_LIST]=N_("E745: Using a List as a Number"), + [VAR_DICT]=N_("E728: Using a Dictionary as a Number"), + [VAR_FLOAT]=N_("E805: Using a Float as a Number"), + [VAR_UNKNOWN]=N_("E685: using an invalid value as a Number"), +}; + +#undef FUNC_ERROR + +/// Check that given value is a number or can be converted to it +/// +/// Error messages are compatible with tv_get_number() previously used for +/// the same purpose. +/// +/// @param[in] tv Value to check. +/// +/// @return true if everything is OK, false otherwise. +bool tv_check_num(const typval_T *const tv) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + switch (tv->v_type) { + case VAR_NUMBER: + case VAR_SPECIAL: + case VAR_STRING: { + return true; + } + case VAR_FUNC: + case VAR_PARTIAL: + case VAR_LIST: + case VAR_DICT: + case VAR_FLOAT: + case VAR_UNKNOWN: { + EMSG(_(num_errors[tv->v_type])); + return false; + } + } + assert(false); + return false; +} + //{{{2 Get +/// Get the number value of a VimL object +/// +/// @note Use tv_get_number_chk() if you need to determine whether there was an +/// error. +/// +/// @param[in] tv Object to get value from. +/// +/// @return Number value: vim_str2nr() output for VAR_STRING objects, value +/// for VAR_NUMBER objects, -1 for other types. +varnumber_T tv_get_number(const typval_T *const tv) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + bool error = false; + return tv_get_number_chk(tv, &error); +} + +/// Get the number value of a VimL object +/// +/// @param[in] tv Object to get value from. +/// @param[out] ret_error If type error occurred then `true` will be written +/// to this location. Otherwise it is not touched. +/// +/// @note Needs to be initialized to `false` to be +/// useful. +/// +/// @return Number value: vim_str2nr() output for VAR_STRING objects, value +/// for VAR_NUMBER objects, -1 (ret_error == NULL) or 0 (otherwise) for +/// other types. +varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1) +{ + switch (tv->v_type) { + case VAR_FUNC: + case VAR_PARTIAL: + case VAR_LIST: + case VAR_DICT: + case VAR_FLOAT: { + EMSG(_(num_errors[tv->v_type])); + break; + } + case VAR_NUMBER: { + return tv->vval.v_number; + } + case VAR_STRING: { + varnumber_T n = 0; + if (tv->vval.v_string != NULL) { + long nr; + vim_str2nr(tv->vval.v_string, NULL, NULL, STR2NR_ALL, &nr, NULL, 0); + n = (varnumber_T)nr; + } + return n; + } + case VAR_SPECIAL: { + switch (tv->vval.v_special) { + case kSpecialVarTrue: { + return 1; + } + case kSpecialVarFalse: + case kSpecialVarNull: { + return 0; + } + } + break; + } + case VAR_UNKNOWN: { + EMSG2(_(e_intern2), "tv_get_number(UNKNOWN)"); + break; + } + } + if (ret_error != NULL) { + *ret_error = true; + } + return (ret_error == NULL ? -1 : 0); +} + /// Get the line number from VimL object /// /// @param[in] tv Object to get value from. Is expected to be a number or @@ -2028,7 +2148,7 @@ bool tv_check_str_or_nr(const typval_T *const tv) linenr_T tv_get_lnum(const typval_T *const tv) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - linenr_T lnum = get_tv_number_chk(tv, NULL); + linenr_T lnum = tv_get_number_chk(tv, NULL); if (lnum == 0) { // No valid number, try using same function as line() does. int fnum; pos_T *const fp = var2fpos(tv, true, &fnum); @@ -2078,7 +2198,7 @@ float_T tv_get_float(const typval_T *const tv) break; } case VAR_UNKNOWN: { - EMSG2(_(e_intern2), "get_tv_float()"); + EMSG2(_(e_intern2), "get_tv_float(UNKNOWN)"); break; } } -- cgit From 50ebd1dff5c4e995c4f7e7980870e43d9defabc6 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 28 Aug 2016 09:15:28 +0300 Subject: eval: Move free_tv to eval/typval.h, remove most of its usages --- src/nvim/eval/typval.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 087e76de10..ca635dcae9 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1742,6 +1742,46 @@ void tv_clear(typval_T *tv) } } +//{{{3 Free + +/// Free allocated VimL object and value stored inside +/// +/// @param tv Object to free. +void tv_free(typval_T *tv) +{ + if (tv != NULL) { + switch (tv->v_type) { + case VAR_PARTIAL: { + partial_unref(tv->vval.v_partial); + break; + } + case VAR_FUNC: { + func_unref(tv->vval.v_string); + // FALLTHROUGH + } + case VAR_STRING: { + xfree(tv->vval.v_string); + break; + } + case VAR_LIST: { + tv_list_unref(tv->vval.v_list); + break; + } + case VAR_DICT: { + tv_dict_unref(tv->vval.v_dict); + break; + } + case VAR_SPECIAL: + case VAR_NUMBER: + case VAR_FLOAT: + case VAR_UNKNOWN: { + break; + } + } + xfree(tv); + } +} + //{{{2 Locks /// Lock or unlock an item -- cgit From c8e63a8db84e9d9f7bd855085a87d93631504fc7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 4 Sep 2016 02:25:24 +0300 Subject: eval: Move remaining get_tv_string* functions to eval/typval.c --- src/nvim/eval/typval.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 111 insertions(+), 7 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ca635dcae9..78eca15fec 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -796,7 +796,7 @@ static bool tv_dict_watcher_matches(DictWatcher *watcher, const char *const key) // of the string means it should match everything up to the '*' instead of the // whole string. const size_t len = strlen(watcher->key_pattern); - if (watcher->key_pattern[len - 1] == '*') { + if (len && watcher->key_pattern[len - 1] == '*') { return strncmp(key, watcher->key_pattern, len - 1) == 0; } else { return strcmp(key, watcher->key_pattern) == 0; @@ -2020,7 +2020,7 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, /// /// @return true if everything is OK, false otherwise. bool tv_check_str_or_nr(const typval_T *const tv) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { switch (tv->v_type) { case VAR_NUMBER: @@ -2072,7 +2072,7 @@ static const char *const num_errors[] = { /// Check that given value is a number or can be converted to it /// -/// Error messages are compatible with tv_get_number() previously used for +/// Error messages are compatible with tv_get_number_chk() previously used for /// the same purpose. /// /// @param[in] tv Value to check. @@ -2101,6 +2101,50 @@ bool tv_check_num(const typval_T *const tv) return false; } +#define FUNC_ERROR "E729: using Funcref as a String" + +static const char *const str_errors[] = { + [VAR_PARTIAL]=N_(FUNC_ERROR), + [VAR_FUNC]=N_(FUNC_ERROR), + [VAR_LIST]=N_("E730: using List as a String"), + [VAR_DICT]=N_("E731: using Dictionary as a String"), + [VAR_FLOAT]=((const char *)e_float_as_string), + [VAR_UNKNOWN]=N_("E908: using an invalid value as a String"), +}; + +#undef FUNC_ERROR + +/// Check that given value is a string or can be converted to it +/// +/// Error messages are compatible with tv_get_string_chk() previously used for +/// the same purpose. +/// +/// @param[in] tv Value to check. +/// +/// @return true if everything is OK, false otherwise. +bool tv_check_str(const typval_T *const tv) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + switch (tv->v_type) { + case VAR_NUMBER: + case VAR_SPECIAL: + case VAR_STRING: { + return true; + } + case VAR_PARTIAL: + case VAR_FUNC: + case VAR_LIST: + case VAR_DICT: + case VAR_FLOAT: + case VAR_UNKNOWN: { + EMSG(_(str_errors[tv->v_type])); + return false; + } + } + assert(false); + return false; +} + //{{{2 Get /// Get the number value of a VimL object @@ -2245,6 +2289,67 @@ float_T tv_get_float(const typval_T *const tv) return 0; } +/// Get the string value of a VimL object +/// +/// @param[in] tv Object to get value of. +/// @param buf Buffer used to hold numbers and special variables converted to +/// string. When function encounters one of these stringified value +/// will be written to buf and buf will be returned. +/// +/// Buffer must have NUMBUFLEN size. +/// +/// @return Object value if it is VAR_STRING object, number converted to +/// a string for VAR_NUMBER, v: variable name for VAR_SPECIAL or NULL. +const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + switch (tv->v_type) { + case VAR_NUMBER: { + snprintf(buf, NUMBUFLEN, "%" PRIdVARNUMBER, tv->vval.v_number); + return buf; + } + case VAR_STRING: { + if (tv->vval.v_string != NULL) { + return (const char *)tv->vval.v_string; + } + return ""; + } + case VAR_SPECIAL: { + STRCPY(buf, encode_special_var_names[tv->vval.v_special]); + return buf; + } + case VAR_PARTIAL: + case VAR_FUNC: + case VAR_LIST: + case VAR_DICT: + case VAR_FLOAT: + case VAR_UNKNOWN: { + EMSG(_(str_errors[tv->v_type])); + return false; + } + } + return NULL; +} + +/// Get the string value of a VimL object +/// +/// @warning For number and special values it uses a single, static buffer. It +/// may be used only once, next call to get_tv_string may reuse it. Use +/// tv_get_string_buf() if you need to use tv_get_string() output after +/// calling it again. +/// +/// @param[in] tv Object to get value of. +/// +/// @return Object value if it is VAR_STRING object, number converted to +/// a string for VAR_NUMBER, v: variable name for VAR_SPECIAL or NULL. +const char *tv_get_string_chk(const typval_T *const tv) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + static char mybuf[NUMBUFLEN]; + + return tv_get_string_buf_chk(tv, mybuf); +} + /// Get the string value of a VimL object /// /// @warning For number and special values it uses a single, static buffer. It @@ -2252,7 +2357,7 @@ float_T tv_get_float(const typval_T *const tv) /// tv_get_string_buf() if you need to use tv_get_string() output after /// calling it again. /// -/// @note get_tv_string_chk() and get_tv_string_buf_chk() are similar, but +/// @note tv_get_string_chk() and tv_get_string_buf_chk() are similar, but /// return NULL on error. /// /// @param[in] tv Object to get value of. @@ -2269,7 +2374,7 @@ const char *tv_get_string(const typval_T *const tv) /// Get the string value of a VimL object /// -/// @note get_tv_string_chk() and get_tv_string_buf_chk() are similar, but +/// @note tv_get_string_chk() and tv_get_string_buf_chk() are similar, but /// return NULL on error. /// /// @param[in] tv Object to get value of. @@ -2285,8 +2390,7 @@ const char *tv_get_string(const typval_T *const tv) const char *tv_get_string_buf(const typval_T *const tv, char *const buf) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT { - const char *const res = (const char *)get_tv_string_buf_chk( - (typval_T *)tv, (char_u *)buf); + const char *const res = (const char *)tv_get_string_buf_chk(tv, buf); return res != NULL ? res : ""; } -- cgit From 2ad4fba46db26d0724106b194f7cb628fb9b02e8 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 11 Sep 2016 03:37:03 +0300 Subject: eval: Move copy_tv to eval/typval --- src/nvim/eval/typval.c | 86 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 13 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 78eca15fec..8a407a4513 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1,6 +1,8 @@ +#include #include -#include +#include #include +#include #include "nvim/lib/queue.h" #include "nvim/eval/typval.h" @@ -276,7 +278,7 @@ void tv_list_insert(list_T *const l, listitem_T *const ni, /// Insert VimL value into a list /// /// @param[out] l List to insert to. -/// @param[in,out] tv Value to insert. Is copied (@see copy_tv()) to an +/// @param[in,out] tv Value to insert. Is copied (@see tv_copy()) to an /// allocated listitem_T and inserted. /// @param[in] item Item to insert before. If NULL, inserts at the end of the /// list. @@ -285,7 +287,7 @@ void tv_list_insert_tv(list_T *const l, typval_T *const tv, { listitem_T *const ni = tv_list_item_alloc(); - copy_tv(tv, &ni->li_tv); + tv_copy(tv, &ni->li_tv); tv_list_insert(l, ni, item); } @@ -313,13 +315,13 @@ void tv_list_append(list_T *const l, listitem_T *const item) /// Append VimL value to the end of list /// /// @param[out] l List to append to. -/// @param[in,out] tv Value to append. Is copied (@see copy_tv()) to an +/// @param[in,out] tv Value to append. Is copied (@see tv_copy()) to an /// allocated listitem_T. 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(); - copy_tv(tv, &li->li_tv); + tv_copy(tv, &li->li_tv); tv_list_append(l, li); } @@ -447,7 +449,7 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig, break; } } else { - copy_tv(&item->li_tv, &ni->li_tv); + tv_copy(&item->li_tv, &ni->li_tv); } tv_list_append(copy, ni); } @@ -828,13 +830,13 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key, if (newtv) { dictitem_T *const v = tv_dict_item_alloc_len(S_LEN("new")); - copy_tv(newtv, &v->di_tv); + tv_copy(newtv, &v->di_tv); tv_dict_add(argv[2].vval.v_dict, v); } if (oldtv) { dictitem_T *const v = tv_dict_item_alloc_len(S_LEN("old")); - copy_tv(oldtv, &v->di_tv); + tv_copy(oldtv, &v->di_tv); tv_dict_add(argv[2].vval.v_dict, v); } @@ -926,7 +928,7 @@ static dictitem_T *tv_dict_item_copy(dictitem_T *const di) FUNC_ATTR_MALLOC { dictitem_T *const new_di = tv_dict_item_alloc((const char *)di->di_key); - copy_tv(&di->di_tv, &new_di->di_tv); + tv_copy(&di->di_tv, &new_di->di_tv); return new_di; } @@ -1161,7 +1163,7 @@ bool tv_dict_get_callback(dict_T *const d, } typval_T tv; - copy_tv(&di->di_tv, &tv); + tv_copy(&di->di_tv, &tv); set_selfdict(&tv, d); bool res = callback_from_typval(result, &tv); tv_clear(&tv); @@ -1338,11 +1340,11 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, } if (watched) { - copy_tv(&di1->di_tv, &oldtv); + tv_copy(&di1->di_tv, &oldtv); } tv_clear(&di1->di_tv); - copy_tv(&di2->di_tv, &di1->di_tv); + tv_copy(&di2->di_tv, &di1->di_tv); if (watched) { tv_dict_watcher_notify(d1, (const char *)di1->di_key, &di1->di_tv, @@ -1433,7 +1435,7 @@ dict_T *tv_dict_copy(const vimconv_T *const conv, break; } } else { - copy_tv(&di->di_tv, &new_di->di_tv); + tv_copy(&di->di_tv, &new_di->di_tv); } if (tv_dict_add(copy, new_di) == FAIL) { tv_dict_item_free(new_di); @@ -1782,6 +1784,64 @@ void tv_free(typval_T *tv) } } +//{{{3 Copy + +/// Copy typval from one location to another +/// +/// When needed allocates string or increases reference count. Does not make +/// a copy of a container, but copies its reference! +/// +/// It is OK for `from` and `to` to point to the same location; this is used to +/// make a copy later. +/// +/// @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) +{ + to->v_type = from->v_type; + to->v_lock = VAR_UNLOCKED; + memmove(&to->vval, &from->vval, sizeof(to->vval)); + switch (from->v_type) { + case VAR_NUMBER: + case VAR_FLOAT: + case VAR_SPECIAL: { + break; + } + case VAR_STRING: + case VAR_FUNC: { + if (from->vval.v_string != NULL) { + to->vval.v_string = vim_strsave(from->vval.v_string); + if (from->v_type == VAR_FUNC) { + func_ref(to->vval.v_string); + } + } + break; + } + case VAR_PARTIAL: { + if (to->vval.v_partial != NULL) { + to->vval.v_partial->pt_refcount++; + } + break; + } + case VAR_LIST: { + if (from->vval.v_list != NULL) { + to->vval.v_list->lv_refcount++; + } + break; + } + case VAR_DICT: { + if (from->vval.v_dict != NULL) { + to->vval.v_dict->dv_refcount++; + } + break; + } + case VAR_UNKNOWN: { + EMSG2(_(e_intern2), "tv_copy(UNKNOWN)"); + break; + } + } +} + //{{{2 Locks /// Lock or unlock an item -- cgit From a32db8ed19c35847373d4a7fd56d7797a5a26897 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 11 Sep 2016 03:54:13 +0300 Subject: eval/typval: Add missing includes, also add a script to find them Contains unfinished attempt to integrate IWYU (ref #549). To finish it different job should be done, specifically: - Instead of feeding IWYU with modified file a mirror source tree should be created with the help of CMake which will contain modified sources. This solves the problem with IWYU thinking that `*.generated.h` headers should be included in place of `*` headers. - Build IWYU as all other third-party utilities. - Make modified sources avoid problems with `nvim/func_attr.h` includes and various related tricks. Current script may only be used for manual checks like this: ./scripts/check-includes.py \ --generated-includes-dir build/include \ --generated-includes-dir build/src/nvim/auto \ --file src/nvim/eval/typval.c \ -- -Isrc -Ibuild/include -Ibuild/src/nvim/auto \ -DINCLUDE_GENERATED_DECLARATIONS (it is also somewhat fine with `--file src/nvim/eval/typval.h`). I have no idea why (I mean, why developer think that these lines are needed, why they are suggested is pretty obvious: because there is typedef which mentions them before structs are defined), but for typval.h it reports, among other things, that it should add lines struct dictvar_S; struct listitem_S; struct listvar_S; struct listwatch_S; --- src/nvim/eval/typval.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 8a407a4513..4777e00a76 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -20,6 +20,11 @@ #include "nvim/ascii.h" #include "nvim/pos.h" #include "nvim/charset.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/message.h" // TODO(ZyX-I): Move line_breakcheck out of misc1 #include "nvim/misc1.h" // For line_breakcheck -- cgit From a394167177390a66c275d32e3862c82c546970ff Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Mar 2017 13:41:11 +0300 Subject: unittests: Test tv_list_item_\* functions To check that memory is free()d correctly. --- src/nvim/eval/typval.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 4777e00a76..a26afb20c6 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -228,6 +228,7 @@ void tv_list_unref(list_T *const l) /// @param[in] item2 Last item to remove. void tv_list_remove_items(list_T *const l, listitem_T *const item, listitem_T *const item2) + FUNC_ATTR_NONNULL_ALL { // notify watchers for (listitem_T *ip = item; ip != NULL; ip = ip->li_next) { -- cgit From 9898f36aa306d2a7f53fbe08c64048260992d3bb Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 17 Sep 2016 22:06:11 +0300 Subject: unittests: Test tv_list_copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also found some bugs: 1. var_item_copy() always fails to copy v:_null_list and v:_null_dict. Fixing this should mean fixing `deepcopy(v:_null_list)` which should’ve been, but was not listed in #4615. This also fixes `deepcopy(v:_null_dict)`. 2. var_item_copy() crashes when trying to copy NULL string with `conv != NULL`. 3. `conv` argument is ignored when copying list unless `deep` is true, but it was not reflected in documentation. 4. `tv_dict_item_alloc_len()` allocated more memory then needed. 5. typvalt2lua was not able to handle self-referencing containers. --- src/nvim/eval/typval.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index a26afb20c6..ae4199697f 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -424,6 +424,7 @@ void tv_list_append_number(list_T *const l, const varnumber_T n) /// Make a copy of list /// /// @param[in] conv If non-NULL, then all internal strings will be converted. +/// Only used when `deep` is true. /// @param[in] orig Original list to copy. /// @param[in] deep If false, then shallow copy will be done. /// @param[in] copyID See var_item_copy(). -- cgit From f80a00469fdba8a3dec0edae30c911d050485055 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 18 Sep 2016 01:59:07 +0300 Subject: eval/typval: Make tv_list_concat handle NULL lists correctly Fixes some FIXMEs in eval/null_spec.lua. --- src/nvim/eval/typval.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ae4199697f..9dd1b62d7d 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -498,20 +498,25 @@ void tv_list_extend(list_T *const l1, list_T *const l2, int tv_list_concat(list_T *const l1, list_T *const l2, typval_T *const tv) FUNC_ATTR_WARN_UNUSED_RESULT { - if (l1 == NULL || l2 == NULL) { - return FAIL; - } + list_T *l; - // make a copy of the first list. - list_T *const l = tv_list_copy(NULL, l1, false, 0); - if (l == NULL) { + tv->v_type = VAR_LIST; + + if (l1 == NULL && l2 == NULL) { + l = NULL; + } else if (l1 == NULL) { + l = tv_list_copy(NULL, l2, false, 0); + } else { + l = tv_list_copy(NULL, l1, false, 0); + if (l != NULL && l2 != NULL) { + tv_list_extend(l, l2, NULL); + } + } + if (l == NULL && !(l1 == NULL && l2 == NULL)) { return FAIL; } - tv->v_type = VAR_LIST; - tv->vval.v_list = l; - // append all items from the second list - tv_list_extend(l, l2, NULL); + tv->vval.v_list = l; return OK; } -- cgit From 56e4c2f67e54b88f637d30e565313bc6b2c22f29 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 19 Sep 2016 00:50:07 +0300 Subject: unittests: Test tv_list_concat() --- src/nvim/eval/typval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 9dd1b62d7d..1cc195ddc4 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -481,7 +481,7 @@ void tv_list_extend(list_T *const l1, list_T *const l2, int todo = l2->lv_len; // 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 + for (listitem_T *item = l2->lv_first ; item != NULL && --todo >= 0 ; item = item->li_next) { tv_list_insert_tv(l1, &item->li_tv, bef); -- cgit From 7ceebacb3fad49ba8321397cf839948caa55b3f5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 24 Sep 2016 00:51:34 +0300 Subject: eval/typval,tests: Fix extending list with itself, add tests Adds unit test for tv_list_extend and regression test for extend() VimL function. --- src/nvim/eval/typval.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 1cc195ddc4..746cbbfe1c 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -479,11 +479,13 @@ void tv_list_extend(list_T *const l1, list_T *const l2, FUNC_ATTR_NONNULL_ARG(1, 2) { int todo = l2->lv_len; + 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 - ; item = item->li_next) { + ; item = (item == befbef ? saved_next : item->li_next)) { tv_list_insert_tv(l1, &item->li_tv, bef); } } -- cgit From b3672ae2fced4715963442d2e19048f8fadbe0b8 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 2 Oct 2016 04:34:30 +0300 Subject: eval/typval: Add tv_list_equal() tests, compare NULL lists equal --- src/nvim/eval/typval.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 746cbbfe1c..b0b95e955f 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -623,13 +623,12 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, const bool recursive) FUNC_ATTR_WARN_UNUSED_RESULT { - if (l1 == NULL || l2 == NULL) { - // FIXME? compare empty list with NULL list equal - return false; - } if (l1 == l2) { return true; } + if (l1 == NULL || l2 == NULL) { + return false; + } if (tv_list_len(l1) != tv_list_len(l2)) { return false; } -- cgit From e5edf07ec44f8d147d7482cae2997be62c30373f Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 4 Nov 2016 19:33:36 +0300 Subject: unittests: Add tests for tv_list_find*() functions Additional modifications: - More `const` qualifiers in tested functions. - `tv_list_find_str()` second argument is more in-line with other `tv_list_find*()` functions. --- src/nvim/eval/typval.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index b0b95e955f..ef64467ee5 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -731,7 +731,7 @@ listitem_T *tv_list_find(list_T *const l, int n) /// `*ret_error` is not touched. /// /// @return Integer value at the given index or -1. -varnumber_T tv_list_find_nr(list_T *const l, const int n, bool *ret_error) +varnumber_T tv_list_find_nr(list_T *const l, const int n, bool *const ret_error) FUNC_ATTR_WARN_UNUSED_RESULT { const listitem_T *const li = tv_list_find(l, n); @@ -744,16 +744,16 @@ varnumber_T tv_list_find_nr(list_T *const l, const int n, bool *ret_error) return tv_get_number_chk(&li->li_tv, ret_error); } -/// Get list item l[n - 1] as a string +/// Get list item l[n] as a string /// /// @param[in] l List to index. /// @param[in] n Index in a list. /// -/// @return [allocated] Copy of the list item string value. -const char *tv_list_find_str(list_T *l, int n) - FUNC_ATTR_MALLOC +/// @return List item string value or NULL in case of error. +const char *tv_list_find_str(list_T *const l, const int n) + FUNC_ATTR_WARN_UNUSED_RESULT { - const listitem_T *const li = tv_list_find(l, n - 1); + const listitem_T *const li = tv_list_find(l, n); if (li == NULL) { EMSGN(_(e_listidx), n); return NULL; -- cgit From 56e51033abf00d66e9c6f9412e8f57c9a24b86ae Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 5 Nov 2016 00:07:34 +0300 Subject: unittests: Add tests for tv_list_idx_of_item --- src/nvim/eval/typval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ef64467ee5..4adc31d10a 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -774,7 +774,7 @@ long tv_list_idx_of_item(const list_T *const l, const listitem_T *const item) return -1; } long idx = 0; - listitem_T *li; + const listitem_T *li; for (li = l->lv_first; li != NULL && li != item; li = li->li_next) { idx++; } -- cgit From 9ed9af7e11e3a707f65abfeb1d02b029e39241ea Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 5 Nov 2016 00:26:33 +0300 Subject: eval/typval: More `const` qualifiers in `tv_dict*` function signatures --- src/nvim/eval/typval.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 4adc31d10a..57025250d4 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1096,7 +1096,7 @@ dictitem_T *tv_dict_find(const dict_T *const d, const char *const key, /// @param[in] key Key to find in dictionary. /// /// @return Dictionary item. -varnumber_T tv_dict_get_number(dict_T *const d, const char *const key) +varnumber_T tv_dict_get_number(const dict_T *const d, const char *const key) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { dictitem_T *const di = tv_dict_find(d, key, -1); @@ -1116,7 +1116,7 @@ varnumber_T tv_dict_get_number(dict_T *const d, const char *const key) /// @return NULL if key does not exist, empty string in case of type error, /// string item value otherwise. If returned value is not NULL, it may /// be allocated depending on `save` argument. -char *tv_dict_get_string(dict_T *const d, const char *const key, +char *tv_dict_get_string(const dict_T *const d, const char *const key, const bool save) FUNC_ATTR_WARN_UNUSED_RESULT { @@ -1136,11 +1136,11 @@ char *tv_dict_get_string(dict_T *const d, const char *const key, /// /// @return NULL if key does not exist, empty string in case of type error, /// string item value otherwise. -const char *tv_dict_get_string_buf(dict_T *const d, const char *const key, +const char *tv_dict_get_string_buf(const dict_T *const d, const char *const key, char *const numbuf) FUNC_ATTR_WARN_UNUSED_RESULT { - dictitem_T *const di = tv_dict_find(d, key, -1); + const dictitem_T *const di = tv_dict_find(d, key, -1); if (di == NULL) { return NULL; } -- cgit From 1e3e302dc2bdced563ecd2c3fb101af31f72b3df Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 4 Dec 2016 21:58:19 +0300 Subject: eval: Move part of dictwatcher* functions to eval/typval --- src/nvim/eval/typval.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 57025250d4..243e738c50 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -790,7 +790,7 @@ long tv_list_idx_of_item(const list_T *const l, const listitem_T *const item) /// Perform all necessary cleanup for a `DictWatcher` instance /// /// @param watcher Watcher to free. -void tv_dict_watcher_free(DictWatcher *watcher) +static void tv_dict_watcher_free(DictWatcher *watcher) FUNC_ATTR_NONNULL_ALL { callback_free(&watcher->callback); @@ -798,6 +798,91 @@ void tv_dict_watcher_free(DictWatcher *watcher) xfree(watcher); } +/// Add watcher to a dictionary +/// +/// @param[in] dict Dictionary to add watcher to. +/// @param[in] key_pattern Pattern to watch for. +/// @param[in] key_pattern_len Key pattern length. +/// @param callback Function to be called on events. +void tv_dict_watcher_add(dict_T *const dict, const char *const key_pattern, + const size_t key_pattern_len, Callback callback) + FUNC_ATTR_NONNULL_ARG(2) +{ + DictWatcher *const watcher = xmalloc(sizeof(DictWatcher)); + watcher->key_pattern = xmemdupz(key_pattern, key_pattern_len); + watcher->key_pattern_len = key_pattern_len; + watcher->callback = callback; + watcher->busy = false; + QUEUE_INSERT_TAIL(&dict->watchers, &watcher->node); +} + +/// Check whether two callbacks are equal +/// +/// @param[in] cb1 First callback to check. +/// @param[in] cb2 Second callback to check. +/// +/// @return True if they are equal, false otherwise. +static bool tv_callback_equal(const Callback *const cb1, + const Callback *const cb2) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (cb1->type != cb2->type) { + return false; + } + switch (cb1->type) { + case kCallbackFuncref: { + return STRCMP(cb1->data.funcref, cb2->data.funcref) == 0; + } + case kCallbackPartial: { + // FIXME: this is inconsistent with tv_equal but is needed for precision + // maybe change dictwatcheradd to return a watcher id instead? + return cb1->data.partial == cb2->data.partial; + } + case kCallbackNone: { + return true; + } + } +} + +/// Remove watcher from a dictionary +/// +/// @param dict Dictionary to remove watcher from. +/// @param[in] key_pattern Pattern to remove watcher for. +/// @param[in] key_pattern_len Pattern length. +/// @param callback Callback to remove watcher for. +/// +/// @return True on success, false if relevant watcher was not found. +bool tv_dict_watcher_remove(dict_T *const dict, const char *const key_pattern, + const size_t key_pattern_len, + Callback callback) + FUNC_ATTR_NONNULL_ARG(2) +{ + if (dict == NULL) { + return false; + } + + QUEUE *w = NULL; + DictWatcher *watcher = NULL; + bool matched = false; + QUEUE_FOREACH(w, &dict->watchers) { + watcher = tv_dict_watcher_node_data(w); + if (tv_callback_equal(&watcher->callback, &callback) + && watcher->key_pattern_len == key_pattern_len + && memcmp(watcher->key_pattern, key_pattern, key_pattern_len) == 0) { + matched = true; + break; + } + } + + if (!matched) { + return false; + } + + QUEUE_REMOVE(w); + tv_dict_watcher_free(watcher); + return true; +} + /// Test if `key` matches with with `watcher->key_pattern` /// /// @param[in] watcher Watcher to check key pattern from. @@ -810,7 +895,7 @@ static bool tv_dict_watcher_matches(DictWatcher *watcher, const char *const key) // For now only allow very simple globbing in key patterns: a '*' at the end // of the string means it should match everything up to the '*' instead of the // whole string. - const size_t len = strlen(watcher->key_pattern); + const size_t len = watcher->key_pattern_len; if (len && watcher->key_pattern[len - 1] == '*') { return strncmp(key, watcher->key_pattern, len - 1) == 0; } else { -- cgit From 3025431c81daac873e69a71aee695ebfd00504f7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 4 Dec 2016 22:59:25 +0300 Subject: eval: Make sure that v:_null_dict does not crash dictwatcher*() Ref #4615 --- src/nvim/eval/typval.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 243e738c50..8c11f4bd8f 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -808,6 +808,9 @@ void tv_dict_watcher_add(dict_T *const dict, const char *const key_pattern, const size_t key_pattern_len, Callback callback) FUNC_ATTR_NONNULL_ARG(2) { + if (dict == NULL) { + return; + } DictWatcher *const watcher = xmalloc(sizeof(DictWatcher)); watcher->key_pattern = xmemdupz(key_pattern, key_pattern_len); watcher->key_pattern_len = key_pattern_len; -- cgit From 506b938947b7083fc8bef20eabc08ed033298add Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 14 Feb 2017 00:12:57 +0300 Subject: *: Make some more things const and with length --- src/nvim/eval/typval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 8c11f4bd8f..2f3415a59d 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2041,7 +2041,7 @@ bool tv_islocked(const typval_T *const tv) /// /// @param[in] lock Lock status. /// @param[in] name Variable name, used in the error message. -/// @param[in] use_gettext True if variable name also is to be translated. +/// @param[in] name_len Variable name length. /// /// @return true if variable is locked, false otherwise. bool tv_check_lock(const VarLockStatus lock, const char *const name, -- cgit From 78a0de2c1b722d5445dfc44cc0635e5ce4bcd23e Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 4 Mar 2017 23:30:10 +0300 Subject: eval/typval: Fix -Werror=return-type --- src/nvim/eval/typval.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 2f3415a59d..326334f149 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -845,6 +845,8 @@ static bool tv_callback_equal(const Callback *const cb1, return true; } } + assert(false); + return false; } /// Remove watcher from a dictionary -- cgit From 38dd81c136c2edbda66614622a4747bf1785af94 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 5 Mar 2017 02:15:17 +0300 Subject: eval/typval: Fix SEGV in test_alot.vim test --- src/nvim/eval/typval.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 326334f149..8d1ecf8f14 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1167,8 +1167,11 @@ void tv_dict_unref(dict_T *const d) /// @return found item or NULL if nothing was found. dictitem_T *tv_dict_find(const dict_T *const d, const char *const key, const ptrdiff_t len) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { + if (d == NULL) { + return NULL; + } hashitem_T *const hi = (len < 0 ? hash_find(&d->dv_hashtab, (const char_u *)key) : hash_find_len(&d->dv_hashtab, key, (size_t)len)); -- cgit From b222453c95dbe466b79abd578642fa6038770a55 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 12 Mar 2017 21:58:38 +0300 Subject: eval/typval: Refactor errors a bit: use emsgf always --- src/nvim/eval/typval.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 8d1ecf8f14..867330c137 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -755,7 +755,7 @@ const char *tv_list_find_str(list_T *const l, const int n) { const listitem_T *const li = tv_list_find(l, n); if (li == NULL) { - EMSGN(_(e_listidx), n); + emsgf(_(e_listidx), (int64_t)n); return NULL; } return tv_get_string(&li->li_tv); @@ -1264,14 +1264,14 @@ bool tv_dict_get_callback(dict_T *const d, if (di->di_tv.v_type != VAR_FUNC && di->di_tv.v_type != VAR_STRING && di->di_tv.v_type != VAR_PARTIAL) { - EMSG(_("Argument is not a function or function name")); + emsgf(_("E6000: Argument is not a function or function name")); return false; } typval_T tv; tv_copy(&di->di_tv, &tv); set_selfdict(&tv, d); - bool res = callback_from_typval(result, &tv); + const bool res = callback_from_typval(result, &tv); tv_clear(&tv); return res; } @@ -1942,7 +1942,7 @@ void tv_copy(typval_T *const from, typval_T *const to) break; } case VAR_UNKNOWN: { - EMSG2(_(e_intern2), "tv_copy(UNKNOWN)"); + emsgf(_(e_intern2), "tv_copy(UNKNOWN)"); break; } } @@ -2259,7 +2259,7 @@ bool tv_check_num(const typval_T *const tv) case VAR_DICT: case VAR_FLOAT: case VAR_UNKNOWN: { - EMSG(_(num_errors[tv->v_type])); + emsgf(_(num_errors[tv->v_type])); return false; } } @@ -2303,7 +2303,7 @@ bool tv_check_str(const typval_T *const tv) case VAR_DICT: case VAR_FLOAT: case VAR_UNKNOWN: { - EMSG(_(str_errors[tv->v_type])); + emsgf(_(str_errors[tv->v_type])); return false; } } @@ -2350,7 +2350,7 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error) case VAR_LIST: case VAR_DICT: case VAR_FLOAT: { - EMSG(_(num_errors[tv->v_type])); + emsgf(_(num_errors[tv->v_type])); break; } case VAR_NUMBER: { @@ -2378,7 +2378,7 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error) break; } case VAR_UNKNOWN: { - EMSG2(_(e_intern2), "tv_get_number(UNKNOWN)"); + emsgf(_(e_intern2), "tv_get_number(UNKNOWN)"); break; } } @@ -2428,27 +2428,27 @@ float_T tv_get_float(const typval_T *const tv) } case VAR_PARTIAL: case VAR_FUNC: { - EMSG(_("E891: Using a Funcref as a Float")); + emsgf(_("E891: Using a Funcref as a Float")); break; } case VAR_STRING: { - EMSG(_("E892: Using a String as a Float")); + emsgf(_("E892: Using a String as a Float")); break; } case VAR_LIST: { - EMSG(_("E893: Using a List as a Float")); + emsgf(_("E893: Using a List as a Float")); break; } case VAR_DICT: { - EMSG(_("E894: Using a Dictionary as a Float")); + emsgf(_("E894: Using a Dictionary as a Float")); break; } case VAR_SPECIAL: { - EMSG(_("E907: Using a special value as a Float")); + emsgf(_("E907: Using a special value as a Float")); break; } case VAR_UNKNOWN: { - EMSG2(_(e_intern2), "get_tv_float(UNKNOWN)"); + emsgf(_(e_intern2), "get_tv_float(UNKNOWN)"); break; } } @@ -2490,7 +2490,7 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf) case VAR_DICT: case VAR_FLOAT: case VAR_UNKNOWN: { - EMSG(_(str_errors[tv->v_type])); + emsgf(_(str_errors[tv->v_type])); return false; } } -- cgit From 270a3889af024485fa7b63f34c4dd3f92f6e0f98 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 13 Mar 2017 01:45:44 +0300 Subject: unittests: Add tv_dict_add* unit tests Also fixes incorrect location of `tv_dict_add` function and three bugs in other functions: 1. `tv_dict_add_list` may free list it does not own (vim/vim#1555). 2. `tv_dict_add_dict` may free dictionary it does not own (vim/vim#1555). 3. `tv_dict_add_dict` ignores `key_len` argument. --- src/nvim/eval/typval.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 867330c137..d9feb2d88e 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1009,18 +1009,6 @@ void tv_dict_item_free(dictitem_T *const item) } } -/// Add item to dictionary -/// -/// @param[out] d Dictionary to add to. -/// @param[in] item Item to add. -/// -/// @return FAIL if key already exists. -int tv_dict_add(dict_T *const d, dictitem_T *const item) - FUNC_ATTR_NONNULL_ALL -{ - return hash_add(&d->dv_hashtab, item->di_key); -} - /// Make a copy of a dictionary item /// /// @param[in] di Item to copy. @@ -1278,6 +1266,18 @@ bool tv_dict_get_callback(dict_T *const d, //{{{2 dict_add* +/// Add item to dictionary +/// +/// @param[out] d Dictionary to add to. +/// @param[in] item Item to add. +/// +/// @return FAIL if key already exists. +int tv_dict_add(dict_T *const d, dictitem_T *const item) + FUNC_ATTR_NONNULL_ALL +{ + return hash_add(&d->dv_hashtab, item->di_key); +} + /// Add a list entry to dictionary /// /// @param[out] d Dictionary to add entry to. @@ -1295,11 +1295,11 @@ 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++; if (tv_dict_add(d, item) == FAIL) { tv_dict_item_free(item); return FAIL; } - list->lv_refcount++; return OK; } @@ -1313,18 +1313,18 @@ int tv_dict_add_list(dict_T *const d, const char *const key, /// @return OK in case of success, FAIL when key already exists. int tv_dict_add_dict(dict_T *const d, const char *const key, const size_t key_len, dict_T *const dict) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ALL { - dictitem_T *const item = tv_dict_item_alloc(key); + 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_DICT; item->di_tv.vval.v_dict = dict; + dict->dv_refcount++; if (tv_dict_add(d, item) == FAIL) { tv_dict_item_free(item); return FAIL; } - dict->dv_refcount++; return OK; } -- cgit From 43e9fad1c8835a3136f4db53c82608e034df2a5e Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 13 Mar 2017 13:43:01 +0300 Subject: eval: Use tv_is_func in place of ==VAR_FUNC||==VAR_PARTIAL Also fixes same error as in vim/vim#1557 --- src/nvim/eval/typval.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index d9feb2d88e..620da0032e 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1250,8 +1250,7 @@ bool tv_dict_get_callback(dict_T *const d, return true; } - if (di->di_tv.v_type != VAR_FUNC && di->di_tv.v_type != VAR_STRING - && di->di_tv.v_type != VAR_PARTIAL) { + if (!tv_is_func(di->di_tv) && di->di_tv.v_type != VAR_STRING) { emsgf(_("E6000: Argument is not a function or function name")); return false; } @@ -1418,7 +1417,7 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, // Disallow replacing a builtin function in l: and g:. // Check the key to be valid when adding to any scope. if (d1->dv_scope == VAR_DEF_SCOPE - && di2->di_tv.v_type == VAR_FUNC + && tv_is_func(di2->di_tv) && !var_check_func_name((const char *)di2->di_key, di1 == NULL)) { break; } @@ -2101,9 +2100,7 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, // TODO(ZyX-I): Make this not recursive static int recursive_cnt = 0; // Catch recursive loops. - if (!((tv1->v_type == VAR_FUNC || tv1->v_type == VAR_PARTIAL) - && (tv2->v_type == VAR_FUNC || tv2->v_type == VAR_PARTIAL)) - && tv1->v_type != tv2->v_type) { + if (!(tv_is_func(*tv1) && tv_is_func(*tv2)) && tv1->v_type != tv2->v_type) { return false; } -- cgit From f0bbd1e825841c55a1f75d66a9caeaa50cc2259c Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 13 Mar 2017 19:35:12 +0300 Subject: unittests: Add tests for tv_clear() --- src/nvim/eval/typval.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 620da0032e..c245b9222e 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1840,10 +1840,11 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, /// Free memory for a variable value and set the value to NULL or 0 /// /// @param[in,out] tv Value to free. -void tv_clear(typval_T *tv) +void tv_clear(typval_T *const tv) { if (tv != NULL && tv->v_type != VAR_UNKNOWN) { - const int evn_ret = encode_vim_to_nothing(NULL, tv, "tv_clear argument"); + const int evn_ret = encode_vim_to_nothing(NULL, tv, + _("tv_clear() argument")); (void)evn_ret; assert(evn_ret == OK); } -- cgit From 630ff33dc144a64b5488b4132c0fc4351a5c84db Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 25 Mar 2017 19:52:20 +0300 Subject: unittests: Test locks section --- src/nvim/eval/typval.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index c245b9222e..185dd0e86c 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1956,6 +1956,7 @@ void tv_copy(typval_T *const from, typval_T *const to) /// @param[in] deep Levels to (un)lock, -1 to (un)lock everything. /// @param[in] lock True if it is needed to lock an item, false to unlock. void tv_item_lock(typval_T *const tv, const int deep, const bool lock) + FUNC_ATTR_NONNULL_ALL { // TODO(ZyX-I): Make this not recursive static int recurse = 0; @@ -2031,13 +2032,13 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) bool tv_islocked(const typval_T *const tv) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - return ((tv->v_lock & VAR_LOCKED) + 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->vval.v_list->lv_lock == VAR_LOCKED)) || (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL - && (tv->vval.v_dict->dv_lock & VAR_LOCKED))); + && (tv->vval.v_dict->dv_lock == VAR_LOCKED))); } /// Return true if typval is locked -- cgit From e08b27ba4acf22fe180c2a4a6b00f0ea0cfc3a79 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 25 Mar 2017 20:46:39 +0300 Subject: unittests: Add tv_get number tests --- src/nvim/eval/typval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 185dd0e86c..48ff3ab623 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2447,7 +2447,7 @@ float_T tv_get_float(const typval_T *const tv) break; } case VAR_UNKNOWN: { - emsgf(_(e_intern2), "get_tv_float(UNKNOWN)"); + emsgf(_(e_intern2), "tv_get_float(UNKNOWN)"); break; } } -- cgit From 58e34e8d99b01bf3937824fc50502e39a8c39eba Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 26 Mar 2017 03:42:23 +0300 Subject: eval/typval: Allow NULL dict as tv_dict_get_callback() argument Also removes NULL key input: tv_dict_find() does not allow this. --- src/nvim/eval/typval.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 48ff3ab623..da58cf5ca9 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -825,8 +825,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. -static bool tv_callback_equal(const Callback *const cb1, - const Callback *const cb2) +bool tv_callback_equal(const Callback *const cb1, const Callback *const cb2) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { if (cb1->type != cb2->type) { @@ -1240,7 +1239,7 @@ const char *tv_dict_get_string_buf(const dict_T *const d, const char *const key, bool tv_dict_get_callback(dict_T *const d, const char *const key, const ptrdiff_t key_len, Callback *const result) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ARG(2, 4) FUNC_ATTR_WARN_UNUSED_RESULT { result->type = kCallbackNone; -- cgit From 114eaa15f009cbd3e3deb177a2a67affa430fbb8 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 28 Mar 2017 01:07:34 +0300 Subject: eval/typval,api/buffer: Fix review comments --- src/nvim/eval/typval.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'src/nvim/eval/typval.c') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index da58cf5ca9..779bb18175 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -164,9 +164,8 @@ void tv_list_free_contents(list_T *const l) } l->lv_len = 0; l->lv_idx_item = NULL; - for (listwatch_T *lw = l->lv_watch; lw != NULL; lw = lw->lw_next) { - lw->lw_item = NULL; - } + l->lv_last = NULL; + assert(l->lv_watch == NULL); } /// Free a list itself, ignoring items it contains @@ -230,13 +229,10 @@ void tv_list_remove_items(list_T *const l, listitem_T *const item, listitem_T *const item2) FUNC_ATTR_NONNULL_ALL { - // notify watchers - for (listitem_T *ip = item; ip != NULL; ip = ip->li_next) { + // Notify watchers. + for (listitem_T *ip = item; ip != item2->li_next; ip = ip->li_next) { l->lv_len--; tv_list_watch_fix(l, ip); - if (ip == item2) { - break; - } } if (item2->li_next == NULL) { -- cgit