aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/private/helpers.c11
-rw-r--r--src/nvim/channel.c1
-rw-r--r--src/nvim/eval.c394
-rw-r--r--src/nvim/eval.h3
-rw-r--r--src/nvim/eval.lua2
-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.c413
-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/globals.h5
-rw-r--r--src/nvim/lua/converter.c9
-rw-r--r--src/nvim/shada.c40
-rw-r--r--src/nvim/testdir/test_blob.vim349
-rw-r--r--src/nvim/testdir/test_const.vim33
-rw-r--r--src/nvim/testdir/test_eval_stuff.vim8
-rw-r--r--src/nvim/testdir/test_filter_map.vim6
-rw-r--r--src/nvim/testdir/test_fnamemodify.vim4
-rw-r--r--src/nvim/testdir/test_listdict.vim4
-rw-r--r--src/nvim/testdir/test_method.vim11
-rw-r--r--src/nvim/testdir/test_quickfix.vim4
-rw-r--r--src/nvim/testdir/test_rename.vim1
-rw-r--r--src/nvim/testdir/test_swap.vim1
-rw-r--r--src/nvim/testdir/test_undo.vim1
-rw-r--r--src/nvim/testdir/test_vimscript.vim4
-rw-r--r--src/nvim/testdir/test_writefile.vim2
-rw-r--r--src/nvim/vim.h1
29 files changed, 1516 insertions, 166 deletions
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 0ed5e6408b..ecbd4e13a3 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -444,6 +444,16 @@ void set_option_to(uint64_t channel_id, void *to, int type,
#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \
TYPVAL_ENCODE_CONV_NIL(tv)
+#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
+ do { \
+ const size_t len_ = (size_t)(len); \
+ const blob_T *const blob_ = (blob); \
+ kvi_push(edata->stack, STRING_OBJ(((String) { \
+ .data = len_ != 0 ? xmemdup(blob_->bv_ga.ga_data, len_) : NULL, \
+ .size = len_ \
+ }))); \
+ } while (0)
+
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
do { \
TYPVAL_ENCODE_CONV_NIL(tv); \
@@ -584,6 +594,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata)
#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/channel.c b/src/nvim/channel.c
index 94db7fb3b9..54a59f6cc1 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -516,6 +516,7 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output,
/// @param data will be consumed
size_t channel_send(uint64_t id, char *data, size_t len,
bool data_owned, const char **error)
+ FUNC_ATTR_NONNULL_ALL
{
Channel *chan = find_channel(id);
size_t written = 0;
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index dfbb187b5b..5607de3cc1 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -69,6 +69,7 @@ static char *e_nowhitespace
= N_("E274: No white space allowed before parenthesis");
static char *e_invalwindow = N_("E957: Invalid window number");
static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
+static char *e_write2 = N_("E80: Error while writing: %s");
// TODO(ZyX-I): move to eval/executor
static char *e_letwrong = N_("E734: Wrong variable type for %s=");
@@ -113,6 +114,8 @@ typedef struct {
int fi_varcount; // nr of variables in the list
listwatch_T fi_lw; // keep an eye on the item used.
list_T *fi_list; // list being used
+ int fi_bi; // index of blob
+ blob_T *fi_blob; // blob being used
} forinfo_T;
// values for vv_flags:
@@ -227,6 +230,7 @@ static struct vimvar {
VV(VV_TYPE_DICT, "t_dict", VAR_NUMBER, VV_RO),
VV(VV_TYPE_FLOAT, "t_float", VAR_NUMBER, VV_RO),
VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO),
+ VV(VV_TYPE_BLOB, "t_blob", VAR_NUMBER, VV_RO),
VV(VV_EVENT, "event", VAR_DICT, VV_RO),
VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO),
VV(VV_ARGV, "argv", VAR_LIST, VV_RO),
@@ -238,6 +242,7 @@ static struct vimvar {
VV(VV__NULL_STRING, "_null_string", VAR_STRING, VV_RO),
VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO),
VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO),
+ VV(VV__NULL_BLOB, "_null_blob", VAR_BLOB, VV_RO),
VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO),
};
#undef VV
@@ -251,6 +256,7 @@ static struct vimvar {
#define vv_str vv_di.di_tv.vval.v_string
#define vv_list vv_di.di_tv.vval.v_list
#define vv_dict vv_di.di_tv.vval.v_dict
+#define vv_blob vv_di.di_tv.vval.v_blob
#define vv_partial vv_di.di_tv.vval.v_partial
#define vv_tv vv_di.di_tv
@@ -393,6 +399,7 @@ void eval_init(void)
set_vim_var_nr(VV_TYPE_DICT, VAR_TYPE_DICT);
set_vim_var_nr(VV_TYPE_FLOAT, VAR_TYPE_FLOAT);
set_vim_var_nr(VV_TYPE_BOOL, VAR_TYPE_BOOL);
+ set_vim_var_nr(VV_TYPE_BLOB, VAR_TYPE_BLOB);
set_vim_var_bool(VV_FALSE, kBoolVarFalse);
set_vim_var_bool(VV_TRUE, kBoolVarTrue);
@@ -2059,18 +2066,17 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
return NULL;
}
- /*
- * Loop until no more [idx] or .key is following.
- */
+ // Loop until no more [idx] or .key is following.
lp->ll_tv = &v->di_tv;
var1.v_type = VAR_UNKNOWN;
var2.v_type = VAR_UNKNOWN;
while (*p == '[' || (*p == '.' && lp->ll_tv->v_type == VAR_DICT)) {
if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL)
- && !(lp->ll_tv->v_type == VAR_DICT
- && lp->ll_tv->vval.v_dict != NULL)) {
- if (!quiet)
- EMSG(_("E689: Can only index a List or Dictionary"));
+ && !(lp->ll_tv->v_type == VAR_DICT && lp->ll_tv->vval.v_dict != NULL)
+ && !(lp->ll_tv->v_type == VAR_BLOB && lp->ll_tv->vval.v_blob != NULL)) {
+ if (!quiet) {
+ EMSG(_("E689: Can only index a List, Dictionary or Blob"));
+ }
return NULL;
}
if (lp->ll_range) {
@@ -2119,10 +2125,11 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
tv_clear(&var1);
return NULL;
}
- if (rettv != NULL && (rettv->v_type != VAR_LIST
- || rettv->vval.v_list == NULL)) {
+ if (rettv != NULL
+ && !(rettv->v_type == VAR_LIST && rettv->vval.v_list != NULL)
+ && !(rettv->v_type == VAR_BLOB && rettv->vval.v_blob != NULL)) {
if (!quiet) {
- EMSG(_("E709: [:] requires a List value"));
+ EMSG(_("E709: [:] requires a List or Blob value"));
}
tv_clear(&var1);
return NULL;
@@ -2236,6 +2243,38 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
tv_clear(&var1);
lp->ll_tv = &lp->ll_di->di_tv;
+ } else if (lp->ll_tv->v_type == VAR_BLOB) {
+ // Get the number and item for the only or first index of the List.
+ if (empty1) {
+ lp->ll_n1 = 0;
+ } else {
+ // Is number or string.
+ lp->ll_n1 = (long)tv_get_number(&var1);
+ }
+ tv_clear(&var1);
+
+ const int bloblen = tv_blob_len(lp->ll_tv->vval.v_blob);
+ if (lp->ll_n1 < 0 || lp->ll_n1 > bloblen
+ || (lp->ll_range && lp->ll_n1 == bloblen)) {
+ if (!quiet) {
+ EMSGN(_(e_blobidx), lp->ll_n1);
+ }
+ tv_clear(&var2);
+ return NULL;
+ }
+ if (lp->ll_range && !lp->ll_empty2) {
+ lp->ll_n2 = (long)tv_get_number(&var2);
+ tv_clear(&var2);
+ if (lp->ll_n2 < 0 || lp->ll_n2 >= bloblen || lp->ll_n2 < lp->ll_n1) {
+ if (!quiet) {
+ EMSGN(_(e_blobidx), lp->ll_n2);
+ }
+ return NULL;
+ }
+ }
+ lp->ll_blob = lp->ll_tv->vval.v_blob;
+ lp->ll_tv = NULL;
+ break;
} else {
// Get the number and item for the only or first index of the List.
if (empty1) {
@@ -2329,7 +2368,50 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
if (lp->ll_tv == NULL) {
cc = *endp;
*endp = NUL;
- if (op != NULL && *op != '=') {
+ if (lp->ll_blob != NULL) {
+ if (op != NULL && *op != '=') {
+ EMSG2(_(e_letwrong), op);
+ return;
+ }
+ if (var_check_lock(lp->ll_blob->bv_lock, lp->ll_name, TV_CSTRING)) {
+ return;
+ }
+
+ if (lp->ll_range && rettv->v_type == VAR_BLOB) {
+ if (lp->ll_empty2) {
+ lp->ll_n2 = tv_blob_len(lp->ll_blob) - 1;
+ }
+
+ if (lp->ll_n2 - lp->ll_n1 + 1 != tv_blob_len(rettv->vval.v_blob)) {
+ EMSG(_("E972: Blob value does not have the right number of bytes"));
+ return;
+ }
+ if (lp->ll_empty2) {
+ lp->ll_n2 = tv_blob_len(lp->ll_blob);
+ }
+
+ for (int il = lp->ll_n1, ir = 0; il <= lp->ll_n2; il++) {
+ tv_blob_set(lp->ll_blob, il, tv_blob_get(rettv->vval.v_blob, ir++));
+ }
+ } else {
+ bool error = false;
+ const char_u val = tv_get_number_chk(rettv, &error);
+ if (!error) {
+ garray_T *const gap = &lp->ll_blob->bv_ga;
+
+ // Allow for appending a byte. Setting a byte beyond
+ // the end is an error otherwise.
+ if (lp->ll_n1 < gap->ga_len || lp->ll_n1 == gap->ga_len) {
+ ga_grow(&lp->ll_blob->bv_ga, 1);
+ tv_blob_set(lp->ll_blob, lp->ll_n1, val);
+ if (lp->ll_n1 == gap->ga_len) {
+ gap->ga_len++;
+ }
+ }
+ // error for invalid range was already given in get_lval()
+ }
+ }
+ } else if (op != NULL && *op != '=') {
typval_T tv;
if (is_const) {
@@ -2508,19 +2590,32 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip)
if (eval0(skipwhite(expr + 2), &tv, nextcmdp, !skip) == OK) {
*errp = false;
if (!skip) {
- l = tv.vval.v_list;
- if (tv.v_type != VAR_LIST) {
- EMSG(_(e_listreq));
- tv_clear(&tv);
- } else if (l == NULL) {
- // a null list is like an empty list: do nothing
+ if (tv.v_type == VAR_LIST) {
+ l = tv.vval.v_list;
+ if (l == NULL) {
+ // a null list is like an empty list: do nothing
+ tv_clear(&tv);
+ } else {
+ // No need to increment the refcount, it's already set for
+ // the list being used in "tv".
+ fi->fi_list = l;
+ tv_list_watch_add(l, &fi->fi_lw);
+ fi->fi_lw.lw_item = tv_list_first(l);
+ }
+ } else if (tv.v_type == VAR_BLOB) {
+ fi->fi_bi = 0;
+ if (tv.vval.v_blob != NULL) {
+ typval_T btv;
+
+ // Make a copy, so that the iteration still works when the
+ // blob is changed.
+ tv_blob_copy(&tv, &btv);
+ fi->fi_blob = btv.vval.v_blob;
+ }
tv_clear(&tv);
} else {
- /* No need to increment the refcount, it's already set for the
- * list being used in "tv". */
- fi->fi_list = l;
- tv_list_watch_add(l, &fi->fi_lw);
- fi->fi_lw.lw_item = tv_list_first(l);
+ EMSG(_(e_listblobreq));
+ tv_clear(&tv);
}
}
}
@@ -2542,6 +2637,19 @@ bool next_for_item(void *fi_void, char_u *arg)
{
forinfo_T *fi = (forinfo_T *)fi_void;
+ if (fi->fi_blob != NULL) {
+ if (fi->fi_bi >= tv_blob_len(fi->fi_blob)) {
+ return false;
+ }
+ typval_T tv;
+ tv.v_type = VAR_NUMBER;
+ tv.v_lock = VAR_FIXED;
+ tv.vval.v_number = tv_blob_get(fi->fi_blob, fi->fi_bi);
+ fi->fi_bi++;
+ return ex_let_vars(arg, &tv, true,
+ fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK;
+ }
+
listitem_T *item = fi->fi_lw.lw_item;
if (item == NULL) {
return false;
@@ -2565,6 +2673,9 @@ void free_for_info(void *fi_void)
tv_list_watch_remove(fi->fi_list, &fi->fi_lw);
tv_list_unref(fi->fi_list);
}
+ if (fi != NULL && fi->fi_blob != NULL) {
+ tv_blob_unref(fi->fi_blob);
+ }
xfree(fi);
}
@@ -2974,7 +3085,7 @@ static int do_lock_var(lval_T *lp, char_u *name_end FUNC_ATTR_UNUSED,
} else {
di->di_flags &= ~DI_FLAGS_LOCK;
}
- tv_item_lock(&di->di_tv, deep, lock);
+ tv_item_lock(&di->di_tv, deep, lock, false);
}
}
} else if (lp->ll_range) {
@@ -2982,16 +3093,16 @@ static int do_lock_var(lval_T *lp, char_u *name_end FUNC_ATTR_UNUSED,
// (un)lock a range of List items.
while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) {
- tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock);
+ tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock, false);
li = TV_LIST_ITEM_NEXT(lp->ll_list, li);
lp->ll_n1++;
}
} else if (lp->ll_list != NULL) {
// (un)lock a List item.
- tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock);
+ tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock, false);
} else {
// (un)lock a Dictionary item.
- tv_item_lock(&lp->ll_di->di_tv, deep, lock);
+ tv_item_lock(&lp->ll_di->di_tv, deep, lock, false);
}
return ret;
@@ -3607,7 +3718,7 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate)
if (op != '+' && op != '-' && op != '.')
break;
- if ((op != '+' || rettv->v_type != VAR_LIST)
+ if ((op != '+' || (rettv->v_type != VAR_LIST && rettv->v_type != VAR_BLOB))
&& (op == '.' || rettv->v_type != VAR_FLOAT)) {
// For "list + ...", an illegal use of the first operand as
// a number cannot be determined before evaluating the 2nd
@@ -3653,6 +3764,21 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate)
tv_clear(rettv);
rettv->v_type = VAR_STRING;
rettv->vval.v_string = p;
+ } else if (op == '+' && rettv->v_type == VAR_BLOB
+ && var2.v_type == VAR_BLOB) {
+ const blob_T *const b1 = rettv->vval.v_blob;
+ const blob_T *const b2 = var2.vval.v_blob;
+ blob_T *const b = tv_blob_alloc();
+
+ for (int i = 0; i < tv_blob_len(b1); i++) {
+ ga_append(&b->bv_ga, tv_blob_get(b1, i));
+ }
+ for (int i = 0; i < tv_blob_len(b2); i++) {
+ ga_append(&b->bv_ga, tv_blob_get(b2, i));
+ }
+
+ tv_clear(rettv);
+ tv_blob_set_ret(rettv, b);
} else if (op == '+' && rettv->v_type == VAR_LIST
&& var2.v_type == VAR_LIST) {
// Concatenate Lists.
@@ -3673,10 +3799,12 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate)
} else {
n1 = tv_get_number_chk(rettv, &error);
if (error) {
- /* This can only happen for "list + non-list". For
- * "non-list + ..." or "something - ...", we returned
- * before evaluating the 2nd operand. */
+ // This can only happen for "list + non-list" or
+ // "blob + non-blob". For "non-list + ..." or
+ // "something - ...", we returned before evaluating the
+ // 2nd operand.
tv_clear(rettv);
+ tv_clear(&var2);
return FAIL;
}
if (var2.v_type == VAR_FLOAT)
@@ -3850,6 +3978,7 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)
// Handle sixth level expression:
// number number constant
+// 0zFFFFFFFF Blob constant
// "string" string constant
// 'string' literal string constant
// &option-name option value
@@ -3946,7 +4075,37 @@ static int eval7(
rettv->v_type = VAR_FLOAT;
rettv->vval.v_float = f;
}
+ } else if (**arg == '0' && ((*arg)[1] == 'z' || (*arg)[1] == 'Z')) {
+ blob_T *blob = NULL;
+ // Blob constant: 0z0123456789abcdef
+ if (evaluate) {
+ blob = tv_blob_alloc();
+ }
+ char_u *bp;
+ for (bp = *arg + 2; ascii_isxdigit(bp[0]); bp += 2) {
+ if (!ascii_isxdigit(bp[1])) {
+ if (blob != NULL) {
+ EMSG(_("E973: Blob literal should have an even number of hex "
+ "characters"));
+ ga_clear(&blob->bv_ga);
+ XFREE_CLEAR(blob);
+ }
+ ret = FAIL;
+ break;
+ }
+ if (blob != NULL) {
+ ga_append(&blob->bv_ga, (hex2nr(*bp) << 4) + hex2nr(*(bp + 1)));
+ }
+ if (bp[2] == '.' && ascii_isxdigit(bp[3])) {
+ bp++;
+ }
+ }
+ if (blob != NULL) {
+ tv_blob_set_ret(rettv, blob);
+ }
+ *arg = bp;
} else {
+ // decimal, hex or octal number
vim_str2nr(*arg, NULL, &len, STR2NR_ALL, &n, NULL, 0, true);
if (len == 0) {
EMSG2(_(e_invexpr2), *arg);
@@ -4336,7 +4495,8 @@ eval_index(
case VAR_STRING:
case VAR_NUMBER:
case VAR_LIST:
- case VAR_DICT: {
+ case VAR_DICT:
+ case VAR_BLOB: {
break;
}
}
@@ -4462,6 +4622,53 @@ eval_index(
rettv->vval.v_string = (char_u *)v;
break;
}
+ case VAR_BLOB: {
+ len = tv_blob_len(rettv->vval.v_blob);
+ if (range) {
+ // The resulting variable is a sub-blob. If the indexes
+ // are out of range the result is empty.
+ if (n1 < 0) {
+ n1 = len + n1;
+ if (n1 < 0) {
+ n1 = 0;
+ }
+ }
+ if (n2 < 0) {
+ n2 = len + n2;
+ } else if (n2 >= len) {
+ n2 = len - 1;
+ }
+ if (n1 >= len || n2 < 0 || n1 > n2) {
+ tv_clear(rettv);
+ rettv->v_type = VAR_BLOB;
+ rettv->vval.v_blob = NULL;
+ } else {
+ blob_T *const blob = tv_blob_alloc();
+ ga_grow(&blob->bv_ga, n2 - n1 + 1);
+ blob->bv_ga.ga_len = n2 - n1 + 1;
+ for (long i = n1; i <= n2; i++) {
+ tv_blob_set(blob, i - n1, tv_blob_get(rettv->vval.v_blob, i));
+ }
+ tv_clear(rettv);
+ tv_blob_set_ret(rettv, blob);
+ }
+ } else {
+ // The resulting variable is a byte value.
+ // If the index is too big or negative that is an error.
+ if (n1 < 0) {
+ n1 = len + n1;
+ }
+ if (n1 < len && n1 >= 0) {
+ const int v = (int)tv_blob_get(rettv->vval.v_blob, n1);
+ tv_clear(rettv);
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = v;
+ } else {
+ EMSGN(_(e_blobidx), n1);
+ }
+ }
+ break;
+ }
case VAR_LIST: {
len = tv_list_len(rettv->vval.v_list);
if (n1 < 0) {
@@ -5398,7 +5605,8 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
case VAR_SPECIAL:
case VAR_FLOAT:
case VAR_NUMBER:
- case VAR_STRING: {
+ case VAR_STRING:
+ case VAR_BLOB: {
break;
}
}
@@ -6161,6 +6369,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
dict_T *d = NULL;
typval_T save_val;
typval_T save_key;
+ blob_T *b = NULL;
int rem = false;
int todo;
char_u *ermsg = (char_u *)(map ? "map()" : "filter()");
@@ -6170,7 +6379,12 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
int save_did_emsg;
int idx = 0;
- if (argvars[0].v_type == VAR_LIST) {
+ if (argvars[0].v_type == VAR_BLOB) {
+ tv_copy(&argvars[0], rettv);
+ if ((b = argvars[0].vval.v_blob) == NULL) {
+ return;
+ }
+ } else if (argvars[0].v_type == VAR_LIST) {
tv_copy(&argvars[0], rettv);
if ((l = argvars[0].vval.v_list) == NULL
|| (!map
@@ -6184,7 +6398,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
return;
}
} else {
- EMSG2(_(e_listdictarg), ermsg);
+ EMSG2(_(e_listdictblobarg), ermsg);
return;
}
@@ -6234,6 +6448,34 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
}
}
hash_unlock(ht);
+ } else if (argvars[0].v_type == VAR_BLOB) {
+ vimvars[VV_KEY].vv_type = VAR_NUMBER;
+
+ for (int i = 0; i < b->bv_ga.ga_len; i++) {
+ typval_T tv;
+ tv.v_type = VAR_NUMBER;
+ const varnumber_T val = tv_blob_get(b, i);
+ tv.vval.v_number = val;
+ vimvars[VV_KEY].vv_nr = idx;
+ if (filter_map_one(&tv, expr, map, &rem) == FAIL || did_emsg) {
+ break;
+ }
+ if (tv.v_type != VAR_NUMBER) {
+ EMSG(_(e_invalblob));
+ return;
+ }
+ if (map) {
+ if (tv.vval.v_number != val) {
+ tv_blob_set(b, i, tv.vval.v_number);
+ }
+ } else if (rem) {
+ char_u *const p = (char_u *)argvars[0].vval.v_blob->bv_ga.ga_data;
+ memmove(p + i, p + i + 1, (size_t)b->bv_ga.ga_len - i - 1);
+ b->bv_ga.ga_len--;
+ i--;
+ }
+ idx++;
+ }
} else {
assert(argvars[0].v_type == VAR_LIST);
vimvars[VV_KEY].vv_type = VAR_NUMBER;
@@ -7728,10 +7970,61 @@ bool write_list(FileDescriptor *const fp, const list_T *const list,
}
return true;
write_list_error:
- emsgf(_("E80: Error while writing: %s"), os_strerror(error));
+ emsgf(_(e_write2), os_strerror(error));
+ return false;
+}
+
+/// Write a blob to file with descriptor `fp`.
+///
+/// @param[in] fp File to write to.
+/// @param[in] blob Blob to write.
+///
+/// @return true on success, or false on failure.
+bool write_blob(FileDescriptor *const fp, const blob_T *const blob)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ int error = 0;
+ const int len = tv_blob_len(blob);
+ if (len > 0) {
+ const ptrdiff_t written = file_write(fp, blob->bv_ga.ga_data, (size_t)len);
+ if (written < (ptrdiff_t)len) {
+ error = (int)written;
+ goto write_blob_error;
+ }
+ }
+ error = file_flush(fp);
+ if (error != 0) {
+ goto write_blob_error;
+ }
+ return true;
+write_blob_error:
+ EMSG2(_(e_write2), os_strerror(error));
return false;
}
+/// Read a blob from a file `fd`.
+///
+/// @param[in] fd File to read from.
+/// @param[in,out] blob Blob to write to.
+///
+/// @return true on success, or false on failure.
+bool read_blob(FILE *const fd, blob_T *const blob)
+ FUNC_ATTR_NONNULL_ALL
+{
+ FileInfo file_info;
+ if (!os_fileinfo_fd(fileno(fd), &file_info)) {
+ return false;
+ }
+ const int size = (int)os_fileinfo_size(&file_info);
+ ga_grow(&blob->bv_ga, size);
+ blob->bv_ga.ga_len = size;
+ if (fread(blob->bv_ga.ga_data, 1, blob->bv_ga.ga_len, fd)
+ < (size_t)blob->bv_ga.ga_len) {
+ return false;
+ }
+ return true;
+}
+
/// Saves a typval_T as a string.
///
/// For lists or buffers, replaces NLs with NUL and separates items with NLs.
@@ -9248,7 +9541,10 @@ static void set_var_const(const char *name, const size_t name_len,
}
if (is_const) {
- tv_item_lock(&v->di_tv, 1, true);
+ // Like :lockvar! name: lock the value and what it contains, but only
+ // if the reference count is up to one. That locks only literal
+ // values.
+ tv_item_lock(&v->di_tv, DICT_MAXNEST, true, true);
}
}
@@ -9456,6 +9752,9 @@ int var_item_copy(const vimconv_T *const conv,
ret = FAIL;
}
break;
+ case VAR_BLOB:
+ tv_blob_copy(from, to);
+ break;
case VAR_DICT:
to->v_type = VAR_DICT;
to->v_lock = VAR_UNLOCKED;
@@ -10815,6 +11114,29 @@ int typval_compare(
// For "is" a different type always means false, for "notis"
// it means true.
n1 = type == EXPR_ISNOT;
+ } else if (typ1->v_type == VAR_BLOB || typ2->v_type == VAR_BLOB) {
+ if (type_is) {
+ n1 = typ1->v_type == typ2->v_type
+ && typ1->vval.v_blob == typ2->vval.v_blob;
+ if (type == EXPR_ISNOT) {
+ n1 = !n1;
+ }
+ } else if (typ1->v_type != typ2->v_type
+ || (type != EXPR_EQUAL && type != EXPR_NEQUAL)) {
+ if (typ1->v_type != typ2->v_type) {
+ EMSG(_("E977: Can only compare Blob with Blob"));
+ } else {
+ EMSG(_(e_invalblob));
+ }
+ tv_clear(typ1);
+ return FAIL;
+ } else {
+ // Compare two Blobs for being equal or unequal.
+ n1 = tv_blob_equal(typ1->vval.v_blob, typ2->vval.v_blob);
+ if (type == EXPR_NEQUAL) {
+ n1 = !n1;
+ }
+ }
} else if (typ1->v_type == VAR_LIST || typ2->v_type == VAR_LIST) {
if (type_is) {
n1 = typ1->v_type == typ2->v_type
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 41120b3c78..2452a0a8c8 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -63,6 +63,7 @@ typedef struct lval_S {
dict_T *ll_dict; ///< The Dictionary or NULL.
dictitem_T *ll_di; ///< The dictitem or NULL.
char_u *ll_newkey; ///< New key for Dict in allocated memory or NULL.
+ blob_T *ll_blob; ///< The Blob or NULL.
} lval_T;
/// enum used by var_flavour()
@@ -154,6 +155,7 @@ typedef enum {
VV_TYPE_DICT,
VV_TYPE_FLOAT,
VV_TYPE_BOOL,
+ VV_TYPE_BLOB,
VV_EVENT,
VV_ECHOSPACE,
VV_ARGV,
@@ -165,6 +167,7 @@ typedef enum {
VV__NULL_STRING, // String with NULL value. For test purposes only.
VV__NULL_LIST, // List with NULL value. For test purposes only.
VV__NULL_DICT, // Dictionary with NULL value. For test purposes only.
+ VV__NULL_BLOB, // Blob with NULL value. For test purposes only.
VV_LUA,
} VimVarIndex;
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index a7242ba73a..5f355abe3c 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -250,7 +250,7 @@ return {
min={args=1, base=1},
mkdir={args={1, 3}},
mode={args={0, 1}},
- msgpackdump={args=1},
+ msgpackdump={args={1, 2}},
msgpackparse={args=1},
nextnonblank={args=1},
nr2char={args={1, 2}},
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 dd83e762ca..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) {
@@ -4791,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;
@@ -4921,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;
@@ -5582,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;
@@ -6392,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];
@@ -6402,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;
}
@@ -6437,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) {
@@ -6481,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);
+ }
}
/*
@@ -6895,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);
@@ -6907,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);
@@ -7191,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;
@@ -7466,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);
+ }
}
}
@@ -11292,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;
@@ -11679,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;
@@ -11728,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/globals.h b/src/nvim/globals.h
index 2a72dbcd09..4d54907a75 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -939,13 +939,18 @@ EXTERN char_u e_readonlyvar[] INIT(= N_(
"E46: Cannot change read-only variable \"%.*s\""));
EXTERN char_u e_stringreq[] INIT(= N_("E928: String required"));
EXTERN char_u e_dictreq[] INIT(= N_("E715: Dictionary required"));
+EXTERN char_u e_blobidx[] INIT(= N_("E979: Blob index out of range: %" PRId64));
+EXTERN char_u e_invalblob[] INIT(= N_("E978: Invalid operation for Blob"));
EXTERN char_u e_toomanyarg[] INIT(= N_(
"E118: Too many arguments for function: %s"));
EXTERN char_u e_dictkey[] INIT(= N_(
"E716: Key not present in Dictionary: \"%s\""));
EXTERN char_u e_listreq[] INIT(= N_("E714: List required"));
+EXTERN char_u e_listblobreq[] INIT(= N_("E897: List or Blob required"));
EXTERN char_u e_listdictarg[] INIT(= N_(
"E712: Argument of %s must be a List or Dictionary"));
+EXTERN char_u e_listdictblobarg[] INIT(= N_(
+ "E896: Argument of %s must be a List, Dictionary or Blob"));
EXTERN char_u e_readerrf[] INIT(= N_("E47: Error while reading errorfile"));
EXTERN char_u e_sandbox[] INIT(= N_("E48: Not allowed in sandbox"));
EXTERN char_u e_secure[] INIT(= N_("E523: Not allowed here"));
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 1a59cd94ae..0adbbdb953 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -481,6 +481,14 @@ static bool typval_conv_special = false;
#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \
TYPVAL_ENCODE_CONV_NIL(tv)
+#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
+ do { \
+ const blob_T *const blob_ = (blob); \
+ lua_pushlstring(lstate, \
+ blob_ != NULL ? (const char *)blob_->bv_ga.ga_data : "", \
+ (size_t)(len)); \
+ } while (0)
+
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
do { \
TYPVAL_ENCODE_CONV_NIL(tv); \
@@ -579,6 +587,7 @@ static bool typval_conv_special = false;
#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/shada.c b/src/nvim/shada.c
index c0e787380f..7d277fe5c8 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -1653,6 +1653,13 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer,
break;
}
case kSDItemVariable: {
+ if (entry.data.global_var.value.v_type == VAR_TYPE_BLOB) {
+ // Strings and Blobs both pack as msgpack BINs; differentiate them by
+ // storing an additional VAR_TYPE_BLOB element alongside Blobs
+ list_T *const list = tv_list_alloc(1);
+ tv_list_append_number(list, VAR_TYPE_BLOB);
+ entry.data.global_var.additional_elements = list;
+ }
const size_t arr_size = 2 + (size_t)(
tv_list_len(entry.data.global_var.additional_elements));
msgpack_pack_array(spacker, arr_size);
@@ -3937,15 +3944,38 @@ shada_read_next_item_start:
entry->data.global_var.name =
xmemdupz(unpacked.data.via.array.ptr[0].via.bin.ptr,
unpacked.data.via.array.ptr[0].via.bin.size);
- if (msgpack_to_vim(unpacked.data.via.array.ptr[1],
- &(entry->data.global_var.value)) == FAIL) {
+ SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, 2,
+ entry->data.global_var.additional_elements,
+ "variable");
+ bool is_blob = false;
+ // A msgpack BIN could be a String or Blob; an additional VAR_TYPE_BLOB
+ // element is stored with Blobs which can be used to differentiate them
+ if (unpacked.data.via.array.ptr[1].type == MSGPACK_OBJECT_BIN) {
+ const listitem_T *type_item
+ = tv_list_first(entry->data.global_var.additional_elements);
+ if (type_item != NULL) {
+ const typval_T *type_tv = TV_LIST_ITEM_TV(type_item);
+ if (type_tv->v_type != VAR_NUMBER
+ || type_tv->vval.v_number != VAR_TYPE_BLOB) {
+ emsgf(_(READERR("variable", "has wrong variable type")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ is_blob = true;
+ }
+ }
+ if (is_blob) {
+ const msgpack_object_bin *const bin
+ = &unpacked.data.via.array.ptr[1].via.bin;
+ blob_T *const blob = tv_blob_alloc();
+ ga_concat_len(&blob->bv_ga, bin->ptr, (size_t)bin->size);
+ tv_blob_set_ret(&entry->data.global_var.value, blob);
+ } else if (msgpack_to_vim(unpacked.data.via.array.ptr[1],
+ &(entry->data.global_var.value)) == FAIL) {
emsgf(_(READERR("variable", "has value that cannot "
"be converted to the VimL value")), initial_fpos);
goto shada_read_next_item_error;
}
- SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, 2,
- entry->data.global_var.additional_elements,
- "variable");
break;
}
case kSDItemSubString: {
diff --git a/src/nvim/testdir/test_blob.vim b/src/nvim/testdir/test_blob.vim
new file mode 100644
index 0000000000..20758b0c0a
--- /dev/null
+++ b/src/nvim/testdir/test_blob.vim
@@ -0,0 +1,349 @@
+" Tests for the Blob types
+
+func TearDown()
+ " Run garbage collection after every test
+ call test_garbagecollect_now()
+endfunc
+
+" Tests for Blob type
+
+" Blob creation from constant
+func Test_blob_create()
+ let b = 0zDEADBEEF
+ call assert_equal(v:t_blob, type(b))
+ call assert_equal(4, len(b))
+ call assert_equal(0xDE, b[0])
+ call assert_equal(0xAD, b[1])
+ call assert_equal(0xBE, b[2])
+ call assert_equal(0xEF, b[3])
+ call assert_fails('let x = b[4]')
+
+ call assert_equal(0xDE, get(b, 0))
+ call assert_equal(0xEF, get(b, 3))
+
+ call assert_fails('let b = 0z1', 'E973:')
+ call assert_fails('let b = 0z1x', 'E973:')
+ call assert_fails('let b = 0z12345', 'E973:')
+
+ call assert_equal(0z, v:_null_blob)
+
+ let b = 0z001122.33445566.778899.aabbcc.dd
+ call assert_equal(0z00112233445566778899aabbccdd, b)
+ call assert_fails('let b = 0z1.1')
+ call assert_fails('let b = 0z.')
+ call assert_fails('let b = 0z001122.')
+ call assert_fails('call get("", 1)', 'E896:')
+ call assert_equal(0, len(v:_null_blob))
+endfunc
+
+" assignment to a blob
+func Test_blob_assign()
+ let b = 0zDEADBEEF
+ let b2 = b[1:2]
+ call assert_equal(0zADBE, b2)
+
+ let bcopy = b[:]
+ call assert_equal(b, bcopy)
+ call assert_false(b is bcopy)
+
+ let b = 0zDEADBEEF
+ let b2 = b
+ call assert_true(b is b2)
+ let b[:] = 0z11223344
+ call assert_equal(0z11223344, b)
+ call assert_equal(0z11223344, b2)
+ call assert_true(b is b2)
+
+ let b = 0zDEADBEEF
+ let b[3:] = 0z66
+ call assert_equal(0zDEADBE66, b)
+ let b[:1] = 0z8899
+ call assert_equal(0z8899BE66, b)
+
+ call assert_fails('let b[2:3] = 0z112233', 'E972:')
+ call assert_fails('let b[2:3] = 0z11', 'E972:')
+ call assert_fails('let b[3:2] = 0z', 'E979:')
+
+ let b = 0zDEADBEEF
+ let b += 0z99
+ call assert_equal(0zDEADBEEF99, b)
+
+ call assert_fails('let b .= 0z33', 'E734:')
+ call assert_fails('let b .= "xx"', 'E734:')
+ call assert_fails('let b += "xx"', 'E734:')
+ call assert_fails('let b[1:1] .= 0z55', 'E734:')
+
+ let l = [0z12]
+ let m = deepcopy(l)
+ let m[0] = 0z34 " E742 or E741 should not occur.
+endfunc
+
+func Test_blob_get_range()
+ let b = 0z0011223344
+ call assert_equal(0z2233, b[2:3])
+ call assert_equal(0z223344, b[2:-1])
+ call assert_equal(0z00, b[0:-5])
+ call assert_equal(0z, b[0:-11])
+ call assert_equal(0z44, b[-1:])
+ call assert_equal(0z0011223344, b[:])
+ call assert_equal(0z0011223344, b[:-1])
+ call assert_equal(0z, b[5:6])
+endfunc
+
+func Test_blob_get()
+ let b = 0z0011223344
+ call assert_equal(0x00, get(b, 0))
+ call assert_equal(0x22, get(b, 2, 999))
+ call assert_equal(0x44, get(b, 4))
+ call assert_equal(0x44, get(b, -1))
+ call assert_equal(-1, get(b, 5))
+ call assert_equal(999, get(b, 5, 999))
+ call assert_equal(-1, get(b, -8))
+ call assert_equal(999, get(b, -8, 999))
+ call assert_equal(10, get(v:_null_blob, 2, 10))
+
+ call assert_equal(0x00, b[0])
+ call assert_equal(0x22, b[2])
+ call assert_equal(0x44, b[4])
+ call assert_equal(0x44, b[-1])
+ call assert_fails('echo b[5]', 'E979:')
+ call assert_fails('echo b[-8]', 'E979:')
+endfunc
+
+func Test_blob_to_string()
+ let b = 0z00112233445566778899aabbccdd
+ call assert_equal('0z00112233.44556677.8899AABB.CCDD', string(b))
+ call assert_equal(b, eval(string(b)))
+ call remove(b, 4, -1)
+ call assert_equal('0z00112233', string(b))
+ call remove(b, 0, 3)
+ call assert_equal('0z', string(b))
+endfunc
+
+func Test_blob_compare()
+ let b1 = 0z0011
+ let b2 = 0z1100
+ let b3 = 0z001122
+ call assert_true(b1 == b1)
+ call assert_false(b1 == b2)
+ call assert_false(b1 == b3)
+ call assert_true(b1 != b2)
+ call assert_true(b1 != b3)
+ call assert_true(b1 == 0z0011)
+ call assert_fails('echo b1 == 9', 'E977:')
+ call assert_fails('echo b1 != 9', 'E977:')
+
+ call assert_false(b1 is b2)
+ let b2 = b1
+ call assert_true(b1 == b2)
+ call assert_true(b1 is b2)
+ let b2 = copy(b1)
+ call assert_true(b1 == b2)
+ call assert_false(b1 is b2)
+ let b2 = b1[:]
+ call assert_true(b1 == b2)
+ call assert_false(b1 is b2)
+
+ call assert_fails('let x = b1 > b2')
+ call assert_fails('let x = b1 < b2')
+ call assert_fails('let x = b1 - b2')
+ call assert_fails('let x = b1 / b2')
+ call assert_fails('let x = b1 * b2')
+endfunc
+
+" test for range assign
+func Test_blob_range_assign()
+ let b = 0z00
+ let b[1] = 0x11
+ let b[2] = 0x22
+ call assert_equal(0z001122, b)
+ call assert_fails('let b[4] = 0x33', 'E979:')
+endfunc
+
+func Test_blob_for_loop()
+ let blob = 0z00010203
+ let i = 0
+ for byte in blob
+ call assert_equal(i, byte)
+ let i += 1
+ endfor
+ call assert_equal(4, i)
+
+ let blob = 0z00
+ call remove(blob, 0)
+ call assert_equal(0, len(blob))
+ for byte in blob
+ call assert_error('loop over empty blob')
+ endfor
+
+ let blob = 0z0001020304
+ let i = 0
+ for byte in blob
+ call assert_equal(i, byte)
+ if i == 1
+ call remove(blob, 0)
+ elseif i == 3
+ call remove(blob, 3)
+ endif
+ let i += 1
+ endfor
+ call assert_equal(5, i)
+endfunc
+
+func Test_blob_concatenate()
+ let b = 0z0011
+ let b += 0z2233
+ call assert_equal(0z00112233, b)
+
+ call assert_fails('let b += "a"')
+ call assert_fails('let b += 88')
+
+ let b = 0zDEAD + 0zBEEF
+ call assert_equal(0zDEADBEEF, b)
+endfunc
+
+func Test_blob_add()
+ let b = 0z0011
+ call add(b, 0x22)
+ call assert_equal(0z001122, b)
+ call add(b, '51')
+ call assert_equal(0z00112233, b)
+
+ call assert_fails('call add(b, [9])', 'E745:')
+ call assert_fails('call add("", 0x01)', 'E897:')
+endfunc
+
+func Test_blob_empty()
+ call assert_false(empty(0z001122))
+ call assert_true(empty(0z))
+ call assert_true(empty(v:_null_blob))
+endfunc
+
+" Test removing items in blob
+func Test_blob_func_remove()
+ " Test removing 1 element
+ let b = 0zDEADBEEF
+ call assert_equal(0xDE, remove(b, 0))
+ call assert_equal(0zADBEEF, b)
+
+ let b = 0zDEADBEEF
+ call assert_equal(0xEF, remove(b, -1))
+ call assert_equal(0zDEADBE, b)
+
+ let b = 0zDEADBEEF
+ call assert_equal(0xAD, remove(b, 1))
+ call assert_equal(0zDEBEEF, b)
+
+ " Test removing range of element(s)
+ let b = 0zDEADBEEF
+ call assert_equal(0zBE, remove(b, 2, 2))
+ call assert_equal(0zDEADEF, b)
+
+ let b = 0zDEADBEEF
+ call assert_equal(0zADBE, remove(b, 1, 2))
+ call assert_equal(0zDEEF, b)
+
+ " Test invalid cases
+ let b = 0zDEADBEEF
+ call assert_fails("call remove(b, 5)", 'E979:')
+ call assert_fails("call remove(b, 1, 5)", 'E979:')
+ call assert_fails("call remove(b, 3, 2)", 'E979:')
+ call assert_fails("call remove(1, 0)", 'E896:')
+ call assert_fails("call remove(b, b)", 'E974:')
+ call assert_fails("call remove(v:_null_blob, 1, 2)", 'E979:')
+
+ " Translated from v8.2.3284
+ let b = 0zDEADBEEF
+ lockvar b
+ call assert_fails('call remove(b, 0)', 'E741:')
+ unlockvar b
+endfunc
+
+func Test_blob_read_write()
+ let b = 0zDEADBEEF
+ call writefile(b, 'Xblob')
+ let br = readfile('Xblob', 'B')
+ call assert_equal(b, br)
+ call delete('Xblob')
+
+ " This was crashing when calling readfile() with a directory.
+ call assert_fails("call readfile('.', 'B')", 'E17: "." is a directory')
+endfunc
+
+" filter() item in blob
+func Test_blob_filter()
+ call assert_equal(0z, filter(0zDEADBEEF, '0'))
+ call assert_equal(0zADBEEF, filter(0zDEADBEEF, 'v:val != 0xDE'))
+ call assert_equal(0zDEADEF, filter(0zDEADBEEF, 'v:val != 0xBE'))
+ call assert_equal(0zDEADBE, filter(0zDEADBEEF, 'v:val != 0xEF'))
+ call assert_equal(0zDEADBEEF, filter(0zDEADBEEF, '1'))
+ call assert_equal(0z01030103, filter(0z010203010203, 'v:val != 0x02'))
+ call assert_equal(0zADEF, filter(0zDEADBEEF, 'v:key % 2'))
+endfunc
+
+" map() item in blob
+func Test_blob_map()
+ call assert_equal(0zDFAEBFF0, map(0zDEADBEEF, 'v:val + 1'))
+ call assert_equal(0z00010203, map(0zDEADBEEF, 'v:key'))
+ call assert_equal(0zDEAEC0F2, map(0zDEADBEEF, 'v:key + v:val'))
+
+ call assert_fails("call map(0z00, '[9]')", 'E978:')
+endfunc
+
+func Test_blob_index()
+ call assert_equal(2, index(0zDEADBEEF, 0xBE))
+ call assert_equal(-1, index(0zDEADBEEF, 0))
+ call assert_equal(2, index(0z11111111, 0x11, 2))
+ call assert_equal(3, index(0z11110111, 0x11, 2))
+ call assert_equal(2, index(0z11111111, 0x11, -2))
+ call assert_equal(3, index(0z11110111, 0x11, -2))
+
+ call assert_fails('call index("asdf", 0)', 'E897:')
+endfunc
+
+func Test_blob_insert()
+ let b = 0zDEADBEEF
+ call insert(b, 0x33)
+ call assert_equal(0z33DEADBEEF, b)
+
+ let b = 0zDEADBEEF
+ call insert(b, 0x33, 2)
+ call assert_equal(0zDEAD33BEEF, b)
+
+ call assert_fails('call insert(b, -1)', 'E475:')
+ call assert_fails('call insert(b, 257)', 'E475:')
+ call assert_fails('call insert(b, 0, [9])', 'E745:')
+ call assert_equal(0, insert(v:_null_blob, 0x33))
+
+ " Translated from v8.2.3284
+ let b = 0zDEADBEEF
+ lockvar b
+ call assert_fails('call insert(b, 3)', 'E741:')
+ unlockvar b
+endfunc
+
+func Test_blob_reverse()
+ call assert_equal(0zEFBEADDE, reverse(0zDEADBEEF))
+ call assert_equal(0zBEADDE, reverse(0zDEADBE))
+ call assert_equal(0zADDE, reverse(0zDEAD))
+ call assert_equal(0zDE, reverse(0zDE))
+ call assert_equal(0z, reverse(v:_null_blob))
+endfunc
+
+func Test_blob_lock()
+ let b = 0z112233
+ lockvar b
+ call assert_fails('let b = 0z44', 'E741:')
+ unlockvar b
+ let b = 0z44
+endfunc
+
+func Test_blob_sort()
+ if has('float')
+ call assert_fails('call sort([1.0, 0z11], "f")', 'E975:')
+ else
+ call assert_fails('call sort(["abc", 0z11], "f")', 'E702:')
+ endif
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim
index ea69c8cba4..0d064617a5 100644
--- a/src/nvim/testdir/test_const.vim
+++ b/src/nvim/testdir/test_const.vim
@@ -244,18 +244,33 @@ func Test_const_with_eval_name()
call assert_fails('const {s2} = "bar"', 'E995:')
endfunc
-func Test_lock_depth_is_1()
- const l = [1, 2, 3]
- const d = {'foo': 10}
-
- " Modify list - setting item is OK, adding/removing items not
- let l[0] = 42
+func Test_lock_depth_is_2()
+ " Modify list - error when changing item or adding/removing items
+ const l = [1, 2, [3, 4]]
+ call assert_fails('let l[0] = 42', 'E741:')
+ call assert_fails('let l[2][0] = 42', 'E741:')
call assert_fails('call add(l, 4)', 'E741:')
call assert_fails('unlet l[1]', 'E741:')
- " Modify dict - changing item is OK, adding/removing items not
- let d['foo'] = 'hello'
- let d.foo = 44
+ " Modify blob - error when changing
+ const b = 0z001122
+ call assert_fails('let b[0] = 42', 'E741:')
+
+ " Modify dict - error when changing item or adding/removing items
+ const d = {'foo': 10}
+ call assert_fails("let d['foo'] = 'hello'", 'E741:')
+ call assert_fails("let d.foo = 'hello'", 'E741:')
call assert_fails("let d['bar'] = 'hello'", 'E741:')
call assert_fails("unlet d['foo']", 'E741:')
+
+ " Modifying list or dict item contents is OK.
+ let lvar = ['a', 'b']
+ let bvar = 0z1122
+ const l2 = [0, lvar, bvar]
+ let l2[1][0] = 'c'
+ let l2[2][1] = 0x33
+ call assert_equal([0, ['c', 'b'], 0z1133], l2)
+
+ const d2 = #{a: 0, b: lvar, c: 4}
+ let d2.b[1] = 'd'
endfunc
diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim
index 1e3dab7cbf..f7b6704610 100644
--- a/src/nvim/testdir/test_eval_stuff.vim
+++ b/src/nvim/testdir/test_eval_stuff.vim
@@ -65,9 +65,11 @@ func Test_E963()
endfunc
func Test_for_invalid()
- call assert_fails("for x in 99", 'E714:')
- call assert_fails("for x in function('winnr')", 'E714:')
- call assert_fails("for x in {'a': 9}", 'E714:')
+ " Vim gives incorrect emsg here until v8.2.3284, but the exact emsg from that
+ " patch cannot be used until v8.2.2658 is ported (for loop over Strings)
+ call assert_fails("for x in 99", 'E897:')
+ call assert_fails("for x in function('winnr')", 'E897:')
+ call assert_fails("for x in {'a': 9}", 'E897:')
if 0
/1/5/2/s/\n
diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim
index a15567bcf2..a52a66ac2f 100644
--- a/src/nvim/testdir/test_filter_map.vim
+++ b/src/nvim/testdir/test_filter_map.vim
@@ -81,7 +81,11 @@ func Test_filter_map_dict_expr_funcref()
call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
endfunc
-func Test_map_fails()
+func Test_map_filter_fails()
call assert_fails('call map([1], "42 +")', 'E15:')
call assert_fails('call filter([1], "42 +")', 'E15:')
+ call assert_fails("let l = map('abc', '\"> \" . v:val')", 'E896:')
+ call assert_fails("let l = filter('abc', '\"> \" . v:val')", 'E896:')
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_fnamemodify.vim b/src/nvim/testdir/test_fnamemodify.vim
index 116d23ba88..fe1df8fd4a 100644
--- a/src/nvim/testdir/test_fnamemodify.vim
+++ b/src/nvim/testdir/test_fnamemodify.vim
@@ -72,4 +72,8 @@ func Test_fnamemodify_er()
" :e never includes the whole filename, so "a.b":e:e:e --> "b"
call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e'))
call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e:e'))
+
+ call assert_equal('', fnamemodify(v:_null_string, v:_null_string))
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim
index 5152af8f58..ae035fa519 100644
--- a/src/nvim/testdir/test_listdict.vim
+++ b/src/nvim/testdir/test_listdict.vim
@@ -139,7 +139,7 @@ func Test_list_func_remove()
call assert_fails("call remove(l, 5)", 'E684:')
call assert_fails("call remove(l, 1, 5)", 'E684:')
call assert_fails("call remove(l, 3, 2)", 'E16:')
- call assert_fails("call remove(1, 0)", 'E712:')
+ call assert_fails("call remove(1, 0)", 'E896:')
call assert_fails("call remove(l, l)", 'E745:')
endfunc
@@ -616,6 +616,8 @@ func Test_reverse_sort_uniq()
call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 1))
call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i'))
call assert_equal(['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l)))
+
+ call assert_fails('call reverse("")', 'E899:')
endfunc
" splitting a string to a List
diff --git a/src/nvim/testdir/test_method.vim b/src/nvim/testdir/test_method.vim
index 7a6e6aa19d..d34448e09e 100644
--- a/src/nvim/testdir/test_method.vim
+++ b/src/nvim/testdir/test_method.vim
@@ -46,11 +46,8 @@ func Test_dict_method()
call assert_equal(#{one: 1, two: 2, three: 3, four: 4}, d->extend(#{four: 4}))
call assert_equal(#{one: 1, two: 2, three: 3}, d->filter('v:val != 4'))
call assert_equal(2, d->get('two'))
- " Nvim doesn't support Blobs yet; expect a different emsg
- " call assert_fails("let x = d->index(2)", 'E897:')
- " call assert_fails("let x = d->insert(0)", 'E899:')
- call assert_fails("let x = d->index(2)", 'E714:')
- call assert_fails("let x = d->insert(0)", 'E686:')
+ call assert_fails("let x = d->index(2)", 'E897:')
+ call assert_fails("let x = d->insert(0)", 'E899:')
call assert_true(d->has_key('two'))
call assert_equal([['one', 1], ['two', 2], ['three', 3]], d->items())
call assert_fails("let x = d->join()", 'E714:')
@@ -63,9 +60,7 @@ func Test_dict_method()
call assert_equal(2, d->remove("two"))
let d.two = 2
call assert_fails('let x = d->repeat(2)', 'E731:')
- " Nvim doesn't support Blobs yet; expect a different emsg
- " call assert_fails('let x = d->reverse()', 'E899:')
- call assert_fails('let x = d->reverse()', 'E686:')
+ call assert_fails('let x = d->reverse()', 'E899:')
call assert_fails('let x = d->sort()', 'E686:')
call assert_equal("{'one': 1, 'two': 2, 'three': 3}", d->string())
call assert_equal(v:t_dict, d->type())
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 283e7bbafe..18587b9b2c 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -2221,6 +2221,10 @@ func Xproperty_tests(cchar)
call g:Xsetlist([], 'a', {'context':246})
let d = g:Xgetlist({'context':1})
call assert_equal(246, d.context)
+ " set other Vim data types as context
+ call g:Xsetlist([], 'a', {'context' : v:_null_blob})
+ call g:Xsetlist([], 'a', {'context' : ''})
+ call test_garbagecollect_now()
if a:cchar == 'l'
" Test for copying context across two different location lists
new | only
diff --git a/src/nvim/testdir/test_rename.vim b/src/nvim/testdir/test_rename.vim
index e4228188bd..2311caf790 100644
--- a/src/nvim/testdir/test_rename.vim
+++ b/src/nvim/testdir/test_rename.vim
@@ -95,7 +95,6 @@ func Test_rename_copy()
endfunc
func Test_rename_fails()
- throw 'skipped: TODO: '
call writefile(['foo'], 'Xrenamefile')
" Can't rename into a non-existing directory.
diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim
index 02bc297de1..e3101d4e44 100644
--- a/src/nvim/testdir/test_swap.vim
+++ b/src/nvim/testdir/test_swap.vim
@@ -168,7 +168,6 @@ func Test_swapname()
endfunc
func Test_swapfile_delete()
- throw 'skipped: need the "blob" feature for this test'
autocmd! SwapExists
function s:swap_exists()
let v:swapchoice = s:swap_choice
diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim
index 54caed3983..c7dcaa0f36 100644
--- a/src/nvim/testdir/test_undo.vim
+++ b/src/nvim/testdir/test_undo.vim
@@ -368,7 +368,6 @@ endfunc
" Check that reading a truncted undo file doesn't hang.
func Test_undofile_truncated()
- throw 'skipped: TODO: '
new
call setline(1, 'hello')
set ul=100
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index d5837e88c9..b18ce563d3 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -1152,6 +1152,10 @@ func Test_type()
call assert_equal(v:t_float, type(0.0))
call assert_equal(v:t_bool, type(v:false))
call assert_equal(v:t_bool, type(v:true))
+ call assert_equal(v:t_string, type(v:_null_string))
+ call assert_equal(v:t_list, type(v:_null_list))
+ call assert_equal(v:t_dict, type(v:_null_dict))
+ call assert_equal(v:t_blob, type(v:_null_blob))
endfunc
"-------------------------------------------------------------------------------
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index 6922e2185d..2504fcb14e 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -17,6 +17,8 @@ func Test_writefile()
call assert_equal("morning", l[3])
call assert_equal("vimmers", l[4])
call delete(f)
+
+ call assert_fails('call writefile("text", "Xfile")', 'E475: Invalid argument: writefile() first argument must be a List or a Blob')
endfunc
func Test_writefile_ignore_regexp_error()
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index c719c064e2..d84979f6fe 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -102,6 +102,7 @@ typedef enum {
#define VAR_TYPE_FLOAT 5
#define VAR_TYPE_BOOL 6
#define VAR_TYPE_SPECIAL 7
+#define VAR_TYPE_BLOB 10
// values for xp_context when doing command line completion