mirror of
https://github.com/neovim/neovim.git
synced 2025-09-13 23:08:16 +00:00
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
This commit is contained in:
@@ -310,6 +310,9 @@ include_directories(SYSTEM ${LIBUV_INCLUDE_DIRS})
|
|||||||
find_package(Msgpack 1.0.0 REQUIRED)
|
find_package(Msgpack 1.0.0 REQUIRED)
|
||||||
include_directories(SYSTEM ${MSGPACK_INCLUDE_DIRS})
|
include_directories(SYSTEM ${MSGPACK_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
find_package(LuaJit REQUIRED)
|
||||||
|
include_directories(SYSTEM ${LUAJIT_INCLUDE_DIRS})
|
||||||
|
|
||||||
if(UNIX)
|
if(UNIX)
|
||||||
option(FEAT_TUI "Enable the Terminal UI" ON)
|
option(FEAT_TUI "Enable the Terminal UI" ON)
|
||||||
else()
|
else()
|
||||||
|
38
scripts/generate_vim_module.lua
Normal file
38
scripts/generate_vim_module.lua
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
assert(#arg == 2)
|
||||||
|
|
||||||
|
module_file = arg[1]
|
||||||
|
target_file = arg[2]
|
||||||
|
|
||||||
|
module = io.open(module_file, 'r')
|
||||||
|
target = io.open(target_file, 'w')
|
||||||
|
|
||||||
|
target:write('#include <stdint.h>\n\n')
|
||||||
|
target:write('static const uint8_t vim_module[] = {\n')
|
||||||
|
|
||||||
|
num_bytes = 0
|
||||||
|
MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line
|
||||||
|
target:write(' ')
|
||||||
|
|
||||||
|
increase_num_bytes = function()
|
||||||
|
num_bytes = num_bytes + 1
|
||||||
|
if num_bytes == MAX_NUM_BYTES then
|
||||||
|
num_bytes = 0
|
||||||
|
target:write('\n ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for line in module:lines() do
|
||||||
|
for i = 1,string.len(line) do
|
||||||
|
byte = string.byte(line, i)
|
||||||
|
assert(byte ~= 0)
|
||||||
|
target:write(string.format(' %3u,', byte))
|
||||||
|
increase_num_bytes()
|
||||||
|
end
|
||||||
|
target:write(string.format(' %3u,', string.byte('\n', 1)))
|
||||||
|
increase_num_bytes()
|
||||||
|
end
|
||||||
|
|
||||||
|
target:write(' 0};\n')
|
||||||
|
|
||||||
|
module:close()
|
||||||
|
target:close()
|
@@ -45,7 +45,16 @@ c_proto = Ct(
|
|||||||
grammar = Ct((c_proto + c_comment + c_preproc + ws) ^ 1)
|
grammar = Ct((c_proto + c_comment + c_preproc + ws) ^ 1)
|
||||||
|
|
||||||
-- we need at least 4 arguments since the last two are output files
|
-- we need at least 4 arguments since the last two are output files
|
||||||
assert(#arg >= 3)
|
if arg[1] == '--help' then
|
||||||
|
print('Usage: genmsgpack.lua args')
|
||||||
|
print('Args: 1: source directory')
|
||||||
|
print(' 2: dispatch output file (dispatch_wrappers.generated.h)')
|
||||||
|
print(' 3: functions metadata output file (funcs_metadata.generated.h)')
|
||||||
|
print(' 4: API metadata output file (api_metadata.mpack)')
|
||||||
|
print(' 5: lua C bindings output file (msgpack_lua_c_bindings.generated.c)')
|
||||||
|
print(' rest: C files where API functions are defined')
|
||||||
|
end
|
||||||
|
assert(#arg >= 4)
|
||||||
functions = {}
|
functions = {}
|
||||||
|
|
||||||
local nvimsrcdir = arg[1]
|
local nvimsrcdir = arg[1]
|
||||||
@@ -56,17 +65,18 @@ package.path = nvimsrcdir .. '/?.lua;' .. package.path
|
|||||||
headers = {}
|
headers = {}
|
||||||
|
|
||||||
-- output h file with generated dispatch functions
|
-- output h file with generated dispatch functions
|
||||||
dispatch_outputf = arg[#arg-2]
|
dispatch_outputf = arg[2]
|
||||||
-- output h file with packed metadata
|
-- output h file with packed metadata
|
||||||
funcs_metadata_outputf = arg[#arg-1]
|
funcs_metadata_outputf = arg[3]
|
||||||
-- output metadata mpack file, for use by other build scripts
|
-- output metadata mpack file, for use by other build scripts
|
||||||
mpack_outputf = arg[#arg]
|
mpack_outputf = arg[4]
|
||||||
|
lua_c_bindings_outputf = arg[5]
|
||||||
|
|
||||||
-- set of function names, used to detect duplicates
|
-- set of function names, used to detect duplicates
|
||||||
function_names = {}
|
function_names = {}
|
||||||
|
|
||||||
-- read each input file, parse and append to the api metadata
|
-- read each input file, parse and append to the api metadata
|
||||||
for i = 2, #arg - 3 do
|
for i = 6, #arg do
|
||||||
local full_path = arg[i]
|
local full_path = arg[i]
|
||||||
local parts = {}
|
local parts = {}
|
||||||
for part in string.gmatch(full_path, '[^/]+') do
|
for part in string.gmatch(full_path, '[^/]+') do
|
||||||
@@ -332,3 +342,128 @@ output:close()
|
|||||||
mpack_output = io.open(mpack_outputf, 'wb')
|
mpack_output = io.open(mpack_outputf, 'wb')
|
||||||
mpack_output:write(mpack.pack(functions))
|
mpack_output:write(mpack.pack(functions))
|
||||||
mpack_output:close()
|
mpack_output:close()
|
||||||
|
|
||||||
|
local function include_headers(output, headers)
|
||||||
|
for i = 1, #headers do
|
||||||
|
if headers[i]:sub(-12) ~= '.generated.h' then
|
||||||
|
output:write('\n#include "nvim/'..headers[i]..'"')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function write_shifted_output(output, str)
|
||||||
|
str = str:gsub('\n ', '\n')
|
||||||
|
str = str:gsub('^ ', '')
|
||||||
|
str = str:gsub(' +$', '')
|
||||||
|
output:write(str)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- start building lua output
|
||||||
|
output = io.open(lua_c_bindings_outputf, 'wb')
|
||||||
|
|
||||||
|
output:write([[
|
||||||
|
#include <lua.h>
|
||||||
|
#include <lualib.h>
|
||||||
|
#include <lauxlib.h>
|
||||||
|
|
||||||
|
#include "nvim/func_attr.h"
|
||||||
|
#include "nvim/api/private/defs.h"
|
||||||
|
#include "nvim/viml/executor/converter.h"
|
||||||
|
]])
|
||||||
|
include_headers(output, headers)
|
||||||
|
output:write('\n')
|
||||||
|
|
||||||
|
lua_c_functions = {}
|
||||||
|
|
||||||
|
local function process_function(fn)
|
||||||
|
lua_c_function_name = ('nlua_msgpack_%s'):format(fn.name)
|
||||||
|
write_shifted_output(output, string.format([[
|
||||||
|
|
||||||
|
static int %s(lua_State *lstate)
|
||||||
|
{
|
||||||
|
Error err = {.set = false};
|
||||||
|
]], lua_c_function_name))
|
||||||
|
lua_c_functions[#lua_c_functions + 1] = {
|
||||||
|
binding=lua_c_function_name,
|
||||||
|
api=fn.name
|
||||||
|
}
|
||||||
|
cparams = ''
|
||||||
|
for j, param in ipairs(fn.parameters) do
|
||||||
|
cparam = string.format('arg%u', j)
|
||||||
|
if param[1]:match('^ArrayOf') then
|
||||||
|
param_type = 'Array'
|
||||||
|
else
|
||||||
|
param_type = param[1]
|
||||||
|
end
|
||||||
|
write_shifted_output(output, string.format([[
|
||||||
|
%s %s = nlua_pop_%s(lstate, &err);
|
||||||
|
if (err.set) {
|
||||||
|
lua_pushstring(lstate, err.msg);
|
||||||
|
return lua_error(lstate);
|
||||||
|
}
|
||||||
|
]], param[1], cparam, param_type))
|
||||||
|
cparams = cparams .. cparam .. ', '
|
||||||
|
end
|
||||||
|
if fn.receives_channel_id then
|
||||||
|
cparams = 'INTERNAL_CALL, ' .. cparams
|
||||||
|
end
|
||||||
|
if fn.can_fail then
|
||||||
|
cparams = cparams .. '&err'
|
||||||
|
else
|
||||||
|
cparams = cparams:gsub(', $', '')
|
||||||
|
end
|
||||||
|
local name = fn.impl_name or fn.name
|
||||||
|
if fn.return_type ~= 'void' then
|
||||||
|
if fn.return_type:match('^ArrayOf') then
|
||||||
|
return_type = 'Array'
|
||||||
|
else
|
||||||
|
return_type = fn.return_type
|
||||||
|
end
|
||||||
|
write_shifted_output(output, string.format([[
|
||||||
|
%s ret = %s(%s);
|
||||||
|
if (err.set) {
|
||||||
|
lua_pushstring(lstate, err.msg);
|
||||||
|
return lua_error(lstate);
|
||||||
|
}
|
||||||
|
nlua_push_%s(lstate, ret);
|
||||||
|
return 1;
|
||||||
|
]], fn.return_type, name, cparams, return_type))
|
||||||
|
else
|
||||||
|
write_shifted_output(output, string.format([[
|
||||||
|
%s(%s);
|
||||||
|
if (err.set) {
|
||||||
|
lua_pushstring(lstate, err.msg);
|
||||||
|
return lua_error(lstate);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
]], name, cparams))
|
||||||
|
end
|
||||||
|
write_shifted_output(output, [[
|
||||||
|
}
|
||||||
|
]])
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, fn in ipairs(functions) do
|
||||||
|
if not fn.noeval then
|
||||||
|
process_function(fn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
output:write(string.format([[
|
||||||
|
void nlua_add_api_functions(lua_State *lstate)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
lua_createtable(lstate, 0, %u);
|
||||||
|
]], #lua_c_functions))
|
||||||
|
for _, func in ipairs(lua_c_functions) do
|
||||||
|
output:write(string.format([[
|
||||||
|
lua_pushcfunction(lstate, &%s);
|
||||||
|
lua_setfield(lstate, -2, "%s");
|
||||||
|
]], func.binding, func.api))
|
||||||
|
end
|
||||||
|
output:write([[
|
||||||
|
lua_setfield(lstate, -2, "api");
|
||||||
|
}
|
||||||
|
]])
|
||||||
|
|
||||||
|
output:close()
|
@@ -11,11 +11,10 @@ endif()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto)
|
set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto)
|
||||||
set(DISPATCH_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gendispatch.lua)
|
set(MSGPACK_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genmsgpack.lua)
|
||||||
file(GLOB API_HEADERS api/*.h)
|
|
||||||
file(GLOB MSGPACK_RPC_HEADERS msgpack_rpc/*.h)
|
|
||||||
set(API_METADATA ${PROJECT_BINARY_DIR}/api_metadata.mpack)
|
set(API_METADATA ${PROJECT_BINARY_DIR}/api_metadata.mpack)
|
||||||
set(FUNCS_DATA ${PROJECT_BINARY_DIR}/funcs_data.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(HEADER_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gendeclarations.lua)
|
||||||
set(GENERATED_INCLUDES_DIR ${PROJECT_BINARY_DIR}/include)
|
set(GENERATED_INCLUDES_DIR ${PROJECT_BINARY_DIR}/include)
|
||||||
set(GENERATED_API_DISPATCH ${GENERATED_DIR}/api/private/dispatch_wrappers.generated.h)
|
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(OPTIONS_LIST_FILE ${PROJECT_SOURCE_DIR}/src/nvim/options.lua)
|
||||||
set(UNICODE_TABLES_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genunicodetables.lua)
|
set(UNICODE_TABLES_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genunicodetables.lua)
|
||||||
set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/unicode)
|
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(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(${GENERATED_DIR})
|
||||||
include_directories(${CACHED_GENERATED_DIR})
|
include_directories(${CACHED_GENERATED_DIR})
|
||||||
@@ -57,6 +62,8 @@ foreach(subdir
|
|||||||
tui
|
tui
|
||||||
event
|
event
|
||||||
eval
|
eval
|
||||||
|
viml
|
||||||
|
viml/executor
|
||||||
)
|
)
|
||||||
if(${subdir} MATCHES "tui" AND NOT FEAT_TUI)
|
if(${subdir} MATCHES "tui" AND NOT FEAT_TUI)
|
||||||
continue()
|
continue()
|
||||||
@@ -198,18 +205,30 @@ add_custom_command(OUTPUT ${GENERATED_UNICODE_TABLES}
|
|||||||
${UNICODE_FILES}
|
${UNICODE_FILES}
|
||||||
)
|
)
|
||||||
|
|
||||||
add_custom_command(OUTPUT ${GENERATED_API_DISPATCH} ${GENERATED_FUNCS_METADATA}
|
add_custom_command(
|
||||||
${API_METADATA}
|
OUTPUT ${GENERATED_API_DISPATCH} ${GENERATED_FUNCS_METADATA}
|
||||||
COMMAND ${LUA_PRG} ${DISPATCH_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}
|
${API_METADATA} ${MSGPACK_LUA_C_BINDINGS}
|
||||||
${API_HEADERS} ${GENERATED_API_DISPATCH}
|
COMMAND ${LUA_PRG} ${MSGPACK_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}
|
||||||
|
${GENERATED_API_DISPATCH}
|
||||||
${GENERATED_FUNCS_METADATA} ${API_METADATA}
|
${GENERATED_FUNCS_METADATA} ${API_METADATA}
|
||||||
|
${MSGPACK_LUA_C_BINDINGS}
|
||||||
|
${API_HEADERS}
|
||||||
DEPENDS
|
DEPENDS
|
||||||
${API_HEADERS}
|
${API_HEADERS}
|
||||||
${MSGPACK_RPC_HEADERS}
|
${MSGPACK_RPC_HEADERS}
|
||||||
${DISPATCH_GENERATOR}
|
${MSGPACK_GENERATOR}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/api/dispatch_deprecated.lua
|
${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
|
list(APPEND NEOVIM_GENERATED_SOURCES
|
||||||
"${PROJECT_BINARY_DIR}/config/auto/pathdef.c"
|
"${PROJECT_BINARY_DIR}/config/auto/pathdef.c"
|
||||||
"${GENERATED_API_DISPATCH}"
|
"${GENERATED_API_DISPATCH}"
|
||||||
@@ -219,6 +238,8 @@ list(APPEND NEOVIM_GENERATED_SOURCES
|
|||||||
"${GENERATED_EVENTS_NAMES_MAP}"
|
"${GENERATED_EVENTS_NAMES_MAP}"
|
||||||
"${GENERATED_OPTIONS}"
|
"${GENERATED_OPTIONS}"
|
||||||
"${GENERATED_UNICODE_TABLES}"
|
"${GENERATED_UNICODE_TABLES}"
|
||||||
|
"${MSGPACK_LUA_C_BINDINGS}"
|
||||||
|
"${VIM_MODULE_FILE}"
|
||||||
)
|
)
|
||||||
|
|
||||||
add_custom_command(OUTPUT ${GENERATED_EX_CMDS_ENUM} ${GENERATED_EX_CMDS_DEFS}
|
add_custom_command(OUTPUT ${GENERATED_EX_CMDS_ENUM} ${GENERATED_EX_CMDS_DEFS}
|
||||||
@@ -270,6 +291,7 @@ list(APPEND NVIM_LINK_LIBRARIES
|
|||||||
${LIBTERMKEY_LIBRARIES}
|
${LIBTERMKEY_LIBRARIES}
|
||||||
${UNIBILIUM_LIBRARIES}
|
${UNIBILIUM_LIBRARIES}
|
||||||
${CMAKE_THREAD_LIBS_INIT}
|
${CMAKE_THREAD_LIBS_INIT}
|
||||||
|
${LUAJIT_LIBRARIES}
|
||||||
)
|
)
|
||||||
|
|
||||||
if(UNIX)
|
if(UNIX)
|
||||||
|
@@ -95,6 +95,7 @@
|
|||||||
#include "nvim/lib/khash.h"
|
#include "nvim/lib/khash.h"
|
||||||
#include "nvim/lib/queue.h"
|
#include "nvim/lib/queue.h"
|
||||||
#include "nvim/eval/typval_encode.h"
|
#include "nvim/eval/typval_encode.h"
|
||||||
|
#include "nvim/viml/executor/executor.h"
|
||||||
|
|
||||||
#define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */
|
#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
|
* "map()" function
|
||||||
*/
|
*/
|
||||||
|
@@ -193,6 +193,7 @@ return {
|
|||||||
localtime={},
|
localtime={},
|
||||||
log={args=1, func="float_op_wrapper", data="&log"},
|
log={args=1, func="float_op_wrapper", data="&log"},
|
||||||
log10={args=1, func="float_op_wrapper", data="&log10"},
|
log10={args=1, func="float_op_wrapper", data="&log10"},
|
||||||
|
luaeval={args={1, 2}},
|
||||||
map={args=2},
|
map={args=2},
|
||||||
maparg={args={1, 4}},
|
maparg={args={1, 4}},
|
||||||
mapcheck={args={1, 3}},
|
mapcheck={args={1, 3}},
|
||||||
|
537
src/nvim/viml/executor/converter.c
Normal file
537
src/nvim/viml/executor/converter.c
Normal file
@@ -0,0 +1,537 @@
|
|||||||
|
#include <lua.h>
|
||||||
|
#include <lualib.h>
|
||||||
|
#include <lauxlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#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
|
12
src/nvim/viml/executor/converter.h
Normal file
12
src/nvim/viml/executor/converter.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef NVIM_VIML_EXECUTOR_CONVERTER_H
|
||||||
|
#define NVIM_VIML_EXECUTOR_CONVERTER_H
|
||||||
|
|
||||||
|
#include <lua.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#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
|
270
src/nvim/viml/executor/executor.c
Normal file
270
src/nvim/viml/executor/executor.c
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
#include <lua.h>
|
||||||
|
#include <lualib.h>
|
||||||
|
#include <lauxlib.h>
|
||||||
|
|
||||||
|
#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 "<VimL compiled string>"
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
23
src/nvim/viml/executor/executor.h
Normal file
23
src/nvim/viml/executor/executor.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef NVIM_VIML_EXECUTOR_EXECUTOR_H
|
||||||
|
#define NVIM_VIML_EXECUTOR_EXECUTOR_H
|
||||||
|
|
||||||
|
#include <lua.h>
|
||||||
|
|
||||||
|
#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
|
2
src/nvim/viml/executor/vim.lua
Normal file
2
src/nvim/viml/executor/vim.lua
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- TODO(ZyX-I): Create compatibility layer.
|
||||||
|
return {}
|
Reference in New Issue
Block a user