0
0
mirror of https://github.com/vim/vim.git synced 2025-09-23 03:43:49 -04:00

patch 8.2.1054: not so easy to pass a lua function to Vim

Problem:    Not so easy to pass a lua function to Vim.
Solution:   Convert a Lua function and closure to a Vim funcref. (Prabir
            Shrestha, closes #6246)
This commit is contained in:
Bram Moolenaar
2020-06-25 19:27:56 +02:00
parent 832adf9bb8
commit 801ab06934
7 changed files with 213 additions and 1 deletions

View File

@@ -35,6 +35,13 @@ typedef struct {
} luaV_Funcref;
typedef void (*msgfunc_T)(char_u *);
typedef struct {
int lua_funcref; // ref to a lua func
int lua_tableref; // ref to a lua table if metatable else LUA_NOREF. used
// for __call
lua_State *L;
} luaV_CFuncState;
static const char LUAVIM_DICT[] = "dict";
static const char LUAVIM_LIST[] = "list";
static const char LUAVIM_BLOB[] = "blob";
@@ -45,6 +52,8 @@ static const char LUAVIM_FREE[] = "luaV_free";
static const char LUAVIM_LUAEVAL[] = "luaV_luaeval";
static const char LUAVIM_SETREF[] = "luaV_setref";
static const char LUA___CALL[] = "__call";
// most functions are closures with a cache table as first upvalue;
// get/setudata manage references to vim userdata in cache table through
// object pointers (light userdata)
@@ -64,7 +73,7 @@ static const char LUAVIM_SETREF[] = "luaV_setref";
#define luaV_emsg(L) luaV_msgfunc((L), (msgfunc_T) emsg)
#define luaV_checktypval(L, a, v, msg) \
do { \
if (luaV_totypval(L, a, v) == FAIL) \
if (luaV_totypval(L, a, v) == FAIL) \
luaL_error(L, msg ": cannot convert value"); \
} while (0)
@@ -72,6 +81,8 @@ static luaV_List *luaV_pushlist(lua_State *L, list_T *lis);
static luaV_Dict *luaV_pushdict(lua_State *L, dict_T *dic);
static luaV_Blob *luaV_pushblob(lua_State *L, blob_T *blo);
static luaV_Funcref *luaV_pushfuncref(lua_State *L, char_u *name);
static int luaV_call_lua_func(int argcount, typval_T *argvars, typval_T *rettv, void *state);
static void luaV_call_lua_func_free(void *state);
#if LUA_VERSION_NUM <= 501
#define luaV_openlib(L, l, n) luaL_openlib(L, NULL, l, n)
@@ -591,6 +602,45 @@ luaV_totypval(lua_State *L, int pos, typval_T *tv)
tv->vval.v_number = (varnumber_T) lua_tointeger(L, pos);
#endif
break;
case LUA_TFUNCTION:
{
char_u *name;
lua_pushvalue(L, pos);
luaV_CFuncState *state = ALLOC_CLEAR_ONE(luaV_CFuncState);
state->lua_funcref = luaL_ref(L, LUA_REGISTRYINDEX);
state->L = L;
state->lua_tableref = LUA_NOREF;
name = register_cfunc(&luaV_call_lua_func,
&luaV_call_lua_func_free, state);
tv->v_type = VAR_FUNC;
tv->vval.v_string = vim_strsave(name);
break;
}
case LUA_TTABLE:
{
lua_pushvalue(L, pos);
int lua_tableref = luaL_ref(L, LUA_REGISTRYINDEX);
if (lua_getmetatable(L, pos)) {
lua_getfield(L, -1, LUA___CALL);
if (lua_isfunction(L, -1)) {
char_u *name;
int lua_funcref = luaL_ref(L, LUA_REGISTRYINDEX);
luaV_CFuncState *state = ALLOC_CLEAR_ONE(luaV_CFuncState);
state->lua_funcref = lua_funcref;
state->L = L;
state->lua_tableref = lua_tableref;
name = register_cfunc(&luaV_call_lua_func,
&luaV_call_lua_func_free, state);
tv->v_type = VAR_FUNC;
tv->vval.v_string = vim_strsave(name);
break;
}
}
tv->v_type = VAR_NUMBER;
tv->vval.v_number = 0;
status = FAIL;
break;
}
case LUA_TUSERDATA:
{
void *p = lua_touserdata(L, pos);
@@ -2415,4 +2465,53 @@ update_package_paths_in_lua()
}
}
/*
* Native C function callback
*/
static int
luaV_call_lua_func(
int argcount,
typval_T *argvars,
typval_T *rettv,
void *state)
{
int i;
int luaargcount = argcount;
luaV_CFuncState *funcstate = (luaV_CFuncState*)state;
lua_rawgeti(funcstate->L, LUA_REGISTRYINDEX, funcstate->lua_funcref);
if (funcstate->lua_tableref != LUA_NOREF)
{
// First arg for metatable __call method is a table
luaargcount += 1;
lua_rawgeti(funcstate->L, LUA_REGISTRYINDEX, funcstate->lua_tableref);
}
for (i = 0; i < argcount; ++i)
luaV_pushtypval(funcstate->L, &argvars[i]);
if (lua_pcall(funcstate->L, luaargcount, 1, 0))
{
luaV_emsg(funcstate->L);
return FCERR_OTHER;
}
luaV_checktypval(funcstate->L, -1, rettv, "get return value");
return FCERR_NONE;
}
/*
* Free up any lua references held by the func state.
*/
static void
luaV_call_lua_func_free(void *state)
{
luaV_CFuncState *funcstate = (luaV_CFuncState*)state;
luaL_unref(L, LUA_REGISTRYINDEX, funcstate->lua_funcref);
funcstate->L = NULL;
if (funcstate->lua_tableref != LUA_NOREF)
luaL_unref(L, LUA_REGISTRYINDEX, funcstate->lua_tableref);
VIM_CLEAR(funcstate);
}
#endif