Patch 8.2.1054

5 views
Skip to first unread message

Bram Moolenaar

unread,
Jun 25, 2020, 1:28:31 PM6/25/20
to vim...@googlegroups.com

Patch 8.2.1054
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)
Files: runtime/doc/if_lua.txt, src/if_lua.c, src/proto/userfunc.pro,
src/structs.h, src/testdir/test_lua.vim, src/userfunc.c


*** ../vim-8.2.1053/runtime/doc/if_lua.txt 2020-05-17 14:32:30.584490790 +0200
--- runtime/doc/if_lua.txt 2020-06-25 19:13:56.712302695 +0200
***************
*** 333,338 ****
--- 333,346 ----
:lua l = d.len -- assign d as 'self'
:lua print(l())
<
+ Lua functions and closures are automatically converted to a Vim |Funcref| and
+ can be accessed in Vim scripts. Example:
+ >
+ lua <<EOF
+ vim.fn.timer_start(1000, function(timer)
+ print('timer callback')
+ end)
+ EOF

==============================================================================
7. Buffer userdata *lua-buffer*
*** ../vim-8.2.1053/src/if_lua.c 2020-05-31 14:07:02.688752446 +0200
--- src/if_lua.c 2020-06-25 19:17:40.263764414 +0200
***************
*** 35,40 ****
--- 35,47 ----
} 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,50 ****
--- 52,59 ----
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,70 ****
#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) \
luaL_error(L, msg ": cannot convert value"); \
} while (0)

--- 73,79 ----
#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) \
luaL_error(L, msg ": cannot convert value"); \
} while (0)

***************
*** 72,77 ****
--- 81,88 ----
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,596 ****
--- 602,646 ----
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,2418 ****
--- 2465,2517 ----
}
}

