Merge pull request #15211 from seandewar/blob-port

Port VimL's Blob type - vim-patch:8.1.{0735,0736,0738,0741,0742,0755,0756,0757,0765,0793,0797,0798,0802,1022,1023,1671},8.2.{0121,0184,0404,0521,0829,1473,1866,2712}
This commit is contained in:
Jan Edmund Lazo
2021-09-16 16:31:41 -04:00
committed by GitHub
46 changed files with 2013 additions and 370 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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}},

View File

@@ -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 {

View File

@@ -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

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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.

View File

@@ -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);

View File

@@ -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"));

View File

@@ -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

View File

@@ -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: {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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())

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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
"-------------------------------------------------------------------------------

View File

@@ -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()

View File

@@ -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