vim-patch:8.1.0735: cannot handle binary data

Problem:    Cannot handle binary data.
Solution:   Add the Blob type. (Yasuhiro Matsumoto, closes vim/vim#3638)
6e5ea8d2a9

Nvim-specific Blob conversions are implemented in future commits.

Refactor write_blob() to use a FileDescriptor, as f_writefile() was
refactored to use one (does not apply to read_blob()).

Use var_check_lock() in f_add() for Blobs from v8.1.0897.

Add a modeline to test_blob.vim and fix some doc typos.

Include if_perl.txt's VIM::Blob() documentation. Interestingly, this
function already worked before this port, as it just returns a Blob
string literal, not an actual Blob object.

N/A patches for version.c:

vim-patch:8.1.0741: viminfo with Blob is not tested

Problem:    Viminfo with Blob is not tested.
Solution:   Extend the viminfo test.  Fix reading a blob.  Fixed storing a
            special variable value.
8c8b8bb56c

vim-patch:8.1.1022: may use NULL pointer when out of memory

Problem:    May use NULL pointer when out of memory. (Coverity)
Solution:   Check for blob_alloc() returning NULL.
e142a9467a
This commit is contained in:
Sean Dewar
2020-11-18 03:11:00 +00:00
parent 685cf39813
commit 9095101743
18 changed files with 1029 additions and 105 deletions

View File

@@ -15,7 +15,7 @@ Using expressions is introduced in chapter 41 of the user manual |usr_41.txt|.
1.1 Variable types ~
*E712*
There are six types of variables:
There are seven types of variables:
*Number* *Integer*
Number A 32 or 64 bit signed number. |expr-number|
@@ -43,6 +43,10 @@ Dictionary An associative, unordered array: Each entry has a key and a
{'blue': "#0000ff", 'red': "#ff0000"}
#{blue: "#0000ff", red: "#ff0000"}
Blob Binary Large Object. Stores any sequence of bytes. *Blob*
Example: 0zFF00ED015DAF
0z is an empty Blob.
The Number and String types are converted automatically, depending on how they
are used.
@@ -97,6 +101,7 @@ Note that " " and "0" are also non-empty strings, thus considered to be TRUE.
A List, Dictionary or Float is not a Number or String, thus evaluate to FALSE.
*E745* *E728* *E703* *E729* *E730* *E731*
*E974* *E975* *E976*
|List|, |Dictionary|, |Funcref|, and |Blob| types are not automatically
converted.
@@ -1012,6 +1017,12 @@ just above. Also see |sublist| below. Examples: >
:let l = mylist[4:4] " List with one item
:let l = mylist[:] " shallow copy of a List
If expr8 is a |Blob| this results in a new |Blob| with the bytes in the
indexes expr1a and expr1b, inclusive. Examples: >
:let b = 0zDEADBEEF
:let bs = b[1:2] " 0zADBE
:let bs = b[] " copy of 0zDEADBEEF
Using expr8[expr1] or expr8[expr1a : expr1b] on a |Funcref| results in an
error.
@@ -1180,6 +1191,14 @@ encodings. Use "\u00ff" to store character 255 correctly as UTF-8.
Note that "\000" and "\x00" force the end of the string.
blob-literal *blob-literal* *E973* *E977* *E978*
------------
Hexadecimal starting with 0z or 0Z, with an arbitrary number of bytes.
The sequence must be an even number of hex characters. Example: >
:let b = 0zFF00ED015DAF
literal-string *literal-string* *E115*
---------------
'string' string constant *expr-'*
@@ -2020,6 +2039,8 @@ v:t_list Value of List type. Read-only. See: |type()|
v:t_number Value of Number type. Read-only. See: |type()|
*v:t_string* *t_string-variable*
v:t_string Value of String type. Read-only. See: |type()|
*v:t_blob* *t_blob-variable*
v:t_blob Value of Blob type. Read-only. See: |type()|
*v:termresponse* *termresponse-variable*
v:termresponse The escape sequence returned by the terminal for the DA
@@ -2315,8 +2336,8 @@ hlID({name}) Number syntax ID of highlight group {name}
hostname() String name of the machine Vim is running on
iconv({expr}, {from}, {to}) String convert encoding of {expr}
indent({lnum}) Number indent of line {lnum}
index({list}, {expr} [, {start} [, {ic}]])
Number index in {list} where {expr} appears
index({object}, {expr} [, {start} [, {ic}]])
Number index in {object} where {expr} appears
input({prompt} [, {text} [, {completion}]])
String get input from the user
inputlist({textlist}) Number let the user pick from a choice list
@@ -2324,8 +2345,8 @@ inputrestore() Number restore typeahead
inputsave() Number save and clear typeahead
inputsecret({prompt} [, {text}])
String like input() but hiding the text
insert({list}, {item} [, {idx}])
List insert {item} in {list} [before {idx}]
insert({object}, {item} [, {idx}])
List insert {item} in {object} [before {idx}]
interrupt() none interrupt script execution
invert({expr}) Number bitwise invert
isdirectory({directory}) Number |TRUE| if {directory} is a directory
@@ -2407,7 +2428,7 @@ pyxeval({expr}) any evaluate |python_x| expression
range({expr} [, {max} [, {stride}]])
List items from {expr} to {max}
readdir({dir} [, {expr}]) List file names in {dir} selected by {expr}
readfile({fname} [, {binary} [, {max}]])
readfile({fname} [, {type} [, {max}]])
List get list of lines from file {fname}
reg_executing() String get the executing register name
reg_recording() String get the recording register name
@@ -2612,8 +2633,8 @@ winrestview({dict}) none restore view of current window
winsaveview() Dict save view of current window
winwidth({nr}) Number width of window {nr}
wordcount() Dict get byte/char/word statistics
writefile({list}, {fname} [, {flags}])
Number write list of lines to file {fname}
writefile({object}, {fname} [, {flags}])
Number write |Blob| or |List| of lines to file
xor({expr}, {expr}) Number bitwise XOR
@@ -5548,17 +5569,21 @@ indent({lnum}) The result is a Number, which is indent of line {lnum} in the
When {lnum} is invalid -1 is returned.
index({list}, {expr} [, {start} [, {ic}]]) *index()*
Return the lowest index in |List| {list} where the item has a
value equal to {expr}. There is no automatic conversion, so
the String "4" is different from the Number 4. And the number
4 is different from the Float 4.0. The value of 'ignorecase'
is not used here, case always matters.
index({object}, {expr} [, {start} [, {ic}]]) *index()*
If {object} is a |List| return the lowest index where the item
has a value equal to {expr}. There is no automatic
conversion, so the String "4" is different from the Number 4.
And the number 4 is different from the Float 4.0. The value
of 'ignorecase' is not used here, case always matters.
If {object} is |Blob| return the lowest index where the byte
value is equal to {expr}.
If {start} is given then start looking at the item with index
{start} (may be negative for an item relative to the end).
When {ic} is given and it is |TRUE|, ignore case. Otherwise
case must match.
-1 is returned when {expr} is not found in {list}.
-1 is returned when {expr} is not found in {object}.
Example: >
:let idx = index(words, "the")
:if index(numbers, 123) >= 0
@@ -5717,13 +5742,16 @@ inputsecret({prompt} [, {text}]) *inputsecret()*
typed on the command-line in response to the issued prompt.
NOTE: Command-line completion is not supported.
insert({list}, {item} [, {idx}]) *insert()*
Insert {item} at the start of |List| {list}.
insert({object}, {item} [, {idx}]) *insert()*
When {object} is a |List| or a |Blob| insert {item} at the start
of it.
If {idx} is specified insert {item} before the item with index
{idx}. If {idx} is zero it goes before the first item, just
like omitting {idx}. A negative {idx} is also possible, see
|list-index|. -1 inserts just before the last item.
Returns the resulting |List|. Examples: >
Returns the resulting |List| or |Blob|. Examples: >
:let mylist = insert([2, 3, 5], 1)
:call insert(mylist, 4, -1)
:call insert(mylist, 6, len(mylist))
@@ -5787,16 +5815,16 @@ islocked({expr}) *islocked()* *E786*
id({expr}) *id()*
Returns a |String| which is a unique identifier of the
container type (|List|, |Dict| and |Partial|). It is
container type (|List|, |Dict|, |Blob| and |Partial|). It is
guaranteed that for the mentioned types `id(v1) ==# id(v2)`
returns true iff `type(v1) == type(v2) && v1 is v2`.
Note that |v:_null_string|, |v:_null_list|, and |v:_null_dict|
have the same `id()` with different types because they are
internally represented as a NULL pointers. `id()` returns a
hexadecimal representanion of the pointers to the containers
(i.e. like `0x994a40`), same as `printf("%p", {expr})`,
but it is advised against counting on the exact format of
return value.
Note that |v:_null_string|, |v:_null_list|, |v:_null_dict| and
|v:_null_blob| have the same `id()` with different types
because they are internally represented as NULL pointers.
`id()` returns a hexadecimal representanion of the pointers to
the containers (i.e. like `0x994a40`), same as `printf("%p",
{expr})`, but it is advised against counting on the exact
format of the return value.
It is not guaranteed that `id(no_longer_existing_container)`
will not be equal to some other `id()`: new containers may
@@ -7168,16 +7196,18 @@ readdir({directory} [, {expr}])
echo s:tree(".")
<
*readfile()*
readfile({fname} [, {binary} [, {max}]])
readfile({fname} [, {type} [, {max}]])
Read file {fname} and return a |List|, each line of the file
as an item. Lines are broken at NL characters. Macintosh
files separated with CR will result in a single long line
(unless a NL appears somewhere).
All NUL characters are replaced with a NL character.
When {binary} contains "b" binary mode is used:
When {type} contains "b" binary mode is used:
- When the last line ends in a NL an extra empty list item is
added.
- No CR characters are removed.
When {type} contains "B" a |Blob| is returned with the binary
data of the file unmodified.
Otherwise:
- CR characters that appear before a NL are removed.
- Whether the last line ends in a NL or not does not matter.
@@ -7357,6 +7387,17 @@ remove({list}, {idx} [, {end}]) *remove()*
< Can also be used as a |method|: >
mylist->remove(idx)
remove({blob}, {idx} [, {end}])
Without {end}: Remove the byte at {idx} from |Blob| {blob} and
return the byte.
With {end}: Remove bytes from {idx} to {end} (inclusive) and
return a |Blob| with these bytes. When {idx} points to the same
byte as {end} a |Blob| with one byte is returned. When {end}
points to a byte before {idx} this is an error.
Example: >
:echo "last byte: " . remove(myblob, -1)
:call remove(mylist, 0, 9)
remove({dict}, {key})
Remove the entry from {dict} with key {key} and return it.
Example: >
@@ -7400,9 +7441,11 @@ resolve({filename}) *resolve()* *E655*
path name) and also keeps a trailing path separator.
*reverse()*
reverse({list}) Reverse the order of items in {list} in-place. Returns
{list}.
If you want a list to remain unmodified make a copy first: >
reverse({object})
Reverse the order of items in {object} in-place.
{object} can be a |List| or a |Blob|.
Returns {object}.
If you want an object to remain unmodified make a copy first: >
:let revlist = reverse(copy(mylist))
< Can also be used as a |method|: >
mylist->reverse()
@@ -9483,6 +9526,7 @@ type({expr}) *type()*
Float: 5 (|v:t_float|)
Boolean: 6 (|v:true| and |v:false|)
Null: 7 (|v:null|)
Blob: 10 (|v:t_blob|)
For backward compatibility, this method can be used: >
:if type(myvar) == type(0)
:if type(myvar) == type("")
@@ -9927,14 +9971,17 @@ wordcount() *wordcount()*
*writefile()*
writefile({list}, {fname} [, {flags}])
Write |List| {list} to file {fname}. Each list item is
separated with a NL. Each list item must be a String or
Number.
writefile({object}, {fname} [, {flags}])
When {object} is a |List| write it to file {fname}. Each list
item is separated with a NL. Each list item must be a String
or Number.
When {flags} contains "b" then binary mode is used: There will
not be a NL after the last list item. An empty item at the
end does cause the last line in the file to end in a NL.
When {object} is a |Blob| write the bytes to file {fname}
unmodified.
When {flags} contains "a" then append mode is used, lines are
appended to the file: >
:call writefile(["foo"], "event.log", "a")
@@ -10450,7 +10497,10 @@ This does NOT work: >
This cannot be used to set a byte in a String. You
can do that like this: >
:let var = var[0:2] . 'X' . var[4:]
<
< When {var-name} is a |Blob| then {idx} can be the
length of the blob, in which case one byte is
appended.
*E711* *E719*
:let {var-name}[{idx1}:{idx2}] = {expr1} *E708* *E709* *E710*
Set a sequence of items in a |List| to the result of

