diff options
author | ZyX <kp-pav@yandex.ru> | 2016-07-11 20:42:27 +0300 |
---|---|---|
committer | ZyX <kp-pav@yandex.ru> | 2017-03-27 00:11:25 +0300 |
commit | ed3115bd26047c9b125798d9cb56d09b155a243b (patch) | |
tree | 3f3a05cb833365d2390531d23a45fd01b200313c | |
parent | a4dc8de0739d8e9e910d786a9b6fbfbc162aee9c (diff) | |
download | rneovim-ed3115bd26047c9b125798d9cb56d09b155a243b.tar.gz rneovim-ed3115bd26047c9b125798d9cb56d09b155a243b.tar.bz2 rneovim-ed3115bd26047c9b125798d9cb56d09b155a243b.zip |
executor: Make sure it works with API values
-rw-r--r-- | src/nvim/api/vim.c | 16 | ||||
-rw-r--r-- | src/nvim/eval.c | 2 | ||||
-rw-r--r-- | src/nvim/viml/executor/converter.c | 547 | ||||
-rw-r--r-- | src/nvim/viml/executor/executor.c | 1 | ||||
-rw-r--r-- | test/functional/lua_spec.lua | 94 |
5 files changed, 455 insertions, 205 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 413456c615..24959e9a59 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -197,7 +197,21 @@ Object nvim_eval(String expr, Error *err) return rv; } -/// Calls a VimL function with the given arguments. +/// Returns object given as argument +/// +/// This API function is used for testing. One should not rely on its presence +/// in plugins. +/// +/// @param[in] obj Object to return. +/// +/// @return its argument. +Object _vim_id(Object obj) +{ + return obj; +} + +/// Calls a VimL function with the given arguments +/// /// On VimL error: Returns a generic error; v:errmsg is not updated. /// /// @param fname Function to call diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c349a601c6..de82f8a145 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -13402,7 +13402,7 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - executor_eval_lua(cstr_as_string(str), &argvars[1], rettv); + executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv); } /* diff --git a/src/nvim/viml/executor/converter.c b/src/nvim/viml/executor/converter.c index fb7bbd51eb..c127b87738 100644 --- a/src/nvim/viml/executor/converter.c +++ b/src/nvim/viml/executor/converter.c @@ -24,10 +24,121 @@ #include "nvim/viml/executor/converter.h" #include "nvim/viml/executor/executor.h" +/// Determine, which keys lua table contains +typedef struct { + size_t maxidx; ///< Maximum positive integral value found. + size_t string_keys_num; ///< Number of string keys. + bool has_string_with_nul; ///< True if there is string key with NUL byte. + ObjectType type; ///< If has_type_key is true then attached value. Otherwise + ///< either kObjectTypeNil, kObjectTypeDictionary or + ///< kObjectTypeArray, depending on other properties. + lua_Number val; ///< If has_val_key and val_type == LUA_TNUMBER: value. +} LuaTableProps; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/executor/converter.c.generated.h" #endif +#define TYPE_IDX_VALUE true +#define VAL_IDX_VALUE false + +#define LUA_PUSH_STATIC_STRING(lstate, s) \ + lua_pushlstring(lstate, s, sizeof(s) - 1) + +static LuaTableProps nlua_traverse_table(lua_State *const lstate) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + bool has_type_key = false; // True if type key was found, + // @see nlua_push_type_idx(). + size_t tsize = 0; // Total number of keys. + int val_type = 0; // If has_val_key: lua type of the value. + bool has_val_key = false; // True if val key was found, + // @see nlua_push_val_idx(). + bool has_other = false; // True if there are keys that are not strings + // or positive integral values. + LuaTableProps ret; + memset(&ret, 0, sizeof(ret)); + if (!lua_checkstack(lstate, lua_gettop(lstate) + 2)) { + emsgf(_("E1502: Lua failed to grow stack to %i"), lua_gettop(lstate) + 2); + ret.type = kObjectTypeNil; + return ret; + } + lua_pushnil(lstate); + while (lua_next(lstate, -2)) { + switch (lua_type(lstate, -2)) { + case LUA_TSTRING: { + size_t len; + const char *s = lua_tolstring(lstate, -2, &len); + if (memchr(s, NUL, len) != NULL) { + ret.has_string_with_nul = true; + } + ret.string_keys_num++; + break; + } + case LUA_TNUMBER: { + const lua_Number n = lua_tonumber(lstate, -2); + if (n > (lua_Number)SIZE_MAX || n <= 0 + || ((lua_Number)((size_t)n)) != n) { + has_other = true; + } else { + const size_t idx = (size_t)n; + if (idx > ret.maxidx) { + ret.maxidx = idx; + } + } + break; + } + case LUA_TBOOLEAN: { + const bool b = lua_toboolean(lstate, -2); + if (b == TYPE_IDX_VALUE) { + if (lua_type(lstate, -1) == LUA_TNUMBER) { + lua_Number n = lua_tonumber(lstate, -1); + if (n == (lua_Number)kObjectTypeFloat + || n == (lua_Number)kObjectTypeArray + || n == (lua_Number)kObjectTypeDictionary) { + has_type_key = true; + ret.type = (ObjectType)n; + } else { + has_other = true; + } + } else { + has_other = true; + } + } else { + has_val_key = true; + val_type = lua_type(lstate, -1); + if (val_type == LUA_TNUMBER) { + ret.val = lua_tonumber(lstate, -1); + } + } + break; + } + default: { + has_other = true; + break; + } + } + tsize++; + lua_pop(lstate, 1); + } + if (has_type_key) { + if (ret.type == kObjectTypeFloat + && (!has_val_key || val_type != LUA_TNUMBER)) { + ret.type = kObjectTypeNil; + } + } else { + if (tsize == 0 + || (tsize == ret.maxidx && !has_other && ret.string_keys_num == 0)) { + ret.type = kObjectTypeArray; + } else if (ret.string_keys_num == tsize) { + ret.type = kObjectTypeDictionary; + } else { + ret.type = kObjectTypeNil; + } + } + return ret; +} + /// Helper structure for nlua_pop_typval typedef struct { typval_T *tv; ///< Location where conversion result is saved. @@ -63,8 +174,15 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) if (cur.container) { if (cur.special || cur.tv->v_type == VAR_DICT) { assert(cur.tv->v_type == (cur.special ? VAR_LIST : VAR_DICT)); - if (lua_next(lstate, -2)) { - assert(lua_type(lstate, -2) == LUA_TSTRING); + bool next_key_found = false; + while (lua_next(lstate, -2)) { + if (lua_type(lstate, -2) == LUA_TSTRING) { + next_key_found = true; + break; + } + lua_pop(lstate, 1); + } + if (next_key_found) { size_t len; const char *s = lua_tolstring(lstate, -2, &len); if (cur.special) { @@ -109,7 +227,11 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) } } assert(!cur.container); - memset(cur.tv, 0, sizeof(*cur.tv)); + *cur.tv = (typval_T) { + .v_type = VAR_NUMBER, + .v_lock = VAR_UNLOCKED, + .vval = { .v_number = 0 }, + }; switch (lua_type(lstate, -1)) { case LUA_TNIL: { cur.tv->v_type = VAR_SPECIAL; @@ -145,81 +267,64 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) break; } case LUA_TTABLE: { - bool has_string = false; - bool has_string_with_nul = false; - bool has_other = false; - size_t maxidx = 0; - size_t tsize = 0; - lua_pushnil(lstate); - while (lua_next(lstate, -2)) { - switch (lua_type(lstate, -2)) { - case LUA_TSTRING: { - size_t len; - const char *s = lua_tolstring(lstate, -2, &len); - if (memchr(s, NUL, len) != NULL) { - has_string_with_nul = true; - } - has_string = true; - break; + const LuaTableProps table_props = nlua_traverse_table(lstate); + + switch (table_props.type) { + case kObjectTypeArray: { + if (table_props.maxidx == 0) { + cur.tv->v_type = VAR_LIST; + cur.tv->vval.v_list = list_alloc(); + cur.tv->vval.v_list->lv_refcount++; + } else { + cur.tv->v_type = VAR_LIST; + cur.tv->vval.v_list = list_alloc(); + cur.tv->vval.v_list->lv_refcount++; + cur.container = true; + kv_push(stack, cur); } - case LUA_TNUMBER: { - const lua_Number n = lua_tonumber(lstate, -2); - if (n > (lua_Number)SIZE_MAX || n <= 0 - || ((lua_Number)((size_t)n)) != n) { - has_other = true; + break; + } + case kObjectTypeDictionary: { + if (table_props.string_keys_num == 0) { + cur.tv->v_type = VAR_DICT; + cur.tv->vval.v_dict = dict_alloc(); + cur.tv->vval.v_dict->dv_refcount++; + } else { + cur.special = table_props.has_string_with_nul; + if (table_props.has_string_with_nul) { + decode_create_map_special_dict(cur.tv); + assert(cur.tv->v_type = VAR_DICT); + dictitem_T *const val_di = dict_find(cur.tv->vval.v_dict, + (char_u *)"_VAL", 4); + assert(val_di != NULL); + cur.tv = &val_di->di_tv; + assert(cur.tv->v_type == VAR_LIST); } else { - const size_t idx = (size_t)n; - if (idx > maxidx) { - maxidx = idx; - } + cur.tv->v_type = VAR_DICT; + cur.tv->vval.v_dict = dict_alloc(); + cur.tv->vval.v_dict->dv_refcount++; } - break; - } - default: { - has_other = true; - break; + cur.container = true; + kv_push(stack, cur); + lua_pushnil(lstate); } + break; } - tsize++; - lua_pop(lstate, 1); - } - - if (tsize == 0) { - // Assuming empty list - cur.tv->v_type = VAR_LIST; - cur.tv->vval.v_list = list_alloc(); - cur.tv->vval.v_list->lv_refcount++; - } else if (tsize == maxidx && !has_other && !has_string) { - // Assuming array - cur.tv->v_type = VAR_LIST; - cur.tv->vval.v_list = list_alloc(); - cur.tv->vval.v_list->lv_refcount++; - cur.container = true; - kv_push(stack, cur); - } else if (has_string && !has_other && maxidx == 0) { - // Assuming dictionary - cur.special = has_string_with_nul; - if (has_string_with_nul) { - decode_create_map_special_dict(cur.tv); - assert(cur.tv->v_type = VAR_DICT); - dictitem_T *const val_di = dict_find(cur.tv->vval.v_dict, - (char_u *)"_VAL", 4); - assert(val_di != NULL); - cur.tv = &val_di->di_tv; - assert(cur.tv->v_type == VAR_LIST); - } else { - cur.tv->v_type = VAR_DICT; - cur.tv->vval.v_dict = dict_alloc(); - cur.tv->vval.v_dict->dv_refcount++; + case kObjectTypeFloat: { + cur.tv->v_type = VAR_FLOAT; + cur.tv->vval.v_float = (float_T)table_props.val; + break; + } + case kObjectTypeNil: { + EMSG(_("E5100: Cannot convert given lua table: table " + "should either have a sequence of positive integer keys " + "or contain only string keys")); + ret = false; + break; + } + default: { + assert(false); } - cur.container = true; - kv_push(stack, cur); - lua_pushnil(lstate); - } else { - EMSG(_("E5100: Cannot convert given lua table: table " - "should either have a sequence of positive integer keys " - "or contain only string keys")); - ret = false; } break; } @@ -236,7 +341,11 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) kv_destroy(stack); if (!ret) { clear_tv(ret_tv); - memset(ret_tv, 0, sizeof(*ret_tv)); + *ret_tv = (typval_T) { + .v_type = VAR_NUMBER, + .v_lock = VAR_UNLOCKED, + .vval = { .v_number = 0 }, + }; lua_pop(lstate, lua_gettop(lstate) - initial_size + 1); } assert(lua_gettop(lstate) == initial_size - 1); @@ -281,7 +390,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) lua_createtable(lstate, 0, 0) #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ - TYPVAL_ENCODE_CONV_EMPTY_LIST() + nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary) #define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ do { \ @@ -432,16 +541,7 @@ bool nlua_push_typval(lua_State *lstate, typval_T *const tv) static inline void nlua_push_type_idx(lua_State *lstate) FUNC_ATTR_NONNULL_ALL { - lua_pushboolean(lstate, true); -} - -/// Push value which is a locks index -/// -/// Used for containers tables. -static inline void nlua_push_locks_idx(lua_State *lstate) - FUNC_ATTR_NONNULL_ALL -{ - lua_pushboolean(lstate, false); + lua_pushboolean(lstate, TYPE_IDX_VALUE); } /// Push value which is a value index @@ -450,7 +550,7 @@ static inline void nlua_push_locks_idx(lua_State *lstate) static inline void nlua_push_val_idx(lua_State *lstate) FUNC_ATTR_NONNULL_ALL { - lua_pushnumber(lstate, (lua_Number)0); + lua_pushboolean(lstate, VAL_IDX_VALUE); } /// Push type @@ -458,14 +558,11 @@ static inline void nlua_push_val_idx(lua_State *lstate) /// Type is a value in vim.types table. /// /// @param[out] lstate Lua state. -/// @param[in] type Type to push (key in vim.types table). -static inline void nlua_push_type(lua_State *lstate, const char *const type) +/// @param[in] type Type to push. +static inline void nlua_push_type(lua_State *lstate, ObjectType type) + FUNC_ATTR_NONNULL_ALL { - lua_getglobal(lstate, "vim"); - lua_getfield(lstate, -1, "types"); - lua_remove(lstate, -2); - lua_getfield(lstate, -1, type); - lua_remove(lstate, -2); + lua_pushnumber(lstate, (lua_Number)type); } /// Create lua table which has an entry that determines its VimL type @@ -477,7 +574,7 @@ static inline void nlua_push_type(lua_State *lstate, const char *const type) static inline void nlua_create_typed_table(lua_State *lstate, const size_t narr, const size_t nrec, - const char *const type) + const ObjectType type) FUNC_ATTR_NONNULL_ALL { lua_createtable(lstate, (int)narr, (int)(1 + nrec)); @@ -511,7 +608,7 @@ void nlua_push_Integer(lua_State *lstate, const Integer n) void nlua_push_Float(lua_State *lstate, const Float f) FUNC_ATTR_NONNULL_ALL { - nlua_create_typed_table(lstate, 0, 1, "float"); + nlua_create_typed_table(lstate, 0, 1, kObjectTypeFloat); nlua_push_val_idx(lstate); lua_pushnumber(lstate, (lua_Number)f); lua_rawset(lstate, -3); @@ -526,21 +623,17 @@ void nlua_push_Boolean(lua_State *lstate, const Boolean b) lua_pushboolean(lstate, b); } -static inline void nlua_add_locks_table(lua_State *lstate) -{ - nlua_push_locks_idx(lstate); - lua_newtable(lstate); - lua_rawset(lstate, -3); -} - /// Convert given Dictionary to lua table /// /// Leaves converted table on top of the stack. void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict) FUNC_ATTR_NONNULL_ALL { - nlua_create_typed_table(lstate, 0, 1 + dict.size, "dict"); - nlua_add_locks_table(lstate); + if (dict.size == 0) { + nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); + } else { + lua_createtable(lstate, 0, (int)dict.size); + } for (size_t i = 0; i < dict.size; i++) { nlua_push_String(lstate, dict.items[i].key); nlua_push_Object(lstate, dict.items[i].value); @@ -554,11 +647,10 @@ void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict) void nlua_push_Array(lua_State *lstate, const Array array) FUNC_ATTR_NONNULL_ALL { - nlua_create_typed_table(lstate, array.size, 1, "float"); - nlua_add_locks_table(lstate); + lua_createtable(lstate, (int)array.size, 0); for (size_t i = 0; i < array.size; i++) { nlua_push_Object(lstate, array.items[i]); - lua_rawseti(lstate, -3, (int)i + 1); + lua_rawseti(lstate, -2, (int)i + 1); } } @@ -663,26 +755,33 @@ Boolean nlua_pop_Boolean(lua_State *lstate, Error *err) return ret; } -static inline bool nlua_check_type(lua_State *lstate, Error *err, - const char *const type) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +/// Check whether typed table on top of the stack has given type +/// +/// @param[in] lstate Lua state. +/// @param[out] err Location where error will be saved. May be NULL. +/// @param[in] type Type to check. +/// +/// @return @see nlua_traverse_table(). +static inline LuaTableProps nlua_check_type(lua_State *const lstate, + Error *const err, + const ObjectType type) + FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT { if (lua_type(lstate, -1) != LUA_TTABLE) { - set_api_error("Expected lua table", err); - return true; + if (err) { + set_api_error("Expected lua table", err); + } + return (LuaTableProps) { .type = kObjectTypeNil }; } + const LuaTableProps table_props = nlua_traverse_table(lstate); - nlua_push_type_idx(lstate); - lua_rawget(lstate, -2); - nlua_push_type(lstate, type); - if (!lua_rawequal(lstate, -2, -1)) { - lua_pop(lstate, 2); - set_api_error("Expected lua table with float type", err); - return true; + if (table_props.type != type) { + if (err) { + set_api_error("Unexpected type", err); + } } - lua_pop(lstate, 2); - return false; + return table_props; } /// Convert lua table to float @@ -691,49 +790,32 @@ static inline bool nlua_check_type(lua_State *lstate, Error *err, Float nlua_pop_Float(lua_State *lstate, Error *err) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - Float ret = 0; - - if (nlua_check_type(lstate, err, "float")) { + if (lua_type(lstate, -1) == LUA_TNUMBER) { + const Float ret = (Float)lua_tonumber(lstate, -1); lua_pop(lstate, 1); - return 0; - } - - nlua_push_val_idx(lstate); - lua_rawget(lstate, -2); - - if (!lua_isnumber(lstate, -1)) { - lua_pop(lstate, 2); - set_api_error("Value field should be lua number", err); return ret; } - ret = lua_tonumber(lstate, -1); - lua_pop(lstate, 2); - return ret; + const LuaTableProps table_props = nlua_check_type(lstate, err, + kObjectTypeFloat); + lua_pop(lstate, 1); + if (table_props.type != kObjectTypeFloat) { + return 0; + } else { + return (Float)table_props.val; + } } -/// Convert lua table to array +/// Convert lua table to array without determining whether it is array /// -/// Always pops one value from the stack. -Array nlua_pop_Array(lua_State *lstate, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +/// @param lstate Lua state. +/// @param[in] table_props nlua_traverse_table() output. +/// @param[out] err Location where error will be saved. +static Array nlua_pop_Array_unchecked(lua_State *const lstate, + const LuaTableProps table_props, + Error *const err) { - Array ret = { .size = 0, .items = NULL }; - - if (nlua_check_type(lstate, err, "list")) { - lua_pop(lstate, 1); - return ret; - } - - for (int i = 1; ; i++, ret.size++) { - lua_rawgeti(lstate, -1, i); - - if (lua_isnil(lstate, -1)) { - lua_pop(lstate, 1); - break; - } - lua_pop(lstate, 1); - } + Array ret = { .size = table_props.maxidx, .items = NULL }; if (ret.size == 0) { lua_pop(lstate, 1); @@ -748,7 +830,7 @@ Array nlua_pop_Array(lua_State *lstate, Error *err) val = nlua_pop_Object(lstate, err); if (err->set) { - ret.size = i; + ret.size = i - 1; lua_pop(lstate, 1); api_free_array(ret); return (Array) { .size = 0, .items = NULL }; @@ -760,24 +842,35 @@ Array nlua_pop_Array(lua_State *lstate, Error *err) return ret; } -/// Convert lua table to dictionary +/// Convert lua table to array /// -/// Always pops one value from the stack. Does not check whether -/// `vim.is_dict(table[type_idx])` or whether topmost value on the stack is -/// a table. -Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, Error *err) +/// Always pops one value from the stack. +Array nlua_pop_Array(lua_State *lstate, Error *err) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - Dictionary ret = { .size = 0, .items = NULL }; - - lua_pushnil(lstate); - - while (lua_next(lstate, -2)) { - if (lua_type(lstate, -2) == LUA_TSTRING) { - ret.size++; - } - lua_pop(lstate, 1); + const LuaTableProps table_props = nlua_check_type(lstate, err, + kObjectTypeArray); + lua_pop(lstate, 1); + if (table_props.type != kObjectTypeArray) { + return (Array) { .size = 0, .items = NULL }; } + return nlua_pop_Array_unchecked(lstate, table_props, err); +} + +/// Convert lua table to dictionary +/// +/// Always pops one value from the stack. Does not check whether whether topmost +/// value on the stack is a table. +/// +/// @param lstate Lua interpreter state. +/// @param[in] table_props nlua_traverse_table() output. +/// @param[out] err Location where error will be saved. +static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, + const LuaTableProps table_props, + Error *err) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + Dictionary ret = { .size = table_props.string_keys_num, .items = NULL }; if (ret.size == 0) { lua_pop(lstate, 1); @@ -786,7 +879,7 @@ Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, Error *err) ret.items = xcalloc(ret.size, sizeof(*ret.items)); lua_pushnil(lstate); - for (size_t i = 0; lua_next(lstate, -2);) { + for (size_t i = 0; lua_next(lstate, -2) && i < ret.size;) { // stack: dict, key, value if (lua_type(lstate, -2) == LUA_TSTRING) { @@ -828,12 +921,14 @@ Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, Error *err) Dictionary nlua_pop_Dictionary(lua_State *lstate, Error *err) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - if (nlua_check_type(lstate, err, "dict")) { + const LuaTableProps table_props = nlua_check_type(lstate, err, + kObjectTypeDictionary); + if (table_props.type != kObjectTypeDictionary) { lua_pop(lstate, 1); return (Dictionary) { .size = 0, .items = NULL }; } - return nlua_pop_Dictionary_unchecked(lstate, err); + return nlua_pop_Dictionary_unchecked(lstate, table_props, err); } /// Convert lua table to object @@ -856,8 +951,16 @@ Object nlua_pop_Object(lua_State *lstate, Error *err) break; } case LUA_TNUMBER: { - ret.type = kObjectTypeInteger; - ret.data.integer = nlua_pop_Integer(lstate, err); + const lua_Number n = lua_tonumber(lstate, -1); + if (n > (lua_Number)API_INTEGER_MAX || n < (lua_Number)API_INTEGER_MIN + || ((lua_Number)((Integer)n)) != n) { + ret.type = kObjectTypeFloat; + ret.data.floating = (Float)n; + } else { + ret.type = kObjectTypeInteger; + ret.data.integer = (Integer)n; + } + lua_pop(lstate, 1); break; } case LUA_TBOOLEAN: { @@ -866,33 +969,26 @@ Object nlua_pop_Object(lua_State *lstate, Error *err) break; } case LUA_TTABLE: { - lua_getglobal(lstate, "vim"); - // stack: obj, vim -#define CHECK_TYPE(Type, key, vim_type) \ - lua_getfield(lstate, -1, "is_" #vim_type); \ - /* stack: obj, vim, checker */ \ - lua_pushvalue(lstate, -3); \ - /* stack: obj, vim, checker, obj */ \ - lua_call(lstate, 1, 1); \ - /* stack: obj, vim, result */ \ - if (lua_toboolean(lstate, -1)) { \ - lua_pop(lstate, 2); \ - /* stack: obj */ \ - ret.type = kObjectType##Type; \ - ret.data.key = nlua_pop_##Type(lstate, err); \ - /* stack: */ \ - break; \ - } \ - lua_pop(lstate, 1); \ - // stack: obj, vim - CHECK_TYPE(Float, floating, float) - CHECK_TYPE(Array, array, list) - CHECK_TYPE(Dictionary, dictionary, dict) -#undef CHECK_TYPE - lua_pop(lstate, 1); - // stack: obj - ret.type = kObjectTypeDictionary; - ret.data.dictionary = nlua_pop_Dictionary_unchecked(lstate, err); + const LuaTableProps table_props = nlua_traverse_table(lstate); + ret.type = table_props.type; + switch (table_props.type) { + case kObjectTypeArray: { + ret.data.array = nlua_pop_Array_unchecked(lstate, table_props, err); + break; + } + case kObjectTypeDictionary: { + ret.data.dictionary = nlua_pop_Dictionary_unchecked(lstate, + table_props, err); + break; + } + case kObjectTypeFloat: { + ret.data.floating = (Float)table_props.val; + break; + } + default: { + assert(false); + } + } break; } default: { @@ -923,3 +1019,50 @@ GENERATE_INDEX_FUNCTION(Window) GENERATE_INDEX_FUNCTION(Tabpage) #undef GENERATE_INDEX_FUNCTION + +/// Record some auxilary values in vim module +/// +/// Assumes that module table is on top of the stack. +/// +/// Recorded values: +/// +/// `vim.type_idx`: @see nlua_push_type_idx() +/// `vim.val_idx`: @see nlua_push_val_idx() +/// `vim.types`: table mapping possible values of `vim.type_idx` to string +/// names (i.e. `array`, `float`, `dictionary`) and back. +void nlua_init_types(lua_State *const lstate) +{ + LUA_PUSH_STATIC_STRING(lstate, "type_idx"); + nlua_push_type_idx(lstate); + lua_rawset(lstate, -3); + + LUA_PUSH_STATIC_STRING(lstate, "val_idx"); + nlua_push_val_idx(lstate); + lua_rawset(lstate, -3); + + LUA_PUSH_STATIC_STRING(lstate, "types"); + lua_createtable(lstate, 0, 3); + + LUA_PUSH_STATIC_STRING(lstate, "float"); + lua_pushnumber(lstate, (lua_Number)kObjectTypeFloat); + lua_rawset(lstate, -3); + lua_pushnumber(lstate, (lua_Number)kObjectTypeFloat); + LUA_PUSH_STATIC_STRING(lstate, "float"); + lua_rawset(lstate, -3); + + LUA_PUSH_STATIC_STRING(lstate, "array"); + lua_pushnumber(lstate, (lua_Number)kObjectTypeArray); + lua_rawset(lstate, -3); + lua_pushnumber(lstate, (lua_Number)kObjectTypeArray); + LUA_PUSH_STATIC_STRING(lstate, "array"); + lua_rawset(lstate, -3); + + LUA_PUSH_STATIC_STRING(lstate, "dictionary"); + lua_pushnumber(lstate, (lua_Number)kObjectTypeDictionary); + lua_rawset(lstate, -3); + lua_pushnumber(lstate, (lua_Number)kObjectTypeDictionary); + LUA_PUSH_STATIC_STRING(lstate, "dictionary"); + lua_rawset(lstate, -3); + + lua_rawset(lstate, -3); +} diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index 1a7b5f8915..33b6870ea0 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -145,6 +145,7 @@ static int nlua_state_init(lua_State *lstate) FUNC_ATTR_NONNULL_ALL return 1; } nlua_add_api_functions(lstate); + nlua_init_types(lstate); lua_setglobal(lstate, "vim"); return 0; } diff --git a/test/functional/lua_spec.lua b/test/functional/lua_spec.lua index fdfd9b4c2d..4f00189519 100644 --- a/test/functional/lua_spec.lua +++ b/test/functional/lua_spec.lua @@ -8,7 +8,7 @@ local clear = helpers.clear local funcs = helpers.funcs local meths = helpers.meths local exc_exec = helpers.exc_exec -local curbufmeths = helpers.curbufmeths +local redir_exec = helpers.redir_exec local function startswith(expected, actual) eq(expected, actual:sub(1, #expected)) @@ -139,5 +139,97 @@ describe('luaeval() function', function() -- The following should not crash: conversion error happens inside eq("Vim(call):E5101: Cannot convert given lua type", exc_exec('call luaeval("vim.api")')) + -- The following should not show internal error + eq("\nE5101: Cannot convert given lua type\n0", + redir_exec('echo luaeval("vim.api")')) end) + + it('correctly converts containers with type_idx', function() + eq(5, eval('type(luaeval("{[vim.type_idx]=vim.types.float, [vim.val_idx]=0}"))')) + eq(4, eval([[type(luaeval('{[vim.type_idx]=vim.types.dictionary}'))]])) + eq(3, eval([[type(luaeval('{[vim.type_idx]=vim.types.array}'))]])) + + eq({}, funcs.luaeval('{[vim.type_idx]=vim.types.array}')) + + -- Presence of type_idx makes Vim ignore some keys + eq({42}, funcs.luaeval('{[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}')) + eq({foo=2}, funcs.luaeval('{[vim.type_idx]=vim.types.dictionary, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}')) + eq(10, funcs.luaeval('{[vim.type_idx]=vim.types.float, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}')) + + -- The following should not crash + eq({}, funcs.luaeval('{[vim.type_idx]=vim.types.dictionary}')) + end) + + it('correctly converts from API objects', function() + eq(1, funcs.luaeval('vim.api.vim_eval("1")')) + eq('1', funcs.luaeval([[vim.api.vim_eval('"1"')]])) + eq({}, funcs.luaeval('vim.api.vim_eval("[]")')) + eq({}, funcs.luaeval('vim.api.vim_eval("{}")')) + eq(1, funcs.luaeval('vim.api.vim_eval("1.0")')) + eq(true, funcs.luaeval('vim.api.vim_eval("v:true")')) + eq(false, funcs.luaeval('vim.api.vim_eval("v:false")')) + eq(NIL, funcs.luaeval('vim.api.vim_eval("v:null")')) + + eq(0, eval([[type(luaeval('vim.api.vim_eval("1")'))]])) + eq(1, eval([[type(luaeval('vim.api.vim_eval("''1''")'))]])) + eq(3, eval([[type(luaeval('vim.api.vim_eval("[]")'))]])) + eq(4, eval([[type(luaeval('vim.api.vim_eval("{}")'))]])) + eq(5, eval([[type(luaeval('vim.api.vim_eval("1.0")'))]])) + eq(6, eval([[type(luaeval('vim.api.vim_eval("v:true")'))]])) + eq(6, eval([[type(luaeval('vim.api.vim_eval("v:false")'))]])) + eq(7, eval([[type(luaeval('vim.api.vim_eval("v:null")'))]])) + + eq({foo=42}, funcs.luaeval([[vim.api.vim_eval('{"foo": 42}')]])) + eq({42}, funcs.luaeval([[vim.api.vim_eval('[42]')]])) + + eq({foo={bar=42}, baz=50}, funcs.luaeval([[vim.api.vim_eval('{"foo": {"bar": 42}, "baz": 50}')]])) + eq({{42}, {}}, funcs.luaeval([=[vim.api.vim_eval('[[42], []]')]=])) + end) + + it('correctly converts to API objects', function() + eq(1, funcs.luaeval('vim.api._vim_id(1)')) + eq('1', funcs.luaeval('vim.api._vim_id("1")')) + eq({1}, funcs.luaeval('vim.api._vim_id({1})')) + eq({foo=1}, funcs.luaeval('vim.api._vim_id({foo=1})')) + eq(1.5, funcs.luaeval('vim.api._vim_id(1.5)')) + eq(true, funcs.luaeval('vim.api._vim_id(true)')) + eq(false, funcs.luaeval('vim.api._vim_id(false)')) + eq(NIL, funcs.luaeval('vim.api._vim_id(nil)')) + + eq(0, eval([[type(luaeval('vim.api._vim_id(1)'))]])) + eq(1, eval([[type(luaeval('vim.api._vim_id("1")'))]])) + eq(3, eval([[type(luaeval('vim.api._vim_id({1})'))]])) + eq(4, eval([[type(luaeval('vim.api._vim_id({foo=1})'))]])) + eq(5, eval([[type(luaeval('vim.api._vim_id(1.5)'))]])) + eq(6, eval([[type(luaeval('vim.api._vim_id(true)'))]])) + eq(6, eval([[type(luaeval('vim.api._vim_id(false)'))]])) + eq(7, eval([[type(luaeval('vim.api._vim_id(nil)'))]])) + + eq({foo=1, bar={42, {{baz=true}, 5}}}, funcs.luaeval('vim.api._vim_id({foo=1, bar={42, {{baz=true}, 5}}})')) + end) + + it('correctly converts containers with type_idx to API objects', function() + -- TODO: Similar tests with _vim_array_id and _vim_dictionary_id, that will + -- follow slightly different code paths. + eq(5, eval('type(luaeval("vim.api._vim_id({[vim.type_idx]=vim.types.float, [vim.val_idx]=0})"))')) + eq(4, eval([[type(luaeval('vim.api._vim_id({[vim.type_idx]=vim.types.dictionary})'))]])) + eq(3, eval([[type(luaeval('vim.api._vim_id({[vim.type_idx]=vim.types.array})'))]])) + + eq({}, funcs.luaeval('vim.api._vim_id({[vim.type_idx]=vim.types.array})')) + + -- Presence of type_idx makes Vim ignore some keys + -- FIXME + -- eq({42}, funcs.luaeval('vim.api._vim_id({[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})')) + eq({foo=2}, funcs.luaeval('vim.api._vim_id({[vim.type_idx]=vim.types.dictionary, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})')) + eq(10, funcs.luaeval('vim.api._vim_id({[vim.type_idx]=vim.types.float, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})')) + end) + -- TODO: check what happens when it errors out on second list item +--[[FIXME + [ + [ it('correctly converts self-containing containers', function() + [ meths.set_var('l', {}) + [ eval('add(l, l)') + [ eq(true, eval('luaeval("_A == _A[1]", l)')) + [ end) + ]] end) |