To: vim_dev@googlegroups.com Subject: Patch 8.2.1054 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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 <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 <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 -- Bram@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 ///