Merge #7234 'built-in expression parser'
This commit is contained in:
commit
3cc7ebf810
|
@ -68,9 +68,9 @@ set(NVIM_VERSION_PATCH 3)
|
|||
set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers
|
||||
|
||||
# API level
|
||||
set(NVIM_API_LEVEL 3) # Bump this after any API change.
|
||||
set(NVIM_API_LEVEL 4) # Bump this after any API change.
|
||||
set(NVIM_API_LEVEL_COMPAT 0) # Adjust this after a _breaking_ API change.
|
||||
set(NVIM_API_PRERELEASE false)
|
||||
set(NVIM_API_PRERELEASE true)
|
||||
|
||||
file(TO_CMAKE_PATH ${CMAKE_CURRENT_LIST_DIR}/.git FORCED_GIT_DIR)
|
||||
include(GetGitRevisionDescription)
|
||||
|
|
|
@ -1559,10 +1559,10 @@ v:exception The value of the exception most recently caught and not
|
|||
< Output: "caught oops".
|
||||
|
||||
*v:false* *false-variable*
|
||||
v:false Special value used to put "false" in JSON and msgpack. See
|
||||
|json_encode()|. This value is converted to "v:false" when used
|
||||
as a String (e.g. in |expr5| with string concatenation
|
||||
operator) and to zero when used as a Number (e.g. in |expr5|
|
||||
v:false Special value used to put "false" in JSON and msgpack. See
|
||||
|json_encode()|. This value is converted to "v:false" when used
|
||||
as a String (e.g. in |expr5| with string concatenation
|
||||
operator) and to zero when used as a Number (e.g. in |expr5|
|
||||
or |expr7| when used with numeric operators). Read-only.
|
||||
|
||||
*v:fcs_reason* *fcs_reason-variable*
|
||||
|
@ -1703,16 +1703,16 @@ v:mouse_col Column number for a mouse click obtained with |getchar()|.
|
|||
value is zero when there was no mouse button click.
|
||||
|
||||
*v:msgpack_types* *msgpack_types-variable*
|
||||
v:msgpack_types Dictionary containing msgpack types used by |msgpackparse()|
|
||||
and |msgpackdump()|. All types inside dictionary are fixed
|
||||
(not editable) empty lists. To check whether some list is one
|
||||
v:msgpack_types Dictionary containing msgpack types used by |msgpackparse()|
|
||||
and |msgpackdump()|. All types inside dictionary are fixed
|
||||
(not editable) empty lists. To check whether some list is one
|
||||
of msgpack types, use |is| operator.
|
||||
|
||||
*v:null* *null-variable*
|
||||
v:null Special value used to put "null" in JSON and NIL in msgpack.
|
||||
See |json_encode()|. This value is converted to "v:null" when
|
||||
used as a String (e.g. in |expr5| with string concatenation
|
||||
operator) and to zero when used as a Number (e.g. in |expr5|
|
||||
v:null Special value used to put "null" in JSON and NIL in msgpack.
|
||||
See |json_encode()|. This value is converted to "v:null" when
|
||||
used as a String (e.g. in |expr5| with string concatenation
|
||||
operator) and to zero when used as a Number (e.g. in |expr5|
|
||||
or |expr7| when used with numeric operators). Read-only.
|
||||
|
||||
*v:oldfiles* *oldfiles-variable*
|
||||
|
@ -1903,10 +1903,10 @@ v:throwpoint The point where the exception most recently caught and not
|
|||
< Output: "Exception from test.vim, line 2"
|
||||
|
||||
*v:true* *true-variable*
|
||||
v:true Special value used to put "true" in JSON and msgpack. See
|
||||
|json_encode()|. This value is converted to "v:true" when used
|
||||
as a String (e.g. in |expr5| with string concatenation
|
||||
operator) and to one when used as a Number (e.g. in |expr5| or
|
||||
v:true Special value used to put "true" in JSON and msgpack. See
|
||||
|json_encode()|. This value is converted to "v:true" when used
|
||||
as a String (e.g. in |expr5| with string concatenation
|
||||
operator) and to one when used as a Number (e.g. in |expr5| or
|
||||
|expr7| when used with numeric operators). Read-only.
|
||||
|
||||
*v:val* *val-variable*
|
||||
|
@ -2492,7 +2492,7 @@ assert_fails({cmd} [, {error}]) *assert_fails()*
|
|||
assert_false({actual} [, {msg}]) *assert_false()*
|
||||
When {actual} is not false an error message is added to
|
||||
|v:errors|, like with |assert_equal()|.
|
||||
A value is false when it is zero or |v:false|. When "{actual}"
|
||||
A value is false when it is zero or |v:false|. When "{actual}"
|
||||
is not a number or |v:false| the assert fails.
|
||||
When {msg} is omitted an error in the form
|
||||
"Expected False but got {actual}" is produced.
|
||||
|
@ -3182,7 +3182,7 @@ 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
|
||||
items. A Number is empty when its value is zero. Special
|
||||
variable is empty when it is |v:false| or |v:null|.
|
||||
|
||||
escape({string}, {chars}) *escape()*
|
||||
|
@ -4749,7 +4749,7 @@ input({opts})
|
|||
string, or a blank string (for no prompt). A '\n' can be used
|
||||
in the prompt to start a new line.
|
||||
|
||||
In the second form it accepts a single dictionary with the
|
||||
In the second form it accepts a single dictionary with the
|
||||
following keys, any of which may be omitted:
|
||||
|
||||
Key Default Description ~
|
||||
|
@ -4757,7 +4757,7 @@ input({opts})
|
|||
default "" Same as {text} in the first form.
|
||||
completion nothing Same as {completion} in the first form.
|
||||
cancelreturn "" Same as {cancelreturn} from
|
||||
|inputdialog()|. Also works with
|
||||
|inputdialog()|. Also works with
|
||||
input().
|
||||
highlight nothing Highlight handler: |Funcref|.
|
||||
|
||||
|
@ -4833,8 +4833,8 @@ input({opts})
|
|||
modifier. If the function causes any errors, it will be
|
||||
skipped for the duration of the current input() call.
|
||||
|
||||
Currently coloring is disabled when command-line contains
|
||||
arabic characters.
|
||||
Highlighting is disabled if command-line contains arabic
|
||||
characters.
|
||||
|
||||
NOTE: This function must not be used in a startup file, for
|
||||
the versions that only run in GUI mode (e.g., the Win32 GUI).
|
||||
|
@ -4948,19 +4948,19 @@ islocked({expr}) *islocked()* *E786*
|
|||
message. Use |exists()| to check for existence.
|
||||
|
||||
id({expr}) *id()*
|
||||
Returns a |String| which is a unique identifier of the
|
||||
container type (|List|, |Dict| 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:
|
||||
|v:_null_list| and |v:_null_dict| have the same `id()` with
|
||||
different types because they are internally represented as
|
||||
a NULL pointers). Currently `id()` returns a hexadecimal
|
||||
representanion of the pointers to the containers (i.e. like
|
||||
`0x994a40`), same as `printf("%p", {expr})`, but it is advised
|
||||
Returns a |String| which is a unique identifier of the
|
||||
container type (|List|, |Dict| 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:
|
||||
|v:_null_list| and |v:_null_dict| have the same `id()` with
|
||||
different types because they are internally represented as
|
||||
a NULL pointers). Currently `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 exact format of return value.
|
||||
|
||||
It is not guaranteed that `id(no_longer_existing_container)`
|
||||
will not be equal to some other `id()`: new containers may
|
||||
It is not guaranteed that `id(no_longer_existing_container)`
|
||||
will not be equal to some other `id()`: new containers may
|
||||
reuse identifiers of the garbage-collected ones.
|
||||
|
||||
items({dict}) *items()*
|
||||
|
@ -5071,14 +5071,14 @@ join({list} [, {sep}]) *join()*
|
|||
The opposite function is |split()|.
|
||||
|
||||
json_decode({expr}) *json_decode()*
|
||||
Convert {expr} from JSON object. Accepts |readfile()|-style
|
||||
list as the input, as well as regular string. May output any
|
||||
Convert {expr} from JSON object. Accepts |readfile()|-style
|
||||
list as the input, as well as regular string. May output any
|
||||
Vim value. In the following cases it will output
|
||||
|msgpack-special-dict|:
|
||||
1. Dictionary contains duplicate key.
|
||||
2. Dictionary contains empty key.
|
||||
3. String contains NUL byte. Two special dictionaries: for
|
||||
dictionary and for string will be emitted in case string
|
||||
3. String contains NUL byte. Two special dictionaries: for
|
||||
dictionary and for string will be emitted in case string
|
||||
with NUL byte was a dictionary key.
|
||||
|
||||
Note: function treats its input as UTF-8 always. The JSON
|
||||
|
@ -5087,14 +5087,14 @@ json_decode({expr}) *json_decode()*
|
|||
Non-UTF-8 characters are an error.
|
||||
|
||||
json_encode({expr}) *json_encode()*
|
||||
Convert {expr} into a JSON string. Accepts
|
||||
|msgpack-special-dict| as the input. Will not convert
|
||||
|Funcref|s, mappings with non-string keys (can be created as
|
||||
|msgpack-special-dict|), values with self-referencing
|
||||
containers, strings which contain non-UTF-8 characters,
|
||||
pseudo-UTF-8 strings which contain codepoints reserved for
|
||||
surrogate pairs (such strings are not valid UTF-8 strings).
|
||||
Non-printable characters are converted into "\u1234" escapes
|
||||
Convert {expr} into a JSON string. Accepts
|
||||
|msgpack-special-dict| as the input. Will not convert
|
||||
|Funcref|s, mappings with non-string keys (can be created as
|
||||
|msgpack-special-dict|), values with self-referencing
|
||||
containers, strings which contain non-UTF-8 characters,
|
||||
pseudo-UTF-8 strings which contain codepoints reserved for
|
||||
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.
|
||||
|
||||
keys({dict}) *keys()*
|
||||
|
@ -5193,7 +5193,7 @@ line({expr}) The result is a Number, which is the line number of the file
|
|||
This autocommand jumps to the last known position in a file
|
||||
just after opening it, if the '" mark is set: >
|
||||
:au BufReadPost *
|
||||
\ if line("'\"") > 1 && line("'\"") <= line("$") && &ft !~# 'commit'
|
||||
\ if line("'\"") > 1 && line("'\"") <= line("$") && &ft !~# 'commit'
|
||||
\ | exe "normal! g`\""
|
||||
\ | endif
|
||||
|
||||
|
@ -5244,7 +5244,7 @@ log10({expr}) *log10()*
|
|||
< -2.0
|
||||
|
||||
luaeval({expr}[, {expr}])
|
||||
Evaluate Lua expression {expr} and return its result converted
|
||||
Evaluate Lua expression {expr} and return its result converted
|
||||
to Vim data structures. See |lua-luaeval| for more details.
|
||||
|
||||
map({expr1}, {expr2}) *map()*
|
||||
|
@ -5675,7 +5675,7 @@ mkdir({name} [, {path} [, {prot}]])
|
|||
:call mkdir($HOME . "/tmp/foo/bar", "p", 0700)
|
||||
< This function is not available in the |sandbox|.
|
||||
|
||||
If you try to create an existing directory with {path} set to
|
||||
If you try to create an existing directory with {path} set to
|
||||
"p" mkdir() will silently exit.
|
||||
|
||||
*mode()*
|
||||
|
@ -5728,78 +5728,76 @@ msgpackdump({list}) {Nvim} *msgpackdump()*
|
|||
5. Points 3. and 4. do not apply to |msgpack-special-dict|s.
|
||||
|
||||
msgpackparse({list}) {Nvim} *msgpackparse()*
|
||||
Convert a |readfile()|-style list to a list of VimL objects.
|
||||
Convert a |readfile()|-style list to a list of VimL objects.
|
||||
Example: >
|
||||
let fname = expand('~/.config/nvim/shada/main.shada')
|
||||
let mpack = readfile(fname, 'b')
|
||||
let shada_objects = msgpackparse(mpack)
|
||||
< This will read ~/.config/nvim/shada/main.shada file to
|
||||
< This will read ~/.config/nvim/shada/main.shada file to
|
||||
`shada_objects` list.
|
||||
|
||||
Limitations:
|
||||
1. Mapping ordering is not preserved unless messagepack
|
||||
mapping is dumped using generic mapping
|
||||
1. Mapping ordering is not preserved unless messagepack
|
||||
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
|
||||
|msgpack-special-dict| format which is not convenient to
|
||||
2. Since the parser aims to preserve all data untouched
|
||||
(except for 1.) some strings are parsed to
|
||||
|msgpack-special-dict| format which is not convenient to
|
||||
use.
|
||||
*msgpack-special-dict*
|
||||
Some messagepack strings may be parsed to special
|
||||
Some messagepack strings may be parsed to special
|
||||
dictionaries. Special dictionaries are dictionaries which
|
||||
|
||||
1. Contain exactly two keys: `_TYPE` and `_VAL`.
|
||||
2. `_TYPE` key is one of the types found in |v:msgpack_types|
|
||||
2. `_TYPE` key is one of the types found in |v:msgpack_types|
|
||||
variable.
|
||||
3. Value for `_VAL` has the following format (Key column
|
||||
3. Value for `_VAL` has the following format (Key column
|
||||
contains name of the key from |v:msgpack_types|):
|
||||
|
||||
Key Value ~
|
||||
nil Zero, ignored when dumping. This value cannot
|
||||
possibly appear in |msgpackparse()| output in Neovim
|
||||
versions which have |v:null|.
|
||||
boolean One or zero. When dumping it is only checked that
|
||||
value is a |Number|. This value cannot possibly
|
||||
appear in |msgpackparse()| output in Neovim versions
|
||||
which have |v:true| and |v:false|.
|
||||
integer |List| with four numbers: sign (-1 or 1), highest two
|
||||
bits, number with bits from 62nd to 31st, lowest 31
|
||||
bits. I.e. to get actual number one will need to use
|
||||
nil Zero, ignored when dumping. Not returned by
|
||||
|msgpackparse()| since |v:null| was introduced.
|
||||
boolean One or zero. When dumping it is only checked that
|
||||
value is a |Number|. Not returned by |msgpackparse()|
|
||||
since |v:true| and |v:false| were introduced.
|
||||
integer |List| with four numbers: sign (-1 or 1), highest two
|
||||
bits, number with bits from 62nd to 31st, lowest 31
|
||||
bits. I.e. to get actual number one will need to use
|
||||
code like >
|
||||
_VAL[0] * ((_VAL[1] << 62)
|
||||
& (_VAL[2] << 31)
|
||||
& _VAL[3])
|
||||
< Special dictionary with this type will appear in
|
||||
|msgpackparse()| output under one of the following
|
||||
< Special dictionary with this type will appear in
|
||||
|msgpackparse()| output under one of the following
|
||||
circumstances:
|
||||
1. |Number| is 32-bit and value is either above
|
||||
1. |Number| is 32-bit and value is either above
|
||||
INT32_MAX or below INT32_MIN.
|
||||
2. |Number| is 64-bit and value is above INT64_MAX. It
|
||||
cannot possibly be below INT64_MIN because msgpack
|
||||
2. |Number| is 64-bit and value is above INT64_MAX. It
|
||||
cannot possibly be below INT64_MIN because msgpack
|
||||
C parser does not support such values.
|
||||
float |Float|. This value cannot possibly appear in
|
||||
float |Float|. This value cannot possibly appear in
|
||||
|msgpackparse()| output.
|
||||
string |readfile()|-style list of strings. This value will
|
||||
appear in |msgpackparse()| output if string contains
|
||||
zero byte or if string is a mapping key and mapping is
|
||||
being represented as special dictionary for other
|
||||
string |readfile()|-style list of strings. This value will
|
||||
appear in |msgpackparse()| output if string contains
|
||||
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
|
||||
binary |readfile()|-style list of strings. This value will
|
||||
appear in |msgpackparse()| output if binary string
|
||||
contains zero byte.
|
||||
array |List|. This value cannot appear in |msgpackparse()|
|
||||
array |List|. This value cannot appear in |msgpackparse()|
|
||||
output.
|
||||
*msgpack-special-map*
|
||||
map |List| of |List|s with two items (key and value) each.
|
||||
This value will appear in |msgpackparse()| output if
|
||||
map |List| of |List|s with two items (key and value) each.
|
||||
This value will appear in |msgpackparse()| output if
|
||||
parsed mapping contains one of the following keys:
|
||||
1. Any key that is not a string (including keys which
|
||||
1. Any key that is not a string (including keys which
|
||||
are binary strings).
|
||||
2. String with NUL byte inside.
|
||||
3. Duplicate key.
|
||||
4. Empty key.
|
||||
ext |List| with two values: first is a signed integer
|
||||
representing extension type. Second is
|
||||
ext |List| with two values: first is a signed integer
|
||||
representing extension type. Second is
|
||||
|readfile()|-style list of strings.
|
||||
|
||||
nextnonblank({lnum}) *nextnonblank()*
|
||||
|
@ -6073,11 +6071,11 @@ pumvisible() *pumvisible()*
|
|||
py3eval({expr}) *py3eval()*
|
||||
Evaluate Python expression {expr} and return its result
|
||||
converted to Vim data structures.
|
||||
Numbers and strings are returned as they are (strings are
|
||||
copied though, Unicode strings are additionally converted to
|
||||
Numbers and strings are returned as they are (strings are
|
||||
copied though, Unicode strings are additionally converted to
|
||||
UTF-8).
|
||||
Lists are represented as Vim |List| type.
|
||||
Dictionaries are represented as Vim |Dictionary| type with
|
||||
Dictionaries are represented as Vim |Dictionary| type with
|
||||
keys converted to strings.
|
||||
{only available when compiled with the |+python3| feature}
|
||||
|
||||
|
@ -6145,7 +6143,7 @@ readfile({fname} [, {binary} [, {max}]])
|
|||
reltime([{start} [, {end}]]) *reltime()*
|
||||
Return an item that represents a time value. The format of
|
||||
the item depends on the system. It can be passed to
|
||||
|reltimestr()| to convert it to a string or |reltimefloat()|
|
||||
|reltimestr()| to convert it to a string or |reltimefloat()|
|
||||
to convert to a float.
|
||||
|
||||
Without an argument it returns the current "relative time", an
|
||||
|
@ -6929,9 +6927,9 @@ setreg({regname}, {value} [, {options}])
|
|||
:call setreg('a', "1\n2\n3", 'b5')
|
||||
|
||||
< This example shows using the functions to save and restore a
|
||||
register (note: you may not reliably restore register value
|
||||
without using the third argument to |getreg()| as without it
|
||||
newlines are represented as newlines AND Nul bytes are
|
||||
register (note: you may not reliably restore register value
|
||||
without using the third argument to |getreg()| as without it
|
||||
newlines are represented as newlines AND Nul bytes are
|
||||
represented as newlines as well, see |NL-used-for-Nul|). >
|
||||
:let var_a = getreg('a', 1, 1)
|
||||
:let var_amode = getregtype('a')
|
||||
|
@ -7369,20 +7367,20 @@ string({expr}) Return {expr} converted to a String. If {expr} is a Number,
|
|||
{expr} type result ~
|
||||
String 'string'
|
||||
Number 123
|
||||
Float 123.123456 or 1.123456e8 or
|
||||
Float 123.123456 or 1.123456e8 or
|
||||
`str2float('inf')`
|
||||
Funcref `function('name')`
|
||||
List [item, item]
|
||||
Dictionary {key: value, key: value}
|
||||
Note that in String values the ' character is doubled.
|
||||
Also see |strtrans()|.
|
||||
Note 2: Output format is mostly compatible with YAML, except
|
||||
for infinite and NaN floating-point values representations
|
||||
which use |str2float()|. Strings are also dumped literally,
|
||||
only single quote is escaped, which does not allow using YAML
|
||||
for parsing back binary strings. |eval()| should always work for
|
||||
strings and floats though and this is the only official
|
||||
method, use |msgpackdump()| or |json_encode()| if you need to
|
||||
Note 2: Output format is mostly compatible with YAML, except
|
||||
for infinite and NaN floating-point values representations
|
||||
which use |str2float()|. Strings are also dumped literally,
|
||||
only single quote is escaped, which does not allow using YAML
|
||||
for parsing back binary strings. |eval()| should always work for
|
||||
strings and floats though and this is the only official
|
||||
method, use |msgpackdump()| or |json_encode()| if you need to
|
||||
share data with other application.
|
||||
|
||||
*strlen()*
|
||||
|
@ -7630,10 +7628,10 @@ system({cmd} [, {input}]) *system()* *E677*
|
|||
redirection syntax) before input can reach it. Use
|
||||
|jobstart()| instead.
|
||||
|
||||
Note: Use |shellescape()| or |::S| with |expand()| or
|
||||
|fnamemodify()| to escape special characters in a command
|
||||
argument. Newlines in {cmd} may cause the command to fail.
|
||||
The characters in 'shellquote' and 'shellxquote' may also
|
||||
Note: Use |shellescape()| or |::S| with |expand()| or
|
||||
|fnamemodify()| to escape special characters in a command
|
||||
argument. Newlines in {cmd} may cause the command to fail.
|
||||
The characters in 'shellquote' and 'shellxquote' may also
|
||||
cause trouble.
|
||||
|
||||
The result is a String. Example: >
|
||||
|
@ -7660,9 +7658,9 @@ system({cmd} [, {input}]) *system()* *E677*
|
|||
|
||||
|
||||
systemlist({cmd} [, {input} [, {keepempty}]]) *systemlist()*
|
||||
Same as |system()|, but returns a |List| with lines (parts of
|
||||
output separated by NL) with NULs transformed into NLs. Output
|
||||
is the same as |readfile()| will output with {binary} argument
|
||||
Same as |system()|, but returns a |List| with lines (parts of
|
||||
output separated by NL) with NULs transformed into NLs. Output
|
||||
is the same as |readfile()| will output with {binary} argument
|
||||
set to "b", except that a final newline is not preserved,
|
||||
unless {keepempty} is non-zero.
|
||||
Note that on MS-Windows you may get trailing CR characters.
|
||||
|
@ -7926,7 +7924,7 @@ type({expr}) *type()*
|
|||
:if type(myvar) == type({})
|
||||
:if type(myvar) == type(0.0)
|
||||
:if type(myvar) == type(v:true)
|
||||
< In place of checking for |v:null| type it is better to check
|
||||
< In place of checking for |v:null| type it is better to check
|
||||
for |v:null| directly as it is the only value of this type: >
|
||||
:if myvar is v:null
|
||||
< To check if the v:t_ variables exist use this: >
|
||||
|
@ -8243,10 +8241,10 @@ writefile({list}, {fname} [, {flags}])
|
|||
:call writefile(["foo"], "event.log", "a")
|
||||
:call writefile(["bar"], "event.log", "a")
|
||||
<
|
||||
When {flags} contains "S" fsync() call is not used, with "s"
|
||||
it is used, 'fsync' option applies by default. No fsync()
|
||||
means that writefile() will finish faster, but writes may be
|
||||
left in OS buffers and not yet written to disk. Such changes
|
||||
When {flags} contains "S" fsync() call is not used, with "s"
|
||||
it is used, 'fsync' option applies by default. No fsync()
|
||||
means that writefile() will finish faster, but writes may be
|
||||
left in OS buffers and not yet written to disk. Such changes
|
||||
will disappear if system crashes before OS does writing.
|
||||
|
||||
All NL characters are replaced with a NUL character.
|
||||
|
@ -8728,7 +8726,7 @@ like this: >
|
|||
|
||||
When such a function is called, and it is not defined yet, Vim will search the
|
||||
"autoload" directories in 'runtimepath' for a script file called
|
||||
"filename.vim". For example "~/.config/nvim/autoload/filename.vim". That
|
||||
"filename.vim". For example "~/.config/nvim/autoload/filename.vim". That
|
||||
file should then define the function like this: >
|
||||
|
||||
function filename#funcname()
|
||||
|
@ -8999,8 +8997,8 @@ This does NOT work: >
|
|||
< *E741* *E940*
|
||||
If you try to change a locked variable you get an
|
||||
error message: "E741: Value is locked: {name}".
|
||||
If you try to lock or unlock a built-in variable you
|
||||
will get an error message "E940: Cannot lock or unlock
|
||||
If you try to lock or unlock a built-in variable you
|
||||
will get an error message "E940: Cannot lock or unlock
|
||||
variable {name}".
|
||||
|
||||
[depth] is relevant when locking a |List| or
|
||||
|
@ -9259,17 +9257,17 @@ This does NOT work: >
|
|||
with the |:redraw| command. Example: >
|
||||
:new | redraw | echo "there is a new window"
|
||||
< *:echo-self-refer*
|
||||
When printing nested containers echo prints second
|
||||
occurrence of the self-referencing container using
|
||||
"[...@level]" (self-referencing |List|) or
|
||||
When printing nested containers echo prints second
|
||||
occurrence of the self-referencing container using
|
||||
"[...@level]" (self-referencing |List|) or
|
||||
"{...@level}" (self-referencing |Dict|): >
|
||||
:let l = []
|
||||
:call add(l, l)
|
||||
:let l2 = []
|
||||
:call add(l2, [l2])
|
||||
:echo l l2
|
||||
< echoes "[[...@0]] [[[...@0]]]". Echoing "[l]" will
|
||||
echo "[[[...@1]]]" because l first occurs at second
|
||||
< echoes "[[...@0]] [[[...@0]]]". Echoing "[l]" will
|
||||
echo "[[[...@1]]]" because l first occurs at second
|
||||
level.
|
||||
|
||||
*:echon*
|
||||
|
@ -10558,7 +10556,7 @@ option will still be marked as it was set in the sandbox.
|
|||
In a few situations it is not allowed to change the text in the buffer, jump
|
||||
to another window and some other things that might confuse or break what Vim
|
||||
is currently doing. This mostly applies to things that happen when Vim is
|
||||
actually doing something else. For example, evaluating the 'balloonexpr' may
|
||||
actually doing something else. For example, evaluating the 'balloonexpr' may
|
||||
happen any moment the mouse cursor is resting at some position.
|
||||
|
||||
This is not allowed when the textlock is active:
|
||||
|
@ -10568,5 +10566,123 @@ This is not allowed when the textlock is active:
|
|||
- closing a window or quitting Vim
|
||||
- etc.
|
||||
|
||||
==============================================================================
|
||||
13. Command-line expressions highlighting *expr-highlight*
|
||||
|
||||
Expressions entered by the user in |i_CTRL-R_=|, |c_CTRL-\_e|, |quote=| are
|
||||
highlighted by the built-in expressions parser. It uses highlight groups
|
||||
described in the table below, which may be overriden by colorschemes.
|
||||
*hl-NvimInvalid*
|
||||
Besides the "Nvim"-prefixed highlight groups described below, there are
|
||||
"NvimInvalid"-prefixed highlight groups which have the same meaning but
|
||||
indicate that the token contains an error or that an error occurred just
|
||||
before it. They have mostly the same hierarchy, except that (by default) in
|
||||
place of any non-Nvim-prefixed group NvimInvalid linking to `Error` is used
|
||||
and some other intermediate groups are present.
|
||||
|
||||
Group Default link Colored expression ~
|
||||
*hl-NvimInternalError* None, red/red Parser bug
|
||||
|
||||
*hl-NvimAssignment* Operator Generic assignment
|
||||
*hl-NvimPlainAssignment* NvimAssignment `=` in |:let|
|
||||
*hl-NvimAugmentedAssignment* NvimAssignment Generic, `+=`/`-=`/`.=`
|
||||
*hl-NvimAssignmentWithAddition* NvimAugmentedAssignment `+=` in |:let+=|
|
||||
*hl-NvimAssignmentWithSubtraction* NvimAugmentedAssignment `-=` in |:let-=|
|
||||
*hl-NvimAssignmentWithConcatenation* NvimAugmentedAssignment `.=` in |:let.=|
|
||||
|
||||
*hl-NvimOperator* Operator Generic operator
|
||||
|
||||
*hl-NvimUnaryOperator* NvimOperator Generic unary op
|
||||
*hl-NvimUnaryPlus* NvimUnaryOperator |expr-unary-+|
|
||||
*hl-NvimUnaryMinus* NvimUnaryOperator |expr-unary--|
|
||||
*hl-NvimNot* NvimUnaryOperator |expr-!|
|
||||
|
||||
*hl-NvimBinaryOperator* NvimOperator Generic binary op
|
||||
*hl-NvimComparison* NvimBinaryOperator Any |expr4| operator
|
||||
*hl-NvimComparisonModifier* NvimComparison `#`/`?` near |expr4| op
|
||||
*hl-NvimBinaryPlus* NvimBinaryOperator |expr-+|
|
||||
*hl-NvimBinaryMinus* NvimBinaryOperator |expr--|
|
||||
*hl-NvimConcat* NvimBinaryOperator |expr-.|
|
||||
*hl-NvimConcatOrSubscript* NvimConcat |expr-.| or |expr-entry|
|
||||
*hl-NvimOr* NvimBinaryOperator |expr-barbar|
|
||||
*hl-NvimAnd* NvimBinaryOperator |expr-&&|
|
||||
*hl-NvimMultiplication* NvimBinaryOperator |expr-star|
|
||||
*hl-NvimDivision* NvimBinaryOperator |expr-/|
|
||||
*hl-NvimMod* NvimBinaryOperator |expr-%|
|
||||
|
||||
*hl-NvimTernary* NvimOperator `?` in |expr1|
|
||||
*hl-NvimTernaryColon* NvimTernary `:` in |expr1|
|
||||
|
||||
*hl-NvimParenthesis* Delimiter Generic bracket
|
||||
*hl-NvimLambda* NvimParenthesis `{`/`}` in |lambda|
|
||||
*hl-NvimNestingParenthesis* NvimParenthesis `(`/`)` in |expr-nesting|
|
||||
*hl-NvimCallingParenthesis* NvimParenthesis `(`/`)` in |expr-function|
|
||||
|
||||
*hl-NvimSubscript* NvimParenthesis Generic subscript
|
||||
*hl-NvimSubscriptBracket* NvimSubscript `[`/`]` in |expr-[]|
|
||||
*hl-NvimSubscriptColon* NvimSubscript `:` in |expr-[:]|
|
||||
*hl-NvimCurly* NvimSubscript `{`/`}` in
|
||||
|curly-braces-names|
|
||||
|
||||
*hl-NvimContainer* NvimParenthesis Generic container
|
||||
*hl-NvimDict* NvimContainer `{`/`}` in |dict| literal
|
||||
*hl-NvimList* NvimContainer `[`/`]` in |list| literal
|
||||
|
||||
*hl-NvimIdentifier* Identifier Generic identifier
|
||||
*hl-NvimIdentifierScope* NvimIdentifier Namespace: letter
|
||||
before `:` in
|
||||
|internal-variables|
|
||||
*hl-NvimIdentifierScopeDelimiter* NvimIdentifier `:` after namespace
|
||||
letter
|
||||
*hl-NvimIdentifierName* NvimIdentifier Rest of the ident
|
||||
*hl-NvimIdentifierKey* NvimIdentifier Identifier after
|
||||
|expr-entry|
|
||||
|
||||
*hl-NvimColon* Delimiter `:` in |dict| literal
|
||||
*hl-NvimComma* Delimiter `,` in |dict|/|list|
|
||||
literal or
|
||||
|expr-function|
|
||||
*hl-NvimArrow* Delimiter `->` in |lambda|
|
||||
|
||||
*hl-NvimRegister* SpecialChar |expr-register|
|
||||
*hl-NvimNumber* Number Non-prefix digits
|
||||
in integer
|
||||
|expr-number|
|
||||
*hl-NvimNumberPrefix* Type `0` for |octal-number|
|
||||
`0x` for |hex-number|
|
||||
`0b` for |binary-number|
|
||||
*hl-NvimFloat* NvimNumber Floating-point
|
||||
number
|
||||
|
||||
*hl-NvimOptionSigil* Type `&` in |expr-option|
|
||||
*hl-NvimOptionScope* NvimIdentifierScope Option scope if any
|
||||
*hl-NvimOptionScopeDelimiter* NvimIdentifierScopeDelimiter
|
||||
`:` after option scope
|
||||
*hl-NvimOptionName* NvimIdentifier Option name
|
||||
|
||||
*hl-NvimEnvironmentSigil* NvimOptionSigil `$` in |expr-env|
|
||||
*hl-NvimEnvironmentName* NvimIdentifier Env variable name
|
||||
|
||||
*hl-NvimString* String Generic string
|
||||
*hl-NvimStringBody* NvimString Generic string
|
||||
literal body
|
||||
*hl-NvimStringQuote* NvimString Generic string quote
|
||||
*hl-NvimStringSpecial* SpecialChar Generic string
|
||||
non-literal body
|
||||
|
||||
*hl-NvimSingleQuote* NvimStringQuote `'` in |expr-'|
|
||||
*hl-NvimSingleQuotedBody* NvimStringBody Literal part of
|
||||
|expr-'| string body
|
||||
*hl-NvimSingleQuotedQuote* NvimStringSpecial `''` inside |expr-'|
|
||||
string body
|
||||
|
||||
*hl-NvimDoubleQuote* NvimStringQuote `"` in |expr-quote|
|
||||
*hl-NvimDoubleQuotedBody* NvimStringBody Literal part of
|
||||
|expr-quote| body
|
||||
*hl-NvimDoubleQuotedEscape* NvimStringSpecial Valid |expr-quote|
|
||||
escape sequence
|
||||
*hl-NvimDoubleQuotedUnknownEscape* NvimInvalidValue Unrecognized
|
||||
|expr-quote| escape
|
||||
sequence
|
||||
|
||||
vim:tw=78:ts=8:noet:ft=help:norl:
|
||||
|
|
|
@ -165,14 +165,17 @@ Highlight groups:
|
|||
|hl-TermCursor|
|
||||
|hl-TermCursorNC|
|
||||
|hl-Whitespace| highlights 'listchars' whitespace
|
||||
|expr-highlight| highlight groups (prefixed with "Nvim")
|
||||
|
||||
UI:
|
||||
*E5408* *E5409* *g:Nvim_color_expr* *g:Nvim_color_cmdline*
|
||||
Command-line coloring is supported. Only |input()| and |inputdialog()| may
|
||||
be colored. For testing purposes expressions (e.g. |i_CTRL-R_=|) and regular
|
||||
command-line (|:|) are colored by callbacks defined in `g:Nvim_color_expr`
|
||||
and `g:Nvim_color_cmdline` respectively (these callbacks are for testing
|
||||
only, and will be removed in a future version).
|
||||
Command-line highlighting:
|
||||
The expression prompt (|@=|, |c_CTRL-R_=|, |i_CTRL-R_=|) is highlighted
|
||||
using a built-in VimL expression parser. |expr-highlight|
|
||||
*E5408* *E5409*
|
||||
|input()|, |inputdialog()| support custom highlighting. |input()-highlight|
|
||||
*g:Nvim_color_cmdline*
|
||||
(Experimental) Command-line (|:|) is colored by callback defined in
|
||||
`g:Nvim_color_cmdline` (this callback is for testing only, and will be
|
||||
removed in the future).
|
||||
|
||||
==============================================================================
|
||||
4. Changed features *nvim-features-changed*
|
||||
|
|
|
@ -86,6 +86,8 @@ foreach(subdir
|
|||
event
|
||||
eval
|
||||
lua
|
||||
viml
|
||||
viml/parser
|
||||
)
|
||||
if(${subdir} MATCHES "tui" AND NOT FEAT_TUI)
|
||||
continue()
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
#include "nvim/syntax.h"
|
||||
#include "nvim/getchar.h"
|
||||
#include "nvim/os/input.h"
|
||||
#include "nvim/viml/parser/expressions.h"
|
||||
#include "nvim/viml/parser/parser.h"
|
||||
|
||||
#define LINE_BUFFER_SIZE 4096
|
||||
|
||||
|
@ -889,6 +891,458 @@ theend:
|
|||
return rv;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
ExprASTNode **node_p;
|
||||
Object *ret_node_p;
|
||||
} ExprASTConvStackItem;
|
||||
|
||||
typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack;
|
||||
|
||||
/// Parse a VimL expression
|
||||
///
|
||||
/// @param[in] expr Expression to parse. Is always treated as a single line.
|
||||
/// @param[in] flags Flags:
|
||||
///
|
||||
/// - "m" if multiple expressions in a row are allowed (only
|
||||
/// the first one will be parsed),
|
||||
/// - "E" if EOC tokens are not allowed (determines whether
|
||||
/// they will stop parsing process or be recognized as an
|
||||
/// operator/space, though also yielding an error).
|
||||
/// - "l" when needing to start parsing with lvalues for
|
||||
/// ":let" or ":for".
|
||||
///
|
||||
/// Common flag sets:
|
||||
/// - "m" to parse like for ":echo".
|
||||
/// - "E" to parse like for "<C-r>=".
|
||||
/// - empty string for ":call".
|
||||
/// - "lm" to parse for ":let".
|
||||
/// @param[in] highlight If true, return value will also include "highlight"
|
||||
/// key containing array of 4-tuples (arrays) (Integer,
|
||||
/// Integer, Integer, String), where first three numbers
|
||||
/// define the highlighted region and represent line,
|
||||
/// starting column and ending column (latter exclusive:
|
||||
/// one should highlight region [start_col, end_col)).
|
||||
///
|
||||
/// @return AST: top-level dictionary holds keys
|
||||
///
|
||||
/// "error": Dictionary with error, present only if parser saw some
|
||||
/// error. Contains the following keys:
|
||||
///
|
||||
/// "message": String, error message in printf format, translated.
|
||||
/// Must contain exactly one "%.*s".
|
||||
/// "arg": String, error message argument.
|
||||
///
|
||||
/// "len": Amount of bytes successfully parsed. With flags equal to ""
|
||||
/// that should be equal to the length of expr string.
|
||||
///
|
||||
/// @note: “Sucessfully parsed” here means “participated in AST
|
||||
/// creation”, not “till the first error”.
|
||||
///
|
||||
/// "ast": AST, either nil or a dictionary with these keys:
|
||||
///
|
||||
/// "type": node type, one of the value names from ExprASTNodeType
|
||||
/// stringified without "kExprNode" prefix.
|
||||
/// "start": a pair [line, column] describing where node is “started”
|
||||
/// where "line" is always 0 (will not be 0 if you will be
|
||||
/// using nvim_parse_viml() on e.g. ":let", but that is not
|
||||
/// present yet). Both elements are Integers.
|
||||
/// "len": “length” of the node. This and "start" are there for
|
||||
/// debugging purposes primary (debugging parser and providing
|
||||
/// debug information).
|
||||
/// "children": a list of nodes described in top/"ast". There always
|
||||
/// is zero, one or two children, key will not be present
|
||||
/// if node has no children. Maximum number of children
|
||||
/// may be found in node_maxchildren array.
|
||||
///
|
||||
/// Local values (present only for certain nodes):
|
||||
///
|
||||
/// "scope": a single Integer, specifies scope for "Option" and
|
||||
/// "PlainIdentifier" nodes. For "Option" it is one of
|
||||
/// ExprOptScope values, for "PlainIdentifier" it is one of
|
||||
/// ExprVarScope values.
|
||||
/// "ident": identifier (without scope, if any), present for "Option",
|
||||
/// "PlainIdentifier", "PlainKey" and "Environment" nodes.
|
||||
/// "name": Integer, register name (one character) or -1. Only present
|
||||
/// for "Register" nodes.
|
||||
/// "cmp_type": String, comparison type, one of the value names from
|
||||
/// ExprComparisonType, stringified without "kExprCmp"
|
||||
/// prefix. Only present for "Comparison" nodes.
|
||||
/// "ccs_strategy": String, case comparison strategy, one of the
|
||||
/// value names from ExprCaseCompareStrategy,
|
||||
/// stringified without "kCCStrategy" prefix. Only
|
||||
/// present for "Comparison" nodes.
|
||||
/// "augmentation": String, augmentation type for "Assignment" nodes.
|
||||
/// Is either an empty string, "Add", "Subtract" or
|
||||
/// "Concat" for "=", "+=", "-=" or ".=" respectively.
|
||||
/// "invert": Boolean, true if result of comparison needs to be
|
||||
/// inverted. Only present for "Comparison" nodes.
|
||||
/// "ivalue": Integer, integer value for "Integer" nodes.
|
||||
/// "fvalue": Float, floating-point value for "Float" nodes.
|
||||
/// "svalue": String, value for "SingleQuotedString" and
|
||||
/// "DoubleQuotedString" nodes.
|
||||
Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight,
|
||||
Error *err)
|
||||
FUNC_API_SINCE(4) FUNC_API_ASYNC
|
||||
{
|
||||
int pflags = 0;
|
||||
for (size_t i = 0 ; i < flags.size ; i++) {
|
||||
switch (flags.data[i]) {
|
||||
case 'm': { pflags |= kExprFlagsMulti; break; }
|
||||
case 'E': { pflags |= kExprFlagsDisallowEOC; break; }
|
||||
case 'l': { pflags |= kExprFlagsParseLet; break; }
|
||||
case NUL: {
|
||||
api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)",
|
||||
(unsigned)flags.data[i]);
|
||||
return (Dictionary)ARRAY_DICT_INIT;
|
||||
}
|
||||
default: {
|
||||
api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)",
|
||||
flags.data[i], (unsigned)flags.data[i]);
|
||||
return (Dictionary)ARRAY_DICT_INIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
ParserLine plines[] = {
|
||||
{
|
||||
.data = expr.data,
|
||||
.size = expr.size,
|
||||
.allocated = false,
|
||||
},
|
||||
{ NULL, 0, false },
|
||||
};
|
||||
ParserLine *plines_p = plines;
|
||||
ParserHighlight colors;
|
||||
kvi_init(colors);
|
||||
ParserHighlight *const colors_p = (highlight ? &colors : NULL);
|
||||
ParserState pstate;
|
||||
viml_parser_init(
|
||||
&pstate, parser_simple_get_line, &plines_p, colors_p);
|
||||
ExprAST east = viml_pexpr_parse(&pstate, pflags);
|
||||
|
||||
const size_t ret_size = (
|
||||
2 // "ast", "len"
|
||||
+ (size_t)(east.err.msg != NULL) // "error"
|
||||
+ (size_t)highlight // "highlight"
|
||||
+ 0);
|
||||
Dictionary ret = {
|
||||
.items = xmalloc(ret_size * sizeof(ret.items[0])),
|
||||
.size = 0,
|
||||
.capacity = ret_size,
|
||||
};
|
||||
ret.items[ret.size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("ast"),
|
||||
.value = NIL,
|
||||
};
|
||||
ret.items[ret.size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("len"),
|
||||
.value = INTEGER_OBJ((Integer)(pstate.pos.line == 1
|
||||
? plines[0].size
|
||||
: pstate.pos.col)),
|
||||
};
|
||||
if (east.err.msg != NULL) {
|
||||
Dictionary err_dict = {
|
||||
.items = xmalloc(2 * sizeof(err_dict.items[0])),
|
||||
.size = 2,
|
||||
.capacity = 2,
|
||||
};
|
||||
err_dict.items[0] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("message"),
|
||||
.value = STRING_OBJ(cstr_to_string(east.err.msg)),
|
||||
};
|
||||
if (east.err.arg == NULL) {
|
||||
err_dict.items[1] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("arg"),
|
||||
.value = STRING_OBJ(STRING_INIT),
|
||||
};
|
||||
} else {
|
||||
err_dict.items[1] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("arg"),
|
||||
.value = STRING_OBJ(((String) {
|
||||
.data = xmemdupz(east.err.arg, (size_t)east.err.arg_len),
|
||||
.size = (size_t)east.err.arg_len,
|
||||
})),
|
||||
};
|
||||
}
|
||||
ret.items[ret.size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("error"),
|
||||
.value = DICTIONARY_OBJ(err_dict),
|
||||
};
|
||||
}
|
||||
if (highlight) {
|
||||
Array hl = (Array) {
|
||||
.items = xmalloc(kv_size(colors) * sizeof(hl.items[0])),
|
||||
.capacity = kv_size(colors),
|
||||
.size = kv_size(colors),
|
||||
};
|
||||
for (size_t i = 0 ; i < kv_size(colors) ; i++) {
|
||||
const ParserHighlightChunk chunk = kv_A(colors, i);
|
||||
Array chunk_arr = (Array) {
|
||||
.items = xmalloc(4 * sizeof(chunk_arr.items[0])),
|
||||
.capacity = 4,
|
||||
.size = 4,
|
||||
};
|
||||
chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line);
|
||||
chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col);
|
||||
chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col);
|
||||
chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group));
|
||||
hl.items[i] = ARRAY_OBJ(chunk_arr);
|
||||
}
|
||||
ret.items[ret.size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("highlight"),
|
||||
.value = ARRAY_OBJ(hl),
|
||||
};
|
||||
}
|
||||
kvi_destroy(colors);
|
||||
|
||||
// Walk over the AST, freeing nodes in process.
|
||||
ExprASTConvStack ast_conv_stack;
|
||||
kvi_init(ast_conv_stack);
|
||||
kvi_push(ast_conv_stack, ((ExprASTConvStackItem) {
|
||||
.node_p = &east.root,
|
||||
.ret_node_p = &ret.items[0].value,
|
||||
}));
|
||||
while (kv_size(ast_conv_stack)) {
|
||||
ExprASTConvStackItem cur_item = kv_last(ast_conv_stack);
|
||||
ExprASTNode *const node = *cur_item.node_p;
|
||||
if (node == NULL) {
|
||||
assert(kv_size(ast_conv_stack) == 1);
|
||||
kv_drop(ast_conv_stack, 1);
|
||||
} else {
|
||||
if (cur_item.ret_node_p->type == kObjectTypeNil) {
|
||||
const size_t ret_node_items_size = (size_t)(
|
||||
3 // "type", "start" and "len"
|
||||
+ (node->children != NULL) // "children"
|
||||
+ (node->type == kExprNodeOption
|
||||
|| node->type == kExprNodePlainIdentifier) // "scope"
|
||||
+ (node->type == kExprNodeOption
|
||||
|| node->type == kExprNodePlainIdentifier
|
||||
|| node->type == kExprNodePlainKey
|
||||
|| node->type == kExprNodeEnvironment) // "ident"
|
||||
+ (node->type == kExprNodeRegister) // "name"
|
||||
+ (3 // "cmp_type", "ccs_strategy", "invert"
|
||||
* (node->type == kExprNodeComparison))
|
||||
+ (node->type == kExprNodeInteger) // "ivalue"
|
||||
+ (node->type == kExprNodeFloat) // "fvalue"
|
||||
+ (node->type == kExprNodeDoubleQuotedString
|
||||
|| node->type == kExprNodeSingleQuotedString) // "svalue"
|
||||
+ (node->type == kExprNodeAssignment) // "augmentation"
|
||||
+ 0);
|
||||
Dictionary ret_node = {
|
||||
.items = xmalloc(ret_node_items_size * sizeof(ret_node.items[0])),
|
||||
.capacity = ret_node_items_size,
|
||||
.size = 0,
|
||||
};
|
||||
*cur_item.ret_node_p = DICTIONARY_OBJ(ret_node);
|
||||
}
|
||||
Dictionary *ret_node = &cur_item.ret_node_p->data.dictionary;
|
||||
if (node->children != NULL) {
|
||||
const size_t num_children = 1 + (node->children->next != NULL);
|
||||
Array children_array = {
|
||||
.items = xmalloc(num_children * sizeof(children_array.items[0])),
|
||||
.capacity = num_children,
|
||||
.size = num_children,
|
||||
};
|
||||
for (size_t i = 0; i < num_children; i++) {
|
||||
children_array.items[i] = NIL;
|
||||
}
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("children"),
|
||||
.value = ARRAY_OBJ(children_array),
|
||||
};
|
||||
kvi_push(ast_conv_stack, ((ExprASTConvStackItem) {
|
||||
.node_p = &node->children,
|
||||
.ret_node_p = &children_array.items[0],
|
||||
}));
|
||||
} else if (node->next != NULL) {
|
||||
kvi_push(ast_conv_stack, ((ExprASTConvStackItem) {
|
||||
.node_p = &node->next,
|
||||
.ret_node_p = cur_item.ret_node_p + 1,
|
||||
}));
|
||||
} else if (node != NULL) {
|
||||
kv_drop(ast_conv_stack, 1);
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("type"),
|
||||
.value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])),
|
||||
};
|
||||
Array start_array = {
|
||||
.items = xmalloc(2 * sizeof(start_array.items[0])),
|
||||
.capacity = 2,
|
||||
.size = 2,
|
||||
};
|
||||
start_array.items[0] = INTEGER_OBJ((Integer)node->start.line);
|
||||
start_array.items[1] = INTEGER_OBJ((Integer)node->start.col);
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("start"),
|
||||
.value = ARRAY_OBJ(start_array),
|
||||
};
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("len"),
|
||||
.value = INTEGER_OBJ((Integer)node->len),
|
||||
};
|
||||
switch (node->type) {
|
||||
case kExprNodeDoubleQuotedString:
|
||||
case kExprNodeSingleQuotedString: {
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("svalue"),
|
||||
.value = STRING_OBJ(((String) {
|
||||
.data = node->data.str.value,
|
||||
.size = node->data.str.size,
|
||||
})),
|
||||
};
|
||||
break;
|
||||
}
|
||||
case kExprNodeOption: {
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("scope"),
|
||||
.value = INTEGER_OBJ(node->data.opt.scope),
|
||||
};
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("ident"),
|
||||
.value = STRING_OBJ(((String) {
|
||||
.data = xmemdupz(node->data.opt.ident,
|
||||
node->data.opt.ident_len),
|
||||
.size = node->data.opt.ident_len,
|
||||
})),
|
||||
};
|
||||
break;
|
||||
}
|
||||
case kExprNodePlainIdentifier: {
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("scope"),
|
||||
.value = INTEGER_OBJ(node->data.var.scope),
|
||||
};
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("ident"),
|
||||
.value = STRING_OBJ(((String) {
|
||||
.data = xmemdupz(node->data.var.ident,
|
||||
node->data.var.ident_len),
|
||||
.size = node->data.var.ident_len,
|
||||
})),
|
||||
};
|
||||
break;
|
||||
}
|
||||
case kExprNodePlainKey: {
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("ident"),
|
||||
.value = STRING_OBJ(((String) {
|
||||
.data = xmemdupz(node->data.var.ident,
|
||||
node->data.var.ident_len),
|
||||
.size = node->data.var.ident_len,
|
||||
})),
|
||||
};
|
||||
break;
|
||||
}
|
||||
case kExprNodeEnvironment: {
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("ident"),
|
||||
.value = STRING_OBJ(((String) {
|
||||
.data = xmemdupz(node->data.env.ident,
|
||||
node->data.env.ident_len),
|
||||
.size = node->data.env.ident_len,
|
||||
})),
|
||||
};
|
||||
break;
|
||||
}
|
||||
case kExprNodeRegister: {
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("name"),
|
||||
.value = INTEGER_OBJ(node->data.reg.name),
|
||||
};
|
||||
break;
|
||||
}
|
||||
case kExprNodeComparison: {
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("cmp_type"),
|
||||
.value = STRING_OBJ(cstr_to_string(
|
||||
eltkn_cmp_type_tab[node->data.cmp.type])),
|
||||
};
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("ccs_strategy"),
|
||||
.value = STRING_OBJ(cstr_to_string(
|
||||
ccs_tab[node->data.cmp.ccs])),
|
||||
};
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("invert"),
|
||||
.value = BOOLEAN_OBJ(node->data.cmp.inv),
|
||||
};
|
||||
break;
|
||||
}
|
||||
case kExprNodeFloat: {
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("fvalue"),
|
||||
.value = FLOAT_OBJ(node->data.flt.value),
|
||||
};
|
||||
break;
|
||||
}
|
||||
case kExprNodeInteger: {
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("ivalue"),
|
||||
.value = INTEGER_OBJ((Integer)(
|
||||
node->data.num.value > API_INTEGER_MAX
|
||||
? API_INTEGER_MAX
|
||||
: (Integer)node->data.num.value)),
|
||||
};
|
||||
break;
|
||||
}
|
||||
case kExprNodeAssignment: {
|
||||
const ExprAssignmentType asgn_type = node->data.ass.type;
|
||||
ret_node->items[ret_node->size++] = (KeyValuePair) {
|
||||
.key = STATIC_CSTR_TO_STRING("augmentation"),
|
||||
.value = STRING_OBJ(
|
||||
asgn_type == kExprAsgnPlain
|
||||
? (String)STRING_INIT
|
||||
: cstr_to_string(expr_asgn_type_tab[asgn_type])),
|
||||
};
|
||||
break;
|
||||
}
|
||||
case kExprNodeMissing:
|
||||
case kExprNodeOpMissing:
|
||||
case kExprNodeTernary:
|
||||
case kExprNodeTernaryValue:
|
||||
case kExprNodeSubscript:
|
||||
case kExprNodeListLiteral:
|
||||
case kExprNodeUnaryPlus:
|
||||
case kExprNodeBinaryPlus:
|
||||
case kExprNodeNested:
|
||||
case kExprNodeCall:
|
||||
case kExprNodeComplexIdentifier:
|
||||
case kExprNodeUnknownFigure:
|
||||
case kExprNodeLambda:
|
||||
case kExprNodeDictLiteral:
|
||||
case kExprNodeCurlyBracesIdentifier:
|
||||
case kExprNodeComma:
|
||||
case kExprNodeColon:
|
||||
case kExprNodeArrow:
|
||||
case kExprNodeConcat:
|
||||
case kExprNodeConcatOrSubscript:
|
||||
case kExprNodeOr:
|
||||
case kExprNodeAnd:
|
||||
case kExprNodeUnaryMinus:
|
||||
case kExprNodeBinaryMinus:
|
||||
case kExprNodeNot:
|
||||
case kExprNodeMultiplication:
|
||||
case kExprNodeDivision:
|
||||
case kExprNodeMod: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(cur_item.ret_node_p->data.dictionary.size
|
||||
== cur_item.ret_node_p->data.dictionary.capacity);
|
||||
xfree(*cur_item.node_p);
|
||||
*cur_item.node_p = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
kvi_destroy(ast_conv_stack);
|
||||
|
||||
assert(ret.size == ret.capacity);
|
||||
// Should be a no-op actually, leaving it in case non-nodes will need to be
|
||||
// freed later.
|
||||
viml_pexpr_free_ast(east);
|
||||
viml_parser_destroy(&pstate);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/// Writes a message to vim output or error buffer. The string is split
|
||||
/// and flushed after each newline. Incomplete lines are kept for writing
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "nvim/macros.h"
|
||||
#include "nvim/func_attr.h"
|
||||
#include "nvim/os/os_defs.h"
|
||||
|
||||
|
@ -98,6 +99,10 @@ static inline bool ascii_isxdigit(int)
|
|||
REAL_FATTR_CONST
|
||||
REAL_FATTR_ALWAYS_INLINE;
|
||||
|
||||
static inline bool ascii_isident(int)
|
||||
REAL_FATTR_CONST
|
||||
REAL_FATTR_ALWAYS_INLINE;
|
||||
|
||||
static inline bool ascii_isbdigit(int)
|
||||
REAL_FATTR_CONST
|
||||
REAL_FATTR_ALWAYS_INLINE;
|
||||
|
@ -138,6 +143,14 @@ static inline bool ascii_isxdigit(int c)
|
|||
|| (c >= 'A' && c <= 'F');
|
||||
}
|
||||
|
||||
/// Checks if `c` is an “identifier” character
|
||||
///
|
||||
/// That is, whether it is alphanumeric character or underscore.
|
||||
static inline bool ascii_isident(const int c)
|
||||
{
|
||||
return ASCII_ISALNUM(c) || c == '_';
|
||||
}
|
||||
|
||||
/// Checks if `c` is a binary digit, that is, 0-1.
|
||||
///
|
||||
/// @see {ascii_isdigit}
|
||||
|
|
|
@ -1611,141 +1611,145 @@ bool vim_isblankline(char_u *lbuf)
|
|||
/// If maxlen > 0, check at a maximum maxlen chars.
|
||||
///
|
||||
/// @param start
|
||||
/// @param prep Returns type of number 0 = decimal, 'x' or 'X' is hex,
|
||||
/// '0' = octal, 'b' or 'B' is bin
|
||||
/// @param prep Returns guessed type of number 0 = decimal, 'x' or 'X' is
|
||||
/// hexadecimal, '0' = octal, 'b' or 'B' is binary. When using
|
||||
/// STR2NR_FORCE is always zero.
|
||||
/// @param len Returns the detected length of number.
|
||||
/// @param what Recognizes what number passed.
|
||||
/// @param what Recognizes what number passed, @see ChStr2NrFlags.
|
||||
/// @param nptr Returns the signed result.
|
||||
/// @param unptr Returns the unsigned result.
|
||||
/// @param maxlen Max length of string to check.
|
||||
void vim_str2nr(const char_u *const start, int *const prep, int *const len,
|
||||
const int what, varnumber_T *const nptr,
|
||||
uvarnumber_T *const unptr, const int maxlen)
|
||||
FUNC_ATTR_NONNULL_ARG(1)
|
||||
{
|
||||
const char_u *ptr = start;
|
||||
const char *ptr = (const char *)start;
|
||||
#define STRING_ENDED(ptr) \
|
||||
(!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen))
|
||||
int pre = 0; // default is decimal
|
||||
bool negative = false;
|
||||
const bool negative = (ptr[0] == '-');
|
||||
uvarnumber_T un = 0;
|
||||
|
||||
if (ptr[0] == '-') {
|
||||
negative = true;
|
||||
if (negative) {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
// Recognize hex, octal and bin.
|
||||
if ((ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9')
|
||||
&& (maxlen == 0 || maxlen > 1)) {
|
||||
pre = ptr[1];
|
||||
|
||||
if ((what & STR2NR_HEX)
|
||||
&& ((pre == 'X') || (pre == 'x'))
|
||||
&& ascii_isxdigit(ptr[2])
|
||||
&& (maxlen == 0 || maxlen > 2)) {
|
||||
// hexadecimal
|
||||
ptr += 2;
|
||||
} else if ((what & STR2NR_BIN)
|
||||
&& ((pre == 'B') || (pre == 'b'))
|
||||
&& ascii_isbdigit(ptr[2])
|
||||
&& (maxlen == 0 || maxlen > 2)) {
|
||||
// binary
|
||||
ptr += 2;
|
||||
} else {
|
||||
// decimal or octal, default is decimal
|
||||
pre = 0;
|
||||
|
||||
if (what & STR2NR_OCT) {
|
||||
// Don't interpret "0", "08" or "0129" as octal.
|
||||
for (int n = 1; ascii_isdigit(ptr[n]); ++n) {
|
||||
if (ptr[n] > '7') {
|
||||
// can't be octal
|
||||
pre = 0;
|
||||
break;
|
||||
}
|
||||
if (ptr[n] >= '0') {
|
||||
// assume octal
|
||||
pre = '0';
|
||||
}
|
||||
if (n == maxlen) {
|
||||
break;
|
||||
}
|
||||
if (what & STR2NR_FORCE) {
|
||||
// When forcing main consideration is skipping the prefix. Octal and decimal
|
||||
// numbers have no prefixes to skip. pre is not set.
|
||||
switch ((unsigned)what & (~(unsigned)STR2NR_FORCE)) {
|
||||
case STR2NR_HEX: {
|
||||
if (!STRING_ENDED(ptr + 2)
|
||||
&& ptr[0] == '0'
|
||||
&& (ptr[1] == 'x' || ptr[1] == 'X')
|
||||
&& ascii_isxdigit(ptr[2])) {
|
||||
ptr += 2;
|
||||
}
|
||||
goto vim_str2nr_hex;
|
||||
}
|
||||
case STR2NR_BIN: {
|
||||
if (!STRING_ENDED(ptr + 2)
|
||||
&& ptr[0] == '0'
|
||||
&& (ptr[1] == 'b' || ptr[1] == 'B')
|
||||
&& ascii_isbdigit(ptr[2])) {
|
||||
ptr += 2;
|
||||
}
|
||||
goto vim_str2nr_bin;
|
||||
}
|
||||
case STR2NR_OCT: {
|
||||
goto vim_str2nr_oct;
|
||||
}
|
||||
case 0: {
|
||||
goto vim_str2nr_dec;
|
||||
}
|
||||
default: {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
} else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN))
|
||||
&& !STRING_ENDED(ptr + 1)
|
||||
&& ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') {
|
||||
pre = ptr[1];
|
||||
// Detect hexadecimal: 0x or 0X follwed by hex digit
|
||||
if ((what & STR2NR_HEX)
|
||||
&& !STRING_ENDED(ptr + 2)
|
||||
&& (pre == 'X' || pre == 'x')
|
||||
&& ascii_isxdigit(ptr[2])) {
|
||||
ptr += 2;
|
||||
goto vim_str2nr_hex;
|
||||
}
|
||||
// Detect binary: 0b or 0B follwed by 0 or 1
|
||||
if ((what & STR2NR_BIN)
|
||||
&& !STRING_ENDED(ptr + 2)
|
||||
&& (pre == 'B' || pre == 'b')
|
||||
&& ascii_isbdigit(ptr[2])) {
|
||||
ptr += 2;
|
||||
goto vim_str2nr_bin;
|
||||
}
|
||||
// Detect octal number: zero followed by octal digits without '8' or '9'
|
||||
pre = 0;
|
||||
if (!(what & STR2NR_OCT)
|
||||
|| !('0' <= ptr[1] && ptr[1] <= '7')) {
|
||||
goto vim_str2nr_dec;
|
||||
}
|
||||
for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) {
|
||||
if (ptr[i] > '7') {
|
||||
goto vim_str2nr_dec;
|
||||
}
|
||||
}
|
||||
pre = '0';
|
||||
goto vim_str2nr_oct;
|
||||
} else {
|
||||
goto vim_str2nr_dec;
|
||||
}
|
||||
|
||||
// Do the string-to-numeric conversion "manually" to avoid sscanf quirks.
|
||||
int n = 1;
|
||||
if ((pre == 'B') || (pre == 'b') || what == STR2NR_BIN + STR2NR_FORCE) {
|
||||
// bin
|
||||
if (pre != 0) {
|
||||
n += 2; // skip over "0b"
|
||||
assert(false); // Should’ve used goto earlier.
|
||||
#define PARSE_NUMBER(base, cond, conv) \
|
||||
do { \
|
||||
while (!STRING_ENDED(ptr) && (cond)) { \
|
||||
/* avoid ubsan error for overflow */ \
|
||||
if (un < UVARNUMBER_MAX / base) { \
|
||||
un = base * un + (uvarnumber_T)(conv); \
|
||||
} else { \
|
||||
un = UVARNUMBER_MAX; \
|
||||
} \
|
||||
ptr++; \
|
||||
} \
|
||||
} while (0)
|
||||
switch (pre) {
|
||||
case 'b':
|
||||
case 'B': {
|
||||
vim_str2nr_bin:
|
||||
PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0'));
|
||||
break;
|
||||
}
|
||||
while ('0' <= *ptr && *ptr <= '1') {
|
||||
// avoid ubsan error for overflow
|
||||
if (un < UVARNUMBER_MAX / 2) {
|
||||
un = 2 * un + (uvarnumber_T)(*ptr - '0');
|
||||
} else {
|
||||
un = UVARNUMBER_MAX;
|
||||
}
|
||||
ptr++;
|
||||
if (n++ == maxlen) {
|
||||
break;
|
||||
}
|
||||
case '0': {
|
||||
vim_str2nr_oct:
|
||||
PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0'));
|
||||
break;
|
||||
}
|
||||
} else if ((pre == '0') || what == STR2NR_OCT + STR2NR_FORCE) {
|
||||
// octal
|
||||
while ('0' <= *ptr && *ptr <= '7') {
|
||||
// avoid ubsan error for overflow
|
||||
if (un < UVARNUMBER_MAX / 8) {
|
||||
un = 8 * un + (uvarnumber_T)(*ptr - '0');
|
||||
} else {
|
||||
un = UVARNUMBER_MAX;
|
||||
}
|
||||
ptr++;
|
||||
if (n++ == maxlen) {
|
||||
break;
|
||||
}
|
||||
case 0: {
|
||||
vim_str2nr_dec:
|
||||
PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0'));
|
||||
break;
|
||||
}
|
||||
} else if ((pre == 'X') || (pre == 'x')
|
||||
|| what == STR2NR_HEX + STR2NR_FORCE) {
|
||||
// hex
|
||||
if (pre != 0) {
|
||||
n += 2; // skip over "0x"
|
||||
}
|
||||
while (ascii_isxdigit(*ptr)) {
|
||||
// avoid ubsan error for overflow
|
||||
if (un < UVARNUMBER_MAX / 16) {
|
||||
un = 16 * un + (uvarnumber_T)hex2nr(*ptr);
|
||||
} else {
|
||||
un = UVARNUMBER_MAX;
|
||||
}
|
||||
ptr++;
|
||||
if (n++ == maxlen) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// decimal
|
||||
while (ascii_isdigit(*ptr)) {
|
||||
// avoid ubsan error for overflow
|
||||
if (un < UVARNUMBER_MAX / 10) {
|
||||
un = 10 * un + (uvarnumber_T)(*ptr - '0');
|
||||
} else {
|
||||
un = UVARNUMBER_MAX;
|
||||
}
|
||||
ptr++;
|
||||
if (n++ == maxlen) {
|
||||
break;
|
||||
}
|
||||
case 'x':
|
||||
case 'X': {
|
||||
vim_str2nr_hex:
|
||||
PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
#undef PARSE_NUMBER
|
||||
|
||||
if (prep != NULL) {
|
||||
*prep = pre;
|
||||
}
|
||||
|
||||
if (len != NULL) {
|
||||
*len = (int)(ptr - start);
|
||||
*len = (int)(ptr - (const char *)start);
|
||||
}
|
||||
|
||||
if (nptr != NULL) {
|
||||
|
@ -1767,6 +1771,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len,
|
|||
if (unptr != NULL) {
|
||||
*unptr = un;
|
||||
}
|
||||
#undef STRING_ENDED
|
||||
}
|
||||
|
||||
/// Return the value of a single hex character.
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "nvim/types.h"
|
||||
#include "nvim/pos.h"
|
||||
#include "nvim/buffer_defs.h"
|
||||
#include "nvim/eval/typval.h"
|
||||
|
||||
/// Return the folded-case equivalent of the given character
|
||||
///
|
||||
|
@ -15,6 +16,21 @@
|
|||
?((int)(uint8_t)(c)) \
|
||||
:((int)(c)))
|
||||
|
||||
/// Flags for vim_str2nr()
|
||||
typedef enum {
|
||||
STR2NR_DEC = 0,
|
||||
STR2NR_BIN = (1 << 0), ///< Allow binary numbers.
|
||||
STR2NR_OCT = (1 << 1), ///< Allow octal numbers.
|
||||
STR2NR_HEX = (1 << 2), ///< Allow hexadecimal numbers.
|
||||
/// Force one of the above variants.
|
||||
///
|
||||
/// STR2NR_FORCE|STR2NR_DEC is actually not different from supplying zero
|
||||
/// as flags, but still present for completeness.
|
||||
STR2NR_FORCE = (1 << 3),
|
||||
/// Recognize all formats vim_str2nr() can recognize.
|
||||
STR2NR_ALL = STR2NR_BIN | STR2NR_OCT | STR2NR_HEX,
|
||||
} ChStr2NrFlags;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "charset.h.generated.h"
|
||||
#endif
|
||||
|
|
|
@ -6066,27 +6066,30 @@ void free_last_insert(void)
|
|||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Add character "c" to buffer "s". Escape the special meaning of K_SPECIAL
|
||||
* and CSI. Handle multi-byte characters.
|
||||
* Returns a pointer to after the added bytes.
|
||||
*/
|
||||
/// Add character "c" to buffer "s"
|
||||
///
|
||||
/// Escapes the special meaning of K_SPECIAL and CSI, handles multi-byte
|
||||
/// characters.
|
||||
///
|
||||
/// @param[in] c Character to add.
|
||||
/// @param[out] s Buffer to add to. Must have at least MB_MAXBYTES + 1 bytes.
|
||||
///
|
||||
/// @return Pointer to after the added bytes.
|
||||
char_u *add_char2buf(int c, char_u *s)
|
||||
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
char_u temp[MB_MAXBYTES + 1];
|
||||
int i;
|
||||
int len;
|
||||
|
||||
len = (*mb_char2bytes)(c, temp);
|
||||
for (i = 0; i < len; ++i) {
|
||||
const int len = utf_char2bytes(c, temp);
|
||||
for (int i = 0; i < len; i++) {
|
||||
c = temp[i];
|
||||
/* Need to escape K_SPECIAL and CSI like in the typeahead buffer. */
|
||||
// Need to escape K_SPECIAL and CSI like in the typeahead buffer.
|
||||
if (c == K_SPECIAL) {
|
||||
*s++ = K_SPECIAL;
|
||||
*s++ = KS_SPECIAL;
|
||||
*s++ = KE_FILLER;
|
||||
} else
|
||||
} else {
|
||||
*s++ = c;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
|
|
@ -5925,9 +5925,10 @@ static void ex_colorscheme(exarg_T *eap)
|
|||
|
||||
static void ex_highlight(exarg_T *eap)
|
||||
{
|
||||
if (*eap->arg == NUL && eap->cmd[2] == '!')
|
||||
if (*eap->arg == NUL && eap->cmd[2] == '!') {
|
||||
MSG(_("Greetings, Vim user!"));
|
||||
do_highlight(eap->arg, eap->forceit, FALSE);
|
||||
}
|
||||
do_highlight((const char *)eap->arg, eap->forceit, false);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -66,6 +66,8 @@
|
|||
#include "nvim/lib/kvec.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/highlight_defs.h"
|
||||
#include "nvim/viml/parser/parser.h"
|
||||
#include "nvim/viml/parser/expressions.h"
|
||||
|
||||
/// Command-line colors: one chunk
|
||||
///
|
||||
|
@ -2428,6 +2430,63 @@ void free_cmdline_buf(void)
|
|||
|
||||
enum { MAX_CB_ERRORS = 1 };
|
||||
|
||||
/// Color expression cmdline using built-in expressions parser
|
||||
///
|
||||
/// @param[in] colored_ccline Command-line to color.
|
||||
/// @param[out] ret_ccline_colors What should be colored.
|
||||
///
|
||||
/// Always colors the whole cmdline.
|
||||
static void color_expr_cmdline(const CmdlineInfo *const colored_ccline,
|
||||
ColoredCmdline *const ret_ccline_colors)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
ParserLine plines[] = {
|
||||
{
|
||||
.data = (const char *)colored_ccline->cmdbuff,
|
||||
.size = STRLEN(colored_ccline->cmdbuff),
|
||||
.allocated = false,
|
||||
},
|
||||
{ NULL, 0, false },
|
||||
};
|
||||
ParserLine *plines_p = plines;
|
||||
ParserHighlight colors;
|
||||
kvi_init(colors);
|
||||
ParserState pstate;
|
||||
viml_parser_init(
|
||||
&pstate, parser_simple_get_line, &plines_p, &colors);
|
||||
ExprAST east = viml_pexpr_parse(&pstate, kExprFlagsDisallowEOC);
|
||||
viml_pexpr_free_ast(east);
|
||||
viml_parser_destroy(&pstate);
|
||||
kv_resize(ret_ccline_colors->colors, kv_size(colors));
|
||||
size_t prev_end = 0;
|
||||
for (size_t i = 0 ; i < kv_size(colors) ; i++) {
|
||||
const ParserHighlightChunk chunk = kv_A(colors, i);
|
||||
if (chunk.start.col != prev_end) {
|
||||
kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
|
||||
.start = prev_end,
|
||||
.end = chunk.start.col,
|
||||
.attr = 0,
|
||||
}));
|
||||
}
|
||||
const int id = syn_name2id((const char_u *)chunk.group);
|
||||
const int attr = (id == 0 ? 0 : syn_id2attr(id));
|
||||
kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
|
||||
.start = chunk.start.col,
|
||||
.end = chunk.end_col,
|
||||
.attr = attr,
|
||||
}));
|
||||
prev_end = chunk.end_col;
|
||||
}
|
||||
if (prev_end < (size_t)colored_ccline->cmdlen) {
|
||||
kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
|
||||
.start = prev_end,
|
||||
.end = (size_t)colored_ccline->cmdlen,
|
||||
.attr = 0,
|
||||
}));
|
||||
}
|
||||
kvi_destroy(colors);
|
||||
}
|
||||
|
||||
/// Color command-line
|
||||
///
|
||||
/// Should use built-in command parser or user-specified one. Currently only the
|
||||
|
@ -2510,13 +2569,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline)
|
|||
tl_ret = try_leave(&tstate, &err);
|
||||
can_free_cb = true;
|
||||
} else if (colored_ccline->cmdfirstc == '=') {
|
||||
try_enter(&tstate);
|
||||
err_errmsg = N_(
|
||||
"E5409: Unable to get g:Nvim_color_expr callback: %s");
|
||||
dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_expr"),
|
||||
&color_cb);
|
||||
tl_ret = try_leave(&tstate, &err);
|
||||
can_free_cb = true;
|
||||
color_expr_cmdline(colored_ccline, ccline_colors);
|
||||
}
|
||||
if (!tl_ret || !dgc_ret) {
|
||||
goto color_cmdline_error;
|
||||
|
|
|
@ -164,9 +164,40 @@ local pattern = concat(
|
|||
)
|
||||
|
||||
if fname == '--help' then
|
||||
print'Usage:'
|
||||
print()
|
||||
print' gendeclarations.lua definitions.c static.h non-static.h preprocessor.i'
|
||||
print([[
|
||||
Usage:
|
||||
|
||||
gendeclarations.lua definitions.c static.h non-static.h definitions.i
|
||||
|
||||
Generates declarations for a C file definitions.c, putting declarations for
|
||||
static functions into static.h and declarations for non-static functions into
|
||||
non-static.h. File `definitions.i' should contain an already preprocessed
|
||||
version of definitions.c and it is the only one which is actually parsed,
|
||||
definitions.c is needed only to determine functions from which file out of all
|
||||
functions found in definitions.i are needed.
|
||||
|
||||
Additionally uses the following environment variables:
|
||||
|
||||
NVIM_GEN_DECLARATIONS_LINE_NUMBERS:
|
||||
If set to 1 then all generated declarations receive a comment with file
|
||||
name and line number after the declaration. This may be useful for
|
||||
debugging gen_declarations script, but not much beyond that with
|
||||
configured development environment (i.e. with ctags/cscope/finding
|
||||
definitions with clang/etc).
|
||||
|
||||
WARNING: setting this to 1 will cause extensive rebuilds: declarations
|
||||
generator script will not regenerate non-static.h file if its
|
||||
contents did not change, but including line numbers will make
|
||||
contents actually change.
|
||||
|
||||
With contents changed timestamp of the file is regenerated even
|
||||
when no real changes were made (e.g. a few lines were added to
|
||||
a function which is not at the bottom of the file).
|
||||
|
||||
With changed timestamp build system will assume that header
|
||||
changed, triggering rebuilds of all C files which depend on the
|
||||
"changed" header.
|
||||
]])
|
||||
os.exit()
|
||||
end
|
||||
|
||||
|
@ -249,8 +280,10 @@ while init ~= nil do
|
|||
declaration = declaration:gsub(' $', '')
|
||||
declaration = declaration:gsub('^ ', '')
|
||||
declaration = declaration .. ';'
|
||||
declaration = declaration .. (' // %s/%s:%u'):format(
|
||||
curdir, curfile, declline)
|
||||
if os.getenv('NVIM_GEN_DECLARATIONS_LINE_NUMBERS') == '1' then
|
||||
declaration = declaration .. (' // %s/%s:%u'):format(
|
||||
curdir, curfile, declline)
|
||||
end
|
||||
declaration = declaration .. '\n'
|
||||
if declaration:sub(1, 6) == 'static' then
|
||||
static = static .. declaration
|
||||
|
|
|
@ -729,29 +729,6 @@ EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */
|
|||
/// Encoding used when 'fencs' is set to "default"
|
||||
EXTERN char_u *fenc_default INIT(= NULL);
|
||||
|
||||
// To speed up BYTELEN(); keep a lookup table to quickly get the length in
|
||||
// bytes of a UTF-8 character from the first byte of a UTF-8 string. Bytes
|
||||
// which are illegal when used as the first byte have a 1. The NUL byte has
|
||||
// length 1.
|
||||
EXTERN char utf8len_tab[256] INIT(= {
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1,
|
||||
});
|
||||
|
||||
# if defined(USE_ICONV) && defined(DYNAMIC_ICONV)
|
||||
/* Pointers to functions and variables to be loaded at runtime */
|
||||
EXTERN size_t (*iconv)(iconv_t cd, const char **inbuf, size_t *inbytesleft,
|
||||
|
|
|
@ -24,23 +24,22 @@
|
|||
* Some useful tables.
|
||||
*/
|
||||
|
||||
static struct modmasktable {
|
||||
short mod_mask; /* Bit-mask for particular key modifier */
|
||||
short mod_flag; /* Bit(s) for particular key modifier */
|
||||
char_u name; /* Single letter name of modifier */
|
||||
} mod_mask_table[] =
|
||||
{
|
||||
{MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M'},
|
||||
{MOD_MASK_META, MOD_MASK_META, (char_u)'T'},
|
||||
{MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C'},
|
||||
{MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S'},
|
||||
{MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2'},
|
||||
{MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3'},
|
||||
{MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4'},
|
||||
{MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D'},
|
||||
static const struct modmasktable {
|
||||
uint16_t mod_mask; ///< Bit-mask for particular key modifier.
|
||||
uint16_t mod_flag; ///< Bit(s) for particular key modifier.
|
||||
char_u name; ///< Single letter name of modifier.
|
||||
} mod_mask_table[] = {
|
||||
{ MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M' },
|
||||
{ MOD_MASK_META, MOD_MASK_META, (char_u)'T' },
|
||||
{ MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C' },
|
||||
{ MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S' },
|
||||
{ MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2' },
|
||||
{ MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3' },
|
||||
{ MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4' },
|
||||
{ MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D' },
|
||||
// 'A' must be the last one
|
||||
{MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A'},
|
||||
{0, 0, NUL}
|
||||
{ MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A' },
|
||||
{ 0, 0, NUL }
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -139,11 +138,10 @@ static char_u modifier_keys_table[] =
|
|||
NUL
|
||||
};
|
||||
|
||||
static struct key_name_entry {
|
||||
static const struct key_name_entry {
|
||||
int key; // Special key code or ascii value
|
||||
char *name; // Name of key
|
||||
} key_names_table[] =
|
||||
{
|
||||
const char *name; // Name of key
|
||||
} key_names_table[] = {
|
||||
{ ' ', "Space" },
|
||||
{ TAB, "Tab" },
|
||||
{ K_TAB, "Tab" },
|
||||
|
@ -318,73 +316,73 @@ static struct mousetable {
|
|||
{0, 0, 0, 0},
|
||||
};
|
||||
|
||||
/*
|
||||
* Return the modifier mask bit (MOD_MASK_*) which corresponds to the given
|
||||
* modifier name ('S' for Shift, 'C' for Ctrl etc).
|
||||
*/
|
||||
/// Return the modifier mask bit (#MOD_MASK_*) corresponding to mod name
|
||||
///
|
||||
/// E.g. 'S' for shift, 'C' for ctrl.
|
||||
int name_to_mod_mask(int c)
|
||||
FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
int i;
|
||||
|
||||
c = TOUPPER_ASC(c);
|
||||
for (i = 0; mod_mask_table[i].mod_mask != 0; i++)
|
||||
if (c == mod_mask_table[i].name)
|
||||
for (size_t i = 0; mod_mask_table[i].mod_mask != 0; i++) {
|
||||
if (c == mod_mask_table[i].name) {
|
||||
return mod_mask_table[i].mod_flag;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if if there is a special key code for "key" that includes the
|
||||
* modifiers specified.
|
||||
*/
|
||||
int simplify_key(int key, int *modifiers)
|
||||
/// Check if there is a special key code for "key" with specified modifiers
|
||||
///
|
||||
/// @param[in] key Initial key code.
|
||||
/// @param[in,out] modifiers Initial modifiers, is adjusted to have simplified
|
||||
/// modifiers.
|
||||
///
|
||||
/// @return Simplified key code.
|
||||
int simplify_key(const int key, int *modifiers)
|
||||
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
int i;
|
||||
int key0;
|
||||
int key1;
|
||||
|
||||
if (*modifiers & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT)) {
|
||||
/* TAB is a special case */
|
||||
// TAB is a special case.
|
||||
if (key == TAB && (*modifiers & MOD_MASK_SHIFT)) {
|
||||
*modifiers &= ~MOD_MASK_SHIFT;
|
||||
return K_S_TAB;
|
||||
}
|
||||
key0 = KEY2TERMCAP0(key);
|
||||
key1 = KEY2TERMCAP1(key);
|
||||
for (i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE)
|
||||
const int key0 = KEY2TERMCAP0(key);
|
||||
const int key1 = KEY2TERMCAP1(key);
|
||||
for (int i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) {
|
||||
if (key0 == modifier_keys_table[i + 3]
|
||||
&& key1 == modifier_keys_table[i + 4]
|
||||
&& (*modifiers & modifier_keys_table[i])) {
|
||||
*modifiers &= ~modifier_keys_table[i];
|
||||
return TERMCAP2KEY(modifier_keys_table[i + 1],
|
||||
modifier_keys_table[i + 2]);
|
||||
modifier_keys_table[i + 2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
/*
|
||||
* Change <xHome> to <Home>, <xUp> to <Up>, etc.
|
||||
*/
|
||||
int handle_x_keys(int key)
|
||||
/// Change <xKey> to <Key>
|
||||
int handle_x_keys(const int key)
|
||||
FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
switch (key) {
|
||||
case K_XUP: return K_UP;
|
||||
case K_XDOWN: return K_DOWN;
|
||||
case K_XLEFT: return K_LEFT;
|
||||
case K_XRIGHT: return K_RIGHT;
|
||||
case K_XHOME: return K_HOME;
|
||||
case K_ZHOME: return K_HOME;
|
||||
case K_XEND: return K_END;
|
||||
case K_ZEND: return K_END;
|
||||
case K_XF1: return K_F1;
|
||||
case K_XF2: return K_F2;
|
||||
case K_XF3: return K_F3;
|
||||
case K_XF4: return K_F4;
|
||||
case K_S_XF1: return K_S_F1;
|
||||
case K_S_XF2: return K_S_F2;
|
||||
case K_S_XF3: return K_S_F3;
|
||||
case K_S_XF4: return K_S_F4;
|
||||
case K_XUP: return K_UP;
|
||||
case K_XDOWN: return K_DOWN;
|
||||
case K_XLEFT: return K_LEFT;
|
||||
case K_XRIGHT: return K_RIGHT;
|
||||
case K_XHOME: return K_HOME;
|
||||
case K_ZHOME: return K_HOME;
|
||||
case K_XEND: return K_END;
|
||||
case K_ZEND: return K_END;
|
||||
case K_XF1: return K_F1;
|
||||
case K_XF2: return K_F2;
|
||||
case K_XF3: return K_F3;
|
||||
case K_XF4: return K_F4;
|
||||
case K_S_XF1: return K_S_F1;
|
||||
case K_S_XF2: return K_S_F2;
|
||||
case K_S_XF3: return K_S_F3;
|
||||
case K_S_XF4: return K_S_F4;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
@ -508,7 +506,7 @@ unsigned int trans_special(const char_u **srcp, const size_t src_len,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Put the appropriate modifier in a string */
|
||||
// Put the appropriate modifier in a string.
|
||||
if (modifiers != 0) {
|
||||
dst[dlen++] = K_SPECIAL;
|
||||
dst[dlen++] = KS_MODIFIER;
|
||||
|
@ -569,15 +567,11 @@ int find_special_key(const char_u **srcp, const size_t src_len, int *const modp,
|
|||
|
||||
// Find end of modifier list
|
||||
last_dash = src;
|
||||
for (bp = src + 1; bp <= end && (*bp == '-' || vim_isIDc(*bp)); bp++) {
|
||||
for (bp = src + 1; bp <= end && (*bp == '-' || ascii_isident(*bp)); bp++) {
|
||||
if (*bp == '-') {
|
||||
last_dash = bp;
|
||||
if (bp + 1 <= end) {
|
||||
if (has_mbyte) {
|
||||
l = mb_ptr2len_len(bp + 1, (int) (end - bp) + 1);
|
||||
} else {
|
||||
l = 1;
|
||||
}
|
||||
l = utfc_ptr2len_len(bp + 1, (int)(end - bp) + 1);
|
||||
// Anything accepted, like <C-?>.
|
||||
// <C-"> or <M-"> are not special in strings as " is
|
||||
// the string delimiter. With a backslash it works: <M-\">
|
||||
|
@ -702,33 +696,39 @@ int find_special_key_in_table(int c)
|
|||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; key_names_table[i].name != NULL; i++)
|
||||
if (c == key_names_table[i].key)
|
||||
for (i = 0; key_names_table[i].name != NULL; i++) {
|
||||
if (c == key_names_table[i].key) {
|
||||
break;
|
||||
if (key_names_table[i].name == NULL)
|
||||
}
|
||||
}
|
||||
if (key_names_table[i].name == NULL) {
|
||||
i = -1;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the special key with the given name (the given string does not have to
|
||||
* end with NUL, the name is assumed to end before the first non-idchar).
|
||||
* If the name starts with "t_" the next two characters are interpreted as a
|
||||
* termcap name.
|
||||
* Return the key code, or 0 if not found.
|
||||
*/
|
||||
/// Find the special key with the given name
|
||||
///
|
||||
/// @param[in] name Name of the special. Does not have to end with NUL, it is
|
||||
/// assumed to end before the first non-idchar. If name starts
|
||||
/// with "t_" the next two characters are interpreted as
|
||||
/// a termcap name.
|
||||
///
|
||||
/// @return Key code or 0 if not found.
|
||||
int get_special_key_code(const char_u *name)
|
||||
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
char *table_name;
|
||||
int i, j;
|
||||
|
||||
for (i = 0; key_names_table[i].name != NULL; i++) {
|
||||
table_name = key_names_table[i].name;
|
||||
for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++)
|
||||
if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j]))
|
||||
for (int i = 0; key_names_table[i].name != NULL; i++) {
|
||||
const char *const table_name = key_names_table[i].name;
|
||||
int j;
|
||||
for (j = 0; ascii_isident(name[j]) && table_name[j] != NUL; j++) {
|
||||
if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) {
|
||||
break;
|
||||
if (!vim_isIDc(name[j]) && table_name[j] == NUL)
|
||||
}
|
||||
}
|
||||
if (!ascii_isident(name[j]) && table_name[j] == NUL) {
|
||||
return key_names_table[i].key;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -62,7 +62,16 @@
|
|||
#define kv_pop(v) ((v).items[--(v).size])
|
||||
#define kv_size(v) ((v).size)
|
||||
#define kv_max(v) ((v).capacity)
|
||||
#define kv_last(v) kv_A(v, kv_size(v) - 1)
|
||||
#define kv_Z(v, i) kv_A(v, kv_size(v) - (i) - 1)
|
||||
#define kv_last(v) kv_Z(v, 0)
|
||||
|
||||
/// Drop last n items from kvec without resizing
|
||||
///
|
||||
/// Previously spelled as `(void)kv_pop(v)`, repeated n times.
|
||||
///
|
||||
/// @param[out] v Kvec to drop items from.
|
||||
/// @param[in] n Number of elements to drop.
|
||||
#define kv_drop(v, n) ((v).size -= (n))
|
||||
|
||||
#define kv_resize(v, s) \
|
||||
((v).capacity = (s), \
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#ifndef NVIM_LIB_RINGBUF_H
|
||||
#define NVIM_LIB_RINGBUF_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
@ -73,6 +74,32 @@ typedef struct { \
|
|||
RBType *buf_end; \
|
||||
} TypeName##RingBuffer;
|
||||
|
||||
/// Dummy item free macros, for use in RINGBUF_INIT
|
||||
///
|
||||
/// This macros actually does nothing.
|
||||
///
|
||||
/// @param[in] item Item to be freed.
|
||||
#define RINGBUF_DUMMY_FREE(item)
|
||||
|
||||
/// Static ring buffer
|
||||
///
|
||||
/// @warning Ring buffers created with this macros must neither be freed nor
|
||||
/// deallocated.
|
||||
///
|
||||
/// @param scope Ring buffer scope.
|
||||
/// @param TypeName Ring buffer type name.
|
||||
/// @param RBType Type of the single ring buffer element.
|
||||
/// @param varname Variable name.
|
||||
/// @param rbsize Ring buffer size.
|
||||
#define RINGBUF_STATIC(scope, TypeName, RBType, varname, rbsize) \
|
||||
static RBType _##varname##_buf[rbsize]; \
|
||||
scope TypeName##RingBuffer varname = { \
|
||||
.buf = _##varname##_buf, \
|
||||
.next = _##varname##_buf, \
|
||||
.first = NULL, \
|
||||
.buf_end = _##varname##_buf + rbsize - 1, \
|
||||
};
|
||||
|
||||
/// Initialize a new ring buffer
|
||||
///
|
||||
/// @param TypeName Ring buffer type name. Actual type name will be
|
||||
|
|
260
src/nvim/mbyte.c
260
src/nvim/mbyte.c
|
@ -72,19 +72,49 @@ struct interval {
|
|||
# include "unicode_tables.generated.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Like utf8len_tab above, but using a zero for illegal lead bytes.
|
||||
*/
|
||||
const uint8_t utf8len_tab_zero[256] =
|
||||
{
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
|
||||
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0,
|
||||
// To speed up BYTELEN(); keep a lookup table to quickly get the length in
|
||||
// bytes of a UTF-8 character from the first byte of a UTF-8 string. Bytes
|
||||
// which are illegal when used as the first byte have a 1. The NUL byte has
|
||||
// length 1.
|
||||
const uint8_t utf8len_tab[] = {
|
||||
// ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B?
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C?
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D?
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E?
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1, // F?
|
||||
};
|
||||
|
||||
// Like utf8len_tab above, but using a zero for illegal lead bytes.
|
||||
const uint8_t utf8len_tab_zero[] = {
|
||||
// ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7?
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8?
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9?
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A?
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B?
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C?
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D?
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E?
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 0, 0, // F?
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -528,45 +558,52 @@ int utf_off2cells(unsigned off, unsigned max_off)
|
|||
return (off + 1 < max_off && ScreenLines[off + 1] == 0) ? 2 : 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a UTF-8 byte sequence to a wide character.
|
||||
* If the sequence is illegal or truncated by a NUL the first byte is
|
||||
* returned.
|
||||
* Does not include composing characters, of course.
|
||||
*/
|
||||
int utf_ptr2char(const char_u *p)
|
||||
/// Convert a UTF-8 byte sequence to a wide character
|
||||
///
|
||||
/// If the sequence is illegal or truncated by a NUL then the first byte is
|
||||
/// returned. Does not include composing characters for obvious reasons.
|
||||
///
|
||||
/// @param[in] p String to convert.
|
||||
///
|
||||
/// @return Unicode codepoint or byte value.
|
||||
int utf_ptr2char(const char_u *const p)
|
||||
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
uint8_t len;
|
||||
|
||||
if (p[0] < 0x80) /* be quick for ASCII */
|
||||
if (p[0] < 0x80) { // Be quick for ASCII.
|
||||
return p[0];
|
||||
}
|
||||
|
||||
len = utf8len_tab_zero[p[0]];
|
||||
const uint8_t len = utf8len_tab_zero[p[0]];
|
||||
if (len > 1 && (p[1] & 0xc0) == 0x80) {
|
||||
if (len == 2)
|
||||
if (len == 2) {
|
||||
return ((p[0] & 0x1f) << 6) + (p[1] & 0x3f);
|
||||
}
|
||||
if ((p[2] & 0xc0) == 0x80) {
|
||||
if (len == 3)
|
||||
return ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6)
|
||||
+ (p[2] & 0x3f);
|
||||
if (len == 3) {
|
||||
return (((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6)
|
||||
+ (p[2] & 0x3f));
|
||||
}
|
||||
if ((p[3] & 0xc0) == 0x80) {
|
||||
if (len == 4)
|
||||
return ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12)
|
||||
+ ((p[2] & 0x3f) << 6) + (p[3] & 0x3f);
|
||||
if (len == 4) {
|
||||
return (((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12)
|
||||
+ ((p[2] & 0x3f) << 6) + (p[3] & 0x3f));
|
||||
}
|
||||
if ((p[4] & 0xc0) == 0x80) {
|
||||
if (len == 5)
|
||||
return ((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18)
|
||||
+ ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6)
|
||||
+ (p[4] & 0x3f);
|
||||
if ((p[5] & 0xc0) == 0x80 && len == 6)
|
||||
return ((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24)
|
||||
+ ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12)
|
||||
+ ((p[4] & 0x3f) << 6) + (p[5] & 0x3f);
|
||||
if (len == 5) {
|
||||
return (((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18)
|
||||
+ ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6)
|
||||
+ (p[4] & 0x3f));
|
||||
}
|
||||
if ((p[5] & 0xc0) == 0x80 && len == 6) {
|
||||
return (((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24)
|
||||
+ ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12)
|
||||
+ ((p[4] & 0x3f) << 6) + (p[5] & 0x3f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Illegal value, just return the first byte */
|
||||
// Illegal value: just return the first byte.
|
||||
return p[0];
|
||||
}
|
||||
|
||||
|
@ -767,23 +804,24 @@ int utfc_char2bytes(int off, char_u *buf)
|
|||
return len;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the length of a UTF-8 byte sequence, not including any following
|
||||
* composing characters.
|
||||
* Returns 0 for "".
|
||||
* Returns 1 for an illegal byte sequence.
|
||||
*/
|
||||
int utf_ptr2len(const char_u *p)
|
||||
/// Get the length of a UTF-8 byte sequence representing a single codepoint
|
||||
///
|
||||
/// @param[in] p UTF-8 string.
|
||||
///
|
||||
/// @return Sequence length, 0 for empty string and 1 for non-UTF-8 byte
|
||||
/// sequence.
|
||||
int utf_ptr2len(const char_u *const p)
|
||||
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
int len;
|
||||
int i;
|
||||
|
||||
if (*p == NUL)
|
||||
if (*p == NUL) {
|
||||
return 0;
|
||||
len = utf8len_tab[*p];
|
||||
for (i = 1; i < len; ++i)
|
||||
if ((p[i] & 0xc0) != 0x80)
|
||||
}
|
||||
const int len = utf8len_tab[*p];
|
||||
for (int i = 1; i < len; i++) {
|
||||
if ((p[i] & 0xc0) != 0x80) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
|
@ -824,38 +862,38 @@ int utf_ptr2len_len(const char_u *p, int size)
|
|||
return len;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the number of bytes the UTF-8 encoding of the character at "p" takes.
|
||||
* This includes following composing characters.
|
||||
*/
|
||||
int utfc_ptr2len(const char_u *p)
|
||||
/// Return the number of bytes occupied by a UTF-8 character in a string
|
||||
///
|
||||
/// This includes following composing characters.
|
||||
int utfc_ptr2len(const char_u *const p)
|
||||
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
int len;
|
||||
int b0 = *p;
|
||||
int prevlen;
|
||||
uint8_t b0 = (uint8_t)(*p);
|
||||
|
||||
if (b0 == NUL)
|
||||
if (b0 == NUL) {
|
||||
return 0;
|
||||
if (b0 < 0x80 && p[1] < 0x80) /* be quick for ASCII */
|
||||
}
|
||||
if (b0 < 0x80 && p[1] < 0x80) { // be quick for ASCII
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Skip over first UTF-8 char, stopping at a NUL byte. */
|
||||
len = utf_ptr2len(p);
|
||||
// Skip over first UTF-8 char, stopping at a NUL byte.
|
||||
int len = utf_ptr2len(p);
|
||||
|
||||
/* Check for illegal byte. */
|
||||
if (len == 1 && b0 >= 0x80)
|
||||
// Check for illegal byte.
|
||||
if (len == 1 && b0 >= 0x80) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for composing characters. We can handle only the first six, but
|
||||
* skip all of them (otherwise the cursor would get stuck).
|
||||
*/
|
||||
prevlen = 0;
|
||||
for (;; ) {
|
||||
if (p[len] < 0x80 || !UTF_COMPOSINGLIKE(p + prevlen, p + len))
|
||||
// Check for composing characters. We can handle only the first six, but
|
||||
// skip all of them (otherwise the cursor would get stuck).
|
||||
int prevlen = 0;
|
||||
for (;;) {
|
||||
if (p[len] < 0x80 || !UTF_COMPOSINGLIKE(p + prevlen, p + len)) {
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Skip over composing char */
|
||||
// Skip over composing char.
|
||||
prevlen = len;
|
||||
len += utf_ptr2len(p + len);
|
||||
}
|
||||
|
@ -913,23 +951,22 @@ int utfc_ptr2len_len(const char_u *p, int size)
|
|||
return len;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the number of bytes the UTF-8 encoding of character "c" takes.
|
||||
* This does not include composing characters.
|
||||
*/
|
||||
int utf_char2len(int c)
|
||||
/// Determine how many bytes certain unicode codepoint will occupy
|
||||
int utf_char2len(const int c)
|
||||
{
|
||||
if (c < 0x80)
|
||||
if (c < 0x80) {
|
||||
return 1;
|
||||
if (c < 0x800)
|
||||
} else if (c < 0x800) {
|
||||
return 2;
|
||||
if (c < 0x10000)
|
||||
} else if (c < 0x10000) {
|
||||
return 3;
|
||||
if (c < 0x200000)
|
||||
} else if (c < 0x200000) {
|
||||
return 4;
|
||||
if (c < 0x4000000)
|
||||
} else if (c < 0x4000000) {
|
||||
return 5;
|
||||
return 6;
|
||||
} else {
|
||||
return 6;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert Unicode character to UTF-8 string
|
||||
|
@ -937,46 +974,42 @@ int utf_char2len(int c)
|
|||
/// @param c character to convert to \p buf
|
||||
/// @param[out] buf UTF-8 string generated from \p c, does not add \0
|
||||
/// @return Number of bytes (1-6). Does not include composing characters.
|
||||
int utf_char2bytes(int c, char_u *const buf)
|
||||
int utf_char2bytes(const int c, char_u *const buf)
|
||||
{
|
||||
if (c < 0x80) { /* 7 bits */
|
||||
if (c < 0x80) { // 7 bits
|
||||
buf[0] = c;
|
||||
return 1;
|
||||
}
|
||||
if (c < 0x800) { /* 11 bits */
|
||||
} else if (c < 0x800) { // 11 bits
|
||||
buf[0] = 0xc0 + ((unsigned)c >> 6);
|
||||
buf[1] = 0x80 + (c & 0x3f);
|
||||
return 2;
|
||||
}
|
||||
if (c < 0x10000) { /* 16 bits */
|
||||
} else if (c < 0x10000) { // 16 bits
|
||||
buf[0] = 0xe0 + ((unsigned)c >> 12);
|
||||
buf[1] = 0x80 + (((unsigned)c >> 6) & 0x3f);
|
||||
buf[2] = 0x80 + (c & 0x3f);
|
||||
return 3;
|
||||
}
|
||||
if (c < 0x200000) { /* 21 bits */
|
||||
} else if (c < 0x200000) { // 21 bits
|
||||
buf[0] = 0xf0 + ((unsigned)c >> 18);
|
||||
buf[1] = 0x80 + (((unsigned)c >> 12) & 0x3f);
|
||||
buf[2] = 0x80 + (((unsigned)c >> 6) & 0x3f);
|
||||
buf[3] = 0x80 + (c & 0x3f);
|
||||
return 4;
|
||||
}
|
||||
if (c < 0x4000000) { /* 26 bits */
|
||||
} else if (c < 0x4000000) { // 26 bits
|
||||
buf[0] = 0xf8 + ((unsigned)c >> 24);
|
||||
buf[1] = 0x80 + (((unsigned)c >> 18) & 0x3f);
|
||||
buf[2] = 0x80 + (((unsigned)c >> 12) & 0x3f);
|
||||
buf[3] = 0x80 + (((unsigned)c >> 6) & 0x3f);
|
||||
buf[4] = 0x80 + (c & 0x3f);
|
||||
return 5;
|
||||
} else { // 31 bits
|
||||
buf[0] = 0xfc + ((unsigned)c >> 30);
|
||||
buf[1] = 0x80 + (((unsigned)c >> 24) & 0x3f);
|
||||
buf[2] = 0x80 + (((unsigned)c >> 18) & 0x3f);
|
||||
buf[3] = 0x80 + (((unsigned)c >> 12) & 0x3f);
|
||||
buf[4] = 0x80 + (((unsigned)c >> 6) & 0x3f);
|
||||
buf[5] = 0x80 + (c & 0x3f);
|
||||
return 6;
|
||||
}
|
||||
/* 31 bits */
|
||||
buf[0] = 0xfc + ((unsigned)c >> 30);
|
||||
buf[1] = 0x80 + (((unsigned)c >> 24) & 0x3f);
|
||||
buf[2] = 0x80 + (((unsigned)c >> 18) & 0x3f);
|
||||
buf[3] = 0x80 + (((unsigned)c >> 12) & 0x3f);
|
||||
buf[4] = 0x80 + (((unsigned)c >> 6) & 0x3f);
|
||||
buf[5] = 0x80 + (c & 0x3f);
|
||||
return 6;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1513,14 +1546,15 @@ int utf_head_off(const char_u *base, const char_u *p)
|
|||
return (int)(p - q);
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy a character from "*fp" to "*tp" and advance the pointers.
|
||||
*/
|
||||
void mb_copy_char(const char_u **fp, char_u **tp)
|
||||
/// Copy a character, advancing the pointers
|
||||
///
|
||||
/// @param[in,out] fp Source of the character to copy.
|
||||
/// @param[in,out] tp Destination to copy to.
|
||||
void mb_copy_char(const char_u **const fp, char_u **const tp)
|
||||
{
|
||||
int l = (*mb_ptr2len)(*fp);
|
||||
const size_t l = (size_t)utfc_ptr2len(*fp);
|
||||
|
||||
memmove(*tp, *fp, (size_t)l);
|
||||
memmove(*tp, *fp, l);
|
||||
*tp += l;
|
||||
*fp += l;
|
||||
}
|
||||
|
@ -2262,9 +2296,7 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8,
|
|||
if (vcp->vc_type == CONV_ICONV && vcp->vc_fd != (iconv_t)-1)
|
||||
iconv_close(vcp->vc_fd);
|
||||
# endif
|
||||
vcp->vc_type = CONV_NONE;
|
||||
vcp->vc_factor = 1;
|
||||
vcp->vc_fail = false;
|
||||
*vcp = (vimconv_T)MBYTE_NONE_CONV;
|
||||
|
||||
/* No conversion when one of the names is empty or they are equal. */
|
||||
if (from == NULL || *from == NUL || to == NULL || *to == NUL
|
||||
|
|
|
@ -60,6 +60,12 @@ typedef enum {
|
|||
CONV_ICONV = 5,
|
||||
} ConvFlags;
|
||||
|
||||
#define MBYTE_NONE_CONV { \
|
||||
.vc_type = CONV_NONE, \
|
||||
.vc_factor = 1, \
|
||||
.vc_fail = false, \
|
||||
}
|
||||
|
||||
/// Structure used for string conversions
|
||||
typedef struct {
|
||||
int vc_type; ///< Zero or more ConvFlags.
|
||||
|
@ -73,6 +79,8 @@ typedef struct {
|
|||
|
||||
extern const uint8_t utf8len_tab_zero[256];
|
||||
|
||||
extern const uint8_t utf8len_tab[256];
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "mbyte.h.generated.h"
|
||||
#endif
|
||||
|
|
|
@ -5930,9 +5930,9 @@ static void syntime_report(void)
|
|||
//
|
||||
// When making changes here, also change runtime/colors/default.vim!
|
||||
|
||||
static char *highlight_init_both[] =
|
||||
{
|
||||
"Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey",
|
||||
static const char *highlight_init_both[] = {
|
||||
"Conceal "
|
||||
"ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey",
|
||||
"Cursor guibg=fg guifg=bg",
|
||||
"lCursor guibg=fg guifg=bg",
|
||||
"DiffText cterm=bold ctermbg=Red gui=bold guibg=Red",
|
||||
|
@ -5955,8 +5955,7 @@ static char *highlight_init_both[] =
|
|||
NULL
|
||||
};
|
||||
|
||||
static char *highlight_init_light[] =
|
||||
{
|
||||
static const char *highlight_init_light[] = {
|
||||
"ColorColumn ctermbg=LightRed guibg=LightRed",
|
||||
"CursorColumn ctermbg=LightGrey guibg=Grey90",
|
||||
"CursorLine cterm=underline guibg=Grey90",
|
||||
|
@ -5989,8 +5988,7 @@ static char *highlight_init_light[] =
|
|||
NULL
|
||||
};
|
||||
|
||||
static char *highlight_init_dark[] =
|
||||
{
|
||||
static const char *highlight_init_dark[] = {
|
||||
"ColorColumn ctermbg=DarkRed guibg=DarkRed",
|
||||
"CursorColumn ctermbg=DarkGrey guibg=Grey40",
|
||||
"CursorLine cterm=underline guibg=Grey40",
|
||||
|
@ -6023,18 +6021,223 @@ static char *highlight_init_dark[] =
|
|||
NULL
|
||||
};
|
||||
|
||||
const char *const highlight_init_cmdline[] = {
|
||||
// XXX When modifying a list modify it in both valid and invalid halfs.
|
||||
// TODO(ZyX-I): merge valid and invalid groups via a macros.
|
||||
|
||||
// NvimInternalError should appear only when highlighter has a bug.
|
||||
"NvimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red",
|
||||
|
||||
// Highlight groups (links) used by parser:
|
||||
|
||||
"default link NvimAssignment Operator",
|
||||
"default link NvimPlainAssignment NvimAssignment",
|
||||
"default link NvimAugmentedAssignment NvimAssignment",
|
||||
"default link NvimAssignmentWithAddition NvimAugmentedAssignment",
|
||||
"default link NvimAssignmentWithSubtraction NvimAugmentedAssignment",
|
||||
"default link NvimAssignmentWithConcatenation NvimAugmentedAssignment",
|
||||
|
||||
"default link NvimOperator Operator",
|
||||
|
||||
"default link NvimUnaryOperator NvimOperator",
|
||||
"default link NvimUnaryPlus NvimUnaryOperator",
|
||||
"default link NvimUnaryMinus NvimUnaryOperator",
|
||||
"default link NvimNot NvimUnaryOperator",
|
||||
|
||||
"default link NvimBinaryOperator NvimOperator",
|
||||
"default link NvimComparison NvimBinaryOperator",
|
||||
"default link NvimComparisonModifier NvimComparison",
|
||||
"default link NvimBinaryPlus NvimBinaryOperator",
|
||||
"default link NvimBinaryMinus NvimBinaryOperator",
|
||||
"default link NvimConcat NvimBinaryOperator",
|
||||
"default link NvimConcatOrSubscript NvimConcat",
|
||||
"default link NvimOr NvimBinaryOperator",
|
||||
"default link NvimAnd NvimBinaryOperator",
|
||||
"default link NvimMultiplication NvimBinaryOperator",
|
||||
"default link NvimDivision NvimBinaryOperator",
|
||||
"default link NvimMod NvimBinaryOperator",
|
||||
|
||||
"default link NvimTernary NvimOperator",
|
||||
"default link NvimTernaryColon NvimTernary",
|
||||
|
||||
"default link NvimParenthesis Delimiter",
|
||||
"default link NvimLambda NvimParenthesis",
|
||||
"default link NvimNestingParenthesis NvimParenthesis",
|
||||
"default link NvimCallingParenthesis NvimParenthesis",
|
||||
|
||||
"default link NvimSubscript NvimParenthesis",
|
||||
"default link NvimSubscriptBracket NvimSubscript",
|
||||
"default link NvimSubscriptColon NvimSubscript",
|
||||
"default link NvimCurly NvimSubscript",
|
||||
|
||||
"default link NvimContainer NvimParenthesis",
|
||||
"default link NvimDict NvimContainer",
|
||||
"default link NvimList NvimContainer",
|
||||
|
||||
"default link NvimIdentifier Identifier",
|
||||
"default link NvimIdentifierScope NvimIdentifier",
|
||||
"default link NvimIdentifierScopeDelimiter NvimIdentifier",
|
||||
"default link NvimIdentifierName NvimIdentifier",
|
||||
"default link NvimIdentifierKey NvimIdentifier",
|
||||
|
||||
"default link NvimColon Delimiter",
|
||||
"default link NvimComma Delimiter",
|
||||
"default link NvimArrow Delimiter",
|
||||
|
||||
"default link NvimRegister SpecialChar",
|
||||
"default link NvimNumber Number",
|
||||
"default link NvimFloat NvimNumber",
|
||||
"default link NvimNumberPrefix Type",
|
||||
|
||||
"default link NvimOptionSigil Type",
|
||||
"default link NvimOptionName NvimIdentifier",
|
||||
"default link NvimOptionScope NvimIdentifierScope",
|
||||
"default link NvimOptionScopeDelimiter NvimIdentifierScopeDelimiter",
|
||||
|
||||
"default link NvimEnvironmentSigil NvimOptionSigil",
|
||||
"default link NvimEnvironmentName NvimIdentifier",
|
||||
|
||||
"default link NvimString String",
|
||||
"default link NvimStringBody NvimString",
|
||||
"default link NvimStringQuote NvimString",
|
||||
"default link NvimStringSpecial SpecialChar",
|
||||
|
||||
"default link NvimSingleQuote NvimStringQuote",
|
||||
"default link NvimSingleQuotedBody NvimStringBody",
|
||||
"default link NvimSingleQuotedQuote NvimStringSpecial",
|
||||
|
||||
"default link NvimDoubleQuote NvimStringQuote",
|
||||
"default link NvimDoubleQuotedBody NvimStringBody",
|
||||
"default link NvimDoubleQuotedEscape NvimStringSpecial",
|
||||
|
||||
"default link NvimFigureBrace NvimInternalError",
|
||||
"default link NvimSingleQuotedUnknownEscape NvimInternalError",
|
||||
|
||||
"default link NvimSpacing Normal",
|
||||
|
||||
// NvimInvalid groups:
|
||||
|
||||
"default link NvimInvalidSingleQuotedUnknownEscape NvimInternalError",
|
||||
|
||||
"default link NvimInvalid Error",
|
||||
|
||||
"default link NvimInvalidAssignment NvimInvalid",
|
||||
"default link NvimInvalidPlainAssignment NvimInvalidAssignment",
|
||||
"default link NvimInvalidAugmentedAssignment NvimInvalidAssignment",
|
||||
"default link NvimInvalidAssignmentWithAddition "
|
||||
"NvimInvalidAugmentedAssignment",
|
||||
"default link NvimInvalidAssignmentWithSubtraction "
|
||||
"NvimInvalidAugmentedAssignment",
|
||||
"default link NvimInvalidAssignmentWithConcatenation "
|
||||
"NvimInvalidAugmentedAssignment",
|
||||
|
||||
"default link NvimInvalidOperator NvimInvalid",
|
||||
|
||||
"default link NvimInvalidUnaryOperator NvimInvalidOperator",
|
||||
"default link NvimInvalidUnaryPlus NvimInvalidUnaryOperator",
|
||||
"default link NvimInvalidUnaryMinus NvimInvalidUnaryOperator",
|
||||
"default link NvimInvalidNot NvimInvalidUnaryOperator",
|
||||
|
||||
"default link NvimInvalidBinaryOperator NvimInvalidOperator",
|
||||
"default link NvimInvalidComparison NvimInvalidBinaryOperator",
|
||||
"default link NvimInvalidComparisonModifier NvimInvalidComparison",
|
||||
"default link NvimInvalidBinaryPlus NvimInvalidBinaryOperator",
|
||||
"default link NvimInvalidBinaryMinus NvimInvalidBinaryOperator",
|
||||
"default link NvimInvalidConcat NvimInvalidBinaryOperator",
|
||||
"default link NvimInvalidConcatOrSubscript NvimInvalidConcat",
|
||||
"default link NvimInvalidOr NvimInvalidBinaryOperator",
|
||||
"default link NvimInvalidAnd NvimInvalidBinaryOperator",
|
||||
"default link NvimInvalidMultiplication NvimInvalidBinaryOperator",
|
||||
"default link NvimInvalidDivision NvimInvalidBinaryOperator",
|
||||
"default link NvimInvalidMod NvimInvalidBinaryOperator",
|
||||
|
||||
"default link NvimInvalidTernary NvimInvalidOperator",
|
||||
"default link NvimInvalidTernaryColon NvimInvalidTernary",
|
||||
|
||||
"default link NvimInvalidDelimiter NvimInvalid",
|
||||
|
||||
"default link NvimInvalidParenthesis NvimInvalidDelimiter",
|
||||
"default link NvimInvalidLambda NvimInvalidParenthesis",
|
||||
"default link NvimInvalidNestingParenthesis NvimInvalidParenthesis",
|
||||
"default link NvimInvalidCallingParenthesis NvimInvalidParenthesis",
|
||||
|
||||
"default link NvimInvalidSubscript NvimInvalidParenthesis",
|
||||
"default link NvimInvalidSubscriptBracket NvimInvalidSubscript",
|
||||
"default link NvimInvalidSubscriptColon NvimInvalidSubscript",
|
||||
"default link NvimInvalidCurly NvimInvalidSubscript",
|
||||
|
||||
"default link NvimInvalidContainer NvimInvalidParenthesis",
|
||||
"default link NvimInvalidDict NvimInvalidContainer",
|
||||
"default link NvimInvalidList NvimInvalidContainer",
|
||||
|
||||
"default link NvimInvalidValue NvimInvalid",
|
||||
|
||||
"default link NvimInvalidIdentifier NvimInvalidValue",
|
||||
"default link NvimInvalidIdentifierScope NvimInvalidIdentifier",
|
||||
"default link NvimInvalidIdentifierScopeDelimiter NvimInvalidIdentifier",
|
||||
"default link NvimInvalidIdentifierName NvimInvalidIdentifier",
|
||||
"default link NvimInvalidIdentifierKey NvimInvalidIdentifier",
|
||||
|
||||
"default link NvimInvalidColon NvimInvalidDelimiter",
|
||||
"default link NvimInvalidComma NvimInvalidDelimiter",
|
||||
"default link NvimInvalidArrow NvimInvalidDelimiter",
|
||||
|
||||
"default link NvimInvalidRegister NvimInvalidValue",
|
||||
"default link NvimInvalidNumber NvimInvalidValue",
|
||||
"default link NvimInvalidFloat NvimInvalidNumber",
|
||||
"default link NvimInvalidNumberPrefix NvimInvalidNumber",
|
||||
|
||||
"default link NvimInvalidOptionSigil NvimInvalidIdentifier",
|
||||
"default link NvimInvalidOptionName NvimInvalidIdentifier",
|
||||
"default link NvimInvalidOptionScope NvimInvalidIdentifierScope",
|
||||
"default link NvimInvalidOptionScopeDelimiter "
|
||||
"NvimInvalidIdentifierScopeDelimiter",
|
||||
|
||||
"default link NvimInvalidEnvironmentSigil NvimInvalidOptionSigil",
|
||||
"default link NvimInvalidEnvironmentName NvimInvalidIdentifier",
|
||||
|
||||
// Invalid string bodies and specials are still highlighted as valid ones to
|
||||
// minimize the red area.
|
||||
"default link NvimInvalidString NvimInvalidValue",
|
||||
"default link NvimInvalidStringBody NvimStringBody",
|
||||
"default link NvimInvalidStringQuote NvimInvalidString",
|
||||
"default link NvimInvalidStringSpecial NvimStringSpecial",
|
||||
|
||||
"default link NvimInvalidSingleQuote NvimInvalidStringQuote",
|
||||
"default link NvimInvalidSingleQuotedBody NvimInvalidStringBody",
|
||||
"default link NvimInvalidSingleQuotedQuote NvimInvalidStringSpecial",
|
||||
|
||||
"default link NvimInvalidDoubleQuote NvimInvalidStringQuote",
|
||||
"default link NvimInvalidDoubleQuotedBody NvimInvalidStringBody",
|
||||
"default link NvimInvalidDoubleQuotedEscape NvimInvalidStringSpecial",
|
||||
"default link NvimInvalidDoubleQuotedUnknownEscape NvimInvalidValue",
|
||||
|
||||
"default link NvimInvalidFigureBrace NvimInvalidDelimiter",
|
||||
|
||||
"default link NvimInvalidSpacing ErrorMsg",
|
||||
|
||||
// Not actually invalid, but we highlight user that he is doing something
|
||||
// wrong.
|
||||
"default link NvimDoubleQuotedUnknownEscape NvimInvalidValue",
|
||||
NULL,
|
||||
};
|
||||
|
||||
/// Create default links for Nvim* highlight groups used for cmdline coloring
|
||||
void syn_init_cmdline_highlight(bool reset, bool init)
|
||||
{
|
||||
for (size_t i = 0 ; highlight_init_cmdline[i] != NULL ; i++) {
|
||||
do_highlight(highlight_init_cmdline[i], reset, init);
|
||||
}
|
||||
}
|
||||
|
||||
/// Load colors from a file if "g:colors_name" is set, otherwise load builtin
|
||||
/// colors
|
||||
///
|
||||
/// @param both include groups where 'bg' doesn't matter
|
||||
/// @param reset clear groups first
|
||||
void
|
||||
init_highlight(int both, int reset)
|
||||
void init_highlight(bool both, bool reset)
|
||||
{
|
||||
int i;
|
||||
char **pp;
|
||||
static int had_both = FALSE;
|
||||
static int had_both = false;
|
||||
|
||||
// Try finding the color scheme file. Used when a color file was loaded
|
||||
// and 'background' or 't_Co' is changed.
|
||||
|
@ -6054,10 +6257,10 @@ init_highlight(int both, int reset)
|
|||
* Didn't use a color file, use the compiled-in colors.
|
||||
*/
|
||||
if (both) {
|
||||
had_both = TRUE;
|
||||
pp = highlight_init_both;
|
||||
for (i = 0; pp[i] != NULL; i++) {
|
||||
do_highlight((char_u *)pp[i], reset, true);
|
||||
had_both = true;
|
||||
const char *const *const pp = highlight_init_both;
|
||||
for (size_t i = 0; pp[i] != NULL; i++) {
|
||||
do_highlight(pp[i], reset, true);
|
||||
}
|
||||
} else if (!had_both) {
|
||||
// Don't do anything before the call with both == TRUE from main().
|
||||
|
@ -6066,10 +6269,11 @@ init_highlight(int both, int reset)
|
|||
return;
|
||||
}
|
||||
|
||||
pp = (*p_bg == 'l') ? highlight_init_light : highlight_init_dark;
|
||||
|
||||
for (i = 0; pp[i] != NULL; i++) {
|
||||
do_highlight((char_u *)pp[i], reset, true);
|
||||
const char *const *const pp = ((*p_bg == 'l')
|
||||
? highlight_init_light
|
||||
: highlight_init_dark);
|
||||
for (size_t i = 0; pp[i] != NULL; i++) {
|
||||
do_highlight(pp[i], reset, true);
|
||||
}
|
||||
|
||||
/* Reverse looks ugly, but grey may not work for 8 colors. Thus let it
|
||||
|
@ -6079,15 +6283,14 @@ init_highlight(int both, int reset)
|
|||
* Clear the attributes, needed when changing the t_Co value. */
|
||||
if (t_colors > 8) {
|
||||
do_highlight(
|
||||
(char_u *)(*p_bg == 'l'
|
||||
? "Visual cterm=NONE ctermbg=LightGrey"
|
||||
: "Visual cterm=NONE ctermbg=DarkGrey"), false,
|
||||
true);
|
||||
(*p_bg == 'l'
|
||||
? "Visual cterm=NONE ctermbg=LightGrey"
|
||||
: "Visual cterm=NONE ctermbg=DarkGrey"), false, true);
|
||||
} else {
|
||||
do_highlight((char_u *)"Visual cterm=reverse ctermbg=NONE",
|
||||
FALSE, TRUE);
|
||||
if (*p_bg == 'l')
|
||||
do_highlight((char_u *)"Search ctermfg=black", FALSE, TRUE);
|
||||
do_highlight("Visual cterm=reverse ctermbg=NONE", false, true);
|
||||
if (*p_bg == 'l') {
|
||||
do_highlight("Search ctermfg=black", false, true);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -6104,6 +6307,7 @@ init_highlight(int both, int reset)
|
|||
recursive--;
|
||||
}
|
||||
}
|
||||
syn_init_cmdline_highlight(false, false);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -6138,17 +6342,22 @@ int load_colors(char_u *name)
|
|||
}
|
||||
|
||||
|
||||
/// Handle the ":highlight .." command.
|
||||
/// When using ":hi clear" this is called recursively for each group with
|
||||
/// "forceit" and "init" both TRUE.
|
||||
/// @param init TRUE when called for initializing
|
||||
void
|
||||
do_highlight(char_u *line, int forceit, int init) {
|
||||
char_u *name_end;
|
||||
char_u *linep;
|
||||
char_u *key_start;
|
||||
char_u *arg_start;
|
||||
char_u *key = NULL, *arg = NULL;
|
||||
/// Handle ":highlight" command
|
||||
///
|
||||
/// When using ":highlight clear" this is called recursively for each group with
|
||||
/// forceit and init being both true.
|
||||
///
|
||||
/// @param[in] line Command arguments.
|
||||
/// @param[in] forceit True when bang is given, allows to link group even if
|
||||
/// it has its own settings.
|
||||
/// @param[in] init True when initializing.
|
||||
void do_highlight(const char *line, const bool forceit, const bool init)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
const char *name_end;
|
||||
const char *linep;
|
||||
const char *key_start;
|
||||
const char *arg_start;
|
||||
long i;
|
||||
int off;
|
||||
int len;
|
||||
|
@ -6162,94 +6371,87 @@ do_highlight(char_u *line, int forceit, int init) {
|
|||
int color;
|
||||
bool is_normal_group = false; // "Normal" group
|
||||
|
||||
/*
|
||||
* If no argument, list current highlighting.
|
||||
*/
|
||||
if (ends_excmd(*line)) {
|
||||
// If no argument, list current highlighting.
|
||||
if (ends_excmd((uint8_t)(*line))) {
|
||||
for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) {
|
||||
// todo(vim): only call when the group has attributes set
|
||||
// TODO(brammool): only call when the group has attributes set
|
||||
highlight_list_one(i);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Isolate the name.
|
||||
*/
|
||||
name_end = skiptowhite(line);
|
||||
linep = skipwhite(name_end);
|
||||
// Isolate the name.
|
||||
name_end = (const char *)skiptowhite((const char_u *)line);
|
||||
linep = (const char *)skipwhite((const char_u *)name_end);
|
||||
|
||||
/*
|
||||
* Check for "default" argument.
|
||||
*/
|
||||
if (STRNCMP(line, "default", name_end - line) == 0) {
|
||||
dodefault = TRUE;
|
||||
// Check for "default" argument.
|
||||
if (strncmp(line, "default", name_end - line) == 0) {
|
||||
dodefault = true;
|
||||
line = linep;
|
||||
name_end = skiptowhite(line);
|
||||
linep = skipwhite(name_end);
|
||||
name_end = (const char *)skiptowhite((const char_u *)line);
|
||||
linep = (const char *)skipwhite((const char_u *)name_end);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for "clear" or "link" argument.
|
||||
*/
|
||||
if (STRNCMP(line, "clear", name_end - line) == 0)
|
||||
doclear = TRUE;
|
||||
if (STRNCMP(line, "link", name_end - line) == 0)
|
||||
dolink = TRUE;
|
||||
// Check for "clear" or "link" argument.
|
||||
if (strncmp(line, "clear", name_end - line) == 0) {
|
||||
doclear = true;
|
||||
} else if (strncmp(line, "link", name_end - line) == 0) {
|
||||
dolink = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* ":highlight {group-name}": list highlighting for one group.
|
||||
*/
|
||||
if (!doclear && !dolink && ends_excmd(*linep)) {
|
||||
id = syn_namen2id(line, (int)(name_end - line));
|
||||
if (id == 0)
|
||||
EMSG2(_("E411: highlight group not found: %s"), line);
|
||||
else
|
||||
// ":highlight {group-name}": list highlighting for one group.
|
||||
if (!doclear && !dolink && ends_excmd((uint8_t)(*linep))) {
|
||||
id = syn_namen2id((const char_u *)line, (int)(name_end - line));
|
||||
if (id == 0) {
|
||||
emsgf(_("E411: highlight group not found: %s"), line);
|
||||
} else {
|
||||
highlight_list_one(id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle ":highlight link {from} {to}" command.
|
||||
*/
|
||||
// Handle ":highlight link {from} {to}" command.
|
||||
if (dolink) {
|
||||
char_u *from_start = linep;
|
||||
char_u *from_end;
|
||||
char_u *to_start;
|
||||
char_u *to_end;
|
||||
const char *from_start = linep;
|
||||
const char *from_end;
|
||||
const char *to_start;
|
||||
const char *to_end;
|
||||
int from_id;
|
||||
int to_id;
|
||||
|
||||
from_end = skiptowhite(from_start);
|
||||
to_start = skipwhite(from_end);
|
||||
to_end = skiptowhite(to_start);
|
||||
from_end = (const char *)skiptowhite((const char_u *)from_start);
|
||||
to_start = (const char *)skipwhite((const char_u *)from_end);
|
||||
to_end = (const char *)skiptowhite((const char_u *)to_start);
|
||||
|
||||
if (ends_excmd(*from_start) || ends_excmd(*to_start)) {
|
||||
EMSG2(_("E412: Not enough arguments: \":highlight link %s\""),
|
||||
from_start);
|
||||
if (ends_excmd((uint8_t)(*from_start))
|
||||
|| ends_excmd((uint8_t)(*to_start))) {
|
||||
emsgf(_("E412: Not enough arguments: \":highlight link %s\""),
|
||||
from_start);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ends_excmd(*skipwhite(to_end))) {
|
||||
EMSG2(_("E413: Too many arguments: \":highlight link %s\""), from_start);
|
||||
if (!ends_excmd(*skipwhite((const char_u *)to_end))) {
|
||||
emsgf(_("E413: Too many arguments: \":highlight link %s\""), from_start);
|
||||
return;
|
||||
}
|
||||
|
||||
from_id = syn_check_group(from_start, (int)(from_end - from_start));
|
||||
if (STRNCMP(to_start, "NONE", 4) == 0)
|
||||
from_id = syn_check_group((const char_u *)from_start,
|
||||
(int)(from_end - from_start));
|
||||
if (strncmp(to_start, "NONE", 4) == 0) {
|
||||
to_id = 0;
|
||||
else
|
||||
to_id = syn_check_group(to_start, (int)(to_end - to_start));
|
||||
} else {
|
||||
to_id = syn_check_group((const char_u *)to_start,
|
||||
(int)(to_end - to_start));
|
||||
}
|
||||
|
||||
if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0)) {
|
||||
/*
|
||||
* Don't allow a link when there already is some highlighting
|
||||
* for the group, unless '!' is used
|
||||
*/
|
||||
// Don't allow a link when there already is some highlighting
|
||||
// for the group, unless '!' is used
|
||||
if (to_id > 0 && !forceit && !init
|
||||
&& hl_has_settings(from_id - 1, dodefault)) {
|
||||
if (sourcing_name == NULL && !dodefault)
|
||||
if (sourcing_name == NULL && !dodefault) {
|
||||
EMSG(_("E414: group has settings, highlight link ignored"));
|
||||
}
|
||||
} else {
|
||||
if (!init)
|
||||
HL_TABLE()[from_id - 1].sg_set |= SG_LINK;
|
||||
|
@ -6259,43 +6461,38 @@ do_highlight(char_u *line, int forceit, int init) {
|
|||
}
|
||||
}
|
||||
|
||||
/* Only call highlight_changed() once, after sourcing a syntax file */
|
||||
need_highlight_changed = TRUE;
|
||||
// Only call highlight_changed() once, after sourcing a syntax file.
|
||||
need_highlight_changed = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (doclear) {
|
||||
/*
|
||||
* ":highlight clear [group]" command.
|
||||
*/
|
||||
// ":highlight clear [group]" command.
|
||||
line = linep;
|
||||
if (ends_excmd(*line)) {
|
||||
if (ends_excmd((uint8_t)(*line))) {
|
||||
do_unlet(S_LEN("colors_name"), true);
|
||||
restore_cterm_colors();
|
||||
|
||||
/*
|
||||
* Clear all default highlight groups and load the defaults.
|
||||
*/
|
||||
for (int idx = 0; idx < highlight_ga.ga_len; ++idx) {
|
||||
// Clear all default highlight groups and load the defaults.
|
||||
for (int idx = 0; idx < highlight_ga.ga_len; idx++) {
|
||||
highlight_clear(idx);
|
||||
}
|
||||
init_highlight(TRUE, TRUE);
|
||||
init_highlight(true, true);
|
||||
highlight_changed();
|
||||
redraw_later_clear();
|
||||
return;
|
||||
}
|
||||
name_end = skiptowhite(line);
|
||||
linep = skipwhite(name_end);
|
||||
name_end = (const char *)skiptowhite((const char_u *)line);
|
||||
linep = (const char *)skipwhite((const char_u *)name_end);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the group name in the table. If it does not exist yet, add it.
|
||||
*/
|
||||
id = syn_check_group(line, (int)(name_end - line));
|
||||
if (id == 0) /* failed (out of memory) */
|
||||
// Find the group name in the table. If it does not exist yet, add it.
|
||||
id = syn_check_group((const char_u *)line, (int)(name_end - line));
|
||||
if (id == 0) { // Failed (out of memory).
|
||||
return;
|
||||
idx = id - 1; /* index is ID minus one */
|
||||
}
|
||||
idx = id - 1; // Index is ID minus one.
|
||||
|
||||
// Return if "default" was used and the group already has settings
|
||||
if (dodefault && hl_has_settings(idx, true)) {
|
||||
|
@ -6304,19 +6501,21 @@ do_highlight(char_u *line, int forceit, int init) {
|
|||
|
||||
is_normal_group = (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0);
|
||||
|
||||
/* Clear the highlighting for ":hi clear {group}" and ":hi clear". */
|
||||
// Clear the highlighting for ":hi clear {group}" and ":hi clear".
|
||||
if (doclear || (forceit && init)) {
|
||||
highlight_clear(idx);
|
||||
if (!doclear)
|
||||
HL_TABLE()[idx].sg_set = 0;
|
||||
}
|
||||
|
||||
char *key = NULL;
|
||||
char *arg = NULL;
|
||||
if (!doclear) {
|
||||
while (!ends_excmd(*linep)) {
|
||||
while (!ends_excmd((uint8_t)(*linep))) {
|
||||
key_start = linep;
|
||||
if (*linep == '=') {
|
||||
EMSG2(_("E415: unexpected equal sign: %s"), key_start);
|
||||
error = TRUE;
|
||||
emsgf(_("E415: unexpected equal sign: %s"), key_start);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -6326,61 +6525,58 @@ do_highlight(char_u *line, int forceit, int init) {
|
|||
linep++;
|
||||
}
|
||||
xfree(key);
|
||||
key = vim_strnsave_up(key_start, (int)(linep - key_start));
|
||||
linep = skipwhite(linep);
|
||||
key = (char *)vim_strnsave_up((const char_u *)key_start,
|
||||
(int)(linep - key_start));
|
||||
linep = (const char *)skipwhite((const char_u *)linep);
|
||||
|
||||
if (STRCMP(key, "NONE") == 0) {
|
||||
if (strcmp(key, "NONE") == 0) {
|
||||
if (!init || HL_TABLE()[idx].sg_set == 0) {
|
||||
if (!init)
|
||||
if (!init) {
|
||||
HL_TABLE()[idx].sg_set |= SG_CTERM+SG_GUI;
|
||||
}
|
||||
highlight_clear(idx);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for the equal sign.
|
||||
*/
|
||||
// Check for the equal sign.
|
||||
if (*linep != '=') {
|
||||
EMSG2(_("E416: missing equal sign: %s"), key_start);
|
||||
error = TRUE;
|
||||
emsgf(_("E416: missing equal sign: %s"), key_start);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
++linep;
|
||||
linep++;
|
||||
|
||||
/*
|
||||
* Isolate the argument.
|
||||
*/
|
||||
linep = skipwhite(linep);
|
||||
if (*linep == '\'') { /* guifg='color name' */
|
||||
// Isolate the argument.
|
||||
linep = (const char *)skipwhite((const char_u *)linep);
|
||||
if (*linep == '\'') { // guifg='color name'
|
||||
arg_start = ++linep;
|
||||
linep = vim_strchr(linep, '\'');
|
||||
linep = strchr(linep, '\'');
|
||||
if (linep == NULL) {
|
||||
EMSG2(_(e_invarg2), key_start);
|
||||
error = TRUE;
|
||||
emsgf(_(e_invarg2), key_start);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
arg_start = linep;
|
||||
linep = skiptowhite(linep);
|
||||
linep = (const char *)skiptowhite((const char_u *)linep);
|
||||
}
|
||||
if (linep == arg_start) {
|
||||
EMSG2(_("E417: missing argument: %s"), key_start);
|
||||
error = TRUE;
|
||||
emsgf(_("E417: missing argument: %s"), key_start);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
xfree(arg);
|
||||
arg = vim_strnsave(arg_start, (int)(linep - arg_start));
|
||||
arg = xstrndup(arg_start, (size_t)(linep - arg_start));
|
||||
|
||||
if (*linep == '\'')
|
||||
++linep;
|
||||
if (*linep == '\'') {
|
||||
linep++;
|
||||
}
|
||||
|
||||
/*
|
||||
* Store the argument.
|
||||
*/
|
||||
if ( STRCMP(key, "TERM") == 0
|
||||
|| STRCMP(key, "CTERM") == 0
|
||||
|| STRCMP(key, "GUI") == 0) {
|
||||
// Store the argument.
|
||||
if (strcmp(key, "TERM") == 0
|
||||
|| strcmp(key, "CTERM") == 0
|
||||
|| strcmp(key, "GUI") == 0) {
|
||||
attr = 0;
|
||||
off = 0;
|
||||
while (arg[off] != NUL) {
|
||||
|
@ -6393,26 +6589,30 @@ do_highlight(char_u *line, int forceit, int init) {
|
|||
}
|
||||
}
|
||||
if (i < 0) {
|
||||
EMSG2(_("E418: Illegal value: %s"), arg);
|
||||
error = TRUE;
|
||||
emsgf(_("E418: Illegal value: %s"), arg);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
if (arg[off] == ',') /* another one follows */
|
||||
++off;
|
||||
if (arg[off] == ',') { // Another one follows.
|
||||
off++;
|
||||
}
|
||||
}
|
||||
if (error)
|
||||
if (error) {
|
||||
break;
|
||||
}
|
||||
if (*key == 'C') {
|
||||
if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) {
|
||||
if (!init)
|
||||
if (!init) {
|
||||
HL_TABLE()[idx].sg_set |= SG_CTERM;
|
||||
}
|
||||
HL_TABLE()[idx].sg_cterm = attr;
|
||||
HL_TABLE()[idx].sg_cterm_bold = FALSE;
|
||||
}
|
||||
} else if (*key == 'G') {
|
||||
if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
|
||||
if (!init)
|
||||
if (!init) {
|
||||
HL_TABLE()[idx].sg_set |= SG_GUI;
|
||||
}
|
||||
HL_TABLE()[idx].sg_gui = attr;
|
||||
}
|
||||
}
|
||||
|
@ -6431,14 +6631,14 @@ do_highlight(char_u *line, int forceit, int init) {
|
|||
HL_TABLE()[idx].sg_cterm_bold = FALSE;
|
||||
}
|
||||
|
||||
if (ascii_isdigit(*arg))
|
||||
if (ascii_isdigit(*arg)) {
|
||||
color = atoi((char *)arg);
|
||||
else if (STRICMP(arg, "fg") == 0) {
|
||||
if (cterm_normal_fg_color)
|
||||
} else if (STRICMP(arg, "fg") == 0) {
|
||||
if (cterm_normal_fg_color) {
|
||||
color = cterm_normal_fg_color - 1;
|
||||
else {
|
||||
} else {
|
||||
EMSG(_("E419: FG color unknown"));
|
||||
error = TRUE;
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
} else if (STRICMP(arg, "bg") == 0) {
|
||||
|
@ -6446,70 +6646,84 @@ do_highlight(char_u *line, int forceit, int init) {
|
|||
color = cterm_normal_bg_color - 1;
|
||||
else {
|
||||
EMSG(_("E420: BG color unknown"));
|
||||
error = TRUE;
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
static char *(color_names[28]) = {
|
||||
static const char *color_names[] = {
|
||||
"Black", "DarkBlue", "DarkGreen", "DarkCyan",
|
||||
"DarkRed", "DarkMagenta", "Brown", "DarkYellow",
|
||||
"Gray", "Grey",
|
||||
"LightGray", "LightGrey", "DarkGray", "DarkGrey",
|
||||
"Blue", "LightBlue", "Green", "LightGreen",
|
||||
"Cyan", "LightCyan", "Red", "LightRed", "Magenta",
|
||||
"LightMagenta", "Yellow", "LightYellow", "White", "NONE"
|
||||
"LightMagenta", "Yellow", "LightYellow", "White",
|
||||
"NONE"
|
||||
};
|
||||
static const int color_numbers_16[] = {
|
||||
0, 1, 2, 3,
|
||||
4, 5, 6, 6,
|
||||
7, 7,
|
||||
7, 7, 8, 8,
|
||||
9, 9, 10, 10,
|
||||
11, 11, 12, 12, 13,
|
||||
13, 14, 14, 15,
|
||||
-1
|
||||
};
|
||||
// For xterm with 88 colors:
|
||||
static int color_numbers_88[] = {
|
||||
0, 4, 2, 6,
|
||||
1, 5, 32, 72,
|
||||
84, 84,
|
||||
7, 7, 82, 82,
|
||||
12, 43, 10, 61,
|
||||
14, 63, 9, 74, 13,
|
||||
75, 11, 78, 15,
|
||||
-1
|
||||
};
|
||||
// For xterm with 256 colors:
|
||||
static int color_numbers_256[] = {
|
||||
0, 4, 2, 6,
|
||||
1, 5, 130, 130,
|
||||
248, 248,
|
||||
7, 7, 242, 242,
|
||||
12, 81, 10, 121,
|
||||
14, 159, 9, 224, 13,
|
||||
225, 11, 229, 15,
|
||||
-1
|
||||
};
|
||||
// For terminals with less than 16 colors:
|
||||
static int color_numbers_8[28] = {
|
||||
0, 4, 2, 6,
|
||||
1, 5, 3, 3,
|
||||
7, 7,
|
||||
7, 7, 0+8, 0+8,
|
||||
4+8, 4+8, 2+8, 2+8,
|
||||
6+8, 6+8, 1+8, 1+8, 5+8,
|
||||
5+8, 3+8, 3+8, 7+8,
|
||||
-1
|
||||
};
|
||||
static int color_numbers_16[28] = {0, 1, 2, 3,
|
||||
4, 5, 6, 6,
|
||||
7, 7,
|
||||
7, 7, 8, 8,
|
||||
9, 9, 10, 10,
|
||||
11, 11, 12, 12, 13,
|
||||
13, 14, 14, 15, -1};
|
||||
/* for xterm with 88 colors... */
|
||||
static int color_numbers_88[28] = {0, 4, 2, 6,
|
||||
1, 5, 32, 72,
|
||||
84, 84,
|
||||
7, 7, 82, 82,
|
||||
12, 43, 10, 61,
|
||||
14, 63, 9, 74, 13,
|
||||
75, 11, 78, 15, -1};
|
||||
/* for xterm with 256 colors... */
|
||||
static int color_numbers_256[28] = {0, 4, 2, 6,
|
||||
1, 5, 130, 130,
|
||||
248, 248,
|
||||
7, 7, 242, 242,
|
||||
12, 81, 10, 121,
|
||||
14, 159, 9, 224, 13,
|
||||
225, 11, 229, 15, -1};
|
||||
/* for terminals with less than 16 colors... */
|
||||
static int color_numbers_8[28] = {0, 4, 2, 6,
|
||||
1, 5, 3, 3,
|
||||
7, 7,
|
||||
7, 7, 0+8, 0+8,
|
||||
4+8, 4+8, 2+8, 2+8,
|
||||
6+8, 6+8, 1+8, 1+8, 5+8,
|
||||
5+8, 3+8, 3+8, 7+8, -1};
|
||||
|
||||
/* reduce calls to STRICMP a bit, it can be slow */
|
||||
// Reduce calls to STRICMP a bit, it can be slow.
|
||||
off = TOUPPER_ASC(*arg);
|
||||
for (i = ARRAY_SIZE(color_names); --i >= 0; )
|
||||
for (i = ARRAY_SIZE(color_names); --i >= 0; ) {
|
||||
if (off == color_names[i][0]
|
||||
&& STRICMP(arg + 1, color_names[i] + 1) == 0)
|
||||
&& STRICMP(arg + 1, color_names[i] + 1) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i < 0) {
|
||||
EMSG2(_(
|
||||
"E421: Color name or number not recognized: %s"),
|
||||
key_start);
|
||||
error = TRUE;
|
||||
emsgf(_("E421: Color name or number not recognized: %s"),
|
||||
key_start);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Use the _16 table to check if its a valid color name. */
|
||||
// Use the _16 table to check if its a valid color name.
|
||||
color = color_numbers_16[i];
|
||||
if (color >= 0) {
|
||||
if (t_colors == 8) {
|
||||
/* t_Co is 8: use the 8 colors table */
|
||||
// t_Co is 8: use the 8 colors table.
|
||||
color = color_numbers_8[i];
|
||||
if (key[5] == 'F') {
|
||||
/* set/reset bold attribute to get light foreground
|
||||
|
@ -6533,16 +6747,14 @@ do_highlight(char_u *line, int forceit, int init) {
|
|||
}
|
||||
}
|
||||
}
|
||||
/* Add one to the argument, to avoid zero. Zero is used for
|
||||
* "NONE", then "color" is -1. */
|
||||
// Add one to the argument, to avoid zero. Zero is used for
|
||||
// "NONE", then "color" is -1.
|
||||
if (key[5] == 'F') {
|
||||
HL_TABLE()[idx].sg_cterm_fg = color + 1;
|
||||
if (is_normal_group) {
|
||||
cterm_normal_fg_color = color + 1;
|
||||
cterm_normal_fg_bold = (HL_TABLE()[idx].sg_cterm & HL_BOLD);
|
||||
{
|
||||
must_redraw = CLEAR;
|
||||
}
|
||||
must_redraw = CLEAR;
|
||||
}
|
||||
} else {
|
||||
HL_TABLE()[idx].sg_cterm_bg = color + 1;
|
||||
|
@ -6566,15 +6778,15 @@ do_highlight(char_u *line, int forceit, int init) {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if (STRCMP(key, "GUIFG") == 0) {
|
||||
} else if (strcmp(key, "GUIFG") == 0) {
|
||||
if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
|
||||
if (!init)
|
||||
HL_TABLE()[idx].sg_set |= SG_GUI;
|
||||
|
||||
xfree(HL_TABLE()[idx].sg_rgb_fg_name);
|
||||
if (STRCMP(arg, "NONE")) {
|
||||
HL_TABLE()[idx].sg_rgb_fg_name = (uint8_t *)xstrdup((char *)arg);
|
||||
HL_TABLE()[idx].sg_rgb_fg = name_to_color(arg);
|
||||
if (strcmp(arg, "NONE")) {
|
||||
HL_TABLE()[idx].sg_rgb_fg_name = (char_u *)xstrdup((char *)arg);
|
||||
HL_TABLE()[idx].sg_rgb_fg = name_to_color((const char_u *)arg);
|
||||
} else {
|
||||
HL_TABLE()[idx].sg_rgb_fg_name = NULL;
|
||||
HL_TABLE()[idx].sg_rgb_fg = -1;
|
||||
|
@ -6591,8 +6803,8 @@ do_highlight(char_u *line, int forceit, int init) {
|
|||
|
||||
xfree(HL_TABLE()[idx].sg_rgb_bg_name);
|
||||
if (STRCMP(arg, "NONE") != 0) {
|
||||
HL_TABLE()[idx].sg_rgb_bg_name = (uint8_t *)xstrdup((char *)arg);
|
||||
HL_TABLE()[idx].sg_rgb_bg = name_to_color(arg);
|
||||
HL_TABLE()[idx].sg_rgb_bg_name = (char_u *)xstrdup((char *)arg);
|
||||
HL_TABLE()[idx].sg_rgb_bg = name_to_color((const char_u *)arg);
|
||||
} else {
|
||||
HL_TABLE()[idx].sg_rgb_bg_name = NULL;
|
||||
HL_TABLE()[idx].sg_rgb_bg = -1;
|
||||
|
@ -6602,15 +6814,15 @@ do_highlight(char_u *line, int forceit, int init) {
|
|||
if (is_normal_group) {
|
||||
normal_bg = HL_TABLE()[idx].sg_rgb_bg;
|
||||
}
|
||||
} else if (STRCMP(key, "GUISP") == 0) {
|
||||
} else if (strcmp(key, "GUISP") == 0) {
|
||||
if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
|
||||
if (!init)
|
||||
HL_TABLE()[idx].sg_set |= SG_GUI;
|
||||
|
||||
xfree(HL_TABLE()[idx].sg_rgb_sp_name);
|
||||
if (STRCMP(arg, "NONE") != 0) {
|
||||
HL_TABLE()[idx].sg_rgb_sp_name = (uint8_t *)xstrdup((char *)arg);
|
||||
HL_TABLE()[idx].sg_rgb_sp = name_to_color(arg);
|
||||
if (strcmp(arg, "NONE") != 0) {
|
||||
HL_TABLE()[idx].sg_rgb_sp_name = (char_u *)xstrdup((char *)arg);
|
||||
HL_TABLE()[idx].sg_rgb_sp = name_to_color((const char_u *)arg);
|
||||
} else {
|
||||
HL_TABLE()[idx].sg_rgb_sp_name = NULL;
|
||||
HL_TABLE()[idx].sg_rgb_sp = -1;
|
||||
|
@ -6620,31 +6832,25 @@ do_highlight(char_u *line, int forceit, int init) {
|
|||
if (is_normal_group) {
|
||||
normal_sp = HL_TABLE()[idx].sg_rgb_sp;
|
||||
}
|
||||
} else if (STRCMP(key, "START") == 0 || STRCMP(key, "STOP") == 0) {
|
||||
} else if (strcmp(key, "START") == 0 || strcmp(key, "STOP") == 0) {
|
||||
// Ignored for now
|
||||
} else {
|
||||
EMSG2(_("E423: Illegal argument: %s"), key_start);
|
||||
error = TRUE;
|
||||
emsgf(_("E423: Illegal argument: %s"), key_start);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* When highlighting has been given for a group, don't link it.
|
||||
*/
|
||||
// When highlighting has been given for a group, don't link it.
|
||||
if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) {
|
||||
HL_TABLE()[idx].sg_link = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Continue with next argument.
|
||||
*/
|
||||
linep = skipwhite(linep);
|
||||
// Continue with next argument.
|
||||
linep = (const char *)skipwhite((const char_u *)linep);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If there is an error, and it's a new entry, remove it from the table.
|
||||
*/
|
||||
// If there is an error, and it's a new entry, remove it from the table.
|
||||
if (error && idx == highlight_ga.ga_len) {
|
||||
syn_unadd_group();
|
||||
} else {
|
||||
|
@ -7202,7 +7408,7 @@ char_u *syn_id2name(int id)
|
|||
/*
|
||||
* Like syn_name2id(), but take a pointer + length argument.
|
||||
*/
|
||||
int syn_namen2id(char_u *linep, int len)
|
||||
int syn_namen2id(const char_u *linep, int len)
|
||||
{
|
||||
char_u *name = vim_strnsave(linep, len);
|
||||
int id = syn_name2id(name);
|
||||
|
@ -7218,7 +7424,7 @@ int syn_namen2id(char_u *linep, int len)
|
|||
/// @param len length of \p pp
|
||||
///
|
||||
/// @return 0 for failure else the id of the group
|
||||
int syn_check_group(char_u *pp, int len)
|
||||
int syn_check_group(const char_u *pp, int len)
|
||||
{
|
||||
char_u *name = vim_strnsave(pp, len);
|
||||
int id = syn_name2id(name);
|
||||
|
@ -8221,7 +8427,7 @@ color_name_table_T color_name_table[] = {
|
|||
///
|
||||
/// @param[in] name string value to convert to RGB
|
||||
/// return the hex value or -1 if could not find a correct value
|
||||
RgbValue name_to_color(const uint8_t *name)
|
||||
RgbValue name_to_color(const char_u *name)
|
||||
{
|
||||
|
||||
if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2])
|
||||
|
|
|
@ -45,6 +45,9 @@ typedef struct {
|
|||
} color_name_table_T;
|
||||
extern color_name_table_T color_name_table[];
|
||||
|
||||
/// Array of highlight definitions, used for unit testing
|
||||
extern const char *const highlight_init_cmdline[];
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "syntax.h.generated.h"
|
||||
#endif
|
||||
|
|
|
@ -30,8 +30,8 @@
|
|||
// for ":version", ":intro", and "nvim --version"
|
||||
#ifndef NVIM_VERSION_MEDIUM
|
||||
#define NVIM_VERSION_MEDIUM "v" STR(NVIM_VERSION_MAJOR)\
|
||||
"." STR(NVIM_VERSION_MINOR) "." STR(NVIM_VERSION_PATCH)\
|
||||
NVIM_VERSION_PRERELEASE
|
||||
"." STR(NVIM_VERSION_MINOR) "." STR(NVIM_VERSION_PATCH)\
|
||||
NVIM_VERSION_PRERELEASE
|
||||
#endif
|
||||
#define NVIM_VERSION_LONG "NVIM " NVIM_VERSION_MEDIUM
|
||||
|
||||
|
@ -47,33 +47,33 @@ char *version_cflags = "Compilation: " NVIM_VERSION_CFLAGS;
|
|||
|
||||
static char *features[] = {
|
||||
#ifdef HAVE_ACL
|
||||
"+acl",
|
||||
"+acl",
|
||||
#else
|
||||
"-acl",
|
||||
"-acl",
|
||||
#endif
|
||||
|
||||
#if (defined(HAVE_ICONV_H) && defined(USE_ICONV)) || defined(DYNAMIC_ICONV)
|
||||
# ifdef DYNAMIC_ICONV
|
||||
"+iconv/dyn",
|
||||
"+iconv/dyn",
|
||||
# else
|
||||
"+iconv",
|
||||
"+iconv",
|
||||
# endif
|
||||
#else
|
||||
"-iconv",
|
||||
"-iconv",
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_JEMALLOC
|
||||
"+jemalloc",
|
||||
"+jemalloc",
|
||||
#else
|
||||
"-jemalloc",
|
||||
"-jemalloc",
|
||||
#endif
|
||||
|
||||
#ifdef FEAT_TUI
|
||||
"+tui",
|
||||
"+tui",
|
||||
#else
|
||||
"-tui",
|
||||
"-tui",
|
||||
#endif
|
||||
NULL
|
||||
NULL
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
|
@ -205,8 +205,8 @@ static const int included_patches[] = {
|
|||
// 1233,
|
||||
// 1232,
|
||||
// 1231,
|
||||
// 1230,
|
||||
// 1229,
|
||||
1230,
|
||||
1229,
|
||||
// 1228,
|
||||
// 1227,
|
||||
// 1226,
|
||||
|
|
|
@ -26,13 +26,6 @@
|
|||
/// length of a buffer to store a number in ASCII (64 bits binary + NUL)
|
||||
enum { NUMBUFLEN = 65 };
|
||||
|
||||
// flags for vim_str2nr()
|
||||
#define STR2NR_BIN 1
|
||||
#define STR2NR_OCT 2
|
||||
#define STR2NR_HEX 4
|
||||
#define STR2NR_ALL (STR2NR_BIN + STR2NR_OCT + STR2NR_HEX)
|
||||
#define STR2NR_FORCE 8 // only when ONE of the above is used
|
||||
|
||||
#define MAX_TYPENR 65535
|
||||
|
||||
#define ROOT_UID 0
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,389 @@
|
|||
#ifndef NVIM_VIML_PARSER_EXPRESSIONS_H
|
||||
#define NVIM_VIML_PARSER_EXPRESSIONS_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "nvim/types.h"
|
||||
#include "nvim/viml/parser/parser.h"
|
||||
#include "nvim/eval/typval.h"
|
||||
|
||||
// Defines whether to ignore case:
|
||||
// == kCCStrategyUseOption
|
||||
// ==# kCCStrategyMatchCase
|
||||
// ==? kCCStrategyIgnoreCase
|
||||
typedef enum {
|
||||
kCCStrategyUseOption = 0, // 0 for xcalloc
|
||||
kCCStrategyMatchCase = '#',
|
||||
kCCStrategyIgnoreCase = '?',
|
||||
} ExprCaseCompareStrategy;
|
||||
|
||||
/// Lexer token type
|
||||
typedef enum {
|
||||
kExprLexInvalid = 0, ///< Invalid token, indicaten an error.
|
||||
kExprLexMissing, ///< Missing token, for use in parser.
|
||||
kExprLexSpacing, ///< Spaces, tabs, newlines, etc.
|
||||
kExprLexEOC, ///< End of command character: NL, |, just end of stream.
|
||||
|
||||
kExprLexQuestion, ///< Question mark, for use in ternary.
|
||||
kExprLexColon, ///< Colon, for use in ternary.
|
||||
kExprLexOr, ///< Logical or operator.
|
||||
kExprLexAnd, ///< Logical and operator.
|
||||
kExprLexComparison, ///< One of the comparison operators.
|
||||
kExprLexPlus, ///< Plus sign.
|
||||
kExprLexMinus, ///< Minus sign.
|
||||
kExprLexDot, ///< Dot: either concat or subscript, also part of the float.
|
||||
kExprLexMultiplication, ///< Multiplication, division or modulo operator.
|
||||
|
||||
kExprLexNot, ///< Not: !.
|
||||
|
||||
kExprLexNumber, ///< Integer number literal, or part of a float.
|
||||
kExprLexSingleQuotedString, ///< Single quoted string literal.
|
||||
kExprLexDoubleQuotedString, ///< Double quoted string literal.
|
||||
kExprLexOption, ///< &optionname option value.
|
||||
kExprLexRegister, ///< @r register value.
|
||||
kExprLexEnv, ///< Environment $variable value.
|
||||
kExprLexPlainIdentifier, ///< Identifier without scope: `abc`, `foo#bar`.
|
||||
|
||||
kExprLexBracket, ///< Bracket, either opening or closing.
|
||||
kExprLexFigureBrace, ///< Figure brace, either opening or closing.
|
||||
kExprLexParenthesis, ///< Parenthesis, either opening or closing.
|
||||
kExprLexComma, ///< Comma.
|
||||
kExprLexArrow, ///< Arrow, like from lambda expressions.
|
||||
kExprLexAssignment, ///< Assignment: `=` or `{op}=`.
|
||||
// XXX When modifying this enum you need to also modify eltkn_type_tab in
|
||||
// expressions.c and tests and, possibly, viml_pexpr_repr_token.
|
||||
} LexExprTokenType;
|
||||
|
||||
typedef enum {
|
||||
kExprCmpEqual, ///< Equality, unequality.
|
||||
kExprCmpMatches, ///< Matches regex, not matches regex.
|
||||
kExprCmpGreater, ///< `>` or `<=`
|
||||
kExprCmpGreaterOrEqual, ///< `>=` or `<`.
|
||||
kExprCmpIdentical, ///< `is` or `isnot`
|
||||
} ExprComparisonType;
|
||||
|
||||
/// All possible option scopes
|
||||
typedef enum {
|
||||
kExprOptScopeUnspecified = 0,
|
||||
kExprOptScopeGlobal = 'g',
|
||||
kExprOptScopeLocal = 'l',
|
||||
} ExprOptScope;
|
||||
|
||||
/// All possible assignment types: `=` and `{op}=`.
|
||||
typedef enum {
|
||||
kExprAsgnPlain = 0, ///< Plain assignment: `=`.
|
||||
kExprAsgnAdd, ///< Assignment augmented with addition: `+=`.
|
||||
kExprAsgnSubtract, ///< Assignment augmented with subtraction: `-=`.
|
||||
kExprAsgnConcat, ///< Assignment augmented with concatenation: `.=`.
|
||||
} ExprAssignmentType;
|
||||
|
||||
#define EXPR_OPT_SCOPE_LIST \
|
||||
((char[]){ kExprOptScopeGlobal, kExprOptScopeLocal })
|
||||
|
||||
/// All possible variable scopes
|
||||
typedef enum {
|
||||
kExprVarScopeMissing = 0,
|
||||
kExprVarScopeScript = 's',
|
||||
kExprVarScopeGlobal = 'g',
|
||||
kExprVarScopeVim = 'v',
|
||||
kExprVarScopeBuffer = 'b',
|
||||
kExprVarScopeWindow = 'w',
|
||||
kExprVarScopeTabpage = 't',
|
||||
kExprVarScopeLocal = 'l',
|
||||
kExprVarScopeArguments = 'a',
|
||||
} ExprVarScope;
|
||||
|
||||
#define EXPR_VAR_SCOPE_LIST \
|
||||
((char[]) { \
|
||||
kExprVarScopeScript, kExprVarScopeGlobal, kExprVarScopeVim, \
|
||||
kExprVarScopeBuffer, kExprVarScopeWindow, kExprVarScopeTabpage, \
|
||||
kExprVarScopeLocal, kExprVarScopeBuffer, kExprVarScopeArguments, \
|
||||
})
|
||||
|
||||
/// Lexer token
|
||||
typedef struct {
|
||||
ParserPosition start;
|
||||
size_t len;
|
||||
LexExprTokenType type;
|
||||
union {
|
||||
struct {
|
||||
ExprComparisonType type; ///< Comparison type.
|
||||
ExprCaseCompareStrategy ccs; ///< Case comparison strategy.
|
||||
bool inv; ///< True if comparison is to be inverted.
|
||||
} cmp; ///< For kExprLexComparison.
|
||||
|
||||
struct {
|
||||
enum {
|
||||
kExprLexMulMul, ///< Real multiplication.
|
||||
kExprLexMulDiv, ///< Division.
|
||||
kExprLexMulMod, ///< Modulo.
|
||||
} type; ///< Multiplication type.
|
||||
} mul; ///< For kExprLexMultiplication.
|
||||
|
||||
struct {
|
||||
bool closing; ///< True if bracket/etc is a closing one.
|
||||
} brc; ///< For brackets/braces/parenthesis.
|
||||
|
||||
struct {
|
||||
int name; ///< Register name, may be -1 if name not present.
|
||||
} reg; ///< For kExprLexRegister.
|
||||
|
||||
struct {
|
||||
bool closed; ///< True if quote was closed.
|
||||
} str; ///< For kExprLexSingleQuotedString and kExprLexDoubleQuotedString.
|
||||
|
||||
struct {
|
||||
const char *name; ///< Option name start.
|
||||
size_t len; ///< Option name length.
|
||||
ExprOptScope scope; ///< Option scope: &l:, &g: or not specified.
|
||||
} opt; ///< Option properties.
|
||||
|
||||
struct {
|
||||
ExprVarScope scope; ///< Scope character or 0 if not present.
|
||||
bool autoload; ///< Has autoload characters.
|
||||
} var; ///< For kExprLexPlainIdentifier
|
||||
|
||||
struct {
|
||||
LexExprTokenType type; ///< Suggested type for parsing incorrect code.
|
||||
const char *msg; ///< Error message.
|
||||
} err; ///< For kExprLexInvalid
|
||||
|
||||
struct {
|
||||
union {
|
||||
float_T floating;
|
||||
uvarnumber_T integer;
|
||||
} val; ///< Number value.
|
||||
uint8_t base; ///< Base: 2, 8, 10 or 16.
|
||||
bool is_float; ///< True if number is a floating-point.
|
||||
} num; ///< For kExprLexNumber
|
||||
|
||||
struct {
|
||||
ExprAssignmentType type;
|
||||
} ass; ///< For kExprLexAssignment
|
||||
} data; ///< Additional data, if needed.
|
||||
} LexExprToken;
|
||||
|
||||
typedef enum {
|
||||
/// If set, “pointer” to the current byte in pstate will not be shifted
|
||||
kELFlagPeek = (1 << 0),
|
||||
/// Determines whether scope is allowed to come before the identifier
|
||||
kELFlagForbidScope = (1 << 1),
|
||||
/// Determines whether floating-point numbers are allowed
|
||||
///
|
||||
/// I.e. whether dot is a decimal point separator or is not a part of
|
||||
/// a number at all.
|
||||
kELFlagAllowFloat = (1 << 2),
|
||||
/// Determines whether `is` and `isnot` are seen as comparison operators
|
||||
///
|
||||
/// If set they are supposed to be just regular identifiers.
|
||||
kELFlagIsNotCmp = (1 << 3),
|
||||
/// Determines whether EOC tokens are allowed
|
||||
///
|
||||
/// If set then it will yield Invalid token with E15 in place of EOC one if
|
||||
/// “EOC” is something like "|". It is fine with emitting EOC at the end of
|
||||
/// string still, with or without this flag set.
|
||||
kELFlagForbidEOC = (1 << 4),
|
||||
// XXX Whenever you add a new flag, alter klee_assume() statement in
|
||||
// viml_expressions_lexer.c.
|
||||
} LexExprFlags;
|
||||
|
||||
/// Expression AST node type
|
||||
typedef enum {
|
||||
kExprNodeMissing = 0,
|
||||
kExprNodeOpMissing,
|
||||
kExprNodeTernary, ///< Ternary operator.
|
||||
kExprNodeTernaryValue, ///< Ternary operator, colon.
|
||||
kExprNodeRegister, ///< Register.
|
||||
kExprNodeSubscript, ///< Subscript.
|
||||
kExprNodeListLiteral, ///< List literal.
|
||||
kExprNodeUnaryPlus,
|
||||
kExprNodeBinaryPlus,
|
||||
kExprNodeNested, ///< Nested parenthesised expression.
|
||||
kExprNodeCall, ///< Function call.
|
||||
/// Plain identifier: simple variable/function name
|
||||
///
|
||||
/// Looks like "string", "g:Foo", etc: consists from a single
|
||||
/// kExprLexPlainIdentifier token.
|
||||
kExprNodePlainIdentifier,
|
||||
/// Plain dictionary key, for use with kExprNodeConcatOrSubscript
|
||||
kExprNodePlainKey,
|
||||
/// Complex identifier: variable/function name with curly braces
|
||||
kExprNodeComplexIdentifier,
|
||||
/// Figure brace expression which is not yet known
|
||||
///
|
||||
/// May resolve to any of kExprNodeDictLiteral, kExprNodeLambda or
|
||||
/// kExprNodeCurlyBracesIdentifier.
|
||||
kExprNodeUnknownFigure,
|
||||
kExprNodeLambda, ///< Lambda.
|
||||
kExprNodeDictLiteral, ///< Dictionary literal.
|
||||
kExprNodeCurlyBracesIdentifier, ///< Part of the curly braces name.
|
||||
kExprNodeComma, ///< Comma “operator”.
|
||||
kExprNodeColon, ///< Colon “operator”.
|
||||
kExprNodeArrow, ///< Arrow “operator”.
|
||||
kExprNodeComparison, ///< Various comparison operators.
|
||||
/// Concat operator
|
||||
///
|
||||
/// To be only used in cases when it is known for sure it is not a subscript.
|
||||
kExprNodeConcat,
|
||||
/// Concat or subscript operator
|
||||
///
|
||||
/// For cases when it is not obvious whether expression is a concat or
|
||||
/// a subscript. May only have either number or plain identifier as the second
|
||||
/// child. To make it easier to avoid curly braces in place of
|
||||
/// kExprNodePlainIdentifier node kExprNodePlainKey is used.
|
||||
kExprNodeConcatOrSubscript,
|
||||
kExprNodeInteger, ///< Integral number.
|
||||
kExprNodeFloat, ///< Floating-point number.
|
||||
kExprNodeSingleQuotedString,
|
||||
kExprNodeDoubleQuotedString,
|
||||
kExprNodeOr,
|
||||
kExprNodeAnd,
|
||||
kExprNodeUnaryMinus,
|
||||
kExprNodeBinaryMinus,
|
||||
kExprNodeNot,
|
||||
kExprNodeMultiplication,
|
||||
kExprNodeDivision,
|
||||
kExprNodeMod,
|
||||
kExprNodeOption,
|
||||
kExprNodeEnvironment,
|
||||
kExprNodeAssignment,
|
||||
// XXX When modifying this list also modify east_node_type_tab both in parser
|
||||
// and in tests, and you most likely will also have to alter list of
|
||||
// highlight groups stored in highlight_init_cmdline variable.
|
||||
} ExprASTNodeType;
|
||||
|
||||
typedef struct expr_ast_node ExprASTNode;
|
||||
|
||||
/// Structure representing one AST node
|
||||
struct expr_ast_node {
|
||||
ExprASTNodeType type; ///< Node type.
|
||||
/// Node children: e.g. for 1 + 2 nodes 1 and 2 will be children of +.
|
||||
ExprASTNode *children;
|
||||
/// Next node: e.g. for 1 + 2 child nodes 1 and 2 are put into a single-linked
|
||||
/// list: `(+)->children` references only node 1, node 2 is in
|
||||
/// `(+)->children->next`.
|
||||
ExprASTNode *next;
|
||||
ParserPosition start;
|
||||
size_t len;
|
||||
union {
|
||||
struct {
|
||||
int name; ///< Register name, may be -1 if name not present.
|
||||
} reg; ///< For kExprNodeRegister.
|
||||
struct {
|
||||
/// Which nodes UnknownFigure can’t possibly represent.
|
||||
struct {
|
||||
/// True if UnknownFigure may actually represent dictionary literal.
|
||||
bool allow_dict;
|
||||
/// True if UnknownFigure may actually represent lambda.
|
||||
bool allow_lambda;
|
||||
/// True if UnknownFigure may actually be part of curly braces name.
|
||||
bool allow_ident;
|
||||
} type_guesses;
|
||||
/// Highlight chunk index, used for rehighlighting if needed
|
||||
size_t opening_hl_idx;
|
||||
} fig; ///< For kExprNodeUnknownFigure.
|
||||
struct {
|
||||
ExprVarScope scope; ///< Scope character or 0 if not present.
|
||||
/// Actual identifier without scope.
|
||||
///
|
||||
/// Points to inside parser reader state.
|
||||
const char *ident;
|
||||
size_t ident_len; ///< Actual identifier length.
|
||||
} var; ///< For kExprNodePlainIdentifier and kExprNodePlainKey.
|
||||
struct {
|
||||
bool got_colon; ///< True if colon was seen.
|
||||
} ter; ///< For kExprNodeTernaryValue.
|
||||
struct {
|
||||
ExprComparisonType type; ///< Comparison type.
|
||||
ExprCaseCompareStrategy ccs; ///< Case comparison strategy.
|
||||
bool inv; ///< True if comparison is to be inverted.
|
||||
} cmp; ///< For kExprNodeComparison.
|
||||
struct {
|
||||
uvarnumber_T value;
|
||||
} num; ///< For kExprNodeInteger.
|
||||
struct {
|
||||
float_T value;
|
||||
} flt; ///< For kExprNodeFloat.
|
||||
struct {
|
||||
char *value;
|
||||
size_t size;
|
||||
} str; ///< For kExprNodeSingleQuotedString and
|
||||
///< kExprNodeDoubleQuotedString.
|
||||
struct {
|
||||
const char *ident; ///< Option name start.
|
||||
size_t ident_len; ///< Option name length.
|
||||
ExprOptScope scope; ///< Option scope: &l:, &g: or not specified.
|
||||
} opt; ///< For kExprNodeOption.
|
||||
struct {
|
||||
const char *ident; ///< Environment variable name start.
|
||||
size_t ident_len; ///< Environment variable name length.
|
||||
} env; ///< For kExprNodeEnvironment.
|
||||
struct {
|
||||
ExprAssignmentType type;
|
||||
} ass; ///< For kExprNodeAssignment
|
||||
} data;
|
||||
};
|
||||
|
||||
enum {
|
||||
/// Allow multiple expressions in a row: e.g. for :echo
|
||||
///
|
||||
/// Parser will still parse only one of them though.
|
||||
kExprFlagsMulti = (1 << 0),
|
||||
/// Allow NL, NUL and bar to be EOC
|
||||
///
|
||||
/// When parsing expressions input by user bar is assumed to be a binary
|
||||
/// operator and other two are spacings.
|
||||
kExprFlagsDisallowEOC = (1 << 1),
|
||||
/// Parse :let argument
|
||||
///
|
||||
/// That mean that top level node must be an assignment and first nodes
|
||||
/// belong to lvalues.
|
||||
kExprFlagsParseLet = (1 << 2),
|
||||
// XXX whenever you add a new flag, alter klee_assume() statement in
|
||||
// viml_expressions_parser.c, nvim_parse_expression() flags parsing
|
||||
// alongside with its documentation and flag sets in check_parsing()
|
||||
// function in expressions parser functional and unit tests.
|
||||
} ExprParserFlags;
|
||||
|
||||
/// AST error definition
|
||||
typedef struct {
|
||||
/// Error message. Must contain a single printf format atom: %.*s.
|
||||
const char *msg;
|
||||
/// Error message argument: points to the location of the error.
|
||||
const char *arg;
|
||||
/// Message argument length: length till the end of string.
|
||||
int arg_len;
|
||||
} ExprASTError;
|
||||
|
||||
/// Structure representing complety AST for one expression
|
||||
typedef struct {
|
||||
/// When AST is not correct this message will be printed.
|
||||
///
|
||||
/// Uses `emsgf(msg, arg_len, arg);`, `msg` is assumed to contain only `%.*s`.
|
||||
ExprASTError err;
|
||||
/// Root node of the AST.
|
||||
ExprASTNode *root;
|
||||
} ExprAST;
|
||||
|
||||
/// Array mapping ExprASTNodeType to maximum amount of children node may have
|
||||
extern const uint8_t node_maxchildren[];
|
||||
|
||||
/// Array mapping ExprASTNodeType values to their stringified versions
|
||||
extern const char *const east_node_type_tab[];
|
||||
|
||||
/// Array mapping ExprComparisonType values to their stringified versions
|
||||
extern const char *const eltkn_cmp_type_tab[];
|
||||
|
||||
/// Array mapping ExprCaseCompareStrategy values to their stringified versions
|
||||
extern const char *const ccs_tab[];
|
||||
|
||||
/// Array mapping ExprAssignmentType values to their stringified versions
|
||||
extern const char *const expr_asgn_type_tab[];
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "viml/parser/expressions.h.generated.h"
|
||||
#endif
|
||||
|
||||
#endif // NVIM_VIML_PARSER_EXPRESSIONS_H
|
|
@ -0,0 +1,13 @@
|
|||
#include "nvim/viml/parser/parser.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "viml/parser/parser.c.generated.h"
|
||||
#endif
|
||||
|
||||
|
||||
void parser_simple_get_line(void *cookie, ParserLine *ret_pline)
|
||||
{
|
||||
ParserLine **plines_p = (ParserLine **)cookie;
|
||||
*ret_pline = **plines_p;
|
||||
(*plines_p)++;
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
#ifndef NVIM_VIML_PARSER_PARSER_H
|
||||
#define NVIM_VIML_PARSER_PARSER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "nvim/lib/kvec.h"
|
||||
#include "nvim/func_attr.h"
|
||||
#include "nvim/mbyte.h"
|
||||
#include "nvim/memory.h"
|
||||
|
||||
/// One parsed line
|
||||
typedef struct {
|
||||
const char *data; ///< Parsed line pointer
|
||||
size_t size; ///< Parsed line size
|
||||
bool allocated; ///< True if line may be freed.
|
||||
} ParserLine;
|
||||
|
||||
/// Line getter type for parser
|
||||
///
|
||||
/// Line getter must return {NULL, 0} for EOF.
|
||||
typedef void (*ParserLineGetter)(void *cookie, ParserLine *ret_pline);
|
||||
|
||||
/// Parser position in the input
|
||||
typedef struct {
|
||||
size_t line; ///< Line index in ParserInputReader.lines.
|
||||
size_t col; ///< Byte index in the line.
|
||||
} ParserPosition;
|
||||
|
||||
/// Parser state item.
|
||||
typedef struct {
|
||||
enum {
|
||||
kPTopStateParsingCommand = 0,
|
||||
kPTopStateParsingExpression,
|
||||
} type;
|
||||
union {
|
||||
struct {
|
||||
enum {
|
||||
kExprUnknown = 0,
|
||||
} type;
|
||||
} expr;
|
||||
} data;
|
||||
} ParserStateItem;
|
||||
|
||||
/// Structure defining input reader
|
||||
typedef struct {
|
||||
/// Function used to get next line.
|
||||
ParserLineGetter get_line;
|
||||
/// Data for get_line function.
|
||||
void *cookie;
|
||||
/// All lines obtained by get_line.
|
||||
kvec_withinit_t(ParserLine, 4) lines;
|
||||
/// Conversion, for :scriptencoding.
|
||||
vimconv_T conv;
|
||||
} ParserInputReader;
|
||||
|
||||
/// Highlighted region definition
|
||||
///
|
||||
/// Note: one chunk may highlight only one line.
|
||||
typedef struct {
|
||||
ParserPosition start; ///< Start of the highlight: line and column.
|
||||
size_t end_col; ///< End column, points to the start of the next character.
|
||||
const char *group; ///< Highlight group.
|
||||
} ParserHighlightChunk;
|
||||
|
||||
/// Highlighting defined by a parser
|
||||
typedef kvec_withinit_t(ParserHighlightChunk, 16) ParserHighlight;
|
||||
|
||||
/// Structure defining parser state
|
||||
typedef struct {
|
||||
/// Line reader.
|
||||
ParserInputReader reader;
|
||||
/// Position up to which input was parsed.
|
||||
ParserPosition pos;
|
||||
/// Parser state stack.
|
||||
kvec_withinit_t(ParserStateItem, 16) stack;
|
||||
/// Highlighting support.
|
||||
ParserHighlight *colors;
|
||||
/// True if line continuation can be used.
|
||||
bool can_continuate;
|
||||
} ParserState;
|
||||
|
||||
static inline void viml_parser_init(
|
||||
ParserState *const ret_pstate,
|
||||
const ParserLineGetter get_line, void *const cookie,
|
||||
ParserHighlight *const colors)
|
||||
REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1, 2);
|
||||
|
||||
/// Initialize a new parser state instance
|
||||
///
|
||||
/// @param[out] ret_pstate Parser state to initialize.
|
||||
/// @param[in] get_line Line getter function.
|
||||
/// @param[in] cookie Argument for the get_line function.
|
||||
/// @param[in] colors Where to save highlighting. May be NULL if it is not
|
||||
/// needed.
|
||||
static inline void viml_parser_init(
|
||||
ParserState *const ret_pstate,
|
||||
const ParserLineGetter get_line, void *const cookie,
|
||||
ParserHighlight *const colors)
|
||||
{
|
||||
*ret_pstate = (ParserState) {
|
||||
.reader = {
|
||||
.get_line = get_line,
|
||||
.cookie = cookie,
|
||||
.conv = MBYTE_NONE_CONV,
|
||||
},
|
||||
.pos = { 0, 0 },
|
||||
.colors = colors,
|
||||
.can_continuate = false,
|
||||
};
|
||||
kvi_init(ret_pstate->reader.lines);
|
||||
kvi_init(ret_pstate->stack);
|
||||
}
|
||||
|
||||
static inline void viml_parser_destroy(ParserState *const pstate)
|
||||
REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE;
|
||||
|
||||
/// Free all memory allocated by the parser on heap
|
||||
///
|
||||
/// @param pstate Parser state to free.
|
||||
static inline void viml_parser_destroy(ParserState *const pstate)
|
||||
{
|
||||
for (size_t i = 0; i < kv_size(pstate->reader.lines); i++) {
|
||||
ParserLine pline = kv_A(pstate->reader.lines, i);
|
||||
if (pline.allocated) {
|
||||
xfree((void *)pline.data);
|
||||
}
|
||||
}
|
||||
kvi_destroy(pstate->reader.lines);
|
||||
kvi_destroy(pstate->stack);
|
||||
}
|
||||
|
||||
static inline void viml_preader_get_line(ParserInputReader *const preader,
|
||||
ParserLine *const ret_pline)
|
||||
REAL_FATTR_NONNULL_ALL;
|
||||
|
||||
/// Get one line from ParserInputReader
|
||||
static inline void viml_preader_get_line(ParserInputReader *const preader,
|
||||
ParserLine *const ret_pline)
|
||||
{
|
||||
ParserLine pline;
|
||||
preader->get_line(preader->cookie, &pline);
|
||||
if (preader->conv.vc_type != CONV_NONE && pline.size) {
|
||||
ParserLine cpline = {
|
||||
.allocated = true,
|
||||
.size = pline.size,
|
||||
};
|
||||
cpline.data = (char *)string_convert(&preader->conv,
|
||||
(char_u *)pline.data,
|
||||
&cpline.size);
|
||||
if (pline.allocated) {
|
||||
xfree((void *)pline.data);
|
||||
}
|
||||
pline = cpline;
|
||||
}
|
||||
kvi_push(preader->lines, pline);
|
||||
*ret_pline = pline;
|
||||
}
|
||||
|
||||
static inline bool viml_parser_get_remaining_line(ParserState *const pstate,
|
||||
ParserLine *const ret_pline)
|
||||
REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL;
|
||||
|
||||
/// Get currently parsed line, shifted to pstate->pos.col
|
||||
///
|
||||
/// @param pstate Parser state to operate on.
|
||||
///
|
||||
/// @return True if there is a line, false in case of EOF.
|
||||
static inline bool viml_parser_get_remaining_line(ParserState *const pstate,
|
||||
ParserLine *const ret_pline)
|
||||
{
|
||||
const size_t num_lines = kv_size(pstate->reader.lines);
|
||||
if (pstate->pos.line == num_lines) {
|
||||
viml_preader_get_line(&pstate->reader, ret_pline);
|
||||
} else {
|
||||
*ret_pline = kv_last(pstate->reader.lines);
|
||||
}
|
||||
assert(pstate->pos.line == kv_size(pstate->reader.lines) - 1);
|
||||
if (ret_pline->data != NULL) {
|
||||
ret_pline->data += pstate->pos.col;
|
||||
ret_pline->size -= pstate->pos.col;
|
||||
}
|
||||
return ret_pline->data != NULL;
|
||||
}
|
||||
|
||||
static inline void viml_parser_advance(ParserState *const pstate,
|
||||
const size_t len)
|
||||
REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL;
|
||||
|
||||
/// Advance position by a given number of bytes
|
||||
///
|
||||
/// At maximum advances to the next line.
|
||||
///
|
||||
/// @param pstate Parser state to advance.
|
||||
/// @param[in] len Number of bytes to advance.
|
||||
static inline void viml_parser_advance(ParserState *const pstate,
|
||||
const size_t len)
|
||||
{
|
||||
assert(pstate->pos.line == kv_size(pstate->reader.lines) - 1);
|
||||
const ParserLine pline = kv_last(pstate->reader.lines);
|
||||
if (pstate->pos.col + len >= pline.size) {
|
||||
pstate->pos.line++;
|
||||
pstate->pos.col = 0;
|
||||
} else {
|
||||
pstate->pos.col += len;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void viml_parser_highlight(ParserState *const pstate,
|
||||
const ParserPosition start,
|
||||
const size_t end_col,
|
||||
const char *const group)
|
||||
REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL;
|
||||
|
||||
/// Record highlighting of some region of text
|
||||
///
|
||||
/// @param pstate Parser state to work with.
|
||||
/// @param[in] start Start position of the highlight.
|
||||
/// @param[in] len Highlighting chunk length.
|
||||
/// @param[in] group Highlight group.
|
||||
static inline void viml_parser_highlight(ParserState *const pstate,
|
||||
const ParserPosition start,
|
||||
const size_t len,
|
||||
const char *const group)
|
||||
{
|
||||
if (pstate->colors == NULL || len == 0) {
|
||||
return;
|
||||
}
|
||||
assert(kv_size(*pstate->colors) == 0
|
||||
|| kv_Z(*pstate->colors, 0).start.line < start.line
|
||||
|| kv_Z(*pstate->colors, 0).end_col <= start.col);
|
||||
kvi_push(*pstate->colors, ((ParserHighlightChunk) {
|
||||
.start = start,
|
||||
.end_col = start.col + len,
|
||||
.group = group,
|
||||
}));
|
||||
}
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "viml/parser/parser.h.generated.h"
|
||||
#endif
|
||||
|
||||
#endif // NVIM_VIML_PARSER_PARSER_H
|
|
@ -341,3 +341,9 @@ disables tracing (the fastest, but you get no data if tests crash and there was
|
|||
no core dump generated), `1` or empty/undefined leaves only C function cals and
|
||||
returns in the trace (faster then recording everything), `2` records all
|
||||
function calls, returns and lua source lines exuecuted.
|
||||
|
||||
`NVIM_TEST_TRACE_ON_ERROR` (U) (1): makes unit tests yield trace on error in
|
||||
addition to regular error message.
|
||||
|
||||
`NVIM_TEST_MAXTRACE` (U) (N): specifies maximum number of trace lines to keep.
|
||||
Default is 1024.
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
local helpers = require('test.functional.helpers')(after_each)
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local global_helpers = require('test.helpers')
|
||||
|
||||
local NIL = helpers.NIL
|
||||
local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq
|
||||
local ok, nvim_async, feed = helpers.ok, helpers.nvim_async, helpers.feed
|
||||
|
@ -10,6 +12,10 @@ local request = helpers.request
|
|||
local meth_pcall = helpers.meth_pcall
|
||||
local command = helpers.command
|
||||
|
||||
local intchar2lua = global_helpers.intchar2lua
|
||||
local format_string = global_helpers.format_string
|
||||
local mergedicts_copy = global_helpers.mergedicts_copy
|
||||
|
||||
describe('api', function()
|
||||
before_each(clear)
|
||||
|
||||
|
@ -710,4 +716,197 @@ describe('api', function()
|
|||
ok(err:match(': Wrong type for argument 1, expecting String') ~= nil)
|
||||
end)
|
||||
|
||||
describe('nvim_parse_expression', function()
|
||||
before_each(function()
|
||||
meths.set_option('isident', '')
|
||||
end)
|
||||
local function simplify_east_api_node(line, east_api_node)
|
||||
if east_api_node == NIL then
|
||||
return nil
|
||||
end
|
||||
if east_api_node.children then
|
||||
for k, v in pairs(east_api_node.children) do
|
||||
east_api_node.children[k] = simplify_east_api_node(line, v)
|
||||
end
|
||||
end
|
||||
local typ = east_api_node.type
|
||||
if typ == 'Register' then
|
||||
typ = typ .. ('(name=%s)'):format(
|
||||
tostring(intchar2lua(east_api_node.name)))
|
||||
east_api_node.name = nil
|
||||
elseif typ == 'PlainIdentifier' then
|
||||
typ = typ .. ('(scope=%s,ident=%s)'):format(
|
||||
tostring(intchar2lua(east_api_node.scope)), east_api_node.ident)
|
||||
east_api_node.scope = nil
|
||||
east_api_node.ident = nil
|
||||
elseif typ == 'PlainKey' then
|
||||
typ = typ .. ('(key=%s)'):format(east_api_node.ident)
|
||||
east_api_node.ident = nil
|
||||
elseif typ == 'Comparison' then
|
||||
typ = typ .. ('(type=%s,inv=%u,ccs=%s)'):format(
|
||||
east_api_node.cmp_type, east_api_node.invert and 1 or 0,
|
||||
east_api_node.ccs_strategy)
|
||||
east_api_node.ccs_strategy = nil
|
||||
east_api_node.cmp_type = nil
|
||||
east_api_node.invert = nil
|
||||
elseif typ == 'Integer' then
|
||||
typ = typ .. ('(val=%u)'):format(east_api_node.ivalue)
|
||||
east_api_node.ivalue = nil
|
||||
elseif typ == 'Float' then
|
||||
typ = typ .. format_string('(val=%e)', east_api_node.fvalue)
|
||||
east_api_node.fvalue = nil
|
||||
elseif typ == 'SingleQuotedString' or typ == 'DoubleQuotedString' then
|
||||
typ = format_string('%s(val=%q)', typ, east_api_node.svalue)
|
||||
east_api_node.svalue = nil
|
||||
elseif typ == 'Option' then
|
||||
typ = ('%s(scope=%s,ident=%s)'):format(
|
||||
typ,
|
||||
tostring(intchar2lua(east_api_node.scope)),
|
||||
east_api_node.ident)
|
||||
east_api_node.ident = nil
|
||||
east_api_node.scope = nil
|
||||
elseif typ == 'Environment' then
|
||||
typ = ('%s(ident=%s)'):format(typ, east_api_node.ident)
|
||||
east_api_node.ident = nil
|
||||
elseif typ == 'Assignment' then
|
||||
local aug = east_api_node.augmentation
|
||||
if aug == '' then aug = 'Plain' end
|
||||
typ = ('%s(%s)'):format(typ, aug)
|
||||
east_api_node.augmentation = nil
|
||||
end
|
||||
typ = ('%s:%u:%u:%s'):format(
|
||||
typ, east_api_node.start[1], east_api_node.start[2],
|
||||
line:sub(east_api_node.start[2] + 1,
|
||||
east_api_node.start[2] + 1 + east_api_node.len - 1))
|
||||
assert(east_api_node.start[2] + east_api_node.len - 1 <= #line)
|
||||
for k, _ in pairs(east_api_node.start) do
|
||||
assert(({true, true})[k])
|
||||
end
|
||||
east_api_node.start = nil
|
||||
east_api_node.type = nil
|
||||
east_api_node.len = nil
|
||||
local can_simplify = true
|
||||
for _, _ in pairs(east_api_node) do
|
||||
if can_simplify then can_simplify = false end
|
||||
end
|
||||
if can_simplify then
|
||||
return typ
|
||||
else
|
||||
east_api_node[1] = typ
|
||||
return east_api_node
|
||||
end
|
||||
end
|
||||
local function simplify_east_api(line, east_api)
|
||||
if east_api.error then
|
||||
east_api.err = east_api.error
|
||||
east_api.error = nil
|
||||
east_api.err.msg = east_api.err.message
|
||||
east_api.err.message = nil
|
||||
end
|
||||
if east_api.ast then
|
||||
east_api.ast = {simplify_east_api_node(line, east_api.ast)}
|
||||
if #east_api.ast == 0 then
|
||||
east_api.ast = nil
|
||||
end
|
||||
end
|
||||
if east_api.len == #line then
|
||||
east_api.len = nil
|
||||
end
|
||||
return east_api
|
||||
end
|
||||
local function simplify_east_hl(line, east_hl)
|
||||
for i, v in ipairs(east_hl) do
|
||||
east_hl[i] = ('%s:%u:%u:%s'):format(
|
||||
v[4],
|
||||
v[1],
|
||||
v[2],
|
||||
line:sub(v[2] + 1, v[3]))
|
||||
end
|
||||
return east_hl
|
||||
end
|
||||
local FLAGS_TO_STR = {
|
||||
[0] = "",
|
||||
[1] = "m",
|
||||
[2] = "E",
|
||||
[3] = "mE",
|
||||
[4] = "l",
|
||||
[5] = "lm",
|
||||
[6] = "lE",
|
||||
[7] = "lmE",
|
||||
}
|
||||
local function _check_parsing(opts, str, exp_ast, exp_highlighting_fs,
|
||||
nz_flags_exps)
|
||||
if type(str) ~= 'string' then
|
||||
return
|
||||
end
|
||||
local zflags = opts.flags[1]
|
||||
nz_flags_exps = nz_flags_exps or {}
|
||||
for _, flags in ipairs(opts.flags) do
|
||||
local err, msg = pcall(function()
|
||||
local east_api = meths.parse_expression(str, FLAGS_TO_STR[flags], true)
|
||||
local east_hl = east_api.highlight
|
||||
east_api.highlight = nil
|
||||
local ast = simplify_east_api(str, east_api)
|
||||
local hls = simplify_east_hl(str, east_hl)
|
||||
local exps = {
|
||||
ast = exp_ast,
|
||||
hl_fs = exp_highlighting_fs,
|
||||
}
|
||||
local add_exps = nz_flags_exps[flags]
|
||||
if not add_exps and flags == 3 + zflags then
|
||||
add_exps = nz_flags_exps[1 + zflags] or nz_flags_exps[2 + zflags]
|
||||
end
|
||||
if add_exps then
|
||||
if add_exps.ast then
|
||||
exps.ast = mergedicts_copy(exps.ast, add_exps.ast)
|
||||
end
|
||||
if add_exps.hl_fs then
|
||||
exps.hl_fs = mergedicts_copy(exps.hl_fs, add_exps.hl_fs)
|
||||
end
|
||||
end
|
||||
eq(exps.ast, ast)
|
||||
if exp_highlighting_fs then
|
||||
local exp_highlighting = {}
|
||||
local next_col = 0
|
||||
for i, h in ipairs(exps.hl_fs) do
|
||||
exp_highlighting[i], next_col = h(next_col)
|
||||
end
|
||||
eq(exp_highlighting, hls)
|
||||
end
|
||||
end)
|
||||
if not err then
|
||||
msg = format_string('Error while processing test (%r, %s):\n%s',
|
||||
str, FLAGS_TO_STR[flags], msg)
|
||||
error(msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
local function hl(group, str, shift)
|
||||
return function(next_col)
|
||||
local col = next_col + (shift or 0)
|
||||
return (('%s:%u:%u:%s'):format(
|
||||
'Nvim' .. group,
|
||||
0,
|
||||
col,
|
||||
str)), (col + #str)
|
||||
end
|
||||
end
|
||||
local function fmtn(typ, args, rest)
|
||||
if (typ == 'UnknownFigure'
|
||||
or typ == 'DictLiteral'
|
||||
or typ == 'CurlyBracesIdentifier'
|
||||
or typ == 'Lambda') then
|
||||
return ('%s%s'):format(typ, rest)
|
||||
elseif typ == 'DoubleQuotedString' or typ == 'SingleQuotedString' then
|
||||
if args:sub(-4) == 'NULL' then
|
||||
args = args:sub(1, -5) .. '""'
|
||||
end
|
||||
return ('%s(%s)%s'):format(typ, args, rest)
|
||||
end
|
||||
end
|
||||
assert:set_parameter('TableFormatLevel', 1000000)
|
||||
require('test.unit.viml.expressions.parser_tests')(
|
||||
it, _check_parsing, hl, fmtn)
|
||||
end)
|
||||
|
||||
end)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
local helpers = require("test.functional.helpers")(after_each)
|
||||
|
||||
local eq = helpers.eq
|
||||
local feed = helpers.feed
|
||||
local meths = helpers.meths
|
||||
local clear = helpers.clear
|
||||
local command = helpers.command
|
||||
|
||||
describe(':*map', function()
|
||||
before_each(clear)
|
||||
|
||||
it('are not affected by &isident', function()
|
||||
meths.set_var('counter', 0)
|
||||
command('nnoremap <C-x> :let counter+=1<CR>')
|
||||
meths.set_option('isident', ('%u'):format(('>'):byte()))
|
||||
command('nnoremap <C-y> :let counter+=1<CR>')
|
||||
-- &isident used to disable keycode parsing here as well
|
||||
feed('\24\25<C-x><C-y>')
|
||||
eq(4, meths.get_var('counter'))
|
||||
end)
|
||||
end)
|
|
@ -144,7 +144,13 @@ before_each(function()
|
|||
EOB={bold = true, foreground = Screen.colors.Blue1},
|
||||
ERR={foreground = Screen.colors.Grey100, background = Screen.colors.Red},
|
||||
SK={foreground = Screen.colors.Blue},
|
||||
PE={bold = true, foreground = Screen.colors.SeaGreen4}
|
||||
PE={bold = true, foreground = Screen.colors.SeaGreen4},
|
||||
NUM={foreground = Screen.colors.Blue2},
|
||||
NPAR={foreground = Screen.colors.Yellow},
|
||||
SQ={foreground = Screen.colors.Blue3},
|
||||
SB={foreground = Screen.colors.Blue4},
|
||||
E={foreground = Screen.colors.Red, background = Screen.colors.Blue},
|
||||
M={bold = true},
|
||||
})
|
||||
end)
|
||||
|
||||
|
@ -863,7 +869,10 @@ describe('Ex commands coloring support', function()
|
|||
end)
|
||||
describe('Expressions coloring support', function()
|
||||
it('works', function()
|
||||
meths.set_var('Nvim_color_expr', 'RainBowParens')
|
||||
meths.command('hi clear NvimNumber')
|
||||
meths.command('hi clear NvimNestingParenthesis')
|
||||
meths.command('hi NvimNumber guifg=Blue2')
|
||||
meths.command('hi NvimNestingParenthesis guifg=Yellow')
|
||||
feed(':echo <C-r>=(((1)))')
|
||||
screen:expect([[
|
||||
|
|
||||
|
@ -873,21 +882,103 @@ describe('Expressions coloring support', function()
|
|||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
={RBP1:(}{RBP2:(}{RBP3:(}1{RBP3:)}{RBP2:)}{RBP1:)}^ |
|
||||
={NPAR:(((}{NUM:1}{NPAR:)))}^ |
|
||||
]])
|
||||
end)
|
||||
it('errors out when failing to get callback', function()
|
||||
it('does not use Nvim_color_expr', function()
|
||||
meths.set_var('Nvim_color_expr', 42)
|
||||
-- Used to error out due to failing to get callback.
|
||||
meths.command('hi clear NvimNumber')
|
||||
meths.command('hi NvimNumber guifg=Blue2')
|
||||
feed(':<C-r>=1')
|
||||
screen:expect([[
|
||||
|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
= |
|
||||
{ERR:E5409: Unable to get g:Nvim_color_expr c}|
|
||||
{ERR:allback: Vim:E6000: Argument is not a fu}|
|
||||
{ERR:nction or function name} |
|
||||
=1^ |
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
={NUM:1}^ |
|
||||
]])
|
||||
end)
|
||||
it('works correctly with non-ASCII and control characters', function()
|
||||
meths.command('hi clear NvimStringBody')
|
||||
meths.command('hi clear NvimStringQuote')
|
||||
meths.command('hi clear NvimInvalid')
|
||||
meths.command('hi NvimStringQuote guifg=Blue3')
|
||||
meths.command('hi NvimStringBody guifg=Blue4')
|
||||
meths.command('hi NvimInvalid guifg=Red guibg=Blue')
|
||||
feed('i<C-r>="«»"«»')
|
||||
screen:expect([[
|
||||
|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
={SQ:"}{SB:«»}{SQ:"}{E:«»}^ |
|
||||
]])
|
||||
feed('<C-c>')
|
||||
screen:expect([[
|
||||
^ |
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{M:-- INSERT --} |
|
||||
]])
|
||||
feed('<Esc>')
|
||||
screen:expect([[
|
||||
^ |
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
|
|
||||
]])
|
||||
feed(':<C-\\>e"<C-v><C-x>"<C-v><C-x>')
|
||||
-- TODO(ZyX-I): Parser highlighting should not override special character
|
||||
-- highlighting.
|
||||
screen:expect([[
|
||||
|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
={SQ:"}{SB:^X}{SQ:"}{ERR:^X}^ |
|
||||
]])
|
||||
feed('<C-c>')
|
||||
screen:expect([[
|
||||
|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
:^ |
|
||||
]])
|
||||
funcs.setreg('a', {'\192'})
|
||||
feed('<C-r>="<C-r><C-r>a"<C-r><C-r>a"foo"')
|
||||
-- TODO(ZyX-I): Parser highlighting should not override special character
|
||||
-- highlighting.
|
||||
screen:expect([[
|
||||
|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
{EOB:~ }|
|
||||
={SQ:"}{SB:<c0>}{SQ:"}{E:<c0>"}{SB:foo}{E:"}^ |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -239,7 +239,11 @@ describe('external cmdline', function()
|
|||
prompt = "",
|
||||
special = {'"', true},
|
||||
},{
|
||||
content = { { {}, "1+2" } },
|
||||
content = {
|
||||
{ {}, "1" },
|
||||
{ {}, "+" },
|
||||
{ {}, "2" },
|
||||
},
|
||||
firstc = "=",
|
||||
indent = 0,
|
||||
pos = 3,
|
||||
|
@ -316,7 +320,7 @@ describe('external cmdline', function()
|
|||
pos = 0,
|
||||
prompt = "",
|
||||
}}, cmdline)
|
||||
eq({{{{}, 'function Foo()'}}}, block)
|
||||
eq({ { { {}, 'function Foo()'} } }, block)
|
||||
end)
|
||||
|
||||
feed('line1<cr>')
|
||||
|
@ -327,8 +331,8 @@ describe('external cmdline', function()
|
|||
{1:~ }|
|
||||
|
|
||||
]], nil, nil, function()
|
||||
eq({{{{}, 'function Foo()'}},
|
||||
{{{}, ' line1'}}}, block)
|
||||
eq({ { { {}, 'function Foo()'} },
|
||||
{ { {}, ' line1'} } }, block)
|
||||
end)
|
||||
|
||||
block = {}
|
||||
|
@ -340,8 +344,8 @@ describe('external cmdline', function()
|
|||
{1:~ }|
|
||||
^ |
|
||||
]], nil, nil, function()
|
||||
eq({{{{}, 'function Foo()'}},
|
||||
{{{}, ' line1'}}}, block)
|
||||
eq({ { { {}, 'function Foo()'} },
|
||||
{ { {}, ' line1'} } }, block)
|
||||
end)
|
||||
|
||||
|
||||
|
|
245
test/helpers.lua
245
test/helpers.lua
|
@ -251,6 +251,9 @@ local function which(exe)
|
|||
end
|
||||
|
||||
local function shallowcopy(orig)
|
||||
if type(orig) ~= 'table' then
|
||||
return orig
|
||||
end
|
||||
local copy = {}
|
||||
for orig_key, orig_value in pairs(orig) do
|
||||
copy[orig_key] = orig_value
|
||||
|
@ -258,6 +261,92 @@ local function shallowcopy(orig)
|
|||
return copy
|
||||
end
|
||||
|
||||
local deepcopy
|
||||
|
||||
local function id(v)
|
||||
return v
|
||||
end
|
||||
|
||||
local deepcopy_funcs = {
|
||||
table = function(orig)
|
||||
local copy = {}
|
||||
for k, v in pairs(orig) do
|
||||
copy[deepcopy(k)] = deepcopy(v)
|
||||
end
|
||||
return copy
|
||||
end,
|
||||
number = id,
|
||||
string = id,
|
||||
['nil'] = id,
|
||||
boolean = id,
|
||||
}
|
||||
|
||||
deepcopy = function(orig)
|
||||
return deepcopy_funcs[type(orig)](orig)
|
||||
end
|
||||
|
||||
local REMOVE_THIS = {}
|
||||
|
||||
local function mergedicts_copy(d1, d2)
|
||||
local ret = shallowcopy(d1)
|
||||
for k, v in pairs(d2) do
|
||||
if d2[k] == REMOVE_THIS then
|
||||
ret[k] = nil
|
||||
elseif type(d1[k]) == 'table' and type(v) == 'table' then
|
||||
ret[k] = mergedicts_copy(d1[k], v)
|
||||
else
|
||||
ret[k] = v
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
-- dictdiff: find a diff so that mergedicts_copy(d1, diff) is equal to d2
|
||||
--
|
||||
-- Note: does not do copies of d2 values used.
|
||||
local function dictdiff(d1, d2)
|
||||
local ret = {}
|
||||
local hasdiff = false
|
||||
for k, v in pairs(d1) do
|
||||
if d2[k] == nil then
|
||||
hasdiff = true
|
||||
ret[k] = REMOVE_THIS
|
||||
elseif type(v) == type(d2[k]) then
|
||||
if type(v) == 'table' then
|
||||
local subdiff = dictdiff(v, d2[k])
|
||||
if subdiff ~= nil then
|
||||
hasdiff = true
|
||||
ret[k] = subdiff
|
||||
end
|
||||
elseif v ~= d2[k] then
|
||||
ret[k] = d2[k]
|
||||
hasdiff = true
|
||||
end
|
||||
else
|
||||
ret[k] = d2[k]
|
||||
hasdiff = true
|
||||
end
|
||||
end
|
||||
for k, v in pairs(d2) do
|
||||
if d1[k] == nil then
|
||||
ret[k] = shallowcopy(v)
|
||||
hasdiff = true
|
||||
end
|
||||
end
|
||||
if hasdiff then
|
||||
return ret
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function updated(d, d2)
|
||||
for k, v in pairs(d2) do
|
||||
d[k] = v
|
||||
end
|
||||
return d
|
||||
end
|
||||
|
||||
local function concat_tables(...)
|
||||
local ret = {}
|
||||
for i = 1, select('#', ...) do
|
||||
|
@ -294,6 +383,152 @@ local function dedent(str, leave_indent)
|
|||
return str
|
||||
end
|
||||
|
||||
local function format_float(v)
|
||||
-- On windows exponent appears to have three digits and not two
|
||||
local ret = ('%.6e'):format(v)
|
||||
local l, f, es, e = ret:match('^(%-?%d)%.(%d+)e([+%-])0*(%d%d+)$')
|
||||
return l .. '.' .. f .. 'e' .. es .. e
|
||||
end
|
||||
|
||||
local SUBTBL = {
|
||||
'\\000', '\\001', '\\002', '\\003', '\\004',
|
||||
'\\005', '\\006', '\\007', '\\008', '\\t',
|
||||
'\\n', '\\011', '\\012', '\\r', '\\014',
|
||||
'\\015', '\\016', '\\017', '\\018', '\\019',
|
||||
'\\020', '\\021', '\\022', '\\023', '\\024',
|
||||
'\\025', '\\026', '\\027', '\\028', '\\029',
|
||||
'\\030', '\\031',
|
||||
}
|
||||
|
||||
local format_luav
|
||||
|
||||
format_luav = function(v, indent, opts)
|
||||
opts = opts or {}
|
||||
local linesep = '\n'
|
||||
local next_indent_arg = nil
|
||||
local indent_shift = opts.indent_shift or ' '
|
||||
local next_indent
|
||||
local nl = '\n'
|
||||
if indent == nil then
|
||||
indent = ''
|
||||
linesep = ''
|
||||
next_indent = ''
|
||||
nl = ' '
|
||||
else
|
||||
next_indent_arg = indent .. indent_shift
|
||||
next_indent = indent .. indent_shift
|
||||
end
|
||||
local ret = ''
|
||||
if type(v) == 'string' then
|
||||
if opts.literal_strings then
|
||||
ret = v
|
||||
else
|
||||
ret = tostring(v):gsub('[\'\\]', '\\%0'):gsub(
|
||||
'[%z\1-\31]', function(match)
|
||||
return SUBTBL[match:byte() + 1]
|
||||
end)
|
||||
ret = '\'' .. ret .. '\''
|
||||
end
|
||||
elseif type(v) == 'table' then
|
||||
if v == REMOVE_THIS then
|
||||
ret = 'REMOVE_THIS'
|
||||
else
|
||||
local processed_keys = {}
|
||||
ret = '{' .. linesep
|
||||
local non_empty = false
|
||||
for i, subv in ipairs(v) do
|
||||
ret = ('%s%s%s,%s'):format(ret, next_indent,
|
||||
format_luav(subv, next_indent_arg, opts), nl)
|
||||
processed_keys[i] = true
|
||||
non_empty = true
|
||||
end
|
||||
for k, subv in pairs(v) do
|
||||
if not processed_keys[k] then
|
||||
if type(k) == 'string' and k:match('^[a-zA-Z_][a-zA-Z0-9_]*$') then
|
||||
ret = ret .. next_indent .. k .. ' = '
|
||||
else
|
||||
ret = ('%s%s[%s] = '):format(ret, next_indent,
|
||||
format_luav(k, nil, opts))
|
||||
end
|
||||
ret = ret .. format_luav(subv, next_indent_arg, opts) .. ',' .. nl
|
||||
non_empty = true
|
||||
end
|
||||
end
|
||||
if nl == ' ' and non_empty then
|
||||
ret = ret:sub(1, -3)
|
||||
end
|
||||
ret = ret .. indent .. '}'
|
||||
end
|
||||
elseif type(v) == 'number' then
|
||||
if v % 1 == 0 then
|
||||
ret = ('%d'):format(v)
|
||||
else
|
||||
ret = format_float(v)
|
||||
end
|
||||
elseif type(v) == 'nil' then
|
||||
ret = 'nil'
|
||||
else
|
||||
print(type(v))
|
||||
-- Not implemented yet
|
||||
assert(false)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
local function format_string(fmt, ...)
|
||||
local i = 0
|
||||
local args = {...}
|
||||
local function getarg()
|
||||
i = i + 1
|
||||
return args[i]
|
||||
end
|
||||
local ret = fmt:gsub('%%[0-9*]*%.?[0-9*]*[cdEefgGiouXxqsr%%]', function(match)
|
||||
local subfmt = match:gsub('%*', function()
|
||||
return tostring(getarg())
|
||||
end)
|
||||
local arg = nil
|
||||
if subfmt:sub(-1) ~= '%' then
|
||||
arg = getarg()
|
||||
end
|
||||
if subfmt:sub(-1) == 'r' then
|
||||
-- %r is like %q, but it is supposed to single-quote strings and not
|
||||
-- double-quote them, and also work not only for strings.
|
||||
subfmt = subfmt:sub(1, -2) .. 's'
|
||||
arg = format_luav(arg)
|
||||
end
|
||||
if subfmt == '%e' then
|
||||
return format_float(arg)
|
||||
else
|
||||
return subfmt:format(arg)
|
||||
end
|
||||
end)
|
||||
return ret
|
||||
end
|
||||
|
||||
local function intchar2lua(ch)
|
||||
ch = tonumber(ch)
|
||||
return (20 <= ch and ch < 127) and ('%c'):format(ch) or ch
|
||||
end
|
||||
|
||||
local fixtbl_metatable = {
|
||||
__newindex = function()
|
||||
assert(false)
|
||||
end,
|
||||
}
|
||||
|
||||
local function fixtbl(tbl)
|
||||
return setmetatable(tbl, fixtbl_metatable)
|
||||
end
|
||||
|
||||
local function fixtbl_rec(tbl)
|
||||
for _, v in pairs(tbl) do
|
||||
if type(v) == 'table' then
|
||||
fixtbl_rec(v)
|
||||
end
|
||||
end
|
||||
return fixtbl(tbl)
|
||||
end
|
||||
|
||||
return {
|
||||
eq = eq,
|
||||
neq = neq,
|
||||
|
@ -308,6 +543,16 @@ return {
|
|||
hasenv = hasenv,
|
||||
which = which,
|
||||
shallowcopy = shallowcopy,
|
||||
deepcopy = deepcopy,
|
||||
mergedicts_copy = mergedicts_copy,
|
||||
dictdiff = dictdiff,
|
||||
REMOVE_THIS = REMOVE_THIS,
|
||||
concat_tables = concat_tables,
|
||||
dedent = dedent,
|
||||
format_luav = format_luav,
|
||||
format_string = format_string,
|
||||
intchar2lua = intchar2lua,
|
||||
updated = updated,
|
||||
fixtbl = fixtbl,
|
||||
fixtbl_rec = fixtbl_rec,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
#include <stdbool.h>
|
||||
|
||||
#include "nvim/ascii.h"
|
||||
#include "nvim/macros.h"
|
||||
#include "nvim/charset.h"
|
||||
#include "nvim/eval/typval.h"
|
||||
#include "nvim/vim.h"
|
||||
|
||||
int hex2nr(int c)
|
||||
{
|
||||
if ((c >= 'a') && (c <= 'f')) {
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
|
||||
if ((c >= 'A') && (c <= 'F')) {
|
||||
return c - 'A' + 10;
|
||||
}
|
||||
return c - '0';
|
||||
}
|
||||
|
||||
void vim_str2nr(const char_u *const start, int *const prep, int *const len,
|
||||
const int what, varnumber_T *const nptr,
|
||||
uvarnumber_T *const unptr, const int maxlen)
|
||||
{
|
||||
const char *ptr = (const char *)start;
|
||||
#define STRING_ENDED(ptr) \
|
||||
(!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen))
|
||||
int pre = 0; // default is decimal
|
||||
const bool negative = (ptr[0] == '-');
|
||||
uvarnumber_T un = 0;
|
||||
|
||||
if (negative) {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
if (what & STR2NR_FORCE) {
|
||||
// When forcing main consideration is skipping the prefix. Octal and decimal
|
||||
// numbers have no prefixes to skip. pre is not set.
|
||||
switch ((unsigned)what & (~(unsigned)STR2NR_FORCE)) {
|
||||
case STR2NR_HEX: {
|
||||
if (!STRING_ENDED(ptr + 2)
|
||||
&& ptr[0] == '0'
|
||||
&& (ptr[1] == 'x' || ptr[1] == 'X')
|
||||
&& ascii_isxdigit(ptr[2])) {
|
||||
ptr += 2;
|
||||
}
|
||||
goto vim_str2nr_hex;
|
||||
}
|
||||
case STR2NR_BIN: {
|
||||
if (!STRING_ENDED(ptr + 2)
|
||||
&& ptr[0] == '0'
|
||||
&& (ptr[1] == 'b' || ptr[1] == 'B')
|
||||
&& ascii_isbdigit(ptr[2])) {
|
||||
ptr += 2;
|
||||
}
|
||||
goto vim_str2nr_bin;
|
||||
}
|
||||
case STR2NR_OCT: {
|
||||
goto vim_str2nr_oct;
|
||||
}
|
||||
case 0: {
|
||||
goto vim_str2nr_dec;
|
||||
}
|
||||
default: {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
} else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN))
|
||||
&& !STRING_ENDED(ptr + 1)
|
||||
&& ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') {
|
||||
pre = ptr[1];
|
||||
// Detect hexadecimal: 0x or 0X follwed by hex digit
|
||||
if ((what & STR2NR_HEX)
|
||||
&& !STRING_ENDED(ptr + 2)
|
||||
&& (pre == 'X' || pre == 'x')
|
||||
&& ascii_isxdigit(ptr[2])) {
|
||||
ptr += 2;
|
||||
goto vim_str2nr_hex;
|
||||
}
|
||||
// Detect binary: 0b or 0B follwed by 0 or 1
|
||||
if ((what & STR2NR_BIN)
|
||||
&& !STRING_ENDED(ptr + 2)
|
||||
&& (pre == 'B' || pre == 'b')
|
||||
&& ascii_isbdigit(ptr[2])) {
|
||||
ptr += 2;
|
||||
goto vim_str2nr_bin;
|
||||
}
|
||||
// Detect octal number: zero followed by octal digits without '8' or '9'
|
||||
pre = 0;
|
||||
if (!(what & STR2NR_OCT)) {
|
||||
goto vim_str2nr_dec;
|
||||
}
|
||||
for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) {
|
||||
if (ptr[i] > '7') {
|
||||
goto vim_str2nr_dec;
|
||||
}
|
||||
}
|
||||
pre = '0';
|
||||
goto vim_str2nr_oct;
|
||||
} else {
|
||||
goto vim_str2nr_dec;
|
||||
}
|
||||
|
||||
// Do the string-to-numeric conversion "manually" to avoid sscanf quirks.
|
||||
assert(false); // Should’ve used goto earlier.
|
||||
#define PARSE_NUMBER(base, cond, conv) \
|
||||
do { \
|
||||
while (!STRING_ENDED(ptr) && (cond)) { \
|
||||
/* avoid ubsan error for overflow */ \
|
||||
if (un < UVARNUMBER_MAX / base) { \
|
||||
un = base * un + (uvarnumber_T)(conv); \
|
||||
} else { \
|
||||
un = UVARNUMBER_MAX; \
|
||||
} \
|
||||
ptr++; \
|
||||
} \
|
||||
} while (0)
|
||||
switch (pre) {
|
||||
case 'b':
|
||||
case 'B': {
|
||||
vim_str2nr_bin:
|
||||
PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0'));
|
||||
break;
|
||||
}
|
||||
case '0': {
|
||||
vim_str2nr_oct:
|
||||
PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0'));
|
||||
break;
|
||||
}
|
||||
case 0: {
|
||||
vim_str2nr_dec:
|
||||
PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0'));
|
||||
break;
|
||||
}
|
||||
case 'x':
|
||||
case 'X': {
|
||||
vim_str2nr_hex:
|
||||
PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
#undef PARSE_NUMBER
|
||||
|
||||
if (prep != NULL) {
|
||||
*prep = pre;
|
||||
}
|
||||
|
||||
if (len != NULL) {
|
||||
*len = (int)(ptr - (const char *)start);
|
||||
}
|
||||
|
||||
if (nptr != NULL) {
|
||||
if (negative) { // account for leading '-' for decimal numbers
|
||||
// avoid ubsan error for overflow
|
||||
if (un > VARNUMBER_MAX) {
|
||||
*nptr = VARNUMBER_MIN;
|
||||
} else {
|
||||
*nptr = -(varnumber_T)un;
|
||||
}
|
||||
} else {
|
||||
if (un > VARNUMBER_MAX) {
|
||||
un = VARNUMBER_MAX;
|
||||
}
|
||||
*nptr = (varnumber_T)un;
|
||||
}
|
||||
}
|
||||
|
||||
if (unptr != NULL) {
|
||||
*unptr = un;
|
||||
}
|
||||
#undef STRING_ENDED
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
// This is an open source non-commercial project. Dear PVS-Studio, please check
|
||||
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
|
||||
/// @file garray.c
|
||||
///
|
||||
/// Functions for handling growing arrays.
|
||||
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/ascii.h"
|
||||
#include "nvim/log.h"
|
||||
#include "nvim/memory.h"
|
||||
#include "nvim/path.h"
|
||||
#include "nvim/garray.h"
|
||||
#include "nvim/strings.h"
|
||||
|
||||
// #include "nvim/globals.h"
|
||||
#include "nvim/memline.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "garray.c.generated.h"
|
||||
#endif
|
||||
|
||||
/// Clear an allocated growing array.
|
||||
void ga_clear(garray_T *gap)
|
||||
{
|
||||
xfree(gap->ga_data);
|
||||
|
||||
// Initialize growing array without resetting itemsize or growsize
|
||||
gap->ga_data = NULL;
|
||||
gap->ga_maxlen = 0;
|
||||
gap->ga_len = 0;
|
||||
}
|
||||
|
||||
/// Clear a growing array that contains a list of strings.
|
||||
///
|
||||
/// @param gap
|
||||
void ga_clear_strings(garray_T *gap)
|
||||
{
|
||||
GA_DEEP_CLEAR_PTR(gap);
|
||||
}
|
||||
|
||||
/// Initialize a growing array.
|
||||
///
|
||||
/// @param gap
|
||||
/// @param itemsize
|
||||
/// @param growsize
|
||||
void ga_init(garray_T *gap, int itemsize, int growsize)
|
||||
{
|
||||
gap->ga_data = NULL;
|
||||
gap->ga_maxlen = 0;
|
||||
gap->ga_len = 0;
|
||||
gap->ga_itemsize = itemsize;
|
||||
ga_set_growsize(gap, growsize);
|
||||
}
|
||||
|
||||
/// A setter for the growsize that guarantees it will be at least 1.
|
||||
///
|
||||
/// @param gap
|
||||
/// @param growsize
|
||||
void ga_set_growsize(garray_T *gap, int growsize)
|
||||
{
|
||||
if (growsize < 1) {
|
||||
WLOG("trying to set an invalid ga_growsize: %d", growsize);
|
||||
gap->ga_growsize = 1;
|
||||
} else {
|
||||
gap->ga_growsize = growsize;
|
||||
}
|
||||
}
|
||||
|
||||
/// Make room in growing array "gap" for at least "n" items.
|
||||
///
|
||||
/// @param gap
|
||||
/// @param n
|
||||
void ga_grow(garray_T *gap, int n)
|
||||
{
|
||||
if (gap->ga_maxlen - gap->ga_len >= n) {
|
||||
// the garray still has enough space, do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
if (gap->ga_growsize < 1) {
|
||||
WLOG("ga_growsize(%d) is less than 1", gap->ga_growsize);
|
||||
}
|
||||
|
||||
// the garray grows by at least growsize
|
||||
if (n < gap->ga_growsize) {
|
||||
n = gap->ga_growsize;
|
||||
}
|
||||
int new_maxlen = gap->ga_len + n;
|
||||
|
||||
size_t new_size = (size_t)(gap->ga_itemsize * new_maxlen);
|
||||
size_t old_size = (size_t)(gap->ga_itemsize * gap->ga_maxlen);
|
||||
|
||||
// reallocate and clear the new memory
|
||||
char *pp = xrealloc(gap->ga_data, new_size);
|
||||
memset(pp + old_size, 0, new_size - old_size);
|
||||
|
||||
gap->ga_maxlen = new_maxlen;
|
||||
gap->ga_data = pp;
|
||||
}
|
||||
|
||||
/// For a growing array that contains a list of strings: concatenate all the
|
||||
/// strings with sep as separator.
|
||||
///
|
||||
/// @param gap
|
||||
/// @param sep
|
||||
///
|
||||
/// @returns the concatenated strings
|
||||
char_u *ga_concat_strings_sep(const garray_T *gap, const char *sep)
|
||||
FUNC_ATTR_NONNULL_RET
|
||||
{
|
||||
const size_t nelem = (size_t) gap->ga_len;
|
||||
const char **strings = gap->ga_data;
|
||||
|
||||
if (nelem == 0) {
|
||||
return (char_u *) xstrdup("");
|
||||
}
|
||||
|
||||
size_t len = 0;
|
||||
for (size_t i = 0; i < nelem; i++) {
|
||||
len += strlen(strings[i]);
|
||||
}
|
||||
|
||||
// add some space for the (num - 1) separators
|
||||
len += (nelem - 1) * strlen(sep);
|
||||
char *const ret = xmallocz(len);
|
||||
|
||||
char *s = ret;
|
||||
for (size_t i = 0; i < nelem - 1; i++) {
|
||||
s = xstpcpy(s, strings[i]);
|
||||
s = xstpcpy(s, sep);
|
||||
}
|
||||
strcpy(s, strings[nelem - 1]);
|
||||
|
||||
return (char_u *) ret;
|
||||
}
|
||||
|
||||
/// For a growing array that contains a list of strings: concatenate all the
|
||||
/// strings with a separating comma.
|
||||
///
|
||||
/// @param gap
|
||||
///
|
||||
/// @returns the concatenated strings
|
||||
char_u* ga_concat_strings(const garray_T *gap) FUNC_ATTR_NONNULL_RET
|
||||
{
|
||||
return ga_concat_strings_sep(gap, ",");
|
||||
}
|
||||
|
||||
/// Concatenate a string to a growarray which contains characters.
|
||||
/// When "s" is NULL does not do anything.
|
||||
///
|
||||
/// WARNING:
|
||||
/// - Does NOT copy the NUL at the end!
|
||||
/// - The parameter may not overlap with the growing array
|
||||
///
|
||||
/// @param gap
|
||||
/// @param s
|
||||
void ga_concat(garray_T *gap, const char_u *restrict s)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ga_concat_len(gap, (const char *restrict) s, strlen((char *) s));
|
||||
}
|
||||
|
||||
/// Concatenate a string to a growarray which contains characters
|
||||
///
|
||||
/// @param[out] gap Growarray to modify.
|
||||
/// @param[in] s String to concatenate.
|
||||
/// @param[in] len String length.
|
||||
void ga_concat_len(garray_T *const gap, const char *restrict s,
|
||||
const size_t len)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
if (len) {
|
||||
ga_grow(gap, (int) len);
|
||||
char *data = gap->ga_data;
|
||||
memcpy(data + gap->ga_len, s, len);
|
||||
gap->ga_len += (int) len;
|
||||
}
|
||||
}
|
||||
|
||||
/// Append one byte to a growarray which contains bytes.
|
||||
///
|
||||
/// @param gap
|
||||
/// @param c
|
||||
void ga_append(garray_T *gap, char c)
|
||||
{
|
||||
GA_APPEND(char, gap, c);
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
char *gettext(const char *s)
|
||||
{
|
||||
return (char *)s;
|
||||
}
|
|
@ -0,0 +1,539 @@
|
|||
#include <stdbool.h>
|
||||
|
||||
#include "nvim/types.h"
|
||||
#include "nvim/keymap.h"
|
||||
#include "nvim/ascii.h"
|
||||
#include "nvim/eval/typval.h"
|
||||
|
||||
#define MOD_KEYS_ENTRY_SIZE 5
|
||||
|
||||
static char_u modifier_keys_table[] =
|
||||
{
|
||||
MOD_MASK_SHIFT, '&', '9', '@', '1',
|
||||
MOD_MASK_SHIFT, '&', '0', '@', '2',
|
||||
MOD_MASK_SHIFT, '*', '1', '@', '4',
|
||||
MOD_MASK_SHIFT, '*', '2', '@', '5',
|
||||
MOD_MASK_SHIFT, '*', '3', '@', '6',
|
||||
MOD_MASK_SHIFT, '*', '4', 'k', 'D',
|
||||
MOD_MASK_SHIFT, '*', '5', 'k', 'L',
|
||||
MOD_MASK_SHIFT, '*', '7', '@', '7',
|
||||
MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_END, '@', '7',
|
||||
MOD_MASK_SHIFT, '*', '9', '@', '9',
|
||||
MOD_MASK_SHIFT, '*', '0', '@', '0',
|
||||
MOD_MASK_SHIFT, '#', '1', '%', '1',
|
||||
MOD_MASK_SHIFT, '#', '2', 'k', 'h',
|
||||
MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_HOME, 'k', 'h',
|
||||
MOD_MASK_SHIFT, '#', '3', 'k', 'I',
|
||||
MOD_MASK_SHIFT, '#', '4', 'k', 'l',
|
||||
MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_LEFT, 'k', 'l',
|
||||
MOD_MASK_SHIFT, '%', 'a', '%', '3',
|
||||
MOD_MASK_SHIFT, '%', 'b', '%', '4',
|
||||
MOD_MASK_SHIFT, '%', 'c', '%', '5',
|
||||
MOD_MASK_SHIFT, '%', 'd', '%', '7',
|
||||
MOD_MASK_SHIFT, '%', 'e', '%', '8',
|
||||
MOD_MASK_SHIFT, '%', 'f', '%', '9',
|
||||
MOD_MASK_SHIFT, '%', 'g', '%', '0',
|
||||
MOD_MASK_SHIFT, '%', 'h', '&', '3',
|
||||
MOD_MASK_SHIFT, '%', 'i', 'k', 'r',
|
||||
MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_RIGHT, 'k', 'r',
|
||||
MOD_MASK_SHIFT, '%', 'j', '&', '5',
|
||||
MOD_MASK_SHIFT, '!', '1', '&', '6',
|
||||
MOD_MASK_SHIFT, '!', '2', '&', '7',
|
||||
MOD_MASK_SHIFT, '!', '3', '&', '8',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_UP, 'k', 'u',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_DOWN, 'k', 'd',
|
||||
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF1, KS_EXTRA, (int)KE_XF1,
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF2, KS_EXTRA, (int)KE_XF2,
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF3, KS_EXTRA, (int)KE_XF3,
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF4, KS_EXTRA, (int)KE_XF4,
|
||||
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F1, 'k', '1',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F2, 'k', '2',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F3, 'k', '3',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F4, 'k', '4',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F5, 'k', '5',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F6, 'k', '6',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F7, 'k', '7',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F8, 'k', '8',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F9, 'k', '9',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F10, 'k', ';',
|
||||
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F11, 'F', '1',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F12, 'F', '2',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F13, 'F', '3',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F14, 'F', '4',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F15, 'F', '5',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F16, 'F', '6',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F17, 'F', '7',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F18, 'F', '8',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F19, 'F', '9',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F20, 'F', 'A',
|
||||
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F21, 'F', 'B',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F22, 'F', 'C',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F23, 'F', 'D',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F24, 'F', 'E',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F25, 'F', 'F',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F26, 'F', 'G',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F27, 'F', 'H',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F28, 'F', 'I',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F29, 'F', 'J',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F30, 'F', 'K',
|
||||
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F31, 'F', 'L',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F32, 'F', 'M',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F33, 'F', 'N',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F34, 'F', 'O',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F35, 'F', 'P',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F36, 'F', 'Q',
|
||||
MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F37, 'F', 'R',
|
||||
|
||||
MOD_MASK_SHIFT, 'k', 'B', KS_EXTRA, (int)KE_TAB,
|
||||
|
||||
NUL
|
||||
};
|
||||
|
||||
int simplify_key(const int key, int *modifiers)
|
||||
{
|
||||
if (*modifiers & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT)) {
|
||||
// TAB is a special case.
|
||||
if (key == TAB && (*modifiers & MOD_MASK_SHIFT)) {
|
||||
*modifiers &= ~MOD_MASK_SHIFT;
|
||||
return K_S_TAB;
|
||||
}
|
||||
const int key0 = KEY2TERMCAP0(key);
|
||||
const int key1 = KEY2TERMCAP1(key);
|
||||
for (int i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) {
|
||||
if (key0 == modifier_keys_table[i + 3]
|
||||
&& key1 == modifier_keys_table[i + 4]
|
||||
&& (*modifiers & modifier_keys_table[i])) {
|
||||
*modifiers &= ~modifier_keys_table[i];
|
||||
return TERMCAP2KEY(modifier_keys_table[i + 1],
|
||||
modifier_keys_table[i + 2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
int handle_x_keys(const int key)
|
||||
FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
switch (key) {
|
||||
case K_XUP: return K_UP;
|
||||
case K_XDOWN: return K_DOWN;
|
||||
case K_XLEFT: return K_LEFT;
|
||||
case K_XRIGHT: return K_RIGHT;
|
||||
case K_XHOME: return K_HOME;
|
||||
case K_ZHOME: return K_HOME;
|
||||
case K_XEND: return K_END;
|
||||
case K_ZEND: return K_END;
|
||||
case K_XF1: return K_F1;
|
||||
case K_XF2: return K_F2;
|
||||
case K_XF3: return K_F3;
|
||||
case K_XF4: return K_F4;
|
||||
case K_S_XF1: return K_S_F1;
|
||||
case K_S_XF2: return K_S_F2;
|
||||
case K_S_XF3: return K_S_F3;
|
||||
case K_S_XF4: return K_S_F4;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
static const struct key_name_entry {
|
||||
int key; // Special key code or ascii value
|
||||
const char *name; // Name of key
|
||||
} key_names_table[] = {
|
||||
{ ' ', "Space" },
|
||||
{ TAB, "Tab" },
|
||||
{ K_TAB, "Tab" },
|
||||
{ NL, "NL" },
|
||||
{ NL, "NewLine" }, // Alternative name
|
||||
{ NL, "LineFeed" }, // Alternative name
|
||||
{ NL, "LF" }, // Alternative name
|
||||
{ CAR, "CR" },
|
||||
{ CAR, "Return" }, // Alternative name
|
||||
{ CAR, "Enter" }, // Alternative name
|
||||
{ K_BS, "BS" },
|
||||
{ K_BS, "BackSpace" }, // Alternative name
|
||||
{ ESC, "Esc" },
|
||||
{ CSI, "CSI" },
|
||||
{ K_CSI, "xCSI" },
|
||||
{ '|', "Bar" },
|
||||
{ '\\', "Bslash" },
|
||||
{ K_DEL, "Del" },
|
||||
{ K_DEL, "Delete" }, // Alternative name
|
||||
{ K_KDEL, "kDel" },
|
||||
{ K_UP, "Up" },
|
||||
{ K_DOWN, "Down" },
|
||||
{ K_LEFT, "Left" },
|
||||
{ K_RIGHT, "Right" },
|
||||
{ K_XUP, "xUp" },
|
||||
{ K_XDOWN, "xDown" },
|
||||
{ K_XLEFT, "xLeft" },
|
||||
{ K_XRIGHT, "xRight" },
|
||||
|
||||
{ K_F1, "F1" },
|
||||
{ K_F2, "F2" },
|
||||
{ K_F3, "F3" },
|
||||
{ K_F4, "F4" },
|
||||
{ K_F5, "F5" },
|
||||
{ K_F6, "F6" },
|
||||
{ K_F7, "F7" },
|
||||
{ K_F8, "F8" },
|
||||
{ K_F9, "F9" },
|
||||
{ K_F10, "F10" },
|
||||
|
||||
{ K_F11, "F11" },
|
||||
{ K_F12, "F12" },
|
||||
{ K_F13, "F13" },
|
||||
{ K_F14, "F14" },
|
||||
{ K_F15, "F15" },
|
||||
{ K_F16, "F16" },
|
||||
{ K_F17, "F17" },
|
||||
{ K_F18, "F18" },
|
||||
{ K_F19, "F19" },
|
||||
{ K_F20, "F20" },
|
||||
|
||||
{ K_F21, "F21" },
|
||||
{ K_F22, "F22" },
|
||||
{ K_F23, "F23" },
|
||||
{ K_F24, "F24" },
|
||||
{ K_F25, "F25" },
|
||||
{ K_F26, "F26" },
|
||||
{ K_F27, "F27" },
|
||||
{ K_F28, "F28" },
|
||||
{ K_F29, "F29" },
|
||||
{ K_F30, "F30" },
|
||||
|
||||
{ K_F31, "F31" },
|
||||
{ K_F32, "F32" },
|
||||
{ K_F33, "F33" },
|
||||
{ K_F34, "F34" },
|
||||
{ K_F35, "F35" },
|
||||
{ K_F36, "F36" },
|
||||
{ K_F37, "F37" },
|
||||
|
||||
{ K_XF1, "xF1" },
|
||||
{ K_XF2, "xF2" },
|
||||
{ K_XF3, "xF3" },
|
||||
{ K_XF4, "xF4" },
|
||||
|
||||
{ K_HELP, "Help" },
|
||||
{ K_UNDO, "Undo" },
|
||||
{ K_INS, "Insert" },
|
||||
{ K_INS, "Ins" }, // Alternative name
|
||||
{ K_KINS, "kInsert" },
|
||||
{ K_HOME, "Home" },
|
||||
{ K_KHOME, "kHome" },
|
||||
{ K_XHOME, "xHome" },
|
||||
{ K_ZHOME, "zHome" },
|
||||
{ K_END, "End" },
|
||||
{ K_KEND, "kEnd" },
|
||||
{ K_XEND, "xEnd" },
|
||||
{ K_ZEND, "zEnd" },
|
||||
{ K_PAGEUP, "PageUp" },
|
||||
{ K_PAGEDOWN, "PageDown" },
|
||||
{ K_KPAGEUP, "kPageUp" },
|
||||
{ K_KPAGEDOWN, "kPageDown" },
|
||||
|
||||
{ K_KPLUS, "kPlus" },
|
||||
{ K_KMINUS, "kMinus" },
|
||||
{ K_KDIVIDE, "kDivide" },
|
||||
{ K_KMULTIPLY, "kMultiply" },
|
||||
{ K_KENTER, "kEnter" },
|
||||
{ K_KPOINT, "kPoint" },
|
||||
|
||||
{ K_K0, "k0" },
|
||||
{ K_K1, "k1" },
|
||||
{ K_K2, "k2" },
|
||||
{ K_K3, "k3" },
|
||||
{ K_K4, "k4" },
|
||||
{ K_K5, "k5" },
|
||||
{ K_K6, "k6" },
|
||||
{ K_K7, "k7" },
|
||||
{ K_K8, "k8" },
|
||||
{ K_K9, "k9" },
|
||||
|
||||
{ '<', "lt" },
|
||||
|
||||
{ K_MOUSE, "Mouse" },
|
||||
{ K_LEFTMOUSE, "LeftMouse" },
|
||||
{ K_LEFTMOUSE_NM, "LeftMouseNM" },
|
||||
{ K_LEFTDRAG, "LeftDrag" },
|
||||
{ K_LEFTRELEASE, "LeftRelease" },
|
||||
{ K_LEFTRELEASE_NM, "LeftReleaseNM" },
|
||||
{ K_MIDDLEMOUSE, "MiddleMouse" },
|
||||
{ K_MIDDLEDRAG, "MiddleDrag" },
|
||||
{ K_MIDDLERELEASE, "MiddleRelease" },
|
||||
{ K_RIGHTMOUSE, "RightMouse" },
|
||||
{ K_RIGHTDRAG, "RightDrag" },
|
||||
{ K_RIGHTRELEASE, "RightRelease" },
|
||||
{ K_MOUSEDOWN, "ScrollWheelUp" },
|
||||
{ K_MOUSEUP, "ScrollWheelDown" },
|
||||
{ K_MOUSELEFT, "ScrollWheelRight" },
|
||||
{ K_MOUSERIGHT, "ScrollWheelLeft" },
|
||||
{ K_MOUSEDOWN, "MouseDown" }, // OBSOLETE: Use
|
||||
{ K_MOUSEUP, "MouseUp" }, // ScrollWheelXXX instead
|
||||
{ K_X1MOUSE, "X1Mouse" },
|
||||
{ K_X1DRAG, "X1Drag" },
|
||||
{ K_X1RELEASE, "X1Release" },
|
||||
{ K_X2MOUSE, "X2Mouse" },
|
||||
{ K_X2DRAG, "X2Drag" },
|
||||
{ K_X2RELEASE, "X2Release" },
|
||||
{ K_DROP, "Drop" },
|
||||
{ K_ZERO, "Nul" },
|
||||
{ K_SNR, "SNR" },
|
||||
{ K_PLUG, "Plug" },
|
||||
{ K_PASTE, "Paste" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
int get_special_key_code(const char_u *name)
|
||||
{
|
||||
for (int i = 0; key_names_table[i].name != NULL; i++) {
|
||||
const char *const table_name = key_names_table[i].name;
|
||||
int j;
|
||||
for (j = 0; ascii_isident(name[j]) && table_name[j] != NUL; j++) {
|
||||
if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ascii_isident(name[j]) && table_name[j] == NUL) {
|
||||
return key_names_table[i].key;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct modmasktable {
|
||||
short mod_mask; ///< Bit-mask for particular key modifier.
|
||||
short mod_flag; ///< Bit(s) for particular key modifier.
|
||||
char_u name; ///< Single letter name of modifier.
|
||||
} mod_mask_table[] = {
|
||||
{MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M'},
|
||||
{MOD_MASK_META, MOD_MASK_META, (char_u)'T'},
|
||||
{MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C'},
|
||||
{MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S'},
|
||||
{MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2'},
|
||||
{MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3'},
|
||||
{MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4'},
|
||||
{MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D'},
|
||||
// 'A' must be the last one
|
||||
{MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A'},
|
||||
{0, 0, NUL}
|
||||
};
|
||||
|
||||
int name_to_mod_mask(int c)
|
||||
{
|
||||
c = TOUPPER_ASC(c);
|
||||
for (size_t i = 0; mod_mask_table[i].mod_mask != 0; i++) {
|
||||
if (c == mod_mask_table[i].name) {
|
||||
return mod_mask_table[i].mod_flag;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int extract_modifiers(int key, int *modp)
|
||||
{
|
||||
int modifiers = *modp;
|
||||
|
||||
if (!(modifiers & MOD_MASK_CMD)) { // Command-key is special
|
||||
if ((modifiers & MOD_MASK_SHIFT) && ASCII_ISALPHA(key)) {
|
||||
key = TOUPPER_ASC(key);
|
||||
modifiers &= ~MOD_MASK_SHIFT;
|
||||
}
|
||||
}
|
||||
if ((modifiers & MOD_MASK_CTRL)
|
||||
&& ((key >= '?' && key <= '_') || ASCII_ISALPHA(key))) {
|
||||
key = Ctrl_chr(key);
|
||||
modifiers &= ~MOD_MASK_CTRL;
|
||||
if (key == 0) { // <C-@> is <Nul>
|
||||
key = K_ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
*modp = modifiers;
|
||||
return key;
|
||||
}
|
||||
|
||||
int find_special_key(const char_u **srcp, const size_t src_len, int *const modp,
|
||||
const bool keycode, const bool keep_x_key,
|
||||
const bool in_string)
|
||||
{
|
||||
const char_u *last_dash;
|
||||
const char_u *end_of_name;
|
||||
const char_u *src;
|
||||
const char_u *bp;
|
||||
const char_u *const end = *srcp + src_len - 1;
|
||||
int modifiers;
|
||||
int bit;
|
||||
int key;
|
||||
uvarnumber_T n;
|
||||
int l;
|
||||
|
||||
if (src_len == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
src = *srcp;
|
||||
if (src[0] != '<') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find end of modifier list
|
||||
last_dash = src;
|
||||
for (bp = src + 1; bp <= end && (*bp == '-' || ascii_isident(*bp)); bp++) {
|
||||
if (*bp == '-') {
|
||||
last_dash = bp;
|
||||
if (bp + 1 <= end) {
|
||||
l = utfc_ptr2len_len(bp + 1, (int)(end - bp) + 1);
|
||||
// Anything accepted, like <C-?>.
|
||||
// <C-"> or <M-"> are not special in strings as " is
|
||||
// the string delimiter. With a backslash it works: <M-\">
|
||||
if (end - bp > l && !(in_string && bp[1] == '"') && bp[2] == '>') {
|
||||
bp += l;
|
||||
} else if (end - bp > 2 && in_string && bp[1] == '\\'
|
||||
&& bp[2] == '"' && bp[3] == '>') {
|
||||
bp += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (end - bp > 3 && bp[0] == 't' && bp[1] == '_') {
|
||||
bp += 3; // skip t_xx, xx may be '-' or '>'
|
||||
} else if (end - bp > 4 && STRNICMP(bp, "char-", 5) == 0) {
|
||||
vim_str2nr(bp + 5, NULL, &l, STR2NR_ALL, NULL, NULL, 0);
|
||||
bp += l + 5;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bp <= end && *bp == '>') { // found matching '>'
|
||||
end_of_name = bp + 1;
|
||||
|
||||
/* Which modifiers are given? */
|
||||
modifiers = 0x0;
|
||||
for (bp = src + 1; bp < last_dash; bp++) {
|
||||
if (*bp != '-') {
|
||||
bit = name_to_mod_mask(*bp);
|
||||
if (bit == 0x0) {
|
||||
break; // Illegal modifier name
|
||||
}
|
||||
modifiers |= bit;
|
||||
}
|
||||
}
|
||||
|
||||
// Legal modifier name.
|
||||
if (bp >= last_dash) {
|
||||
if (STRNICMP(last_dash + 1, "char-", 5) == 0
|
||||
&& ascii_isdigit(last_dash[6])) {
|
||||
// <Char-123> or <Char-033> or <Char-0x33>
|
||||
vim_str2nr(last_dash + 6, NULL, NULL, STR2NR_ALL, NULL, &n, 0);
|
||||
key = (int)n;
|
||||
} else {
|
||||
int off = 1;
|
||||
|
||||
// Modifier with single letter, or special key name.
|
||||
if (in_string && last_dash[1] == '\\' && last_dash[2] == '"') {
|
||||
off = 2;
|
||||
}
|
||||
l = mb_ptr2len(last_dash + 1);
|
||||
if (modifiers != 0 && last_dash[l + 1] == '>') {
|
||||
key = PTR2CHAR(last_dash + off);
|
||||
} else {
|
||||
key = get_special_key_code(last_dash + off);
|
||||
if (!keep_x_key) {
|
||||
key = handle_x_keys(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get_special_key_code() may return NUL for invalid
|
||||
// special key name.
|
||||
if (key != NUL) {
|
||||
// Only use a modifier when there is no special key code that
|
||||
// includes the modifier.
|
||||
key = simplify_key(key, &modifiers);
|
||||
|
||||
if (!keycode) {
|
||||
// don't want keycode, use single byte code
|
||||
if (key == K_BS) {
|
||||
key = BS;
|
||||
} else if (key == K_DEL || key == K_KDEL) {
|
||||
key = DEL;
|
||||
}
|
||||
}
|
||||
|
||||
// Normal Key with modifier:
|
||||
// Try to make a single byte code (except for Alt/Meta modifiers).
|
||||
if (!IS_SPECIAL(key)) {
|
||||
key = extract_modifiers(key, &modifiers);
|
||||
}
|
||||
|
||||
*modp = modifiers;
|
||||
*srcp = end_of_name;
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char_u *add_char2buf(int c, char_u *s)
|
||||
{
|
||||
char_u temp[MB_MAXBYTES + 1];
|
||||
const int len = utf_char2bytes(c, temp);
|
||||
for (int i = 0; i < len; ++i) {
|
||||
c = temp[i];
|
||||
// Need to escape K_SPECIAL and CSI like in the typeahead buffer.
|
||||
if (c == K_SPECIAL) {
|
||||
*s++ = K_SPECIAL;
|
||||
*s++ = KS_SPECIAL;
|
||||
*s++ = KE_FILLER;
|
||||
} else {
|
||||
*s++ = c;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
unsigned int trans_special(const char_u **srcp, const size_t src_len,
|
||||
char_u *const dst, const bool keycode,
|
||||
const bool in_string)
|
||||
{
|
||||
int modifiers = 0;
|
||||
int key;
|
||||
unsigned int dlen = 0;
|
||||
|
||||
key = find_special_key(srcp, src_len, &modifiers, keycode, false, in_string);
|
||||
if (key == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Put the appropriate modifier in a string.
|
||||
if (modifiers != 0) {
|
||||
dst[dlen++] = K_SPECIAL;
|
||||
dst[dlen++] = KS_MODIFIER;
|
||||
dst[dlen++] = (char_u)modifiers;
|
||||
}
|
||||
|
||||
if (IS_SPECIAL(key)) {
|
||||
dst[dlen++] = K_SPECIAL;
|
||||
dst[dlen++] = (char_u)KEY2TERMCAP0(key);
|
||||
dst[dlen++] = KEY2TERMCAP1(key);
|
||||
} else if (has_mbyte && !keycode) {
|
||||
dlen += (unsigned int)(*mb_char2bytes)(key, dst + dlen);
|
||||
} else if (keycode) {
|
||||
char_u *after = add_char2buf(key, dst + dlen);
|
||||
assert(after >= dst && (uintmax_t)(after - dst) <= UINT_MAX);
|
||||
dlen = (unsigned int)(after - dst);
|
||||
} else {
|
||||
dst[dlen++] = (char_u)key;
|
||||
}
|
||||
|
||||
return dlen;
|
||||
}
|
|
@ -0,0 +1,266 @@
|
|||
#include <stddef.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "nvim/types.h"
|
||||
#include "nvim/mbyte.h"
|
||||
#include "nvim/ascii.h"
|
||||
|
||||
const uint8_t utf8len_tab_zero[] = {
|
||||
//1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 2
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 4
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 6
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 8
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // A
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // C
|
||||
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0, // E
|
||||
};
|
||||
|
||||
const uint8_t utf8len_tab[] = {
|
||||
// ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B?
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C?
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D?
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E?
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1, // F?
|
||||
};
|
||||
|
||||
int utf_ptr2char(const char_u *const p)
|
||||
{
|
||||
if (p[0] < 0x80) { // Be quick for ASCII.
|
||||
return p[0];
|
||||
}
|
||||
|
||||
const uint8_t len = utf8len_tab_zero[p[0]];
|
||||
if (len > 1 && (p[1] & 0xc0) == 0x80) {
|
||||
if (len == 2) {
|
||||
return ((p[0] & 0x1f) << 6) + (p[1] & 0x3f);
|
||||
}
|
||||
if ((p[2] & 0xc0) == 0x80) {
|
||||
if (len == 3) {
|
||||
return (((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6)
|
||||
+ (p[2] & 0x3f));
|
||||
}
|
||||
if ((p[3] & 0xc0) == 0x80) {
|
||||
if (len == 4) {
|
||||
return (((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12)
|
||||
+ ((p[2] & 0x3f) << 6) + (p[3] & 0x3f));
|
||||
}
|
||||
if ((p[4] & 0xc0) == 0x80) {
|
||||
if (len == 5) {
|
||||
return (((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18)
|
||||
+ ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6)
|
||||
+ (p[4] & 0x3f));
|
||||
}
|
||||
if ((p[5] & 0xc0) == 0x80 && len == 6) {
|
||||
return (((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24)
|
||||
+ ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12)
|
||||
+ ((p[4] & 0x3f) << 6) + (p[5] & 0x3f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Illegal value: just return the first byte.
|
||||
return p[0];
|
||||
}
|
||||
|
||||
bool utf_composinglike(const char_u *p1, const char_u *p2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
char_u *string_convert(const vimconv_T *conv, char_u *data, size_t *size)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int utf_ptr2len_len(const char_u *p, int size)
|
||||
{
|
||||
int len;
|
||||
int i;
|
||||
int m;
|
||||
|
||||
len = utf8len_tab[*p];
|
||||
if (len == 1)
|
||||
return 1; /* NUL, ascii or illegal lead byte */
|
||||
if (len > size)
|
||||
m = size; /* incomplete byte sequence. */
|
||||
else
|
||||
m = len;
|
||||
for (i = 1; i < m; ++i)
|
||||
if ((p[i] & 0xc0) != 0x80)
|
||||
return 1;
|
||||
return len;
|
||||
}
|
||||
|
||||
int utfc_ptr2len_len(const char_u *p, int size)
|
||||
{
|
||||
int len;
|
||||
int prevlen;
|
||||
|
||||
if (size < 1 || *p == NUL)
|
||||
return 0;
|
||||
if (p[0] < 0x80 && (size == 1 || p[1] < 0x80)) /* be quick for ASCII */
|
||||
return 1;
|
||||
|
||||
/* Skip over first UTF-8 char, stopping at a NUL byte. */
|
||||
len = utf_ptr2len_len(p, size);
|
||||
|
||||
/* Check for illegal byte and incomplete byte sequence. */
|
||||
if ((len == 1 && p[0] >= 0x80) || len > size)
|
||||
return 1;
|
||||
|
||||
/*
|
||||
* Check for composing characters. We can handle only the first six, but
|
||||
* skip all of them (otherwise the cursor would get stuck).
|
||||
*/
|
||||
prevlen = 0;
|
||||
while (len < size) {
|
||||
int len_next_char;
|
||||
|
||||
if (p[len] < 0x80)
|
||||
break;
|
||||
|
||||
/*
|
||||
* Next character length should not go beyond size to ensure that
|
||||
* UTF_COMPOSINGLIKE(...) does not read beyond size.
|
||||
*/
|
||||
len_next_char = utf_ptr2len_len(p + len, size - len);
|
||||
if (len_next_char > size - len)
|
||||
break;
|
||||
|
||||
if (!UTF_COMPOSINGLIKE(p + prevlen, p + len))
|
||||
break;
|
||||
|
||||
/* Skip over composing char */
|
||||
prevlen = len;
|
||||
len += len_next_char;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
int utf_char2len(const int c)
|
||||
{
|
||||
if (c < 0x80) {
|
||||
return 1;
|
||||
} else if (c < 0x800) {
|
||||
return 2;
|
||||
} else if (c < 0x10000) {
|
||||
return 3;
|
||||
} else if (c < 0x200000) {
|
||||
return 4;
|
||||
} else if (c < 0x4000000) {
|
||||
return 5;
|
||||
} else {
|
||||
return 6;
|
||||
}
|
||||
}
|
||||
|
||||
int utf_char2bytes(const int c, char_u *const buf)
|
||||
{
|
||||
if (c < 0x80) { // 7 bits
|
||||
buf[0] = c;
|
||||
return 1;
|
||||
} else if (c < 0x800) { // 11 bits
|
||||
buf[0] = 0xc0 + ((unsigned)c >> 6);
|
||||
buf[1] = 0x80 + (c & 0x3f);
|
||||
return 2;
|
||||
} else if (c < 0x10000) { // 16 bits
|
||||
buf[0] = 0xe0 + ((unsigned)c >> 12);
|
||||
buf[1] = 0x80 + (((unsigned)c >> 6) & 0x3f);
|
||||
buf[2] = 0x80 + (c & 0x3f);
|
||||
return 3;
|
||||
} else if (c < 0x200000) { // 21 bits
|
||||
buf[0] = 0xf0 + ((unsigned)c >> 18);
|
||||
buf[1] = 0x80 + (((unsigned)c >> 12) & 0x3f);
|
||||
buf[2] = 0x80 + (((unsigned)c >> 6) & 0x3f);
|
||||
buf[3] = 0x80 + (c & 0x3f);
|
||||
return 4;
|
||||
} else if (c < 0x4000000) { // 26 bits
|
||||
buf[0] = 0xf8 + ((unsigned)c >> 24);
|
||||
buf[1] = 0x80 + (((unsigned)c >> 18) & 0x3f);
|
||||
buf[2] = 0x80 + (((unsigned)c >> 12) & 0x3f);
|
||||
buf[3] = 0x80 + (((unsigned)c >> 6) & 0x3f);
|
||||
buf[4] = 0x80 + (c & 0x3f);
|
||||
return 5;
|
||||
} else { // 31 bits
|
||||
buf[0] = 0xfc + ((unsigned)c >> 30);
|
||||
buf[1] = 0x80 + (((unsigned)c >> 24) & 0x3f);
|
||||
buf[2] = 0x80 + (((unsigned)c >> 18) & 0x3f);
|
||||
buf[3] = 0x80 + (((unsigned)c >> 12) & 0x3f);
|
||||
buf[4] = 0x80 + (((unsigned)c >> 6) & 0x3f);
|
||||
buf[5] = 0x80 + (c & 0x3f);
|
||||
return 6;
|
||||
}
|
||||
}
|
||||
|
||||
int utf_ptr2len(const char_u *const p)
|
||||
{
|
||||
if (*p == NUL) {
|
||||
return 0;
|
||||
}
|
||||
const int len = utf8len_tab[*p];
|
||||
for (int i = 1; i < len; i++) {
|
||||
if ((p[i] & 0xc0) != 0x80) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
int utfc_ptr2len(const char_u *const p)
|
||||
{
|
||||
uint8_t b0 = (uint8_t)(*p);
|
||||
|
||||
if (b0 == NUL) {
|
||||
return 0;
|
||||
}
|
||||
if (b0 < 0x80 && p[1] < 0x80) { // be quick for ASCII
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Skip over first UTF-8 char, stopping at a NUL byte.
|
||||
int len = utf_ptr2len(p);
|
||||
|
||||
// Check for illegal byte.
|
||||
if (len == 1 && b0 >= 0x80) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Check for composing characters. We can handle only the first six, but
|
||||
// skip all of them (otherwise the cursor would get stuck).
|
||||
int prevlen = 0;
|
||||
for (;;) {
|
||||
if (p[len] < 0x80 || !UTF_COMPOSINGLIKE(p + prevlen, p + len)) {
|
||||
return len;
|
||||
}
|
||||
|
||||
// Skip over composing char.
|
||||
prevlen = len;
|
||||
len += utf_ptr2len(p + len);
|
||||
}
|
||||
}
|
||||
|
||||
void mb_copy_char(const char_u **fp, char_u **tp)
|
||||
{
|
||||
const size_t l = utfc_ptr2len(*fp);
|
||||
|
||||
memmove(*tp, *fp, (size_t)l);
|
||||
*tp += l;
|
||||
*fp += l;
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "nvim/lib/ringbuf.h"
|
||||
|
||||
enum { RB_SIZE = 1024 };
|
||||
|
||||
typedef struct {
|
||||
void *ptr;
|
||||
size_t size;
|
||||
} AllocRecord;
|
||||
|
||||
RINGBUF_TYPEDEF(AllocRecords, AllocRecord)
|
||||
RINGBUF_INIT(AllocRecords, arecs, AllocRecord, RINGBUF_DUMMY_FREE)
|
||||
RINGBUF_STATIC(static, AllocRecords, AllocRecord, arecs, RB_SIZE)
|
||||
|
||||
size_t allocated_memory = 0;
|
||||
size_t ever_allocated_memory = 0;
|
||||
|
||||
size_t allocated_memory_limit = SIZE_MAX;
|
||||
|
||||
void *xmalloc(const size_t size)
|
||||
{
|
||||
void *ret = malloc(size);
|
||||
allocated_memory += size;
|
||||
ever_allocated_memory += size;
|
||||
assert(allocated_memory <= allocated_memory_limit);
|
||||
assert(arecs_rb_length(&arecs) < RB_SIZE);
|
||||
arecs_rb_push(&arecs, (AllocRecord) {
|
||||
.ptr = ret,
|
||||
.size = size,
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
void xfree(void *const p)
|
||||
{
|
||||
if (p == NULL) {
|
||||
return;
|
||||
}
|
||||
RINGBUF_FORALL(&arecs, AllocRecord, arec) {
|
||||
if (arec->ptr == p) {
|
||||
allocated_memory -= arec->size;
|
||||
arecs_rb_remove(&arecs, arecs_rb_find_idx(&arecs, arec));
|
||||
return;
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
}
|
||||
|
||||
void *xrealloc(void *const p, size_t new_size)
|
||||
{
|
||||
void *ret = realloc(p, new_size);
|
||||
RINGBUF_FORALL(&arecs, AllocRecord, arec) {
|
||||
if (arec->ptr == p) {
|
||||
allocated_memory -= arec->size;
|
||||
allocated_memory += new_size;
|
||||
if (new_size > arec->size) {
|
||||
ever_allocated_memory += (new_size - arec->size);
|
||||
}
|
||||
arec->ptr = ret;
|
||||
arec->size = new_size;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
return (void *)(intptr_t)1;
|
||||
}
|
||||
|
||||
char *xstrdup(const char *str)
|
||||
FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
return xmemdupz(str, strlen(str));
|
||||
}
|
||||
|
||||
void *xmallocz(size_t size)
|
||||
FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
size_t total_size = size + 1;
|
||||
assert(total_size > size);
|
||||
|
||||
void *ret = xmalloc(total_size);
|
||||
((char *)ret)[size] = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *xstpcpy(char *restrict dst, const char *restrict src)
|
||||
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
const size_t len = strlen(src);
|
||||
return (char *)memcpy(dst, src, len + 1) + len;
|
||||
}
|
||||
|
||||
void *xmemdupz(const void *data, size_t len)
|
||||
FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
return memcpy(xmallocz(len), data, len);
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
set -x
|
||||
test -z "$POSH_VERSION" && set -u
|
||||
|
||||
PROJECT_SOURCE_DIR=.
|
||||
PROJECT_BINARY_DIR="$PROJECT_SOURCE_DIR/build"
|
||||
KLEE_TEST_DIR="$PROJECT_SOURCE_DIR/test/symbolic/klee"
|
||||
KLEE_BIN_DIR="$PROJECT_BINARY_DIR/klee"
|
||||
KLEE_OUT_DIR="$KLEE_BIN_DIR/out"
|
||||
|
||||
help() {
|
||||
echo "Usage:"
|
||||
echo
|
||||
echo " $0 -c fname"
|
||||
echo " $0 fname"
|
||||
echo " $0 -s"
|
||||
echo
|
||||
echo "First form compiles executable out of test/symbolic/klee/{fname}.c."
|
||||
echo "Compiled executable is placed into build/klee/{fname}. Must first"
|
||||
echo "successfully compile Neovim in order to generate declarations."
|
||||
echo
|
||||
echo "Second form runs KLEE in a docker container using file "
|
||||
echo "test/symbolic/klee/{fname.c}. Bitcode is placed into build/klee/a.bc,"
|
||||
echo "results are placed into build/klee/out/. The latter is first deleted if"
|
||||
echo "it exists."
|
||||
echo
|
||||
echo "Third form runs ktest-tool which prints errors found by KLEE via "
|
||||
echo "the same container used to run KLEE."
|
||||
}
|
||||
|
||||
main() {
|
||||
local compile=
|
||||
local print_errs=
|
||||
if test "$1" = "--help" ; then
|
||||
help
|
||||
return
|
||||
fi
|
||||
if test "$1" = "-s" ; then
|
||||
print_errs=1
|
||||
shift
|
||||
elif test "$1" = "-c" ; then
|
||||
compile=1
|
||||
shift
|
||||
fi
|
||||
if test -z "$print_errs" ; then
|
||||
local test="$1" ; shift
|
||||
fi
|
||||
|
||||
local includes=
|
||||
includes="$includes -I$KLEE_TEST_DIR"
|
||||
includes="$includes -I/home/klee/klee_src/include"
|
||||
includes="$includes -I$PROJECT_SOURCE_DIR/src"
|
||||
includes="$includes -I$PROJECT_BINARY_DIR/src/nvim/auto"
|
||||
includes="$includes -I$PROJECT_BINARY_DIR/include"
|
||||
includes="$includes -I$PROJECT_BINARY_DIR/config"
|
||||
includes="$includes -I/host-includes"
|
||||
|
||||
local defines=
|
||||
defines="$defines -DMIN_LOG_LEVEL=9999"
|
||||
defines="$defines -DINCLUDE_GENERATED_DECLARATIONS"
|
||||
|
||||
test -z "$compile" && defines="$defines -DUSE_KLEE"
|
||||
|
||||
test -d "$KLEE_BIN_DIR" || mkdir -p "$KLEE_BIN_DIR"
|
||||
|
||||
if test -z "$compile" ; then
|
||||
local line1='cd /image'
|
||||
if test -z "$print_errs" ; then
|
||||
test -d "$KLEE_OUT_DIR" && rm -r "$KLEE_OUT_DIR"
|
||||
|
||||
line1="$line1 && $(echo clang \
|
||||
$includes $defines \
|
||||
-o "$KLEE_BIN_DIR/a.bc" -emit-llvm -g -c \
|
||||
"$KLEE_TEST_DIR/$test.c")"
|
||||
line1="$line1 && klee --libc=uclibc --posix-runtime "
|
||||
line1="$line1 '--output-dir=$KLEE_OUT_DIR' '$KLEE_BIN_DIR/a.bc'"
|
||||
fi
|
||||
local line2="for t in '$KLEE_OUT_DIR'/*.err"
|
||||
line2="$line2 ; do ktest-tool --write-ints"
|
||||
line2="$line2 \"\$(printf '%s' \"\$t\" | sed -e 's@\\.[^/]*\$@.ktest@')\""
|
||||
line2="$line2 ; done"
|
||||
printf '%s\n%s\n' "$line1" "$line2" | \
|
||||
docker run \
|
||||
--volume "$(cd "$PROJECT_SOURCE_DIR" && pwd)":/image \
|
||||
--volume "/usr/include":/host-includes \
|
||||
--interactive \
|
||||
--rm \
|
||||
--ulimit='stack=-1:-1' \
|
||||
klee/klee \
|
||||
/bin/sh -x
|
||||
else
|
||||
clang \
|
||||
$includes $defines \
|
||||
-o "$KLEE_BIN_DIR/$test" \
|
||||
-O0 -g \
|
||||
"$KLEE_TEST_DIR/$test.c"
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
|
@ -0,0 +1,105 @@
|
|||
#ifdef USE_KLEE
|
||||
# include <klee/klee.h>
|
||||
#else
|
||||
# include <string.h>
|
||||
# include <stdio.h>
|
||||
#endif
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "nvim/viml/parser/expressions.h"
|
||||
#include "nvim/viml/parser/parser.h"
|
||||
#include "nvim/mbyte.h"
|
||||
|
||||
#include "nvim/memory.c"
|
||||
#include "nvim/mbyte.c"
|
||||
#include "nvim/charset.c"
|
||||
#include "nvim/garray.c"
|
||||
#include "nvim/gettext.c"
|
||||
#include "nvim/keymap.c"
|
||||
#include "nvim/viml/parser/expressions.c"
|
||||
|
||||
#define INPUT_SIZE 7
|
||||
|
||||
uint8_t avoid_optimizing_out;
|
||||
|
||||
void simple_get_line(void *cookie, ParserLine *ret_pline)
|
||||
{
|
||||
ParserLine **plines_p = (ParserLine **)cookie;
|
||||
*ret_pline = **plines_p;
|
||||
(*plines_p)++;
|
||||
}
|
||||
|
||||
int main(const int argc, const char *const *const argv,
|
||||
const char *const *const environ)
|
||||
{
|
||||
char input[INPUT_SIZE];
|
||||
uint8_t shift;
|
||||
int flags;
|
||||
avoid_optimizing_out = argc;
|
||||
|
||||
#ifndef USE_KLEE
|
||||
sscanf(argv[2], "%d", &flags);
|
||||
#endif
|
||||
|
||||
#ifdef USE_KLEE
|
||||
klee_make_symbolic(input, sizeof(input), "input");
|
||||
klee_make_symbolic(&shift, sizeof(shift), "shift");
|
||||
klee_make_symbolic(&flags, sizeof(flags), "flags");
|
||||
klee_assume(shift < INPUT_SIZE);
|
||||
klee_assume(flags <= (kELFlagPeek|kELFlagAllowFloat|kELFlagForbidEOC
|
||||
|kELFlagForbidScope|kELFlagIsNotCmp));
|
||||
#endif
|
||||
|
||||
ParserLine plines[] = {
|
||||
{
|
||||
#ifdef USE_KLEE
|
||||
.data = &input[shift],
|
||||
.size = sizeof(input) - shift,
|
||||
#else
|
||||
.data = (const char *)argv[1],
|
||||
.size = strlen(argv[1]),
|
||||
#endif
|
||||
.allocated = false,
|
||||
},
|
||||
{
|
||||
.data = NULL,
|
||||
.size = 0,
|
||||
.allocated = false,
|
||||
},
|
||||
};
|
||||
#ifdef USE_KLEE
|
||||
assert(plines[0].size <= INPUT_SIZE);
|
||||
assert((plines[0].data[0] != 5) | (plines[0].data[0] != argc));
|
||||
#endif
|
||||
ParserLine *cur_pline = &plines[0];
|
||||
|
||||
ParserState pstate = {
|
||||
.reader = {
|
||||
.get_line = simple_get_line,
|
||||
.cookie = &cur_pline,
|
||||
.lines = KV_INITIAL_VALUE,
|
||||
.conv.vc_type = CONV_NONE,
|
||||
},
|
||||
.pos = { 0, 0 },
|
||||
.colors = NULL,
|
||||
.can_continuate = false,
|
||||
};
|
||||
kvi_init(pstate.reader.lines);
|
||||
|
||||
allocated_memory_limit = 0;
|
||||
LexExprToken token = viml_pexpr_next_token(&pstate, flags);
|
||||
if (flags & kELFlagPeek) {
|
||||
assert(pstate.pos.line == 0 && pstate.pos.col == 0);
|
||||
} else {
|
||||
assert((pstate.pos.line == 0)
|
||||
? (pstate.pos.col > 0)
|
||||
: (pstate.pos.line == 1 && pstate.pos.col == 0));
|
||||
}
|
||||
assert(allocated_memory == 0);
|
||||
assert(ever_allocated_memory == 0);
|
||||
#ifndef USE_KLEE
|
||||
fprintf(stderr, "tkn: %s\n", viml_pexpr_repr_token(&pstate, token, NULL));
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
#ifdef USE_KLEE
|
||||
# include <klee/klee.h>
|
||||
#else
|
||||
# include <string.h>
|
||||
#endif
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "nvim/viml/parser/expressions.h"
|
||||
#include "nvim/viml/parser/parser.h"
|
||||
#include "nvim/mbyte.h"
|
||||
|
||||
#include "nvim/memory.c"
|
||||
#include "nvim/mbyte.c"
|
||||
#include "nvim/charset.c"
|
||||
#include "nvim/garray.c"
|
||||
#include "nvim/gettext.c"
|
||||
#include "nvim/viml/parser/expressions.c"
|
||||
#include "nvim/keymap.c"
|
||||
|
||||
#define INPUT_SIZE 50
|
||||
|
||||
uint8_t avoid_optimizing_out;
|
||||
|
||||
void simple_get_line(void *cookie, ParserLine *ret_pline)
|
||||
{
|
||||
ParserLine **plines_p = (ParserLine **)cookie;
|
||||
*ret_pline = **plines_p;
|
||||
(*plines_p)++;
|
||||
}
|
||||
|
||||
int main(const int argc, const char *const *const argv,
|
||||
const char *const *const environ)
|
||||
{
|
||||
char input[INPUT_SIZE];
|
||||
uint8_t shift;
|
||||
unsigned flags;
|
||||
const bool peek = false;
|
||||
avoid_optimizing_out = argc;
|
||||
|
||||
#ifndef USE_KLEE
|
||||
sscanf(argv[2], "%d", &flags);
|
||||
#endif
|
||||
|
||||
#ifdef USE_KLEE
|
||||
klee_make_symbolic(input, sizeof(input), "input");
|
||||
klee_make_symbolic(&shift, sizeof(shift), "shift");
|
||||
klee_make_symbolic(&flags, sizeof(flags), "flags");
|
||||
klee_assume(shift < INPUT_SIZE);
|
||||
klee_assume(
|
||||
flags <= (kExprFlagsMulti|kExprFlagsDisallowEOC|kExprFlagsParseLet));
|
||||
#endif
|
||||
|
||||
ParserLine plines[] = {
|
||||
{
|
||||
#ifdef USE_KLEE
|
||||
.data = &input[shift],
|
||||
.size = sizeof(input) - shift,
|
||||
#else
|
||||
.data = argv[1],
|
||||
.size = strlen(argv[1]),
|
||||
#endif
|
||||
.allocated = false,
|
||||
},
|
||||
{
|
||||
.data = NULL,
|
||||
.size = 0,
|
||||
.allocated = false,
|
||||
},
|
||||
};
|
||||
#ifdef USE_KLEE
|
||||
assert(plines[0].size <= INPUT_SIZE);
|
||||
assert((plines[0].data[0] != 5) | (plines[0].data[0] != argc));
|
||||
#endif
|
||||
ParserLine *cur_pline = &plines[0];
|
||||
|
||||
ParserHighlight colors;
|
||||
kvi_init(colors);
|
||||
|
||||
ParserState pstate = {
|
||||
.reader = {
|
||||
.get_line = simple_get_line,
|
||||
.cookie = &cur_pline,
|
||||
.lines = KV_INITIAL_VALUE,
|
||||
.conv.vc_type = CONV_NONE,
|
||||
},
|
||||
.pos = { 0, 0 },
|
||||
.colors = &colors,
|
||||
.can_continuate = false,
|
||||
};
|
||||
kvi_init(pstate.reader.lines);
|
||||
|
||||
const ExprAST ast = viml_pexpr_parse(&pstate, (int)flags);
|
||||
assert(ast.root != NULL || ast.err.msg);
|
||||
if (flags & kExprFlagsParseLet) {
|
||||
assert(ast.err.msg != NULL
|
||||
|| ast.root->type == kExprNodeAssignment
|
||||
|| (ast.root->type == kExprNodeListLiteral
|
||||
&& ast.root->children != NULL)
|
||||
|| ast.root->type == kExprNodeComplexIdentifier
|
||||
|| ast.root->type == kExprNodeCurlyBracesIdentifier
|
||||
|| ast.root->type == kExprNodePlainIdentifier
|
||||
|| ast.root->type == kExprNodeRegister
|
||||
|| ast.root->type == kExprNodeEnvironment
|
||||
|| ast.root->type == kExprNodeOption
|
||||
|| ast.root->type == kExprNodeSubscript
|
||||
|| ast.root->type == kExprNodeConcatOrSubscript);
|
||||
}
|
||||
// Can’t possibly have more highlight tokens then there are bytes in string.
|
||||
assert(kv_size(colors) <= INPUT_SIZE - shift);
|
||||
kvi_destroy(colors);
|
||||
// Not destroying pstate.reader.lines because there is no way it could exceed
|
||||
// its limits in the current circumstances.
|
||||
viml_pexpr_free_ast(ast);
|
||||
assert(allocated_memory == 0);
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
local helpers = require("test.unit.helpers")(after_each)
|
||||
local bit = require('bit')
|
||||
|
||||
local itp = helpers.gen_itp(it)
|
||||
|
||||
local child_call_once = helpers.child_call_once
|
||||
local cimport = helpers.cimport
|
||||
local ffi = helpers.ffi
|
||||
|
||||
local lib = cimport('./src/nvim/charset.h')
|
||||
|
||||
local ARGTYPES
|
||||
|
||||
child_call_once(function()
|
||||
ARGTYPES = {
|
||||
num = ffi.typeof('varnumber_T[1]'),
|
||||
unum = ffi.typeof('uvarnumber_T[1]'),
|
||||
pre = ffi.typeof('int[1]'),
|
||||
len = ffi.typeof('int[1]'),
|
||||
}
|
||||
end)
|
||||
|
||||
local icnt = -42
|
||||
local ucnt = 4242
|
||||
|
||||
local function arginit(arg)
|
||||
if arg == 'unum' then
|
||||
ucnt = ucnt + 1
|
||||
return ARGTYPES[arg]({ucnt})
|
||||
else
|
||||
icnt = icnt - 1
|
||||
return ARGTYPES[arg]({icnt})
|
||||
end
|
||||
end
|
||||
|
||||
local function argreset(arg, args)
|
||||
if arg == 'unum' then
|
||||
ucnt = ucnt + 1
|
||||
args[arg][0] = ucnt
|
||||
else
|
||||
icnt = icnt - 1
|
||||
args[arg][0] = icnt
|
||||
end
|
||||
end
|
||||
|
||||
local function test_vim_str2nr(s, what, exp, maxlen)
|
||||
local bits = {}
|
||||
for k, _ in pairs(exp) do
|
||||
bits[#bits + 1] = k
|
||||
end
|
||||
maxlen = maxlen or #s
|
||||
local args = {}
|
||||
for k, _ in pairs(ARGTYPES) do
|
||||
args[k] = arginit(k)
|
||||
end
|
||||
for case = 0, ((2 ^ (#bits)) - 1) do
|
||||
local cv = {}
|
||||
for b = 0, (#bits - 1) do
|
||||
if bit.band(case, (2 ^ b)) == 0 then
|
||||
local k = bits[b + 1]
|
||||
argreset(k, args)
|
||||
cv[k] = args[k]
|
||||
end
|
||||
end
|
||||
lib.vim_str2nr(s, cv.pre, cv.len, what, cv.num, cv.unum, maxlen)
|
||||
for cck, ccv in pairs(cv) do
|
||||
if exp[cck] ~= tonumber(ccv[0]) then
|
||||
error(('Failed check (%s = %d) in test (s=%s, w=%u, m=%d): %d'):format(
|
||||
cck, exp[cck], s, tonumber(what), maxlen, tonumber(ccv[0])
|
||||
))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local _itp = itp
|
||||
itp = function(...)
|
||||
collectgarbage('restart')
|
||||
_itp(...)
|
||||
end
|
||||
|
||||
describe('vim_str2nr()', function()
|
||||
itp('works fine when it has nothing to do', function()
|
||||
test_vim_str2nr('', 0, {len = 0, num = 0, unum = 0, pre = 0}, 0)
|
||||
test_vim_str2nr('', lib.STR2NR_ALL, {len = 0, num = 0, unum = 0, pre = 0}, 0)
|
||||
test_vim_str2nr('', lib.STR2NR_BIN, {len = 0, num = 0, unum = 0, pre = 0}, 0)
|
||||
test_vim_str2nr('', lib.STR2NR_OCT, {len = 0, num = 0, unum = 0, pre = 0}, 0)
|
||||
test_vim_str2nr('', lib.STR2NR_HEX, {len = 0, num = 0, unum = 0, pre = 0}, 0)
|
||||
test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_DEC, {len = 0, num = 0, unum = 0, pre = 0}, 0)
|
||||
test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_BIN, {len = 0, num = 0, unum = 0, pre = 0}, 0)
|
||||
test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_OCT, {len = 0, num = 0, unum = 0, pre = 0}, 0)
|
||||
test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_HEX, {len = 0, num = 0, unum = 0, pre = 0}, 0)
|
||||
end)
|
||||
itp('works with decimal numbers', function()
|
||||
for _, flags in ipairs({
|
||||
0,
|
||||
lib.STR2NR_BIN,
|
||||
lib.STR2NR_OCT,
|
||||
lib.STR2NR_HEX,
|
||||
lib.STR2NR_BIN + lib.STR2NR_OCT,
|
||||
lib.STR2NR_BIN + lib.STR2NR_HEX,
|
||||
lib.STR2NR_OCT + lib.STR2NR_HEX,
|
||||
lib.STR2NR_ALL,
|
||||
lib.STR2NR_FORCE + lib.STR2NR_DEC,
|
||||
}) do
|
||||
-- Check that all digits are recognized
|
||||
test_vim_str2nr( '12345', flags, {len = 5, num = 12345, unum = 12345, pre = 0}, 0)
|
||||
test_vim_str2nr( '67890', flags, {len = 5, num = 67890, unum = 67890, pre = 0}, 0)
|
||||
test_vim_str2nr( '12345A', flags, {len = 5, num = 12345, unum = 12345, pre = 0}, 0)
|
||||
test_vim_str2nr( '67890A', flags, {len = 5, num = 67890, unum = 67890, pre = 0}, 0)
|
||||
|
||||
test_vim_str2nr( '42', flags, {len = 2, num = 42, unum = 42, pre = 0}, 0)
|
||||
test_vim_str2nr( '42', flags, {len = 1, num = 4, unum = 4, pre = 0}, 1)
|
||||
test_vim_str2nr( '42', flags, {len = 2, num = 42, unum = 42, pre = 0}, 2)
|
||||
test_vim_str2nr( '42', flags, {len = 2, num = 42, unum = 42, pre = 0}, 3) -- includes NUL byte in maxlen
|
||||
|
||||
test_vim_str2nr( '42x', flags, {len = 2, num = 42, unum = 42, pre = 0}, 0)
|
||||
test_vim_str2nr( '42x', flags, {len = 2, num = 42, unum = 42, pre = 0}, 3)
|
||||
|
||||
test_vim_str2nr('-42', flags, {len = 3, num = -42, unum = 42, pre = 0}, 3)
|
||||
test_vim_str2nr('-42', flags, {len = 1, num = 0, unum = 0, pre = 0}, 1)
|
||||
|
||||
test_vim_str2nr('-42x', flags, {len = 3, num = -42, unum = 42, pre = 0}, 0)
|
||||
test_vim_str2nr('-42x', flags, {len = 3, num = -42, unum = 42, pre = 0}, 4)
|
||||
end
|
||||
end)
|
||||
itp('works with binary numbers', function()
|
||||
for _, flags in ipairs({
|
||||
lib.STR2NR_BIN,
|
||||
lib.STR2NR_BIN + lib.STR2NR_OCT,
|
||||
lib.STR2NR_BIN + lib.STR2NR_HEX,
|
||||
lib.STR2NR_ALL,
|
||||
lib.STR2NR_FORCE + lib.STR2NR_BIN,
|
||||
}) do
|
||||
local bin
|
||||
local BIN
|
||||
if flags > lib.STR2NR_FORCE then
|
||||
bin = 0
|
||||
BIN = 0
|
||||
else
|
||||
bin = ('b'):byte()
|
||||
BIN = ('B'):byte()
|
||||
end
|
||||
|
||||
test_vim_str2nr( '0b101', flags, {len = 5, num = 5, unum = 5, pre = bin}, 0)
|
||||
test_vim_str2nr( '0b101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
|
||||
test_vim_str2nr( '0b101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2)
|
||||
test_vim_str2nr( '0b101', flags, {len = 3, num = 1, unum = 1, pre = bin}, 3)
|
||||
test_vim_str2nr( '0b101', flags, {len = 4, num = 2, unum = 2, pre = bin}, 4)
|
||||
test_vim_str2nr( '0b101', flags, {len = 5, num = 5, unum = 5, pre = bin}, 5)
|
||||
test_vim_str2nr( '0b101', flags, {len = 5, num = 5, unum = 5, pre = bin}, 6)
|
||||
|
||||
test_vim_str2nr( '0b1012', flags, {len = 5, num = 5, unum = 5, pre = bin}, 0)
|
||||
test_vim_str2nr( '0b1012', flags, {len = 5, num = 5, unum = 5, pre = bin}, 6)
|
||||
|
||||
test_vim_str2nr('-0b101', flags, {len = 6, num = -5, unum = 5, pre = bin}, 0)
|
||||
test_vim_str2nr('-0b101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
|
||||
test_vim_str2nr('-0b101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2)
|
||||
test_vim_str2nr('-0b101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3)
|
||||
test_vim_str2nr('-0b101', flags, {len = 4, num = -1, unum = 1, pre = bin}, 4)
|
||||
test_vim_str2nr('-0b101', flags, {len = 5, num = -2, unum = 2, pre = bin}, 5)
|
||||
test_vim_str2nr('-0b101', flags, {len = 6, num = -5, unum = 5, pre = bin}, 6)
|
||||
test_vim_str2nr('-0b101', flags, {len = 6, num = -5, unum = 5, pre = bin}, 7)
|
||||
|
||||
test_vim_str2nr('-0b1012', flags, {len = 6, num = -5, unum = 5, pre = bin}, 0)
|
||||
test_vim_str2nr('-0b1012', flags, {len = 6, num = -5, unum = 5, pre = bin}, 7)
|
||||
|
||||
test_vim_str2nr( '0B101', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 0)
|
||||
test_vim_str2nr( '0B101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
|
||||
test_vim_str2nr( '0B101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2)
|
||||
test_vim_str2nr( '0B101', flags, {len = 3, num = 1, unum = 1, pre = BIN}, 3)
|
||||
test_vim_str2nr( '0B101', flags, {len = 4, num = 2, unum = 2, pre = BIN}, 4)
|
||||
test_vim_str2nr( '0B101', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 5)
|
||||
test_vim_str2nr( '0B101', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 6)
|
||||
|
||||
test_vim_str2nr( '0B1012', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 0)
|
||||
test_vim_str2nr( '0B1012', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 6)
|
||||
|
||||
test_vim_str2nr('-0B101', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 0)
|
||||
test_vim_str2nr('-0B101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
|
||||
test_vim_str2nr('-0B101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2)
|
||||
test_vim_str2nr('-0B101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3)
|
||||
test_vim_str2nr('-0B101', flags, {len = 4, num = -1, unum = 1, pre = BIN}, 4)
|
||||
test_vim_str2nr('-0B101', flags, {len = 5, num = -2, unum = 2, pre = BIN}, 5)
|
||||
test_vim_str2nr('-0B101', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 6)
|
||||
test_vim_str2nr('-0B101', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 7)
|
||||
|
||||
test_vim_str2nr('-0B1012', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 0)
|
||||
test_vim_str2nr('-0B1012', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 7)
|
||||
|
||||
if flags > lib.STR2NR_FORCE then
|
||||
test_vim_str2nr('-101', flags, {len = 4, num = -5, unum = 5, pre = 0}, 0)
|
||||
end
|
||||
end
|
||||
end)
|
||||
itp('works with octal numbers', function()
|
||||
for _, flags in ipairs({
|
||||
lib.STR2NR_OCT,
|
||||
lib.STR2NR_OCT + lib.STR2NR_BIN,
|
||||
lib.STR2NR_OCT + lib.STR2NR_HEX,
|
||||
lib.STR2NR_ALL,
|
||||
lib.STR2NR_FORCE + lib.STR2NR_OCT,
|
||||
}) do
|
||||
local oct
|
||||
if flags > lib.STR2NR_FORCE then
|
||||
oct = 0
|
||||
else
|
||||
oct = ('0'):byte()
|
||||
end
|
||||
|
||||
-- Check that all digits are recognized
|
||||
test_vim_str2nr( '012345670', flags, {len = 9, num = 2739128, unum = 2739128, pre = oct}, 0)
|
||||
|
||||
test_vim_str2nr( '054', flags, {len = 3, num = 44, unum = 44, pre = oct}, 0)
|
||||
test_vim_str2nr( '054', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
|
||||
test_vim_str2nr( '054', flags, {len = 2, num = 5, unum = 5, pre = oct}, 2)
|
||||
test_vim_str2nr( '054', flags, {len = 3, num = 44, unum = 44, pre = oct}, 3)
|
||||
test_vim_str2nr( '0548', flags, {len = 3, num = 44, unum = 44, pre = oct}, 3)
|
||||
test_vim_str2nr( '054', flags, {len = 3, num = 44, unum = 44, pre = oct}, 4)
|
||||
|
||||
test_vim_str2nr( '054x', flags, {len = 3, num = 44, unum = 44, pre = oct}, 4)
|
||||
test_vim_str2nr( '054x', flags, {len = 3, num = 44, unum = 44, pre = oct}, 0)
|
||||
|
||||
test_vim_str2nr('-054', flags, {len = 4, num = -44, unum = 44, pre = oct}, 0)
|
||||
test_vim_str2nr('-054', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
|
||||
test_vim_str2nr('-054', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2)
|
||||
test_vim_str2nr('-054', flags, {len = 3, num = -5, unum = 5, pre = oct}, 3)
|
||||
test_vim_str2nr('-054', flags, {len = 4, num = -44, unum = 44, pre = oct}, 4)
|
||||
test_vim_str2nr('-0548', flags, {len = 4, num = -44, unum = 44, pre = oct}, 4)
|
||||
test_vim_str2nr('-054', flags, {len = 4, num = -44, unum = 44, pre = oct}, 5)
|
||||
|
||||
test_vim_str2nr('-054x', flags, {len = 4, num = -44, unum = 44, pre = oct}, 5)
|
||||
test_vim_str2nr('-054x', flags, {len = 4, num = -44, unum = 44, pre = oct}, 0)
|
||||
|
||||
if flags > lib.STR2NR_FORCE then
|
||||
test_vim_str2nr('-54', flags, {len = 3, num = -44, unum = 44, pre = 0}, 0)
|
||||
test_vim_str2nr('-0548', flags, {len = 4, num = -44, unum = 44, pre = 0}, 5)
|
||||
test_vim_str2nr('-0548', flags, {len = 4, num = -44, unum = 44, pre = 0}, 0)
|
||||
else
|
||||
test_vim_str2nr('-0548', flags, {len = 5, num = -548, unum = 548, pre = 0}, 5)
|
||||
test_vim_str2nr('-0548', flags, {len = 5, num = -548, unum = 548, pre = 0}, 0)
|
||||
end
|
||||
end
|
||||
end)
|
||||
itp('works with hexadecimal numbers', function()
|
||||
for _, flags in ipairs({
|
||||
lib.STR2NR_HEX,
|
||||
lib.STR2NR_HEX + lib.STR2NR_BIN,
|
||||
lib.STR2NR_HEX + lib.STR2NR_OCT,
|
||||
lib.STR2NR_ALL,
|
||||
lib.STR2NR_FORCE + lib.STR2NR_HEX,
|
||||
}) do
|
||||
local hex
|
||||
local HEX
|
||||
if flags > lib.STR2NR_FORCE then
|
||||
hex = 0
|
||||
HEX = 0
|
||||
else
|
||||
hex = ('x'):byte()
|
||||
HEX = ('X'):byte()
|
||||
end
|
||||
|
||||
-- Check that all digits are recognized
|
||||
test_vim_str2nr('0x12345', flags, {len = 7, num = 74565, unum = 74565, pre = hex}, 0)
|
||||
test_vim_str2nr('0x67890', flags, {len = 7, num = 424080, unum = 424080, pre = hex}, 0)
|
||||
test_vim_str2nr('0xABCDEF', flags, {len = 8, num = 11259375, unum = 11259375, pre = hex}, 0)
|
||||
test_vim_str2nr('0xabcdef', flags, {len = 8, num = 11259375, unum = 11259375, pre = hex}, 0)
|
||||
|
||||
test_vim_str2nr( '0x101', flags, {len = 5, num = 257, unum =257, pre = hex}, 0)
|
||||
test_vim_str2nr( '0x101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
|
||||
test_vim_str2nr( '0x101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2)
|
||||
test_vim_str2nr( '0x101', flags, {len = 3, num = 1, unum = 1, pre = hex}, 3)
|
||||
test_vim_str2nr( '0x101', flags, {len = 4, num = 16, unum = 16, pre = hex}, 4)
|
||||
test_vim_str2nr( '0x101', flags, {len = 5, num = 257, unum =257, pre = hex}, 5)
|
||||
test_vim_str2nr( '0x101', flags, {len = 5, num = 257, unum =257, pre = hex}, 6)
|
||||
|
||||
test_vim_str2nr( '0x101G', flags, {len = 5, num = 257, unum =257, pre = hex}, 0)
|
||||
test_vim_str2nr( '0x101G', flags, {len = 5, num = 257, unum =257, pre = hex}, 6)
|
||||
|
||||
test_vim_str2nr('-0x101', flags, {len = 6, num =-257, unum =257, pre = hex}, 0)
|
||||
test_vim_str2nr('-0x101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
|
||||
test_vim_str2nr('-0x101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2)
|
||||
test_vim_str2nr('-0x101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3)
|
||||
test_vim_str2nr('-0x101', flags, {len = 4, num = -1, unum = 1, pre = hex}, 4)
|
||||
test_vim_str2nr('-0x101', flags, {len = 5, num = -16, unum = 16, pre = hex}, 5)
|
||||
test_vim_str2nr('-0x101', flags, {len = 6, num =-257, unum =257, pre = hex}, 6)
|
||||
test_vim_str2nr('-0x101', flags, {len = 6, num =-257, unum =257, pre = hex}, 7)
|
||||
|
||||
test_vim_str2nr('-0x101G', flags, {len = 6, num =-257, unum =257, pre = hex}, 0)
|
||||
test_vim_str2nr('-0x101G', flags, {len = 6, num =-257, unum =257, pre = hex}, 7)
|
||||
|
||||
test_vim_str2nr( '0X101', flags, {len = 5, num = 257, unum =257, pre = HEX}, 0)
|
||||
test_vim_str2nr( '0X101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
|
||||
test_vim_str2nr( '0X101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2)
|
||||
test_vim_str2nr( '0X101', flags, {len = 3, num = 1, unum = 1, pre = HEX}, 3)
|
||||
test_vim_str2nr( '0X101', flags, {len = 4, num = 16, unum = 16, pre = HEX}, 4)
|
||||
test_vim_str2nr( '0X101', flags, {len = 5, num = 257, unum =257, pre = HEX}, 5)
|
||||
test_vim_str2nr( '0X101', flags, {len = 5, num = 257, unum =257, pre = HEX}, 6)
|
||||
|
||||
test_vim_str2nr( '0X101G', flags, {len = 5, num = 257, unum =257, pre = HEX}, 0)
|
||||
test_vim_str2nr( '0X101G', flags, {len = 5, num = 257, unum =257, pre = HEX}, 6)
|
||||
|
||||
test_vim_str2nr('-0X101', flags, {len = 6, num =-257, unum =257, pre = HEX}, 0)
|
||||
test_vim_str2nr('-0X101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
|
||||
test_vim_str2nr('-0X101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2)
|
||||
test_vim_str2nr('-0X101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3)
|
||||
test_vim_str2nr('-0X101', flags, {len = 4, num = -1, unum = 1, pre = HEX}, 4)
|
||||
test_vim_str2nr('-0X101', flags, {len = 5, num = -16, unum = 16, pre = HEX}, 5)
|
||||
test_vim_str2nr('-0X101', flags, {len = 6, num =-257, unum =257, pre = HEX}, 6)
|
||||
test_vim_str2nr('-0X101', flags, {len = 6, num =-257, unum =257, pre = HEX}, 7)
|
||||
|
||||
test_vim_str2nr('-0X101G', flags, {len = 6, num =-257, unum =257, pre = HEX}, 0)
|
||||
test_vim_str2nr('-0X101G', flags, {len = 6, num =-257, unum =257, pre = HEX}, 7)
|
||||
|
||||
if flags > lib.STR2NR_FORCE then
|
||||
test_vim_str2nr('-101', flags, {len = 4, num = -257, unum = 257, pre = 0}, 0)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end)
|
|
@ -1,5 +1,6 @@
|
|||
local helpers = require('test.unit.helpers')(nil)
|
||||
|
||||
local ptr2key = helpers.ptr2key
|
||||
local cimport = helpers.cimport
|
||||
local to_cstr = helpers.to_cstr
|
||||
local ffi = helpers.ffi
|
||||
|
@ -91,10 +92,6 @@ local function populate_partial(pt, lua_pt, processed)
|
|||
return pt
|
||||
end
|
||||
|
||||
local ptr2key = function(ptr)
|
||||
return tostring(ptr)
|
||||
end
|
||||
|
||||
local lst2tbl
|
||||
local dct2tbl
|
||||
|
||||
|
|
|
@ -65,11 +65,12 @@ local tokens = P { "tokens";
|
|||
identifier = Ct(C(R("az","AZ","__") * R("09","az","AZ","__")^0) * Cc"identifier"),
|
||||
|
||||
-- Single character in a string
|
||||
string_char = R("az","AZ","09") + S"$%^&*()_-+={[}]:;@~#<,>.!?/ \t" + (P"\\" * S[[ntvbrfa\?'"0x]]),
|
||||
sstring_char = R("\001&","([","]\255") + (P"\\" * S[[ntvbrfa\?'"0x]]),
|
||||
dstring_char = R("\001!","#[","]\255") + (P"\\" * S[[ntvbrfa\?'"0x]]),
|
||||
|
||||
-- String literal
|
||||
string = Ct(C(P"'" * (V"string_char" + P'"')^0 * P"'" +
|
||||
P'"' * (V"string_char" + P"'")^0 * P'"') * Cc"string"),
|
||||
string = Ct(C(P"'" * (V"sstring_char" + P'"')^0 * P"'" +
|
||||
P'"' * (V"dstring_char" + P"'")^0 * P'"') * Cc"string"),
|
||||
|
||||
-- Operator
|
||||
operator = Ct(C(P">>=" + P"<<=" + P"..." +
|
||||
|
|
|
@ -316,7 +316,7 @@ local function alloc_log_new()
|
|||
eq(exp, self.log)
|
||||
self:clear()
|
||||
end
|
||||
function log:clear_tmp_allocs()
|
||||
function log:clear_tmp_allocs(clear_null_frees)
|
||||
local toremove = {}
|
||||
local allocs = {}
|
||||
for i, v in ipairs(self.log) do
|
||||
|
@ -328,6 +328,8 @@ local function alloc_log_new()
|
|||
if v.func == 'free' then
|
||||
toremove[#toremove + 1] = i
|
||||
end
|
||||
elseif clear_null_frees and v.args[1] == self.null then
|
||||
toremove[#toremove + 1] = i
|
||||
end
|
||||
if v.func == 'realloc' then
|
||||
allocs[tostring(v.ret)] = i
|
||||
|
@ -528,9 +530,13 @@ local hook_numlen = 5
|
|||
local hook_msglen = 1 + 1 + 1 + (1 + hook_fnamelen) + (1 + hook_sfnamelen) + (1 + hook_numlen) + 1
|
||||
|
||||
local tracehelp = dedent([[
|
||||
Trace: either in the format described below or custom debug output starting
|
||||
with `>`. Latter lines still have the same width in byte.
|
||||
|
||||
┌ Trace type: _r_eturn from function , function _c_all, _l_ine executed,
|
||||
│ _t_ail return, _C_ount (should not actually appear),
|
||||
│ _s_aved from previous run for reference.
|
||||
│ _s_aved from previous run for reference, _>_ for custom debug
|
||||
│ output.
|
||||
│┏ Function type: _L_ua function, _C_ function, _m_ain part of chunk,
|
||||
│┃ function that did _t_ail call.
|
||||
│┃┌ Function name type: _g_lobal, _l_ocal, _m_ethod, _f_ield, _u_pvalue,
|
||||
|
@ -628,14 +634,26 @@ end
|
|||
|
||||
local trace_end_msg = ('E%s\n'):format((' '):rep(hook_msglen - 2))
|
||||
|
||||
local _debug_log
|
||||
|
||||
local debug_log = only_separate(function(...)
|
||||
return _debug_log(...)
|
||||
end)
|
||||
|
||||
local function itp_child(wr, func)
|
||||
init()
|
||||
collectgarbage('stop')
|
||||
child_sethook(wr)
|
||||
local err, emsg = pcall(func)
|
||||
collectgarbage('restart')
|
||||
collectgarbage()
|
||||
debug.sethook()
|
||||
_debug_log = function(s)
|
||||
s = s:sub(1, hook_msglen - 2)
|
||||
sc.write(wr, '>' .. s .. (' '):rep(hook_msglen - 2 - #s) .. '\n')
|
||||
end
|
||||
local err, emsg = pcall(init)
|
||||
if err then
|
||||
collectgarbage('stop')
|
||||
child_sethook(wr)
|
||||
err, emsg = pcall(func)
|
||||
collectgarbage('restart')
|
||||
collectgarbage()
|
||||
debug.sethook()
|
||||
end
|
||||
emsg = tostring(emsg)
|
||||
sc.write(wr, trace_end_msg)
|
||||
if not err then
|
||||
|
@ -657,6 +675,7 @@ end
|
|||
local function check_child_err(rd)
|
||||
local trace = {}
|
||||
local did_traceline = false
|
||||
local maxtrace = tonumber(os.getenv('NVIM_TEST_MAXTRACE')) or 1024
|
||||
while true do
|
||||
local traceline = sc.read(rd, hook_msglen)
|
||||
if #traceline ~= hook_msglen then
|
||||
|
@ -671,6 +690,7 @@ local function check_child_err(rd)
|
|||
break
|
||||
end
|
||||
trace[#trace + 1] = traceline
|
||||
table.remove(trace, maxtrace + 1)
|
||||
end
|
||||
local res = sc.read(rd, 2)
|
||||
if #res ~= 2 then
|
||||
|
@ -699,7 +719,14 @@ local function check_child_err(rd)
|
|||
local len_s = sc.read(rd, 5)
|
||||
local len = tonumber(len_s)
|
||||
neq(0, len)
|
||||
local err = sc.read(rd, len + 1)
|
||||
local err = ''
|
||||
if os.getenv('NVIM_TEST_TRACE_ON_ERROR') == '1' and #trace ~= 0 then
|
||||
err = '\nTest failed, trace:\n' .. tracehelp
|
||||
for _, traceline in ipairs(trace) do
|
||||
err = err .. traceline
|
||||
end
|
||||
end
|
||||
err = err .. sc.read(rd, len + 1)
|
||||
assert.just_fail(err)
|
||||
end
|
||||
|
||||
|
@ -754,6 +781,60 @@ end
|
|||
|
||||
cimport('./src/nvim/types.h', './src/nvim/main.h', './src/nvim/os/time.h')
|
||||
|
||||
local function conv_enum(etab, eval)
|
||||
local n = tonumber(eval)
|
||||
return etab[n] or n
|
||||
end
|
||||
|
||||
local function array_size(arr)
|
||||
return ffi.sizeof(arr) / ffi.sizeof(arr[0])
|
||||
end
|
||||
|
||||
local function kvi_size(kvi)
|
||||
return array_size(kvi.init_array)
|
||||
end
|
||||
|
||||
local function kvi_init(kvi)
|
||||
kvi.capacity = kvi_size(kvi)
|
||||
kvi.items = kvi.init_array
|
||||
return kvi
|
||||
end
|
||||
|
||||
local function kvi_destroy(kvi)
|
||||
if kvi.items ~= kvi.init_array then
|
||||
lib.xfree(kvi.items)
|
||||
end
|
||||
end
|
||||
|
||||
local function kvi_new(ct)
|
||||
return kvi_init(ffi.new(ct))
|
||||
end
|
||||
|
||||
local function make_enum_conv_tab(m, values, skip_pref, set_cb)
|
||||
child_call_once(function()
|
||||
local ret = {}
|
||||
for _, v in ipairs(values) do
|
||||
local str_v = v
|
||||
if v:sub(1, #skip_pref) == skip_pref then
|
||||
str_v = v:sub(#skip_pref + 1)
|
||||
end
|
||||
ret[tonumber(m[v])] = str_v
|
||||
end
|
||||
set_cb(ret)
|
||||
end)
|
||||
end
|
||||
|
||||
local function ptr2addr(ptr)
|
||||
return tonumber(ffi.cast('intptr_t', ffi.cast('void *', ptr)))
|
||||
end
|
||||
|
||||
local s = ffi.new('char[64]', {0})
|
||||
|
||||
local function ptr2key(ptr)
|
||||
ffi.C.snprintf(s, ffi.sizeof(s), '%p', ffi.cast('void *', ptr))
|
||||
return ffi.string(s)
|
||||
end
|
||||
|
||||
local module = {
|
||||
cimport = cimport,
|
||||
cppimport = cppimport,
|
||||
|
@ -774,6 +855,16 @@ local module = {
|
|||
child_call_once = child_call_once,
|
||||
child_cleanup_once = child_cleanup_once,
|
||||
sc = sc,
|
||||
conv_enum = conv_enum,
|
||||
array_size = array_size,
|
||||
kvi_destroy = kvi_destroy,
|
||||
kvi_size = kvi_size,
|
||||
kvi_init = kvi_init,
|
||||
kvi_new = kvi_new,
|
||||
make_enum_conv_tab = make_enum_conv_tab,
|
||||
ptr2addr = ptr2addr,
|
||||
ptr2key = ptr2key,
|
||||
debug_log = debug_log,
|
||||
}
|
||||
return function()
|
||||
return module
|
||||
|
|
|
@ -0,0 +1,428 @@
|
|||
local helpers = require('test.unit.helpers')(after_each)
|
||||
local global_helpers = require('test.helpers')
|
||||
local itp = helpers.gen_itp(it)
|
||||
local viml_helpers = require('test.unit.viml.helpers')
|
||||
|
||||
local child_call_once = helpers.child_call_once
|
||||
local conv_enum = helpers.conv_enum
|
||||
local cimport = helpers.cimport
|
||||
local ffi = helpers.ffi
|
||||
local eq = helpers.eq
|
||||
|
||||
local conv_ccs = viml_helpers.conv_ccs
|
||||
local new_pstate = viml_helpers.new_pstate
|
||||
local conv_cmp_type = viml_helpers.conv_cmp_type
|
||||
local pstate_set_str = viml_helpers.pstate_set_str
|
||||
local conv_expr_asgn_type = viml_helpers.conv_expr_asgn_type
|
||||
|
||||
local shallowcopy = global_helpers.shallowcopy
|
||||
local intchar2lua = global_helpers.intchar2lua
|
||||
|
||||
local lib = cimport('./src/nvim/viml/parser/expressions.h')
|
||||
|
||||
local eltkn_type_tab, eltkn_mul_type_tab, eltkn_opt_scope_tab
|
||||
child_call_once(function()
|
||||
eltkn_type_tab = {
|
||||
[tonumber(lib.kExprLexInvalid)] = 'Invalid',
|
||||
[tonumber(lib.kExprLexMissing)] = 'Missing',
|
||||
[tonumber(lib.kExprLexSpacing)] = 'Spacing',
|
||||
[tonumber(lib.kExprLexEOC)] = 'EOC',
|
||||
|
||||
[tonumber(lib.kExprLexQuestion)] = 'Question',
|
||||
[tonumber(lib.kExprLexColon)] = 'Colon',
|
||||
[tonumber(lib.kExprLexOr)] = 'Or',
|
||||
[tonumber(lib.kExprLexAnd)] = 'And',
|
||||
[tonumber(lib.kExprLexComparison)] = 'Comparison',
|
||||
[tonumber(lib.kExprLexPlus)] = 'Plus',
|
||||
[tonumber(lib.kExprLexMinus)] = 'Minus',
|
||||
[tonumber(lib.kExprLexDot)] = 'Dot',
|
||||
[tonumber(lib.kExprLexMultiplication)] = 'Multiplication',
|
||||
|
||||
[tonumber(lib.kExprLexNot)] = 'Not',
|
||||
|
||||
[tonumber(lib.kExprLexNumber)] = 'Number',
|
||||
[tonumber(lib.kExprLexSingleQuotedString)] = 'SingleQuotedString',
|
||||
[tonumber(lib.kExprLexDoubleQuotedString)] = 'DoubleQuotedString',
|
||||
[tonumber(lib.kExprLexOption)] = 'Option',
|
||||
[tonumber(lib.kExprLexRegister)] = 'Register',
|
||||
[tonumber(lib.kExprLexEnv)] = 'Env',
|
||||
[tonumber(lib.kExprLexPlainIdentifier)] = 'PlainIdentifier',
|
||||
|
||||
[tonumber(lib.kExprLexBracket)] = 'Bracket',
|
||||
[tonumber(lib.kExprLexFigureBrace)] = 'FigureBrace',
|
||||
[tonumber(lib.kExprLexParenthesis)] = 'Parenthesis',
|
||||
[tonumber(lib.kExprLexComma)] = 'Comma',
|
||||
[tonumber(lib.kExprLexArrow)] = 'Arrow',
|
||||
|
||||
[tonumber(lib.kExprLexAssignment)] = 'Assignment',
|
||||
}
|
||||
|
||||
eltkn_mul_type_tab = {
|
||||
[tonumber(lib.kExprLexMulMul)] = 'Mul',
|
||||
[tonumber(lib.kExprLexMulDiv)] = 'Div',
|
||||
[tonumber(lib.kExprLexMulMod)] = 'Mod',
|
||||
}
|
||||
|
||||
eltkn_opt_scope_tab = {
|
||||
[tonumber(lib.kExprOptScopeUnspecified)] = 'Unspecified',
|
||||
[tonumber(lib.kExprOptScopeGlobal)] = 'Global',
|
||||
[tonumber(lib.kExprOptScopeLocal)] = 'Local',
|
||||
}
|
||||
end)
|
||||
|
||||
local function conv_eltkn_type(typ)
|
||||
return conv_enum(eltkn_type_tab, typ)
|
||||
end
|
||||
|
||||
local bracket_types = {
|
||||
Bracket = true,
|
||||
FigureBrace = true,
|
||||
Parenthesis = true,
|
||||
}
|
||||
|
||||
local function eltkn2lua(pstate, tkn)
|
||||
local ret = {
|
||||
type = conv_eltkn_type(tkn.type),
|
||||
}
|
||||
pstate_set_str(pstate, tkn.start, tkn.len, ret)
|
||||
if not ret.error and (#(ret.str) ~= ret.len) then
|
||||
ret.error = '#str /= len'
|
||||
end
|
||||
if ret.type == 'Comparison' then
|
||||
ret.data = {
|
||||
type = conv_cmp_type(tkn.data.cmp.type),
|
||||
ccs = conv_ccs(tkn.data.cmp.ccs),
|
||||
inv = (not not tkn.data.cmp.inv),
|
||||
}
|
||||
elseif ret.type == 'Multiplication' then
|
||||
ret.data = { type = conv_enum(eltkn_mul_type_tab, tkn.data.mul.type) }
|
||||
elseif bracket_types[ret.type] then
|
||||
ret.data = { closing = (not not tkn.data.brc.closing) }
|
||||
elseif ret.type == 'Register' then
|
||||
ret.data = { name = intchar2lua(tkn.data.reg.name) }
|
||||
elseif (ret.type == 'SingleQuotedString'
|
||||
or ret.type == 'DoubleQuotedString') then
|
||||
ret.data = { closed = (not not tkn.data.str.closed) }
|
||||
elseif ret.type == 'Option' then
|
||||
ret.data = {
|
||||
scope = conv_enum(eltkn_opt_scope_tab, tkn.data.opt.scope),
|
||||
name = ffi.string(tkn.data.opt.name, tkn.data.opt.len),
|
||||
}
|
||||
elseif ret.type == 'PlainIdentifier' then
|
||||
ret.data = {
|
||||
scope = intchar2lua(tkn.data.var.scope),
|
||||
autoload = (not not tkn.data.var.autoload),
|
||||
}
|
||||
elseif ret.type == 'Number' then
|
||||
ret.data = {
|
||||
is_float = (not not tkn.data.num.is_float),
|
||||
base = tonumber(tkn.data.num.base),
|
||||
}
|
||||
ret.data.val = tonumber(tkn.data.num.is_float
|
||||
and tkn.data.num.val.floating
|
||||
or tkn.data.num.val.integer)
|
||||
elseif ret.type == 'Assignment' then
|
||||
ret.data = { type = conv_expr_asgn_type(tkn.data.ass.type) }
|
||||
elseif ret.type == 'Invalid' then
|
||||
ret.data = { error = ffi.string(tkn.data.err.msg) }
|
||||
end
|
||||
return ret, tkn
|
||||
end
|
||||
|
||||
local function next_eltkn(pstate, flags)
|
||||
return eltkn2lua(pstate, lib.viml_pexpr_next_token(pstate, flags))
|
||||
end
|
||||
|
||||
describe('Expressions lexer', function()
|
||||
local flags = 0
|
||||
local should_advance = true
|
||||
local function check_advance(pstate, bytes_to_advance, initial_col)
|
||||
local tgt = initial_col + bytes_to_advance
|
||||
if should_advance then
|
||||
if pstate.reader.lines.items[0].size == tgt then
|
||||
eq(1, pstate.pos.line)
|
||||
eq(0, pstate.pos.col)
|
||||
else
|
||||
eq(0, pstate.pos.line)
|
||||
eq(tgt, pstate.pos.col)
|
||||
end
|
||||
else
|
||||
eq(0, pstate.pos.line)
|
||||
eq(initial_col, pstate.pos.col)
|
||||
end
|
||||
end
|
||||
local function singl_eltkn_test(typ, str, data)
|
||||
local pstate = new_pstate({str})
|
||||
eq({data=data, len=#str, start={col=0, line=0}, str=str, type=typ},
|
||||
next_eltkn(pstate, flags))
|
||||
check_advance(pstate, #str, 0)
|
||||
if not (
|
||||
typ == 'Spacing'
|
||||
or (typ == 'Register' and str == '@')
|
||||
or ((typ == 'SingleQuotedString' or typ == 'DoubleQuotedString')
|
||||
and not data.closed)
|
||||
) then
|
||||
pstate = new_pstate({str .. ' '})
|
||||
eq({data=data, len=#str, start={col=0, line=0}, str=str, type=typ},
|
||||
next_eltkn(pstate, flags))
|
||||
check_advance(pstate, #str, 0)
|
||||
end
|
||||
pstate = new_pstate({'x' .. str})
|
||||
pstate.pos.col = 1
|
||||
eq({data=data, len=#str, start={col=1, line=0}, str=str, type=typ},
|
||||
next_eltkn(pstate, flags))
|
||||
check_advance(pstate, #str, 1)
|
||||
end
|
||||
local function scope_test(scope)
|
||||
singl_eltkn_test('PlainIdentifier', scope .. ':test#var', {autoload=true, scope=scope})
|
||||
singl_eltkn_test('PlainIdentifier', scope .. ':', {autoload=false, scope=scope})
|
||||
end
|
||||
local function comparison_test(op, inv_op, cmp_type)
|
||||
singl_eltkn_test('Comparison', op, {type=cmp_type, inv=false, ccs='UseOption'})
|
||||
singl_eltkn_test('Comparison', inv_op, {type=cmp_type, inv=true, ccs='UseOption'})
|
||||
singl_eltkn_test('Comparison', op .. '#', {type=cmp_type, inv=false, ccs='MatchCase'})
|
||||
singl_eltkn_test('Comparison', inv_op .. '#', {type=cmp_type, inv=true, ccs='MatchCase'})
|
||||
singl_eltkn_test('Comparison', op .. '?', {type=cmp_type, inv=false, ccs='IgnoreCase'})
|
||||
singl_eltkn_test('Comparison', inv_op .. '?', {type=cmp_type, inv=true, ccs='IgnoreCase'})
|
||||
end
|
||||
local function simple_test(pstate_arg, exp_type, exp_len, exp)
|
||||
local pstate = new_pstate(pstate_arg)
|
||||
exp = shallowcopy(exp)
|
||||
exp.type = exp_type
|
||||
exp.len = exp_len or #(pstate_arg[0])
|
||||
exp.start = { col = 0, line = 0 }
|
||||
eq(exp, next_eltkn(pstate, flags))
|
||||
end
|
||||
local function stable_tests()
|
||||
singl_eltkn_test('Parenthesis', '(', {closing=false})
|
||||
singl_eltkn_test('Parenthesis', ')', {closing=true})
|
||||
singl_eltkn_test('Bracket', '[', {closing=false})
|
||||
singl_eltkn_test('Bracket', ']', {closing=true})
|
||||
singl_eltkn_test('FigureBrace', '{', {closing=false})
|
||||
singl_eltkn_test('FigureBrace', '}', {closing=true})
|
||||
singl_eltkn_test('Question', '?')
|
||||
singl_eltkn_test('Colon', ':')
|
||||
singl_eltkn_test('Dot', '.')
|
||||
singl_eltkn_test('Assignment', '.=', {type='Concat'})
|
||||
singl_eltkn_test('Plus', '+')
|
||||
singl_eltkn_test('Assignment', '+=', {type='Add'})
|
||||
singl_eltkn_test('Comma', ',')
|
||||
singl_eltkn_test('Multiplication', '*', {type='Mul'})
|
||||
singl_eltkn_test('Multiplication', '/', {type='Div'})
|
||||
singl_eltkn_test('Multiplication', '%', {type='Mod'})
|
||||
singl_eltkn_test('Spacing', ' \t\t \t\t')
|
||||
singl_eltkn_test('Spacing', ' ')
|
||||
singl_eltkn_test('Spacing', '\t')
|
||||
singl_eltkn_test('Invalid', '\x01\x02\x03', {error='E15: Invalid control character present in input: %.*s'})
|
||||
singl_eltkn_test('Number', '0123', {is_float=false, base=8, val=83})
|
||||
singl_eltkn_test('Number', '01234567', {is_float=false, base=8, val=342391})
|
||||
singl_eltkn_test('Number', '012345678', {is_float=false, base=10, val=12345678})
|
||||
singl_eltkn_test('Number', '0x123', {is_float=false, base=16, val=291})
|
||||
singl_eltkn_test('Number', '0x56FF', {is_float=false, base=16, val=22271})
|
||||
singl_eltkn_test('Number', '0xabcdef', {is_float=false, base=16, val=11259375})
|
||||
singl_eltkn_test('Number', '0xABCDEF', {is_float=false, base=16, val=11259375})
|
||||
singl_eltkn_test('Number', '0x0', {is_float=false, base=16, val=0})
|
||||
singl_eltkn_test('Number', '00', {is_float=false, base=8, val=0})
|
||||
singl_eltkn_test('Number', '0b0', {is_float=false, base=2, val=0})
|
||||
singl_eltkn_test('Number', '0b010111', {is_float=false, base=2, val=23})
|
||||
singl_eltkn_test('Number', '0b100111', {is_float=false, base=2, val=39})
|
||||
singl_eltkn_test('Number', '0', {is_float=false, base=10, val=0})
|
||||
singl_eltkn_test('Number', '9', {is_float=false, base=10, val=9})
|
||||
singl_eltkn_test('Env', '$abc')
|
||||
singl_eltkn_test('Env', '$')
|
||||
singl_eltkn_test('PlainIdentifier', 'test', {autoload=false, scope=0})
|
||||
singl_eltkn_test('PlainIdentifier', '_test', {autoload=false, scope=0})
|
||||
singl_eltkn_test('PlainIdentifier', '_test_foo', {autoload=false, scope=0})
|
||||
singl_eltkn_test('PlainIdentifier', 't', {autoload=false, scope=0})
|
||||
singl_eltkn_test('PlainIdentifier', 'test5', {autoload=false, scope=0})
|
||||
singl_eltkn_test('PlainIdentifier', 't0', {autoload=false, scope=0})
|
||||
singl_eltkn_test('PlainIdentifier', 'test#var', {autoload=true, scope=0})
|
||||
singl_eltkn_test('PlainIdentifier', 'test#var#val###', {autoload=true, scope=0})
|
||||
singl_eltkn_test('PlainIdentifier', 't#####', {autoload=true, scope=0})
|
||||
singl_eltkn_test('And', '&&')
|
||||
singl_eltkn_test('Or', '||')
|
||||
singl_eltkn_test('Invalid', '&', {error='E112: Option name missing: %.*s'})
|
||||
singl_eltkn_test('Option', '&opt', {scope='Unspecified', name='opt'})
|
||||
singl_eltkn_test('Option', '&t_xx', {scope='Unspecified', name='t_xx'})
|
||||
singl_eltkn_test('Option', '&t_\r\r', {scope='Unspecified', name='t_\r\r'})
|
||||
singl_eltkn_test('Option', '&t_\t\t', {scope='Unspecified', name='t_\t\t'})
|
||||
singl_eltkn_test('Option', '&t_ ', {scope='Unspecified', name='t_ '})
|
||||
singl_eltkn_test('Option', '&g:opt', {scope='Global', name='opt'})
|
||||
singl_eltkn_test('Option', '&l:opt', {scope='Local', name='opt'})
|
||||
singl_eltkn_test('Invalid', '&l:', {error='E112: Option name missing: %.*s'})
|
||||
singl_eltkn_test('Invalid', '&g:', {error='E112: Option name missing: %.*s'})
|
||||
singl_eltkn_test('Register', '@', {name=-1})
|
||||
singl_eltkn_test('Register', '@a', {name='a'})
|
||||
singl_eltkn_test('Register', '@\r', {name=13})
|
||||
singl_eltkn_test('Register', '@ ', {name=' '})
|
||||
singl_eltkn_test('Register', '@\t', {name=9})
|
||||
singl_eltkn_test('SingleQuotedString', '\'test', {closed=false})
|
||||
singl_eltkn_test('SingleQuotedString', '\'test\'', {closed=true})
|
||||
singl_eltkn_test('SingleQuotedString', '\'\'\'\'', {closed=true})
|
||||
singl_eltkn_test('SingleQuotedString', '\'x\'\'\'', {closed=true})
|
||||
singl_eltkn_test('SingleQuotedString', '\'\'\'x\'', {closed=true})
|
||||
singl_eltkn_test('SingleQuotedString', '\'\'\'', {closed=false})
|
||||
singl_eltkn_test('SingleQuotedString', '\'x\'\'', {closed=false})
|
||||
singl_eltkn_test('SingleQuotedString', '\'\'\'x', {closed=false})
|
||||
singl_eltkn_test('DoubleQuotedString', '"test', {closed=false})
|
||||
singl_eltkn_test('DoubleQuotedString', '"test"', {closed=true})
|
||||
singl_eltkn_test('DoubleQuotedString', '"\\""', {closed=true})
|
||||
singl_eltkn_test('DoubleQuotedString', '"x\\""', {closed=true})
|
||||
singl_eltkn_test('DoubleQuotedString', '"\\"x"', {closed=true})
|
||||
singl_eltkn_test('DoubleQuotedString', '"\\"', {closed=false})
|
||||
singl_eltkn_test('DoubleQuotedString', '"x\\"', {closed=false})
|
||||
singl_eltkn_test('DoubleQuotedString', '"\\"x', {closed=false})
|
||||
singl_eltkn_test('Not', '!')
|
||||
singl_eltkn_test('Assignment', '=', {type='Plain'})
|
||||
comparison_test('==', '!=', 'Equal')
|
||||
comparison_test('=~', '!~', 'Matches')
|
||||
comparison_test('>', '<=', 'Greater')
|
||||
comparison_test('>=', '<', 'GreaterOrEqual')
|
||||
singl_eltkn_test('Minus', '-')
|
||||
singl_eltkn_test('Assignment', '-=', {type='Subtract'})
|
||||
singl_eltkn_test('Arrow', '->')
|
||||
singl_eltkn_test('Invalid', '~', {error='E15: Unidentified character: %.*s'})
|
||||
simple_test({{data=nil, size=0}}, 'EOC', 0, {error='start.col >= #pstr'})
|
||||
simple_test({''}, 'EOC', 0, {error='start.col >= #pstr'})
|
||||
simple_test({'2.'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({'2e5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({'2.x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({'2.2.'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({'2.0x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({'2.0e'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({'2.0e+'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({'2.0e-'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({'2.0e+x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({'2.0e-x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({'2.0e+1a'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({'2.0e-1a'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({'0b102'}, 'Number', 4, {data={is_float=false, base=2, val=2}, str='0b10'})
|
||||
simple_test({'10F'}, 'Number', 2, {data={is_float=false, base=10, val=10}, str='10'})
|
||||
simple_test({'0x0123456789ABCDEFG'}, 'Number', 18, {data={is_float=false, base=16, val=81985529216486895}, str='0x0123456789ABCDEF'})
|
||||
simple_test({{data='00', size=2}}, 'Number', 2, {data={is_float=false, base=8, val=0}, str='00'})
|
||||
simple_test({{data='009', size=2}}, 'Number', 2, {data={is_float=false, base=8, val=0}, str='00'})
|
||||
simple_test({{data='01', size=1}}, 'Number', 1, {data={is_float=false, base=10, val=0}, str='0'})
|
||||
end
|
||||
|
||||
local function regular_scope_tests()
|
||||
scope_test('s')
|
||||
scope_test('g')
|
||||
scope_test('v')
|
||||
scope_test('b')
|
||||
scope_test('w')
|
||||
scope_test('t')
|
||||
scope_test('l')
|
||||
scope_test('a')
|
||||
|
||||
simple_test({'g:'}, 'PlainIdentifier', 2, {data={scope='g', autoload=false}, str='g:'})
|
||||
simple_test({'g:is#foo'}, 'PlainIdentifier', 8, {data={scope='g', autoload=true}, str='g:is#foo'})
|
||||
simple_test({'g:isnot#foo'}, 'PlainIdentifier', 11, {data={scope='g', autoload=true}, str='g:isnot#foo'})
|
||||
end
|
||||
|
||||
local function regular_is_tests()
|
||||
comparison_test('is', 'isnot', 'Identical')
|
||||
|
||||
simple_test({'is'}, 'Comparison', 2, {data={type='Identical', inv=false, ccs='UseOption'}, str='is'})
|
||||
simple_test({'isnot'}, 'Comparison', 5, {data={type='Identical', inv=true, ccs='UseOption'}, str='isnot'})
|
||||
simple_test({'is?'}, 'Comparison', 3, {data={type='Identical', inv=false, ccs='IgnoreCase'}, str='is?'})
|
||||
simple_test({'isnot?'}, 'Comparison', 6, {data={type='Identical', inv=true, ccs='IgnoreCase'}, str='isnot?'})
|
||||
simple_test({'is#'}, 'Comparison', 3, {data={type='Identical', inv=false, ccs='MatchCase'}, str='is#'})
|
||||
simple_test({'isnot#'}, 'Comparison', 6, {data={type='Identical', inv=true, ccs='MatchCase'}, str='isnot#'})
|
||||
simple_test({'is#foo'}, 'Comparison', 3, {data={type='Identical', inv=false, ccs='MatchCase'}, str='is#'})
|
||||
simple_test({'isnot#foo'}, 'Comparison', 6, {data={type='Identical', inv=true, ccs='MatchCase'}, str='isnot#'})
|
||||
end
|
||||
|
||||
local function regular_number_tests()
|
||||
simple_test({'2.0'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({'2.0e5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({'2.0e+5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({'2.0e-5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
end
|
||||
|
||||
local function regular_eoc_tests()
|
||||
singl_eltkn_test('EOC', '|')
|
||||
singl_eltkn_test('EOC', '\0')
|
||||
singl_eltkn_test('EOC', '\n')
|
||||
end
|
||||
|
||||
itp('works (single tokens, zero flags)', function()
|
||||
stable_tests()
|
||||
|
||||
regular_eoc_tests()
|
||||
regular_scope_tests()
|
||||
regular_is_tests()
|
||||
regular_number_tests()
|
||||
end)
|
||||
itp('peeks', function()
|
||||
flags = tonumber(lib.kELFlagPeek)
|
||||
should_advance = false
|
||||
stable_tests()
|
||||
|
||||
regular_eoc_tests()
|
||||
regular_scope_tests()
|
||||
regular_is_tests()
|
||||
regular_number_tests()
|
||||
end)
|
||||
itp('forbids scope', function()
|
||||
flags = tonumber(lib.kELFlagForbidScope)
|
||||
stable_tests()
|
||||
|
||||
regular_eoc_tests()
|
||||
regular_is_tests()
|
||||
regular_number_tests()
|
||||
|
||||
simple_test({'g:'}, 'PlainIdentifier', 1, {data={scope=0, autoload=false}, str='g'})
|
||||
end)
|
||||
itp('allows floats', function()
|
||||
flags = tonumber(lib.kELFlagAllowFloat)
|
||||
stable_tests()
|
||||
|
||||
regular_eoc_tests()
|
||||
regular_scope_tests()
|
||||
regular_is_tests()
|
||||
|
||||
simple_test({'2.2'}, 'Number', 3, {data={is_float=true, base=10, val=2.2}, str='2.2'})
|
||||
simple_test({'2.0e5'}, 'Number', 5, {data={is_float=true, base=10, val=2e5}, str='2.0e5'})
|
||||
simple_test({'2.0e+5'}, 'Number', 6, {data={is_float=true, base=10, val=2e5}, str='2.0e+5'})
|
||||
simple_test({'2.0e-5'}, 'Number', 6, {data={is_float=true, base=10, val=2e-5}, str='2.0e-5'})
|
||||
simple_test({'2.500000e-5'}, 'Number', 11, {data={is_float=true, base=10, val=2.5e-5}, str='2.500000e-5'})
|
||||
simple_test({'2.5555e2'}, 'Number', 8, {data={is_float=true, base=10, val=2.5555e2}, str='2.5555e2'})
|
||||
simple_test({'2.5555e+2'}, 'Number', 9, {data={is_float=true, base=10, val=2.5555e2}, str='2.5555e+2'})
|
||||
simple_test({'2.5555e-2'}, 'Number', 9, {data={is_float=true, base=10, val=2.5555e-2}, str='2.5555e-2'})
|
||||
simple_test({{data='2.5e-5', size=3}},
|
||||
'Number', 3, {data={is_float=true, base=10, val=2.5}, str='2.5'})
|
||||
simple_test({{data='2.5e5', size=4}},
|
||||
'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
|
||||
simple_test({{data='2.5e-50', size=6}},
|
||||
'Number', 6, {data={is_float=true, base=10, val=2.5e-5}, str='2.5e-5'})
|
||||
end)
|
||||
itp('treats `is` as an identifier', function()
|
||||
flags = tonumber(lib.kELFlagIsNotCmp)
|
||||
stable_tests()
|
||||
|
||||
regular_eoc_tests()
|
||||
regular_scope_tests()
|
||||
regular_number_tests()
|
||||
|
||||
simple_test({'is'}, 'PlainIdentifier', 2, {data={scope=0, autoload=false}, str='is'})
|
||||
simple_test({'isnot'}, 'PlainIdentifier', 5, {data={scope=0, autoload=false}, str='isnot'})
|
||||
simple_test({'is?'}, 'PlainIdentifier', 2, {data={scope=0, autoload=false}, str='is'})
|
||||
simple_test({'isnot?'}, 'PlainIdentifier', 5, {data={scope=0, autoload=false}, str='isnot'})
|
||||
simple_test({'is#'}, 'PlainIdentifier', 3, {data={scope=0, autoload=true}, str='is#'})
|
||||
simple_test({'isnot#'}, 'PlainIdentifier', 6, {data={scope=0, autoload=true}, str='isnot#'})
|
||||
simple_test({'is#foo'}, 'PlainIdentifier', 6, {data={scope=0, autoload=true}, str='is#foo'})
|
||||
simple_test({'isnot#foo'}, 'PlainIdentifier', 9, {data={scope=0, autoload=true}, str='isnot#foo'})
|
||||
end)
|
||||
itp('forbids EOC', function()
|
||||
flags = tonumber(lib.kELFlagForbidEOC)
|
||||
stable_tests()
|
||||
|
||||
regular_scope_tests()
|
||||
regular_is_tests()
|
||||
regular_number_tests()
|
||||
|
||||
singl_eltkn_test('Invalid', '|', {error='E15: Unexpected EOC character: %.*s'})
|
||||
singl_eltkn_test('Invalid', '\0', {error='E15: Unexpected EOC character: %.*s'})
|
||||
singl_eltkn_test('Invalid', '\n', {error='E15: Unexpected EOC character: %.*s'})
|
||||
end)
|
||||
end)
|
|
@ -0,0 +1,540 @@
|
|||
local helpers = require('test.unit.helpers')(after_each)
|
||||
local global_helpers = require('test.helpers')
|
||||
local itp = helpers.gen_itp(it)
|
||||
local viml_helpers = require('test.unit.viml.helpers')
|
||||
|
||||
local make_enum_conv_tab = helpers.make_enum_conv_tab
|
||||
local child_call_once = helpers.child_call_once
|
||||
local alloc_log_new = helpers.alloc_log_new
|
||||
local kvi_destroy = helpers.kvi_destroy
|
||||
local conv_enum = helpers.conv_enum
|
||||
local debug_log = helpers.debug_log
|
||||
local ptr2key = helpers.ptr2key
|
||||
local cimport = helpers.cimport
|
||||
local ffi = helpers.ffi
|
||||
local neq = helpers.neq
|
||||
local eq = helpers.eq
|
||||
|
||||
local conv_ccs = viml_helpers.conv_ccs
|
||||
local new_pstate = viml_helpers.new_pstate
|
||||
local conv_cmp_type = viml_helpers.conv_cmp_type
|
||||
local pstate_set_str = viml_helpers.pstate_set_str
|
||||
local conv_expr_asgn_type = viml_helpers.conv_expr_asgn_type
|
||||
|
||||
local mergedicts_copy = global_helpers.mergedicts_copy
|
||||
local format_string = global_helpers.format_string
|
||||
local format_luav = global_helpers.format_luav
|
||||
local intchar2lua = global_helpers.intchar2lua
|
||||
local dictdiff = global_helpers.dictdiff
|
||||
|
||||
local lib = cimport('./src/nvim/viml/parser/expressions.h',
|
||||
'./src/nvim/syntax.h')
|
||||
|
||||
local alloc_log = alloc_log_new()
|
||||
|
||||
local predefined_hl_defs = {
|
||||
-- From highlight_init_both
|
||||
Conceal=true,
|
||||
Cursor=true,
|
||||
lCursor=true,
|
||||
DiffText=true,
|
||||
ErrorMsg=true,
|
||||
IncSearch=true,
|
||||
ModeMsg=true,
|
||||
NonText=true,
|
||||
PmenuSbar=true,
|
||||
StatusLine=true,
|
||||
StatusLineNC=true,
|
||||
TabLineFill=true,
|
||||
TabLineSel=true,
|
||||
TermCursor=true,
|
||||
VertSplit=true,
|
||||
WildMenu=true,
|
||||
EndOfBuffer=true,
|
||||
QuickFixLine=true,
|
||||
Substitute=true,
|
||||
Whitespace=true,
|
||||
|
||||
-- From highlight_init_(dark|light)
|
||||
ColorColumn=true,
|
||||
CursorColumn=true,
|
||||
CursorLine=true,
|
||||
CursorLineNr=true,
|
||||
DiffAdd=true,
|
||||
DiffChange=true,
|
||||
DiffDelete=true,
|
||||
Directory=true,
|
||||
FoldColumn=true,
|
||||
Folded=true,
|
||||
LineNr=true,
|
||||
MatchParen=true,
|
||||
MoreMsg=true,
|
||||
Pmenu=true,
|
||||
PmenuSel=true,
|
||||
PmenuThumb=true,
|
||||
Question=true,
|
||||
Search=true,
|
||||
SignColumn=true,
|
||||
SpecialKey=true,
|
||||
SpellBad=true,
|
||||
SpellCap=true,
|
||||
SpellLocal=true,
|
||||
SpellRare=true,
|
||||
TabLine=true,
|
||||
Title=true,
|
||||
Visual=true,
|
||||
WarningMsg=true,
|
||||
Normal=true,
|
||||
|
||||
-- From syncolor.vim, if &background
|
||||
Comment=true,
|
||||
Constant=true,
|
||||
Special=true,
|
||||
Identifier=true,
|
||||
Statement=true,
|
||||
PreProc=true,
|
||||
Type=true,
|
||||
Underlined=true,
|
||||
Ignore=true,
|
||||
|
||||
-- From syncolor.vim, below if &background
|
||||
Error=true,
|
||||
Todo=true,
|
||||
|
||||
-- From syncolor.vim, links at the bottom
|
||||
String=true,
|
||||
Character=true,
|
||||
Number=true,
|
||||
Boolean=true,
|
||||
Float=true,
|
||||
Function=true,
|
||||
Conditional=true,
|
||||
Repeat=true,
|
||||
Label=true,
|
||||
Operator=true,
|
||||
Keyword=true,
|
||||
Exception=true,
|
||||
Include=true,
|
||||
Define=true,
|
||||
Macro=true,
|
||||
PreCondit=true,
|
||||
StorageClass=true,
|
||||
Structure=true,
|
||||
Typedef=true,
|
||||
Tag=true,
|
||||
SpecialChar=true,
|
||||
Delimiter=true,
|
||||
SpecialComment=true,
|
||||
Debug=true,
|
||||
}
|
||||
|
||||
local nvim_hl_defs = {}
|
||||
|
||||
child_call_once(function()
|
||||
local i = 0
|
||||
while lib.highlight_init_cmdline[i] ~= nil do
|
||||
local hl_args = lib.highlight_init_cmdline[i]
|
||||
local s = ffi.string(hl_args)
|
||||
local err, msg = pcall(function()
|
||||
if s:sub(1, 13) == 'default link ' then
|
||||
local new_grp, grp_link = s:match('^default link (%w+) (%w+)$')
|
||||
neq(nil, new_grp)
|
||||
-- Note: group to link to must be already defined at the time of
|
||||
-- linking, otherwise it will be created as cleared. So existence
|
||||
-- of the group is checked here and not in the next pass over
|
||||
-- nvim_hl_defs.
|
||||
eq(true, not not (nvim_hl_defs[grp_link]
|
||||
or predefined_hl_defs[grp_link]))
|
||||
eq(false, not not (nvim_hl_defs[new_grp]
|
||||
or predefined_hl_defs[new_grp]))
|
||||
nvim_hl_defs[new_grp] = {'link', grp_link}
|
||||
else
|
||||
local new_grp, grp_args = s:match('^(%w+) (.*)')
|
||||
neq(nil, new_grp)
|
||||
eq(false, not not (nvim_hl_defs[new_grp]
|
||||
or predefined_hl_defs[new_grp]))
|
||||
nvim_hl_defs[new_grp] = {'definition', grp_args}
|
||||
end
|
||||
end)
|
||||
if not err then
|
||||
msg = format_string(
|
||||
'Error while processing string %s at position %u:\n%s', s, i, msg)
|
||||
error(msg)
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
for k, _ in ipairs(nvim_hl_defs) do
|
||||
eq('Nvim', k:sub(1, 4))
|
||||
-- NvimInvalid
|
||||
-- 12345678901
|
||||
local err, msg = pcall(function()
|
||||
if k:sub(5, 11) == 'Invalid' then
|
||||
neq(nil, nvim_hl_defs['Nvim' .. k:sub(12)])
|
||||
else
|
||||
neq(nil, nvim_hl_defs['NvimInvalid' .. k:sub(5)])
|
||||
end
|
||||
end)
|
||||
if not err then
|
||||
msg = format_string('Error while processing group %s:\n%s', k, msg)
|
||||
error(msg)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local function hls_to_hl_fs(hls)
|
||||
local ret = {}
|
||||
local next_col = 0
|
||||
for i, v in ipairs(hls) do
|
||||
local group, line, col, str = v:match('^Nvim([a-zA-Z]+):(%d+):(%d+):(.*)$')
|
||||
col = tonumber(col)
|
||||
line = tonumber(line)
|
||||
assert(line == 0)
|
||||
local col_shift = col - next_col
|
||||
assert(col_shift >= 0)
|
||||
next_col = col + #str
|
||||
ret[i] = format_string('hl(%r, %r%s)',
|
||||
group,
|
||||
str,
|
||||
(col_shift == 0
|
||||
and ''
|
||||
or (', %u'):format(col_shift)))
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
local function format_check(expr, format_check_data, opts)
|
||||
-- That forces specific order.
|
||||
local zflags = opts.flags[1]
|
||||
local zdata = format_check_data[zflags]
|
||||
local dig_len
|
||||
if opts.funcname then
|
||||
print(format_string('\n%s(%r, {', opts.funcname, expr))
|
||||
dig_len = #opts.funcname + 2
|
||||
else
|
||||
print(format_string('\n_check_parsing(%r, %r, {', opts, expr))
|
||||
dig_len = #('_check_parsing(, \'') + #(format_string('%r', opts))
|
||||
end
|
||||
local digits = ' --' .. (' '):rep(dig_len - #(' --'))
|
||||
local digits2 = digits:sub(1, -10)
|
||||
for i = 0, #expr - 1 do
|
||||
if i % 10 == 0 then
|
||||
digits2 = ('%s%10u'):format(digits2, i / 10)
|
||||
end
|
||||
digits = ('%s%u'):format(digits, i % 10)
|
||||
end
|
||||
print(digits)
|
||||
if #expr > 10 then
|
||||
print(digits2)
|
||||
end
|
||||
if zdata.ast.len then
|
||||
print((' len = %u,'):format(zdata.ast.len))
|
||||
end
|
||||
print(' ast = ' .. format_luav(zdata.ast.ast, ' ') .. ',')
|
||||
if zdata.ast.err then
|
||||
print(' err = {')
|
||||
print(' arg = ' .. format_luav(zdata.ast.err.arg) .. ',')
|
||||
print(' msg = ' .. format_luav(zdata.ast.err.msg) .. ',')
|
||||
print(' },')
|
||||
end
|
||||
print('}, {')
|
||||
for _, v in ipairs(zdata.hl_fs) do
|
||||
print(' ' .. v .. ',')
|
||||
end
|
||||
local diffs = {}
|
||||
local diffs_num = 0
|
||||
for flags, v in pairs(format_check_data) do
|
||||
if flags ~= zflags then
|
||||
diffs[flags] = dictdiff(zdata, v)
|
||||
if diffs[flags] then
|
||||
if flags == 3 + zflags then
|
||||
if (dictdiff(format_check_data[1 + zflags],
|
||||
format_check_data[3 + zflags]) == nil
|
||||
or dictdiff(format_check_data[2 + zflags],
|
||||
format_check_data[3 + zflags]) == nil)
|
||||
then
|
||||
diffs[flags] = nil
|
||||
else
|
||||
diffs_num = diffs_num + 1
|
||||
end
|
||||
else
|
||||
diffs_num = diffs_num + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if diffs_num ~= 0 then
|
||||
print('}, {')
|
||||
local flags = 1
|
||||
while diffs_num ~= 0 do
|
||||
if diffs[flags] then
|
||||
diffs_num = diffs_num - 1
|
||||
local diff = diffs[flags]
|
||||
print((' [%u] = {'):format(flags))
|
||||
if diff.ast then
|
||||
print(' ast = ' .. format_luav(diff.ast, ' ') .. ',')
|
||||
end
|
||||
if diff.hl_fs then
|
||||
print(' hl_fs = ' .. format_luav(diff.hl_fs, ' ', {
|
||||
literal_strings=true
|
||||
}) .. ',')
|
||||
end
|
||||
print(' },')
|
||||
end
|
||||
flags = flags + 1
|
||||
end
|
||||
end
|
||||
print('})')
|
||||
end
|
||||
|
||||
local east_node_type_tab
|
||||
make_enum_conv_tab(lib, {
|
||||
'kExprNodeMissing',
|
||||
'kExprNodeOpMissing',
|
||||
'kExprNodeTernary',
|
||||
'kExprNodeTernaryValue',
|
||||
'kExprNodeRegister',
|
||||
'kExprNodeSubscript',
|
||||
'kExprNodeListLiteral',
|
||||
'kExprNodeUnaryPlus',
|
||||
'kExprNodeBinaryPlus',
|
||||
'kExprNodeNested',
|
||||
'kExprNodeCall',
|
||||
'kExprNodePlainIdentifier',
|
||||
'kExprNodePlainKey',
|
||||
'kExprNodeComplexIdentifier',
|
||||
'kExprNodeUnknownFigure',
|
||||
'kExprNodeLambda',
|
||||
'kExprNodeDictLiteral',
|
||||
'kExprNodeCurlyBracesIdentifier',
|
||||
'kExprNodeComma',
|
||||
'kExprNodeColon',
|
||||
'kExprNodeArrow',
|
||||
'kExprNodeComparison',
|
||||
'kExprNodeConcat',
|
||||
'kExprNodeConcatOrSubscript',
|
||||
'kExprNodeInteger',
|
||||
'kExprNodeFloat',
|
||||
'kExprNodeSingleQuotedString',
|
||||
'kExprNodeDoubleQuotedString',
|
||||
'kExprNodeOr',
|
||||
'kExprNodeAnd',
|
||||
'kExprNodeUnaryMinus',
|
||||
'kExprNodeBinaryMinus',
|
||||
'kExprNodeNot',
|
||||
'kExprNodeMultiplication',
|
||||
'kExprNodeDivision',
|
||||
'kExprNodeMod',
|
||||
'kExprNodeOption',
|
||||
'kExprNodeEnvironment',
|
||||
'kExprNodeAssignment',
|
||||
}, 'kExprNode', function(ret) east_node_type_tab = ret end)
|
||||
|
||||
local function conv_east_node_type(typ)
|
||||
return conv_enum(east_node_type_tab, typ)
|
||||
end
|
||||
|
||||
local eastnodelist2lua
|
||||
|
||||
local function eastnode2lua(pstate, eastnode, checked_nodes)
|
||||
local key = ptr2key(eastnode)
|
||||
if checked_nodes[key] then
|
||||
checked_nodes[key].duplicate_key = key
|
||||
return { duplicate = key }
|
||||
end
|
||||
local typ = conv_east_node_type(eastnode.type)
|
||||
local ret = {}
|
||||
checked_nodes[key] = ret
|
||||
ret.children = eastnodelist2lua(pstate, eastnode.children, checked_nodes)
|
||||
local str = pstate_set_str(pstate, eastnode.start, eastnode.len)
|
||||
local ret_str
|
||||
if str.error then
|
||||
ret_str = 'error:' .. str.error
|
||||
else
|
||||
ret_str = ('%u:%u:%s'):format(str.start.line, str.start.col, str.str)
|
||||
end
|
||||
if typ == 'Register' then
|
||||
typ = typ .. ('(name=%s)'):format(
|
||||
tostring(intchar2lua(eastnode.data.reg.name)))
|
||||
elseif typ == 'PlainIdentifier' then
|
||||
typ = typ .. ('(scope=%s,ident=%s)'):format(
|
||||
tostring(intchar2lua(eastnode.data.var.scope)),
|
||||
ffi.string(eastnode.data.var.ident, eastnode.data.var.ident_len))
|
||||
elseif typ == 'PlainKey' then
|
||||
typ = typ .. ('(key=%s)'):format(
|
||||
ffi.string(eastnode.data.var.ident, eastnode.data.var.ident_len))
|
||||
elseif (typ == 'UnknownFigure' or typ == 'DictLiteral'
|
||||
or typ == 'CurlyBracesIdentifier' or typ == 'Lambda') then
|
||||
typ = typ .. ('(%s)'):format(
|
||||
(eastnode.data.fig.type_guesses.allow_lambda and '\\' or '-')
|
||||
.. (eastnode.data.fig.type_guesses.allow_dict and 'd' or '-')
|
||||
.. (eastnode.data.fig.type_guesses.allow_ident and 'i' or '-'))
|
||||
elseif typ == 'Comparison' then
|
||||
typ = typ .. ('(type=%s,inv=%u,ccs=%s)'):format(
|
||||
conv_cmp_type(eastnode.data.cmp.type), eastnode.data.cmp.inv and 1 or 0,
|
||||
conv_ccs(eastnode.data.cmp.ccs))
|
||||
elseif typ == 'Integer' then
|
||||
typ = typ .. ('(val=%u)'):format(tonumber(eastnode.data.num.value))
|
||||
elseif typ == 'Float' then
|
||||
typ = typ .. format_string('(val=%e)', tonumber(eastnode.data.flt.value))
|
||||
elseif typ == 'SingleQuotedString' or typ == 'DoubleQuotedString' then
|
||||
if eastnode.data.str.value == nil then
|
||||
typ = typ .. '(val=NULL)'
|
||||
else
|
||||
local s = ffi.string(eastnode.data.str.value, eastnode.data.str.size)
|
||||
typ = format_string('%s(val=%q)', typ, s)
|
||||
end
|
||||
elseif typ == 'Option' then
|
||||
typ = ('%s(scope=%s,ident=%s)'):format(
|
||||
typ,
|
||||
tostring(intchar2lua(eastnode.data.opt.scope)),
|
||||
ffi.string(eastnode.data.opt.ident, eastnode.data.opt.ident_len))
|
||||
elseif typ == 'Environment' then
|
||||
typ = ('%s(ident=%s)'):format(
|
||||
typ,
|
||||
ffi.string(eastnode.data.env.ident, eastnode.data.env.ident_len))
|
||||
elseif typ == 'Assignment' then
|
||||
typ = ('%s(%s)'):format(typ, conv_expr_asgn_type(eastnode.data.ass.type))
|
||||
end
|
||||
ret_str = typ .. ':' .. ret_str
|
||||
local can_simplify = not ret.children
|
||||
if can_simplify then
|
||||
ret = ret_str
|
||||
else
|
||||
ret[1] = ret_str
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
eastnodelist2lua = function(pstate, eastnode, checked_nodes)
|
||||
local ret = {}
|
||||
while eastnode ~= nil do
|
||||
ret[#ret + 1] = eastnode2lua(pstate, eastnode, checked_nodes)
|
||||
eastnode = eastnode.next
|
||||
end
|
||||
if #ret == 0 then
|
||||
ret = nil
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
local function east2lua(str, pstate, east)
|
||||
local checked_nodes = {}
|
||||
local len = tonumber(pstate.pos.col)
|
||||
if pstate.pos.line == 1 then
|
||||
len = tonumber(pstate.reader.lines.items[0].size)
|
||||
end
|
||||
if type(str) == 'string' and len == #str then
|
||||
len = nil
|
||||
end
|
||||
return {
|
||||
err = east.err.msg ~= nil and {
|
||||
msg = ffi.string(east.err.msg),
|
||||
arg = ffi.string(east.err.arg, east.err.arg_len),
|
||||
} or nil,
|
||||
len = len,
|
||||
ast = eastnodelist2lua(pstate, east.root, checked_nodes),
|
||||
}
|
||||
end
|
||||
|
||||
local function phl2lua(pstate)
|
||||
local ret = {}
|
||||
for i = 0, (tonumber(pstate.colors.size) - 1) do
|
||||
local chunk = pstate.colors.items[i]
|
||||
local chunk_tbl = pstate_set_str(
|
||||
pstate, chunk.start, chunk.end_col - chunk.start.col, {
|
||||
group = ffi.string(chunk.group),
|
||||
})
|
||||
ret[i + 1] = ('%s:%u:%u:%s'):format(
|
||||
chunk_tbl.group,
|
||||
chunk_tbl.start.line,
|
||||
chunk_tbl.start.col,
|
||||
chunk_tbl.str)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
child_call_once(function()
|
||||
assert:set_parameter('TableFormatLevel', 1000000)
|
||||
end)
|
||||
|
||||
describe('Expressions parser', function()
|
||||
local function _check_parsing(opts, str, exp_ast, exp_highlighting_fs,
|
||||
nz_flags_exps)
|
||||
local zflags = opts.flags[1]
|
||||
nz_flags_exps = nz_flags_exps or {}
|
||||
local format_check_data = {}
|
||||
for _, flags in ipairs(opts.flags) do
|
||||
debug_log(('Running test case (%s, %u)'):format(str, flags))
|
||||
local err, msg = pcall(function()
|
||||
if os.getenv('NVIM_TEST_PARSER_SPEC_PRINT_TEST_CASE') == '1' then
|
||||
print(str, flags)
|
||||
end
|
||||
alloc_log:check({})
|
||||
|
||||
local pstate = new_pstate({str})
|
||||
local east = lib.viml_pexpr_parse(pstate, flags)
|
||||
local ast = east2lua(str, pstate, east)
|
||||
local hls = phl2lua(pstate)
|
||||
if exp_ast == nil then
|
||||
format_check_data[flags] = {ast=ast, hl_fs=hls_to_hl_fs(hls)}
|
||||
else
|
||||
local exps = {
|
||||
ast = exp_ast,
|
||||
hl_fs = exp_highlighting_fs,
|
||||
}
|
||||
local add_exps = nz_flags_exps[flags]
|
||||
if not add_exps and flags == 3 + zflags then
|
||||
add_exps = nz_flags_exps[1 + zflags] or nz_flags_exps[2 + zflags]
|
||||
end
|
||||
if add_exps then
|
||||
if add_exps.ast then
|
||||
exps.ast = mergedicts_copy(exps.ast, add_exps.ast)
|
||||
end
|
||||
if add_exps.hl_fs then
|
||||
exps.hl_fs = mergedicts_copy(exps.hl_fs, add_exps.hl_fs)
|
||||
end
|
||||
end
|
||||
eq(exps.ast, ast)
|
||||
if exp_highlighting_fs then
|
||||
local exp_highlighting = {}
|
||||
local next_col = 0
|
||||
for i, h in ipairs(exps.hl_fs) do
|
||||
exp_highlighting[i], next_col = h(next_col)
|
||||
end
|
||||
eq(exp_highlighting, hls)
|
||||
end
|
||||
end
|
||||
lib.viml_pexpr_free_ast(east)
|
||||
kvi_destroy(pstate.colors)
|
||||
alloc_log:clear_tmp_allocs(true)
|
||||
alloc_log:check({})
|
||||
end)
|
||||
if not err then
|
||||
msg = format_string('Error while processing test (%r, %u):\n%s',
|
||||
str, flags, msg)
|
||||
error(msg)
|
||||
end
|
||||
end
|
||||
if exp_ast == nil then
|
||||
format_check(str, format_check_data, opts)
|
||||
end
|
||||
end
|
||||
local function hl(group, str, shift)
|
||||
return function(next_col)
|
||||
if nvim_hl_defs['Nvim' .. group] == nil then
|
||||
error(('Unknown group: Nvim%s'):format(group))
|
||||
end
|
||||
local col = next_col + (shift or 0)
|
||||
return (('%s:%u:%u:%s'):format(
|
||||
'Nvim' .. group,
|
||||
0,
|
||||
col,
|
||||
str)), (col + #str)
|
||||
end
|
||||
end
|
||||
local function fmtn(typ, args, rest)
|
||||
return ('%s(%s)%s'):format(typ, args, rest)
|
||||
end
|
||||
require('test.unit.viml.expressions.parser_tests')(
|
||||
itp, _check_parsing, hl, fmtn)
|
||||
end)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,130 @@
|
|||
local helpers = require('test.unit.helpers')(nil)
|
||||
|
||||
local ffi = helpers.ffi
|
||||
local cimport = helpers.cimport
|
||||
local kvi_new = helpers.kvi_new
|
||||
local kvi_init = helpers.kvi_init
|
||||
local conv_enum = helpers.conv_enum
|
||||
local make_enum_conv_tab = helpers.make_enum_conv_tab
|
||||
|
||||
local lib = cimport('./src/nvim/viml/parser/expressions.h')
|
||||
|
||||
local function new_pstate(strings)
|
||||
local strings_idx = 0
|
||||
local function get_line(_, ret_pline)
|
||||
strings_idx = strings_idx + 1
|
||||
local str = strings[strings_idx]
|
||||
local data, size
|
||||
if type(str) == 'string' then
|
||||
data = str
|
||||
size = #str
|
||||
elseif type(str) == 'nil' then
|
||||
data = nil
|
||||
size = 0
|
||||
elseif type(str) == 'table' then
|
||||
data = str.data
|
||||
size = str.size
|
||||
elseif type(str) == 'function' then
|
||||
data, size = str()
|
||||
size = size or 0
|
||||
end
|
||||
ret_pline.data = data
|
||||
ret_pline.size = size
|
||||
ret_pline.allocated = false
|
||||
end
|
||||
local state = {
|
||||
reader = {
|
||||
get_line = get_line,
|
||||
cookie = nil,
|
||||
conv = {
|
||||
vc_type = 0,
|
||||
vc_factor = 1,
|
||||
vc_fail = false,
|
||||
},
|
||||
},
|
||||
pos = { line = 0, col = 0 },
|
||||
colors = kvi_new('ParserHighlight'),
|
||||
can_continuate = false,
|
||||
}
|
||||
local ret = ffi.new('ParserState', state)
|
||||
kvi_init(ret.reader.lines)
|
||||
kvi_init(ret.stack)
|
||||
return ret
|
||||
end
|
||||
|
||||
local function pline2lua(pline)
|
||||
return ffi.string(pline.data, pline.size)
|
||||
end
|
||||
|
||||
local function pstate_str(pstate, start, len)
|
||||
local str = nil
|
||||
local err = nil
|
||||
if start.line < pstate.reader.lines.size then
|
||||
local pstr = pline2lua(pstate.reader.lines.items[start.line])
|
||||
if start.col >= #pstr then
|
||||
err = 'start.col >= #pstr'
|
||||
else
|
||||
str = pstr:sub(tonumber(start.col) + 1, tonumber(start.col + len))
|
||||
end
|
||||
else
|
||||
err = 'start.line >= pstate.reader.lines.size'
|
||||
end
|
||||
return str, err
|
||||
end
|
||||
|
||||
local function pstate_set_str(pstate, start, len, ret)
|
||||
ret = ret or {}
|
||||
ret.start = {
|
||||
line = tonumber(start.line),
|
||||
col = tonumber(start.col)
|
||||
}
|
||||
ret.len = tonumber(len)
|
||||
ret.str, ret.error = pstate_str(pstate, start, len)
|
||||
return ret
|
||||
end
|
||||
|
||||
local eltkn_cmp_type_tab
|
||||
make_enum_conv_tab(lib, {
|
||||
'kExprCmpEqual',
|
||||
'kExprCmpMatches',
|
||||
'kExprCmpGreater',
|
||||
'kExprCmpGreaterOrEqual',
|
||||
'kExprCmpIdentical',
|
||||
}, 'kExprCmp', function(ret) eltkn_cmp_type_tab = ret end)
|
||||
|
||||
local function conv_cmp_type(typ)
|
||||
return conv_enum(eltkn_cmp_type_tab, typ)
|
||||
end
|
||||
|
||||
local ccs_tab
|
||||
make_enum_conv_tab(lib, {
|
||||
'kCCStrategyUseOption',
|
||||
'kCCStrategyMatchCase',
|
||||
'kCCStrategyIgnoreCase',
|
||||
}, 'kCCStrategy', function(ret) ccs_tab = ret end)
|
||||
|
||||
local function conv_ccs(ccs)
|
||||
return conv_enum(ccs_tab, ccs)
|
||||
end
|
||||
|
||||
local expr_asgn_type_tab
|
||||
make_enum_conv_tab(lib, {
|
||||
'kExprAsgnPlain',
|
||||
'kExprAsgnAdd',
|
||||
'kExprAsgnSubtract',
|
||||
'kExprAsgnConcat',
|
||||
}, 'kExprAsgn', function(ret) expr_asgn_type_tab = ret end)
|
||||
|
||||
local function conv_expr_asgn_type(expr_asgn_type)
|
||||
return conv_enum(expr_asgn_type_tab, expr_asgn_type)
|
||||
end
|
||||
|
||||
return {
|
||||
conv_ccs = conv_ccs,
|
||||
pline2lua = pline2lua,
|
||||
pstate_str = pstate_str,
|
||||
new_pstate = new_pstate,
|
||||
conv_cmp_type = conv_cmp_type,
|
||||
pstate_set_str = pstate_set_str,
|
||||
conv_expr_asgn_type = conv_expr_asgn_type,
|
||||
}
|
Loading…
Reference in New Issue