diff options
-rw-r--r-- | src/nvim/viml/parser/expressions.c | 87 | ||||
-rw-r--r-- | src/nvim/viml/parser/expressions.h | 6 | ||||
-rw-r--r-- | test/unit/viml/expressions/parser_spec.lua | 651 |
3 files changed, 705 insertions, 39 deletions
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index c283241cb4..41c77c5c88 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -46,6 +46,7 @@ typedef enum { kEOpLvlArrow, kEOpLvlComma, kEOpLvlColon, + kEOpLvlTernaryValue, kEOpLvlTernary, kEOpLvlOr, kEOpLvlAnd, @@ -770,7 +771,8 @@ static inline void viml_pexpr_debug_print_token( // NVimUnaryOperator -> NVimOperator // NVimBinaryOperator -> NVimOperator // NVimComparisonOperator -> NVimOperator -// NVimTernaryOperator -> NVimOperator +// NVimTernary -> NVimOperator +// NVimTernaryColon -> NVimTernary // // NVimParenthesis -> Delimiter // @@ -790,7 +792,8 @@ static inline void viml_pexpr_debug_print_token( // // NVimInvalidComma -> NVimInvalidDelimiter // NVimInvalidSpacing -> NVimInvalid -// NVimInvalidTernaryOperator -> NVimInvalidOperator +// NVimInvalidTernary -> NVimInvalidOperator +// NVimInvalidTernaryColon -> NVimInvalidTernary // NVimInvalidRegister -> NVimInvalidValue // NVimInvalidClosingBracket -> NVimInvalidDelimiter // NVimInvalidSpacing -> NVimInvalid @@ -823,30 +826,6 @@ static inline ExprASTNode *viml_pexpr_new_node(const ExprASTNodeType type) return ret; } -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; - -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, @@ -868,6 +847,8 @@ static const ExprOpLvl node_type_to_op_lvl[] = { [kExprNodeTernary] = kEOpLvlTernary, + [kExprNodeTernaryValue] = kEOpLvlTernaryValue, + [kExprNodeBinaryPlus] = kEOpLvlAddition, [kExprNodeUnaryPlus] = kEOpLvlUnary, @@ -907,7 +888,9 @@ static const ExprOpAssociativity node_type_to_op_ass[] = { // about associativity, only about order of execution. [kExprNodeComma] = kEOpAssRight, - [kExprNodeTernary] = kEOpAssNo, + [kExprNodeTernary] = kEOpAssRight, + + [kExprNodeTernaryValue] = kEOpAssRight, [kExprNodeBinaryPlus] = kEOpAssLeft, @@ -1450,9 +1433,20 @@ viml_pexpr_parse_invalid_comma: 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); + if (can_be_ternary && eastnode_type == kExprNodeTernaryValue + && !(*eastnode_p)->data.ter.got_colon) { + kv_drop(ast_stack, i); + (*eastnode_p)->start = cur_token.start; + (*eastnode_p)->len = cur_token.len; + if (prev_token.type == kExprLexSpacing) { + (*eastnode_p)->start = prev_token.start; + (*eastnode_p)->len += prev_token.len; + } is_ternary = true; + (*eastnode_p)->data.ter.got_colon = true; + assert((*eastnode_p)->children != NULL); + assert((*eastnode_p)->children->next == NULL); + kvi_push(ast_stack, &(*eastnode_p)->children->next); break; } else if (eastnode_type == kExprNodeUnknownFigure) { SELECT_FIGURE_BRACE_TYPE(*eastnode_p, DictLiteral, Dict); @@ -1460,7 +1454,7 @@ viml_pexpr_parse_invalid_comma: } else if (eastnode_type == kExprNodeDictLiteral || eastnode_type == kExprNodeComma) { break; - } else if (eastnode_lvl > kEOpLvlTernary) { + } else if (eastnode_lvl >= kEOpLvlTernaryValue) { // Do nothing } else if (eastnode_lvl > kEOpLvlComma) { can_be_ternary = false; @@ -1476,11 +1470,11 @@ viml_pexpr_parse_invalid_colon: goto viml_pexpr_parse_invalid_colon; } } - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); - viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); if (is_ternary) { HL_CUR_TOKEN(TernaryColon); } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); HL_CUR_TOKEN(Colon); } want_node = kENodeValue; @@ -1683,8 +1677,6 @@ viml_pexpr_parse_figure_brace_closing_error: cur_token.len - scope_shift, HL(Identifier)); } - // FIXME: Actually, g{foo}g:foo is valid: "1?g{foo}g:foo" is like - // "g{foo}g" and not an error. } else { if (cur_token.data.var.scope == 0) { ADD_IDENT( @@ -1792,6 +1784,21 @@ viml_pexpr_parse_no_paren_closing_error: {} } break; } + case kExprLexQuestion: { + ADD_VALUE_IF_MISSING(_("E15: Expected value, got question mark: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeTernary); + viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); + HL_CUR_TOKEN(Ternary); + ExprASTNode *ter_val_node; + NEW_NODE_WITH_CUR_POS(ter_val_node, kExprNodeTernaryValue); + ter_val_node->data.ter.got_colon = false; + assert(cur_node->children != NULL); + assert(cur_node->children->next == NULL); + assert(kv_last(ast_stack) == &cur_node->children->next); + *kv_last(ast_stack) = ter_val_node; + kvi_push(ast_stack, &ter_val_node->children); + break; + } } viml_pexpr_parse_cycle_end: prev_token = cur_token; @@ -1815,6 +1822,7 @@ viml_pexpr_parse_end: const ExprASTNode *const cur_node = (*kv_pop(ast_stack)); // This should only happen when want_node == kENodeValue. assert(cur_node != NULL); + // TODO(ZyX-I): Rehighlight as invalid? switch (cur_node->type) { case kExprNodeOpMissing: case kExprNodeMissing: { @@ -1822,7 +1830,6 @@ viml_pexpr_parse_end: break; } case kExprNodeCall: { - // TODO(ZyX-I): Rehighlight as invalid? east_set_error( &ast, pstate, _("E116: Missing closing parenthesis for function call: %.*s"), @@ -1830,7 +1837,6 @@ viml_pexpr_parse_end: break; } case kExprNodeNested: { - // TODO(ZyX-I): Rehighlight as invalid? east_set_error( &ast, pstate, _("E110: Missing closing parenthesis for nested expression" @@ -1844,6 +1850,15 @@ viml_pexpr_parse_end: // It is OK to see these in the stack. break; } + case kExprNodeTernaryValue: { + if (!cur_node->data.ter.got_colon) { + // Actually Vim throws E109 in more cases. + east_set_error( + &ast, pstate, _("E109: Missing ':' after '?': %.*s"), + cur_node->start); + } + break; + } // TODO(ZyX-I): handle other values } } diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 01a51e4eda..cf0907850a 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -145,6 +145,7 @@ typedef enum { kExprNodeMissing = 'X', kExprNodeOpMissing = '_', kExprNodeTernary = '?', ///< Ternary operator. + kExprNodeTernaryValue = 'C', ///< Ternary operator, colon. kExprNodeRegister = '@', ///< Register. kExprNodeSubscript = 's', ///< Subscript. kExprNodeListLiteral = 'l', ///< List literal. @@ -209,7 +210,10 @@ struct expr_ast_node { /// Points to inside parser reader state. const char *ident; size_t ident_len; ///< Actual identifier length. - } var; + } var; ///< For kExprNodePlainIdentifier. + struct { + bool got_colon; ///< True if colon was seen. + } ter; ///< For kExprNodeTernaryValue. } data; }; diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 1f734c3c2a..ea37d64662 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -66,6 +66,7 @@ make_enum_conv_tab(lib, { 'kExprNodeMissing', 'kExprNodeOpMissing', 'kExprNodeTernary', + 'kExprNodeTernaryValue', 'kExprNodeRegister', 'kExprNodeSubscript', 'kExprNodeListLiteral', @@ -2489,6 +2490,652 @@ describe('Expressions parser', function() hl('Dict', '}'), }) end) - -- FIXME: Test sequence of arrows inside and outside lambdas. - -- FIXME: Test autoload character and scope in lambda arguments. + itp('works with ternary operator', function() + check_parsing('a ? b : c', 0, { + -- 012345678 + ast = { + { + 'Ternary:0:1: ?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:5: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:3: b', + 'PlainIdentifier(scope=0,ident=c):0:7: c', + }, + }, + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('Ternary', '?', 1), + hl('Identifier', 'b', 1), + hl('TernaryColon', ':', 1), + hl('Identifier', 'c', 1), + }) + check_parsing('@a?@b?@c:@d:@e', 0, { + -- 01234567890123 + -- 0 1 + ast = { + { + 'Ternary:0:2:?', + children = { + 'Register(name=a):0:0:@a', + { + 'TernaryValue:0:11::', + children = { + { + 'Ternary:0:5:?', + children = { + 'Register(name=b):0:3:@b', + { + 'TernaryValue:0:8::', + children = { + 'Register(name=c):0:6:@c', + 'Register(name=d):0:9:@d', + }, + }, + }, + }, + 'Register(name=e):0:12:@e', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('Ternary', '?'), + hl('Register', '@c'), + hl('TernaryColon', ':'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + }) + check_parsing('@a?@b:@c?@d:@e', 0, { + -- 01234567890123 + -- 0 1 + ast = { + { + 'Ternary:0:2:?', + children = { + 'Register(name=a):0:0:@a', + { + 'TernaryValue:0:5::', + children = { + 'Register(name=b):0:3:@b', + { + 'Ternary:0:8:?', + children = { + 'Register(name=c):0:6:@c', + { + 'TernaryValue:0:11::', + children = { + 'Register(name=d):0:9:@d', + 'Register(name=e):0:12:@e', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + }) + check_parsing('@a?@b?@c?@d:@e?@f:@g:@h?@i:@j:@k', 0, { + -- 01234567890123456789012345678901 + -- 0 1 2 3 + ast = { + { + 'Ternary:0:2:?', + children = { + 'Register(name=a):0:0:@a', + { + 'TernaryValue:0:29::', + children = { + { + 'Ternary:0:5:?', + children = { + 'Register(name=b):0:3:@b', + { + 'TernaryValue:0:20::', + children = { + { + 'Ternary:0:8:?', + children = { + 'Register(name=c):0:6:@c', + { + 'TernaryValue:0:11::', + children = { + 'Register(name=d):0:9:@d', + { + 'Ternary:0:14:?', + children = { + 'Register(name=e):0:12:@e', + { + 'TernaryValue:0:17::', + children = { + 'Register(name=f):0:15:@f', + 'Register(name=g):0:18:@g', + }, + }, + }, + }, + }, + }, + }, + }, + { + 'Ternary:0:23:?', + children = { + 'Register(name=h):0:21:@h', + { + 'TernaryValue:0:26::', + children = { + 'Register(name=i):0:24:@i', + 'Register(name=j):0:27:@j', + }, + }, + }, + }, + }, + }, + }, + }, + 'Register(name=k):0:30:@k', + }, + }, + }, + }, + }, + }, { + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('Ternary', '?'), + hl('Register', '@c'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + hl('Ternary', '?'), + hl('Register', '@f'), + hl('TernaryColon', ':'), + hl('Register', '@g'), + hl('TernaryColon', ':'), + hl('Register', '@h'), + hl('Ternary', '?'), + hl('Register', '@i'), + hl('TernaryColon', ':'), + hl('Register', '@j'), + hl('TernaryColon', ':'), + hl('Register', '@k'), + }) + check_parsing('?', 0, { + -- 0 + ast = { + { + 'Ternary:0:0:?', + children = { + 'Missing:0:0:', + 'TernaryValue:0:0:?', + }, + }, + }, + err = { + arg = '?', + msg = 'E15: Expected value, got question mark: %.*s', + }, + }, { + hl('InvalidTernary', '?'), + }) + + check_parsing('?:', 0, { + -- 01 + ast = { + { + 'Ternary:0:0:?', + children = { + 'Missing:0:0:', + { + 'TernaryValue:0:1::', + children = { + 'Missing:0:1:', + }, + }, + }, + }, + }, + err = { + arg = '?:', + msg = 'E15: Expected value, got question mark: %.*s', + }, + }, { + hl('InvalidTernary', '?'), + hl('InvalidTernaryColon', ':'), + }) + + check_parsing('?::', 0, { + -- 012 + ast = { + { + 'Colon:0:2::', + children = { + { + 'Ternary:0:0:?', + children = { + 'Missing:0:0:', + { + 'TernaryValue:0:1::', + children = { + 'Missing:0:1:', + 'Missing:0:2:', + }, + }, + }, + }, + }, + }, + }, + err = { + arg = '?::', + msg = 'E15: Expected value, got question mark: %.*s', + }, + }, { + hl('InvalidTernary', '?'), + hl('InvalidTernaryColon', ':'), + hl('InvalidColon', ':'), + }) + + check_parsing('a?b', 0, { + -- 012 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, + }, + err = { + arg = '?b', + msg = 'E109: Missing \':\' after \'?\': %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('Ternary', '?'), + hl('Identifier', 'b'), + }) + check_parsing('a?b:', 0, { + -- 0123 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:1:?', + children = { + 'PlainIdentifier(scope=b,ident=):0:2:b:', + }, + }, + }, + }, + }, + err = { + arg = '?b:', + msg = 'E109: Missing \':\' after \'?\': %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('Ternary', '?'), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + }) + + check_parsing('a?b::c', 0, { + -- 012345 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:4::', + children = { + 'PlainIdentifier(scope=b,ident=):0:2:b:', + 'PlainIdentifier(scope=0,ident=c):0:5:c', + }, + }, + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('Ternary', '?'), + hl('IdentifierScope', 'b'), + hl('IdentifierScopeDelimiter', ':'), + hl('TernaryColon', ':'), + hl('Identifier', 'c'), + }) + + check_parsing('a?b :', 0, { + -- 01234 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:3: :', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + }, + }, + }, + }, + }, + err = { + arg = '', + msg = 'E15: Expected value, got EOC: %.*s', + }, + }, { + hl('Identifier', 'a'), + hl('Ternary', '?'), + hl('Identifier', 'b'), + hl('TernaryColon', ':', 1), + }) + + check_parsing('(@a?@b:@c)?@d:@e', 0, { + -- 0123456789012345 + -- 0 1 + ast = { + { + 'Ternary:0:10:?', + children = { + { + 'Nested:0:0:(', + children = { + { + 'Ternary:0:3:?', + children = { + 'Register(name=a):0:1:@a', + { + 'TernaryValue:0:6::', + children = { + 'Register(name=b):0:4:@b', + 'Register(name=c):0:7:@c', + }, + }, + }, + }, + }, + }, + { + 'TernaryValue:0:13::', + children = { + 'Register(name=d):0:11:@d', + 'Register(name=e):0:14:@e', + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('NestingParenthesis', ')'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('TernaryColon', ':'), + hl('Register', '@e'), + }) + + check_parsing('(@a?@b:@c)?(@d?@e:@f):(@g?@h:@i)', 0, { + -- 01234567890123456789012345678901 + -- 0 1 2 3 + ast = { + { + 'Ternary:0:10:?', + children = { + { + 'Nested:0:0:(', + children = { + { + 'Ternary:0:3:?', + children = { + 'Register(name=a):0:1:@a', + { + 'TernaryValue:0:6::', + children = { + 'Register(name=b):0:4:@b', + 'Register(name=c):0:7:@c', + }, + }, + }, + }, + }, + }, + { + 'TernaryValue:0:21::', + children = { + { + 'Nested:0:11:(', + children = { + { + 'Ternary:0:14:?', + children = { + 'Register(name=d):0:12:@d', + { + 'TernaryValue:0:17::', + children = { + 'Register(name=e):0:15:@e', + 'Register(name=f):0:18:@f', + }, + }, + }, + }, + }, + }, + { + 'Nested:0:22:(', + children = { + { + 'Ternary:0:25:?', + children = { + 'Register(name=g):0:23:@g', + { + 'TernaryValue:0:28::', + children = { + 'Register(name=h):0:26:@h', + 'Register(name=i):0:29:@i', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('NestingParenthesis', ')'), + hl('Ternary', '?'), + hl('NestingParenthesis', '('), + hl('Register', '@d'), + hl('Ternary', '?'), + hl('Register', '@e'), + hl('TernaryColon', ':'), + hl('Register', '@f'), + hl('NestingParenthesis', ')'), + hl('TernaryColon', ':'), + hl('NestingParenthesis', '('), + hl('Register', '@g'), + hl('Ternary', '?'), + hl('Register', '@h'), + hl('TernaryColon', ':'), + hl('Register', '@i'), + hl('NestingParenthesis', ')'), + }) + + check_parsing('(@a?@b:@c)?@d?@e:@f:@g?@h:@i', 0, { + -- 0123456789012345678901234567 + -- 0 1 2 + ast = { + { + 'Ternary:0:10:?', + children = { + { + 'Nested:0:0:(', + children = { + { + 'Ternary:0:3:?', + children = { + 'Register(name=a):0:1:@a', + { + 'TernaryValue:0:6::', + children = { + 'Register(name=b):0:4:@b', + 'Register(name=c):0:7:@c', + }, + }, + }, + }, + }, + }, + { + 'TernaryValue:0:19::', + children = { + { + 'Ternary:0:13:?', + children = { + 'Register(name=d):0:11:@d', + { + 'TernaryValue:0:16::', + children = { + 'Register(name=e):0:14:@e', + 'Register(name=f):0:17:@f', + }, + }, + }, + }, + { + 'Ternary:0:22:?', + children = { + 'Register(name=g):0:20:@g', + { + 'TernaryValue:0:25::', + children = { + 'Register(name=h):0:23:@h', + 'Register(name=i):0:26:@i', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + hl('NestingParenthesis', '('), + hl('Register', '@a'), + hl('Ternary', '?'), + hl('Register', '@b'), + hl('TernaryColon', ':'), + hl('Register', '@c'), + hl('NestingParenthesis', ')'), + hl('Ternary', '?'), + hl('Register', '@d'), + hl('Ternary', '?'), + hl('Register', '@e'), + hl('TernaryColon', ':'), + hl('Register', '@f'), + hl('TernaryColon', ':'), + hl('Register', '@g'), + hl('Ternary', '?'), + hl('Register', '@h'), + hl('TernaryColon', ':'), + hl('Register', '@i'), + }) + check_parsing('a?b{cdef}g:h', 0, { + -- 012345678901 + -- 0 1 + ast = { + { + 'Ternary:0:1:?', + children = { + 'PlainIdentifier(scope=0,ident=a):0:0:a', + { + 'TernaryValue:0:10::', + children = { + { + 'ComplexIdentifier:0:3:', + children = { + 'PlainIdentifier(scope=0,ident=b):0:2:b', + { + 'ComplexIdentifier:0:9:', + children = { + { + 'CurlyBracesIdentifier(--i):0:3:{', + children = { + 'PlainIdentifier(scope=0,ident=cdef):0:4:cdef', + }, + }, + 'PlainIdentifier(scope=0,ident=g):0:9:g', + }, + }, + }, + }, + 'PlainIdentifier(scope=0,ident=h):0:11:h', + }, + }, + }, + }, + }, + }, { + hl('Identifier', 'a'), + hl('Ternary', '?'), + hl('Identifier', 'b'), + hl('Curly', '{'), + hl('Identifier', 'cdef'), + hl('Curly', '}'), + hl('Identifier', 'g'), + hl('TernaryColon', ':'), + hl('Identifier', 'h'), + }) + end) end) |