mirror of
https://github.com/neovim/neovim.git
synced 2025-09-14 07:18:17 +00:00
viml/executor: Directly generate typval_T values
Note: this will *still* crash when using API in cases similar to the one described in first commit. Just it needs different code to reproduce.
This commit is contained in:
@@ -6480,28 +6480,44 @@ static void dict_free_dict(dict_T *d) {
|
|||||||
xfree(d);
|
xfree(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dict_free(dict_T *d) {
|
void dict_free(dict_T *d)
|
||||||
|
{
|
||||||
if (!in_free_unref_items) {
|
if (!in_free_unref_items) {
|
||||||
dict_free_contents(d);
|
dict_free_contents(d);
|
||||||
dict_free_dict(d);
|
dict_free_dict(d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// Allocate a dictionary item
|
||||||
* Allocate a Dictionary item.
|
///
|
||||||
* The "key" is copied to the new item.
|
/// @note that the value of the item di_tv still needs to be initialized.
|
||||||
* Note that the value of the item "di_tv" still needs to be initialized!
|
///
|
||||||
*/
|
/// @param[in] key Item key.
|
||||||
dictitem_T *dictitem_alloc(char_u *key) FUNC_ATTR_NONNULL_RET
|
/// @param[in] len Key length.
|
||||||
|
///
|
||||||
|
/// @return [allocated] New dictionary item.
|
||||||
|
dictitem_T *dictitem_alloc_len(const char *const key, const size_t len)
|
||||||
|
FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
|
||||||
{
|
{
|
||||||
dictitem_T *di = xmalloc(offsetof(dictitem_T, di_key) + STRLEN(key) + 1);
|
dictitem_T *const di = xmallocz(offsetof(dictitem_T, di_key) + len);
|
||||||
#ifndef __clang_analyzer__
|
memcpy(di->di_key, key, len);
|
||||||
STRCPY(di->di_key, key);
|
|
||||||
#endif
|
|
||||||
di->di_flags = DI_FLAGS_ALLOC;
|
di->di_flags = DI_FLAGS_ALLOC;
|
||||||
return di;
|
return di;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Allocate a dictionary item
|
||||||
|
///
|
||||||
|
/// @note that the value of the item di_tv still needs to be initialized.
|
||||||
|
///
|
||||||
|
/// @param[in] key Item key, NUL-terminated string.
|
||||||
|
///
|
||||||
|
/// @return [allocated] New dictionary item.
|
||||||
|
dictitem_T *dictitem_alloc(const char_u *const key)
|
||||||
|
FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
|
||||||
|
{
|
||||||
|
return dictitem_alloc_len((const char *)key, STRLEN(key));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make a copy of a Dictionary item.
|
* Make a copy of a Dictionary item.
|
||||||
*/
|
*/
|
||||||
@@ -13386,37 +13402,7 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object arg;
|
executor_eval_lua(cstr_as_string(str), &argvars[1], rettv);
|
||||||
if (argvars[1].v_type == VAR_UNKNOWN) {
|
|
||||||
arg = NIL;
|
|
||||||
} else {
|
|
||||||
arg = vim_to_object(&argvars[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(ZyX-I): Create function which converts lua objects directly to VimL
|
|
||||||
// objects, not to API objects.
|
|
||||||
Error err;
|
|
||||||
String err_str;
|
|
||||||
Object ret = executor_eval_lua(cstr_as_string(str), arg, &err, &err_str);
|
|
||||||
if (err.set) {
|
|
||||||
if (err_str.size) {
|
|
||||||
EMSG3(_("E971: Failed to eval lua string: %s (%s)"), err.msg,
|
|
||||||
err_str.data);
|
|
||||||
} else {
|
|
||||||
EMSG2(_("E971: Failed to eval lua string: %s"), err.msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
api_free_string(err_str);
|
|
||||||
|
|
||||||
if (!err.set) {
|
|
||||||
if (!object_to_vim(ret, rettv, &err)) {
|
|
||||||
EMSG2(_("E972: Failed to convert resulting API object to VimL: %s"),
|
|
||||||
err.msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
api_free_object(ret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@@ -7,6 +7,7 @@
|
|||||||
#include "nvim/eval/encode.h"
|
#include "nvim/eval/encode.h"
|
||||||
#include "nvim/ascii.h"
|
#include "nvim/ascii.h"
|
||||||
#include "nvim/message.h"
|
#include "nvim/message.h"
|
||||||
|
#include "nvim/globals.h"
|
||||||
#include "nvim/charset.h" // vim_str2nr
|
#include "nvim/charset.h" // vim_str2nr
|
||||||
#include "nvim/lib/kvec.h"
|
#include "nvim/lib/kvec.h"
|
||||||
#include "nvim/vim.h" // OK, FAIL
|
#include "nvim/vim.h" // OK, FAIL
|
||||||
@@ -218,6 +219,69 @@ static inline int json_decoder_pop(ValuesStackItem obj,
|
|||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
/// Create a new special dictionary that ought to represent a MAP
|
||||||
|
///
|
||||||
|
/// @param[out] ret_tv Address where new special dictionary is saved.
|
||||||
|
///
|
||||||
|
/// @return [allocated] list which should contain key-value pairs. Return value
|
||||||
|
/// may be safely ignored.
|
||||||
|
list_T *decode_create_map_special_dict(typval_T *const ret_tv)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
list_T *const list = list_alloc();
|
||||||
|
list->lv_refcount++;
|
||||||
|
create_special_dict(ret_tv, kMPMap, ((typval_T) {
|
||||||
|
.v_type = VAR_LIST,
|
||||||
|
.v_lock = VAR_UNLOCKED,
|
||||||
|
.vval = { .v_list = list },
|
||||||
|
}));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert char* string to typval_T
|
||||||
|
///
|
||||||
|
/// Depending on whether string has (no) NUL bytes, it may use a special
|
||||||
|
/// dictionary or decode string to VAR_STRING.
|
||||||
|
///
|
||||||
|
/// @param[in] s String to decode.
|
||||||
|
/// @param[in] len String length.
|
||||||
|
/// @param[in] hasnul Whether string has NUL byte, not or it was not yet
|
||||||
|
/// determined.
|
||||||
|
/// @param[in] binary If true, save special string type as kMPBinary,
|
||||||
|
/// otherwise kMPString.
|
||||||
|
///
|
||||||
|
/// @return Decoded string.
|
||||||
|
typval_T decode_string(const char *const s, const size_t len,
|
||||||
|
const TriState hasnul, const bool binary)
|
||||||
|
FUNC_ATTR_WARN_UNUSED_RESULT
|
||||||
|
{
|
||||||
|
assert(s != NULL || len == 0);
|
||||||
|
const bool really_hasnul = (hasnul == kNone
|
||||||
|
? memchr(s, NUL, len) != NULL
|
||||||
|
: (bool)hasnul);
|
||||||
|
if (really_hasnul) {
|
||||||
|
list_T *const list = list_alloc();
|
||||||
|
list->lv_refcount++;
|
||||||
|
typval_T tv;
|
||||||
|
create_special_dict(&tv, binary ? kMPBinary : kMPString, ((typval_T) {
|
||||||
|
.v_type = VAR_LIST,
|
||||||
|
.v_lock = VAR_UNLOCKED,
|
||||||
|
.vval = { .v_list = list },
|
||||||
|
}));
|
||||||
|
if (encode_list_write((void *)list, s, len) == -1) {
|
||||||
|
clear_tv(&tv);
|
||||||
|
return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED };
|
||||||
|
}
|
||||||
|
return tv;
|
||||||
|
} else {
|
||||||
|
return (typval_T) {
|
||||||
|
.v_type = VAR_STRING,
|
||||||
|
.v_lock = VAR_UNLOCKED,
|
||||||
|
.vval = { .v_string = xmemdupz(s, len) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse JSON double-quoted string
|
/// Parse JSON double-quoted string
|
||||||
///
|
///
|
||||||
/// @param[in] conv Defines conversion necessary to convert UTF-8 string to
|
/// @param[in] conv Defines conversion necessary to convert UTF-8 string to
|
||||||
@@ -428,29 +492,13 @@ static inline int parse_json_string(vimconv_T *const conv,
|
|||||||
str = new_str;
|
str = new_str;
|
||||||
str_end = new_str + str_len;
|
str_end = new_str + str_len;
|
||||||
}
|
}
|
||||||
if (hasnul) {
|
|
||||||
typval_T obj;
|
typval_T obj;
|
||||||
list_T *const list = list_alloc();
|
obj = decode_string(str, (size_t)(str_end - str), hasnul ? kTrue : kFalse,
|
||||||
list->lv_refcount++;
|
false);
|
||||||
create_special_dict(&obj, kMPString, ((typval_T) {
|
if (obj.v_type == VAR_UNKNOWN) {
|
||||||
.v_type = VAR_LIST,
|
|
||||||
.v_lock = VAR_UNLOCKED,
|
|
||||||
.vval = { .v_list = list },
|
|
||||||
}));
|
|
||||||
if (encode_list_write((void *) list, str, (size_t) (str_end - str))
|
|
||||||
== -1) {
|
|
||||||
clear_tv(&obj);
|
|
||||||
goto parse_json_string_fail;
|
goto parse_json_string_fail;
|
||||||
}
|
}
|
||||||
xfree(str);
|
POP(obj, obj.v_type != VAR_STRING);
|
||||||
POP(obj, true);
|
|
||||||
} else {
|
|
||||||
*str_end = NUL;
|
|
||||||
POP(((typval_T) {
|
|
||||||
.v_type = VAR_STRING,
|
|
||||||
.vval = { .v_string = (char_u *) str },
|
|
||||||
}), false);
|
|
||||||
}
|
|
||||||
goto parse_json_string_ret;
|
goto parse_json_string_ret;
|
||||||
parse_json_string_fail:
|
parse_json_string_fail:
|
||||||
ret = FAIL;
|
ret = FAIL;
|
||||||
@@ -827,13 +875,7 @@ json_decode_string_cycle_start:
|
|||||||
list_T *val_list = NULL;
|
list_T *val_list = NULL;
|
||||||
if (next_map_special) {
|
if (next_map_special) {
|
||||||
next_map_special = false;
|
next_map_special = false;
|
||||||
val_list = list_alloc();
|
val_list = decode_create_map_special_dict(&tv);
|
||||||
val_list->lv_refcount++;
|
|
||||||
create_special_dict(&tv, kMPMap, ((typval_T) {
|
|
||||||
.v_type = VAR_LIST,
|
|
||||||
.v_lock = VAR_UNLOCKED,
|
|
||||||
.vval = { .v_list = val_list },
|
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
dict_T *dict = dict_alloc();
|
dict_T *dict = dict_alloc();
|
||||||
dict->dv_refcount++;
|
dict->dv_refcount++;
|
||||||
@@ -980,37 +1022,15 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MSGPACK_OBJECT_STR: {
|
case MSGPACK_OBJECT_STR: {
|
||||||
list_T *const list = list_alloc();
|
*rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kTrue, false);
|
||||||
list->lv_refcount++;
|
if (rettv->v_type == VAR_UNKNOWN) {
|
||||||
create_special_dict(rettv, kMPString, ((typval_T) {
|
|
||||||
.v_type = VAR_LIST,
|
|
||||||
.v_lock = VAR_UNLOCKED,
|
|
||||||
.vval = { .v_list = list },
|
|
||||||
}));
|
|
||||||
if (encode_list_write((void *) list, mobj.via.str.ptr, mobj.via.str.size)
|
|
||||||
== -1) {
|
|
||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MSGPACK_OBJECT_BIN: {
|
case MSGPACK_OBJECT_BIN: {
|
||||||
if (memchr(mobj.via.bin.ptr, NUL, mobj.via.bin.size) == NULL) {
|
*rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kNone, true);
|
||||||
*rettv = (typval_T) {
|
if (rettv->v_type == VAR_UNKNOWN) {
|
||||||
.v_type = VAR_STRING,
|
|
||||||
.v_lock = VAR_UNLOCKED,
|
|
||||||
.vval = { .v_string = xmemdupz(mobj.via.bin.ptr, mobj.via.bin.size) },
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
list_T *const list = list_alloc();
|
|
||||||
list->lv_refcount++;
|
|
||||||
create_special_dict(rettv, kMPBinary, ((typval_T) {
|
|
||||||
.v_type = VAR_LIST,
|
|
||||||
.v_lock = VAR_UNLOCKED,
|
|
||||||
.vval = { .v_list = list },
|
|
||||||
}));
|
|
||||||
if (encode_list_write((void *) list, mobj.via.bin.ptr, mobj.via.bin.size)
|
|
||||||
== -1) {
|
|
||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1067,13 +1087,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
msgpack_to_vim_generic_map: {}
|
msgpack_to_vim_generic_map: {}
|
||||||
list_T *const list = list_alloc();
|
list_T *const list = decode_create_map_special_dict(rettv);
|
||||||
list->lv_refcount++;
|
|
||||||
create_special_dict(rettv, kMPMap, ((typval_T) {
|
|
||||||
.v_type = VAR_LIST,
|
|
||||||
.v_lock = VAR_UNLOCKED,
|
|
||||||
.vval = { .v_list = list },
|
|
||||||
}));
|
|
||||||
for (size_t i = 0; i < mobj.via.map.size; i++) {
|
for (size_t i = 0; i < mobj.via.map.size; i++) {
|
||||||
list_T *const kv_pair = list_alloc();
|
list_T *const kv_pair = list_alloc();
|
||||||
list_append_list(list, kv_pair);
|
list_append_list(list, kv_pair);
|
||||||
|
@@ -2,12 +2,24 @@
|
|||||||
#include <lualib.h>
|
#include <lualib.h>
|
||||||
#include <lauxlib.h>
|
#include <lauxlib.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "nvim/api/private/defs.h"
|
#include "nvim/api/private/defs.h"
|
||||||
#include "nvim/api/private/helpers.h"
|
#include "nvim/api/private/helpers.h"
|
||||||
#include "nvim/func_attr.h"
|
#include "nvim/func_attr.h"
|
||||||
#include "nvim/memory.h"
|
#include "nvim/memory.h"
|
||||||
#include "nvim/assert.h"
|
#include "nvim/assert.h"
|
||||||
|
// FIXME: vim.h is not actually needed, but otherwise it states MAXPATHL is
|
||||||
|
// redefined
|
||||||
|
#include "nvim/vim.h"
|
||||||
|
#include "nvim/globals.h"
|
||||||
|
#include "nvim/message.h"
|
||||||
|
#include "nvim/eval_defs.h"
|
||||||
|
#include "nvim/ascii.h"
|
||||||
|
|
||||||
|
#include "nvim/lib/kvec.h"
|
||||||
|
#include "nvim/eval/decode.h"
|
||||||
|
|
||||||
#include "nvim/viml/executor/converter.h"
|
#include "nvim/viml/executor/converter.h"
|
||||||
#include "nvim/viml/executor/executor.h"
|
#include "nvim/viml/executor/executor.h"
|
||||||
@@ -16,6 +28,382 @@
|
|||||||
# include "viml/executor/converter.c.generated.h"
|
# include "viml/executor/converter.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// Helper structure for nlua_pop_typval
|
||||||
|
typedef struct {
|
||||||
|
typval_T *tv; ///< Location where conversion result is saved.
|
||||||
|
bool container; ///< True if tv is a container.
|
||||||
|
bool special; ///< If true then tv is a _VAL part of special dictionary
|
||||||
|
///< that represents mapping.
|
||||||
|
} PopStackItem;
|
||||||
|
|
||||||
|
/// Convert lua object to VimL typval_T
|
||||||
|
///
|
||||||
|
/// Should pop exactly one value from lua stack.
|
||||||
|
///
|
||||||
|
/// @param lstate Lua state.
|
||||||
|
/// @param[out] ret_tv Where to put the result.
|
||||||
|
///
|
||||||
|
/// @return `true` in case of success, `false` in case of failure. Error is
|
||||||
|
/// reported automatically.
|
||||||
|
bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
|
||||||
|
{
|
||||||
|
bool ret = true;
|
||||||
|
#ifndef NDEBUG
|
||||||
|
const int initial_size = lua_gettop(lstate);
|
||||||
|
#endif
|
||||||
|
kvec_t(PopStackItem) stack = KV_INITIAL_VALUE;
|
||||||
|
kv_push(stack, ((PopStackItem) { ret_tv, false, false }));
|
||||||
|
while (ret && kv_size(stack)) {
|
||||||
|
if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) {
|
||||||
|
emsgf(_("E1502: Lua failed to grow stack to %i"), lua_gettop(lstate) + 3);
|
||||||
|
ret = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
PopStackItem cur = kv_pop(stack);
|
||||||
|
if (cur.container) {
|
||||||
|
if (cur.special || cur.tv->v_type == VAR_DICT) {
|
||||||
|
assert(cur.tv->v_type == (cur.special ? VAR_LIST : VAR_DICT));
|
||||||
|
if (lua_next(lstate, -2)) {
|
||||||
|
assert(lua_type(lstate, -2) == LUA_TSTRING);
|
||||||
|
size_t len;
|
||||||
|
const char *s = lua_tolstring(lstate, -2, &len);
|
||||||
|
if (cur.special) {
|
||||||
|
list_T *const kv_pair = list_alloc();
|
||||||
|
list_append_list(cur.tv->vval.v_list, kv_pair);
|
||||||
|
listitem_T *const key = listitem_alloc();
|
||||||
|
key->li_tv = decode_string(s, len, kTrue, false);
|
||||||
|
list_append(kv_pair, key);
|
||||||
|
if (key->li_tv.v_type == VAR_UNKNOWN) {
|
||||||
|
ret = false;
|
||||||
|
list_unref(kv_pair);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
listitem_T *const val = listitem_alloc();
|
||||||
|
list_append(kv_pair, val);
|
||||||
|
kv_push(stack, cur);
|
||||||
|
cur = (PopStackItem) { &val->li_tv, false, false };
|
||||||
|
} else {
|
||||||
|
dictitem_T *const di = dictitem_alloc_len(s, len);
|
||||||
|
if (dict_add(cur.tv->vval.v_dict, di) == FAIL) {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
kv_push(stack, cur);
|
||||||
|
cur = (PopStackItem) { &di->di_tv, false, false };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lua_pop(lstate, 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert(cur.tv->v_type == VAR_LIST);
|
||||||
|
lua_rawgeti(lstate, -1, cur.tv->vval.v_list->lv_len + 1);
|
||||||
|
if (lua_isnil(lstate, -1)) {
|
||||||
|
lua_pop(lstate, 1);
|
||||||
|
lua_pop(lstate, 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
listitem_T *li = listitem_alloc();
|
||||||
|
list_append(cur.tv->vval.v_list, li);
|
||||||
|
kv_push(stack, cur);
|
||||||
|
cur = (PopStackItem) { &li->li_tv, false, false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(!cur.container);
|
||||||
|
memset(cur.tv, 0, sizeof(*cur.tv));
|
||||||
|
switch (lua_type(lstate, -1)) {
|
||||||
|
case LUA_TNIL: {
|
||||||
|
cur.tv->v_type = VAR_SPECIAL;
|
||||||
|
cur.tv->vval.v_special = kSpecialVarNull;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LUA_TBOOLEAN: {
|
||||||
|
cur.tv->v_type = VAR_SPECIAL;
|
||||||
|
cur.tv->vval.v_special = (lua_toboolean(lstate, -1)
|
||||||
|
? kSpecialVarTrue
|
||||||
|
: kSpecialVarFalse);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LUA_TSTRING: {
|
||||||
|
size_t len;
|
||||||
|
const char *s = lua_tolstring(lstate, -1, &len);
|
||||||
|
*cur.tv = decode_string(s, len, kNone, true);
|
||||||
|
if (cur.tv->v_type == VAR_UNKNOWN) {
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LUA_TNUMBER: {
|
||||||
|
const lua_Number n = lua_tonumber(lstate, -1);
|
||||||
|
if (n > (lua_Number)VARNUMBER_MAX || n < (lua_Number)VARNUMBER_MIN
|
||||||
|
|| ((lua_Number)((varnumber_T)n)) != n) {
|
||||||
|
cur.tv->v_type = VAR_FLOAT;
|
||||||
|
cur.tv->vval.v_float = (float_T)n;
|
||||||
|
} else {
|
||||||
|
cur.tv->v_type = VAR_NUMBER;
|
||||||
|
cur.tv->vval.v_number = (varnumber_T)n;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LUA_TTABLE: {
|
||||||
|
bool has_string = false;
|
||||||
|
bool has_string_with_nul = false;
|
||||||
|
bool has_other = false;
|
||||||
|
size_t maxidx = 0;
|
||||||
|
size_t tsize = 0;
|
||||||
|
lua_pushnil(lstate);
|
||||||
|
while (lua_next(lstate, -2)) {
|
||||||
|
switch (lua_type(lstate, -2)) {
|
||||||
|
case LUA_TSTRING: {
|
||||||
|
size_t len;
|
||||||
|
const char *s = lua_tolstring(lstate, -2, &len);
|
||||||
|
if (memchr(s, NUL, len) != NULL) {
|
||||||
|
has_string_with_nul = true;
|
||||||
|
}
|
||||||
|
has_string = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LUA_TNUMBER: {
|
||||||
|
const lua_Number n = lua_tonumber(lstate, -2);
|
||||||
|
if (n > (lua_Number)SIZE_MAX || n <= 0
|
||||||
|
|| ((lua_Number)((size_t)n)) != n) {
|
||||||
|
has_other = true;
|
||||||
|
} else {
|
||||||
|
const size_t idx = (size_t)n;
|
||||||
|
if (idx > maxidx) {
|
||||||
|
maxidx = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
has_other = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tsize++;
|
||||||
|
lua_pop(lstate, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tsize == 0) {
|
||||||
|
// Assuming empty list
|
||||||
|
cur.tv->v_type = VAR_LIST;
|
||||||
|
cur.tv->vval.v_list = list_alloc();
|
||||||
|
cur.tv->vval.v_list->lv_refcount++;
|
||||||
|
} else if (tsize == maxidx && !has_other && !has_string) {
|
||||||
|
// Assuming array
|
||||||
|
cur.tv->v_type = VAR_LIST;
|
||||||
|
cur.tv->vval.v_list = list_alloc();
|
||||||
|
cur.tv->vval.v_list->lv_refcount++;
|
||||||
|
cur.container = true;
|
||||||
|
kv_push(stack, cur);
|
||||||
|
} else if (has_string && !has_other && maxidx == 0) {
|
||||||
|
// Assuming dictionary
|
||||||
|
cur.special = has_string_with_nul;
|
||||||
|
if (has_string_with_nul) {
|
||||||
|
decode_create_map_special_dict(cur.tv);
|
||||||
|
assert(cur.tv->v_type = VAR_DICT);
|
||||||
|
dictitem_T *const val_di = dict_find(cur.tv->vval.v_dict,
|
||||||
|
(char_u *)"_VAL", 4);
|
||||||
|
assert(val_di != NULL);
|
||||||
|
cur.tv = &val_di->di_tv;
|
||||||
|
assert(cur.tv->v_type == VAR_LIST);
|
||||||
|
} else {
|
||||||
|
cur.tv->v_type = VAR_DICT;
|
||||||
|
cur.tv->vval.v_dict = dict_alloc();
|
||||||
|
cur.tv->vval.v_dict->dv_refcount++;
|
||||||
|
}
|
||||||
|
cur.container = true;
|
||||||
|
kv_push(stack, cur);
|
||||||
|
lua_pushnil(lstate);
|
||||||
|
} else {
|
||||||
|
EMSG(_("E5100: Cannot convert given lua table: table "
|
||||||
|
"should either have a sequence of positive integer keys "
|
||||||
|
"or contain only string keys"));
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
EMSG(_("E5101: Cannot convert given lua type"));
|
||||||
|
ret = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!cur.container) {
|
||||||
|
lua_pop(lstate, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kv_destroy(stack);
|
||||||
|
if (!ret) {
|
||||||
|
clear_tv(ret_tv);
|
||||||
|
memset(ret_tv, 0, sizeof(*ret_tv));
|
||||||
|
lua_pop(lstate, lua_gettop(lstate) - initial_size + 1);
|
||||||
|
}
|
||||||
|
assert(lua_gettop(lstate) == initial_size - 1);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_ALLOW_SPECIALS true
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_NIL(tv) \
|
||||||
|
lua_pushnil(lstate)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
|
||||||
|
lua_pushboolean(lstate, (bool)(num))
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
|
||||||
|
lua_pushnumber(lstate, (lua_Number)(num))
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \
|
||||||
|
TYPVAL_ENCODE_CONV_NUMBER(tv, flt)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \
|
||||||
|
lua_pushlstring(lstate, (const char *)(str), (len))
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \
|
||||||
|
TYPVAL_ENCODE_CONV_NIL()
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
|
||||||
|
do { \
|
||||||
|
TYPVAL_ENCODE_CONV_NIL(tv); \
|
||||||
|
goto typval_encode_stop_converting_one_item; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len)
|
||||||
|
#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len)
|
||||||
|
#define TYPVAL_ENCODE_CONV_FUNC_END(tv)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \
|
||||||
|
lua_createtable(lstate, 0, 0)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \
|
||||||
|
TYPVAL_ENCODE_CONV_EMPTY_LIST()
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \
|
||||||
|
do { \
|
||||||
|
if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { \
|
||||||
|
emsgf(_("E5102: Lua failed to grow stack to %i"), \
|
||||||
|
lua_gettop(lstate) + 3); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
lua_createtable(lstate, (int)(len), 0); \
|
||||||
|
lua_pushnumber(lstate, 1); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \
|
||||||
|
do { \
|
||||||
|
lua_Number idx = lua_tonumber(lstate, -2); \
|
||||||
|
lua_rawset(lstate, -3); \
|
||||||
|
lua_pushnumber(lstate, idx + 1); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_LIST_END(tv) \
|
||||||
|
lua_rawset(lstate, -3)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \
|
||||||
|
do { \
|
||||||
|
if (!lua_checkstack(lstate, lua_gettop(lstate) + 3)) { \
|
||||||
|
emsgf(_("E5102: Lua failed to grow stack to %i"), \
|
||||||
|
lua_gettop(lstate) + 3); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
lua_createtable(lstate, 0, (int)(len)); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, kv_pair)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \
|
||||||
|
lua_rawset(lstate, -3)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \
|
||||||
|
TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \
|
||||||
|
do { \
|
||||||
|
for (size_t backref = kv_size(*mpstack); backref; backref--) { \
|
||||||
|
const MPConvStackVal mpval = kv_A(*mpstack, backref - 1); \
|
||||||
|
if (mpval.type == conv_type) { \
|
||||||
|
if (conv_type == kMPConvDict \
|
||||||
|
? (void *) mpval.data.d.dict == (void *) (val) \
|
||||||
|
: (void *) mpval.data.l.list == (void *) (val)) { \
|
||||||
|
lua_pushvalue(lstate, \
|
||||||
|
1 - ((int)((kv_size(*mpstack) - backref + 1) * 2))); \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define TYPVAL_ENCODE_SCOPE static
|
||||||
|
#define TYPVAL_ENCODE_NAME lua
|
||||||
|
#define TYPVAL_ENCODE_FIRST_ARG_TYPE lua_State *const
|
||||||
|
#define TYPVAL_ENCODE_FIRST_ARG_NAME lstate
|
||||||
|
#include "nvim/eval/typval_encode.c.h"
|
||||||
|
#undef TYPVAL_ENCODE_SCOPE
|
||||||
|
#undef TYPVAL_ENCODE_NAME
|
||||||
|
#undef TYPVAL_ENCODE_FIRST_ARG_TYPE
|
||||||
|
#undef TYPVAL_ENCODE_FIRST_ARG_NAME
|
||||||
|
|
||||||
|
#undef TYPVAL_ENCODE_CONV_STRING
|
||||||
|
#undef TYPVAL_ENCODE_CONV_STR_STRING
|
||||||
|
#undef TYPVAL_ENCODE_CONV_EXT_STRING
|
||||||
|
#undef TYPVAL_ENCODE_CONV_NUMBER
|
||||||
|
#undef TYPVAL_ENCODE_CONV_FLOAT
|
||||||
|
#undef TYPVAL_ENCODE_CONV_FUNC_START
|
||||||
|
#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS
|
||||||
|
#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF
|
||||||
|
#undef TYPVAL_ENCODE_CONV_FUNC_END
|
||||||
|
#undef TYPVAL_ENCODE_CONV_EMPTY_LIST
|
||||||
|
#undef TYPVAL_ENCODE_CONV_LIST_START
|
||||||
|
#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START
|
||||||
|
#undef TYPVAL_ENCODE_CONV_EMPTY_DICT
|
||||||
|
#undef TYPVAL_ENCODE_CONV_NIL
|
||||||
|
#undef TYPVAL_ENCODE_CONV_BOOL
|
||||||
|
#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
|
||||||
|
#undef TYPVAL_ENCODE_CONV_DICT_START
|
||||||
|
#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START
|
||||||
|
#undef TYPVAL_ENCODE_CONV_DICT_END
|
||||||
|
#undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY
|
||||||
|
#undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS
|
||||||
|
#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK
|
||||||
|
#undef TYPVAL_ENCODE_CONV_LIST_END
|
||||||
|
#undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS
|
||||||
|
#undef TYPVAL_ENCODE_CONV_RECURSE
|
||||||
|
#undef TYPVAL_ENCODE_ALLOW_SPECIALS
|
||||||
|
|
||||||
|
/// Convert VimL typval_T to lua value
|
||||||
|
///
|
||||||
|
/// Should leave single value in lua stack. May only fail if lua failed to grow
|
||||||
|
/// stack.
|
||||||
|
///
|
||||||
|
/// @param lstate Lua interpreter state.
|
||||||
|
/// @param[in] tv typval_T to convert.
|
||||||
|
///
|
||||||
|
/// @return true in case of success, false otherwise.
|
||||||
|
bool nlua_push_typval(lua_State *lstate, typval_T *const tv)
|
||||||
|
{
|
||||||
|
const int initial_size = lua_gettop(lstate);
|
||||||
|
if (!lua_checkstack(lstate, initial_size + 1)) {
|
||||||
|
emsgf(_("E1502: Lua failed to grow stack to %i"), initial_size + 4);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (encode_vim_to_lua(lstate, tv, "nlua_push_typval argument") == FAIL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
assert(lua_gettop(lstate) == initial_size + 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
#define NLUA_PUSH_IDX(lstate, type, idx) \
|
#define NLUA_PUSH_IDX(lstate, type, idx) \
|
||||||
do { \
|
do { \
|
||||||
STATIC_ASSERT(sizeof(type) <= sizeof(lua_Number), \
|
STATIC_ASSERT(sizeof(type) <= sizeof(lua_Number), \
|
||||||
|
@@ -3,8 +3,11 @@
|
|||||||
|
|
||||||
#include <lua.h>
|
#include <lua.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "nvim/api/private/defs.h"
|
#include "nvim/api/private/defs.h"
|
||||||
#include "nvim/func_attr.h"
|
#include "nvim/func_attr.h"
|
||||||
|
#include "nvim/eval.h"
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "viml/executor/converter.h.generated.h"
|
# include "viml/executor/converter.h.generated.h"
|
||||||
|
@@ -36,7 +36,19 @@ typedef struct {
|
|||||||
lua_pushcfunction(lstate, &function); \
|
lua_pushcfunction(lstate, &function); \
|
||||||
lua_call(lstate, 0, numret); \
|
lua_call(lstate, 0, numret); \
|
||||||
} while (0)
|
} while (0)
|
||||||
/// Call C function which expects four arguments
|
/// Call C function which expects two arguments
|
||||||
|
///
|
||||||
|
/// @param function Called function
|
||||||
|
/// @param numret Number of returned arguments
|
||||||
|
/// @param a… Supplied argument (should be a void* pointer)
|
||||||
|
#define NLUA_CALL_C_FUNCTION_2(lstate, function, numret, a1, a2) \
|
||||||
|
do { \
|
||||||
|
lua_pushcfunction(lstate, &function); \
|
||||||
|
lua_pushlightuserdata(lstate, a1); \
|
||||||
|
lua_pushlightuserdata(lstate, a2); \
|
||||||
|
lua_call(lstate, 2, numret); \
|
||||||
|
} while (0)
|
||||||
|
/// Call C function which expects three arguments
|
||||||
///
|
///
|
||||||
/// @param function Called function
|
/// @param function Called function
|
||||||
/// @param numret Number of returned arguments
|
/// @param numret Number of returned arguments
|
||||||
@@ -64,15 +76,19 @@ typedef struct {
|
|||||||
lua_call(lstate, 4, numret); \
|
lua_call(lstate, 4, numret); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
static void set_lua_error(lua_State *lstate, LuaError *lerr)
|
/// Convert lua error into a Vim error message
|
||||||
|
///
|
||||||
|
/// @param lstate Lua interpreter state.
|
||||||
|
/// @param[in] msg Message base, must contain one `%s`.
|
||||||
|
static void nlua_error(lua_State *const lstate, const char *const msg)
|
||||||
FUNC_ATTR_NONNULL_ALL
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
const char *const str = lua_tolstring(lstate, -1, &lerr->lua_err_str.size);
|
size_t len;
|
||||||
lerr->lua_err_str.data = xmemdupz(str, lerr->lua_err_str.size);
|
const char *const str = lua_tolstring(lstate, -1, &len);
|
||||||
lua_pop(lstate, 1);
|
|
||||||
|
|
||||||
// FIXME? More specific error?
|
EMSG2(msg, str);
|
||||||
set_api_error("Error while executing lua code", &lerr->err);
|
|
||||||
|
lua_pop(lstate, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compare two strings, ignoring case
|
/// Compare two strings, ignoring case
|
||||||
@@ -94,25 +110,26 @@ static int nlua_stricmp(lua_State *lstate) FUNC_ATTR_NONNULL_ALL
|
|||||||
|
|
||||||
/// Evaluate lua string
|
/// Evaluate lua string
|
||||||
///
|
///
|
||||||
/// Expects three values on the stack: string to evaluate, pointer to the
|
/// Expects two values on the stack: string to evaluate, pointer to the
|
||||||
/// location where result is saved, pointer to the location where error is
|
/// location where result is saved. Always returns nothing (from the lua point
|
||||||
/// saved. Always returns nothing (from the lua point of view).
|
/// of view).
|
||||||
static int nlua_exec_lua_string(lua_State *lstate) FUNC_ATTR_NONNULL_ALL
|
static int nlua_exec_lua_string(lua_State *lstate) FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
String *str = (String *)lua_touserdata(lstate, 1);
|
String *str = (String *)lua_touserdata(lstate, 1);
|
||||||
Object *obj = (Object *) lua_touserdata(lstate, 2);
|
typval_T *ret_tv = (typval_T *)lua_touserdata(lstate, 2);
|
||||||
LuaError *lerr = (LuaError *) lua_touserdata(lstate, 3);
|
lua_pop(lstate, 2);
|
||||||
lua_pop(lstate, 3);
|
|
||||||
|
|
||||||
if (luaL_loadbuffer(lstate, str->data, str->size, NLUA_EVAL_NAME)) {
|
if (luaL_loadbuffer(lstate, str->data, str->size, NLUA_EVAL_NAME)) {
|
||||||
set_lua_error(lstate, lerr);
|
nlua_error(lstate, _("E5104: Error while creating lua chunk: %s"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (lua_pcall(lstate, 0, 1, 0)) {
|
if (lua_pcall(lstate, 0, 1, 0)) {
|
||||||
set_lua_error(lstate, lerr);
|
nlua_error(lstate, _("E5105: Error while calling lua chunk: %s"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!nlua_pop_typval(lstate, ret_tv)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
*obj = nlua_pop_Object(lstate, &lerr->err);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,8 +141,7 @@ static int nlua_state_init(lua_State *lstate) FUNC_ATTR_NONNULL_ALL
|
|||||||
lua_pushcfunction(lstate, &nlua_stricmp);
|
lua_pushcfunction(lstate, &nlua_stricmp);
|
||||||
lua_setglobal(lstate, "stricmp");
|
lua_setglobal(lstate, "stricmp");
|
||||||
if (luaL_dostring(lstate, (char *) &vim_module[0])) {
|
if (luaL_dostring(lstate, (char *) &vim_module[0])) {
|
||||||
LuaError lerr;
|
nlua_error(lstate, _("E5106: Error while creating vim module: %s"));
|
||||||
set_lua_error(lstate, &lerr);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
nlua_add_api_functions(lstate);
|
nlua_add_api_functions(lstate);
|
||||||
@@ -150,14 +166,6 @@ static lua_State *init_lua(void)
|
|||||||
return lstate;
|
return lstate;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Object exec_lua_string(lua_State *lstate, String str, LuaError *lerr)
|
|
||||||
FUNC_ATTR_NONNULL_ALL
|
|
||||||
{
|
|
||||||
Object ret = { kObjectTypeNil, { false } };
|
|
||||||
NLUA_CALL_C_FUNCTION_3(lstate, nlua_exec_lua_string, 0, &str, &ret, lerr);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static lua_State *global_lstate = NULL;
|
static lua_State *global_lstate = NULL;
|
||||||
|
|
||||||
/// Execute lua string
|
/// Execute lua string
|
||||||
@@ -165,28 +173,17 @@ static lua_State *global_lstate = NULL;
|
|||||||
/// Used for :lua.
|
/// Used for :lua.
|
||||||
///
|
///
|
||||||
/// @param[in] str String to execute.
|
/// @param[in] str String to execute.
|
||||||
/// @param[out] err Location where error will be saved.
|
/// @param[out] ret_tv Location where result will be saved.
|
||||||
/// @param[out] err_str Location where lua error string will be saved, if any.
|
|
||||||
///
|
///
|
||||||
/// @return Result of the execution.
|
/// @return Result of the execution.
|
||||||
Object executor_exec_lua(String str, Error *err, String *err_str)
|
void executor_exec_lua(String str, typval_T *ret_tv)
|
||||||
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
if (global_lstate == NULL) {
|
if (global_lstate == NULL) {
|
||||||
global_lstate = init_lua();
|
global_lstate = init_lua();
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaError lerr = {
|
NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_lua_string, 0, &str, ret_tv);
|
||||||
.err = { .set = false },
|
|
||||||
.lua_err_str = STRING_INIT,
|
|
||||||
};
|
|
||||||
|
|
||||||
Object ret = exec_lua_string(global_lstate, str, &lerr);
|
|
||||||
|
|
||||||
*err = lerr.err;
|
|
||||||
*err_str = lerr.lua_err_str;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate lua string
|
/// Evaluate lua string
|
||||||
@@ -196,48 +193,54 @@ Object executor_exec_lua(String str, Error *err, String *err_str)
|
|||||||
/// 1. String to evaluate.
|
/// 1. String to evaluate.
|
||||||
/// 2. _A value.
|
/// 2. _A value.
|
||||||
/// 3. Pointer to location where result is saved.
|
/// 3. Pointer to location where result is saved.
|
||||||
/// 4. Pointer to location where error will be saved.
|
|
||||||
///
|
///
|
||||||
/// @param[in,out] lstate Lua interpreter state.
|
/// @param[in,out] lstate Lua interpreter state.
|
||||||
static int nlua_eval_lua_string(lua_State *lstate)
|
static int nlua_eval_lua_string(lua_State *lstate)
|
||||||
FUNC_ATTR_NONNULL_ALL
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
String *str = (String *)lua_touserdata(lstate, 1);
|
String *str = (String *)lua_touserdata(lstate, 1);
|
||||||
Object *arg = (Object *) lua_touserdata(lstate, 2);
|
typval_T *arg = (typval_T *)lua_touserdata(lstate, 2);
|
||||||
Object *ret = (Object *) lua_touserdata(lstate, 3);
|
typval_T *ret_tv = (typval_T *)lua_touserdata(lstate, 3);
|
||||||
LuaError *lerr = (LuaError *) lua_touserdata(lstate, 4);
|
lua_pop(lstate, 3);
|
||||||
|
|
||||||
garray_T str_ga;
|
garray_T str_ga;
|
||||||
ga_init(&str_ga, 1, 80);
|
ga_init(&str_ga, 1, 80);
|
||||||
#define EVALHEADER "local _A=select(1,...) return "
|
#define EVALHEADER "local _A=select(1,...) return ("
|
||||||
ga_concat_len(&str_ga, EVALHEADER, sizeof(EVALHEADER) - 1);
|
const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str->size + 1;
|
||||||
|
char *lcmd;
|
||||||
|
if (lcmd_len < IOSIZE) {
|
||||||
|
lcmd = (char *)IObuff;
|
||||||
|
} else {
|
||||||
|
lcmd = xmalloc(lcmd_len);
|
||||||
|
}
|
||||||
|
memcpy(lcmd, EVALHEADER, sizeof(EVALHEADER) - 1);
|
||||||
|
memcpy(lcmd + sizeof(EVALHEADER) - 1, str->data, str->size);
|
||||||
|
lcmd[lcmd_len - 1] = ')';
|
||||||
#undef EVALHEADER
|
#undef EVALHEADER
|
||||||
ga_concat_len(&str_ga, str->data, str->size);
|
if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) {
|
||||||
if (luaL_loadbuffer(lstate, str_ga.ga_data, (size_t) str_ga.ga_len,
|
nlua_error(lstate,
|
||||||
NLUA_EVAL_NAME)) {
|
_("E5107: Error while creating lua chunk for luaeval(): %s"));
|
||||||
set_lua_error(lstate, lerr);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
ga_clear(&str_ga);
|
if (lcmd != (char *)IObuff) {
|
||||||
|
xfree(lcmd);
|
||||||
|
}
|
||||||
|
|
||||||
nlua_push_Object(lstate, *arg);
|
if (arg == NULL || arg->v_type == VAR_UNKNOWN) {
|
||||||
|
lua_pushnil(lstate);
|
||||||
|
} else {
|
||||||
|
nlua_push_typval(lstate, arg);
|
||||||
|
}
|
||||||
if (lua_pcall(lstate, 1, 1, 0)) {
|
if (lua_pcall(lstate, 1, 1, 0)) {
|
||||||
set_lua_error(lstate, lerr);
|
nlua_error(lstate,
|
||||||
|
_("E5108: Error while calling lua chunk for luaeval(): %s"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
*ret = nlua_pop_Object(lstate, &lerr->err);
|
if (!nlua_pop_typval(lstate, ret_tv)) {
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Object eval_lua_string(lua_State *lstate, String str, Object arg,
|
return 0;
|
||||||
LuaError *lerr)
|
|
||||||
FUNC_ATTR_NONNULL_ALL
|
|
||||||
{
|
|
||||||
Object ret = { kObjectTypeNil, { false } };
|
|
||||||
NLUA_CALL_C_FUNCTION_4(lstate, nlua_eval_lua_string, 0,
|
|
||||||
&str, &arg, &ret, lerr);
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate lua string
|
/// Evaluate lua string
|
||||||
@@ -245,26 +248,18 @@ static Object eval_lua_string(lua_State *lstate, String str, Object arg,
|
|||||||
/// Used for luaeval().
|
/// Used for luaeval().
|
||||||
///
|
///
|
||||||
/// @param[in] str String to execute.
|
/// @param[in] str String to execute.
|
||||||
/// @param[out] err Location where error will be saved.
|
/// @param[in] arg Second argument to `luaeval()`.
|
||||||
/// @param[out] err_str Location where lua error string will be saved, if any.
|
/// @param[out] ret_tv Location where result will be saved.
|
||||||
///
|
///
|
||||||
/// @return Result of the execution.
|
/// @return Result of the execution.
|
||||||
Object executor_eval_lua(String str, Object arg, Error *err, String *err_str)
|
void executor_eval_lua(const String str, typval_T *const arg,
|
||||||
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
|
typval_T *const ret_tv)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
if (global_lstate == NULL) {
|
if (global_lstate == NULL) {
|
||||||
global_lstate = init_lua();
|
global_lstate = init_lua();
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaError lerr = {
|
NLUA_CALL_C_FUNCTION_3(global_lstate, nlua_eval_lua_string, 0,
|
||||||
.err = { .set = false },
|
(void *)&str, arg, ret_tv);
|
||||||
.lua_err_str = STRING_INIT,
|
|
||||||
};
|
|
||||||
|
|
||||||
Object ret = eval_lua_string(global_lstate, str, arg, &lerr);
|
|
||||||
|
|
||||||
*err = lerr.err;
|
|
||||||
*err_str = lerr.lua_err_str;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
143
test/functional/lua_spec.lua
Normal file
143
test/functional/lua_spec.lua
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
local helpers = require('test.functional.helpers')(after_each)
|
||||||
|
|
||||||
|
local eq = helpers.eq
|
||||||
|
local neq = helpers.neq
|
||||||
|
local NIL = helpers.NIL
|
||||||
|
local eval = helpers.eval
|
||||||
|
local clear = helpers.clear
|
||||||
|
local funcs = helpers.funcs
|
||||||
|
local meths = helpers.meths
|
||||||
|
local exc_exec = helpers.exc_exec
|
||||||
|
local curbufmeths = helpers.curbufmeths
|
||||||
|
|
||||||
|
local function startswith(expected, actual)
|
||||||
|
eq(expected, actual:sub(1, #expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
before_each(clear)
|
||||||
|
|
||||||
|
describe('luaeval() function', function()
|
||||||
|
local nested_by_level = {}
|
||||||
|
local nested = {}
|
||||||
|
local nested_s = '{}'
|
||||||
|
for i=1,100 do
|
||||||
|
if i % 2 == 0 then
|
||||||
|
nested = {nested}
|
||||||
|
nested_s = '{' .. nested_s .. '}'
|
||||||
|
else
|
||||||
|
nested = {nested=nested}
|
||||||
|
nested_s = '{nested=' .. nested_s .. '}'
|
||||||
|
end
|
||||||
|
nested_by_level[i] = {o=nested, s=nested_s}
|
||||||
|
end
|
||||||
|
|
||||||
|
it('correctly evaluates scalars', function()
|
||||||
|
eq(1, funcs.luaeval('1'))
|
||||||
|
eq(0, eval('type(luaeval("1"))'))
|
||||||
|
|
||||||
|
eq(1.5, funcs.luaeval('1.5'))
|
||||||
|
eq(5, eval('type(luaeval("1.5"))'))
|
||||||
|
|
||||||
|
eq("test", funcs.luaeval('"test"'))
|
||||||
|
eq(1, eval('type(luaeval("\'test\'"))'))
|
||||||
|
|
||||||
|
eq('', funcs.luaeval('""'))
|
||||||
|
eq({_TYPE={}, _VAL={'\n'}}, funcs.luaeval([['\0']]))
|
||||||
|
eq({_TYPE={}, _VAL={'\n', '\n'}}, funcs.luaeval([['\0\n\0']]))
|
||||||
|
eq(1, eval([[luaeval('"\0\n\0"')._TYPE is v:msgpack_types.binary]]))
|
||||||
|
|
||||||
|
eq(true, funcs.luaeval('true'))
|
||||||
|
eq(false, funcs.luaeval('false'))
|
||||||
|
eq(NIL, funcs.luaeval('nil'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('correctly evaluates containers', function()
|
||||||
|
eq({}, funcs.luaeval('{}'))
|
||||||
|
eq(3, eval('type(luaeval("{}"))'))
|
||||||
|
|
||||||
|
eq({test=1, foo=2}, funcs.luaeval('{test=1, foo=2}'))
|
||||||
|
eq(4, eval('type(luaeval("{test=1, foo=2}"))'))
|
||||||
|
|
||||||
|
eq({4, 2}, funcs.luaeval('{4, 2}'))
|
||||||
|
eq(3, eval('type(luaeval("{4, 2}"))'))
|
||||||
|
|
||||||
|
local level = 30
|
||||||
|
eq(nested_by_level[level].o, funcs.luaeval(nested_by_level[level].s))
|
||||||
|
|
||||||
|
eq({_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n', '\n'}}, {_TYPE={}, _VAL={'\n', '\n\n'}}}}},
|
||||||
|
funcs.luaeval([[{['\0\n\0']='\0\n\0\0'}]]))
|
||||||
|
eq(1, eval([[luaeval('{["\0\n\0"]="\0\n\0\0"}')._TYPE is v:msgpack_types.map]]))
|
||||||
|
eq(1, eval([[luaeval('{["\0\n\0"]="\0\n\0\0"}')._VAL[0][0]._TYPE is v:msgpack_types.string]]))
|
||||||
|
eq(1, eval([[luaeval('{["\0\n\0"]="\0\n\0\0"}')._VAL[0][1]._TYPE is v:msgpack_types.binary]]))
|
||||||
|
eq({nested={{_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n', '\n'}}, {_TYPE={}, _VAL={'\n', '\n\n'}}}}}}},
|
||||||
|
funcs.luaeval([[{nested={{['\0\n\0']='\0\n\0\0'}}}]]))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('correctly passes scalars as argument', function()
|
||||||
|
eq(1, funcs.luaeval('_A', 1))
|
||||||
|
eq(1.5, funcs.luaeval('_A', 1.5))
|
||||||
|
eq('', funcs.luaeval('_A', ''))
|
||||||
|
eq('test', funcs.luaeval('_A', 'test'))
|
||||||
|
eq(NIL, funcs.luaeval('_A', NIL))
|
||||||
|
eq(true, funcs.luaeval('_A', true))
|
||||||
|
eq(false, funcs.luaeval('_A', false))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('correctly passes containers as argument', function()
|
||||||
|
eq({}, funcs.luaeval('_A', {}))
|
||||||
|
eq({test=1}, funcs.luaeval('_A', {test=1}))
|
||||||
|
eq({4, 2}, funcs.luaeval('_A', {4, 2}))
|
||||||
|
local level = 28
|
||||||
|
eq(nested_by_level[level].o, funcs.luaeval('_A', nested_by_level[level].o))
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function sp(typ, val)
|
||||||
|
return ('{"_TYPE": v:msgpack_types.%s, "_VAL": %s}'):format(typ, val)
|
||||||
|
end
|
||||||
|
local function mapsp(...)
|
||||||
|
local val = ''
|
||||||
|
for i=1,(select('#', ...)/2) do
|
||||||
|
val = ('%s[%s,%s],'):format(val, select(i * 2 - 1, ...),
|
||||||
|
select(i * 2, ...))
|
||||||
|
end
|
||||||
|
return sp('map', '[' .. val .. ']')
|
||||||
|
end
|
||||||
|
local function luaevalarg(argexpr, expr)
|
||||||
|
return eval(([=[
|
||||||
|
[
|
||||||
|
extend(g:, {'_ret': luaeval(%s, %s)})._ret,
|
||||||
|
type(g:_ret)==type({})&&has_key(g:_ret, '_TYPE')
|
||||||
|
? [
|
||||||
|
get(keys(filter(copy(v:msgpack_types), 'v:val is g:_ret._TYPE')), 0,
|
||||||
|
g:_ret._TYPE),
|
||||||
|
get(g:_ret, '_VAL', g:_ret)
|
||||||
|
]
|
||||||
|
: [0, g:_ret]][1]
|
||||||
|
]=]):format(expr or '"_A"', argexpr):gsub('\n', ''))
|
||||||
|
end
|
||||||
|
|
||||||
|
it('correctly passes special dictionaries', function()
|
||||||
|
eq({'binary', {'\n', '\n'}}, luaevalarg(sp('binary', '["\\n", "\\n"]')))
|
||||||
|
eq({'binary', {'\n', '\n'}}, luaevalarg(sp('string', '["\\n", "\\n"]')))
|
||||||
|
eq({0, true}, luaevalarg(sp('boolean', 1)))
|
||||||
|
eq({0, false}, luaevalarg(sp('boolean', 0)))
|
||||||
|
eq({0, NIL}, luaevalarg(sp('nil', 0)))
|
||||||
|
eq({0, {[""]=""}}, luaevalarg(mapsp(sp('binary', '[""]'), '""')))
|
||||||
|
eq({0, {[""]=""}}, luaevalarg(mapsp(sp('string', '[""]'), '""')))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('issues an error in some cases', function()
|
||||||
|
eq("Vim(call):E5100: Cannot convert given lua table: table should either have a sequence of positive integer keys or contain only string keys",
|
||||||
|
exc_exec('call luaeval("{1, foo=2}")'))
|
||||||
|
eq("Vim(call):E5101: Cannot convert given lua type",
|
||||||
|
exc_exec('call luaeval("vim.api.buffer_get_line_slice")'))
|
||||||
|
startswith("Vim(call):E5107: Error while creating lua chunk for luaeval(): ",
|
||||||
|
exc_exec('call luaeval("1, 2, 3")'))
|
||||||
|
startswith("Vim(call):E5108: Error while calling lua chunk for luaeval(): ",
|
||||||
|
exc_exec('call luaeval("(nil)()")'))
|
||||||
|
|
||||||
|
-- The following should not crash: conversion error happens inside
|
||||||
|
eq("Vim(call):E5101: Cannot convert given lua type",
|
||||||
|
exc_exec('call luaeval("vim.api")'))
|
||||||
|
end)
|
||||||
|
end)
|
Reference in New Issue
Block a user