View File

@@ -189,6 +189,9 @@ VIM::Eval({expr}) Evaluates {expr} and returns (success, value) in list
A |List| is turned into a string by joining the items
and inserting line breaks.
*perl-Blob*
VIM::Blob({expr}) Return Blob literal string 0zXXXX from scalar value.
==============================================================================
3. VIM::Buffer objects *perl-buffer*

View File

@@ -444,6 +444,9 @@ 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) \
abort() /* TODO(seandewar) */ \
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
do { \
TYPVAL_ENCODE_CONV_NIL(tv); \
@@ -584,6 +587,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

@@ -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),
@@ -251,6 +255,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 +398,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 +2065,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 +2124,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 +2242,28 @@ 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);
if (lp->ll_n1 < 0 || lp->ll_n1 > tv_blob_len(lp->ll_tv->vval.v_blob)) {
if (!quiet) {
EMSGN(_(e_listidx), lp->ll_n1);
}
return NULL;
}
if (lp->ll_range && !lp->ll_empty2) {
lp->ll_n2 = (long)tv_get_number(&var2);
tv_clear(&var2);
}
lp->ll_blob = lp->ll_tv->vval.v_blob;
lp->ll_tv = NULL;
} else {
// Get the number and item for the only or first index of the List.
if (empty1) {
@@ -2329,7 +2357,41 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
if (lp->ll_tv == NULL) {
cc = *endp;
*endp = NUL;
if (lp->ll_blob != NULL) {
if (op != NULL && *op != '=') {
EMSG2(_(e_letwrong), op);
return;
}
if (lp->ll_range && rettv->v_type == VAR_BLOB) {
if (tv_blob_len(rettv->vval.v_blob) != tv_blob_len(lp->ll_blob)) {
EMSG(_("E972: Blob value has more items than target"));
return;
}
for (int i = lp->ll_n1; i <= lp->ll_n2; i++) {
tv_blob_set(lp->ll_blob, i, tv_blob_get(rettv->vval.v_blob, i));
}
} 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++;
}
} else {
EMSG(_(e_invrange));
}
}
}
} else if (op != NULL && *op != '=') {
typval_T tv;
if (is_const) {
@@ -2508,20 +2570,30 @@ 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) {
if (tv.v_type == VAR_LIST) {
l = tv.vval.v_list;
if (tv.v_type != VAR_LIST) {
EMSG(_(e_listreq));
tv_clear(&tv);
} else if (l == NULL) {
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". */
// 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) {
blob_T *const b = tv.vval.v_blob;
if (b == NULL) {
tv_clear(&tv);
} else {
fi->fi_blob = b;
fi->fi_bi = 0;
}
} else {
EMSG(_(e_listreq));
tv_clear(&tv);
}
}
}
if (skip)
@@ -2542,6 +2614,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;
@@ -3607,7 +3692,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 +3738,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.
@@ -3850,6 +3950,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 +4047,31 @@ 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])) {
EMSG(_("E973: Blob literal should have an even number of hex "
"characters"));
xfree(blob);
ret = FAIL;
break;
}
if (blob != NULL) {
ga_append(&blob->bv_ga, (hex2nr(*bp) << 4) + hex2nr(*(bp + 1)));
}
}
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 +4461,8 @@ eval_index(
case VAR_STRING:
case VAR_NUMBER:
case VAR_LIST:
case VAR_DICT: {
case VAR_DICT:
case VAR_BLOB: {
break;
}
}
@@ -4462,6 +4588,51 @@ 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 substring. 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 string of a single
// character. If the index is too big or negative the
// result is empty.
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 +5569,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 +6333,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 +6343,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
@@ -6234,6 +6412,30 @@ 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;
tv.vval.v_number = tv_blob_get(b, i);
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;
}
tv.v_type = VAR_NUMBER;
tv_blob_set(b, i, tv.vval.v_number);
if (!map && rem) {
char_u *const p = (char_u *)argvars[0].vval.v_blob->bv_ga.ga_data;
memmove(p + idx, p + i + 1, (size_t)b->bv_ga.ga_len - i - 1);
b->bv_ga.ga_len--;
i--;
}
}
} else {
assert(argvars[0].v_type == VAR_LIST);
vimvars[VV_KEY].vv_type = VAR_NUMBER;
@@ -7728,10 +7930,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.
@@ -9423,6 +9676,7 @@ int var_item_copy(const vimconv_T *const conv,
case VAR_PARTIAL:
case VAR_BOOL:
case VAR_SPECIAL:
case VAR_BLOB:
tv_copy(from, to);
break;
case VAR_STRING:
@@ -10815,6 +11069,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,

View File

@@ -319,6 +319,28 @@ 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, "[]"); \
} else { \
ga_grow(gap, 1 + len_ * 5); \
ga_append(gap, '['); \
char numbuf[NUMBUFLEN]; \
for (int i_ = 0; i_ < len_; i_++) { \
if (i_ > 0) { \
ga_append(gap, ','); \
} \
vim_snprintf((char *)numbuf, ARRAY_SIZE(numbuf), "0x%02X", \
(int)tv_blob_get(blob_, i_)); \
ga_concat(gap, numbuf); \
} \
ga_append(gap, ']'); \
} \
} while (0)
#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
do { \
char numbuf[NUMBUFLEN]; \
@@ -705,6 +727,10 @@ 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) \
abort() /* TODO(seandewar) */ \
#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 +796,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 +931,9 @@ char *encode_tv2json(typval_T *tv, size_t *len)
} \
} while (0)
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
abort() /* TODO(seandewar) */ \
#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
msgpack_pack_int64(packer, (int64_t)(num))
@@ -982,6 +1012,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

