mirror of
https://github.com/neovim/neovim.git
synced 2026-01-05 12:57:45 +00:00
viml/parser/expressions: Start creating expressions parser
Currently supported nodes: - Register as it is one of the simplest value nodes (even numbers are not that simple with that dot handling). - Plus, both unary and binary. - Parenthesis, both nesting and calling. Note regarding unit tests: it stores data for AST in highlighting in strings in place of tables because luassert fails to do a good job at representing big tables. Squashing a bunch of data into a single string simply yields more readable result.
This commit is contained in:
@@ -13,10 +13,18 @@
|
|||||||
#include "nvim/types.h"
|
#include "nvim/types.h"
|
||||||
#include "nvim/charset.h"
|
#include "nvim/charset.h"
|
||||||
#include "nvim/ascii.h"
|
#include "nvim/ascii.h"
|
||||||
|
#include "nvim/lib/kvec.h"
|
||||||
|
|
||||||
#include "nvim/viml/parser/expressions.h"
|
#include "nvim/viml/parser/expressions.h"
|
||||||
#include "nvim/viml/parser/parser.h"
|
#include "nvim/viml/parser/parser.h"
|
||||||
|
|
||||||
|
typedef kvec_withinit_t(ExprASTNode **, 16) ExprASTStack;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
kELvlOperator, ///< Operators: function call, subscripts, binary operators, …
|
||||||
|
kELvlValue, ///< Actual value: literals, variables, nested expressions.
|
||||||
|
} ExprASTLevel;
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "viml/parser/expressions.c.generated.h"
|
# include "viml/parser/expressions.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -144,6 +152,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek)
|
|||||||
|
|
||||||
// Environment variable.
|
// Environment variable.
|
||||||
case '$': {
|
case '$': {
|
||||||
|
// FIXME: Parser function can’t be thread-safe with vim_isIDc.
|
||||||
CHARREG(kExprLexEnv, vim_isIDc);
|
CHARREG(kExprLexEnv, vim_isIDc);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -183,6 +192,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const bool peek)
|
|||||||
ret.data.var.autoload = (
|
ret.data.var.autoload = (
|
||||||
memchr(pline.data + 2, AUTOLOAD_CHAR, ret.len - 2)
|
memchr(pline.data + 2, AUTOLOAD_CHAR, ret.len - 2)
|
||||||
!= NULL);
|
!= NULL);
|
||||||
|
// FIXME: Resolve ambiguity with an argument to the lexer function.
|
||||||
// Previous CHARREG stopped at autoload character in order to make it
|
// Previous CHARREG stopped at autoload character in order to make it
|
||||||
// possible to detect `is#`. Continue now with autoload characters
|
// possible to detect `is#`. Continue now with autoload characters
|
||||||
// included.
|
// included.
|
||||||
@@ -372,3 +382,618 @@ viml_pexpr_next_token_adv_return:
|
|||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start = s ternary_expr s EOC
|
||||||
|
// ternary_expr = binop_expr
|
||||||
|
// ( s Question s ternary_expr s Colon s ternary_expr s )?
|
||||||
|
// binop_expr = unaryop_expr ( binop unaryop_expr )?
|
||||||
|
// unaryop_expr = ( unaryop )? subscript_expr
|
||||||
|
// subscript_expr = subscript_expr subscript
|
||||||
|
// | value_expr
|
||||||
|
// subscript = Bracket('[') s ternary_expr s Bracket(']')
|
||||||
|
// | s Parenthesis('(') call_args Parenthesis(')')
|
||||||
|
// | Dot ( PlainIdentifier | Number )+
|
||||||
|
// # Note: `s` before Parenthesis('(') is only valid if preceding subscript_expr
|
||||||
|
// # is PlainIdentifier
|
||||||
|
// value_expr = ( float | Number
|
||||||
|
// | DoubleQuotedString | SingleQuotedString
|
||||||
|
// | paren_expr
|
||||||
|
// | list_literal
|
||||||
|
// | lambda_literal
|
||||||
|
// | dict_literal
|
||||||
|
// | Environment
|
||||||
|
// | Option
|
||||||
|
// | Register
|
||||||
|
// | var )
|
||||||
|
// float = Number Dot Number ( PlainIdentifier('e') ( Plus | Minus )? Number )?
|
||||||
|
// # Note: `1.2.3` is concat and not float. `"abc".2.3` is also concat without
|
||||||
|
// # floats.
|
||||||
|
// paren_expr = Parenthesis('(') s ternary_expr s Parenthesis(')')
|
||||||
|
// list_literal = Bracket('[') s
|
||||||
|
// ( ternary_expr s Comma s )*
|
||||||
|
// ternary_expr? s
|
||||||
|
// Bracket(']')
|
||||||
|
// dict_literal = FigureBrace('{') s
|
||||||
|
// ( ternary_expr s Colon s ternary_expr s Comma s )*
|
||||||
|
// ( ternary_expr s Colon s ternary_expr s )?
|
||||||
|
// FigureBrace('}')
|
||||||
|
// lambda_literal = FigureBrace('{') s
|
||||||
|
// ( PlainIdentifier s Comma s )*
|
||||||
|
// PlainIdentifier s
|
||||||
|
// Arrow s
|
||||||
|
// ternary_expr s
|
||||||
|
// FigureBrace('}')
|
||||||
|
// var = varchunk+
|
||||||
|
// varchunk = PlainIdentifier
|
||||||
|
// | Comparison("is" | "is#" | "isnot" | "isnot#")
|
||||||
|
// | FigureBrace('{') s ternary_expr s FigureBrace('}')
|
||||||
|
// call_args = ( s ternary_expr s Comma s )* s ternary_expr? s
|
||||||
|
// binop = s ( Plus | Minus | Dot
|
||||||
|
// | Comparison
|
||||||
|
// | Multiplication
|
||||||
|
// | Or
|
||||||
|
// | And ) s
|
||||||
|
// unaryop = s ( Not | Plus | Minus ) s
|
||||||
|
// s = Spacing?
|
||||||
|
//
|
||||||
|
// Binary operator precedence and associativity:
|
||||||
|
//
|
||||||
|
// Operator | Precedence | Associativity
|
||||||
|
// ---------+------------+-----------------
|
||||||
|
// || | 2 | left
|
||||||
|
// && | 3 | left
|
||||||
|
// cmp* | 4 | not associative
|
||||||
|
// + - . | 5 | left
|
||||||
|
// * / % | 6 | left
|
||||||
|
//
|
||||||
|
// * comparison operators:
|
||||||
|
//
|
||||||
|
// == ==# ==? != !=# !=?
|
||||||
|
// =~ =~# =~? !~ !~# !~?
|
||||||
|
// > ># >? <= <=# <=?
|
||||||
|
// < <# <? >= >=# >=?
|
||||||
|
// is is# is? isnot isnot# isnot?
|
||||||
|
//
|
||||||
|
// Used highlighting groups and assumed linkage:
|
||||||
|
//
|
||||||
|
// NVimInvalid -> Error
|
||||||
|
// NVimInvalidValue -> NVimInvalid
|
||||||
|
// NVimInvalidOperator -> NVimInvalid
|
||||||
|
// NVimInvalidDelimiter -> NVimInvalid
|
||||||
|
//
|
||||||
|
// NVimOperator -> Operator
|
||||||
|
// NVimUnaryOperator -> NVimOperator
|
||||||
|
// NVimBinaryOperator -> NVimOperator
|
||||||
|
// NVimComparisonOperator -> NVimOperator
|
||||||
|
// NVimTernaryOperator -> NVimOperator
|
||||||
|
//
|
||||||
|
// NVimParenthesis -> Delimiter
|
||||||
|
//
|
||||||
|
// NVimInvalidSpacing -> NVimInvalid
|
||||||
|
// NVimInvalidTernaryOperator -> NVimInvalidOperator
|
||||||
|
// NVimInvalidRegister -> NVimInvalidValue
|
||||||
|
// NVimInvalidClosingBracket -> NVimInvalidDelimiter
|
||||||
|
// NVimInvalidSpacing -> NVimInvalid
|
||||||
|
//
|
||||||
|
// NVimUnaryPlus -> NVimUnaryOperator
|
||||||
|
// NVimBinaryPlus -> NVimBinaryOperator
|
||||||
|
// NVimRegister -> SpecialChar
|
||||||
|
// NVimNestingParenthesis -> NVimParenthesis
|
||||||
|
// NVimCallingParenthesis -> NVimParenthesis
|
||||||
|
|
||||||
|
/// Allocate a new node and set some of the values
|
||||||
|
///
|
||||||
|
/// @param[in] type Node type to allocate.
|
||||||
|
/// @param[in] level Node level to allocate
|
||||||
|
static inline ExprASTNode *viml_pexpr_new_node(const ExprASTNodeType type)
|
||||||
|
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
|
||||||
|
{
|
||||||
|
ExprASTNode *ret = xmalloc(sizeof(*ret));
|
||||||
|
ret->type = type;
|
||||||
|
ret->children = NULL;
|
||||||
|
ret->next = NULL;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
kEOpLvlInvalid = 0,
|
||||||
|
kEOpLvlParens,
|
||||||
|
kEOpLvlTernary,
|
||||||
|
kEOpLvlOr,
|
||||||
|
kEOpLvlAnd,
|
||||||
|
kEOpLvlComparison,
|
||||||
|
kEOpLvlAddition, ///< Addition, subtraction and concatenation.
|
||||||
|
kEOpLvlMultiplication, ///< Multiplication, division and modulo.
|
||||||
|
kEOpLvlUnary, ///< Unary operations: not, minus, plus.
|
||||||
|
kEOpLvlSubscript, ///< Subscripts.
|
||||||
|
kEOpLvlValue, ///< Values: literals, variables, nested expressions, …
|
||||||
|
} ExprOpLvl;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
kEOpAssNo= 'n', ///< Not associative / not applicable.
|
||||||
|
kEOpAssLeft = 'l', ///< Left associativity.
|
||||||
|
kEOpAssRight = 'r', ///< Right associativity.
|
||||||
|
} ExprOpAssociativity;
|
||||||
|
|
||||||
|
static const ExprOpLvl node_type_to_op_lvl[] = {
|
||||||
|
[kExprNodeMissing] = kEOpLvlInvalid,
|
||||||
|
[kExprNodeOpMissing] = kEOpLvlMultiplication,
|
||||||
|
|
||||||
|
[kExprNodeNested] = kEOpLvlParens,
|
||||||
|
[kExprNodeComplexIdentifier] = kEOpLvlParens,
|
||||||
|
|
||||||
|
[kExprNodeTernary] = kEOpLvlTernary,
|
||||||
|
|
||||||
|
[kExprNodeBinaryPlus] = kEOpLvlAddition,
|
||||||
|
|
||||||
|
[kExprNodeUnaryPlus] = kEOpLvlUnary,
|
||||||
|
|
||||||
|
[kExprNodeSubscript] = kEOpLvlSubscript,
|
||||||
|
[kExprNodeCall] = kEOpLvlSubscript,
|
||||||
|
|
||||||
|
[kExprNodeRegister] = kEOpLvlValue,
|
||||||
|
[kExprNodeListLiteral] = kEOpLvlValue,
|
||||||
|
[kExprNodePlainIdentifier] = kEOpLvlValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const ExprOpAssociativity node_type_to_op_ass[] = {
|
||||||
|
[kExprNodeMissing] = kEOpAssNo,
|
||||||
|
[kExprNodeOpMissing] = kEOpAssNo,
|
||||||
|
|
||||||
|
[kExprNodeNested] = kEOpAssNo,
|
||||||
|
[kExprNodeComplexIdentifier] = kEOpAssLeft,
|
||||||
|
|
||||||
|
[kExprNodeTernary] = kEOpAssNo,
|
||||||
|
|
||||||
|
[kExprNodeBinaryPlus] = kEOpAssLeft,
|
||||||
|
|
||||||
|
[kExprNodeUnaryPlus] = kEOpAssNo,
|
||||||
|
|
||||||
|
[kExprNodeSubscript] = kEOpAssLeft,
|
||||||
|
[kExprNodeCall] = kEOpAssLeft,
|
||||||
|
|
||||||
|
[kExprNodeRegister] = kEOpAssNo,
|
||||||
|
[kExprNodeListLiteral] = kEOpAssNo,
|
||||||
|
[kExprNodePlainIdentifier] = kEOpAssNo,
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef UNIT_TESTING
|
||||||
|
#include <stdio.h>
|
||||||
|
REAL_FATTR_UNUSED
|
||||||
|
static inline void viml_pexpr_debug_print_ast_stack(
|
||||||
|
const ExprASTStack *const ast_stack,
|
||||||
|
const char *const msg)
|
||||||
|
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE
|
||||||
|
{
|
||||||
|
fprintf(stderr, "\n%sstack: %zu:\n", msg, kv_size(*ast_stack));
|
||||||
|
for (size_t i = 0; i < kv_size(*ast_stack); i++) {
|
||||||
|
const ExprASTNode *const *const eastnode_p = (
|
||||||
|
(const ExprASTNode *const *)kv_A(*ast_stack, i));
|
||||||
|
if (*eastnode_p == NULL) {
|
||||||
|
fprintf(stderr, "- %p : NULL\n", (void *)eastnode_p);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "- %p : %p : %c : %zu:%zu:%zu\n",
|
||||||
|
(void *)eastnode_p, (void *)(*eastnode_p), (*eastnode_p)->type,
|
||||||
|
(*eastnode_p)->start.line, (*eastnode_p)->start.col,
|
||||||
|
(*eastnode_p)->len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#define PSTACK(msg) \
|
||||||
|
viml_pexpr_debug_print_ast_stack(&ast_stack, #msg)
|
||||||
|
#define PSTACK_P(msg) \
|
||||||
|
viml_pexpr_debug_print_ast_stack(ast_stack, #msg)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Handle binary operator
|
||||||
|
///
|
||||||
|
/// This function is responsible for handling priority levels as well.
|
||||||
|
static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack,
|
||||||
|
ExprASTNode *const bop_node,
|
||||||
|
ExprASTLevel *const want_level_p)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
ExprASTNode **top_node_p = NULL;
|
||||||
|
ExprASTNode *top_node;
|
||||||
|
ExprOpLvl top_node_lvl;
|
||||||
|
ExprOpAssociativity top_node_ass;
|
||||||
|
assert(kv_size(*ast_stack));
|
||||||
|
const ExprOpLvl bop_node_lvl = node_type_to_op_lvl[bop_node->type];
|
||||||
|
do {
|
||||||
|
ExprASTNode **new_top_node_p = kv_last(*ast_stack);
|
||||||
|
ExprASTNode *new_top_node = *new_top_node_p;
|
||||||
|
assert(new_top_node != NULL);
|
||||||
|
const ExprOpLvl new_top_node_lvl = node_type_to_op_lvl[new_top_node->type];
|
||||||
|
const ExprOpAssociativity new_top_node_ass = (
|
||||||
|
node_type_to_op_ass[new_top_node->type]);
|
||||||
|
if (top_node_p != NULL
|
||||||
|
&& ((bop_node_lvl > new_top_node_lvl
|
||||||
|
|| (bop_node_lvl == new_top_node_lvl
|
||||||
|
&& new_top_node_ass == kEOpAssNo)))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
kv_drop(*ast_stack, 1);
|
||||||
|
top_node_p = new_top_node_p;
|
||||||
|
top_node = new_top_node;
|
||||||
|
top_node_lvl = new_top_node_lvl;
|
||||||
|
top_node_ass = new_top_node_ass;
|
||||||
|
} while (kv_size(*ast_stack));
|
||||||
|
// FIXME Handle right and no associativity correctly
|
||||||
|
*top_node_p = bop_node;
|
||||||
|
bop_node->children = top_node;
|
||||||
|
assert(bop_node->children->next == NULL);
|
||||||
|
kvi_push(*ast_stack, top_node_p);
|
||||||
|
kvi_push(*ast_stack, &bop_node->children->next);
|
||||||
|
*want_level_p = kELvlValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get highlight group name
|
||||||
|
#define HL(g) (is_invalid ? "NVimInvalid" #g : "NVim" #g)
|
||||||
|
|
||||||
|
/// Highlight current token with the given group
|
||||||
|
#define HL_CUR_TOKEN(g) \
|
||||||
|
viml_parser_highlight(pstate, cur_token.start, cur_token.len, \
|
||||||
|
HL(g))
|
||||||
|
|
||||||
|
/// Allocate new node, saving some values
|
||||||
|
#define NEW_NODE(type) \
|
||||||
|
viml_pexpr_new_node(type)
|
||||||
|
|
||||||
|
/// Set position of the given node to position from the given token
|
||||||
|
///
|
||||||
|
/// @param cur_node Node to modify.
|
||||||
|
/// @param cur_token Token to set position from.
|
||||||
|
#define POS_FROM_TOKEN(cur_node, cur_token) \
|
||||||
|
do { \
|
||||||
|
cur_node->start = cur_token.start; \
|
||||||
|
cur_node->len = cur_token.len; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/// Allocate new node and set its position from the current token
|
||||||
|
///
|
||||||
|
/// If previous token happened to contain spacing then it will be included.
|
||||||
|
///
|
||||||
|
/// @param cur_node Variable to save allocated node to.
|
||||||
|
/// @param typ Node type.
|
||||||
|
#define NEW_NODE_WITH_CUR_POS(cur_node, typ) \
|
||||||
|
do { \
|
||||||
|
cur_node = NEW_NODE(typ); \
|
||||||
|
POS_FROM_TOKEN(cur_node, cur_token); \
|
||||||
|
if (prev_token.type == kExprLexSpacing) { \
|
||||||
|
cur_node->start = prev_token.start; \
|
||||||
|
cur_node->len += prev_token.len; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
// TODO(ZyX-I): actual condition
|
||||||
|
/// Check whether it is possible to have next expression after current
|
||||||
|
///
|
||||||
|
/// For :echo: `:echo @a @a` is a valid expression. `:echo (@a @a)` is not.
|
||||||
|
#define MAY_HAVE_NEXT_EXPR \
|
||||||
|
(kv_size(ast_stack) == 1)
|
||||||
|
|
||||||
|
/// Record missing operator: for things like
|
||||||
|
///
|
||||||
|
/// :echo @a @a
|
||||||
|
///
|
||||||
|
/// (allowed) or
|
||||||
|
///
|
||||||
|
/// :echo (@a @a)
|
||||||
|
///
|
||||||
|
/// (parsed as OpMissing(@a, @a)).
|
||||||
|
#define OP_MISSING \
|
||||||
|
do { \
|
||||||
|
if (flags & kExprFlagsMulti && MAY_HAVE_NEXT_EXPR) { \
|
||||||
|
/* Multiple expressions allowed, return without calling */ \
|
||||||
|
/* viml_parser_advance(). */ \
|
||||||
|
goto viml_pexpr_parse_end; \
|
||||||
|
} else { \
|
||||||
|
assert(*top_node_p != NULL); \
|
||||||
|
ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Missing operator: %.*s")); \
|
||||||
|
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOpMissing); \
|
||||||
|
cur_node->len = 0; \
|
||||||
|
viml_pexpr_handle_bop(&ast_stack, cur_node, &want_level); \
|
||||||
|
is_invalid = true; \
|
||||||
|
goto viml_pexpr_parse_process_token; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/// Set AST error, unless AST already is not correct
|
||||||
|
///
|
||||||
|
/// @param[out] ret_ast AST to set error in.
|
||||||
|
/// @param[in] pstate Parser state, used to get error message argument.
|
||||||
|
/// @param[in] msg Error message, assumed to be already translated and
|
||||||
|
/// containing a single %token "%.*s".
|
||||||
|
/// @param[in] start Position at which error occurred.
|
||||||
|
static inline void east_set_error(ExprAST *const ret_ast,
|
||||||
|
const ParserState *const pstate,
|
||||||
|
const char *const msg,
|
||||||
|
const ParserPosition start)
|
||||||
|
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE
|
||||||
|
{
|
||||||
|
if (!ret_ast->correct) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ParserLine pline = pstate->reader.lines.items[start.line];
|
||||||
|
ret_ast->correct = false;
|
||||||
|
ret_ast->err.msg = msg;
|
||||||
|
ret_ast->err.arg_len = (int)(pline.size - start.col);
|
||||||
|
ret_ast->err.arg = pline.data + start.col;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set error from the given kExprLexInvalid token and given message
|
||||||
|
#define ERROR_FROM_TOKEN_AND_MSG(cur_token, msg) \
|
||||||
|
east_set_error(&ast, pstate, msg, cur_token.start)
|
||||||
|
|
||||||
|
/// Set error from the given kExprLexInvalid token
|
||||||
|
#define ERROR_FROM_TOKEN(cur_token) \
|
||||||
|
ERROR_FROM_TOKEN_AND_MSG(cur_token, cur_token.data.err.msg)
|
||||||
|
|
||||||
|
/// Parse one VimL expression
|
||||||
|
///
|
||||||
|
/// @param pstate Parser state.
|
||||||
|
/// @param[in] flags Additional flags, see ExprParserFlags
|
||||||
|
///
|
||||||
|
/// @return Parsed AST.
|
||||||
|
ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags)
|
||||||
|
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
ExprAST ast = {
|
||||||
|
.correct = true,
|
||||||
|
.err = {
|
||||||
|
.msg = NULL,
|
||||||
|
.arg_len = 0,
|
||||||
|
.arg = NULL,
|
||||||
|
},
|
||||||
|
.root = NULL,
|
||||||
|
};
|
||||||
|
ExprASTStack ast_stack;
|
||||||
|
kvi_init(ast_stack);
|
||||||
|
kvi_push(ast_stack, &ast.root);
|
||||||
|
// Expressions stack:
|
||||||
|
// 1. *last is NULL if want_level is kExprLexValue. Indicates where expression
|
||||||
|
// is to be put.
|
||||||
|
// 2. *last is not NULL otherwise, indicates current expression to be used as
|
||||||
|
// an operator argument.
|
||||||
|
ExprASTLevel want_level = kELvlValue;
|
||||||
|
LexExprToken prev_token = { .type = kExprLexMissing };
|
||||||
|
bool highlighted_prev_spacing = false;
|
||||||
|
do {
|
||||||
|
LexExprToken cur_token = viml_pexpr_next_token(pstate, true);
|
||||||
|
if (cur_token.type == kExprLexEOC) {
|
||||||
|
if (flags & kExprFlagsDisallowEOC) {
|
||||||
|
if (cur_token.len == 0) {
|
||||||
|
// It is end of string, break.
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// It is NL, NUL or bar.
|
||||||
|
//
|
||||||
|
// Note: `<C-r>=1 | 2<CR>` actually yields 1 in Vim without any
|
||||||
|
// errors. This will be changed here.
|
||||||
|
cur_token.type = kExprLexInvalid;
|
||||||
|
cur_token.data.err.msg = _("E15: Unexpected EOC character: %.*s");
|
||||||
|
const ParserLine pline = (
|
||||||
|
pstate->reader.lines.items[cur_token.start.line]);
|
||||||
|
const char eoc_char = pline.data[cur_token.start.col];
|
||||||
|
cur_token.data.err.type = ((eoc_char == NUL || eoc_char == NL)
|
||||||
|
? kExprLexSpacing
|
||||||
|
: kExprLexOr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LexExprTokenType tok_type = cur_token.type;
|
||||||
|
const bool token_invalid = (tok_type == kExprLexInvalid);
|
||||||
|
bool is_invalid = token_invalid;
|
||||||
|
viml_pexpr_parse_process_token:
|
||||||
|
if (tok_type == kExprLexSpacing) {
|
||||||
|
if (is_invalid) {
|
||||||
|
viml_parser_highlight(pstate, cur_token.start, cur_token.len,
|
||||||
|
HL(Spacing));
|
||||||
|
} else {
|
||||||
|
// Do not do anything: let regular spacing be highlighted as normal.
|
||||||
|
// This also allows later to highlight spacing as invalid.
|
||||||
|
}
|
||||||
|
goto viml_pexpr_parse_cycle_end;
|
||||||
|
} else if (is_invalid && prev_token.type == kExprLexSpacing
|
||||||
|
&& !highlighted_prev_spacing) {
|
||||||
|
viml_parser_highlight(pstate, prev_token.start, prev_token.len,
|
||||||
|
HL(Spacing));
|
||||||
|
is_invalid = false;
|
||||||
|
highlighted_prev_spacing = true;
|
||||||
|
}
|
||||||
|
ExprASTNode **const top_node_p = kv_last(ast_stack);
|
||||||
|
ExprASTNode *cur_node = NULL;
|
||||||
|
// Keep these two asserts separate for debugging purposes.
|
||||||
|
assert(want_level == kELvlValue || *top_node_p != NULL);
|
||||||
|
assert(want_level != kELvlValue || *top_node_p == NULL);
|
||||||
|
switch (tok_type) {
|
||||||
|
case kExprLexEOC: {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
case kExprLexInvalid: {
|
||||||
|
ERROR_FROM_TOKEN(cur_token);
|
||||||
|
tok_type = cur_token.data.err.type;
|
||||||
|
goto viml_pexpr_parse_process_token;
|
||||||
|
}
|
||||||
|
case kExprLexRegister: {
|
||||||
|
if (want_level == kELvlValue) {
|
||||||
|
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeRegister);
|
||||||
|
cur_node->data.reg.name = cur_token.data.reg.name;
|
||||||
|
*top_node_p = cur_node;
|
||||||
|
want_level = kELvlOperator;
|
||||||
|
viml_parser_highlight(pstate, cur_token.start, cur_token.len,
|
||||||
|
HL(Register));
|
||||||
|
} else {
|
||||||
|
// Register in operator position: e.g. @a @a
|
||||||
|
OP_MISSING;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kExprLexPlus: {
|
||||||
|
if (want_level == kELvlValue) {
|
||||||
|
// Value level: assume unary plus
|
||||||
|
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnaryPlus);
|
||||||
|
*top_node_p = cur_node;
|
||||||
|
kvi_push(ast_stack, &cur_node->children);
|
||||||
|
HL_CUR_TOKEN(UnaryPlus);
|
||||||
|
} else if (want_level < kELvlValue) {
|
||||||
|
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinaryPlus);
|
||||||
|
viml_pexpr_handle_bop(&ast_stack, cur_node, &want_level);
|
||||||
|
HL_CUR_TOKEN(BinaryPlus);
|
||||||
|
}
|
||||||
|
want_level = kELvlValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kExprLexParenthesis: {
|
||||||
|
if (cur_token.data.brc.closing) {
|
||||||
|
if (want_level == kELvlValue) {
|
||||||
|
if (kv_size(ast_stack) > 1) {
|
||||||
|
const ExprASTNode *const prev_top_node = *kv_Z(ast_stack, 1);
|
||||||
|
if (prev_top_node->type == kExprNodeCall) {
|
||||||
|
// Function call without arguments, this is not an error.
|
||||||
|
// But further code does not expect NULL nodes.
|
||||||
|
kv_drop(ast_stack, 1);
|
||||||
|
goto viml_pexpr_parse_no_paren_closing_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is_invalid = true;
|
||||||
|
ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Expected value: %.*s"));
|
||||||
|
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing);
|
||||||
|
cur_node->len = 0;
|
||||||
|
*top_node_p = cur_node;
|
||||||
|
} else {
|
||||||
|
// Always drop the topmost value: when want_level != kELvlValue
|
||||||
|
// topmost item on stack is a *finished* left operand, which may as
|
||||||
|
// well be "(@a)" which needs not be finished.
|
||||||
|
kv_drop(ast_stack, 1);
|
||||||
|
}
|
||||||
|
viml_pexpr_parse_no_paren_closing_error: {}
|
||||||
|
ExprASTNode **new_top_node_p = NULL;
|
||||||
|
while (kv_size(ast_stack)
|
||||||
|
&& (new_top_node_p == NULL
|
||||||
|
|| ((*new_top_node_p)->type != kExprNodeNested
|
||||||
|
&& (*new_top_node_p)->type != kExprNodeCall))) {
|
||||||
|
new_top_node_p = kv_pop(ast_stack);
|
||||||
|
}
|
||||||
|
if (new_top_node_p != NULL
|
||||||
|
&& ((*new_top_node_p)->type == kExprNodeNested
|
||||||
|
|| (*new_top_node_p)->type == kExprNodeCall)) {
|
||||||
|
if ((*new_top_node_p)->type == kExprNodeNested) {
|
||||||
|
HL_CUR_TOKEN(NestingParenthesis);
|
||||||
|
} else {
|
||||||
|
HL_CUR_TOKEN(CallingParenthesis);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// “Always drop the topmost value” branch has got rid of the single
|
||||||
|
// value stack had, so there is nothing known to enclose. Correct
|
||||||
|
// this.
|
||||||
|
if (new_top_node_p == NULL) {
|
||||||
|
new_top_node_p = top_node_p;
|
||||||
|
}
|
||||||
|
is_invalid = true;
|
||||||
|
HL_CUR_TOKEN(NestingParenthesis);
|
||||||
|
ERROR_FROM_TOKEN_AND_MSG(
|
||||||
|
cur_token, _("E15: Unexpected closing parenthesis: %.*s"));
|
||||||
|
cur_node = NEW_NODE(kExprNodeNested);
|
||||||
|
cur_node->start = cur_token.start;
|
||||||
|
cur_node->len = 0;
|
||||||
|
// Unexpected closing parenthesis, assume that it was wanted to
|
||||||
|
// enclose everything in ().
|
||||||
|
cur_node->children = *new_top_node_p;
|
||||||
|
*new_top_node_p = cur_node;
|
||||||
|
assert(cur_node->next == NULL);
|
||||||
|
}
|
||||||
|
kvi_push(ast_stack, new_top_node_p);
|
||||||
|
want_level = kELvlOperator;
|
||||||
|
} else {
|
||||||
|
if (want_level == kELvlValue) {
|
||||||
|
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeNested);
|
||||||
|
*top_node_p = cur_node;
|
||||||
|
kvi_push(ast_stack, &cur_node->children);
|
||||||
|
HL_CUR_TOKEN(NestingParenthesis);
|
||||||
|
} else if (want_level == kELvlOperator) {
|
||||||
|
if (prev_token.type == kExprLexSpacing) {
|
||||||
|
// For some reason "function (args)" is a function call, but
|
||||||
|
// "(funcref) (args)" is not. AFAIR this somehow involves
|
||||||
|
// compatibility and Bram was commenting that this is
|
||||||
|
// intentionally inconsistent and he is not very happy with the
|
||||||
|
// situation himself.
|
||||||
|
if ((*top_node_p)->type != kExprNodePlainIdentifier
|
||||||
|
&& (*top_node_p)->type != kExprNodeComplexIdentifier) {
|
||||||
|
OP_MISSING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCall);
|
||||||
|
viml_pexpr_handle_bop(&ast_stack, cur_node, &want_level);
|
||||||
|
HL_CUR_TOKEN(CallingParenthesis);
|
||||||
|
} else {
|
||||||
|
// Currently it is impossible to reach this.
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
want_level = kELvlValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viml_pexpr_parse_cycle_end:
|
||||||
|
prev_token = cur_token;
|
||||||
|
highlighted_prev_spacing = false;
|
||||||
|
viml_parser_advance(pstate, cur_token.len);
|
||||||
|
} while (true);
|
||||||
|
viml_pexpr_parse_end:
|
||||||
|
if (want_level == kELvlValue) {
|
||||||
|
east_set_error(&ast, pstate, _("E15: Expected value: %.*s"), pstate->pos);
|
||||||
|
} else if (kv_size(ast_stack) != 1) {
|
||||||
|
// Something may be wrong, check whether it really is.
|
||||||
|
|
||||||
|
// Pointer to ast.root must never be dropped, so “!= 1” is expected to be
|
||||||
|
// the same as “> 1”.
|
||||||
|
assert(kv_size(ast_stack));
|
||||||
|
// Topmost stack item must be a *finished* value, so it must not be
|
||||||
|
// analyzed. E.g. it may contain an already finished nested expression.
|
||||||
|
kv_drop(ast_stack, 1);
|
||||||
|
while (ast.correct && kv_size(ast_stack)) {
|
||||||
|
const ExprASTNode *const cur_node = (*kv_pop(ast_stack));
|
||||||
|
// This should only happen when want_level == kELvlValue.
|
||||||
|
assert(cur_node != NULL);
|
||||||
|
switch (cur_node->type) {
|
||||||
|
case kExprNodeOpMissing:
|
||||||
|
case kExprNodeMissing: {
|
||||||
|
// Error should’ve been already reported.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kExprNodeCall: {
|
||||||
|
// TODO(ZyX-I): Rehighlight as invalid?
|
||||||
|
east_set_error(
|
||||||
|
&ast, pstate,
|
||||||
|
_("E116: Missing closing parenthesis for function call: %.*s"),
|
||||||
|
cur_node->start);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kExprNodeNested: {
|
||||||
|
// TODO(ZyX-I): Rehighlight as invalid?
|
||||||
|
east_set_error(
|
||||||
|
&ast, pstate,
|
||||||
|
_("E110: Missing closing parenthesis for nested expression"
|
||||||
|
": %.*s"),
|
||||||
|
cur_node->start);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kExprNodeBinaryPlus:
|
||||||
|
case kExprNodeUnaryPlus:
|
||||||
|
case kExprNodeRegister: {
|
||||||
|
// It is OK to see these in the stack.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// TODO(ZyX-I): handle other values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kvi_destroy(ast_stack);
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef NEW_NODE
|
||||||
|
#undef HL
|
||||||
|
|||||||
@@ -111,6 +111,80 @@ typedef struct {
|
|||||||
} data; ///< Additional data, if needed.
|
} data; ///< Additional data, if needed.
|
||||||
} LexExprToken;
|
} LexExprToken;
|
||||||
|
|
||||||
|
/// Expression AST node type
|
||||||
|
typedef enum {
|
||||||
|
kExprNodeMissing = 'X',
|
||||||
|
kExprNodeOpMissing = '_',
|
||||||
|
kExprNodeTernary = '?', ///< Ternary operator, valid one has three children.
|
||||||
|
kExprNodeRegister = '@', ///< Register, no children.
|
||||||
|
kExprNodeSubscript = 's', ///< Subscript, should have two or three children.
|
||||||
|
kExprNodeListLiteral = 'l', ///< List literal, any number of children.
|
||||||
|
kExprNodeUnaryPlus = 'p',
|
||||||
|
kExprNodeBinaryPlus = '+',
|
||||||
|
kExprNodeNested = 'e', ///< Nested parenthesised expression.
|
||||||
|
kExprNodeCall = 'c', ///< Function call.
|
||||||
|
/// Plain identifier: simple variable/function name
|
||||||
|
///
|
||||||
|
/// Looks like "string", "g:Foo", etc: consists from a single
|
||||||
|
/// kExprLexPlainIdentifier token.
|
||||||
|
kExprNodePlainIdentifier = 'i',
|
||||||
|
/// Complex identifier: variable/function name with curly braces
|
||||||
|
kExprNodeComplexIdentifier = 'I',
|
||||||
|
} 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.
|
||||||
|
} 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),
|
||||||
|
/// Print errors when encountered
|
||||||
|
///
|
||||||
|
/// Without the flag they are only taken into account when parsing.
|
||||||
|
kExprFlagsPrintError = (1 << 2),
|
||||||
|
} ExprParserFlags;
|
||||||
|
|
||||||
|
/// Structure representing complety AST for one expression
|
||||||
|
typedef struct {
|
||||||
|
/// True if represented AST is correct and can be executed. Incorrect ones may
|
||||||
|
/// still be used for completion, or in linters.
|
||||||
|
bool correct;
|
||||||
|
/// When AST is not correct this message will be printed.
|
||||||
|
///
|
||||||
|
/// Uses `emsgf(msg, arg_len, arg);`, `msg` is assumed to contain only `%.*s`.
|
||||||
|
struct {
|
||||||
|
const char *msg;
|
||||||
|
int arg_len;
|
||||||
|
const char *arg;
|
||||||
|
} err;
|
||||||
|
/// Root node of the AST.
|
||||||
|
ExprASTNode *root;
|
||||||
|
} ExprAST;
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "viml/parser/expressions.h.generated.h"
|
# include "viml/parser/expressions.h.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
local helpers = require('test.unit.helpers')(nil)
|
local helpers = require('test.unit.helpers')(nil)
|
||||||
|
|
||||||
|
local ptr2key = helpers.ptr2key
|
||||||
local cimport = helpers.cimport
|
local cimport = helpers.cimport
|
||||||
local to_cstr = helpers.to_cstr
|
local to_cstr = helpers.to_cstr
|
||||||
local ffi = helpers.ffi
|
local ffi = helpers.ffi
|
||||||
@@ -91,10 +92,6 @@ local function populate_partial(pt, lua_pt, processed)
|
|||||||
return pt
|
return pt
|
||||||
end
|
end
|
||||||
|
|
||||||
local ptr2key = function(ptr)
|
|
||||||
return tostring(ptr)
|
|
||||||
end
|
|
||||||
|
|
||||||
local lst2tbl
|
local lst2tbl
|
||||||
local dct2tbl
|
local dct2tbl
|
||||||
|
|
||||||
|
|||||||
@@ -783,6 +783,31 @@ local function kvi_new(ct)
|
|||||||
return kvi_init(ffi.new(ct))
|
return kvi_init(ffi.new(ct))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function make_enum_conv_tab(lib, 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(lib[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 = {
|
local module = {
|
||||||
cimport = cimport,
|
cimport = cimport,
|
||||||
cppimport = cppimport,
|
cppimport = cppimport,
|
||||||
@@ -808,6 +833,9 @@ local module = {
|
|||||||
kvi_size = kvi_size,
|
kvi_size = kvi_size,
|
||||||
kvi_init = kvi_init,
|
kvi_init = kvi_init,
|
||||||
kvi_new = kvi_new,
|
kvi_new = kvi_new,
|
||||||
|
make_enum_conv_tab = make_enum_conv_tab,
|
||||||
|
ptr2addr = ptr2addr,
|
||||||
|
ptr2key = ptr2key,
|
||||||
}
|
}
|
||||||
return function()
|
return function()
|
||||||
return module
|
return module
|
||||||
|
|||||||
887
test/unit/viml/expressions/parser_spec.lua
Normal file
887
test/unit/viml/expressions/parser_spec.lua
Normal file
@@ -0,0 +1,887 @@
|
|||||||
|
local helpers = require('test.unit.helpers')(after_each)
|
||||||
|
local viml_helpers = require('test.unit.viml.helpers')
|
||||||
|
local itp = helpers.gen_itp(it)
|
||||||
|
|
||||||
|
local make_enum_conv_tab = helpers.make_enum_conv_tab
|
||||||
|
local child_call_once = helpers.child_call_once
|
||||||
|
local conv_enum = helpers.conv_enum
|
||||||
|
local ptr2key = helpers.ptr2key
|
||||||
|
local cimport = helpers.cimport
|
||||||
|
local ffi = helpers.ffi
|
||||||
|
local eq = helpers.eq
|
||||||
|
|
||||||
|
local pline2lua = viml_helpers.pline2lua
|
||||||
|
local new_pstate = viml_helpers.new_pstate
|
||||||
|
local intchar2lua = viml_helpers.intchar2lua
|
||||||
|
local pstate_set_str = viml_helpers.pstate_set_str
|
||||||
|
|
||||||
|
local lib = cimport('./src/nvim/viml/parser/expressions.h')
|
||||||
|
|
||||||
|
local east_node_type_tab
|
||||||
|
make_enum_conv_tab(lib, {
|
||||||
|
'kExprNodeMissing',
|
||||||
|
'kExprNodeOpMissing',
|
||||||
|
'kExprNodeTernary',
|
||||||
|
'kExprNodeRegister',
|
||||||
|
'kExprNodeSubscript',
|
||||||
|
'kExprNodeListLiteral',
|
||||||
|
'kExprNodeUnaryPlus',
|
||||||
|
'kExprNodeBinaryPlus',
|
||||||
|
'kExprNodeNested',
|
||||||
|
'kExprNodeCall',
|
||||||
|
'kExprNodePlainIdentifier',
|
||||||
|
'kExprNodeComplexIdentifier',
|
||||||
|
}, '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)))
|
||||||
|
end
|
||||||
|
ret_str = typ .. ':' .. ret_str
|
||||||
|
local can_simplify = true
|
||||||
|
for k, v in pairs(ret) do
|
||||||
|
can_simplify = false
|
||||||
|
end
|
||||||
|
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(pstate, east)
|
||||||
|
local checked_nodes = {}
|
||||||
|
return {
|
||||||
|
err = (not east.correct) and {
|
||||||
|
msg = ffi.string(east.err.msg),
|
||||||
|
arg = ('%u:%s'):format(
|
||||||
|
tonumber(east.err.arg_len),
|
||||||
|
ffi.string(east.err.arg, east.err.arg_len)),
|
||||||
|
} or nil,
|
||||||
|
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),
|
||||||
|
})
|
||||||
|
chunk_str = ('%s:%u:%u:%s'):format(
|
||||||
|
chunk_tbl.group,
|
||||||
|
chunk_tbl.start.line,
|
||||||
|
chunk_tbl.start.col,
|
||||||
|
chunk_tbl.str)
|
||||||
|
ret[i + 1] = chunk_str
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
child_call_once(function()
|
||||||
|
assert:set_parameter('TableFormatLevel', 1000000)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('Expressions parser', function()
|
||||||
|
itp('works', function()
|
||||||
|
local function check_parsing(str, flags, exp_ast, exp_highlighting_fs)
|
||||||
|
local pstate = new_pstate({str})
|
||||||
|
local east = lib.viml_pexpr_parse(pstate, flags)
|
||||||
|
local ast = east2lua(pstate, east)
|
||||||
|
eq(exp_ast, ast)
|
||||||
|
if exp_highlighting_fs then
|
||||||
|
local exp_highlighting = {}
|
||||||
|
local next_col = 0
|
||||||
|
for i, h in ipairs(exp_highlighting_fs) do
|
||||||
|
exp_highlighting[i], next_col = h(next_col)
|
||||||
|
end
|
||||||
|
eq(exp_highlighting, phl2lua(pstate))
|
||||||
|
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
|
||||||
|
check_parsing('@a', 0, {
|
||||||
|
ast = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
})
|
||||||
|
check_parsing('+@a', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'UnaryPlus:0:0:+',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:1:@a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('UnaryPlus', '+'),
|
||||||
|
hl('Register', '@a'),
|
||||||
|
})
|
||||||
|
check_parsing('@a+@b', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:2:+',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
'Register(name=b):0:3:@b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('BinaryPlus', '+'),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
})
|
||||||
|
check_parsing('@a+@b+@c', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:5:+',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:2:+',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
'Register(name=b):0:3:@b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Register(name=c):0:6:@c',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('BinaryPlus', '+'),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
hl('BinaryPlus', '+'),
|
||||||
|
hl('Register', '@c'),
|
||||||
|
})
|
||||||
|
check_parsing('+@a+@b', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:3:+',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'UnaryPlus:0:0:+',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:1:@a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Register(name=b):0:4:@b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('UnaryPlus', '+'),
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('BinaryPlus', '+'),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
})
|
||||||
|
check_parsing('+@a++@b', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:3:+',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'UnaryPlus:0:0:+',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:1:@a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'UnaryPlus:0:4:+',
|
||||||
|
children = {
|
||||||
|
'Register(name=b):0:5:@b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('UnaryPlus', '+'),
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('BinaryPlus', '+'),
|
||||||
|
hl('UnaryPlus', '+'),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
})
|
||||||
|
check_parsing('@a@b', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'OpMissing:0:2:',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
'Register(name=b):0:2:@b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err = {
|
||||||
|
arg = '2:@b',
|
||||||
|
msg = 'E15: Missing operator: %.*s',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('InvalidRegister', '@b'),
|
||||||
|
})
|
||||||
|
check_parsing(' @a \t @b', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'OpMissing:0:3:',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0: @a',
|
||||||
|
'Register(name=b):0:3: \t @b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err = {
|
||||||
|
arg = '2:@b',
|
||||||
|
msg = 'E15: Missing operator: %.*s',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a', 1),
|
||||||
|
hl('InvalidSpacing', ' \t '),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
})
|
||||||
|
check_parsing('+', 0, {
|
||||||
|
ast = {
|
||||||
|
'UnaryPlus:0:0:+',
|
||||||
|
},
|
||||||
|
err = {
|
||||||
|
arg = '0:',
|
||||||
|
msg = 'E15: Expected value: %.*s',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('UnaryPlus', '+'),
|
||||||
|
})
|
||||||
|
check_parsing(' +', 0, {
|
||||||
|
ast = {
|
||||||
|
'UnaryPlus:0:0: +',
|
||||||
|
},
|
||||||
|
err = {
|
||||||
|
arg = '0:',
|
||||||
|
msg = 'E15: Expected value: %.*s',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('UnaryPlus', '+', 1),
|
||||||
|
})
|
||||||
|
check_parsing('@a+ ', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:2:+',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err = {
|
||||||
|
arg = '0:',
|
||||||
|
msg = 'E15: Expected value: %.*s',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('BinaryPlus', '+'),
|
||||||
|
})
|
||||||
|
check_parsing('(@a)', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'Nested:0:0:(',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:1:@a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('NestingParenthesis', '('),
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('NestingParenthesis', ')'),
|
||||||
|
})
|
||||||
|
check_parsing('()', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'Nested:0:0:(',
|
||||||
|
children = {
|
||||||
|
'Missing:0:1:',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err = {
|
||||||
|
arg = '1:)',
|
||||||
|
msg = 'E15: Expected value: %.*s',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('NestingParenthesis', '('),
|
||||||
|
hl('InvalidNestingParenthesis', ')'),
|
||||||
|
})
|
||||||
|
check_parsing(')', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'Nested:0:0:',
|
||||||
|
children = {
|
||||||
|
'Missing:0:0:',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err = {
|
||||||
|
arg = '1:)',
|
||||||
|
msg = 'E15: Expected value: %.*s',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('InvalidNestingParenthesis', ')'),
|
||||||
|
})
|
||||||
|
check_parsing('+)', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'Nested:0:1:',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'UnaryPlus:0:0:+',
|
||||||
|
children = {
|
||||||
|
'Missing:0:1:',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err = {
|
||||||
|
arg = '1:)',
|
||||||
|
msg = 'E15: Expected value: %.*s',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('UnaryPlus', '+'),
|
||||||
|
hl('InvalidNestingParenthesis', ')'),
|
||||||
|
})
|
||||||
|
check_parsing('+@a(@b)', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'UnaryPlus:0:0:+',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'Call:0:3:(',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:1:@a',
|
||||||
|
'Register(name=b):0:4:@b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('UnaryPlus', '+'),
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('CallingParenthesis', '('),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
hl('CallingParenthesis', ')'),
|
||||||
|
})
|
||||||
|
check_parsing('@a+@b(@c)', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:2:+',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
{
|
||||||
|
'Call:0:5:(',
|
||||||
|
children = {
|
||||||
|
'Register(name=b):0:3:@b',
|
||||||
|
'Register(name=c):0:6:@c',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('BinaryPlus', '+'),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
hl('CallingParenthesis', '('),
|
||||||
|
hl('Register', '@c'),
|
||||||
|
hl('CallingParenthesis', ')'),
|
||||||
|
})
|
||||||
|
check_parsing('@a()', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'Call:0:2:(',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('CallingParenthesis', '('),
|
||||||
|
hl('CallingParenthesis', ')'),
|
||||||
|
})
|
||||||
|
check_parsing('@a ()', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'OpMissing:0:2:',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
{
|
||||||
|
'Nested:0:2: (',
|
||||||
|
children = {
|
||||||
|
'Missing:0:4:',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err = {
|
||||||
|
arg = '2:()',
|
||||||
|
msg = 'E15: Missing operator: %.*s',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('InvalidSpacing', ' '),
|
||||||
|
hl('NestingParenthesis', '('),
|
||||||
|
hl('InvalidNestingParenthesis', ')'),
|
||||||
|
})
|
||||||
|
check_parsing(
|
||||||
|
'@a + (@b)', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:2: +',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
{
|
||||||
|
'Nested:0:4: (',
|
||||||
|
children = {
|
||||||
|
'Register(name=b):0:6:@b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('BinaryPlus', '+', 1),
|
||||||
|
hl('NestingParenthesis', '(', 1),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
hl('NestingParenthesis', ')'),
|
||||||
|
})
|
||||||
|
check_parsing(
|
||||||
|
'@a + (+@b)', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:2: +',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
{
|
||||||
|
'Nested:0:4: (',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'UnaryPlus:0:6:+',
|
||||||
|
children = {
|
||||||
|
'Register(name=b):0:7:@b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('BinaryPlus', '+', 1),
|
||||||
|
hl('NestingParenthesis', '(', 1),
|
||||||
|
hl('UnaryPlus', '+'),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
hl('NestingParenthesis', ')'),
|
||||||
|
})
|
||||||
|
check_parsing(
|
||||||
|
'@a + (@b + @c)', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:2: +',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
{
|
||||||
|
'Nested:0:4: (',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:8: +',
|
||||||
|
children = {
|
||||||
|
'Register(name=b):0:6:@b',
|
||||||
|
'Register(name=c):0:10: @c',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('BinaryPlus', '+', 1),
|
||||||
|
hl('NestingParenthesis', '(', 1),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
hl('BinaryPlus', '+', 1),
|
||||||
|
hl('Register', '@c', 1),
|
||||||
|
hl('NestingParenthesis', ')'),
|
||||||
|
})
|
||||||
|
check_parsing('(@a)+@b', 0, {
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:4:+',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'Nested:0:0:(',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:1:@a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Register(name=b):0:5:@b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('NestingParenthesis', '('),
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('NestingParenthesis', ')'),
|
||||||
|
hl('BinaryPlus', '+'),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
})
|
||||||
|
check_parsing('@a+(@b)(@c)', 0, {
|
||||||
|
-- 01234567890
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:2:+',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
{
|
||||||
|
'Call:0:7:(',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'Nested:0:3:(',
|
||||||
|
children = { 'Register(name=b):0:4:@b' },
|
||||||
|
},
|
||||||
|
'Register(name=c):0:8:@c',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('BinaryPlus', '+'),
|
||||||
|
hl('NestingParenthesis', '('),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
hl('NestingParenthesis', ')'),
|
||||||
|
hl('CallingParenthesis', '('),
|
||||||
|
hl('Register', '@c'),
|
||||||
|
hl('CallingParenthesis', ')'),
|
||||||
|
})
|
||||||
|
check_parsing('@a+((@b))(@c)', 0, {
|
||||||
|
-- 01234567890123456890123456789
|
||||||
|
-- 0 1 2
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:2:+',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
{
|
||||||
|
'Call:0:9:(',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'Nested:0:3:(',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'Nested:0:4:(',
|
||||||
|
children = { 'Register(name=b):0:5:@b' }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Register(name=c):0:10:@c',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('BinaryPlus', '+'),
|
||||||
|
hl('NestingParenthesis', '('),
|
||||||
|
hl('NestingParenthesis', '('),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
hl('NestingParenthesis', ')'),
|
||||||
|
hl('NestingParenthesis', ')'),
|
||||||
|
hl('CallingParenthesis', '('),
|
||||||
|
hl('Register', '@c'),
|
||||||
|
hl('CallingParenthesis', ')'),
|
||||||
|
})
|
||||||
|
check_parsing('@a+((@b))+@c', 0, {
|
||||||
|
-- 01234567890123456890123456789
|
||||||
|
-- 0 1 2
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:9:+',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:2:+',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
{
|
||||||
|
'Nested:0:3:(',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'Nested:0:4:(',
|
||||||
|
children = { 'Register(name=b):0:5:@b' }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Register(name=c):0:10:@c',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('BinaryPlus', '+'),
|
||||||
|
hl('NestingParenthesis', '('),
|
||||||
|
hl('NestingParenthesis', '('),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
hl('NestingParenthesis', ')'),
|
||||||
|
hl('NestingParenthesis', ')'),
|
||||||
|
hl('BinaryPlus', '+'),
|
||||||
|
hl('Register', '@c'),
|
||||||
|
})
|
||||||
|
check_parsing(
|
||||||
|
'@a + (@b + @c) + @d(@e) + (+@f) + ((+@g(@h))(@j)(@k))(@l)', 0, {--[[
|
||||||
|
| | | | | | | | || | | || | | ||| || || || ||
|
||||||
|
000000000011111111112222222222333333333344444444445555555
|
||||||
|
012345678901234567890123456789012345678901234567890123456
|
||||||
|
]]
|
||||||
|
ast = {{
|
||||||
|
'BinaryPlus:0:31: +',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:23: +',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:14: +',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:2: +',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
{
|
||||||
|
'Nested:0:4: (',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:8: +',
|
||||||
|
children = {
|
||||||
|
'Register(name=b):0:6:@b',
|
||||||
|
'Register(name=c):0:10: @c',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Call:0:19:(',
|
||||||
|
children = {
|
||||||
|
'Register(name=d):0:16: @d',
|
||||||
|
'Register(name=e):0:20:@e',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Nested:0:25: (',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'UnaryPlus:0:27:+',
|
||||||
|
children = {
|
||||||
|
'Register(name=f):0:28:@f',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Call:0:53:(',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'Nested:0:33: (',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'Call:0:48:(',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'Call:0:44:(',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'Nested:0:35:(',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'UnaryPlus:0:36:+',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'Call:0:39:(',
|
||||||
|
children = {
|
||||||
|
'Register(name=g):0:37:@g',
|
||||||
|
'Register(name=h):0:40:@h',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Register(name=j):0:45:@j',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Register(name=k):0:49:@k',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Register(name=l):0:54:@l',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('BinaryPlus', '+', 1),
|
||||||
|
hl('NestingParenthesis', '(', 1),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
hl('BinaryPlus', '+', 1),
|
||||||
|
hl('Register', '@c', 1),
|
||||||
|
hl('NestingParenthesis', ')'),
|
||||||
|
hl('BinaryPlus', '+', 1),
|
||||||
|
hl('Register', '@d', 1),
|
||||||
|
hl('CallingParenthesis', '('),
|
||||||
|
hl('Register', '@e'),
|
||||||
|
hl('CallingParenthesis', ')'),
|
||||||
|
hl('BinaryPlus', '+', 1),
|
||||||
|
hl('NestingParenthesis', '(', 1),
|
||||||
|
hl('UnaryPlus', '+'),
|
||||||
|
hl('Register', '@f'),
|
||||||
|
hl('NestingParenthesis', ')'),
|
||||||
|
hl('BinaryPlus', '+', 1),
|
||||||
|
hl('NestingParenthesis', '(', 1),
|
||||||
|
hl('NestingParenthesis', '('),
|
||||||
|
hl('UnaryPlus', '+'),
|
||||||
|
hl('Register', '@g'),
|
||||||
|
hl('CallingParenthesis', '('),
|
||||||
|
hl('Register', '@h'),
|
||||||
|
hl('CallingParenthesis', ')'),
|
||||||
|
hl('NestingParenthesis', ')'),
|
||||||
|
hl('CallingParenthesis', '('),
|
||||||
|
hl('Register', '@j'),
|
||||||
|
hl('CallingParenthesis', ')'),
|
||||||
|
hl('CallingParenthesis', '('),
|
||||||
|
hl('Register', '@k'),
|
||||||
|
hl('CallingParenthesis', ')'),
|
||||||
|
hl('NestingParenthesis', ')'),
|
||||||
|
hl('CallingParenthesis', '('),
|
||||||
|
hl('Register', '@l'),
|
||||||
|
hl('CallingParenthesis', ')'),
|
||||||
|
})
|
||||||
|
check_parsing('@a)', 0, {
|
||||||
|
-- 012
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'Nested:0:2:',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err = {
|
||||||
|
arg = '1:)',
|
||||||
|
msg = 'E15: Unexpected closing parenthesis: %.*s',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('InvalidNestingParenthesis', ')'),
|
||||||
|
})
|
||||||
|
check_parsing('(@a', 0, {
|
||||||
|
-- 012
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'Nested:0:0:(',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:1:@a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err = {
|
||||||
|
arg = '3:(@a',
|
||||||
|
msg = 'E110: Missing closing parenthesis for nested expression: %.*s',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('NestingParenthesis', '('),
|
||||||
|
hl('Register', '@a'),
|
||||||
|
})
|
||||||
|
check_parsing('@a(@b', 0, {
|
||||||
|
-- 01234
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'Call:0:2:(',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:0:@a',
|
||||||
|
'Register(name=b):0:3:@b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err = {
|
||||||
|
arg = '3:(@b',
|
||||||
|
msg = 'E116: Missing closing parenthesis for function call: %.*s',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('CallingParenthesis', '('),
|
||||||
|
hl('Register', '@b'),
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end)
|
||||||
Reference in New Issue
Block a user