# HG changeset patch # User Bram Moolenaar # Date 1553346006 -3600 # Node ID a2f0e93a5857922288b26e938b260b62e6f66f40 # Parent 5ca428f99af9fc77aea03256d9f97ecd8102e800 patch 8.1.1043: Lua interface does not support Blob commit https://github.com/vim/vim/commit/b78286903300477bb8578a47b8170b4551e290c8 Author: Bram Moolenaar Date: Sat Mar 23 13:57:02 2019 +0100 patch 8.1.1043: Lua interface does not support Blob Problem: Lua interface does not support Blob. Solution: Add support to Blob. (Ozaki Kiichi, closes https://github.com/vim/vim/issues/4151) diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt --- a/runtime/doc/if_lua.txt +++ b/runtime/doc/if_lua.txt @@ -10,11 +10,12 @@ 1. Commands |lua-commands| 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| +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} @@ -141,6 +142,14 @@ Vim evaluation and command execution, an :" {'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(). @@ -260,7 +269,34 @@ Examples: < ============================================================================== -5. Funcref userdata *lua-funcref* +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 @@ -293,7 +329,7 @@ Examples: < ============================================================================== -6. Buffer userdata *lua-buffer* +7. Buffer userdata *lua-buffer* Buffer userdata represent vim buffers. A buffer userdata "b" has the following properties and methods: @@ -345,7 +381,7 @@ Examples: < ============================================================================== -7. Window userdata *lua-window* +8. Window userdata *lua-window* Window objects represent vim windows. A window userdata "w" has the following properties and methods: @@ -377,7 +413,7 @@ Examples: < ============================================================================== -8. The luaeval function *lua-luaeval* *lua-eval* +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 @@ -390,10 +426,10 @@ returns the result of the expression. It 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. +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: > @@ -408,7 +444,7 @@ Examples: > ============================================================================== -9. Dynamic loading *lua-dynamic* +10. Dynamic loading *lua-dynamic* On MS-Windows and Unix the Lua library can be loaded dynamically. The |:version| output then includes |+lua/dyn|. diff --git a/src/if_lua.c b/src/if_lua.c --- a/src/if_lua.c +++ b/src/if_lua.c @@ -28,6 +28,7 @@ typedef buf_T *luaV_Buffer; 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,6 +37,7 @@ typedef void (*msgfunc_T)(char_u *); 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,6 +70,7 @@ static const char LUAVIM_SETREF[] = "lua 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,6 +544,9 @@ luaV_pushtypval(lua_State *L, typval_T * 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,43 +588,53 @@ luaV_totypval(lua_State *L, int pos, typ { void *p = lua_touserdata(L, pos); - if (lua_getmetatable(L, pos)) /* has metatable? */ + if (lua_getmetatable(L, pos)) // has metatable? { - /* check list */ + // 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 */ + lua_pop(L, 2); // MTs break; } - /* check dict */ + // 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 */ + lua_pop(L, 3); // MTs break; } - /* check funcref */ + // 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, -4)) + 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, 4); /* MTs */ + lua_pop(L, 5); // MTs break; } - lua_pop(L, 4); /* MTs */ + lua_pop(L, 4); // MTs } } - /* FALLTHROUGH */ + // FALLTHROUGH default: tv->v_type = VAR_NUMBER; tv->vval.v_number = 0; @@ -753,7 +769,7 @@ luaV_type_tostring(list, LUAVIM_LIST) 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); + lua_pushinteger(L, (int) list_len(l)); return 1; } @@ -909,7 +925,7 @@ luaV_type_tostring(dict, LUAVIM_DICT) 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); + lua_pushinteger(L, (int) dict_len(d)); return 1; } @@ -1029,6 +1045,124 @@ static const luaL_Reg luaV_Dict_mt[] = { }; +/* ======= 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,6 +1758,33 @@ luaV_dict(lua_State *L) } 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,6 +1878,12 @@ luaV_type(lua_State *L) 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,6 +1915,7 @@ static const luaL_Reg luaV_module[] = { {"line", luaV_line}, {"list", luaV_list}, {"dict", luaV_dict}, + {"blob", luaV_blob}, {"funcref", luaV_funcref}, {"buffer", luaV_buffer}, {"window", luaV_window}, @@ -1883,6 +2051,9 @@ luaopen_vim(lua_State *L) 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); diff --git a/src/testdir/test_lua.vim b/src/testdir/test_lua.vim --- a/src/testdir/test_lua.vim +++ b/src/testdir/test_lua.vim @@ -50,6 +50,11 @@ func Test_eval() 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,6 +433,30 @@ func Test_dict_iter() 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 diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -776,6 +776,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1043, +/**/ 1042, /**/ 1041,