diff src/if_lua.c @ 21006:ae185f35e256 v8.2.1054

patch 8.2.1054: not so easy to pass a lua function to Vim Commit: https://github.com/vim/vim/commit/801ab069341c8652680d63c174530fd4feb2911e Author: Bram Moolenaar <Bram@vim.org> Date: Thu Jun 25 19:27:56 2020 +0200 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)
author Bram Moolenaar <Bram@vim.org>
date Thu, 25 Jun 2020 19:30:21 +0200
parents 054ba681412d
children 380923b96878
line wrap: on
line diff
--- a/src/if_lua.c
+++ b/src/if_lua.c
@@ -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_
 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[] = "lua
 #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_Stat
 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, typ
 	    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