diff options
Diffstat (limited to 'src/nvim/lua/executor.c')
-rw-r--r-- | src/nvim/lua/executor.c | 779 |
1 files changed, 710 insertions, 69 deletions
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index f51aa3c6d4..0d5622f1e7 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -11,26 +11,33 @@ #include "nvim/func_attr.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/private/handle.h" #include "nvim/api/vim.h" +#include "nvim/msgpack_rpc/channel.h" #include "nvim/vim.h" #include "nvim/ex_getln.h" #include "nvim/ex_cmds2.h" #include "nvim/message.h" #include "nvim/memline.h" #include "nvim/buffer_defs.h" +#include "nvim/regexp.h" #include "nvim/macros.h" #include "nvim/screen.h" #include "nvim/cursor.h" #include "nvim/undo.h" #include "nvim/ascii.h" #include "nvim/change.h" +#include "nvim/eval/userfunc.h" +#include "nvim/event/time.h" +#include "nvim/event/loop.h" #ifdef WIN32 #include "nvim/os/os.h" #endif -#include "nvim/lua/executor.h" #include "nvim/lua/converter.h" +#include "nvim/lua/executor.h" +#include "nvim/lua/treesitter.h" #include "luv/luv.h" @@ -46,8 +53,14 @@ typedef struct { # include "lua/executor.c.generated.h" #endif -/// Name of the run code for use in messages -#define NLUA_EVAL_NAME "<VimL compiled string>" +#define PUSH_ALL_TYPVALS(lstate, args, argcount, special) \ + for (int i = 0; i < argcount; i++) { \ + if (args[i].v_type == VAR_UNKNOWN) { \ + lua_pushnil(lstate); \ + } else { \ + nlua_push_typval(lstate, &args[i], special); \ + } \ + } /// Convert lua error into a Vim error message /// @@ -245,6 +258,109 @@ static int nlua_schedule(lua_State *const lstate) return 0; } +static struct luaL_Reg regex_meta[] = { + { "__gc", regex_gc }, + { "__tostring", regex_tostring }, + { "match_str", regex_match_str }, + { "match_line", regex_match_line }, + { NULL, NULL } +}; + +// Dummy timer callback. Used by f_wait(). +static void dummy_timer_due_cb(TimeWatcher *tw, void *data) +{ +} + +// Dummy timer close callback. Used by f_wait(). +static void dummy_timer_close_cb(TimeWatcher *tw, void *data) +{ + xfree(tw); +} + +static bool nlua_wait_condition(lua_State *lstate, int *status, + bool *callback_result) +{ + lua_pushvalue(lstate, 2); + *status = lua_pcall(lstate, 0, 1, 0); + if (*status) { + return true; // break on error, but keep error on stack + } + *callback_result = lua_toboolean(lstate, -1); + lua_pop(lstate, 1); + return *callback_result; // break if true +} + +/// "vim.wait(timeout, condition[, interval])" function +static int nlua_wait(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + intptr_t timeout = luaL_checkinteger(lstate, 1); + if (timeout < 0) { + return luaL_error(lstate, "timeout must be > 0"); + } + + // Check if condition can be called. + bool is_function = (lua_type(lstate, 2) == LUA_TFUNCTION); + + // Check if condition is callable table + if (!is_function && luaL_getmetafield(lstate, 2, "__call") != 0) { + is_function = (lua_type(lstate, -1) == LUA_TFUNCTION); + lua_pop(lstate, 1); + } + + if (!is_function) { + lua_pushliteral(lstate, "vim.wait: condition must be a function"); + return lua_error(lstate); + } + + intptr_t interval = 200; + if (lua_gettop(lstate) >= 3) { + interval = luaL_checkinteger(lstate, 3); + if (interval < 0) { + return luaL_error(lstate, "interval must be > 0"); + } + } + + TimeWatcher *tw = xmalloc(sizeof(TimeWatcher)); + + // Start dummy timer. + time_watcher_init(&main_loop, tw, NULL); + tw->events = main_loop.events; + tw->blockable = true; + time_watcher_start(tw, dummy_timer_due_cb, + (uint64_t)interval, (uint64_t)interval); + + int pcall_status = 0; + bool callback_result = false; + + LOOP_PROCESS_EVENTS_UNTIL( + &main_loop, + main_loop.events, + (int)timeout, + nlua_wait_condition(lstate, &pcall_status, &callback_result) || got_int); + + // Stop dummy timer + time_watcher_stop(tw); + time_watcher_close(tw, dummy_timer_close_cb); + + if (pcall_status) { + return lua_error(lstate); + } else if (callback_result) { + lua_pushboolean(lstate, 1); + lua_pushnil(lstate); + } else if (got_int) { + got_int = false; + vgetc(); + lua_pushboolean(lstate, 0); + lua_pushinteger(lstate, -2); + } else { + lua_pushboolean(lstate, 0); + lua_pushinteger(lstate, -1); + } + + return 2; +} + /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. @@ -269,12 +385,7 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL #endif // vim - const char *code = (char *)&vim_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua") - || lua_pcall(lstate, 0, LUA_MULTRET, 0)) { - nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); - return 1; - } + lua_newtable(lstate); // vim.api nlua_add_api_functions(lstate); // vim.types, vim.type_idx, vim.val_idx @@ -294,6 +405,29 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // in_fast_event lua_pushcfunction(lstate, &nlua_in_fast_event); lua_setfield(lstate, -2, "in_fast_event"); + // call + lua_pushcfunction(lstate, &nlua_call); + lua_setfield(lstate, -2, "call"); + // regex + lua_pushcfunction(lstate, &nlua_regex); + lua_setfield(lstate, -2, "regex"); + luaL_newmetatable(lstate, "nvim_regex"); + luaL_register(lstate, NULL, regex_meta); + lua_pushvalue(lstate, -1); // [meta, meta] + lua_setfield(lstate, -2, "__index"); // [meta] + lua_pop(lstate, 1); // don't use metatable now + + // rpcrequest + lua_pushcfunction(lstate, &nlua_rpcrequest); + lua_setfield(lstate, -2, "rpcrequest"); + + // rpcnotify + lua_pushcfunction(lstate, &nlua_rpcnotify); + lua_setfield(lstate, -2, "rpcnotify"); + + // wait + lua_pushcfunction(lstate, &nlua_wait); + lua_setfield(lstate, -2, "wait"); // vim.loop luv_set_loop(lstate, &main_loop.uv); @@ -310,7 +444,45 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setfield(lstate, -2, "luv"); lua_pop(lstate, 3); + // vim.NIL + lua_newuserdata(lstate, 0); + lua_createtable(lstate, 0, 0); + lua_pushcfunction(lstate, &nlua_nil_tostring); + lua_setfield(lstate, -2, "__tostring"); + lua_setmetatable(lstate, -2); + nlua_nil_ref = nlua_ref(lstate, -1); + lua_setfield(lstate, -2, "NIL"); + + // vim._empty_dict_mt + lua_createtable(lstate, 0, 0); + lua_pushcfunction(lstate, &nlua_empty_dict_tostring); + lua_setfield(lstate, -2, "__tostring"); + nlua_empty_dict_ref = nlua_ref(lstate, -1); + lua_setfield(lstate, -2, "_empty_dict_mt"); + + // internal vim._treesitter... API + nlua_add_treesitter(lstate); + lua_setglobal(lstate, "vim"); + + { + const char *code = (char *)&shared_module[0]; + if (luaL_loadbuffer(lstate, code, strlen(code), "@shared.lua") + || lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("E5106: Error while creating shared module: %.*s")); + return 1; + } + } + + { + const char *code = (char *)&vim_module[0]; + if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua") + || lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); + return 1; + } + } + return 0; } @@ -371,29 +543,6 @@ static lua_State *nlua_enter(void) return lstate; } -/// 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 -{ - lua_State *const lstate = nlua_enter(); - - if (luaL_loadbuffer(lstate, str.data, str.size, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s")); - return; - } - if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s")); - return; - } - - nlua_pop_typval(lstate, ret_tv); -} - static void nlua_print_event(void **argv) { char *str = argv[0]; @@ -508,7 +657,7 @@ int nlua_debug(lua_State *lstate) for (;;) { lua_settop(lstate, 0); typval_T input; - get_user_input(input_args, &input, false); + get_user_input(input_args, &input, false, false); msg_putchar('\n'); // Avoid outputting on input line. if (input.v_type != VAR_STRING || input.vval.v_string == NULL @@ -534,6 +683,132 @@ int nlua_in_fast_event(lua_State *lstate) return 1; } +int nlua_call(lua_State *lstate) +{ + Error err = ERROR_INIT; + size_t name_len; + const char_u *name = (const char_u *)luaL_checklstring(lstate, 1, &name_len); + if (!nlua_is_deferred_safe(lstate)) { + return luaL_error(lstate, e_luv_api_disabled, "vimL function"); + } + + int nargs = lua_gettop(lstate)-1; + if (nargs > MAX_FUNC_ARGS) { + return luaL_error(lstate, "Function called with too many arguments"); + } + + typval_T vim_args[MAX_FUNC_ARGS + 1]; + int i = 0; // also used for freeing the variables + for (; i < nargs; i++) { + lua_pushvalue(lstate, (int)i+2); + if (!nlua_pop_typval(lstate, &vim_args[i])) { + api_set_error(&err, kErrorTypeException, + "error converting argument %d", i+1); + goto free_vim_args; + } + } + + TRY_WRAP({ + // TODO(bfredl): this should be simplified in error handling refactor + force_abort = false; + suppress_errthrow = false; + current_exception = NULL; + did_emsg = false; + + try_start(); + typval_T rettv; + int dummy; + // call_func() retval is deceptive, ignore it. Instead we set `msg_list` + // (TRY_WRAP) to capture abort-causing non-exception errors. + (void)call_func(name, (int)name_len, &rettv, nargs, + vim_args, NULL, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, NULL, NULL); + if (!try_end(&err)) { + nlua_push_typval(lstate, &rettv, false); + } + tv_clear(&rettv); + }); + +free_vim_args: + while (i > 0) { + tv_clear(&vim_args[--i]); + } + if (ERROR_SET(&err)) { + lua_pushstring(lstate, err.msg); + api_clear_error(&err); + return lua_error(lstate); + } + return 1; +} + +static int nlua_rpcrequest(lua_State *lstate) +{ + if (!nlua_is_deferred_safe(lstate)) { + return luaL_error(lstate, e_luv_api_disabled, "rpcrequest"); + } + return nlua_rpc(lstate, true); +} + +static int nlua_rpcnotify(lua_State *lstate) +{ + return nlua_rpc(lstate, false); +} + +static int nlua_rpc(lua_State *lstate, bool request) +{ + size_t name_len; + uint64_t chan_id = (uint64_t)luaL_checkinteger(lstate, 1); + const char *name = luaL_checklstring(lstate, 2, &name_len); + int nargs = lua_gettop(lstate)-2; + Error err = ERROR_INIT; + Array args = ARRAY_DICT_INIT; + + for (int i = 0; i < nargs; i++) { + lua_pushvalue(lstate, (int)i+3); + ADD(args, nlua_pop_Object(lstate, false, &err)); + if (ERROR_SET(&err)) { + api_free_array(args); + goto check_err; + } + } + + if (request) { + Object result = rpc_send_call(chan_id, name, args, &err); + if (!ERROR_SET(&err)) { + nlua_push_Object(lstate, result, false); + api_free_object(result); + } + } else { + if (!rpc_send_event(chan_id, name, args)) { + api_set_error(&err, kErrorTypeValidation, + "Invalid channel: %"PRIu64, chan_id); + } + } + +check_err: + if (ERROR_SET(&err)) { + lua_pushstring(lstate, err.msg); + api_clear_error(&err); + return lua_error(lstate); + } + + return request ? 1 : 0; +} + +static int nlua_nil_tostring(lua_State *lstate) +{ + lua_pushstring(lstate, "vim.NIL"); + return 1; +} + +static int nlua_empty_dict_tostring(lua_State *lstate) +{ + lua_pushstring(lstate, "vim.empty_dict()"); + return 1; +} + + #ifdef WIN32 /// os.getenv: override os.getenv to maintain coherency. #9681 /// @@ -568,12 +843,25 @@ void executor_free_luaref(LuaRef ref) nlua_unref(lstate, ref); } -/// push a value referenced in the regirstry +/// push a value referenced in the registry void nlua_pushref(lua_State *lstate, LuaRef ref) { lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref); } +/// Gets a new reference to an object stored at original_ref +/// +/// NOTE: It does not copy the value, it creates a new ref to the lua object. +/// Leaves the stack unchanged. +LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref) +{ + nlua_pushref(lstate, original_ref); + LuaRef new_ref = nlua_ref(lstate, -1); + lua_pop(lstate, 1); + + return new_ref; +} + /// Evaluate lua string /// /// Used for luaeval(). @@ -587,10 +875,6 @@ void executor_eval_lua(const String str, typval_T *const arg, typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { - lua_State *const lstate = nlua_enter(); - - 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; @@ -603,35 +887,118 @@ void executor_eval_lua(const String str, typval_T *const arg, 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; - } + typval_exec_lua(lcmd, lcmd_len, "luaeval()", arg, 1, true, ret_tv); + if (lcmd != (char *)IObuff) { xfree(lcmd); } +} - if (arg->v_type == VAR_UNKNOWN) { - lua_pushnil(lstate); +void executor_call_lua(const char *str, size_t len, typval_T *const args, + int argcount, typval_T *ret_tv) + FUNC_ATTR_NONNULL_ALL +{ +#define CALLHEADER "return " +#define CALLSUFFIX "(...)" + const size_t lcmd_len = sizeof(CALLHEADER) - 1 + len + sizeof(CALLSUFFIX) - 1; + char *lcmd; + if (lcmd_len < IOSIZE) { + lcmd = (char *)IObuff; } else { - nlua_push_typval(lstate, arg); + lcmd = xmalloc(lcmd_len); + } + memcpy(lcmd, CALLHEADER, sizeof(CALLHEADER) - 1); + memcpy(lcmd + sizeof(CALLHEADER) - 1, str, len); + memcpy(lcmd + sizeof(CALLHEADER) - 1 + len, CALLSUFFIX, + sizeof(CALLSUFFIX) - 1); +#undef CALLHEADER +#undef CALLSUFFIX + + typval_exec_lua(lcmd, lcmd_len, "v:lua", args, argcount, false, ret_tv); + + if (lcmd != (char *)IObuff) { + xfree(lcmd); + } +} + +static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, + typval_T *const args, int argcount, bool special, + typval_T *ret_tv) +{ + if (check_restricted() || check_secure()) { + if (ret_tv) { + ret_tv->v_type = VAR_NUMBER; + ret_tv->vval.v_number = 0; + } + return; + } + + lua_State *const lstate = nlua_enter(); + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, name)) { + nlua_error(lstate, _("E5107: Error loading lua %.*s")); + return; } - if (lua_pcall(lstate, 1, 1, 0)) { - nlua_error(lstate, - _("E5108: Error while calling lua chunk for luaeval(): %.*s")); + + PUSH_ALL_TYPVALS(lstate, args, argcount, special); + + if (lua_pcall(lstate, argcount, ret_tv ? 1 : 0, 0)) { + nlua_error(lstate, _("E5108: Error executing lua %.*s")); return; } - nlua_pop_typval(lstate, ret_tv); + if (ret_tv) { + nlua_pop_typval(lstate, ret_tv); + } +} + +/// Call a LuaCallable given some typvals +/// +/// Used to call any lua callable passed from Lua into VimL +/// +/// @param[in] lstate Lua State +/// @param[in] lua_cb Lua Callable +/// @param[in] argcount Count of typval arguments +/// @param[in] argvars Typval Arguments +/// @param[out] rettv The return value from the called function. +int typval_exec_lua_callable( + lua_State *lstate, + LuaCallable lua_cb, + int argcount, + typval_T *argvars, + typval_T *rettv +) +{ + int offset = 0; + LuaRef cb = lua_cb.func_ref; + + if (cb == LUA_NOREF) { + // This shouldn't happen. + luaL_error(lstate, "Invalid function passed to VimL"); + return ERROR_OTHER; + } + + nlua_pushref(lstate, cb); + + if (lua_cb.table_ref != LUA_NOREF) { + offset += 1; + nlua_pushref(lstate, lua_cb.table_ref); + } + + PUSH_ALL_TYPVALS(lstate, argvars, argcount, false); + + if (lua_pcall(lstate, argcount + offset, 1, 0)) { + nlua_print(lstate); + return ERROR_OTHER; + } + + nlua_pop_typval(lstate, rettv); + + return ERROR_NONE; } -/// Execute lua string +/// Execute Lua string /// -/// Used for nvim_execute_lua(). +/// Used for nvim_exec_lua(). /// /// @param[in] str String to execute. /// @param[in] args array of ... args @@ -666,7 +1033,7 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err) } Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args, - bool retval) + bool retval, Error *err) { lua_State *const lstate = nlua_enter(); nlua_pushref(lstate, ref); @@ -676,16 +1043,24 @@ Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args, } if (lua_pcall(lstate, (int)args.size+1, retval ? 1 : 0, 0)) { - // TODO(bfredl): callbacks:s might not always be msg-safe, for instance - // lua callbacks for redraw events. Later on let the caller deal with the - // error instead. - nlua_error(lstate, _("Error executing lua callback: %.*s")); + // if err is passed, the caller will deal with the error. + if (err) { + size_t len; + const char *errstr = lua_tolstring(lstate, -1, &len); + api_set_error(err, kErrorTypeException, + "Error executing lua: %.*s", (int)len, errstr); + } else { + nlua_error(lstate, _("Error executing lua callback: %.*s")); + } return NIL; } - Error err = ERROR_INIT; if (retval) { - return nlua_pop_Object(lstate, false, &err); + Error dummy = ERROR_INIT; + if (err == NULL) { + err = &dummy; + } + return nlua_pop_Object(lstate, false, err); } else { return NIL; } @@ -712,9 +1087,8 @@ void ex_lua(exarg_T *const eap) xfree(code); return; } - typval_T tv = { .v_type = VAR_UNKNOWN }; - executor_exec_lua((String) { .data = code, .size = len }, &tv); - tv_clear(&tv); + typval_exec_lua(code, len, ":lua", NULL, 0, false, NULL); + xfree(code); } @@ -752,8 +1126,8 @@ void ex_luado(exarg_T *const eap) #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 (luaL_loadbuffer(lstate, lcmd, lcmd_len, ":luado")) { + nlua_error(lstate, _("E5109: Error loading lua: %.*s")); if (lcmd_len >= IOSIZE) { xfree(lcmd); } @@ -763,7 +1137,7 @@ void ex_luado(exarg_T *const eap) xfree(lcmd); } if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5110: Error while creating lua function: %.*s")); + nlua_error(lstate, _("E5110: Error executing lua: %.*s")); return; } for (linenr_T l = eap->line1; l <= eap->line2; l++) { @@ -774,7 +1148,7 @@ void ex_luado(exarg_T *const eap) 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")); + nlua_error(lstate, _("E5111: Error calling lua: %.*s")); break; } if (lua_isstring(lstate, -1)) { @@ -816,3 +1190,270 @@ void ex_luafile(exarg_T *const eap) return; } } + +static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +{ + tslua_init(lstate); + + lua_pushcfunction(lstate, tslua_push_parser); + lua_setfield(lstate, -2, "_create_ts_parser"); + + lua_pushcfunction(lstate, tslua_add_language); + lua_setfield(lstate, -2, "_ts_add_language"); + + lua_pushcfunction(lstate, tslua_has_language); + lua_setfield(lstate, -2, "_ts_has_language"); + + lua_pushcfunction(lstate, tslua_inspect_lang); + lua_setfield(lstate, -2, "_ts_inspect_language"); + + lua_pushcfunction(lstate, ts_lua_parse_query); + lua_setfield(lstate, -2, "_ts_parse_query"); +} + +static int nlua_regex(lua_State *lstate) +{ + Error err = ERROR_INIT; + const char *text = luaL_checkstring(lstate, 1); + regprog_T *prog = NULL; + + TRY_WRAP({ + try_start(); + prog = vim_regcomp((char_u *)text, RE_AUTO | RE_MAGIC | RE_STRICT); + try_end(&err); + }); + + if (ERROR_SET(&err)) { + return luaL_error(lstate, "couldn't parse regex: %s", err.msg); + } + assert(prog); + + regprog_T **p = lua_newuserdata(lstate, sizeof(regprog_T *)); + *p = prog; + + lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim_regex"); // [udata, meta] + lua_setmetatable(lstate, -2); // [udata] + return 1; +} + +static regprog_T **regex_check(lua_State *L) +{ + return luaL_checkudata(L, 1, "nvim_regex"); +} + + +static int regex_gc(lua_State *lstate) +{ + regprog_T **prog = regex_check(lstate); + vim_regfree(*prog); + return 0; +} + +static int regex_tostring(lua_State *lstate) +{ + lua_pushstring(lstate, "<regex>"); + return 1; +} + +static int regex_match(lua_State *lstate, regprog_T **prog, char_u *str) +{ + regmatch_T rm; + rm.regprog = *prog; + rm.rm_ic = false; + bool match = vim_regexec(&rm, str, 0); + *prog = rm.regprog; + + if (match) { + lua_pushinteger(lstate, (lua_Integer)(rm.startp[0]-str)); + lua_pushinteger(lstate, (lua_Integer)(rm.endp[0]-str)); + return 2; + } + return 0; +} + +static int regex_match_str(lua_State *lstate) +{ + regprog_T **prog = regex_check(lstate); + const char *str = luaL_checkstring(lstate, 2); + int nret = regex_match(lstate, prog, (char_u *)str); + + if (!*prog) { + return luaL_error(lstate, "regex: internal error"); + } + + return nret; +} + +static int regex_match_line(lua_State *lstate) +{ + regprog_T **prog = regex_check(lstate); + + int narg = lua_gettop(lstate); + if (narg < 3) { + return luaL_error(lstate, "not enough args"); + } + + long bufnr = luaL_checkinteger(lstate, 2); + long rownr = luaL_checkinteger(lstate, 3); + long start = 0, end = -1; + if (narg >= 4) { + start = luaL_checkinteger(lstate, 4); + } + if (narg >= 5) { + end = luaL_checkinteger(lstate, 5); + if (end < 0) { + return luaL_error(lstate, "invalid end"); + } + } + + buf_T *buf = bufnr ? handle_get_buffer((int)bufnr) : curbuf; + if (!buf || buf->b_ml.ml_mfp == NULL) { + return luaL_error(lstate, "invalid buffer"); + } + + if (rownr >= buf->b_ml.ml_line_count) { + return luaL_error(lstate, "invalid row"); + } + + char_u *line = ml_get_buf(buf, rownr+1, false); + size_t len = STRLEN(line); + + if (start < 0 || (size_t)start > len) { + return luaL_error(lstate, "invalid start"); + } + + char_u save = NUL; + if (end >= 0) { + if ((size_t)end > len || end < start) { + return luaL_error(lstate, "invalid end"); + } + save = line[end]; + line[end] = NUL; + } + + int nret = regex_match(lstate, prog, line+start); + + if (end >= 0) { + line[end] = save; + } + + if (!*prog) { + return luaL_error(lstate, "regex: internal error"); + } + + return nret; +} + +int nlua_CFunction_func_call( + int argcount, + typval_T *argvars, + typval_T *rettv, + void *state) +{ + lua_State *const lstate = nlua_enter(); + LuaCFunctionState *funcstate = (LuaCFunctionState *)state; + + return typval_exec_lua_callable( + lstate, + funcstate->lua_callable, + argcount, + argvars, + rettv); +} +/// Required functions for lua c functions as VimL callbacks +void nlua_CFunction_func_free(void *state) +{ + lua_State *const lstate = nlua_enter(); + LuaCFunctionState *funcstate = (LuaCFunctionState *)state; + + nlua_unref(lstate, funcstate->lua_callable.func_ref); + nlua_unref(lstate, funcstate->lua_callable.table_ref); + xfree(funcstate); +} + +bool nlua_is_table_from_lua(typval_T *const arg) +{ + if (arg->v_type != VAR_DICT && arg->v_type != VAR_LIST) { + return false; + } + + if (arg->v_type == VAR_DICT) { + return arg->vval.v_dict->lua_table_ref > 0 + && arg->vval.v_dict->lua_table_ref != LUA_NOREF; + } else if (arg->v_type == VAR_LIST) { + return arg->vval.v_list->lua_table_ref > 0 + && arg->vval.v_list->lua_table_ref != LUA_NOREF; + } + + return false; +} + +char_u *nlua_register_table_as_callable(typval_T *const arg) +{ + if (!nlua_is_table_from_lua(arg)) { + return NULL; + } + + LuaRef table_ref; + if (arg->v_type == VAR_DICT) { + table_ref = arg->vval.v_dict->lua_table_ref; + } else if (arg->v_type == VAR_LIST) { + table_ref = arg->vval.v_list->lua_table_ref; + } else { + return NULL; + } + + lua_State *const lstate = nlua_enter(); + +#ifndef NDEBUG + int top = lua_gettop(lstate); +#endif + + nlua_pushref(lstate, table_ref); + if (!lua_getmetatable(lstate, -1)) { + return NULL; + } + + lua_getfield(lstate, -1, "__call"); + if (!lua_isfunction(lstate, -1)) { + return NULL; + } + + LuaRef new_table_ref = nlua_newref(lstate, table_ref); + + LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); + state->lua_callable.func_ref = nlua_ref(lstate, -1); + state->lua_callable.table_ref = new_table_ref; + + char_u *name = register_cfunc( + &nlua_CFunction_func_call, + &nlua_CFunction_func_free, + state); + + + lua_pop(lstate, 3); + assert(top == lua_gettop(lstate)); + + return name; +} + +/// Helper function to free a list_T +void nlua_free_typval_list(list_T *const l) +{ + if (l->lua_table_ref != LUA_NOREF && l->lua_table_ref > 0) { + lua_State *const lstate = nlua_enter(); + nlua_unref(lstate, l->lua_table_ref); + l->lua_table_ref = LUA_NOREF; + } +} + + +/// Helper function to free a dict_T +void nlua_free_typval_dict(dict_T *const d) +{ + if (d->lua_table_ref != LUA_NOREF && d->lua_table_ref > 0) { + lua_State *const lstate = nlua_enter(); + nlua_unref(lstate, d->lua_table_ref); + d->lua_table_ref = LUA_NOREF; + } +} |