To: vim_dev@googlegroups.com Subject: Patch 8.1.0134 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.1.0134 Problem: Lua interface does not support funcref. Solution: Add funcref support. (Luis Carvalho) Files: src/if_lua.c, src/testdir/test_lua.vim *** ../vim-8.1.0133/src/if_lua.c 2017-12-16 18:17:27.000000000 +0100 --- src/if_lua.c 2018-07-01 14:49:42.013143173 +0200 *************** *** 28,37 **** --- 28,43 ---- typedef win_T *luaV_Window; typedef dict_T *luaV_Dict; typedef list_T *luaV_List; + typedef struct { + typval_T tv; // funcref + typval_T args; + dict_T *self; // selfdict + } luaV_Funcref; typedef void (*msgfunc_T)(char_u *); static const char LUAVIM_DICT[] = "dict"; static const char LUAVIM_LIST[] = "list"; + static const char LUAVIM_FUNCREF[] = "funcref"; static const char LUAVIM_BUFFER[] = "buffer"; static const char LUAVIM_WINDOW[] = "window"; static const char LUAVIM_FREE[] = "luaV_free"; *************** *** 55,63 **** if (sandbox) luaL_error((L), "not allowed in sandbox") #define luaV_msg(L) luaV_msgfunc((L), (msgfunc_T) msg) #define luaV_emsg(L) luaV_msgfunc((L), (msgfunc_T) emsg) ! ! static luaV_List *luaV_pushlist (lua_State *L, list_T *lis); ! static luaV_Dict *luaV_pushdict (lua_State *L, dict_T *dic); #if LUA_VERSION_NUM <= 501 #define luaV_openlib(L, l, n) luaL_openlib(L, NULL, l, n) --- 61,75 ---- if (sandbox) luaL_error((L), "not allowed in sandbox") #define luaV_msg(L) luaV_msgfunc((L), (msgfunc_T) msg) #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) ! ! static luaV_List *luaV_pushlist(lua_State *L, list_T *lis); ! static luaV_Dict *luaV_pushdict(lua_State *L, dict_T *dic); ! static luaV_Funcref *luaV_pushfuncref(lua_State *L, typval_T *tv); #if LUA_VERSION_NUM <= 501 #define luaV_openlib(L, l, n) luaL_openlib(L, NULL, l, n) *************** *** 506,521 **** else lua_pushnil(L); break; default: lua_pushnil(L); } } ! /* converts lua value at 'pos' to typval 'tv' */ ! static void ! luaV_totypval (lua_State *L, int pos, typval_T *tv) { ! switch(lua_type(L, pos)) { case LUA_TBOOLEAN: tv->v_type = VAR_SPECIAL; tv->vval.v_number = (varnumber_T) lua_toboolean(L, pos); --- 518,542 ---- else lua_pushnil(L); break; + case VAR_FUNC: + luaV_pushfuncref(L, tv); + break; default: lua_pushnil(L); } } ! /* ! * Converts lua value at 'pos' to typval 'tv'. ! * Returns OK or FAIL. ! */ ! static int ! luaV_totypval(lua_State *L, int pos, typval_T *tv) { ! int status = OK; ! ! switch (lua_type(L, pos)) ! { case LUA_TBOOLEAN: tv->v_type = VAR_SPECIAL; tv->vval.v_number = (varnumber_T) lua_toboolean(L, pos); *************** *** 533,540 **** tv->vval.v_number = (varnumber_T) lua_tointeger(L, pos); #endif break; ! case LUA_TUSERDATA: { void *p = lua_touserdata(L, pos); if (lua_getmetatable(L, pos)) /* has metatable? */ { /* check list */ --- 554,563 ---- tv->vval.v_number = (varnumber_T) lua_tointeger(L, pos); #endif break; ! case LUA_TUSERDATA: ! { void *p = lua_touserdata(L, pos); + if (lua_getmetatable(L, pos)) /* has metatable? */ { /* check list */ *************** *** 545,551 **** tv->vval.v_list = *((luaV_List *) p); ++tv->vval.v_list->lv_refcount; lua_pop(L, 2); /* MTs */ ! return; } /* check dict */ luaV_getfield(L, LUAVIM_DICT); --- 568,574 ---- tv->vval.v_list = *((luaV_List *) p); ++tv->vval.v_list->lv_refcount; lua_pop(L, 2); /* MTs */ ! break; } /* check dict */ luaV_getfield(L, LUAVIM_DICT); *************** *** 555,570 **** tv->vval.v_dict = *((luaV_Dict *) p); ++tv->vval.v_dict->dv_refcount; lua_pop(L, 3); /* MTs */ ! return; } ! lua_pop(L, 3); /* MTs */ } - break; } default: tv->v_type = VAR_NUMBER; tv->vval.v_number = 0; } } /* similar to luaL_addlstring, but replaces \0 with \n if toline and --- 578,604 ---- tv->vval.v_dict = *((luaV_Dict *) p); ++tv->vval.v_dict->dv_refcount; lua_pop(L, 3); /* MTs */ ! break; } ! /* check funcref */ ! luaV_getfield(L, LUAVIM_FUNCREF); ! if (lua_rawequal(L, -1, -4)) ! { ! luaV_Funcref *f = (luaV_Funcref *) p; ! copy_tv(&f->tv, tv); ! lua_pop(L, 4); /* MTs */ ! break; ! } ! lua_pop(L, 4); /* MTs */ } } + /* FALLTHROUGH */ default: tv->v_type = VAR_NUMBER; tv->vval.v_number = 0; + status = FAIL; } + return status; } /* similar to luaL_addlstring, but replaces \0 with \n if toline and *************** *** 646,652 **** #define luaV_pushtype(typ,tname,luatyp) \ static luatyp * \ ! luaV_push##tname (lua_State *L, typ *obj) \ { \ luatyp *o = NULL; \ if (obj == NULL) \ --- 680,686 ---- #define luaV_pushtype(typ,tname,luatyp) \ static luatyp * \ ! luaV_push##tname(lua_State *L, typ *obj) \ { \ luatyp *o = NULL; \ if (obj == NULL) \ *************** *** 766,772 **** else { typval_T v; ! luaV_totypval(L, 3, &v); clear_tv(&li->li_tv); copy_tv(&v, &li->li_tv); clear_tv(&v); --- 800,806 ---- else { typval_T v; ! luaV_checktypval(L, 3, &v, "setting list item"); clear_tv(&li->li_tv); copy_tv(&v, &li->li_tv); clear_tv(&v); *************** *** 783,793 **** if (l->lv_lock) luaL_error(L, "list is locked"); lua_settop(L, 2); ! luaV_totypval(L, 2, &v); if (list_append_tv(l, &v) == FAIL) { clear_tv(&v); ! luaL_error(L, "Failed to add item to list"); } clear_tv(&v); lua_settop(L, 1); --- 817,827 ---- if (l->lv_lock) luaL_error(L, "list is locked"); lua_settop(L, 2); ! luaV_checktypval(L, 2, &v, "adding list item"); if (list_append_tv(l, &v) == FAIL) { clear_tv(&v); ! luaL_error(L, "failed to add item to list"); } clear_tv(&v); lua_settop(L, 1); *************** *** 811,821 **** luaL_error(L, "invalid position"); } lua_settop(L, 2); ! luaV_totypval(L, 2, &v); if (list_insert_tv(l, &v, li) == FAIL) { clear_tv(&v); ! luaL_error(L, "Failed to add item to list"); } clear_tv(&v); lua_settop(L, 1); --- 845,855 ---- luaL_error(L, "invalid position"); } lua_settop(L, 2); ! luaV_checktypval(L, 2, &v, "inserting list item"); if (list_insert_tv(l, &v, li) == FAIL) { clear_tv(&v); ! luaL_error(L, "failed to add item to list"); } clear_tv(&v); lua_settop(L, 1); *************** *** 894,919 **** } static int ! luaV_dict_index (lua_State *L) { dict_T *d = luaV_unbox(L, luaV_Dict, 1); char_u *key = (char_u *) luaL_checkstring(L, 2); dictitem_T *di = dict_find(d, key, -1); if (di == NULL) lua_pushnil(L); else luaV_pushtypval(L, &di->di_tv); return 1; } static int ! luaV_dict_newindex (lua_State *L) { dict_T *d = luaV_unbox(L, luaV_Dict, 1); char_u *key = (char_u *) luaL_checkstring(L, 2); dictitem_T *di; if (d->dv_lock) luaL_error(L, "dict is locked"); di = dict_find(d, key, -1); if (di == NULL) /* non-existing key? */ { --- 928,970 ---- } static int ! luaV_dict_index(lua_State *L) { dict_T *d = luaV_unbox(L, luaV_Dict, 1); char_u *key = (char_u *) luaL_checkstring(L, 2); dictitem_T *di = dict_find(d, key, -1); + if (di == NULL) lua_pushnil(L); else + { luaV_pushtypval(L, &di->di_tv); + if (di->di_tv.v_type == VAR_FUNC) /* funcref? */ + { + luaV_Funcref *f = (luaV_Funcref *) lua_touserdata(L, -1); + f->self = d; /* keep "self" reference */ + d->dv_refcount++; + } + } return 1; } static int ! luaV_dict_newindex(lua_State *L) { dict_T *d = luaV_unbox(L, luaV_Dict, 1); char_u *key = (char_u *) luaL_checkstring(L, 2); dictitem_T *di; + typval_T v; if (d->dv_lock) luaL_error(L, "dict is locked"); + if (key != NULL && *key == NUL) + luaL_error(L, "empty key"); + if (!lua_isnil(L, 3)) { /* read value? */ + luaV_checktypval(L, 3, &v, "setting dict item"); + if (d->dv_scope == VAR_DEF_SCOPE && v.v_type == VAR_FUNC) + luaL_error(L, "cannot assign funcref to builtin scope"); + } di = dict_find(d, key, -1); if (di == NULL) /* non-existing key? */ { *************** *** 934,942 **** hash_remove(&d->dv_hashtab, hi); dictitem_free(di); } ! else { ! typval_T v; ! luaV_totypval(L, 3, &v); copy_tv(&v, &di->di_tv); clear_tv(&v); } --- 985,992 ---- hash_remove(&d->dv_hashtab, hi); dictitem_free(di); } ! else ! { copy_tv(&v, &di->di_tv); clear_tv(&v); } *************** *** 953,958 **** --- 1003,1094 ---- }; + /* ======= Funcref type ======= */ + + static luaV_Funcref * + luaV_newfuncref(lua_State *L, char_u *name) + { + luaV_Funcref *f = (luaV_Funcref *)lua_newuserdata(L, sizeof(luaV_Funcref)); + + if (name != NULL) + { + func_ref(name); /* as in copy_tv */ + f->tv.vval.v_string = vim_strsave(name); + } + f->tv.v_type = VAR_FUNC; + f->args.v_type = VAR_LIST; + f->self = NULL; + luaV_getfield(L, LUAVIM_FUNCREF); + lua_setmetatable(L, -2); + return f; + } + + static luaV_Funcref * + luaV_pushfuncref(lua_State *L, typval_T *tv) + { + luaV_Funcref *f = luaV_newfuncref(L, NULL); + copy_tv(tv, &f->tv); + clear_tv(tv); + return f; + } + + + luaV_type_tostring(funcref, LUAVIM_FUNCREF) + + static int + luaV_funcref_gc(lua_State *L) + { + luaV_Funcref *f = (luaV_Funcref *) lua_touserdata(L, 1); + + func_unref(f->tv.vval.v_string); + vim_free(f->tv.vval.v_string); + dict_unref(f->self); + return 0; + } + + /* equivalent to string(funcref) */ + static int + luaV_funcref_len(lua_State *L) + { + luaV_Funcref *f = (luaV_Funcref *) lua_touserdata(L, 1); + + lua_pushstring(L, (const char *) f->tv.vval.v_string); + return 1; + } + + static int + luaV_funcref_call(lua_State *L) + { + luaV_Funcref *f = (luaV_Funcref *) lua_touserdata(L, 1); + int i, n = lua_gettop(L) - 1; /* #args */ + int status; + typval_T v, rettv; + + f->args.vval.v_list = list_alloc(); + rettv.v_type = VAR_UNKNOWN; /* as in clear_tv */ + for (i = 0; i < n; i++) { + luaV_checktypval(L, i + 2, &v, "calling funcref"); + list_append_tv(f->args.vval.v_list, &v); + } + status = func_call(f->tv.vval.v_string, &f->args, NULL, f->self, &rettv); + if (status == OK) + luaV_pushtypval(L, &rettv); + clear_tv(&f->args); + clear_tv(&rettv); + if (status != OK) + luaL_error(L, "cannot call funcref"); + return 1; + } + + static const luaL_Reg luaV_Funcref_mt[] = { + {"__tostring", luaV_funcref_tostring}, + {"__gc", luaV_funcref_gc}, + {"__len", luaV_funcref_len}, + {"__call", luaV_funcref_call}, + {NULL, NULL} + }; + + /* ======= Buffer type ======= */ luaV_newtype(buf_T, buffer, luaV_Buffer, LUAVIM_BUFFER) *************** *** 1033,1039 **** curbuf = buf; luaL_error(L, "cannot delete line"); } ! else { deleted_lines_mark(n, 1L); if (b == curwin->w_buffer) /* fix cursor in current window? */ { --- 1169,1176 ---- curbuf = buf; luaL_error(L, "cannot delete line"); } ! else ! { deleted_lines_mark(n, 1L); if (b == curwin->w_buffer) /* fix cursor in current window? */ { *************** *** 1371,1392 **** static int luaV_list(lua_State *L) { ! list_T *l = list_alloc(); if (l == NULL) lua_pushnil(L); else luaV_newlist(L, l); return 1; } static int luaV_dict(lua_State *L) { ! dict_T *d = dict_alloc(); if (d == NULL) lua_pushnil(L); else luaV_newdict(L, d); return 1; } --- 1508,1591 ---- static int luaV_list(lua_State *L) { ! list_T *l; ! int initarg = !lua_isnoneornil(L, 1); ! ! if (initarg && lua_type(L, 1) != LUA_TTABLE) ! luaL_error(L, "table expected, got %s", luaL_typename(L, 1)); ! l = list_alloc(); if (l == NULL) lua_pushnil(L); else + { luaV_newlist(L, l); + if (initarg) { /* traverse table to init dict */ + int notnil, i = 0; + typval_T v; + do { + lua_rawgeti(L, 1, ++i); + notnil = !lua_isnil(L, -1); + if (notnil) { + luaV_checktypval(L, -1, &v, "vim.list"); + list_append_tv(l, &v); + } + lua_pop(L, 1); /* value */ + } while (notnil); + } + } return 1; } static int luaV_dict(lua_State *L) { ! dict_T *d; ! int initarg = !lua_isnoneornil(L, 1); ! ! if (initarg && lua_type(L, 1) != LUA_TTABLE) ! luaL_error(L, "table expected, got %s", luaL_typename(L, 1)); ! d = dict_alloc(); if (d == NULL) lua_pushnil(L); else + { luaV_newdict(L, d); + if (initarg) /* traverse table to init dict */ + { + lua_pushnil(L); + while (lua_next(L, 1)) + { + char_u *key; + dictitem_T *di; + typval_T v; + lua_pushvalue(L, -2); /* dup key in case it's a number */ + key = (char_u *) lua_tostring(L, -1); + if (key != NULL && *key == NUL) + luaL_error(L, "table has empty key"); + luaV_checktypval(L, -2, &v, "vim.dict"); /* value */ + di = dictitem_alloc(key); + if (di == NULL || dict_add(d, di) == FAIL) { + vim_free(di); + lua_pushnil(L); + return 1; + } + copy_tv(&v, &di->di_tv); + clear_tv(&v); + lua_pop(L, 2); /* key copy and value */ + } + } + } + return 1; + } + + static int + luaV_funcref(lua_State *L) + { + const char *name = luaL_checkstring(L, 1); + /* note: not checking if function exists (needs function_exists) */ + if (name == NULL || *name == NUL || VIM_ISDIGIT(*name)) + luaL_error(L, "invalid function name: %s", name); + luaV_newfuncref(L, (char_u *) name); return 1; } *************** *** 1402,1408 **** FOR_ALL_BUFFERS(buf) if (buf->b_fnum == n) break; } ! else { /* by name */ size_t l; const char *s = lua_tolstring(L, 1, &l); FOR_ALL_BUFFERS(buf) --- 1601,1608 ---- FOR_ALL_BUFFERS(buf) if (buf->b_fnum == n) break; } ! else // by name ! { size_t l; const char *s = lua_tolstring(L, 1, &l); FOR_ALL_BUFFERS(buf) *************** *** 1472,1477 **** --- 1672,1683 ---- lua_pushstring(L, "dict"); return 1; } + luaV_getfield(L, LUAVIM_FUNCREF); + if (lua_rawequal(L, -1, 2)) + { + lua_pushstring(L, "funcref"); + return 1; + } luaV_getfield(L, LUAVIM_BUFFER); if (lua_rawequal(L, -1, 2)) { *************** *** 1497,1502 **** --- 1703,1709 ---- {"line", luaV_line}, {"list", luaV_list}, {"dict", luaV_dict}, + {"funcref", luaV_funcref}, {"buffer", luaV_buffer}, {"window", luaV_window}, {"open", luaV_open}, *************** *** 1537,1543 **** luaV_emsg(L); return 0; } ! luaV_totypval(L, -1, rettv); return 0; } --- 1744,1751 ---- luaV_emsg(L); return 0; } ! if (luaV_totypval(L, -1, rettv) == FAIL) ! EMSG("luaeval: cannot convert value"); return 0; } *************** *** 1612,1617 **** --- 1820,1828 ---- luaV_newmetatable(L, LUAVIM_DICT); lua_pushvalue(L, 1); luaV_openlib(L, luaV_Dict_mt, 1); + luaV_newmetatable(L, LUAVIM_FUNCREF); + lua_pushvalue(L, 1); + luaV_openlib(L, luaV_Funcref_mt, 1); luaV_newmetatable(L, LUAVIM_BUFFER); lua_pushvalue(L, 1); /* cache table */ luaV_openlib(L, luaV_Buffer_mt, 1); *** ../vim-8.1.0133/src/testdir/test_lua.vim 2018-06-30 21:50:16.856674912 +0200 --- src/testdir/test_lua.vim 2018-07-01 15:00:55.884371772 +0200 *************** *** 397,402 **** --- 397,425 ---- lua str, k, v, d = nil, nil, nil, nil endfunc + func Test_funcref() + function I(x) + return a:x + endfunction + let R = function('I') + lua i1 = vim.funcref"I" + lua i2 = vim.eval"R" + lua msg = "funcref|test|" .. (#i2(i1) == #i1(i2) and "OK" or "FAIL") + lua msg = vim.funcref"tr"(msg, "|", " ") + call assert_equal("funcref test OK", luaeval('msg')) + + " dict funcref + function Mylen() dict + return len(self.data) + endfunction + let l = [0, 1, 2, 3] + let mydict = {'data': l} + lua d = vim.eval"mydict" + lua d.len = vim.funcref"Mylen" -- assign d as 'self' + lua res = (d.len() == vim.funcref"len"(vim.eval"l")) and "OK" or "FAIL" + call assert_equal("OK", luaeval('res')) + endfunc + " Test vim.type() func Test_type() " The following values are identical to Lua's type function. *************** *** 414,419 **** --- 437,443 ---- call assert_equal('buffer', luaeval('vim.type(vim.buffer())')) call assert_equal('list', luaeval('vim.type(vim.list())')) call assert_equal('dict', luaeval('vim.type(vim.dict())')) + call assert_equal('funcref', luaeval('vim.type(vim.funcref("Test_type"))')) endfunc " Test vim.open() *** ../vim-8.1.0133/src/version.c 2018-06-30 22:40:39.097551835 +0200 --- src/version.c 2018-07-01 15:01:56.939951330 +0200 *************** *** 791,792 **** --- 791,794 ---- { /* Add new patch number below this line */ + /**/ + 134, /**/ -- hundred-and-one symptoms of being an internet addict: 162. You go outside and look for a brightness knob to turn down the sun. /// 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 ///