mirror of
https://github.com/neovim/neovim.git
synced 2025-09-25 04:28:33 +00:00
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:
@@ -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
|
||||
|
@@ -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*
|
||||
|
||||
|
@@ -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
|
||||
|
319
src/nvim/eval.c
319
src/nvim/eval.c
@@ -69,6 +69,7 @@ static char *e_nowhitespace
|
||||
= N_("E274: No white space allowed before parenthesis");
|
||||
static char *e_invalwindow = N_("E957: Invalid window number");
|
||||
static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
|
||||
static char *e_write2 = N_("E80: Error while writing: %s");
|
||||
|
||||
// TODO(ZyX-I): move to eval/executor
|
||||
static char *e_letwrong = N_("E734: Wrong variable type for %s=");
|
||||
@@ -113,6 +114,8 @@ typedef struct {
|
||||
int fi_varcount; // nr of variables in the list
|
||||
listwatch_T fi_lw; // keep an eye on the item used.
|
||||
list_T *fi_list; // list being used
|
||||
int fi_bi; // index of blob
|
||||
blob_T *fi_blob; // blob being used
|
||||
} forinfo_T;
|
||||
|
||||
// values for vv_flags:
|
||||
@@ -227,6 +230,7 @@ static struct vimvar {
|
||||
VV(VV_TYPE_DICT, "t_dict", VAR_NUMBER, VV_RO),
|
||||
VV(VV_TYPE_FLOAT, "t_float", VAR_NUMBER, VV_RO),
|
||||
VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO),
|
||||
VV(VV_TYPE_BLOB, "t_blob", VAR_NUMBER, VV_RO),
|
||||
VV(VV_EVENT, "event", VAR_DICT, VV_RO),
|
||||
VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO),
|
||||
VV(VV_ARGV, "argv", VAR_LIST, VV_RO),
|
||||
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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,15 +7608,27 @@ 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
|
||||
#define SP_REPEAT 0x02 ///< repeat to find outer pair
|
||||
@@ -11300,6 +11455,7 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
case VAR_FLOAT: n = VAR_TYPE_FLOAT; break;
|
||||
case VAR_BOOL: n = VAR_TYPE_BOOL; break;
|
||||
case VAR_SPECIAL: n = VAR_TYPE_SPECIAL; break;
|
||||
case VAR_BLOB: n = VAR_TYPE_BLOB; break;
|
||||
case VAR_UNKNOWN: {
|
||||
internal_error("f_type(UNKNOWN)");
|
||||
break;
|
||||
@@ -11678,16 +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) {
|
||||
|
@@ -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;
|
||||
|
@@ -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.
|
||||
|
@@ -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);
|
||||
|
@@ -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_(
|
||||
|
@@ -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
|
||||
|
176
src/nvim/testdir/test_blob.vim
Normal file
176
src/nvim/testdir/test_blob.vim
Normal 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
|
@@ -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())
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user