+ /*
+ * 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
*** ../vim-8.2.1053/src/proto/userfunc.pro 2020-05-24 23:00:06.440196016 +0200
--- src/proto/userfunc.pro 2020-06-25 19:09:32.524908200 +0200
***************
*** 4,9 ****
--- 4,10 ----
int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, garray_T *argtypes, int *varargs, garray_T *default_args, int skip, exarg_T *eap, char_u **line_to_free);
char_u *get_lambda_name(void);
int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate);
+ char_u *register_cfunc(cfunc_T cb, cfunc_free_T free_cb, void *state);
char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload);
void emsg_funcname(char *ermsg, char_u *name);
int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, funcexe_T *funcexe);
*** ../vim-8.2.1053/src/structs.h 2020-06-24 20:33:59.561106332 +0200
--- src/structs.h 2020-06-25 19:20:10.439298026 +0200
***************
*** 1529,1534 ****
--- 1529,1537 ----
char bv_lock; // zero, VAR_LOCKED, VAR_FIXED
};

+ typedef int (*cfunc_T)(int argcount, typval_T *argvars, typval_T *rettv, void *state);
+ typedef void (*cfunc_free_T)(void *state);
+
#if defined(FEAT_EVAL) || defined(PROTO)
typedef struct funccall_S funccall_T;

***************
*** 1562,1567 ****
--- 1565,1575 ----
char_u *uf_va_name; // name from "...name" or NULL
type_T *uf_va_type; // type from "...name: type" or NULL
type_T *uf_func_type; // type of the function, &t_func_any if unknown
+ # if defined(FEAT_LUA)
+ cfunc_T uf_cb; // callback function for cfunc
+ cfunc_free_T uf_cb_free; // callback function to free cfunc
+ void *uf_cb_state; // state of uf_cb
+ # endif

garray_T uf_lines; // function lines
# ifdef FEAT_PROFILE
***************
*** 1607,1612 ****
--- 1615,1621 ----
#define FC_EXPORT 0x100 // "export def Func()"
#define FC_NOARGS 0x200 // no a: variables in lambda
#define FC_VIM9 0x400 // defined in vim9 script file
+ #define FC_CFUNC 0x800 // defined as Lua C func

#define MAX_FUNC_ARGS 20 // maximum number of function arguments
#define VAR_SHORT_LEN 20 // short variable name length
*** ../vim-8.2.1053/src/testdir/test_lua.vim 2020-05-31 14:07:02.688752446 +0200
--- src/testdir/test_lua.vim 2020-06-25 19:09:32.528908191 +0200
***************
*** 541,546 ****
--- 541,575 ----
call assert_equal("hello from lua", luaeval("require('testluaplugin').hello()"))
endfunc

+ func Vim_func_call_lua_callback(Concat, Cb)
+ let l:message = a:Concat("hello", "vim")
+ call a:Cb(l:message)
+ endfunc
+
+ func Test_pass_lua_callback_to_vim_from_lua()
+ lua pass_lua_callback_to_vim_from_lua_result = ""
+ call assert_equal("", luaeval("pass_lua_callback_to_vim_from_lua_result"))
+ lua <<EOF
+ vim.funcref('Vim_func_call_lua_callback')(
+ function(greeting, message)
+ return greeting .. " " .. message
+ end,
+ function(message)
+ pass_lua_callback_to_vim_from_lua_result = message
+ end)
+ EOF
+ call assert_equal("hello vim", luaeval("pass_lua_callback_to_vim_from_lua_result"))
+ endfunc
+
+ func Vim_func_call_metatable_lua_callback(Greet)
+ return a:Greet("world")
+ endfunc
+
+ func Test_pass_lua_metatable_callback_to_vim_from_lua()
+ let result = luaeval("vim.funcref('Vim_func_call_metatable_lua_callback')(setmetatable({ space = ' '}, { __call = function(tbl, msg) return 'hello' .. tbl.space .. msg end }) )")
+ call assert_equal("hello world", result)
+ endfunc
+
" Test vim.line()
func Test_lua_line()
new
*** ../vim-8.2.1053/src/userfunc.c 2020-06-24 20:33:59.569106308 +0200
--- src/userfunc.c 2020-06-25 19:22:31.434268094 +0200
***************
*** 341,346 ****
--- 341,391 ----
return name;
}

+ #if defined(FEAT_LUA) || defined(PROTO)
+ /*
+ * Registers a native C callback which can be called from Vim script.
+ * Returns the name of the Vim script function.
+ */
+ char_u *
+ register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
+ {
+ char_u *name = get_lambda_name();
+ ufunc_T *fp = NULL;
+ garray_T newargs;
+ garray_T newlines;
+
+ ga_init(&newargs);
+ ga_init(&newlines);
+
+ fp = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
+ if (fp == NULL)
+ goto errret;
+
+ fp->uf_dfunc_idx = UF_NOT_COMPILED;
+ fp->uf_refcount = 1;
+ fp->uf_varargs = TRUE;
+ fp->uf_flags = FC_CFUNC;
+ fp->uf_calls = 0;
+ fp->uf_script_ctx = current_sctx;
+ fp->uf_lines = newlines;
+ fp->uf_args = newargs;
+ fp->uf_cb = cb;
+ fp->uf_cb_free = cb_free;
+ fp->uf_cb_state = state;
+
+ set_ufunc_name(fp, name);
+ hash_add(&func_hashtab, UF2HIKEY(fp));
+
+ return name;
+
+ errret:
+ ga_clear_strings(&newargs);
+ ga_clear_strings(&newlines);
+ vim_free(fp);
+ return NULL;
+ }
+ #endif
+
/*
* Parse a lambda expression and get a Funcref from "*arg".
* Return OK or FAIL. Returns NOTDONE for dict or {expr}.
***************
*** 1027,1032 ****
--- 1072,1088 ----
vim_free(((type_T **)fp->uf_type_list.ga_data)
[--fp->uf_type_list.ga_len]);
ga_clear(&fp->uf_type_list);
+
+ #ifdef FEAT_LUA
+ if (fp->uf_cb_free != NULL)
+ {
+ fp->uf_cb_free(fp->uf_cb_state);
+ fp->uf_cb_free = NULL;
+ }
+
+ fp->uf_cb_state = NULL;
+ fp->uf_cb = NULL;
+ #endif
#ifdef FEAT_PROFILE
VIM_CLEAR(fp->uf_tml_count);
VIM_CLEAR(fp->uf_tml_total);
***************
*** 1973,1978 ****
--- 2029,2042 ----

if (fp != NULL && (fp->uf_flags & FC_DELETED))
error = FCERR_DELETED;
+ #ifdef FEAT_LUA
+ else if (fp != NULL && (fp->uf_flags & FC_CFUNC))
+ {
+ cfunc_T cb = fp->uf_cb;
+
+ error = (*cb)(argcount, argvars, rettv, fp->uf_cb_state);
+ }
+ #endif
else if (fp != NULL)
{
if (funcexe->argv_func != NULL)
*** ../vim-8.2.1053/src/version.c 2020-06-25 19:01:32.989844093 +0200
--- src/version.c 2020-06-25 19:11:30.652642598 +0200
***************
*** 756,757 ****
--- 756,759 ----
{ /* Add new patch number below this line */
+ /**/
+ 1054,
/**/

--
There is no right or wrong, there is only your personal opinion.
(Bram Moolenaar)

/// Bram Moolenaar -- Br...@Moolenaar.net -- http://www.Moolenaar.net \\\
/// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\ an exciting new programming language -- http://www.Zimbu.org ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///
Reply all
Reply to author
Forward
0 new messages