To: vim_dev@googlegroups.com Subject: Patch 8.1.1043 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.1.1043 Problem: Lua interface does not support Blob. Solution: Add support to Blob. (Ozaki Kiichi, closes #4151) Files: runtime/doc/if_lua.txt, src/if_lua.c, src/testdir/test_lua.vim *** ../vim-8.1.1042/runtime/doc/if_lua.txt 2018-07-01 19:49:23.605793273 +0200 --- runtime/doc/if_lua.txt 2019-03-23 13:53:32.131159759 +0100 *************** *** 10,20 **** 2. The vim module |lua-vim| 3. List userdata |lua-list| 4. Dict userdata |lua-dict| ! 5. Funcref userdata |lua-funcref| ! 6. Buffer userdata |lua-buffer| ! 7. Window userdata |lua-window| ! 8. The luaeval function |lua-luaeval| ! 9. Dynamic loading |lua-dynamic| {Vi does not have any of these commands} --- 10,21 ---- 2. The vim module |lua-vim| 3. List userdata |lua-list| 4. Dict userdata |lua-dict| ! 5. Blob userdata |lua-blob| ! 6. Funcref userdata |lua-funcref| ! 7. Buffer userdata |lua-buffer| ! 8. Window userdata |lua-window| ! 9. luaeval() Vim function |lua-luaeval| ! 10. Dynamic loading |lua-dynamic| {Vi does not have any of these commands} *************** *** 125,133 **** Non-numeric keys are not used to initialize the list. See also |lua-eval| for conversion rules. Example: > ! :lua t = {math.pi, false, say = 'hi'} ! :echo luaeval('vim.list(t)') ! :" [3.141593, v:false], 'say' is ignored < vim.dict([arg]) Returns an empty dictionary or, if "arg" is a Lua table, returns a dict d such that d[k] = --- 126,134 ---- Non-numeric keys are not used to initialize the list. See also |lua-eval| for conversion rules. Example: > ! :lua t = {math.pi, false, say = 'hi'} ! :echo luaeval('vim.list(t)') ! :" [3.141593, v:false], 'say' is ignored < vim.dict([arg]) Returns an empty dictionary or, if "arg" is a Lua table, returns a dict d such that d[k] = *************** *** 136,147 **** strings. Keys that are not strings are not used to initialize the dictionary. See also |lua-eval| for conversion rules. Example: > ! :lua t = {math.pi, false, say = 'hi'} ! :echo luaeval('vim.dict(t)') ! :" {'say': 'hi'}, numeric keys ignored < vim.funcref({name}) Returns a Funcref to function {name} (see ! |Funcref|). It is equivalent to Vim's function(). vim.buffer([arg]) If "arg" is a number, returns buffer with number "arg" in the buffer list or, if "arg" --- 137,158 ---- strings. Keys that are not strings are not used to initialize the dictionary. See also |lua-eval| for conversion rules. Example: > ! :lua t = {math.pi, false, say = 'hi'} ! :echo luaeval('vim.dict(t)') ! :" {'1': 3.141593, '2': v:false, ! :" 'say': 'hi'} ! < ! vim.blob([arg]) Returns an empty blob or, if "arg" is a Lua ! string, returns a blob b such that b is ! equivalent to "arg" as a byte string. ! Examples: > ! :lua s = "12ab\x00\x80\xfe\xff" ! :echo luaeval('vim.blob(s)') ! :" 0z31326162.0080FEFF < vim.funcref({name}) Returns a Funcref to function {name} (see ! |Funcref|). It is equivalent to Vim's ! function(). vim.buffer([arg]) If "arg" is a number, returns buffer with number "arg" in the buffer list or, if "arg" *************** *** 258,264 **** < ============================================================================== ! 5. Funcref userdata *lua-funcref* Funcref userdata represent funcref variables in Vim. Funcrefs that were defined with a "dict" attribute need to be obtained as a dictionary key --- 269,302 ---- < ============================================================================== ! 5. Blob userdata *lua-blob* ! ! Blob userdata represent vim blobs. A blob "b" has the following properties: ! ! Properties ! ---------- ! o "#b" is the length of blob "b", equivalent to "len(b)" in Vim. ! o "b[k]" returns the k-th item in "b"; "b" is zero-indexed, as in Vim. ! To modify the k-th item, simply do "b[k] = number"; in particular, ! "b[#b] = number" can append a byte to tail. ! ! Methods ! ------- ! o "b:add(bytes)" appends "bytes" to the end of "b". ! ! Examples: ! > ! :let b = 0z001122 ! :lua b = vim.eval('b') -- same 'b' ! :lua print(b, b[0], #b) ! :lua b[1] = 32 ! :lua b[#b] = 0x33 -- append a byte to tail ! :lua b:add("\x80\x81\xfe\xff") ! :echo b ! < ! ! ============================================================================== ! 6. Funcref userdata *lua-funcref* Funcref userdata represent funcref variables in Vim. Funcrefs that were defined with a "dict" attribute need to be obtained as a dictionary key *************** *** 291,297 **** < ============================================================================== ! 6. Buffer userdata *lua-buffer* Buffer userdata represent vim buffers. A buffer userdata "b" has the following properties and methods: --- 329,335 ---- < ============================================================================== ! 7. Buffer userdata *lua-buffer* Buffer userdata represent vim buffers. A buffer userdata "b" has the following properties and methods: *************** *** 343,349 **** < ============================================================================== ! 7. Window userdata *lua-window* Window objects represent vim windows. A window userdata "w" has the following properties and methods: --- 381,387 ---- < ============================================================================== ! 8. Window userdata *lua-window* Window objects represent vim windows. A window userdata "w" has the following properties and methods: *************** *** 375,381 **** < ============================================================================== ! 8. The luaeval function *lua-luaeval* *lua-eval* The (dual) equivalent of "vim.eval" for passing Lua values to Vim is "luaeval". "luaeval" takes an expression string and an optional argument and --- 413,419 ---- < ============================================================================== ! 9. luaeval() Vim function *lua-luaeval* *lua-eval* The (dual) equivalent of "vim.eval" for passing Lua values to Vim is "luaeval". "luaeval" takes an expression string and an optional argument and *************** *** 388,397 **** end < Note that "_A" receives the argument to "luaeval". Lua numbers, strings, and ! list, dict, and funcref userdata are converted to their Vim respective types, ! while Lua booleans are converted to numbers. An error is thrown if conversion ! of any of the remaining Lua types, including userdata other than lists, dicts, ! and funcrefs, is attempted. Examples: > --- 426,435 ---- end < Note that "_A" receives the argument to "luaeval". Lua numbers, strings, and ! list, dict, blob, and funcref userdata are converted to their Vim respective ! types, while Lua booleans are converted to numbers. An error is thrown if ! conversion of any of the remaining Lua types, including userdata other than ! lists, dicts, blobs, and funcrefs, is attempted. Examples: > *************** *** 406,412 **** ============================================================================== ! 9. Dynamic loading *lua-dynamic* On MS-Windows and Unix the Lua library can be loaded dynamically. The |:version| output then includes |+lua/dyn|. --- 444,450 ---- ============================================================================== ! 10. Dynamic loading *lua-dynamic* On MS-Windows and Unix the Lua library can be loaded dynamically. The |:version| output then includes |+lua/dyn|. *** ../vim-8.1.1042/src/if_lua.c 2019-03-19 21:59:16.268914799 +0100 --- src/if_lua.c 2019-03-23 13:53:32.131159759 +0100 *************** *** 28,33 **** --- 28,34 ---- typedef win_T *luaV_Window; typedef dict_T *luaV_Dict; typedef list_T *luaV_List; + typedef blob_T *luaV_Blob; typedef struct { char_u *name; // funcref dict_T *self; // selfdict *************** *** 36,41 **** --- 37,43 ---- static const char LUAVIM_DICT[] = "dict"; static const char LUAVIM_LIST[] = "list"; + static const char LUAVIM_BLOB[] = "blob"; static const char LUAVIM_FUNCREF[] = "funcref"; static const char LUAVIM_BUFFER[] = "buffer"; static const char LUAVIM_WINDOW[] = "window"; *************** *** 68,73 **** --- 70,76 ---- 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); #if LUA_VERSION_NUM <= 501 *************** *** 541,546 **** --- 544,552 ---- case VAR_FUNC: luaV_pushfuncref(L, tv->vval.v_string); break; + case VAR_BLOB: + luaV_pushblob(L, tv->vval.v_blob); + break; default: lua_pushnil(L); } *************** *** 582,624 **** { void *p = lua_touserdata(L, pos); ! if (lua_getmetatable(L, pos)) /* has metatable? */ { ! /* check list */ luaV_getfield(L, LUAVIM_LIST); if (lua_rawequal(L, -1, -2)) { tv->v_type = VAR_LIST; 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); if (lua_rawequal(L, -1, -3)) { tv->v_type = VAR_DICT; 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; func_ref(f->name); tv->v_type = VAR_FUNC; tv->vval.v_string = vim_strsave(f->name); ! lua_pop(L, 4); /* MTs */ break; } ! lua_pop(L, 4); /* MTs */ } } ! /* FALLTHROUGH */ default: tv->v_type = VAR_NUMBER; tv->vval.v_number = 0; --- 588,640 ---- { void *p = lua_touserdata(L, pos); ! if (lua_getmetatable(L, pos)) // has metatable? { ! // check list luaV_getfield(L, LUAVIM_LIST); if (lua_rawequal(L, -1, -2)) { tv->v_type = VAR_LIST; 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); if (lua_rawequal(L, -1, -3)) { tv->v_type = VAR_DICT; tv->vval.v_dict = *((luaV_Dict *) p); ++tv->vval.v_dict->dv_refcount; ! lua_pop(L, 3); // MTs break; } ! // check blob ! luaV_getfield(L, LUAVIM_BLOB); if (lua_rawequal(L, -1, -4)) { + tv->v_type = VAR_BLOB; + tv->vval.v_blob = *((luaV_Blob *) p); + ++tv->vval.v_blob->bv_refcount; + lua_pop(L, 4); // MTs + break; + } + // check funcref + luaV_getfield(L, LUAVIM_FUNCREF); + if (lua_rawequal(L, -1, -5)) + { luaV_Funcref *f = (luaV_Funcref *) p; func_ref(f->name); tv->v_type = VAR_FUNC; tv->vval.v_string = vim_strsave(f->name); ! lua_pop(L, 5); // MTs break; } ! lua_pop(L, 4); // MTs } } ! // FALLTHROUGH default: tv->v_type = VAR_NUMBER; tv->vval.v_number = 0; *************** *** 753,759 **** luaV_list_len(lua_State *L) { list_T *l = luaV_unbox(L, luaV_List, 1); ! lua_pushinteger(L, (l == NULL) ? 0 : (int) l->lv_len); return 1; } --- 769,775 ---- luaV_list_len(lua_State *L) { list_T *l = luaV_unbox(L, luaV_List, 1); ! lua_pushinteger(L, (int) list_len(l)); return 1; } *************** *** 909,915 **** luaV_dict_len(lua_State *L) { dict_T *d = luaV_unbox(L, luaV_Dict, 1); ! lua_pushinteger(L, (d == NULL) ? 0 : (int) d->dv_hashtab.ht_used); return 1; } --- 925,931 ---- luaV_dict_len(lua_State *L) { dict_T *d = luaV_unbox(L, luaV_Dict, 1); ! lua_pushinteger(L, (int) dict_len(d)); return 1; } *************** *** 1029,1034 **** --- 1045,1168 ---- }; + /* ======= Blob type ======= */ + + static luaV_Blob * + luaV_newblob(lua_State *L, blob_T *blo) + { + luaV_Blob *b = (luaV_Blob *) lua_newuserdata(L, sizeof(luaV_Blob)); + *b = blo; + blo->bv_refcount++; /* reference in Lua */ + luaV_setudata(L, blo); /* cache[blo] = udata */ + luaV_getfield(L, LUAVIM_BLOB); + lua_setmetatable(L, -2); + return b; + } + + luaV_pushtype(blob_T, blob, luaV_Blob) + luaV_type_tostring(blob, LUAVIM_BLOB) + + static int + luaV_blob_gc(lua_State *L) + { + blob_T *b = luaV_unbox(L, luaV_Blob, 1); + blob_unref(b); + return 0; + } + + static int + luaV_blob_len(lua_State *L) + { + blob_T *b = luaV_unbox(L, luaV_Blob, 1); + lua_pushinteger(L, (int) blob_len(b)); + return 1; + } + + static int + luaV_blob_index(lua_State *L) + { + blob_T *b = luaV_unbox(L, luaV_Blob, 1); + if (lua_isnumber(L, 2)) + { + int idx = luaL_checkinteger(L, 2); + if (idx < blob_len(b)) + lua_pushnumber(L, (lua_Number) blob_get(b, idx)); + else + lua_pushnil(L); + } + else if (lua_isstring(L, 2)) + { + const char *s = lua_tostring(L, 2); + if (strncmp(s, "add", 3) == 0) + { + lua_getmetatable(L, 1); + lua_getfield(L, -1, s); + } + else + lua_pushnil(L); + } + else + lua_pushnil(L); + return 1; + } + + static int + luaV_blob_newindex(lua_State *L) + { + blob_T *b = luaV_unbox(L, luaV_Blob, 1); + if (b->bv_lock) + luaL_error(L, "blob is locked"); + if (lua_isnumber(L, 2)) + { + long len = blob_len(b); + int idx = luaL_checkinteger(L, 2); + int val = luaL_checkinteger(L, 3); + if (idx < len || (idx == len && ga_grow(&b->bv_ga, 1) == OK)) + { + blob_set(b, idx, (char_u) val); + if (idx == len) + ++b->bv_ga.ga_len; + } + else + luaL_error(L, "index out of range"); + } + return 0; + } + + static int + luaV_blob_add(lua_State *L) + { + luaV_Blob *blo = luaV_checkudata(L, 1, LUAVIM_BLOB); + blob_T *b = (blob_T *) luaV_checkcache(L, (void *) *blo); + if (b->bv_lock) + luaL_error(L, "blob is locked"); + lua_settop(L, 2); + if (!lua_isstring(L, 2)) + luaL_error(L, "string expected, got %s", luaL_typename(L, 2)); + else + { + size_t i, l = 0; + const char *s = lua_tolstring(L, 2, &l); + + ga_grow(&b->bv_ga, l); + for (i = 0; i < l; ++i) + ga_append(&b->bv_ga, s[i]); + } + lua_settop(L, 1); + return 1; + } + + static const luaL_Reg luaV_Blob_mt[] = { + {"__tostring", luaV_blob_tostring}, + {"__gc", luaV_blob_gc}, + {"__len", luaV_blob_len}, + {"__index", luaV_blob_index}, + {"__newindex", luaV_blob_newindex}, + {"add", luaV_blob_add}, + {NULL, NULL} + }; + + /* ======= Funcref type ======= */ static luaV_Funcref * *************** *** 1624,1629 **** --- 1758,1790 ---- } static int + luaV_blob(lua_State *L) + { + blob_T *b; + int initarg = !lua_isnoneornil(L, 1); + + if (initarg && !lua_isstring(L, 1)) + luaL_error(L, "string expected, got %s", luaL_typename(L, 1)); + b = blob_alloc(); + if (b == NULL) + lua_pushnil(L); + else + { + luaV_newblob(L, b); + if (initarg) + { + size_t i, l = 0; + const char *s = lua_tolstring(L, 1, &l); + + ga_grow(&b->bv_ga, l); + for (i = 0; i < l; ++i) + ga_append(&b->bv_ga, s[i]); + } + } + return 1; + } + + static int luaV_funcref(lua_State *L) { const char *name = luaL_checkstring(L, 1); *************** *** 1717,1722 **** --- 1878,1889 ---- lua_pushstring(L, "dict"); return 1; } + luaV_getfield(L, LUAVIM_BLOB); + if (lua_rawequal(L, -1, 2)) + { + lua_pushstring(L, "blob"); + return 1; + } luaV_getfield(L, LUAVIM_FUNCREF); if (lua_rawequal(L, -1, 2)) { *************** *** 1748,1753 **** --- 1915,1921 ---- {"line", luaV_line}, {"list", luaV_list}, {"dict", luaV_dict}, + {"blob", luaV_blob}, {"funcref", luaV_funcref}, {"buffer", luaV_buffer}, {"window", luaV_window}, *************** *** 1883,1888 **** --- 2051,2059 ---- luaV_newmetatable(L, LUAVIM_DICT); lua_pushvalue(L, 1); luaV_openlib(L, luaV_Dict_mt, 1); + luaV_newmetatable(L, LUAVIM_BLOB); + lua_pushvalue(L, 1); + luaV_openlib(L, luaV_Blob_mt, 1); luaV_newmetatable(L, LUAVIM_FUNCREF); lua_pushvalue(L, 1); luaV_openlib(L, luaV_Funcref_mt, 1); *** ../vim-8.1.1042/src/testdir/test_lua.vim 2019-03-19 21:59:16.268914799 +0100 --- src/testdir/test_lua.vim 2019-03-23 13:53:32.131159759 +0100 *************** *** 50,55 **** --- 50,60 ---- call assert_equal('dict', luaeval('vim.type(v)')) call assert_equal({'a':'b'}, luaeval('v')) + " lua.eval with a blob + lua v = vim.eval("0z00112233.deadbeef") + call assert_equal('blob', luaeval('vim.type(v)')) + call assert_equal(0z00112233.deadbeef, luaeval('v')) + call assert_fails('lua v = vim.eval(nil)', \ "[string \"vim chunk\"]:1: bad argument #1 to 'eval' (string expected, got nil)") call assert_fails('lua v = vim.eval(true)', *************** *** 428,433 **** --- 433,462 ---- lua str, d = nil endfunc + func Test_blob() + call assert_equal(0z, luaeval('vim.blob("")')) + call assert_equal(0z31326162, luaeval('vim.blob("12ab")')) + call assert_equal(0z00010203, luaeval('vim.blob("\x00\x01\x02\x03")')) + call assert_equal(0z8081FEFF, luaeval('vim.blob("\x80\x81\xfe\xff")')) + + lua b = vim.blob("\x00\x00\x00\x00") + call assert_equal(0z00000000, luaeval('b')) + call assert_equal(4.0, luaeval('#b')) + lua b[0], b[1], b[2], b[3] = 1, 32, 256, 0xff + call assert_equal(0z012000ff, luaeval('b')) + lua b[4] = string.byte("z", 1) + call assert_equal(0z012000ff.7a, luaeval('b')) + call assert_equal(5.0, luaeval('#b')) + call assert_fails('lua b[#b+1] = 0x80', '[string "vim chunk"]:1: index out of range') + lua b:add("12ab") + call assert_equal(0z012000ff.7a313261.62, luaeval('b')) + call assert_equal(9.0, luaeval('#b')) + call assert_fails('lua b:add(nil)', '[string "vim chunk"]:1: string expected, got nil') + call assert_fails('lua b:add(true)', '[string "vim chunk"]:1: string expected, got boolean') + call assert_fails('lua b:add({})', '[string "vim chunk"]:1: string expected, got table') + lua b = nil + endfunc + func Test_funcref() function I(x) return a:x *** ../vim-8.1.1042/src/version.c 2019-03-23 13:30:19.251356505 +0100 --- src/version.c 2019-03-23 13:54:37.582657565 +0100 *************** *** 777,778 **** --- 777,780 ---- { /* Add new patch number below this line */ + /**/ + 1043, /**/ -- To be rich is not the end, but only a change of worries. /// 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 ///