Merge #7234 'built-in expression parser'

This commit is contained in:
Justin M. Keyes 2017-12-09 18:47:34 +01:00
commit 3cc7ebf810
49 changed files with 17465 additions and 772 deletions

View File

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

View File

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

View File

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

View File

@ -86,6 +86,8 @@ foreach(subdir
event
eval
lua
viml
viml/parser
)
if(${subdir} MATCHES "tui" AND NOT FEAT_TUI)
continue()

View File

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

View File

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

View File

@ -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); // Shouldve 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.

View File

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

View File

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

View File

@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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), \

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)++;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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); // Shouldve 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
}

View File

@ -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);
}

View File

@ -0,0 +1,4 @@
char *gettext(const char *s)
{
return (char *)s;
}

View File

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

View File

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

View File

@ -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);
}

102
test/symbolic/klee/run.sh Executable file
View File

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

View File

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

View File

@ -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);
}
// Cant 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);
}

View File

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

View File

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

View File

@ -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"..." +

View File

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

View File

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

View File

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

130
test/unit/viml/helpers.lua Normal file
View File

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