aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/eval
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/eval')
-rw-r--r--src/nvim/eval/decode.c42
-rw-r--r--src/nvim/eval/encode.c65
-rw-r--r--src/nvim/eval/executor.c14
-rw-r--r--src/nvim/eval/funcs.c414
-rw-r--r--src/nvim/eval/typval.c173
-rw-r--r--src/nvim/eval/typval.h69
-rw-r--r--src/nvim/eval/typval_encode.c.h12
-rw-r--r--src/nvim/eval/userfunc.c1
8 files changed, 691 insertions, 99 deletions
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c
index 89e1f04bfd..5c03f55621 100644
--- a/src/nvim/eval/decode.c
+++ b/src/nvim/eval/decode.c
@@ -246,14 +246,15 @@ list_T *decode_create_map_special_dict(typval_T *const ret_tv,
/// 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.
+/// dictionary, VAR_BLOB, 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.
+/// @param[in] binary Determines decode type if string has NUL bytes.
+/// If true convert string to VAR_BLOB, otherwise to the
+/// kMPString special type.
/// @param[in] s_allocated If true, then `s` was allocated and can be saved in
/// a returned structure. If it is not saved there, it
/// will be freed.
@@ -269,21 +270,28 @@ typval_T decode_string(const char *const s, const size_t len,
? ((s != NULL) && (memchr(s, NUL, len) != NULL))
: (bool)hasnul);
if (really_hasnul) {
- list_T *const list = tv_list_alloc(kListLenMayKnow);
- tv_list_ref(list);
typval_T tv;
- create_special_dict(&tv, binary ? kMPBinary : kMPString, ((typval_T) {
- .v_type = VAR_LIST,
- .v_lock = VAR_UNLOCKED,
- .vval = { .v_list = list },
- }));
- const int elw_ret = encode_list_write((void *)list, s, len);
- if (s_allocated) {
- xfree((void *)s);
- }
- if (elw_ret == -1) {
- tv_clear(&tv);
- return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED };
+ tv.v_lock = VAR_UNLOCKED;
+ if (binary) {
+ tv_blob_alloc_ret(&tv);
+ ga_concat_len(&tv.vval.v_blob->bv_ga, s, len);
+ } else {
+ list_T *const list = tv_list_alloc(kListLenMayKnow);
+ tv_list_ref(list);
+ create_special_dict(&tv, kMPString,
+ ((typval_T){
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_list = list },
+ }));
+ const int elw_ret = encode_list_write((void *)list, s, len);
+ if (s_allocated) {
+ xfree((void *)s);
+ }
+ if (elw_ret == -1) {
+ tv_clear(&tv);
+ return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED };
+ }
}
return tv;
} else {
diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c
index a4d7af7971..b5e50e7ef5 100644
--- a/src/nvim/eval/encode.c
+++ b/src/nvim/eval/encode.c
@@ -47,6 +47,14 @@ const char *const encode_special_var_names[] = {
# include "eval/encode.c.generated.h"
#endif
+/// Msgpack callback for writing to a Blob
+int encode_blob_write(void *const data, const char *const buf, const size_t len)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ ga_concat_len(&((blob_T *)data)->bv_ga, buf, len);
+ return (int)len;
+}
+
/// Msgpack callback for writing to readfile()-style list
int encode_list_write(void *const data, const char *const buf, const size_t len)
FUNC_ATTR_NONNULL_ARG(1)
@@ -319,6 +327,30 @@ int encode_read_from_list(ListReaderState *const state, char *const buf,
#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type)
+#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
+ do { \
+ const blob_T *const blob_ = (blob); \
+ const int len_ = (len); \
+ if (len_ == 0) { \
+ ga_concat(gap, "0z"); \
+ } else { \
+ /* Allocate space for "0z", the two hex chars per byte, and a */ \
+ /* "." separator after every eight hex chars. */ \
+ /* Example: "0z00112233.44556677.8899" */ \
+ ga_grow(gap, 2 + 2 * len_ + (len_ - 1) / 4); \
+ ga_concat(gap, "0z"); \
+ char numbuf[NUMBUFLEN]; \
+ for (int i_ = 0; i_ < len_; i_++) { \
+ if (i_ > 0 && (i_ & 3) == 0) { \
+ ga_append(gap, '.'); \
+ } \
+ vim_snprintf((char *)numbuf, ARRAY_SIZE(numbuf), "%02X", \
+ (int)tv_blob_get(blob_, i_)); \
+ ga_concat(gap, numbuf); \
+ } \
+ } \
+ } while (0)
+
#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
do { \
char numbuf[NUMBUFLEN]; \
@@ -705,6 +737,28 @@ static inline int convert_to_json_string(garray_T *const gap,
return FAIL; \
} while (0)
+#undef TYPVAL_ENCODE_CONV_BLOB
+#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
+ do { \
+ const blob_T *const blob_ = (blob); \
+ const int len_ = (len); \
+ if (len_ == 0) { \
+ ga_concat(gap, "[]"); \
+ } else { \
+ ga_append(gap, '['); \
+ char numbuf[NUMBUFLEN]; \
+ for (int i_ = 0; i_ < len_; i_++) { \
+ if (i_ > 0) { \
+ ga_concat(gap, ", "); \
+ } \
+ vim_snprintf((char *)numbuf, ARRAY_SIZE(numbuf), "%d", \
+ (int)tv_blob_get(blob_, i_)); \
+ ga_concat(gap, numbuf); \
+ } \
+ ga_append(gap, ']'); \
+ } \
+ } while (0)
+
#undef TYPVAL_ENCODE_CONV_FUNC_START
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
return conv_error(_("E474: Error while dumping %s, %s: " \
@@ -770,6 +824,7 @@ bool encode_check_json_key(const typval_T *const tv)
#undef TYPVAL_ENCODE_CONV_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING
+#undef TYPVAL_ENCODE_CONV_BLOB
#undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT
#undef TYPVAL_ENCODE_CONV_FUNC_START
@@ -904,6 +959,15 @@ char *encode_tv2json(typval_T *tv, size_t *len)
} \
} while (0)
+#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
+ do { \
+ const size_t len_ = (size_t)(len); \
+ msgpack_pack_bin(packer, len_); \
+ if (len_ > 0) { \
+ msgpack_pack_bin_body(packer, (blob)->bv_ga.ga_data, len_); \
+ } \
+ } while (0)
+
#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
msgpack_pack_int64(packer, (int64_t)(num))
@@ -982,6 +1046,7 @@ char *encode_tv2json(typval_T *tv, size_t *len)
#undef TYPVAL_ENCODE_CONV_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING
+#undef TYPVAL_ENCODE_CONV_BLOB
#undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT
#undef TYPVAL_ENCODE_CONV_FUNC_START
diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c
index 8ac2c3b8eb..5ced2a0535 100644
--- a/src/nvim/eval/executor.c
+++ b/src/nvim/eval/executor.c
@@ -38,6 +38,20 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2,
case VAR_SPECIAL: {
break;
}
+ case VAR_BLOB: {
+ if (*op != '+' || tv2->v_type != VAR_BLOB) {
+ break;
+ }
+ // Blob += Blob
+ if (tv1->vval.v_blob != NULL && tv2->vval.v_blob != NULL) {
+ blob_T *const b1 = tv1->vval.v_blob;
+ blob_T *const b2 = tv2->vval.v_blob;
+ for (int i = 0; i < tv_blob_len(b2); i++) {
+ ga_append(&b1->bv_ga, (char)tv_blob_get(b2, i));
+ }
+ }
+ return OK;
+ }
case VAR_LIST: {
if (*op != '+' || tv2->v_type != VAR_LIST) {
break;
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 801b0f9d1c..bcbd2266d3 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -96,6 +96,7 @@ PRAGMA_DIAG_POP
static char *e_listarg = N_("E686: Argument of %s must be a List");
+static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob");
static char *e_invalwindow = N_("E957: Invalid window number");
/// Dummy va_list for passing to vim_snprintf
@@ -321,8 +322,20 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)
tv_list_append_tv(l, &argvars[1]);
tv_copy(&argvars[0], rettv);
}
+ } else if (argvars[0].v_type == VAR_BLOB) {
+ blob_T *const b = argvars[0].vval.v_blob;
+ if (b != NULL
+ && !var_check_lock(b->bv_lock, N_("add() argument"), TV_TRANSLATE)) {
+ bool error = false;
+ const varnumber_T n = tv_get_number_chk(&argvars[1], &error);
+
+ if (!error) {
+ ga_append(&b->bv_ga, (int)n);
+ tv_copy(&argvars[0], rettv);
+ }
+ }
} else {
- EMSG(_(e_listreq));
+ EMSG(_(e_listblobreq));
}
}
@@ -959,7 +972,17 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
ptrdiff_t input_len = 0;
- char *input = save_tv_as_string(&argvars[1], &input_len, false);
+ char *input = NULL;
+ if (argvars[1].v_type == VAR_BLOB) {
+ const blob_T *const b = argvars[1].vval.v_blob;
+ input_len = tv_blob_len(b);
+ if (input_len > 0) {
+ input = xmemdup(b->bv_ga.ga_data, input_len);
+ }
+ } else {
+ input = save_tv_as_string(&argvars[1], &input_len, false);
+ }
+
if (!input) {
// Either the error has been handled by save_tv_as_string(),
// or there is no input to send.
@@ -1874,6 +1897,10 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr)
n = argvars[0].vval.v_special == kSpecialVarNull;
break;
}
+ case VAR_BLOB: {
+ n = (tv_blob_len(argvars[0].vval.v_blob) == 0);
+ break;
+ }
case VAR_UNKNOWN: {
internal_error("f_empty(UNKNOWN)");
break;
@@ -2791,7 +2818,23 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
typval_T *tv = NULL;
bool what_is_dict = false;
- if (argvars[0].v_type == VAR_LIST) {
+ if (argvars[0].v_type == VAR_BLOB) {
+ bool error = false;
+ int idx = tv_get_number_chk(&argvars[1], &error);
+
+ if (!error) {
+ rettv->v_type = VAR_NUMBER;
+ if (idx < 0) {
+ idx = tv_blob_len(argvars[0].vval.v_blob) + idx;
+ }
+ if (idx < 0 || idx >= tv_blob_len(argvars[0].vval.v_blob)) {
+ rettv->vval.v_number = -1;
+ } else {
+ rettv->vval.v_number = tv_blob_get(argvars[0].vval.v_blob, idx);
+ tv = rettv;
+ }
+ }
+ } else if (argvars[0].v_type == VAR_LIST) {
if ((l = argvars[0].vval.v_list) != NULL) {
bool error = false;
@@ -2852,7 +2895,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
} else {
- EMSG2(_(e_listdictarg), "get()");
+ EMSG2(_(e_listdictblobarg), "get()");
}
if (tv == NULL) {
@@ -4403,6 +4446,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
"user_commands",
"vartabs",
"vertsplit",
+ "vimscript-1",
"virtualedit",
"visual",
"visualextra",
@@ -4790,8 +4834,38 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool ic = false;
rettv->vval.v_number = -1;
- if (argvars[0].v_type != VAR_LIST) {
- EMSG(_(e_listreq));
+ if (argvars[0].v_type == VAR_BLOB) {
+ bool error = false;
+ int start = 0;
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ start = tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ return;
+ }
+ }
+ blob_T *const b = argvars[0].vval.v_blob;
+ if (b == NULL) {
+ return;
+ }
+ if (start < 0) {
+ start = tv_blob_len(b) + start;
+ if (start < 0) {
+ start = 0;
+ }
+ }
+ for (idx = start; idx < tv_blob_len(b); idx++) {
+ typval_T tv;
+ tv.v_type = VAR_NUMBER;
+ tv.vval.v_number = tv_blob_get(b, idx);
+ if (tv_equal(&tv, &argvars[1], ic, false)) {
+ rettv->vval.v_number = idx;
+ return;
+ }
+ }
+ return;
+ } else if (argvars[0].v_type != VAR_LIST) {
+ EMSG(_(e_listblobreq));
return;
}
list_T *const l = argvars[0].vval.v_list;
@@ -4920,8 +4994,46 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
list_T *l;
bool error = false;
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "insert()");
+ if (argvars[0].v_type == VAR_BLOB) {
+ blob_T *const b = argvars[0].vval.v_blob;
+
+ if (b == NULL
+ || var_check_lock(b->bv_lock, N_("insert() argument"),
+ TV_TRANSLATE)) {
+ return;
+ }
+
+ long before = 0;
+ const int len = tv_blob_len(b);
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ before = (long)tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ return; // type error; errmsg already given
+ }
+ if (before < 0 || before > len) {
+ EMSG2(_(e_invarg2), tv_get_string(&argvars[2]));
+ return;
+ }
+ }
+ const int val = tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ return;
+ }
+ if (val < 0 || val > 255) {
+ EMSG2(_(e_invarg2), tv_get_string(&argvars[1]));
+ return;
+ }
+
+ ga_grow(&b->bv_ga, 1);
+ char_u *const p = (char_u *)b->bv_ga.ga_data;
+ memmove(p + before + 1, p + before, (size_t)len - before);
+ *(p + before) = val;
+ b->bv_ga.ga_len++;
+
+ tv_copy(&argvars[0], rettv);
+ } else if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listblobarg), "insert()");
} else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
N_("insert() argument"), TV_TRANSLATE)) {
long before = 0;
@@ -5581,6 +5693,10 @@ static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr)
tv_get_string(&argvars[0]));
break;
}
+ case VAR_BLOB: {
+ rettv->vval.v_number = tv_blob_len(argvars[0].vval.v_blob);
+ break;
+ }
case VAR_LIST: {
rettv->vval.v_number = tv_list_len(argvars[0].vval.v_list);
break;
@@ -6391,9 +6507,16 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
EMSG2(_(e_listarg), "msgpackdump()");
return;
}
- list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
list_T *const list = argvars[0].vval.v_list;
- msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write);
+ msgpack_packer *packer;
+ if (argvars[1].v_type != VAR_UNKNOWN
+ && strequal(tv_get_string(&argvars[1]), "B")) {
+ tv_blob_alloc_ret(rettv);
+ packer = msgpack_packer_new(rettv->vval.v_blob, &encode_blob_write);
+ } else {
+ packer = msgpack_packer_new(tv_list_alloc_ret(rettv, kListLenMayKnow),
+ &encode_list_write);
+ }
const char *const msg = _("msgpackdump() argument, index %i");
// Assume that translation will not take more then 4 times more space
char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN];
@@ -6401,23 +6524,50 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
TV_LIST_ITER(list, li, {
vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx);
idx++;
- if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {
+ if (encode_vim_to_msgpack(packer, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {
break;
}
});
- msgpack_packer_free(lpacker);
+ msgpack_packer_free(packer);
}
-/// "msgpackparse" function
-static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+static int msgpackparse_convert_item(const msgpack_object data,
+ const msgpack_unpack_return result,
+ list_T *const ret_list,
+ const bool fail_if_incomplete)
FUNC_ATTR_NONNULL_ALL
{
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "msgpackparse()");
- return;
+ switch (result) {
+ case MSGPACK_UNPACK_PARSE_ERROR:
+ EMSG2(_(e_invarg2), "Failed to parse msgpack string");
+ return FAIL;
+ case MSGPACK_UNPACK_NOMEM_ERROR:
+ EMSG(_(e_outofmem));
+ return FAIL;
+ case MSGPACK_UNPACK_CONTINUE:
+ if (fail_if_incomplete) {
+ EMSG2(_(e_invarg2), "Incomplete msgpack string");
+ return FAIL;
+ }
+ return NOTDONE;
+ case MSGPACK_UNPACK_SUCCESS: {
+ typval_T tv = { .v_type = VAR_UNKNOWN };
+ if (msgpack_to_vim(data, &tv) == FAIL) {
+ EMSG2(_(e_invarg2), "Failed to convert msgpack string");
+ return FAIL;
+ }
+ tv_list_append_owned_tv(ret_list, tv);
+ return OK;
+ }
+ default:
+ abort();
}
- list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
- const list_T *const list = argvars[0].vval.v_list;
+}
+
+static void msgpackparse_unpack_list(const list_T *const list,
+ list_T *const ret_list)
+ FUNC_ATTR_NONNULL_ARG(2)
+{
if (tv_list_len(list) == 0) {
return;
}
@@ -6436,43 +6586,28 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
do {
if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) {
EMSG(_(e_outofmem));
- goto f_msgpackparse_exit;
+ goto end;
}
size_t read_bytes;
const int rlret = encode_read_from_list(
&lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, &read_bytes);
if (rlret == FAIL) {
EMSG2(_(e_invarg2), "List item is not a string");
- goto f_msgpackparse_exit;
+ goto end;
}
msgpack_unpacker_buffer_consumed(unpacker, read_bytes);
if (read_bytes == 0) {
break;
}
while (unpacker->off < unpacker->used) {
- const msgpack_unpack_return result = msgpack_unpacker_next(unpacker,
- &unpacked);
- if (result == MSGPACK_UNPACK_PARSE_ERROR) {
- EMSG2(_(e_invarg2), "Failed to parse msgpack string");
- goto f_msgpackparse_exit;
- }
- if (result == MSGPACK_UNPACK_NOMEM_ERROR) {
- EMSG(_(e_outofmem));
- goto f_msgpackparse_exit;
- }
- if (result == MSGPACK_UNPACK_SUCCESS) {
- typval_T tv = { .v_type = VAR_UNKNOWN };
- if (msgpack_to_vim(unpacked.data, &tv) == FAIL) {
- EMSG2(_(e_invarg2), "Failed to convert msgpack string");
- goto f_msgpackparse_exit;
- }
- tv_list_append_owned_tv(ret_list, tv);
- }
- if (result == MSGPACK_UNPACK_CONTINUE) {
- if (rlret == OK) {
- EMSG2(_(e_invarg2), "Incomplete msgpack string");
- }
+ const msgpack_unpack_return result
+ = msgpack_unpacker_next(unpacker, &unpacked);
+ const int conv_result = msgpackparse_convert_item(unpacked.data, result,
+ ret_list, rlret == OK);
+ if (conv_result == NOTDONE) {
break;
+ } else if (conv_result == FAIL) {
+ goto end;
}
}
if (rlret == OK) {
@@ -6480,10 +6615,47 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
} while (true);
-f_msgpackparse_exit:
- msgpack_unpacked_destroy(&unpacked);
+end:
msgpack_unpacker_free(unpacker);
- return;
+ msgpack_unpacked_destroy(&unpacked);
+}
+
+static void msgpackparse_unpack_blob(const blob_T *const blob,
+ list_T *const ret_list)
+ FUNC_ATTR_NONNULL_ARG(2)
+{
+ const int len = tv_blob_len(blob);
+ if (len == 0) {
+ return;
+ }
+ msgpack_unpacked unpacked;
+ msgpack_unpacked_init(&unpacked);
+ for (size_t offset = 0; offset < (size_t)len;) {
+ const msgpack_unpack_return result
+ = msgpack_unpack_next(&unpacked, blob->bv_ga.ga_data, len, &offset);
+ if (msgpackparse_convert_item(unpacked.data, result, ret_list, true)
+ != OK) {
+ break;
+ }
+ }
+
+ msgpack_unpacked_destroy(&unpacked);
+}
+
+/// "msgpackparse" function
+static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) {
+ EMSG2(_(e_listblobarg), "msgpackparse()");
+ return;
+ }
+ list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
+ if (argvars[0].v_type == VAR_LIST) {
+ msgpackparse_unpack_list(argvars[0].vval.v_list, ret_list);
+ } else {
+ msgpackparse_unpack_blob(argvars[0].vval.v_blob, ret_list);
+ }
}
/*
@@ -6894,6 +7066,7 @@ static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
bool binary = false;
+ bool blob = false;
FILE *fd;
char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1
int io_size = sizeof(buf);
@@ -6906,22 +7079,41 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (argvars[1].v_type != VAR_UNKNOWN) {
if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
binary = true;
+ } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) {
+ blob = true;
}
if (argvars[2].v_type != VAR_UNKNOWN) {
maxline = tv_get_number(&argvars[2]);
}
}
- list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown);
-
// Always open the file in binary mode, library functions have a mind of
// their own about CR-LF conversion.
const char *const fname = tv_get_string(&argvars[0]);
+
+ if (os_isdir((const char_u *)fname)) {
+ EMSG2(_(e_isadir2), fname);
+ return;
+ }
if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) {
EMSG2(_(e_notopen), *fname == NUL ? _("<empty>") : fname);
return;
}
+ if (blob) {
+ tv_blob_alloc_ret(rettv);
+ if (!read_blob(fd, rettv->vval.v_blob)) {
+ EMSG2(_(e_notread), fname);
+ // An empty blob is returned on error.
+ tv_blob_free(rettv->vval.v_blob);
+ rettv->vval.v_blob = NULL;
+ }
+ fclose(fd);
+ return;
+ }
+
+ list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown);
+
while (maxline < 0 || tv_list_len(l) < maxline) {
readlen = (int)fread(buf, 1, io_size, fd);
@@ -7190,8 +7382,64 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
}
+ } else if (argvars[0].v_type == VAR_BLOB) {
+ blob_T *const b = argvars[0].vval.v_blob;
+
+ if (b != NULL && var_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE)) {
+ return;
+ }
+
+ bool error = false;
+ idx = (long)tv_get_number_chk(&argvars[1], &error);
+
+ if (!error) {
+ const int len = tv_blob_len(b);
+
+ if (idx < 0) {
+ // count from the end
+ idx = len + idx;
+ }
+ if (idx < 0 || idx >= len) {
+ EMSGN(_(e_blobidx), idx);
+ return;
+ }
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ // Remove one item, return its value.
+ char_u *const p = (char_u *)b->bv_ga.ga_data;
+ rettv->vval.v_number = (varnumber_T)(*(p + idx));
+ memmove(p + idx, p + idx + 1, (size_t)len - idx - 1);
+ b->bv_ga.ga_len--;
+ } else {
+ // Remove range of items, return blob with values.
+ end = (long)tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ return;
+ }
+ if (end < 0) {
+ // count from the end
+ end = len + end;
+ }
+ if (end >= len || idx > end) {
+ EMSGN(_(e_blobidx), end);
+ return;
+ }
+ blob_T *const blob = tv_blob_alloc();
+ blob->bv_ga.ga_len = end - idx + 1;
+ ga_grow(&blob->bv_ga, end - idx + 1);
+
+ char_u *const p = (char_u *)b->bv_ga.ga_data;
+ memmove((char_u *)blob->bv_ga.ga_data, p + idx,
+ (size_t)(end - idx + 1));
+ tv_blob_set_ret(rettv, blob);
+
+ if (len - end - 1 > 0) {
+ memmove(p + idx, p + end + 1, (size_t)(len - end - 1));
+ }
+ b->bv_ga.ga_len -= end - idx + 1;
+ }
+ }
} else if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listdictarg), "remove()");
+ EMSG2(_(e_listdictblobarg), "remove()");
} else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
arg_errmsg, TV_TRANSLATE)) {
bool error = false;
@@ -7465,13 +7713,25 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- list_T *l;
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "reverse()");
- } else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
- N_("reverse() argument"), TV_TRANSLATE)) {
- tv_list_reverse(l);
- tv_list_set_ret(rettv, l);
+ if (argvars[0].v_type == VAR_BLOB) {
+ blob_T *const b = argvars[0].vval.v_blob;
+ const int len = tv_blob_len(b);
+
+ for (int i = 0; i < len / 2; i++) {
+ const char_u tmp = tv_blob_get(b, i);
+ tv_blob_set(b, i, tv_blob_get(b, len - i - 1));
+ tv_blob_set(b, len - i - 1, tmp);
+ }
+ tv_blob_set_ret(rettv, b);
+ } else if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listblobarg), "reverse()");
+ } else {
+ list_T *const l = argvars[0].vval.v_list;
+ if (!var_check_lock(tv_list_locked(l), N_("reverse() argument"),
+ TV_TRANSLATE)) {
+ tv_list_reverse(l);
+ tv_list_set_ret(rettv, l);
+ }
}
}
@@ -11291,15 +11551,16 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr)
int n = -1;
switch (argvars[0].v_type) {
- case VAR_NUMBER: n = VAR_TYPE_NUMBER; break;
- case VAR_STRING: n = VAR_TYPE_STRING; break;
+ case VAR_NUMBER: n = VAR_TYPE_NUMBER; break;
+ case VAR_STRING: n = VAR_TYPE_STRING; break;
case VAR_PARTIAL:
- case VAR_FUNC: n = VAR_TYPE_FUNC; break;
- case VAR_LIST: n = VAR_TYPE_LIST; break;
- case VAR_DICT: n = VAR_TYPE_DICT; break;
- case VAR_FLOAT: n = VAR_TYPE_FLOAT; break;
- case VAR_BOOL: n = VAR_TYPE_BOOL; break;
- case VAR_SPECIAL:n = VAR_TYPE_SPECIAL; break;
+ case VAR_FUNC: n = VAR_TYPE_FUNC; break;
+ case VAR_LIST: n = VAR_TYPE_LIST; break;
+ case VAR_DICT: n = VAR_TYPE_DICT; break;
+ case VAR_FLOAT: n = VAR_TYPE_FLOAT; break;
+ case VAR_BOOL: n = VAR_TYPE_BOOL; break;
+ case VAR_SPECIAL: n = VAR_TYPE_SPECIAL; break;
+ case VAR_BLOB: n = VAR_TYPE_BLOB; break;
case VAR_UNKNOWN: {
internal_error("f_type(UNKNOWN)");
break;
@@ -11678,16 +11939,17 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "writefile()");
+ if (argvars[0].v_type == VAR_LIST) {
+ TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
+ if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) {
+ return;
+ }
+ });
+ } else if (argvars[0].v_type != VAR_BLOB) {
+ EMSG2(_(e_invarg2),
+ _("writefile() first argument must be a List or a Blob"));
return;
}
- const list_T *const list = argvars[0].vval.v_list;
- TV_LIST_ITER_CONST(list, li, {
- if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) {
- return;
- }
- });
bool binary = false;
bool append = false;
@@ -11727,7 +11989,13 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
emsgf(_("E482: Can't open file %s for writing: %s"),
fname, os_strerror(error));
} else {
- if (write_list(&fp, list, binary)) {
+ bool write_ok;
+ if (argvars[0].v_type == VAR_BLOB) {
+ write_ok = write_blob(&fp, argvars[0].vval.v_blob);
+ } else {
+ write_ok = write_list(&fp, argvars[0].vval.v_list, binary);
+ }
+ if (write_ok) {
rettv->vval.v_number = 0;
}
if ((error = file_close(&fp, do_fsync)) != 0) {
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 22b3bf026b..381d70ea1b 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -2125,6 +2125,77 @@ void tv_dict_set_keys_readonly(dict_T *const dict)
});
}
+//{{{1 Blobs
+//{{{2 Alloc/free
+
+/// Allocate an empty blob.
+///
+/// Caller should take care of the reference count.
+///
+/// @return [allocated] new blob.
+blob_T *tv_blob_alloc(void)
+ FUNC_ATTR_NONNULL_RET
+{
+ blob_T *const blob = xcalloc(1, sizeof(blob_T));
+ ga_init(&blob->bv_ga, 1, 100);
+ return blob;
+}
+
+/// Free a blob. Ignores the reference count.
+///
+/// @param[in,out] b Blob to free.
+void tv_blob_free(blob_T *const b)
+ FUNC_ATTR_NONNULL_ALL
+{
+ ga_clear(&b->bv_ga);
+ xfree(b);
+}
+
+/// Unreference a blob.
+///
+/// Decrements the reference count and frees blob when it becomes zero.
+///
+/// @param[in,out] b Blob to operate on.
+void tv_blob_unref(blob_T *const b)
+{
+ if (b != NULL && --b->bv_refcount <= 0) {
+ tv_blob_free(b);
+ }
+}
+
+//{{{2 Operations on the whole blob
+
+/// Check whether two blobs are equal.
+///
+/// @param[in] b1 First blob.
+/// @param[in] b2 Second blob.
+///
+/// @return true if blobs are equal, false otherwise.
+bool tv_blob_equal(const blob_T *const b1, const blob_T *const b2)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const int len1 = tv_blob_len(b1);
+ const int len2 = tv_blob_len(b2);
+
+ // empty and NULL are considered the same
+ if (len1 == 0 && len2 == 0) {
+ return true;
+ }
+ if (b1 == b2) {
+ return true;
+ }
+ if (len1 != len2) {
+ return false;
+ }
+
+ for (int i = 0; i < b1->bv_ga.ga_len; i++) {
+ if (tv_blob_get(b1, i) != tv_blob_get(b2, i)) {
+ return false;
+ }
+ }
+ return true;
+}
+
//{{{1 Generic typval operations
//{{{2 Init/alloc/clear
//{{{3 Alloc
@@ -2169,6 +2240,44 @@ void tv_dict_alloc_ret(typval_T *const ret_tv)
tv_dict_set_ret(ret_tv, d);
}
+/// Allocate an empty blob for a return value.
+///
+/// Also sets reference count.
+///
+/// @param[out] ret_tv Structure where blob is saved.
+void tv_blob_alloc_ret(typval_T *const ret_tv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ blob_T *const b = tv_blob_alloc();
+ tv_blob_set_ret(ret_tv, b);
+}
+
+/// Copy a blob typval to a different typval.
+///
+/// @param[in] from Blob object to copy from.
+/// @param[out] to Blob object to copy to.
+void tv_blob_copy(typval_T *const from, typval_T *const to)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(from->v_type == VAR_BLOB);
+
+ to->v_type = VAR_BLOB;
+ to->v_lock = VAR_UNLOCKED;
+ if (from->vval.v_blob == NULL) {
+ to->vval.v_blob = NULL;
+ } else {
+ tv_blob_alloc_ret(to);
+ int len = from->vval.v_blob->bv_ga.ga_len;
+
+ if (len > 0) {
+ to->vval.v_blob->bv_ga.ga_data
+ = xmemdup(from->vval.v_blob->bv_ga.ga_data, (size_t)len);
+ }
+ to->vval.v_blob->bv_ga.ga_len = len;
+ to->vval.v_blob->bv_ga.ga_maxlen = len;
+ }
+}
+
//{{{3 Clear
#define TYPVAL_ENCODE_ALLOW_SPECIALS false
@@ -2210,6 +2319,13 @@ void tv_dict_alloc_ret(typval_T *const ret_tv)
#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type)
+#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
+ do { \
+ tv_blob_unref(tv->vval.v_blob); \
+ tv->vval.v_blob = NULL; \
+ tv->v_lock = VAR_UNLOCKED; \
+ } while (0)
+
static inline int _nothing_conv_func_start(typval_T *const tv,
char_u *const fun)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1)
@@ -2392,6 +2508,7 @@ static inline void _nothing_conv_dict_end(typval_T *const tv,
#undef TYPVAL_ENCODE_CONV_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING
+#undef TYPVAL_ENCODE_CONV_BLOB
#undef TYPVAL_ENCODE_CONV_FUNC_START
#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS
#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF
@@ -2449,6 +2566,10 @@ void tv_free(typval_T *tv)
xfree(tv->vval.v_string);
break;
}
+ case VAR_BLOB: {
+ tv_blob_unref(tv->vval.v_blob);
+ break;
+ }
case VAR_LIST: {
tv_list_unref(tv->vval.v_list);
break;
@@ -2509,6 +2630,12 @@ void tv_copy(const typval_T *const from, typval_T *const to)
}
break;
}
+ case VAR_BLOB: {
+ if (from->vval.v_blob != NULL) {
+ to->vval.v_blob->bv_refcount++;
+ }
+ break;
+ }
case VAR_LIST: {
tv_list_ref(to->vval.v_list);
break;
@@ -2533,7 +2660,10 @@ void tv_copy(const typval_T *const from, typval_T *const to)
/// @param[out] tv Item to (un)lock.
/// @param[in] deep Levels to (un)lock, -1 to (un)lock everything.
/// @param[in] lock True if it is needed to lock an item, false to unlock.
-void tv_item_lock(typval_T *const tv, const int deep, const bool lock)
+/// @param[in] check_refcount If true, do not lock a list or dict with a
+/// reference count larger than 1.
+void tv_item_lock(typval_T *const tv, const int deep, const bool lock,
+ const bool check_refcount)
FUNC_ATTR_NONNULL_ALL
{
// TODO(ZyX-I): Make this not recursive
@@ -2560,14 +2690,21 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock)
CHANGE_LOCK(lock, tv->v_lock);
switch (tv->v_type) {
+ case VAR_BLOB: {
+ blob_T *const b = tv->vval.v_blob;
+ if (b != NULL && !(check_refcount && b->bv_refcount > 1)) {
+ CHANGE_LOCK(lock, b->bv_lock);
+ }
+ break;
+ }
case VAR_LIST: {
list_T *const l = tv->vval.v_list;
- if (l != NULL) {
+ if (l != NULL && !(check_refcount && l->lv_refcount > 1)) {
CHANGE_LOCK(lock, l->lv_lock);
if (deep < 0 || deep > 1) {
// Recursive: lock/unlock the items the List contains.
TV_LIST_ITER(l, li, {
- tv_item_lock(TV_LIST_ITEM_TV(li), deep - 1, lock);
+ tv_item_lock(TV_LIST_ITEM_TV(li), deep - 1, lock, check_refcount);
});
}
}
@@ -2575,12 +2712,12 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock)
}
case VAR_DICT: {
dict_T *const d = tv->vval.v_dict;
- if (d != NULL) {
+ if (d != NULL && !(check_refcount && d->dv_refcount > 1)) {
CHANGE_LOCK(lock, d->dv_lock);
if (deep < 0 || deep > 1) {
// recursive: lock/unlock the items the List contains
TV_DICT_ITER(d, di, {
- tv_item_lock(&di->di_tv, deep - 1, lock);
+ tv_item_lock(&di->di_tv, deep - 1, lock, check_refcount);
});
}
}
@@ -2646,10 +2783,11 @@ bool tv_check_lock(const typval_T *tv, const char *name,
VarLockStatus lock = VAR_UNLOCKED;
switch (tv->v_type) {
- // case VAR_BLOB:
- // if (tv->vval.v_blob != NULL)
- // lock = tv->vval.v_blob->bv_lock;
- // break;
+ case VAR_BLOB:
+ if (tv->vval.v_blob != NULL) {
+ lock = tv->vval.v_blob->bv_lock;
+ }
+ break;
case VAR_LIST:
if (tv->vval.v_list != NULL) {
lock = tv->vval.v_list->lv_lock;
@@ -2769,6 +2907,9 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic,
recursive_cnt--;
return r;
}
+ case VAR_BLOB: {
+ return tv_blob_equal(tv1->vval.v_blob, tv2->vval.v_blob);
+ }
case VAR_NUMBER: {
return tv1->vval.v_number == tv2->vval.v_number;
}
@@ -2835,6 +2976,10 @@ bool tv_check_str_or_nr(const typval_T *const tv)
EMSG(_("E728: Expected a Number or a String, Dictionary found"));
return false;
}
+ case VAR_BLOB: {
+ EMSG(_("E974: Expected a Number or a String, Blob found"));
+ return false;
+ }
case VAR_BOOL: {
EMSG(_("E5299: Expected a Number or a String, Boolean found"));
return false;
@@ -2860,6 +3005,7 @@ static const char *const num_errors[] = {
[VAR_LIST]=N_("E745: Using a List as a Number"),
[VAR_DICT]=N_("E728: Using a Dictionary as a Number"),
[VAR_FLOAT]=N_("E805: Using a Float as a Number"),
+ [VAR_BLOB]=N_("E974: Using a Blob as a Number"),
[VAR_UNKNOWN]=N_("E685: using an invalid value as a Number"),
};
@@ -2888,6 +3034,7 @@ bool tv_check_num(const typval_T *const tv)
case VAR_LIST:
case VAR_DICT:
case VAR_FLOAT:
+ case VAR_BLOB:
case VAR_UNKNOWN: {
EMSG(_(num_errors[tv->v_type]));
return false;
@@ -2905,6 +3052,7 @@ static const char *const str_errors[] = {
[VAR_LIST]=N_("E730: using List as a String"),
[VAR_DICT]=N_("E731: using Dictionary as a String"),
[VAR_FLOAT]=((const char *)e_float_as_string),
+ [VAR_BLOB]=N_("E976: using Blob as a String"),
[VAR_UNKNOWN]=N_("E908: using an invalid value as a String"),
};
@@ -2933,6 +3081,7 @@ bool tv_check_str(const typval_T *const tv)
case VAR_LIST:
case VAR_DICT:
case VAR_FLOAT:
+ case VAR_BLOB:
case VAR_UNKNOWN: {
EMSG(_(str_errors[tv->v_type]));
return false;
@@ -2980,6 +3129,7 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error)
case VAR_PARTIAL:
case VAR_LIST:
case VAR_DICT:
+ case VAR_BLOB:
case VAR_FLOAT: {
EMSG(_(num_errors[tv->v_type]));
break;
@@ -3075,6 +3225,10 @@ float_T tv_get_float(const typval_T *const tv)
EMSG(_("E907: Using a special value as a Float"));
break;
}
+ case VAR_BLOB: {
+ EMSG(_("E975: Using a Blob as a Float"));
+ break;
+ }
case VAR_UNKNOWN: {
emsgf(_(e_intern2), "tv_get_float(UNKNOWN)");
break;
@@ -3134,6 +3288,7 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf)
case VAR_LIST:
case VAR_DICT:
case VAR_FLOAT:
+ case VAR_BLOB:
case VAR_UNKNOWN: {
EMSG(_(str_errors[tv->v_type]));
return false;
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index ef49fa1de6..5aecaccee9 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -64,6 +64,7 @@ enum ListLenSpecials {
typedef struct listvar_S list_T;
typedef struct dictvar_S dict_T;
typedef struct partial_S partial_T;
+typedef struct blobvar_S blob_T;
typedef struct ufunc ufunc_T;
@@ -123,6 +124,7 @@ typedef enum {
VAR_SPECIAL, ///< Special value (null), .v_special
///< is used.
VAR_PARTIAL, ///< Partial, .v_partial is used.
+ VAR_BLOB, ///< Blob, .v_blob is used.
} VarType;
/// Structure that holds an internal variable value
@@ -138,6 +140,7 @@ typedef struct {
list_T *v_list; ///< List for VAR_LIST, can be NULL.
dict_T *v_dict; ///< Dictionary for VAR_DICT, can be NULL.
partial_T *v_partial; ///< Closure: function with args.
+ blob_T *v_blob; ///< Blob for VAR_BLOB, can be NULL.
} vval; ///< Actual value.
} typval_T;
@@ -252,6 +255,13 @@ struct dictvar_S {
LuaRef lua_table_ref;
};
+/// Structure to hold info about a Blob
+struct blobvar_S {
+ garray_T bv_ga; ///< Growarray with the data.
+ int bv_refcount; ///< Reference count.
+ VarLockStatus bv_lock; ///< VAR_UNLOCKED, VAR_LOCKED, VAR_FIXED.
+};
+
/// Type used for script ID
typedef int scid_T;
/// Format argument for scid_T
@@ -711,6 +721,65 @@ static inline bool tv_dict_is_watched(const dict_T *const d)
return d && !QUEUE_EMPTY(&d->watchers);
}
+static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b)
+ REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1);
+
+/// Set a blob as the return value.
+///
+/// Increments the reference count.
+///
+/// @param[out] tv Object to receive the blob.
+/// @param[in,out] b Blob to pass to the object.
+static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b)
+{
+ tv->v_type = VAR_BLOB;
+ tv->vval.v_blob = b;
+ if (b != NULL) {
+ b->bv_refcount++;
+ }
+}
+
+static inline int tv_blob_len(const blob_T *const b)
+ REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT;
+
+/// Get the length of the data in the blob, in bytes.
+///
+/// @param[in] b Blob to check.
+static inline int tv_blob_len(const blob_T *const b)
+{
+ if (b == NULL) {
+ return 0;
+ }
+ return b->bv_ga.ga_len;
+}
+
+static inline char_u tv_blob_get(const blob_T *const b, int idx)
+ REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT;
+
+/// Get the byte at index `idx` in the blob.
+///
+/// @param[in] b Blob to index. Cannot be NULL.
+/// @param[in] idx Index in a blob. Must be valid.
+///
+/// @return Byte value at the given index.
+static inline char_u tv_blob_get(const blob_T *const b, int idx)
+{
+ return ((char_u *)b->bv_ga.ga_data)[idx];
+}
+
+static inline void tv_blob_set(blob_T *const b, int idx, char_u c)
+ REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL;
+
+/// Store the byte `c` at index `idx` in the blob.
+///
+/// @param[in] b Blob to index. Cannot be NULL.
+/// @param[in] idx Index in a blob. Must be valid.
+/// @param[in] c Value to store.
+static inline void tv_blob_set(blob_T *const b, int idx, char_u c)
+{
+ ((char_u *)b->bv_ga.ga_data)[idx] = c;
+}
+
/// Initialize VimL object
///
/// Initializes to unlocked VAR_UNKNOWN object.
diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h
index 91c948ce7e..cd1be1eecc 100644
--- a/src/nvim/eval/typval_encode.c.h
+++ b/src/nvim/eval/typval_encode.c.h
@@ -83,6 +83,13 @@
/// @param len String length.
/// @param type EXT type.
+/// @def TYPVAL_ENCODE_CONV_BLOB
+/// @brief Macros used to convert a blob
+///
+/// @param tv Pointer to typval where value is stored. May not be NULL.
+/// @param blob Pointer to the blob to convert.
+/// @param len Blob length.
+
/// @def TYPVAL_ENCODE_CONV_FUNC_START
/// @brief Macros used when starting to convert a funcref or a partial
///
@@ -330,6 +337,11 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
TYPVAL_ENCODE_CONV_FLOAT(tv, tv->vval.v_float);
break;
}
+ case VAR_BLOB: {
+ TYPVAL_ENCODE_CONV_BLOB(tv, tv->vval.v_blob,
+ tv_blob_len(tv->vval.v_blob));
+ break;
+ }
case VAR_FUNC: {
TYPVAL_ENCODE_CONV_FUNC_START(tv, tv->vval.v_string);
TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, 0);
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 19535c2868..350c5ee1c2 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -5,6 +5,7 @@
#include "nvim/ascii.h"
#include "nvim/charset.h"
+#include "nvim/debugger.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"