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:
ZyX
2016-03-04 21:55:28 +03:00
parent f9a31e9850
commit e7bbd8256b
11 changed files with 1100 additions and 14 deletions

View File

@@ -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()

View 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()

View File

@@ -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()

View File

@@ -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)

View File

@@ -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
*/ */

View File

@@ -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}},

View 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

View 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

View 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;
}

View 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

View File

@@ -0,0 +1,2 @@
-- TODO(ZyX-I): Create compatibility layer.
return {}