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/charset.h"
#include "nvim/ascii.h"
#include "nvim/assert.h"
#include "nvim/lib/kvec.h"
#include "nvim/viml/parser/expressions.h"
@ -37,6 +38,32 @@ typedef enum {
kENodeArgumentSeparator,
} 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
# include "viml/parser/expressions.c.generated.h"
#endif
@ -747,6 +774,7 @@ static inline void viml_pexpr_debug_print_token(
//
// NVimParenthesis -> Delimiter
//
// NVimColon -> Delimiter
// NVimComma -> Delimiter
// NVimArrow -> Delimiter
//
@ -895,6 +923,32 @@ static const ExprOpAssociativity node_type_to_op_ass[] = {
[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
///
/// 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));
const ExprOpLvl bop_node_lvl = (bop_node->type == kExprNodeCall
? kEOpLvlSubscript
: node_type_to_op_lvl[bop_node->type]);
: node_lvl(*bop_node));
#ifndef NDEBUG
const ExprOpAssociativity bop_node_ass = (
bop_node->type == kExprNodeCall
? kEOpAssLeft
: node_type_to_op_ass[bop_node->type]);
: node_ass(*bop_node));
#endif
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]);
const ExprOpLvl new_top_node_lvl = node_lvl(*new_top_node);
const ExprOpAssociativity new_top_node_ass = node_ass(*new_top_node);
assert(bop_node_lvl != new_top_node_lvl
|| bop_node_ass == new_top_node_ass);
if (top_node_p != NULL
@ -1352,31 +1405,30 @@ viml_pexpr_parse_process_token:
goto viml_pexpr_parse_invalid_comma;
}
for (size_t i = 1; i < kv_size(ast_stack); i++) {
const ExprASTNode *const *const eastnode_p =
(const ExprASTNode *const *)kv_Z(ast_stack, i);
if (!((*eastnode_p)->type == kExprNodeComma
|| ((*eastnode_p)->type == kExprNodeColon
&& i == 1))
|| i == kv_size(ast_stack) - 1) {
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;
}
}
ExprASTNode *const *const eastnode_p =
(ExprASTNode *const *)kv_Z(ast_stack, i);
const ExprASTNodeType eastnode_type = (*eastnode_p)->type;
const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p);
if (eastnode_type == kExprNodeLambda) {
assert(want_node == kENodeArgumentSeparator);
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);
@ -1389,37 +1441,48 @@ viml_pexpr_parse_invalid_comma:
if (kv_size(ast_stack) < 2) {
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++) {
ExprASTNode *const *const eastnode_p =
(ExprASTNode *const *)kv_Z(ast_stack, i);
if ((*eastnode_p)->type != kExprNodeColon
|| i == kv_size(ast_stack) - 1) {
switch ((*eastnode_p)->type) {
case kExprNodeUnknownFigure: {
SELECT_FIGURE_BRACE_TYPE((*eastnode_p), DictLiteral, Dict);
break;
}
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;
}
}
const ExprASTNodeType eastnode_type = (*eastnode_p)->type;
const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p);
STATIC_ASSERT(kEOpLvlTernary > kEOpLvlComma,
"Unexpected operator priorities");
if (can_be_ternary && eastnode_lvl == kEOpLvlTernary) {
assert(eastnode_type == kExprNodeTernary);
is_ternary = true;
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);
viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node);
// FIXME: Handle ternary operator.
HL_CUR_TOKEN(Colon);
if (is_ternary) {
HL_CUR_TOKEN(TernaryColon);
} else {
HL_CUR_TOKEN(Colon);
}
want_node = kENodeValue;
break;
}

View File

@ -144,10 +144,10 @@ typedef enum {
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.
kExprNodeTernary = '?', ///< Ternary operator.
kExprNodeRegister = '@', ///< Register.
kExprNodeSubscript = 's', ///< Subscript.
kExprNodeListLiteral = 'l', ///< List literal.
kExprNodeUnaryPlus = 'p',
kExprNodeBinaryPlus = '+',
kExprNodeNested = 'e', ///< Nested parenthesised expression.

View File

@ -181,14 +181,14 @@ child_call_once(function()
end)
describe('Expressions parser', function()
local function check_parsing(str, flags, exp_ast, exp_highlighting_fs,
print_exp)
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)
local hls = phl2lua(pstate)
if print_exp then
if exp_ast == nil then
format_check(str, flags, ast, hls)
return
end
eq(exp_ast, ast)
if exp_highlighting_fs then
@ -2416,6 +2416,78 @@ describe('Expressions parser', function()
hl('Curly', '}'),
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)
-- FIXME: Test sequence of arrows inside and outside lambdas.
-- FIXME: Test autoload character and scope in lambda arguments.