From e7bbd8256b8c701205389be431bbafd8743c72a9 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 4 Mar 2016 21:55:28 +0300 Subject: eval: Add luaeval function No tests yet, no documentation update, no :lua* stuff, no vim module. converter.c should also work with typval_T, not Object. Known problem: luaeval("1", {}) results in PANIC: unprotected error in call to Lua API (attempt to index a nil value) Ref #3823 --- src/nvim/CMakeLists.txt | 40 ++- src/nvim/eval.c | 43 +++ src/nvim/eval.lua | 1 + src/nvim/viml/executor/converter.c | 537 +++++++++++++++++++++++++++++++++++++ src/nvim/viml/executor/converter.h | 12 + src/nvim/viml/executor/executor.c | 270 +++++++++++++++++++ src/nvim/viml/executor/executor.h | 23 ++ src/nvim/viml/executor/vim.lua | 2 + 8 files changed, 919 insertions(+), 9 deletions(-) create mode 100644 src/nvim/viml/executor/converter.c create mode 100644 src/nvim/viml/executor/converter.h create mode 100644 src/nvim/viml/executor/executor.c create mode 100644 src/nvim/viml/executor/executor.h create mode 100644 src/nvim/viml/executor/vim.lua (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 22cf1f3a3d..a47a8e49c7 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -11,11 +11,10 @@ endif() endif() set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto) -set(DISPATCH_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gendispatch.lua) -file(GLOB API_HEADERS api/*.h) -file(GLOB MSGPACK_RPC_HEADERS msgpack_rpc/*.h) +set(MSGPACK_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genmsgpack.lua) set(API_METADATA ${PROJECT_BINARY_DIR}/api_metadata.mpack) set(FUNCS_DATA ${PROJECT_BINARY_DIR}/funcs_data.mpack) +set(MSGPACK_LUA_C_BINDINGS ${GENERATED_DIR}/msgpack_lua_c_bindings.generated.c) set(HEADER_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gendeclarations.lua) set(GENERATED_INCLUDES_DIR ${PROJECT_BINARY_DIR}/include) set(GENERATED_API_DISPATCH ${GENERATED_DIR}/api/private/dispatch_wrappers.generated.h) @@ -37,8 +36,14 @@ set(EVAL_DEFS_FILE ${PROJECT_SOURCE_DIR}/src/nvim/eval.lua) set(OPTIONS_LIST_FILE ${PROJECT_SOURCE_DIR}/src/nvim/options.lua) set(UNICODE_TABLES_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genunicodetables.lua) set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/unicode) -file(GLOB UNICODE_FILES ${UNICODE_DIR}/*.txt) set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h) +set(VIM_MODULE_FILE ${GENERATED_DIR}/viml/executor/vim_module.generated.h) +set(VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/viml/executor/vim.lua) +set(VIM_MODULE_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/generate_vim_module.lua) + +file(GLOB API_HEADERS api/*.h) +file(GLOB MSGPACK_RPC_HEADERS msgpack_rpc/*.h) +file(GLOB UNICODE_FILES ${UNICODE_DIR}/*.txt) include_directories(${GENERATED_DIR}) include_directories(${CACHED_GENERATED_DIR}) @@ -57,6 +62,8 @@ foreach(subdir tui event eval + viml + viml/executor ) if(${subdir} MATCHES "tui" AND NOT FEAT_TUI) continue() @@ -198,18 +205,30 @@ add_custom_command(OUTPUT ${GENERATED_UNICODE_TABLES} ${UNICODE_FILES} ) -add_custom_command(OUTPUT ${GENERATED_API_DISPATCH} ${GENERATED_FUNCS_METADATA} - ${API_METADATA} - COMMAND ${LUA_PRG} ${DISPATCH_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} - ${API_HEADERS} ${GENERATED_API_DISPATCH} +add_custom_command( + OUTPUT ${GENERATED_API_DISPATCH} ${GENERATED_FUNCS_METADATA} + ${API_METADATA} ${MSGPACK_LUA_C_BINDINGS} + COMMAND ${LUA_PRG} ${MSGPACK_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} + ${GENERATED_API_DISPATCH} ${GENERATED_FUNCS_METADATA} ${API_METADATA} + ${MSGPACK_LUA_C_BINDINGS} + ${API_HEADERS} DEPENDS ${API_HEADERS} ${MSGPACK_RPC_HEADERS} - ${DISPATCH_GENERATOR} + ${MSGPACK_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/api/dispatch_deprecated.lua ) +add_custom_command( + OUTPUT ${VIM_MODULE_FILE} + COMMAND ${LUA_PRG} ${VIM_MODULE_GENERATOR} ${VIM_MODULE_SOURCE} + ${VIM_MODULE_FILE} + DEPENDS + ${VIM_MODULE_GENERATOR} + ${VIM_MODULE_SOURCE} +) + list(APPEND NEOVIM_GENERATED_SOURCES "${PROJECT_BINARY_DIR}/config/auto/pathdef.c" "${GENERATED_API_DISPATCH}" @@ -219,6 +238,8 @@ list(APPEND NEOVIM_GENERATED_SOURCES "${GENERATED_EVENTS_NAMES_MAP}" "${GENERATED_OPTIONS}" "${GENERATED_UNICODE_TABLES}" + "${MSGPACK_LUA_C_BINDINGS}" + "${VIM_MODULE_FILE}" ) add_custom_command(OUTPUT ${GENERATED_EX_CMDS_ENUM} ${GENERATED_EX_CMDS_DEFS} @@ -270,6 +291,7 @@ list(APPEND NVIM_LINK_LIBRARIES ${LIBTERMKEY_LIBRARIES} ${UNIBILIUM_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + ${LUAJIT_LIBRARIES} ) if(UNIX) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index d49fcbf17e..c9141fbcbf 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -95,6 +95,7 @@ #include "nvim/lib/khash.h" #include "nvim/lib/queue.h" #include "nvim/eval/typval_encode.h" +#include "nvim/viml/executor/executor.h" #define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */ @@ -13376,6 +13377,48 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) } } +/// luaeval() function implementation +static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) + FUNC_ATTR_NONNULL_ALL +{ + char *const str = (char *) get_tv_string(&argvars[0]); + if (str == NULL) { + return; + } + + Object arg; + if (argvars[1].v_type == VAR_UNKNOWN) { + arg = NIL; + } else { + arg = vim_to_object(&argvars[1]); + } + + // TODO(ZyX-I): Create function which converts lua objects directly to VimL + // objects, not to API objects. + Error err; + String err_str; + Object ret = executor_eval_lua(cstr_as_string(str), arg, &err, &err_str); + if (err.set) { + if (err_str.size) { + EMSG3(_("E971: Failed to eval lua string: %s (%s)"), err.msg, + err_str.data); + } else { + EMSG2(_("E971: Failed to eval lua string: %s"), err.msg); + } + } + + api_free_string(err_str); + + if (!err.set) { + if (!object_to_vim(ret, rettv, &err)) { + EMSG2(_("E972: Failed to convert resulting API object to VimL: %s"), + err.msg); + } + } + + api_free_object(ret); +} + /* * "map()" function */ diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index e3c5981b32..9db90ce05d 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -193,6 +193,7 @@ return { localtime={}, log={args=1, func="float_op_wrapper", data="&log"}, log10={args=1, func="float_op_wrapper", data="&log10"}, + luaeval={args={1, 2}}, map={args=2}, maparg={args={1, 4}}, mapcheck={args={1, 3}}, diff --git a/src/nvim/viml/executor/converter.c b/src/nvim/viml/executor/converter.c new file mode 100644 index 0000000000..2105beb08a --- /dev/null +++ b/src/nvim/viml/executor/converter.c @@ -0,0 +1,537 @@ +#include +#include +#include +#include + +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/func_attr.h" +#include "nvim/memory.h" +#include "nvim/assert.h" + +#include "nvim/viml/executor/converter.h" +#include "nvim/viml/executor/executor.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/executor/converter.c.generated.h" +#endif + +#define NLUA_PUSH_IDX(lstate, type, idx) \ + do { \ + STATIC_ASSERT(sizeof(type) <= sizeof(lua_Number), \ + "Number sizes do not match"); \ + const type src = idx; \ + lua_Number tgt; \ + memset(&tgt, 0, sizeof(tgt)); \ + memcpy(&tgt, &src, sizeof(src)); \ + lua_pushnumber(lstate, tgt); \ + } while (0) + +#define NLUA_POP_IDX(lstate, type, stack_idx, idx) \ + do { \ + STATIC_ASSERT(sizeof(type) <= sizeof(lua_Number), \ + "Number sizes do not match"); \ + const lua_Number src = lua_tonumber(lstate, stack_idx); \ + type tgt; \ + memcpy(&tgt, &src, sizeof(tgt)); \ + idx = tgt; \ + } while (0) + +/// Push value which is a type index +/// +/// Used for all “typed” tables: i.e. for all tables which represent VimL +/// values. +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); +} + +/// Push value which is a value index +/// +/// Used for tables which represent scalar values, like float value. +static inline void nlua_push_val_idx(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + lua_pushnumber(lstate, (lua_Number) 0); +} + +/// Push type +/// +/// 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) +{ + lua_getglobal(lstate, "vim"); + lua_getfield(lstate, -1, "types"); + lua_remove(lstate, -2); + lua_getfield(lstate, -1, type); + lua_remove(lstate, -2); +} + +/// Create lua table which has an entry that determines its VimL type +/// +/// @param[out] lstate Lua state. +/// @param[in] narr Number of “array” entries to be populated later. +/// @param[in] nrec Number of “dictionary” entries to be populated later. +/// @param[in] type Type of the table. +static inline void nlua_create_typed_table(lua_State *lstate, + const size_t narr, + const size_t nrec, + const char *const type) + FUNC_ATTR_NONNULL_ALL +{ + lua_createtable(lstate, (int) narr, (int) (1 + nrec)); + nlua_push_type_idx(lstate); + nlua_push_type(lstate, type); + lua_rawset(lstate, -3); +} + + +/// Convert given String to lua string +/// +/// Leaves converted string on top of the stack. +void nlua_push_String(lua_State *lstate, const String s) + FUNC_ATTR_NONNULL_ALL +{ + lua_pushlstring(lstate, s.data, s.size); +} + +/// Convert given Integer to lua number +/// +/// Leaves converted number on top of the stack. +void nlua_push_Integer(lua_State *lstate, const Integer n) + FUNC_ATTR_NONNULL_ALL +{ + lua_pushnumber(lstate, (lua_Number) n); +} + +/// Convert given Float to lua table +/// +/// Leaves converted table on top of the stack. +void nlua_push_Float(lua_State *lstate, const Float f) + FUNC_ATTR_NONNULL_ALL +{ + nlua_create_typed_table(lstate, 0, 1, "float"); + nlua_push_val_idx(lstate); + lua_pushnumber(lstate, (lua_Number) f); + lua_rawset(lstate, -3); +} + +/// Convert given Float to lua boolean +/// +/// Leaves converted value on top of the stack. +void nlua_push_Boolean(lua_State *lstate, const Boolean b) + FUNC_ATTR_NONNULL_ALL +{ + 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); + 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); + lua_rawset(lstate, -3); + } +} + +/// Convert given Array to lua table +/// +/// Leaves converted table on top of the stack. +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); + for (size_t i = 0; i < array.size; i++) { + nlua_push_Object(lstate, array.items[i]); + lua_rawseti(lstate, -3, (int) i + 1); + } +} + +#define GENERATE_INDEX_FUNCTION(type) \ +void nlua_push_##type(lua_State *lstate, const type item) \ + FUNC_ATTR_NONNULL_ALL \ +{ \ + NLUA_PUSH_IDX(lstate, type, item); \ +} + +GENERATE_INDEX_FUNCTION(Buffer) +GENERATE_INDEX_FUNCTION(Window) +GENERATE_INDEX_FUNCTION(Tabpage) + +#undef GENERATE_INDEX_FUNCTION + +/// Convert given Object to lua value +/// +/// Leaves converted value on top of the stack. +void nlua_push_Object(lua_State *lstate, const Object obj) + FUNC_ATTR_NONNULL_ALL +{ + switch (obj.type) { + case kObjectTypeNil: { + lua_pushnil(lstate); + break; + } +#define ADD_TYPE(type, data_key) \ + case kObjectType##type: { \ + nlua_push_##type(lstate, obj.data.data_key); \ + break; \ + } + ADD_TYPE(Boolean, boolean) + ADD_TYPE(Integer, integer) + ADD_TYPE(Float, floating) + ADD_TYPE(String, string) + ADD_TYPE(Array, array) + ADD_TYPE(Dictionary, dictionary) +#undef ADD_TYPE +#define ADD_REMOTE_TYPE(type) \ + case kObjectType##type: { \ + nlua_push_##type(lstate, (type)obj.data.integer); \ + break; \ + } + ADD_REMOTE_TYPE(Buffer) + ADD_REMOTE_TYPE(Window) + ADD_REMOTE_TYPE(Tabpage) +#undef ADD_REMOTE_TYPE + } +} + + +/// Convert lua value to string +/// +/// Always pops one value from the stack. +String nlua_pop_String(lua_State *lstate, Error *err) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + String ret; + + ret.data = (char *) lua_tolstring(lstate, -1, &(ret.size)); + + if (ret.data == NULL) { + lua_pop(lstate, 1); + set_api_error("Expected lua string", err); + return (String) { .size = 0, .data = NULL }; + } + + ret.data = xmemdupz(ret.data, ret.size); + lua_pop(lstate, 1); + + return ret; +} + +/// Convert lua value to integer +/// +/// Always pops one value from the stack. +Integer nlua_pop_Integer(lua_State *lstate, Error *err) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + Integer ret = 0; + + if (!lua_isnumber(lstate, -1)) { + lua_pop(lstate, 1); + set_api_error("Expected lua integer", err); + return ret; + } + ret = (Integer) lua_tonumber(lstate, -1); + lua_pop(lstate, 1); + + return ret; +} + +/// Convert lua value to boolean +/// +/// Always pops one value from the stack. +Boolean nlua_pop_Boolean(lua_State *lstate, Error *err) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + Boolean ret = lua_toboolean(lstate, -1); + lua_pop(lstate, 1); + 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 +{ + if (lua_type(lstate, -1) != LUA_TTABLE) { + set_api_error("Expected lua table", err); + return true; + } + + 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; + } + lua_pop(lstate, 2); + + return false; +} + +/// Convert lua table to float +/// +/// Always pops one value from the stack. +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")) { + 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; +} + +/// Convert lua table to 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 +{ + 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); + } + + if (ret.size == 0) { + lua_pop(lstate, 1); + return ret; + } + + ret.items = xcalloc(ret.size, sizeof(*ret.items)); + for (size_t i = 1; i <= ret.size; i++) { + Object val; + + lua_rawgeti(lstate, -1, (int) i); + + val = nlua_pop_Object(lstate, err); + if (err->set) { + ret.size = i; + lua_pop(lstate, 1); + api_free_array(ret); + return (Array) { .size = 0, .items = NULL }; + } + ret.items[i - 1] = val; + } + lua_pop(lstate, 1); + + return ret; +} + +/// Convert lua table to dictionary +/// +/// 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) + 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); + } + + if (ret.size == 0) { + lua_pop(lstate, 1); + return ret; + } + ret.items = xcalloc(ret.size, sizeof(*ret.items)); + + lua_pushnil(lstate); + for (size_t i = 0; lua_next(lstate, -2);) { + // stack: dict, key, value + + if (lua_type(lstate, -2) == LUA_TSTRING) { + lua_pushvalue(lstate, -2); + // stack: dict, key, value, key + + ret.items[i].key = nlua_pop_String(lstate, err); + // stack: dict, key, value + + if (!err->set) { + ret.items[i].value = nlua_pop_Object(lstate, err); + // stack: dict, key + } else { + lua_pop(lstate, 1); + // stack: dict, key + } + + if (err->set) { + ret.size = i; + api_free_dictionary(ret); + lua_pop(lstate, 2); + // stack: + return (Dictionary) { .size = 0, .items = NULL }; + } + i++; + } else { + lua_pop(lstate, 1); + // stack: dict, key + } + } + lua_pop(lstate, 1); + + return ret; +} + +/// Convert lua table to dictionary +/// +/// Always pops one value from the stack. +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")) { + lua_pop(lstate, 1); + return (Dictionary) { .size = 0, .items = NULL }; + } + + return nlua_pop_Dictionary_unchecked(lstate, err); +} + +/// Convert lua table to object +/// +/// Always pops one value from the stack. +Object nlua_pop_Object(lua_State *lstate, Error *err) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + Object ret = { .type = kObjectTypeNil }; + + switch (lua_type(lstate, -1)) { + case LUA_TNIL: { + ret.type = kObjectTypeNil; + lua_pop(lstate, 1); + break; + } + case LUA_TSTRING: { + ret.type = kObjectTypeString; + ret.data.string = nlua_pop_String(lstate, err); + break; + } + case LUA_TNUMBER: { + ret.type = kObjectTypeInteger; + ret.data.integer = nlua_pop_Integer(lstate, err); + break; + } + case LUA_TBOOLEAN: { + ret.type = kObjectTypeBoolean; + ret.data.boolean = nlua_pop_Boolean(lstate, 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); + break; + } + default: { + lua_pop(lstate, 1); + set_api_error("Cannot convert given lua type", err); + break; + } + } + if (err->set) { + ret.type = kObjectTypeNil; + } + + return ret; +} + +#define GENERATE_INDEX_FUNCTION(type) \ +type nlua_pop_##type(lua_State *lstate, Error *err) \ + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \ +{ \ + type ret; \ + NLUA_POP_IDX(lstate, type, -1, ret); \ + lua_pop(lstate, 1); \ + return ret; \ +} + +GENERATE_INDEX_FUNCTION(Buffer) +GENERATE_INDEX_FUNCTION(Window) +GENERATE_INDEX_FUNCTION(Tabpage) + +#undef GENERATE_INDEX_FUNCTION diff --git a/src/nvim/viml/executor/converter.h b/src/nvim/viml/executor/converter.h new file mode 100644 index 0000000000..e11d0cef19 --- /dev/null +++ b/src/nvim/viml/executor/converter.h @@ -0,0 +1,12 @@ +#ifndef NVIM_VIML_EXECUTOR_CONVERTER_H +#define NVIM_VIML_EXECUTOR_CONVERTER_H + +#include +#include +#include "nvim/api/private/defs.h" +#include "nvim/func_attr.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/executor/converter.h.generated.h" +#endif +#endif // NVIM_VIML_EXECUTOR_CONVERTER_H diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c new file mode 100644 index 0000000000..6f1e847649 --- /dev/null +++ b/src/nvim/viml/executor/executor.c @@ -0,0 +1,270 @@ +#include +#include +#include + +#include "nvim/misc1.h" +#include "nvim/getchar.h" +#include "nvim/garray.h" +#include "nvim/func_attr.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/vim.h" +#include "nvim/vim.h" +#include "nvim/message.h" + +#include "nvim/viml/executor/executor.h" +#include "nvim/viml/executor/converter.h" + +typedef struct { + Error err; + String lua_err_str; +} LuaError; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/executor/vim_module.generated.h" +# include "viml/executor/executor.c.generated.h" +#endif + +/// Name of the run code for use in messages +#define NLUA_EVAL_NAME "" + +/// Call C function which does not expect any arguments +/// +/// @param function Called function +/// @param numret Number of returned arguments +#define NLUA_CALL_C_FUNCTION_0(lstate, function, numret) \ + do { \ + lua_pushcfunction(lstate, &function); \ + lua_call(lstate, 0, numret); \ + } while (0) +/// Call C function which expects four arguments +/// +/// @param function Called function +/// @param numret Number of returned arguments +/// @param a… Supplied argument (should be a void* pointer) +#define NLUA_CALL_C_FUNCTION_3(lstate, function, numret, a1, a2, a3) \ + do { \ + lua_pushcfunction(lstate, &function); \ + lua_pushlightuserdata(lstate, a1); \ + lua_pushlightuserdata(lstate, a2); \ + lua_pushlightuserdata(lstate, a3); \ + lua_call(lstate, 3, numret); \ + } while (0) +/// Call C function which expects five arguments +/// +/// @param function Called function +/// @param numret Number of returned arguments +/// @param a… Supplied argument (should be a void* pointer) +#define NLUA_CALL_C_FUNCTION_4(lstate, function, numret, a1, a2, a3, a4) \ + do { \ + lua_pushcfunction(lstate, &function); \ + lua_pushlightuserdata(lstate, a1); \ + lua_pushlightuserdata(lstate, a2); \ + lua_pushlightuserdata(lstate, a3); \ + lua_pushlightuserdata(lstate, a4); \ + lua_call(lstate, 4, numret); \ + } while (0) + +static void set_lua_error(lua_State *lstate, LuaError *lerr) + FUNC_ATTR_NONNULL_ALL +{ + const char *const str = lua_tolstring(lstate, -1, &lerr->lua_err_str.size); + lerr->lua_err_str.data = xmemdupz(str, lerr->lua_err_str.size); + lua_pop(lstate, 1); + + // FIXME? More specific error? + set_api_error("Error while executing lua code", &lerr->err); +} + +/// Compare two strings, ignoring case +/// +/// Expects two values on the stack: compared strings. Returns one of the +/// following numbers: 0, -1 or 1. +/// +/// Does no error handling: never call it with non-string or with some arguments +/// omitted. +static int nlua_stricmp(lua_State *lstate) FUNC_ATTR_NONNULL_ALL +{ + const char *s1 = luaL_checklstring(lstate, 1, NULL); + const char *s2 = luaL_checklstring(lstate, 2, NULL); + const int ret = STRICMP(s1, s2); + lua_pop(lstate, 2); + lua_pushnumber(lstate, (lua_Number) ((ret > 0) - (ret < 0))); + return 1; +} + +/// Evaluate lua string +/// +/// Expects three values on the stack: string to evaluate, pointer to the +/// location where result is saved, pointer to the location where error is +/// saved. Always returns nothing (from the lua point of view). +static int nlua_exec_lua_string(lua_State *lstate) FUNC_ATTR_NONNULL_ALL +{ + String *str = (String *) lua_touserdata(lstate, 1); + Object *obj = (Object *) lua_touserdata(lstate, 2); + LuaError *lerr = (LuaError *) lua_touserdata(lstate, 3); + lua_pop(lstate, 3); + + if (luaL_loadbuffer(lstate, str->data, str->size, NLUA_EVAL_NAME)) { + set_lua_error(lstate, lerr); + return 0; + } + if (lua_pcall(lstate, 0, 1, 0)) { + set_lua_error(lstate, lerr); + return 0; + } + *obj = nlua_pop_Object(lstate, &lerr->err); + return 0; +} + +/// Initialize lua interpreter state +/// +/// Called by lua interpreter itself to initialize state. +static int nlua_state_init(lua_State *lstate) FUNC_ATTR_NONNULL_ALL +{ + lua_pushcfunction(lstate, &nlua_stricmp); + lua_setglobal(lstate, "stricmp"); + if (luaL_dostring(lstate, (char *) &vim_module[0])) { + LuaError lerr; + set_lua_error(lstate, &lerr); + return 1; + } + nlua_add_api_functions(lstate); + lua_setglobal(lstate, "vim"); + return 0; +} + +/// Initialize lua interpreter +/// +/// Crashes NeoVim if initialization fails. Should be called once per lua +/// interpreter instance. +static lua_State *init_lua(void) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT +{ + lua_State *lstate = luaL_newstate(); + if (lstate == NULL) { + EMSG(_("E970: Failed to initialize lua interpreter")); + preserve_exit(); + } + luaL_openlibs(lstate); + NLUA_CALL_C_FUNCTION_0(lstate, nlua_state_init, 0); + return lstate; +} + +static Object exec_lua_string(lua_State *lstate, String str, LuaError *lerr) + FUNC_ATTR_NONNULL_ALL +{ + Object ret = { kObjectTypeNil, { false } }; + NLUA_CALL_C_FUNCTION_3(lstate, nlua_exec_lua_string, 0, &str, &ret, lerr); + return ret; +} + +static lua_State *global_lstate = NULL; + +/// Execute lua string +/// +/// Used for :lua. +/// +/// @param[in] str String to execute. +/// @param[out] err Location where error will be saved. +/// @param[out] err_str Location where lua error string will be saved, if any. +/// +/// @return Result of the execution. +Object executor_exec_lua(String str, Error *err, String *err_str) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (global_lstate == NULL) { + global_lstate = init_lua(); + } + + LuaError lerr = { + .err = { .set = false }, + .lua_err_str = STRING_INIT, + }; + + Object ret = exec_lua_string(global_lstate, str, &lerr); + + *err = lerr.err; + *err_str = lerr.lua_err_str; + + return ret; +} + +/// Evaluate lua string +/// +/// Used for luaeval(). Expects three values on the stack: +/// +/// 1. String to evaluate. +/// 2. _A value. +/// 3. Pointer to location where result is saved. +/// 4. Pointer to location where error will be saved. +/// +/// @param[in,out] lstate Lua interpreter state. +static int nlua_eval_lua_string(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + String *str = (String *) lua_touserdata(lstate, 1); + Object *arg = (Object *) lua_touserdata(lstate, 2); + Object *ret = (Object *) lua_touserdata(lstate, 3); + LuaError *lerr = (LuaError *) lua_touserdata(lstate, 4); + + garray_T str_ga; + ga_init(&str_ga, 1, 80); +#define EVALHEADER "local _A=select(1,...) return " + ga_concat_len(&str_ga, EVALHEADER, sizeof(EVALHEADER) - 1); +#undef EVALHEADER + ga_concat_len(&str_ga, str->data, str->size); + if (luaL_loadbuffer(lstate, str_ga.ga_data, (size_t) str_ga.ga_len, + NLUA_EVAL_NAME)) { + set_lua_error(lstate, lerr); + return 0; + } + ga_clear(&str_ga); + + nlua_push_Object(lstate, *arg); + if (lua_pcall(lstate, 1, 1, 0)) { + set_lua_error(lstate, lerr); + return 0; + } + *ret = nlua_pop_Object(lstate, &lerr->err); + + return 0; +} + +static Object eval_lua_string(lua_State *lstate, String str, Object arg, + LuaError *lerr) + FUNC_ATTR_NONNULL_ALL +{ + Object ret = { kObjectTypeNil, { false } }; + NLUA_CALL_C_FUNCTION_4(lstate, nlua_eval_lua_string, 0, + &str, &arg, &ret, lerr); + return ret; +} + +/// Evaluate lua string +/// +/// Used for luaeval(). +/// +/// @param[in] str String to execute. +/// @param[out] err Location where error will be saved. +/// @param[out] err_str Location where lua error string will be saved, if any. +/// +/// @return Result of the execution. +Object executor_eval_lua(String str, Object arg, Error *err, String *err_str) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (global_lstate == NULL) { + global_lstate = init_lua(); + } + + LuaError lerr = { + .err = { .set = false }, + .lua_err_str = STRING_INIT, + }; + + Object ret = eval_lua_string(global_lstate, str, arg, &lerr); + + *err = lerr.err; + *err_str = lerr.lua_err_str; + + return ret; +} diff --git a/src/nvim/viml/executor/executor.h b/src/nvim/viml/executor/executor.h new file mode 100644 index 0000000000..85cb3550e7 --- /dev/null +++ b/src/nvim/viml/executor/executor.h @@ -0,0 +1,23 @@ +#ifndef NVIM_VIML_EXECUTOR_EXECUTOR_H +#define NVIM_VIML_EXECUTOR_EXECUTOR_H + +#include + +#include "nvim/api/private/defs.h" +#include "nvim/func_attr.h" + +// Generated by msgpack-gen.lua +void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; + +#define set_api_error(s, err) \ + do { \ + Error *err_ = (err); \ + err_->type = kErrorTypeException; \ + err_->set = true; \ + memcpy(&err_->msg[0], s, sizeof(s)); \ + } while (0) + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/executor/executor.h.generated.h" +#endif +#endif // NVIM_VIML_EXECUTOR_EXECUTOR_H diff --git a/src/nvim/viml/executor/vim.lua b/src/nvim/viml/executor/vim.lua new file mode 100644 index 0000000000..8d1c5bdf4f --- /dev/null +++ b/src/nvim/viml/executor/vim.lua @@ -0,0 +1,2 @@ +-- TODO(ZyX-I): Create compatibility layer. +return {} -- cgit From f551df17f33e7f38b7e5693eb923118ca1542d27 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Jul 2016 08:03:14 +0300 Subject: viml/executor: Directly generate typval_T values Note: this will *still* crash when using API in cases similar to the one described in first commit. Just it needs different code to reproduce. --- src/nvim/eval.c | 70 +++---- src/nvim/eval/decode.c | 138 +++++++------ src/nvim/viml/executor/converter.c | 388 +++++++++++++++++++++++++++++++++++++ src/nvim/viml/executor/converter.h | 3 + src/nvim/viml/executor/executor.c | 159 ++++++++------- 5 files changed, 572 insertions(+), 186 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c9141fbcbf..248383d2e1 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6480,28 +6480,44 @@ static void dict_free_dict(dict_T *d) { xfree(d); } -void dict_free(dict_T *d) { +void dict_free(dict_T *d) +{ if (!in_free_unref_items) { dict_free_contents(d); dict_free_dict(d); } } -/* - * Allocate a Dictionary item. - * The "key" is copied to the new item. - * Note that the value of the item "di_tv" still needs to be initialized! - */ -dictitem_T *dictitem_alloc(char_u *key) FUNC_ATTR_NONNULL_RET +/// Allocate a dictionary item +/// +/// @note that the value of the item di_tv still needs to be initialized. +/// +/// @param[in] key Item key. +/// @param[in] len Key length. +/// +/// @return [allocated] New dictionary item. +dictitem_T *dictitem_alloc_len(const char *const key, const size_t len) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT { - dictitem_T *di = xmalloc(offsetof(dictitem_T, di_key) + STRLEN(key) + 1); -#ifndef __clang_analyzer__ - STRCPY(di->di_key, key); -#endif + dictitem_T *const di = xmallocz(offsetof(dictitem_T, di_key) + len); + memcpy(di->di_key, key, len); di->di_flags = DI_FLAGS_ALLOC; return di; } +/// Allocate a dictionary item +/// +/// @note that the value of the item di_tv still needs to be initialized. +/// +/// @param[in] key Item key, NUL-terminated string. +/// +/// @return [allocated] New dictionary item. +dictitem_T *dictitem_alloc(const char_u *const key) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT +{ + return dictitem_alloc_len((const char *)key, STRLEN(key)); +} + /* * Make a copy of a Dictionary item. */ @@ -13386,37 +13402,7 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - Object arg; - if (argvars[1].v_type == VAR_UNKNOWN) { - arg = NIL; - } else { - arg = vim_to_object(&argvars[1]); - } - - // TODO(ZyX-I): Create function which converts lua objects directly to VimL - // objects, not to API objects. - Error err; - String err_str; - Object ret = executor_eval_lua(cstr_as_string(str), arg, &err, &err_str); - if (err.set) { - if (err_str.size) { - EMSG3(_("E971: Failed to eval lua string: %s (%s)"), err.msg, - err_str.data); - } else { - EMSG2(_("E971: Failed to eval lua string: %s"), err.msg); - } - } - - api_free_string(err_str); - - if (!err.set) { - if (!object_to_vim(ret, rettv, &err)) { - EMSG2(_("E972: Failed to convert resulting API object to VimL: %s"), - err.msg); - } - } - - api_free_object(ret); + executor_eval_lua(cstr_as_string(str), &argvars[1], rettv); } /* diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 43e9f76c0f..cb5a624f7b 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -7,6 +7,7 @@ #include "nvim/eval/encode.h" #include "nvim/ascii.h" #include "nvim/message.h" +#include "nvim/globals.h" #include "nvim/charset.h" // vim_str2nr #include "nvim/lib/kvec.h" #include "nvim/vim.h" // OK, FAIL @@ -218,6 +219,69 @@ static inline int json_decoder_pop(ValuesStackItem obj, } \ } while (0) +/// Create a new special dictionary that ought to represent a MAP +/// +/// @param[out] ret_tv Address where new special dictionary is saved. +/// +/// @return [allocated] list which should contain key-value pairs. Return value +/// may be safely ignored. +list_T *decode_create_map_special_dict(typval_T *const ret_tv) + FUNC_ATTR_NONNULL_ALL +{ + list_T *const list = list_alloc(); + list->lv_refcount++; + create_special_dict(ret_tv, kMPMap, ((typval_T) { + .v_type = VAR_LIST, + .v_lock = VAR_UNLOCKED, + .vval = { .v_list = list }, + })); + return list; +} + +/// Convert char* string to typval_T +/// +/// Depending on whether string has (no) NUL bytes, it may use a special +/// dictionary or decode string to VAR_STRING. +/// +/// @param[in] s String to decode. +/// @param[in] len String length. +/// @param[in] hasnul Whether string has NUL byte, not or it was not yet +/// determined. +/// @param[in] binary If true, save special string type as kMPBinary, +/// otherwise kMPString. +/// +/// @return Decoded string. +typval_T decode_string(const char *const s, const size_t len, + const TriState hasnul, const bool binary) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + assert(s != NULL || len == 0); + const bool really_hasnul = (hasnul == kNone + ? memchr(s, NUL, len) != NULL + : (bool)hasnul); + if (really_hasnul) { + list_T *const list = list_alloc(); + list->lv_refcount++; + typval_T tv; + create_special_dict(&tv, binary ? kMPBinary : kMPString, ((typval_T) { + .v_type = VAR_LIST, + .v_lock = VAR_UNLOCKED, + .vval = { .v_list = list }, + })); + if (encode_list_write((void *)list, s, len) == -1) { + clear_tv(&tv); + return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; + } + return tv; + } else { + return (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval = { .v_string = xmemdupz(s, len) }, + }; + } +} + /// Parse JSON double-quoted string /// /// @param[in] conv Defines conversion necessary to convert UTF-8 string to @@ -428,29 +492,13 @@ static inline int parse_json_string(vimconv_T *const conv, str = new_str; str_end = new_str + str_len; } - if (hasnul) { - typval_T obj; - list_T *const list = list_alloc(); - list->lv_refcount++; - create_special_dict(&obj, kMPString, ((typval_T) { - .v_type = VAR_LIST, - .v_lock = VAR_UNLOCKED, - .vval = { .v_list = list }, - })); - if (encode_list_write((void *) list, str, (size_t) (str_end - str)) - == -1) { - clear_tv(&obj); - goto parse_json_string_fail; - } - xfree(str); - POP(obj, true); - } else { - *str_end = NUL; - POP(((typval_T) { - .v_type = VAR_STRING, - .vval = { .v_string = (char_u *) str }, - }), false); + typval_T obj; + obj = decode_string(str, (size_t)(str_end - str), hasnul ? kTrue : kFalse, + false); + if (obj.v_type == VAR_UNKNOWN) { + goto parse_json_string_fail; } + POP(obj, obj.v_type != VAR_STRING); goto parse_json_string_ret; parse_json_string_fail: ret = FAIL; @@ -827,13 +875,7 @@ json_decode_string_cycle_start: list_T *val_list = NULL; if (next_map_special) { next_map_special = false; - val_list = list_alloc(); - val_list->lv_refcount++; - create_special_dict(&tv, kMPMap, ((typval_T) { - .v_type = VAR_LIST, - .v_lock = VAR_UNLOCKED, - .vval = { .v_list = val_list }, - })); + val_list = decode_create_map_special_dict(&tv); } else { dict_T *dict = dict_alloc(); dict->dv_refcount++; @@ -980,37 +1022,15 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) break; } case MSGPACK_OBJECT_STR: { - list_T *const list = list_alloc(); - list->lv_refcount++; - create_special_dict(rettv, kMPString, ((typval_T) { - .v_type = VAR_LIST, - .v_lock = VAR_UNLOCKED, - .vval = { .v_list = list }, - })); - if (encode_list_write((void *) list, mobj.via.str.ptr, mobj.via.str.size) - == -1) { + *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kTrue, false); + if (rettv->v_type == VAR_UNKNOWN) { return FAIL; } break; } case MSGPACK_OBJECT_BIN: { - if (memchr(mobj.via.bin.ptr, NUL, mobj.via.bin.size) == NULL) { - *rettv = (typval_T) { - .v_type = VAR_STRING, - .v_lock = VAR_UNLOCKED, - .vval = { .v_string = xmemdupz(mobj.via.bin.ptr, mobj.via.bin.size) }, - }; - break; - } - list_T *const list = list_alloc(); - list->lv_refcount++; - create_special_dict(rettv, kMPBinary, ((typval_T) { - .v_type = VAR_LIST, - .v_lock = VAR_UNLOCKED, - .vval = { .v_list = list }, - })); - if (encode_list_write((void *) list, mobj.via.bin.ptr, mobj.via.bin.size) - == -1) { + *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kNone, true); + if (rettv->v_type == VAR_UNKNOWN) { return FAIL; } break; @@ -1067,13 +1087,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) } break; msgpack_to_vim_generic_map: {} - list_T *const list = list_alloc(); - list->lv_refcount++; - create_special_dict(rettv, kMPMap, ((typval_T) { - .v_type = VAR_LIST, - .v_lock = VAR_UNLOCKED, - .vval = { .v_list = list }, - })); + list_T *const list = decode_create_map_special_dict(rettv); for (size_t i = 0; i < mobj.via.map.size; i++) { list_T *const kv_pair = list_alloc(); list_append_list(list, kv_pair); diff --git a/src/nvim/viml/executor/converter.c b/src/nvim/viml/executor/converter.c index 2105beb08a..123659aa5c 100644 --- a/src/nvim/viml/executor/converter.c +++ b/src/nvim/viml/executor/converter.c @@ -2,12 +2,24 @@ #include #include #include +#include +#include #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/func_attr.h" #include "nvim/memory.h" #include "nvim/assert.h" +// FIXME: vim.h is not actually needed, but otherwise it states MAXPATHL is +// redefined +#include "nvim/vim.h" +#include "nvim/globals.h" +#include "nvim/message.h" +#include "nvim/eval_defs.h" +#include "nvim/ascii.h" + +#include "nvim/lib/kvec.h" +#include "nvim/eval/decode.h" #include "nvim/viml/executor/converter.h" #include "nvim/viml/executor/executor.h" @@ -16,6 +28,382 @@ # include "viml/executor/converter.c.generated.h" #endif +/// Helper structure for nlua_pop_typval +typedef struct { + typval_T *tv; ///< Location where conversion result is saved. + bool container; ///< True if tv is a container. + bool special; ///< If true then tv is a _VAL part of special dictionary + ///< that represents mapping. +} PopStackItem; + +/// Convert lua object to VimL typval_T +/// +/// Should pop exactly one value from lua stack. +/// +/// @param lstate Lua state. +/// @param[out] ret_tv Where to put the result. +/// +/// @return `true` in case of success, `false` in case of failure. Error is +/// reported automatically. +bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) +{ + bool ret = true; +#ifndef NDEBUG + const int initial_size = lua_gettop(lstate); +#endif + kvec_t(PopStackItem) stack = KV_INITIAL_VALUE; + kv_push(stack, ((PopStackItem) { ret_tv, false, false })); + while (ret && kv_size(stack)) { + if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { + emsgf(_("E1502: Lua failed to grow stack to %i"), lua_gettop(lstate) + 3); + ret = false; + break; + } + PopStackItem cur = kv_pop(stack); + 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); + size_t len; + const char *s = lua_tolstring(lstate, -2, &len); + if (cur.special) { + list_T *const kv_pair = list_alloc(); + list_append_list(cur.tv->vval.v_list, kv_pair); + listitem_T *const key = listitem_alloc(); + key->li_tv = decode_string(s, len, kTrue, false); + list_append(kv_pair, key); + if (key->li_tv.v_type == VAR_UNKNOWN) { + ret = false; + list_unref(kv_pair); + continue; + } + listitem_T *const val = listitem_alloc(); + list_append(kv_pair, val); + kv_push(stack, cur); + cur = (PopStackItem) { &val->li_tv, false, false }; + } else { + dictitem_T *const di = dictitem_alloc_len(s, len); + if (dict_add(cur.tv->vval.v_dict, di) == FAIL) { + assert(false); + } + kv_push(stack, cur); + cur = (PopStackItem) { &di->di_tv, false, false }; + } + } else { + lua_pop(lstate, 1); + continue; + } + } else { + assert(cur.tv->v_type == VAR_LIST); + lua_rawgeti(lstate, -1, cur.tv->vval.v_list->lv_len + 1); + if (lua_isnil(lstate, -1)) { + lua_pop(lstate, 1); + lua_pop(lstate, 1); + continue; + } + listitem_T *li = listitem_alloc(); + list_append(cur.tv->vval.v_list, li); + kv_push(stack, cur); + cur = (PopStackItem) { &li->li_tv, false, false }; + } + } + assert(!cur.container); + memset(cur.tv, 0, sizeof(*cur.tv)); + switch (lua_type(lstate, -1)) { + case LUA_TNIL: { + cur.tv->v_type = VAR_SPECIAL; + cur.tv->vval.v_special = kSpecialVarNull; + break; + } + case LUA_TBOOLEAN: { + cur.tv->v_type = VAR_SPECIAL; + cur.tv->vval.v_special = (lua_toboolean(lstate, -1) + ? kSpecialVarTrue + : kSpecialVarFalse); + break; + } + case LUA_TSTRING: { + size_t len; + const char *s = lua_tolstring(lstate, -1, &len); + *cur.tv = decode_string(s, len, kNone, true); + if (cur.tv->v_type == VAR_UNKNOWN) { + ret = false; + } + break; + } + case LUA_TNUMBER: { + const lua_Number n = lua_tonumber(lstate, -1); + if (n > (lua_Number)VARNUMBER_MAX || n < (lua_Number)VARNUMBER_MIN + || ((lua_Number)((varnumber_T)n)) != n) { + cur.tv->v_type = VAR_FLOAT; + cur.tv->vval.v_float = (float_T)n; + } else { + cur.tv->v_type = VAR_NUMBER; + cur.tv->vval.v_number = (varnumber_T)n; + } + 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; + } + 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 > maxidx) { + maxidx = idx; + } + } + break; + } + default: { + has_other = true; + 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++; + } + 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; + } + default: { + EMSG(_("E5101: Cannot convert given lua type")); + ret = false; + break; + } + } + if (!cur.container) { + lua_pop(lstate, 1); + } + } + kv_destroy(stack); + if (!ret) { + clear_tv(ret_tv); + memset(ret_tv, 0, sizeof(*ret_tv)); + lua_pop(lstate, lua_gettop(lstate) - initial_size + 1); + } + assert(lua_gettop(lstate) == initial_size - 1); + return ret; +} + +#define TYPVAL_ENCODE_ALLOW_SPECIALS true + +#define TYPVAL_ENCODE_CONV_NIL(tv) \ + lua_pushnil(lstate) + +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ + lua_pushboolean(lstate, (bool)(num)) + +#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ + lua_pushnumber(lstate, (lua_Number)(num)) + +#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER + +#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ + TYPVAL_ENCODE_CONV_NUMBER(tv, flt) + +#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \ + lua_pushlstring(lstate, (const char *)(str), (len)) + +#define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING + +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \ + 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_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(tv) \ + lua_createtable(lstate, 0, 0) + +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ + TYPVAL_ENCODE_CONV_EMPTY_LIST() + +#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ + do { \ + if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { \ + emsgf(_("E5102: Lua failed to grow stack to %i"), \ + lua_gettop(lstate) + 3); \ + return false; \ + } \ + lua_createtable(lstate, (int)(len), 0); \ + lua_pushnumber(lstate, 1); \ + } while (0) + +#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) + +#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \ + do { \ + lua_Number idx = lua_tonumber(lstate, -2); \ + lua_rawset(lstate, -3); \ + lua_pushnumber(lstate, idx + 1); \ + } while (0) + +#define TYPVAL_ENCODE_CONV_LIST_END(tv) \ + lua_rawset(lstate, -3) + +#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ + do { \ + if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { \ + emsgf(_("E5102: Lua failed to grow stack to %i"), \ + lua_gettop(lstate) + 3); \ + return false; \ + } \ + lua_createtable(lstate, 0, (int)(len)); \ + } while (0) + +#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, kv_pair) + +#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) + +#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) + +#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \ + lua_rawset(lstate, -3) + +#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \ + TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) + +#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ + do { \ + for (size_t backref = kv_size(*mpstack); backref; backref--) { \ + const MPConvStackVal mpval = kv_A(*mpstack, backref - 1); \ + if (mpval.type == conv_type) { \ + if (conv_type == kMPConvDict \ + ? (void *) mpval.data.d.dict == (void *) (val) \ + : (void *) mpval.data.l.list == (void *) (val)) { \ + lua_pushvalue(lstate, \ + 1 - ((int)((kv_size(*mpstack) - backref + 1) * 2))); \ + break; \ + } \ + } \ + } \ + } while (0) + +#define TYPVAL_ENCODE_SCOPE static +#define TYPVAL_ENCODE_NAME lua +#define TYPVAL_ENCODE_FIRST_ARG_TYPE lua_State *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME lstate +#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_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_SPECIAL_DICT_KEY_CHECK +#undef TYPVAL_ENCODE_CONV_LIST_END +#undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS +#undef TYPVAL_ENCODE_CONV_RECURSE +#undef TYPVAL_ENCODE_ALLOW_SPECIALS + +/// Convert VimL typval_T to lua value +/// +/// Should leave single value in lua stack. May only fail if lua failed to grow +/// stack. +/// +/// @param lstate Lua interpreter state. +/// @param[in] tv typval_T to convert. +/// +/// @return true in case of success, false otherwise. +bool nlua_push_typval(lua_State *lstate, typval_T *const tv) +{ + const int initial_size = lua_gettop(lstate); + if (!lua_checkstack(lstate, initial_size + 1)) { + emsgf(_("E1502: Lua failed to grow stack to %i"), initial_size + 4); + return false; + } + if (encode_vim_to_lua(lstate, tv, "nlua_push_typval argument") == FAIL) { + return false; + } + assert(lua_gettop(lstate) == initial_size + 1); + return true; +} + #define NLUA_PUSH_IDX(lstate, type, idx) \ do { \ STATIC_ASSERT(sizeof(type) <= sizeof(lua_Number), \ diff --git a/src/nvim/viml/executor/converter.h b/src/nvim/viml/executor/converter.h index e11d0cef19..dbbaaebf6b 100644 --- a/src/nvim/viml/executor/converter.h +++ b/src/nvim/viml/executor/converter.h @@ -3,8 +3,11 @@ #include #include +#include + #include "nvim/api/private/defs.h" #include "nvim/func_attr.h" +#include "nvim/eval.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/executor/converter.h.generated.h" diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index 6f1e847649..a9d05bcd31 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -36,7 +36,19 @@ typedef struct { lua_pushcfunction(lstate, &function); \ lua_call(lstate, 0, numret); \ } while (0) -/// Call C function which expects four arguments +/// Call C function which expects two arguments +/// +/// @param function Called function +/// @param numret Number of returned arguments +/// @param a… Supplied argument (should be a void* pointer) +#define NLUA_CALL_C_FUNCTION_2(lstate, function, numret, a1, a2) \ + do { \ + lua_pushcfunction(lstate, &function); \ + lua_pushlightuserdata(lstate, a1); \ + lua_pushlightuserdata(lstate, a2); \ + lua_call(lstate, 2, numret); \ + } while (0) +/// Call C function which expects three arguments /// /// @param function Called function /// @param numret Number of returned arguments @@ -64,15 +76,19 @@ typedef struct { lua_call(lstate, 4, numret); \ } while (0) -static void set_lua_error(lua_State *lstate, LuaError *lerr) +/// Convert lua error into a Vim error message +/// +/// @param lstate Lua interpreter state. +/// @param[in] msg Message base, must contain one `%s`. +static void nlua_error(lua_State *const lstate, const char *const msg) FUNC_ATTR_NONNULL_ALL { - const char *const str = lua_tolstring(lstate, -1, &lerr->lua_err_str.size); - lerr->lua_err_str.data = xmemdupz(str, lerr->lua_err_str.size); - lua_pop(lstate, 1); + size_t len; + const char *const str = lua_tolstring(lstate, -1, &len); + + EMSG2(msg, str); - // FIXME? More specific error? - set_api_error("Error while executing lua code", &lerr->err); + lua_pop(lstate, 1); } /// Compare two strings, ignoring case @@ -94,25 +110,26 @@ static int nlua_stricmp(lua_State *lstate) FUNC_ATTR_NONNULL_ALL /// Evaluate lua string /// -/// Expects three values on the stack: string to evaluate, pointer to the -/// location where result is saved, pointer to the location where error is -/// saved. Always returns nothing (from the lua point of view). +/// Expects two values on the stack: string to evaluate, pointer to the +/// location where result is saved. Always returns nothing (from the lua point +/// of view). static int nlua_exec_lua_string(lua_State *lstate) FUNC_ATTR_NONNULL_ALL { - String *str = (String *) lua_touserdata(lstate, 1); - Object *obj = (Object *) lua_touserdata(lstate, 2); - LuaError *lerr = (LuaError *) lua_touserdata(lstate, 3); - lua_pop(lstate, 3); + String *str = (String *)lua_touserdata(lstate, 1); + typval_T *ret_tv = (typval_T *)lua_touserdata(lstate, 2); + lua_pop(lstate, 2); if (luaL_loadbuffer(lstate, str->data, str->size, NLUA_EVAL_NAME)) { - set_lua_error(lstate, lerr); + nlua_error(lstate, _("E5104: Error while creating lua chunk: %s")); return 0; } if (lua_pcall(lstate, 0, 1, 0)) { - set_lua_error(lstate, lerr); + nlua_error(lstate, _("E5105: Error while calling lua chunk: %s")); + return 0; + } + if (!nlua_pop_typval(lstate, ret_tv)) { return 0; } - *obj = nlua_pop_Object(lstate, &lerr->err); return 0; } @@ -124,8 +141,7 @@ static int nlua_state_init(lua_State *lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, &nlua_stricmp); lua_setglobal(lstate, "stricmp"); if (luaL_dostring(lstate, (char *) &vim_module[0])) { - LuaError lerr; - set_lua_error(lstate, &lerr); + nlua_error(lstate, _("E5106: Error while creating vim module: %s")); return 1; } nlua_add_api_functions(lstate); @@ -150,14 +166,6 @@ static lua_State *init_lua(void) return lstate; } -static Object exec_lua_string(lua_State *lstate, String str, LuaError *lerr) - FUNC_ATTR_NONNULL_ALL -{ - Object ret = { kObjectTypeNil, { false } }; - NLUA_CALL_C_FUNCTION_3(lstate, nlua_exec_lua_string, 0, &str, &ret, lerr); - return ret; -} - static lua_State *global_lstate = NULL; /// Execute lua string @@ -165,28 +173,17 @@ static lua_State *global_lstate = NULL; /// Used for :lua. /// /// @param[in] str String to execute. -/// @param[out] err Location where error will be saved. -/// @param[out] err_str Location where lua error string will be saved, if any. +/// @param[out] ret_tv Location where result will be saved. /// /// @return Result of the execution. -Object executor_exec_lua(String str, Error *err, String *err_str) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +void executor_exec_lua(String str, typval_T *ret_tv) + FUNC_ATTR_NONNULL_ALL { if (global_lstate == NULL) { global_lstate = init_lua(); } - LuaError lerr = { - .err = { .set = false }, - .lua_err_str = STRING_INIT, - }; - - Object ret = exec_lua_string(global_lstate, str, &lerr); - - *err = lerr.err; - *err_str = lerr.lua_err_str; - - return ret; + NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_lua_string, 0, &str, ret_tv); } /// Evaluate lua string @@ -196,75 +193,73 @@ Object executor_exec_lua(String str, Error *err, String *err_str) /// 1. String to evaluate. /// 2. _A value. /// 3. Pointer to location where result is saved. -/// 4. Pointer to location where error will be saved. /// /// @param[in,out] lstate Lua interpreter state. static int nlua_eval_lua_string(lua_State *lstate) FUNC_ATTR_NONNULL_ALL { - String *str = (String *) lua_touserdata(lstate, 1); - Object *arg = (Object *) lua_touserdata(lstate, 2); - Object *ret = (Object *) lua_touserdata(lstate, 3); - LuaError *lerr = (LuaError *) lua_touserdata(lstate, 4); + String *str = (String *)lua_touserdata(lstate, 1); + typval_T *arg = (typval_T *)lua_touserdata(lstate, 2); + typval_T *ret_tv = (typval_T *)lua_touserdata(lstate, 3); + lua_pop(lstate, 3); garray_T str_ga; ga_init(&str_ga, 1, 80); -#define EVALHEADER "local _A=select(1,...) return " - ga_concat_len(&str_ga, EVALHEADER, sizeof(EVALHEADER) - 1); +#define EVALHEADER "local _A=select(1,...) return (" + const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str->size + 1; + char *lcmd; + if (lcmd_len < IOSIZE) { + lcmd = (char *)IObuff; + } else { + lcmd = xmalloc(lcmd_len); + } + memcpy(lcmd, EVALHEADER, sizeof(EVALHEADER) - 1); + memcpy(lcmd + sizeof(EVALHEADER) - 1, str->data, str->size); + lcmd[lcmd_len - 1] = ')'; #undef EVALHEADER - ga_concat_len(&str_ga, str->data, str->size); - if (luaL_loadbuffer(lstate, str_ga.ga_data, (size_t) str_ga.ga_len, - NLUA_EVAL_NAME)) { - set_lua_error(lstate, lerr); + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { + nlua_error(lstate, + _("E5107: Error while creating lua chunk for luaeval(): %s")); return 0; } - ga_clear(&str_ga); + if (lcmd != (char *)IObuff) { + xfree(lcmd); + } - nlua_push_Object(lstate, *arg); + if (arg == NULL || arg->v_type == VAR_UNKNOWN) { + lua_pushnil(lstate); + } else { + nlua_push_typval(lstate, arg); + } if (lua_pcall(lstate, 1, 1, 0)) { - set_lua_error(lstate, lerr); + nlua_error(lstate, + _("E5108: Error while calling lua chunk for luaeval(): %s")); + return 0; + } + if (!nlua_pop_typval(lstate, ret_tv)) { return 0; } - *ret = nlua_pop_Object(lstate, &lerr->err); return 0; } -static Object eval_lua_string(lua_State *lstate, String str, Object arg, - LuaError *lerr) - FUNC_ATTR_NONNULL_ALL -{ - Object ret = { kObjectTypeNil, { false } }; - NLUA_CALL_C_FUNCTION_4(lstate, nlua_eval_lua_string, 0, - &str, &arg, &ret, lerr); - return ret; -} - /// Evaluate lua string /// /// Used for luaeval(). /// /// @param[in] str String to execute. -/// @param[out] err Location where error will be saved. -/// @param[out] err_str Location where lua error string will be saved, if any. +/// @param[in] arg Second argument to `luaeval()`. +/// @param[out] ret_tv Location where result will be saved. /// /// @return Result of the execution. -Object executor_eval_lua(String str, Object arg, Error *err, String *err_str) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +void executor_eval_lua(const String str, typval_T *const arg, + typval_T *const ret_tv) + FUNC_ATTR_NONNULL_ALL { if (global_lstate == NULL) { global_lstate = init_lua(); } - LuaError lerr = { - .err = { .set = false }, - .lua_err_str = STRING_INIT, - }; - - Object ret = eval_lua_string(global_lstate, str, arg, &lerr); - - *err = lerr.err; - *err_str = lerr.lua_err_str; - - return ret; + NLUA_CALL_C_FUNCTION_3(global_lstate, nlua_eval_lua_string, 0, + (void *)&str, arg, ret_tv); } -- cgit From a4dc8de0739d8e9e910d786a9b6fbfbc162aee9c Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 10 Jul 2016 21:49:59 +0300 Subject: *: Silence linter --- src/nvim/eval.c | 2 +- src/nvim/viml/executor/converter.c | 20 ++++++++++---------- src/nvim/viml/executor/executor.c | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 248383d2e1..c349a601c6 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -13397,7 +13397,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) FUNC_ATTR_NONNULL_ALL { - char *const str = (char *) get_tv_string(&argvars[0]); + const char *const str = (const char *)get_tv_string(&argvars[0]); if (str == NULL) { return; } diff --git a/src/nvim/viml/executor/converter.c b/src/nvim/viml/executor/converter.c index 123659aa5c..fb7bbd51eb 100644 --- a/src/nvim/viml/executor/converter.c +++ b/src/nvim/viml/executor/converter.c @@ -334,8 +334,8 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) const MPConvStackVal mpval = kv_A(*mpstack, backref - 1); \ if (mpval.type == conv_type) { \ if (conv_type == kMPConvDict \ - ? (void *) mpval.data.d.dict == (void *) (val) \ - : (void *) mpval.data.l.list == (void *) (val)) { \ + ? (void *)mpval.data.d.dict == (void *)(val) \ + : (void *)mpval.data.l.list == (void *)(val)) { \ lua_pushvalue(lstate, \ 1 - ((int)((kv_size(*mpstack) - backref + 1) * 2))); \ break; \ @@ -450,7 +450,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_pushnumber(lstate, (lua_Number)0); } /// Push type @@ -480,7 +480,7 @@ static inline void nlua_create_typed_table(lua_State *lstate, const char *const type) FUNC_ATTR_NONNULL_ALL { - lua_createtable(lstate, (int) narr, (int) (1 + nrec)); + lua_createtable(lstate, (int)narr, (int)(1 + nrec)); nlua_push_type_idx(lstate); nlua_push_type(lstate, type); lua_rawset(lstate, -3); @@ -502,7 +502,7 @@ void nlua_push_String(lua_State *lstate, const String s) void nlua_push_Integer(lua_State *lstate, const Integer n) FUNC_ATTR_NONNULL_ALL { - lua_pushnumber(lstate, (lua_Number) n); + lua_pushnumber(lstate, (lua_Number)n); } /// Convert given Float to lua table @@ -513,7 +513,7 @@ void nlua_push_Float(lua_State *lstate, const Float f) { nlua_create_typed_table(lstate, 0, 1, "float"); nlua_push_val_idx(lstate); - lua_pushnumber(lstate, (lua_Number) f); + lua_pushnumber(lstate, (lua_Number)f); lua_rawset(lstate, -3); } @@ -558,7 +558,7 @@ void nlua_push_Array(lua_State *lstate, const Array array) nlua_add_locks_table(lstate); 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, -3, (int)i + 1); } } @@ -619,7 +619,7 @@ String nlua_pop_String(lua_State *lstate, Error *err) { String ret; - ret.data = (char *) lua_tolstring(lstate, -1, &(ret.size)); + ret.data = (char *)lua_tolstring(lstate, -1, &(ret.size)); if (ret.data == NULL) { lua_pop(lstate, 1); @@ -646,7 +646,7 @@ Integer nlua_pop_Integer(lua_State *lstate, Error *err) set_api_error("Expected lua integer", err); return ret; } - ret = (Integer) lua_tonumber(lstate, -1); + ret = (Integer)lua_tonumber(lstate, -1); lua_pop(lstate, 1); return ret; @@ -744,7 +744,7 @@ Array nlua_pop_Array(lua_State *lstate, Error *err) for (size_t i = 1; i <= ret.size; i++) { Object val; - lua_rawgeti(lstate, -1, (int) i); + lua_rawgeti(lstate, -1, (int)i); val = nlua_pop_Object(lstate, err); if (err->set) { diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index a9d05bcd31..1a7b5f8915 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -104,7 +104,7 @@ static int nlua_stricmp(lua_State *lstate) FUNC_ATTR_NONNULL_ALL const char *s2 = luaL_checklstring(lstate, 2, NULL); const int ret = STRICMP(s1, s2); lua_pop(lstate, 2); - lua_pushnumber(lstate, (lua_Number) ((ret > 0) - (ret < 0))); + lua_pushnumber(lstate, (lua_Number)((ret > 0) - (ret < 0))); return 1; } @@ -140,7 +140,7 @@ static int nlua_state_init(lua_State *lstate) FUNC_ATTR_NONNULL_ALL { lua_pushcfunction(lstate, &nlua_stricmp); lua_setglobal(lstate, "stricmp"); - if (luaL_dostring(lstate, (char *) &vim_module[0])) { + if (luaL_dostring(lstate, (char *)&vim_module[0])) { nlua_error(lstate, _("E5106: Error while creating vim module: %s")); return 1; } -- cgit From ed3115bd26047c9b125798d9cb56d09b155a243b Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 11 Jul 2016 20:42:27 +0300 Subject: executor: Make sure it works with API values --- src/nvim/api/vim.c | 16 +- src/nvim/eval.c | 2 +- src/nvim/viml/executor/converter.c | 547 +++++++++++++++++++++++-------------- src/nvim/viml/executor/executor.c | 1 + 4 files changed, 362 insertions(+), 204 deletions(-) (limited to 'src') 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; } -- cgit From 3fa4ca81880bc5113c32a89de965ce593e9b001f Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 12 Jul 2016 19:04:12 +0300 Subject: executor/converter: Fix conversion of self-containing containers --- src/nvim/viml/executor/converter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/viml/executor/converter.c b/src/nvim/viml/executor/converter.c index c127b87738..319e07bdbc 100644 --- a/src/nvim/viml/executor/converter.c +++ b/src/nvim/viml/executor/converter.c @@ -446,7 +446,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) ? (void *)mpval.data.d.dict == (void *)(val) \ : (void *)mpval.data.l.list == (void *)(val)) { \ lua_pushvalue(lstate, \ - 1 - ((int)((kv_size(*mpstack) - backref + 1) * 2))); \ + -((int)((kv_size(*mpstack) - backref + 1) * 2))); \ break; \ } \ } \ -- cgit From 9297d941e2f1576006d77bfd6391cecc3bea37b0 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 12 Jul 2016 19:20:57 +0300 Subject: executor/converter: Fix how maxidx is determined --- src/nvim/viml/executor/converter.c | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/executor/converter.c b/src/nvim/viml/executor/converter.c index 319e07bdbc..c8d6848006 100644 --- a/src/nvim/viml/executor/converter.c +++ b/src/nvim/viml/executor/converter.c @@ -54,8 +54,8 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) 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. + size_t other_keys_num = 0; // Number of keys that are not string, integral + // or type keys. LuaTableProps ret; memset(&ret, 0, sizeof(ret)); if (!lua_checkstack(lstate, lua_gettop(lstate) + 2)) { @@ -79,7 +79,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) 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; + other_keys_num++; } else { const size_t idx = (size_t)n; if (idx > ret.maxidx) { @@ -99,10 +99,10 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) has_type_key = true; ret.type = (ObjectType)n; } else { - has_other = true; + other_keys_num++; } } else { - has_other = true; + other_keys_num++; } } else { has_val_key = true; @@ -114,7 +114,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) break; } default: { - has_other = true; + other_keys_num++; break; } } @@ -125,10 +125,33 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) if (ret.type == kObjectTypeFloat && (!has_val_key || val_type != LUA_TNUMBER)) { ret.type = kObjectTypeNil; + } else if (ret.type == kObjectTypeArray) { + // Determine what is the last number in a *sequence* of keys. + // This condition makes sure that Neovim will not crash when it gets table + // {[vim.type_idx]=vim.types.array, [SIZE_MAX]=1}: without it maxidx will + // be SIZE_MAX, with this condition it should be zero and [SIZE_MAX] key + // should be ignored. + if (ret.maxidx != 0 + && ret.maxidx != (tsize + - has_type_key + - other_keys_num + - has_val_key + - ret.string_keys_num)) { + for (ret.maxidx = 0;; ret.maxidx++) { + lua_rawgeti(lstate, -1, (int)ret.maxidx + 1); + if (lua_isnil(lstate, -1)) { + lua_pop(lstate, 1); + break; + } + lua_pop(lstate, 1); + } + } } } else { if (tsize == 0 - || (tsize == ret.maxidx && !has_other && ret.string_keys_num == 0)) { + || (tsize == ret.maxidx + && other_keys_num == 0 + && ret.string_keys_num == 0)) { ret.type = kObjectTypeArray; } else if (ret.string_keys_num == tsize) { ret.type = kObjectTypeDictionary; -- cgit From 425d348f0f9f680a44af31fc3cecd20a07374bb5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 16 Jul 2016 00:34:24 +0300 Subject: executor/converter: Make nlua_pop_Object not recursive --- src/nvim/api/private/defs.h | 2 +- src/nvim/api/vim.c | 54 +++++++-- src/nvim/viml/executor/converter.c | 243 ++++++++++++++++++++++++++----------- 3 files changed, 211 insertions(+), 88 deletions(-) (limited to 'src') diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 223aab09dc..86b549cb44 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -76,10 +76,10 @@ typedef struct { } Dictionary; typedef enum { + kObjectTypeNil = 0, kObjectTypeBuffer, kObjectTypeWindow, kObjectTypeTabpage, - kObjectTypeNil, kObjectTypeBoolean, kObjectTypeInteger, kObjectTypeFloat, diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 24959e9a59..5d862628cb 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -197,19 +197,6 @@ Object nvim_eval(String expr, Error *err) return rv; } -/// 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. @@ -843,3 +830,44 @@ static void write_msg(String message, bool to_err) --no_wait_return; msg_end(); } + +// Functions used for testing purposes + +/// 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; +} + +/// Returns array given as argument +/// +/// This API function is used for testing. One should not rely on its presence +/// in plugins. +/// +/// @param[in] arr Array to return. +/// +/// @return its argument. +Array _vim_id_array(Array arr) +{ + return arr; +} + +/// Returns dictionary given as argument +/// +/// This API function is used for testing. One should not rely on its presence +/// in plugins. +/// +/// @param[in] dct Dictionary to return. +/// +/// @return its argument. +Dictionary _vim_id_dictionary(Dictionary dct) +{ + return dct; +} diff --git a/src/nvim/viml/executor/converter.c b/src/nvim/viml/executor/converter.c index c8d6848006..6c9b42b38c 100644 --- a/src/nvim/viml/executor/converter.c +++ b/src/nvim/viml/executor/converter.c @@ -58,7 +58,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) // or type keys. LuaTableProps ret; memset(&ret, 0, sizeof(ret)); - if (!lua_checkstack(lstate, lua_gettop(lstate) + 2)) { + if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { emsgf(_("E1502: Lua failed to grow stack to %i"), lua_gettop(lstate) + 2); ret.type = kObjectTypeNil; return ret; @@ -168,7 +168,7 @@ typedef struct { bool container; ///< True if tv is a container. bool special; ///< If true then tv is a _VAL part of special dictionary ///< that represents mapping. -} PopStackItem; +} TVPopStackItem; /// Convert lua object to VimL typval_T /// @@ -182,18 +182,16 @@ typedef struct { bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) { bool ret = true; -#ifndef NDEBUG const int initial_size = lua_gettop(lstate); -#endif - kvec_t(PopStackItem) stack = KV_INITIAL_VALUE; - kv_push(stack, ((PopStackItem) { ret_tv, false, false })); + kvec_t(TVPopStackItem) stack = KV_INITIAL_VALUE; + kv_push(stack, ((TVPopStackItem) { ret_tv, false, false })); while (ret && kv_size(stack)) { if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { emsgf(_("E1502: Lua failed to grow stack to %i"), lua_gettop(lstate) + 3); ret = false; break; } - PopStackItem cur = kv_pop(stack); + TVPopStackItem cur = kv_pop(stack); if (cur.container) { if (cur.special || cur.tv->v_type == VAR_DICT) { assert(cur.tv->v_type == (cur.special ? VAR_LIST : VAR_DICT)); @@ -222,14 +220,14 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) listitem_T *const val = listitem_alloc(); list_append(kv_pair, val); kv_push(stack, cur); - cur = (PopStackItem) { &val->li_tv, false, false }; + cur = (TVPopStackItem) { &val->li_tv, false, false }; } else { dictitem_T *const di = dictitem_alloc_len(s, len); if (dict_add(cur.tv->vval.v_dict, di) == FAIL) { assert(false); } kv_push(stack, cur); - cur = (PopStackItem) { &di->di_tv, false, false }; + cur = (TVPopStackItem) { &di->di_tv, false, false }; } } else { lua_pop(lstate, 1); @@ -239,14 +237,13 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) assert(cur.tv->v_type == VAR_LIST); lua_rawgeti(lstate, -1, cur.tv->vval.v_list->lv_len + 1); if (lua_isnil(lstate, -1)) { - lua_pop(lstate, 1); - lua_pop(lstate, 1); + lua_pop(lstate, 2); continue; } listitem_T *li = listitem_alloc(); list_append(cur.tv->vval.v_list, li); kv_push(stack, cur); - cur = (PopStackItem) { &li->li_tv, false, false }; + cur = (TVPopStackItem) { &li->li_tv, false, false }; } } assert(!cur.container); @@ -294,14 +291,10 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) 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.tv->v_type = VAR_LIST; + cur.tv->vval.v_list = list_alloc(); + cur.tv->vval.v_list->lv_refcount++; + if (table_props.maxidx != 0) { cur.container = true; kv_push(stack, cur); } @@ -525,7 +518,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) bool nlua_push_typval(lua_State *lstate, typval_T *const tv) { const int initial_size = lua_gettop(lstate); - if (!lua_checkstack(lstate, initial_size + 1)) { + if (!lua_checkstack(lstate, initial_size + 2)) { emsgf(_("E1502: Lua failed to grow stack to %i"), initial_size + 4); return false; } @@ -873,7 +866,6 @@ Array nlua_pop_Array(lua_State *lstate, Error *err) { 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 }; } @@ -954,76 +946,179 @@ Dictionary nlua_pop_Dictionary(lua_State *lstate, Error *err) return nlua_pop_Dictionary_unchecked(lstate, table_props, err); } +/// Helper structure for nlua_pop_Object +typedef struct { + Object *obj; ///< Location where conversion result is saved. + bool container; ///< True if tv is a container. +} ObjPopStackItem; + /// Convert lua table to object /// /// Always pops one value from the stack. -Object nlua_pop_Object(lua_State *lstate, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +Object nlua_pop_Object(lua_State *const lstate, Error *const err) { - Object ret = { .type = kObjectTypeNil }; - - switch (lua_type(lstate, -1)) { - case LUA_TNIL: { - ret.type = kObjectTypeNil; - lua_pop(lstate, 1); - break; - } - case LUA_TSTRING: { - ret.type = kObjectTypeString; - ret.data.string = nlua_pop_String(lstate, err); + Object ret = NIL; + const int initial_size = lua_gettop(lstate); + kvec_t(ObjPopStackItem) stack = KV_INITIAL_VALUE; + kv_push(stack, ((ObjPopStackItem) { &ret, false })); + while (!err->set && kv_size(stack)) { + if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { + api_set_error(err, Exception, "Lua failed to grow stack"); break; } - case LUA_TNUMBER: { - 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; + ObjPopStackItem cur = kv_pop(stack); + if (cur.container) { + if (cur.obj->type == kObjectTypeDictionary) { + // stack: …, dict, key + if (cur.obj->data.dictionary.size + == cur.obj->data.dictionary.capacity) { + lua_pop(lstate, 2); + continue; + } + bool next_key_found = false; + while (lua_next(lstate, -2)) { + // stack: …, dict, new key, val + if (lua_type(lstate, -2) == LUA_TSTRING) { + next_key_found = true; + break; + } + lua_pop(lstate, 1); + // stack: …, dict, new key + } + if (next_key_found) { + // stack: …, dict, new key, val + size_t len; + const char *s = lua_tolstring(lstate, -2, &len); + const size_t idx = cur.obj->data.dictionary.size++; + cur.obj->data.dictionary.items[idx].key = (String) { + .data = xmemdupz(s, len), + .size = len, + }; + kv_push(stack, cur); + cur = (ObjPopStackItem) { + .obj = &cur.obj->data.dictionary.items[idx].value, + .container = false, + }; + } else { + // stack: …, dict + lua_pop(lstate, 1); + // stack: … + continue; + } } else { - ret.type = kObjectTypeInteger; - ret.data.integer = (Integer)n; - } - lua_pop(lstate, 1); - break; - } - case LUA_TBOOLEAN: { - ret.type = kObjectTypeBoolean; - ret.data.boolean = nlua_pop_Boolean(lstate, err); - break; - } - case LUA_TTABLE: { - 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; + if (cur.obj->data.array.size == cur.obj->data.array.capacity) { + lua_pop(lstate, 1); + continue; } - case kObjectTypeDictionary: { - ret.data.dictionary = nlua_pop_Dictionary_unchecked(lstate, - table_props, err); - break; + const size_t idx = cur.obj->data.array.size++; + lua_rawgeti(lstate, -1, (int)idx + 1); + if (lua_isnil(lstate, -1)) { + lua_pop(lstate, 2); + continue; } - case kObjectTypeFloat: { - ret.data.floating = (Float)table_props.val; - break; + kv_push(stack, cur); + cur = (ObjPopStackItem) { + .obj = &cur.obj->data.array.items[idx], + .container = false, + }; + } + } + assert(!cur.container); + *cur.obj = NIL; + switch (lua_type(lstate, -1)) { + case LUA_TNIL: { + break; + } + case LUA_TBOOLEAN: { + *cur.obj = BOOLEAN_OBJ(lua_toboolean(lstate, -1)); + break; + } + case LUA_TSTRING: { + size_t len; + const char *s = lua_tolstring(lstate, -1, &len); + *cur.obj = STRING_OBJ(((String) { + .data = xmemdupz(s, len), + .size = len, + })); + break; + } + case LUA_TNUMBER: { + 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) { + *cur.obj = FLOATING_OBJ((Float)n); + } else { + *cur.obj = INTEGER_OBJ((Integer)n); } - default: { - assert(false); + break; + } + case LUA_TTABLE: { + const LuaTableProps table_props = nlua_traverse_table(lstate); + + switch (table_props.type) { + case kObjectTypeArray: { + *cur.obj = ARRAY_OBJ(((Array) { + .items = NULL, + .size = 0, + .capacity = 0, + })); + if (table_props.maxidx != 0) { + cur.obj->data.array.items = + xcalloc(table_props.maxidx, + sizeof(cur.obj->data.array.items[0])); + cur.obj->data.array.capacity = table_props.maxidx; + cur.container = true; + kv_push(stack, cur); + } + break; + } + case kObjectTypeDictionary: { + *cur.obj = DICTIONARY_OBJ(((Dictionary) { + .items = NULL, + .size = 0, + .capacity = 0, + })); + if (table_props.string_keys_num != 0) { + cur.obj->data.dictionary.items = + xcalloc(table_props.string_keys_num, + sizeof(cur.obj->data.dictionary.items[0])); + cur.obj->data.dictionary.capacity = table_props.string_keys_num; + cur.container = true; + kv_push(stack, cur); + lua_pushnil(lstate); + } + break; + } + case kObjectTypeFloat: { + *cur.obj = FLOATING_OBJ((Float)table_props.val); + break; + } + case kObjectTypeNil: { + api_set_error(err, Validation, "Cannot convert given lua table"); + break; + } + default: { + assert(false); + } } + break; + } + default: { + api_set_error(err, Validation, "Cannot convert given lua type"); + break; } - break; } - default: { + if (!cur.container) { lua_pop(lstate, 1); - set_api_error("Cannot convert given lua type", err); - break; } } + kv_destroy(stack); if (err->set) { - ret.type = kObjectTypeNil; + api_free_object(ret); + ret = NIL; + lua_pop(lstate, lua_gettop(lstate) - initial_size + 1); } - + assert(lua_gettop(lstate) == initial_size - 1); return ret; } -- cgit From 7a013e93e0364f78a2bc04eadaaeeaa689d0258a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 16 Jul 2016 00:48:25 +0300 Subject: executor/converter: Make it possible to supply `{}` to Dictionary arg --- src/nvim/viml/executor/converter.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/executor/converter.c b/src/nvim/viml/executor/converter.c index 6c9b42b38c..a6399500f2 100644 --- a/src/nvim/viml/executor/converter.c +++ b/src/nvim/viml/executor/converter.c @@ -33,6 +33,7 @@ typedef struct { ///< either kObjectTypeNil, kObjectTypeDictionary or ///< kObjectTypeArray, depending on other properties. lua_Number val; ///< If has_val_key and val_type == LUA_TNUMBER: value. + bool has_type_key; ///< True if type key is present. } LuaTableProps; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -48,8 +49,6 @@ typedef struct { 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, @@ -96,7 +95,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) if (n == (lua_Number)kObjectTypeFloat || n == (lua_Number)kObjectTypeArray || n == (lua_Number)kObjectTypeDictionary) { - has_type_key = true; + ret.has_type_key = true; ret.type = (ObjectType)n; } else { other_keys_num++; @@ -121,7 +120,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) tsize++; lua_pop(lstate, 1); } - if (has_type_key) { + if (ret.has_type_key) { if (ret.type == kObjectTypeFloat && (!has_val_key || val_type != LUA_TNUMBER)) { ret.type = kObjectTypeNil; @@ -133,7 +132,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) // should be ignored. if (ret.maxidx != 0 && ret.maxidx != (tsize - - has_type_key + - ret.has_type_key - other_keys_num - has_val_key - ret.string_keys_num)) { @@ -789,7 +788,12 @@ static inline LuaTableProps nlua_check_type(lua_State *const lstate, } return (LuaTableProps) { .type = kObjectTypeNil }; } - const LuaTableProps table_props = nlua_traverse_table(lstate); + LuaTableProps table_props = nlua_traverse_table(lstate); + + if (type == kObjectTypeDictionary && table_props.type == kObjectTypeArray + && table_props.maxidx == 0 && !table_props.has_type_key) { + table_props.type = kObjectTypeDictionary; + } if (table_props.type != type) { if (err) { -- cgit From ba2f615cd40d5d809d1a141c7b098e3bd22ff7bb Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 16 Jul 2016 02:26:04 +0300 Subject: functests: Test for error conditions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During testing found the following bugs: 1. msgpack-gen.lua script is completely unprepared for Float values either in return type or in arguments. Specifically: 1. At the time of writing relevant code FLOAT_OBJ did not exist as well as FLOATING_OBJ, but it would be used by msgpack-gen.lua should return type be Float. I added FLOATING_OBJ macros later because did not know that msgpack-gen.lua uses these _OBJ macros, otherwise it would be FLOAT_OBJ. 2. msgpack-gen.lua should use .data.floating in place of .data.float. But it did not expect that .data subattribute may have name different from lowercased type name. 2. vim_replace_termcodes returned its argument as-is if it receives an empty string (as well as _vim_id*() functions did). But if something in returned argument lives in an allocated memory such action will cause double free: once when freeing arguments, then when freeing return value. It did not cause problems yet because msgpack bindings return empty string as {NULL, 0} and nothing was actually allocated. 3. New code in msgpack-gen.lua popped arguments in reversed order, making lua bindings’ signatures be different from API ones. --- src/nvim/api/private/helpers.c | 2 +- src/nvim/api/private/helpers.h | 2 +- src/nvim/api/vim.c | 21 ++++++++++++++++---- src/nvim/msgpack_rpc/helpers.c | 2 +- src/nvim/viml/executor/converter.c | 39 +++++++++++++++++++------------------- 5 files changed, 40 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 7efa086af2..23d1540e2f 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -351,7 +351,7 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) #define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER #define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ - kv_push(edata->stack, FLOATING_OBJ((Float)(flt))) + kv_push(edata->stack, FLOAT_OBJ((Float)(flt))) #define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \ do { \ diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 9fe8c351cf..640e901fa1 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -27,7 +27,7 @@ .type = kObjectTypeInteger, \ .data.integer = i }) -#define FLOATING_OBJ(f) ((Object) { \ +#define FLOAT_OBJ(f) ((Object) { \ .type = kObjectTypeFloat, \ .data.floating = f }) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 5d862628cb..3fd1f57ace 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -139,7 +139,7 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, { if (str.size == 0) { // Empty string - return str; + return (String) { .data = NULL, .size = 0 }; } char *ptr = NULL; @@ -843,7 +843,7 @@ static void write_msg(String message, bool to_err) /// @return its argument. Object _vim_id(Object obj) { - return obj; + return copy_object(obj); } /// Returns array given as argument @@ -856,7 +856,7 @@ Object _vim_id(Object obj) /// @return its argument. Array _vim_id_array(Array arr) { - return arr; + return copy_object(ARRAY_OBJ(arr)).data.array; } /// Returns dictionary given as argument @@ -869,5 +869,18 @@ Array _vim_id_array(Array arr) /// @return its argument. Dictionary _vim_id_dictionary(Dictionary dct) { - return dct; + return copy_object(DICTIONARY_OBJ(dct)).data.dictionary; +} + +/// Returns floating-point value given as argument +/// +/// This API function is used for testing. One should not rely on its presence +/// in plugins. +/// +/// @param[in] flt Value to return. +/// +/// @return its argument. +Float _vim_id_float(Float flt) +{ + return flt; } diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index 5137b375f0..64a018f5c3 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -117,7 +117,7 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) case MSGPACK_OBJECT_FLOAT: { STATIC_ASSERT(sizeof(Float) == sizeof(cur.mobj->via.f64), "Msgpack floating-point size does not match API integer"); - *cur.aobj = FLOATING_OBJ(cur.mobj->via.f64); + *cur.aobj = FLOAT_OBJ(cur.mobj->via.f64); break; } #define STR_CASE(type, attr, obj, dest, conv) \ diff --git a/src/nvim/viml/executor/converter.c b/src/nvim/viml/executor/converter.c index a6399500f2..a741d3a752 100644 --- a/src/nvim/viml/executor/converter.c +++ b/src/nvim/viml/executor/converter.c @@ -724,16 +724,15 @@ void nlua_push_Object(lua_State *lstate, const Object obj) String nlua_pop_String(lua_State *lstate, Error *err) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - String ret; - - ret.data = (char *)lua_tolstring(lstate, -1, &(ret.size)); - - if (ret.data == NULL) { + if (lua_type(lstate, -1) != LUA_TSTRING) { lua_pop(lstate, 1); - set_api_error("Expected lua string", err); + api_set_error(err, Validation, "Expected lua string"); return (String) { .size = 0, .data = NULL }; } + String ret; + ret.data = (char *)lua_tolstring(lstate, -1, &(ret.size)); + assert(ret.data != NULL); ret.data = xmemdupz(ret.data, ret.size); lua_pop(lstate, 1); @@ -746,17 +745,19 @@ String nlua_pop_String(lua_State *lstate, Error *err) Integer nlua_pop_Integer(lua_State *lstate, Error *err) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - Integer ret = 0; - - if (!lua_isnumber(lstate, -1)) { + if (lua_type(lstate, -1) != LUA_TNUMBER) { lua_pop(lstate, 1); - set_api_error("Expected lua integer", err); - return ret; + api_set_error(err, Validation, "Expected lua number"); + return 0; } - ret = (Integer)lua_tonumber(lstate, -1); + const lua_Number n = lua_tonumber(lstate, -1); lua_pop(lstate, 1); - - return ret; + if (n > (lua_Number)API_INTEGER_MAX || n < (lua_Number)API_INTEGER_MIN + || ((lua_Number)((Integer)n)) != n) { + api_set_error(err, Exception, "Number is not integral"); + return 0; + } + return (Integer)n; } /// Convert lua value to boolean @@ -765,7 +766,7 @@ Integer nlua_pop_Integer(lua_State *lstate, Error *err) Boolean nlua_pop_Boolean(lua_State *lstate, Error *err) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - Boolean ret = lua_toboolean(lstate, -1); + const Boolean ret = lua_toboolean(lstate, -1); lua_pop(lstate, 1); return ret; } @@ -784,7 +785,7 @@ static inline LuaTableProps nlua_check_type(lua_State *const lstate, { if (lua_type(lstate, -1) != LUA_TTABLE) { if (err) { - set_api_error("Expected lua table", err); + api_set_error(err, Validation, "Expected lua table"); } return (LuaTableProps) { .type = kObjectTypeNil }; } @@ -797,7 +798,7 @@ static inline LuaTableProps nlua_check_type(lua_State *const lstate, if (table_props.type != type) { if (err) { - set_api_error("Unexpected type", err); + api_set_error(err, Validation, "Unexpected type"); } } @@ -1050,7 +1051,7 @@ Object nlua_pop_Object(lua_State *const lstate, Error *const 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) { - *cur.obj = FLOATING_OBJ((Float)n); + *cur.obj = FLOAT_OBJ((Float)n); } else { *cur.obj = INTEGER_OBJ((Integer)n); } @@ -1094,7 +1095,7 @@ Object nlua_pop_Object(lua_State *const lstate, Error *const err) break; } case kObjectTypeFloat: { - *cur.obj = FLOATING_OBJ((Float)table_props.val); + *cur.obj = FLOAT_OBJ((Float)table_props.val); break; } case kObjectTypeNil: { -- cgit From d932693d5147ac12d181e0810a20bdcbffab2818 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 20 Jan 2017 23:00:36 +0300 Subject: executor/converter: Allow converting self-referencing lua objects --- src/nvim/viml/executor/converter.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/executor/converter.c b/src/nvim/viml/executor/converter.c index a741d3a752..b7bacc8ed9 100644 --- a/src/nvim/viml/executor/converter.c +++ b/src/nvim/viml/executor/converter.c @@ -167,6 +167,7 @@ typedef struct { bool container; ///< True if tv is a container. bool special; ///< If true then tv is a _VAL part of special dictionary ///< that represents mapping. + int idx; ///< Container index (used to detect self-referencing structures). } TVPopStackItem; /// Convert lua object to VimL typval_T @@ -183,7 +184,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) bool ret = true; const int initial_size = lua_gettop(lstate); kvec_t(TVPopStackItem) stack = KV_INITIAL_VALUE; - kv_push(stack, ((TVPopStackItem) { ret_tv, false, false })); + kv_push(stack, ((TVPopStackItem) { ret_tv, false, false, 0 })); while (ret && kv_size(stack)) { if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { emsgf(_("E1502: Lua failed to grow stack to %i"), lua_gettop(lstate) + 3); @@ -219,14 +220,14 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) listitem_T *const val = listitem_alloc(); list_append(kv_pair, val); kv_push(stack, cur); - cur = (TVPopStackItem) { &val->li_tv, false, false }; + cur = (TVPopStackItem) { &val->li_tv, false, false, 0 }; } else { dictitem_T *const di = dictitem_alloc_len(s, len); if (dict_add(cur.tv->vval.v_dict, di) == FAIL) { assert(false); } kv_push(stack, cur); - cur = (TVPopStackItem) { &di->di_tv, false, false }; + cur = (TVPopStackItem) { &di->di_tv, false, false, 0 }; } } else { lua_pop(lstate, 1); @@ -242,7 +243,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) listitem_T *li = listitem_alloc(); list_append(cur.tv->vval.v_list, li); kv_push(stack, cur); - cur = (TVPopStackItem) { &li->li_tv, false, false }; + cur = (TVPopStackItem) { &li->li_tv, false, false, 0 }; } } assert(!cur.container); @@ -288,6 +289,14 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) case LUA_TTABLE: { const LuaTableProps table_props = nlua_traverse_table(lstate); + for (size_t i = 0; i < kv_size(stack); i++) { + const TVPopStackItem item = kv_A(stack, i); + if (item.container && lua_rawequal(lstate, -1, item.idx)) { + copy_tv(item.tv, cur.tv); + goto nlua_pop_typval_table_processing_end; + } + } + switch (table_props.type) { case kObjectTypeArray: { cur.tv->v_type = VAR_LIST; @@ -295,6 +304,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) cur.tv->vval.v_list->lv_refcount++; if (table_props.maxidx != 0) { cur.container = true; + cur.idx = lua_gettop(lstate); kv_push(stack, cur); } break; @@ -320,6 +330,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) cur.tv->vval.v_dict->dv_refcount++; } cur.container = true; + cur.idx = lua_gettop(lstate); kv_push(stack, cur); lua_pushnil(lstate); } @@ -341,6 +352,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) assert(false); } } +nlua_pop_typval_table_processing_end: break; } default: { -- cgit From 5c1b9a0d2af86461f56f0d27ed275456921f6187 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 20 Jan 2017 23:06:22 +0300 Subject: api: Reserve more numbers for internal calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reasoning; currently INTERNAL_CALL is mostly used to determine whether it is needed to deal with NL-used-as-NUL problem. This code is useful for nvim_… API calls done from VimL, but not for API calls done from lua, yet lua needs to supply something as channel_id. --- src/nvim/api/buffer.c | 4 ++-- src/nvim/api/private/defs.h | 27 +++++++++++++++++++++++++-- src/nvim/eval.c | 2 +- 3 files changed, 28 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index b75a2c7211..5eda88025f 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -192,7 +192,7 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, Object str = STRING_OBJ(cstr_to_string(bufstr)); // Vim represents NULs as NLs, but this may confuse clients. - if (channel_id != INTERNAL_CALL) { + if (channel_id != VIML_INTERNAL_CALL) { strchrsub(str.data.string.data, '\n', '\0'); } @@ -313,7 +313,7 @@ void nvim_buf_set_lines(uint64_t channel_id, // line and convert NULs to newlines to avoid truncation. lines[i] = xmallocz(l.size); for (size_t j = 0; j < l.size; j++) { - if (l.data[j] == '\n' && channel_id != INTERNAL_CALL) { + if (l.data[j] == '\n' && channel_id != VIML_INTERNAL_CALL) { api_set_error(err, Exception, _("string cannot contain newlines")); new_len = i + 1; goto end; diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 86b549cb44..cb7ee8eb4c 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -5,6 +5,8 @@ #include #include +#include "nvim/func_attr.h" + #define ARRAY_DICT_INIT {.size = 0, .capacity = 0, .items = NULL} #define STRING_INIT {.data = NULL, .size = 0} #define OBJECT_INIT { .type = kObjectTypeNil } @@ -33,8 +35,29 @@ typedef enum { /// Used as the message ID of notifications. #define NO_RESPONSE UINT64_MAX -/// Used as channel_id when the call is local. -#define INTERNAL_CALL UINT64_MAX +/// Mask for all internal calls +#define INTERNAL_CALL_MASK (UINT64_MAX ^ (UINT64_MAX >> 1)) +// (1 << 63) in all forms produces “warning: shift count >= width of type +// [-Wshift-count-overflow]” + +/// Internal call from VimL code +#define VIML_INTERNAL_CALL INTERNAL_CALL_MASK + +/// Internal call from lua code +#define LUA_INTERNAL_CALL (VIML_INTERNAL_CALL + 1) + +static inline bool is_internal_call(uint64_t channel_id) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_CONST; + +/// Check whether call is internal +/// +/// @param[in] channel_id Channel id. +/// +/// @return true if channel_id refers to internal channel. +static inline bool is_internal_call(const uint64_t channel_id) +{ + return !!(channel_id & INTERNAL_CALL_MASK); +} typedef struct { ErrorType type; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index de82f8a145..8b6638f1d7 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -7796,7 +7796,7 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) } Error err = ERROR_INIT; - Object result = fn(INTERNAL_CALL, args, &err); + Object result = fn(VIML_INTERNAL_CALL, args, &err); if (err.set) { nvim_err_writeln(cstr_as_string(err.msg)); -- cgit From 8679feb3cbffd6b175be6a2868e980ca971125f7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 21 Jan 2017 00:00:09 +0300 Subject: executor/converter: Use readable lua numbers for handles --- src/nvim/viml/executor/converter.c | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/executor/converter.c b/src/nvim/viml/executor/converter.c index b7bacc8ed9..316a5aa93f 100644 --- a/src/nvim/viml/executor/converter.c +++ b/src/nvim/viml/executor/converter.c @@ -217,7 +217,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) list_unref(kv_pair); continue; } - listitem_T *const val = listitem_alloc(); + listitem_T *const val = listitem_alloc(); list_append(kv_pair, val); kv_push(stack, cur); cur = (TVPopStackItem) { &val->li_tv, false, false, 0 }; @@ -240,7 +240,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) lua_pop(lstate, 2); continue; } - listitem_T *li = listitem_alloc(); + listitem_T *const li = listitem_alloc(); list_append(cur.tv->vval.v_list, li); kv_push(stack, cur); cur = (TVPopStackItem) { &li->li_tv, false, false, 0 }; @@ -293,6 +293,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) const TVPopStackItem item = kv_A(stack, i); if (item.container && lua_rawequal(lstate, -1, item.idx)) { copy_tv(item.tv, cur.tv); + cur.container = false; goto nlua_pop_typval_table_processing_end; } } @@ -540,25 +541,14 @@ bool nlua_push_typval(lua_State *lstate, typval_T *const tv) return true; } -#define NLUA_PUSH_IDX(lstate, type, idx) \ +#define NLUA_PUSH_HANDLE(lstate, type, idx) \ do { \ - STATIC_ASSERT(sizeof(type) <= sizeof(lua_Number), \ - "Number sizes do not match"); \ - const type src = idx; \ - lua_Number tgt; \ - memset(&tgt, 0, sizeof(tgt)); \ - memcpy(&tgt, &src, sizeof(src)); \ - lua_pushnumber(lstate, tgt); \ + lua_pushnumber(lstate, (lua_Number)(idx)); \ } while (0) -#define NLUA_POP_IDX(lstate, type, stack_idx, idx) \ +#define NLUA_POP_HANDLE(lstate, type, stack_idx, idx) \ do { \ - STATIC_ASSERT(sizeof(type) <= sizeof(lua_Number), \ - "Number sizes do not match"); \ - const lua_Number src = lua_tonumber(lstate, stack_idx); \ - type tgt; \ - memcpy(&tgt, &src, sizeof(tgt)); \ - idx = tgt; \ + idx = (type)lua_tonumber(lstate, stack_idx); \ } while (0) /// Push value which is a type index @@ -685,7 +675,7 @@ void nlua_push_Array(lua_State *lstate, const Array array) void nlua_push_##type(lua_State *lstate, const type item) \ FUNC_ATTR_NONNULL_ALL \ { \ - NLUA_PUSH_IDX(lstate, type, item); \ + NLUA_PUSH_HANDLE(lstate, type, item); \ } GENERATE_INDEX_FUNCTION(Buffer) @@ -1144,7 +1134,7 @@ type nlua_pop_##type(lua_State *lstate, Error *err) \ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \ { \ type ret; \ - NLUA_POP_IDX(lstate, type, -1, ret); \ + NLUA_POP_HANDLE(lstate, type, -1, ret); \ lua_pop(lstate, 1); \ return ret; \ } -- cgit From 45feaa73d0759858a9a4454037fe4a41ea97e5b9 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 21 Jan 2017 00:16:25 +0300 Subject: eval/decode: Fix memory leak in JSON functions --- src/nvim/eval/decode.c | 24 ++++++++++++++++++------ src/nvim/viml/executor/converter.c | 4 ++-- 2 files changed, 20 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index cb5a624f7b..d95e75170a 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -249,10 +249,14 @@ list_T *decode_create_map_special_dict(typval_T *const ret_tv) /// determined. /// @param[in] binary If true, save special string type as kMPBinary, /// otherwise kMPString. +/// @param[in] s_allocated If true, then `s` was allocated and can be saved in +/// a returned structure. If it is not saved there, it +/// will be freed. /// /// @return Decoded string. typval_T decode_string(const char *const s, const size_t len, - const TriState hasnul, const bool binary) + const TriState hasnul, const bool binary, + const bool s_allocated) FUNC_ATTR_WARN_UNUSED_RESULT { assert(s != NULL || len == 0); @@ -268,7 +272,11 @@ typval_T decode_string(const char *const s, const size_t len, .v_lock = VAR_UNLOCKED, .vval = { .v_list = list }, })); - if (encode_list_write((void *)list, s, len) == -1) { + const int elw_ret = encode_list_write((void *)list, s, len); + if (s_allocated) { + xfree((void *)s); + } + if (elw_ret == -1) { clear_tv(&tv); return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; } @@ -277,7 +285,8 @@ typval_T decode_string(const char *const s, const size_t len, return (typval_T) { .v_type = VAR_STRING, .v_lock = VAR_UNLOCKED, - .vval = { .v_string = xmemdupz(s, len) }, + .vval = { .v_string = (char_u *)( + s_allocated ? (char *)s : xmemdupz(s, len)) }, }; } } @@ -492,9 +501,10 @@ static inline int parse_json_string(vimconv_T *const conv, str = new_str; str_end = new_str + str_len; } + *str_end = NUL; typval_T obj; obj = decode_string(str, (size_t)(str_end - str), hasnul ? kTrue : kFalse, - false); + false, true); if (obj.v_type == VAR_UNKNOWN) { goto parse_json_string_fail; } @@ -1022,14 +1032,16 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) break; } case MSGPACK_OBJECT_STR: { - *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kTrue, false); + *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kTrue, false, + false); if (rettv->v_type == VAR_UNKNOWN) { return FAIL; } break; } case MSGPACK_OBJECT_BIN: { - *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kNone, true); + *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kNone, true, + false); if (rettv->v_type == VAR_UNKNOWN) { return FAIL; } diff --git a/src/nvim/viml/executor/converter.c b/src/nvim/viml/executor/converter.c index 316a5aa93f..a39f573036 100644 --- a/src/nvim/viml/executor/converter.c +++ b/src/nvim/viml/executor/converter.c @@ -210,7 +210,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) list_T *const kv_pair = list_alloc(); list_append_list(cur.tv->vval.v_list, kv_pair); listitem_T *const key = listitem_alloc(); - key->li_tv = decode_string(s, len, kTrue, false); + key->li_tv = decode_string(s, len, kTrue, false, false); list_append(kv_pair, key); if (key->li_tv.v_type == VAR_UNKNOWN) { ret = false; @@ -268,7 +268,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) case LUA_TSTRING: { size_t len; const char *s = lua_tolstring(lstate, -1, &len); - *cur.tv = decode_string(s, len, kNone, true); + *cur.tv = decode_string(s, len, kNone, true, false); if (cur.tv->v_type == VAR_UNKNOWN) { ret = false; } -- cgit From f8d55266e461a0a85e17e5adfd33e8429e45d9a5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 21 Jan 2017 01:02:15 +0300 Subject: executor/executor: When reporting errors use lua string length --- src/nvim/viml/executor/executor.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index 33b6870ea0..29ec8dc927 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -86,7 +86,7 @@ static void nlua_error(lua_State *const lstate, const char *const msg) size_t len; const char *const str = lua_tolstring(lstate, -1, &len); - EMSG2(msg, str); + emsgf(msg, (int)len, str); lua_pop(lstate, 1); } @@ -120,11 +120,11 @@ static int nlua_exec_lua_string(lua_State *lstate) FUNC_ATTR_NONNULL_ALL lua_pop(lstate, 2); if (luaL_loadbuffer(lstate, str->data, str->size, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5104: Error while creating lua chunk: %s")); + nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s")); return 0; } if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5105: Error while calling lua chunk: %s")); + nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s")); return 0; } if (!nlua_pop_typval(lstate, ret_tv)) { @@ -141,7 +141,7 @@ static int nlua_state_init(lua_State *lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, &nlua_stricmp); lua_setglobal(lstate, "stricmp"); if (luaL_dostring(lstate, (char *)&vim_module[0])) { - nlua_error(lstate, _("E5106: Error while creating vim module: %s")); + nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); return 1; } nlua_add_api_functions(lstate); @@ -220,7 +220,7 @@ static int nlua_eval_lua_string(lua_State *lstate) #undef EVALHEADER if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { nlua_error(lstate, - _("E5107: Error while creating lua chunk for luaeval(): %s")); + _("E5107: Error while creating lua chunk for luaeval(): %.*s")); return 0; } if (lcmd != (char *)IObuff) { @@ -234,7 +234,7 @@ static int nlua_eval_lua_string(lua_State *lstate) } if (lua_pcall(lstate, 1, 1, 0)) { nlua_error(lstate, - _("E5108: Error while calling lua chunk for luaeval(): %s")); + _("E5108: Error while calling lua chunk for luaeval(): %.*s")); return 0; } if (!nlua_pop_typval(lstate, ret_tv)) { -- cgit From 53b89c1dcf44c95827cc85a9657faee451c573c5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 21 Jan 2017 01:42:50 +0300 Subject: executor/executor: Free lcmd on error --- src/nvim/viml/executor/executor.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index 29ec8dc927..acc375881c 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -221,6 +221,9 @@ static int nlua_eval_lua_string(lua_State *lstate) if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { nlua_error(lstate, _("E5107: Error while creating lua chunk for luaeval(): %.*s")); + if (lcmd != (char *)IObuff) { + xfree(lcmd); + } return 0; } if (lcmd != (char *)IObuff) { -- cgit From 1646a28173d1b7c8309188ebe7b6fb38a6ca8315 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 21 Jan 2017 02:33:09 +0300 Subject: cmake: Allow switching from luajit to lua --- src/nvim/CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index a47a8e49c7..7a7dbbcce6 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -291,8 +291,12 @@ list(APPEND NVIM_LINK_LIBRARIES ${LIBTERMKEY_LIBRARIES} ${UNIBILIUM_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} - ${LUAJIT_LIBRARIES} ) +if(PREFER_LUAJIT) + list(APPEND NVIM_LINK_LIBRARIES ${LUAJIT_LIBRARIES}) +else() + list(APPEND NVIM_LINK_LIBRARIES ${LUA_LIBRARIES}) +endif() if(UNIX) list(APPEND NVIM_LINK_LIBRARIES -- cgit From 6b4a51f7ea49a6ef8305831e8d96b2a7cc4ba5f3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 22 Jan 2017 04:34:26 +0300 Subject: scripts: Make generate_vim_module more generic --- src/nvim/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 7a7dbbcce6..21a39b950f 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -39,7 +39,7 @@ set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/unicode) set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h) set(VIM_MODULE_FILE ${GENERATED_DIR}/viml/executor/vim_module.generated.h) set(VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/viml/executor/vim.lua) -set(VIM_MODULE_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/generate_vim_module.lua) +set(CHAR_BLOB_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gencharblob.lua) file(GLOB API_HEADERS api/*.h) file(GLOB MSGPACK_RPC_HEADERS msgpack_rpc/*.h) @@ -222,10 +222,10 @@ add_custom_command( add_custom_command( OUTPUT ${VIM_MODULE_FILE} - COMMAND ${LUA_PRG} ${VIM_MODULE_GENERATOR} ${VIM_MODULE_SOURCE} - ${VIM_MODULE_FILE} + COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_SOURCE} + ${VIM_MODULE_FILE} vim_module DEPENDS - ${VIM_MODULE_GENERATOR} + ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_SOURCE} ) -- cgit From ca4c8b7f8a7b9543ef01157cb5ab94783f624ac6 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 22 Jan 2017 04:55:26 +0300 Subject: api: Allow kObjectTypeNil to be zero without breaking compatibility --- src/nvim/api/private/defs.h | 7 ++++--- src/nvim/msgpack_rpc/helpers.c | 26 +++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index cb7ee8eb4c..432ab347bc 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -100,15 +100,16 @@ typedef struct { typedef enum { kObjectTypeNil = 0, - kObjectTypeBuffer, - kObjectTypeWindow, - kObjectTypeTabpage, kObjectTypeBoolean, kObjectTypeInteger, kObjectTypeFloat, kObjectTypeString, kObjectTypeArray, kObjectTypeDictionary, + // EXT types, cannot be split or reordered, see #EXT_OBJECT_TYPE_SHIFT + kObjectTypeBuffer, + kObjectTypeWindow, + kObjectTypeTabpage, } ObjectType; struct object { diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index 64a018f5c3..972d5b3441 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -20,13 +20,20 @@ static msgpack_zone zone; static msgpack_sbuffer sbuffer; +/// Value by which objects represented as EXT type are shifted +/// +/// Subtracted when packing, added when unpacking. Used to allow moving +/// buffer/window/tabpage block inside ObjectType enum. This block yet cannot be +/// split or reordered. +#define EXT_OBJECT_TYPE_SHIFT kObjectTypeBuffer + #define HANDLE_TYPE_CONVERSION_IMPL(t, lt) \ bool msgpack_rpc_to_##lt(const msgpack_object *const obj, \ Integer *const arg) \ FUNC_ATTR_NONNULL_ALL \ { \ if (obj->type != MSGPACK_OBJECT_EXT \ - || obj->via.ext.type != kObjectType##t) { \ + || obj->via.ext.type + EXT_OBJECT_TYPE_SHIFT != kObjectType##t) { \ return false; \ } \ \ @@ -51,7 +58,8 @@ static msgpack_sbuffer sbuffer; msgpack_packer pac; \ msgpack_packer_init(&pac, &sbuffer, msgpack_sbuffer_write); \ msgpack_pack_int64(&pac, (handle_T)o); \ - msgpack_pack_ext(res, sbuffer.size, kObjectType##t); \ + msgpack_pack_ext(res, sbuffer.size, \ + kObjectType##t - EXT_OBJECT_TYPE_SHIFT); \ msgpack_pack_ext_body(res, sbuffer.data, sbuffer.size); \ msgpack_sbuffer_clear(&sbuffer); \ } @@ -211,7 +219,7 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) break; } case MSGPACK_OBJECT_EXT: { - switch (cur.mobj->via.ext.type) { + switch ((ObjectType)(cur.mobj->via.ext.type + EXT_OBJECT_TYPE_SHIFT)) { case kObjectTypeBuffer: { cur.aobj->type = kObjectTypeBuffer; ret = msgpack_rpc_to_buffer(cur.mobj, &cur.aobj->data.integer); @@ -227,6 +235,15 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) ret = msgpack_rpc_to_tabpage(cur.mobj, &cur.aobj->data.integer); break; } + case kObjectTypeNil: + case kObjectTypeBoolean: + case kObjectTypeInteger: + case kObjectTypeFloat: + case kObjectTypeString: + case kObjectTypeArray: + case kObjectTypeDictionary: { + break; + } } break; } @@ -350,6 +367,9 @@ void msgpack_rpc_from_object(const Object result, msgpack_packer *const res) kv_push(stack, ((APIToMPObjectStackItem) { &result, false, 0 })); while (kv_size(stack)) { APIToMPObjectStackItem cur = kv_last(stack); + STATIC_ASSERT(kObjectTypeWindow == kObjectTypeBuffer + 1 + && kObjectTypeTabpage == kObjectTypeWindow + 1, + "Buffer, window and tabpage enum items are in order"); switch (cur.aobj->type) { case kObjectTypeNil: { msgpack_pack_nil(res); -- cgit From 927e6efc3d93ff9d34c532180591dc3ca326edf5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 22 Jan 2017 05:16:05 +0300 Subject: clint: Allow omitting include guards in .c.h file and func_attr.h file --- src/clint.py | 5 +++++ src/nvim/eval/typval_encode.c.h | 5 ----- src/nvim/func_attr.h | 5 ----- 3 files changed, 5 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/clint.py b/src/clint.py index ce31822ada..ca4dbdb1bc 100755 --- a/src/clint.py +++ b/src/clint.py @@ -182,6 +182,7 @@ _ERROR_CATEGORIES = [ 'build/include_order', 'build/printf_format', 'build/storage_class', + 'build/useless_fattr', 'readability/alt_tokens', 'readability/bool', 'readability/braces', @@ -1224,6 +1225,10 @@ def CheckForHeaderGuard(filename, lines, error): lines: An array of strings, each representing a line of the file. error: The function to call with any errors found. """ + if filename.endswith('.c.h') or FileInfo(filename).RelativePath() in set(( + 'func_attr.h', + )): + return cppvar = GetHeaderGuardCPPVariable(filename) diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 4ff5589887..812340b9c3 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -233,10 +233,6 @@ /// /// 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 #include #include @@ -816,4 +812,3 @@ encode_vim_to__error_ret: // Prevent “unused label” warnings. goto typval_encode_stop_converting_one_item; } -#endif // NVIM_EVAL_TYPVAL_ENCODE_C_H diff --git a/src/nvim/func_attr.h b/src/nvim/func_attr.h index 18410445e1..7b8fcc4343 100644 --- a/src/nvim/func_attr.h +++ b/src/nvim/func_attr.h @@ -41,10 +41,6 @@ // $ gcc -E -dM - Date: Sun, 22 Jan 2017 05:35:48 +0300 Subject: msgpack_rpc: Fix #HANDLE_TYPE_CONVERSION_IMPL Function declarations generator is able to handle properly only the *first* function definition that is in macros, and only if it is the first entity in the macros. So msgpack_rpc_from_* was already really a static function, additionally its attributes were useless. This commit switches to explicit declarations and makes generated functions static. --- src/nvim/msgpack_rpc/helpers.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index 972d5b3441..94a4f5ce3e 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -28,9 +28,11 @@ static msgpack_sbuffer sbuffer; #define EXT_OBJECT_TYPE_SHIFT kObjectTypeBuffer #define HANDLE_TYPE_CONVERSION_IMPL(t, lt) \ - bool msgpack_rpc_to_##lt(const msgpack_object *const obj, \ - Integer *const arg) \ - FUNC_ATTR_NONNULL_ALL \ + static bool msgpack_rpc_to_##lt(const msgpack_object *const obj, \ + Integer *const arg) \ + REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT; \ + static bool msgpack_rpc_to_##lt(const msgpack_object *const obj, \ + Integer *const arg) \ { \ if (obj->type != MSGPACK_OBJECT_EXT \ || obj->via.ext.type + EXT_OBJECT_TYPE_SHIFT != kObjectType##t) { \ @@ -52,8 +54,9 @@ static msgpack_sbuffer sbuffer; return true; \ } \ \ - void msgpack_rpc_from_##lt(Integer o, msgpack_packer *res) \ - FUNC_ATTR_NONNULL_ARG(2) \ + static void msgpack_rpc_from_##lt(Integer o, msgpack_packer *res) \ + REAL_FATTR_NONNULL_ARG(2); \ + static void msgpack_rpc_from_##lt(Integer o, msgpack_packer *res) \ { \ msgpack_packer pac; \ msgpack_packer_init(&pac, &sbuffer, msgpack_sbuffer_write); \ -- cgit From d33b13dd6ba5fddbb0903c28fd78a69b5f0a11bd Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 22 Jan 2017 22:02:39 +0300 Subject: cmake: Try fixing ASAN nvim-test compilation --- src/nvim/CMakeLists.txt | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 21a39b950f..918a64c2b4 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -363,6 +363,23 @@ if(WIN32) add_dependencies(nvim_runtime_deps external_blobs) endif() +add_library(libnvim STATIC EXCLUDE_FROM_ALL ${NEOVIM_GENERATED_SOURCES} + ${NEOVIM_SOURCES} ${NEOVIM_HEADERS}) +target_link_libraries(libnvim ${NVIM_LINK_LIBRARIES}) +set_target_properties(libnvim PROPERTIES + POSITION_INDEPENDENT_CODE ON + OUTPUT_NAME nvim) +set_property(TARGET libnvim + APPEND_STRING PROPERTY COMPILE_FLAGS " -DMAKE_LIB ") + +add_library(nvim-test MODULE EXCLUDE_FROM_ALL ${NEOVIM_GENERATED_SOURCES} + ${NEOVIM_SOURCES} ${UNIT_TEST_FIXTURES} ${NEOVIM_HEADERS}) +target_link_libraries(nvim-test ${NVIM_LINK_LIBRARIES}) +set_target_properties(nvim-test PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_property(TARGET nvim-test + APPEND_STRING PROPERTY COMPILE_FLAGS " -DUNIT_TESTING ") + if(CLANG_ASAN_UBSAN) message(STATUS "Enabling Clang address sanitizer and undefined behavior sanitizer for nvim.") check_c_compiler_flag(-fno-sanitize-recover=all SANITIZE_RECOVER_ALL) @@ -374,6 +391,9 @@ if(CLANG_ASAN_UBSAN) set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "-DEXITFREE ") set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "${SANITIZE_RECOVER} -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address -fsanitize=undefined -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/src/.asan-blacklist") set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=address -fsanitize=undefined ") + + set_property(TARGET nvim-test APPEND_STRING PROPERTY COMPILE_FLAGS "${SANITIZE_RECOVER} -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/src/.asan-blacklist") + set_property(TARGET nvim-test APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=address ") elseif(CLANG_MSAN) message(STATUS "Enabling Clang memory sanitizer for nvim.") set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "-DEXITFREE ") @@ -387,17 +407,4 @@ elseif(CLANG_TSAN) set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=thread ") endif() -add_library(libnvim STATIC EXCLUDE_FROM_ALL ${NEOVIM_GENERATED_SOURCES} - ${NEOVIM_SOURCES} ${NEOVIM_HEADERS}) -target_link_libraries(libnvim ${NVIM_LINK_LIBRARIES}) -set_target_properties(libnvim PROPERTIES - POSITION_INDEPENDENT_CODE ON - OUTPUT_NAME nvim) -set_property(TARGET libnvim APPEND_STRING PROPERTY COMPILE_FLAGS " -DMAKE_LIB ") - -add_library(nvim-test MODULE EXCLUDE_FROM_ALL ${NEOVIM_GENERATED_SOURCES} - ${NEOVIM_SOURCES} ${UNIT_TEST_FIXTURES} ${NEOVIM_HEADERS}) -target_link_libraries(nvim-test ${NVIM_LINK_LIBRARIES}) -set_property(TARGET nvim-test APPEND_STRING PROPERTY COMPILE_FLAGS -DUNIT_TESTING) - add_subdirectory(po) -- cgit From d60302d5177e2ba99e84588c8ddd70dad0cbc64a Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 27 Jan 2017 02:55:34 +0300 Subject: cmake: Link libnvim-test with luajit in place of lua, disable ASAN Reasoning: luajit is not being compiled with sanitizers, lua is. Given that linking with sanitized libraries requires sanitizers enabled, it is needed to either compile libnvim-test with sanitizers or link it with lua compiled without sanitizers. Most easy way to do the latter is just use luajit which is compiled without sanitizers (as they do not work well with luajit). --- src/nvim/CMakeLists.txt | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 918a64c2b4..467af5bc00 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -292,11 +292,6 @@ list(APPEND NVIM_LINK_LIBRARIES ${UNIBILIUM_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) -if(PREFER_LUAJIT) - list(APPEND NVIM_LINK_LIBRARIES ${LUAJIT_LIBRARIES}) -else() - list(APPEND NVIM_LINK_LIBRARIES ${LUA_LIBRARIES}) -endif() if(UNIX) list(APPEND NVIM_LINK_LIBRARIES @@ -306,6 +301,14 @@ if(UNIX) endif() set(NVIM_EXEC_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES}) +set(NVIM_TEST_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES}) + +if(PREFER_LUAJIT) + list(APPEND NVIM_EXEC_LINK_LIBRARIES ${LUAJIT_LIBRARIES}) +else() + list(APPEND NVIM_EXEC_LINK_LIBRARIES ${LUA_LIBRARIES}) +endif() +list(APPEND NVIM_TEST_LINK_LIBRARIES ${LUAJIT_LIBRARIES}) # Don't use jemalloc in the unit test library. if(JEMALLOC_FOUND) @@ -317,6 +320,12 @@ add_executable(nvim ${NEOVIM_GENERATED_SOURCES} ${NEOVIM_SOURCES} target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES}) install_helper(TARGETS nvim) +if(PREFER_LUAJIT) + target_include_directories(nvim SYSTEM PRIVATE ${LUAJIT_INCLUDE_DIRS}) +else() + target_include_directories(nvim SYSTEM PRIVATE ${LUA_INCLUDE_DIRS}) +endif() + if(WIN32) # Copy DLLs and third-party tools to bin/ and install them along with nvim add_custom_target(nvim_runtime_deps ALL @@ -365,7 +374,7 @@ endif() add_library(libnvim STATIC EXCLUDE_FROM_ALL ${NEOVIM_GENERATED_SOURCES} ${NEOVIM_SOURCES} ${NEOVIM_HEADERS}) -target_link_libraries(libnvim ${NVIM_LINK_LIBRARIES}) +target_link_libraries(libnvim ${NVIM_TEST_LINK_LIBRARIES}) set_target_properties(libnvim PROPERTIES POSITION_INDEPENDENT_CODE ON OUTPUT_NAME nvim) @@ -374,7 +383,8 @@ set_property(TARGET libnvim add_library(nvim-test MODULE EXCLUDE_FROM_ALL ${NEOVIM_GENERATED_SOURCES} ${NEOVIM_SOURCES} ${UNIT_TEST_FIXTURES} ${NEOVIM_HEADERS}) -target_link_libraries(nvim-test ${NVIM_LINK_LIBRARIES}) +target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES}) +target_include_directories(nvim-test SYSTEM PRIVATE ${LUAJIT_INCLUDE_DIRS}) set_target_properties(nvim-test PROPERTIES POSITION_INDEPENDENT_CODE ON) set_property(TARGET nvim-test @@ -391,9 +401,6 @@ if(CLANG_ASAN_UBSAN) set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "-DEXITFREE ") set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "${SANITIZE_RECOVER} -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address -fsanitize=undefined -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/src/.asan-blacklist") set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=address -fsanitize=undefined ") - - set_property(TARGET nvim-test APPEND_STRING PROPERTY COMPILE_FLAGS "${SANITIZE_RECOVER} -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/src/.asan-blacklist") - set_property(TARGET nvim-test APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=address ") elseif(CLANG_MSAN) message(STATUS "Enabling Clang memory sanitizer for nvim.") set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "-DEXITFREE ") -- cgit From 9c743df2d532b3adc04b532fa6fc9c7838b31353 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 27 Jan 2017 03:12:53 +0300 Subject: cmake: Use LuaJIT include directory for declarations generator --- src/nvim/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 467af5bc00..be3c8d1b07 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -151,7 +151,7 @@ if(CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN) endif() get_directory_property(gen_includes INCLUDE_DIRECTORIES) -foreach(gen_include ${gen_includes}) +foreach(gen_include ${gen_includes} ${LUAJIT_INCLUDE_DIRS}) list(APPEND gen_cflags "-I${gen_include}") endforeach() string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type) -- cgit From ae4adcc70735a89bffb110bcf9d5a993b0786c4d Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 28 Jan 2017 03:47:15 +0300 Subject: gendeclarations: Make declarations generator work with macros funcs Now it checks functions also after every semicolon and closing figure brace, possibly preceded by whitespaces (tabs and spaces). This should make messing with declarations in macros not needed. --- src/nvim/msgpack_rpc/helpers.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index 94a4f5ce3e..ec35d56978 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -30,9 +30,7 @@ static msgpack_sbuffer sbuffer; #define HANDLE_TYPE_CONVERSION_IMPL(t, lt) \ static bool msgpack_rpc_to_##lt(const msgpack_object *const obj, \ Integer *const arg) \ - REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT; \ - static bool msgpack_rpc_to_##lt(const msgpack_object *const obj, \ - Integer *const arg) \ + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \ { \ if (obj->type != MSGPACK_OBJECT_EXT \ || obj->via.ext.type + EXT_OBJECT_TYPE_SHIFT != kObjectType##t) { \ @@ -55,8 +53,7 @@ static msgpack_sbuffer sbuffer; } \ \ static void msgpack_rpc_from_##lt(Integer o, msgpack_packer *res) \ - REAL_FATTR_NONNULL_ARG(2); \ - static void msgpack_rpc_from_##lt(Integer o, msgpack_packer *res) \ + FUNC_ATTR_NONNULL_ARG(2) \ { \ msgpack_packer pac; \ msgpack_packer_init(&pac, &sbuffer, msgpack_sbuffer_write); \ -- cgit From d836464cd2a6cdb4f4b797677794a376d5a12a45 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 28 Jan 2017 22:16:24 +0300 Subject: cmake: Also include luajit directories for libnvim target --- src/nvim/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index be3c8d1b07..0cf331206e 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -374,6 +374,7 @@ endif() add_library(libnvim STATIC EXCLUDE_FROM_ALL ${NEOVIM_GENERATED_SOURCES} ${NEOVIM_SOURCES} ${NEOVIM_HEADERS}) +target_include_directories(libnvim SYSTEM PRIVATE ${LUAJIT_INCLUDE_DIRS}) target_link_libraries(libnvim ${NVIM_TEST_LINK_LIBRARIES}) set_target_properties(libnvim PROPERTIES POSITION_INDEPENDENT_CODE ON -- cgit From 62fde319360e27a86c0deba0053ff230f80ca772 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Jan 2017 01:29:51 +0300 Subject: api: Also shift numbers in api_metadata output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes problem introduced by “api: Allow kObjectTypeNil to be zero without breaking compatibility”: apparently there are clients which use metadata and there are which aren’t. For the first that commit would not be needed, for the second that commit misses this critical piece. --- src/nvim/api/private/helpers.c | 9 ++++++--- src/nvim/msgpack_rpc/helpers.c | 7 ------- src/nvim/msgpack_rpc/helpers.h | 7 +++++++ 3 files changed, 13 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 23d1540e2f..373e509120 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -851,15 +851,18 @@ static void init_type_metadata(Dictionary *metadata) Dictionary types = ARRAY_DICT_INIT; Dictionary buffer_metadata = ARRAY_DICT_INIT; - PUT(buffer_metadata, "id", INTEGER_OBJ(kObjectTypeBuffer)); + PUT(buffer_metadata, "id", + INTEGER_OBJ(kObjectTypeBuffer - EXT_OBJECT_TYPE_SHIFT)); PUT(buffer_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_buf_"))); Dictionary window_metadata = ARRAY_DICT_INIT; - PUT(window_metadata, "id", INTEGER_OBJ(kObjectTypeWindow)); + PUT(window_metadata, "id", + INTEGER_OBJ(kObjectTypeWindow - EXT_OBJECT_TYPE_SHIFT)); PUT(window_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_win_"))); Dictionary tabpage_metadata = ARRAY_DICT_INIT; - PUT(tabpage_metadata, "id", INTEGER_OBJ(kObjectTypeTabpage)); + PUT(tabpage_metadata, "id", + INTEGER_OBJ(kObjectTypeTabpage - EXT_OBJECT_TYPE_SHIFT)); PUT(tabpage_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_tabpage_"))); PUT(types, "Buffer", DICTIONARY_OBJ(buffer_metadata)); diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index ec35d56978..21acd6a394 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -20,13 +20,6 @@ static msgpack_zone zone; static msgpack_sbuffer sbuffer; -/// Value by which objects represented as EXT type are shifted -/// -/// Subtracted when packing, added when unpacking. Used to allow moving -/// buffer/window/tabpage block inside ObjectType enum. This block yet cannot be -/// split or reordered. -#define EXT_OBJECT_TYPE_SHIFT kObjectTypeBuffer - #define HANDLE_TYPE_CONVERSION_IMPL(t, lt) \ static bool msgpack_rpc_to_##lt(const msgpack_object *const obj, \ Integer *const arg) \ diff --git a/src/nvim/msgpack_rpc/helpers.h b/src/nvim/msgpack_rpc/helpers.h index 7d9f114140..0e4cd1be6d 100644 --- a/src/nvim/msgpack_rpc/helpers.h +++ b/src/nvim/msgpack_rpc/helpers.h @@ -9,6 +9,13 @@ #include "nvim/event/wstream.h" #include "nvim/api/private/defs.h" +/// Value by which objects represented as EXT type are shifted +/// +/// Subtracted when packing, added when unpacking. Used to allow moving +/// buffer/window/tabpage block inside ObjectType enum. This block yet cannot be +/// split or reordered. +#define EXT_OBJECT_TYPE_SHIFT kObjectTypeBuffer + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "msgpack_rpc/helpers.h.generated.h" #endif -- cgit From 872a909150506828f72a63636e1cfd6b72f1b306 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Jan 2017 02:41:37 +0300 Subject: executor: Add :lua command Does not work currently. --- src/nvim/ex_cmds.lua | 2 +- src/nvim/ex_docmd.c | 1 + src/nvim/viml/executor/executor.c | 19 +++++++++++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 92f0669422..b34975f00e 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -1542,7 +1542,7 @@ return { command='lua', flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), addr_type=ADDR_LINES, - func='ex_script_ni', + func='ex_lua', }, { command='luado', diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 486baaad47..aa43b71a0d 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -67,6 +67,7 @@ #include "nvim/event/rstream.h" #include "nvim/event/wstream.h" #include "nvim/shada.h" +#include "nvim/viml/executor/executor.h" static int quitmore = 0; static int ex_pressedreturn = FALSE; diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index acc375881c..8da218270a 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -7,8 +7,10 @@ #include "nvim/garray.h" #include "nvim/func_attr.h" #include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" #include "nvim/vim.h" +#include "nvim/ex_getln.h" #include "nvim/message.h" #include "nvim/viml/executor/executor.h" @@ -171,8 +173,6 @@ static lua_State *global_lstate = NULL; /// Execute lua string /// -/// Used for :lua. -/// /// @param[in] str String to execute. /// @param[out] ret_tv Location where result will be saved. /// @@ -267,3 +267,18 @@ void executor_eval_lua(const String str, typval_T *const arg, NLUA_CALL_C_FUNCTION_3(global_lstate, nlua_eval_lua_string, 0, (void *)&str, arg, ret_tv); } + +/// Run lua string +/// +/// Used for :lua. +/// +/// @param eap VimL command being run. +void ex_lua(exarg_T *const eap) + FUNC_ATTR_NONNULL_ALL +{ + char *const code = (char *)script_get(eap, eap->arg); + typval_T tv = { .v_type = VAR_UNKNOWN }; + executor_exec_lua(cstr_as_string(code), &tv); + clear_tv(&tv); + xfree(code); +} -- cgit From 3531d8c8eaa6783637f510dbc5dbd58c9d435b61 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Jan 2017 03:08:56 +0300 Subject: executor: Add some const qualifiers --- src/nvim/viml/executor/executor.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index 8da218270a..38e3cf1c6d 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -100,7 +100,7 @@ static void nlua_error(lua_State *const lstate, const char *const msg) /// /// Does no error handling: never call it with non-string or with some arguments /// omitted. -static int nlua_stricmp(lua_State *lstate) FUNC_ATTR_NONNULL_ALL +static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { const char *s1 = luaL_checklstring(lstate, 1, NULL); const char *s2 = luaL_checklstring(lstate, 2, NULL); @@ -115,10 +115,10 @@ static int nlua_stricmp(lua_State *lstate) FUNC_ATTR_NONNULL_ALL /// Expects two values on the stack: string to evaluate, pointer to the /// location where result is saved. Always returns nothing (from the lua point /// of view). -static int nlua_exec_lua_string(lua_State *lstate) FUNC_ATTR_NONNULL_ALL +static int nlua_exec_lua_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { - String *str = (String *)lua_touserdata(lstate, 1); - typval_T *ret_tv = (typval_T *)lua_touserdata(lstate, 2); + const String *const str = (String *)lua_touserdata(lstate, 1); + typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 2); lua_pop(lstate, 2); if (luaL_loadbuffer(lstate, str->data, str->size, NLUA_EVAL_NAME)) { @@ -138,7 +138,7 @@ static int nlua_exec_lua_string(lua_State *lstate) FUNC_ATTR_NONNULL_ALL /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. -static int nlua_state_init(lua_State *lstate) FUNC_ATTR_NONNULL_ALL +static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { lua_pushcfunction(lstate, &nlua_stricmp); lua_setglobal(lstate, "stricmp"); @@ -177,14 +177,15 @@ static lua_State *global_lstate = NULL; /// @param[out] ret_tv Location where result will be saved. /// /// @return Result of the execution. -void executor_exec_lua(String str, typval_T *ret_tv) +void executor_exec_lua(const String str, typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { if (global_lstate == NULL) { global_lstate = init_lua(); } - NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_lua_string, 0, &str, ret_tv); + NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_lua_string, 0, + (void *)&str, ret_tv); } /// Evaluate lua string @@ -196,12 +197,12 @@ void executor_exec_lua(String str, typval_T *ret_tv) /// 3. Pointer to location where result is saved. /// /// @param[in,out] lstate Lua interpreter state. -static int nlua_eval_lua_string(lua_State *lstate) +static int nlua_eval_lua_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { - String *str = (String *)lua_touserdata(lstate, 1); - typval_T *arg = (typval_T *)lua_touserdata(lstate, 2); - typval_T *ret_tv = (typval_T *)lua_touserdata(lstate, 3); + const String *const str = (String *)lua_touserdata(lstate, 1); + typval_T *const arg = (typval_T *)lua_touserdata(lstate, 2); + typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 3); lua_pop(lstate, 3); garray_T str_ga; -- cgit From 3d48c35d6bfa83ba3ae621fa9ec3f512da199f59 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Jan 2017 03:36:47 +0300 Subject: ex_getln: Refactor script_get() 1. Use `char *` for strings. 2. Add `const` qualifiers. 3. Add attributes and documentation. 4. Handle skipping *inside*. 5. Handle non-heredoc argument also inside: deferring this to the caller is pointless because all callers need the same thing. Though new ex_lua caller may live without allocations in this case, allocating nevertheless produces cleaner code. 6. Note that all callers call script_get with `eap` and `eap->arg`. Thus second argument is useless in practice: it is one and the same always and can be reached through the first argument. --- src/nvim/ex_cmds2.c | 11 +++---- src/nvim/ex_docmd.c | 8 +++-- src/nvim/ex_getln.c | 68 +++++++++++++++++++++++---------------- src/nvim/viml/executor/executor.c | 9 ++++-- 4 files changed, 58 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 9fc4ef2a02..092bb38dd0 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -3695,19 +3695,18 @@ char_u *get_locales(expand_T *xp, int idx) static void script_host_execute(char *name, exarg_T *eap) { - uint8_t *script = script_get(eap, eap->arg); + size_t len; + char *const script = script_get(eap, &len); - if (!eap->skip) { - list_T *args = list_alloc(); + if (script != NULL) { + list_T *const args = list_alloc(); // script - list_append_string(args, script ? script : eap->arg, -1); + list_append_allocated_string(args, script); // current range list_append_number(args, (int)eap->line1); list_append_number(args, (int)eap->line2); (void)eval_call_provider(name, "execute", args); } - - xfree(script); } static void script_host_execute_file(char *name, exarg_T *eap) diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index aa43b71a0d..74bb6177c6 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -3734,10 +3734,12 @@ void ex_ni(exarg_T *eap) /// Skips over ":perl <skip) + if (!eap->skip) { ex_ni(eap); - else - xfree(script_get(eap, eap->arg)); + } else { + size_t len; + xfree(script_get(eap, &len)); + } } /* diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 58979c0e43..f7e10f3787 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -5343,47 +5343,61 @@ static int ex_window(void) return cmdwin_result; } -/* - * Used for commands that either take a simple command string argument, or: - * cmd << endmarker - * {script} - * endmarker - * Returns a pointer to allocated memory with {script} or NULL. - */ -char_u *script_get(exarg_T *eap, char_u *cmd) +/// Get script string +/// +/// Used for commands which accept either `:command script` or +/// +/// :command << endmarker +/// script +/// endmarker +/// +/// @param eap Command being run. +/// @param[out] lenp Location where length of resulting string is saved. Will +/// be set to zero when skipping. +/// +/// @return [allocated] NULL or script. Does not show any error messages. +/// NULL is returned when skipping and on error. +char *script_get(exarg_T *const eap, size_t *const lenp) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC { - char_u *theline; - char *end_pattern = NULL; - char dot[] = "."; - garray_T ga; + const char *const cmd = (const char *)eap->arg; - if (cmd[0] != '<' || cmd[1] != '<' || eap->getline == NULL) - return NULL; - - ga_init(&ga, 1, 0x400); + if (cmd[0] != '<' || cmd[1] != '<' || eap->getline == NULL) { + *lenp = STRLEN(eap->arg); + return xmemdupz(eap->arg, *lenp); + } - if (cmd[2] != NUL) - end_pattern = (char *)skipwhite(cmd + 2); - else - end_pattern = dot; + garray_T ga = { .ga_data = NULL, .ga_len = 0 }; + if (!eap->skip) { + ga_init(&ga, 1, 0x400); + } - for (;; ) { - theline = eap->getline( + const char *const end_pattern = ( + cmd[2] != NUL + ? (const char *)skipwhite((const char_u *)cmd + 2) + : "."); + for (;;) { + char *const theline = (char *)eap->getline( eap->cstack->cs_looplevel > 0 ? -1 : NUL, eap->cookie, 0); - if (theline == NULL || STRCMP(end_pattern, theline) == 0) { + if (theline == NULL || strcmp(end_pattern, theline) == 0) { xfree(theline); break; } - ga_concat(&ga, theline); - ga_append(&ga, '\n'); + if (!eap->skip) { + ga_concat(&ga, (const char_u *)theline); + ga_append(&ga, '\n'); + } xfree(theline); } - ga_append(&ga, NUL); + *lenp = (size_t)ga.ga_len; // Set length without trailing NUL. + if (!eap->skip) { + ga_append(&ga, NUL); + } - return (char_u *)ga.ga_data; + return (char *)ga.ga_data; } /// Iterate over history items diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index 38e3cf1c6d..fb39716f5c 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -277,9 +277,14 @@ void executor_eval_lua(const String str, typval_T *const arg, void ex_lua(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { - char *const code = (char *)script_get(eap, eap->arg); + size_t len; + char *const code = script_get(eap, &len); + if (eap->skip) { + xfree(code); + return; + } typval_T tv = { .v_type = VAR_UNKNOWN }; - executor_exec_lua(cstr_as_string(code), &tv); + executor_exec_lua((String) { .data = code, .size = len }, &tv); clear_tv(&tv); xfree(code); } -- cgit From 9114d9be778b07f4a49edc078f1c159aa51320d8 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Jan 2017 18:40:39 +0300 Subject: executor: Add :luado command --- src/nvim/ex_cmds.lua | 2 +- src/nvim/macros.h | 9 +++ src/nvim/viml/executor/executor.c | 121 +++++++++++++++++++++++++++++++++++++- 3 files changed, 128 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index b34975f00e..25e74ebf9b 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -1548,7 +1548,7 @@ return { command='luado', flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), addr_type=ADDR_LINES, - func='ex_ni', + func='ex_luado', }, { command='luafile', diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 650bf76156..5042663041 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -19,6 +19,15 @@ # define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) #endif +/// String with length +/// +/// For use in functions which accept (char *s, size_t len) pair in arguments. +/// +/// @param[in] s Static string. +/// +/// @return `s, sizeof(s) - 1` +#define S_LEN(s) (s), (sizeof(s) - 1) + /* * Position comparisons */ diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index fb39716f5c..80f2651afc 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -12,6 +12,13 @@ #include "nvim/vim.h" #include "nvim/ex_getln.h" #include "nvim/message.h" +#include "nvim/memline.h" +#include "nvim/buffer_defs.h" +#include "nvim/macros.h" +#include "nvim/screen.h" +#include "nvim/cursor.h" +#include "nvim/undo.h" +#include "nvim/ascii.h" #include "nvim/viml/executor/executor.h" #include "nvim/viml/executor/converter.h" @@ -38,6 +45,17 @@ typedef struct { lua_pushcfunction(lstate, &function); \ lua_call(lstate, 0, numret); \ } while (0) +/// Call C function which expects one argument +/// +/// @param function Called function +/// @param numret Number of returned arguments +/// @param a… Supplied argument (should be a void* pointer) +#define NLUA_CALL_C_FUNCTION_1(lstate, function, numret, a1) \ + do { \ + lua_pushcfunction(lstate, &function); \ + lua_pushlightuserdata(lstate, a1); \ + lua_call(lstate, 1, numret); \ + } while (0) /// Call C function which expects two arguments /// /// @param function Called function @@ -117,7 +135,7 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL /// of view). static int nlua_exec_lua_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { - const String *const str = (String *)lua_touserdata(lstate, 1); + const String *const str = (const String *)lua_touserdata(lstate, 1); typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 2); lua_pop(lstate, 2); @@ -135,6 +153,79 @@ static int nlua_exec_lua_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL return 0; } +/// Evaluate lua string for each line in range +/// +/// Expects two values on the stack: string to evaluate and pointer to integer +/// array with line range. Always returns nothing (from the lua point of view). +static int nlua_exec_luado_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +{ + const String *const str = (const String *)lua_touserdata(lstate, 1); + const linenr_T *const range = (const linenr_T *)lua_touserdata(lstate, 1); + lua_pop(lstate, 1); + +#define DOSTART "return function(line, linenr) " +#define DOEND " end" + const size_t lcmd_len = str->size + (sizeof(DOSTART) - 1) + (sizeof(DOEND) - 1); + char *lcmd; + if (lcmd_len < IOSIZE) { + lcmd = (char *)IObuff; + } else { + lcmd = xmalloc(lcmd_len); + } + memcpy(lcmd, S_LEN(DOSTART)); + memcpy(lcmd + sizeof(DOSTART) - 1, str->data, str->size); + memcpy(lcmd + sizeof(DOSTART) - 1 + str->size, S_LEN(DOEND)); +#undef DOSTART +#undef DOEND + + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { + nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s")); + return 0; + } + if (lua_pcall(lstate, 0, 1, 0)) { + nlua_error(lstate, _("E5110: Error while creating lua function: %.*s")); + return 0; + } + for (linenr_T l = range[0]; l < range[1]; l++) { + if (l > curbuf->b_ml.ml_line_count) { + break; + } + lua_pushvalue(lstate, -1); + lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false)); + lua_pushnumber(lstate, (lua_Number)l); + if (lua_pcall(lstate, 2, 1, 0)) { + nlua_error(lstate, _("E5111: Error while calling lua function: %.*s")); + break; + } + if (lua_isstring(lstate, -1)) { + if (sandbox) { + EMSG(_("E5112: Not allowed in sandbox")); + lua_pop(lstate, 1); + break; + } + size_t new_line_len; + const char *new_line = lua_tolstring(lstate, -1, &new_line_len); + char *const new_line_transformed = ( + new_line_len < IOSIZE + ? memcpy(IObuff, new_line, new_line_len) + : xmemdupz(new_line, new_line_len)); + new_line_transformed[new_line_len] = NUL; + for (size_t i = 0; i < new_line_len; i++) { + if (new_line_transformed[new_line_len] == NUL) { + new_line_transformed[new_line_len] = '\n'; + } + } + ml_replace(l, (char_u *)new_line_transformed, true); + changed_bytes(l, 0); + } + lua_pop(lstate, 1); + } + lua_pop(lstate, 1); + check_cursor(); + update_screen(NOT_VALID); + return 0; +} + /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. @@ -200,7 +291,7 @@ void executor_exec_lua(const String str, typval_T *const ret_tv) static int nlua_eval_lua_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { - const String *const str = (String *)lua_touserdata(lstate, 1); + const String *const str = (const String *)lua_touserdata(lstate, 1); typval_T *const arg = (typval_T *)lua_touserdata(lstate, 2); typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 3); lua_pop(lstate, 3); @@ -215,7 +306,7 @@ static int nlua_eval_lua_string(lua_State *const lstate) } else { lcmd = xmalloc(lcmd_len); } - memcpy(lcmd, EVALHEADER, sizeof(EVALHEADER) - 1); + memcpy(lcmd, S_LEN(EVALHEADER)); memcpy(lcmd + sizeof(EVALHEADER) - 1, str->data, str->size); lcmd[lcmd_len - 1] = ')'; #undef EVALHEADER @@ -288,3 +379,27 @@ void ex_lua(exarg_T *const eap) clear_tv(&tv); xfree(code); } + +/// Run lua string for each line in range +/// +/// Used for :luado. +/// +/// @param eap VimL command being run. +void ex_luado(exarg_T *const eap) + FUNC_ATTR_NONNULL_ALL +{ + if (global_lstate == NULL) { + global_lstate = init_lua(); + } + if (u_save(eap->line1 - 1, eap->line2 + 1) == FAIL) { + EMSG(_("cannot save undo information")); + return; + } + const String cmd = { + .size = STRLEN(eap->arg), + .data = (char *)eap->arg, + }; + const linenr_T range[] = { eap->line1, eap->line2 }; + NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_luado_string, 0, + (void *)&cmd, (void *)range); +} -- cgit From e1bbaca7acf7fc498da49081d069b00aa05506df Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Jan 2017 19:26:22 +0300 Subject: executor,functests: Add tests for :luado, also some fixes Fixes: 1. Allocate space for the NUL byte. 2. Do not exclude last line from range. 3. Remove code for sandbox: it is handled earlier. 4. Fix index in new_line_transformed when converting NULs to NLs. 5. Always allocate new_line_transformed, but save allocated value. --- src/nvim/viml/executor/executor.c | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index 80f2651afc..c426806b6f 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -160,17 +160,19 @@ static int nlua_exec_lua_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL static int nlua_exec_luado_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { const String *const str = (const String *)lua_touserdata(lstate, 1); - const linenr_T *const range = (const linenr_T *)lua_touserdata(lstate, 1); + const linenr_T *const range = (const linenr_T *)lua_touserdata(lstate, 2); lua_pop(lstate, 1); #define DOSTART "return function(line, linenr) " #define DOEND " end" - const size_t lcmd_len = str->size + (sizeof(DOSTART) - 1) + (sizeof(DOEND) - 1); + const size_t lcmd_len = (str->size + + (sizeof(DOSTART) - 1) + + (sizeof(DOEND) - 1)); char *lcmd; if (lcmd_len < IOSIZE) { lcmd = (char *)IObuff; } else { - lcmd = xmalloc(lcmd_len); + lcmd = xmalloc(lcmd_len + 1); } memcpy(lcmd, S_LEN(DOSTART)); memcpy(lcmd + sizeof(DOSTART) - 1, str->data, str->size); @@ -186,7 +188,7 @@ static int nlua_exec_luado_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL nlua_error(lstate, _("E5110: Error while creating lua function: %.*s")); return 0; } - for (linenr_T l = range[0]; l < range[1]; l++) { + for (linenr_T l = range[0]; l <= range[1]; l++) { if (l > curbuf->b_ml.ml_line_count) { break; } @@ -198,24 +200,15 @@ static int nlua_exec_luado_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL break; } if (lua_isstring(lstate, -1)) { - if (sandbox) { - EMSG(_("E5112: Not allowed in sandbox")); - lua_pop(lstate, 1); - break; - } size_t new_line_len; - const char *new_line = lua_tolstring(lstate, -1, &new_line_len); - char *const new_line_transformed = ( - new_line_len < IOSIZE - ? memcpy(IObuff, new_line, new_line_len) - : xmemdupz(new_line, new_line_len)); - new_line_transformed[new_line_len] = NUL; + const char *const new_line = lua_tolstring(lstate, -1, &new_line_len); + char *const new_line_transformed = xmemdupz(new_line, new_line_len); for (size_t i = 0; i < new_line_len; i++) { - if (new_line_transformed[new_line_len] == NUL) { - new_line_transformed[new_line_len] = '\n'; + if (new_line_transformed[i] == NUL) { + new_line_transformed[i] = '\n'; } } - ml_replace(l, (char_u *)new_line_transformed, true); + ml_replace(l, (char_u *)new_line_transformed, false); changed_bytes(l, 0); } lua_pop(lstate, 1); -- cgit From 295e7607c472b49e8c0762977e38c4527b746b6f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Jan 2017 19:32:01 +0300 Subject: executor: Fix some memory leaks --- src/nvim/viml/executor/executor.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index c426806b6f..df63667102 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -182,8 +182,14 @@ static int nlua_exec_luado_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s")); + if (lcmd_len >= IOSIZE) { + xfree(lcmd); + } return 0; } + if (lcmd_len >= IOSIZE) { + xfree(lcmd); + } if (lua_pcall(lstate, 0, 1, 0)) { nlua_error(lstate, _("E5110: Error while creating lua function: %.*s")); return 0; -- cgit From dcb992ab3759b604c1824913002714a018be0532 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Jan 2017 19:51:55 +0300 Subject: executor: Add :luafile command --- src/nvim/ex_cmds.lua | 2 +- src/nvim/viml/executor/executor.c | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 25e74ebf9b..ad2fb642d4 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -1554,7 +1554,7 @@ return { command='luafile', flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), addr_type=ADDR_LINES, - func='ex_ni', + func='ex_luafile', }, { command='lvimgrep', diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index df63667102..a01bf9966d 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -161,7 +161,7 @@ static int nlua_exec_luado_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { const String *const str = (const String *)lua_touserdata(lstate, 1); const linenr_T *const range = (const linenr_T *)lua_touserdata(lstate, 2); - lua_pop(lstate, 1); + lua_pop(lstate, 2); #define DOSTART "return function(line, linenr) " #define DOEND " end" @@ -225,6 +225,26 @@ static int nlua_exec_luado_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL return 0; } +/// Evaluate lua file +/// +/// Expects one value on the stack: file to evaluate. Always returns nothing +/// (from the lua point of view). +static int nlua_exec_lua_file(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +{ + const char *const filename = (const char *)lua_touserdata(lstate, 1); + lua_pop(lstate, 1); + + if (luaL_loadfile(lstate, filename)) { + nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s")); + return 0; + } + if (lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("E5113: Error while calling lua chunk: %.*s")); + return 0; + } + return 0; +} + /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. @@ -402,3 +422,18 @@ void ex_luado(exarg_T *const eap) NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_luado_string, 0, (void *)&cmd, (void *)range); } + +/// Run lua file +/// +/// Used for :luafile. +/// +/// @param eap VimL command being run. +void ex_luafile(exarg_T *const eap) + FUNC_ATTR_NONNULL_ALL +{ + if (global_lstate == NULL) { + global_lstate = init_lua(); + } + NLUA_CALL_C_FUNCTION_1(global_lstate, nlua_exec_lua_file, 0, + (void *)eap->arg); +} -- cgit From 1801d44f53e4340f5483902e6f0a2aa0ab4f551a Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Jan 2017 20:20:41 +0300 Subject: executor: Do not use S_LEN for memcpy Sometimes it is implemented as a macro and `S_LEN` is treated as a single argument in this case. --- src/nvim/viml/executor/executor.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index a01bf9966d..ad92b52f3e 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -174,9 +174,9 @@ static int nlua_exec_luado_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL } else { lcmd = xmalloc(lcmd_len + 1); } - memcpy(lcmd, S_LEN(DOSTART)); + memcpy(lcmd, DOSTART, sizeof(DOSTART) - 1); memcpy(lcmd + sizeof(DOSTART) - 1, str->data, str->size); - memcpy(lcmd + sizeof(DOSTART) - 1 + str->size, S_LEN(DOEND)); + memcpy(lcmd + sizeof(DOSTART) - 1 + str->size, DOEND, sizeof(DOEND) - 1); #undef DOSTART #undef DOEND @@ -325,7 +325,7 @@ static int nlua_eval_lua_string(lua_State *const lstate) } else { lcmd = xmalloc(lcmd_len); } - memcpy(lcmd, S_LEN(EVALHEADER)); + memcpy(lcmd, EVALHEADER, sizeof(EVALHEADER) - 1); memcpy(lcmd + sizeof(EVALHEADER) - 1, str->data, str->size); lcmd[lcmd_len - 1] = ')'; #undef EVALHEADER -- cgit From f2ad6201d94fec1e0c98550e55f3b069fa24a68b Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Jan 2017 21:03:36 +0300 Subject: api: Use a form of `1 << 63` for INTERNAL_CALL_MASK --- src/nvim/api/private/defs.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 432ab347bc..8bd1dd5085 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -36,9 +36,7 @@ typedef enum { #define NO_RESPONSE UINT64_MAX /// Mask for all internal calls -#define INTERNAL_CALL_MASK (UINT64_MAX ^ (UINT64_MAX >> 1)) -// (1 << 63) in all forms produces “warning: shift count >= width of type -// [-Wshift-count-overflow]” +#define INTERNAL_CALL_MASK (((uint64_t)1) << (sizeof(uint64_t) * 8 - 1)) /// Internal call from VimL code #define VIML_INTERNAL_CALL INTERNAL_CALL_MASK -- cgit From 90e2a043e3394f83eb5d5e32e13cda00d752f212 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Jan 2017 21:58:55 +0300 Subject: executor: Add print() function --- src/nvim/viml/executor/executor.c | 74 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) (limited to 'src') diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index ad92b52f3e..48149c4420 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -252,6 +252,8 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { lua_pushcfunction(lstate, &nlua_stricmp); lua_setglobal(lstate, "stricmp"); + lua_pushcfunction(lstate, &nlua_print); + lua_setglobal(lstate, "print"); if (luaL_dostring(lstate, (char *)&vim_module[0])) { nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); return 1; @@ -358,6 +360,78 @@ static int nlua_eval_lua_string(lua_State *const lstate) return 0; } +/// Print as a Vim message +/// +/// @param lstate Lua interpreter state. +static int nlua_print(lua_State *const lstate) + FUNC_ATTR_NONNULL_ALL +{ +#define PRINT_ERROR(msg) \ + do { \ + errmsg = msg; \ + errmsg_len = sizeof(msg) - 1; \ + goto nlua_print_error; \ + } while (0) + const int nargs = lua_gettop(lstate); + lua_getglobal(lstate, "tostring"); + const char *errmsg = NULL; + size_t errmsg_len = 0; + garray_T msg_ga; + ga_init(&msg_ga, 1, 80); + int curargidx = 1; + for (; curargidx <= nargs; curargidx++) { + lua_pushvalue(lstate, -1); // tostring + lua_pushvalue(lstate, curargidx); // arg + if (lua_pcall(lstate, 1, 1, 0)) { + errmsg = lua_tolstring(lstate, -1, &errmsg_len); + if (!errmsg) { + PRINT_ERROR(""); + } + goto nlua_print_error; + } + size_t len; + const char *const s = lua_tolstring(lstate, -1, &len); + if (s == NULL) { + PRINT_ERROR( + ""); + } + ga_concat_len(&msg_ga, s, len); + if (curargidx < nargs) { + ga_append(&msg_ga, ' '); + } + lua_pop(lstate, 1); + } +#undef PRINT_ERROR + lua_pop(lstate, nargs + 1); + ga_append(&msg_ga, NUL); + { + const size_t len = (size_t)msg_ga.ga_len - 1; + char *const str = (char *)msg_ga.ga_data; + + for (size_t i = 0; i < len - 1;) { + const size_t start = i; + while (str[i] != NL && i < len - 1) { + if (str[i] == NUL) { + str[i] = NL; + } + i++; + } + if (str[i] == NL) { + str[i] = NUL; + } + msg((char_u *)str + start); + } + } + ga_clear(&msg_ga); + return 0; +nlua_print_error: + emsgf(_("E5114: Error while converting print argument #%i: %.*s"), + curargidx, errmsg_len, errmsg); + ga_clear(&msg_ga); + lua_pop(lstate, lua_gettop(lstate)); + return 0; +} + /// Evaluate lua string /// /// Used for luaeval(). -- cgit From 9fd2bf67aa1db66e3465753d5aaaec342f4ce193 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 29 Jan 2017 23:22:50 +0300 Subject: executor,functests: Add print() tests, some fixes --- src/nvim/viml/executor/executor.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index 48149c4420..4eb63f38ad 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -384,9 +384,6 @@ static int nlua_print(lua_State *const lstate) lua_pushvalue(lstate, curargidx); // arg if (lua_pcall(lstate, 1, 1, 0)) { errmsg = lua_tolstring(lstate, -1, &errmsg_len); - if (!errmsg) { - PRINT_ERROR(""); - } goto nlua_print_error; } size_t len; @@ -408,19 +405,32 @@ static int nlua_print(lua_State *const lstate) const size_t len = (size_t)msg_ga.ga_len - 1; char *const str = (char *)msg_ga.ga_data; - for (size_t i = 0; i < len - 1;) { + for (size_t i = 0; i < len;) { const size_t start = i; - while (str[i] != NL && i < len - 1) { - if (str[i] == NUL) { - str[i] = NL; + while (i < len) { + switch (str[i]) { + case NUL: { + str[i] = NL; + i++; + continue; + } + case NL: { + str[i] = NUL; + i++; + break; + } + default: { + i++; + continue; + } } - i++; - } - if (str[i] == NL) { - str[i] = NUL; + break; } msg((char_u *)str + start); } + if (str[len - 1] == NUL) { // Last was newline + msg((char_u *)""); + } } ga_clear(&msg_ga); return 0; -- cgit From 73d37f8b6e9eba0d2f2c59694ae5a13777a7f1cd Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 30 Jan 2017 00:34:33 +0300 Subject: executor: Add :lua debug.debug mock --- src/nvim/eval.c | 177 ++++++++++++++++++++------------------ src/nvim/ex_docmd.c | 4 +- src/nvim/message.c | 3 +- src/nvim/viml/executor/executor.c | 38 ++++++++ 4 files changed, 136 insertions(+), 86 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 8b6638f1d7..fa817bb705 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -12280,93 +12280,70 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) static int inputsecret_flag = 0; +/// Get user input +/// +/// Used for f_input and f_inputdialog functions. +/// +/// @param[in] prompt Input prompt. +/// @param[in] initval Initial value, may be NULL. +/// @param[in] xp_name Completion, for input(). +/// @param[in] cancelval Value returned when user cancelled dialog, for +/// inputdialog(). +/// +/// @return [allocated] User input or NULL. +char *get_user_input(const char *const prompt, + const char *const initval, + const char *const xp_name, + const char *const cancelval) + FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC +{ + char *ret = NULL; + const int saved_cmd_silent = cmd_silent; + cmd_silent = false; // Want to see the prompt. + // Only the part of the message after the last NL is considered as + // prompt for the command line. + const char *p = strrchr(prompt, NL); + if (p == NULL) { + p = prompt; + } else { + p++; + msg_start(); + msg_clr_eos(); + msg_puts_attr_len(prompt, (int)(p - prompt) + 1, echo_attr); + msg_didout = false; + msg_starthere(); + } + cmdline_row = msg_row; -/* - * This function is used by f_input() and f_inputdialog() functions. The third - * argument to f_input() specifies the type of completion to use at the - * prompt. The third argument to f_inputdialog() specifies the value to return - * when the user cancels the prompt. - */ -static void get_user_input(typval_T *argvars, typval_T *rettv, int inputdialog) -{ - char_u *prompt = get_tv_string_chk(&argvars[0]); - char_u *p = NULL; - int c; - char_u buf[NUMBUFLEN]; - int cmd_silent_save = cmd_silent; - char_u *defstr = (char_u *)""; - int xp_type = EXPAND_NOTHING; - char_u *xp_arg = NULL; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; + stuffReadbuffSpec((char_u *)initval); - cmd_silent = FALSE; /* Want to see the prompt. */ - if (prompt != NULL) { - /* Only the part of the message after the last NL is considered as - * prompt for the command line */ - p = vim_strrchr(prompt, '\n'); - if (p == NULL) - p = prompt; - else { - ++p; - c = *p; - *p = NUL; - msg_start(); - msg_clr_eos(); - msg_puts_attr((const char *)prompt, echo_attr); - msg_didout = false; - msg_starthere(); - *p = c; + char *xp_arg = NULL; + int xp_type = EXPAND_NOTHING; + if (xp_name != NULL) { + uint32_t argt; + if (parse_compl_arg((const char_u *)xp_name, (int)strlen(xp_name), + &xp_type, &argt, (char_u **)&xp_arg) == FAIL) { + return NULL; } - cmdline_row = msg_row; - - if (argvars[1].v_type != VAR_UNKNOWN) { - defstr = get_tv_string_buf_chk(&argvars[1], buf); - if (defstr != NULL) - stuffReadbuffSpec(defstr); - - if (!inputdialog && argvars[2].v_type != VAR_UNKNOWN) { - char_u *xp_name; - int xp_namelen; - uint32_t argt; - - /* input() with a third argument: completion */ - rettv->vval.v_string = NULL; - - xp_name = get_tv_string_buf_chk(&argvars[2], buf); - if (xp_name == NULL) - return; + } - xp_namelen = (int)STRLEN(xp_name); + const int saved_ex_normal_busy = ex_normal_busy; + ex_normal_busy = 0; + ret = (char *)getcmdline_prompt(inputsecret_flag ? NUL : '@', (char_u *)p, + echo_attr, xp_type, (char_u *)xp_arg); + ex_normal_busy = saved_ex_normal_busy; - if (parse_compl_arg(xp_name, xp_namelen, &xp_type, &argt, - &xp_arg) == FAIL) - return; - } - } - - if (defstr != NULL) { - int save_ex_normal_busy = ex_normal_busy; - ex_normal_busy = 0; - rettv->vval.v_string = - getcmdline_prompt(inputsecret_flag ? NUL : '@', p, echo_attr, - xp_type, xp_arg); - ex_normal_busy = save_ex_normal_busy; - } - if (inputdialog && rettv->vval.v_string == NULL - && argvars[1].v_type != VAR_UNKNOWN - && argvars[2].v_type != VAR_UNKNOWN) - rettv->vval.v_string = vim_strsave(get_tv_string_buf( - &argvars[2], buf)); + if (ret == NULL && cancelval != NULL) { + ret = xstrdup(cancelval); + } - xfree(xp_arg); + xfree(xp_arg); - /* since the user typed this, no need to wait for return */ - need_wait_return = FALSE; - msg_didout = FALSE; - } - cmd_silent = cmd_silent_save; + // Since the user typed this, no need to wait for return. + need_wait_return = false; + msg_didout = false; + cmd_silent = saved_cmd_silent; + return ret; } /* @@ -12375,7 +12352,25 @@ static void get_user_input(typval_T *argvars, typval_T *rettv, int inputdialog) */ static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - get_user_input(argvars, rettv, FALSE); + char initval_buf[NUMBUFLEN]; + char xp_name_buf[NUMBUFLEN]; + const char *const prompt = (const char *)get_tv_string_chk(&argvars[0]); + const char *const initval = ( + argvars[1].v_type != VAR_UNKNOWN + ? (const char *)get_tv_string_buf(&argvars[1], (char_u *)initval_buf) + : ""); + const char *const xp_name = ( + argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN + ? (const char *)get_tv_string_buf(&argvars[2], (char_u *)xp_name_buf) + : NULL); + if (prompt == NULL || initval == NULL || ( + argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN + && xp_name == NULL)) { + return; + } + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)get_user_input(prompt, initval, xp_name, + NULL); } /* @@ -12383,7 +12378,25 @@ static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - get_user_input(argvars, rettv, TRUE); + char initval_buf[NUMBUFLEN]; + char cancelval_buf[NUMBUFLEN]; + const char *const prompt = (const char *)get_tv_string_chk(&argvars[0]); + const char *const initval = ( + argvars[1].v_type != VAR_UNKNOWN + ? (const char *)get_tv_string_buf(&argvars[1], (char_u *)initval_buf) + : ""); + const char *const cancelval = ( + argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN + ? (const char *)get_tv_string_buf(&argvars[2], (char_u *)cancelval_buf) + : NULL); + if (prompt == NULL || initval == NULL || ( + argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN + && cancelval == NULL)) { + return; + } + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *)get_user_input(prompt, initval, NULL, + cancelval); } /* diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 74bb6177c6..69dab9c18f 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5757,10 +5757,10 @@ int parse_addr_type_arg(char_u *value, int vallen, uint32_t *argt, * copied to allocated memory and stored in "*compl_arg". * Returns FAIL if something is wrong. */ -int parse_compl_arg(char_u *value, int vallen, int *complp, +int parse_compl_arg(const char_u *value, int vallen, int *complp, uint32_t *argt, char_u **compl_arg) { - char_u *arg = NULL; + const char_u *arg = NULL; size_t arglen = 0; int i; int valend = vallen; diff --git a/src/nvim/message.c b/src/nvim/message.c index bf54284881..4b786c11dd 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1564,7 +1564,6 @@ void msg_puts_attr(const char *const s, const int attr) /// Like msg_puts_attr(), but with a maximum length "maxlen" (in bytes). /// When "maxlen" is -1 there is no maximum length. -/// When "maxlen" is >= 0 the message is not put in the history. void msg_puts_attr_len(const char *str, const ptrdiff_t maxlen, int attr) { // If redirection is on, also write to the redirection file. @@ -1576,7 +1575,7 @@ void msg_puts_attr_len(const char *str, const ptrdiff_t maxlen, int attr) } // if MSG_HIST flag set, add message to history - if ((attr & MSG_HIST) && maxlen < 0) { + if (attr & MSG_HIST) { add_msg_hist(str, -1, attr); attr &= ~MSG_HIST; } diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c index 4eb63f38ad..d0e269bf6a 100644 --- a/src/nvim/viml/executor/executor.c +++ b/src/nvim/viml/executor/executor.c @@ -250,15 +250,28 @@ static int nlua_exec_lua_file(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL /// Called by lua interpreter itself to initialize state. static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { + // stricmp lua_pushcfunction(lstate, &nlua_stricmp); lua_setglobal(lstate, "stricmp"); + + // print lua_pushcfunction(lstate, &nlua_print); lua_setglobal(lstate, "print"); + + // debug.debug + lua_getglobal(lstate, "debug"); + lua_pushcfunction(lstate, &nlua_debug); + lua_setfield(lstate, -2, "debug"); + lua_pop(lstate, 1); + + // vim if (luaL_dostring(lstate, (char *)&vim_module[0])) { nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); return 1; } + // vim.api nlua_add_api_functions(lstate); + // vim.types, vim.type_idx, vim.val_idx nlua_init_types(lstate); lua_setglobal(lstate, "vim"); return 0; @@ -442,6 +455,31 @@ nlua_print_error: return 0; } +/// debug.debug implementation: interaction with user while debugging +/// +/// @param lstate Lua interpreter state. +int nlua_debug(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + for (;;) { + lua_settop(lstate, 0); + char *const input = get_user_input("lua_debug> ", "", NULL, NULL); + msg_putchar('\n'); // Avoid outputting on input line. + if (input == NULL || *input == NUL || strcmp(input, "cont") == 0) { + xfree(input); + return 0; + } + if (luaL_loadbuffer(lstate, input, strlen(input), "=(debug command)")) { + nlua_error(lstate, _("E5115: Error while loading debug string: %.*s")); + } + xfree(input); + if (lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("E5116: Error while calling debug string: %.*s")); + } + } + return 0; +} + /// Evaluate lua string /// /// Used for luaeval(). -- cgit From a24e94215ed0a4bfe55b6741ab2e9477b278dbb7 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 30 Jan 2017 03:35:45 +0300 Subject: eval,functests: Fix linter errors --- src/nvim/eval.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index fa817bb705..67b755f053 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -12363,8 +12363,8 @@ static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr) argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN ? (const char *)get_tv_string_buf(&argvars[2], (char_u *)xp_name_buf) : NULL); - if (prompt == NULL || initval == NULL || ( - argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN + if (prompt == NULL || initval == NULL + || (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN && xp_name == NULL)) { return; } @@ -12389,8 +12389,8 @@ static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN ? (const char *)get_tv_string_buf(&argvars[2], (char_u *)cancelval_buf) : NULL); - if (prompt == NULL || initval == NULL || ( - argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN + if (prompt == NULL || initval == NULL + || (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN && cancelval == NULL)) { return; } -- cgit From 5992cdf3c27ee9c73cea22e288c6ea6d54867394 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 30 Jan 2017 23:13:06 +0300 Subject: cmake: Use set_property in place of target_include_dirs Should work with cmake-2.8.7. --- src/nvim/CMakeLists.txt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 0cf331206e..a6ae758a8e 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -321,9 +321,11 @@ target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES}) install_helper(TARGETS nvim) if(PREFER_LUAJIT) - target_include_directories(nvim SYSTEM PRIVATE ${LUAJIT_INCLUDE_DIRS}) + set_property(TARGET nvim APPEND PROPERTY + INCLUDE_DIRECTORIES ${LUAJIT_INCLUDE_DIRS}) else() - target_include_directories(nvim SYSTEM PRIVATE ${LUA_INCLUDE_DIRS}) + set_property(TARGET nvim APPEND PROPERTY + INCLUDE_DIRECTORIES ${LUA_INCLUDE_DIRS}) endif() if(WIN32) @@ -374,7 +376,8 @@ endif() add_library(libnvim STATIC EXCLUDE_FROM_ALL ${NEOVIM_GENERATED_SOURCES} ${NEOVIM_SOURCES} ${NEOVIM_HEADERS}) -target_include_directories(libnvim SYSTEM PRIVATE ${LUAJIT_INCLUDE_DIRS}) +set_property(TARGET libnvim APPEND PROPERTY + INCLUDE_DIRECTORIES ${LUAJIT_INCLUDE_DIRS}) target_link_libraries(libnvim ${NVIM_TEST_LINK_LIBRARIES}) set_target_properties(libnvim PROPERTIES POSITION_INDEPENDENT_CODE ON @@ -385,7 +388,8 @@ set_property(TARGET libnvim add_library(nvim-test MODULE EXCLUDE_FROM_ALL ${NEOVIM_GENERATED_SOURCES} ${NEOVIM_SOURCES} ${UNIT_TEST_FIXTURES} ${NEOVIM_HEADERS}) target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES}) -target_include_directories(nvim-test SYSTEM PRIVATE ${LUAJIT_INCLUDE_DIRS}) +set_property(TARGET nvim-test APPEND PROPERTY + INCLUDE_DIRECTORIES ${LUAJIT_INCLUDE_DIRS}) set_target_properties(nvim-test PROPERTIES POSITION_INDEPENDENT_CODE ON) set_property(TARGET nvim-test -- cgit From 7b6b629e1a1e3c9f49f75f3bf2c53bb76f77e209 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 8 Apr 2017 20:30:26 +0300 Subject: api: Add FUNC_API_SINCE(1) to new functions --- src/nvim/api/vim.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 511c87f408..05317b8d69 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -844,6 +844,7 @@ static void write_msg(String message, bool to_err) /// /// @return its argument. Object _vim_id(Object obj) + FUNC_API_SINCE(1) { return copy_object(obj); } @@ -857,6 +858,7 @@ Object _vim_id(Object obj) /// /// @return its argument. Array _vim_id_array(Array arr) + FUNC_API_SINCE(1) { return copy_object(ARRAY_OBJ(arr)).data.array; } @@ -870,6 +872,7 @@ Array _vim_id_array(Array arr) /// /// @return its argument. Dictionary _vim_id_dictionary(Dictionary dct) + FUNC_API_SINCE(1) { return copy_object(DICTIONARY_OBJ(dct)).data.dictionary; } @@ -883,6 +886,7 @@ Dictionary _vim_id_dictionary(Dictionary dct) /// /// @return its argument. Float _vim_id_float(Float flt) + FUNC_API_SINCE(1) { return flt; } -- cgit From a40a969e9a4776f1e274dcf0e59c8f1ec1770ca0 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 8 Apr 2017 20:33:48 +0300 Subject: api: Rename _vim_id functions to nvim__id --- src/nvim/api/vim.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 05317b8d69..9ee3827040 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -843,7 +843,7 @@ static void write_msg(String message, bool to_err) /// @param[in] obj Object to return. /// /// @return its argument. -Object _vim_id(Object obj) +Object nvim__id(Object obj) FUNC_API_SINCE(1) { return copy_object(obj); @@ -857,7 +857,7 @@ Object _vim_id(Object obj) /// @param[in] arr Array to return. /// /// @return its argument. -Array _vim_id_array(Array arr) +Array nvim__id_array(Array arr) FUNC_API_SINCE(1) { return copy_object(ARRAY_OBJ(arr)).data.array; @@ -871,7 +871,7 @@ Array _vim_id_array(Array arr) /// @param[in] dct Dictionary to return. /// /// @return its argument. -Dictionary _vim_id_dictionary(Dictionary dct) +Dictionary nvim__id_dictionary(Dictionary dct) FUNC_API_SINCE(1) { return copy_object(DICTIONARY_OBJ(dct)).data.dictionary; @@ -885,7 +885,7 @@ Dictionary _vim_id_dictionary(Dictionary dct) /// @param[in] flt Value to return. /// /// @return its argument. -Float _vim_id_float(Float flt) +Float nvim__id_float(Float flt) FUNC_API_SINCE(1) { return flt; -- cgit From f3093bc508631930d41e9603b43687f4e51af2b3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 10 Apr 2017 23:10:01 +0300 Subject: api: Bump nvim__*id functions since value --- src/nvim/api/vim.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 9ee3827040..0056ea1725 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -844,7 +844,7 @@ static void write_msg(String message, bool to_err) /// /// @return its argument. Object nvim__id(Object obj) - FUNC_API_SINCE(1) + FUNC_API_SINCE(2) { return copy_object(obj); } @@ -858,7 +858,7 @@ Object nvim__id(Object obj) /// /// @return its argument. Array nvim__id_array(Array arr) - FUNC_API_SINCE(1) + FUNC_API_SINCE(2) { return copy_object(ARRAY_OBJ(arr)).data.array; } @@ -872,7 +872,7 @@ Array nvim__id_array(Array arr) /// /// @return its argument. Dictionary nvim__id_dictionary(Dictionary dct) - FUNC_API_SINCE(1) + FUNC_API_SINCE(2) { return copy_object(DICTIONARY_OBJ(dct)).data.dictionary; } @@ -886,7 +886,7 @@ Dictionary nvim__id_dictionary(Dictionary dct) /// /// @return its argument. Float nvim__id_float(Float flt) - FUNC_API_SINCE(1) + FUNC_API_SINCE(2) { return flt; } -- cgit From 57308c4f82aa18b4c6a1af76d224c40e442c9a1e Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 10 Apr 2017 23:22:59 +0300 Subject: eval/decode: Include header needed for TriState --- src/nvim/eval/decode.h | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/eval/decode.h b/src/nvim/eval/decode.h index c8e7a189e3..77fc4c78c2 100644 --- a/src/nvim/eval/decode.h +++ b/src/nvim/eval/decode.h @@ -6,6 +6,7 @@ #include #include "nvim/eval/typval.h" +#include "nvim/globals.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/decode.h.generated.h" -- cgit From 1751ec192d860f2591d18c4aad4248b5ab786cec Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 11 Apr 2017 01:05:56 +0300 Subject: viml/executor: Fix check-single-includes --- src/nvim/viml/executor/executor.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/nvim/viml/executor/executor.h b/src/nvim/viml/executor/executor.h index 85cb3550e7..648bb73785 100644 --- a/src/nvim/viml/executor/executor.h +++ b/src/nvim/viml/executor/executor.h @@ -5,6 +5,8 @@ #include "nvim/api/private/defs.h" #include "nvim/func_attr.h" +#include "nvim/eval/typval.h" +#include "nvim/ex_cmds_defs.h" // Generated by msgpack-gen.lua void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; -- cgit From f98a3d85ed2f34a62300097fd30b393a3b3be393 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 11 Apr 2017 01:09:36 +0300 Subject: lua: Move files from src/nvim/viml/executor to src/nvim/lua --- src/nvim/CMakeLists.txt | 7 +- src/nvim/eval.c | 2 +- src/nvim/ex_docmd.c | 2 +- src/nvim/lua/converter.c | 1194 ++++++++++++++++++++++++++++++++++++ src/nvim/lua/converter.h | 15 + src/nvim/lua/executor.c | 576 +++++++++++++++++ src/nvim/lua/executor.h | 25 + src/nvim/lua/vim.lua | 2 + src/nvim/viml/executor/converter.c | 1194 ------------------------------------ src/nvim/viml/executor/converter.h | 15 - src/nvim/viml/executor/executor.c | 576 ----------------- src/nvim/viml/executor/executor.h | 25 - src/nvim/viml/executor/vim.lua | 2 - 13 files changed, 1817 insertions(+), 1818 deletions(-) create mode 100644 src/nvim/lua/converter.c create mode 100644 src/nvim/lua/converter.h create mode 100644 src/nvim/lua/executor.c create mode 100644 src/nvim/lua/executor.h create mode 100644 src/nvim/lua/vim.lua delete mode 100644 src/nvim/viml/executor/converter.c delete mode 100644 src/nvim/viml/executor/converter.h delete mode 100644 src/nvim/viml/executor/executor.c delete mode 100644 src/nvim/viml/executor/executor.h delete mode 100644 src/nvim/viml/executor/vim.lua (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index cec9a09141..1401029cf5 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -33,8 +33,8 @@ set(OPTIONS_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genoptions.lua) set(UNICODE_TABLES_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genunicodetables.lua) set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/unicode) set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h) -set(VIM_MODULE_FILE ${GENERATED_DIR}/viml/executor/vim_module.generated.h) -set(VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/viml/executor/vim.lua) +set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h) +set(VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua) set(CHAR_BLOB_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gencharblob.lua) set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json) set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint") @@ -72,8 +72,7 @@ foreach(subdir tui event eval - viml - viml/executor + lua ) if(${subdir} MATCHES "tui" AND NOT FEAT_TUI) continue() diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 75cd100061..e60cc4e218 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -94,7 +94,7 @@ #include "nvim/lib/kvec.h" #include "nvim/lib/khash.h" #include "nvim/lib/queue.h" -#include "nvim/viml/executor/executor.h" +#include "nvim/lua/executor.h" #include "nvim/eval/typval.h" #include "nvim/eval/executor.h" #include "nvim/eval/gc.h" diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index e44d759e04..b7bf6b4728 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -67,7 +67,7 @@ #include "nvim/event/rstream.h" #include "nvim/event/wstream.h" #include "nvim/shada.h" -#include "nvim/viml/executor/executor.h" +#include "nvim/lua/executor.h" #include "nvim/globals.h" static int quitmore = 0; diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c new file mode 100644 index 0000000000..348315124b --- /dev/null +++ b/src/nvim/lua/converter.c @@ -0,0 +1,1194 @@ +#include +#include +#include +#include +#include +#include + +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/func_attr.h" +#include "nvim/memory.h" +#include "nvim/assert.h" +// FIXME: vim.h is not actually needed, but otherwise it states MAXPATHL is +// redefined +#include "nvim/vim.h" +#include "nvim/globals.h" +#include "nvim/message.h" +#include "nvim/eval/typval.h" +#include "nvim/ascii.h" +#include "nvim/macros.h" + +#include "nvim/lib/kvec.h" +#include "nvim/eval/decode.h" + +#include "nvim/lua/converter.h" +#include "nvim/lua/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. + bool has_type_key; ///< True if type key is present. +} LuaTableProps; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/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 +{ + 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(). + size_t other_keys_num = 0; // Number of keys that are not string, integral + // or type keys. + LuaTableProps ret; + memset(&ret, 0, sizeof(ret)); + if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { + 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) { + other_keys_num++; + } 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) { + ret.has_type_key = true; + ret.type = (ObjectType)n; + } else { + other_keys_num++; + } + } else { + other_keys_num++; + } + } else { + has_val_key = true; + val_type = lua_type(lstate, -1); + if (val_type == LUA_TNUMBER) { + ret.val = lua_tonumber(lstate, -1); + } + } + break; + } + default: { + other_keys_num++; + break; + } + } + tsize++; + lua_pop(lstate, 1); + } + if (ret.has_type_key) { + if (ret.type == kObjectTypeFloat + && (!has_val_key || val_type != LUA_TNUMBER)) { + ret.type = kObjectTypeNil; + } else if (ret.type == kObjectTypeArray) { + // Determine what is the last number in a *sequence* of keys. + // This condition makes sure that Neovim will not crash when it gets table + // {[vim.type_idx]=vim.types.array, [SIZE_MAX]=1}: without it maxidx will + // be SIZE_MAX, with this condition it should be zero and [SIZE_MAX] key + // should be ignored. + if (ret.maxidx != 0 + && ret.maxidx != (tsize + - ret.has_type_key + - other_keys_num + - has_val_key + - ret.string_keys_num)) { + for (ret.maxidx = 0;; ret.maxidx++) { + lua_rawgeti(lstate, -1, (int)ret.maxidx + 1); + if (lua_isnil(lstate, -1)) { + lua_pop(lstate, 1); + break; + } + lua_pop(lstate, 1); + } + } + } + } else { + if (tsize == 0 + || (tsize == ret.maxidx + && other_keys_num == 0 + && 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. + bool container; ///< True if tv is a container. + bool special; ///< If true then tv is a _VAL part of special dictionary + ///< that represents mapping. + int idx; ///< Container index (used to detect self-referencing structures). +} TVPopStackItem; + +/// Convert lua object to VimL typval_T +/// +/// Should pop exactly one value from lua stack. +/// +/// @param lstate Lua state. +/// @param[out] ret_tv Where to put the result. +/// +/// @return `true` in case of success, `false` in case of failure. Error is +/// reported automatically. +bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) +{ + bool ret = true; + const int initial_size = lua_gettop(lstate); + kvec_t(TVPopStackItem) stack = KV_INITIAL_VALUE; + kv_push(stack, ((TVPopStackItem) { ret_tv, false, false, 0 })); + while (ret && kv_size(stack)) { + if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { + emsgf(_("E1502: Lua failed to grow stack to %i"), lua_gettop(lstate) + 3); + ret = false; + break; + } + TVPopStackItem cur = kv_pop(stack); + if (cur.container) { + if (cur.special || cur.tv->v_type == VAR_DICT) { + assert(cur.tv->v_type == (cur.special ? VAR_LIST : VAR_DICT)); + 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) { + list_T *const kv_pair = tv_list_alloc(); + tv_list_append_list(cur.tv->vval.v_list, kv_pair); + listitem_T *const key = tv_list_item_alloc(); + key->li_tv = decode_string(s, len, kTrue, false, false); + tv_list_append(kv_pair, key); + if (key->li_tv.v_type == VAR_UNKNOWN) { + ret = false; + tv_list_unref(kv_pair); + continue; + } + listitem_T *const val = tv_list_item_alloc(); + tv_list_append(kv_pair, val); + kv_push(stack, cur); + cur = (TVPopStackItem) { &val->li_tv, false, false, 0 }; + } else { + dictitem_T *const di = tv_dict_item_alloc_len(s, len); + if (tv_dict_add(cur.tv->vval.v_dict, di) == FAIL) { + assert(false); + } + kv_push(stack, cur); + cur = (TVPopStackItem) { &di->di_tv, false, false, 0 }; + } + } else { + lua_pop(lstate, 1); + continue; + } + } else { + assert(cur.tv->v_type == VAR_LIST); + lua_rawgeti(lstate, -1, cur.tv->vval.v_list->lv_len + 1); + if (lua_isnil(lstate, -1)) { + lua_pop(lstate, 2); + continue; + } + listitem_T *const li = tv_list_item_alloc(); + tv_list_append(cur.tv->vval.v_list, li); + kv_push(stack, cur); + cur = (TVPopStackItem) { &li->li_tv, false, false, 0 }; + } + } + assert(!cur.container); + *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; + cur.tv->vval.v_special = kSpecialVarNull; + break; + } + case LUA_TBOOLEAN: { + cur.tv->v_type = VAR_SPECIAL; + cur.tv->vval.v_special = (lua_toboolean(lstate, -1) + ? kSpecialVarTrue + : kSpecialVarFalse); + break; + } + case LUA_TSTRING: { + size_t len; + const char *s = lua_tolstring(lstate, -1, &len); + *cur.tv = decode_string(s, len, kNone, true, false); + if (cur.tv->v_type == VAR_UNKNOWN) { + ret = false; + } + break; + } + case LUA_TNUMBER: { + const lua_Number n = lua_tonumber(lstate, -1); + if (n > (lua_Number)VARNUMBER_MAX || n < (lua_Number)VARNUMBER_MIN + || ((lua_Number)((varnumber_T)n)) != n) { + cur.tv->v_type = VAR_FLOAT; + cur.tv->vval.v_float = (float_T)n; + } else { + cur.tv->v_type = VAR_NUMBER; + cur.tv->vval.v_number = (varnumber_T)n; + } + break; + } + case LUA_TTABLE: { + const LuaTableProps table_props = nlua_traverse_table(lstate); + + for (size_t i = 0; i < kv_size(stack); i++) { + const TVPopStackItem item = kv_A(stack, i); + if (item.container && lua_rawequal(lstate, -1, item.idx)) { + tv_copy(item.tv, cur.tv); + cur.container = false; + goto nlua_pop_typval_table_processing_end; + } + } + + switch (table_props.type) { + case kObjectTypeArray: { + cur.tv->v_type = VAR_LIST; + cur.tv->vval.v_list = tv_list_alloc(); + cur.tv->vval.v_list->lv_refcount++; + if (table_props.maxidx != 0) { + cur.container = true; + cur.idx = lua_gettop(lstate); + kv_push(stack, cur); + } + break; + } + case kObjectTypeDictionary: { + if (table_props.string_keys_num == 0) { + cur.tv->v_type = VAR_DICT; + cur.tv->vval.v_dict = tv_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 = tv_dict_find(cur.tv->vval.v_dict, + S_LEN("_VAL")); + 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 = tv_dict_alloc(); + cur.tv->vval.v_dict->dv_refcount++; + } + cur.container = true; + cur.idx = lua_gettop(lstate); + kv_push(stack, cur); + lua_pushnil(lstate); + } + break; + } + 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); + } + } +nlua_pop_typval_table_processing_end: + break; + } + default: { + EMSG(_("E5101: Cannot convert given lua type")); + ret = false; + break; + } + } + if (!cur.container) { + lua_pop(lstate, 1); + } + } + kv_destroy(stack); + if (!ret) { + tv_clear(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); + return ret; +} + +#define TYPVAL_ENCODE_ALLOW_SPECIALS true + +#define TYPVAL_ENCODE_CONV_NIL(tv) \ + lua_pushnil(lstate) + +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ + lua_pushboolean(lstate, (bool)(num)) + +#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ + lua_pushnumber(lstate, (lua_Number)(num)) + +#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER + +#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ + TYPVAL_ENCODE_CONV_NUMBER(tv, flt) + +#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \ + lua_pushlstring(lstate, (const char *)(str), (len)) + +#define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING + +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \ + 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_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(tv) \ + lua_createtable(lstate, 0, 0) + +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ + nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary) + +#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ + do { \ + if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { \ + emsgf(_("E5102: Lua failed to grow stack to %i"), \ + lua_gettop(lstate) + 3); \ + return false; \ + } \ + lua_createtable(lstate, (int)(len), 0); \ + lua_pushnumber(lstate, 1); \ + } while (0) + +#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) + +#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \ + do { \ + lua_Number idx = lua_tonumber(lstate, -2); \ + lua_rawset(lstate, -3); \ + lua_pushnumber(lstate, idx + 1); \ + } while (0) + +#define TYPVAL_ENCODE_CONV_LIST_END(tv) \ + lua_rawset(lstate, -3) + +#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ + do { \ + if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { \ + emsgf(_("E5102: Lua failed to grow stack to %i"), \ + lua_gettop(lstate) + 3); \ + return false; \ + } \ + lua_createtable(lstate, 0, (int)(len)); \ + } while (0) + +#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, kv_pair) + +#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) + +#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) + +#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \ + lua_rawset(lstate, -3) + +#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \ + TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) + +#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ + do { \ + for (size_t backref = kv_size(*mpstack); backref; backref--) { \ + const MPConvStackVal mpval = kv_A(*mpstack, backref - 1); \ + if (mpval.type == conv_type) { \ + if (conv_type == kMPConvDict \ + ? (void *)mpval.data.d.dict == (void *)(val) \ + : (void *)mpval.data.l.list == (void *)(val)) { \ + lua_pushvalue(lstate, \ + -((int)((kv_size(*mpstack) - backref + 1) * 2))); \ + break; \ + } \ + } \ + } \ + } while (0) + +#define TYPVAL_ENCODE_SCOPE static +#define TYPVAL_ENCODE_NAME lua +#define TYPVAL_ENCODE_FIRST_ARG_TYPE lua_State *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME lstate +#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_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_SPECIAL_DICT_KEY_CHECK +#undef TYPVAL_ENCODE_CONV_LIST_END +#undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS +#undef TYPVAL_ENCODE_CONV_RECURSE +#undef TYPVAL_ENCODE_ALLOW_SPECIALS + +/// Convert VimL typval_T to lua value +/// +/// Should leave single value in lua stack. May only fail if lua failed to grow +/// stack. +/// +/// @param lstate Lua interpreter state. +/// @param[in] tv typval_T to convert. +/// +/// @return true in case of success, false otherwise. +bool nlua_push_typval(lua_State *lstate, typval_T *const tv) +{ + const int initial_size = lua_gettop(lstate); + if (!lua_checkstack(lstate, initial_size + 2)) { + emsgf(_("E1502: Lua failed to grow stack to %i"), initial_size + 4); + return false; + } + if (encode_vim_to_lua(lstate, tv, "nlua_push_typval argument") == FAIL) { + return false; + } + assert(lua_gettop(lstate) == initial_size + 1); + return true; +} + +#define NLUA_PUSH_HANDLE(lstate, type, idx) \ + do { \ + lua_pushnumber(lstate, (lua_Number)(idx)); \ + } while (0) + +#define NLUA_POP_HANDLE(lstate, type, stack_idx, idx) \ + do { \ + idx = (type)lua_tonumber(lstate, stack_idx); \ + } while (0) + +/// Push value which is a type index +/// +/// Used for all “typed” tables: i.e. for all tables which represent VimL +/// values. +static inline void nlua_push_type_idx(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + lua_pushboolean(lstate, TYPE_IDX_VALUE); +} + +/// Push value which is a value index +/// +/// Used for tables which represent scalar values, like float value. +static inline void nlua_push_val_idx(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + lua_pushboolean(lstate, VAL_IDX_VALUE); +} + +/// Push type +/// +/// Type is a value in vim.types table. +/// +/// @param[out] lstate Lua state. +/// @param[in] type Type to push. +static inline void nlua_push_type(lua_State *lstate, ObjectType type) + FUNC_ATTR_NONNULL_ALL +{ + lua_pushnumber(lstate, (lua_Number)type); +} + +/// Create lua table which has an entry that determines its VimL type +/// +/// @param[out] lstate Lua state. +/// @param[in] narr Number of “array” entries to be populated later. +/// @param[in] nrec Number of “dictionary” entries to be populated later. +/// @param[in] type Type of the table. +static inline void nlua_create_typed_table(lua_State *lstate, + const size_t narr, + const size_t nrec, + const ObjectType type) + FUNC_ATTR_NONNULL_ALL +{ + lua_createtable(lstate, (int)narr, (int)(1 + nrec)); + nlua_push_type_idx(lstate); + nlua_push_type(lstate, type); + lua_rawset(lstate, -3); +} + + +/// Convert given String to lua string +/// +/// Leaves converted string on top of the stack. +void nlua_push_String(lua_State *lstate, const String s) + FUNC_ATTR_NONNULL_ALL +{ + lua_pushlstring(lstate, s.data, s.size); +} + +/// Convert given Integer to lua number +/// +/// Leaves converted number on top of the stack. +void nlua_push_Integer(lua_State *lstate, const Integer n) + FUNC_ATTR_NONNULL_ALL +{ + lua_pushnumber(lstate, (lua_Number)n); +} + +/// Convert given Float to lua table +/// +/// Leaves converted table on top of the stack. +void nlua_push_Float(lua_State *lstate, const Float f) + FUNC_ATTR_NONNULL_ALL +{ + nlua_create_typed_table(lstate, 0, 1, kObjectTypeFloat); + nlua_push_val_idx(lstate); + lua_pushnumber(lstate, (lua_Number)f); + lua_rawset(lstate, -3); +} + +/// Convert given Float to lua boolean +/// +/// Leaves converted value on top of the stack. +void nlua_push_Boolean(lua_State *lstate, const Boolean b) + FUNC_ATTR_NONNULL_ALL +{ + lua_pushboolean(lstate, b); +} + +/// 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 +{ + 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); + lua_rawset(lstate, -3); + } +} + +/// Convert given Array to lua table +/// +/// Leaves converted table on top of the stack. +void nlua_push_Array(lua_State *lstate, const Array array) + FUNC_ATTR_NONNULL_ALL +{ + 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, -2, (int)i + 1); + } +} + +#define GENERATE_INDEX_FUNCTION(type) \ +void nlua_push_##type(lua_State *lstate, const type item) \ + FUNC_ATTR_NONNULL_ALL \ +{ \ + NLUA_PUSH_HANDLE(lstate, type, item); \ +} + +GENERATE_INDEX_FUNCTION(Buffer) +GENERATE_INDEX_FUNCTION(Window) +GENERATE_INDEX_FUNCTION(Tabpage) + +#undef GENERATE_INDEX_FUNCTION + +/// Convert given Object to lua value +/// +/// Leaves converted value on top of the stack. +void nlua_push_Object(lua_State *lstate, const Object obj) + FUNC_ATTR_NONNULL_ALL +{ + switch (obj.type) { + case kObjectTypeNil: { + lua_pushnil(lstate); + break; + } +#define ADD_TYPE(type, data_key) \ + case kObjectType##type: { \ + nlua_push_##type(lstate, obj.data.data_key); \ + break; \ + } + ADD_TYPE(Boolean, boolean) + ADD_TYPE(Integer, integer) + ADD_TYPE(Float, floating) + ADD_TYPE(String, string) + ADD_TYPE(Array, array) + ADD_TYPE(Dictionary, dictionary) +#undef ADD_TYPE +#define ADD_REMOTE_TYPE(type) \ + case kObjectType##type: { \ + nlua_push_##type(lstate, (type)obj.data.integer); \ + break; \ + } + ADD_REMOTE_TYPE(Buffer) + ADD_REMOTE_TYPE(Window) + ADD_REMOTE_TYPE(Tabpage) +#undef ADD_REMOTE_TYPE + } +} + + +/// Convert lua value to string +/// +/// Always pops one value from the stack. +String nlua_pop_String(lua_State *lstate, Error *err) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (lua_type(lstate, -1) != LUA_TSTRING) { + lua_pop(lstate, 1); + api_set_error(err, Validation, "Expected lua string"); + return (String) { .size = 0, .data = NULL }; + } + String ret; + + ret.data = (char *)lua_tolstring(lstate, -1, &(ret.size)); + assert(ret.data != NULL); + ret.data = xmemdupz(ret.data, ret.size); + lua_pop(lstate, 1); + + return ret; +} + +/// Convert lua value to integer +/// +/// Always pops one value from the stack. +Integer nlua_pop_Integer(lua_State *lstate, Error *err) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (lua_type(lstate, -1) != LUA_TNUMBER) { + lua_pop(lstate, 1); + api_set_error(err, Validation, "Expected lua number"); + return 0; + } + const lua_Number n = lua_tonumber(lstate, -1); + lua_pop(lstate, 1); + if (n > (lua_Number)API_INTEGER_MAX || n < (lua_Number)API_INTEGER_MIN + || ((lua_Number)((Integer)n)) != n) { + api_set_error(err, Exception, "Number is not integral"); + return 0; + } + return (Integer)n; +} + +/// Convert lua value to boolean +/// +/// Always pops one value from the stack. +Boolean nlua_pop_Boolean(lua_State *lstate, Error *err) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + const Boolean ret = lua_toboolean(lstate, -1); + lua_pop(lstate, 1); + return ret; +} + +/// 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) { + if (err) { + api_set_error(err, Validation, "Expected lua table"); + } + return (LuaTableProps) { .type = kObjectTypeNil }; + } + LuaTableProps table_props = nlua_traverse_table(lstate); + + if (type == kObjectTypeDictionary && table_props.type == kObjectTypeArray + && table_props.maxidx == 0 && !table_props.has_type_key) { + table_props.type = kObjectTypeDictionary; + } + + if (table_props.type != type) { + if (err) { + api_set_error(err, Validation, "Unexpected type"); + } + } + + return table_props; +} + +/// Convert lua table to float +/// +/// Always pops one value from the stack. +Float nlua_pop_Float(lua_State *lstate, Error *err) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (lua_type(lstate, -1) == LUA_TNUMBER) { + const Float ret = (Float)lua_tonumber(lstate, -1); + lua_pop(lstate, 1); + 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 without determining whether it is array +/// +/// @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 = table_props.maxidx, .items = NULL }; + + if (ret.size == 0) { + lua_pop(lstate, 1); + return ret; + } + + ret.items = xcalloc(ret.size, sizeof(*ret.items)); + for (size_t i = 1; i <= ret.size; i++) { + Object val; + + lua_rawgeti(lstate, -1, (int)i); + + val = nlua_pop_Object(lstate, err); + if (err->set) { + ret.size = i - 1; + lua_pop(lstate, 1); + api_free_array(ret); + return (Array) { .size = 0, .items = NULL }; + } + ret.items[i - 1] = val; + } + lua_pop(lstate, 1); + + return ret; +} + +/// Convert lua table to 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 +{ + const LuaTableProps table_props = nlua_check_type(lstate, err, + kObjectTypeArray); + 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); + return ret; + } + ret.items = xcalloc(ret.size, sizeof(*ret.items)); + + lua_pushnil(lstate); + for (size_t i = 0; lua_next(lstate, -2) && i < ret.size;) { + // stack: dict, key, value + + if (lua_type(lstate, -2) == LUA_TSTRING) { + lua_pushvalue(lstate, -2); + // stack: dict, key, value, key + + ret.items[i].key = nlua_pop_String(lstate, err); + // stack: dict, key, value + + if (!err->set) { + ret.items[i].value = nlua_pop_Object(lstate, err); + // stack: dict, key + } else { + lua_pop(lstate, 1); + // stack: dict, key + } + + if (err->set) { + ret.size = i; + api_free_dictionary(ret); + lua_pop(lstate, 2); + // stack: + return (Dictionary) { .size = 0, .items = NULL }; + } + i++; + } else { + lua_pop(lstate, 1); + // stack: dict, key + } + } + lua_pop(lstate, 1); + + return ret; +} + +/// Convert lua table to dictionary +/// +/// Always pops one value from the stack. +Dictionary nlua_pop_Dictionary(lua_State *lstate, Error *err) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + 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, table_props, err); +} + +/// Helper structure for nlua_pop_Object +typedef struct { + Object *obj; ///< Location where conversion result is saved. + bool container; ///< True if tv is a container. +} ObjPopStackItem; + +/// Convert lua table to object +/// +/// Always pops one value from the stack. +Object nlua_pop_Object(lua_State *const lstate, Error *const err) +{ + Object ret = NIL; + const int initial_size = lua_gettop(lstate); + kvec_t(ObjPopStackItem) stack = KV_INITIAL_VALUE; + kv_push(stack, ((ObjPopStackItem) { &ret, false })); + while (!err->set && kv_size(stack)) { + if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { + api_set_error(err, Exception, "Lua failed to grow stack"); + break; + } + ObjPopStackItem cur = kv_pop(stack); + if (cur.container) { + if (cur.obj->type == kObjectTypeDictionary) { + // stack: …, dict, key + if (cur.obj->data.dictionary.size + == cur.obj->data.dictionary.capacity) { + lua_pop(lstate, 2); + continue; + } + bool next_key_found = false; + while (lua_next(lstate, -2)) { + // stack: …, dict, new key, val + if (lua_type(lstate, -2) == LUA_TSTRING) { + next_key_found = true; + break; + } + lua_pop(lstate, 1); + // stack: …, dict, new key + } + if (next_key_found) { + // stack: …, dict, new key, val + size_t len; + const char *s = lua_tolstring(lstate, -2, &len); + const size_t idx = cur.obj->data.dictionary.size++; + cur.obj->data.dictionary.items[idx].key = (String) { + .data = xmemdupz(s, len), + .size = len, + }; + kv_push(stack, cur); + cur = (ObjPopStackItem) { + .obj = &cur.obj->data.dictionary.items[idx].value, + .container = false, + }; + } else { + // stack: …, dict + lua_pop(lstate, 1); + // stack: … + continue; + } + } else { + if (cur.obj->data.array.size == cur.obj->data.array.capacity) { + lua_pop(lstate, 1); + continue; + } + const size_t idx = cur.obj->data.array.size++; + lua_rawgeti(lstate, -1, (int)idx + 1); + if (lua_isnil(lstate, -1)) { + lua_pop(lstate, 2); + continue; + } + kv_push(stack, cur); + cur = (ObjPopStackItem) { + .obj = &cur.obj->data.array.items[idx], + .container = false, + }; + } + } + assert(!cur.container); + *cur.obj = NIL; + switch (lua_type(lstate, -1)) { + case LUA_TNIL: { + break; + } + case LUA_TBOOLEAN: { + *cur.obj = BOOLEAN_OBJ(lua_toboolean(lstate, -1)); + break; + } + case LUA_TSTRING: { + size_t len; + const char *s = lua_tolstring(lstate, -1, &len); + *cur.obj = STRING_OBJ(((String) { + .data = xmemdupz(s, len), + .size = len, + })); + break; + } + case LUA_TNUMBER: { + 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) { + *cur.obj = FLOAT_OBJ((Float)n); + } else { + *cur.obj = INTEGER_OBJ((Integer)n); + } + break; + } + case LUA_TTABLE: { + const LuaTableProps table_props = nlua_traverse_table(lstate); + + switch (table_props.type) { + case kObjectTypeArray: { + *cur.obj = ARRAY_OBJ(((Array) { + .items = NULL, + .size = 0, + .capacity = 0, + })); + if (table_props.maxidx != 0) { + cur.obj->data.array.items = + xcalloc(table_props.maxidx, + sizeof(cur.obj->data.array.items[0])); + cur.obj->data.array.capacity = table_props.maxidx; + cur.container = true; + kv_push(stack, cur); + } + break; + } + case kObjectTypeDictionary: { + *cur.obj = DICTIONARY_OBJ(((Dictionary) { + .items = NULL, + .size = 0, + .capacity = 0, + })); + if (table_props.string_keys_num != 0) { + cur.obj->data.dictionary.items = + xcalloc(table_props.string_keys_num, + sizeof(cur.obj->data.dictionary.items[0])); + cur.obj->data.dictionary.capacity = table_props.string_keys_num; + cur.container = true; + kv_push(stack, cur); + lua_pushnil(lstate); + } + break; + } + case kObjectTypeFloat: { + *cur.obj = FLOAT_OBJ((Float)table_props.val); + break; + } + case kObjectTypeNil: { + api_set_error(err, Validation, "Cannot convert given lua table"); + break; + } + default: { + assert(false); + } + } + break; + } + default: { + api_set_error(err, Validation, "Cannot convert given lua type"); + break; + } + } + if (!cur.container) { + lua_pop(lstate, 1); + } + } + kv_destroy(stack); + if (err->set) { + api_free_object(ret); + ret = NIL; + lua_pop(lstate, lua_gettop(lstate) - initial_size + 1); + } + assert(lua_gettop(lstate) == initial_size - 1); + return ret; +} + +#define GENERATE_INDEX_FUNCTION(type) \ +type nlua_pop_##type(lua_State *lstate, Error *err) \ + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \ +{ \ + type ret; \ + NLUA_POP_HANDLE(lstate, type, -1, ret); \ + lua_pop(lstate, 1); \ + return ret; \ +} + +GENERATE_INDEX_FUNCTION(Buffer) +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/lua/converter.h b/src/nvim/lua/converter.h new file mode 100644 index 0000000000..49f6ac4016 --- /dev/null +++ b/src/nvim/lua/converter.h @@ -0,0 +1,15 @@ +#ifndef NVIM_VIML_EXECUTOR_CONVERTER_H +#define NVIM_VIML_EXECUTOR_CONVERTER_H + +#include +#include +#include + +#include "nvim/api/private/defs.h" +#include "nvim/func_attr.h" +#include "nvim/eval.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/converter.h.generated.h" +#endif +#endif // NVIM_VIML_EXECUTOR_CONVERTER_H diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c new file mode 100644 index 0000000000..7cf326aef5 --- /dev/null +++ b/src/nvim/lua/executor.c @@ -0,0 +1,576 @@ +#include +#include +#include + +#include "nvim/misc1.h" +#include "nvim/getchar.h" +#include "nvim/garray.h" +#include "nvim/func_attr.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/api/vim.h" +#include "nvim/vim.h" +#include "nvim/ex_getln.h" +#include "nvim/message.h" +#include "nvim/memline.h" +#include "nvim/buffer_defs.h" +#include "nvim/macros.h" +#include "nvim/screen.h" +#include "nvim/cursor.h" +#include "nvim/undo.h" +#include "nvim/ascii.h" + +#include "nvim/lua/executor.h" +#include "nvim/lua/converter.h" + +typedef struct { + Error err; + String lua_err_str; +} LuaError; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/vim_module.generated.h" +# include "lua/executor.c.generated.h" +#endif + +/// Name of the run code for use in messages +#define NLUA_EVAL_NAME "" + +/// Call C function which does not expect any arguments +/// +/// @param function Called function +/// @param numret Number of returned arguments +#define NLUA_CALL_C_FUNCTION_0(lstate, function, numret) \ + do { \ + lua_pushcfunction(lstate, &function); \ + lua_call(lstate, 0, numret); \ + } while (0) +/// Call C function which expects one argument +/// +/// @param function Called function +/// @param numret Number of returned arguments +/// @param a… Supplied argument (should be a void* pointer) +#define NLUA_CALL_C_FUNCTION_1(lstate, function, numret, a1) \ + do { \ + lua_pushcfunction(lstate, &function); \ + lua_pushlightuserdata(lstate, a1); \ + lua_call(lstate, 1, numret); \ + } while (0) +/// Call C function which expects two arguments +/// +/// @param function Called function +/// @param numret Number of returned arguments +/// @param a… Supplied argument (should be a void* pointer) +#define NLUA_CALL_C_FUNCTION_2(lstate, function, numret, a1, a2) \ + do { \ + lua_pushcfunction(lstate, &function); \ + lua_pushlightuserdata(lstate, a1); \ + lua_pushlightuserdata(lstate, a2); \ + lua_call(lstate, 2, numret); \ + } while (0) +/// Call C function which expects three arguments +/// +/// @param function Called function +/// @param numret Number of returned arguments +/// @param a… Supplied argument (should be a void* pointer) +#define NLUA_CALL_C_FUNCTION_3(lstate, function, numret, a1, a2, a3) \ + do { \ + lua_pushcfunction(lstate, &function); \ + lua_pushlightuserdata(lstate, a1); \ + lua_pushlightuserdata(lstate, a2); \ + lua_pushlightuserdata(lstate, a3); \ + lua_call(lstate, 3, numret); \ + } while (0) +/// Call C function which expects five arguments +/// +/// @param function Called function +/// @param numret Number of returned arguments +/// @param a… Supplied argument (should be a void* pointer) +#define NLUA_CALL_C_FUNCTION_4(lstate, function, numret, a1, a2, a3, a4) \ + do { \ + lua_pushcfunction(lstate, &function); \ + lua_pushlightuserdata(lstate, a1); \ + lua_pushlightuserdata(lstate, a2); \ + lua_pushlightuserdata(lstate, a3); \ + lua_pushlightuserdata(lstate, a4); \ + lua_call(lstate, 4, numret); \ + } while (0) + +/// Convert lua error into a Vim error message +/// +/// @param lstate Lua interpreter state. +/// @param[in] msg Message base, must contain one `%s`. +static void nlua_error(lua_State *const lstate, const char *const msg) + FUNC_ATTR_NONNULL_ALL +{ + size_t len; + const char *const str = lua_tolstring(lstate, -1, &len); + + emsgf(msg, (int)len, str); + + lua_pop(lstate, 1); +} + +/// Compare two strings, ignoring case +/// +/// Expects two values on the stack: compared strings. Returns one of the +/// following numbers: 0, -1 or 1. +/// +/// Does no error handling: never call it with non-string or with some arguments +/// omitted. +static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +{ + const char *s1 = luaL_checklstring(lstate, 1, NULL); + const char *s2 = luaL_checklstring(lstate, 2, NULL); + const int ret = STRICMP(s1, s2); + lua_pop(lstate, 2); + lua_pushnumber(lstate, (lua_Number)((ret > 0) - (ret < 0))); + return 1; +} + +/// Evaluate lua string +/// +/// Expects two values on the stack: string to evaluate, pointer to the +/// location where result is saved. Always returns nothing (from the lua point +/// of view). +static int nlua_exec_lua_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +{ + const String *const str = (const String *)lua_touserdata(lstate, 1); + typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 2); + lua_pop(lstate, 2); + + if (luaL_loadbuffer(lstate, str->data, str->size, NLUA_EVAL_NAME)) { + nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s")); + return 0; + } + if (lua_pcall(lstate, 0, 1, 0)) { + nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s")); + return 0; + } + if (!nlua_pop_typval(lstate, ret_tv)) { + return 0; + } + return 0; +} + +/// Evaluate lua string for each line in range +/// +/// Expects two values on the stack: string to evaluate and pointer to integer +/// array with line range. Always returns nothing (from the lua point of view). +static int nlua_exec_luado_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +{ + const String *const str = (const String *)lua_touserdata(lstate, 1); + const linenr_T *const range = (const linenr_T *)lua_touserdata(lstate, 2); + lua_pop(lstate, 2); + +#define DOSTART "return function(line, linenr) " +#define DOEND " end" + const size_t lcmd_len = (str->size + + (sizeof(DOSTART) - 1) + + (sizeof(DOEND) - 1)); + char *lcmd; + if (lcmd_len < IOSIZE) { + lcmd = (char *)IObuff; + } else { + lcmd = xmalloc(lcmd_len + 1); + } + memcpy(lcmd, DOSTART, sizeof(DOSTART) - 1); + memcpy(lcmd + sizeof(DOSTART) - 1, str->data, str->size); + memcpy(lcmd + sizeof(DOSTART) - 1 + str->size, DOEND, sizeof(DOEND) - 1); +#undef DOSTART +#undef DOEND + + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { + nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s")); + if (lcmd_len >= IOSIZE) { + xfree(lcmd); + } + return 0; + } + if (lcmd_len >= IOSIZE) { + xfree(lcmd); + } + if (lua_pcall(lstate, 0, 1, 0)) { + nlua_error(lstate, _("E5110: Error while creating lua function: %.*s")); + return 0; + } + for (linenr_T l = range[0]; l <= range[1]; l++) { + if (l > curbuf->b_ml.ml_line_count) { + break; + } + lua_pushvalue(lstate, -1); + lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false)); + lua_pushnumber(lstate, (lua_Number)l); + if (lua_pcall(lstate, 2, 1, 0)) { + nlua_error(lstate, _("E5111: Error while calling lua function: %.*s")); + break; + } + if (lua_isstring(lstate, -1)) { + size_t new_line_len; + const char *const new_line = lua_tolstring(lstate, -1, &new_line_len); + char *const new_line_transformed = xmemdupz(new_line, new_line_len); + for (size_t i = 0; i < new_line_len; i++) { + if (new_line_transformed[i] == NUL) { + new_line_transformed[i] = '\n'; + } + } + ml_replace(l, (char_u *)new_line_transformed, false); + changed_bytes(l, 0); + } + lua_pop(lstate, 1); + } + lua_pop(lstate, 1); + check_cursor(); + update_screen(NOT_VALID); + return 0; +} + +/// Evaluate lua file +/// +/// Expects one value on the stack: file to evaluate. Always returns nothing +/// (from the lua point of view). +static int nlua_exec_lua_file(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +{ + const char *const filename = (const char *)lua_touserdata(lstate, 1); + lua_pop(lstate, 1); + + if (luaL_loadfile(lstate, filename)) { + nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s")); + return 0; + } + if (lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("E5113: Error while calling lua chunk: %.*s")); + return 0; + } + return 0; +} + +/// Initialize lua interpreter state +/// +/// Called by lua interpreter itself to initialize state. +static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +{ + // stricmp + lua_pushcfunction(lstate, &nlua_stricmp); + lua_setglobal(lstate, "stricmp"); + + // print + lua_pushcfunction(lstate, &nlua_print); + lua_setglobal(lstate, "print"); + + // debug.debug + lua_getglobal(lstate, "debug"); + lua_pushcfunction(lstate, &nlua_debug); + lua_setfield(lstate, -2, "debug"); + lua_pop(lstate, 1); + + // vim + if (luaL_dostring(lstate, (char *)&vim_module[0])) { + nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); + return 1; + } + // vim.api + nlua_add_api_functions(lstate); + // vim.types, vim.type_idx, vim.val_idx + nlua_init_types(lstate); + lua_setglobal(lstate, "vim"); + return 0; +} + +/// Initialize lua interpreter +/// +/// Crashes NeoVim if initialization fails. Should be called once per lua +/// interpreter instance. +static lua_State *init_lua(void) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT +{ + lua_State *lstate = luaL_newstate(); + if (lstate == NULL) { + EMSG(_("E970: Failed to initialize lua interpreter")); + preserve_exit(); + } + luaL_openlibs(lstate); + NLUA_CALL_C_FUNCTION_0(lstate, nlua_state_init, 0); + return lstate; +} + +static lua_State *global_lstate = NULL; + +/// Execute lua string +/// +/// @param[in] str String to execute. +/// @param[out] ret_tv Location where result will be saved. +/// +/// @return Result of the execution. +void executor_exec_lua(const String str, typval_T *const ret_tv) + FUNC_ATTR_NONNULL_ALL +{ + if (global_lstate == NULL) { + global_lstate = init_lua(); + } + + NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_lua_string, 0, + (void *)&str, ret_tv); +} + +/// Evaluate lua string +/// +/// Used for luaeval(). Expects three values on the stack: +/// +/// 1. String to evaluate. +/// 2. _A value. +/// 3. Pointer to location where result is saved. +/// +/// @param[in,out] lstate Lua interpreter state. +static int nlua_eval_lua_string(lua_State *const lstate) + FUNC_ATTR_NONNULL_ALL +{ + const String *const str = (const String *)lua_touserdata(lstate, 1); + typval_T *const arg = (typval_T *)lua_touserdata(lstate, 2); + typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 3); + lua_pop(lstate, 3); + + garray_T str_ga; + ga_init(&str_ga, 1, 80); +#define EVALHEADER "local _A=select(1,...) return (" + const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str->size + 1; + char *lcmd; + if (lcmd_len < IOSIZE) { + lcmd = (char *)IObuff; + } else { + lcmd = xmalloc(lcmd_len); + } + memcpy(lcmd, EVALHEADER, sizeof(EVALHEADER) - 1); + memcpy(lcmd + sizeof(EVALHEADER) - 1, str->data, str->size); + lcmd[lcmd_len - 1] = ')'; +#undef EVALHEADER + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { + nlua_error(lstate, + _("E5107: Error while creating lua chunk for luaeval(): %.*s")); + if (lcmd != (char *)IObuff) { + xfree(lcmd); + } + return 0; + } + if (lcmd != (char *)IObuff) { + xfree(lcmd); + } + + if (arg == NULL || arg->v_type == VAR_UNKNOWN) { + lua_pushnil(lstate); + } else { + nlua_push_typval(lstate, arg); + } + if (lua_pcall(lstate, 1, 1, 0)) { + nlua_error(lstate, + _("E5108: Error while calling lua chunk for luaeval(): %.*s")); + return 0; + } + if (!nlua_pop_typval(lstate, ret_tv)) { + return 0; + } + + return 0; +} + +/// Print as a Vim message +/// +/// @param lstate Lua interpreter state. +static int nlua_print(lua_State *const lstate) + FUNC_ATTR_NONNULL_ALL +{ +#define PRINT_ERROR(msg) \ + do { \ + errmsg = msg; \ + errmsg_len = sizeof(msg) - 1; \ + goto nlua_print_error; \ + } while (0) + const int nargs = lua_gettop(lstate); + lua_getglobal(lstate, "tostring"); + const char *errmsg = NULL; + size_t errmsg_len = 0; + garray_T msg_ga; + ga_init(&msg_ga, 1, 80); + int curargidx = 1; + for (; curargidx <= nargs; curargidx++) { + lua_pushvalue(lstate, -1); // tostring + lua_pushvalue(lstate, curargidx); // arg + if (lua_pcall(lstate, 1, 1, 0)) { + errmsg = lua_tolstring(lstate, -1, &errmsg_len); + goto nlua_print_error; + } + size_t len; + const char *const s = lua_tolstring(lstate, -1, &len); + if (s == NULL) { + PRINT_ERROR( + ""); + } + ga_concat_len(&msg_ga, s, len); + if (curargidx < nargs) { + ga_append(&msg_ga, ' '); + } + lua_pop(lstate, 1); + } +#undef PRINT_ERROR + lua_pop(lstate, nargs + 1); + ga_append(&msg_ga, NUL); + { + const size_t len = (size_t)msg_ga.ga_len - 1; + char *const str = (char *)msg_ga.ga_data; + + for (size_t i = 0; i < len;) { + const size_t start = i; + while (i < len) { + switch (str[i]) { + case NUL: { + str[i] = NL; + i++; + continue; + } + case NL: { + str[i] = NUL; + i++; + break; + } + default: { + i++; + continue; + } + } + break; + } + msg((char_u *)str + start); + } + if (str[len - 1] == NUL) { // Last was newline + msg((char_u *)""); + } + } + ga_clear(&msg_ga); + return 0; +nlua_print_error: + emsgf(_("E5114: Error while converting print argument #%i: %.*s"), + curargidx, errmsg_len, errmsg); + ga_clear(&msg_ga); + lua_pop(lstate, lua_gettop(lstate)); + return 0; +} + +/// debug.debug implementation: interaction with user while debugging +/// +/// @param lstate Lua interpreter state. +int nlua_debug(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + const typval_T input_args[] = { + { + .v_lock = VAR_FIXED, + .v_type = VAR_STRING, + .vval.v_string = (char_u *)"lua_debug> ", + }, + { + .v_type = VAR_UNKNOWN, + }, + }; + for (;;) { + lua_settop(lstate, 0); + typval_T input; + get_user_input(input_args, &input, false); + msg_putchar('\n'); // Avoid outputting on input line. + if (input.v_type != VAR_STRING + || input.vval.v_string == NULL + || *input.vval.v_string == NUL + || STRCMP(input.vval.v_string, "cont") == 0) { + tv_clear(&input); + return 0; + } + if (luaL_loadbuffer(lstate, (const char *)input.vval.v_string, + STRLEN(input.vval.v_string), "=(debug command)")) { + nlua_error(lstate, _("E5115: Error while loading debug string: %.*s")); + } + tv_clear(&input); + if (lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("E5116: Error while calling debug string: %.*s")); + } + } + return 0; +} + +/// Evaluate lua string +/// +/// Used for luaeval(). +/// +/// @param[in] str String to execute. +/// @param[in] arg Second argument to `luaeval()`. +/// @param[out] ret_tv Location where result will be saved. +/// +/// @return Result of the execution. +void executor_eval_lua(const String str, typval_T *const arg, + typval_T *const ret_tv) + FUNC_ATTR_NONNULL_ALL +{ + if (global_lstate == NULL) { + global_lstate = init_lua(); + } + + NLUA_CALL_C_FUNCTION_3(global_lstate, nlua_eval_lua_string, 0, + (void *)&str, arg, ret_tv); +} + +/// Run lua string +/// +/// Used for :lua. +/// +/// @param eap VimL command being run. +void ex_lua(exarg_T *const eap) + FUNC_ATTR_NONNULL_ALL +{ + size_t len; + char *const code = script_get(eap, &len); + if (eap->skip) { + xfree(code); + return; + } + typval_T tv = { .v_type = VAR_UNKNOWN }; + executor_exec_lua((String) { .data = code, .size = len }, &tv); + tv_clear(&tv); + xfree(code); +} + +/// Run lua string for each line in range +/// +/// Used for :luado. +/// +/// @param eap VimL command being run. +void ex_luado(exarg_T *const eap) + FUNC_ATTR_NONNULL_ALL +{ + if (global_lstate == NULL) { + global_lstate = init_lua(); + } + if (u_save(eap->line1 - 1, eap->line2 + 1) == FAIL) { + EMSG(_("cannot save undo information")); + return; + } + const String cmd = { + .size = STRLEN(eap->arg), + .data = (char *)eap->arg, + }; + const linenr_T range[] = { eap->line1, eap->line2 }; + NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_luado_string, 0, + (void *)&cmd, (void *)range); +} + +/// Run lua file +/// +/// Used for :luafile. +/// +/// @param eap VimL command being run. +void ex_luafile(exarg_T *const eap) + FUNC_ATTR_NONNULL_ALL +{ + if (global_lstate == NULL) { + global_lstate = init_lua(); + } + NLUA_CALL_C_FUNCTION_1(global_lstate, nlua_exec_lua_file, 0, + (void *)eap->arg); +} diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h new file mode 100644 index 0000000000..22e55a56d3 --- /dev/null +++ b/src/nvim/lua/executor.h @@ -0,0 +1,25 @@ +#ifndef NVIM_VIML_EXECUTOR_EXECUTOR_H +#define NVIM_VIML_EXECUTOR_EXECUTOR_H + +#include + +#include "nvim/api/private/defs.h" +#include "nvim/func_attr.h" +#include "nvim/eval/typval.h" +#include "nvim/ex_cmds_defs.h" + +// Generated by msgpack-gen.lua +void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; + +#define set_api_error(s, err) \ + do { \ + Error *err_ = (err); \ + err_->type = kErrorTypeException; \ + err_->set = true; \ + memcpy(&err_->msg[0], s, sizeof(s)); \ + } while (0) + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/executor.h.generated.h" +#endif +#endif // NVIM_VIML_EXECUTOR_EXECUTOR_H diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua new file mode 100644 index 0000000000..8d1c5bdf4f --- /dev/null +++ b/src/nvim/lua/vim.lua @@ -0,0 +1,2 @@ +-- TODO(ZyX-I): Create compatibility layer. +return {} diff --git a/src/nvim/viml/executor/converter.c b/src/nvim/viml/executor/converter.c deleted file mode 100644 index 425a38538d..0000000000 --- a/src/nvim/viml/executor/converter.c +++ /dev/null @@ -1,1194 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "nvim/api/private/defs.h" -#include "nvim/api/private/helpers.h" -#include "nvim/func_attr.h" -#include "nvim/memory.h" -#include "nvim/assert.h" -// FIXME: vim.h is not actually needed, but otherwise it states MAXPATHL is -// redefined -#include "nvim/vim.h" -#include "nvim/globals.h" -#include "nvim/message.h" -#include "nvim/eval/typval.h" -#include "nvim/ascii.h" -#include "nvim/macros.h" - -#include "nvim/lib/kvec.h" -#include "nvim/eval/decode.h" - -#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. - bool has_type_key; ///< True if type key is present. -} 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 -{ - 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(). - size_t other_keys_num = 0; // Number of keys that are not string, integral - // or type keys. - LuaTableProps ret; - memset(&ret, 0, sizeof(ret)); - if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { - 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) { - other_keys_num++; - } 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) { - ret.has_type_key = true; - ret.type = (ObjectType)n; - } else { - other_keys_num++; - } - } else { - other_keys_num++; - } - } else { - has_val_key = true; - val_type = lua_type(lstate, -1); - if (val_type == LUA_TNUMBER) { - ret.val = lua_tonumber(lstate, -1); - } - } - break; - } - default: { - other_keys_num++; - break; - } - } - tsize++; - lua_pop(lstate, 1); - } - if (ret.has_type_key) { - if (ret.type == kObjectTypeFloat - && (!has_val_key || val_type != LUA_TNUMBER)) { - ret.type = kObjectTypeNil; - } else if (ret.type == kObjectTypeArray) { - // Determine what is the last number in a *sequence* of keys. - // This condition makes sure that Neovim will not crash when it gets table - // {[vim.type_idx]=vim.types.array, [SIZE_MAX]=1}: without it maxidx will - // be SIZE_MAX, with this condition it should be zero and [SIZE_MAX] key - // should be ignored. - if (ret.maxidx != 0 - && ret.maxidx != (tsize - - ret.has_type_key - - other_keys_num - - has_val_key - - ret.string_keys_num)) { - for (ret.maxidx = 0;; ret.maxidx++) { - lua_rawgeti(lstate, -1, (int)ret.maxidx + 1); - if (lua_isnil(lstate, -1)) { - lua_pop(lstate, 1); - break; - } - lua_pop(lstate, 1); - } - } - } - } else { - if (tsize == 0 - || (tsize == ret.maxidx - && other_keys_num == 0 - && 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. - bool container; ///< True if tv is a container. - bool special; ///< If true then tv is a _VAL part of special dictionary - ///< that represents mapping. - int idx; ///< Container index (used to detect self-referencing structures). -} TVPopStackItem; - -/// Convert lua object to VimL typval_T -/// -/// Should pop exactly one value from lua stack. -/// -/// @param lstate Lua state. -/// @param[out] ret_tv Where to put the result. -/// -/// @return `true` in case of success, `false` in case of failure. Error is -/// reported automatically. -bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) -{ - bool ret = true; - const int initial_size = lua_gettop(lstate); - kvec_t(TVPopStackItem) stack = KV_INITIAL_VALUE; - kv_push(stack, ((TVPopStackItem) { ret_tv, false, false, 0 })); - while (ret && kv_size(stack)) { - if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { - emsgf(_("E1502: Lua failed to grow stack to %i"), lua_gettop(lstate) + 3); - ret = false; - break; - } - TVPopStackItem cur = kv_pop(stack); - if (cur.container) { - if (cur.special || cur.tv->v_type == VAR_DICT) { - assert(cur.tv->v_type == (cur.special ? VAR_LIST : VAR_DICT)); - 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) { - list_T *const kv_pair = tv_list_alloc(); - tv_list_append_list(cur.tv->vval.v_list, kv_pair); - listitem_T *const key = tv_list_item_alloc(); - key->li_tv = decode_string(s, len, kTrue, false, false); - tv_list_append(kv_pair, key); - if (key->li_tv.v_type == VAR_UNKNOWN) { - ret = false; - tv_list_unref(kv_pair); - continue; - } - listitem_T *const val = tv_list_item_alloc(); - tv_list_append(kv_pair, val); - kv_push(stack, cur); - cur = (TVPopStackItem) { &val->li_tv, false, false, 0 }; - } else { - dictitem_T *const di = tv_dict_item_alloc_len(s, len); - if (tv_dict_add(cur.tv->vval.v_dict, di) == FAIL) { - assert(false); - } - kv_push(stack, cur); - cur = (TVPopStackItem) { &di->di_tv, false, false, 0 }; - } - } else { - lua_pop(lstate, 1); - continue; - } - } else { - assert(cur.tv->v_type == VAR_LIST); - lua_rawgeti(lstate, -1, cur.tv->vval.v_list->lv_len + 1); - if (lua_isnil(lstate, -1)) { - lua_pop(lstate, 2); - continue; - } - listitem_T *const li = tv_list_item_alloc(); - tv_list_append(cur.tv->vval.v_list, li); - kv_push(stack, cur); - cur = (TVPopStackItem) { &li->li_tv, false, false, 0 }; - } - } - assert(!cur.container); - *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; - cur.tv->vval.v_special = kSpecialVarNull; - break; - } - case LUA_TBOOLEAN: { - cur.tv->v_type = VAR_SPECIAL; - cur.tv->vval.v_special = (lua_toboolean(lstate, -1) - ? kSpecialVarTrue - : kSpecialVarFalse); - break; - } - case LUA_TSTRING: { - size_t len; - const char *s = lua_tolstring(lstate, -1, &len); - *cur.tv = decode_string(s, len, kNone, true, false); - if (cur.tv->v_type == VAR_UNKNOWN) { - ret = false; - } - break; - } - case LUA_TNUMBER: { - const lua_Number n = lua_tonumber(lstate, -1); - if (n > (lua_Number)VARNUMBER_MAX || n < (lua_Number)VARNUMBER_MIN - || ((lua_Number)((varnumber_T)n)) != n) { - cur.tv->v_type = VAR_FLOAT; - cur.tv->vval.v_float = (float_T)n; - } else { - cur.tv->v_type = VAR_NUMBER; - cur.tv->vval.v_number = (varnumber_T)n; - } - break; - } - case LUA_TTABLE: { - const LuaTableProps table_props = nlua_traverse_table(lstate); - - for (size_t i = 0; i < kv_size(stack); i++) { - const TVPopStackItem item = kv_A(stack, i); - if (item.container && lua_rawequal(lstate, -1, item.idx)) { - tv_copy(item.tv, cur.tv); - cur.container = false; - goto nlua_pop_typval_table_processing_end; - } - } - - switch (table_props.type) { - case kObjectTypeArray: { - cur.tv->v_type = VAR_LIST; - cur.tv->vval.v_list = tv_list_alloc(); - cur.tv->vval.v_list->lv_refcount++; - if (table_props.maxidx != 0) { - cur.container = true; - cur.idx = lua_gettop(lstate); - kv_push(stack, cur); - } - break; - } - case kObjectTypeDictionary: { - if (table_props.string_keys_num == 0) { - cur.tv->v_type = VAR_DICT; - cur.tv->vval.v_dict = tv_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 = tv_dict_find(cur.tv->vval.v_dict, - S_LEN("_VAL")); - 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 = tv_dict_alloc(); - cur.tv->vval.v_dict->dv_refcount++; - } - cur.container = true; - cur.idx = lua_gettop(lstate); - kv_push(stack, cur); - lua_pushnil(lstate); - } - break; - } - 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); - } - } -nlua_pop_typval_table_processing_end: - break; - } - default: { - EMSG(_("E5101: Cannot convert given lua type")); - ret = false; - break; - } - } - if (!cur.container) { - lua_pop(lstate, 1); - } - } - kv_destroy(stack); - if (!ret) { - tv_clear(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); - return ret; -} - -#define TYPVAL_ENCODE_ALLOW_SPECIALS true - -#define TYPVAL_ENCODE_CONV_NIL(tv) \ - lua_pushnil(lstate) - -#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ - lua_pushboolean(lstate, (bool)(num)) - -#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ - lua_pushnumber(lstate, (lua_Number)(num)) - -#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER - -#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ - TYPVAL_ENCODE_CONV_NUMBER(tv, flt) - -#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \ - lua_pushlstring(lstate, (const char *)(str), (len)) - -#define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING - -#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \ - 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_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(tv) \ - lua_createtable(lstate, 0, 0) - -#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ - nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary) - -#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ - do { \ - if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { \ - emsgf(_("E5102: Lua failed to grow stack to %i"), \ - lua_gettop(lstate) + 3); \ - return false; \ - } \ - lua_createtable(lstate, (int)(len), 0); \ - lua_pushnumber(lstate, 1); \ - } while (0) - -#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) - -#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \ - do { \ - lua_Number idx = lua_tonumber(lstate, -2); \ - lua_rawset(lstate, -3); \ - lua_pushnumber(lstate, idx + 1); \ - } while (0) - -#define TYPVAL_ENCODE_CONV_LIST_END(tv) \ - lua_rawset(lstate, -3) - -#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ - do { \ - if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { \ - emsgf(_("E5102: Lua failed to grow stack to %i"), \ - lua_gettop(lstate) + 3); \ - return false; \ - } \ - lua_createtable(lstate, 0, (int)(len)); \ - } while (0) - -#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, kv_pair) - -#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) - -#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) - -#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \ - lua_rawset(lstate, -3) - -#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \ - TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) - -#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ - do { \ - for (size_t backref = kv_size(*mpstack); backref; backref--) { \ - const MPConvStackVal mpval = kv_A(*mpstack, backref - 1); \ - if (mpval.type == conv_type) { \ - if (conv_type == kMPConvDict \ - ? (void *)mpval.data.d.dict == (void *)(val) \ - : (void *)mpval.data.l.list == (void *)(val)) { \ - lua_pushvalue(lstate, \ - -((int)((kv_size(*mpstack) - backref + 1) * 2))); \ - break; \ - } \ - } \ - } \ - } while (0) - -#define TYPVAL_ENCODE_SCOPE static -#define TYPVAL_ENCODE_NAME lua -#define TYPVAL_ENCODE_FIRST_ARG_TYPE lua_State *const -#define TYPVAL_ENCODE_FIRST_ARG_NAME lstate -#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_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_SPECIAL_DICT_KEY_CHECK -#undef TYPVAL_ENCODE_CONV_LIST_END -#undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS -#undef TYPVAL_ENCODE_CONV_RECURSE -#undef TYPVAL_ENCODE_ALLOW_SPECIALS - -/// Convert VimL typval_T to lua value -/// -/// Should leave single value in lua stack. May only fail if lua failed to grow -/// stack. -/// -/// @param lstate Lua interpreter state. -/// @param[in] tv typval_T to convert. -/// -/// @return true in case of success, false otherwise. -bool nlua_push_typval(lua_State *lstate, typval_T *const tv) -{ - const int initial_size = lua_gettop(lstate); - if (!lua_checkstack(lstate, initial_size + 2)) { - emsgf(_("E1502: Lua failed to grow stack to %i"), initial_size + 4); - return false; - } - if (encode_vim_to_lua(lstate, tv, "nlua_push_typval argument") == FAIL) { - return false; - } - assert(lua_gettop(lstate) == initial_size + 1); - return true; -} - -#define NLUA_PUSH_HANDLE(lstate, type, idx) \ - do { \ - lua_pushnumber(lstate, (lua_Number)(idx)); \ - } while (0) - -#define NLUA_POP_HANDLE(lstate, type, stack_idx, idx) \ - do { \ - idx = (type)lua_tonumber(lstate, stack_idx); \ - } while (0) - -/// Push value which is a type index -/// -/// Used for all “typed” tables: i.e. for all tables which represent VimL -/// values. -static inline void nlua_push_type_idx(lua_State *lstate) - FUNC_ATTR_NONNULL_ALL -{ - lua_pushboolean(lstate, TYPE_IDX_VALUE); -} - -/// Push value which is a value index -/// -/// Used for tables which represent scalar values, like float value. -static inline void nlua_push_val_idx(lua_State *lstate) - FUNC_ATTR_NONNULL_ALL -{ - lua_pushboolean(lstate, VAL_IDX_VALUE); -} - -/// Push type -/// -/// Type is a value in vim.types table. -/// -/// @param[out] lstate Lua state. -/// @param[in] type Type to push. -static inline void nlua_push_type(lua_State *lstate, ObjectType type) - FUNC_ATTR_NONNULL_ALL -{ - lua_pushnumber(lstate, (lua_Number)type); -} - -/// Create lua table which has an entry that determines its VimL type -/// -/// @param[out] lstate Lua state. -/// @param[in] narr Number of “array” entries to be populated later. -/// @param[in] nrec Number of “dictionary” entries to be populated later. -/// @param[in] type Type of the table. -static inline void nlua_create_typed_table(lua_State *lstate, - const size_t narr, - const size_t nrec, - const ObjectType type) - FUNC_ATTR_NONNULL_ALL -{ - lua_createtable(lstate, (int)narr, (int)(1 + nrec)); - nlua_push_type_idx(lstate); - nlua_push_type(lstate, type); - lua_rawset(lstate, -3); -} - - -/// Convert given String to lua string -/// -/// Leaves converted string on top of the stack. -void nlua_push_String(lua_State *lstate, const String s) - FUNC_ATTR_NONNULL_ALL -{ - lua_pushlstring(lstate, s.data, s.size); -} - -/// Convert given Integer to lua number -/// -/// Leaves converted number on top of the stack. -void nlua_push_Integer(lua_State *lstate, const Integer n) - FUNC_ATTR_NONNULL_ALL -{ - lua_pushnumber(lstate, (lua_Number)n); -} - -/// Convert given Float to lua table -/// -/// Leaves converted table on top of the stack. -void nlua_push_Float(lua_State *lstate, const Float f) - FUNC_ATTR_NONNULL_ALL -{ - nlua_create_typed_table(lstate, 0, 1, kObjectTypeFloat); - nlua_push_val_idx(lstate); - lua_pushnumber(lstate, (lua_Number)f); - lua_rawset(lstate, -3); -} - -/// Convert given Float to lua boolean -/// -/// Leaves converted value on top of the stack. -void nlua_push_Boolean(lua_State *lstate, const Boolean b) - FUNC_ATTR_NONNULL_ALL -{ - lua_pushboolean(lstate, b); -} - -/// 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 -{ - 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); - lua_rawset(lstate, -3); - } -} - -/// Convert given Array to lua table -/// -/// Leaves converted table on top of the stack. -void nlua_push_Array(lua_State *lstate, const Array array) - FUNC_ATTR_NONNULL_ALL -{ - 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, -2, (int)i + 1); - } -} - -#define GENERATE_INDEX_FUNCTION(type) \ -void nlua_push_##type(lua_State *lstate, const type item) \ - FUNC_ATTR_NONNULL_ALL \ -{ \ - NLUA_PUSH_HANDLE(lstate, type, item); \ -} - -GENERATE_INDEX_FUNCTION(Buffer) -GENERATE_INDEX_FUNCTION(Window) -GENERATE_INDEX_FUNCTION(Tabpage) - -#undef GENERATE_INDEX_FUNCTION - -/// Convert given Object to lua value -/// -/// Leaves converted value on top of the stack. -void nlua_push_Object(lua_State *lstate, const Object obj) - FUNC_ATTR_NONNULL_ALL -{ - switch (obj.type) { - case kObjectTypeNil: { - lua_pushnil(lstate); - break; - } -#define ADD_TYPE(type, data_key) \ - case kObjectType##type: { \ - nlua_push_##type(lstate, obj.data.data_key); \ - break; \ - } - ADD_TYPE(Boolean, boolean) - ADD_TYPE(Integer, integer) - ADD_TYPE(Float, floating) - ADD_TYPE(String, string) - ADD_TYPE(Array, array) - ADD_TYPE(Dictionary, dictionary) -#undef ADD_TYPE -#define ADD_REMOTE_TYPE(type) \ - case kObjectType##type: { \ - nlua_push_##type(lstate, (type)obj.data.integer); \ - break; \ - } - ADD_REMOTE_TYPE(Buffer) - ADD_REMOTE_TYPE(Window) - ADD_REMOTE_TYPE(Tabpage) -#undef ADD_REMOTE_TYPE - } -} - - -/// Convert lua value to string -/// -/// Always pops one value from the stack. -String nlua_pop_String(lua_State *lstate, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (lua_type(lstate, -1) != LUA_TSTRING) { - lua_pop(lstate, 1); - api_set_error(err, Validation, "Expected lua string"); - return (String) { .size = 0, .data = NULL }; - } - String ret; - - ret.data = (char *)lua_tolstring(lstate, -1, &(ret.size)); - assert(ret.data != NULL); - ret.data = xmemdupz(ret.data, ret.size); - lua_pop(lstate, 1); - - return ret; -} - -/// Convert lua value to integer -/// -/// Always pops one value from the stack. -Integer nlua_pop_Integer(lua_State *lstate, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (lua_type(lstate, -1) != LUA_TNUMBER) { - lua_pop(lstate, 1); - api_set_error(err, Validation, "Expected lua number"); - return 0; - } - const lua_Number n = lua_tonumber(lstate, -1); - lua_pop(lstate, 1); - if (n > (lua_Number)API_INTEGER_MAX || n < (lua_Number)API_INTEGER_MIN - || ((lua_Number)((Integer)n)) != n) { - api_set_error(err, Exception, "Number is not integral"); - return 0; - } - return (Integer)n; -} - -/// Convert lua value to boolean -/// -/// Always pops one value from the stack. -Boolean nlua_pop_Boolean(lua_State *lstate, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - const Boolean ret = lua_toboolean(lstate, -1); - lua_pop(lstate, 1); - return ret; -} - -/// 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) { - if (err) { - api_set_error(err, Validation, "Expected lua table"); - } - return (LuaTableProps) { .type = kObjectTypeNil }; - } - LuaTableProps table_props = nlua_traverse_table(lstate); - - if (type == kObjectTypeDictionary && table_props.type == kObjectTypeArray - && table_props.maxidx == 0 && !table_props.has_type_key) { - table_props.type = kObjectTypeDictionary; - } - - if (table_props.type != type) { - if (err) { - api_set_error(err, Validation, "Unexpected type"); - } - } - - return table_props; -} - -/// Convert lua table to float -/// -/// Always pops one value from the stack. -Float nlua_pop_Float(lua_State *lstate, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (lua_type(lstate, -1) == LUA_TNUMBER) { - const Float ret = (Float)lua_tonumber(lstate, -1); - lua_pop(lstate, 1); - 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 without determining whether it is array -/// -/// @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 = table_props.maxidx, .items = NULL }; - - if (ret.size == 0) { - lua_pop(lstate, 1); - return ret; - } - - ret.items = xcalloc(ret.size, sizeof(*ret.items)); - for (size_t i = 1; i <= ret.size; i++) { - Object val; - - lua_rawgeti(lstate, -1, (int)i); - - val = nlua_pop_Object(lstate, err); - if (err->set) { - ret.size = i - 1; - lua_pop(lstate, 1); - api_free_array(ret); - return (Array) { .size = 0, .items = NULL }; - } - ret.items[i - 1] = val; - } - lua_pop(lstate, 1); - - return ret; -} - -/// Convert lua table to 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 -{ - const LuaTableProps table_props = nlua_check_type(lstate, err, - kObjectTypeArray); - 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); - return ret; - } - ret.items = xcalloc(ret.size, sizeof(*ret.items)); - - lua_pushnil(lstate); - for (size_t i = 0; lua_next(lstate, -2) && i < ret.size;) { - // stack: dict, key, value - - if (lua_type(lstate, -2) == LUA_TSTRING) { - lua_pushvalue(lstate, -2); - // stack: dict, key, value, key - - ret.items[i].key = nlua_pop_String(lstate, err); - // stack: dict, key, value - - if (!err->set) { - ret.items[i].value = nlua_pop_Object(lstate, err); - // stack: dict, key - } else { - lua_pop(lstate, 1); - // stack: dict, key - } - - if (err->set) { - ret.size = i; - api_free_dictionary(ret); - lua_pop(lstate, 2); - // stack: - return (Dictionary) { .size = 0, .items = NULL }; - } - i++; - } else { - lua_pop(lstate, 1); - // stack: dict, key - } - } - lua_pop(lstate, 1); - - return ret; -} - -/// Convert lua table to dictionary -/// -/// Always pops one value from the stack. -Dictionary nlua_pop_Dictionary(lua_State *lstate, Error *err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - 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, table_props, err); -} - -/// Helper structure for nlua_pop_Object -typedef struct { - Object *obj; ///< Location where conversion result is saved. - bool container; ///< True if tv is a container. -} ObjPopStackItem; - -/// Convert lua table to object -/// -/// Always pops one value from the stack. -Object nlua_pop_Object(lua_State *const lstate, Error *const err) -{ - Object ret = NIL; - const int initial_size = lua_gettop(lstate); - kvec_t(ObjPopStackItem) stack = KV_INITIAL_VALUE; - kv_push(stack, ((ObjPopStackItem) { &ret, false })); - while (!err->set && kv_size(stack)) { - if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { - api_set_error(err, Exception, "Lua failed to grow stack"); - break; - } - ObjPopStackItem cur = kv_pop(stack); - if (cur.container) { - if (cur.obj->type == kObjectTypeDictionary) { - // stack: …, dict, key - if (cur.obj->data.dictionary.size - == cur.obj->data.dictionary.capacity) { - lua_pop(lstate, 2); - continue; - } - bool next_key_found = false; - while (lua_next(lstate, -2)) { - // stack: …, dict, new key, val - if (lua_type(lstate, -2) == LUA_TSTRING) { - next_key_found = true; - break; - } - lua_pop(lstate, 1); - // stack: …, dict, new key - } - if (next_key_found) { - // stack: …, dict, new key, val - size_t len; - const char *s = lua_tolstring(lstate, -2, &len); - const size_t idx = cur.obj->data.dictionary.size++; - cur.obj->data.dictionary.items[idx].key = (String) { - .data = xmemdupz(s, len), - .size = len, - }; - kv_push(stack, cur); - cur = (ObjPopStackItem) { - .obj = &cur.obj->data.dictionary.items[idx].value, - .container = false, - }; - } else { - // stack: …, dict - lua_pop(lstate, 1); - // stack: … - continue; - } - } else { - if (cur.obj->data.array.size == cur.obj->data.array.capacity) { - lua_pop(lstate, 1); - continue; - } - const size_t idx = cur.obj->data.array.size++; - lua_rawgeti(lstate, -1, (int)idx + 1); - if (lua_isnil(lstate, -1)) { - lua_pop(lstate, 2); - continue; - } - kv_push(stack, cur); - cur = (ObjPopStackItem) { - .obj = &cur.obj->data.array.items[idx], - .container = false, - }; - } - } - assert(!cur.container); - *cur.obj = NIL; - switch (lua_type(lstate, -1)) { - case LUA_TNIL: { - break; - } - case LUA_TBOOLEAN: { - *cur.obj = BOOLEAN_OBJ(lua_toboolean(lstate, -1)); - break; - } - case LUA_TSTRING: { - size_t len; - const char *s = lua_tolstring(lstate, -1, &len); - *cur.obj = STRING_OBJ(((String) { - .data = xmemdupz(s, len), - .size = len, - })); - break; - } - case LUA_TNUMBER: { - 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) { - *cur.obj = FLOAT_OBJ((Float)n); - } else { - *cur.obj = INTEGER_OBJ((Integer)n); - } - break; - } - case LUA_TTABLE: { - const LuaTableProps table_props = nlua_traverse_table(lstate); - - switch (table_props.type) { - case kObjectTypeArray: { - *cur.obj = ARRAY_OBJ(((Array) { - .items = NULL, - .size = 0, - .capacity = 0, - })); - if (table_props.maxidx != 0) { - cur.obj->data.array.items = - xcalloc(table_props.maxidx, - sizeof(cur.obj->data.array.items[0])); - cur.obj->data.array.capacity = table_props.maxidx; - cur.container = true; - kv_push(stack, cur); - } - break; - } - case kObjectTypeDictionary: { - *cur.obj = DICTIONARY_OBJ(((Dictionary) { - .items = NULL, - .size = 0, - .capacity = 0, - })); - if (table_props.string_keys_num != 0) { - cur.obj->data.dictionary.items = - xcalloc(table_props.string_keys_num, - sizeof(cur.obj->data.dictionary.items[0])); - cur.obj->data.dictionary.capacity = table_props.string_keys_num; - cur.container = true; - kv_push(stack, cur); - lua_pushnil(lstate); - } - break; - } - case kObjectTypeFloat: { - *cur.obj = FLOAT_OBJ((Float)table_props.val); - break; - } - case kObjectTypeNil: { - api_set_error(err, Validation, "Cannot convert given lua table"); - break; - } - default: { - assert(false); - } - } - break; - } - default: { - api_set_error(err, Validation, "Cannot convert given lua type"); - break; - } - } - if (!cur.container) { - lua_pop(lstate, 1); - } - } - kv_destroy(stack); - if (err->set) { - api_free_object(ret); - ret = NIL; - lua_pop(lstate, lua_gettop(lstate) - initial_size + 1); - } - assert(lua_gettop(lstate) == initial_size - 1); - return ret; -} - -#define GENERATE_INDEX_FUNCTION(type) \ -type nlua_pop_##type(lua_State *lstate, Error *err) \ - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \ -{ \ - type ret; \ - NLUA_POP_HANDLE(lstate, type, -1, ret); \ - lua_pop(lstate, 1); \ - return ret; \ -} - -GENERATE_INDEX_FUNCTION(Buffer) -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/converter.h b/src/nvim/viml/executor/converter.h deleted file mode 100644 index dbbaaebf6b..0000000000 --- a/src/nvim/viml/executor/converter.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef NVIM_VIML_EXECUTOR_CONVERTER_H -#define NVIM_VIML_EXECUTOR_CONVERTER_H - -#include -#include -#include - -#include "nvim/api/private/defs.h" -#include "nvim/func_attr.h" -#include "nvim/eval.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "viml/executor/converter.h.generated.h" -#endif -#endif // NVIM_VIML_EXECUTOR_CONVERTER_H diff --git a/src/nvim/viml/executor/executor.c b/src/nvim/viml/executor/executor.c deleted file mode 100644 index 826460772e..0000000000 --- a/src/nvim/viml/executor/executor.c +++ /dev/null @@ -1,576 +0,0 @@ -#include -#include -#include - -#include "nvim/misc1.h" -#include "nvim/getchar.h" -#include "nvim/garray.h" -#include "nvim/func_attr.h" -#include "nvim/api/private/defs.h" -#include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" -#include "nvim/vim.h" -#include "nvim/ex_getln.h" -#include "nvim/message.h" -#include "nvim/memline.h" -#include "nvim/buffer_defs.h" -#include "nvim/macros.h" -#include "nvim/screen.h" -#include "nvim/cursor.h" -#include "nvim/undo.h" -#include "nvim/ascii.h" - -#include "nvim/viml/executor/executor.h" -#include "nvim/viml/executor/converter.h" - -typedef struct { - Error err; - String lua_err_str; -} LuaError; - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "viml/executor/vim_module.generated.h" -# include "viml/executor/executor.c.generated.h" -#endif - -/// Name of the run code for use in messages -#define NLUA_EVAL_NAME "" - -/// Call C function which does not expect any arguments -/// -/// @param function Called function -/// @param numret Number of returned arguments -#define NLUA_CALL_C_FUNCTION_0(lstate, function, numret) \ - do { \ - lua_pushcfunction(lstate, &function); \ - lua_call(lstate, 0, numret); \ - } while (0) -/// Call C function which expects one argument -/// -/// @param function Called function -/// @param numret Number of returned arguments -/// @param a… Supplied argument (should be a void* pointer) -#define NLUA_CALL_C_FUNCTION_1(lstate, function, numret, a1) \ - do { \ - lua_pushcfunction(lstate, &function); \ - lua_pushlightuserdata(lstate, a1); \ - lua_call(lstate, 1, numret); \ - } while (0) -/// Call C function which expects two arguments -/// -/// @param function Called function -/// @param numret Number of returned arguments -/// @param a… Supplied argument (should be a void* pointer) -#define NLUA_CALL_C_FUNCTION_2(lstate, function, numret, a1, a2) \ - do { \ - lua_pushcfunction(lstate, &function); \ - lua_pushlightuserdata(lstate, a1); \ - lua_pushlightuserdata(lstate, a2); \ - lua_call(lstate, 2, numret); \ - } while (0) -/// Call C function which expects three arguments -/// -/// @param function Called function -/// @param numret Number of returned arguments -/// @param a… Supplied argument (should be a void* pointer) -#define NLUA_CALL_C_FUNCTION_3(lstate, function, numret, a1, a2, a3) \ - do { \ - lua_pushcfunction(lstate, &function); \ - lua_pushlightuserdata(lstate, a1); \ - lua_pushlightuserdata(lstate, a2); \ - lua_pushlightuserdata(lstate, a3); \ - lua_call(lstate, 3, numret); \ - } while (0) -/// Call C function which expects five arguments -/// -/// @param function Called function -/// @param numret Number of returned arguments -/// @param a… Supplied argument (should be a void* pointer) -#define NLUA_CALL_C_FUNCTION_4(lstate, function, numret, a1, a2, a3, a4) \ - do { \ - lua_pushcfunction(lstate, &function); \ - lua_pushlightuserdata(lstate, a1); \ - lua_pushlightuserdata(lstate, a2); \ - lua_pushlightuserdata(lstate, a3); \ - lua_pushlightuserdata(lstate, a4); \ - lua_call(lstate, 4, numret); \ - } while (0) - -/// Convert lua error into a Vim error message -/// -/// @param lstate Lua interpreter state. -/// @param[in] msg Message base, must contain one `%s`. -static void nlua_error(lua_State *const lstate, const char *const msg) - FUNC_ATTR_NONNULL_ALL -{ - size_t len; - const char *const str = lua_tolstring(lstate, -1, &len); - - emsgf(msg, (int)len, str); - - lua_pop(lstate, 1); -} - -/// Compare two strings, ignoring case -/// -/// Expects two values on the stack: compared strings. Returns one of the -/// following numbers: 0, -1 or 1. -/// -/// Does no error handling: never call it with non-string or with some arguments -/// omitted. -static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL -{ - const char *s1 = luaL_checklstring(lstate, 1, NULL); - const char *s2 = luaL_checklstring(lstate, 2, NULL); - const int ret = STRICMP(s1, s2); - lua_pop(lstate, 2); - lua_pushnumber(lstate, (lua_Number)((ret > 0) - (ret < 0))); - return 1; -} - -/// Evaluate lua string -/// -/// Expects two values on the stack: string to evaluate, pointer to the -/// location where result is saved. Always returns nothing (from the lua point -/// of view). -static int nlua_exec_lua_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL -{ - const String *const str = (const String *)lua_touserdata(lstate, 1); - typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 2); - lua_pop(lstate, 2); - - if (luaL_loadbuffer(lstate, str->data, str->size, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s")); - return 0; - } - if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s")); - return 0; - } - if (!nlua_pop_typval(lstate, ret_tv)) { - return 0; - } - return 0; -} - -/// Evaluate lua string for each line in range -/// -/// Expects two values on the stack: string to evaluate and pointer to integer -/// array with line range. Always returns nothing (from the lua point of view). -static int nlua_exec_luado_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL -{ - const String *const str = (const String *)lua_touserdata(lstate, 1); - const linenr_T *const range = (const linenr_T *)lua_touserdata(lstate, 2); - lua_pop(lstate, 2); - -#define DOSTART "return function(line, linenr) " -#define DOEND " end" - const size_t lcmd_len = (str->size - + (sizeof(DOSTART) - 1) - + (sizeof(DOEND) - 1)); - char *lcmd; - if (lcmd_len < IOSIZE) { - lcmd = (char *)IObuff; - } else { - lcmd = xmalloc(lcmd_len + 1); - } - memcpy(lcmd, DOSTART, sizeof(DOSTART) - 1); - memcpy(lcmd + sizeof(DOSTART) - 1, str->data, str->size); - memcpy(lcmd + sizeof(DOSTART) - 1 + str->size, DOEND, sizeof(DOEND) - 1); -#undef DOSTART -#undef DOEND - - if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s")); - if (lcmd_len >= IOSIZE) { - xfree(lcmd); - } - return 0; - } - if (lcmd_len >= IOSIZE) { - xfree(lcmd); - } - if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5110: Error while creating lua function: %.*s")); - return 0; - } - for (linenr_T l = range[0]; l <= range[1]; l++) { - if (l > curbuf->b_ml.ml_line_count) { - break; - } - lua_pushvalue(lstate, -1); - lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false)); - lua_pushnumber(lstate, (lua_Number)l); - if (lua_pcall(lstate, 2, 1, 0)) { - nlua_error(lstate, _("E5111: Error while calling lua function: %.*s")); - break; - } - if (lua_isstring(lstate, -1)) { - size_t new_line_len; - const char *const new_line = lua_tolstring(lstate, -1, &new_line_len); - char *const new_line_transformed = xmemdupz(new_line, new_line_len); - for (size_t i = 0; i < new_line_len; i++) { - if (new_line_transformed[i] == NUL) { - new_line_transformed[i] = '\n'; - } - } - ml_replace(l, (char_u *)new_line_transformed, false); - changed_bytes(l, 0); - } - lua_pop(lstate, 1); - } - lua_pop(lstate, 1); - check_cursor(); - update_screen(NOT_VALID); - return 0; -} - -/// Evaluate lua file -/// -/// Expects one value on the stack: file to evaluate. Always returns nothing -/// (from the lua point of view). -static int nlua_exec_lua_file(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL -{ - const char *const filename = (const char *)lua_touserdata(lstate, 1); - lua_pop(lstate, 1); - - if (luaL_loadfile(lstate, filename)) { - nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s")); - return 0; - } - if (lua_pcall(lstate, 0, 0, 0)) { - nlua_error(lstate, _("E5113: Error while calling lua chunk: %.*s")); - return 0; - } - return 0; -} - -/// Initialize lua interpreter state -/// -/// Called by lua interpreter itself to initialize state. -static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL -{ - // stricmp - lua_pushcfunction(lstate, &nlua_stricmp); - lua_setglobal(lstate, "stricmp"); - - // print - lua_pushcfunction(lstate, &nlua_print); - lua_setglobal(lstate, "print"); - - // debug.debug - lua_getglobal(lstate, "debug"); - lua_pushcfunction(lstate, &nlua_debug); - lua_setfield(lstate, -2, "debug"); - lua_pop(lstate, 1); - - // vim - if (luaL_dostring(lstate, (char *)&vim_module[0])) { - nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); - return 1; - } - // vim.api - nlua_add_api_functions(lstate); - // vim.types, vim.type_idx, vim.val_idx - nlua_init_types(lstate); - lua_setglobal(lstate, "vim"); - return 0; -} - -/// Initialize lua interpreter -/// -/// Crashes NeoVim if initialization fails. Should be called once per lua -/// interpreter instance. -static lua_State *init_lua(void) - FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT -{ - lua_State *lstate = luaL_newstate(); - if (lstate == NULL) { - EMSG(_("E970: Failed to initialize lua interpreter")); - preserve_exit(); - } - luaL_openlibs(lstate); - NLUA_CALL_C_FUNCTION_0(lstate, nlua_state_init, 0); - return lstate; -} - -static lua_State *global_lstate = NULL; - -/// Execute lua string -/// -/// @param[in] str String to execute. -/// @param[out] ret_tv Location where result will be saved. -/// -/// @return Result of the execution. -void executor_exec_lua(const String str, typval_T *const ret_tv) - FUNC_ATTR_NONNULL_ALL -{ - if (global_lstate == NULL) { - global_lstate = init_lua(); - } - - NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_lua_string, 0, - (void *)&str, ret_tv); -} - -/// Evaluate lua string -/// -/// Used for luaeval(). Expects three values on the stack: -/// -/// 1. String to evaluate. -/// 2. _A value. -/// 3. Pointer to location where result is saved. -/// -/// @param[in,out] lstate Lua interpreter state. -static int nlua_eval_lua_string(lua_State *const lstate) - FUNC_ATTR_NONNULL_ALL -{ - const String *const str = (const String *)lua_touserdata(lstate, 1); - typval_T *const arg = (typval_T *)lua_touserdata(lstate, 2); - typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 3); - lua_pop(lstate, 3); - - garray_T str_ga; - ga_init(&str_ga, 1, 80); -#define EVALHEADER "local _A=select(1,...) return (" - const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str->size + 1; - char *lcmd; - if (lcmd_len < IOSIZE) { - lcmd = (char *)IObuff; - } else { - lcmd = xmalloc(lcmd_len); - } - memcpy(lcmd, EVALHEADER, sizeof(EVALHEADER) - 1); - memcpy(lcmd + sizeof(EVALHEADER) - 1, str->data, str->size); - lcmd[lcmd_len - 1] = ')'; -#undef EVALHEADER - if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { - nlua_error(lstate, - _("E5107: Error while creating lua chunk for luaeval(): %.*s")); - if (lcmd != (char *)IObuff) { - xfree(lcmd); - } - return 0; - } - if (lcmd != (char *)IObuff) { - xfree(lcmd); - } - - if (arg == NULL || arg->v_type == VAR_UNKNOWN) { - lua_pushnil(lstate); - } else { - nlua_push_typval(lstate, arg); - } - if (lua_pcall(lstate, 1, 1, 0)) { - nlua_error(lstate, - _("E5108: Error while calling lua chunk for luaeval(): %.*s")); - return 0; - } - if (!nlua_pop_typval(lstate, ret_tv)) { - return 0; - } - - return 0; -} - -/// Print as a Vim message -/// -/// @param lstate Lua interpreter state. -static int nlua_print(lua_State *const lstate) - FUNC_ATTR_NONNULL_ALL -{ -#define PRINT_ERROR(msg) \ - do { \ - errmsg = msg; \ - errmsg_len = sizeof(msg) - 1; \ - goto nlua_print_error; \ - } while (0) - const int nargs = lua_gettop(lstate); - lua_getglobal(lstate, "tostring"); - const char *errmsg = NULL; - size_t errmsg_len = 0; - garray_T msg_ga; - ga_init(&msg_ga, 1, 80); - int curargidx = 1; - for (; curargidx <= nargs; curargidx++) { - lua_pushvalue(lstate, -1); // tostring - lua_pushvalue(lstate, curargidx); // arg - if (lua_pcall(lstate, 1, 1, 0)) { - errmsg = lua_tolstring(lstate, -1, &errmsg_len); - goto nlua_print_error; - } - size_t len; - const char *const s = lua_tolstring(lstate, -1, &len); - if (s == NULL) { - PRINT_ERROR( - ""); - } - ga_concat_len(&msg_ga, s, len); - if (curargidx < nargs) { - ga_append(&msg_ga, ' '); - } - lua_pop(lstate, 1); - } -#undef PRINT_ERROR - lua_pop(lstate, nargs + 1); - ga_append(&msg_ga, NUL); - { - const size_t len = (size_t)msg_ga.ga_len - 1; - char *const str = (char *)msg_ga.ga_data; - - for (size_t i = 0; i < len;) { - const size_t start = i; - while (i < len) { - switch (str[i]) { - case NUL: { - str[i] = NL; - i++; - continue; - } - case NL: { - str[i] = NUL; - i++; - break; - } - default: { - i++; - continue; - } - } - break; - } - msg((char_u *)str + start); - } - if (str[len - 1] == NUL) { // Last was newline - msg((char_u *)""); - } - } - ga_clear(&msg_ga); - return 0; -nlua_print_error: - emsgf(_("E5114: Error while converting print argument #%i: %.*s"), - curargidx, errmsg_len, errmsg); - ga_clear(&msg_ga); - lua_pop(lstate, lua_gettop(lstate)); - return 0; -} - -/// debug.debug implementation: interaction with user while debugging -/// -/// @param lstate Lua interpreter state. -int nlua_debug(lua_State *lstate) - FUNC_ATTR_NONNULL_ALL -{ - const typval_T input_args[] = { - { - .v_lock = VAR_FIXED, - .v_type = VAR_STRING, - .vval.v_string = (char_u *)"lua_debug> ", - }, - { - .v_type = VAR_UNKNOWN, - }, - }; - for (;;) { - lua_settop(lstate, 0); - typval_T input; - get_user_input(input_args, &input, false); - msg_putchar('\n'); // Avoid outputting on input line. - if (input.v_type != VAR_STRING - || input.vval.v_string == NULL - || *input.vval.v_string == NUL - || STRCMP(input.vval.v_string, "cont") == 0) { - tv_clear(&input); - return 0; - } - if (luaL_loadbuffer(lstate, (const char *)input.vval.v_string, - STRLEN(input.vval.v_string), "=(debug command)")) { - nlua_error(lstate, _("E5115: Error while loading debug string: %.*s")); - } - tv_clear(&input); - if (lua_pcall(lstate, 0, 0, 0)) { - nlua_error(lstate, _("E5116: Error while calling debug string: %.*s")); - } - } - return 0; -} - -/// Evaluate lua string -/// -/// Used for luaeval(). -/// -/// @param[in] str String to execute. -/// @param[in] arg Second argument to `luaeval()`. -/// @param[out] ret_tv Location where result will be saved. -/// -/// @return Result of the execution. -void executor_eval_lua(const String str, typval_T *const arg, - typval_T *const ret_tv) - FUNC_ATTR_NONNULL_ALL -{ - if (global_lstate == NULL) { - global_lstate = init_lua(); - } - - NLUA_CALL_C_FUNCTION_3(global_lstate, nlua_eval_lua_string, 0, - (void *)&str, arg, ret_tv); -} - -/// Run lua string -/// -/// Used for :lua. -/// -/// @param eap VimL command being run. -void ex_lua(exarg_T *const eap) - FUNC_ATTR_NONNULL_ALL -{ - size_t len; - char *const code = script_get(eap, &len); - if (eap->skip) { - xfree(code); - return; - } - typval_T tv = { .v_type = VAR_UNKNOWN }; - executor_exec_lua((String) { .data = code, .size = len }, &tv); - tv_clear(&tv); - xfree(code); -} - -/// Run lua string for each line in range -/// -/// Used for :luado. -/// -/// @param eap VimL command being run. -void ex_luado(exarg_T *const eap) - FUNC_ATTR_NONNULL_ALL -{ - if (global_lstate == NULL) { - global_lstate = init_lua(); - } - if (u_save(eap->line1 - 1, eap->line2 + 1) == FAIL) { - EMSG(_("cannot save undo information")); - return; - } - const String cmd = { - .size = STRLEN(eap->arg), - .data = (char *)eap->arg, - }; - const linenr_T range[] = { eap->line1, eap->line2 }; - NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_luado_string, 0, - (void *)&cmd, (void *)range); -} - -/// Run lua file -/// -/// Used for :luafile. -/// -/// @param eap VimL command being run. -void ex_luafile(exarg_T *const eap) - FUNC_ATTR_NONNULL_ALL -{ - if (global_lstate == NULL) { - global_lstate = init_lua(); - } - NLUA_CALL_C_FUNCTION_1(global_lstate, nlua_exec_lua_file, 0, - (void *)eap->arg); -} diff --git a/src/nvim/viml/executor/executor.h b/src/nvim/viml/executor/executor.h deleted file mode 100644 index 648bb73785..0000000000 --- a/src/nvim/viml/executor/executor.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef NVIM_VIML_EXECUTOR_EXECUTOR_H -#define NVIM_VIML_EXECUTOR_EXECUTOR_H - -#include - -#include "nvim/api/private/defs.h" -#include "nvim/func_attr.h" -#include "nvim/eval/typval.h" -#include "nvim/ex_cmds_defs.h" - -// Generated by msgpack-gen.lua -void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; - -#define set_api_error(s, err) \ - do { \ - Error *err_ = (err); \ - err_->type = kErrorTypeException; \ - err_->set = true; \ - memcpy(&err_->msg[0], s, sizeof(s)); \ - } while (0) - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "viml/executor/executor.h.generated.h" -#endif -#endif // NVIM_VIML_EXECUTOR_EXECUTOR_H diff --git a/src/nvim/viml/executor/vim.lua b/src/nvim/viml/executor/vim.lua deleted file mode 100644 index 8d1c5bdf4f..0000000000 --- a/src/nvim/viml/executor/vim.lua +++ /dev/null @@ -1,2 +0,0 @@ --- TODO(ZyX-I): Create compatibility layer. -return {} -- cgit From 0c5e359cb502cc858892930bcc8dbfadcdf09bd3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 11 Apr 2017 01:32:35 +0300 Subject: cmake: Append lua include also to single-includes targets --- src/nvim/CMakeLists.txt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 1401029cf5..7111e4aa81 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -358,12 +358,12 @@ target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES}) install_helper(TARGETS nvim) if(PREFER_LUAJIT) - set_property(TARGET nvim APPEND PROPERTY - INCLUDE_DIRECTORIES ${LUAJIT_INCLUDE_DIRS}) + set(LUA_PREFERRED_INCLUDE_DIRS ${LUAJIT_INCLUDE_DIRS}) else() - set_property(TARGET nvim APPEND PROPERTY - INCLUDE_DIRECTORIES ${LUA_INCLUDE_DIRS}) + set(LUA_PREFERRED_INCLUDE_DIRS ${LUA_INCLUDE_DIRS}) endif() +set_property(TARGET nvim APPEND PROPERTY + INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) if(WIN32) # Copy DLLs and third-party tools to bin/ and install them along with nvim @@ -540,6 +540,10 @@ foreach(hfile ${NVIM_HEADERS}) ${texe} EXCLUDE_FROM_ALL ${tsource} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_HEADERS}) + set_property( + TARGET ${texe} + APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS} + ) list(FIND NO_SINGLE_CHECK_HEADERS "${relative_path}" hfile_exclude_idx) if(${hfile_exclude_idx} EQUAL -1) -- cgit From 9bf15ca3fa2aa5f1a533b7b1598b6e3937fb1b17 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 11 Apr 2017 10:18:53 +0300 Subject: lua: Fix header guards --- src/nvim/lua/converter.h | 6 +++--- src/nvim/lua/executor.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h index 49f6ac4016..542c56ea3e 100644 --- a/src/nvim/lua/converter.h +++ b/src/nvim/lua/converter.h @@ -1,5 +1,5 @@ -#ifndef NVIM_VIML_EXECUTOR_CONVERTER_H -#define NVIM_VIML_EXECUTOR_CONVERTER_H +#ifndef NVIM_LUA_CONVERTER_H +#define NVIM_LUA_CONVERTER_H #include #include @@ -12,4 +12,4 @@ #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/converter.h.generated.h" #endif -#endif // NVIM_VIML_EXECUTOR_CONVERTER_H +#endif // NVIM_LUA_CONVERTER_H diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 22e55a56d3..0cbf290f64 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -1,5 +1,5 @@ -#ifndef NVIM_VIML_EXECUTOR_EXECUTOR_H -#define NVIM_VIML_EXECUTOR_EXECUTOR_H +#ifndef NVIM_LUA_EXECUTOR_H +#define NVIM_LUA_EXECUTOR_H #include @@ -22,4 +22,4 @@ void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/executor.h.generated.h" #endif -#endif // NVIM_VIML_EXECUTOR_EXECUTOR_H +#endif // NVIM_LUA_EXECUTOR_H -- cgit From a8ade2441d41289581628118a88c260c3ebb3fa5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 11 Apr 2017 10:37:14 +0300 Subject: lua/converter: Remove useless macros --- src/nvim/lua/converter.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 348315124b..80d9096212 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -542,16 +542,6 @@ bool nlua_push_typval(lua_State *lstate, typval_T *const tv) return true; } -#define NLUA_PUSH_HANDLE(lstate, type, idx) \ - do { \ - lua_pushnumber(lstate, (lua_Number)(idx)); \ - } while (0) - -#define NLUA_POP_HANDLE(lstate, type, stack_idx, idx) \ - do { \ - idx = (type)lua_tonumber(lstate, stack_idx); \ - } while (0) - /// Push value which is a type index /// /// Used for all “typed” tables: i.e. for all tables which represent VimL @@ -676,7 +666,7 @@ void nlua_push_Array(lua_State *lstate, const Array array) void nlua_push_##type(lua_State *lstate, const type item) \ FUNC_ATTR_NONNULL_ALL \ { \ - NLUA_PUSH_HANDLE(lstate, type, item); \ + lua_pushnumber(lstate, (lua_Number)(item)); \ } GENERATE_INDEX_FUNCTION(Buffer) @@ -1135,7 +1125,7 @@ type nlua_pop_##type(lua_State *lstate, Error *err) \ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \ { \ type ret; \ - NLUA_POP_HANDLE(lstate, type, -1, ret); \ + ret = (type)lua_tonumber(lstate, -1); \ lua_pop(lstate, 1); \ return ret; \ } -- cgit From 1bd39fb8d009f5ff62023d0f2fe86bbcdaeb3abc Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 11 Apr 2017 23:59:05 +0300 Subject: api: Remove FUNC_API_SINCE for nvim__ functions --- src/nvim/api/vim.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 0056ea1725..1d44e779c7 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -844,7 +844,6 @@ static void write_msg(String message, bool to_err) /// /// @return its argument. Object nvim__id(Object obj) - FUNC_API_SINCE(2) { return copy_object(obj); } @@ -858,7 +857,6 @@ Object nvim__id(Object obj) /// /// @return its argument. Array nvim__id_array(Array arr) - FUNC_API_SINCE(2) { return copy_object(ARRAY_OBJ(arr)).data.array; } @@ -872,7 +870,6 @@ Array nvim__id_array(Array arr) /// /// @return its argument. Dictionary nvim__id_dictionary(Dictionary dct) - FUNC_API_SINCE(2) { return copy_object(DICTIONARY_OBJ(dct)).data.dictionary; } @@ -886,7 +883,6 @@ Dictionary nvim__id_dictionary(Dictionary dct) /// /// @return its argument. Float nvim__id_float(Float flt) - FUNC_API_SINCE(2) { return flt; } -- cgit From 1d7fde39a6927d01de74aefb540ad445bfdfbfda Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 12 Apr 2017 00:31:01 +0300 Subject: api/buffer: Validate replacement array in a separate cycle Should not really change anything, but code should be more efficient by using more optimized libc functions (memchrsub is not libc, but it uses memchr) in place of a cycle. --- src/nvim/api/buffer.c | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 611f29f1f2..0a7b7982e2 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -292,6 +292,23 @@ void nvim_buf_set_lines(uint64_t channel_id, return; } + for (size_t i = 0; i < replacement.size; i++) { + if (replacement.items[i].type != kObjectTypeString) { + api_set_error(err, + Validation, + _("All items in the replacement array must be strings")); + return; + } + // Disallow newlines in the middle of the line. + if (channel_id != VIML_INTERNAL_CALL) { + const String l = replacement.items[i].data.string; + if (memchr(l.data, NL, l.size)) { + api_set_error(err, Validation, _("string cannot contain newlines")); + return; + } + } + } + win_T *save_curwin = NULL; tabpage_T *save_curtab = NULL; size_t new_len = replacement.size; @@ -300,26 +317,12 @@ void nvim_buf_set_lines(uint64_t channel_id, char **lines = (new_len != 0) ? xcalloc(new_len, sizeof(char *)) : NULL; for (size_t i = 0; i < new_len; i++) { - if (replacement.items[i].type != kObjectTypeString) { - api_set_error(err, - Validation, - _("All items in the replacement array must be strings")); - goto end; - } - - String l = replacement.items[i].data.string; + const String l = replacement.items[i].data.string; - // Fill lines[i] with l's contents. Disallow newlines in the middle of a - // line and convert NULs to newlines to avoid truncation. - lines[i] = xmallocz(l.size); - for (size_t j = 0; j < l.size; j++) { - if (l.data[j] == '\n' && channel_id != VIML_INTERNAL_CALL) { - api_set_error(err, Exception, _("string cannot contain newlines")); - new_len = i + 1; - goto end; - } - lines[i][j] = (char) (l.data[j] == '\0' ? '\n' : l.data[j]); - } + // Fill lines[i] with l's contents. Convert NULs to newlines as required by + // NL-used-for-NUL. + lines[i] = xmemdupz(l.data, l.size); + memchrsub(lines[i], NUL, NL, l.size); } try_start(); -- cgit From db1155f71393db3265cca0b71f89292f132a2347 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 8 May 2017 18:42:57 +0300 Subject: cmake: Workaround CMake not supporting INCLUDE_DIRECTORIES target prop --- src/nvim/CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 386048c781..23ec74fe5b 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -340,6 +340,15 @@ endif() set(NVIM_EXEC_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES}) set(NVIM_TEST_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES}) +if(CMAKE_VERSION VERSION_LESS "2.8.8") + if(PREFER_LUAJIT) + include_directories(${LUAJIT_INCLUDE_DIRS}) + else() + message(FATAL_ERROR + "Must support INCLUDE_DIRECTORIES target property to build") + endif() +endif() + if(PREFER_LUAJIT) list(APPEND NVIM_EXEC_LINK_LIBRARIES ${LUAJIT_LIBRARIES}) else() -- cgit From 85bf64da0a06a06548fb06838eddb884fcaef38b Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 8 May 2017 20:55:00 +0300 Subject: api/window: Fix memory leak in nvim_win_set_cursor --- src/nvim/api/window.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 859bf88398..22902800ea 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -62,6 +62,10 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) { win_T *win = find_window_by_handle(window, err); + if (!win) { + return; + } + if (pos.size != 2 || pos.items[0].type != kObjectTypeInteger || pos.items[1].type != kObjectTypeInteger) { api_set_error(err, @@ -70,10 +74,6 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) return; } - if (!win) { - return; - } - int64_t row = pos.items[0].data.integer; int64_t col = pos.items[1].data.integer; -- cgit