mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
viml/parser/expressions: Fix determining invalid commas/colons
This commit is contained in:
parent
3735537a50
commit
9e721031d5
@ -13,6 +13,7 @@
|
|||||||
#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/assert.h"
|
||||||
#include "nvim/lib/kvec.h"
|
#include "nvim/lib/kvec.h"
|
||||||
|
|
||||||
#include "nvim/viml/parser/expressions.h"
|
#include "nvim/viml/parser/expressions.h"
|
||||||
@ -37,6 +38,32 @@ typedef enum {
|
|||||||
kENodeArgumentSeparator,
|
kENodeArgumentSeparator,
|
||||||
} ExprASTWantedNode;
|
} ExprASTWantedNode;
|
||||||
|
|
||||||
|
/// Operator priority level
|
||||||
|
typedef enum {
|
||||||
|
kEOpLvlInvalid = 0,
|
||||||
|
kEOpLvlComplexIdentifier,
|
||||||
|
kEOpLvlParens,
|
||||||
|
kEOpLvlArrow,
|
||||||
|
kEOpLvlComma,
|
||||||
|
kEOpLvlColon,
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// Operator associativity
|
||||||
|
typedef enum {
|
||||||
|
kEOpAssNo= 'n', ///< Not associative / not applicable.
|
||||||
|
kEOpAssLeft = 'l', ///< Left associativity.
|
||||||
|
kEOpAssRight = 'r', ///< Right associativity.
|
||||||
|
} ExprOpAssociativity;
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "viml/parser/expressions.c.generated.h"
|
# include "viml/parser/expressions.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
@ -747,6 +774,7 @@ static inline void viml_pexpr_debug_print_token(
|
|||||||
//
|
//
|
||||||
// NVimParenthesis -> Delimiter
|
// NVimParenthesis -> Delimiter
|
||||||
//
|
//
|
||||||
|
// NVimColon -> Delimiter
|
||||||
// NVimComma -> Delimiter
|
// NVimComma -> Delimiter
|
||||||
// NVimArrow -> Delimiter
|
// NVimArrow -> Delimiter
|
||||||
//
|
//
|
||||||
@ -895,6 +923,32 @@ static const ExprOpAssociativity node_type_to_op_ass[] = {
|
|||||||
[kExprNodeListLiteral] = kEOpAssNo,
|
[kExprNodeListLiteral] = kEOpAssNo,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Get AST node priority level
|
||||||
|
///
|
||||||
|
/// Used primary to reduce line length, so keep the name short.
|
||||||
|
///
|
||||||
|
/// @param[in] node Node to get priority for.
|
||||||
|
///
|
||||||
|
/// @return Node priority level.
|
||||||
|
static inline ExprOpLvl node_lvl(const ExprASTNode node)
|
||||||
|
FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
|
||||||
|
{
|
||||||
|
return node_type_to_op_lvl[node.type];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get AST node associativity, to be used for operator nodes primary
|
||||||
|
///
|
||||||
|
/// Used primary to reduce line length, so keep the name short.
|
||||||
|
///
|
||||||
|
/// @param[in] node Node to get priority for.
|
||||||
|
///
|
||||||
|
/// @return Node associativity.
|
||||||
|
static inline ExprOpAssociativity node_ass(const ExprASTNode node)
|
||||||
|
FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
|
||||||
|
{
|
||||||
|
return node_type_to_op_ass[node.type];
|
||||||
|
}
|
||||||
|
|
||||||
/// Handle binary operator
|
/// Handle binary operator
|
||||||
///
|
///
|
||||||
/// This function is responsible for handling priority levels as well.
|
/// This function is responsible for handling priority levels as well.
|
||||||
@ -910,20 +964,19 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack,
|
|||||||
assert(kv_size(*ast_stack));
|
assert(kv_size(*ast_stack));
|
||||||
const ExprOpLvl bop_node_lvl = (bop_node->type == kExprNodeCall
|
const ExprOpLvl bop_node_lvl = (bop_node->type == kExprNodeCall
|
||||||
? kEOpLvlSubscript
|
? kEOpLvlSubscript
|
||||||
: node_type_to_op_lvl[bop_node->type]);
|
: node_lvl(*bop_node));
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
const ExprOpAssociativity bop_node_ass = (
|
const ExprOpAssociativity bop_node_ass = (
|
||||||
bop_node->type == kExprNodeCall
|
bop_node->type == kExprNodeCall
|
||||||
? kEOpAssLeft
|
? kEOpAssLeft
|
||||||
: node_type_to_op_ass[bop_node->type]);
|
: node_ass(*bop_node));
|
||||||
#endif
|
#endif
|
||||||
do {
|
do {
|
||||||
ExprASTNode **new_top_node_p = kv_last(*ast_stack);
|
ExprASTNode **new_top_node_p = kv_last(*ast_stack);
|
||||||
ExprASTNode *new_top_node = *new_top_node_p;
|
ExprASTNode *new_top_node = *new_top_node_p;
|
||||||
assert(new_top_node != NULL);
|
assert(new_top_node != NULL);
|
||||||
const ExprOpLvl new_top_node_lvl = node_type_to_op_lvl[new_top_node->type];
|
const ExprOpLvl new_top_node_lvl = node_lvl(*new_top_node);
|
||||||
const ExprOpAssociativity new_top_node_ass = (
|
const ExprOpAssociativity new_top_node_ass = node_ass(*new_top_node);
|
||||||
node_type_to_op_ass[new_top_node->type]);
|
|
||||||
assert(bop_node_lvl != new_top_node_lvl
|
assert(bop_node_lvl != new_top_node_lvl
|
||||||
|| bop_node_ass == new_top_node_ass);
|
|| bop_node_ass == new_top_node_ass);
|
||||||
if (top_node_p != NULL
|
if (top_node_p != NULL
|
||||||
@ -1352,31 +1405,30 @@ viml_pexpr_parse_process_token:
|
|||||||
goto viml_pexpr_parse_invalid_comma;
|
goto viml_pexpr_parse_invalid_comma;
|
||||||
}
|
}
|
||||||
for (size_t i = 1; i < kv_size(ast_stack); i++) {
|
for (size_t i = 1; i < kv_size(ast_stack); i++) {
|
||||||
const ExprASTNode *const *const eastnode_p =
|
ExprASTNode *const *const eastnode_p =
|
||||||
(const ExprASTNode *const *)kv_Z(ast_stack, i);
|
(ExprASTNode *const *)kv_Z(ast_stack, i);
|
||||||
if (!((*eastnode_p)->type == kExprNodeComma
|
const ExprASTNodeType eastnode_type = (*eastnode_p)->type;
|
||||||
|| ((*eastnode_p)->type == kExprNodeColon
|
const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p);
|
||||||
&& i == 1))
|
if (eastnode_type == kExprNodeLambda) {
|
||||||
|| i == kv_size(ast_stack) - 1) {
|
assert(want_node == kENodeArgumentSeparator);
|
||||||
switch ((*eastnode_p)->type) {
|
|
||||||
case kExprNodeLambda: {
|
|
||||||
assert(want_node == kENodeArgumentSeparator);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case kExprNodeDictLiteral:
|
|
||||||
case kExprNodeListLiteral:
|
|
||||||
case kExprNodeCall: {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
viml_pexpr_parse_invalid_comma:
|
|
||||||
ERROR_FROM_TOKEN_AND_MSG(
|
|
||||||
cur_token,
|
|
||||||
_("E15: Comma outside of call, lambda or literal: %.*s"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
} else if (eastnode_type == kExprNodeDictLiteral
|
||||||
|
|| eastnode_type == kExprNodeListLiteral
|
||||||
|
|| eastnode_type == kExprNodeCall) {
|
||||||
|
break;
|
||||||
|
} else if (eastnode_type == kExprNodeComma
|
||||||
|
|| eastnode_type == kExprNodeColon
|
||||||
|
|| eastnode_lvl > kEOpLvlComma) {
|
||||||
|
// Do nothing
|
||||||
|
} else {
|
||||||
|
viml_pexpr_parse_invalid_comma:
|
||||||
|
ERROR_FROM_TOKEN_AND_MSG(
|
||||||
|
cur_token,
|
||||||
|
_("E15: Comma outside of call, lambda or literal: %.*s"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i == kv_size(ast_stack) - 1) {
|
||||||
|
goto viml_pexpr_parse_invalid_comma;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComma);
|
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComma);
|
||||||
@ -1389,37 +1441,48 @@ viml_pexpr_parse_invalid_comma:
|
|||||||
if (kv_size(ast_stack) < 2) {
|
if (kv_size(ast_stack) < 2) {
|
||||||
goto viml_pexpr_parse_invalid_colon;
|
goto viml_pexpr_parse_invalid_colon;
|
||||||
}
|
}
|
||||||
|
bool is_ternary = false;
|
||||||
|
bool can_be_ternary = true;
|
||||||
for (size_t i = 1; i < kv_size(ast_stack); i++) {
|
for (size_t i = 1; i < kv_size(ast_stack); i++) {
|
||||||
ExprASTNode *const *const eastnode_p =
|
ExprASTNode *const *const eastnode_p =
|
||||||
(ExprASTNode *const *)kv_Z(ast_stack, i);
|
(ExprASTNode *const *)kv_Z(ast_stack, i);
|
||||||
if ((*eastnode_p)->type != kExprNodeColon
|
const ExprASTNodeType eastnode_type = (*eastnode_p)->type;
|
||||||
|| i == kv_size(ast_stack) - 1) {
|
const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p);
|
||||||
switch ((*eastnode_p)->type) {
|
STATIC_ASSERT(kEOpLvlTernary > kEOpLvlComma,
|
||||||
case kExprNodeUnknownFigure: {
|
"Unexpected operator priorities");
|
||||||
SELECT_FIGURE_BRACE_TYPE((*eastnode_p), DictLiteral, Dict);
|
if (can_be_ternary && eastnode_lvl == kEOpLvlTernary) {
|
||||||
break;
|
assert(eastnode_type == kExprNodeTernary);
|
||||||
}
|
is_ternary = true;
|
||||||
case kExprNodeComma:
|
|
||||||
case kExprNodeDictLiteral:
|
|
||||||
case kExprNodeTernary: {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
viml_pexpr_parse_invalid_colon:
|
|
||||||
ERROR_FROM_TOKEN_AND_MSG(
|
|
||||||
cur_token,
|
|
||||||
_("E15: Colon outside of dictionary or ternary operator: "
|
|
||||||
"%.*s"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
} else if (eastnode_type == kExprNodeUnknownFigure) {
|
||||||
|
SELECT_FIGURE_BRACE_TYPE(*eastnode_p, DictLiteral, Dict);
|
||||||
|
break;
|
||||||
|
} else if (eastnode_type == kExprNodeDictLiteral
|
||||||
|
|| eastnode_type == kExprNodeComma) {
|
||||||
|
break;
|
||||||
|
} else if (eastnode_lvl > kEOpLvlTernary) {
|
||||||
|
// Do nothing
|
||||||
|
} else if (eastnode_lvl > kEOpLvlComma) {
|
||||||
|
can_be_ternary = false;
|
||||||
|
} else {
|
||||||
|
viml_pexpr_parse_invalid_colon:
|
||||||
|
ERROR_FROM_TOKEN_AND_MSG(
|
||||||
|
cur_token,
|
||||||
|
_("E15: Colon outside of dictionary or ternary operator: "
|
||||||
|
"%.*s"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i == kv_size(ast_stack) - 1) {
|
||||||
|
goto viml_pexpr_parse_invalid_colon;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon);
|
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon);
|
||||||
viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node);
|
viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node);
|
||||||
// FIXME: Handle ternary operator.
|
if (is_ternary) {
|
||||||
HL_CUR_TOKEN(Colon);
|
HL_CUR_TOKEN(TernaryColon);
|
||||||
|
} else {
|
||||||
|
HL_CUR_TOKEN(Colon);
|
||||||
|
}
|
||||||
want_node = kENodeValue;
|
want_node = kENodeValue;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -144,10 +144,10 @@ typedef enum {
|
|||||||
typedef enum {
|
typedef enum {
|
||||||
kExprNodeMissing = 'X',
|
kExprNodeMissing = 'X',
|
||||||
kExprNodeOpMissing = '_',
|
kExprNodeOpMissing = '_',
|
||||||
kExprNodeTernary = '?', ///< Ternary operator, valid one has three children.
|
kExprNodeTernary = '?', ///< Ternary operator.
|
||||||
kExprNodeRegister = '@', ///< Register, no children.
|
kExprNodeRegister = '@', ///< Register.
|
||||||
kExprNodeSubscript = 's', ///< Subscript, should have two or three children.
|
kExprNodeSubscript = 's', ///< Subscript.
|
||||||
kExprNodeListLiteral = 'l', ///< List literal, any number of children.
|
kExprNodeListLiteral = 'l', ///< List literal.
|
||||||
kExprNodeUnaryPlus = 'p',
|
kExprNodeUnaryPlus = 'p',
|
||||||
kExprNodeBinaryPlus = '+',
|
kExprNodeBinaryPlus = '+',
|
||||||
kExprNodeNested = 'e', ///< Nested parenthesised expression.
|
kExprNodeNested = 'e', ///< Nested parenthesised expression.
|
||||||
|
@ -181,14 +181,14 @@ child_call_once(function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
describe('Expressions parser', function()
|
describe('Expressions parser', function()
|
||||||
local function check_parsing(str, flags, exp_ast, exp_highlighting_fs,
|
local function check_parsing(str, flags, exp_ast, exp_highlighting_fs)
|
||||||
print_exp)
|
|
||||||
local pstate = new_pstate({str})
|
local pstate = new_pstate({str})
|
||||||
local east = lib.viml_pexpr_parse(pstate, flags)
|
local east = lib.viml_pexpr_parse(pstate, flags)
|
||||||
local ast = east2lua(pstate, east)
|
local ast = east2lua(pstate, east)
|
||||||
local hls = phl2lua(pstate)
|
local hls = phl2lua(pstate)
|
||||||
if print_exp then
|
if exp_ast == nil then
|
||||||
format_check(str, flags, ast, hls)
|
format_check(str, flags, ast, hls)
|
||||||
|
return
|
||||||
end
|
end
|
||||||
eq(exp_ast, ast)
|
eq(exp_ast, ast)
|
||||||
if exp_highlighting_fs then
|
if exp_highlighting_fs then
|
||||||
@ -2416,6 +2416,78 @@ describe('Expressions parser', function()
|
|||||||
hl('Curly', '}'),
|
hl('Curly', '}'),
|
||||||
hl('Identifier', 'j'),
|
hl('Identifier', 'j'),
|
||||||
})
|
})
|
||||||
|
check_parsing('{@a + @b : @c + @d, @e + @f : @g + @i}', 0, {
|
||||||
|
-- 01234567890123456789012345678901234567
|
||||||
|
-- 0 1 2 3
|
||||||
|
ast = {
|
||||||
|
{
|
||||||
|
'DictLiteral(-di):0:0:{',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'Comma:0:18:,',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'Colon:0:8: :',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:3: +',
|
||||||
|
children = {
|
||||||
|
'Register(name=a):0:1:@a',
|
||||||
|
'Register(name=b):0:5: @b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:13: +',
|
||||||
|
children = {
|
||||||
|
'Register(name=c):0:10: @c',
|
||||||
|
'Register(name=d):0:15: @d',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Colon:0:27: :',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:22: +',
|
||||||
|
children = {
|
||||||
|
'Register(name=e):0:19: @e',
|
||||||
|
'Register(name=f):0:24: @f',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'BinaryPlus:0:32: +',
|
||||||
|
children = {
|
||||||
|
'Register(name=g):0:29: @g',
|
||||||
|
'Register(name=i):0:34: @i',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
hl('Dict', '{'),
|
||||||
|
hl('Register', '@a'),
|
||||||
|
hl('BinaryPlus', '+', 1),
|
||||||
|
hl('Register', '@b', 1),
|
||||||
|
hl('Colon', ':', 1),
|
||||||
|
hl('Register', '@c', 1),
|
||||||
|
hl('BinaryPlus', '+', 1),
|
||||||
|
hl('Register', '@d', 1),
|
||||||
|
hl('Comma', ','),
|
||||||
|
hl('Register', '@e', 1),
|
||||||
|
hl('BinaryPlus', '+', 1),
|
||||||
|
hl('Register', '@f', 1),
|
||||||
|
hl('Colon', ':', 1),
|
||||||
|
hl('Register', '@g', 1),
|
||||||
|
hl('BinaryPlus', '+', 1),
|
||||||
|
hl('Register', '@i', 1),
|
||||||
|
hl('Dict', '}'),
|
||||||
|
})
|
||||||
end)
|
end)
|
||||||
-- FIXME: Test sequence of arrows inside and outside lambdas.
|
-- FIXME: Test sequence of arrows inside and outside lambdas.
|
||||||
-- FIXME: Test autoload character and scope in lambda arguments.
|
-- FIXME: Test autoload character and scope in lambda arguments.
|
||||||
|
Loading…
Reference in New Issue
Block a user