viml/parser/expressions: Fix determining invalid commas/colons

This commit is contained in:
ZyX 2017-10-01 16:50:46 +03:00
parent 3735537a50
commit 9e721031d5
3 changed files with 194 additions and 59 deletions

View File

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

View File

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

View File

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