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 ~ 1.1 Variable types ~
*E712* *E712*
There are six types of variables: There are seven types of variables:
*Number* *Integer* *Number* *Integer*
Number A 32 or 64 bit signed number. |expr-number| 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"}
#{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 The Number and String types are converted automatically, depending on how they
are used. 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. A List, Dictionary or Float is not a Number or String, thus evaluate to FALSE.
*E745* *E728* *E703* *E729* *E730* *E731* *E745* *E728* *E703* *E729* *E730* *E731*
*E974* *E975* *E976*
|List|, |Dictionary|, |Funcref|, and |Blob| types are not automatically |List|, |Dictionary|, |Funcref|, and |Blob| types are not automatically
converted. converted.
@@ -1012,6 +1017,12 @@ just above. Also see |sublist| below. Examples: >
:let l = mylist[4:4] " List with one item :let l = mylist[4:4] " List with one item
:let l = mylist[:] " shallow copy of a List :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 Using expr8[expr1] or expr8[expr1a : expr1b] on a |Funcref| results in an
error. 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. 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* literal-string *literal-string* *E115*
--------------- ---------------
'string' string constant *expr-'* '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_number Value of Number type. Read-only. See: |type()|
*v:t_string* *t_string-variable* *v:t_string* *t_string-variable*
v:t_string Value of String type. Read-only. See: |type()| 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* *termresponse-variable*
v:termresponse The escape sequence returned by the terminal for the DA 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 hostname() String name of the machine Vim is running on
iconv({expr}, {from}, {to}) String convert encoding of {expr} iconv({expr}, {from}, {to}) String convert encoding of {expr}
indent({lnum}) Number indent of line {lnum} indent({lnum}) Number indent of line {lnum}
index({list}, {expr} [, {start} [, {ic}]]) index({object}, {expr} [, {start} [, {ic}]])
Number index in {list} where {expr} appears Number index in {object} where {expr} appears
input({prompt} [, {text} [, {completion}]]) input({prompt} [, {text} [, {completion}]])
String get input from the user String get input from the user
inputlist({textlist}) Number let the user pick from a choice list 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 inputsave() Number save and clear typeahead
inputsecret({prompt} [, {text}]) inputsecret({prompt} [, {text}])
String like input() but hiding the text String like input() but hiding the text
insert({list}, {item} [, {idx}]) insert({object}, {item} [, {idx}])
List insert {item} in {list} [before {idx}] List insert {item} in {object} [before {idx}]
interrupt() none interrupt script execution interrupt() none interrupt script execution
invert({expr}) Number bitwise invert invert({expr}) Number bitwise invert
isdirectory({directory}) Number |TRUE| if {directory} is a directory isdirectory({directory}) Number |TRUE| if {directory} is a directory
@@ -2407,7 +2428,7 @@ pyxeval({expr}) any evaluate |python_x| expression
range({expr} [, {max} [, {stride}]]) range({expr} [, {max} [, {stride}]])
List items from {expr} to {max} List items from {expr} to {max}
readdir({dir} [, {expr}]) List file names in {dir} selected by {expr} 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} List get list of lines from file {fname}
reg_executing() String get the executing register name reg_executing() String get the executing register name
reg_recording() String get the recording 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 winsaveview() Dict save view of current window
winwidth({nr}) Number width of window {nr} winwidth({nr}) Number width of window {nr}
wordcount() Dict get byte/char/word statistics wordcount() Dict get byte/char/word statistics
writefile({list}, {fname} [, {flags}]) writefile({object}, {fname} [, {flags}])
Number write list of lines to file {fname} Number write |Blob| or |List| of lines to file
xor({expr}, {expr}) Number bitwise XOR 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. When {lnum} is invalid -1 is returned.
index({list}, {expr} [, {start} [, {ic}]]) *index()* index({object}, {expr} [, {start} [, {ic}]]) *index()*
Return the lowest index in |List| {list} where the item has a If {object} is a |List| return the lowest index where the item
value equal to {expr}. There is no automatic conversion, so has a value equal to {expr}. There is no automatic
the String "4" is different from the Number 4. And the number conversion, so the String "4" is different from the Number 4.
4 is different from the Float 4.0. The value of 'ignorecase' And the number 4 is different from the Float 4.0. The value
is not used here, case always matters. 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 If {start} is given then start looking at the item with index
{start} (may be negative for an item relative to the end). {start} (may be negative for an item relative to the end).
When {ic} is given and it is |TRUE|, ignore case. Otherwise When {ic} is given and it is |TRUE|, ignore case. Otherwise
case must match. case must match.
-1 is returned when {expr} is not found in {list}. -1 is returned when {expr} is not found in {object}.
Example: > Example: >
:let idx = index(words, "the") :let idx = index(words, "the")
:if index(numbers, 123) >= 0 :if index(numbers, 123) >= 0
@@ -5717,13 +5742,16 @@ inputsecret({prompt} [, {text}]) *inputsecret()*
typed on the command-line in response to the issued prompt. typed on the command-line in response to the issued prompt.
NOTE: Command-line completion is not supported. NOTE: Command-line completion is not supported.
insert({list}, {item} [, {idx}]) *insert()* insert({object}, {item} [, {idx}]) *insert()*
Insert {item} at the start of |List| {list}. 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 If {idx} is specified insert {item} before the item with index
{idx}. If {idx} is zero it goes before the first item, just {idx}. If {idx} is zero it goes before the first item, just
like omitting {idx}. A negative {idx} is also possible, see like omitting {idx}. A negative {idx} is also possible, see
|list-index|. -1 inserts just before the last item. |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) :let mylist = insert([2, 3, 5], 1)
:call insert(mylist, 4, -1) :call insert(mylist, 4, -1)
:call insert(mylist, 6, len(mylist)) :call insert(mylist, 6, len(mylist))
@@ -5787,16 +5815,16 @@ islocked({expr}) *islocked()* *E786*
id({expr}) *id()* id({expr}) *id()*
Returns a |String| which is a unique identifier of the 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)` guaranteed that for the mentioned types `id(v1) ==# id(v2)`
returns true iff `type(v1) == type(v2) && v1 is v2`. returns true iff `type(v1) == type(v2) && v1 is v2`.
Note that |v:_null_string|, |v:_null_list|, and |v:_null_dict| Note that |v:_null_string|, |v:_null_list|, |v:_null_dict| and
have the same `id()` with different types because they are |v:_null_blob| have the same `id()` with different types
internally represented as a NULL pointers. `id()` returns a because they are internally represented as NULL pointers.
hexadecimal representanion of the pointers to the containers `id()` returns a hexadecimal representanion of the pointers to
(i.e. like `0x994a40`), same as `printf("%p", {expr})`, the containers (i.e. like `0x994a40`), same as `printf("%p",
but it is advised against counting on the exact format of {expr})`, but it is advised against counting on the exact
return value. format of the return value.
It is not guaranteed that `id(no_longer_existing_container)` It is not guaranteed that `id(no_longer_existing_container)`
will not be equal to some other `id()`: new containers may will not be equal to some other `id()`: new containers may
@@ -7168,16 +7196,18 @@ readdir({directory} [, {expr}])
echo s:tree(".") echo s:tree(".")
< <
*readfile()* *readfile()*
readfile({fname} [, {binary} [, {max}]]) readfile({fname} [, {type} [, {max}]])
Read file {fname} and return a |List|, each line of the file Read file {fname} and return a |List|, each line of the file
as an item. Lines are broken at NL characters. Macintosh as an item. Lines are broken at NL characters. Macintosh
files separated with CR will result in a single long line files separated with CR will result in a single long line
(unless a NL appears somewhere). (unless a NL appears somewhere).
All NUL characters are replaced with a NL character. 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 - When the last line ends in a NL an extra empty list item is
added. added.
- No CR characters are removed. - No CR characters are removed.
When {type} contains "B" a |Blob| is returned with the binary
data of the file unmodified.
Otherwise: Otherwise:
- CR characters that appear before a NL are removed. - CR characters that appear before a NL are removed.
- Whether the last line ends in a NL or not does not matter. - 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|: > < Can also be used as a |method|: >
mylist->remove(idx) 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({dict}, {key})
Remove the entry from {dict} with key {key} and return it. Remove the entry from {dict} with key {key} and return it.
Example: > Example: >
@@ -7400,9 +7441,11 @@ resolve({filename}) *resolve()* *E655*
path name) and also keeps a trailing path separator. path name) and also keeps a trailing path separator.
*reverse()* *reverse()*
reverse({list}) Reverse the order of items in {list} in-place. Returns reverse({object})
{list}. Reverse the order of items in {object} in-place.
If you want a list to remain unmodified make a copy first: > {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)) :let revlist = reverse(copy(mylist))
< Can also be used as a |method|: > < Can also be used as a |method|: >
mylist->reverse() mylist->reverse()
@@ -9483,6 +9526,7 @@ type({expr}) *type()*
Float: 5 (|v:t_float|) Float: 5 (|v:t_float|)
Boolean: 6 (|v:true| and |v:false|) Boolean: 6 (|v:true| and |v:false|)
Null: 7 (|v:null|) Null: 7 (|v:null|)
Blob: 10 (|v:t_blob|)
For backward compatibility, this method can be used: > For backward compatibility, this method can be used: >
:if type(myvar) == type(0) :if type(myvar) == type(0)
:if type(myvar) == type("") :if type(myvar) == type("")
@@ -9927,14 +9971,17 @@ wordcount() *wordcount()*
*writefile()* *writefile()*
writefile({list}, {fname} [, {flags}]) writefile({object}, {fname} [, {flags}])
Write |List| {list} to file {fname}. Each list item is When {object} is a |List| write it to file {fname}. Each list
separated with a NL. Each list item must be a String or item is separated with a NL. Each list item must be a String
Number. or Number.
When {flags} contains "b" then binary mode is used: There will 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 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. 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 When {flags} contains "a" then append mode is used, lines are
appended to the file: > appended to the file: >
:call writefile(["foo"], "event.log", "a") :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 This cannot be used to set a byte in a String. You
can do that like this: > can do that like this: >
:let var = var[0:2] . 'X' . var[4:] :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* *E711* *E719*
:let {var-name}[{idx1}:{idx2}] = {expr1} *E708* *E709* *E710* :let {var-name}[{idx1}:{idx2}] = {expr1} *E708* *E709* *E710*
Set a sequence of items in a |List| to the result of 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 A |List| is turned into a string by joining the items
and inserting line breaks. and inserting line breaks.
*perl-Blob*
VIM::Blob({expr}) Return Blob literal string 0zXXXX from scalar value.
============================================================================== ==============================================================================
3. VIM::Buffer objects *perl-buffer* 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) \ #define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \
TYPVAL_ENCODE_CONV_NIL(tv) TYPVAL_ENCODE_CONV_NIL(tv)
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
abort() /* TODO(seandewar) */ \
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
do { \ do { \
TYPVAL_ENCODE_CONV_NIL(tv); \ 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_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING
#undef TYPVAL_ENCODE_CONV_BLOB
#undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT #undef TYPVAL_ENCODE_CONV_FLOAT
#undef TYPVAL_ENCODE_CONV_FUNC_START #undef TYPVAL_ENCODE_CONV_FUNC_START

View File

@@ -69,6 +69,7 @@ static char *e_nowhitespace
= N_("E274: No white space allowed before parenthesis"); = N_("E274: No white space allowed before parenthesis");
static char *e_invalwindow = N_("E957: Invalid window number"); 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_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 // TODO(ZyX-I): move to eval/executor
static char *e_letwrong = N_("E734: Wrong variable type for %s="); 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 int fi_varcount; // nr of variables in the list
listwatch_T fi_lw; // keep an eye on the item used. listwatch_T fi_lw; // keep an eye on the item used.
list_T *fi_list; // list being used list_T *fi_list; // list being used
int fi_bi; // index of blob
blob_T *fi_blob; // blob being used
} forinfo_T; } forinfo_T;
// values for vv_flags: // values for vv_flags:
@@ -227,6 +230,7 @@ static struct vimvar {
VV(VV_TYPE_DICT, "t_dict", VAR_NUMBER, VV_RO), VV(VV_TYPE_DICT, "t_dict", VAR_NUMBER, VV_RO),
VV(VV_TYPE_FLOAT, "t_float", 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_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_EVENT, "event", VAR_DICT, VV_RO),
VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO),
VV(VV_ARGV, "argv", VAR_LIST, 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_str vv_di.di_tv.vval.v_string
#define vv_list vv_di.di_tv.vval.v_list #define vv_list vv_di.di_tv.vval.v_list
#define vv_dict vv_di.di_tv.vval.v_dict #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_partial vv_di.di_tv.vval.v_partial
#define vv_tv vv_di.di_tv #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_DICT, VAR_TYPE_DICT);
set_vim_var_nr(VV_TYPE_FLOAT, VAR_TYPE_FLOAT); 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_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_FALSE, kBoolVarFalse);
set_vim_var_bool(VV_TRUE, kBoolVarTrue); 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; 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; lp->ll_tv = &v->di_tv;
var1.v_type = VAR_UNKNOWN; var1.v_type = VAR_UNKNOWN;
var2.v_type = VAR_UNKNOWN; var2.v_type = VAR_UNKNOWN;
while (*p == '[' || (*p == '.' && lp->ll_tv->v_type == VAR_DICT)) { 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) 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->v_type == VAR_DICT && lp->ll_tv->vval.v_dict != NULL)
&& lp->ll_tv->vval.v_dict != NULL)) { && !(lp->ll_tv->v_type == VAR_BLOB && lp->ll_tv->vval.v_blob != NULL)) {
if (!quiet) if (!quiet) {
EMSG(_("E689: Can only index a List or Dictionary")); EMSG(_("E689: Can only index a List, Dictionary or Blob"));
}
return NULL; return NULL;
} }
if (lp->ll_range) { if (lp->ll_range) {
@@ -2119,10 +2124,11 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
tv_clear(&var1); tv_clear(&var1);
return NULL; return NULL;
} }
if (rettv != NULL && (rettv->v_type != VAR_LIST if (rettv != NULL
|| rettv->vval.v_list == NULL)) { && !(rettv->v_type == VAR_LIST || rettv->vval.v_list != NULL)
&& !(rettv->v_type == VAR_BLOB || rettv->vval.v_blob != NULL)) {
if (!quiet) { if (!quiet) {
EMSG(_("E709: [:] requires a List value")); EMSG(_("E709: [:] requires a List or Blob value"));
} }
tv_clear(&var1); tv_clear(&var1);
return NULL; return NULL;
@@ -2236,6 +2242,28 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
tv_clear(&var1); tv_clear(&var1);
lp->ll_tv = &lp->ll_di->di_tv; 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 { } else {
// Get the number and item for the only or first index of the List. // Get the number and item for the only or first index of the List.
if (empty1) { 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) { if (lp->ll_tv == NULL) {
cc = *endp; cc = *endp;
*endp = NUL; *endp = NUL;
if (lp->ll_blob != NULL) {
if (op != NULL && *op != '=') { 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; typval_T tv;
if (is_const) { 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) { if (eval0(skipwhite(expr + 2), &tv, nextcmdp, !skip) == OK) {
*errp = false; *errp = false;
if (!skip) { if (!skip) {
if (tv.v_type == VAR_LIST) {
l = tv.vval.v_list; l = tv.vval.v_list;
if (tv.v_type != VAR_LIST) { if (l == NULL) {
EMSG(_(e_listreq));
tv_clear(&tv);
} else if (l == NULL) {
// a null list is like an empty list: do nothing // a null list is like an empty list: do nothing
tv_clear(&tv); tv_clear(&tv);
} else { } else {
/* No need to increment the refcount, it's already set for the // No need to increment the refcount, it's already set for
* list being used in "tv". */ // the list being used in "tv".
fi->fi_list = l; fi->fi_list = l;
tv_list_watch_add(l, &fi->fi_lw); tv_list_watch_add(l, &fi->fi_lw);
fi->fi_lw.lw_item = tv_list_first(l); 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) if (skip)
@@ -2542,6 +2614,19 @@ bool next_for_item(void *fi_void, char_u *arg)
{ {
forinfo_T *fi = (forinfo_T *)fi_void; 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; listitem_T *item = fi->fi_lw.lw_item;
if (item == NULL) { if (item == NULL) {
return false; return false;
@@ -3607,7 +3692,7 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate)
if (op != '+' && op != '-' && op != '.') if (op != '+' && op != '-' && op != '.')
break; 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)) { && (op == '.' || rettv->v_type != VAR_FLOAT)) {
// For "list + ...", an illegal use of the first operand as // For "list + ...", an illegal use of the first operand as
// a number cannot be determined before evaluating the 2nd // 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); tv_clear(rettv);
rettv->v_type = VAR_STRING; rettv->v_type = VAR_STRING;
rettv->vval.v_string = p; 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 } else if (op == '+' && rettv->v_type == VAR_LIST
&& var2.v_type == VAR_LIST) { && var2.v_type == VAR_LIST) {
// Concatenate Lists. // Concatenate Lists.
@@ -3850,6 +3950,7 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)
// Handle sixth level expression: // Handle sixth level expression:
// number number constant // number number constant
// 0zFFFFFFFF Blob constant
// "string" string constant // "string" string constant
// 'string' literal string constant // 'string' literal string constant
// &option-name option value // &option-name option value
@@ -3946,7 +4047,31 @@ static int eval7(
rettv->v_type = VAR_FLOAT; rettv->v_type = VAR_FLOAT;
rettv->vval.v_float = f; 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 { } else {
// decimal, hex or octal number
vim_str2nr(*arg, NULL, &len, STR2NR_ALL, &n, NULL, 0, true); vim_str2nr(*arg, NULL, &len, STR2NR_ALL, &n, NULL, 0, true);
if (len == 0) { if (len == 0) {
EMSG2(_(e_invexpr2), *arg); EMSG2(_(e_invexpr2), *arg);
@@ -4336,7 +4461,8 @@ eval_index(
case VAR_STRING: case VAR_STRING:
case VAR_NUMBER: case VAR_NUMBER:
case VAR_LIST: case VAR_LIST:
case VAR_DICT: { case VAR_DICT:
case VAR_BLOB: {
break; break;
} }
} }
@@ -4462,6 +4588,51 @@ eval_index(
rettv->vval.v_string = (char_u *)v; rettv->vval.v_string = (char_u *)v;
break; 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: { case VAR_LIST: {
len = tv_list_len(rettv->vval.v_list); len = tv_list_len(rettv->vval.v_list);
if (n1 < 0) { 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_SPECIAL:
case VAR_FLOAT: case VAR_FLOAT:
case VAR_NUMBER: case VAR_NUMBER:
case VAR_STRING: { case VAR_STRING:
case VAR_BLOB: {
break; break;
} }
} }
@@ -6161,6 +6333,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
dict_T *d = NULL; dict_T *d = NULL;
typval_T save_val; typval_T save_val;
typval_T save_key; typval_T save_key;
blob_T *b = NULL;
int rem = false; int rem = false;
int todo; int todo;
char_u *ermsg = (char_u *)(map ? "map()" : "filter()"); 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 save_did_emsg;
int idx = 0; 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); tv_copy(&argvars[0], rettv);
if ((l = argvars[0].vval.v_list) == NULL if ((l = argvars[0].vval.v_list) == NULL
|| (!map || (!map
@@ -6234,6 +6412,30 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
} }
} }
hash_unlock(ht); 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 { } else {
assert(argvars[0].v_type == VAR_LIST); assert(argvars[0].v_type == VAR_LIST);
vimvars[VV_KEY].vv_type = VAR_NUMBER; vimvars[VV_KEY].vv_type = VAR_NUMBER;
@@ -7728,10 +7930,61 @@ bool write_list(FileDescriptor *const fp, const list_T *const list,
} }
return true; return true;
write_list_error: write_list_error:
emsgf(_("E80: Error while writing: %s"), os_strerror(error)); emsgf(_(e_write2), os_strerror(error));
return false; 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. /// Saves a typval_T as a string.
/// ///
/// For lists or buffers, replaces NLs with NUL and separates items with NLs. /// 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_PARTIAL:
case VAR_BOOL: case VAR_BOOL:
case VAR_SPECIAL: case VAR_SPECIAL:
case VAR_BLOB:
tv_copy(from, to); tv_copy(from, to);
break; break;
case VAR_STRING: case VAR_STRING:
@@ -10815,6 +11069,29 @@ int typval_compare(
// For "is" a different type always means false, for "notis" // For "is" a different type always means false, for "notis"
// it means true. // it means true.
n1 = type == EXPR_ISNOT; 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) { } else if (typ1->v_type == VAR_LIST || typ2->v_type == VAR_LIST) {
if (type_is) { if (type_is) {
n1 = typ1->v_type == typ2->v_type 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. dict_T *ll_dict; ///< The Dictionary or NULL.
dictitem_T *ll_di; ///< The dictitem or NULL. dictitem_T *ll_di; ///< The dictitem or NULL.
char_u *ll_newkey; ///< New key for Dict in allocated memory 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; } lval_T;
/// enum used by var_flavour() /// enum used by var_flavour()
@@ -154,6 +155,7 @@ typedef enum {
VV_TYPE_DICT, VV_TYPE_DICT,
VV_TYPE_FLOAT, VV_TYPE_FLOAT,
VV_TYPE_BOOL, VV_TYPE_BOOL,
VV_TYPE_BLOB,
VV_EVENT, VV_EVENT,
VV_ECHOSPACE, VV_ECHOSPACE,
VV_ARGV, 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_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) \ #define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
do { \ do { \
char numbuf[NUMBUFLEN]; \ char numbuf[NUMBUFLEN]; \
@@ -705,6 +727,10 @@ static inline int convert_to_json_string(garray_T *const gap,
return FAIL; \ return FAIL; \
} while (0) } while (0)
#undef TYPVAL_ENCODE_CONV_BLOB
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
abort() /* TODO(seandewar) */ \
#undef TYPVAL_ENCODE_CONV_FUNC_START #undef TYPVAL_ENCODE_CONV_FUNC_START
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
return conv_error(_("E474: Error while dumping %s, %s: " \ 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_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING
#undef TYPVAL_ENCODE_CONV_BLOB
#undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT #undef TYPVAL_ENCODE_CONV_FLOAT
#undef TYPVAL_ENCODE_CONV_FUNC_START #undef TYPVAL_ENCODE_CONV_FUNC_START
@@ -904,6 +931,9 @@ char *encode_tv2json(typval_T *tv, size_t *len)
} \ } \
} while (0) } while (0)
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
abort() /* TODO(seandewar) */ \
#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ #define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
msgpack_pack_int64(packer, (int64_t)(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_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING
#undef TYPVAL_ENCODE_CONV_BLOB
#undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT #undef TYPVAL_ENCODE_CONV_FLOAT
#undef TYPVAL_ENCODE_CONV_FUNC_START #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: { case VAR_SPECIAL: {
break; 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: { case VAR_LIST: {
if (*op != '+' || tv2->v_type != VAR_LIST) { if (*op != '+' || tv2->v_type != VAR_LIST) {
break; 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_list_append_tv(l, &argvars[1]);
tv_copy(&argvars[0], rettv); 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 { } else {
EMSG(_(e_listreq)); 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; n = argvars[0].vval.v_special == kSpecialVarNull;
break; break;
} }
case VAR_BLOB: {
n = (tv_blob_len(argvars[0].vval.v_blob) == 0);
break;
}
case VAR_UNKNOWN: { case VAR_UNKNOWN: {
internal_error("f_empty(UNKNOWN)"); internal_error("f_empty(UNKNOWN)");
break; break;
@@ -2791,7 +2802,19 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
typval_T *tv = NULL; typval_T *tv = NULL;
bool what_is_dict = false; 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) { if ((l = argvars[0].vval.v_list) != NULL) {
bool error = false; bool error = false;
@@ -4790,7 +4813,31 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool ic = false; bool ic = false;
rettv->vval.v_number = -1; 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)); EMSG(_(e_listreq));
return; return;
} }
@@ -4920,7 +4967,37 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
list_T *l; list_T *l;
bool error = false; 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()"); EMSG2(_(e_listarg), "insert()");
} else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), } else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
N_("insert() argument"), TV_TRANSLATE)) { 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])); tv_get_string(&argvars[0]));
break; break;
} }
case VAR_BLOB: {
rettv->vval.v_number = tv_blob_len(argvars[0].vval.v_blob);
break;
}
case VAR_LIST: { case VAR_LIST: {
rettv->vval.v_number = tv_list_len(argvars[0].vval.v_list); rettv->vval.v_number = tv_list_len(argvars[0].vval.v_list);
break; 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) static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{ {
bool binary = false; bool binary = false;
bool blob = false;
FILE *fd; FILE *fd;
char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1
int io_size = sizeof(buf); 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 (argvars[1].v_type != VAR_UNKNOWN) {
if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
binary = true; binary = true;
} else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) {
blob = true;
} }
if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_UNKNOWN) {
maxline = tv_get_number(&argvars[2]); 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 // Always open the file in binary mode, library functions have a mind of
// their own about CR-LF conversion. // their own about CR-LF conversion.
const char *const fname = tv_get_string(&argvars[0]); 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; 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) { while (maxline < 0 || tv_list_len(l) < maxline) {
readlen = (int)fread(buf, 1, io_size, fd); 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) { } else if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listdictarg), "remove()"); EMSG2(_(e_listdictarg), "remove()");
} else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), } 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) static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{ {
list_T *l; if (argvars[0].v_type == VAR_BLOB) {
if (argvars[0].v_type != VAR_LIST) { 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()"); EMSG2(_(e_listarg), "reverse()");
} else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), } else {
N_("reverse() argument"), TV_TRANSLATE)) { 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_reverse(l);
tv_list_set_ret(rettv, l); tv_list_set_ret(rettv, l);
} }
} }
}
#define SP_NOMOVE 0x01 ///< don't move cursor #define SP_NOMOVE 0x01 ///< don't move cursor
#define SP_REPEAT 0x02 ///< repeat to find outer pair #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_FLOAT: n = VAR_TYPE_FLOAT; break;
case VAR_BOOL: n = VAR_TYPE_BOOL; 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: { case VAR_UNKNOWN: {
internal_error("f_type(UNKNOWN)"); internal_error("f_type(UNKNOWN)");
break; break;
@@ -11678,16 +11834,16 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return; return;
} }
if (argvars[0].v_type != VAR_LIST) { if (argvars[0].v_type == VAR_LIST) {
EMSG2(_(e_listarg), "writefile()"); TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
return;
}
const list_T *const list = argvars[0].vval.v_list;
TV_LIST_ITER_CONST(list, li, {
if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) { if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) {
return; return;
} }
}); });
} else if (argvars[0].v_type != VAR_BLOB) {
EMSG2(_(e_invarg2), "writefile()");
return;
}
bool binary = false; bool binary = false;
bool append = 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"), emsgf(_("E482: Can't open file %s for writing: %s"),
fname, os_strerror(error)); fname, os_strerror(error));
} else { } 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; rettv->vval.v_number = 0;
} }
if ((error = file_close(&fp, do_fsync)) != 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 //{{{1 Generic typval operations
//{{{2 Init/alloc/clear //{{{2 Init/alloc/clear
//{{{3 Alloc //{{{3 Alloc
@@ -2169,6 +2236,18 @@ void tv_dict_alloc_ret(typval_T *const ret_tv)
tv_dict_set_ret(ret_tv, d); 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 //{{{3 Clear
#define TYPVAL_ENCODE_ALLOW_SPECIALS false #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_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, static inline int _nothing_conv_func_start(typval_T *const tv,
char_u *const fun) char_u *const fun)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1) 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_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING
#undef TYPVAL_ENCODE_CONV_BLOB
#undef TYPVAL_ENCODE_CONV_FUNC_START #undef TYPVAL_ENCODE_CONV_FUNC_START
#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS #undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS
#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF #undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF
@@ -2449,6 +2536,10 @@ void tv_free(typval_T *tv)
xfree(tv->vval.v_string); xfree(tv->vval.v_string);
break; break;
} }
case VAR_BLOB: {
tv_blob_unref(tv->vval.v_blob);
break;
}
case VAR_LIST: { case VAR_LIST: {
tv_list_unref(tv->vval.v_list); tv_list_unref(tv->vval.v_list);
break; break;
@@ -2509,6 +2600,12 @@ void tv_copy(const typval_T *const from, typval_T *const to)
} }
break; break;
} }
case VAR_BLOB: {
if (from->vval.v_blob != NULL) {
to->vval.v_blob->bv_refcount++;
}
break;
}
case VAR_LIST: { case VAR_LIST: {
tv_list_ref(to->vval.v_list); tv_list_ref(to->vval.v_list);
break; 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); CHANGE_LOCK(lock, tv->v_lock);
switch (tv->v_type) { 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: { case VAR_LIST: {
list_T *const l = tv->vval.v_list; list_T *const l = tv->vval.v_list;
if (l != NULL) { if (l != NULL) {
@@ -2646,10 +2750,11 @@ bool tv_check_lock(const typval_T *tv, const char *name,
VarLockStatus lock = VAR_UNLOCKED; VarLockStatus lock = VAR_UNLOCKED;
switch (tv->v_type) { switch (tv->v_type) {
// case VAR_BLOB: case VAR_BLOB:
// if (tv->vval.v_blob != NULL) if (tv->vval.v_blob != NULL) {
// lock = tv->vval.v_blob->bv_lock; lock = tv->vval.v_blob->bv_lock;
// break; }
break;
case VAR_LIST: case VAR_LIST:
if (tv->vval.v_list != NULL) { if (tv->vval.v_list != NULL) {
lock = tv->vval.v_list->lv_lock; 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--; recursive_cnt--;
return r; return r;
} }
case VAR_BLOB: {
return tv_blob_equal(tv1->vval.v_blob, tv2->vval.v_blob);
}
case VAR_NUMBER: { case VAR_NUMBER: {
return tv1->vval.v_number == tv2->vval.v_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")); EMSG(_("E728: Expected a Number or a String, Dictionary found"));
return false; return false;
} }
case VAR_BLOB: {
EMSG(_("E974: Expected a Number or a String, Blob found"));
return false;
}
case VAR_BOOL: { case VAR_BOOL: {
EMSG(_("E5299: Expected a Number or a String, Boolean found")); EMSG(_("E5299: Expected a Number or a String, Boolean found"));
return false; return false;
@@ -2860,6 +2972,7 @@ static const char *const num_errors[] = {
[VAR_LIST]=N_("E745: Using a List as a Number"), [VAR_LIST]=N_("E745: Using a List as a Number"),
[VAR_DICT]=N_("E728: Using a Dictionary as a Number"), [VAR_DICT]=N_("E728: Using a Dictionary as a Number"),
[VAR_FLOAT]=N_("E805: Using a Float 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"), [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_LIST:
case VAR_DICT: case VAR_DICT:
case VAR_FLOAT: case VAR_FLOAT:
case VAR_BLOB:
case VAR_UNKNOWN: { case VAR_UNKNOWN: {
EMSG(_(num_errors[tv->v_type])); EMSG(_(num_errors[tv->v_type]));
return false; return false;
@@ -2905,6 +3019,7 @@ static const char *const str_errors[] = {
[VAR_LIST]=N_("E730: using List as a String"), [VAR_LIST]=N_("E730: using List as a String"),
[VAR_DICT]=N_("E731: using Dictionary as a String"), [VAR_DICT]=N_("E731: using Dictionary as a String"),
[VAR_FLOAT]=((const char *)e_float_as_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"), [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_LIST:
case VAR_DICT: case VAR_DICT:
case VAR_FLOAT: case VAR_FLOAT:
case VAR_BLOB:
case VAR_UNKNOWN: { case VAR_UNKNOWN: {
EMSG(_(str_errors[tv->v_type])); EMSG(_(str_errors[tv->v_type]));
return false; 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_PARTIAL:
case VAR_LIST: case VAR_LIST:
case VAR_DICT: case VAR_DICT:
case VAR_BLOB:
case VAR_FLOAT: { case VAR_FLOAT: {
EMSG(_(num_errors[tv->v_type])); EMSG(_(num_errors[tv->v_type]));
break; break;
@@ -3075,6 +3192,10 @@ float_T tv_get_float(const typval_T *const tv)
EMSG(_("E907: Using a special value as a Float")); EMSG(_("E907: Using a special value as a Float"));
break; break;
} }
case VAR_BLOB: {
EMSG(_("E975: Using a Blob as a Float"));
break;
}
case VAR_UNKNOWN: { case VAR_UNKNOWN: {
emsgf(_(e_intern2), "tv_get_float(UNKNOWN)"); emsgf(_(e_intern2), "tv_get_float(UNKNOWN)");
break; 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_LIST:
case VAR_DICT: case VAR_DICT:
case VAR_FLOAT: case VAR_FLOAT:
case VAR_BLOB:
case VAR_UNKNOWN: { case VAR_UNKNOWN: {
EMSG(_(str_errors[tv->v_type])); EMSG(_(str_errors[tv->v_type]));
return false; return false;

View File

@@ -64,6 +64,7 @@ enum ListLenSpecials {
typedef struct listvar_S list_T; typedef struct listvar_S list_T;
typedef struct dictvar_S dict_T; typedef struct dictvar_S dict_T;
typedef struct partial_S partial_T; typedef struct partial_S partial_T;
typedef struct blobvar_S blob_T;
typedef struct ufunc ufunc_T; typedef struct ufunc ufunc_T;
@@ -123,6 +124,7 @@ typedef enum {
VAR_SPECIAL, ///< Special value (null), .v_special VAR_SPECIAL, ///< Special value (null), .v_special
///< is used. ///< is used.
VAR_PARTIAL, ///< Partial, .v_partial is used. VAR_PARTIAL, ///< Partial, .v_partial is used.
VAR_BLOB, ///< Blob, .v_blob is used.
} VarType; } VarType;
/// Structure that holds an internal variable value /// Structure that holds an internal variable value
@@ -138,6 +140,7 @@ typedef struct {
list_T *v_list; ///< List for VAR_LIST, can be NULL. list_T *v_list; ///< List for VAR_LIST, can be NULL.
dict_T *v_dict; ///< Dictionary for VAR_DICT, can be NULL. dict_T *v_dict; ///< Dictionary for VAR_DICT, can be NULL.
partial_T *v_partial; ///< Closure: function with args. partial_T *v_partial; ///< Closure: function with args.
blob_T *v_blob; ///< Blob for VAR_BLOB, can be NULL.
} vval; ///< Actual value. } vval; ///< Actual value.
} typval_T; } typval_T;
@@ -252,6 +255,13 @@ struct dictvar_S {
LuaRef lua_table_ref; 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 /// Type used for script ID
typedef int scid_T; typedef int scid_T;
/// Format argument for 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); 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 /// Initialize VimL object
/// ///
/// Initializes to unlocked VAR_UNKNOWN object. /// Initializes to unlocked VAR_UNKNOWN object.

View File

@@ -83,6 +83,13 @@
/// @param len String length. /// @param len String length.
/// @param type EXT type. /// @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 /// @def TYPVAL_ENCODE_CONV_FUNC_START
/// @brief Macros used when starting to convert a funcref or a partial /// @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); TYPVAL_ENCODE_CONV_FLOAT(tv, tv->vval.v_float);
break; break;
} }
case VAR_BLOB: {
TYPVAL_ENCODE_CONV_BLOB(tv, tv->vval.v_blob,
tv_blob_len(tv->vval.v_blob));
break;
}
case VAR_FUNC: { case VAR_FUNC: {
TYPVAL_ENCODE_CONV_FUNC_START(tv, tv->vval.v_string); TYPVAL_ENCODE_CONV_FUNC_START(tv, tv->vval.v_string);
TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, 0); 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\"")); "E46: Cannot change read-only variable \"%.*s\""));
EXTERN char_u e_stringreq[] INIT(= N_("E928: String required")); EXTERN char_u e_stringreq[] INIT(= N_("E928: String required"));
EXTERN char_u e_dictreq[] INIT(= N_("E715: Dictionary 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_( EXTERN char_u e_toomanyarg[] INIT(= N_(
"E118: Too many arguments for function: %s")); "E118: Too many arguments for function: %s"));
EXTERN char_u e_dictkey[] INIT(= N_( 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) \ #define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \
TYPVAL_ENCODE_CONV_NIL(tv) TYPVAL_ENCODE_CONV_NIL(tv)
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
abort() /* TODO(seandewar) */ \
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
do { \ do { \
TYPVAL_ENCODE_CONV_NIL(tv); \ TYPVAL_ENCODE_CONV_NIL(tv); \
@@ -579,6 +582,7 @@ static bool typval_conv_special = false;
#undef TYPVAL_ENCODE_CONV_STRING #undef TYPVAL_ENCODE_CONV_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING
#undef TYPVAL_ENCODE_CONV_BLOB
#undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT #undef TYPVAL_ENCODE_CONV_FLOAT
#undef TYPVAL_ENCODE_CONV_FUNC_START #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, four: 4}, d->extend(#{four: 4}))
call assert_equal(#{one: 1, two: 2, three: 3}, d->filter('v:val != 4')) call assert_equal(#{one: 1, two: 2, three: 3}, d->filter('v:val != 4'))
call assert_equal(2, d->get('two')) 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->index(2)", 'E897:') call assert_fails("let x = d->insert(0)", 'E899:')
" 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_true(d->has_key('two')) call assert_true(d->has_key('two'))
call assert_equal([['one', 1], ['two', 2], ['three', 3]], d->items()) call assert_equal([['one', 1], ['two', 2], ['three', 3]], d->items())
call assert_fails("let x = d->join()", 'E714:') call assert_fails("let x = d->join()", 'E714:')
@@ -63,9 +60,7 @@ func Test_dict_method()
call assert_equal(2, d->remove("two")) call assert_equal(2, d->remove("two"))
let d.two = 2 let d.two = 2
call assert_fails('let x = d->repeat(2)', 'E731:') 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()', 'E899:')
call assert_fails('let x = d->reverse()', 'E686:')
call assert_fails('let x = d->sort()', 'E686:') call assert_fails('let x = d->sort()', 'E686:')
call assert_equal("{'one': 1, 'two': 2, 'three': 3}", d->string()) call assert_equal("{'one': 1, 'two': 2, 'three': 3}", d->string())
call assert_equal(v:t_dict, d->type()) call assert_equal(v:t_dict, d->type())

View File

@@ -102,6 +102,7 @@ typedef enum {
#define VAR_TYPE_FLOAT 5 #define VAR_TYPE_FLOAT 5
#define VAR_TYPE_BOOL 6 #define VAR_TYPE_BOOL 6
#define VAR_TYPE_SPECIAL 7 #define VAR_TYPE_SPECIAL 7
#define VAR_TYPE_BLOB 10
// values for xp_context when doing command line completion // values for xp_context when doing command line completion

View File

@@ -44,7 +44,7 @@ describe('NULL', function()
-- Incorrect behaviour -- Incorrect behaviour
-- FIXME Should error out with different message -- FIXME Should error out with different message
null_test('makes :unlet act as if it is not a list', ':unlet L[0]', 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 -- Subjectable behaviour

View File

@@ -119,7 +119,7 @@ describe('writefile()', function()
eq('\nE118: Too many arguments for function: writefile', eq('\nE118: Too many arguments for function: writefile',
redir_exec(('call writefile([], "%s", "b", 1)'):format(fname))) redir_exec(('call writefile([], "%s", "b", 1)'):format(fname)))
for _, arg in ipairs({'0', '0.0', 'function("tr")', '{}', '"test"'}) do 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))) redir_exec(('call writefile(%s, "%s", "b")'):format(arg, fname)))
end end
for _, args in ipairs({'[], %s, "b"', '[], "' .. fname .. '", %s'}) do for _, args in ipairs({'[], %s, "b"', '[], "' .. fname .. '", %s'}) do