@@ -321,6 +321,13 @@ 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)) {
ga_append(&b->bv_ga, (char_u)tv_get_number(&argvars[1]));
tv_copy(&argvars[0], rettv);
}
} else {
EMSG(_(e_listreq));
}
@@ -1874,6 +1881,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 +2802,19 @@ 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;
const int idx = tv_get_number_chk(&argvars[1], &error);
if (!error) {
rettv->v_type = VAR_NUMBER;
if (idx >= tv_blob_len(argvars[0].vval.v_blob)) {
EMSGN(_(e_blobidx), idx);
} else {
rettv->vval.v_number = tv_blob_get(argvars[0].vval.v_blob, idx);
}
}
} else if (argvars[0].v_type == VAR_LIST) {
if ((l = argvars[0].vval.v_list) != NULL) {
bool error = false;
@@ -4790,7 +4813,31 @@ 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) {
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;
}
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_listreq));
return;
}
@@ -4920,7 +4967,37 @@ 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) {
if (argvars[0].v_type == VAR_BLOB) {
long before = 0;
const int len = tv_blob_len(argvars[0].vval.v_blob);
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(&argvars[0].vval.v_blob->bv_ga, 1);
char_u *const p = (char_u *)argvars[0].vval.v_blob->bv_ga.ga_data;
memmove(p + before + 1, p + before, (size_t)len - before);
*(p + before) = val;
argvars[0].vval.v_blob->bv_ga.ga_len++;
tv_copy(&argvars[0], rettv);
} else if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listarg), "insert()");
} else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
N_("insert() argument"), TV_TRANSLATE)) {
@@ -5581,6 +5658,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;
@@ -6894,6 +6975,7 @@ static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
bool binary = false;
bool blob = false;
FILE *fd;
char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1
int io_size = sizeof(buf);
@@ -6906,14 +6988,14 @@ 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]);
@@ -6922,6 +7004,18 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
if (blob) {
tv_blob_alloc_ret(rettv);
if (!read_blob(fd, rettv->vval.v_blob)) {
EMSG("cannot read file");
tv_blob_free(rettv->vval.v_blob);
}
fclose(fd);
return;
}
list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown);
while (maxline < 0 || tv_list_len(l) < maxline) {
readlen = (int)fread(buf, 1, io_size, fd);
@@ -7190,6 +7284,55 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
}
} else if (argvars[0].v_type == VAR_BLOB) {
bool error = false;
idx = (long)tv_get_number_chk(&argvars[1], &error);
if (!error) {
blob_T *const b = argvars[0].vval.v_blob;
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 list 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);
memmove(p + idx, p + end + 1, (size_t)(len - end));
b->bv_ga.ga_len -= end - idx + 1;
}
}
} else if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listdictarg), "remove()");
} else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
@@ -7465,14 +7608,26 @@ 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) {
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_listarg), "reverse()");
} else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
N_("reverse() argument"), TV_TRANSLATE)) {
} 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);
}
}
}
#define SP_NOMOVE 0x01 ///< don't move cursor
@@ -11299,7 +11454,8 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr)
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_SPECIAL: n = VAR_TYPE_SPECIAL; break;
case VAR_BLOB: n = VAR_TYPE_BLOB; break;
case VAR_UNKNOWN: {
internal_error("f_type(UNKNOWN)");
break;
@@ -11678,16 +11834,16 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listarg), "writefile()");
return;
}
const list_T *const list = argvars[0].vval.v_list;
TV_LIST_ITER_CONST(list, li, {
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()");
return;
}
bool binary = false;
bool append = false;
@@ -11727,7 +11883,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,73 @@ 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
{
if (b1 == NULL || b2 == NULL) {
return false;
}
if (b1 == b2) {
return true;
}
if (tv_blob_len(b1) != tv_blob_len(b2)) {
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 +2236,18 @@ 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);
}
//{{{3 Clear
#define TYPVAL_ENCODE_ALLOW_SPECIALS false
@@ -2210,6 +2289,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 +2478,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 +2536,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 +2600,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;
@@ -2560,6 +2657,13 @@ 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) {
CHANGE_LOCK(lock, b->bv_lock);
}
break;
}
case VAR_LIST: {
list_T *const l = tv->vval.v_list;
if (l != NULL) {
@@ -2646,10 +2750,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 +2874,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 +2943,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 +2972,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 +3001,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 +3019,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 +3048,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 +3096,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 +3192,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 +3255,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,6 +939,8 @@ 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_(

View File

@@ -481,6 +481,9 @@ 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) \
abort() /* TODO(seandewar) */ \
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
do { \
TYPVAL_ENCODE_CONV_NIL(tv); \
@@ -579,6 +582,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

@@ -0,0 +1,176 @@
" 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 x = get(b, 4)')
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)
endfunc
func Test_blob_to_string()
let b = 0zDEADBEEF
call assert_equal('[0xDE,0xAD,0xBE,0xEF]', string(b))
call remove(b, 0, 3)
call assert_equal('[]', string(b))
endfunc
func Test_blob_compare()
let b1 = 0z0011
let b2 = 0z1100
call assert_false(b1 == b2)
call assert_true(b1 != b2)
call assert_true(b1 == 0z0011)
call assert_false(b1 is b2)
let b2 = b1
call assert_true(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')
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
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
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
" 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)", 'E712:')
call assert_fails("call remove(b, b)", 'E974:')
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')
endfunc
" filter() item in blob
func Test_blob_filter()
let b = 0zDEADBEEF
call filter(b, 'v:val != 0xEF')
call assert_equal(0zDEADBE, b)
endfunc
" map() item in blob
func Test_blob_map()
let b = 0zDEADBEEF
call map(b, 'v:val + 1')
call assert_equal(0zDFAEBFF0, b)
endfunc
func Test_blob_index()
call assert_equal(2, index(0zDEADBEEF, 0xBE))
call assert_equal(-1, index(0zDEADBEEF, 0))
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)
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))
endfunc
" vim: shiftwidth=2 sts=2 expandtab

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

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

View File

@@ -44,7 +44,7 @@ describe('NULL', function()
-- Incorrect behaviour
-- FIXME Should error out with different message
null_test('makes :unlet act as if it is not a list', ':unlet L[0]',
'Vim(unlet):E689: Can only index a List or Dictionary')
'Vim(unlet):E689: Can only index a List, Dictionary or Blob')
-- Subjectable behaviour

View File

@@ -119,7 +119,7 @@ describe('writefile()', function()
eq('\nE118: Too many arguments for function: writefile',
redir_exec(('call writefile([], "%s", "b", 1)'):format(fname)))
for _, arg in ipairs({'0', '0.0', 'function("tr")', '{}', '"test"'}) do
eq('\nE686: Argument of writefile() must be a List',
eq('\nE475: Invalid argument: writefile()',
redir_exec(('call writefile(%s, "%s", "b")'):format(arg, fname)))
end
for _, args in ipairs({'[], %s, "b"', '[], "' .. fname .. '", %s'}) do