diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/clint.py | 7 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 78 | ||||
-rw-r--r-- | src/nvim/eval.c | 240 | ||||
-rw-r--r-- | src/nvim/eval/encode.c | 323 | ||||
-rw-r--r-- | src/nvim/eval/typval_encode.c.h | 802 | ||||
-rw-r--r-- | src/nvim/eval/typval_encode.h | 594 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 4 | ||||
-rw-r--r-- | src/nvim/fileio.c | 123 | ||||
-rw-r--r-- | src/nvim/globals.h | 8 | ||||
-rw-r--r-- | src/nvim/hashtab.c | 12 | ||||
-rw-r--r-- | src/nvim/hashtab.h | 9 | ||||
-rw-r--r-- | src/nvim/memory.c | 49 | ||||
-rw-r--r-- | src/nvim/memory.h | 34 | ||||
-rw-r--r-- | src/nvim/os/time.c | 39 | ||||
-rw-r--r-- | src/nvim/path.c | 47 | ||||
-rw-r--r-- | src/nvim/testdir/test_autocmd.vim | 31 | ||||
-rw-r--r-- | src/nvim/testdir/test_usercommands.vim | 64 | ||||
-rw-r--r-- | src/nvim/version.c | 7 |
18 files changed, 1585 insertions, 886 deletions
diff --git a/src/clint.py b/src/clint.py index 0470f824fa..76982144fb 100755 --- a/src/clint.py +++ b/src/clint.py @@ -3005,9 +3005,10 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): include = match.group(2) is_system = (match.group(1) == '<') if include in include_state: - error(filename, linenum, 'build/include', 4, - '"%s" already included at %s:%s' % - (include, filename, include_state[include])) + if is_system or not include.endswith('.c.h'): + error(filename, linenum, 'build/include', 4, + '"%s" already included at %s:%s' % + (include, filename, include_state[include])) else: include_state[include] = linenum diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index b004cfc7a1..7daa4d7207 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -19,7 +19,6 @@ #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/version.h" -#include "nvim/eval/typval_encode.h" #include "nvim/lib/kvec.h" /// Helper structure for vim_to_object @@ -327,21 +326,21 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) #define TYPVAL_ENCODE_ALLOW_SPECIALS false -#define TYPVAL_ENCODE_CONV_NIL() \ +#define TYPVAL_ENCODE_CONV_NIL(tv) \ kv_push(edata->stack, NIL) -#define TYPVAL_ENCODE_CONV_BOOL(num) \ +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ kv_push(edata->stack, BOOLEAN_OBJ((Boolean)(num))) -#define TYPVAL_ENCODE_CONV_NUMBER(num) \ +#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ kv_push(edata->stack, INTEGER_OBJ((Integer)(num))) #define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER -#define TYPVAL_ENCODE_CONV_FLOAT(flt) \ +#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ kv_push(edata->stack, FLOATING_OBJ((Float)(flt))) -#define TYPVAL_ENCODE_CONV_STRING(str, len) \ +#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \ do { \ const size_t len_ = (size_t)(len); \ const char *const str_ = (const char *)(str); \ @@ -354,19 +353,23 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) #define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING -#define TYPVAL_ENCODE_CONV_EXT_STRING(str, len, type) \ - TYPVAL_ENCODE_CONV_NIL() +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \ + TYPVAL_ENCODE_CONV_NIL(tv) -#define TYPVAL_ENCODE_CONV_FUNC(fun) \ - TYPVAL_ENCODE_CONV_NIL() +#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ + do { \ + TYPVAL_ENCODE_CONV_NIL(tv); \ + goto typval_encode_stop_converting_one_item; \ + } while (0) -#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ - TYPVAL_ENCODE_CONV_NIL() +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_END(tv) -#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ +#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \ kv_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = 0, .size = 0 }))) -#define TYPVAL_ENCODE_CONV_EMPTY_DICT() \ +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ kv_push(edata->stack, \ DICTIONARY_OBJ(((Dictionary) { .capacity = 0, .size = 0 }))) @@ -381,9 +384,11 @@ static inline void typval_encode_list_start(EncodedData *const edata, }))); } -#define TYPVAL_ENCODE_CONV_LIST_START(len) \ +#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ typval_encode_list_start(edata, (size_t)(len)) +#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) + static inline void typval_encode_between_list_items(EncodedData *const edata) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL { @@ -394,7 +399,7 @@ static inline void typval_encode_between_list_items(EncodedData *const edata) list->data.array.items[list->data.array.size++] = item; } -#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS() \ +#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \ typval_encode_between_list_items(edata) static inline void typval_encode_list_end(EncodedData *const edata) @@ -407,7 +412,7 @@ static inline void typval_encode_list_end(EncodedData *const edata) #endif } -#define TYPVAL_ENCODE_CONV_LIST_END() \ +#define TYPVAL_ENCODE_CONV_LIST_END(tv) \ typval_encode_list_end(edata) static inline void typval_encode_dict_start(EncodedData *const edata, @@ -421,10 +426,12 @@ static inline void typval_encode_dict_start(EncodedData *const edata, }))); } -#define TYPVAL_ENCODE_CONV_DICT_START(len) \ +#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ typval_encode_dict_start(edata, (size_t)(len)) -#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(label, kv_pair) +#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) + +#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, kv_pair) static inline void typval_encode_after_key(EncodedData *const edata) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL @@ -443,7 +450,7 @@ static inline void typval_encode_after_key(EncodedData *const edata) } } -#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY() \ +#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) \ typval_encode_after_key(edata) static inline void typval_encode_between_dict_items(EncodedData *const edata) @@ -456,7 +463,7 @@ static inline void typval_encode_between_dict_items(EncodedData *const edata) dict->data.dictionary.items[dict->data.dictionary.size++].value = val; } -#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS() \ +#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \ typval_encode_between_dict_items(edata) static inline void typval_encode_dict_end(EncodedData *const edata) @@ -469,34 +476,44 @@ static inline void typval_encode_dict_end(EncodedData *const edata) #endif } -#define TYPVAL_ENCODE_CONV_DICT_END() \ +#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \ typval_encode_dict_end(edata) #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ TYPVAL_ENCODE_CONV_NIL() -// object_convert_one_value() -// encode_vim_to_object() -TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, object, EncodedData *const, edata) +#define TYPVAL_ENCODE_SCOPE static +#define TYPVAL_ENCODE_NAME object +#define TYPVAL_ENCODE_FIRST_ARG_TYPE EncodedData *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME edata +#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_CONV_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT -#undef TYPVAL_ENCODE_CONV_FUNC -#undef TYPVAL_ENCODE_CONV_PARTIAL +#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_LIST_START +#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT #undef TYPVAL_ENCODE_CONV_NIL #undef TYPVAL_ENCODE_CONV_BOOL #undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER #undef TYPVAL_ENCODE_CONV_DICT_START +#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START #undef TYPVAL_ENCODE_CONV_DICT_END #undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY #undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS -#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK +#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK #undef TYPVAL_ENCODE_CONV_LIST_END #undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS #undef TYPVAL_ENCODE_CONV_RECURSE @@ -510,7 +527,10 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, object, EncodedData *const, edata) Object vim_to_object(typval_T *obj) { EncodedData edata = { .stack = KV_INITIAL_VALUE }; - encode_vim_to_object(&edata, obj, "vim_to_object argument"); + const int evo_ret = encode_vim_to_object(&edata, obj, + "vim_to_object argument"); + (void)evo_ret; + assert(evo_ret == OK); Object ret = kv_A(edata.stack, 0); assert(kv_size(edata.stack) == 1); kv_destroy(edata.stack); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2ca621ad22..a046b2a288 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6445,7 +6445,7 @@ void dict_free(dict_T *d) { */ dictitem_T *dictitem_alloc(char_u *key) FUNC_ATTR_NONNULL_RET { - dictitem_T *di = xmalloc(sizeof(dictitem_T) + STRLEN(key)); + dictitem_T *di = xmalloc(offsetof(dictitem_T, di_key) + STRLEN(key) + 1); #ifndef __clang_analyzer__ STRCPY(di->di_key, key); #endif @@ -9251,7 +9251,7 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) dict_T *d = NULL; typval_T save_val; typval_T save_key; - int rem; + int rem = false; int todo; char_u *ermsg = (char_u *)(map ? "map()" : "filter()"); char_u *arg_errmsg = (char_u *)(map ? N_("map() argument") @@ -19042,124 +19042,205 @@ void free_tv(typval_T *varp) #define TYPVAL_ENCODE_ALLOW_SPECIALS false -#define TYPVAL_ENCODE_CONV_NIL() \ +#define TYPVAL_ENCODE_CONV_NIL(tv) \ do { \ tv->vval.v_special = kSpecialVarFalse; \ tv->v_lock = VAR_UNLOCKED; \ } while (0) -#define TYPVAL_ENCODE_CONV_BOOL(ignored) \ - TYPVAL_ENCODE_CONV_NIL() +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ + TYPVAL_ENCODE_CONV_NIL(tv) -#define TYPVAL_ENCODE_CONV_NUMBER(ignored) \ +#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ do { \ - (void)ignored; \ + (void)num; \ tv->vval.v_number = 0; \ tv->v_lock = VAR_UNLOCKED; \ } while (0) -#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(ignored) \ - assert(false) +#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) -#define TYPVAL_ENCODE_CONV_FLOAT(ignored) \ +#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(str, ignored) \ +#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \ do { \ - xfree(str); \ + xfree(buf); \ tv->vval.v_string = NULL; \ tv->v_lock = VAR_UNLOCKED; \ } while (0) -#define TYPVAL_ENCODE_CONV_STR_STRING(ignored1, ignored2) +#define TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len) -#define TYPVAL_ENCODE_CONV_EXT_STRING(ignored1, ignored2, ignored3) +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type) -#define TYPVAL_ENCODE_CONV_FUNC(fun) \ +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 (fun != empty_string) { + xfree(fun); + } + tv->vval.v_string = NULL; + } + return NOTDONE; +} +#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ - func_unref(fun); \ - if (fun != empty_string) { \ - xfree(fun); \ + if (_nothing_conv_func_start(tv, fun) != NOTDONE) { \ + return OK; \ } \ - tv->vval.v_string = NULL; \ - tv->v_lock = VAR_UNLOCKED; \ } while (0) -#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \ - do { \ - partial_unref(pt); \ - pt = NULL; \ - tv->v_lock = VAR_UNLOCKED; \ - } while (0) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len) -#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ +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 { \ list_unref(tv->vval.v_list); \ tv->vval.v_list = NULL; \ tv->v_lock = VAR_UNLOCKED; \ } while (0) -#define TYPVAL_ENCODE_CONV_EMPTY_DICT() \ +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ do { \ - dict_unref(tv->vval.v_dict); \ - tv->vval.v_dict = NULL; \ - tv->v_lock = VAR_UNLOCKED; \ - } while (0) - -#define TYPVAL_ENCODE_CONV_LIST_START(ignored) \ - do { \ - if (tv->vval.v_list->lv_refcount > 1) { \ - tv->vval.v_list->lv_refcount--; \ - tv->vval.v_list = NULL; \ - tv->v_lock = VAR_UNLOCKED; \ - return OK; \ + 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) -#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS() - -#define TYPVAL_ENCODE_CONV_LIST_END() \ - do { \ - typval_T *const cur_tv = cur_mpsv->tv; \ - assert(cur_tv->v_type == VAR_LIST); \ - list_unref(cur_tv->vval.v_list); \ - cur_tv->vval.v_list = NULL; \ - cur_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_DICT_START(ignored) \ +#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) \ do { \ - if (tv->vval.v_dict->dv_refcount > 1) { \ - tv->vval.v_dict->dv_refcount--; \ - tv->vval.v_dict = NULL; \ - tv->v_lock = VAR_UNLOCKED; \ - return OK; \ + if (_nothing_conv_real_list_after_start(tv, &mpsv) != NOTDONE) { \ + goto typval_encode_stop_converting_one_item; \ } \ } while (0) -#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(ignored1, ignored2) +#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) -#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY() +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; + list_unref(list); + tv->vval.v_list = NULL; +} +#define TYPVAL_ENCODE_CONV_LIST_END(tv) _nothing_conv_list_end(tv) -#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS() +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_DICT_END() \ +#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) \ do { \ - typval_T *const cur_tv = cur_mpsv->tv; \ - assert(cur_tv->v_type == VAR_DICT); \ - dict_unref(cur_tv->vval.v_dict); \ - cur_tv->vval.v_dict = NULL; \ - cur_tv->v_lock = VAR_UNLOCKED; \ + 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_CONV_RECURSE(ignored1, ignored2) +#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) -// nothing_convert_one_value() -// encode_vim_to_nothing() -TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, nothing, void *, ignored) +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 @@ -19170,15 +19251,19 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, nothing, void *, ignored) #undef TYPVAL_ENCODE_CONV_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING -#undef TYPVAL_ENCODE_CONV_FUNC -#undef TYPVAL_ENCODE_CONV_PARTIAL +#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_SPECIAL_DICT_KEY_CHECK +#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 @@ -19190,7 +19275,9 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, nothing, void *, ignored) void clear_tv(typval_T *varp) { if (varp != NULL && varp->v_type != VAR_UNKNOWN) { - encode_vim_to_nothing(varp, varp, "clear_tv argument"); + const int evn_ret = encode_vim_to_nothing(varp, varp, "clear_tv argument"); + (void)evn_ret; + assert(evn_ret == OK); } } @@ -21578,9 +21665,12 @@ void func_unref(char_u *name) fp = find_func(name); if (fp == NULL) { #ifdef EXITFREE - if (!entered_free_all_mem) // NOLINT(readability/braces) -#endif + if (!entered_free_all_mem) { EMSG2(_(e_intern2), "func_unref()"); + } +#else + EMSG2(_(e_intern2), "func_unref()"); +#endif } else { user_func_unref(fp); } diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 5af4893975..ee66b7cf09 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -108,9 +108,12 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, { garray_T msg_ga; ga_init(&msg_ga, (int)sizeof(char), 80); - char *const key_msg = _("key %s"); - char *const key_pair_msg = _("key %s at index %i from special map"); - char *const idx_msg = _("index %i"); + const char *const key_msg = _("key %s"); + const char *const key_pair_msg = _("key %s at index %i from special map"); + const char *const idx_msg = _("index %i"); + const char *const partial_arg_msg = _("partial"); + const char *const partial_arg_i_msg = _("argument %i"); + const char *const partial_self_msg = _("partial self dictionary"); for (size_t i = 0; i < kv_size(*mpstack); i++) { if (i != 0) { ga_concat(&msg_ga, ", "); @@ -154,6 +157,29 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, } break; } + case kMPConvPartial: { + switch (v.data.p.stage) { + case kMPConvPartialArgs: { + assert(false); + break; + } + case kMPConvPartialSelf: { + ga_concat(&msg_ga, partial_arg_msg); + break; + } + case kMPConvPartialEnd: { + ga_concat(&msg_ga, partial_self_msg); + break; + } + } + break; + } + case kMPConvPartialList: { + const int idx = (int)(v.data.a.arg - v.data.a.argv) - 1; + vim_snprintf((char *)IObuff, IOSIZE, partial_arg_i_msg, idx); + ga_concat(&msg_ga, IObuff); + break; + } } } EMSG3(msg, objname, (kv_size(*mpstack) == 0 @@ -254,7 +280,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, : OK); } -#define TYPVAL_ENCODE_CONV_STRING(buf, len) \ +#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \ do { \ const char *const buf_ = (const char *) buf; \ if (buf == NULL) { \ @@ -273,19 +299,19 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, } \ } while (0) -#define TYPVAL_ENCODE_CONV_STR_STRING(buf, len) \ - TYPVAL_ENCODE_CONV_STRING(buf, len) +#define TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len) \ + TYPVAL_ENCODE_CONV_STRING(tv, buf, len) -#define TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type) +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type) -#define TYPVAL_ENCODE_CONV_NUMBER(num) \ +#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ do { \ char numbuf[NUMBUFLEN]; \ vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%" PRId64, (int64_t) (num)); \ ga_concat(gap, numbuf); \ } while (0) -#define TYPVAL_ENCODE_CONV_FLOAT(flt) \ +#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ do { \ const float_T flt_ = (flt); \ switch (fpclassify(flt_)) { \ @@ -308,103 +334,75 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, } \ } while (0) -#define TYPVAL_ENCODE_CONV_FUNC(fun) \ +#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ - ga_concat(gap, "function("); \ - TYPVAL_ENCODE_CONV_STRING(fun, STRLEN(fun)); \ - ga_append(gap, ')'); \ + const char *const fun_ = (const char *)(fun); \ + if (fun_ == NULL) { \ + EMSG2(_(e_intern2), "string(): NULL function name"); \ + ga_concat(gap, "function(NULL"); \ + } else { \ + ga_concat(gap, "function("); \ + TYPVAL_ENCODE_CONV_STRING(tv, fun_, strlen(fun_)); \ + }\ } while (0) -#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \ +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) \ do { \ - int i; \ - ga_concat(gap, "function("); \ - if (pt->pt_name != NULL) { \ - size_t len; \ - char_u *p; \ - len = 3; \ - len += STRLEN(pt->pt_name); \ - for (p = pt->pt_name; *p != NUL; mb_ptr_adv(p)) { \ - if (*p == '\'') { \ - len++; \ - } \ - } \ - char_u *r, *s; \ - s = r = xmalloc(len); \ - if (r != NULL) { \ - *r++ = '\''; \ - for (p = pt->pt_name; *p != NUL; ) { \ - if (*p == '\'') { \ - *r++ = '\''; \ - } \ - MB_COPY_CHAR(p, r); \ - } \ - *r++ = '\''; \ - *r++ = NUL; \ - } \ - ga_concat(gap, s); \ - xfree(s); \ - } \ - if (pt->pt_argc > 0) { \ - ga_concat(gap, ", ["); \ - for (i = 0; i < pt->pt_argc; i++) { \ - if (i > 0) { \ - ga_concat(gap, ", "); \ - } \ - char *tofree = encode_tv2string(&pt->pt_argv[i], NULL); \ - ga_concat(gap, tofree); \ - xfree(tofree); \ - } \ - ga_append(gap, ']'); \ - } \ - if (pt->pt_dict != NULL) { \ - typval_T dtv; \ - ga_concat(gap, ", "); \ - dtv.v_type = VAR_DICT; \ - dtv.vval.v_dict = pt->pt_dict; \ - char *tofree = encode_tv2string(&dtv, NULL); \ - ga_concat(gap, tofree); \ - xfree(tofree); \ - } \ - ga_append(gap, ')'); \ + if (len != 0) { \ + ga_concat(gap, ", "); \ + } \ + } while (0) + +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len) \ + do { \ + if ((ptrdiff_t)len != -1) { \ + ga_concat(gap, ", "); \ + } \ } while (0) -#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ +#define TYPVAL_ENCODE_CONV_FUNC_END(tv) \ + ga_append(gap, ')') + +#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \ ga_concat(gap, "[]") -#define TYPVAL_ENCODE_CONV_LIST_START(len) \ +#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ ga_append(gap, '[') -#define TYPVAL_ENCODE_CONV_EMPTY_DICT() \ +#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) + +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ ga_concat(gap, "{}") -#define TYPVAL_ENCODE_CONV_NIL() \ +#define TYPVAL_ENCODE_CONV_NIL(tv) \ ga_concat(gap, "v:null") -#define TYPVAL_ENCODE_CONV_BOOL(num) \ +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ ga_concat(gap, ((num)? "v:true": "v:false")) -#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(num) +#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) -#define TYPVAL_ENCODE_CONV_DICT_START(len) \ +#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ ga_append(gap, '{') -#define TYPVAL_ENCODE_CONV_DICT_END() \ +#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) + +#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \ ga_append(gap, '}') -#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY() \ +#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) \ ga_concat(gap, ": ") -#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS() \ +#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \ ga_concat(gap, ", ") -#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(label, key) +#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, key) -#define TYPVAL_ENCODE_CONV_LIST_END() \ +#define TYPVAL_ENCODE_CONV_LIST_END(tv) \ ga_append(gap, ']') -#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS() \ - TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS() +#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \ + TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, NULL) #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ do { \ @@ -437,9 +435,15 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_ALLOW_SPECIALS false -// string_convert_one_value() -// encode_vim_to_string() -TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, string, garray_T *const, gap) +#define TYPVAL_ENCODE_SCOPE static +#define TYPVAL_ENCODE_NAME string +#define TYPVAL_ENCODE_FIRST_ARG_TYPE garray_T *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME gap +#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_CONV_RECURSE #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ @@ -469,9 +473,15 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, string, garray_T *const, gap) return OK; \ } while (0) -// echo_convert_one_value() -// encode_vim_to_echo() -TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(, echo, garray_T *const, gap) +#define TYPVAL_ENCODE_SCOPE +#define TYPVAL_ENCODE_NAME echo +#define TYPVAL_ENCODE_FIRST_ARG_TYPE garray_T *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME gap +#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_CONV_RECURSE #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ @@ -489,15 +499,15 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(, echo, garray_T *const, gap) #define TYPVAL_ENCODE_ALLOW_SPECIALS true #undef TYPVAL_ENCODE_CONV_NIL -#define TYPVAL_ENCODE_CONV_NIL() \ +#define TYPVAL_ENCODE_CONV_NIL(tv) \ ga_concat(gap, "null") #undef TYPVAL_ENCODE_CONV_BOOL -#define TYPVAL_ENCODE_CONV_BOOL(num) \ +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ ga_concat(gap, ((num)? "true": "false")) #undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER -#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(num) \ +#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) \ do { \ char numbuf[NUMBUFLEN]; \ vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%" PRIu64, (num)); \ @@ -505,7 +515,7 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(, echo, garray_T *const, gap) } while (0) #undef TYPVAL_ENCODE_CONV_FLOAT -#define TYPVAL_ENCODE_CONV_FLOAT(flt) \ +#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ do { \ const float_T flt_ = (flt); \ switch (fpclassify(flt_)) { \ @@ -694,7 +704,7 @@ static inline int convert_to_json_string(garray_T *const gap, } #undef TYPVAL_ENCODE_CONV_STRING -#define TYPVAL_ENCODE_CONV_STRING(buf, len) \ +#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \ do { \ if (convert_to_json_string(gap, (const char *) (buf), (len)) != OK) { \ return FAIL; \ @@ -702,25 +712,19 @@ static inline int convert_to_json_string(garray_T *const gap, } while (0) #undef TYPVAL_ENCODE_CONV_EXT_STRING -#define TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type) \ +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type) \ do { \ xfree(buf); \ EMSG(_("E474: Unable to convert EXT string to JSON")); \ return FAIL; \ } while (0) -#undef TYPVAL_ENCODE_CONV_FUNC -#define TYPVAL_ENCODE_CONV_FUNC(fun) \ +#undef TYPVAL_ENCODE_CONV_FUNC_START +#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ return conv_error(_("E474: Error while dumping %s, %s: " \ "attempt to dump function reference"), \ mpstack, objname) -#undef TYPVAL_ENCODE_CONV_PARTIAL -#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \ - return conv_error(_("E474: Error while dumping %s, %s: " \ - "attempt to dump partial"), \ - mpstack, objname) - /// Check whether given key can be used in json_encode() /// /// @param[in] tv Key to check. @@ -759,8 +763,8 @@ bool encode_check_json_key(const typval_T *const tv) return true; } -#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK -#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(label, key) \ +#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK +#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, key) \ do { \ if (!encode_check_json_key(&key)) { \ EMSG(_("E474: Invalid key in special dictionary")); \ @@ -768,28 +772,38 @@ bool encode_check_json_key(const typval_T *const tv) } \ } while (0) -// json_convert_one_value() -// encode_vim_to_json() -TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, json, garray_T *const, gap) +#define TYPVAL_ENCODE_SCOPE static +#define TYPVAL_ENCODE_NAME json +#define TYPVAL_ENCODE_FIRST_ARG_TYPE garray_T *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME gap +#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_CONV_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT -#undef TYPVAL_ENCODE_CONV_FUNC -#undef TYPVAL_ENCODE_CONV_PARTIAL +#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_LIST_START +#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT #undef TYPVAL_ENCODE_CONV_NIL #undef TYPVAL_ENCODE_CONV_BOOL #undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER #undef TYPVAL_ENCODE_CONV_DICT_START +#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START #undef TYPVAL_ENCODE_CONV_DICT_END #undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY #undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS -#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK +#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK #undef TYPVAL_ENCODE_CONV_LIST_END #undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS #undef TYPVAL_ENCODE_CONV_RECURSE @@ -807,7 +821,10 @@ char *encode_tv2string(typval_T *tv, size_t *len) { garray_T ga; ga_init(&ga, (int)sizeof(char), 80); - encode_vim_to_string(&ga, tv, "encode_tv2string() argument"); + const int evs_ret = encode_vim_to_string(&ga, tv, + "encode_tv2string() argument"); + (void)evs_ret; + assert(evs_ret == OK); did_echo_string_emsg = false; if (len != NULL) { *len = (size_t) ga.ga_len; @@ -833,7 +850,9 @@ char *encode_tv2echo(typval_T *tv, size_t *len) ga_concat(&ga, tv->vval.v_string); } } else { - encode_vim_to_echo(&ga, tv, ":echo argument"); + const int eve_ret = encode_vim_to_echo(&ga, tv, ":echo argument"); + (void)eve_ret; + assert(eve_ret == OK); } if (len != NULL) { *len = (size_t) ga.ga_len; @@ -854,16 +873,19 @@ char *encode_tv2json(typval_T *tv, size_t *len) { garray_T ga; ga_init(&ga, (int)sizeof(char), 80); - encode_vim_to_json(&ga, tv, "encode_tv2json() argument"); + const int evj_ret = encode_vim_to_json(&ga, tv, "encode_tv2json() argument"); + if (!evj_ret) { + ga_clear(&ga); + } did_echo_string_emsg = false; if (len != NULL) { - *len = (size_t) ga.ga_len; + *len = (size_t)ga.ga_len; } ga_append(&ga, '\0'); - return (char *) ga.ga_data; + return (char *)ga.ga_data; } -#define TYPVAL_ENCODE_CONV_STRING(buf, len) \ +#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \ do { \ if (buf == NULL) { \ msgpack_pack_bin(packer, 0); \ @@ -874,7 +896,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) } \ } while (0) -#define TYPVAL_ENCODE_CONV_STR_STRING(buf, len) \ +#define TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len) \ do { \ if (buf == NULL) { \ msgpack_pack_str(packer, 0); \ @@ -885,7 +907,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) } \ } while (0) -#define TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type) \ +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type) \ do { \ if (buf == NULL) { \ msgpack_pack_ext(packer, 0, (int8_t) type); \ @@ -896,35 +918,36 @@ char *encode_tv2json(typval_T *tv, size_t *len) } \ } while (0) -#define TYPVAL_ENCODE_CONV_NUMBER(num) \ - msgpack_pack_int64(packer, (int64_t) (num)) +#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ + msgpack_pack_int64(packer, (int64_t)(num)) -#define TYPVAL_ENCODE_CONV_FLOAT(flt) \ - msgpack_pack_double(packer, (double) (flt)) +#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ + msgpack_pack_double(packer, (double)(flt)) -#define TYPVAL_ENCODE_CONV_FUNC(fun) \ +#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ return conv_error(_("E5004: Error while dumping %s, %s: " \ "attempt to dump function reference"), \ mpstack, objname) -#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ - return conv_error(_("E5004: Error while dumping %s, %s: " \ - "attempt to dump partial"), \ - mpstack, objname) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_END(tv) -#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ +#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \ msgpack_pack_array(packer, 0) -#define TYPVAL_ENCODE_CONV_LIST_START(len) \ - msgpack_pack_array(packer, (size_t) (len)) +#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ + msgpack_pack_array(packer, (size_t)(len)) -#define TYPVAL_ENCODE_CONV_EMPTY_DICT() \ +#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) + +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ msgpack_pack_map(packer, 0) -#define TYPVAL_ENCODE_CONV_NIL() \ +#define TYPVAL_ENCODE_CONV_NIL(tv) \ msgpack_pack_nil(packer) -#define TYPVAL_ENCODE_CONV_BOOL(num) \ +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ do { \ if ((num)) { \ msgpack_pack_true(packer); \ @@ -933,23 +956,25 @@ char *encode_tv2json(typval_T *tv, size_t *len) } \ } while (0) -#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(num) \ +#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) \ msgpack_pack_uint64(packer, (num)) -#define TYPVAL_ENCODE_CONV_DICT_START(len) \ - msgpack_pack_map(packer, (size_t) (len)) +#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ + msgpack_pack_map(packer, (size_t)(len)) + +#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) -#define TYPVAL_ENCODE_CONV_DICT_END() +#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) -#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY() +#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) -#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS() +#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) -#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(label, key) +#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, key) -#define TYPVAL_ENCODE_CONV_LIST_END() +#define TYPVAL_ENCODE_CONV_LIST_END(tv) -#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS() +#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ return conv_error(_("E5005: Unable to dump %s: " \ @@ -958,28 +983,38 @@ char *encode_tv2json(typval_T *tv, size_t *len) #define TYPVAL_ENCODE_ALLOW_SPECIALS true -// msgpack_convert_one_value() -// encode_vim_to_msgpack() -TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(, msgpack, msgpack_packer *const, packer) +#define TYPVAL_ENCODE_SCOPE +#define TYPVAL_ENCODE_NAME msgpack +#define TYPVAL_ENCODE_FIRST_ARG_TYPE msgpack_packer *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME packer +#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_CONV_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT -#undef TYPVAL_ENCODE_CONV_FUNC -#undef TYPVAL_ENCODE_CONV_PARTIAL +#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_LIST_START +#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT #undef TYPVAL_ENCODE_CONV_NIL #undef TYPVAL_ENCODE_CONV_BOOL #undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER #undef TYPVAL_ENCODE_CONV_DICT_START +#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START #undef TYPVAL_ENCODE_CONV_DICT_END #undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY #undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS -#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK +#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK #undef TYPVAL_ENCODE_CONV_LIST_END #undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS #undef TYPVAL_ENCODE_CONV_RECURSE diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h new file mode 100644 index 0000000000..365eb2dd77 --- /dev/null +++ b/src/nvim/eval/typval_encode.c.h @@ -0,0 +1,802 @@ +/// @file eval/typval_encode.c.h +/// +/// Contains set of macros used to convert (possibly recursive) typval_T into +/// something else. For these macros to work the following macros must be +/// defined: + +/// @def TYPVAL_ENCODE_CONV_NIL +/// @brief Macros used to convert NIL value +/// +/// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS +/// is false) and `v:null`. +/// +/// @param tv Pointer to typval where value is stored. May not be NULL. May +/// point to special dictionary. + +/// @def TYPVAL_ENCODE_CONV_BOOL +/// @brief Macros used to convert boolean value +/// +/// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS +/// is false) and `v:true`/`v:false`. +/// +/// @param tv Pointer to typval where value is stored. May not be NULL. May +/// point to a special dictionary. +/// @param num Boolean value to convert. Value is an expression which +/// evaluates to some integer. + +/// @def TYPVAL_ENCODE_CONV_NUMBER +/// @brief Macros used to convert integer +/// +/// @param tv Pointer to typval where value is stored. May not be NULL. May +/// point to a special dictionary. +/// @param num Integer to convert, must accept both varnumber_T and int64_t. + +/// @def TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER +/// @brief Macros used to convert unsigned integer +/// +/// Not used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be +/// defined. +/// +/// @param tv Pointer to typval where value is stored. May not be NULL. Points +/// to a special dictionary. +/// @param num Integer to convert, must accept uint64_t. + +/// @def TYPVAL_ENCODE_CONV_FLOAT +/// @brief Macros used to convert floating-point number +/// +/// @param tv Pointer to typval where value is stored. May not be NULL. May +/// point to a special dictionary. +/// @param flt Number to convert, must accept float_T. + +/// @def TYPVAL_ENCODE_CONV_STRING +/// @brief Macros used to convert plain string +/// +/// Is used to convert VAR_STRING objects as well as BIN strings represented as +/// special dictionary. +/// +/// @param tv Pointer to typval where value is stored. May not be NULL. May +/// point to a special dictionary. +/// @param buf String to convert. Is a char[] buffer, not NUL-terminated. +/// @param len String length. + +/// @def TYPVAL_ENCODE_CONV_STR_STRING +/// @brief Like #TYPVAL_ENCODE_CONV_STRING, but for STR strings +/// +/// Is used to convert dictionary keys and STR strings represented as special +/// dictionaries. +/// +/// @param tv Pointer to typval where value is stored. May be NULL. May +/// point to a special dictionary. +/// @param buf String to convert. Is a char[] buffer, not NUL-terminated. +/// @param len String length. + +/// @def TYPVAL_ENCODE_CONV_EXT_STRING +/// @brief Macros used to convert EXT string +/// +/// Is used to convert EXT strings represented as special dictionaries. Never +/// actually used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be +/// defined. +/// +/// @param tv Pointer to typval where value is stored. May not be NULL. Points +/// to a special dictionary. +/// @param buf String to convert. Is a char[] buffer, not NUL-terminated. +/// @param len String length. +/// @param type EXT type. + +/// @def TYPVAL_ENCODE_CONV_FUNC_START +/// @brief Macros used when starting to convert a funcref or a partial +/// +/// @param tv Pointer to typval where value is stored. May not be NULL. +/// @param fun Function name. May be NULL. + +/// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS +/// @brief Macros used before starting to convert partial arguments +/// +/// @param tv Pointer to typval where value is stored. May not be NULL. +/// @param len Number of arguments. Zero for absent arguments or when +/// converting a funcref. + +/// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF +/// @brief Macros used before starting to convert self dictionary +/// +/// @param tv Pointer to typval where value is stored. May not be NULL. +/// @param len Number of arguments. May be zero for empty dictionary or -1 for +/// missing self dictionary, also when converting function +/// reference. + +/// @def TYPVAL_ENCODE_CONV_FUNC_END +/// @brief Macros used after converting a funcref or a partial +/// +/// @param tv Pointer to typval where value is stored. May not be NULL. + +/// @def TYPVAL_ENCODE_CONV_EMPTY_LIST +/// @brief Macros used to convert an empty list +/// +/// @param tv Pointer to typval where value is stored. May not be NULL. + +/// @def TYPVAL_ENCODE_CONV_EMPTY_DICT +/// @brief Macros used to convert an empty dictionary +/// +/// @param tv Pointer to typval where value is stored. May be NULL. May +/// point to a special dictionary. +/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR +/// (for dictionaries represented as special lists). + +/// @def TYPVAL_ENCODE_CONV_LIST_START +/// @brief Macros used before starting to convert non-empty list +/// +/// @param tv Pointer to typval where value is stored. May be NULL. May +/// point to a special dictionary. +/// @param len List length. Is an expression which evaluates to an integer. + +/// @def TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START +/// @brief Macros used after pushing list onto the stack +/// +/// Only used for real list_T* lists, not for special dictionaries or partial +/// arguments. +/// +/// @param tv Pointer to typval where value is stored. May be NULL. May +/// point to a special dictionary. +/// @param mpsv Pushed MPConvStackVal value. + +/// @def TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS +/// @brief Macros used after finishing converting non-last list item +/// +/// @param tv Pointer to typval where list is stored. May be NULL. + +/// @def TYPVAL_ENCODE_CONV_LIST_END +/// @brief Macros used after converting non-empty list +/// +/// @param tv Pointer to typval where list is stored. May be NULL. + +/// @def TYPVAL_ENCODE_CONV_DICT_START +/// @brief Macros used before starting to convert non-empty dictionary +/// +/// Only used for real dict_T* dictionaries, not for special dictionaries. Also +/// used for partial self dictionary. +/// +/// @param tv Pointer to typval where dictionary is stored. May be NULL. May +/// point to a special dictionary. +/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR +/// (for dictionaries represented as special lists). +/// @param len Dictionary length. Is an expression which evaluates to an +/// integer. + +/// @def TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START +/// @brief Macros used after pushing dictionary onto the stack +/// +/// @param tv Pointer to typval where dictionary is stored. May be NULL. +/// May not point to a special dictionary. +/// @param dict Converted dictionary, lvalue. +/// @param mpsv Pushed MPConvStackVal value. + +/// @def TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK +/// @brief Macros used to check special dictionary key +/// +/// @param label Label for goto in case check was not successfull. +/// @param key typval_T key to check. + +/// @def TYPVAL_ENCODE_CONV_DICT_AFTER_KEY +/// @brief Macros used after finishing converting dictionary key +/// +/// @param tv Pointer to typval where dictionary is stored. May be NULL. May +/// point to a special dictionary. +/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR +/// (for dictionaries represented as special lists). + +/// @def TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS +/// @brief Macros used after finishing converting non-last dictionary value +/// +/// @param tv Pointer to typval where dictionary is stored. May be NULL. May +/// point to a special dictionary. +/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR +/// (for dictionaries represented as special lists). + +/// @def TYPVAL_ENCODE_CONV_DICT_END +/// @brief Macros used after converting non-empty dictionary +/// +/// @param tv Pointer to typval where dictionary is stored. May be NULL. May +/// point to a special dictionary. +/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR +/// (for dictionaries represented as special lists). + +/// @def TYPVAL_ENCODE_CONV_RECURSE +/// @brief Macros used when self-containing container is detected +/// +/// @param val Container for which this situation was detected. +/// @param conv_type Type of the stack entry, @see MPConvStackValType. + +/// @def TYPVAL_ENCODE_ALLOW_SPECIALS +/// @brief Macros that specifies whether special dictionaries are special +/// +/// Must be something that evaluates to boolean, most likely `true` or `false`. +/// If it is false then special dictionaries are not treated specially. + +/// @def TYPVAL_ENCODE_SCOPE +/// @brief Scope of the main function: either nothing or `static` + +/// @def TYPVAL_ENCODE_NAME +/// @brief Name of the target converter +/// +/// After including this file it will define function +/// `encode_vim_to_{TYPVAL_ENCODE_NAME}` with scope #TYPVAL_ENCODE_SCOPE and +/// static functions `_typval_encode_{TYPVAL_ENCODE_NAME}_convert_one_value` and +/// `_typval_encode_{TYPVAL_ENCODE_NAME}_check_self_reference`. + +/// @def TYPVAL_ENCODE_FIRST_ARG_TYPE +/// @brief Type of the first argument, which will be used to return the results +/// +/// Is expected to be a pointer type. + +/// @def TYPVAL_ENCODE_FIRST_ARG_NAME +/// @brief Name of the first argument +/// +/// This name will only be used by one of the above macros which are defined by +/// the caller. Functions defined here do not use first argument directly. +#ifndef NVIM_EVAL_TYPVAL_ENCODE_C_H +#define NVIM_EVAL_TYPVAL_ENCODE_C_H +#undef NVIM_EVAL_TYPVAL_ENCODE_C_H + +#include <stddef.h> +#include <inttypes.h> +#include <assert.h> + +#include "nvim/lib/kvec.h" +#include "nvim/eval_defs.h" +#include "nvim/eval/encode.h" +#include "nvim/func_attr.h" +#include "nvim/eval/typval_encode.h" + +/// Dummy variable used because some macros need lvalue +/// +/// Must not be written to, if needed one must check that address of the +/// macros argument is (not) equal to `&TYPVAL_ENCODE_NODICT_VAR`. +const dict_T *const TYPVAL_ENCODE_NODICT_VAR = NULL; + +static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( + TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, + void *const val, int *const val_copyID, + const MPConvStack *const mpstack, const int copyID, + const MPConvStackValType conv_type, + const char *const objname) + REAL_FATTR_NONNULL_ARG(2, 3, 4, 7) REAL_FATTR_WARN_UNUSED_RESULT + REAL_FATTR_ALWAYS_INLINE; + +/// Function for checking whether container references itself +/// +/// @param TYPVAL_ENCODE_FIRST_ARG_NAME First argument. +/// @param[in,out] val Container to check. +/// @param val_copyID Pointer to the container attribute that holds copyID. +/// After checking whether value of this attribute is +/// copyID (variable) it is set to copyID. +/// @param[in] mpstack Stack with values to convert. Read-only, used for error +/// reporting. +/// @param[in] copyID CopyID used by the caller. +/// @param[in] conv_type Type of the conversion, @see MPConvStackValType. +/// @param[in] objname Object name, used for error reporting. +/// +/// @return NOTDONE in case of success, what to return in case of failure. +static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( + TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, + void *const val, int *const val_copyID, + const MPConvStack *const mpstack, const int copyID, + const MPConvStackValType conv_type, + const char *const objname) +{ + if (*val_copyID == copyID) { + TYPVAL_ENCODE_CONV_RECURSE(val, conv_type); + return OK; + } + *val_copyID = copyID; + return NOTDONE; +} + +static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( + TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, + MPConvStack *const mpstack, MPConvStackVal *const cur_mpsv, + typval_T *const tv, const int copyID, + const char *const objname) + REAL_FATTR_NONNULL_ARG(2, 4, 6) REAL_FATTR_WARN_UNUSED_RESULT; + +/// Convert single value +/// +/// Only scalar values are converted immediately, everything else is pushed onto +/// the stack. +/// +/// @param TYPVAL_ENCODE_FIRST_ARG_NAME First argument, defined by the +/// includer. Only meaningful to macros +/// defined by the includer. +/// @param mpstack Stack with values to convert. Values which are not +/// converted completely by this function (i.e. +/// non-scalars) are pushed here. +/// @param cur_mpsv Currently converted value from stack. +/// @param tv Converted value. +/// @param[in] copyID CopyID. +/// @param[in] objname Object name, used for error reporting. +/// +/// @return OK in case of success, FAIL in case of failure. +static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( + TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, + MPConvStack *const mpstack, MPConvStackVal *const cur_mpsv, + typval_T *const tv, const int copyID, + const char *const objname) +{ + switch (tv->v_type) { + case VAR_STRING: { + TYPVAL_ENCODE_CONV_STRING(tv, tv->vval.v_string, tv_strlen(tv)); + break; + } + case VAR_NUMBER: { + TYPVAL_ENCODE_CONV_NUMBER(tv, tv->vval.v_number); + break; + } + case VAR_FLOAT: { + TYPVAL_ENCODE_CONV_FLOAT(tv, tv->vval.v_float); + break; + } + case VAR_FUNC: { + TYPVAL_ENCODE_CONV_FUNC_START(tv, tv->vval.v_string); + TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, 0); + TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1); + TYPVAL_ENCODE_CONV_FUNC_END(tv); + break; + } + case VAR_PARTIAL: { + partial_T *const pt = tv->vval.v_partial; + (void)pt; + TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : pt->pt_name)); + _mp_push(*mpstack, ((MPConvStackVal) { + .type = kMPConvPartial, + .tv = tv, + .data = { + .p = { + .stage = kMPConvPartialArgs, + .pt = tv->vval.v_partial, + }, + }, + })); + break; + } + case VAR_LIST: { + if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { + TYPVAL_ENCODE_CONV_EMPTY_LIST(tv); + break; + } + _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID, + kMPConvList); + TYPVAL_ENCODE_CONV_LIST_START(tv, tv->vval.v_list->lv_len); + _mp_push(*mpstack, ((MPConvStackVal) { + .type = kMPConvList, + .tv = tv, + .data = { + .l = { + .list = tv->vval.v_list, + .li = tv->vval.v_list->lv_first, + }, + }, + })); + TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, _mp_last(*mpstack)); + break; + } + case VAR_SPECIAL: { + switch (tv->vval.v_special) { + case kSpecialVarNull: { + TYPVAL_ENCODE_CONV_NIL(tv); + break; + } + case kSpecialVarTrue: + case kSpecialVarFalse: { + TYPVAL_ENCODE_CONV_BOOL(tv, tv->vval.v_special == kSpecialVarTrue); + break; + } + } + break; + } + case VAR_DICT: { + if (tv->vval.v_dict == NULL + || tv->vval.v_dict->dv_hashtab.ht_used == 0) { + TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, tv->vval.v_dict); + break; + } + const dictitem_T *type_di; + const dictitem_T *val_di; + if (TYPVAL_ENCODE_ALLOW_SPECIALS + && tv->vval.v_dict->dv_hashtab.ht_used == 2 + && (type_di = dict_find((dict_T *)tv->vval.v_dict, + (char_u *)"_TYPE", -1)) != NULL + && type_di->di_tv.v_type == VAR_LIST + && (val_di = dict_find((dict_T *)tv->vval.v_dict, + (char_u *)"_VAL", -1)) != NULL) { + size_t i; + for (i = 0; i < ARRAY_SIZE(eval_msgpack_type_lists); i++) { + if (type_di->di_tv.vval.v_list == eval_msgpack_type_lists[i]) { + break; + } + } + if (i == ARRAY_SIZE(eval_msgpack_type_lists)) { + goto _convert_one_value_regular_dict; + } + switch ((MessagePackType)i) { + case kMPNil: { + TYPVAL_ENCODE_CONV_NIL(tv); + break; + } + case kMPBoolean: { + if (val_di->di_tv.v_type != VAR_NUMBER) { + goto _convert_one_value_regular_dict; + } + TYPVAL_ENCODE_CONV_BOOL(tv, val_di->di_tv.vval.v_number); + break; + } + case kMPInteger: { + const list_T *val_list; + varnumber_T sign; + varnumber_T highest_bits; + varnumber_T high_bits; + varnumber_T low_bits; + // List of 4 integers; first is signed (should be 1 or -1, but + // this is not checked), second is unsigned and have at most + // one (sign is -1) or two (sign is 1) non-zero bits (number of + // bits is not checked), other unsigned and have at most 31 + // non-zero bits (number of bits is not checked). + if (val_di->di_tv.v_type != VAR_LIST + || (val_list = val_di->di_tv.vval.v_list) == NULL + || val_list->lv_len != 4 + || val_list->lv_first->li_tv.v_type != VAR_NUMBER + || (sign = val_list->lv_first->li_tv.vval.v_number) == 0 + || val_list->lv_first->li_next->li_tv.v_type != VAR_NUMBER + || (highest_bits = + val_list->lv_first->li_next->li_tv.vval.v_number) < 0 + || val_list->lv_last->li_prev->li_tv.v_type != VAR_NUMBER + || (high_bits = + val_list->lv_last->li_prev->li_tv.vval.v_number) < 0 + || val_list->lv_last->li_tv.v_type != VAR_NUMBER + || (low_bits = val_list->lv_last->li_tv.vval.v_number) < 0) { + goto _convert_one_value_regular_dict; + } + uint64_t number = ((uint64_t)(((uint64_t)highest_bits) << 62) + | (uint64_t)(((uint64_t)high_bits) << 31) + | (uint64_t)low_bits); + if (sign > 0) { + TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, number); + } else { + TYPVAL_ENCODE_CONV_NUMBER(tv, -number); + } + break; + } + case kMPFloat: { + if (val_di->di_tv.v_type != VAR_FLOAT) { + goto _convert_one_value_regular_dict; + } + TYPVAL_ENCODE_CONV_FLOAT(tv, val_di->di_tv.vval.v_float); + break; + } + case kMPString: + case kMPBinary: { + const bool is_string = ((MessagePackType)i == kMPString); + if (val_di->di_tv.v_type != VAR_LIST) { + goto _convert_one_value_regular_dict; + } + size_t len; + char *buf; + if (!encode_vim_list_to_buf(val_di->di_tv.vval.v_list, &len, + &buf)) { + goto _convert_one_value_regular_dict; + } + if (is_string) { + TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len); + } else { + TYPVAL_ENCODE_CONV_STRING(tv, buf, len); + } + xfree(buf); + break; + } + case kMPArray: { + if (val_di->di_tv.v_type != VAR_LIST) { + goto _convert_one_value_regular_dict; + } + _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, + lv_copyID, copyID, + kMPConvList); + TYPVAL_ENCODE_CONV_LIST_START(tv, + val_di->di_tv.vval.v_list->lv_len); + _mp_push(*mpstack, ((MPConvStackVal) { + .tv = tv, + .type = kMPConvList, + .data = { + .l = { + .list = val_di->di_tv.vval.v_list, + .li = val_di->di_tv.vval.v_list->lv_first, + }, + }, + })); + break; + } + case kMPMap: { + if (val_di->di_tv.v_type != VAR_LIST) { + goto _convert_one_value_regular_dict; + } + list_T *const val_list = val_di->di_tv.vval.v_list; + if (val_list == NULL || val_list->lv_len == 0) { + TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, TYPVAL_ENCODE_NODICT_VAR); + break; + } + for (const listitem_T *li = val_list->lv_first; li != NULL; + li = li->li_next) { + if (li->li_tv.v_type != VAR_LIST + || li->li_tv.vval.v_list->lv_len != 2) { + goto _convert_one_value_regular_dict; + } + } + _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID, + kMPConvPairs); + TYPVAL_ENCODE_CONV_DICT_START(tv, TYPVAL_ENCODE_NODICT_VAR, + val_list->lv_len); + _mp_push(*mpstack, ((MPConvStackVal) { + .tv = tv, + .type = kMPConvPairs, + .data = { + .l = { + .list = val_list, + .li = val_list->lv_first, + }, + }, + })); + break; + } + case kMPExt: { + const list_T *val_list; + varnumber_T type; + if (val_di->di_tv.v_type != VAR_LIST + || (val_list = val_di->di_tv.vval.v_list) == NULL + || val_list->lv_len != 2 + || (val_list->lv_first->li_tv.v_type != VAR_NUMBER) + || (type = val_list->lv_first->li_tv.vval.v_number) > INT8_MAX + || type < INT8_MIN + || (val_list->lv_last->li_tv.v_type != VAR_LIST)) { + goto _convert_one_value_regular_dict; + } + size_t len; + char *buf; + if (!encode_vim_list_to_buf(val_list->lv_last->li_tv.vval.v_list, + &len, &buf)) { + goto _convert_one_value_regular_dict; + } + TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type); + xfree(buf); + break; + } + } + break; + } +_convert_one_value_regular_dict: + _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, copyID, + kMPConvDict); + TYPVAL_ENCODE_CONV_DICT_START(tv, tv->vval.v_dict, + tv->vval.v_dict->dv_hashtab.ht_used); + _mp_push(*mpstack, ((MPConvStackVal) { + .tv = tv, + .type = kMPConvDict, + .data = { + .d = { + .dict = tv->vval.v_dict, + .dictp = &tv->vval.v_dict, + .hi = tv->vval.v_dict->dv_hashtab.ht_array, + .todo = tv->vval.v_dict->dv_hashtab.ht_used, + }, + }, + })); + TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, tv->vval.v_dict, + _mp_last(*mpstack)); + break; + } + case VAR_UNKNOWN: { + EMSG2(_(e_intern2), STR(_TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()"); + return FAIL; + } + } +typval_encode_stop_converting_one_item: + return OK; + // Prevent “unused label” warnings. + goto typval_encode_stop_converting_one_item; +} + +TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( + TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, + typval_T *const tv, const char *const objname) + REAL_FATTR_NONNULL_ARG(2, 3) REAL_FATTR_WARN_UNUSED_RESULT; + +/// Convert the whole typval +/// +/// @param TYPVAL_ENCODE_FIRST_ARG_NAME First argument, defined by the +/// includer. Only meaningful to macros +/// defined by the includer. +/// @param top_tv Converted value. +/// @param[in] objname Object name, used for error reporting. +/// +/// @return OK in case of success, FAIL in case of failure. +TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( + TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, + typval_T *const top_tv, const char *const objname) +{ + const int copyID = get_copyID(); + MPConvStack mpstack; + _mp_init(mpstack); + if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, + NULL, + top_tv, copyID, objname) + == FAIL) { + goto encode_vim_to__error_ret; + } +/// Label common for this and convert_one_value functions, used for escaping +/// from macros like TYPVAL_ENCODE_CONV_DICT_START. +typval_encode_stop_converting_one_item: + while (_mp_size(mpstack)) { + MPConvStackVal *cur_mpsv = &_mp_last(mpstack); + typval_T *tv = NULL; + switch (cur_mpsv->type) { + case kMPConvDict: { + if (!cur_mpsv->data.d.todo) { + (void)_mp_pop(mpstack); + cur_mpsv->data.d.dict->dv_copyID = copyID - 1; + TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, *cur_mpsv->data.d.dictp); + continue; + } else if (cur_mpsv->data.d.todo + != cur_mpsv->data.d.dict->dv_hashtab.ht_used) { + TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(cur_mpsv->tv, + *cur_mpsv->data.d.dictp); + } + while (HASHITEM_EMPTY(cur_mpsv->data.d.hi)) { + cur_mpsv->data.d.hi++; + } + dictitem_T *const di = HI2DI(cur_mpsv->data.d.hi); + cur_mpsv->data.d.todo--; + cur_mpsv->data.d.hi++; + TYPVAL_ENCODE_CONV_STR_STRING(NULL, &di->di_key[0], + strlen((char *)&di->di_key[0])); + TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(cur_mpsv->tv, + *cur_mpsv->data.d.dictp); + tv = &di->di_tv; + break; + } + case kMPConvList: { + if (cur_mpsv->data.l.li == NULL) { + (void)_mp_pop(mpstack); + cur_mpsv->data.l.list->lv_copyID = copyID - 1; + TYPVAL_ENCODE_CONV_LIST_END(cur_mpsv->tv); + continue; + } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { + TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(cur_mpsv->tv); + } + tv = &cur_mpsv->data.l.li->li_tv; + cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; + break; + } + case kMPConvPairs: { + if (cur_mpsv->data.l.li == NULL) { + (void)_mp_pop(mpstack); + cur_mpsv->data.l.list->lv_copyID = copyID - 1; + TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); + continue; + } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { + TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS( + cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); + } + const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; + TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK( + encode_vim_to__error_ret, kv_pair->lv_first->li_tv); + if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, + &mpstack, cur_mpsv, + &kv_pair->lv_first->li_tv, + copyID, + objname) == FAIL) { + goto encode_vim_to__error_ret; + } + TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(cur_mpsv->tv, + TYPVAL_ENCODE_NODICT_VAR); + tv = &kv_pair->lv_last->li_tv; + cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; + break; + } + case kMPConvPartial: { + partial_T *const pt = cur_mpsv->data.p.pt; + tv = cur_mpsv->tv; + switch (cur_mpsv->data.p.stage) { + case kMPConvPartialArgs: { + TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, + pt == NULL ? 0 : pt->pt_argc); + cur_mpsv->data.p.stage = kMPConvPartialSelf; + if (pt != NULL && pt->pt_argc > 0) { + TYPVAL_ENCODE_CONV_LIST_START(NULL, pt->pt_argc); + _mp_push(mpstack, ((MPConvStackVal) { + .type = kMPConvPartialList, + .tv = NULL, + .data = { + .a = { + .arg = pt->pt_argv, + .argv = pt->pt_argv, + .todo = (size_t)pt->pt_argc, + }, + }, + })); + } + break; + } + case kMPConvPartialSelf: { + cur_mpsv->data.p.stage = kMPConvPartialEnd; + dict_T *const dict = pt == NULL ? NULL : pt->pt_dict; + if (dict != NULL) { + TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, dict->dv_hashtab.ht_used); + if (dict->dv_hashtab.ht_used == 0) { + TYPVAL_ENCODE_CONV_EMPTY_DICT(NULL, pt->pt_dict); + continue; + } + const int te_csr_ret = _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( + TYPVAL_ENCODE_FIRST_ARG_NAME, + dict, &dict->dv_copyID, &mpstack, copyID, kMPConvDict, + objname); + if (te_csr_ret != NOTDONE) { + if (te_csr_ret == FAIL) { + goto encode_vim_to__error_ret; + } else { + continue; + } + } + TYPVAL_ENCODE_CONV_DICT_START(NULL, pt->pt_dict, + dict->dv_hashtab.ht_used); + _mp_push(mpstack, ((MPConvStackVal) { + .type = kMPConvDict, + .tv = NULL, + .data = { + .d = { + .dict = dict, + .dictp = &pt->pt_dict, + .hi = dict->dv_hashtab.ht_array, + .todo = dict->dv_hashtab.ht_used, + }, + }, + })); + TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(NULL, pt->pt_dict, + _mp_last(mpstack)); + } else { + TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1); + } + break; + } + case kMPConvPartialEnd: { + TYPVAL_ENCODE_CONV_FUNC_END(tv); + (void)_mp_pop(mpstack); + break; + } + } + continue; + } + case kMPConvPartialList: { + if (!cur_mpsv->data.a.todo) { + (void)_mp_pop(mpstack); + TYPVAL_ENCODE_CONV_LIST_END(NULL); + continue; + } else if (cur_mpsv->data.a.argv != cur_mpsv->data.a.arg) { + TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(NULL); + } + tv = cur_mpsv->data.a.arg++; + cur_mpsv->data.a.todo--; + break; + } + } + assert(tv != NULL); + if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, + cur_mpsv, tv, copyID, objname) + == FAIL) { + goto encode_vim_to__error_ret; + } + } + _mp_destroy(mpstack); + return OK; +encode_vim_to__error_ret: + _mp_destroy(mpstack); + return FAIL; + // Prevent “unused label” warnings. + goto typval_encode_stop_converting_one_item; +} +#endif // NVIM_EVAL_TYPVAL_ENCODE_C_H diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index b79158b30c..ba325b8f55 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -1,161 +1,35 @@ -/// @file eval/typval_convert.h +/// @file eval/typval_encode.h /// -/// Contains set of macros used to convert (possibly recursive) typval_T into -/// something else. For these macros to work the following macros must be -/// defined: - -/// @def TYPVAL_ENCODE_CONV_NIL -/// @brief Macros used to convert NIL value -/// -/// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS -/// is false) and `v:null`. Accepts no arguments, but still must be -/// a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_BOOL -/// @brief Macros used to convert boolean value -/// -/// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS -/// is false) and `v:true`/`v:false`. -/// -/// @param num Boolean value to convert. Value is an expression which -/// evaluates to some integer. - -/// @def TYPVAL_ENCODE_CONV_NUMBER -/// @brief Macros used to convert integer -/// -/// @param num Integer to convert, must accept both varnumber_T and int64_t. - -/// @def TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER -/// @brief Macros used to convert unsigned integer -/// -/// Not used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be -/// defined. -/// -/// @param num Integer to convert, must accept uint64_t. - -/// @def TYPVAL_ENCODE_CONV_FLOAT -/// @brief Macros used to convert floating-point number -/// -/// @param flt Number to convert, must accept float_T. - -/// @def TYPVAL_ENCODE_CONV_STRING -/// @brief Macros used to convert plain string -/// -/// Is used to convert VAR_STRING objects as well as BIN strings represented as -/// special dictionary. -/// -/// @param buf String to convert. Is a char[] buffer, not NUL-terminated. -/// @param len String length. - -/// @def TYPVAL_ENCODE_CONV_STR_STRING -/// @brief Like #TYPVAL_ENCODE_CONV_STRING, but for STR strings -/// -/// Is used to convert dictionary keys and STR strings represented as special -/// dictionaries. - -/// @def TYPVAL_ENCODE_CONV_EXT_STRING -/// @brief Macros used to convert EXT string -/// -/// Is used to convert EXT strings represented as special dictionaries. Never -/// actually used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be -/// defined. -/// -/// @param buf String to convert. Is a char[] buffer, not NUL-terminated. -/// @param len String length. -/// @param type EXT type. - -/// @def TYPVAL_ENCODE_CONV_FUNC -/// @brief Macros used to convert a function reference -/// -/// @param fun Function name. - -/// @def TYPVAL_ENCODE_CONV_PARTIAL -/// @brief Macros used to convert a partial -/// -/// @param pt Partial name. - -/// @def TYPVAL_ENCODE_CONV_EMPTY_LIST -/// @brief Macros used to convert an empty list -/// -/// Accepts no arguments, but still must be a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_EMPTY_DICT -/// @brief Macros used to convert an empty dictionary -/// -/// Accepts no arguments, but still must be a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_LIST_START -/// @brief Macros used before starting to convert non-empty list -/// -/// @param len List length. Is an expression which evaluates to an integer. - -/// @def TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS -/// @brief Macros used after finishing converting non-last list item -/// -/// Accepts no arguments, but still must be a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_LIST_END -/// @brief Macros used after converting non-empty list -/// -/// Accepts no arguments, but still must be a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_DICT_START -/// @brief Macros used before starting to convert non-empty dictionary -/// -/// @param len Dictionary length. Is an expression which evaluates to an -/// integer. - -/// @def TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK -/// @brief Macros used to check special dictionary key -/// -/// @param label Label for goto in case check was not successfull. -/// @param key typval_T key to check. - -/// @def TYPVAL_ENCODE_CONV_DICT_AFTER_KEY -/// @brief Macros used after finishing converting dictionary key -/// -/// Accepts no arguments, but still must be a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS -/// @brief Macros used after finishing converting non-last dictionary value -/// -/// Accepts no arguments, but still must be a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_DICT_END -/// @brief Macros used after converting non-empty dictionary -/// -/// Accepts no arguments, but still must be a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_RECURSE -/// @brief Macros used when self-containing container is detected -/// -/// @param val Container for which this situation was detected. -/// @param conv_type Type of the stack entry, @see MPConvStackValType. - -/// @def TYPVAL_ENCODE_ALLOW_SPECIALS -/// @brief Macros that specifies whether special dictionaries are special -/// -/// Must be something that evaluates to boolean, most likely `true` or `false`. -/// If it is false then special dictionaries are not treated specially. +/// Contains common definitions for eval/typval_encode.c.h. Most of time should +/// not be included directly. #ifndef NVIM_EVAL_TYPVAL_ENCODE_H #define NVIM_EVAL_TYPVAL_ENCODE_H #include <stddef.h> #include <inttypes.h> +#include <string.h> #include <assert.h> #include "nvim/lib/kvec.h" #include "nvim/eval_defs.h" -#include "nvim/eval/encode.h" #include "nvim/func_attr.h" /// Type of the stack entry typedef enum { - kMPConvDict, ///< Convert dict_T *dictionary. - kMPConvList, ///< Convert list_T *list. + kMPConvDict, ///< Convert dict_T *dictionary. + kMPConvList, ///< Convert list_T *list. kMPConvPairs, ///< Convert mapping represented as a list_T* of pairs. + kMPConvPartial, ///< Convert partial_T* partial. + kMPConvPartialList, ///< Convert argc/argv pair coming from a partial. } MPConvStackValType; +/// Stage at which partial is being converted +typedef enum { + kMPConvPartialArgs, ///< About to convert arguments. + kMPConvPartialSelf, ///< About to convert self dictionary. + kMPConvPartialEnd, ///< Already converted everything. +} MPConvPartialStage; + /// Structure representing current VimL to messagepack conversion state typedef struct { MPConvStackValType type; ///< Type of the stack entry. @@ -163,6 +37,9 @@ typedef struct { union { struct { dict_T *dict; ///< Currently converted dictionary. + dict_T **dictp; ///< Location where that dictionary is stored. + ///< Normally it is &.tv->vval.v_dict, but not when + ///< converting partials. hashitem_T *hi; ///< Currently converted dictionary item. size_t todo; ///< Amount of items left to process. } d; ///< State of dictionary conversion. @@ -170,6 +47,15 @@ typedef struct { list_T *list; ///< Currently converted list. listitem_T *li; ///< Currently converted list item. } l; ///< State of list or generic mapping conversion. + struct { + MPConvPartialStage stage; ///< Stage at which partial is being converted. + partial_T *pt; ///< Currently converted partial. + } p; ///< State of partial conversion. + struct { + typval_T *arg; ///< Currently converted argument. + typval_T *argv; ///< Start of the argument list. + size_t todo; ///< Number of items left to process. + } a; ///< State of list or generic mapping conversion. } data; ///< Data to convert. } MPConvStackVal; @@ -184,21 +70,9 @@ typedef kvec_withinit_t(MPConvStackVal, 8) MPConvStack; #define _mp_pop kv_pop #define _mp_last kv_last -/// Code for checking whether container references itself -/// -/// @param[in,out] val Container to check. -/// @param copyID_attr Name of the container attribute that holds copyID. -/// After checking whether value of this attribute is -/// copyID (variable) it is set to copyID. -/// @param conv_type Type of the conversion, @see MPConvStackValType. -#define _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val, copyID_attr, conv_type) \ - do { \ - if ((val)->copyID_attr == copyID) { \ - TYPVAL_ENCODE_CONV_RECURSE((val), conv_type); \ - return OK; \ - } \ - (val)->copyID_attr = copyID; \ - } while (0) +static inline size_t tv_strlen(const typval_T *const tv) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT + REAL_FATTR_NONNULL_ALL; /// Length of the string stored in typval_T /// @@ -208,8 +82,6 @@ typedef kvec_withinit_t(MPConvStackVal, 8) MPConvStack; /// @return Length of the string stored in typval_T, including 0 for NULL /// string. static inline size_t tv_strlen(const typval_T *const tv) - FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT - FUNC_ATTR_NONNULL_ALL { assert(tv->v_type == VAR_STRING); return (tv->vval.v_string == NULL @@ -217,363 +89,57 @@ static inline size_t tv_strlen(const typval_T *const tv) : strlen((char *) tv->vval.v_string)); } -/// Define functions to convert a VimL value: -/// `{name}_convert_one_value(...)` -/// `encode_vim_to_{name}(...)` +/// Code for checking whether container references itself /// -/// @param scope Scope of the main function: either nothing or `static`. -/// @param name Name of the target converter. -/// @param firstargtype Type of the first argument. It will be used to return -/// the results. -/// @param firstargname Name of the first argument. -#define TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(scope, name, firstargtype, \ - firstargname) \ -/* Returns OK or FAIL */ \ -static int name##_convert_one_value(firstargtype firstargname, \ - MPConvStack *const mpstack, \ - typval_T *const tv, \ - const int copyID, \ - const char *const objname) \ - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \ -{ \ - switch (tv->v_type) { \ - case VAR_STRING: { \ - TYPVAL_ENCODE_CONV_STRING(tv->vval.v_string, tv_strlen(tv)); \ - break; \ - } \ - case VAR_NUMBER: { \ - TYPVAL_ENCODE_CONV_NUMBER(tv->vval.v_number); \ - break; \ - } \ - case VAR_FLOAT: { \ - TYPVAL_ENCODE_CONV_FLOAT(tv->vval.v_float); \ - break; \ - } \ - case VAR_FUNC: { \ - TYPVAL_ENCODE_CONV_FUNC(tv->vval.v_string); \ - break; \ - } \ - case VAR_PARTIAL: { \ - TYPVAL_ENCODE_CONV_PARTIAL(tv->vval.v_partial); \ - break; \ - } \ - case VAR_LIST: { \ - if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { \ - TYPVAL_ENCODE_CONV_EMPTY_LIST(); \ - break; \ - } \ - _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, \ - kMPConvList); \ - TYPVAL_ENCODE_CONV_LIST_START(tv->vval.v_list->lv_len); \ - _mp_push(*mpstack, ((MPConvStackVal) { \ - .type = kMPConvList, \ - .tv = tv, \ - .data = { \ - .l = { \ - .list = tv->vval.v_list, \ - .li = tv->vval.v_list->lv_first, \ - }, \ - }, \ - })); \ - break; \ - } \ - case VAR_SPECIAL: { \ - switch (tv->vval.v_special) { \ - case kSpecialVarNull: { \ - TYPVAL_ENCODE_CONV_NIL(); \ - break; \ - } \ - case kSpecialVarTrue: \ - case kSpecialVarFalse: { \ - TYPVAL_ENCODE_CONV_BOOL(tv->vval.v_special == kSpecialVarTrue); \ - break; \ - } \ - } \ - break; \ - } \ - case VAR_DICT: { \ - if (tv->vval.v_dict == NULL \ - || tv->vval.v_dict->dv_hashtab.ht_used == 0) { \ - TYPVAL_ENCODE_CONV_EMPTY_DICT(); \ - break; \ - } \ - const dictitem_T *type_di; \ - const dictitem_T *val_di; \ - if (TYPVAL_ENCODE_ALLOW_SPECIALS \ - && tv->vval.v_dict->dv_hashtab.ht_used == 2 \ - && (type_di = dict_find((dict_T *) tv->vval.v_dict, \ - (char_u *) "_TYPE", -1)) != NULL \ - && type_di->di_tv.v_type == VAR_LIST \ - && (val_di = dict_find((dict_T *) tv->vval.v_dict, \ - (char_u *) "_VAL", -1)) != NULL) { \ - size_t i; \ - for (i = 0; i < ARRAY_SIZE(eval_msgpack_type_lists); i++) { \ - if (type_di->di_tv.vval.v_list == eval_msgpack_type_lists[i]) { \ - break; \ - } \ - } \ - if (i == ARRAY_SIZE(eval_msgpack_type_lists)) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - switch ((MessagePackType) i) { \ - case kMPNil: { \ - TYPVAL_ENCODE_CONV_NIL(); \ - break; \ - } \ - case kMPBoolean: { \ - if (val_di->di_tv.v_type != VAR_NUMBER) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - TYPVAL_ENCODE_CONV_BOOL(val_di->di_tv.vval.v_number); \ - break; \ - } \ - case kMPInteger: { \ - const list_T *val_list; \ - varnumber_T sign; \ - varnumber_T highest_bits; \ - varnumber_T high_bits; \ - varnumber_T low_bits; \ - /* List of 4 integers; first is signed (should be 1 or -1, but */ \ - /* this is not checked), second is unsigned and have at most */ \ - /* one (sign is -1) or two (sign is 1) non-zero bits (number of */ \ - /* bits is not checked), other unsigned and have at most 31 */ \ - /* non-zero bits (number of bits is not checked).*/ \ - if (val_di->di_tv.v_type != VAR_LIST \ - || (val_list = val_di->di_tv.vval.v_list) == NULL \ - || val_list->lv_len != 4 \ - || val_list->lv_first->li_tv.v_type != VAR_NUMBER \ - || (sign = val_list->lv_first->li_tv.vval.v_number) == 0 \ - || val_list->lv_first->li_next->li_tv.v_type != VAR_NUMBER \ - || (highest_bits = \ - val_list->lv_first->li_next->li_tv.vval.v_number) < 0 \ - || val_list->lv_last->li_prev->li_tv.v_type != VAR_NUMBER \ - || (high_bits = \ - val_list->lv_last->li_prev->li_tv.vval.v_number) < 0 \ - || val_list->lv_last->li_tv.v_type != VAR_NUMBER \ - || (low_bits = val_list->lv_last->li_tv.vval.v_number) < 0) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - uint64_t number = ((uint64_t) (((uint64_t) highest_bits) << 62) \ - | (uint64_t) (((uint64_t) high_bits) << 31) \ - | (uint64_t) low_bits); \ - if (sign > 0) { \ - TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(number); \ - } else { \ - TYPVAL_ENCODE_CONV_NUMBER(-number); \ - } \ - break; \ - } \ - case kMPFloat: { \ - if (val_di->di_tv.v_type != VAR_FLOAT) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - TYPVAL_ENCODE_CONV_FLOAT(val_di->di_tv.vval.v_float); \ - break; \ - } \ - case kMPString: \ - case kMPBinary: { \ - const bool is_string = ((MessagePackType) i == kMPString); \ - if (val_di->di_tv.v_type != VAR_LIST) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - size_t len; \ - char *buf; \ - if (!encode_vim_list_to_buf(val_di->di_tv.vval.v_list, &len, \ - &buf)) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - if (is_string) { \ - TYPVAL_ENCODE_CONV_STR_STRING(buf, len); \ - } else { \ - TYPVAL_ENCODE_CONV_STRING(buf, len); \ - } \ - xfree(buf); \ - break; \ - } \ - case kMPArray: { \ - if (val_di->di_tv.v_type != VAR_LIST) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, \ - lv_copyID, kMPConvList); \ - TYPVAL_ENCODE_CONV_LIST_START(val_di->di_tv.vval.v_list->lv_len); \ - _mp_push(*mpstack, ((MPConvStackVal) { \ - .tv = tv, \ - .type = kMPConvList, \ - .data = { \ - .l = { \ - .list = val_di->di_tv.vval.v_list, \ - .li = val_di->di_tv.vval.v_list->lv_first, \ - }, \ - }, \ - })); \ - break; \ - } \ - case kMPMap: { \ - if (val_di->di_tv.v_type != VAR_LIST) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - list_T *const val_list = val_di->di_tv.vval.v_list; \ - if (val_list == NULL || val_list->lv_len == 0) { \ - TYPVAL_ENCODE_CONV_EMPTY_DICT(); \ - break; \ - } \ - for (const listitem_T *li = val_list->lv_first; li != NULL; \ - li = li->li_next) { \ - if (li->li_tv.v_type != VAR_LIST \ - || li->li_tv.vval.v_list->lv_len != 2) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - } \ - _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val_list, lv_copyID, \ - kMPConvPairs); \ - TYPVAL_ENCODE_CONV_DICT_START(val_list->lv_len); \ - _mp_push(*mpstack, ((MPConvStackVal) { \ - .tv = tv, \ - .type = kMPConvPairs, \ - .data = { \ - .l = { \ - .list = val_list, \ - .li = val_list->lv_first, \ - }, \ - }, \ - })); \ - break; \ - } \ - case kMPExt: { \ - const list_T *val_list; \ - varnumber_T type; \ - if (val_di->di_tv.v_type != VAR_LIST \ - || (val_list = val_di->di_tv.vval.v_list) == NULL \ - || val_list->lv_len != 2 \ - || (val_list->lv_first->li_tv.v_type != VAR_NUMBER) \ - || (type = val_list->lv_first->li_tv.vval.v_number) > INT8_MAX \ - || type < INT8_MIN \ - || (val_list->lv_last->li_tv.v_type != VAR_LIST)) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - size_t len; \ - char *buf; \ - if (!encode_vim_list_to_buf(val_list->lv_last->li_tv.vval.v_list, \ - &len, &buf)) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type); \ - xfree(buf); \ - break; \ - } \ - } \ - break; \ - } \ -name##_convert_one_value_regular_dict: \ - _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, \ - kMPConvDict); \ - TYPVAL_ENCODE_CONV_DICT_START(tv->vval.v_dict->dv_hashtab.ht_used); \ - _mp_push(*mpstack, ((MPConvStackVal) { \ - .tv = tv, \ - .type = kMPConvDict, \ - .data = { \ - .d = { \ - .dict = tv->vval.v_dict, \ - .hi = tv->vval.v_dict->dv_hashtab.ht_array, \ - .todo = tv->vval.v_dict->dv_hashtab.ht_used, \ - }, \ - }, \ - })); \ - break; \ - } \ - case VAR_UNKNOWN: { \ - EMSG2(_(e_intern2), #name "_convert_one_value()"); \ - return FAIL; \ - } \ - } \ - return OK; \ -} \ -\ -scope int encode_vim_to_##name(firstargtype firstargname, typval_T *const tv, \ - const char *const objname) \ - FUNC_ATTR_WARN_UNUSED_RESULT \ -{ \ - const int copyID = get_copyID(); \ - MPConvStack mpstack; \ - _mp_init(mpstack); \ - if (name##_convert_one_value(firstargname, &mpstack, tv, copyID, objname) \ - == FAIL) { \ - goto encode_vim_to_##name##_error_ret; \ - } \ - while (_mp_size(mpstack)) { \ - MPConvStackVal *cur_mpsv = &_mp_last(mpstack); \ - typval_T *cur_tv = NULL; \ - switch (cur_mpsv->type) { \ - case kMPConvDict: { \ - if (!cur_mpsv->data.d.todo) { \ - (void) _mp_pop(mpstack); \ - cur_mpsv->data.d.dict->dv_copyID = copyID - 1; \ - TYPVAL_ENCODE_CONV_DICT_END(); \ - continue; \ - } else if (cur_mpsv->data.d.todo \ - != cur_mpsv->data.d.dict->dv_hashtab.ht_used) { \ - TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(); \ - } \ - while (HASHITEM_EMPTY(cur_mpsv->data.d.hi)) { \ - cur_mpsv->data.d.hi++; \ - } \ - dictitem_T *const di = HI2DI(cur_mpsv->data.d.hi); \ - cur_mpsv->data.d.todo--; \ - cur_mpsv->data.d.hi++; \ - TYPVAL_ENCODE_CONV_STR_STRING(&di->di_key[0], \ - strlen((char *) &di->di_key[0])); \ - TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(); \ - cur_tv = &di->di_tv; \ - break; \ - } \ - case kMPConvList: { \ - if (cur_mpsv->data.l.li == NULL) { \ - (void) _mp_pop(mpstack); \ - cur_mpsv->data.l.list->lv_copyID = copyID - 1; \ - TYPVAL_ENCODE_CONV_LIST_END(); \ - continue; \ - } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { \ - TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(); \ - } \ - cur_tv = &cur_mpsv->data.l.li->li_tv; \ - cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; \ - break; \ - } \ - case kMPConvPairs: { \ - if (cur_mpsv->data.l.li == NULL) { \ - (void) _mp_pop(mpstack); \ - cur_mpsv->data.l.list->lv_copyID = copyID - 1; \ - TYPVAL_ENCODE_CONV_DICT_END(); \ - continue; \ - } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { \ - TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(); \ - } \ - const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; \ - TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK( \ - encode_vim_to_##name##_error_ret, kv_pair->lv_first->li_tv); \ - if (name##_convert_one_value(firstargname, &mpstack, \ - &kv_pair->lv_first->li_tv, copyID, \ - objname) == FAIL) { \ - goto encode_vim_to_##name##_error_ret; \ - } \ - TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(); \ - cur_tv = &kv_pair->lv_last->li_tv; \ - cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; \ - break; \ +/// @param[in,out] val Container to check. +/// @param copyID_attr Name of the container attribute that holds copyID. +/// After checking whether value of this attribute is +/// copyID (variable) it is set to copyID. +/// @param[in] copyID CopyID used by the caller. +/// @param conv_type Type of the conversion, @see MPConvStackValType. +#define _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val, copyID_attr, copyID, \ + conv_type) \ + do { \ + const int te_csr_ret = _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( \ + TYPVAL_ENCODE_FIRST_ARG_NAME, \ + (val), &(val)->copyID_attr, mpstack, copyID, conv_type, objname); \ + if (te_csr_ret != NOTDONE) { \ + return te_csr_ret; \ } \ - } \ - assert(cur_tv != NULL); \ - if (name##_convert_one_value(firstargname, &mpstack, cur_tv, copyID, \ - objname) == FAIL) { \ - goto encode_vim_to_##name##_error_ret; \ - } \ - } \ - _mp_destroy(mpstack); \ - return OK; \ -encode_vim_to_##name##_error_ret: \ - _mp_destroy(mpstack); \ - return FAIL; \ -} + } while (0) + +#define _TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf) \ + pref##name##suf +#define _TYPVAL_ENCODE_FUNC_NAME_INNER(pref, name, suf) \ + _TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf) + +/// Construct function name, possibly using macros +/// +/// Is used to expand macros that may appear in arguments. +/// +/// @note Expands all arguments, even if only one is needed. +/// +/// @param[in] pref Prefix. +/// @param[in] suf Suffix. +/// +/// @return Concat: pref + #TYPVAL_ENCODE_NAME + suf. +#define _TYPVAL_ENCODE_FUNC_NAME(pref, suf) \ + _TYPVAL_ENCODE_FUNC_NAME_INNER(pref, TYPVAL_ENCODE_NAME, suf) + +/// Self reference checker function name +#define _TYPVAL_ENCODE_CHECK_SELF_REFERENCE \ + _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _check_self_reference) + +/// Entry point function name +#define _TYPVAL_ENCODE_ENCODE \ + _TYPVAL_ENCODE_FUNC_NAME(encode_vim_to_, ) + +/// Name of the …convert_one_value function +#define _TYPVAL_ENCODE_CONVERT_ONE_VALUE \ + _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _convert_one_value) + +/// Name of the dummy const dict_T *const variable +#define TYPVAL_ENCODE_NODICT_VAR \ + _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _nodict_var) #endif // NVIM_EVAL_TYPVAL_ENCODE_H diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 79a0b5849f..eccece7ac7 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1374,7 +1374,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, } continue; } - if (!checkforcmd(&ea.cmd, "noswapfile", 6)) { + if (!checkforcmd(&ea.cmd, "noswapfile", 3)) { break; } cmdmod.noswapfile = true; @@ -1711,7 +1711,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, xfree(p); // If the autocommands did something and didn't cause an error, try // finding the command again. - p = (ret && !aborting()) ? find_command(&ea, NULL) : NULL; + p = (ret && !aborting()) ? find_command(&ea, NULL) : ea.cmd; } if (p == NULL) { diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 529b48adbd..bbfa56dfbf 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -5652,13 +5652,14 @@ static event_T event_name2nr(char_u *start, char_u **end) int i; int len; - /* the event name ends with end of line, a blank or a comma */ - for (p = start; *p && !ascii_iswhite(*p) && *p != ','; ++p) - ; - for (i = 0; event_names[i].name != NULL; ++i) { - len = (int) event_names[i].len; - if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0) + // the event name ends with end of line, '|', a blank or a comma */ + for (p = start; *p && !ascii_iswhite(*p) && *p != ',' && *p != '|'; p++) { + } + for (i = 0; event_names[i].name != NULL; i++) { + len = (int)event_names[i].len; + if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0) { break; + } } if (*p == ',') ++p; @@ -5700,7 +5701,7 @@ find_end_event ( } pat = arg + 1; } else { - for (pat = arg; *pat && !ascii_iswhite(*pat); pat = p) { + for (pat = arg; *pat && *pat != '|' && !ascii_iswhite(*pat); pat = p) { if ((int)event_name2nr(pat, &p) >= (int)NUM_EVENTS) { if (have_group) EMSG2(_("E216: No such event: %s"), pat); @@ -5815,8 +5816,9 @@ void au_event_restore(char_u *old_ei) * * Mostly a {group} argument can optionally appear before <event>. */ -void do_autocmd(char_u *arg, int forceit) +void do_autocmd(char_u *arg_in, int forceit) { + char_u *arg = arg_in; char_u *pat; char_u *envpat = NULL; char_u *cmd; @@ -5825,10 +5827,13 @@ void do_autocmd(char_u *arg, int forceit) int nested = FALSE; int group; - /* - * Check for a legal group name. If not, use AUGROUP_ALL. - */ - group = au_get_grouparg(&arg); + if (*arg == '|') { + arg = (char_u *)""; + group = AUGROUP_ALL; // no argument, use all groups + } else { + // Check for a legal group name. If not, use AUGROUP_ALL. + group = au_get_grouparg(&arg); + } /* * Scan over the events. @@ -5838,50 +5843,54 @@ void do_autocmd(char_u *arg, int forceit) if (pat == NULL) return; - /* - * Scan over the pattern. Put a NUL at the end. - */ pat = skipwhite(pat); - cmd = pat; - while (*cmd && (!ascii_iswhite(*cmd) || cmd[-1] == '\\')) - cmd++; - if (*cmd) - *cmd++ = NUL; - - /* Expand environment variables in the pattern. Set 'shellslash', we want - * forward slashes here. */ - if (vim_strchr(pat, '$') != NULL || vim_strchr(pat, '~') != NULL) { + if (*pat == '|') { + pat = (char_u *)""; + cmd = (char_u *)""; + } else { + // Scan over the pattern. Put a NUL at the end. + cmd = pat; + while (*cmd && (!ascii_iswhite(*cmd) || cmd[-1] == '\\')) { + cmd++; + } + if (*cmd) { + *cmd++ = NUL; + } + + // Expand environment variables in the pattern. Set 'shellslash', we want + // forward slashes here. + if (vim_strchr(pat, '$') != NULL || vim_strchr(pat, '~') != NULL) { #ifdef BACKSLASH_IN_FILENAME - int p_ssl_save = p_ssl; + int p_ssl_save = p_ssl; - p_ssl = TRUE; + p_ssl = true; #endif - envpat = expand_env_save(pat); + envpat = expand_env_save(pat); #ifdef BACKSLASH_IN_FILENAME - p_ssl = p_ssl_save; + p_ssl = p_ssl_save; #endif - if (envpat != NULL) - pat = envpat; - } + if (envpat != NULL) { + pat = envpat; + } + } - /* - * Check for "nested" flag. - */ - cmd = skipwhite(cmd); - if (*cmd != NUL && STRNCMP(cmd, "nested", 6) == 0 && ascii_iswhite(cmd[6])) { - nested = TRUE; - cmd = skipwhite(cmd + 6); - } + // Check for "nested" flag. + cmd = skipwhite(cmd); + if (*cmd != NUL && STRNCMP(cmd, "nested", 6) == 0 + && ascii_iswhite(cmd[6])) { + nested = true; + cmd = skipwhite(cmd + 6); + } - /* - * Find the start of the commands. - * Expand <sfile> in it. - */ - if (*cmd != NUL) { - cmd = expand_sfile(cmd); - if (cmd == NULL) /* some error */ - return; - need_free = TRUE; + // Find the start of the commands. + // Expand <sfile> in it. + if (*cmd != NUL) { + cmd = expand_sfile(cmd); + if (cmd == NULL) { // some error + return; + } + need_free = true; + } } /* @@ -5895,16 +5904,17 @@ void do_autocmd(char_u *arg, int forceit) /* * Loop over the events. */ - last_event = (event_T)-1; /* for listing the event name */ - last_group = AUGROUP_ERROR; /* for listing the group name */ - if (*arg == '*' || *arg == NUL) { + last_event = (event_T)-1; // for listing the event name + last_group = AUGROUP_ERROR; // for listing the group name + if (*arg == '*' || *arg == NUL || *arg == '|') { for (event = (event_T)0; (int)event < (int)NUM_EVENTS; - event = (event_T)((int)event + 1)) - if (do_autocmd_event(event, pat, - nested, cmd, forceit, group) == FAIL) + event = (event_T)((int)event + 1)) { + if (do_autocmd_event(event, pat, nested, cmd, forceit, group) == FAIL) { break; + } + } } else { - while (*arg && !ascii_iswhite(*arg)) { + while (*arg && *arg != '|' && !ascii_iswhite(*arg)) { event_T event = event_name2nr(arg, &arg); assert(event < NUM_EVENTS); if (do_autocmd_event(event, pat, nested, cmd, forceit, group) == FAIL) { @@ -5931,7 +5941,8 @@ static int au_get_grouparg(char_u **argp) char_u *arg = *argp; int group = AUGROUP_ALL; - p = skiptowhite(arg); + for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) { + } if (p > arg) { group_name = vim_strnsave(arg, (int)(p - arg)); group = au_find_group(group_name); diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 872ff8d5b8..5ee04ad982 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -409,10 +409,6 @@ EXTERN struct caller_scope { } provider_caller_scope; EXTERN int provider_call_nesting INIT(= 0); -/* Magic number used for hashitem "hi_key" value indicating a deleted item. - * Only the address is used. */ -EXTERN char_u hash_removed; - EXTERN int t_colors INIT(= 256); // int value of T_CCO @@ -636,10 +632,6 @@ EXTERN int exiting INIT(= FALSE); /* TRUE when planning to exit Vim. Might * still keep on running if there is a changed * buffer. */ -#if defined(EXITFREE) -// true when in or after free_all_mem() -EXTERN bool entered_free_all_mem INIT(= false); -#endif // volatile because it is used in signal handler deathtrap(). EXTERN volatile int full_screen INIT(= false); // TRUE when doing full-screen output diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c index fa4077f22f..376f33e23e 100644 --- a/src/nvim/hashtab.c +++ b/src/nvim/hashtab.c @@ -36,6 +36,8 @@ # include "hashtab.c.generated.h" #endif +char hash_removed; + /// Initialize an empty hash table. void hash_init(hashtab_T *ht) { @@ -380,3 +382,13 @@ hash_T hash_hash(char_u *key) return hash; } + +/// Function to get HI_KEY_REMOVED value +/// +/// Used for testing because luajit ffi does not allow getting addresses of +/// globals. +const char_u *_hash_key_removed(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return HI_KEY_REMOVED; +} diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h index 7233d8c47c..0da2b13f2e 100644 --- a/src/nvim/hashtab.h +++ b/src/nvim/hashtab.h @@ -5,14 +5,19 @@ #include "nvim/types.h" +/// Magic number used for hashitem "hi_key" value indicating a deleted item +/// +/// Only the address is used. +extern char hash_removed; + /// Type for hash number (hash calculation result). typedef size_t hash_T; /// The address of "hash_removed" is used as a magic number /// for hi_key to indicate a removed item. -#define HI_KEY_REMOVED &hash_removed +#define HI_KEY_REMOVED ((char_u *)&hash_removed) #define HASHITEM_EMPTY(hi) ((hi)->hi_key == NULL \ - || (hi)->hi_key == &hash_removed) + || (hi)->hi_key == (char_u *)&hash_removed) /// A hastable item. /// diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 1884d55999..92ead873ae 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -17,16 +17,41 @@ // Force je_ prefix on jemalloc functions. # define JEMALLOC_NO_DEMANGLE # include <jemalloc/jemalloc.h> -# define malloc(size) je_malloc(size) -# define calloc(count, size) je_calloc(count, size) -# define realloc(ptr, size) je_realloc(ptr, size) -# define free(ptr) je_free(ptr) +#endif + +#ifdef UNIT_TESTING +# define malloc(size) mem_malloc(size) +# define calloc(count, size) mem_calloc(count, size) +# define realloc(ptr, size) mem_realloc(ptr, size) +# define free(ptr) mem_free(ptr) +# ifdef HAVE_JEMALLOC +MemMalloc mem_malloc = &je_malloc; +MemFree mem_free = &je_free; +MemCalloc mem_calloc = &je_calloc; +MemRealloc mem_realloc = &je_realloc; +# else +MemMalloc mem_malloc = &malloc; +MemFree mem_free = &free; +MemCalloc mem_calloc = &calloc; +MemRealloc mem_realloc = &realloc; +# endif +#else +# ifdef HAVE_JEMALLOC +# define malloc(size) je_malloc(size) +# define calloc(count, size) je_calloc(count, size) +# define realloc(ptr, size) je_realloc(ptr, size) +# define free(ptr) je_free(ptr) +# endif #endif #ifdef INCLUDE_GENERATED_DECLARATIONS # include "memory.c.generated.h" #endif +#ifdef EXITFREE +bool entered_free_all_mem = false; +#endif + /// Try to free memory. Used when trying to recover from out of memory errors. /// @see {xmalloc} void try_to_free_memory(void) @@ -353,15 +378,15 @@ char *xstpncpy(char *restrict dst, const char *restrict src, size_t maxlen) size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t size) FUNC_ATTR_NONNULL_ALL { - size_t ret = strlen(src); + size_t ret = strlen(src); - if (size) { - size_t len = (ret >= size) ? size - 1 : ret; - memcpy(dst, src, len); - dst[len] = '\0'; - } + if (size) { + size_t len = (ret >= size) ? size - 1 : ret; + memcpy(dst, src, len); + dst[len] = '\0'; + } - return ret; + return ret; } /// strdup() wrapper @@ -371,6 +396,7 @@ size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t size) /// @return pointer to a copy of the string char *xstrdup(const char *str) FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET + FUNC_ATTR_NONNULL_ALL { return xmemdupz(str, strlen(str)); } @@ -401,6 +427,7 @@ void *xmemrchr(const void *src, uint8_t c, size_t len) /// @return pointer to a copy of the string char *xstrndup(const char *str, size_t len) FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET + FUNC_ATTR_NONNULL_ALL { char *p = memchr(str, '\0', len); return xmemdupz(str, p ? (size_t)(p - str) : len); diff --git a/src/nvim/memory.h b/src/nvim/memory.h index 62cc78360c..250ac3e08f 100644 --- a/src/nvim/memory.h +++ b/src/nvim/memory.h @@ -1,9 +1,41 @@ #ifndef NVIM_MEMORY_H #define NVIM_MEMORY_H +#include <stdbool.h> // for bool #include <stdint.h> // for uint8_t #include <stddef.h> // for size_t -#include <time.h> // for time_t +#include <time.h> // for time_t + +/// `malloc()` function signature +typedef void *(*MemMalloc)(size_t); + +/// `free()` function signature +typedef void (*MemFree)(void *); + +/// `calloc()` function signature +typedef void *(*MemCalloc)(size_t, size_t); + +/// `realloc()` function signature +typedef void *(*MemRealloc)(void *, size_t); + +#ifdef UNIT_TESTING +/// When unit testing: pointer to the `malloc()` function, may be altered +extern MemMalloc mem_malloc; + +/// When unit testing: pointer to the `free()` function, may be altered +extern MemFree mem_free; + +/// When unit testing: pointer to the `calloc()` function, may be altered +extern MemCalloc mem_calloc; + +/// When unit testing: pointer to the `realloc()` function, may be altered +extern MemRealloc mem_realloc; +#endif + +#ifdef EXITFREE +/// Indicates that free_all_mem function was or is running +extern bool entered_free_all_mem; +#endif #ifdef INCLUDE_GENERATED_DECLARATIONS # include "memory.h.generated.h" diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index 2205ad0958..8ce2ecf4f4 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -7,6 +7,7 @@ #include <uv.h> #include "nvim/os/time.h" +#include "nvim/os/input.h" #include "nvim/event/loop.h" #include "nvim/vim.h" #include "nvim/main.h" @@ -34,10 +35,10 @@ uint64_t os_hrtime(void) return uv_hrtime(); } -/// Sleeps for a certain amount of milliseconds +/// Sleeps for a certain amount of milliseconds. /// /// @param milliseconds Number of milliseconds to sleep -/// @param ignoreinput If true, allow a SIGINT to interrupt us +/// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt. void os_delay(uint64_t milliseconds, bool ignoreinput) { if (ignoreinput) { @@ -46,26 +47,42 @@ void os_delay(uint64_t milliseconds, bool ignoreinput) } LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)milliseconds, got_int); } else { - os_microdelay(milliseconds * 1000); + os_microdelay(milliseconds * 1000u, ignoreinput); } } -/// Sleeps for a certain amount of microseconds +/// Sleeps for a certain amount of microseconds. /// -/// @param microseconds Number of microseconds to sleep -void os_microdelay(uint64_t microseconds) +/// @param ms Number of microseconds to sleep. +/// @param ignoreinput If true, ignore all input (including SIGINT/CTRL-C). +/// If false, waiting is aborted on any input. +void os_microdelay(uint64_t ms, bool ignoreinput) { - uint64_t elapsed = 0; - uint64_t ns = microseconds * 1000; // convert to nanoseconds + uint64_t elapsed = 0u; uint64_t base = uv_hrtime(); + // Convert microseconds to nanoseconds, or UINT64_MAX on overflow. + const uint64_t ns = (ms < UINT64_MAX / 1000u) ? ms * 1000u : UINT64_MAX; uv_mutex_lock(&delay_mutex); while (elapsed < ns) { - if (uv_cond_timedwait(&delay_cond, &delay_mutex, ns - elapsed) - == UV_ETIMEDOUT) + // If ignoring input, we simply wait the full delay. + // Else we check for input in ~100ms intervals. + const uint64_t ns_delta = ignoreinput + ? ns - elapsed + : MIN(ns - elapsed, 100000000u); // 100ms + + const int rv = uv_cond_timedwait(&delay_cond, &delay_mutex, ns_delta); + if (0 != rv && UV_ETIMEDOUT != rv) { + assert(false); + break; + } // Else: Timeout proceeded normally. + + if (!ignoreinput && os_char_avail()) { break; - uint64_t now = uv_hrtime(); + } + + const uint64_t now = uv_hrtime(); elapsed += now - base; base = now; } diff --git a/src/nvim/path.c b/src/nvim/path.c index 6149e1ab99..3d1def8dd4 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -417,15 +417,11 @@ char *FullName_save(char *fname, bool force) } char *buf = xmalloc(MAXPATHL); - char *new_fname = NULL; - if (vim_FullName(fname, buf, MAXPATHL, force) != FAIL) { - new_fname = xstrdup(buf); - } else { - new_fname = xstrdup(fname); + if (vim_FullName(fname, buf, MAXPATHL, force) == FAIL) { + xfree(buf); + return xstrdup(fname); } - xfree(buf); - - return new_fname; + return buf; } /// Saves the absolute path. @@ -1649,30 +1645,37 @@ bool vim_isAbsName(char_u *name) /// Save absolute file name to "buf[len]". /// -/// @param fname is the filename to evaluate -/// @param[out] buf is the buffer for returning the absolute path for `fname` -/// @param len is the length of `buf` -/// @param force is a flag to force expanding even if the path is absolute +/// @param fname filename to evaluate +/// @param[out] buf contains `fname` absolute path, or: +/// - truncated `fname` if longer than `len` +/// - unmodified `fname` if absolute path fails or is a URL +/// @param len length of `buf` +/// @param force flag to force expanding even if the path is absolute /// /// @return FAIL for failure, OK otherwise int vim_FullName(const char *fname, char *buf, size_t len, bool force) FUNC_ATTR_NONNULL_ARG(2) { - int retval = OK; - int url; - *buf = NUL; - if (fname == NULL) + if (fname == NULL) { return FAIL; + } - url = path_with_url(fname); - if (!url) - retval = path_get_absolute_path((char_u *)fname, (char_u *)buf, len, force); - if (url || retval == FAIL) { - /* something failed; use the file name (truncate when too long) */ + if (strlen(fname) > (len - 1)) { + xstrlcpy(buf, fname, len); // truncate + return FAIL; + } + + if (path_with_url(fname)) { xstrlcpy(buf, fname, len); + return OK; } - return retval; + + int rv = path_get_absolute_path((char_u *)fname, (char_u *)buf, len, force); + if (rv == FAIL) { + xstrlcpy(buf, fname, len); // something failed; use the filename + } + return rv; } /// Get the full resolved path for `fname` diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 1dceb70cd4..5675697dc4 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -19,6 +19,7 @@ if has('timers') call timer_start(100, 'ExitInsertMode') call feedkeys('a', 'x!') call assert_equal(1, g:triggered) + au! CursorHoldI endfunc func Test_cursorhold_insert_ctrl_x() @@ -29,6 +30,7 @@ if has('timers') " CursorHoldI does not trigger after CTRL-X call feedkeys("a\<C-X>", 'x!') call assert_equal(0, g:triggered) + au! CursorHoldI endfunc endif @@ -58,5 +60,34 @@ function Test_bufunload() bwipeout call assert_equal(["bufunload", "bufdelete", "bufwipeout"], s:li) + au! test_bufunload_group augroup! test_bufunload_group endfunc + +func s:AddAnAutocmd() + augroup vimBarTest + au BufReadCmd * echo 'hello' + augroup END + call assert_equal(3, len(split(execute('au vimBarTest'), "\n"))) +endfunc + +func Test_early_bar() + " test that a bar is recognized before the {event} + call s:AddAnAutocmd() + augroup vimBarTest | au! | augroup END + call assert_equal(1, len(split(execute('au vimBarTest'), "\n"))) + + call s:AddAnAutocmd() + augroup vimBarTest| au!| augroup END + call assert_equal(1, len(split(execute('au vimBarTest'), "\n"))) + + " test that a bar is recognized after the {event} + call s:AddAnAutocmd() + augroup vimBarTest| au!BufReadCmd| augroup END + call assert_equal(1, len(split(execute('au vimBarTest'), "\n"))) + + " test that a bar is recognized after the {group} + call s:AddAnAutocmd() + au! vimBarTest|echo 'hello' + call assert_equal(1, len(split(execute('au vimBarTest'), "\n"))) +endfunc diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim index f593d16dbf..d0864ec64c 100644 --- a/src/nvim/testdir/test_usercommands.vim +++ b/src/nvim/testdir/test_usercommands.vim @@ -8,31 +8,57 @@ function Test_cmdmods() MyCmd aboveleft MyCmd + abo MyCmd belowright MyCmd + bel MyCmd botright MyCmd + bo MyCmd browse MyCmd + bro MyCmd confirm MyCmd + conf MyCmd hide MyCmd + hid MyCmd keepalt MyCmd + keepa MyCmd keepjumps MyCmd + keepj MyCmd keepmarks MyCmd + kee MyCmd keeppatterns MyCmd + keepp MyCmd + leftabove MyCmd " results in :aboveleft + lefta MyCmd lockmarks MyCmd + loc MyCmd + " noautocmd MyCmd noswapfile MyCmd + nos MyCmd + rightbelow MyCmd " results in :belowright + rightb MyCmd + " sandbox MyCmd silent MyCmd + sil MyCmd tab MyCmd topleft MyCmd + to MyCmd + " unsilent MyCmd verbose MyCmd + verb MyCmd vertical MyCmd + vert MyCmd aboveleft belowright botright browse confirm hide keepalt keepjumps \ keepmarks keeppatterns lockmarks noswapfile silent tab \ topleft verbose vertical MyCmd - call assert_equal(' aboveleft belowright botright browse confirm ' . - \ 'hide keepalt keepjumps keepmarks keeppatterns lockmarks ' . - \ 'noswapfile silent tab topleft verbose vertical aboveleft ' . - \ 'belowright botright browse confirm hide keepalt keepjumps ' . + call assert_equal(' aboveleft aboveleft belowright belowright botright ' . + \ 'botright browse browse confirm confirm hide hide ' . + \ 'keepalt keepalt keepjumps keepjumps keepmarks keepmarks ' . + \ 'keeppatterns keeppatterns aboveleft aboveleft lockmarks lockmarks noswapfile ' . + \ 'noswapfile belowright belowright silent silent tab topleft topleft verbose verbose ' . + \ 'vertical vertical ' . + \ 'aboveleft belowright botright browse confirm hide keepalt keepjumps ' . \ 'keepmarks keeppatterns lockmarks noswapfile silent tab topleft ' . \ 'verbose vertical ', g:mods) @@ -46,3 +72,33 @@ function Test_cmdmods() delcommand MyQCmd unlet g:mods endfunction + +func Test_Ambiguous() + command Doit let g:didit = 'yes' + command Dothat let g:didthat = 'also' + call assert_fails('Do', 'E464:') + Doit + call assert_equal('yes', g:didit) + Dothat + call assert_equal('also', g:didthat) + unlet g:didit + unlet g:didthat + + delcommand Doit + Do + call assert_equal('also', g:didthat) + delcommand Dothat +endfunc + +func Test_CmdUndefined() + call assert_fails('Doit', 'E492:') + au CmdUndefined Doit :command Doit let g:didit = 'yes' + Doit + call assert_equal('yes', g:didit) + delcommand Doit + + call assert_fails('Dothat', 'E492:') + au CmdUndefined * let g:didnot = 'yes' + call assert_fails('Dothat', 'E492:') + call assert_equal('yes', g:didnot) +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index 6914cd9409..19062be730 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -313,7 +313,7 @@ static int included_patches[] = { // 2130 NA // 2129 NA 2128, - // 2127, + 2127, 2126, // 2125 NA 2124, @@ -330,14 +330,14 @@ static int included_patches[] = { // 2113, 2112, // 2111, - // 2110, + 2110, 2109, // 2108 NA // 2107, // 2106, // 2105 NA // 2104, - // 2103, + 2103, // 2102 NA // 2101, // 2100, @@ -689,7 +689,6 @@ static int included_patches[] = { 1755, 1754, 1753, - // 1753, 1752, 1751, // 1750 NA |