mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 09:44:31 +00:00 
			
		
		
		
	Merge #7234 'built-in expression parser'
This commit is contained in:
		@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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).
 | 
			
		||||
@@ -5755,13 +5755,11 @@ msgpackparse({list})				   {Nvim} *msgpackparse()*
 | 
			
		||||
		   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|.
 | 
			
		||||
		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|.  This value cannot possibly 
 | 
			
		||||
			appear in |msgpackparse()| output in Neovim versions 
 | 
			
		||||
			which have |v:true| and |v:false|.
 | 
			
		||||
			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
 | 
			
		||||
@@ -10568,5 +10566,123 @@ This is not allowed when the textlock is active:
 | 
			
		||||
	- closing a window or quitting Vim
 | 
			
		||||
	- etc.
 | 
			
		||||
 | 
			
		||||
==============================================================================
 | 
			
		||||
13. Command-line expressions highlighting		*expr-highlight*
 | 
			
		||||
 | 
			
		||||
Expressions entered by the user in |i_CTRL-R_=|, |c_CTRL-\_e|, |quote=| are
 | 
			
		||||
highlighted by the built-in expressions parser.  It uses highlight groups
 | 
			
		||||
described in the table below, which may be overriden by colorschemes.
 | 
			
		||||
							*hl-NvimInvalid*
 | 
			
		||||
Besides the "Nvim"-prefixed highlight groups described below, there are
 | 
			
		||||
"NvimInvalid"-prefixed highlight groups which have the same meaning but
 | 
			
		||||
indicate that the token contains an error or that an error occurred just
 | 
			
		||||
before it.  They have mostly the same hierarchy, except that (by default) in
 | 
			
		||||
place of any non-Nvim-prefixed group NvimInvalid linking to `Error` is used
 | 
			
		||||
and some other intermediate groups are present.
 | 
			
		||||
 | 
			
		||||
Group                              Default link            Colored expression ~
 | 
			
		||||
*hl-NvimInternalError*               None, red/red           Parser bug
 | 
			
		||||
 | 
			
		||||
*hl-NvimAssignment*                  Operator                Generic assignment
 | 
			
		||||
*hl-NvimPlainAssignment*             NvimAssignment          `=` in |:let|
 | 
			
		||||
*hl-NvimAugmentedAssignment*         NvimAssignment          Generic, `+=`/`-=`/`.=`
 | 
			
		||||
*hl-NvimAssignmentWithAddition*      NvimAugmentedAssignment `+=` in |:let+=|
 | 
			
		||||
*hl-NvimAssignmentWithSubtraction*   NvimAugmentedAssignment `-=` in |:let-=|
 | 
			
		||||
*hl-NvimAssignmentWithConcatenation* NvimAugmentedAssignment `.=` in |:let.=|
 | 
			
		||||
 | 
			
		||||
*hl-NvimOperator*                    Operator                Generic operator
 | 
			
		||||
 | 
			
		||||
*hl-NvimUnaryOperator*               NvimOperator            Generic unary op
 | 
			
		||||
*hl-NvimUnaryPlus*                   NvimUnaryOperator       |expr-unary-+|
 | 
			
		||||
*hl-NvimUnaryMinus*                  NvimUnaryOperator       |expr-unary--|
 | 
			
		||||
*hl-NvimNot*                         NvimUnaryOperator       |expr-!|
 | 
			
		||||
 | 
			
		||||
*hl-NvimBinaryOperator*              NvimOperator            Generic binary op
 | 
			
		||||
*hl-NvimComparison*                  NvimBinaryOperator      Any |expr4| operator
 | 
			
		||||
*hl-NvimComparisonModifier*          NvimComparison          `#`/`?` near |expr4| op
 | 
			
		||||
*hl-NvimBinaryPlus*                  NvimBinaryOperator      |expr-+|
 | 
			
		||||
*hl-NvimBinaryMinus*                 NvimBinaryOperator      |expr--|
 | 
			
		||||
*hl-NvimConcat*                      NvimBinaryOperator      |expr-.|
 | 
			
		||||
*hl-NvimConcatOrSubscript*           NvimConcat              |expr-.| or |expr-entry|
 | 
			
		||||
*hl-NvimOr*                          NvimBinaryOperator      |expr-barbar|
 | 
			
		||||
*hl-NvimAnd*                         NvimBinaryOperator      |expr-&&|
 | 
			
		||||
*hl-NvimMultiplication*              NvimBinaryOperator      |expr-star|
 | 
			
		||||
*hl-NvimDivision*                    NvimBinaryOperator      |expr-/|
 | 
			
		||||
*hl-NvimMod*                         NvimBinaryOperator      |expr-%|
 | 
			
		||||
 | 
			
		||||
*hl-NvimTernary*                     NvimOperator            `?` in |expr1|
 | 
			
		||||
*hl-NvimTernaryColon*                NvimTernary             `:` in |expr1|
 | 
			
		||||
 | 
			
		||||
*hl-NvimParenthesis*                 Delimiter               Generic bracket
 | 
			
		||||
*hl-NvimLambda*                      NvimParenthesis         `{`/`}` in |lambda|
 | 
			
		||||
*hl-NvimNestingParenthesis*          NvimParenthesis         `(`/`)` in |expr-nesting|
 | 
			
		||||
*hl-NvimCallingParenthesis*          NvimParenthesis         `(`/`)` in |expr-function|
 | 
			
		||||
 | 
			
		||||
*hl-NvimSubscript*                   NvimParenthesis         Generic subscript
 | 
			
		||||
*hl-NvimSubscriptBracket*            NvimSubscript           `[`/`]` in |expr-[]|
 | 
			
		||||
*hl-NvimSubscriptColon*              NvimSubscript           `:` in |expr-[:]|
 | 
			
		||||
*hl-NvimCurly*                       NvimSubscript           `{`/`}` in
 | 
			
		||||
                                                           |curly-braces-names|
 | 
			
		||||
 | 
			
		||||
*hl-NvimContainer*                   NvimParenthesis         Generic container
 | 
			
		||||
*hl-NvimDict*                        NvimContainer           `{`/`}` in |dict| literal
 | 
			
		||||
*hl-NvimList*                        NvimContainer           `[`/`]` in |list| literal
 | 
			
		||||
 | 
			
		||||
*hl-NvimIdentifier*                  Identifier              Generic identifier
 | 
			
		||||
*hl-NvimIdentifierScope*             NvimIdentifier          Namespace: letter
 | 
			
		||||
                                                           before `:` in
 | 
			
		||||
                                                           |internal-variables|
 | 
			
		||||
*hl-NvimIdentifierScopeDelimiter*    NvimIdentifier          `:` after namespace
 | 
			
		||||
                                                           letter
 | 
			
		||||
*hl-NvimIdentifierName*              NvimIdentifier          Rest of the ident
 | 
			
		||||
*hl-NvimIdentifierKey*               NvimIdentifier          Identifier after
 | 
			
		||||
                                                           |expr-entry|
 | 
			
		||||
 | 
			
		||||
*hl-NvimColon*                       Delimiter               `:` in |dict| literal
 | 
			
		||||
*hl-NvimComma*                       Delimiter               `,` in |dict|/|list|
 | 
			
		||||
                                                           literal or
 | 
			
		||||
                                                           |expr-function|
 | 
			
		||||
*hl-NvimArrow*                       Delimiter               `->` in |lambda|
 | 
			
		||||
 | 
			
		||||
*hl-NvimRegister*                    SpecialChar             |expr-register|
 | 
			
		||||
*hl-NvimNumber*                      Number                  Non-prefix digits
 | 
			
		||||
                                                           in integer
 | 
			
		||||
                                                           |expr-number|
 | 
			
		||||
*hl-NvimNumberPrefix*                Type                    `0` for |octal-number|
 | 
			
		||||
                                                           `0x` for |hex-number|
 | 
			
		||||
                                                           `0b` for |binary-number|
 | 
			
		||||
*hl-NvimFloat*                       NvimNumber              Floating-point
 | 
			
		||||
                                                           number
 | 
			
		||||
 | 
			
		||||
*hl-NvimOptionSigil*                 Type                    `&` in |expr-option|
 | 
			
		||||
*hl-NvimOptionScope*                 NvimIdentifierScope     Option scope if any
 | 
			
		||||
*hl-NvimOptionScopeDelimiter*        NvimIdentifierScopeDelimiter
 | 
			
		||||
                                                           `:` after option scope
 | 
			
		||||
*hl-NvimOptionName*                  NvimIdentifier          Option name
 | 
			
		||||
 | 
			
		||||
*hl-NvimEnvironmentSigil*            NvimOptionSigil         `$` in |expr-env|
 | 
			
		||||
*hl-NvimEnvironmentName*             NvimIdentifier          Env variable name
 | 
			
		||||
 | 
			
		||||
*hl-NvimString*                      String                  Generic string
 | 
			
		||||
*hl-NvimStringBody*                  NvimString              Generic string
 | 
			
		||||
                                                           literal body
 | 
			
		||||
*hl-NvimStringQuote*                 NvimString              Generic string quote
 | 
			
		||||
*hl-NvimStringSpecial*               SpecialChar             Generic string
 | 
			
		||||
                                                           non-literal body
 | 
			
		||||
 | 
			
		||||
*hl-NvimSingleQuote*                 NvimStringQuote         `'` in |expr-'|
 | 
			
		||||
*hl-NvimSingleQuotedBody*            NvimStringBody          Literal part of
 | 
			
		||||
                                                           |expr-'| string body
 | 
			
		||||
*hl-NvimSingleQuotedQuote*           NvimStringSpecial       `''` inside |expr-'|
 | 
			
		||||
                                                           string body
 | 
			
		||||
 | 
			
		||||
*hl-NvimDoubleQuote*                 NvimStringQuote         `"` in |expr-quote|
 | 
			
		||||
*hl-NvimDoubleQuotedBody*            NvimStringBody          Literal part of
 | 
			
		||||
                                                           |expr-quote| body
 | 
			
		||||
*hl-NvimDoubleQuotedEscape*          NvimStringSpecial       Valid |expr-quote|
 | 
			
		||||
                                                           escape sequence
 | 
			
		||||
*hl-NvimDoubleQuotedUnknownEscape*   NvimInvalidValue        Unrecognized
 | 
			
		||||
                                                           |expr-quote| escape
 | 
			
		||||
                                                           sequence
 | 
			
		||||
 | 
			
		||||
 vim:tw=78:ts=8:noet:ft=help:norl:
 | 
			
		||||
 
 | 
			
		||||
@@ -165,14 +165,17 @@ Highlight groups:
 | 
			
		||||
  |hl-TermCursor|
 | 
			
		||||
  |hl-TermCursorNC|
 | 
			
		||||
  |hl-Whitespace| highlights 'listchars' whitespace
 | 
			
		||||
  |expr-highlight| highlight groups (prefixed with "Nvim")
 | 
			
		||||
 | 
			
		||||
UI:
 | 
			
		||||
		*E5408* *E5409* *g:Nvim_color_expr* *g:Nvim_color_cmdline*
 | 
			
		||||
  Command-line coloring is supported. Only |input()| and |inputdialog()| may
 | 
			
		||||
  be colored. For testing purposes expressions (e.g. |i_CTRL-R_=|) and regular
 | 
			
		||||
  command-line (|:|) are colored by callbacks defined in `g:Nvim_color_expr`
 | 
			
		||||
  and `g:Nvim_color_cmdline` respectively (these callbacks are for testing
 | 
			
		||||
  only, and will be removed in a future version). 
 | 
			
		||||
Command-line highlighting:
 | 
			
		||||
  The expression prompt (|@=|, |c_CTRL-R_=|, |i_CTRL-R_=|) is highlighted
 | 
			
		||||
  using a built-in VimL expression parser. |expr-highlight|
 | 
			
		||||
					*E5408* *E5409*
 | 
			
		||||
  |input()|, |inputdialog()| support custom highlighting. |input()-highlight|
 | 
			
		||||
					*g:Nvim_color_cmdline*
 | 
			
		||||
  (Experimental) Command-line (|:|) is colored by callback defined in
 | 
			
		||||
  `g:Nvim_color_cmdline` (this callback is for testing only, and will be
 | 
			
		||||
  removed in the future).
 | 
			
		||||
 | 
			
		||||
==============================================================================
 | 
			
		||||
4. Changed features					 *nvim-features-changed*
 | 
			
		||||
 
 | 
			
		||||
@@ -86,6 +86,8 @@ foreach(subdir
 | 
			
		||||
        event
 | 
			
		||||
        eval
 | 
			
		||||
        lua
 | 
			
		||||
        viml
 | 
			
		||||
        viml/parser
 | 
			
		||||
       )
 | 
			
		||||
  if(${subdir} MATCHES "tui" AND NOT FEAT_TUI)
 | 
			
		||||
    continue()
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,8 @@
 | 
			
		||||
#include "nvim/syntax.h"
 | 
			
		||||
#include "nvim/getchar.h"
 | 
			
		||||
#include "nvim/os/input.h"
 | 
			
		||||
#include "nvim/viml/parser/expressions.h"
 | 
			
		||||
#include "nvim/viml/parser/parser.h"
 | 
			
		||||
 | 
			
		||||
#define LINE_BUFFER_SIZE 4096
 | 
			
		||||
 | 
			
		||||
@@ -889,6 +891,458 @@ theend:
 | 
			
		||||
  return rv;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
  ExprASTNode **node_p;
 | 
			
		||||
  Object *ret_node_p;
 | 
			
		||||
} ExprASTConvStackItem;
 | 
			
		||||
 | 
			
		||||
typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack;
 | 
			
		||||
 | 
			
		||||
/// Parse a VimL expression
 | 
			
		||||
///
 | 
			
		||||
/// @param[in]  expr  Expression to parse. Is always treated as a single line.
 | 
			
		||||
/// @param[in]  flags  Flags:
 | 
			
		||||
///
 | 
			
		||||
///                    - "m" if multiple expressions in a row are allowed (only
 | 
			
		||||
///                      the first one will be parsed),
 | 
			
		||||
///                    - "E" if EOC tokens are not allowed (determines whether
 | 
			
		||||
///                      they will stop parsing process or be recognized as an
 | 
			
		||||
///                      operator/space, though also yielding an error).
 | 
			
		||||
///                    - "l" when needing to start parsing with lvalues for
 | 
			
		||||
///                      ":let" or ":for".
 | 
			
		||||
///
 | 
			
		||||
///                    Common flag sets:
 | 
			
		||||
///                    - "m" to parse like for ":echo".
 | 
			
		||||
///                    - "E" to parse like for "<C-r>=".
 | 
			
		||||
///                    - empty string for ":call".
 | 
			
		||||
///                    - "lm" to parse for ":let".
 | 
			
		||||
/// @param[in]  highlight  If true, return value will also include "highlight"
 | 
			
		||||
///                        key containing array of 4-tuples (arrays) (Integer,
 | 
			
		||||
///                        Integer, Integer, String), where first three numbers
 | 
			
		||||
///                        define the highlighted region and represent line,
 | 
			
		||||
///                        starting column and ending column (latter exclusive:
 | 
			
		||||
///                        one should highlight region [start_col, end_col)).
 | 
			
		||||
///
 | 
			
		||||
/// @return AST: top-level dictionary holds keys
 | 
			
		||||
///
 | 
			
		||||
///         "error": Dictionary with error, present only if parser saw some
 | 
			
		||||
///                  error. Contains the following keys:
 | 
			
		||||
///
 | 
			
		||||
///           "message": String, error message in printf format, translated.
 | 
			
		||||
///                      Must contain exactly one "%.*s".
 | 
			
		||||
///           "arg": String, error message argument.
 | 
			
		||||
///
 | 
			
		||||
///         "len": Amount of bytes successfully parsed. With flags equal to ""
 | 
			
		||||
///                that should be equal to the length of expr string.
 | 
			
		||||
///
 | 
			
		||||
///                @note: “Sucessfully parsed” here means “participated in AST
 | 
			
		||||
///                       creation”, not “till the first error”.
 | 
			
		||||
///
 | 
			
		||||
///         "ast": AST, either nil or a dictionary with these keys:
 | 
			
		||||
///
 | 
			
		||||
///           "type": node type, one of the value names from ExprASTNodeType
 | 
			
		||||
///                   stringified without "kExprNode" prefix.
 | 
			
		||||
///           "start": a pair [line, column] describing where node is “started”
 | 
			
		||||
///                    where "line" is always 0 (will not be 0 if you will be
 | 
			
		||||
///                    using nvim_parse_viml() on e.g. ":let", but that is not
 | 
			
		||||
///                    present yet). Both elements are Integers.
 | 
			
		||||
///           "len": “length” of the node. This and "start" are there for
 | 
			
		||||
///                  debugging purposes primary (debugging parser and providing
 | 
			
		||||
///                  debug information).
 | 
			
		||||
///           "children": a list of nodes described in top/"ast". There always
 | 
			
		||||
///                       is zero, one or two children, key will not be present
 | 
			
		||||
///                       if node has no children. Maximum number of children
 | 
			
		||||
///                       may be found in node_maxchildren array.
 | 
			
		||||
///
 | 
			
		||||
///           Local values (present only for certain nodes):
 | 
			
		||||
///
 | 
			
		||||
///           "scope": a single Integer, specifies scope for "Option" and
 | 
			
		||||
///                    "PlainIdentifier" nodes. For "Option" it is one of
 | 
			
		||||
///                    ExprOptScope values, for "PlainIdentifier" it is one of
 | 
			
		||||
///                    ExprVarScope values.
 | 
			
		||||
///           "ident": identifier (without scope, if any), present for "Option",
 | 
			
		||||
///                    "PlainIdentifier", "PlainKey" and "Environment" nodes.
 | 
			
		||||
///           "name": Integer, register name (one character) or -1. Only present
 | 
			
		||||
///                   for "Register" nodes.
 | 
			
		||||
///           "cmp_type": String, comparison type, one of the value names from
 | 
			
		||||
///                       ExprComparisonType, stringified without "kExprCmp"
 | 
			
		||||
///                       prefix. Only present for "Comparison" nodes.
 | 
			
		||||
///           "ccs_strategy": String, case comparison strategy, one of the
 | 
			
		||||
///                           value names from ExprCaseCompareStrategy,
 | 
			
		||||
///                           stringified without "kCCStrategy" prefix. Only
 | 
			
		||||
///                           present for "Comparison" nodes.
 | 
			
		||||
///           "augmentation": String, augmentation type for "Assignment" nodes.
 | 
			
		||||
///                           Is either an empty string, "Add", "Subtract" or
 | 
			
		||||
///                           "Concat" for "=", "+=", "-=" or ".=" respectively.
 | 
			
		||||
///           "invert": Boolean, true if result of comparison needs to be
 | 
			
		||||
///                     inverted. Only present for "Comparison" nodes.
 | 
			
		||||
///           "ivalue": Integer, integer value for "Integer" nodes.
 | 
			
		||||
///           "fvalue": Float, floating-point value for "Float" nodes.
 | 
			
		||||
///           "svalue": String, value for "SingleQuotedString" and
 | 
			
		||||
