Merge pull request #15211 from seandewar/blob-port

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

View File

@ -14,8 +14,8 @@ Using expressions is introduced in chapter 41 of the user manual |usr_41.txt|.
1. Variables *variables*
1.1 Variable types ~
*E712*
There are six types of variables:
*E712* *E896* *E897* *E899*
There are seven types of variables:
*Number* *Integer*
Number A 32 or 64 bit signed number. |expr-number|
@ -34,7 +34,7 @@ Funcref A reference to a function |Funcref|.
like a Partial.
Example: function("Callback", [arg], myDict)
List An ordered sequence of items |List|.
List An ordered sequence of items, see |List| for details.
Example: [1, 2, ['a', 'b']]
Dictionary An associative, unordered array: Each entry has a key and a
@ -43,6 +43,11 @@ 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. See |Blob|
for details.
Example: 0zFF00ED015DAF
0z is an empty Blob.
The Number and String types are converted automatically, depending on how they
are used.
@ -97,6 +102,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.
@ -366,8 +372,8 @@ Changing the order of items in a list: >
For loop ~
The |:for| loop executes commands for each item in a list. A variable is set
to each item in the list in sequence. Example: >
The |:for| loop executes commands for each item in a |List| or |Blob|.
A variable is set to each item in the sequence. Example with a List: >
:for item in mylist
: call Doit(item)
:endfor
@ -400,6 +406,8 @@ It is also possible to put remaining items in a List variable: >
: endif
:endfor
For a Blob one byte at a time is used.
List functions ~
*E714*
@ -594,7 +602,137 @@ Functions that can be used with a Dictionary: >
:call map(dict, '">> " . v:val') " prepend ">> " to each item
1.5 More about variables ~
1.5 Blobs ~
*blob* *Blob* *Blobs* *E978*
A Blob is a binary object. It can be used to read an image from a file and
send it over a channel, for example.
A Blob mostly behaves like a |List| of numbers, where each number has the
value of an 8-bit byte, from 0 to 255.
Blob creation ~
A Blob can be created with a |blob-literal|: >
:let b = 0zFF00ED015DAF
Dots can be inserted between bytes (pair of hex characters) for readability,
they don't change the value: >
:let b = 0zFF00.ED01.5DAF
A blob can be read from a file with |readfile()| passing the {type} argument
set to "B", for example: >
:let b = readfile('image.png', 'B')
Blob index ~
*blob-index* *E979*
A byte in the Blob can be accessed by putting the index in square brackets
after the Blob. Indexes are zero-based, thus the first byte has index zero. >
:let myblob = 0z00112233
:let byte = myblob[0] " get the first byte: 0x00
:let byte = myblob[2] " get the third byte: 0x22
A negative index is counted from the end. Index -1 refers to the last byte in
the Blob, -2 to the last but one byte, etc. >
:let last = myblob[-1] " get the last byte: 0x33
To avoid an error for an invalid index use the |get()| function. When an item
is not available it returns -1 or the default value you specify: >
:echo get(myblob, idx)
:echo get(myblob, idx, 999)
Blob iteration ~
The |:for| loop executes commands for each byte of a Blob. The loop variable is
set to each byte in the Blob. Example: >
:for byte in 0z112233
: call Doit(byte)
:endfor
This calls Doit() with 0x11, 0x22 and 0x33.
Blob concatenation ~
Two blobs can be concatenated with the "+" operator: >
:let longblob = myblob + 0z4455
:let myblob += 0z6677
To change a blob in-place see |blob-modification| below.
Part of a blob ~
A part of the Blob can be obtained by specifying the first and last index,
separated by a colon in square brackets: >
:let myblob = 0z00112233
:let shortblob = myblob[1:2] " get 0z1122
:let shortblob = myblob[2:-1] " get 0z2233
Omitting the first index is similar to zero. Omitting the last index is
similar to -1. >
:let endblob = myblob[2:] " from item 2 to the end: 0z2233
:let shortblob = myblob[2:2] " Blob with one byte: 0z22
:let otherblob = myblob[:] " make a copy of the Blob
If the first index is beyond the last byte of the Blob or the second byte is
before the first byte, the result is an empty Blob. There is no error
message.
If the second index is equal to or greater than the length of the Blob the
length minus one is used: >
:echo myblob[2:8] " result: 0z2233
Blob modification ~
*blob-modification*
To change a specific byte of a blob use |:let| this way: >
:let blob[4] = 0x44
When the index is just one beyond the end of the Blob, it is appended. Any
higher index is an error.
To change a sequence of bytes the [:] notation can be used: >
let blob[1:3] = 0z445566
The length of the replaced bytes must be exactly the same as the value
provided. *E972*
To change part of a blob you can specify the first and last byte to be
modified. The value must at least have the number of bytes in the range: >
:let blob[3:5] = [3, 4, 5]
You can also use the functions |add()|, |remove()| and |insert()|.
Blob identity ~
Blobs can be compared for equality: >
if blob == 0z001122
And for equal identity: >
if blob is otherblob
< *blob-identity* *E977*
When variable "aa" is a Blob and you assign it to another variable "bb", both
variables refer to the same Blob. Then the "is" operator returns true.
When making a copy using [:] or |copy()| the values are the same, but the
identity is different: >
:let blob = 0z112233
:let blob2 = blob
:echo blob == blob2
< 1 >
:echo blob is blob2
< 1 >
:let blob3 = blob[:]
:echo blob == blob3
< 1 >
:echo blob is blob3
< 0
Making a copy of a Blob is done with the |copy()| function. Using [:] also
works, as explained above.
1.6 More about variables ~
*more-variables*
If you need to know the type of a variable or expression, use the |type()|
function.
@ -645,8 +783,9 @@ Expression syntax summary, from least to most significant:
etc. As above, append ? for ignoring case, # for
matching case
expr5 is expr5 same |List| instance
expr5 isnot expr5 different |List| instance
expr5 is expr5 same |List|, |Dictionary| or |Blob| instance
expr5 isnot expr5 different |List|, |Dictionary| or |Blob|
instance
|expr5| expr6
expr6 + expr6 ... number addition, list or blob concatenation
@ -817,12 +956,12 @@ Dictionary and arguments, use |get()| to get the function name: >
if get(Part1, 'name') == get(Part2, 'name')
" Part1 and Part2 refer to the same function
When using "is" or "isnot" with a |List| or a |Dictionary| this checks if the
expressions are referring to the same |List| or |Dictionary| instance. A copy
of a |List| is different from the original |List|. When using "is" without
a |List| or a |Dictionary| it is equivalent to using "equal", using "isnot"
equivalent to using "not equal". Except that a different type means the
values are different: >
Using "is" or "isnot" with a |List|, |Dictionary| or |Blob| checks whether
the expressions are referring to the same |List|, |Dictionary| or |Blob|
instance. A copy of a |List| is different from the original |List|. When
using "is" without a |List|, |Dictionary| or |Blob|, it is equivalent to
using "equal", using "isnot" is equivalent to using "not equal". Except that
a different type means the values are different: >
echo 4 == '4'
1
echo 4 is '4'
@ -1012,6 +1151,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 +1325,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*
------------
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-'*
@ -2007,19 +2160,21 @@ v:swapcommand Normal mode command to be executed after a file has been
For ":edit +cmd file" the value is ":cmd\r".
*v:t_TYPE* *v:t_bool* *t_bool-variable*
v:t_bool Value of Boolean type. Read-only. See: |type()|
v:t_bool Value of |Boolean| type. Read-only. See: |type()|
*v:t_dict* *t_dict-variable*
v:t_dict Value of Dictionary type. Read-only. See: |type()|
v:t_dict Value of |Dictionary| type. Read-only. See: |type()|
*v:t_float* *t_float-variable*
v:t_float Value of Float type. Read-only. See: |type()|
v:t_float Value of |Float| type. Read-only. See: |type()|
*v:t_func* *t_func-variable*
v:t_func Value of Funcref type. Read-only. See: |type()|
v:t_func Value of |Funcref| type. Read-only. See: |type()|
*v:t_list* *t_list-variable*
v:t_list Value of List type. Read-only. See: |type()|
v:t_list Value of |List| type. Read-only. See: |type()|
*v:t_number* *t_number-variable*
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 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 The escape sequence returned by the terminal for the DA
@ -2103,7 +2258,7 @@ USAGE RESULT DESCRIPTION ~
abs({expr}) Float or Number absolute value of {expr}
acos({expr}) Float arc cosine of {expr}
add({list}, {item}) List append {item} to |List| {list}
add({object}, {item}) List/Blob append {item} to {object}
and({expr}, {expr}) Number bitwise AND
api_info() Dict api metadata
append({lnum}, {string}) Number append {string} below line {lnum}
@ -2315,8 +2470,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 +2479,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
@ -2384,8 +2539,8 @@ min({expr}) Number minimum value of items in {expr}
mkdir({name} [, {path} [, {prot}]])
Number create directory {name}
mode([expr]) String current editing mode
msgpackdump({list}) List dump a list of objects to msgpack
msgpackparse({list}) List parse msgpack to a list of objects
msgpackdump({list} [, {type}]) List/Blob dump objects to msgpack
msgpackparse({data}) List parse msgpack to a list of objects
nextnonblank({lnum}) Number line nr of non-blank line >= {lnum}
nr2char({expr}[, {utf8}]) String single char with ASCII/UTF8 value {expr}
nvim_...({args}...) any call nvim |api| functions
@ -2407,7 +2562,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
@ -2424,7 +2579,10 @@ remote_read({serverid} [, {timeout}])
remote_send({server}, {string} [, {idvar}])
String send key sequence
remote_startserver({name}) none become server {name}
remove({list}, {idx} [, {end}]) any remove items {idx}-{end} from {list}
remove({list}, {idx} [, {end}]) any/List
remove items {idx}-{end} from {list}
remove({blob}, {idx} [, {end}]) Number/Blob
remove bytes {idx}-{end} from {blob}
remove({dict}, {key}) any remove entry {key} from {dict}
rename({from}, {to}) Number rename (move) file from {from} to {to}
repeat({expr}, {count}) String repeat {expr} {count} times
@ -2612,8 +2770,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
@ -2647,13 +2805,14 @@ acos({expr}) *acos()*
Can also be used as a |method|: >
Compute()->acos()
add({list}, {expr}) *add()*
Append the item {expr} to |List| {list}. Returns the
resulting |List|. Examples: >
add({object}, {expr}) *add()*
Append the item {expr} to |List| or |Blob| {object}. Returns
the resulting |List| or |Blob|. Examples: >
:let alist = add([1, 2, 3], item)
:call add(mylist, "woodstock")
< Note that when {expr} is a |List| it is appended as a single
item. Use |extend()| to concatenate |Lists|.
When {object} is a |Blob| then {expr} must be a number.
Use |insert()| to add an item at another position.
Can also be used as a |method|: >
@ -3066,8 +3225,8 @@ chansend({id}, {data}) *chansend()*
written if the write succeeded, 0 otherwise.
See |channel-bytes| for more information.
{data} may be a string, string convertible, or a list. If
{data} is a list, the items will be joined by newlines; any
{data} may be a string, string convertible, |Blob|, or a list.
If {data} is a list, the items will be joined by newlines; any
newlines in an item will be sent as NUL. To send a final
newline, include a final empty string. Example: >
:call chansend(id, ["abc", "123\n456", ""])
@ -3640,9 +3799,13 @@ diff_hlID({lnum}, {col}) *diff_hlID()*
empty({expr}) *empty()*
Return the Number 1 if {expr} is empty, zero otherwise.
A |List| or |Dictionary| is empty when it does not have any
items. A Number is empty when its value is zero. Special
variable is empty when it is |v:false| or |v:null|.
- A |List| or |Dictionary| is empty when it does not have any
items.
- A |String| is empty when its length is zero.
- A |Number| and |Float| are empty when their value is zero.
- |v:false| and |v:null| are empty, |v:true| is not.
- A |Blob| is empty when its length is zero.
Can also be used as a |method|: >
mylist->empty()
@ -3665,8 +3828,8 @@ escape({string}, {chars}) *escape()*
*eval()*
eval({string}) Evaluate {string} and return the result. Especially useful to
turn the result of |string()| back into the original value.
This works for Numbers, Floats, Strings and composites of
them. Also works for |Funcref|s that refer to existing
This works for Numbers, Floats, Strings, Blobs and composites
of them. Also works for |Funcref|s that refer to existing
functions.
Can also be used as a |method|: >
@ -4081,7 +4244,7 @@ filter({expr1}, {expr2}) *filter()*
|Dictionary| to remain unmodified make a copy first: >
:let l = filter(copy(mylist), 'v:val =~ "KEEP"')
< Returns {expr1}, the |List| , |Blob| or |Dictionary| that was
< Returns {expr1}, the |List|, |Blob| or |Dictionary| that was
filtered. When an error is encountered while evaluating
{expr2} no further items in {expr1} are processed. When
{expr2} is a Funcref errors inside a function are ignored,
@ -4362,6 +4525,10 @@ get({list}, {idx} [, {default}]) *get()*
omitted.
Can also be used as a |method|: >
mylist->get(idx)
get({blob}, {idx} [, {default}])
Get byte {idx} from |Blob| {blob}. When this byte is not
available return {default}. Return -1 when {default} is
omitted.
get({dict}, {key} [, {default}])
Get item with key {key} from |Dictionary| {dict}. When this
item is not available return {default}. Return zero when
@ -5548,17 +5715,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 a |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 +5888,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 +5961,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
@ -5806,8 +5980,13 @@ items({dict}) *items()*
Return a |List| with all the key-value pairs of {dict}. Each
|List| item is a list with two items: the key of a {dict}
entry and the value of this entry. The |List| is in arbitrary
order.
Can also be used as a |method|: >
order. Also see |keys()| and |values()|.
Example: >
for [key, value] in items(mydict)
echo key . ': ' . value
endfor
< Can also be used as a |method|: >
mydict->items()
isnan({expr}) *isnan()*
@ -5974,10 +6153,12 @@ json_encode({expr}) *json_encode()*
surrogate pairs (such strings are not valid UTF-8 strings).
Non-printable characters are converted into "\u1234" escapes
or special escapes like "\t", other are dumped as-is.
|Blob|s are converted to arrays of the individual bytes.
keys({dict}) *keys()*
Return a |List| with all the keys of {dict}. The |List| is in
arbitrary order.
arbitrary order. Also see |items()| and |values()|.
Can also be used as a |method|: >
mydict->keys()
@ -6141,12 +6322,14 @@ luaeval({expr}[, {expr}])
to Vim data structures. See |lua-eval| for more details.
map({expr1}, {expr2}) *map()*
{expr1} must be a |List| or a |Dictionary|.
{expr1} must be a |List|, |Blob| or |Dictionary|.
Replace each item in {expr1} with the result of evaluating
{expr2}. {expr2} must be a |string| or |Funcref|.
{expr2}. For a |Blob| each byte is replaced.
{expr2} must be a |string| or |Funcref|.
If {expr2} is a |string|, inside {expr2} |v:val| has the value
of the current item. For a |Dictionary| |v:key| has the key
of the current item. For a |Dictionary| |v:key| has the key
of the current item and for a |List| |v:key| has the index of
the current item. For a |Blob| |v:key| has the index of the
current byte.
@ -6179,11 +6362,11 @@ map({expr1}, {expr2}) *map()*
|Dictionary| to remain unmodified make a copy first: >
:let tlist = map(copy(mylist), ' v:val . "\t"')
< Returns {expr1}, the |List| or |Dictionary| that was filtered.
When an error is encountered while evaluating {expr2} no
further items in {expr1} are processed. When {expr2} is a
Funcref errors inside a function are ignored, unless it was
defined with the "abort" flag.
< Returns {expr1}, the |List|, |Blob| or |Dictionary| that was
filtered. When an error is encountered while evaluating
{expr2} no further items in {expr1} are processed. When
{expr2} is a Funcref errors inside a function are ignored,
unless it was defined with the "abort" flag.
Can also be used as a |method|: >
mylist->map(expr2)
@ -6651,11 +6834,15 @@ mode([expr]) Return a string that indicates the current mode.
the leading character(s).
Also see |visualmode()|.
msgpackdump({list}) *msgpackdump()*
Convert a list of VimL objects to msgpack. Returned value is
|readfile()|-style list. Example: >
msgpackdump({list} [, {type}]) *msgpackdump()*
Convert a list of VimL objects to msgpack. Returned value is a
|readfile()|-style list. When {type} contains "B", a |Blob| is
returned instead. Example: >
call writefile(msgpackdump([{}]), 'fname.mpack', 'b')
< This will write the single 0x80 byte to `fname.mpack` file
< or, using a |Blob|: >
call writefile(msgpackdump([{}], 'B'), 'fname.mpack')
<
This will write the single 0x80 byte to a `fname.mpack` file
(dictionary with zero items is represented by 0x80 byte in
messagepack).
@ -6663,11 +6850,12 @@ msgpackdump({list}) *msgpackdump()*
1. |Funcref|s cannot be dumped.
2. Containers that reference themselves cannot be dumped.
3. Dictionary keys are always dumped as STR strings.
4. Other strings are always dumped as BIN strings.
4. Other strings and |Blob|s are always dumped as BIN strings.
5. Points 3. and 4. do not apply to |msgpack-special-dict|s.
msgpackparse({list}) *msgpackparse()*
Convert a |readfile()|-style list to a list of VimL objects.
msgpackparse({data}) *msgpackparse()*
Convert a |readfile()|-style list or a |Blob| to a list of
VimL objects.
Example: >
let fname = expand('~/.config/nvim/shada/main.shada')
let mpack = readfile(fname, 'b')
@ -6677,7 +6865,7 @@ msgpackparse({list}) *msgpackparse()*
Limitations:
1. Mapping ordering is not preserved unless messagepack
mapping is dumped using generic mapping
mapping is dumped using generic mapping
(|msgpack-special-map|).
2. Since the parser aims to preserve all data untouched
(except for 1.) some strings are parsed to
@ -6721,9 +6909,9 @@ msgpackparse({list}) *msgpackparse()*
zero byte or if string is a mapping key and mapping is
being represented as special dictionary for other
reasons.
binary |readfile()|-style list of strings. This value will
appear in |msgpackparse()| output if binary string
contains zero byte.
binary |String|, or |Blob| if binary string contains zero
byte. This value cannot appear in |msgpackparse()|
output since blobs were introduced.
array |List|. This value cannot appear in |msgpackparse()|
output.
*msgpack-special-map*
@ -7168,16 +7356,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 +7547,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 +7601,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()
@ -8803,14 +9006,15 @@ stridx({haystack}, {needle} [, {start}]) *stridx()*
*string()*
string({expr}) Return {expr} converted to a String. If {expr} is a Number,
Float, String or a composition of them, then the result can be
parsed back with |eval()|.
Float, String, Blob or a composition of them, then the result
can be parsed back with |eval()|.
{expr} type result ~
String 'string'
Number 123
Float 123.123456 or 1.123456e8 or
`str2float('inf')`
Funcref `function('name')`
Blob 0z00112233.44556677.8899
List [item, item]
Dictionary {key: value, key: value}
Note that in String values the ' character is doubled.
@ -9483,6 +9687,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("")
@ -9567,7 +9772,7 @@ uniq({list} [, {func} [, {dict}]]) *uniq()* *E882*
values({dict}) *values()*
Return a |List| with all the values of {dict}. The |List| is
in arbitrary order.
in arbitrary order. Also see |items()| and |keys()|.
Can also be used as a |method|: >
mydict->values()
@ -9927,14 +10132,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 +10658,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
@ -10687,10 +10898,18 @@ text...
:const x = 1
< is equivalent to: >
:let x = 1
:lockvar 1 x
:lockvar! x
< This is useful if you want to make sure the variable
is not modified.
*E995*
is not modified. If the value is a List or Dictionary
literal then the items also cannot be changed: >
const ll = [1, 2, 3]
let ll[1] = 5 " Error!
< Nested references are not locked: >
let lvar = ['a']
const lconst = [0, lvar]
let lconst[0] = 2 " Error!
let lconst[1][0] = 'b' " OK
< *E995*
|:const| does not allow to for changing a variable. >
:let x = 1
:const x = 2 " Error!
@ -10807,28 +11026,34 @@ text...
NOTE: The ":append" and ":insert" commands don't work
properly inside a ":while" and ":for" loop.
:for {var} in {list} *:for* *E690* *E732*
:for {var} in {object} *:for* *E690* *E732*
:endfo[r] *:endfo* *:endfor*
Repeat the commands between ":for" and ":endfor" for
each item in {list}. Variable {var} is set to the
value of each item.
When an error is detected for a command inside the
loop, execution continues after the "endfor".
Changing {list} inside the loop affects what items are
used. Make a copy if this is unwanted: >
each item in {object}. {object} can be a |List| or
a |Blob|. Variable {var} is set to the value of each
item. When an error is detected for a command inside
the loop, execution continues after the "endfor".
Changing {object} inside the loop affects what items
are used. Make a copy if this is unwanted: >
:for item in copy(mylist)
< When not making a copy, Vim stores a reference to the
next item in the list, before executing the commands
with the current item. Thus the current item can be
removed without effect. Removing any later item means
it will not be found. Thus the following example
works (an inefficient way to make a list empty): >
<
When {object} is a |List| and not making a copy, Vim
stores a reference to the next item in the |List|
before executing the commands with the current item.
Thus the current item can be removed without effect.
Removing any later item means it will not be found.
Thus the following example works (an inefficient way
to make a |List| empty): >
for item in mylist
call remove(mylist, 0)
endfor
< Note that reordering the list (e.g., with sort() or
< Note that reordering the |List| (e.g., with sort() or
reverse()) may have unexpected effects.
When {object} is a |Blob|, Vim always makes a copy to
iterate over. Unlike with |List|, modifying the
|Blob| does not affect the iteration.
:for [{var1}, {var2}, ...] in {listlist}
:endfo[r]
Like ":for" above, but each item in {listlist} must be

View File

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

View File

@ -326,7 +326,8 @@ semantically equivalent in Lua to:
end
Lua nils, numbers, strings, tables and booleans are converted to their
respective Vimscript types. Conversion of other Lua types is an error.
respective Vimscript types. If a Lua string contains a NUL byte, it will be
converted to a |Blob|. Conversion of other Lua types is an error.
The magic global "_A" contains the second argument to luaeval().

View File

@ -444,6 +444,16 @@ void set_option_to(uint64_t channel_id, void *to, int type,
#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \
TYPVAL_ENCODE_CONV_NIL(tv)
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
do { \
const size_t len_ = (size_t)(len); \
const blob_T *const blob_ = (blob); \
kvi_push(edata->stack, STRING_OBJ(((String) { \
.data = len_ != 0 ? xmemdup(blob_->bv_ga.ga_data, len_) : NULL, \
.size = len_ \
}))); \
} while (0)
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
do { \
TYPVAL_ENCODE_CONV_NIL(tv); \
@ -584,6 +594,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata)
#undef TYPVAL_ENCODE_CONV_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING
#undef TYPVAL_ENCODE_CONV_BLOB
#undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT
#undef TYPVAL_ENCODE_CONV_FUNC_START

View File

@ -516,6 +516,7 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output,
/// @param data will be consumed
size_t channel_send(uint64_t id, char *data, size_t len,
bool data_owned, const char **error)
FUNC_ATTR_NONNULL_ALL
{
Channel *chan = find_channel(id);
size_t written = 0;

View File

@ -69,6 +69,7 @@ static char *e_nowhitespace
= N_("E274: No white space allowed before parenthesis");
static char *e_invalwindow = N_("E957: Invalid window number");
static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
static char *e_write2 = N_("E80: Error while writing: %s");
// TODO(ZyX-I): move to eval/executor
static char *e_letwrong = N_("E734: Wrong variable type for %s=");
@ -113,6 +114,8 @@ typedef struct {
int fi_varcount; // nr of variables in the list
listwatch_T fi_lw; // keep an eye on the item used.
list_T *fi_list; // list being used
int fi_bi; // index of blob
blob_T *fi_blob; // blob being used
} forinfo_T;
// values for vv_flags:
@ -227,6 +230,7 @@ static struct vimvar {
VV(VV_TYPE_DICT, "t_dict", VAR_NUMBER, VV_RO),
VV(VV_TYPE_FLOAT, "t_float", VAR_NUMBER, VV_RO),
VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO),
VV(VV_TYPE_BLOB, "t_blob", VAR_NUMBER, VV_RO),
VV(VV_EVENT, "event", VAR_DICT, VV_RO),
VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO),
VV(VV_ARGV, "argv", VAR_LIST, VV_RO),
@ -238,6 +242,7 @@ static struct vimvar {
VV(VV__NULL_STRING, "_null_string", VAR_STRING, VV_RO),
VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO),
VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO),
VV(VV__NULL_BLOB, "_null_blob", VAR_BLOB, VV_RO),
VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO),
};
#undef VV
@ -251,6 +256,7 @@ static struct vimvar {
#define vv_str vv_di.di_tv.vval.v_string
#define vv_list vv_di.di_tv.vval.v_list
#define vv_dict vv_di.di_tv.vval.v_dict
#define vv_blob vv_di.di_tv.vval.v_blob
#define vv_partial vv_di.di_tv.vval.v_partial
#define vv_tv vv_di.di_tv
@ -393,6 +399,7 @@ void eval_init(void)
set_vim_var_nr(VV_TYPE_DICT, VAR_TYPE_DICT);
set_vim_var_nr(VV_TYPE_FLOAT, VAR_TYPE_FLOAT);
set_vim_var_nr(VV_TYPE_BOOL, VAR_TYPE_BOOL);
set_vim_var_nr(VV_TYPE_BLOB, VAR_TYPE_BLOB);
set_vim_var_bool(VV_FALSE, kBoolVarFalse);
set_vim_var_bool(VV_TRUE, kBoolVarTrue);
@ -2059,18 +2066,17 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
return NULL;
}
/*
* Loop until no more [idx] or .key is following.
*/
// Loop until no more [idx] or .key is following.
lp->ll_tv = &v->di_tv;
var1.v_type = VAR_UNKNOWN;
var2.v_type = VAR_UNKNOWN;
while (*p == '[' || (*p == '.' && lp->ll_tv->v_type == VAR_DICT)) {
if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL)
&& !(lp->ll_tv->v_type == VAR_DICT
&& lp->ll_tv->vval.v_dict != NULL)) {
if (!quiet)
EMSG(_("E689: Can only index a List or Dictionary"));
&& !(lp->ll_tv->v_type == VAR_DICT && lp->ll_tv->vval.v_dict != NULL)
&& !(lp->ll_tv->v_type == VAR_BLOB && lp->ll_tv->vval.v_blob != NULL)) {
if (!quiet) {
EMSG(_("E689: Can only index a List, Dictionary or Blob"));
}
return NULL;
}
if (lp->ll_range) {
@ -2119,10 +2125,11 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
tv_clear(&var1);
return NULL;
}
if (rettv != NULL && (rettv->v_type != VAR_LIST
|| rettv->vval.v_list == NULL)) {
if (rettv != NULL
&& !(rettv->v_type == VAR_LIST && rettv->vval.v_list != NULL)
&& !(rettv->v_type == VAR_BLOB && rettv->vval.v_blob != NULL)) {
if (!quiet) {
EMSG(_("E709: [:] requires a List value"));
EMSG(_("E709: [:] requires a List or Blob value"));
}
tv_clear(&var1);
return NULL;
@ -2236,6 +2243,38 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
tv_clear(&var1);
lp->ll_tv = &lp->ll_di->di_tv;
} else if (lp->ll_tv->v_type == VAR_BLOB) {
// Get the number and item for the only or first index of the List.
if (empty1) {
lp->ll_n1 = 0;
} else {
// Is number or string.
lp->ll_n1 = (long)tv_get_number(&var1);
}
tv_clear(&var1);
const int bloblen = tv_blob_len(lp->ll_tv->vval.v_blob);
if (lp->ll_n1 < 0 || lp->ll_n1 > bloblen
|| (lp->ll_range && lp->ll_n1 == bloblen)) {
if (!quiet) {
EMSGN(_(e_blobidx), lp->ll_n1);
}
tv_clear(&var2);
return NULL;
}
if (lp->ll_range && !lp->ll_empty2) {
lp->ll_n2 = (long)tv_get_number(&var2);
tv_clear(&var2);
if (lp->ll_n2 < 0 || lp->ll_n2 >= bloblen || lp->ll_n2 < lp->ll_n1) {
if (!quiet) {
EMSGN(_(e_blobidx), lp->ll_n2);
}
return NULL;
}
}
lp->ll_blob = lp->ll_tv->vval.v_blob;
lp->ll_tv = NULL;
break;
} else {
// Get the number and item for the only or first index of the List.
if (empty1) {
@ -2329,7 +2368,50 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
if (lp->ll_tv == NULL) {
cc = *endp;
*endp = NUL;
if (op != NULL && *op != '=') {
if (lp->ll_blob != NULL) {
if (op != NULL && *op != '=') {
EMSG2(_(e_letwrong), op);
return;
}
if (var_check_lock(lp->ll_blob->bv_lock, lp->ll_name, TV_CSTRING)) {
return;
}
if (lp->ll_range && rettv->v_type == VAR_BLOB) {
if (lp->ll_empty2) {
lp->ll_n2 = tv_blob_len(lp->ll_blob) - 1;
}
if (lp->ll_n2 - lp->ll_n1 + 1 != tv_blob_len(rettv->vval.v_blob)) {
EMSG(_("E972: Blob value does not have the right number of bytes"));
return;
}
if (lp->ll_empty2) {
lp->ll_n2 = tv_blob_len(lp->ll_blob);
}
for (int il = lp->ll_n1, ir = 0; il <= lp->ll_n2; il++) {
tv_blob_set(lp->ll_blob, il, tv_blob_get(rettv->vval.v_blob, ir++));
}
} else {
bool error = false;
const char_u val = tv_get_number_chk(rettv, &error);
if (!error) {
garray_T *const gap = &lp->ll_blob->bv_ga;
// Allow for appending a byte. Setting a byte beyond
// the end is an error otherwise.
if (lp->ll_n1 < gap->ga_len || lp->ll_n1 == gap->ga_len) {
ga_grow(&lp->ll_blob->bv_ga, 1);
tv_blob_set(lp->ll_blob, lp->ll_n1, val);
if (lp->ll_n1 == gap->ga_len) {
gap->ga_len++;
}
}
// error for invalid range was already given in get_lval()
}
}
} else if (op != NULL && *op != '=') {
typval_T tv;
if (is_const) {
@ -2508,19 +2590,32 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip)
if (eval0(skipwhite(expr + 2), &tv, nextcmdp, !skip) == OK) {
*errp = false;
if (!skip) {
l = tv.vval.v_list;
if (tv.v_type != VAR_LIST) {
EMSG(_(e_listreq));
tv_clear(&tv);
} else if (l == NULL) {
// a null list is like an empty list: do nothing
if (tv.v_type == VAR_LIST) {
l = tv.vval.v_list;
if (l == NULL) {
// a null list is like an empty list: do nothing
tv_clear(&tv);
} else {
// No need to increment the refcount, it's already set for
// the list being used in "tv".
fi->fi_list = l;
tv_list_watch_add(l, &fi->fi_lw);
fi->fi_lw.lw_item = tv_list_first(l);
}
} else if (tv.v_type == VAR_BLOB) {
fi->fi_bi = 0;
if (tv.vval.v_blob != NULL) {
typval_T btv;
// Make a copy, so that the iteration still works when the
// blob is changed.
tv_blob_copy(&tv, &btv);
fi->fi_blob = btv.vval.v_blob;
}
tv_clear(&tv);
} else {
/* No need to increment the refcount, it's already set for the
* list being used in "tv". */
fi->fi_list = l;
tv_list_watch_add(l, &fi->fi_lw);
fi->fi_lw.lw_item = tv_list_first(l);
EMSG(_(e_listblobreq));
tv_clear(&tv);
}
}
}
@ -2542,6 +2637,19 @@ bool next_for_item(void *fi_void, char_u *arg)
{
forinfo_T *fi = (forinfo_T *)fi_void;
if (fi->fi_blob != NULL) {
if (fi->fi_bi >= tv_blob_len(fi->fi_blob)) {
return false;
}
typval_T tv;
tv.v_type = VAR_NUMBER;
tv.v_lock = VAR_FIXED;
tv.vval.v_number = tv_blob_get(fi->fi_blob, fi->fi_bi);
fi->fi_bi++;
return ex_let_vars(arg, &tv, true,
fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK;
}
listitem_T *item = fi->fi_lw.lw_item;
if (item == NULL) {
return false;
@ -2565,6 +2673,9 @@ void free_for_info(void *fi_void)
tv_list_watch_remove(fi->fi_list, &fi->fi_lw);
tv_list_unref(fi->fi_list);
}
if (fi != NULL && fi->fi_blob != NULL) {
tv_blob_unref(fi->fi_blob);
}
xfree(fi);
}
@ -2974,7 +3085,7 @@ static int do_lock_var(lval_T *lp, char_u *name_end FUNC_ATTR_UNUSED,
} else {
di->di_flags &= ~DI_FLAGS_LOCK;
}
tv_item_lock(&di->di_tv, deep, lock);
tv_item_lock(&di->di_tv, deep, lock, false);
}
}
} else if (lp->ll_range) {
@ -2982,16 +3093,16 @@ static int do_lock_var(lval_T *lp, char_u *name_end FUNC_ATTR_UNUSED,
// (un)lock a range of List items.
while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) {
tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock);
tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock, false);
li = TV_LIST_ITEM_NEXT(lp->ll_list, li);
lp->ll_n1++;
}
} else if (lp->ll_list != NULL) {
// (un)lock a List item.
tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock);
tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock, false);
} else {
// (un)lock a Dictionary item.
tv_item_lock(&lp->ll_di->di_tv, deep, lock);
tv_item_lock(&lp->ll_di->di_tv, deep, lock, false);
}
return ret;
@ -3607,7 +3718,7 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate)
if (op != '+' && op != '-' && op != '.')
break;
if ((op != '+' || rettv->v_type != VAR_LIST)
if ((op != '+' || (rettv->v_type != VAR_LIST && rettv->v_type != VAR_BLOB))
&& (op == '.' || rettv->v_type != VAR_FLOAT)) {
// For "list + ...", an illegal use of the first operand as
// a number cannot be determined before evaluating the 2nd
@ -3653,6 +3764,21 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate)
tv_clear(rettv);
rettv->v_type = VAR_STRING;
rettv->vval.v_string = p;
} else if (op == '+' && rettv->v_type == VAR_BLOB
&& var2.v_type == VAR_BLOB) {
const blob_T *const b1 = rettv->vval.v_blob;
const blob_T *const b2 = var2.vval.v_blob;
blob_T *const b = tv_blob_alloc();
for (int i = 0; i < tv_blob_len(b1); i++) {
ga_append(&b->bv_ga, tv_blob_get(b1, i));
}
for (int i = 0; i < tv_blob_len(b2); i++) {
ga_append(&b->bv_ga, tv_blob_get(b2, i));
}
tv_clear(rettv);
tv_blob_set_ret(rettv, b);
} else if (op == '+' && rettv->v_type == VAR_LIST
&& var2.v_type == VAR_LIST) {
// Concatenate Lists.
@ -3673,10 +3799,12 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate)
} else {
n1 = tv_get_number_chk(rettv, &error);
if (error) {
/* This can only happen for "list + non-list". For
* "non-list + ..." or "something - ...", we returned
* before evaluating the 2nd operand. */
// This can only happen for "list + non-list" or
// "blob + non-blob". For "non-list + ..." or
// "something - ...", we returned before evaluating the
// 2nd operand.
tv_clear(rettv);
tv_clear(&var2);
return FAIL;
}
if (var2.v_type == VAR_FLOAT)
@ -3850,6 +3978,7 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)
// Handle sixth level expression:
// number number constant
// 0zFFFFFFFF Blob constant
// "string" string constant
// 'string' literal string constant
// &option-name option value
@ -3946,7 +4075,37 @@ static int eval7(
rettv->v_type = VAR_FLOAT;
rettv->vval.v_float = f;
}
} else if (**arg == '0' && ((*arg)[1] == 'z' || (*arg)[1] == 'Z')) {
blob_T *blob = NULL;
// Blob constant: 0z0123456789abcdef
if (evaluate) {
blob = tv_blob_alloc();
}
char_u *bp;
for (bp = *arg + 2; ascii_isxdigit(bp[0]); bp += 2) {
if (!ascii_isxdigit(bp[1])) {
if (blob != NULL) {
EMSG(_("E973: Blob literal should have an even number of hex "
"characters"));
ga_clear(&blob->bv_ga);
XFREE_CLEAR(blob);
}
ret = FAIL;
break;
}
if (blob != NULL) {
ga_append(&blob->bv_ga, (hex2nr(*bp) << 4) + hex2nr(*(bp + 1)));
}
if (bp[2] == '.' && ascii_isxdigit(bp[3])) {
bp++;
}
}
if (blob != NULL) {
tv_blob_set_ret(rettv, blob);
}
*arg = bp;
} else {
// decimal, hex or octal number
vim_str2nr(*arg, NULL, &len, STR2NR_ALL, &n, NULL, 0, true);
if (len == 0) {
EMSG2(_(e_invexpr2), *arg);
@ -4336,7 +4495,8 @@ eval_index(
case VAR_STRING:
case VAR_NUMBER:
case VAR_LIST:
case VAR_DICT: {
case VAR_DICT:
case VAR_BLOB: {
break;
}
}
@ -4462,6 +4622,53 @@ eval_index(
rettv->vval.v_string = (char_u *)v;
break;
}
case VAR_BLOB: {
len = tv_blob_len(rettv->vval.v_blob);
if (range) {
// The resulting variable is a sub-blob. If the indexes
// are out of range the result is empty.
if (n1 < 0) {
n1 = len + n1;
if (n1 < 0) {
n1 = 0;
}
}
if (n2 < 0) {
n2 = len + n2;
} else if (n2 >= len) {
n2 = len - 1;
}
if (n1 >= len || n2 < 0 || n1 > n2) {
tv_clear(rettv);
rettv->v_type = VAR_BLOB;
rettv->vval.v_blob = NULL;
} else {
blob_T *const blob = tv_blob_alloc();
ga_grow(&blob->bv_ga, n2 - n1 + 1);
blob->bv_ga.ga_len = n2 - n1 + 1;
for (long i = n1; i <= n2; i++) {
tv_blob_set(blob, i - n1, tv_blob_get(rettv->vval.v_blob, i));
}
tv_clear(rettv);
tv_blob_set_ret(rettv, blob);
}
} else {
// The resulting variable is a byte value.
// If the index is too big or negative that is an error.
if (n1 < 0) {
n1 = len + n1;
}
if (n1 < len && n1 >= 0) {
const int v = (int)tv_blob_get(rettv->vval.v_blob, n1);
tv_clear(rettv);
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = v;
} else {
EMSGN(_(e_blobidx), n1);
}
}
break;
}
case VAR_LIST: {
len = tv_list_len(rettv->vval.v_list);
if (n1 < 0) {
@ -5398,7 +5605,8 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
case VAR_SPECIAL:
case VAR_FLOAT:
case VAR_NUMBER:
case VAR_STRING: {
case VAR_STRING:
case VAR_BLOB: {
break;
}
}
@ -6161,6 +6369,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
dict_T *d = NULL;
typval_T save_val;
typval_T save_key;
blob_T *b = NULL;
int rem = false;
int todo;
char_u *ermsg = (char_u *)(map ? "map()" : "filter()");
@ -6170,7 +6379,12 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
int save_did_emsg;
int idx = 0;
if (argvars[0].v_type == VAR_LIST) {
if (argvars[0].v_type == VAR_BLOB) {
tv_copy(&argvars[0], rettv);
if ((b = argvars[0].vval.v_blob) == NULL) {
return;
}
} else if (argvars[0].v_type == VAR_LIST) {
tv_copy(&argvars[0], rettv);
if ((l = argvars[0].vval.v_list) == NULL
|| (!map
@ -6184,7 +6398,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
return;
}
} else {
EMSG2(_(e_listdictarg), ermsg);
EMSG2(_(e_listdictblobarg), ermsg);
return;
}
@ -6234,6 +6448,34 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
}
}
hash_unlock(ht);
} else if (argvars[0].v_type == VAR_BLOB) {
vimvars[VV_KEY].vv_type = VAR_NUMBER;
for (int i = 0; i < b->bv_ga.ga_len; i++) {
typval_T tv;
tv.v_type = VAR_NUMBER;
const varnumber_T val = tv_blob_get(b, i);
tv.vval.v_number = val;
vimvars[VV_KEY].vv_nr = idx;
if (filter_map_one(&tv, expr, map, &rem) == FAIL || did_emsg) {
break;
}
if (tv.v_type != VAR_NUMBER) {
EMSG(_(e_invalblob));
return;
}
if (map) {
if (tv.vval.v_number != val) {
tv_blob_set(b, i, tv.vval.v_number);
}
} else if (rem) {
char_u *const p = (char_u *)argvars[0].vval.v_blob->bv_ga.ga_data;
memmove(p + i, p + i + 1, (size_t)b->bv_ga.ga_len - i - 1);
b->bv_ga.ga_len--;
i--;
}
idx++;
}
} else {
assert(argvars[0].v_type == VAR_LIST);
vimvars[VV_KEY].vv_type = VAR_NUMBER;
@ -7728,10 +7970,61 @@ bool write_list(FileDescriptor *const fp, const list_T *const list,
}
return true;
write_list_error:
emsgf(_("E80: Error while writing: %s"), os_strerror(error));
emsgf(_(e_write2), os_strerror(error));
return false;
}
/// Write a blob to file with descriptor `fp`.
///
/// @param[in] fp File to write to.
/// @param[in] blob Blob to write.
///
/// @return true on success, or false on failure.
bool write_blob(FileDescriptor *const fp, const blob_T *const blob)
FUNC_ATTR_NONNULL_ARG(1)
{
int error = 0;
const int len = tv_blob_len(blob);
if (len > 0) {
const ptrdiff_t written = file_write(fp, blob->bv_ga.ga_data, (size_t)len);
if (written < (ptrdiff_t)len) {
error = (int)written;
goto write_blob_error;
}
}
error = file_flush(fp);
if (error != 0) {
goto write_blob_error;
}
return true;
write_blob_error:
EMSG2(_(e_write2), os_strerror(error));
return false;
}
/// Read a blob from a file `fd`.
///
/// @param[in] fd File to read from.
/// @param[in,out] blob Blob to write to.
///
/// @return true on success, or false on failure.
bool read_blob(FILE *const fd, blob_T *const blob)
FUNC_ATTR_NONNULL_ALL
{
FileInfo file_info;
if (!os_fileinfo_fd(fileno(fd), &file_info)) {
return false;
}
const int size = (int)os_fileinfo_size(&file_info);
ga_grow(&blob->bv_ga, size);
blob->bv_ga.ga_len = size;
if (fread(blob->bv_ga.ga_data, 1, blob->bv_ga.ga_len, fd)
< (size_t)blob->bv_ga.ga_len) {
return false;
}
return true;
}
/// Saves a typval_T as a string.
///
/// For lists or buffers, replaces NLs with NUL and separates items with NLs.
@ -9248,7 +9541,10 @@ static void set_var_const(const char *name, const size_t name_len,
}
if (is_const) {
tv_item_lock(&v->di_tv, 1, true);
// Like :lockvar! name: lock the value and what it contains, but only
// if the reference count is up to one. That locks only literal
// values.
tv_item_lock(&v->di_tv, DICT_MAXNEST, true, true);
}
}
@ -9456,6 +9752,9 @@ int var_item_copy(const vimconv_T *const conv,
ret = FAIL;
}
break;
case VAR_BLOB:
tv_blob_copy(from, to);
break;
case VAR_DICT:
to->v_type = VAR_DICT;
to->v_lock = VAR_UNLOCKED;
@ -10815,6 +11114,29 @@ int typval_compare(
// For "is" a different type always means false, for "notis"
// it means true.
n1 = type == EXPR_ISNOT;
} else if (typ1->v_type == VAR_BLOB || typ2->v_type == VAR_BLOB) {
if (type_is) {
n1 = typ1->v_type == typ2->v_type
&& typ1->vval.v_blob == typ2->vval.v_blob;
if (type == EXPR_ISNOT) {
n1 = !n1;
}
} else if (typ1->v_type != typ2->v_type
|| (type != EXPR_EQUAL && type != EXPR_NEQUAL)) {
if (typ1->v_type != typ2->v_type) {
EMSG(_("E977: Can only compare Blob with Blob"));
} else {
EMSG(_(e_invalblob));
}
tv_clear(typ1);
return FAIL;
} else {
// Compare two Blobs for being equal or unequal.
n1 = tv_blob_equal(typ1->vval.v_blob, typ2->vval.v_blob);
if (type == EXPR_NEQUAL) {
n1 = !n1;
}
}
} else if (typ1->v_type == VAR_LIST || typ2->v_type == VAR_LIST) {
if (type_is) {
n1 = typ1->v_type == typ2->v_type

View File

@ -63,6 +63,7 @@ typedef struct lval_S {
dict_T *ll_dict; ///< The Dictionary or NULL.
dictitem_T *ll_di; ///< The dictitem or NULL.
char_u *ll_newkey; ///< New key for Dict in allocated memory or NULL.
blob_T *ll_blob; ///< The Blob or NULL.
} lval_T;
/// enum used by var_flavour()
@ -154,6 +155,7 @@ typedef enum {
VV_TYPE_DICT,
VV_TYPE_FLOAT,
VV_TYPE_BOOL,
VV_TYPE_BLOB,
VV_EVENT,
VV_ECHOSPACE,
VV_ARGV,
@ -165,6 +167,7 @@ typedef enum {
VV__NULL_STRING, // String with NULL value. For test purposes only.
VV__NULL_LIST, // List with NULL value. For test purposes only.
VV__NULL_DICT, // Dictionary with NULL value. For test purposes only.
VV__NULL_BLOB, // Blob with NULL value. For test purposes only.
VV_LUA,
} VimVarIndex;

View File

@ -250,7 +250,7 @@ return {
min={args=1, base=1},
mkdir={args={1, 3}},
mode={args={0, 1}},
msgpackdump={args=1},
msgpackdump={args={1, 2}},
msgpackparse={args=1},
nextnonblank={args=1},
nr2char={args={1, 2}},

View File

@ -246,14 +246,15 @@ list_T *decode_create_map_special_dict(typval_T *const ret_tv,
/// Convert char* string to typval_T
///
/// Depending on whether string has (no) NUL bytes, it may use a special
/// dictionary or decode string to VAR_STRING.
/// dictionary, VAR_BLOB, or decode string to VAR_STRING.
///
/// @param[in] s String to decode.
/// @param[in] len String length.
/// @param[in] hasnul Whether string has NUL byte, not or it was not yet
/// determined.
/// @param[in] binary If true, save special string type as kMPBinary,
/// otherwise kMPString.
/// @param[in] binary Determines decode type if string has NUL bytes.
/// If true convert string to VAR_BLOB, otherwise to the
/// kMPString special type.
/// @param[in] s_allocated If true, then `s` was allocated and can be saved in
/// a returned structure. If it is not saved there, it
/// will be freed.
@ -269,21 +270,28 @@ typval_T decode_string(const char *const s, const size_t len,
? ((s != NULL) && (memchr(s, NUL, len) != NULL))
: (bool)hasnul);
if (really_hasnul) {
list_T *const list = tv_list_alloc(kListLenMayKnow);
tv_list_ref(list);
typval_T tv;
create_special_dict(&tv, binary ? kMPBinary : kMPString, ((typval_T) {
.v_type = VAR_LIST,
.v_lock = VAR_UNLOCKED,
.vval = { .v_list = list },
}));
const int elw_ret = encode_list_write((void *)list, s, len);
if (s_allocated) {
xfree((void *)s);
}
if (elw_ret == -1) {
tv_clear(&tv);
return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED };
tv.v_lock = VAR_UNLOCKED;
if (binary) {
tv_blob_alloc_ret(&tv);
ga_concat_len(&tv.vval.v_blob->bv_ga, s, len);
} else {
list_T *const list = tv_list_alloc(kListLenMayKnow);
tv_list_ref(list);
create_special_dict(&tv, kMPString,
((typval_T){
.v_type = VAR_LIST,
.v_lock = VAR_UNLOCKED,
.vval = { .v_list = list },
}));
const int elw_ret = encode_list_write((void *)list, s, len);
if (s_allocated) {
xfree((void *)s);
}
if (elw_ret == -1) {
tv_clear(&tv);
return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED };
}
}
return tv;
} else {

View File

@ -47,6 +47,14 @@ const char *const encode_special_var_names[] = {
# include "eval/encode.c.generated.h"
#endif
/// Msgpack callback for writing to a Blob
int encode_blob_write(void *const data, const char *const buf, const size_t len)
FUNC_ATTR_NONNULL_ARG(1)
{
ga_concat_len(&((blob_T *)data)->bv_ga, buf, len);
return (int)len;
}
/// Msgpack callback for writing to readfile()-style list
int encode_list_write(void *const data, const char *const buf, const size_t len)
FUNC_ATTR_NONNULL_ARG(1)
@ -319,6 +327,30 @@ int encode_read_from_list(ListReaderState *const state, char *const buf,
#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type)
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
do { \
const blob_T *const blob_ = (blob); \
const int len_ = (len); \
if (len_ == 0) { \
ga_concat(gap, "0z"); \
} else { \
/* Allocate space for "0z", the two hex chars per byte, and a */ \
/* "." separator after every eight hex chars. */ \
/* Example: "0z00112233.44556677.8899" */ \
ga_grow(gap, 2 + 2 * len_ + (len_ - 1) / 4); \
ga_concat(gap, "0z"); \
char numbuf[NUMBUFLEN]; \
for (int i_ = 0; i_ < len_; i_++) { \
if (i_ > 0 && (i_ & 3) == 0) { \
ga_append(gap, '.'); \
} \
vim_snprintf((char *)numbuf, ARRAY_SIZE(numbuf), "%02X", \
(int)tv_blob_get(blob_, i_)); \
ga_concat(gap, numbuf); \
} \
} \
} while (0)
#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
do { \
char numbuf[NUMBUFLEN]; \
@ -705,6 +737,28 @@ static inline int convert_to_json_string(garray_T *const gap,
return FAIL; \
} while (0)
#undef TYPVAL_ENCODE_CONV_BLOB
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
do { \
const blob_T *const blob_ = (blob); \
const int len_ = (len); \
if (len_ == 0) { \
ga_concat(gap, "[]"); \
} else { \
ga_append(gap, '['); \
char numbuf[NUMBUFLEN]; \
for (int i_ = 0; i_ < len_; i_++) { \
if (i_ > 0) { \
ga_concat(gap, ", "); \
} \
vim_snprintf((char *)numbuf, ARRAY_SIZE(numbuf), "%d", \
(int)tv_blob_get(blob_, i_)); \
ga_concat(gap, numbuf); \
} \
ga_append(gap, ']'); \
} \
} while (0)
#undef TYPVAL_ENCODE_CONV_FUNC_START
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
return conv_error(_("E474: Error while dumping %s, %s: " \
@ -770,6 +824,7 @@ bool encode_check_json_key(const typval_T *const tv)
#undef TYPVAL_ENCODE_CONV_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING
#undef TYPVAL_ENCODE_CONV_BLOB
#undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT
#undef TYPVAL_ENCODE_CONV_FUNC_START
@ -904,6 +959,15 @@ char *encode_tv2json(typval_T *tv, size_t *len)
} \
} while (0)
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
do { \
const size_t len_ = (size_t)(len); \
msgpack_pack_bin(packer, len_); \
if (len_ > 0) { \
msgpack_pack_bin_body(packer, (blob)->bv_ga.ga_data, len_); \
} \
} while (0)
#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
msgpack_pack_int64(packer, (int64_t)(num))
@ -982,6 +1046,7 @@ char *encode_tv2json(typval_T *tv, size_t *len)
#undef TYPVAL_ENCODE_CONV_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING
#undef TYPVAL_ENCODE_CONV_BLOB
#undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT
#undef TYPVAL_ENCODE_CONV_FUNC_START

View File

@ -38,6 +38,20 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2,
case VAR_SPECIAL: {
break;
}
case VAR_BLOB: {
if (*op != '+' || tv2->v_type != VAR_BLOB) {
break;
}
// Blob += Blob
if (tv1->vval.v_blob != NULL && tv2->vval.v_blob != NULL) {
blob_T *const b1 = tv1->vval.v_blob;
blob_T *const b2 = tv2->vval.v_blob;
for (int i = 0; i < tv_blob_len(b2); i++) {
ga_append(&b1->bv_ga, (char)tv_blob_get(b2, i));
}
}
return OK;
}
case VAR_LIST: {
if (*op != '+' || tv2->v_type != VAR_LIST) {
break;

View File

@ -96,6 +96,7 @@ PRAGMA_DIAG_POP
static char *e_listarg = N_("E686: Argument of %s must be a List");
static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob");
static char *e_invalwindow = N_("E957: Invalid window number");
/// Dummy va_list for passing to vim_snprintf
@ -321,8 +322,20 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)
tv_list_append_tv(l, &argvars[1]);
tv_copy(&argvars[0], rettv);
}
} else if (argvars[0].v_type == VAR_BLOB) {
blob_T *const b = argvars[0].vval.v_blob;
if (b != NULL
&& !var_check_lock(b->bv_lock, N_("add() argument"), TV_TRANSLATE)) {
bool error = false;
const varnumber_T n = tv_get_number_chk(&argvars[1], &error);
if (!error) {
ga_append(&b->bv_ga, (int)n);
tv_copy(&argvars[0], rettv);
}
}
} else {
EMSG(_(e_listreq));
EMSG(_(e_listblobreq));
}
}
@ -959,7 +972,17 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
ptrdiff_t input_len = 0;
char *input = save_tv_as_string(&argvars[1], &input_len, false);
char *input = NULL;
if (argvars[1].v_type == VAR_BLOB) {
const blob_T *const b = argvars[1].vval.v_blob;
input_len = tv_blob_len(b);
if (input_len > 0) {
input = xmemdup(b->bv_ga.ga_data, input_len);
}
} else {
input = save_tv_as_string(&argvars[1], &input_len, false);
}
if (!input) {
// Either the error has been handled by save_tv_as_string(),
// or there is no input to send.
@ -1874,6 +1897,10 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr)
n = argvars[0].vval.v_special == kSpecialVarNull;
break;
}
case VAR_BLOB: {
n = (tv_blob_len(argvars[0].vval.v_blob) == 0);
break;
}
case VAR_UNKNOWN: {
internal_error("f_empty(UNKNOWN)");
break;
@ -2791,7 +2818,23 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
typval_T *tv = NULL;
bool what_is_dict = false;
if (argvars[0].v_type == VAR_LIST) {
if (argvars[0].v_type == VAR_BLOB) {
bool error = false;
int idx = tv_get_number_chk(&argvars[1], &error);
if (!error) {
rettv->v_type = VAR_NUMBER;
if (idx < 0) {
idx = tv_blob_len(argvars[0].vval.v_blob) + idx;
}
if (idx < 0 || idx >= tv_blob_len(argvars[0].vval.v_blob)) {
rettv->vval.v_number = -1;
} else {
rettv->vval.v_number = tv_blob_get(argvars[0].vval.v_blob, idx);
tv = rettv;
}
}
} else if (argvars[0].v_type == VAR_LIST) {
if ((l = argvars[0].vval.v_list) != NULL) {
bool error = false;
@ -2852,7 +2895,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
} else {
EMSG2(_(e_listdictarg), "get()");
EMSG2(_(e_listdictblobarg), "get()");
}
if (tv == NULL) {
@ -4791,8 +4834,38 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool ic = false;
rettv->vval.v_number = -1;
if (argvars[0].v_type != VAR_LIST) {
EMSG(_(e_listreq));
if (argvars[0].v_type == VAR_BLOB) {
bool error = false;
int start = 0;
if (argvars[2].v_type != VAR_UNKNOWN) {
start = tv_get_number_chk(&argvars[2], &error);
if (error) {
return;
}
}
blob_T *const b = argvars[0].vval.v_blob;
if (b == NULL) {
return;
}
if (start < 0) {
start = tv_blob_len(b) + start;
if (start < 0) {
start = 0;
}
}
for (idx = start; idx < tv_blob_len(b); idx++) {
typval_T tv;
tv.v_type = VAR_NUMBER;
tv.vval.v_number = tv_blob_get(b, idx);
if (tv_equal(&tv, &argvars[1], ic, false)) {
rettv->vval.v_number = idx;
return;
}
}
return;
} else if (argvars[0].v_type != VAR_LIST) {
EMSG(_(e_listblobreq));
return;
}
list_T *const l = argvars[0].vval.v_list;
@ -4921,8 +4994,46 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
list_T *l;
bool error = false;
if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listarg), "insert()");
if (argvars[0].v_type == VAR_BLOB) {
blob_T *const b = argvars[0].vval.v_blob;
if (b == NULL
|| var_check_lock(b->bv_lock, N_("insert() argument"),
TV_TRANSLATE)) {
return;
}
long before = 0;
const int len = tv_blob_len(b);
if (argvars[2].v_type != VAR_UNKNOWN) {
before = (long)tv_get_number_chk(&argvars[2], &error);
if (error) {
return; // type error; errmsg already given
}
if (before < 0 || before > len) {
EMSG2(_(e_invarg2), tv_get_string(&argvars[2]));
return;
}
}
const int val = tv_get_number_chk(&argvars[1], &error);
if (error) {
return;
}
if (val < 0 || val > 255) {
EMSG2(_(e_invarg2), tv_get_string(&argvars[1]));
return;
}
ga_grow(&b->bv_ga, 1);
char_u *const p = (char_u *)b->bv_ga.ga_data;
memmove(p + before + 1, p + before, (size_t)len - before);
*(p + before) = val;
b->bv_ga.ga_len++;
tv_copy(&argvars[0], rettv);
} else if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listblobarg), "insert()");
} else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
N_("insert() argument"), TV_TRANSLATE)) {
long before = 0;
@ -5582,6 +5693,10 @@ static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr)
tv_get_string(&argvars[0]));
break;
}
case VAR_BLOB: {
rettv->vval.v_number = tv_blob_len(argvars[0].vval.v_blob);
break;
}
case VAR_LIST: {
rettv->vval.v_number = tv_list_len(argvars[0].vval.v_list);
break;
@ -6392,9 +6507,16 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
EMSG2(_(e_listarg), "msgpackdump()");
return;
}
list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
list_T *const list = argvars[0].vval.v_list;
msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write);
msgpack_packer *packer;
if (argvars[1].v_type != VAR_UNKNOWN
&& strequal(tv_get_string(&argvars[1]), "B")) {
tv_blob_alloc_ret(rettv);
packer = msgpack_packer_new(rettv->vval.v_blob, &encode_blob_write);
} else {
packer = msgpack_packer_new(tv_list_alloc_ret(rettv, kListLenMayKnow),
&encode_list_write);
}
const char *const msg = _("msgpackdump() argument, index %i");
// Assume that translation will not take more then 4 times more space
char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN];
@ -6402,23 +6524,50 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
TV_LIST_ITER(list, li, {
vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx);
idx++;
if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {
if (encode_vim_to_msgpack(packer, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {
break;
}
});
msgpack_packer_free(lpacker);
msgpack_packer_free(packer);
}
/// "msgpackparse" function
static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static int msgpackparse_convert_item(const msgpack_object data,
const msgpack_unpack_return result,
list_T *const ret_list,
const bool fail_if_incomplete)
FUNC_ATTR_NONNULL_ALL
{
if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listarg), "msgpackparse()");
return;
switch (result) {
case MSGPACK_UNPACK_PARSE_ERROR:
EMSG2(_(e_invarg2), "Failed to parse msgpack string");
return FAIL;
case MSGPACK_UNPACK_NOMEM_ERROR:
EMSG(_(e_outofmem));
return FAIL;
case MSGPACK_UNPACK_CONTINUE:
if (fail_if_incomplete) {
EMSG2(_(e_invarg2), "Incomplete msgpack string");
return FAIL;
}
return NOTDONE;
case MSGPACK_UNPACK_SUCCESS: {
typval_T tv = { .v_type = VAR_UNKNOWN };
if (msgpack_to_vim(data, &tv) == FAIL) {
EMSG2(_(e_invarg2), "Failed to convert msgpack string");
return FAIL;
}
tv_list_append_owned_tv(ret_list, tv);
return OK;
}
default:
abort();
}
list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
const list_T *const list = argvars[0].vval.v_list;
}
static void msgpackparse_unpack_list(const list_T *const list,
list_T *const ret_list)
FUNC_ATTR_NONNULL_ARG(2)
{
if (tv_list_len(list) == 0) {
return;
}
@ -6437,43 +6586,28 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
do {
if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) {
EMSG(_(e_outofmem));
goto f_msgpackparse_exit;
goto end;
}
size_t read_bytes;
const int rlret = encode_read_from_list(
&lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, &read_bytes);
if (rlret == FAIL) {
EMSG2(_(e_invarg2), "List item is not a string");
goto f_msgpackparse_exit;
goto end;
}
msgpack_unpacker_buffer_consumed(unpacker, read_bytes);
if (read_bytes == 0) {
break;
}
while (unpacker->off < unpacker->used) {
const msgpack_unpack_return result = msgpack_unpacker_next(unpacker,
&unpacked);
if (result == MSGPACK_UNPACK_PARSE_ERROR) {
EMSG2(_(e_invarg2), "Failed to parse msgpack string");
goto f_msgpackparse_exit;
}
if (result == MSGPACK_UNPACK_NOMEM_ERROR) {
EMSG(_(e_outofmem));
goto f_msgpackparse_exit;
}
if (result == MSGPACK_UNPACK_SUCCESS) {
typval_T tv = { .v_type = VAR_UNKNOWN };
if (msgpack_to_vim(unpacked.data, &tv) == FAIL) {
EMSG2(_(e_invarg2), "Failed to convert msgpack string");
goto f_msgpackparse_exit;
}
tv_list_append_owned_tv(ret_list, tv);
}
if (result == MSGPACK_UNPACK_CONTINUE) {
if (rlret == OK) {
EMSG2(_(e_invarg2), "Incomplete msgpack string");
}
const msgpack_unpack_return result
= msgpack_unpacker_next(unpacker, &unpacked);
const int conv_result = msgpackparse_convert_item(unpacked.data, result,
ret_list, rlret == OK);
if (conv_result == NOTDONE) {
break;
} else if (conv_result == FAIL) {
goto end;
}
}
if (rlret == OK) {
@ -6481,10 +6615,47 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
} while (true);
f_msgpackparse_exit:
msgpack_unpacked_destroy(&unpacked);
end:
msgpack_unpacker_free(unpacker);
return;
msgpack_unpacked_destroy(&unpacked);
}
static void msgpackparse_unpack_blob(const blob_T *const blob,
list_T *const ret_list)
FUNC_ATTR_NONNULL_ARG(2)
{
const int len = tv_blob_len(blob);
if (len == 0) {
return;
}
msgpack_unpacked unpacked;
msgpack_unpacked_init(&unpacked);
for (size_t offset = 0; offset < (size_t)len;) {
const msgpack_unpack_return result
= msgpack_unpack_next(&unpacked, blob->bv_ga.ga_data, len, &offset);
if (msgpackparse_convert_item(unpacked.data, result, ret_list, true)
!= OK) {
break;
}
}
msgpack_unpacked_destroy(&unpacked);
}
/// "msgpackparse" function
static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
FUNC_ATTR_NONNULL_ALL
{
if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) {
EMSG2(_(e_listblobarg), "msgpackparse()");
return;
}
list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
if (argvars[0].v_type == VAR_LIST) {
msgpackparse_unpack_list(argvars[0].vval.v_list, ret_list);
} else {
msgpackparse_unpack_blob(argvars[0].vval.v_blob, ret_list);
}
}
/*
@ -6895,6 +7066,7 @@ static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
bool binary = false;
bool blob = false;
FILE *fd;
char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1
int io_size = sizeof(buf);
@ -6907,22 +7079,41 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (argvars[1].v_type != VAR_UNKNOWN) {
if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
binary = true;
} else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) {
blob = true;
}
if (argvars[2].v_type != VAR_UNKNOWN) {
maxline = tv_get_number(&argvars[2]);
}
}
list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown);
// Always open the file in binary mode, library functions have a mind of
// their own about CR-LF conversion.
const char *const fname = tv_get_string(&argvars[0]);
if (os_isdir((const char_u *)fname)) {
EMSG2(_(e_isadir2), fname);
return;
}
if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) {
EMSG2(_(e_notopen), *fname == NUL ? _("<empty>") : fname);
return;
}
if (blob) {
tv_blob_alloc_ret(rettv);
if (!read_blob(fd, rettv->vval.v_blob)) {
EMSG2(_(e_notread), fname);
// An empty blob is returned on error.
tv_blob_free(rettv->vval.v_blob);
rettv->vval.v_blob = NULL;
}
fclose(fd);
return;
}
list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown);
while (maxline < 0 || tv_list_len(l) < maxline) {
readlen = (int)fread(buf, 1, io_size, fd);
@ -7191,8 +7382,64 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
}
} else if (argvars[0].v_type == VAR_BLOB) {
blob_T *const b = argvars[0].vval.v_blob;
if (b != NULL && var_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE)) {
return;
}
bool error = false;
idx = (long)tv_get_number_chk(&argvars[1], &error);
if (!error) {
const int len = tv_blob_len(b);
if (idx < 0) {
// count from the end
idx = len + idx;
}
if (idx < 0 || idx >= len) {
EMSGN(_(e_blobidx), idx);
return;
}
if (argvars[2].v_type == VAR_UNKNOWN) {
// Remove one item, return its value.
char_u *const p = (char_u *)b->bv_ga.ga_data;
rettv->vval.v_number = (varnumber_T)(*(p + idx));
memmove(p + idx, p + idx + 1, (size_t)len - idx - 1);
b->bv_ga.ga_len--;
} else {
// Remove range of items, return blob with values.
end = (long)tv_get_number_chk(&argvars[2], &error);
if (error) {
return;
}
if (end < 0) {
// count from the end
end = len + end;
}
if (end >= len || idx > end) {
EMSGN(_(e_blobidx), end);
return;
}
blob_T *const blob = tv_blob_alloc();
blob->bv_ga.ga_len = end - idx + 1;
ga_grow(&blob->bv_ga, end - idx + 1);
char_u *const p = (char_u *)b->bv_ga.ga_data;
memmove((char_u *)blob->bv_ga.ga_data, p + idx,
(size_t)(end - idx + 1));
tv_blob_set_ret(rettv, blob);
if (len - end - 1 > 0) {
memmove(p + idx, p + end + 1, (size_t)(len - end - 1));
}
b->bv_ga.ga_len -= end - idx + 1;
}
}
} else if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listdictarg), "remove()");
EMSG2(_(e_listdictblobarg), "remove()");
} else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
arg_errmsg, TV_TRANSLATE)) {
bool error = false;
@ -7466,13 +7713,25 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
list_T *l;
if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listarg), "reverse()");
} else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
N_("reverse() argument"), TV_TRANSLATE)) {
tv_list_reverse(l);
tv_list_set_ret(rettv, l);
if (argvars[0].v_type == VAR_BLOB) {
blob_T *const b = argvars[0].vval.v_blob;
const int len = tv_blob_len(b);
for (int i = 0; i < len / 2; i++) {
const char_u tmp = tv_blob_get(b, i);
tv_blob_set(b, i, tv_blob_get(b, len - i - 1));
tv_blob_set(b, len - i - 1, tmp);
}
tv_blob_set_ret(rettv, b);
} else if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listblobarg), "reverse()");
} else {
list_T *const l = argvars[0].vval.v_list;
if (!var_check_lock(tv_list_locked(l), N_("reverse() argument"),
TV_TRANSLATE)) {
tv_list_reverse(l);
tv_list_set_ret(rettv, l);
}
}
}
@ -11292,15 +11551,16 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr)
int n = -1;
switch (argvars[0].v_type) {
case VAR_NUMBER: n = VAR_TYPE_NUMBER; break;
case VAR_STRING: n = VAR_TYPE_STRING; break;
case VAR_NUMBER: n = VAR_TYPE_NUMBER; break;
case VAR_STRING: n = VAR_TYPE_STRING; break;
case VAR_PARTIAL:
case VAR_FUNC: n = VAR_TYPE_FUNC; break;
case VAR_LIST: n = VAR_TYPE_LIST; break;
case VAR_DICT: n = VAR_TYPE_DICT; break;
case VAR_FLOAT: n = VAR_TYPE_FLOAT; break;
case VAR_BOOL: n = VAR_TYPE_BOOL; break;
case VAR_SPECIAL:n = VAR_TYPE_SPECIAL; break;
case VAR_FUNC: n = VAR_TYPE_FUNC; break;
case VAR_LIST: n = VAR_TYPE_LIST; break;
case VAR_DICT: n = VAR_TYPE_DICT; break;
case VAR_FLOAT: n = VAR_TYPE_FLOAT; break;
case VAR_BOOL: n = VAR_TYPE_BOOL; break;
case VAR_SPECIAL: n = VAR_TYPE_SPECIAL; break;
case VAR_BLOB: n = VAR_TYPE_BLOB; break;
case VAR_UNKNOWN: {
internal_error("f_type(UNKNOWN)");
break;
@ -11679,16 +11939,17 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listarg), "writefile()");
if (argvars[0].v_type == VAR_LIST) {
TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) {
return;
}
});
} else if (argvars[0].v_type != VAR_BLOB) {
EMSG2(_(e_invarg2),
_("writefile() first argument must be a List or a Blob"));
return;
}
const list_T *const list = argvars[0].vval.v_list;
TV_LIST_ITER_CONST(list, li, {
if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) {
return;
}
});
bool binary = false;
bool append = false;
@ -11728,7 +11989,13 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
emsgf(_("E482: Can't open file %s for writing: %s"),
fname, os_strerror(error));
} else {
if (write_list(&fp, list, binary)) {
bool write_ok;
if (argvars[0].v_type == VAR_BLOB) {
write_ok = write_blob(&fp, argvars[0].vval.v_blob);
} else {
write_ok = write_list(&fp, argvars[0].vval.v_list, binary);
}
if (write_ok) {
rettv->vval.v_number = 0;
}
if ((error = file_close(&fp, do_fsync)) != 0) {

View File

@ -2125,6 +2125,77 @@ void tv_dict_set_keys_readonly(dict_T *const dict)
});
}
//{{{1 Blobs
//{{{2 Alloc/free
/// Allocate an empty blob.
///
/// Caller should take care of the reference count.
///
/// @return [allocated] new blob.
blob_T *tv_blob_alloc(void)
FUNC_ATTR_NONNULL_RET
{
blob_T *const blob = xcalloc(1, sizeof(blob_T));
ga_init(&blob->bv_ga, 1, 100);
return blob;
}
/// Free a blob. Ignores the reference count.
///
/// @param[in,out] b Blob to free.
void tv_blob_free(blob_T *const b)
FUNC_ATTR_NONNULL_ALL
{
ga_clear(&b->bv_ga);
xfree(b);
}
/// Unreference a blob.
///
/// Decrements the reference count and frees blob when it becomes zero.
///
/// @param[in,out] b Blob to operate on.
void tv_blob_unref(blob_T *const b)
{
if (b != NULL && --b->bv_refcount <= 0) {
tv_blob_free(b);
}
}
//{{{2 Operations on the whole blob
/// Check whether two blobs are equal.
///
/// @param[in] b1 First blob.
/// @param[in] b2 Second blob.
///
/// @return true if blobs are equal, false otherwise.
bool tv_blob_equal(const blob_T *const b1, const blob_T *const b2)
FUNC_ATTR_WARN_UNUSED_RESULT
{
const int len1 = tv_blob_len(b1);
const int len2 = tv_blob_len(b2);
// empty and NULL are considered the same
if (len1 == 0 && len2 == 0) {
return true;
}
if (b1 == b2) {
return true;
}
if (len1 != len2) {
return false;
}
for (int i = 0; i < b1->bv_ga.ga_len; i++) {
if (tv_blob_get(b1, i) != tv_blob_get(b2, i)) {
return false;
}
}
return true;
}
//{{{1 Generic typval operations
//{{{2 Init/alloc/clear
//{{{3 Alloc
@ -2169,6 +2240,44 @@ void tv_dict_alloc_ret(typval_T *const ret_tv)
tv_dict_set_ret(ret_tv, d);
}
/// Allocate an empty blob for a return value.
///
/// Also sets reference count.
///
/// @param[out] ret_tv Structure where blob is saved.
void tv_blob_alloc_ret(typval_T *const ret_tv)
FUNC_ATTR_NONNULL_ALL
{
blob_T *const b = tv_blob_alloc();
tv_blob_set_ret(ret_tv, b);
}
/// Copy a blob typval to a different typval.
///
/// @param[in] from Blob object to copy from.
/// @param[out] to Blob object to copy to.
void tv_blob_copy(typval_T *const from, typval_T *const to)
FUNC_ATTR_NONNULL_ALL
{
assert(from->v_type == VAR_BLOB);
to->v_type = VAR_BLOB;
to->v_lock = VAR_UNLOCKED;
if (from->vval.v_blob == NULL) {
to->vval.v_blob = NULL;
} else {
tv_blob_alloc_ret(to);
int len = from->vval.v_blob->bv_ga.ga_len;
if (len > 0) {
to->vval.v_blob->bv_ga.ga_data
= xmemdup(from->vval.v_blob->bv_ga.ga_data, (size_t)len);
}
to->vval.v_blob->bv_ga.ga_len = len;
to->vval.v_blob->bv_ga.ga_maxlen = len;
}
}
//{{{3 Clear
#define TYPVAL_ENCODE_ALLOW_SPECIALS false
@ -2210,6 +2319,13 @@ void tv_dict_alloc_ret(typval_T *const ret_tv)
#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type)
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
do { \
tv_blob_unref(tv->vval.v_blob); \
tv->vval.v_blob = NULL; \
tv->v_lock = VAR_UNLOCKED; \
} while (0)
static inline int _nothing_conv_func_start(typval_T *const tv,
char_u *const fun)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1)
@ -2392,6 +2508,7 @@ static inline void _nothing_conv_dict_end(typval_T *const tv,
#undef TYPVAL_ENCODE_CONV_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING
#undef TYPVAL_ENCODE_CONV_BLOB
#undef TYPVAL_ENCODE_CONV_FUNC_START
#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS
#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF
@ -2449,6 +2566,10 @@ void tv_free(typval_T *tv)
xfree(tv->vval.v_string);
break;
}
case VAR_BLOB: {
tv_blob_unref(tv->vval.v_blob);
break;
}
case VAR_LIST: {
tv_list_unref(tv->vval.v_list);
break;
@ -2509,6 +2630,12 @@ void tv_copy(const typval_T *const from, typval_T *const to)
}
break;
}
case VAR_BLOB: {
if (from->vval.v_blob != NULL) {
to->vval.v_blob->bv_refcount++;
}
break;
}
case VAR_LIST: {
tv_list_ref(to->vval.v_list);
break;
@ -2533,7 +2660,10 @@ void tv_copy(const typval_T *const from, typval_T *const to)
/// @param[out] tv Item to (un)lock.
/// @param[in] deep Levels to (un)lock, -1 to (un)lock everything.
/// @param[in] lock True if it is needed to lock an item, false to unlock.
void tv_item_lock(typval_T *const tv, const int deep, const bool lock)
/// @param[in] check_refcount If true, do not lock a list or dict with a
/// reference count larger than 1.
void tv_item_lock(typval_T *const tv, const int deep, const bool lock,
const bool check_refcount)
FUNC_ATTR_NONNULL_ALL
{
// TODO(ZyX-I): Make this not recursive
@ -2560,14 +2690,21 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock)
CHANGE_LOCK(lock, tv->v_lock);
switch (tv->v_type) {
case VAR_BLOB: {
blob_T *const b = tv->vval.v_blob;
if (b != NULL && !(check_refcount && b->bv_refcount > 1)) {
CHANGE_LOCK(lock, b->bv_lock);
}
break;
}
case VAR_LIST: {
list_T *const l = tv->vval.v_list;
if (l != NULL) {
if (l != NULL && !(check_refcount && l->lv_refcount > 1)) {
CHANGE_LOCK(lock, l->lv_lock);
if (deep < 0 || deep > 1) {
// Recursive: lock/unlock the items the List contains.
TV_LIST_ITER(l, li, {
tv_item_lock(TV_LIST_ITEM_TV(li), deep - 1, lock);
tv_item_lock(TV_LIST_ITEM_TV(li), deep - 1, lock, check_refcount);
});
}
}
@ -2575,12 +2712,12 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock)
}
case VAR_DICT: {
dict_T *const d = tv->vval.v_dict;
if (d != NULL) {
if (d != NULL && !(check_refcount && d->dv_refcount > 1)) {
CHANGE_LOCK(lock, d->dv_lock);
if (deep < 0 || deep > 1) {
// recursive: lock/unlock the items the List contains
TV_DICT_ITER(d, di, {
tv_item_lock(&di->di_tv, deep - 1, lock);
tv_item_lock(&di->di_tv, deep - 1, lock, check_refcount);
});
}
}
@ -2646,10 +2783,11 @@ bool tv_check_lock(const typval_T *tv, const char *name,
VarLockStatus lock = VAR_UNLOCKED;
switch (tv->v_type) {
// case VAR_BLOB:
// if (tv->vval.v_blob != NULL)
// lock = tv->vval.v_blob->bv_lock;
// break;
case VAR_BLOB:
if (tv->vval.v_blob != NULL) {
lock = tv->vval.v_blob->bv_lock;
}
break;
case VAR_LIST:
if (tv->vval.v_list != NULL) {
lock = tv->vval.v_list->lv_lock;
@ -2769,6 +2907,9 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic,
recursive_cnt--;
return r;
}
case VAR_BLOB: {
return tv_blob_equal(tv1->vval.v_blob, tv2->vval.v_blob);
}
case VAR_NUMBER: {
return tv1->vval.v_number == tv2->vval.v_number;
}
@ -2835,6 +2976,10 @@ bool tv_check_str_or_nr(const typval_T *const tv)
EMSG(_("E728: Expected a Number or a String, Dictionary found"));
return false;
}
case VAR_BLOB: {
EMSG(_("E974: Expected a Number or a String, Blob found"));
return false;
}
case VAR_BOOL: {
EMSG(_("E5299: Expected a Number or a String, Boolean found"));
return false;
@ -2860,6 +3005,7 @@ static const char *const num_errors[] = {
[VAR_LIST]=N_("E745: Using a List as a Number"),
[VAR_DICT]=N_("E728: Using a Dictionary as a Number"),
[VAR_FLOAT]=N_("E805: Using a Float as a Number"),
[VAR_BLOB]=N_("E974: Using a Blob as a Number"),
[VAR_UNKNOWN]=N_("E685: using an invalid value as a Number"),
};
@ -2888,6 +3034,7 @@ bool tv_check_num(const typval_T *const tv)
case VAR_LIST:
case VAR_DICT:
case VAR_FLOAT:
case VAR_BLOB:
case VAR_UNKNOWN: {
EMSG(_(num_errors[tv->v_type]));
return false;
@ -2905,6 +3052,7 @@ static const char *const str_errors[] = {
[VAR_LIST]=N_("E730: using List as a String"),
[VAR_DICT]=N_("E731: using Dictionary as a String"),
[VAR_FLOAT]=((const char *)e_float_as_string),
[VAR_BLOB]=N_("E976: using Blob as a String"),
[VAR_UNKNOWN]=N_("E908: using an invalid value as a String"),
};
@ -2933,6 +3081,7 @@ bool tv_check_str(const typval_T *const tv)
case VAR_LIST:
case VAR_DICT:
case VAR_FLOAT:
case VAR_BLOB:
case VAR_UNKNOWN: {
EMSG(_(str_errors[tv->v_type]));
return false;
@ -2980,6 +3129,7 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error)
case VAR_PARTIAL:
case VAR_LIST:
case VAR_DICT:
case VAR_BLOB:
case VAR_FLOAT: {
EMSG(_(num_errors[tv->v_type]));
break;
@ -3075,6 +3225,10 @@ float_T tv_get_float(const typval_T *const tv)
EMSG(_("E907: Using a special value as a Float"));
break;
}
case VAR_BLOB: {
EMSG(_("E975: Using a Blob as a Float"));
break;
}
case VAR_UNKNOWN: {
emsgf(_(e_intern2), "tv_get_float(UNKNOWN)");
break;
@ -3134,6 +3288,7 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf)
case VAR_LIST:
case VAR_DICT:
case VAR_FLOAT:
case VAR_BLOB:
case VAR_UNKNOWN: {
EMSG(_(str_errors[tv->v_type]));
return false;

View File

@ -64,6 +64,7 @@ enum ListLenSpecials {
typedef struct listvar_S list_T;
typedef struct dictvar_S dict_T;
typedef struct partial_S partial_T;
typedef struct blobvar_S blob_T;
typedef struct ufunc ufunc_T;
@ -123,6 +124,7 @@ typedef enum {
VAR_SPECIAL, ///< Special value (null), .v_special
///< is used.
VAR_PARTIAL, ///< Partial, .v_partial is used.
VAR_BLOB, ///< Blob, .v_blob is used.
} VarType;
/// Structure that holds an internal variable value
@ -138,6 +140,7 @@ typedef struct {
list_T *v_list; ///< List for VAR_LIST, can be NULL.
dict_T *v_dict; ///< Dictionary for VAR_DICT, can be NULL.
partial_T *v_partial; ///< Closure: function with args.
blob_T *v_blob; ///< Blob for VAR_BLOB, can be NULL.
} vval; ///< Actual value.
} typval_T;
@ -252,6 +255,13 @@ struct dictvar_S {
LuaRef lua_table_ref;
};
/// Structure to hold info about a Blob
struct blobvar_S {
garray_T bv_ga; ///< Growarray with the data.
int bv_refcount; ///< Reference count.
VarLockStatus bv_lock; ///< VAR_UNLOCKED, VAR_LOCKED, VAR_FIXED.
};
/// Type used for script ID
typedef int scid_T;
/// Format argument for scid_T
@ -711,6 +721,65 @@ static inline bool tv_dict_is_watched(const dict_T *const d)
return d && !QUEUE_EMPTY(&d->watchers);
}
static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b)
REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1);
/// Set a blob as the return value.
///
/// Increments the reference count.
///
/// @param[out] tv Object to receive the blob.
/// @param[in,out] b Blob to pass to the object.
static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b)
{
tv->v_type = VAR_BLOB;
tv->vval.v_blob = b;
if (b != NULL) {
b->bv_refcount++;
}
}
static inline int tv_blob_len(const blob_T *const b)
REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT;
/// Get the length of the data in the blob, in bytes.
///
/// @param[in] b Blob to check.
static inline int tv_blob_len(const blob_T *const b)
{
if (b == NULL) {
return 0;
}
return b->bv_ga.ga_len;
}
static inline char_u tv_blob_get(const blob_T *const b, int idx)
REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT;
/// Get the byte at index `idx` in the blob.
///
/// @param[in] b Blob to index. Cannot be NULL.
/// @param[in] idx Index in a blob. Must be valid.
///
/// @return Byte value at the given index.
static inline char_u tv_blob_get(const blob_T *const b, int idx)
{
return ((char_u *)b->bv_ga.ga_data)[idx];
}
static inline void tv_blob_set(blob_T *const b, int idx, char_u c)
REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL;
/// Store the byte `c` at index `idx` in the blob.
///
/// @param[in] b Blob to index. Cannot be NULL.
/// @param[in] idx Index in a blob. Must be valid.
/// @param[in] c Value to store.
static inline void tv_blob_set(blob_T *const b, int idx, char_u c)
{
((char_u *)b->bv_ga.ga_data)[idx] = c;
}
/// Initialize VimL object
///
/// Initializes to unlocked VAR_UNKNOWN object.

View File

@ -83,6 +83,13 @@
/// @param len String length.
/// @param type EXT type.
/// @def TYPVAL_ENCODE_CONV_BLOB
/// @brief Macros used to convert a blob
///
/// @param tv Pointer to typval where value is stored. May not be NULL.
/// @param blob Pointer to the blob to convert.
/// @param len Blob length.
/// @def TYPVAL_ENCODE_CONV_FUNC_START
/// @brief Macros used when starting to convert a funcref or a partial
///
@ -330,6 +337,11 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
TYPVAL_ENCODE_CONV_FLOAT(tv, tv->vval.v_float);
break;
}
case VAR_BLOB: {
TYPVAL_ENCODE_CONV_BLOB(tv, tv->vval.v_blob,
tv_blob_len(tv->vval.v_blob));
break;
}
case VAR_FUNC: {
TYPVAL_ENCODE_CONV_FUNC_START(tv, tv->vval.v_string);
TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, 0);

View File

@ -939,13 +939,18 @@ EXTERN char_u e_readonlyvar[] INIT(= N_(
"E46: Cannot change read-only variable \"%.*s\""));
EXTERN char_u e_stringreq[] INIT(= N_("E928: String required"));
EXTERN char_u e_dictreq[] INIT(= N_("E715: Dictionary required"));
EXTERN char_u e_blobidx[] INIT(= N_("E979: Blob index out of range: %" PRId64));
EXTERN char_u e_invalblob[] INIT(= N_("E978: Invalid operation for Blob"));
EXTERN char_u e_toomanyarg[] INIT(= N_(
"E118: Too many arguments for function: %s"));
EXTERN char_u e_dictkey[] INIT(= N_(
"E716: Key not present in Dictionary: \"%s\""));
EXTERN char_u e_listreq[] INIT(= N_("E714: List required"));
EXTERN char_u e_listblobreq[] INIT(= N_("E897: List or Blob required"));
EXTERN char_u e_listdictarg[] INIT(= N_(
"E712: Argument of %s must be a List or Dictionary"));
EXTERN char_u e_listdictblobarg[] INIT(= N_(
"E896: Argument of %s must be a List, Dictionary or Blob"));
EXTERN char_u e_readerrf[] INIT(= N_("E47: Error while reading errorfile"));
EXTERN char_u e_sandbox[] INIT(= N_("E48: Not allowed in sandbox"));
EXTERN char_u e_secure[] INIT(= N_("E523: Not allowed here"));

View File

@ -481,6 +481,14 @@ static bool typval_conv_special = false;
#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \
TYPVAL_ENCODE_CONV_NIL(tv)
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
do { \
const blob_T *const blob_ = (blob); \
lua_pushlstring(lstate, \
blob_ != NULL ? (const char *)blob_->bv_ga.ga_data : "", \
(size_t)(len)); \
} while (0)
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
do { \
TYPVAL_ENCODE_CONV_NIL(tv); \
@ -579,6 +587,7 @@ static bool typval_conv_special = false;
#undef TYPVAL_ENCODE_CONV_STRING
#undef TYPVAL_ENCODE_CONV_STR_STRING
#undef TYPVAL_ENCODE_CONV_EXT_STRING
#undef TYPVAL_ENCODE_CONV_BLOB
#undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT
#undef TYPVAL_ENCODE_CONV_FUNC_START

View File

@ -1653,6 +1653,13 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer,
break;
}
case kSDItemVariable: {
if (entry.data.global_var.value.v_type == VAR_TYPE_BLOB) {
// Strings and Blobs both pack as msgpack BINs; differentiate them by
// storing an additional VAR_TYPE_BLOB element alongside Blobs
list_T *const list = tv_list_alloc(1);
tv_list_append_number(list, VAR_TYPE_BLOB);
entry.data.global_var.additional_elements = list;
}
const size_t arr_size = 2 + (size_t)(
tv_list_len(entry.data.global_var.additional_elements));
msgpack_pack_array(spacker, arr_size);
@ -3937,15 +3944,38 @@ shada_read_next_item_start:
entry->data.global_var.name =
xmemdupz(unpacked.data.via.array.ptr[0].via.bin.ptr,
unpacked.data.via.array.ptr[0].via.bin.size);
if (msgpack_to_vim(unpacked.data.via.array.ptr[1],
&(entry->data.global_var.value)) == FAIL) {
SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, 2,
entry->data.global_var.additional_elements,
"variable");
bool is_blob = false;
// A msgpack BIN could be a String or Blob; an additional VAR_TYPE_BLOB
// element is stored with Blobs which can be used to differentiate them
if (unpacked.data.via.array.ptr[1].type == MSGPACK_OBJECT_BIN) {
const listitem_T *type_item
= tv_list_first(entry->data.global_var.additional_elements);
if (type_item != NULL) {
const typval_T *type_tv = TV_LIST_ITEM_TV(type_item);
if (type_tv->v_type != VAR_NUMBER
|| type_tv->vval.v_number != VAR_TYPE_BLOB) {
emsgf(_(READERR("variable", "has wrong variable type")),
initial_fpos);
goto shada_read_next_item_error;
}
is_blob = true;
}
}
if (is_blob) {
const msgpack_object_bin *const bin
= &unpacked.data.via.array.ptr[1].via.bin;
blob_T *const blob = tv_blob_alloc();
ga_concat_len(&blob->bv_ga, bin->ptr, (size_t)bin->size);
tv_blob_set_ret(&entry->data.global_var.value, blob);
} else if (msgpack_to_vim(unpacked.data.via.array.ptr[1],
&(entry->data.global_var.value)) == FAIL) {
emsgf(_(READERR("variable", "has value that cannot "
"be converted to the VimL value")), initial_fpos);
goto shada_read_next_item_error;
}
SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, 2,
entry->data.global_var.additional_elements,
"variable");
break;
}
case kSDItemSubString: {

View File

@ -0,0 +1,349 @@
" Tests for the Blob types
func TearDown()
" Run garbage collection after every test
call test_garbagecollect_now()
endfunc
" Tests for Blob type
" Blob creation from constant
func Test_blob_create()
let b = 0zDEADBEEF
call assert_equal(v:t_blob, type(b))
call assert_equal(4, len(b))
call assert_equal(0xDE, b[0])
call assert_equal(0xAD, b[1])
call assert_equal(0xBE, b[2])
call assert_equal(0xEF, b[3])
call assert_fails('let x = b[4]')
call assert_equal(0xDE, get(b, 0))
call assert_equal(0xEF, get(b, 3))
call assert_fails('let b = 0z1', 'E973:')
call assert_fails('let b = 0z1x', 'E973:')
call assert_fails('let b = 0z12345', 'E973:')
call assert_equal(0z, v:_null_blob)
let b = 0z001122.33445566.778899.aabbcc.dd
call assert_equal(0z00112233445566778899aabbccdd, b)
call assert_fails('let b = 0z1.1')
call assert_fails('let b = 0z.')
call assert_fails('let b = 0z001122.')
call assert_fails('call get("", 1)', 'E896:')
call assert_equal(0, len(v:_null_blob))
endfunc
" assignment to a blob
func Test_blob_assign()
let b = 0zDEADBEEF
let b2 = b[1:2]
call assert_equal(0zADBE, b2)
let bcopy = b[:]
call assert_equal(b, bcopy)
call assert_false(b is bcopy)
let b = 0zDEADBEEF
let b2 = b
call assert_true(b is b2)
let b[:] = 0z11223344
call assert_equal(0z11223344, b)
call assert_equal(0z11223344, b2)
call assert_true(b is b2)
let b = 0zDEADBEEF
let b[3:] = 0z66
call assert_equal(0zDEADBE66, b)
let b[:1] = 0z8899
call assert_equal(0z8899BE66, b)
call assert_fails('let b[2:3] = 0z112233', 'E972:')
call assert_fails('let b[2:3] = 0z11', 'E972:')
call assert_fails('let b[3:2] = 0z', 'E979:')
let b = 0zDEADBEEF
let b += 0z99
call assert_equal(0zDEADBEEF99, b)
call assert_fails('let b .= 0z33', 'E734:')
call assert_fails('let b .= "xx"', 'E734:')
call assert_fails('let b += "xx"', 'E734:')
call assert_fails('let b[1:1] .= 0z55', 'E734:')
let l = [0z12]
let m = deepcopy(l)
let m[0] = 0z34 " E742 or E741 should not occur.
endfunc
func Test_blob_get_range()
let b = 0z0011223344
call assert_equal(0z2233, b[2:3])
call assert_equal(0z223344, b[2:-1])
call assert_equal(0z00, b[0:-5])
call assert_equal(0z, b[0:-11])
call assert_equal(0z44, b[-1:])
call assert_equal(0z0011223344, b[:])
call assert_equal(0z0011223344, b[:-1])
call assert_equal(0z, b[5:6])
endfunc
func Test_blob_get()
let b = 0z0011223344
call assert_equal(0x00, get(b, 0))
call assert_equal(0x22, get(b, 2, 999))
call assert_equal(0x44, get(b, 4))
call assert_equal(0x44, get(b, -1))
call assert_equal(-1, get(b, 5))
call assert_equal(999, get(b, 5, 999))
call assert_equal(-1, get(b, -8))
call assert_equal(999, get(b, -8, 999))
call assert_equal(10, get(v:_null_blob, 2, 10))
call assert_equal(0x00, b[0])
call assert_equal(0x22, b[2])
call assert_equal(0x44, b[4])
call assert_equal(0x44, b[-1])
call assert_fails('echo b[5]', 'E979:')
call assert_fails('echo b[-8]', 'E979:')
endfunc
func Test_blob_to_string()
let b = 0z00112233445566778899aabbccdd
call assert_equal('0z00112233.44556677.8899AABB.CCDD', string(b))
call assert_equal(b, eval(string(b)))
call remove(b, 4, -1)
call assert_equal('0z00112233', string(b))
call remove(b, 0, 3)
call assert_equal('0z', string(b))
endfunc
func Test_blob_compare()
let b1 = 0z0011
let b2 = 0z1100
let b3 = 0z001122
call assert_true(b1 == b1)
call assert_false(b1 == b2)
call assert_false(b1 == b3)
call assert_true(b1 != b2)
call assert_true(b1 != b3)
call assert_true(b1 == 0z0011)
call assert_fails('echo b1 == 9', 'E977:')
call assert_fails('echo b1 != 9', 'E977:')
call assert_false(b1 is b2)
let b2 = b1
call assert_true(b1 == b2)
call assert_true(b1 is b2)
let b2 = copy(b1)
call assert_true(b1 == b2)
call assert_false(b1 is b2)
let b2 = b1[:]
call assert_true(b1 == b2)
call assert_false(b1 is b2)
call assert_fails('let x = b1 > b2')
call assert_fails('let x = b1 < b2')
call assert_fails('let x = b1 - b2')
call assert_fails('let x = b1 / b2')
call assert_fails('let x = b1 * b2')
endfunc
" test for range assign
func Test_blob_range_assign()
let b = 0z00
let b[1] = 0x11
let b[2] = 0x22
call assert_equal(0z001122, b)
call assert_fails('let b[4] = 0x33', 'E979:')
endfunc
func Test_blob_for_loop()
let blob = 0z00010203
let i = 0
for byte in blob
call assert_equal(i, byte)
let i += 1
endfor
call assert_equal(4, i)
let blob = 0z00
call remove(blob, 0)
call assert_equal(0, len(blob))
for byte in blob
call assert_error('loop over empty blob')
endfor
let blob = 0z0001020304
let i = 0
for byte in blob
call assert_equal(i, byte)
if i == 1
call remove(blob, 0)
elseif i == 3
call remove(blob, 3)
endif
let i += 1
endfor
call assert_equal(5, i)
endfunc
func Test_blob_concatenate()
let b = 0z0011
let b += 0z2233
call assert_equal(0z00112233, b)
call assert_fails('let b += "a"')
call assert_fails('let b += 88')
let b = 0zDEAD + 0zBEEF
call assert_equal(0zDEADBEEF, b)
endfunc
func Test_blob_add()
let b = 0z0011
call add(b, 0x22)
call assert_equal(0z001122, b)
call add(b, '51')
call assert_equal(0z00112233, b)
call assert_fails('call add(b, [9])', 'E745:')
call assert_fails('call add("", 0x01)', 'E897:')
endfunc
func Test_blob_empty()
call assert_false(empty(0z001122))
call assert_true(empty(0z))
call assert_true(empty(v:_null_blob))
endfunc
" Test removing items in blob
func Test_blob_func_remove()
" Test removing 1 element
let b = 0zDEADBEEF
call assert_equal(0xDE, remove(b, 0))
call assert_equal(0zADBEEF, b)
let b = 0zDEADBEEF
call assert_equal(0xEF, remove(b, -1))
call assert_equal(0zDEADBE, b)
let b = 0zDEADBEEF
call assert_equal(0xAD, remove(b, 1))
call assert_equal(0zDEBEEF, b)
" Test removing range of element(s)
let b = 0zDEADBEEF
call assert_equal(0zBE, remove(b, 2, 2))
call assert_equal(0zDEADEF, b)
let b = 0zDEADBEEF
call assert_equal(0zADBE, remove(b, 1, 2))
call assert_equal(0zDEEF, b)
" Test invalid cases
let b = 0zDEADBEEF
call assert_fails("call remove(b, 5)", 'E979:')
call assert_fails("call remove(b, 1, 5)", 'E979:')
call assert_fails("call remove(b, 3, 2)", 'E979:')
call assert_fails("call remove(1, 0)", 'E896:')
call assert_fails("call remove(b, b)", 'E974:')
call assert_fails("call remove(v:_null_blob, 1, 2)", 'E979:')
" Translated from v8.2.3284
let b = 0zDEADBEEF
lockvar b
call assert_fails('call remove(b, 0)', 'E741:')
unlockvar b
endfunc
func Test_blob_read_write()
let b = 0zDEADBEEF
call writefile(b, 'Xblob')
let br = readfile('Xblob', 'B')
call assert_equal(b, br)
call delete('Xblob')
" This was crashing when calling readfile() with a directory.
call assert_fails("call readfile('.', 'B')", 'E17: "." is a directory')
endfunc
" filter() item in blob
func Test_blob_filter()
call assert_equal(0z, filter(0zDEADBEEF, '0'))
call assert_equal(0zADBEEF, filter(0zDEADBEEF, 'v:val != 0xDE'))
call assert_equal(0zDEADEF, filter(0zDEADBEEF, 'v:val != 0xBE'))
call assert_equal(0zDEADBE, filter(0zDEADBEEF, 'v:val != 0xEF'))
call assert_equal(0zDEADBEEF, filter(0zDEADBEEF, '1'))
call assert_equal(0z01030103, filter(0z010203010203, 'v:val != 0x02'))
call assert_equal(0zADEF, filter(0zDEADBEEF, 'v:key % 2'))
endfunc
" map() item in blob
func Test_blob_map()
call assert_equal(0zDFAEBFF0, map(0zDEADBEEF, 'v:val + 1'))
call assert_equal(0z00010203, map(0zDEADBEEF, 'v:key'))
call assert_equal(0zDEAEC0F2, map(0zDEADBEEF, 'v:key + v:val'))
call assert_fails("call map(0z00, '[9]')", 'E978:')
endfunc
func Test_blob_index()
call assert_equal(2, index(0zDEADBEEF, 0xBE))
call assert_equal(-1, index(0zDEADBEEF, 0))
call assert_equal(2, index(0z11111111, 0x11, 2))
call assert_equal(3, index(0z11110111, 0x11, 2))
call assert_equal(2, index(0z11111111, 0x11, -2))
call assert_equal(3, index(0z11110111, 0x11, -2))
call assert_fails('call index("asdf", 0)', 'E897:')
endfunc
func Test_blob_insert()
let b = 0zDEADBEEF
call insert(b, 0x33)
call assert_equal(0z33DEADBEEF, b)
let b = 0zDEADBEEF
call insert(b, 0x33, 2)
call assert_equal(0zDEAD33BEEF, b)
call assert_fails('call insert(b, -1)', 'E475:')
call assert_fails('call insert(b, 257)', 'E475:')
call assert_fails('call insert(b, 0, [9])', 'E745:')
call assert_equal(0, insert(v:_null_blob, 0x33))
" Translated from v8.2.3284
let b = 0zDEADBEEF
lockvar b
call assert_fails('call insert(b, 3)', 'E741:')
unlockvar b
endfunc
func Test_blob_reverse()
call assert_equal(0zEFBEADDE, reverse(0zDEADBEEF))
call assert_equal(0zBEADDE, reverse(0zDEADBE))
call assert_equal(0zADDE, reverse(0zDEAD))
call assert_equal(0zDE, reverse(0zDE))
call assert_equal(0z, reverse(v:_null_blob))
endfunc
func Test_blob_lock()
let b = 0z112233
lockvar b
call assert_fails('let b = 0z44', 'E741:')
unlockvar b
let b = 0z44
endfunc
func Test_blob_sort()
if has('float')
call assert_fails('call sort([1.0, 0z11], "f")', 'E975:')
else
call assert_fails('call sort(["abc", 0z11], "f")', 'E702:')
endif
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@ -244,18 +244,33 @@ func Test_const_with_eval_name()
call assert_fails('const {s2} = "bar"', 'E995:')
endfunc
func Test_lock_depth_is_1()
const l = [1, 2, 3]
const d = {'foo': 10}
" Modify list - setting item is OK, adding/removing items not
let l[0] = 42
func Test_lock_depth_is_2()
" Modify list - error when changing item or adding/removing items
const l = [1, 2, [3, 4]]
call assert_fails('let l[0] = 42', 'E741:')
call assert_fails('let l[2][0] = 42', 'E741:')
call assert_fails('call add(l, 4)', 'E741:')
call assert_fails('unlet l[1]', 'E741:')
" Modify dict - changing item is OK, adding/removing items not
let d['foo'] = 'hello'
let d.foo = 44
" Modify blob - error when changing
const b = 0z001122
call assert_fails('let b[0] = 42', 'E741:')
" Modify dict - error when changing item or adding/removing items
const d = {'foo': 10}
call assert_fails("let d['foo'] = 'hello'", 'E741:')
call assert_fails("let d.foo = 'hello'", 'E741:')
call assert_fails("let d['bar'] = 'hello'", 'E741:')
call assert_fails("unlet d['foo']", 'E741:')
" Modifying list or dict item contents is OK.
let lvar = ['a', 'b']
let bvar = 0z1122
const l2 = [0, lvar, bvar]
let l2[1][0] = 'c'
let l2[2][1] = 0x33
call assert_equal([0, ['c', 'b'], 0z1133], l2)
const d2 = #{a: 0, b: lvar, c: 4}
let d2.b[1] = 'd'
endfunc

View File

@ -65,9 +65,11 @@ func Test_E963()
endfunc
func Test_for_invalid()
call assert_fails("for x in 99", 'E714:')
call assert_fails("for x in function('winnr')", 'E714:')
call assert_fails("for x in {'a': 9}", 'E714:')
" Vim gives incorrect emsg here until v8.2.3284, but the exact emsg from that
" patch cannot be used until v8.2.2658 is ported (for loop over Strings)
call assert_fails("for x in 99", 'E897:')
call assert_fails("for x in function('winnr')", 'E897:')
call assert_fails("for x in {'a': 9}", 'E897:')
if 0
/1/5/2/s/\n

View File

@ -81,7 +81,11 @@ func Test_filter_map_dict_expr_funcref()
call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
endfunc
func Test_map_fails()
func Test_map_filter_fails()
call assert_fails('call map([1], "42 +")', 'E15:')
call assert_fails('call filter([1], "42 +")', 'E15:')
call assert_fails("let l = map('abc', '\"> \" . v:val')", 'E896:')
call assert_fails("let l = filter('abc', '\"> \" . v:val')", 'E896:')
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@ -72,4 +72,8 @@ func Test_fnamemodify_er()
" :e never includes the whole filename, so "a.b":e:e:e --> "b"
call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e'))
call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e:e'))
call assert_equal('', fnamemodify(v:_null_string, v:_null_string))
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@ -139,7 +139,7 @@ func Test_list_func_remove()
call assert_fails("call remove(l, 5)", 'E684:')
call assert_fails("call remove(l, 1, 5)", 'E684:')
call assert_fails("call remove(l, 3, 2)", 'E16:')
call assert_fails("call remove(1, 0)", 'E712:')
call assert_fails("call remove(1, 0)", 'E896:')
call assert_fails("call remove(l, l)", 'E745:')
endfunc
@ -616,6 +616,8 @@ func Test_reverse_sort_uniq()
call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 1))
call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i'))
call assert_equal(['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l)))
call assert_fails('call reverse("")', 'E899:')
endfunc
" splitting a string to a List

View File

@ -46,11 +46,8 @@ func Test_dict_method()
call assert_equal(#{one: 1, two: 2, three: 3, four: 4}, d->extend(#{four: 4}))
call assert_equal(#{one: 1, two: 2, three: 3}, d->filter('v:val != 4'))
call assert_equal(2, d->get('two'))
" Nvim doesn't support Blobs yet; expect a different emsg
" call assert_fails("let x = d->index(2)", 'E897:')
" call assert_fails("let x = d->insert(0)", 'E899:')
call assert_fails("let x = d->index(2)", 'E714:')
call assert_fails("let x = d->insert(0)", 'E686:')
call assert_fails("let x = d->index(2)", 'E897:')
call assert_fails("let x = d->insert(0)", 'E899:')
call assert_true(d->has_key('two'))
call assert_equal([['one', 1], ['two', 2], ['three', 3]], d->items())
call assert_fails("let x = d->join()", 'E714:')
@ -63,9 +60,7 @@ func Test_dict_method()
call assert_equal(2, d->remove("two"))
let d.two = 2
call assert_fails('let x = d->repeat(2)', 'E731:')
" Nvim doesn't support Blobs yet; expect a different emsg
" call assert_fails('let x = d->reverse()', 'E899:')
call assert_fails('let x = d->reverse()', 'E686:')
call assert_fails('let x = d->reverse()', 'E899:')
call assert_fails('let x = d->sort()', 'E686:')
call assert_equal("{'one': 1, 'two': 2, 'three': 3}", d->string())
call assert_equal(v:t_dict, d->type())

View File

@ -2221,6 +2221,10 @@ func Xproperty_tests(cchar)
call g:Xsetlist([], 'a', {'context':246})
let d = g:Xgetlist({'context':1})
call assert_equal(246, d.context)
" set other Vim data types as context
call g:Xsetlist([], 'a', {'context' : v:_null_blob})
call g:Xsetlist([], 'a', {'context' : ''})
call test_garbagecollect_now()
if a:cchar == 'l'
" Test for copying context across two different location lists
new | only

View File

@ -95,7 +95,6 @@ func Test_rename_copy()
endfunc
func Test_rename_fails()
throw 'skipped: TODO: '
call writefile(['foo'], 'Xrenamefile')
" Can't rename into a non-existing directory.

View File

@ -168,7 +168,6 @@ func Test_swapname()
endfunc
func Test_swapfile_delete()
throw 'skipped: need the "blob" feature for this test'
autocmd! SwapExists
function s:swap_exists()
let v:swapchoice = s:swap_choice

View File

@ -368,7 +368,6 @@ endfunc
" Check that reading a truncted undo file doesn't hang.
func Test_undofile_truncated()
throw 'skipped: TODO: '
new
call setline(1, 'hello')
set ul=100

View File

@ -1152,6 +1152,10 @@ func Test_type()
call assert_equal(v:t_float, type(0.0))
call assert_equal(v:t_bool, type(v:false))
call assert_equal(v:t_bool, type(v:true))
call assert_equal(v:t_string, type(v:_null_string))
call assert_equal(v:t_list, type(v:_null_list))
call assert_equal(v:t_dict, type(v:_null_dict))
call assert_equal(v:t_blob, type(v:_null_blob))
endfunc
"-------------------------------------------------------------------------------

View File

@ -17,6 +17,8 @@ func Test_writefile()
call assert_equal("morning", l[3])
call assert_equal("vimmers", l[4])
call delete(f)
call assert_fails('call writefile("text", "Xfile")', 'E475: Invalid argument: writefile() first argument must be a List or a Blob')
endfunc
func Test_writefile_ignore_regexp_error()

View File

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

View File

@ -89,6 +89,9 @@ describe('channels', function()
command("call chansend(id, 'howdy')")
eq({"notification", "stdout", {id, {"[1, ['howdy'], 'stdin']"}}}, next_msg())
command("call chansend(id, 0z686f6c61)")
eq({"notification", "stdout", {id, {"[1, ['hola'], 'stdin']"}}}, next_msg())
command("call chanclose(id, 'stdin')")
expect_twostreams({{"notification", "stdout", {id, {"[1, [''], 'stdin']"}}},
{'notification', 'stdout', {id, {''}}}},
@ -131,6 +134,8 @@ describe('channels', function()
command("call chansend(id, 'TEXT\n')")
expect_twoline(id, "stdout", "TEXT\r", "[1, ['TEXT', ''], 'stdin']")
command("call chansend(id, 0z426c6f6273210a)")
expect_twoline(id, "stdout", "Blobs!\r", "[1, ['Blobs!', ''], 'stdin']")
command("call chansend(id, 'neovan')")
eq({"notification", "stdout", {id, {"neovan"}}}, next_msg())

View File

@ -155,4 +155,13 @@ describe('eval-API', function()
pcall_err(command, "sandbox call nvim_input('ievil')"))
eq({''}, meths.buf_get_lines(0, 0, -1, true))
end)
it('converts blobs to API strings', function()
command('let g:v1 = nvim__id(0z68656c6c6f)')
command('let g:v2 = nvim__id(v:_null_blob)')
eq(1, eval('type(g:v1)'))
eq(1, eval('type(g:v2)'))
eq('hello', eval('g:v1'))
eq('', eval('g:v2'))
end)
end)

View File

@ -322,16 +322,16 @@ describe('execute()', function()
eq('Vim(call):E731: using Dictionary as a String', ret)
ret = exc_exec('call execute("echo add(1, 1)", "")')
eq('Vim(echo):E714: List required', ret)
eq('Vim(echo):E897: List or Blob required', ret)
ret = exc_exec('call execute(["echon 42", "echo add(1, 1)"], "")')
eq('Vim(echo):E714: List required', ret)
eq('Vim(echo):E897: List or Blob required', ret)
ret = exc_exec('call execute("echo add(1, 1)", "silent")')
eq('Vim(echo):E714: List required', ret)
eq('Vim(echo):E897: List or Blob required', ret)
ret = exc_exec('call execute(["echon 42", "echo add(1, 1)"], "silent")')
eq('Vim(echo):E714: List required', ret)
eq('Vim(echo):E897: List or Blob required', ret)
end)
end)
end)

View File

@ -538,6 +538,11 @@ describe('json_encode() function', function()
eq('"þÿþ"', funcs.json_encode('þÿþ'))
end)
it('dumps blobs', function()
eq('[]', eval('json_encode(0z)'))
eq('[222, 173, 190, 239]', eval('json_encode(0zDEADBEEF)'))
end)
it('dumps numbers', function()
eq('0', funcs.json_encode(0))
eq('10', funcs.json_encode(10))
@ -769,6 +774,10 @@ describe('json_encode() function', function()
eq('""', eval('json_encode($XXX_UNEXISTENT_VAR_XXX)'))
end)
it('can dump NULL blob', function()
eq('[]', eval('json_encode(v:_null_blob)'))
end)
it('can dump NULL list', function()
eq('[]', eval('json_encode(v:_null_list)'))
end)

View File

@ -13,6 +13,7 @@ describe('msgpack*() functions', function()
it(msg, function()
nvim('set_var', 'obj', obj)
eq(obj, eval('msgpackparse(msgpackdump(g:obj))'))
eq(obj, eval('msgpackparse(msgpackdump(g:obj, "B"))'))
end)
end
@ -364,8 +365,7 @@ describe('msgpack*() functions', function()
command('let dumped = ["\\xC4\\x01\\n"]')
command('let parsed = msgpackparse(dumped)')
command('let dumped2 = msgpackdump(parsed)')
eq({{_TYPE={}, _VAL={'\n'}}}, eval('parsed'))
eq(1, eval('parsed[0]._TYPE is v:msgpack_types.binary'))
eq({'\000'}, eval('parsed'))
eq(1, eval('dumped ==# dumped2'))
end)
@ -392,56 +392,61 @@ describe('msgpack*() functions', function()
end)
end)
local blobstr = function(list)
local l = {}
for i,v in ipairs(list) do
l[i] = v:gsub('\n', '\000')
end
return table.concat(l, '\n')
end
-- Test msgpackparse() with a readfile()-style list and a blob argument
local parse_eq = function(expect, list_arg)
local blob_expr = '0z' .. blobstr(list_arg):gsub('(.)', function(c)
return ('%.2x'):format(c:byte())
end)
eq(expect, funcs.msgpackparse(list_arg))
command('let g:parsed = msgpackparse(' .. blob_expr .. ')')
eq(expect, eval('g:parsed'))
end
describe('msgpackparse() function', function()
before_each(clear)
it('restores nil as v:null', function()
command('let dumped = ["\\xC0"]')
command('let parsed = msgpackparse(dumped)')
eq('[v:null]', eval('string(parsed)'))
parse_eq(eval('[v:null]'), {'\192'})
end)
it('restores boolean false as v:false', function()
command('let dumped = ["\\xC2"]')
command('let parsed = msgpackparse(dumped)')
eq({false}, eval('parsed'))
parse_eq({false}, {'\194'})
end)
it('restores boolean true as v:true', function()
command('let dumped = ["\\xC3"]')
command('let parsed = msgpackparse(dumped)')
eq({true}, eval('parsed'))
parse_eq({true}, {'\195'})
end)
it('restores FIXSTR as special dict', function()
command('let dumped = ["\\xa2ab"]')
command('let parsed = msgpackparse(dumped)')
eq({{_TYPE={}, _VAL={'ab'}}}, eval('parsed'))
parse_eq({{_TYPE={}, _VAL={'ab'}}}, {'\162ab'})
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.string'))
end)
it('restores BIN 8 as string', function()
command('let dumped = ["\\xC4\\x02ab"]')
eq({'ab'}, eval('msgpackparse(dumped)'))
parse_eq({'ab'}, {'\196\002ab'})
end)
it('restores FIXEXT1 as special dictionary', function()
command('let dumped = ["\\xD4\\x10", ""]')
command('let parsed = msgpackparse(dumped)')
eq({{_TYPE={}, _VAL={0x10, {"", ""}}}}, eval('parsed'))
parse_eq({{_TYPE={}, _VAL={0x10, {"", ""}}}}, {'\212\016', ''})
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.ext'))
end)
it('restores MAP with BIN key as special dictionary', function()
command('let dumped = ["\\x81\\xC4\\x01a\\xC4\\n"]')
command('let parsed = msgpackparse(dumped)')
eq({{_TYPE={}, _VAL={{'a', ''}}}}, eval('parsed'))
parse_eq({{_TYPE={}, _VAL={{'a', ''}}}}, {'\129\196\001a\196\n'})
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.map'))
end)
it('restores MAP with duplicate STR keys as special dictionary', function()
command('let dumped = ["\\x82\\xA1a\\xC4\\n\\xA1a\\xC4\\n"]')
-- FIXME Internal error bug
-- FIXME Internal error bug, can't use parse_eq() here
command('silent! let parsed = msgpackparse(dumped)')
eq({{_TYPE={}, _VAL={ {{_TYPE={}, _VAL={'a'}}, ''},
{{_TYPE={}, _VAL={'a'}}, ''}}} }, eval('parsed'))
@ -451,9 +456,7 @@ describe('msgpackparse() function', function()
end)
it('restores MAP with MAP key as special dictionary', function()
command('let dumped = ["\\x81\\x80\\xC4\\n"]')
command('let parsed = msgpackparse(dumped)')
eq({{_TYPE={}, _VAL={{{}, ''}}}}, eval('parsed'))
parse_eq({{_TYPE={}, _VAL={{{}, ''}}}}, {'\129\128\196\n'})
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.map'))
end)
@ -478,43 +481,65 @@ describe('msgpackparse() function', function()
end)
it('fails to parse a string', function()
eq('Vim(call):E686: Argument of msgpackparse() must be a List',
eq('Vim(call):E899: Argument of msgpackparse() must be a List or Blob',
exc_exec('call msgpackparse("abcdefghijklmnopqrstuvwxyz")'))
end)
it('fails to parse a number', function()
eq('Vim(call):E686: Argument of msgpackparse() must be a List',
eq('Vim(call):E899: Argument of msgpackparse() must be a List or Blob',
exc_exec('call msgpackparse(127)'))
end)
it('fails to parse a dictionary', function()
eq('Vim(call):E686: Argument of msgpackparse() must be a List',
eq('Vim(call):E899: Argument of msgpackparse() must be a List or Blob',
exc_exec('call msgpackparse({})'))
end)
it('fails to parse a funcref', function()
eq('Vim(call):E686: Argument of msgpackparse() must be a List',
eq('Vim(call):E899: Argument of msgpackparse() must be a List or Blob',
exc_exec('call msgpackparse(function("tr"))'))
end)
it('fails to parse a partial', function()
command('function T() dict\nendfunction')
eq('Vim(call):E686: Argument of msgpackparse() must be a List',
eq('Vim(call):E899: Argument of msgpackparse() must be a List or Blob',
exc_exec('call msgpackparse(function("T", [1, 2], {}))'))
end)
it('fails to parse a float', function()
eq('Vim(call):E686: Argument of msgpackparse() must be a List',
eq('Vim(call):E899: Argument of msgpackparse() must be a List or Blob',
exc_exec('call msgpackparse(0.0)'))
end)
it('fails on incomplete msgpack string', function()
local expected = 'Vim(call):E475: Invalid argument: Incomplete msgpack string'
eq(expected, exc_exec([[call msgpackparse(["\xc4"])]]))
eq(expected, exc_exec([[call msgpackparse(["\xca", "\x02\x03"])]]))
eq(expected, exc_exec('call msgpackparse(0zc4)'))
eq(expected, exc_exec('call msgpackparse(0zca0a0203)'))
end)
it('fails when unable to parse msgpack string', function()
local expected = 'Vim(call):E475: Invalid argument: Failed to parse msgpack string'
eq(expected, exc_exec([[call msgpackparse(["\xc1"])]]))
eq(expected, exc_exec('call msgpackparse(0zc1)'))
end)
end)
describe('msgpackdump() function', function()
before_each(clear)
local dump_eq = function(exp_list, arg_expr)
eq(exp_list, eval('msgpackdump(' .. arg_expr .. ')'))
eq(blobstr(exp_list), eval('msgpackdump(' .. arg_expr .. ', "B")'))
end
it('dumps string as BIN 8', function()
nvim('set_var', 'obj', {'Test'})
eq({"\196\004Test"}, eval('msgpackdump(obj)'))
dump_eq({'\196\004Test'}, '["Test"]')
end)
it('dumps blob as BIN 8', function()
dump_eq({'\196\005Bl\nb!'}, '[0z426c006221]')
end)
it('can dump generic mapping with generic mapping keys and values', function()
@ -522,56 +547,56 @@ describe('msgpackdump() function', function()
command('let todumpv1 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
command('let todumpv2 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
command('call add(todump._VAL, [todumpv1, todumpv2])')
eq({'\129\128\128'}, eval('msgpackdump([todump])'))
dump_eq({'\129\128\128'}, '[todump]')
end)
it('can dump v:true', function()
eq({'\195'}, funcs.msgpackdump({true}))
dump_eq({'\195'}, '[v:true]')
end)
it('can dump v:false', function()
eq({'\194'}, funcs.msgpackdump({false}))
dump_eq({'\194'}, '[v:false]')
end)
it('can v:null', function()
command('let todump = v:null')
it('can dump v:null', function()
dump_eq({'\192'}, '[v:null]')
end)
it('can dump special bool mapping (true)', function()
command('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 1}')
eq({'\195'}, eval('msgpackdump([todump])'))
dump_eq({'\195'}, '[todump]')
end)
it('can dump special bool mapping (false)', function()
command('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 0}')
eq({'\194'}, eval('msgpackdump([todump])'))
dump_eq({'\194'}, '[todump]')
end)
it('can dump special nil mapping', function()
command('let todump = {"_TYPE": v:msgpack_types.nil, "_VAL": 0}')
eq({'\192'}, eval('msgpackdump([todump])'))
dump_eq({'\192'}, '[todump]')
end)
it('can dump special ext mapping', function()
command('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}')
eq({'\212\005', ''}, eval('msgpackdump([todump])'))
dump_eq({'\212\005', ''}, '[todump]')
end)
it('can dump special array mapping', function()
command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}')
eq({'\146\005\145\196\n'}, eval('msgpackdump([todump])'))
dump_eq({'\146\005\145\196\n'}, '[todump]')
end)
it('can dump special UINT64_MAX mapping', function()
command('let todump = {"_TYPE": v:msgpack_types.integer}')
command('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]')
eq({'\207\255\255\255\255\255\255\255\255'}, eval('msgpackdump([todump])'))
dump_eq({'\207\255\255\255\255\255\255\255\255'}, '[todump]')
end)
it('can dump special INT64_MIN mapping', function()
command('let todump = {"_TYPE": v:msgpack_types.integer}')
command('let todump._VAL = [-1, 2, 0, 0]')
eq({'\211\128\n\n\n\n\n\n\n'}, eval('msgpackdump([todump])'))
dump_eq({'\211\128\n\n\n\n\n\n\n'}, '[todump]')
end)
it('fails to dump a function reference', function()
@ -610,13 +635,13 @@ describe('msgpackdump() function', function()
it('can dump dict with two same dicts inside', function()
command('let inter = {}')
command('let todump = {"a": inter, "b": inter}')
eq({"\130\161a\128\161b\128"}, eval('msgpackdump([todump])'))
dump_eq({"\130\161a\128\161b\128"}, '[todump]')
end)
it('can dump list with two same lists inside', function()
command('let inter = []')
command('let todump = [inter, inter]')
eq({"\146\144\144"}, eval('msgpackdump([todump])'))
dump_eq({"\146\144\144"}, '[todump]')
end)
it('fails to dump a recursive list in a special dict', function()
@ -667,9 +692,9 @@ describe('msgpackdump() function', function()
exc_exec('call msgpackdump()'))
end)
it('fails when called with two arguments', function()
it('fails when called with three arguments', function()
eq('Vim(call):E118: Too many arguments for function: msgpackdump',
exc_exec('call msgpackdump(["", ""], 1)'))
exc_exec('call msgpackdump(["", ""], 1, 2)'))
end)
it('fails to dump a string', function()
@ -711,9 +736,13 @@ describe('msgpackdump() function', function()
end)
it('can dump NULL string', function()
eq({'\196\n'}, eval('msgpackdump([$XXX_UNEXISTENT_VAR_XXX])'))
eq({'\196\n'}, eval('msgpackdump([{"_TYPE": v:msgpack_types.binary, "_VAL": [$XXX_UNEXISTENT_VAR_XXX]}])'))
eq({'\160'}, eval('msgpackdump([{"_TYPE": v:msgpack_types.string, "_VAL": [$XXX_UNEXISTENT_VAR_XXX]}])'))
dump_eq({'\196\n'}, '[$XXX_UNEXISTENT_VAR_XXX]')
dump_eq({'\196\n'}, '[{"_TYPE": v:msgpack_types.binary, "_VAL": [$XXX_UNEXISTENT_VAR_XXX]}]')
dump_eq({'\160'}, '[{"_TYPE": v:msgpack_types.string, "_VAL": [$XXX_UNEXISTENT_VAR_XXX]}]')
end)
it('can dump NULL blob', function()
eq({'\196\n'}, eval('msgpackdump([v:_null_blob])'))
end)
it('can dump NULL list', function()

View File

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

View File

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

View File

@ -15,7 +15,7 @@ describe('luaeval(vim.api.…)', function()
describe('nvim_buf_get_lines', function()
it('works', function()
funcs.setline(1, {"abc", "def", "a\nb", "ttt"})
eq({{_TYPE={}, _VAL={'a\nb'}}},
eq({'a\000b'},
funcs.luaeval('vim.api.nvim_buf_get_lines(1, 2, 3, false)'))
end)
end)
@ -23,7 +23,7 @@ describe('luaeval(vim.api.…)', function()
it('works', function()
funcs.setline(1, {"abc", "def", "a\nb", "ttt"})
eq(NIL, funcs.luaeval('vim.api.nvim_buf_set_lines(1, 1, 2, false, {"b\\0a"})'))
eq({'abc', {_TYPE={}, _VAL={'b\na'}}, {_TYPE={}, _VAL={'a\nb'}}, 'ttt'},
eq({'abc', 'b\000a', 'a\000b', 'ttt'},
funcs.luaeval('vim.api.nvim_buf_get_lines(1, 0, 4, false)'))
end)
end)
@ -64,15 +64,18 @@ describe('luaeval(vim.api.…)', function()
it('correctly converts from API objects', function()
eq(1, funcs.luaeval('vim.api.nvim_eval("1")'))
eq('1', funcs.luaeval([[vim.api.nvim_eval('"1"')]]))
eq('Blobby', funcs.luaeval('vim.api.nvim_eval("0z426c6f626279")'))
eq({}, funcs.luaeval('vim.api.nvim_eval("[]")'))
eq({}, funcs.luaeval('vim.api.nvim_eval("{}")'))
eq(1, funcs.luaeval('vim.api.nvim_eval("1.0")'))
eq('\000', funcs.luaeval('vim.api.nvim_eval("0z00")'))
eq(true, funcs.luaeval('vim.api.nvim_eval("v:true")'))
eq(false, funcs.luaeval('vim.api.nvim_eval("v:false")'))
eq(NIL, funcs.luaeval('vim.api.nvim_eval("v:null")'))
eq(0, eval([[type(luaeval('vim.api.nvim_eval("1")'))]]))
eq(1, eval([[type(luaeval('vim.api.nvim_eval("''1''")'))]]))
eq(1, eval([[type(luaeval('vim.api.nvim_eval("0zbeef")'))]]))
eq(3, eval([[type(luaeval('vim.api.nvim_eval("[]")'))]]))
eq(4, eval([[type(luaeval('vim.api.nvim_eval("{}")'))]]))
eq(5, eval([[type(luaeval('vim.api.nvim_eval("1.0")'))]]))

View File

@ -63,11 +63,10 @@ describe('luaeval()', function()
eq('\n[[...@0]]', funcs.execute('echo luaeval("l")'))
end)
end)
describe('strings', function()
it('are successfully converted to special dictionaries', function()
describe('strings with NULs', function()
it('are successfully converted to blobs', function()
command([[let s = luaeval('"\0"')]])
eq({_TYPE={}, _VAL={'\n'}}, meths.get_var('s'))
eq(1, funcs.eval('s._TYPE is v:msgpack_types.binary'))
eq('\000', meths.get_var('s'))
end)
it('are successfully converted to special dictionaries in table keys',
function()
@ -76,13 +75,10 @@ describe('luaeval()', function()
eq(1, funcs.eval('d._TYPE is v:msgpack_types.map'))
eq(1, funcs.eval('d._VAL[0][0]._TYPE is v:msgpack_types.string'))
end)
it('are successfully converted to special dictionaries from a list',
it('are successfully converted to blobs from a list',
function()
command([[let l = luaeval('{"abc", "a\0b", "c\0d", "def"}')]])
eq({'abc', {_TYPE={}, _VAL={'a\nb'}}, {_TYPE={}, _VAL={'c\nd'}}, 'def'},
meths.get_var('l'))
eq(1, funcs.eval('l[1]._TYPE is v:msgpack_types.binary'))
eq(1, funcs.eval('l[2]._TYPE is v:msgpack_types.binary'))
eq({'abc', 'a\000b', 'c\000d', 'def'}, meths.get_var('l'))
end)
end)
@ -100,9 +96,9 @@ describe('luaeval()', function()
eq(1, eval('type(luaeval("\'test\'"))'))
eq('', funcs.luaeval('""'))
eq({_TYPE={}, _VAL={'\n'}}, funcs.luaeval([['\0']]))
eq({_TYPE={}, _VAL={'\n', '\n'}}, funcs.luaeval([['\0\n\0']]))
eq(1, eval([[luaeval('"\0\n\0"')._TYPE is v:msgpack_types.binary]]))
eq('\000', funcs.luaeval([['\0']]))
eq('\000\n\000', funcs.luaeval([['\0\n\0']]))
eq(10, eval([[type(luaeval("'\\0\\n\\0'"))]]))
eq(true, funcs.luaeval('true'))
eq(false, funcs.luaeval('false'))
@ -122,12 +118,11 @@ describe('luaeval()', function()
local level = 30
eq(nested_by_level[level].o, funcs.luaeval(nested_by_level[level].s))
eq({_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n', '\n'}}, {_TYPE={}, _VAL={'\n', '\n\n'}}}}},
eq({_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n', '\n'}}, '\000\n\000\000'}}},
funcs.luaeval([[{['\0\n\0']='\0\n\0\0'}]]))
eq(1, eval([[luaeval('{["\0\n\0"]="\0\n\0\0"}')._TYPE is v:msgpack_types.map]]))
eq(1, eval([[luaeval('{["\0\n\0"]="\0\n\0\0"}')._VAL[0][0]._TYPE is v:msgpack_types.string]]))
eq(1, eval([[luaeval('{["\0\n\0"]="\0\n\0\0"}')._VAL[0][1]._TYPE is v:msgpack_types.binary]]))
eq({nested={{_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n', '\n'}}, {_TYPE={}, _VAL={'\n', '\n\n'}}}}}}},
eq({nested={{_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n', '\n'}}, '\000\n\000\000'}}}}},
funcs.luaeval([[{nested={{['\0\n\0']='\0\n\0\0'}}}]]))
end)
@ -175,8 +170,8 @@ describe('luaeval()', function()
end
it('correctly passes special dictionaries', function()
eq({'binary', {'\n', '\n'}}, luaevalarg(sp('binary', '["\\n", "\\n"]')))
eq({'binary', {'\n', '\n'}}, luaevalarg(sp('string', '["\\n", "\\n"]')))
eq({0, '\000\n\000'}, luaevalarg(sp('binary', '["\\n", "\\n"]')))
eq({0, '\000\n\000'}, luaevalarg(sp('string', '["\\n", "\\n"]')))
eq({0, true}, luaevalarg(sp('boolean', 1)))
eq({0, false}, luaevalarg(sp('boolean', 0)))
eq({0, NIL}, luaevalarg(sp('nil', 0)))
@ -458,6 +453,9 @@ describe('v:lua', function()
function mymod.crashy()
nonexistent()
end
function mymod.whatis(value)
return type(value) .. ": " .. tostring(value)
end
function mymod.omni(findstart, base)
if findstart == 1 then
return 5
@ -476,6 +474,8 @@ describe('v:lua', function()
eq(true, exec_lua([[return _G.val == vim.NIL]]))
eq(NIL, eval('v:lua.mymod.noisy("eval")'))
eq("hey eval", meths.get_current_line())
eq("string: abc", eval('v:lua.mymod.whatis(0z616263)'))
eq("string: ", eval('v:lua.mymod.whatis(v:_null_blob)'))
eq("Vim:E5108: Error executing lua [string \"<nvim>\"]:0: attempt to call global 'nonexistent' (a nil value)",
pcall_err(eval, 'v:lua.mymod.crashy()'))

View File

@ -739,7 +739,7 @@ describe('lua stdlib', function()
eq({NIL, NIL}, exec_lua([[return vim.fn.Nilly()]]))
-- error handling
eq({false, 'Vim:E714: List required'}, exec_lua([[return {pcall(vim.fn.add, "aa", "bb")}]]))
eq({false, 'Vim:E897: List or Blob required'}, exec_lua([[return {pcall(vim.fn.add, "aa", "bb")}]]))
end)
it('vim.fn should error when calling API function', function()

View File

@ -342,6 +342,11 @@ describe('ShaDa error handling', function()
eq('Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 has wrong variable name type', exc_exec(sdrcmd()))
end)
it('fails on variable item with BIN value and type value != VAR_TYPE_BLOB', function()
wshada('\006\000\007\147\196\001\065\196\000\000')
eq('Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 has wrong variable type', exc_exec(sdrcmd()))
end)
it('fails on replacement item with NIL value', function()
wshada('\003\000\001\192')
eq('Vim(rshada):E575: Error while reading ShaDa file: sub string entry at position 0 is not an array', exc_exec(sdrcmd()))

View File

@ -1,7 +1,7 @@
-- ShaDa variables saving/reading support
local helpers = require('test.functional.helpers')(after_each)
local meths, funcs, nvim_command, eq =
helpers.meths, helpers.funcs, helpers.command, helpers.eq
local meths, funcs, nvim_command, eq, eval =
helpers.meths, helpers.funcs, helpers.command, helpers.eq, helpers.eval
local shada_helpers = require('test.functional.shada.helpers')
local reset, clear = shada_helpers.reset, shada_helpers.clear
@ -30,10 +30,12 @@ describe('ShaDa support code', function()
else
meths.set_var(varname, varval)
end
local vartype = eval('type(g:' .. varname .. ')')
-- Exit during `reset` is not a regular exit: it does not write shada
-- automatically
nvim_command('qall')
reset('set shada+=!')
eq(vartype, eval('type(g:' .. varname .. ')'))
eq(varval, meths.get_var(varname))
end)
end
@ -47,6 +49,8 @@ describe('ShaDa support code', function()
autotest('false', 'FALSEVAR', false)
autotest('null', 'NULLVAR', 'v:null', true)
autotest('ext', 'EXTVAR', '{"_TYPE": v:msgpack_types.ext, "_VAL": [2, ["", ""]]}', true)
autotest('blob', 'BLOBVAR', '0z12ab34cd', true)
autotest('blob (with NULs)', 'BLOBVARNULS', '0z004e554c7300', true)
it('does not read back variables without `!` in &shada', function()
meths.set_var('STRVAR', 'foo')

View File

@ -70,7 +70,7 @@ local function dumplog(logfile, fn, ...)
if status == false then
logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog'
local logtail = module.read_nvim_log(logfile)
error(string.format('%s\n%s', rv, logtail))
error(string.format('%s\n%s', tostring(rv), logtail))
end
end
function module.eq(expected, actual, context, logfile)

View File

@ -2531,7 +2531,7 @@ describe('typval.c', function()
value='tr',
dict={},
})
lib.tv_item_lock(p_tv, -1, true)
lib.tv_item_lock(p_tv, -1, true, false)
eq(lib.VAR_UNLOCKED, p_tv.vval.v_partial.pt_dict.dv_lock)
end)
itp('does not change VAR_FIXED values', function()
@ -2542,14 +2542,14 @@ describe('typval.c', function()
d_tv.vval.v_dict.dv_lock = lib.VAR_FIXED
l_tv.v_lock = lib.VAR_FIXED
l_tv.vval.v_list.lv_lock = lib.VAR_FIXED
lib.tv_item_lock(d_tv, 1, true)
lib.tv_item_lock(l_tv, 1, true)
lib.tv_item_lock(d_tv, 1, true, false)
lib.tv_item_lock(l_tv, 1, true, false)
eq(lib.VAR_FIXED, d_tv.v_lock)
eq(lib.VAR_FIXED, l_tv.v_lock)
eq(lib.VAR_FIXED, d_tv.vval.v_dict.dv_lock)
eq(lib.VAR_FIXED, l_tv.vval.v_list.lv_lock)
lib.tv_item_lock(d_tv, 1, false)
lib.tv_item_lock(l_tv, 1, false)
lib.tv_item_lock(d_tv, 1, false, false)
lib.tv_item_lock(l_tv, 1, false, false)
eq(lib.VAR_FIXED, d_tv.v_lock)
eq(lib.VAR_FIXED, l_tv.v_lock)
eq(lib.VAR_FIXED, d_tv.vval.v_dict.dv_lock)
@ -2561,9 +2561,9 @@ describe('typval.c', function()
local d_tv = lua2typvalt(null_dict)
local s_tv = lua2typvalt(null_string)
alloc_log:clear()
lib.tv_item_lock(l_tv, 1, true)
lib.tv_item_lock(d_tv, 1, true)
lib.tv_item_lock(s_tv, 1, true)
lib.tv_item_lock(l_tv, 1, true, false)
lib.tv_item_lock(d_tv, 1, true, false)
lib.tv_item_lock(s_tv, 1, true, false)
eq(null_list, typvalt2lua(l_tv))
eq(null_dict, typvalt2lua(d_tv))
eq(null_string, typvalt2lua(s_tv))