///                     "DoubleQuotedString" nodes.
 | 
			
		||||
Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight,
 | 
			
		||||
                                 Error *err)
 | 
			
		||||
  FUNC_API_SINCE(4) FUNC_API_ASYNC
 | 
			
		||||
{
 | 
			
		||||
  int pflags = 0;
 | 
			
		||||
  for (size_t i = 0 ; i < flags.size ; i++) {
 | 
			
		||||
    switch (flags.data[i]) {
 | 
			
		||||
      case 'm': { pflags |= kExprFlagsMulti; break; }
 | 
			
		||||
      case 'E': { pflags |= kExprFlagsDisallowEOC; break; }
 | 
			
		||||
      case 'l': { pflags |= kExprFlagsParseLet; break; }
 | 
			
		||||
      case NUL: {
 | 
			
		||||
        api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)",
 | 
			
		||||
                      (unsigned)flags.data[i]);
 | 
			
		||||
        return (Dictionary)ARRAY_DICT_INIT;
 | 
			
		||||
      }
 | 
			
		||||
      default: {
 | 
			
		||||
        api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)",
 | 
			
		||||
                      flags.data[i], (unsigned)flags.data[i]);
 | 
			
		||||
        return (Dictionary)ARRAY_DICT_INIT;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  ParserLine plines[] = {
 | 
			
		||||
    {
 | 
			
		||||
      .data = expr.data,
 | 
			
		||||
      .size = expr.size,
 | 
			
		||||
      .allocated = false,
 | 
			
		||||
    },
 | 
			
		||||
    { NULL, 0, false },
 | 
			
		||||
  };
 | 
			
		||||
  ParserLine *plines_p = plines;
 | 
			
		||||
  ParserHighlight colors;
 | 
			
		||||
  kvi_init(colors);
 | 
			
		||||
  ParserHighlight *const colors_p = (highlight ? &colors : NULL);
 | 
			
		||||
  ParserState pstate;
 | 
			
		||||
  viml_parser_init(
 | 
			
		||||
      &pstate, parser_simple_get_line, &plines_p, colors_p);
 | 
			
		||||
  ExprAST east = viml_pexpr_parse(&pstate, pflags);
 | 
			
		||||
 | 
			
		||||
  const size_t ret_size = (
 | 
			
		||||
      2  // "ast", "len"
 | 
			
		||||
      + (size_t)(east.err.msg != NULL)  // "error"
 | 
			
		||||
      + (size_t)highlight  // "highlight"
 | 
			
		||||
      + 0);
 | 
			
		||||
  Dictionary ret = {
 | 
			
		||||
    .items = xmalloc(ret_size * sizeof(ret.items[0])),
 | 
			
		||||
    .size = 0,
 | 
			
		||||
    .capacity = ret_size,
 | 
			
		||||
  };
 | 
			
		||||
  ret.items[ret.size++] = (KeyValuePair) {
 | 
			
		||||
    .key = STATIC_CSTR_TO_STRING("ast"),
 | 
			
		||||
    .value = NIL,
 | 
			
		||||
  };
 | 
			
		||||
  ret.items[ret.size++] = (KeyValuePair) {
 | 
			
		||||
    .key = STATIC_CSTR_TO_STRING("len"),
 | 
			
		||||
    .value = INTEGER_OBJ((Integer)(pstate.pos.line == 1
 | 
			
		||||
                                   ? plines[0].size
 | 
			
		||||
                                   : pstate.pos.col)),
 | 
			
		||||
  };
 | 
			
		||||
  if (east.err.msg != NULL) {
 | 
			
		||||
    Dictionary err_dict = {
 | 
			
		||||
      .items = xmalloc(2 * sizeof(err_dict.items[0])),
 | 
			
		||||
      .size = 2,
 | 
			
		||||
      .capacity = 2,
 | 
			
		||||
    };
 | 
			
		||||
    err_dict.items[0] = (KeyValuePair) {
 | 
			
		||||
      .key = STATIC_CSTR_TO_STRING("message"),
 | 
			
		||||
      .value = STRING_OBJ(cstr_to_string(east.err.msg)),
 | 
			
		||||
    };
 | 
			
		||||
    if (east.err.arg == NULL) {
 | 
			
		||||
      err_dict.items[1] = (KeyValuePair) {
 | 
			
		||||
        .key = STATIC_CSTR_TO_STRING("arg"),
 | 
			
		||||
        .value = STRING_OBJ(STRING_INIT),
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
      err_dict.items[1] = (KeyValuePair) {
 | 
			
		||||
        .key = STATIC_CSTR_TO_STRING("arg"),
 | 
			
		||||
        .value = STRING_OBJ(((String) {
 | 
			
		||||
          .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len),
 | 
			
		||||
          .size = (size_t)east.err.arg_len,
 | 
			
		||||
        })),
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
    ret.items[ret.size++] = (KeyValuePair) {
 | 
			
		||||
      .key = STATIC_CSTR_TO_STRING("error"),
 | 
			
		||||
      .value = DICTIONARY_OBJ(err_dict),
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  if (highlight) {
 | 
			
		||||
    Array hl = (Array) {
 | 
			
		||||
      .items = xmalloc(kv_size(colors) * sizeof(hl.items[0])),
 | 
			
		||||
      .capacity = kv_size(colors),
 | 
			
		||||
      .size = kv_size(colors),
 | 
			
		||||
    };
 | 
			
		||||
    for (size_t i = 0 ; i < kv_size(colors) ; i++) {
 | 
			
		||||
      const ParserHighlightChunk chunk = kv_A(colors, i);
 | 
			
		||||
      Array chunk_arr = (Array) {
 | 
			
		||||
        .items = xmalloc(4 * sizeof(chunk_arr.items[0])),
 | 
			
		||||
        .capacity = 4,
 | 
			
		||||
        .size = 4,
 | 
			
		||||
      };
 | 
			
		||||
      chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line);
 | 
			
		||||
      chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col);
 | 
			
		||||
      chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col);
 | 
			
		||||
      chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group));
 | 
			
		||||
      hl.items[i] = ARRAY_OBJ(chunk_arr);
 | 
			
		||||
    }
 | 
			
		||||
    ret.items[ret.size++] = (KeyValuePair) {
 | 
			
		||||
      .key = STATIC_CSTR_TO_STRING("highlight"),
 | 
			
		||||
      .value = ARRAY_OBJ(hl),
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  kvi_destroy(colors);
 | 
			
		||||
 | 
			
		||||
  // Walk over the AST, freeing nodes in process.
 | 
			
		||||
  ExprASTConvStack ast_conv_stack;
 | 
			
		||||
  kvi_init(ast_conv_stack);
 | 
			
		||||
  kvi_push(ast_conv_stack, ((ExprASTConvStackItem) {
 | 
			
		||||
    .node_p = &east.root,
 | 
			
		||||
    .ret_node_p = &ret.items[0].value,
 | 
			
		||||
  }));
 | 
			
		||||
  while (kv_size(ast_conv_stack)) {
 | 
			
		||||
    ExprASTConvStackItem cur_item = kv_last(ast_conv_stack);
 | 
			
		||||
    ExprASTNode *const node = *cur_item.node_p;
 | 
			
		||||
    if (node == NULL) {
 | 
			
		||||
      assert(kv_size(ast_conv_stack) == 1);
 | 
			
		||||
      kv_drop(ast_conv_stack, 1);
 | 
			
		||||
    } else {
 | 
			
		||||
      if (cur_item.ret_node_p->type == kObjectTypeNil) {
 | 
			
		||||
        const size_t ret_node_items_size = (size_t)(
 | 
			
		||||
            3  // "type", "start" and "len"
 | 
			
		||||
            + (node->children != NULL)  // "children"
 | 
			
		||||
            + (node->type == kExprNodeOption
 | 
			
		||||
               || node->type == kExprNodePlainIdentifier)  // "scope"
 | 
			
		||||
            + (node->type == kExprNodeOption
 | 
			
		||||
               || node->type == kExprNodePlainIdentifier
 | 
			
		||||
               || node->type == kExprNodePlainKey
 | 
			
		||||
               || node->type == kExprNodeEnvironment)  // "ident"
 | 
			
		||||
            + (node->type == kExprNodeRegister)  // "name"
 | 
			
		||||
            + (3  // "cmp_type", "ccs_strategy", "invert"
 | 
			
		||||
               * (node->type == kExprNodeComparison))
 | 
			
		||||
            + (node->type == kExprNodeInteger)  // "ivalue"
 | 
			
		||||
            + (node->type == kExprNodeFloat)  // "fvalue"
 | 
			
		||||
            + (node->type == kExprNodeDoubleQuotedString
 | 
			
		||||
               || node->type == kExprNodeSingleQuotedString)  // "svalue"
 | 
			
		||||
            + (node->type == kExprNodeAssignment)  // "augmentation"
 | 
			
		||||
            + 0);
 | 
			
		||||
        Dictionary ret_node = {
 | 
			
		||||
          .items = xmalloc(ret_node_items_size * sizeof(ret_node.items[0])),
 | 
			
		||||
          .capacity = ret_node_items_size,
 | 
			
		||||
          .size = 0,
 | 
			
		||||
        };
 | 
			
		||||
        *cur_item.ret_node_p = DICTIONARY_OBJ(ret_node);
 | 
			
		||||
      }
 | 
			
		||||
      Dictionary *ret_node = &cur_item.ret_node_p->data.dictionary;
 | 
			
		||||
      if (node->children != NULL) {
 | 
			
		||||
        const size_t num_children = 1 + (node->children->next != NULL);
 | 
			
		||||
        Array children_array = {
 | 
			
		||||
          .items = xmalloc(num_children * sizeof(children_array.items[0])),
 | 
			
		||||
          .capacity = num_children,
 | 
			
		||||
          .size = num_children,
 | 
			
		||||
        };
 | 
			
		||||
        for (size_t i = 0; i < num_children; i++) {
 | 
			
		||||
          children_array.items[i] = NIL;
 | 
			
		||||
        }
 | 
			
		||||
        ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
          .key = STATIC_CSTR_TO_STRING("children"),
 | 
			
		||||
          .value = ARRAY_OBJ(children_array),
 | 
			
		||||
        };
 | 
			
		||||
        kvi_push(ast_conv_stack, ((ExprASTConvStackItem) {
 | 
			
		||||
          .node_p = &node->children,
 | 
			
		||||
          .ret_node_p = &children_array.items[0],
 | 
			
		||||
        }));
 | 
			
		||||
      } else if (node->next != NULL) {
 | 
			
		||||
        kvi_push(ast_conv_stack, ((ExprASTConvStackItem) {
 | 
			
		||||
          .node_p = &node->next,
 | 
			
		||||
          .ret_node_p = cur_item.ret_node_p + 1,
 | 
			
		||||
        }));
 | 
			
		||||
      } else if (node != NULL) {
 | 
			
		||||
        kv_drop(ast_conv_stack, 1);
 | 
			
		||||
        ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
          .key = STATIC_CSTR_TO_STRING("type"),
 | 
			
		||||
          .value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])),
 | 
			
		||||
        };
 | 
			
		||||
        Array start_array = {
 | 
			
		||||
          .items = xmalloc(2 * sizeof(start_array.items[0])),
 | 
			
		||||
          .capacity = 2,
 | 
			
		||||
          .size = 2,
 | 
			
		||||
        };
 | 
			
		||||
        start_array.items[0] = INTEGER_OBJ((Integer)node->start.line);
 | 
			
		||||
        start_array.items[1] = INTEGER_OBJ((Integer)node->start.col);
 | 
			
		||||
        ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
          .key = STATIC_CSTR_TO_STRING("start"),
 | 
			
		||||
          .value = ARRAY_OBJ(start_array),
 | 
			
		||||
        };
 | 
			
		||||
        ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
          .key = STATIC_CSTR_TO_STRING("len"),
 | 
			
		||||
          .value = INTEGER_OBJ((Integer)node->len),
 | 
			
		||||
        };
 | 
			
		||||
        switch (node->type) {
 | 
			
		||||
          case kExprNodeDoubleQuotedString:
 | 
			
		||||
          case kExprNodeSingleQuotedString: {
 | 
			
		||||
            ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
              .key = STATIC_CSTR_TO_STRING("svalue"),
 | 
			
		||||
              .value = STRING_OBJ(((String) {
 | 
			
		||||
                .data = node->data.str.value,
 | 
			
		||||
                .size = node->data.str.size,
 | 
			
		||||
              })),
 | 
			
		||||
            };
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          case kExprNodeOption: {
 | 
			
		||||
            ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
              .key = STATIC_CSTR_TO_STRING("scope"),
 | 
			
		||||
              .value = INTEGER_OBJ(node->data.opt.scope),
 | 
			
		||||
            };
 | 
			
		||||
            ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
              .key = STATIC_CSTR_TO_STRING("ident"),
 | 
			
		||||
              .value = STRING_OBJ(((String) {
 | 
			
		||||
                .data = xmemdupz(node->data.opt.ident,
 | 
			
		||||
                                 node->data.opt.ident_len),
 | 
			
		||||
                .size = node->data.opt.ident_len,
 | 
			
		||||
              })),
 | 
			
		||||
            };
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          case kExprNodePlainIdentifier: {
 | 
			
		||||
            ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
              .key = STATIC_CSTR_TO_STRING("scope"),
 | 
			
		||||
              .value = INTEGER_OBJ(node->data.var.scope),
 | 
			
		||||
            };
 | 
			
		||||
            ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
              .key = STATIC_CSTR_TO_STRING("ident"),
 | 
			
		||||
              .value = STRING_OBJ(((String) {
 | 
			
		||||
                .data = xmemdupz(node->data.var.ident,
 | 
			
		||||
                                 node->data.var.ident_len),
 | 
			
		||||
                .size = node->data.var.ident_len,
 | 
			
		||||
              })),
 | 
			
		||||
            };
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          case kExprNodePlainKey: {
 | 
			
		||||
            ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
              .key = STATIC_CSTR_TO_STRING("ident"),
 | 
			
		||||
              .value = STRING_OBJ(((String) {
 | 
			
		||||
                .data = xmemdupz(node->data.var.ident,
 | 
			
		||||
                                 node->data.var.ident_len),
 | 
			
		||||
                .size = node->data.var.ident_len,
 | 
			
		||||
              })),
 | 
			
		||||
            };
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          case kExprNodeEnvironment: {
 | 
			
		||||
            ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
              .key = STATIC_CSTR_TO_STRING("ident"),
 | 
			
		||||
              .value = STRING_OBJ(((String) {
 | 
			
		||||
                .data = xmemdupz(node->data.env.ident,
 | 
			
		||||
                                 node->data.env.ident_len),
 | 
			
		||||
                .size = node->data.env.ident_len,
 | 
			
		||||
              })),
 | 
			
		||||
            };
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          case kExprNodeRegister: {
 | 
			
		||||
            ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
              .key = STATIC_CSTR_TO_STRING("name"),
 | 
			
		||||
              .value = INTEGER_OBJ(node->data.reg.name),
 | 
			
		||||
            };
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          case kExprNodeComparison: {
 | 
			
		||||
            ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
              .key = STATIC_CSTR_TO_STRING("cmp_type"),
 | 
			
		||||
              .value = STRING_OBJ(cstr_to_string(
 | 
			
		||||
                  eltkn_cmp_type_tab[node->data.cmp.type])),
 | 
			
		||||
            };
 | 
			
		||||
            ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
              .key = STATIC_CSTR_TO_STRING("ccs_strategy"),
 | 
			
		||||
              .value = STRING_OBJ(cstr_to_string(
 | 
			
		||||
                  ccs_tab[node->data.cmp.ccs])),
 | 
			
		||||
            };
 | 
			
		||||
            ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
              .key = STATIC_CSTR_TO_STRING("invert"),
 | 
			
		||||
              .value = BOOLEAN_OBJ(node->data.cmp.inv),
 | 
			
		||||
            };
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          case kExprNodeFloat: {
 | 
			
		||||
            ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
              .key = STATIC_CSTR_TO_STRING("fvalue"),
 | 
			
		||||
              .value = FLOAT_OBJ(node->data.flt.value),
 | 
			
		||||
            };
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          case kExprNodeInteger: {
 | 
			
		||||
            ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
              .key = STATIC_CSTR_TO_STRING("ivalue"),
 | 
			
		||||
              .value = INTEGER_OBJ((Integer)(
 | 
			
		||||
                  node->data.num.value > API_INTEGER_MAX
 | 
			
		||||
                  ? API_INTEGER_MAX
 | 
			
		||||
                  : (Integer)node->data.num.value)),
 | 
			
		||||
            };
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          case kExprNodeAssignment: {
 | 
			
		||||
            const ExprAssignmentType asgn_type = node->data.ass.type;
 | 
			
		||||
            ret_node->items[ret_node->size++] = (KeyValuePair) {
 | 
			
		||||
              .key = STATIC_CSTR_TO_STRING("augmentation"),
 | 
			
		||||
              .value = STRING_OBJ(
 | 
			
		||||
                  asgn_type == kExprAsgnPlain
 | 
			
		||||
                  ? (String)STRING_INIT
 | 
			
		||||
                  : cstr_to_string(expr_asgn_type_tab[asgn_type])),
 | 
			
		||||
            };
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          case kExprNodeMissing:
 | 
			
		||||
          case kExprNodeOpMissing:
 | 
			
		||||
          case kExprNodeTernary:
 | 
			
		||||
          case kExprNodeTernaryValue:
 | 
			
		||||
          case kExprNodeSubscript:
 | 
			
		||||
          case kExprNodeListLiteral:
 | 
			
		||||
          case kExprNodeUnaryPlus:
 | 
			
		||||
          case kExprNodeBinaryPlus:
 | 
			
		||||
          case kExprNodeNested:
 | 
			
		||||
          case kExprNodeCall:
 | 
			
		||||
          case kExprNodeComplexIdentifier:
 | 
			
		||||
          case kExprNodeUnknownFigure:
 | 
			
		||||
          case kExprNodeLambda:
 | 
			
		||||
          case kExprNodeDictLiteral:
 | 
			
		||||
          case kExprNodeCurlyBracesIdentifier:
 | 
			
		||||
          case kExprNodeComma:
 | 
			
		||||
          case kExprNodeColon:
 | 
			
		||||
          case kExprNodeArrow:
 | 
			
		||||
          case kExprNodeConcat:
 | 
			
		||||
          case kExprNodeConcatOrSubscript:
 | 
			
		||||
          case kExprNodeOr:
 | 
			
		||||
          case kExprNodeAnd:
 | 
			
		||||
          case kExprNodeUnaryMinus:
 | 
			
		||||
          case kExprNodeBinaryMinus:
 | 
			
		||||
          case kExprNodeNot:
 | 
			
		||||
          case kExprNodeMultiplication:
 | 
			
		||||
          case kExprNodeDivision:
 | 
			
		||||
          case kExprNodeMod: {
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        assert(cur_item.ret_node_p->data.dictionary.size
 | 
			
		||||
               == cur_item.ret_node_p->data.dictionary.capacity);
 | 
			
		||||
        xfree(*cur_item.node_p);
 | 
			
		||||
        *cur_item.node_p = NULL;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  kvi_destroy(ast_conv_stack);
 | 
			
		||||
 | 
			
		||||
  assert(ret.size == ret.capacity);
 | 
			
		||||
  // Should be a no-op actually, leaving it in case non-nodes will need to be
 | 
			
		||||
  // freed later.
 | 
			
		||||
  viml_pexpr_free_ast(east);
 | 
			
		||||
  viml_parser_destroy(&pstate);
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Writes a message to vim output or error buffer. The string is split
 | 
			
		||||
/// and flushed after each newline. Incomplete lines are kept for writing
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
#include "nvim/macros.h"
 | 
			
		||||
#include "nvim/func_attr.h"
 | 
			
		||||
#include "nvim/os/os_defs.h"
 | 
			
		||||
 | 
			
		||||
@@ -98,6 +99,10 @@ static inline bool ascii_isxdigit(int)
 | 
			
		||||
  REAL_FATTR_CONST
 | 
			
		||||
  REAL_FATTR_ALWAYS_INLINE;
 | 
			
		||||
 | 
			
		||||
static inline bool ascii_isident(int)
 | 
			
		||||
  REAL_FATTR_CONST
 | 
			
		||||
  REAL_FATTR_ALWAYS_INLINE;
 | 
			
		||||
 | 
			
		||||
static inline bool ascii_isbdigit(int)
 | 
			
		||||
  REAL_FATTR_CONST
 | 
			
		||||
  REAL_FATTR_ALWAYS_INLINE;
 | 
			
		||||
@@ -138,6 +143,14 @@ static inline bool ascii_isxdigit(int c)
 | 
			
		||||
         || (c >= 'A' && c <= 'F');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Checks if `c` is an “identifier” character
 | 
			
		||||
///
 | 
			
		||||
/// That is, whether it is alphanumeric character or underscore.
 | 
			
		||||
static inline bool ascii_isident(const int c)
 | 
			
		||||
{
 | 
			
		||||
  return ASCII_ISALNUM(c) || c == '_';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Checks if `c` is a binary digit, that is, 0-1.
 | 
			
		||||
///
 | 
			
		||||
/// @see {ascii_isdigit}
 | 
			
		||||
 
 | 
			
		||||
@@ -1611,141 +1611,145 @@ bool vim_isblankline(char_u *lbuf)
 | 
			
		||||
/// If maxlen > 0, check at a maximum maxlen chars.
 | 
			
		||||
///
 | 
			
		||||
/// @param start
 | 
			
		||||
/// @param prep Returns type of number 0 = decimal, 'x' or 'X' is hex,
 | 
			
		||||
///        '0' = octal, 'b' or 'B' is bin
 | 
			
		||||
/// @param prep Returns guessed type of number 0 = decimal, 'x' or 'X' is
 | 
			
		||||
///             hexadecimal, '0' = octal, 'b' or 'B' is binary. When using
 | 
			
		||||
///             STR2NR_FORCE is always zero.
 | 
			
		||||
/// @param len Returns the detected length of number.
 | 
			
		||||
/// @param what Recognizes what number passed.
 | 
			
		||||
/// @param what Recognizes what number passed, @see ChStr2NrFlags.
 | 
			
		||||
/// @param nptr Returns the signed result.
 | 
			
		||||
/// @param unptr Returns the unsigned result.
 | 
			
		||||
/// @param maxlen Max length of string to check.
 | 
			
		||||
void vim_str2nr(const char_u *const start, int *const prep, int *const len,
 | 
			
		||||
                const int what, varnumber_T *const nptr,
 | 
			
		||||
                uvarnumber_T *const unptr, const int maxlen)
 | 
			
		||||
  FUNC_ATTR_NONNULL_ARG(1)
 | 
			
		||||
{
 | 
			
		||||
  const char_u *ptr = start;
 | 
			
		||||
  const char *ptr = (const char *)start;
 | 
			
		||||
#define STRING_ENDED(ptr) \
 | 
			
		||||
    (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen))
 | 
			
		||||
  int pre = 0;  // default is decimal
 | 
			
		||||
  bool negative = false;
 | 
			
		||||
  const bool negative = (ptr[0] == '-');
 | 
			
		||||
  uvarnumber_T un = 0;
 | 
			
		||||
 | 
			
		||||
  if (ptr[0] == '-') {
 | 
			
		||||
    negative = true;
 | 
			
		||||
  if (negative) {
 | 
			
		||||
    ptr++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Recognize hex, octal and bin.
 | 
			
		||||
  if ((ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9')
 | 
			
		||||
      && (maxlen == 0 || maxlen > 1)) {
 | 
			
		||||
  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)
 | 
			
		||||
        && ((pre == 'X') || (pre == 'x'))
 | 
			
		||||
        && ascii_isxdigit(ptr[2])
 | 
			
		||||
        && (maxlen == 0 || maxlen > 2)) {
 | 
			
		||||
      // hexadecimal
 | 
			
		||||
        && !STRING_ENDED(ptr + 2)
 | 
			
		||||
        && (pre == 'X' || pre == 'x')
 | 
			
		||||
        && ascii_isxdigit(ptr[2])) {
 | 
			
		||||
      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;
 | 
			
		||||
      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;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
          if (ptr[n] >= '0') {
 | 
			
		||||
            // assume octal
 | 
			
		||||
    pre = '0';
 | 
			
		||||
          }
 | 
			
		||||
          if (n == maxlen) {
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    goto vim_str2nr_oct;
 | 
			
		||||
  } else {
 | 
			
		||||
    goto vim_str2nr_dec;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Do the string-to-numeric conversion "manually" to avoid sscanf quirks.
 | 
			
		||||
  int n = 1;
 | 
			
		||||
  if ((pre == 'B') || (pre == 'b') || what == STR2NR_BIN + STR2NR_FORCE) {
 | 
			
		||||
    // bin
 | 
			
		||||
    if (pre != 0) {
 | 
			
		||||
      n += 2;  // skip over "0b"
 | 
			
		||||
  assert(false);  // Should’ve used goto earlier.
 | 
			
		||||
#define PARSE_NUMBER(base, cond, conv) \
 | 
			
		||||
  do { \
 | 
			
		||||
    while (!STRING_ENDED(ptr) && (cond)) { \
 | 
			
		||||
      /* avoid ubsan error for overflow */ \
 | 
			
		||||
      if (un < UVARNUMBER_MAX / base) { \
 | 
			
		||||
        un = base * un + (uvarnumber_T)(conv); \
 | 
			
		||||
      } else { \
 | 
			
		||||
        un = UVARNUMBER_MAX; \
 | 
			
		||||
      } \
 | 
			
		||||
      ptr++; \
 | 
			
		||||
    } \
 | 
			
		||||
  } while (0)
 | 
			
		||||
  switch (pre) {
 | 
			
		||||
    case 'b':
 | 
			
		||||
    case 'B': {
 | 
			
		||||
vim_str2nr_bin:
 | 
			
		||||
      PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0'));
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    while ('0' <= *ptr && *ptr <= '1') {
 | 
			
		||||
      // avoid ubsan error for overflow
 | 
			
		||||
      if (un < UVARNUMBER_MAX / 2) {
 | 
			
		||||
        un = 2 * un + (uvarnumber_T)(*ptr - '0');
 | 
			
		||||
      } else {
 | 
			
		||||
        un = UVARNUMBER_MAX;
 | 
			
		||||
    case '0': {
 | 
			
		||||
vim_str2nr_oct:
 | 
			
		||||
      PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0'));
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
      ptr++;
 | 
			
		||||
      if (n++ == maxlen) {
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  } 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;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } 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;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#undef PARSE_NUMBER
 | 
			
		||||
 | 
			
		||||
  if (prep != NULL) {
 | 
			
		||||
    *prep = pre;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (len != NULL) {
 | 
			
		||||
    *len = (int)(ptr - start);
 | 
			
		||||
    *len = (int)(ptr - (const char *)start);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (nptr != NULL) {
 | 
			
		||||
@@ -1767,6 +1771,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len,
 | 
			
		||||
  if (unptr != NULL) {
 | 
			
		||||
    *unptr = un;
 | 
			
		||||
  }
 | 
			
		||||
#undef STRING_ENDED
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Return the value of a single hex character.
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
#include "nvim/types.h"
 | 
			
		||||
#include "nvim/pos.h"
 | 
			
		||||
#include "nvim/buffer_defs.h"
 | 
			
		||||
#include "nvim/eval/typval.h"
 | 
			
		||||
 | 
			
		||||
/// Return the folded-case equivalent of the given character
 | 
			
		||||
///
 | 
			
		||||
@@ -15,6 +16,21 @@
 | 
			
		||||
             ?((int)(uint8_t)(c)) \
 | 
			
		||||
             :((int)(c)))
 | 
			
		||||
 | 
			
		||||
/// Flags for vim_str2nr()
 | 
			
		||||
typedef enum {
 | 
			
		||||
  STR2NR_DEC = 0,
 | 
			
		||||
  STR2NR_BIN = (1 << 0),  ///< Allow binary numbers.
 | 
			
		||||
  STR2NR_OCT = (1 << 1),  ///< Allow octal numbers.
 | 
			
		||||
  STR2NR_HEX = (1 << 2),  ///< Allow hexadecimal numbers.
 | 
			
		||||
  /// Force one of the above variants.
 | 
			
		||||
  ///
 | 
			
		||||
  /// STR2NR_FORCE|STR2NR_DEC is actually not different from supplying zero
 | 
			
		||||
  /// as flags, but still present for completeness.
 | 
			
		||||
  STR2NR_FORCE = (1 << 3),
 | 
			
		||||
  /// Recognize all formats vim_str2nr() can recognize.
 | 
			
		||||
  STR2NR_ALL = STR2NR_BIN | STR2NR_OCT | STR2NR_HEX,
 | 
			
		||||
} ChStr2NrFlags;
 | 
			
		||||
 | 
			
		||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
 | 
			
		||||
# include "charset.h.generated.h"
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -6066,28 +6066,31 @@ void free_last_insert(void)
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Add character "c" to buffer "s".  Escape the special meaning of K_SPECIAL
 | 
			
		||||
 * and CSI.  Handle multi-byte characters.
 | 
			
		||||
 * Returns a pointer to after the added bytes.
 | 
			
		||||
 */
 | 
			
		||||
/// Add character "c" to buffer "s"
 | 
			
		||||
///
 | 
			
		||||
/// Escapes the special meaning of K_SPECIAL and CSI, handles multi-byte
 | 
			
		||||
/// characters.
 | 
			
		||||
///
 | 
			
		||||
/// @param[in]  c  Character to add.
 | 
			
		||||
/// @param[out]  s  Buffer to add to. Must have at least MB_MAXBYTES + 1 bytes.
 | 
			
		||||
///
 | 
			
		||||
/// @return Pointer to after the added bytes.
 | 
			
		||||
char_u *add_char2buf(int c, char_u *s)
 | 
			
		||||
  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
 | 
			
		||||
{
 | 
			
		||||
  char_u temp[MB_MAXBYTES + 1];
 | 
			
		||||
  int i;
 | 
			
		||||
  int len;
 | 
			
		||||
 | 
			
		||||
  len = (*mb_char2bytes)(c, temp);
 | 
			
		||||
  for (i = 0; i < len; ++i) {
 | 
			
		||||
  const int len = utf_char2bytes(c, temp);
 | 
			
		||||
  for (int i = 0; i < len; i++) {
 | 
			
		||||
    c = temp[i];
 | 
			
		||||
    /* Need to escape K_SPECIAL and CSI like in the typeahead buffer. */
 | 
			
		||||
    // Need to escape K_SPECIAL and CSI like in the typeahead buffer.
 | 
			
		||||
    if (c == K_SPECIAL) {
 | 
			
		||||
      *s++ = K_SPECIAL;
 | 
			
		||||
      *s++ = KS_SPECIAL;
 | 
			
		||||
      *s++ = KE_FILLER;
 | 
			
		||||
    } else
 | 
			
		||||
    } else {
 | 
			
		||||
      *s++ = c;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5925,9 +5925,10 @@ static void ex_colorscheme(exarg_T *eap)
 | 
			
		||||
 | 
			
		||||
static void ex_highlight(exarg_T *eap)
 | 
			
		||||
{
 | 
			
		||||
  if (*eap->arg == NUL && eap->cmd[2] == '!')
 | 
			
		||||
  if (*eap->arg == NUL && eap->cmd[2] == '!') {
 | 
			
		||||
    MSG(_("Greetings, Vim user!"));
 | 
			
		||||
  do_highlight(eap->arg, eap->forceit, FALSE);
 | 
			
		||||
  }
 | 
			
		||||
  do_highlight((const char *)eap->arg, eap->forceit, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -66,6 +66,8 @@
 | 
			
		||||
#include "nvim/lib/kvec.h"
 | 
			
		||||
#include "nvim/api/private/helpers.h"
 | 
			
		||||
#include "nvim/highlight_defs.h"
 | 
			
		||||
#include "nvim/viml/parser/parser.h"
 | 
			
		||||
#include "nvim/viml/parser/expressions.h"
 | 
			
		||||
 | 
			
		||||
/// Command-line colors: one chunk
 | 
			
		||||
///
 | 
			
		||||
@@ -2428,6 +2430,63 @@ void free_cmdline_buf(void)
 | 
			
		||||
 | 
			
		||||
enum { MAX_CB_ERRORS = 1 };
 | 
			
		||||
 | 
			
		||||
/// Color expression cmdline using built-in expressions parser
 | 
			
		||||
///
 | 
			
		||||
/// @param[in]  colored_ccline  Command-line to color.
 | 
			
		||||
/// @param[out]  ret_ccline_colors  What should be colored.
 | 
			
		||||
///
 | 
			
		||||
/// Always colors the whole cmdline.
 | 
			
		||||
static void color_expr_cmdline(const CmdlineInfo *const colored_ccline,
 | 
			
		||||
                               ColoredCmdline *const ret_ccline_colors)
 | 
			
		||||
  FUNC_ATTR_NONNULL_ALL
 | 
			
		||||
{
 | 
			
		||||
  ParserLine plines[] = {
 | 
			
		||||
    {
 | 
			
		||||
      .data = (const char *)colored_ccline->cmdbuff,
 | 
			
		||||
      .size = STRLEN(colored_ccline->cmdbuff),
 | 
			
		||||
      .allocated = false,
 | 
			
		||||
    },
 | 
			
		||||
    { NULL, 0, false },
 | 
			
		||||
  };
 | 
			
		||||
  ParserLine *plines_p = plines;
 | 
			
		||||
  ParserHighlight colors;
 | 
			
		||||
  kvi_init(colors);
 | 
			
		||||
  ParserState pstate;
 | 
			
		||||
  viml_parser_init(
 | 
			
		||||
      &pstate, parser_simple_get_line, &plines_p, &colors);
 | 
			
		||||
  ExprAST east = viml_pexpr_parse(&pstate, kExprFlagsDisallowEOC);
 | 
			
		||||
  viml_pexpr_free_ast(east);
 | 
			
		||||
  viml_parser_destroy(&pstate);
 | 
			
		||||
  kv_resize(ret_ccline_colors->colors, kv_size(colors));
 | 
			
		||||
  size_t prev_end = 0;
 | 
			
		||||
  for (size_t i = 0 ; i < kv_size(colors) ; i++) {
 | 
			
		||||
    const ParserHighlightChunk chunk = kv_A(colors, i);
 | 
			
		||||
    if (chunk.start.col != prev_end) {
 | 
			
		||||
      kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
 | 
			
		||||
        .start = prev_end,
 | 
			
		||||
        .end = chunk.start.col,
 | 
			
		||||
        .attr = 0,
 | 
			
		||||
      }));
 | 
			
		||||
    }
 | 
			
		||||
    const int id = syn_name2id((const char_u *)chunk.group);
 | 
			
		||||
    const int attr = (id == 0 ? 0 : syn_id2attr(id));
 | 
			
		||||
    kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
 | 
			
		||||
        .start = chunk.start.col,
 | 
			
		||||
        .end = chunk.end_col,
 | 
			
		||||
        .attr = attr,
 | 
			
		||||
    }));
 | 
			
		||||
    prev_end = chunk.end_col;
 | 
			
		||||
  }
 | 
			
		||||
  if (prev_end < (size_t)colored_ccline->cmdlen) {
 | 
			
		||||
    kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
 | 
			
		||||
      .start = prev_end,
 | 
			
		||||
      .end = (size_t)colored_ccline->cmdlen,
 | 
			
		||||
      .attr = 0,
 | 
			
		||||
    }));
 | 
			
		||||
  }
 | 
			
		||||
  kvi_destroy(colors);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Color command-line
 | 
			
		||||
///
 | 
			
		||||
/// Should use built-in command parser or user-specified one. Currently only the
 | 
			
		||||
@@ -2510,13 +2569,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline)
 | 
			
		||||
    tl_ret = try_leave(&tstate, &err);
 | 
			
		||||
    can_free_cb = true;
 | 
			
		||||
  } else if (colored_ccline->cmdfirstc == '=') {
 | 
			
		||||
    try_enter(&tstate);
 | 
			
		||||
    err_errmsg = N_(
 | 
			
		||||
        "E5409: Unable to get g:Nvim_color_expr callback: %s");
 | 
			
		||||
    dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_expr"),
 | 
			
		||||
                                   &color_cb);
 | 
			
		||||
    tl_ret = try_leave(&tstate, &err);
 | 
			
		||||
    can_free_cb = true;
 | 
			
		||||
    color_expr_cmdline(colored_ccline, ccline_colors);
 | 
			
		||||
  }
 | 
			
		||||
  if (!tl_ret || !dgc_ret) {
 | 
			
		||||
    goto color_cmdline_error;
 | 
			
		||||
 
 | 
			
		||||
@@ -164,9 +164,40 @@ local pattern = concat(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if fname == '--help' then
 | 
			
		||||
  print'Usage:'
 | 
			
		||||
  print()
 | 
			
		||||
  print'  gendeclarations.lua definitions.c static.h non-static.h preprocessor.i'
 | 
			
		||||
  print([[
 | 
			
		||||
Usage:
 | 
			
		||||
 | 
			
		||||
    gendeclarations.lua definitions.c static.h non-static.h definitions.i
 | 
			
		||||
 | 
			
		||||
Generates declarations for a C file definitions.c, putting declarations for
 | 
			
		||||
static functions into static.h and declarations for non-static functions into
 | 
			
		||||
non-static.h. File `definitions.i' should contain an already preprocessed
 | 
			
		||||
version of definitions.c and it is the only one which is actually parsed,
 | 
			
		||||
definitions.c is needed only to determine functions from which file out of all
 | 
			
		||||
functions found in definitions.i are needed.
 | 
			
		||||
 | 
			
		||||
Additionally uses the following environment variables:
 | 
			
		||||
 | 
			
		||||
    NVIM_GEN_DECLARATIONS_LINE_NUMBERS:
 | 
			
		||||
        If set to 1 then all generated declarations receive a comment with file
 | 
			
		||||
        name and line number after the declaration. This may be useful for
 | 
			
		||||
        debugging gen_declarations script, but not much beyond that with
 | 
			
		||||
        configured development environment (i.e. with ctags/cscope/finding
 | 
			
		||||
        definitions with clang/etc).
 | 
			
		||||
 | 
			
		||||
        WARNING: setting this to 1 will cause extensive rebuilds: declarations
 | 
			
		||||
                 generator script will not regenerate non-static.h file if its
 | 
			
		||||
                 contents did not change, but including line numbers will make
 | 
			
		||||
                 contents actually change.
 | 
			
		||||
 | 
			
		||||
                 With contents changed timestamp of the file is regenerated even
 | 
			
		||||
                 when no real changes were made (e.g. a few lines were added to
 | 
			
		||||
                 a function which is not at the bottom of the file).
 | 
			
		||||
 | 
			
		||||
                 With changed timestamp build system will assume that header
 | 
			
		||||
                 changed, triggering rebuilds of all C files which depend on the
 | 
			
		||||
                 "changed" header.
 | 
			
		||||
]])
 | 
			
		||||
  os.exit()
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@@ -249,8 +280,10 @@ while init ~= nil do
 | 
			
		||||
      declaration = declaration:gsub(' $', '')
 | 
			
		||||
      declaration = declaration:gsub('^ ', '')
 | 
			
		||||
      declaration = declaration .. ';'
 | 
			
		||||
      if os.getenv('NVIM_GEN_DECLARATIONS_LINE_NUMBERS') == '1' then
 | 
			
		||||
        declaration = declaration .. ('  // %s/%s:%u'):format(
 | 
			
		||||
            curdir, curfile, declline)
 | 
			
		||||
      end
 | 
			
		||||
      declaration = declaration .. '\n'
 | 
			
		||||
      if declaration:sub(1, 6) == 'static' then
 | 
			
		||||
        static = static .. declaration
 | 
			
		||||
 
 | 
			
		||||
@@ -729,29 +729,6 @@ EXTERN int vr_lines_changed INIT(= 0);      /* #Lines changed by "gR" so far */
 | 
			
		||||
/// Encoding used when 'fencs' is set to "default"
 | 
			
		||||
EXTERN char_u *fenc_default INIT(= NULL);
 | 
			
		||||
 | 
			
		||||
// To speed up BYTELEN(); keep a lookup table to quickly get the length in
 | 
			
		||||
// bytes of a UTF-8 character from the first byte of a UTF-8 string.  Bytes
 | 
			
		||||
// which are illegal when used as the first byte have a 1.  The NUL byte has
 | 
			
		||||
// length 1.
 | 
			
		||||
EXTERN char utf8len_tab[256] INIT(= {
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
 | 
			
		||||
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
 | 
			
		||||
  3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
 | 
			
		||||
  4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
# if defined(USE_ICONV) && defined(DYNAMIC_ICONV)
 | 
			
		||||
/* Pointers to functions and variables to be loaded at runtime */
 | 
			
		||||
EXTERN size_t (*iconv)(iconv_t cd, const char **inbuf, size_t *inbytesleft,
 | 
			
		||||
 
 | 
			
		||||
@@ -24,23 +24,22 @@
 | 
			
		||||
 * Some useful tables.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
static struct modmasktable {
 | 
			
		||||
  short mod_mask;               /* Bit-mask for particular key modifier */
 | 
			
		||||
  short mod_flag;               /* Bit(s) for particular key modifier */
 | 
			
		||||
  char_u name;                  /* Single letter name of modifier */
 | 
			
		||||
} mod_mask_table[] =
 | 
			
		||||
{
 | 
			
		||||
  {MOD_MASK_ALT,              MOD_MASK_ALT,           (char_u)'M'},
 | 
			
		||||
  {MOD_MASK_META,             MOD_MASK_META,          (char_u)'T'},
 | 
			
		||||
  {MOD_MASK_CTRL,             MOD_MASK_CTRL,          (char_u)'C'},
 | 
			
		||||
  {MOD_MASK_SHIFT,            MOD_MASK_SHIFT,         (char_u)'S'},
 | 
			
		||||
  {MOD_MASK_MULTI_CLICK,      MOD_MASK_2CLICK,        (char_u)'2'},
 | 
			
		||||
  {MOD_MASK_MULTI_CLICK,      MOD_MASK_3CLICK,        (char_u)'3'},
 | 
			
		||||
  {MOD_MASK_MULTI_CLICK,      MOD_MASK_4CLICK,        (char_u)'4'},
 | 
			
		||||
  {MOD_MASK_CMD,              MOD_MASK_CMD,           (char_u)'D'},
 | 
			
		||||
static const struct modmasktable {
 | 
			
		||||
  uint16_t mod_mask;  ///< Bit-mask for particular key modifier.
 | 
			
		||||
  uint16_t mod_flag;  ///< Bit(s) for particular key modifier.
 | 
			
		||||
  char_u name;  ///< Single letter name of modifier.
 | 
			
		||||
} mod_mask_table[] = {
 | 
			
		||||
  { MOD_MASK_ALT,              MOD_MASK_ALT,           (char_u)'M' },
 | 
			
		||||
  { MOD_MASK_META,             MOD_MASK_META,          (char_u)'T' },
 | 
			
		||||
  { MOD_MASK_CTRL,             MOD_MASK_CTRL,          (char_u)'C' },
 | 
			
		||||
  { MOD_MASK_SHIFT,            MOD_MASK_SHIFT,         (char_u)'S' },
 | 
			
		||||
  { MOD_MASK_MULTI_CLICK,      MOD_MASK_2CLICK,        (char_u)'2' },
 | 
			
		||||
  { MOD_MASK_MULTI_CLICK,      MOD_MASK_3CLICK,        (char_u)'3' },
 | 
			
		||||
  { MOD_MASK_MULTI_CLICK,      MOD_MASK_4CLICK,        (char_u)'4' },
 | 
			
		||||
  { MOD_MASK_CMD,              MOD_MASK_CMD,           (char_u)'D' },
 | 
			
		||||
  // 'A' must be the last one
 | 
			
		||||
  {MOD_MASK_ALT,              MOD_MASK_ALT,           (char_u)'A'},
 | 
			
		||||
  {0, 0, NUL}
 | 
			
		||||
  { MOD_MASK_ALT,              MOD_MASK_ALT,           (char_u)'A' },
 | 
			
		||||
  { 0, 0, NUL }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@@ -139,11 +138,10 @@ static char_u modifier_keys_table[] =
 | 
			
		||||
  NUL
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct key_name_entry {
 | 
			
		||||
static const struct key_name_entry {
 | 
			
		||||
  int key;              // Special key code or ascii value
 | 
			
		||||
  char *name;           // Name of key
 | 
			
		||||
} key_names_table[] =
 | 
			
		||||
{
 | 
			
		||||
  const char *name;           // Name of key
 | 
			
		||||
} key_names_table[] = {
 | 
			
		||||
  { ' ',               "Space" },
 | 
			
		||||
  { TAB,               "Tab" },
 | 
			
		||||
  { K_TAB,             "Tab" },
 | 
			
		||||
@@ -318,40 +316,40 @@ 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])) {
 | 
			
		||||
@@ -360,13 +358,13 @@ int simplify_key(int key, int *modifiers)
 | 
			
		||||
                           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;
 | 
			
		||||
@@ -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,34 +696,40 @@ int find_special_key_in_table(int c)
 | 
			
		||||
{
 | 
			
		||||
  int i;
 | 
			
		||||
 | 
			
		||||
  for (i = 0; key_names_table[i].name != NULL; i++)
 | 
			
		||||
    if (c == key_names_table[i].key)
 | 
			
		||||
  for (i = 0; key_names_table[i].name != NULL; i++) {
 | 
			
		||||
    if (c == key_names_table[i].key) {
 | 
			
		||||
      break;
 | 
			
		||||
  if (key_names_table[i].name == NULL)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (key_names_table[i].name == NULL) {
 | 
			
		||||
    i = -1;
 | 
			
		||||
  }
 | 
			
		||||
  return i;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Find the special key with the given name (the given string does not have to
 | 
			
		||||
 * end with NUL, the name is assumed to end before the first non-idchar).
 | 
			
		||||
 * If the name starts with "t_" the next two characters are interpreted as a
 | 
			
		||||
 * termcap name.
 | 
			
		||||
 * Return the key code, or 0 if not found.
 | 
			
		||||
 */
 | 
			
		||||
/// Find the special key with the given name
 | 
			
		||||
///
 | 
			
		||||
/// @param[in]  name  Name of the special. Does not have to end with NUL, it is
 | 
			
		||||
///                   assumed to end before the first non-idchar. If name starts
 | 
			
		||||
///                   with "t_" the next two characters are interpreted as
 | 
			
		||||
///                   a termcap name.
 | 
			
		||||
///
 | 
			
		||||
/// @return Key code or 0 if not found.
 | 
			
		||||
int get_special_key_code(const char_u *name)
 | 
			
		||||
  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
 | 
			
		||||
{
 | 
			
		||||
  char *table_name;
 | 
			
		||||
  int i, j;
 | 
			
		||||
 | 
			
		||||
  for (i = 0; key_names_table[i].name != NULL; i++) {
 | 
			
		||||
    table_name = key_names_table[i].name;
 | 
			
		||||
    for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++)
 | 
			
		||||
      if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j]))
 | 
			
		||||
  for (int i = 0; key_names_table[i].name != NULL; i++) {
 | 
			
		||||
    const char *const table_name = key_names_table[i].name;
 | 
			
		||||
    int j;
 | 
			
		||||
    for (j = 0; ascii_isident(name[j]) && table_name[j] != NUL; j++) {
 | 
			
		||||
      if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) {
 | 
			
		||||
        break;
 | 
			
		||||
    if (!vim_isIDc(name[j]) && table_name[j] == NUL)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (!ascii_isident(name[j]) && table_name[j] == NUL) {
 | 
			
		||||
      return key_names_table[i].key;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,16 @@
 | 
			
		||||
#define kv_pop(v) ((v).items[--(v).size])
 | 
			
		||||
#define kv_size(v) ((v).size)
 | 
			
		||||
#define kv_max(v) ((v).capacity)
 | 
			
		||||
#define kv_last(v) kv_A(v, kv_size(v) - 1)
 | 
			
		||||
#define kv_Z(v, i) kv_A(v, kv_size(v) - (i) - 1)
 | 
			
		||||
#define kv_last(v) kv_Z(v, 0)
 | 
			
		||||
 | 
			
		||||
/// Drop last n items from kvec without resizing
 | 
			
		||||
///
 | 
			
		||||
/// Previously spelled as `(void)kv_pop(v)`, repeated n times.
 | 
			
		||||
///
 | 
			
		||||
/// @param[out]  v  Kvec to drop items from.
 | 
			
		||||
/// @param[in]  n  Number of elements to drop.
 | 
			
		||||
#define kv_drop(v, n) ((v).size -= (n))
 | 
			
		||||
 | 
			
		||||
#define kv_resize(v, s) \
 | 
			
		||||
    ((v).capacity = (s), \
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@
 | 
			
		||||
#ifndef NVIM_LIB_RINGBUF_H
 | 
			
		||||
#define NVIM_LIB_RINGBUF_H
 | 
			
		||||
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
@@ -73,6 +74,32 @@ typedef struct { \
 | 
			
		||||
  RBType *buf_end; \
 | 
			
		||||
} TypeName##RingBuffer;
 | 
			
		||||
 | 
			
		||||
/// Dummy item free macros, for use in RINGBUF_INIT
 | 
			
		||||
///
 | 
			
		||||
/// This macros actually does nothing.
 | 
			
		||||
///
 | 
			
		||||
/// @param[in]  item  Item to be freed.
 | 
			
		||||
#define RINGBUF_DUMMY_FREE(item)
 | 
			
		||||
 | 
			
		||||
/// Static ring buffer
 | 
			
		||||
///
 | 
			
		||||
/// @warning Ring buffers created with this macros must neither be freed nor
 | 
			
		||||
///          deallocated.
 | 
			
		||||
///
 | 
			
		||||
/// @param  scope  Ring buffer scope.
 | 
			
		||||
/// @param  TypeName  Ring buffer type name.
 | 
			
		||||
/// @param  RBType  Type of the single ring buffer element.
 | 
			
		||||
/// @param  varname  Variable name.
 | 
			
		||||
/// @param  rbsize  Ring buffer size.
 | 
			
		||||
#define RINGBUF_STATIC(scope, TypeName, RBType, varname, rbsize) \
 | 
			
		||||
static RBType _##varname##_buf[rbsize]; \
 | 
			
		||||
scope TypeName##RingBuffer varname = { \
 | 
			
		||||
  .buf = _##varname##_buf, \
 | 
			
		||||
  .next = _##varname##_buf, \
 | 
			
		||||
  .first = NULL, \
 | 
			
		||||
  .buf_end = _##varname##_buf + rbsize - 1, \
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Initialize a new ring buffer
 | 
			
		||||
///
 | 
			
		||||
/// @param TypeName    Ring buffer type name. Actual type name will be
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										242
									
								
								src/nvim/mbyte.c
									
									
									
									
									
								
							
							
						
						
									
										242
									
								
								src/nvim/mbyte.c
									
									
									
									
									
								
							@@ -72,19 +72,49 @@ struct interval {
 | 
			
		||||
# include "unicode_tables.generated.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Like utf8len_tab above, but using a zero for illegal lead bytes.
 | 
			
		||||
 */
 | 
			
		||||
const uint8_t utf8len_tab_zero[256] =
 | 
			
		||||
{
 | 
			
		||||
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
 | 
			
		||||
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
 | 
			
		||||
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
 | 
			
		||||
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
 | 
			
		||||
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
 | 
			
		||||
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
 | 
			
		||||
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
 | 
			
		||||
  3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0,
 | 
			
		||||
// To speed up BYTELEN(); keep a lookup table to quickly get the length in
 | 
			
		||||
// bytes of a UTF-8 character from the first byte of a UTF-8 string.  Bytes
 | 
			
		||||
// which are illegal when used as the first byte have a 1.  The NUL byte has
 | 
			
		||||
// length 1.
 | 
			
		||||
const uint8_t utf8len_tab[] = {
 | 
			
		||||
  // ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 0?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 1?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 2?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 3?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 4?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 5?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 6?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 7?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 8?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 9?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // A?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // B?
 | 
			
		||||
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,  // C?
 | 
			
		||||
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,  // D?
 | 
			
		||||
  3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,  // E?
 | 
			
		||||
  4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1,  // F?
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Like utf8len_tab above, but using a zero for illegal lead bytes.
 | 
			
		||||
const uint8_t utf8len_tab_zero[] = {
 | 
			
		||||
  // ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 0?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 1?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 2?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 3?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 4?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 5?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 6?
 | 
			
		||||
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 7?
 | 
			
		||||
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 8?
 | 
			
		||||
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 9?
 | 
			
		||||
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // A?
 | 
			
		||||
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // B?
 | 
			
		||||
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,  // C?
 | 
			
		||||
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,  // D?
 | 
			
		||||
  3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,  // E?
 | 
			
		||||
  4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 0, 0,  // F?
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@@ -528,45 +558,52 @@ int utf_off2cells(unsigned off, unsigned max_off)
 | 
			
		||||
  return (off + 1 < max_off && ScreenLines[off + 1] == 0) ? 2 : 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Convert a UTF-8 byte sequence to a wide character.
 | 
			
		||||
 * If the sequence is illegal or truncated by a NUL the first byte is
 | 
			
		||||
 * returned.
 | 
			
		||||
 * Does not include composing characters, of course.
 | 
			
		||||
 */
 | 
			
		||||
int utf_ptr2char(const char_u *p)
 | 
			
		||||
/// Convert a UTF-8 byte sequence to a wide character
 | 
			
		||||
///
 | 
			
		||||
/// If the sequence is illegal or truncated by a NUL then the first byte is
 | 
			
		||||
/// returned. Does not include composing characters for obvious reasons.
 | 
			
		||||
///
 | 
			
		||||
/// @param[in]  p  String to convert.
 | 
			
		||||
///
 | 
			
		||||
/// @return Unicode codepoint or byte value.
 | 
			
		||||
int utf_ptr2char(const char_u *const p)
 | 
			
		||||
  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
 | 
			
		||||
{
 | 
			
		||||
  uint8_t len;
 | 
			
		||||
 | 
			
		||||
  if (p[0] < 0x80)      /* be quick for ASCII */
 | 
			
		||||
  if (p[0] < 0x80) {  // Be quick for ASCII.
 | 
			
		||||
    return p[0];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  len = utf8len_tab_zero[p[0]];
 | 
			
		||||
  const uint8_t len = utf8len_tab_zero[p[0]];
 | 
			
		||||
  if (len > 1 && (p[1] & 0xc0) == 0x80) {
 | 
			
		||||
    if (len == 2)
 | 
			
		||||
    if (len == 2) {
 | 
			
		||||
      return ((p[0] & 0x1f) << 6) + (p[1] & 0x3f);
 | 
			
		||||
    }
 | 
			
		||||
    if ((p[2] & 0xc0) == 0x80) {
 | 
			
		||||
      if (len == 3)
 | 
			
		||||
        return ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6)
 | 
			
		||||
          + (p[2] & 0x3f);
 | 
			
		||||
      if (len == 3) {
 | 
			
		||||
        return (((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6)
 | 
			
		||||
                + (p[2] & 0x3f));
 | 
			
		||||
      }
 | 
			
		||||
      if ((p[3] & 0xc0) == 0x80) {
 | 
			
		||||
        if (len == 4)
 | 
			
		||||
          return ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12)
 | 
			
		||||
            + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f);
 | 
			
		||||
        if (len == 4) {
 | 
			
		||||
          return (((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12)
 | 
			
		||||
                  + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f));
 | 
			
		||||
        }
 | 
			
		||||
        if ((p[4] & 0xc0) == 0x80) {
 | 
			
		||||
          if (len == 5)
 | 
			
		||||
            return ((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18)
 | 
			
		||||
          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[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);
 | 
			
		||||
                    + ((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;
 | 
			
		||||
  } else {
 | 
			
		||||
    return 6;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Convert Unicode character to UTF-8 string
 | 
			
		||||
@@ -937,39 +974,34 @@ 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;
 | 
			
		||||
  }
 | 
			
		||||
  /* 31 bits */
 | 
			
		||||
  } else {  // 31 bits
 | 
			
		||||
    buf[0] = 0xfc + ((unsigned)c >> 30);
 | 
			
		||||
    buf[1] = 0x80 + (((unsigned)c >> 24) & 0x3f);
 | 
			
		||||
    buf[2] = 0x80 + (((unsigned)c >> 18) & 0x3f);
 | 
			
		||||
@@ -977,6 +1009,7 @@ int utf_char2bytes(int c, char_u *const buf)
 | 
			
		||||
    buf[4] = 0x80 + (((unsigned)c >> 6) & 0x3f);
 | 
			
		||||
    buf[5] = 0x80 + (c & 0x3f);
 | 
			
		||||
    return 6;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@@ -1513,14 +1546,15 @@ int utf_head_off(const char_u *base, const char_u *p)
 | 
			
		||||
  return (int)(p - q);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Copy a character from "*fp" to "*tp" and advance the pointers.
 | 
			
		||||
 */
 | 
			
		||||
void mb_copy_char(const char_u **fp, char_u **tp)
 | 
			
		||||
/// Copy a character, advancing the pointers
 | 
			
		||||
///
 | 
			
		||||
/// @param[in,out]  fp  Source of the character to copy.
 | 
			
		||||
/// @param[in,out]  tp  Destination to copy to.
 | 
			
		||||
void mb_copy_char(const char_u **const fp, char_u **const tp)
 | 
			
		||||
{
 | 
			
		||||
  int l = (*mb_ptr2len)(*fp);
 | 
			
		||||
  const size_t l = (size_t)utfc_ptr2len(*fp);
 | 
			
		||||
 | 
			
		||||
  memmove(*tp, *fp, (size_t)l);
 | 
			
		||||
  memmove(*tp, *fp, l);
 | 
			
		||||
  *tp += l;
 | 
			
		||||
  *fp += l;
 | 
			
		||||
}
 | 
			
		||||
@@ -2262,9 +2296,7 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8,
 | 
			
		||||
  if (vcp->vc_type == CONV_ICONV && vcp->vc_fd != (iconv_t)-1)
 | 
			
		||||
    iconv_close(vcp->vc_fd);
 | 
			
		||||
# endif
 | 
			
		||||
  vcp->vc_type = CONV_NONE;
 | 
			
		||||
  vcp->vc_factor = 1;
 | 
			
		||||
  vcp->vc_fail = false;
 | 
			
		||||
  *vcp = (vimconv_T)MBYTE_NONE_CONV;
 | 
			
		||||
 | 
			
		||||
  /* No conversion when one of the names is empty or they are equal. */
 | 
			
		||||
  if (from == NULL || *from == NUL || to == NULL || *to == NUL
 | 
			
		||||
 
 | 
			
		||||
@@ -60,6 +60,12 @@ typedef enum {
 | 
			
		||||
  CONV_ICONV     = 5,
 | 
			
		||||
} ConvFlags;
 | 
			
		||||
 | 
			
		||||
#define MBYTE_NONE_CONV { \
 | 
			
		||||
  .vc_type = CONV_NONE, \
 | 
			
		||||
  .vc_factor = 1, \
 | 
			
		||||
  .vc_fail = false, \
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Structure used for string conversions
 | 
			
		||||
typedef struct {
 | 
			
		||||
  int vc_type;  ///< Zero or more ConvFlags.
 | 
			
		||||
@@ -73,6 +79,8 @@ typedef struct {
 | 
			
		||||
 | 
			
		||||
extern const uint8_t utf8len_tab_zero[256];
 | 
			
		||||
 | 
			
		||||
extern const uint8_t utf8len_tab[256];
 | 
			
		||||
 | 
			
		||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
 | 
			
		||||
# include "mbyte.h.generated.h"
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -5930,9 +5930,9 @@ static void syntime_report(void)
 | 
			
		||||
//
 | 
			
		||||
// When making changes here, also change runtime/colors/default.vim!
 | 
			
		||||
 | 
			
		||||
static char *highlight_init_both[] =
 | 
			
		||||
{
 | 
			
		||||
  "Conceal      ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey",
 | 
			
		||||
static const char *highlight_init_both[] = {
 | 
			
		||||
  "Conceal "
 | 
			
		||||
      "ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey",
 | 
			
		||||
  "Cursor       guibg=fg guifg=bg",
 | 
			
		||||
  "lCursor      guibg=fg guifg=bg",
 | 
			
		||||
  "DiffText     cterm=bold ctermbg=Red gui=bold guibg=Red",
 | 
			
		||||
@@ -5955,8 +5955,7 @@ static char *highlight_init_both[] =
 | 
			
		||||
  NULL
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static char *highlight_init_light[] =
 | 
			
		||||
{
 | 
			
		||||
static const char *highlight_init_light[] = {
 | 
			
		||||
  "ColorColumn  ctermbg=LightRed guibg=LightRed",
 | 
			
		||||
  "CursorColumn ctermbg=LightGrey guibg=Grey90",
 | 
			
		||||
  "CursorLine   cterm=underline guibg=Grey90",
 | 
			
		||||
@@ -5989,8 +5988,7 @@ static char *highlight_init_light[] =
 | 
			
		||||
  NULL
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static char *highlight_init_dark[] =
 | 
			
		||||
{
 | 
			
		||||
static const char *highlight_init_dark[] = {
 | 
			
		||||
  "ColorColumn  ctermbg=DarkRed guibg=DarkRed",
 | 
			
		||||
  "CursorColumn ctermbg=DarkGrey guibg=Grey40",
 | 
			
		||||
  "CursorLine   cterm=underline guibg=Grey40",
 | 
			
		||||
@@ -6023,18 +6021,223 @@ static char *highlight_init_dark[] =
 | 
			
		||||
  NULL
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const char *const highlight_init_cmdline[] = {
 | 
			
		||||
  // XXX When modifying a list modify it in both valid and invalid halfs.
 | 
			
		||||
  // TODO(ZyX-I): merge valid and invalid groups via a macros.
 | 
			
		||||
 | 
			
		||||
  // NvimInternalError should appear only when highlighter has a bug.
 | 
			
		||||
  "NvimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red",
 | 
			
		||||
 | 
			
		||||
  // Highlight groups (links) used by parser:
 | 
			
		||||
 | 
			
		||||
  "default link NvimAssignment Operator",
 | 
			
		||||
  "default link NvimPlainAssignment NvimAssignment",
 | 
			
		||||
  "default link NvimAugmentedAssignment NvimAssignment",
 | 
			
		||||
  "default link NvimAssignmentWithAddition NvimAugmentedAssignment",
 | 
			
		||||
  "default link NvimAssignmentWithSubtraction NvimAugmentedAssignment",
 | 
			
		||||
  "default link NvimAssignmentWithConcatenation NvimAugmentedAssignment",
 | 
			
		||||
 | 
			
		||||
  "default link NvimOperator Operator",
 | 
			
		||||
 | 
			
		||||
  "default link NvimUnaryOperator NvimOperator",
 | 
			
		||||
  "default link NvimUnaryPlus NvimUnaryOperator",
 | 
			
		||||
  "default link NvimUnaryMinus NvimUnaryOperator",
 | 
			
		||||
  "default link NvimNot NvimUnaryOperator",
 | 
			
		||||
 | 
			
		||||
  "default link NvimBinaryOperator NvimOperator",
 | 
			
		||||
  "default link NvimComparison NvimBinaryOperator",
 | 
			
		||||
  "default link NvimComparisonModifier NvimComparison",
 | 
			
		||||
  "default link NvimBinaryPlus NvimBinaryOperator",
 | 
			
		||||
  "default link NvimBinaryMinus NvimBinaryOperator",
 | 
			
		||||
  "default link NvimConcat NvimBinaryOperator",
 | 
			
		||||
  "default link NvimConcatOrSubscript NvimConcat",
 | 
			
		||||
  "default link NvimOr NvimBinaryOperator",
 | 
			
		||||
  "default link NvimAnd NvimBinaryOperator",
 | 
			
		||||
  "default link NvimMultiplication NvimBinaryOperator",
 | 
			
		||||
  "default link NvimDivision NvimBinaryOperator",
 | 
			
		||||
  "default link NvimMod NvimBinaryOperator",
 | 
			
		||||
 | 
			
		||||
  "default link NvimTernary NvimOperator",
 | 
			
		||||
  "default link NvimTernaryColon NvimTernary",
 | 
			
		||||
 | 
			
		||||
  "default link NvimParenthesis Delimiter",
 | 
			
		||||
  "default link NvimLambda NvimParenthesis",
 | 
			
		||||
  "default link NvimNestingParenthesis NvimParenthesis",
 | 
			
		||||
  "default link NvimCallingParenthesis NvimParenthesis",
 | 
			
		||||
 | 
			
		||||
  "default link NvimSubscript NvimParenthesis",
 | 
			
		||||
  "default link NvimSubscriptBracket NvimSubscript",
 | 
			
		||||
  "default link NvimSubscriptColon NvimSubscript",
 | 
			
		||||
  "default link NvimCurly NvimSubscript",
 | 
			
		||||
 | 
			
		||||
  "default link NvimContainer NvimParenthesis",
 | 
			
		||||
  "default link NvimDict NvimContainer",
 | 
			
		||||
  "default link NvimList NvimContainer",
 | 
			
		||||
 | 
			
		||||
  "default link NvimIdentifier Identifier",
 | 
			
		||||
  "default link NvimIdentifierScope NvimIdentifier",
 | 
			
		||||
  "default link NvimIdentifierScopeDelimiter NvimIdentifier",
 | 
			
		||||
  "default link NvimIdentifierName NvimIdentifier",
 | 
			
		||||
  "default link NvimIdentifierKey NvimIdentifier",
 | 
			
		||||
 | 
			
		||||
  "default link NvimColon Delimiter",
 | 
			
		||||
  "default link NvimComma Delimiter",
 | 
			
		||||
  "default link NvimArrow Delimiter",
 | 
			
		||||
 | 
			
		||||
  "default link NvimRegister SpecialChar",
 | 
			
		||||
  "default link NvimNumber Number",
 | 
			
		||||
  "default link NvimFloat NvimNumber",
 | 
			
		||||
  "default link NvimNumberPrefix Type",
 | 
			
		||||
 | 
			
		||||
  "default link NvimOptionSigil Type",
 | 
			
		||||
  "default link NvimOptionName NvimIdentifier",
 | 
			
		||||
  "default link NvimOptionScope NvimIdentifierScope",
 | 
			
		||||
  "default link NvimOptionScopeDelimiter NvimIdentifierScopeDelimiter",
 | 
			
		||||
 | 
			
		||||
  "default link NvimEnvironmentSigil NvimOptionSigil",
 | 
			
		||||
  "default link NvimEnvironmentName NvimIdentifier",
 | 
			
		||||
 | 
			
		||||
  "default link NvimString String",
 | 
			
		||||
  "default link NvimStringBody NvimString",
 | 
			
		||||
  "default link NvimStringQuote NvimString",
 | 
			
		||||
  "default link NvimStringSpecial SpecialChar",
 | 
			
		||||
 | 
			
		||||
  "default link NvimSingleQuote NvimStringQuote",
 | 
			
		||||
  "default link NvimSingleQuotedBody NvimStringBody",
 | 
			
		||||
  "default link NvimSingleQuotedQuote NvimStringSpecial",
 | 
			
		||||
 | 
			
		||||
  "default link NvimDoubleQuote NvimStringQuote",
 | 
			
		||||
  "default link NvimDoubleQuotedBody NvimStringBody",
 | 
			
		||||
  "default link NvimDoubleQuotedEscape NvimStringSpecial",
 | 
			
		||||
 | 
			
		||||
  "default link NvimFigureBrace NvimInternalError",
 | 
			
		||||
  "default link NvimSingleQuotedUnknownEscape NvimInternalError",
 | 
			
		||||
 | 
			
		||||
  "default link NvimSpacing Normal",
 | 
			
		||||
 | 
			
		||||
  // NvimInvalid groups:
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidSingleQuotedUnknownEscape NvimInternalError",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalid Error",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidAssignment NvimInvalid",
 | 
			
		||||
  "default link NvimInvalidPlainAssignment NvimInvalidAssignment",
 | 
			
		||||
  "default link NvimInvalidAugmentedAssignment NvimInvalidAssignment",
 | 
			
		||||
  "default link NvimInvalidAssignmentWithAddition "
 | 
			
		||||
      "NvimInvalidAugmentedAssignment",
 | 
			
		||||
  "default link NvimInvalidAssignmentWithSubtraction "
 | 
			
		||||
      "NvimInvalidAugmentedAssignment",
 | 
			
		||||
  "default link NvimInvalidAssignmentWithConcatenation "
 | 
			
		||||
      "NvimInvalidAugmentedAssignment",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidOperator NvimInvalid",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidUnaryOperator NvimInvalidOperator",
 | 
			
		||||
  "default link NvimInvalidUnaryPlus NvimInvalidUnaryOperator",
 | 
			
		||||
  "default link NvimInvalidUnaryMinus NvimInvalidUnaryOperator",
 | 
			
		||||
  "default link NvimInvalidNot NvimInvalidUnaryOperator",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidBinaryOperator NvimInvalidOperator",
 | 
			
		||||
  "default link NvimInvalidComparison NvimInvalidBinaryOperator",
 | 
			
		||||
  "default link NvimInvalidComparisonModifier NvimInvalidComparison",
 | 
			
		||||
  "default link NvimInvalidBinaryPlus NvimInvalidBinaryOperator",
 | 
			
		||||
  "default link NvimInvalidBinaryMinus NvimInvalidBinaryOperator",
 | 
			
		||||
  "default link NvimInvalidConcat NvimInvalidBinaryOperator",
 | 
			
		||||
  "default link NvimInvalidConcatOrSubscript NvimInvalidConcat",
 | 
			
		||||
  "default link NvimInvalidOr NvimInvalidBinaryOperator",
 | 
			
		||||
  "default link NvimInvalidAnd NvimInvalidBinaryOperator",
 | 
			
		||||
  "default link NvimInvalidMultiplication NvimInvalidBinaryOperator",
 | 
			
		||||
  "default link NvimInvalidDivision NvimInvalidBinaryOperator",
 | 
			
		||||
  "default link NvimInvalidMod NvimInvalidBinaryOperator",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidTernary NvimInvalidOperator",
 | 
			
		||||
  "default link NvimInvalidTernaryColon NvimInvalidTernary",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidDelimiter NvimInvalid",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidParenthesis NvimInvalidDelimiter",
 | 
			
		||||
  "default link NvimInvalidLambda NvimInvalidParenthesis",
 | 
			
		||||
  "default link NvimInvalidNestingParenthesis NvimInvalidParenthesis",
 | 
			
		||||
  "default link NvimInvalidCallingParenthesis NvimInvalidParenthesis",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidSubscript NvimInvalidParenthesis",
 | 
			
		||||
  "default link NvimInvalidSubscriptBracket NvimInvalidSubscript",
 | 
			
		||||
  "default link NvimInvalidSubscriptColon NvimInvalidSubscript",
 | 
			
		||||
  "default link NvimInvalidCurly NvimInvalidSubscript",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidContainer NvimInvalidParenthesis",
 | 
			
		||||
  "default link NvimInvalidDict NvimInvalidContainer",
 | 
			
		||||
  "default link NvimInvalidList NvimInvalidContainer",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidValue NvimInvalid",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidIdentifier NvimInvalidValue",
 | 
			
		||||
  "default link NvimInvalidIdentifierScope NvimInvalidIdentifier",
 | 
			
		||||
  "default link NvimInvalidIdentifierScopeDelimiter NvimInvalidIdentifier",
 | 
			
		||||
  "default link NvimInvalidIdentifierName NvimInvalidIdentifier",
 | 
			
		||||
  "default link NvimInvalidIdentifierKey NvimInvalidIdentifier",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidColon NvimInvalidDelimiter",
 | 
			
		||||
  "default link NvimInvalidComma NvimInvalidDelimiter",
 | 
			
		||||
  "default link NvimInvalidArrow NvimInvalidDelimiter",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidRegister NvimInvalidValue",
 | 
			
		||||
  "default link NvimInvalidNumber NvimInvalidValue",
 | 
			
		||||
  "default link NvimInvalidFloat NvimInvalidNumber",
 | 
			
		||||
  "default link NvimInvalidNumberPrefix NvimInvalidNumber",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidOptionSigil NvimInvalidIdentifier",
 | 
			
		||||
  "default link NvimInvalidOptionName NvimInvalidIdentifier",
 | 
			
		||||
  "default link NvimInvalidOptionScope NvimInvalidIdentifierScope",
 | 
			
		||||
  "default link NvimInvalidOptionScopeDelimiter "
 | 
			
		||||
      "NvimInvalidIdentifierScopeDelimiter",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidEnvironmentSigil NvimInvalidOptionSigil",
 | 
			
		||||
  "default link NvimInvalidEnvironmentName NvimInvalidIdentifier",
 | 
			
		||||
 | 
			
		||||
  // Invalid string bodies and specials are still highlighted as valid ones to
 | 
			
		||||
  // minimize the red area.
 | 
			
		||||
  "default link NvimInvalidString NvimInvalidValue",
 | 
			
		||||
  "default link NvimInvalidStringBody NvimStringBody",
 | 
			
		||||
  "default link NvimInvalidStringQuote NvimInvalidString",
 | 
			
		||||
  "default link NvimInvalidStringSpecial NvimStringSpecial",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidSingleQuote NvimInvalidStringQuote",
 | 
			
		||||
  "default link NvimInvalidSingleQuotedBody NvimInvalidStringBody",
 | 
			
		||||
  "default link NvimInvalidSingleQuotedQuote NvimInvalidStringSpecial",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidDoubleQuote NvimInvalidStringQuote",
 | 
			
		||||
  "default link NvimInvalidDoubleQuotedBody NvimInvalidStringBody",
 | 
			
		||||
  "default link NvimInvalidDoubleQuotedEscape NvimInvalidStringSpecial",
 | 
			
		||||
  "default link NvimInvalidDoubleQuotedUnknownEscape NvimInvalidValue",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidFigureBrace NvimInvalidDelimiter",
 | 
			
		||||
 | 
			
		||||
  "default link NvimInvalidSpacing ErrorMsg",
 | 
			
		||||
 | 
			
		||||
  // Not actually invalid, but we highlight user that he is doing something
 | 
			
		||||
  // wrong.
 | 
			
		||||
  "default link NvimDoubleQuotedUnknownEscape NvimInvalidValue",
 | 
			
		||||
  NULL,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Create default links for Nvim* highlight groups used for cmdline coloring
 | 
			
		||||
void syn_init_cmdline_highlight(bool reset, bool init)
 | 
			
		||||
{
 | 
			
		||||
  for (size_t i = 0 ; highlight_init_cmdline[i] != NULL ; i++) {
 | 
			
		||||
    do_highlight(highlight_init_cmdline[i], reset, init);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Load colors from a file if "g:colors_name" is set, otherwise load builtin
 | 
			
		||||
/// colors
 | 
			
		||||
///
 | 
			
		||||
/// @param both include groups where 'bg' doesn't matter
 | 
			
		||||
/// @param reset clear groups first
 | 
			
		||||
void
 | 
			
		||||
init_highlight(int both, int reset)
 | 
			
		||||
void init_highlight(bool both, bool reset)
 | 
			
		||||
{
 | 
			
		||||
  int i;
 | 
			
		||||
  char        **pp;
 | 
			
		||||
  static int had_both = FALSE;
 | 
			
		||||
  static int had_both = false;
 | 
			
		||||
 | 
			
		||||
  // Try finding the color scheme file.  Used when a color file was loaded
 | 
			
		||||
  // and 'background' or 't_Co' is changed.
 | 
			
		||||
@@ -6054,10 +6257,10 @@ init_highlight(int both, int reset)
 | 
			
		||||
   * Didn't use a color file, use the compiled-in colors.
 | 
			
		||||
   */
 | 
			
		||||
  if (both) {
 | 
			
		||||
    had_both = TRUE;
 | 
			
		||||
    pp = highlight_init_both;
 | 
			
		||||
    for (i = 0; pp[i] != NULL; i++) {
 | 
			
		||||
      do_highlight((char_u *)pp[i], reset, true);
 | 
			
		||||
    had_both = true;
 | 
			
		||||
    const char *const *const pp = highlight_init_both;
 | 
			
		||||
    for (size_t i = 0; pp[i] != NULL; i++) {
 | 
			
		||||
      do_highlight(pp[i], reset, true);
 | 
			
		||||
    }
 | 
			
		||||
  } else if (!had_both) {
 | 
			
		||||
    // Don't do anything before the call with both == TRUE from main().
 | 
			
		||||
@@ -6066,10 +6269,11 @@ init_highlight(int both, int reset)
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pp = (*p_bg == 'l') ?  highlight_init_light : highlight_init_dark;
 | 
			
		||||
 | 
			
		||||
  for (i = 0; pp[i] != NULL; i++) {
 | 
			
		||||
    do_highlight((char_u *)pp[i], reset, true);
 | 
			
		||||
  const char *const *const pp = ((*p_bg == 'l')
 | 
			
		||||
                                 ? highlight_init_light
 | 
			
		||||
                                 : highlight_init_dark);
 | 
			
		||||
  for (size_t i = 0; pp[i] != NULL; i++) {
 | 
			
		||||
    do_highlight(pp[i], reset, true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* Reverse looks ugly, but grey may not work for 8 colors.  Thus let it
 | 
			
		||||
@@ -6079,15 +6283,14 @@ init_highlight(int both, int reset)
 | 
			
		||||
   * Clear the attributes, needed when changing the t_Co value. */
 | 
			
		||||
  if (t_colors > 8) {
 | 
			
		||||
    do_highlight(
 | 
			
		||||
        (char_u *)(*p_bg == 'l'
 | 
			
		||||
        (*p_bg == 'l'
 | 
			
		||||
         ? "Visual cterm=NONE ctermbg=LightGrey"
 | 
			
		||||
                   : "Visual cterm=NONE ctermbg=DarkGrey"), false,
 | 
			
		||||
        true);
 | 
			
		||||
         : "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\""),
 | 
			
		||||
    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 int color_numbers_16[28] = {0, 1, 2, 3,
 | 
			
		||||
            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[28] = {0, 4, 2, 6,
 | 
			
		||||
              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[28] = {0, 4, 2, 6,
 | 
			
		||||
              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,
 | 
			
		||||
              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};
 | 
			
		||||
              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"),
 | 
			
		||||
              emsgf(_("E421: Color name or number not recognized: %s"),
 | 
			
		||||
                    key_start);
 | 
			
		||||
              error = TRUE;
 | 
			
		||||
              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,17 +6747,15 @@ 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;
 | 
			
		||||
            }
 | 
			
		||||
            }
 | 
			
		||||
          } else {
 | 
			
		||||
            HL_TABLE()[idx].sg_cterm_bg = color + 1;
 | 
			
		||||
            if (is_normal_group) {
 | 
			
		||||
@@ -6566,15 +6778,15 @@ do_highlight(char_u *line, int forceit, int init) {
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } else if (STRCMP(key, "GUIFG") == 0)   {
 | 
			
		||||
      } else if (strcmp(key, "GUIFG") == 0)   {
 | 
			
		||||
        if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
 | 
			
		||||
          if (!init)
 | 
			
		||||
            HL_TABLE()[idx].sg_set |= SG_GUI;
 | 
			
		||||
 | 
			
		||||
          xfree(HL_TABLE()[idx].sg_rgb_fg_name);
 | 
			
		||||
          if (STRCMP(arg, "NONE")) {
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_fg_name = (uint8_t *)xstrdup((char *)arg);
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_fg = name_to_color(arg);
 | 
			
		||||
          if (strcmp(arg, "NONE")) {
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_fg_name = (char_u *)xstrdup((char *)arg);
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_fg = name_to_color((const char_u *)arg);
 | 
			
		||||
          } else {
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_fg_name = NULL;
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_fg = -1;
 | 
			
		||||
@@ -6591,8 +6803,8 @@ do_highlight(char_u *line, int forceit, int init) {
 | 
			
		||||
 | 
			
		||||
          xfree(HL_TABLE()[idx].sg_rgb_bg_name);
 | 
			
		||||
          if (STRCMP(arg, "NONE") != 0) {
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_bg_name = (uint8_t *)xstrdup((char *)arg);
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_bg = name_to_color(arg);
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_bg_name = (char_u *)xstrdup((char *)arg);
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_bg = name_to_color((const char_u *)arg);
 | 
			
		||||
          } else {
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_bg_name = NULL;
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_bg = -1;
 | 
			
		||||
@@ -6602,15 +6814,15 @@ do_highlight(char_u *line, int forceit, int init) {
 | 
			
		||||
        if (is_normal_group) {
 | 
			
		||||
          normal_bg = HL_TABLE()[idx].sg_rgb_bg;
 | 
			
		||||
        }
 | 
			
		||||
      } else if (STRCMP(key, "GUISP") == 0)   {
 | 
			
		||||
      } else if (strcmp(key, "GUISP") == 0)   {
 | 
			
		||||
        if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
 | 
			
		||||
          if (!init)
 | 
			
		||||
            HL_TABLE()[idx].sg_set |= SG_GUI;
 | 
			
		||||
 | 
			
		||||
          xfree(HL_TABLE()[idx].sg_rgb_sp_name);
 | 
			
		||||
          if (STRCMP(arg, "NONE") != 0) {
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_sp_name = (uint8_t *)xstrdup((char *)arg);
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_sp = name_to_color(arg);
 | 
			
		||||
          if (strcmp(arg, "NONE") != 0) {
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_sp_name = (char_u *)xstrdup((char *)arg);
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_sp = name_to_color((const char_u *)arg);
 | 
			
		||||
          } else {
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_sp_name = NULL;
 | 
			
		||||
            HL_TABLE()[idx].sg_rgb_sp = -1;
 | 
			
		||||
@@ -6620,31 +6832,25 @@ do_highlight(char_u *line, int forceit, int init) {
 | 
			
		||||
        if (is_normal_group) {
 | 
			
		||||
          normal_sp = HL_TABLE()[idx].sg_rgb_sp;
 | 
			
		||||
        }
 | 
			
		||||
      } else if (STRCMP(key, "START") == 0 || STRCMP(key, "STOP") == 0)   {
 | 
			
		||||
      } else if (strcmp(key, "START") == 0 || strcmp(key, "STOP") == 0)   {
 | 
			
		||||
        // Ignored for now
 | 
			
		||||
      } else {
 | 
			
		||||
        EMSG2(_("E423: Illegal argument: %s"), key_start);
 | 
			
		||||
        error = TRUE;
 | 
			
		||||
        emsgf(_("E423: Illegal argument: %s"), key_start);
 | 
			
		||||
        error = true;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      /*
 | 
			
		||||
       * When highlighting has been given for a group, don't link it.
 | 
			
		||||
       */
 | 
			
		||||
      // When highlighting has been given for a group, don't link it.
 | 
			
		||||
      if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) {
 | 
			
		||||
        HL_TABLE()[idx].sg_link = 0;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      /*
 | 
			
		||||
       * Continue with next argument.
 | 
			
		||||
       */
 | 
			
		||||
      linep = skipwhite(linep);
 | 
			
		||||
      // Continue with next argument.
 | 
			
		||||
      linep = (const char *)skipwhite((const char_u *)linep);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * If there is an error, and it's a new entry, remove it from the table.
 | 
			
		||||
   */
 | 
			
		||||
  // If there is an error, and it's a new entry, remove it from the table.
 | 
			
		||||
  if (error && idx == highlight_ga.ga_len) {
 | 
			
		||||
    syn_unadd_group();
 | 
			
		||||
  } else {
 | 
			
		||||
@@ -7202,7 +7408,7 @@ char_u *syn_id2name(int id)
 | 
			
		||||
/*
 | 
			
		||||
 * Like syn_name2id(), but take a pointer + length argument.
 | 
			
		||||
 */
 | 
			
		||||
int syn_namen2id(char_u *linep, int len)
 | 
			
		||||
int syn_namen2id(const char_u *linep, int len)
 | 
			
		||||
{
 | 
			
		||||
  char_u *name = vim_strnsave(linep, len);
 | 
			
		||||
  int id = syn_name2id(name);
 | 
			
		||||
@@ -7218,7 +7424,7 @@ int syn_namen2id(char_u *linep, int len)
 | 
			
		||||
/// @param len length of \p pp
 | 
			
		||||
///
 | 
			
		||||
/// @return 0 for failure else the id of the group
 | 
			
		||||
int syn_check_group(char_u *pp, int len)
 | 
			
		||||
int syn_check_group(const char_u *pp, int len)
 | 
			
		||||
{
 | 
			
		||||
  char_u  *name = vim_strnsave(pp, len);
 | 
			
		||||
  int id = syn_name2id(name);
 | 
			
		||||
@@ -8221,7 +8427,7 @@ color_name_table_T color_name_table[] = {
 | 
			
		||||
///
 | 
			
		||||
/// @param[in] name string value to convert to RGB
 | 
			
		||||
/// return the hex value or -1 if could not find a correct value
 | 
			
		||||
RgbValue name_to_color(const uint8_t *name)
 | 
			
		||||
RgbValue name_to_color(const char_u *name)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
  if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2])
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,9 @@ typedef struct {
 | 
			
		||||
} color_name_table_T;
 | 
			
		||||
extern color_name_table_T color_name_table[];
 | 
			
		||||
 | 
			
		||||
/// Array of highlight definitions, used for unit testing
 | 
			
		||||
extern const char *const highlight_init_cmdline[];
 | 
			
		||||
 | 
			
		||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
 | 
			
		||||
# include "syntax.h.generated.h"
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -30,8 +30,8 @@
 | 
			
		||||
// for ":version", ":intro", and "nvim --version"
 | 
			
		||||
#ifndef NVIM_VERSION_MEDIUM
 | 
			
		||||
#define NVIM_VERSION_MEDIUM "v" STR(NVIM_VERSION_MAJOR)\
 | 
			
		||||
  "." STR(NVIM_VERSION_MINOR) "." STR(NVIM_VERSION_PATCH)\
 | 
			
		||||
  NVIM_VERSION_PRERELEASE
 | 
			
		||||
"." STR(NVIM_VERSION_MINOR) "." STR(NVIM_VERSION_PATCH)\
 | 
			
		||||
NVIM_VERSION_PRERELEASE
 | 
			
		||||
#endif
 | 
			
		||||
#define NVIM_VERSION_LONG "NVIM " NVIM_VERSION_MEDIUM
 | 
			
		||||
 | 
			
		||||
@@ -47,33 +47,33 @@ char *version_cflags = "Compilation: " NVIM_VERSION_CFLAGS;
 | 
			
		||||
 | 
			
		||||
static char *features[] = {
 | 
			
		||||
#ifdef HAVE_ACL
 | 
			
		||||
  "+acl",
 | 
			
		||||
"+acl",
 | 
			
		||||
#else
 | 
			
		||||
  "-acl",
 | 
			
		||||
"-acl",
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if (defined(HAVE_ICONV_H) && defined(USE_ICONV)) || defined(DYNAMIC_ICONV)
 | 
			
		||||
# ifdef DYNAMIC_ICONV
 | 
			
		||||
  "+iconv/dyn",
 | 
			
		||||
"+iconv/dyn",
 | 
			
		||||
# else
 | 
			
		||||
  "+iconv",
 | 
			
		||||
"+iconv",
 | 
			
		||||
# endif
 | 
			
		||||
#else
 | 
			
		||||
  "-iconv",
 | 
			
		||||
"-iconv",
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_JEMALLOC
 | 
			
		||||
  "+jemalloc",
 | 
			
		||||
"+jemalloc",
 | 
			
		||||
#else
 | 
			
		||||
  "-jemalloc",
 | 
			
		||||
"-jemalloc",
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef FEAT_TUI
 | 
			
		||||
  "+tui",
 | 
			
		||||
"+tui",
 | 
			
		||||
#else
 | 
			
		||||
  "-tui",
 | 
			
		||||
"-tui",
 | 
			
		||||
#endif
 | 
			
		||||
  NULL
 | 
			
		||||
NULL
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// clang-format off
 | 
			
		||||
@@ -205,8 +205,8 @@ static const int included_patches[] = {
 | 
			
		||||
  // 1233,
 | 
			
		||||
  // 1232,
 | 
			
		||||
  // 1231,
 | 
			
		||||
  // 1230,
 | 
			
		||||
  // 1229,
 | 
			
		||||
  1230,
 | 
			
		||||
  1229,
 | 
			
		||||
  // 1228,
 | 
			
		||||
  // 1227,
 | 
			
		||||
  // 1226,
 | 
			
		||||
 
 | 
			
		||||
@@ -26,13 +26,6 @@
 | 
			
		||||
/// length of a buffer to store a number in ASCII (64 bits binary + NUL)
 | 
			
		||||
enum { NUMBUFLEN = 65 };
 | 
			
		||||
 | 
			
		||||
// flags for vim_str2nr()
 | 
			
		||||
#define STR2NR_BIN 1
 | 
			
		||||
#define STR2NR_OCT 2
 | 
			
		||||
#define STR2NR_HEX 4
 | 
			
		||||
#define STR2NR_ALL (STR2NR_BIN + STR2NR_OCT + STR2NR_HEX)
 | 
			
		||||
#define STR2NR_FORCE 8  // only when ONE of the above is used
 | 
			
		||||
 | 
			
		||||
#define MAX_TYPENR 65535
 | 
			
		||||
 | 
			
		||||
#define ROOT_UID 0
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3102
									
								
								src/nvim/viml/parser/expressions.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3102
									
								
								src/nvim/viml/parser/expressions.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										389
									
								
								src/nvim/viml/parser/expressions.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										389
									
								
								src/nvim/viml/parser/expressions.h
									
									
									
									
									
										Normal 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 can’t possibly represent.
 | 
			
		||||
      struct {
 | 
			
		||||
        /// True if UnknownFigure may actually represent dictionary literal.
 | 
			
		||||
        bool allow_dict;
 | 
			
		||||
        /// True if UnknownFigure may actually represent lambda.
 | 
			
		||||
        bool allow_lambda;
 | 
			
		||||
        /// True if UnknownFigure may actually be part of curly braces name.
 | 
			
		||||
        bool allow_ident;
 | 
			
		||||
      } type_guesses;
 | 
			
		||||
      /// Highlight chunk index, used for rehighlighting if needed
 | 
			
		||||
      size_t opening_hl_idx;
 | 
			
		||||
    } fig;  ///< For kExprNodeUnknownFigure.
 | 
			
		||||
    struct {
 | 
			
		||||
      ExprVarScope scope;  ///< Scope character or 0 if not present.
 | 
			
		||||
      /// Actual identifier without scope.
 | 
			
		||||
      ///
 | 
			
		||||
      /// Points to inside parser reader state.
 | 
			
		||||
      const char *ident;
 | 
			
		||||
      size_t ident_len;  ///< Actual identifier length.
 | 
			
		||||
    } var;  ///< For kExprNodePlainIdentifier and kExprNodePlainKey.
 | 
			
		||||
    struct {
 | 
			
		||||
      bool got_colon;  ///< True if colon was seen.
 | 
			
		||||
    } ter;  ///< For kExprNodeTernaryValue.
 | 
			
		||||
    struct {
 | 
			
		||||
      ExprComparisonType type;  ///< Comparison type.
 | 
			
		||||
      ExprCaseCompareStrategy ccs;  ///< Case comparison strategy.
 | 
			
		||||
      bool inv;  ///< True if comparison is to be inverted.
 | 
			
		||||
    } cmp;  ///< For kExprNodeComparison.
 | 
			
		||||
    struct {
 | 
			
		||||
      uvarnumber_T value;
 | 
			
		||||
    } num;  ///< For kExprNodeInteger.
 | 
			
		||||
    struct {
 | 
			
		||||
      float_T value;
 | 
			
		||||
    } flt;  ///< For kExprNodeFloat.
 | 
			
		||||
    struct {
 | 
			
		||||
      char *value;
 | 
			
		||||
      size_t size;
 | 
			
		||||
    } str;  ///< For kExprNodeSingleQuotedString and
 | 
			
		||||
            ///< kExprNodeDoubleQuotedString.
 | 
			
		||||
    struct {
 | 
			
		||||
      const char *ident;  ///< Option name start.
 | 
			
		||||
      size_t ident_len;  ///< Option name length.
 | 
			
		||||
      ExprOptScope scope;  ///< Option scope: &l:, &g: or not specified.
 | 
			
		||||
    } opt;  ///< For kExprNodeOption.
 | 
			
		||||
    struct {
 | 
			
		||||
      const char *ident;  ///< Environment variable name start.
 | 
			
		||||
      size_t ident_len;  ///< Environment variable name length.
 | 
			
		||||
    } env;  ///< For kExprNodeEnvironment.
 | 
			
		||||
    struct {
 | 
			
		||||
      ExprAssignmentType type;
 | 
			
		||||
    } ass;  ///< For kExprNodeAssignment
 | 
			
		||||
  } data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
  /// Allow multiple expressions in a row: e.g. for :echo
 | 
			
		||||
  ///
 | 
			
		||||
  /// Parser will still parse only one of them though.
 | 
			
		||||
  kExprFlagsMulti = (1 << 0),
 | 
			
		||||
  /// Allow NL, NUL and bar to be EOC
 | 
			
		||||
  ///
 | 
			
		||||
  /// When parsing expressions input by user bar is assumed to be a binary
 | 
			
		||||
  /// operator and other two are spacings.
 | 
			
		||||
  kExprFlagsDisallowEOC = (1 << 1),
 | 
			
		||||
  /// Parse :let argument
 | 
			
		||||
  ///
 | 
			
		||||
  /// That mean that top level node must be an assignment and first nodes
 | 
			
		||||
  /// belong to lvalues.
 | 
			
		||||
  kExprFlagsParseLet = (1 << 2),
 | 
			
		||||
  // XXX whenever you add a new flag, alter klee_assume() statement in
 | 
			
		||||
  //     viml_expressions_parser.c, nvim_parse_expression() flags parsing
 | 
			
		||||
  //     alongside with its documentation and flag sets in check_parsing()
 | 
			
		||||
  //     function in expressions parser functional and unit tests.
 | 
			
		||||
} ExprParserFlags;
 | 
			
		||||
 | 
			
		||||
/// AST error definition
 | 
			
		||||
typedef struct {
 | 
			
		||||
  /// Error message. Must contain a single printf format atom: %.*s.
 | 
			
		||||
  const char *msg;
 | 
			
		||||
  /// Error message argument: points to the location of the error.
 | 
			
		||||
  const char *arg;
 | 
			
		||||
  /// Message argument length: length till the end of string.
 | 
			
		||||
  int arg_len;
 | 
			
		||||
} ExprASTError;
 | 
			
		||||
 | 
			
		||||
/// Structure representing complety AST for one expression
 | 
			
		||||
typedef struct {
 | 
			
		||||
  /// When AST is not correct this message will be printed.
 | 
			
		||||
  ///
 | 
			
		||||
  /// Uses `emsgf(msg, arg_len, arg);`, `msg` is assumed to contain only `%.*s`.
 | 
			
		||||
  ExprASTError err;
 | 
			
		||||
  /// Root node of the AST.
 | 
			
		||||
  ExprASTNode *root;
 | 
			
		||||
} ExprAST;
 | 
			
		||||
 | 
			
		||||
/// Array mapping ExprASTNodeType to maximum amount of children node may have
 | 
			
		||||
extern const uint8_t node_maxchildren[];
 | 
			
		||||
 | 
			
		||||
/// Array mapping ExprASTNodeType values to their stringified versions
 | 
			
		||||
extern const char *const east_node_type_tab[];
 | 
			
		||||
 | 
			
		||||
/// Array mapping ExprComparisonType values to their stringified versions
 | 
			
		||||
extern const char *const eltkn_cmp_type_tab[];
 | 
			
		||||
 | 
			
		||||
/// Array mapping ExprCaseCompareStrategy values to their stringified versions
 | 
			
		||||
extern const char *const ccs_tab[];
 | 
			
		||||
 | 
			
		||||
/// Array mapping ExprAssignmentType values to their stringified versions
 | 
			
		||||
extern const char *const expr_asgn_type_tab[];
 | 
			
		||||
 | 
			
		||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
 | 
			
		||||
# include "viml/parser/expressions.h.generated.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#endif  // NVIM_VIML_PARSER_EXPRESSIONS_H
 | 
			
		||||
							
								
								
									
										13
									
								
								src/nvim/viml/parser/parser.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/nvim/viml/parser/parser.c
									
									
									
									
									
										Normal 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)++;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										244
									
								
								src/nvim/viml/parser/parser.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								src/nvim/viml/parser/parser.h
									
									
									
									
									
										Normal 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
 | 
			
		||||
@@ -341,3 +341,9 @@ disables tracing (the fastest, but you get no data if tests crash and there was
 | 
			
		||||
no core dump generated), `1` or empty/undefined leaves only C function cals and 
 | 
			
		||||
returns in the trace (faster then recording everything), `2` records all 
 | 
			
		||||
function calls, returns and lua source lines exuecuted.
 | 
			
		||||
 | 
			
		||||
`NVIM_TEST_TRACE_ON_ERROR` (U) (1): makes unit tests yield trace on error in 
 | 
			
		||||
addition to regular error message.
 | 
			
		||||
 | 
			
		||||
`NVIM_TEST_MAXTRACE` (U) (N): specifies maximum number of trace lines to keep. 
 | 
			
		||||
Default is 1024.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
local helpers = require('test.functional.helpers')(after_each)
 | 
			
		||||
local Screen = require('test.functional.ui.screen')
 | 
			
		||||
local global_helpers = require('test.helpers')
 | 
			
		||||
 | 
			
		||||
local NIL = helpers.NIL
 | 
			
		||||
local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq
 | 
			
		||||
local ok, nvim_async, feed = helpers.ok, helpers.nvim_async, helpers.feed
 | 
			
		||||
@@ -10,6 +12,10 @@ local request = helpers.request
 | 
			
		||||
local meth_pcall = helpers.meth_pcall
 | 
			
		||||
local command = helpers.command
 | 
			
		||||
 | 
			
		||||
local intchar2lua = global_helpers.intchar2lua
 | 
			
		||||
local format_string = global_helpers.format_string
 | 
			
		||||
local mergedicts_copy = global_helpers.mergedicts_copy
 | 
			
		||||
 | 
			
		||||
describe('api', function()
 | 
			
		||||
  before_each(clear)
 | 
			
		||||
 | 
			
		||||
@@ -710,4 +716,197 @@ describe('api', function()
 | 
			
		||||
    ok(err:match(': Wrong type for argument 1, expecting String') ~= nil)
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
  describe('nvim_parse_expression', function()
 | 
			
		||||
    before_each(function()
 | 
			
		||||
      meths.set_option('isident', '')
 | 
			
		||||
    end)
 | 
			
		||||
    local function simplify_east_api_node(line, east_api_node)
 | 
			
		||||
      if east_api_node == NIL then
 | 
			
		||||
        return nil
 | 
			
		||||
      end
 | 
			
		||||
      if east_api_node.children then
 | 
			
		||||
        for k, v in pairs(east_api_node.children) do
 | 
			
		||||
          east_api_node.children[k] = simplify_east_api_node(line, v)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
      local typ = east_api_node.type
 | 
			
		||||
      if typ == 'Register' then
 | 
			
		||||
        typ = typ .. ('(name=%s)'):format(
 | 
			
		||||
          tostring(intchar2lua(east_api_node.name)))
 | 
			
		||||
        east_api_node.name = nil
 | 
			
		||||
      elseif typ == 'PlainIdentifier' then
 | 
			
		||||
        typ = typ .. ('(scope=%s,ident=%s)'):format(
 | 
			
		||||
          tostring(intchar2lua(east_api_node.scope)), east_api_node.ident)
 | 
			
		||||
        east_api_node.scope = nil
 | 
			
		||||
        east_api_node.ident = nil
 | 
			
		||||
      elseif typ == 'PlainKey' then
 | 
			
		||||
        typ = typ .. ('(key=%s)'):format(east_api_node.ident)
 | 
			
		||||
        east_api_node.ident = nil
 | 
			
		||||
      elseif typ == 'Comparison' then
 | 
			
		||||
        typ = typ .. ('(type=%s,inv=%u,ccs=%s)'):format(
 | 
			
		||||
          east_api_node.cmp_type, east_api_node.invert and 1 or 0,
 | 
			
		||||
          east_api_node.ccs_strategy)
 | 
			
		||||
        east_api_node.ccs_strategy = nil
 | 
			
		||||
        east_api_node.cmp_type = nil
 | 
			
		||||
        east_api_node.invert = nil
 | 
			
		||||
      elseif typ == 'Integer' then
 | 
			
		||||
        typ = typ .. ('(val=%u)'):format(east_api_node.ivalue)
 | 
			
		||||
        east_api_node.ivalue = nil
 | 
			
		||||
      elseif typ == 'Float' then
 | 
			
		||||
        typ = typ .. format_string('(val=%e)', east_api_node.fvalue)
 | 
			
		||||
        east_api_node.fvalue = nil
 | 
			
		||||
      elseif typ == 'SingleQuotedString' or typ == 'DoubleQuotedString' then
 | 
			
		||||
        typ = format_string('%s(val=%q)', typ, east_api_node.svalue)
 | 
			
		||||
        east_api_node.svalue = nil
 | 
			
		||||
      elseif typ == 'Option' then
 | 
			
		||||
        typ = ('%s(scope=%s,ident=%s)'):format(
 | 
			
		||||
          typ,
 | 
			
		||||
          tostring(intchar2lua(east_api_node.scope)),
 | 
			
		||||
          east_api_node.ident)
 | 
			
		||||
        east_api_node.ident = nil
 | 
			
		||||
        east_api_node.scope = nil
 | 
			
		||||
      elseif typ == 'Environment' then
 | 
			
		||||
        typ = ('%s(ident=%s)'):format(typ, east_api_node.ident)
 | 
			
		||||
        east_api_node.ident = nil
 | 
			
		||||
      elseif typ == 'Assignment' then
 | 
			
		||||
        local aug = east_api_node.augmentation
 | 
			
		||||
        if aug == '' then aug = 'Plain' end
 | 
			
		||||
        typ = ('%s(%s)'):format(typ, aug)
 | 
			
		||||
        east_api_node.augmentation = nil
 | 
			
		||||
      end
 | 
			
		||||
      typ = ('%s:%u:%u:%s'):format(
 | 
			
		||||
        typ, east_api_node.start[1], east_api_node.start[2],
 | 
			
		||||
        line:sub(east_api_node.start[2] + 1,
 | 
			
		||||
                 east_api_node.start[2] + 1 + east_api_node.len - 1))
 | 
			
		||||
      assert(east_api_node.start[2] + east_api_node.len - 1 <= #line)
 | 
			
		||||
      for k, _ in pairs(east_api_node.start) do
 | 
			
		||||
        assert(({true, true})[k])
 | 
			
		||||
      end
 | 
			
		||||
      east_api_node.start = nil
 | 
			
		||||
      east_api_node.type = nil
 | 
			
		||||
      east_api_node.len = nil
 | 
			
		||||
      local can_simplify = true
 | 
			
		||||
      for _, _ in pairs(east_api_node) do
 | 
			
		||||
        if can_simplify then can_simplify = false end
 | 
			
		||||
      end
 | 
			
		||||
      if can_simplify then
 | 
			
		||||
        return typ
 | 
			
		||||
      else
 | 
			
		||||
        east_api_node[1] = typ
 | 
			
		||||
        return east_api_node
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    local function simplify_east_api(line, east_api)
 | 
			
		||||
      if east_api.error then
 | 
			
		||||
        east_api.err = east_api.error
 | 
			
		||||
        east_api.error = nil
 | 
			
		||||
        east_api.err.msg = east_api.err.message
 | 
			
		||||
        east_api.err.message = nil
 | 
			
		||||
      end
 | 
			
		||||
      if east_api.ast then
 | 
			
		||||
        east_api.ast = {simplify_east_api_node(line, east_api.ast)}
 | 
			
		||||
        if #east_api.ast == 0 then
 | 
			
		||||
          east_api.ast = nil
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
      if east_api.len == #line then
 | 
			
		||||
        east_api.len = nil
 | 
			
		||||
      end
 | 
			
		||||
      return east_api
 | 
			
		||||
    end
 | 
			
		||||
    local function simplify_east_hl(line, east_hl)
 | 
			
		||||
      for i, v in ipairs(east_hl) do
 | 
			
		||||
        east_hl[i] = ('%s:%u:%u:%s'):format(
 | 
			
		||||
          v[4],
 | 
			
		||||
          v[1],
 | 
			
		||||
          v[2],
 | 
			
		||||
          line:sub(v[2] + 1, v[3]))
 | 
			
		||||
      end
 | 
			
		||||
      return east_hl
 | 
			
		||||
    end
 | 
			
		||||
    local FLAGS_TO_STR = {
 | 
			
		||||
      [0] = "",
 | 
			
		||||
      [1] = "m",
 | 
			
		||||
      [2] = "E",
 | 
			
		||||
      [3] = "mE",
 | 
			
		||||
      [4] = "l",
 | 
			
		||||
      [5] = "lm",
 | 
			
		||||
      [6] = "lE",
 | 
			
		||||
      [7] = "lmE",
 | 
			
		||||
    }
 | 
			
		||||
    local function _check_parsing(opts, str, exp_ast, exp_highlighting_fs,
 | 
			
		||||
                                  nz_flags_exps)
 | 
			
		||||
      if type(str) ~= 'string' then
 | 
			
		||||
        return
 | 
			
		||||
      end
 | 
			
		||||
      local zflags = opts.flags[1]
 | 
			
		||||
      nz_flags_exps = nz_flags_exps or {}
 | 
			
		||||
      for _, flags in ipairs(opts.flags) do
 | 
			
		||||
        local err, msg = pcall(function()
 | 
			
		||||
          local east_api = meths.parse_expression(str, FLAGS_TO_STR[flags], true)
 | 
			
		||||
          local east_hl = east_api.highlight
 | 
			
		||||
          east_api.highlight = nil
 | 
			
		||||
          local ast = simplify_east_api(str, east_api)
 | 
			
		||||
          local hls = simplify_east_hl(str, east_hl)
 | 
			
		||||
          local exps = {
 | 
			
		||||
            ast = exp_ast,
 | 
			
		||||
            hl_fs = exp_highlighting_fs,
 | 
			
		||||
          }
 | 
			
		||||
          local add_exps = nz_flags_exps[flags]
 | 
			
		||||
          if not add_exps and flags == 3 + zflags then
 | 
			
		||||
            add_exps = nz_flags_exps[1 + zflags] or nz_flags_exps[2 + zflags]
 | 
			
		||||
          end
 | 
			
		||||
          if add_exps then
 | 
			
		||||
            if add_exps.ast then
 | 
			
		||||
              exps.ast = mergedicts_copy(exps.ast, add_exps.ast)
 | 
			
		||||
            end
 | 
			
		||||
            if add_exps.hl_fs then
 | 
			
		||||
              exps.hl_fs = mergedicts_copy(exps.hl_fs, add_exps.hl_fs)
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
          eq(exps.ast, ast)
 | 
			
		||||
          if exp_highlighting_fs then
 | 
			
		||||
            local exp_highlighting = {}
 | 
			
		||||
            local next_col = 0
 | 
			
		||||
            for i, h in ipairs(exps.hl_fs) do
 | 
			
		||||
              exp_highlighting[i], next_col = h(next_col)
 | 
			
		||||
            end
 | 
			
		||||
            eq(exp_highlighting, hls)
 | 
			
		||||
          end
 | 
			
		||||
        end)
 | 
			
		||||
        if not err then
 | 
			
		||||
          msg = format_string('Error while processing test (%r, %s):\n%s',
 | 
			
		||||
                              str, FLAGS_TO_STR[flags], msg)
 | 
			
		||||
          error(msg)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    local function hl(group, str, shift)
 | 
			
		||||
      return function(next_col)
 | 
			
		||||
        local col = next_col + (shift or 0)
 | 
			
		||||
        return (('%s:%u:%u:%s'):format(
 | 
			
		||||
          'Nvim' .. group,
 | 
			
		||||
          0,
 | 
			
		||||
          col,
 | 
			
		||||
          str)), (col + #str)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    local function fmtn(typ, args, rest)
 | 
			
		||||
      if (typ == 'UnknownFigure'
 | 
			
		||||
          or typ == 'DictLiteral'
 | 
			
		||||
          or typ == 'CurlyBracesIdentifier'
 | 
			
		||||
          or typ == 'Lambda') then
 | 
			
		||||
        return ('%s%s'):format(typ, rest)
 | 
			
		||||
      elseif typ == 'DoubleQuotedString' or typ == 'SingleQuotedString' then
 | 
			
		||||
        if args:sub(-4) == 'NULL' then
 | 
			
		||||
          args = args:sub(1, -5) .. '""'
 | 
			
		||||
        end
 | 
			
		||||
        return ('%s(%s)%s'):format(typ, args, rest)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    assert:set_parameter('TableFormatLevel', 1000000)
 | 
			
		||||
    require('test.unit.viml.expressions.parser_tests')(
 | 
			
		||||
        it, _check_parsing, hl, fmtn)
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
end)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								test/functional/ex_cmds/map_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								test/functional/ex_cmds/map_spec.lua
									
									
									
									
									
										Normal 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)
 | 
			
		||||
@@ -144,7 +144,13 @@ before_each(function()
 | 
			
		||||
    EOB={bold = true, foreground = Screen.colors.Blue1},
 | 
			
		||||
    ERR={foreground = Screen.colors.Grey100, background = Screen.colors.Red},
 | 
			
		||||
    SK={foreground = Screen.colors.Blue},
 | 
			
		||||
    PE={bold = true, foreground = Screen.colors.SeaGreen4}
 | 
			
		||||
    PE={bold = true, foreground = Screen.colors.SeaGreen4},
 | 
			
		||||
    NUM={foreground = Screen.colors.Blue2},
 | 
			
		||||
    NPAR={foreground = Screen.colors.Yellow},
 | 
			
		||||
    SQ={foreground = Screen.colors.Blue3},
 | 
			
		||||
    SB={foreground = Screen.colors.Blue4},
 | 
			
		||||
    E={foreground = Screen.colors.Red, background = Screen.colors.Blue},
 | 
			
		||||
    M={bold = true},
 | 
			
		||||
  })
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
@@ -863,7 +869,10 @@ describe('Ex commands coloring support', function()
 | 
			
		||||
end)
 | 
			
		||||
describe('Expressions coloring support', function()
 | 
			
		||||
  it('works', function()
 | 
			
		||||
    meths.set_var('Nvim_color_expr', 'RainBowParens')
 | 
			
		||||
    meths.command('hi clear NvimNumber')
 | 
			
		||||
    meths.command('hi clear NvimNestingParenthesis')
 | 
			
		||||
    meths.command('hi NvimNumber guifg=Blue2')
 | 
			
		||||
    meths.command('hi NvimNestingParenthesis guifg=Yellow')
 | 
			
		||||
    feed(':echo <C-r>=(((1)))')
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
                                              |
 | 
			
		||||
@@ -873,21 +882,103 @@ describe('Expressions coloring support', function()
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      ={RBP1:(}{RBP2:(}{RBP3:(}1{RBP3:)}{RBP2:)}{RBP1:)}^                                |
 | 
			
		||||
      ={NPAR:(((}{NUM:1}{NPAR:)))}^                                |
 | 
			
		||||
    ]])
 | 
			
		||||
  end)
 | 
			
		||||
  it('errors out when failing to get callback', function()
 | 
			
		||||
  it('does not use Nvim_color_expr', function()
 | 
			
		||||
    meths.set_var('Nvim_color_expr', 42)
 | 
			
		||||
    -- Used to error out due to failing to get callback.
 | 
			
		||||
    meths.command('hi clear NvimNumber')
 | 
			
		||||
    meths.command('hi NvimNumber guifg=Blue2')
 | 
			
		||||
    feed(':<C-r>=1')
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
                                              |
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      =                                       |
 | 
			
		||||
      {ERR:E5409: Unable to get g:Nvim_color_expr c}|
 | 
			
		||||
      {ERR:allback: Vim:E6000: Argument is not a fu}|
 | 
			
		||||
      {ERR:nction or function name}                 |
 | 
			
		||||
      =1^                                      |
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      ={NUM:1}^                                      |
 | 
			
		||||
    ]])
 | 
			
		||||
  end)
 | 
			
		||||
  it('works correctly with non-ASCII and control characters', function()
 | 
			
		||||
    meths.command('hi clear NvimStringBody')
 | 
			
		||||
    meths.command('hi clear NvimStringQuote')
 | 
			
		||||
    meths.command('hi clear NvimInvalid')
 | 
			
		||||
    meths.command('hi NvimStringQuote guifg=Blue3')
 | 
			
		||||
    meths.command('hi NvimStringBody guifg=Blue4')
 | 
			
		||||
    meths.command('hi NvimInvalid guifg=Red guibg=Blue')
 | 
			
		||||
    feed('i<C-r>="«»"«»')
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
                                              |
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      ={SQ:"}{SB:«»}{SQ:"}{E:«»}^                                 |
 | 
			
		||||
    ]])
 | 
			
		||||
    feed('<C-c>')
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
      ^                                        |
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {M:-- INSERT --}                            |
 | 
			
		||||
    ]])
 | 
			
		||||
    feed('<Esc>')
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
      ^                                        |
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
                                              |
 | 
			
		||||
    ]])
 | 
			
		||||
    feed(':<C-\\>e"<C-v><C-x>"<C-v><C-x>')
 | 
			
		||||
    -- TODO(ZyX-I): Parser highlighting should not override special character
 | 
			
		||||
    --              highlighting.
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
                                              |
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      ={SQ:"}{SB:^X}{SQ:"}{ERR:^X}^                                 |
 | 
			
		||||
    ]])
 | 
			
		||||
    feed('<C-c>')
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
                                              |
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      :^                                       |
 | 
			
		||||
    ]])
 | 
			
		||||
    funcs.setreg('a', {'\192'})
 | 
			
		||||
    feed('<C-r>="<C-r><C-r>a"<C-r><C-r>a"foo"')
 | 
			
		||||
    -- TODO(ZyX-I): Parser highlighting should not override special character
 | 
			
		||||
    --              highlighting.
 | 
			
		||||
    screen:expect([[
 | 
			
		||||
                                              |
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      {EOB:~                                       }|
 | 
			
		||||
      ={SQ:"}{SB:<c0>}{SQ:"}{E:<c0>"}{SB:foo}{E:"}^                        |
 | 
			
		||||
    ]])
 | 
			
		||||
  end)
 | 
			
		||||
end)
 | 
			
		||||
 
 | 
			
		||||
@@ -239,7 +239,11 @@ describe('external cmdline', function()
 | 
			
		||||
        prompt = "",
 | 
			
		||||
        special = {'"', true},
 | 
			
		||||
      },{
 | 
			
		||||
        content = { { {}, "1+2" } },
 | 
			
		||||
        content = {
 | 
			
		||||
          { {}, "1" },
 | 
			
		||||
          { {}, "+" },
 | 
			
		||||
          { {}, "2" },
 | 
			
		||||
        },
 | 
			
		||||
        firstc = "=",
 | 
			
		||||
        indent = 0,
 | 
			
		||||
        pos = 3,
 | 
			
		||||
@@ -316,7 +320,7 @@ describe('external cmdline', function()
 | 
			
		||||
        pos = 0,
 | 
			
		||||
        prompt = "",
 | 
			
		||||
      }}, cmdline)
 | 
			
		||||
      eq({{{{}, 'function Foo()'}}}, block)
 | 
			
		||||
      eq({ { { {}, 'function Foo()'} } }, block)
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    feed('line1<cr>')
 | 
			
		||||
@@ -327,8 +331,8 @@ describe('external cmdline', function()
 | 
			
		||||
      {1:~                        }|
 | 
			
		||||
                               |
 | 
			
		||||
    ]], nil, nil, function()
 | 
			
		||||
      eq({{{{}, 'function Foo()'}},
 | 
			
		||||
          {{{}, '  line1'}}}, block)
 | 
			
		||||
      eq({ { { {}, 'function Foo()'} },
 | 
			
		||||
           { { {}, '  line1'} } }, block)
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    block = {}
 | 
			
		||||
@@ -340,8 +344,8 @@ describe('external cmdline', function()
 | 
			
		||||
      {1:~                        }|
 | 
			
		||||
      ^                         |
 | 
			
		||||
    ]], nil, nil, function()
 | 
			
		||||
      eq({{{{}, 'function Foo()'}},
 | 
			
		||||
          {{{}, '  line1'}}}, block)
 | 
			
		||||
      eq({ { { {}, 'function Foo()'} },
 | 
			
		||||
           { { {}, '  line1'} } }, block)
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										245
									
								
								test/helpers.lua
									
									
									
									
									
								
							
							
						
						
									
										245
									
								
								test/helpers.lua
									
									
									
									
									
								
							@@ -251,6 +251,9 @@ local function which(exe)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function shallowcopy(orig)
 | 
			
		||||
  if type(orig) ~= 'table' then
 | 
			
		||||
    return orig
 | 
			
		||||
  end
 | 
			
		||||
  local copy = {}
 | 
			
		||||
  for orig_key, orig_value in pairs(orig) do
 | 
			
		||||
    copy[orig_key] = orig_value
 | 
			
		||||
@@ -258,6 +261,92 @@ local function shallowcopy(orig)
 | 
			
		||||
  return copy
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local deepcopy
 | 
			
		||||
 | 
			
		||||
local function id(v)
 | 
			
		||||
  return v
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local deepcopy_funcs = {
 | 
			
		||||
  table = function(orig)
 | 
			
		||||
    local copy = {}
 | 
			
		||||
    for k, v in pairs(orig) do
 | 
			
		||||
      copy[deepcopy(k)] = deepcopy(v)
 | 
			
		||||
    end
 | 
			
		||||
    return copy
 | 
			
		||||
  end,
 | 
			
		||||
  number = id,
 | 
			
		||||
  string = id,
 | 
			
		||||
  ['nil'] = id,
 | 
			
		||||
  boolean = id,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
deepcopy = function(orig)
 | 
			
		||||
  return deepcopy_funcs[type(orig)](orig)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local REMOVE_THIS = {}
 | 
			
		||||
 | 
			
		||||
local function mergedicts_copy(d1, d2)
 | 
			
		||||
  local ret = shallowcopy(d1)
 | 
			
		||||
  for k, v in pairs(d2) do
 | 
			
		||||
    if d2[k] == REMOVE_THIS then
 | 
			
		||||
      ret[k] = nil
 | 
			
		||||
    elseif type(d1[k]) == 'table' and type(v) == 'table' then
 | 
			
		||||
      ret[k] = mergedicts_copy(d1[k], v)
 | 
			
		||||
    else
 | 
			
		||||
      ret[k] = v
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
  return ret
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
-- dictdiff: find a diff so that mergedicts_copy(d1, diff) is equal to d2
 | 
			
		||||
--
 | 
			
		||||
-- Note: does not do copies of d2 values used.
 | 
			
		||||
local function dictdiff(d1, d2)
 | 
			
		||||
  local ret = {}
 | 
			
		||||
  local hasdiff = false
 | 
			
		||||
  for k, v in pairs(d1) do
 | 
			
		||||
    if d2[k] == nil then
 | 
			
		||||
      hasdiff = true
 | 
			
		||||
      ret[k] = REMOVE_THIS
 | 
			
		||||
    elseif type(v) == type(d2[k]) then
 | 
			
		||||
      if type(v) == 'table' then
 | 
			
		||||
        local subdiff = dictdiff(v, d2[k])
 | 
			
		||||
        if subdiff ~= nil then
 | 
			
		||||
          hasdiff = true
 | 
			
		||||
          ret[k] = subdiff
 | 
			
		||||
        end
 | 
			
		||||
      elseif v ~= d2[k] then
 | 
			
		||||
        ret[k] = d2[k]
 | 
			
		||||
        hasdiff = true
 | 
			
		||||
      end
 | 
			
		||||
    else
 | 
			
		||||
      ret[k] = d2[k]
 | 
			
		||||
      hasdiff = true
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
  for k, v in pairs(d2) do
 | 
			
		||||
    if d1[k] == nil then
 | 
			
		||||
      ret[k] = shallowcopy(v)
 | 
			
		||||
      hasdiff = true
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
  if hasdiff then
 | 
			
		||||
    return ret
 | 
			
		||||
  else
 | 
			
		||||
    return nil
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function updated(d, d2)
 | 
			
		||||
  for k, v in pairs(d2) do
 | 
			
		||||
    d[k] = v
 | 
			
		||||
  end
 | 
			
		||||
  return d
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function concat_tables(...)
 | 
			
		||||
  local ret = {}
 | 
			
		||||
  for i = 1, select('#', ...) do
 | 
			
		||||
@@ -294,6 +383,152 @@ local function dedent(str, leave_indent)
 | 
			
		||||
  return str
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function format_float(v)
 | 
			
		||||
  -- On windows exponent appears to have three digits and not two
 | 
			
		||||
  local ret = ('%.6e'):format(v)
 | 
			
		||||
  local l, f, es, e = ret:match('^(%-?%d)%.(%d+)e([+%-])0*(%d%d+)$')
 | 
			
		||||
  return l .. '.' .. f .. 'e' .. es .. e
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local SUBTBL = {
 | 
			
		||||
  '\\000', '\\001', '\\002', '\\003', '\\004',
 | 
			
		||||
  '\\005', '\\006', '\\007', '\\008', '\\t',
 | 
			
		||||
  '\\n',   '\\011', '\\012', '\\r',   '\\014',
 | 
			
		||||
  '\\015', '\\016', '\\017', '\\018', '\\019',
 | 
			
		||||
  '\\020', '\\021', '\\022', '\\023', '\\024',
 | 
			
		||||
  '\\025', '\\026', '\\027', '\\028', '\\029',
 | 
			
		||||
  '\\030', '\\031',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
local format_luav
 | 
			
		||||
 | 
			
		||||
format_luav = function(v, indent, opts)
 | 
			
		||||
  opts = opts or {}
 | 
			
		||||
  local linesep = '\n'
 | 
			
		||||
  local next_indent_arg = nil
 | 
			
		||||
  local indent_shift = opts.indent_shift or '  '
 | 
			
		||||
  local next_indent
 | 
			
		||||
  local nl = '\n'
 | 
			
		||||
  if indent == nil then
 | 
			
		||||
    indent = ''
 | 
			
		||||
    linesep = ''
 | 
			
		||||
    next_indent = ''
 | 
			
		||||
    nl = ' '
 | 
			
		||||
  else
 | 
			
		||||
    next_indent_arg = indent .. indent_shift
 | 
			
		||||
    next_indent = indent .. indent_shift
 | 
			
		||||
  end
 | 
			
		||||
  local ret = ''
 | 
			
		||||
  if type(v) == 'string' then
 | 
			
		||||
    if opts.literal_strings then
 | 
			
		||||
      ret = v
 | 
			
		||||
    else
 | 
			
		||||
      ret = tostring(v):gsub('[\'\\]', '\\%0'):gsub(
 | 
			
		||||
        '[%z\1-\31]', function(match)
 | 
			
		||||
          return SUBTBL[match:byte() + 1]
 | 
			
		||||
        end)
 | 
			
		||||
      ret = '\'' .. ret .. '\''
 | 
			
		||||
    end
 | 
			
		||||
  elseif type(v) == 'table' then
 | 
			
		||||
    if v == REMOVE_THIS then
 | 
			
		||||
      ret = 'REMOVE_THIS'
 | 
			
		||||
    else
 | 
			
		||||
      local processed_keys = {}
 | 
			
		||||
      ret = '{' .. linesep
 | 
			
		||||
      local non_empty = false
 | 
			
		||||
      for i, subv in ipairs(v) do
 | 
			
		||||
        ret = ('%s%s%s,%s'):format(ret, next_indent,
 | 
			
		||||
                                   format_luav(subv, next_indent_arg, opts), nl)
 | 
			
		||||
        processed_keys[i] = true
 | 
			
		||||
        non_empty = true
 | 
			
		||||
      end
 | 
			
		||||
      for k, subv in pairs(v) do
 | 
			
		||||
        if not processed_keys[k] then
 | 
			
		||||
          if type(k) == 'string' and k:match('^[a-zA-Z_][a-zA-Z0-9_]*$') then
 | 
			
		||||
            ret = ret .. next_indent .. k .. ' = '
 | 
			
		||||
          else
 | 
			
		||||
            ret = ('%s%s[%s] = '):format(ret, next_indent,
 | 
			
		||||
                                         format_luav(k, nil, opts))
 | 
			
		||||
          end
 | 
			
		||||
          ret = ret .. format_luav(subv, next_indent_arg, opts) .. ',' .. nl
 | 
			
		||||
          non_empty = true
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
      if nl == ' ' and non_empty then
 | 
			
		||||
        ret = ret:sub(1, -3)
 | 
			
		||||
      end
 | 
			
		||||
      ret = ret  .. indent .. '}'
 | 
			
		||||
    end
 | 
			
		||||
  elseif type(v) == 'number' then
 | 
			
		||||
    if v % 1 == 0 then
 | 
			
		||||
      ret = ('%d'):format(v)
 | 
			
		||||
    else
 | 
			
		||||
      ret = format_float(v)
 | 
			
		||||
    end
 | 
			
		||||
  elseif type(v) == 'nil' then
 | 
			
		||||
    ret = 'nil'
 | 
			
		||||
  else
 | 
			
		||||
    print(type(v))
 | 
			
		||||
    -- Not implemented yet
 | 
			
		||||
    assert(false)
 | 
			
		||||
  end
 | 
			
		||||
  return ret
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function format_string(fmt, ...)
 | 
			
		||||
  local i = 0
 | 
			
		||||
  local args = {...}
 | 
			
		||||
  local function getarg()
 | 
			
		||||
    i = i + 1
 | 
			
		||||
    return args[i]
 | 
			
		||||
  end
 | 
			
		||||
  local ret = fmt:gsub('%%[0-9*]*%.?[0-9*]*[cdEefgGiouXxqsr%%]', function(match)
 | 
			
		||||
    local subfmt = match:gsub('%*', function()
 | 
			
		||||
      return tostring(getarg())
 | 
			
		||||
    end)
 | 
			
		||||
    local arg = nil
 | 
			
		||||
    if subfmt:sub(-1) ~= '%' then
 | 
			
		||||
      arg = getarg()
 | 
			
		||||
    end
 | 
			
		||||
    if subfmt:sub(-1) == 'r' then
 | 
			
		||||
      -- %r is like %q, but it is supposed to single-quote strings and not
 | 
			
		||||
      -- double-quote them, and also work not only for strings.
 | 
			
		||||
      subfmt = subfmt:sub(1, -2) .. 's'
 | 
			
		||||
      arg = format_luav(arg)
 | 
			
		||||
    end
 | 
			
		||||
    if subfmt == '%e' then
 | 
			
		||||
      return format_float(arg)
 | 
			
		||||
    else
 | 
			
		||||
      return subfmt:format(arg)
 | 
			
		||||
    end
 | 
			
		||||
  end)
 | 
			
		||||
  return ret
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function intchar2lua(ch)
 | 
			
		||||
  ch = tonumber(ch)
 | 
			
		||||
  return (20 <= ch and ch < 127) and ('%c'):format(ch) or ch
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local fixtbl_metatable = {
 | 
			
		||||
  __newindex = function()
 | 
			
		||||
    assert(false)
 | 
			
		||||
  end,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
local function fixtbl(tbl)
 | 
			
		||||
  return setmetatable(tbl, fixtbl_metatable)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function fixtbl_rec(tbl)
 | 
			
		||||
  for _, v in pairs(tbl) do
 | 
			
		||||
    if type(v) == 'table' then
 | 
			
		||||
      fixtbl_rec(v)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
  return fixtbl(tbl)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
return {
 | 
			
		||||
  eq = eq,
 | 
			
		||||
  neq = neq,
 | 
			
		||||
@@ -308,6 +543,16 @@ return {
 | 
			
		||||
  hasenv = hasenv,
 | 
			
		||||
  which = which,
 | 
			
		||||
  shallowcopy = shallowcopy,
 | 
			
		||||
  deepcopy = deepcopy,
 | 
			
		||||
  mergedicts_copy = mergedicts_copy,
 | 
			
		||||
  dictdiff = dictdiff,
 | 
			
		||||
  REMOVE_THIS = REMOVE_THIS,
 | 
			
		||||
  concat_tables = concat_tables,
 | 
			
		||||
  dedent = dedent,
 | 
			
		||||
  format_luav = format_luav,
 | 
			
		||||
  format_string = format_string,
 | 
			
		||||
  intchar2lua = intchar2lua,
 | 
			
		||||
  updated = updated,
 | 
			
		||||
  fixtbl = fixtbl,
 | 
			
		||||
  fixtbl_rec = fixtbl_rec,
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										172
									
								
								test/symbolic/klee/nvim/charset.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								test/symbolic/klee/nvim/charset.c
									
									
									
									
									
										Normal 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);  // Should’ve used goto earlier.
 | 
			
		||||
#define PARSE_NUMBER(base, cond, conv) \
 | 
			
		||||
  do { \
 | 
			
		||||
    while (!STRING_ENDED(ptr) && (cond)) { \
 | 
			
		||||
      /* avoid ubsan error for overflow */ \
 | 
			
		||||
      if (un < UVARNUMBER_MAX / base) { \
 | 
			
		||||
        un = base * un + (uvarnumber_T)(conv); \
 | 
			
		||||
      } else { \
 | 
			
		||||
        un = UVARNUMBER_MAX; \
 | 
			
		||||
      } \
 | 
			
		||||
      ptr++; \
 | 
			
		||||
    } \
 | 
			
		||||
  } while (0)
 | 
			
		||||
  switch (pre) {
 | 
			
		||||
    case 'b':
 | 
			
		||||
    case 'B': {
 | 
			
		||||
vim_str2nr_bin:
 | 
			
		||||
      PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0'));
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case '0': {
 | 
			
		||||
vim_str2nr_oct:
 | 
			
		||||
      PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0'));
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 0: {
 | 
			
		||||
vim_str2nr_dec:
 | 
			
		||||
      PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0'));
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 'x':
 | 
			
		||||
    case 'X': {
 | 
			
		||||
vim_str2nr_hex:
 | 
			
		||||
      PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr)));
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#undef PARSE_NUMBER
 | 
			
		||||
 | 
			
		||||
  if (prep != NULL) {
 | 
			
		||||
    *prep = pre;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (len != NULL) {
 | 
			
		||||
    *len = (int)(ptr - (const char *)start);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (nptr != NULL) {
 | 
			
		||||
    if (negative) {  // account for leading '-' for decimal numbers
 | 
			
		||||
      // avoid ubsan error for overflow
 | 
			
		||||
      if (un > VARNUMBER_MAX) {
 | 
			
		||||
        *nptr = VARNUMBER_MIN;
 | 
			
		||||
      } else {
 | 
			
		||||
        *nptr = -(varnumber_T)un;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      if (un > VARNUMBER_MAX) {
 | 
			
		||||
        un = VARNUMBER_MAX;
 | 
			
		||||
      }
 | 
			
		||||
      *nptr = (varnumber_T)un;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (unptr != NULL) {
 | 
			
		||||
    *unptr = un;
 | 
			
		||||
  }
 | 
			
		||||
#undef STRING_ENDED
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										195
									
								
								test/symbolic/klee/nvim/garray.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								test/symbolic/klee/nvim/garray.c
									
									
									
									
									
										Normal 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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								test/symbolic/klee/nvim/gettext.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								test/symbolic/klee/nvim/gettext.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
char *gettext(const char *s)
 | 
			
		||||
{
 | 
			
		||||
  return (char *)s;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										539
									
								
								test/symbolic/klee/nvim/keymap.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										539
									
								
								test/symbolic/klee/nvim/keymap.c
									
									
									
									
									
										Normal 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										266
									
								
								test/symbolic/klee/nvim/mbyte.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								test/symbolic/klee/nvim/mbyte.c
									
									
									
									
									
										Normal 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										101
									
								
								test/symbolic/klee/nvim/memory.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								test/symbolic/klee/nvim/memory.c
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										102
									
								
								test/symbolic/klee/run.sh
									
									
									
									
									
										Executable 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 "$@"
 | 
			
		||||
							
								
								
									
										105
									
								
								test/symbolic/klee/viml_expressions_lexer.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								test/symbolic/klee/viml_expressions_lexer.c
									
									
									
									
									
										Normal 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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										117
									
								
								test/symbolic/klee/viml_expressions_parser.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								test/symbolic/klee/viml_expressions_parser.c
									
									
									
									
									
										Normal 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);
 | 
			
		||||
  }
 | 
			
		||||
  // Can’t possibly have more highlight tokens then there are bytes in string.
 | 
			
		||||
  assert(kv_size(colors) <= INPUT_SIZE - shift);
 | 
			
		||||
  kvi_destroy(colors);
 | 
			
		||||
  // Not destroying pstate.reader.lines because there is no way it could exceed
 | 
			
		||||
  // its limits in the current circumstances.
 | 
			
		||||
  viml_pexpr_free_ast(ast);
 | 
			
		||||
  assert(allocated_memory == 0);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										320
									
								
								test/unit/charset/vim_str2nr_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										320
									
								
								test/unit/charset/vim_str2nr_spec.lua
									
									
									
									
									
										Normal 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)
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
local helpers = require('test.unit.helpers')(nil)
 | 
			
		||||
 | 
			
		||||
local ptr2key = helpers.ptr2key
 | 
			
		||||
local cimport = helpers.cimport
 | 
			
		||||
local to_cstr = helpers.to_cstr
 | 
			
		||||
local ffi = helpers.ffi
 | 
			
		||||
@@ -91,10 +92,6 @@ local function populate_partial(pt, lua_pt, processed)
 | 
			
		||||
  return pt
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local ptr2key = function(ptr)
 | 
			
		||||
  return tostring(ptr)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local lst2tbl
 | 
			
		||||
local dct2tbl
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -65,11 +65,12 @@ local tokens = P { "tokens";
 | 
			
		||||
  identifier = Ct(C(R("az","AZ","__") * R("09","az","AZ","__")^0) * Cc"identifier"),
 | 
			
		||||
 | 
			
		||||
  -- Single character in a string
 | 
			
		||||
  string_char = R("az","AZ","09") + S"$%^&*()_-+={[}]:;@~#<,>.!?/ \t" + (P"\\" * S[[ntvbrfa\?'"0x]]),
 | 
			
		||||
  sstring_char = R("\001&","([","]\255") + (P"\\" * S[[ntvbrfa\?'"0x]]),
 | 
			
		||||
  dstring_char = R("\001!","#[","]\255") + (P"\\" * S[[ntvbrfa\?'"0x]]),
 | 
			
		||||
 | 
			
		||||
  -- String literal
 | 
			
		||||
  string = Ct(C(P"'" * (V"string_char" + P'"')^0 * P"'" +
 | 
			
		||||
                P'"' * (V"string_char" + P"'")^0 * P'"') * Cc"string"),
 | 
			
		||||
  string = Ct(C(P"'" * (V"sstring_char" + P'"')^0 * P"'" +
 | 
			
		||||
                P'"' * (V"dstring_char" + P"'")^0 * P'"') * Cc"string"),
 | 
			
		||||
 | 
			
		||||
  -- Operator
 | 
			
		||||
  operator = Ct(C(P">>=" + P"<<=" + P"..." +
 | 
			
		||||
 
 | 
			
		||||
@@ -316,7 +316,7 @@ local function alloc_log_new()
 | 
			
		||||
    eq(exp, self.log)
 | 
			
		||||
    self:clear()
 | 
			
		||||
  end
 | 
			
		||||
  function log:clear_tmp_allocs()
 | 
			
		||||
  function log:clear_tmp_allocs(clear_null_frees)
 | 
			
		||||
    local toremove = {}
 | 
			
		||||
    local allocs = {}
 | 
			
		||||
    for i, v in ipairs(self.log) do
 | 
			
		||||
@@ -328,6 +328,8 @@ local function alloc_log_new()
 | 
			
		||||
          if v.func == 'free' then
 | 
			
		||||
            toremove[#toremove + 1] = i
 | 
			
		||||
          end
 | 
			
		||||
        elseif clear_null_frees and v.args[1] == self.null then
 | 
			
		||||
          toremove[#toremove + 1] = i
 | 
			
		||||
        end
 | 
			
		||||
        if v.func == 'realloc' then
 | 
			
		||||
          allocs[tostring(v.ret)] = i
 | 
			
		||||
@@ -528,9 +530,13 @@ local hook_numlen = 5
 | 
			
		||||
local hook_msglen = 1 + 1 + 1 + (1 + hook_fnamelen) + (1 + hook_sfnamelen) + (1 + hook_numlen) + 1
 | 
			
		||||
 | 
			
		||||
local tracehelp = dedent([[
 | 
			
		||||
  Trace: either in the format described below or custom debug output starting
 | 
			
		||||
  with `>`. Latter lines still have the same width in byte.
 | 
			
		||||
 | 
			
		||||
  ┌ Trace type: _r_eturn from function , function _c_all, _l_ine executed,
 | 
			
		||||
  │             _t_ail return, _C_ount (should not actually appear),
 | 
			
		||||
  │             _s_aved from previous run for reference.
 | 
			
		||||
  │             _s_aved from previous run for reference, _>_ for custom debug
 | 
			
		||||
  │             output.
 | 
			
		||||
  │┏ Function type: _L_ua function, _C_ function, _m_ain part of chunk,
 | 
			
		||||
  │┃                function that did _t_ail call.
 | 
			
		||||
  │┃┌ Function name type: _g_lobal, _l_ocal, _m_ethod, _f_ield, _u_pvalue,
 | 
			
		||||
@@ -628,14 +634,26 @@ end
 | 
			
		||||
 | 
			
		||||
local trace_end_msg = ('E%s\n'):format((' '):rep(hook_msglen - 2))
 | 
			
		||||
 | 
			
		||||
local _debug_log
 | 
			
		||||
 | 
			
		||||
local debug_log = only_separate(function(...)
 | 
			
		||||
  return _debug_log(...)
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
local function itp_child(wr, func)
 | 
			
		||||
  init()
 | 
			
		||||
  _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)
 | 
			
		||||
  local err, emsg = pcall(func)
 | 
			
		||||
    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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										428
									
								
								test/unit/viml/expressions/lexer_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										428
									
								
								test/unit/viml/expressions/lexer_spec.lua
									
									
									
									
									
										Normal 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)
 | 
			
		||||
							
								
								
									
										540
									
								
								test/unit/viml/expressions/parser_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										540
									
								
								test/unit/viml/expressions/parser_spec.lua
									
									
									
									
									
										Normal 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)
 | 
			
		||||
							
								
								
									
										8317
									
								
								test/unit/viml/expressions/parser_tests.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8317
									
								
								test/unit/viml/expressions/parser_tests.lua
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										130
									
								
								test/unit/viml/helpers.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								test/unit/viml/helpers.lua
									
									
									
									
									
										Normal 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,
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user