diff options
author | ZyX <kp-pav@yandex.ru> | 2017-11-12 02:18:43 +0300 |
---|---|---|
committer | ZyX <kp-pav@yandex.ru> | 2017-11-12 02:18:43 +0300 |
commit | c7495ebcc0918ffd682083408895451318e41d1f (patch) | |
tree | a5868eb68f0308c8ce5eae057aa47f9adb6ab36d | |
parent | 1aa6276c29d562a6287519e6755a613eabca5c31 (diff) | |
download | rneovim-c7495ebcc0918ffd682083408895451318e41d1f.tar.gz rneovim-c7495ebcc0918ffd682083408895451318e41d1f.tar.bz2 rneovim-c7495ebcc0918ffd682083408895451318e41d1f.zip |
viml/parser/expressions: Add support for parsing assignments
-rw-r--r-- | src/nvim/api/vim.c | 39 | ||||
-rw-r--r-- | src/nvim/syntax.c | 20 | ||||
-rw-r--r-- | src/nvim/viml/parser/expressions.c | 239 | ||||
-rw-r--r-- | src/nvim/viml/parser/expressions.h | 40 | ||||
-rw-r--r-- | test/symbolic/klee/viml_expressions_parser.c | 3 | ||||
-rw-r--r-- | test/unit/viml/expressions/lexer_spec.lua | 10 | ||||
-rw-r--r-- | test/unit/viml/expressions/parser_spec.lua | 23 | ||||
-rw-r--r-- | test/unit/viml/helpers.lua | 13 |
8 files changed, 327 insertions, 60 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index bac19ef363..ce554d351c 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -900,14 +900,21 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// Parse a VimL expression /// /// @param[in] expr Expression to parse. Is always treated as a single line. -/// @param[in] flags Flags: "m" if multiple expressions in a row are allowed -/// (only the first one will be parsed), "E" if EOC tokens -/// are not allowed (determines whether they will stop -/// parsing process or be recognized as an operator/space, -/// though also yielding an error). -/// -/// Use only "m" to parse like for "<C-r>=", only "E" to -/// parse like for ":echo", empty string for ":let". +/// @param[in] flags Flags: +/// +/// - "m" if multiple expressions in a row are allowed (only +/// the first one will be parsed), +/// - "E" if EOC tokens are not allowed (determines whether +/// they will stop parsing process or be recognized as an +/// operator/space, though also yielding an error). +/// - "l" when needing to start parsing with lvalues for +/// ":let" or ":for". +/// +/// Common flag sets: +/// - "m" to parse like for ":echo". +/// - "E" to parse like for "<C-r>=". +/// - empty string for ":call". +/// - "lm" to parse for ":let". /// @param[in] highlight If true, return value will also include "highlight" /// key containing array of 4-tuples (arrays) (Integer, /// Integer, Integer, String), where first three numbers @@ -964,6 +971,9 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// value names from ExprCaseCompareStrategy, /// stringified without "kCCStrategy" prefix. Only /// present for "Comparison" nodes. +/// "augmentation": String, augmentation type for "Assignment" nodes. +/// Is either an empty string, "Add", "Subtract" or +/// "Concat" for "=", "+=", "-=" or ".=" respectively. /// "invert": Boolean, true if result of comparison needs to be /// inverted. Only present for "Comparison" nodes. /// "ivalue": Integer, integer value for "Integer" nodes. @@ -979,6 +989,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, switch (flags.data[i]) { case 'm': { pflags |= kExprFlagsMulti; break; } case 'E': { pflags |= kExprFlagsDisallowEOC; break; } + case 'l': { pflags |= kExprFlagsParseLet; break; } case NUL: { api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)", (unsigned)flags.data[i]); @@ -1114,6 +1125,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, + (node->type == kExprNodeFloat) // "fvalue" + (node->type == kExprNodeDoubleQuotedString || node->type == kExprNodeSingleQuotedString) // "svalue" + + (node->type == kExprNodeAssignment) // "augmentation" + 0); Dictionary ret_node = { .items = xmalloc(ret_node_items_size * sizeof(ret_node.items[0])), @@ -1272,6 +1284,17 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, }; break; } + case kExprNodeAssignment: { + const ExprAssignmentType asgn_type = node->data.ass.type; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("augmentation"), + .value = STRING_OBJ( + asgn_type == kExprAsgnPlain + ? (String)STRING_INIT + : cstr_to_string(expr_asgn_type_tab[asgn_type])), + }; + break; + } case kExprNodeMissing: case kExprNodeOpMissing: case kExprNodeTernary: diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index e0bf74567d..9f98b26905 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6021,11 +6021,21 @@ static const char *highlight_init_dark[] = { }; static const char *highlight_init_cmdline[] = { + // XXX When modifying a list modify it in both valid and invalid halfs. + // TODO(ZyX-I): merge valid and invalid groups via a macros. + // NVimInternalError should appear only when highlighter has a bug. "NVimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red", // Highlight groups (links) used by parser: + "default link NVimAssignment Operator", + "default link NVimPlainAssignment NVimAssignment", + "default link NVimAugmentedAssignment NVimAssignment", + "default link NVimAssignmentWithAddition NVimAugmentedAssignment", + "default link NVimAssignmentWithSubtraction NVimAugmentedAssignment", + "default link NVimAssignmentWithConcatenation NVimAugmentedAssignment", + "default link NVimOperator Operator", "default link NVimUnaryOperator NVimOperator", @@ -6113,6 +6123,16 @@ static const char *highlight_init_cmdline[] = { "default link NVimInvalid Error", + "default link NVimInvalidAssignment NVimInvalid", + "default link NVimInvalidPlainAssignment NVimInvalidAssignment", + "default link NVimInvalidAugmentedAssignment NVimInvalidAssignment", + "default link NVimInvalidAssignmentWithAddition " + "NVimInvalidAugmentedAssignment", + "default link NVimInvalidAssignmentWithSubtraction " + "NVimInvalidAugmentedAssignment", + "default link NVimInvalidAssignmentWithConcatenation " + "NVimInvalidAugmentedAssignment", + "default link NVimInvalidOperator NVimInvalid", "default link NVimInvalidUnaryOperator NVimInvalidOperator", diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index e23c58bfd1..13f7131744 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -84,6 +84,10 @@ typedef enum { /// Just like parsing function arguments, but it is valid to be ended with an /// arrow only. kEPTLambdaArguments, + /// Assignment: parsing for :let + kEPTAssignment, + /// Single assignment: used when lists are not allowed (i.e. when nesting) + kEPTSingleAssignment, } ExprASTParseType; typedef kvec_withinit_t(ExprASTParseType, 4) ExprASTParseTypeStack; @@ -93,6 +97,7 @@ typedef enum { kEOpLvlInvalid = 0, kEOpLvlComplexIdentifier, kEOpLvlParens, + kEOpLvlAssignment, kEOpLvlArrow, kEOpLvlComma, kEOpLvlColon, @@ -217,8 +222,6 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) } CHAR(kExprLexQuestion, '?') CHAR(kExprLexColon, ':') - CHAR(kExprLexDot, '.') - CHAR(kExprLexPlus, '+') CHAR(kExprLexComma, ',') #undef CHAR @@ -532,12 +535,8 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) case '!': case '=': { if (pline.size == 1) { -viml_pexpr_next_token_invalid_comparison: - ret.type = (schar == '!' ? kExprLexNot : kExprLexInvalid); - if (ret.type == kExprLexInvalid) { - ret.data.err.msg = _("E15: Expected == or =~: %.*s"); - ret.data.err.type = kExprLexComparison; - } + ret.type = (schar == '!' ? kExprLexNot : kExprLexAssignment); + ret.data.ass.type = kExprAsgnPlain; break; } ret.type = kExprLexComparison; @@ -548,8 +547,11 @@ viml_pexpr_next_token_invalid_comparison: } else if (pline.data[1] == '~') { ret.data.cmp.type = kExprCmpMatches; ret.len++; + } else if (schar == '!') { + ret.type = kExprLexNot; } else { - goto viml_pexpr_next_token_invalid_comparison; + ret.type = kExprLexAssignment; + ret.data.ass.type = kExprAsgnPlain; } GET_CCS(ret, pline); break; @@ -571,17 +573,37 @@ viml_pexpr_next_token_invalid_comparison: break; } - // Minus sign or arrow from lambdas. + // Minus sign, arrow from lambdas or augmented assignment. case '-': { if (pline.size > 1 && pline.data[1] == '>') { ret.len++; ret.type = kExprLexArrow; + } else if (pline.size > 1 && pline.data[1] == '=') { + ret.len++; + ret.type = kExprLexAssignment; + ret.data.ass.type = kExprAsgnSubtract; } else { ret.type = kExprLexMinus; } break; } + // Sign or augmented assignment. +#define CHAR_OR_ASSIGN(ch, ch_type, ass_type) \ + case ch: { \ + if (pline.size > 1 && pline.data[1] == '=') { \ + ret.len++; \ + ret.type = kExprLexAssignment; \ + ret.data.ass.type = ass_type; \ + } else { \ + ret.type = ch_type; \ + } \ + break; \ + } + CHAR_OR_ASSIGN('+', kExprLexPlus, kExprAsgnAdd) + CHAR_OR_ASSIGN('.', kExprLexDot, kExprAsgnConcat) +#undef CHAR_OR_ASSIGN + // Expression end because Ex command ended. case NUL: case NL: { @@ -661,6 +683,7 @@ static const char *const eltkn_type_tab[] = { [kExprLexParenthesis] = "Parenthesis", [kExprLexComma] = "Comma", [kExprLexArrow] = "Arrow", + [kExprLexAssignment] = "Assignment", }; const char *const eltkn_cmp_type_tab[] = { @@ -671,6 +694,13 @@ const char *const eltkn_cmp_type_tab[] = { [kExprCmpIdentical] = "Identical", }; +const char *const expr_asgn_type_tab[] = { + [kExprAsgnPlain] = "Plain", + [kExprAsgnAdd] = "Add", + [kExprAsgnSubtract] = "Subtract", + [kExprAsgnConcat] = "Concat", +}; + const char *const ccs_tab[] = { [kCCStrategyUseOption] = "UseOption", [kCCStrategyMatchCase] = "MatchCase", @@ -732,6 +762,8 @@ const char *viml_pexpr_repr_token(const ParserState *const pstate, (int)token.data.cmp.inv) TKNARGS(kExprLexMultiplication, "(type=%s)", eltkn_mul_type_tab[token.data.mul.type]) + TKNARGS(kExprLexAssignment, "(type=%s)", + expr_asgn_type_tab[token.data.ass.type]) TKNARGS(kExprLexRegister, "(name=%s)", intchar2str(token.data.reg.name)) case kExprLexDoubleQuotedString: TKNARGS(kExprLexSingleQuotedString, "(closed=%i)", @@ -811,6 +843,7 @@ const char *const east_node_type_tab[] = { [kExprNodeMod] = "Mod", [kExprNodeOption] = "Option", [kExprNodeEnvironment] = "Environment", + [kExprNodeAssignment] = "Assignment", }; /// Represent `int` character as a string @@ -933,6 +966,7 @@ const uint8_t node_maxchildren[] = { [kExprNodeMod] = 2, [kExprNodeOption] = 0, [kExprNodeEnvironment] = 0, + [kExprNodeAssignment] = 2, }; /// Free memory occupied by AST @@ -993,6 +1027,7 @@ void viml_pexpr_free_ast(ExprAST ast) case kExprNodeLambda: case kExprNodeDictLiteral: case kExprNodeCurlyBracesIdentifier: + case kExprNodeAssignment: case kExprNodeComma: case kExprNodeColon: case kExprNodeArrow: @@ -1111,6 +1146,8 @@ static struct { [kExprNodeCurlyBracesIdentifier] = { kEOpLvlComplexIdentifier, kEOpAssLeft }, + [kExprNodeAssignment] = { kEOpLvlAssignment, kEOpAssLeft }, + [kExprNodeComplexIdentifier] = { kEOpLvlValue, kEOpAssLeft }, [kExprNodePlainIdentifier] = { kEOpLvlValue, kEOpAssNo }, @@ -1478,6 +1515,17 @@ static inline void east_set_error(const ParserState *const pstate, } \ } while (0) +/// Determine whether given parse type is an assignment +/// +/// @param[in] pt Checked parse type. +/// +/// @return true if parsing an assignment, false otherwise. +static inline bool pt_is_assignment(const ExprASTParseType pt) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (pt == kEPTAssignment || pt == kEPTSingleAssignment); +} + /// Structure used to define “string shifts” necessary to map string /// highlighting to actual strings. typedef struct { @@ -1839,6 +1887,9 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) ExprASTParseTypeStack pt_stack; kvi_init(pt_stack); kvi_push(pt_stack, kEPTExpr); + if (flags & kExprFlagsParseLet) { + kvi_push(pt_stack, kEPTAssignment); + } LexExprToken prev_token = { .type = kExprLexMissing }; bool highlighted_prev_spacing = false; // Lambda node, valid when parsing lambda arguments only. @@ -1938,33 +1989,83 @@ viml_pexpr_parse_process_token: // circumstances, and in any case runtime and not parse time errors. (*kv_Z(ast_stack, 1))->type = kExprNodeConcat; } - if (kv_last(pt_stack) == kEPTLambdaArguments - && ((want_node == kENodeOperator + // Pop some stack pt_stack items in case of misplaced nodes. + const bool is_single_assignment = kv_last(pt_stack) == kEPTSingleAssignment; + switch (kv_last(pt_stack)) { + case kEPTExpr: { + break; + } + case kEPTLambdaArguments: { + if ((want_node == kENodeOperator && tok_type != kExprLexComma && tok_type != kExprLexArrow) || (want_node == kENodeValue && !(cur_token.type == kExprLexPlainIdentifier && cur_token.data.var.scope == kExprVarScopeMissing && !cur_token.data.var.autoload) - && tok_type != kExprLexArrow))) { - lambda_node->data.fig.type_guesses.allow_lambda = false; - if (lambda_node->children != NULL - && lambda_node->children->type == kExprNodeComma) { - // If lambda has comma child this means that parser has already seen at - // least "{arg1,", so node cannot possibly be anything, but lambda. - - // Vim may give E121 or E720 in this case, but it does not look right to - // have either because both are results of reevaluation possibly-lambda - // node as a dictionary and here this is not going to happen. - ERROR_FROM_TOKEN_AND_MSG( - cur_token, _("E15: Expected lambda arguments list or arrow: %.*s")); - } else { - // Else it may appear that possibly-lambda node is actually a dictionary - // or curly-braces-name identifier. - lambda_node = NULL; - kv_drop(pt_stack, 1); + && tok_type != kExprLexArrow)) { + lambda_node->data.fig.type_guesses.allow_lambda = false; + if (lambda_node->children != NULL + && lambda_node->children->type == kExprNodeComma) { + // If lambda has comma child this means that parser has already seen at + // least "{arg1,", so node cannot possibly be anything, but lambda. + + // Vim may give E121 or E720 in this case, but it does not look right to + // have either because both are results of reevaluation possibly-lambda + // node as a dictionary and here this is not going to happen. + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Expected lambda arguments list or arrow: %.*s")); + } else { + // Else it may appear that possibly-lambda node is actually a dictionary + // or curly-braces-name identifier. + lambda_node = NULL; + kv_drop(pt_stack, 1); + } + } + break; + } + case kEPTSingleAssignment: { + if (tok_type == kExprLexBracket && !cur_token.data.brc.closing) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E475: Nested lists not allowed when assigning: %.*s")); + kv_drop(pt_stack, 2); + assert(kv_size(pt_stack)); + assert(kv_last(pt_stack) == kEPTExpr); + break; + } + FALLTHROUGH; + } + case kEPTAssignment: { + if (want_node == kENodeValue + && tok_type != kExprLexBracket + && tok_type != kExprLexPlainIdentifier + && (tok_type != kExprLexFigureBrace || cur_token.data.brc.closing) + && !(node_is_key && tok_type == kExprLexNumber) + && tok_type != kExprLexEnv + && tok_type != kExprLexOption + && tok_type != kExprLexRegister) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected value part of assignment lvalue: %.*s")); + kv_drop(pt_stack, 1); + } else if (want_node == kENodeOperator + && tok_type != kExprLexBracket + && (tok_type != kExprLexFigureBrace + || cur_token.data.brc.closing) + && tok_type != kExprLexDot + && (tok_type != kExprLexComma || !is_single_assignment) + && tok_type != kExprLexAssignment) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected assignment operator or subscript: %.*s")); + kv_drop(pt_stack, 1); + } + assert(kv_size(pt_stack)); + break; } } + assert(kv_size(pt_stack)); const ExprASTParseType cur_pt = kv_last(pt_stack); assert(lambda_node == NULL || cur_pt == kEPTLambdaArguments); switch (tok_type) { @@ -2339,21 +2440,41 @@ viml_pexpr_parse_bracket_closing_error: } kvi_push(ast_stack, new_top_node_p); want_node = kENodeOperator; + if (cur_pt == kEPTSingleAssignment) { + kv_drop(pt_stack, 1); + } else if (cur_pt == kEPTAssignment) { + assert(ast.err.msg); + } else if (cur_pt == kEPTExpr + && kv_size(pt_stack) > 1 + && pt_is_assignment(kv_Z(pt_stack, 1))) { + kv_drop(pt_stack, 1); + } } else { if (want_node == kENodeValue) { - // Value means list literal. + // Value means list literal or list assignment. HL_CUR_TOKEN(List); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeListLiteral); *top_node_p = cur_node; kvi_push(ast_stack, &cur_node->children); want_node = kENodeValue; + if (cur_pt == kEPTAssignment) { + // Additional assignment parse type allows to easily forbid nested + // lists. + kvi_push(pt_stack, kEPTSingleAssignment); + } } else { + // Operator means subscript, also in assignment. But in assignment + // subscript may be pretty much any expression, so need to push + // kEPTExpr. if (prev_token.type == kExprLexSpacing) { OP_MISSING; } NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeSubscript); ADD_OP_NODE(cur_node); HL_CUR_TOKEN(SubscriptBracket); + if (pt_is_assignment(cur_pt)) { + kvi_push(pt_stack, kEPTExpr); + } } } break; @@ -2458,15 +2579,31 @@ viml_pexpr_parse_figure_brace_closing_error: } kvi_push(ast_stack, new_top_node_p); want_node = kENodeOperator; + if (cur_pt == kEPTExpr + && kv_size(pt_stack) > 1 + && pt_is_assignment(kv_Z(pt_stack, 1))) { + kv_drop(pt_stack, 1); + } } else { if (want_node == kENodeValue) { HL_CUR_TOKEN(FigureBrace); // Value: may be any of lambda, dictionary literal and curly braces // name. - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnknownFigure); - cur_node->data.fig.type_guesses.allow_lambda = true; - cur_node->data.fig.type_guesses.allow_dict = true; - cur_node->data.fig.type_guesses.allow_ident = true; + + // Though if we are in an assignment this may only be a curly braces + // name. + if (pt_is_assignment(cur_pt)) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCurlyBracesIdentifier); + cur_node->data.fig.type_guesses.allow_lambda = false; + cur_node->data.fig.type_guesses.allow_dict = false; + cur_node->data.fig.type_guesses.allow_ident = true; + kvi_push(pt_stack, kEPTExpr); + } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnknownFigure); + cur_node->data.fig.type_guesses.allow_lambda = true; + cur_node->data.fig.type_guesses.allow_dict = true; + cur_node->data.fig.type_guesses.allow_ident = true; + } if (pstate->colors) { cur_node->data.fig.opening_hl_idx = kv_size(*pstate->colors) - 1; } @@ -2484,6 +2621,9 @@ viml_pexpr_parse_figure_brace_closing_error: cur_node->data.fig.type_guesses.allow_dict = false; cur_node->data.fig.type_guesses.allow_ident = true; kvi_push(ast_stack, &cur_node->children); + if (pt_is_assignment(cur_pt)) { + kvi_push(pt_stack, kEPTExpr); + } want_node = kENodeValue; } while (0), Curly); @@ -2746,6 +2886,36 @@ viml_pexpr_parse_no_paren_closing_error: {} want_node = kENodeOperator; break; } + case kExprLexAssignment: { + if (cur_pt == kEPTAssignment) { + kv_drop(pt_stack, 1); + } else if (cur_pt == kEPTSingleAssignment) { + kv_drop(pt_stack, 2); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E475: Expected closing bracket to end list assignment " + "lvalue: %.*s")); + } else { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Misplaced assignment: %.*s")); + } + assert(kv_size(pt_stack)); + assert(kv_last(pt_stack) == kEPTExpr); + ADD_VALUE_IF_MISSING(_("E15: Unexpected assignment: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeAssignment); + cur_node->data.ass.type = cur_token.data.ass.type; + switch (cur_token.data.ass.type) { +#define HL_ASGN(asgn, hl) \ + case kExprAsgn##asgn: { HL_CUR_TOKEN(hl); break; } + HL_ASGN(Plain, PlainAssignment) + HL_ASGN(Add, AssignmentWithAddition) + HL_ASGN(Subtract, AssignmentWithSubtraction) + HL_ASGN(Concat, AssignmentWithConcatenation) +#undef HL_ASGN + } + ADD_OP_NODE(cur_node); + break; + } } viml_pexpr_parse_cycle_end: prev_token = cur_token; @@ -2862,6 +3032,7 @@ viml_pexpr_parse_end: // FIXME: Investigate whether above are OK to be present in the stack. break; } + case kExprNodeAssignment: case kExprNodeMod: case kExprNodeDivision: case kExprNodeMultiplication: diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 648f8cbc1f..23e172da75 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -51,6 +51,9 @@ typedef enum { kExprLexParenthesis, ///< Parenthesis, either opening or closing. kExprLexComma, ///< Comma. kExprLexArrow, ///< Arrow, like from lambda expressions. + kExprLexAssignment, ///< Assignment: `=` or `{op}=`. + // XXX When modifying this enum you need to also modify eltkn_type_tab in + // expressions.c and tests and, possibly, viml_pexpr_repr_token. } LexExprTokenType; typedef enum { @@ -68,6 +71,14 @@ typedef enum { kExprOptScopeLocal = 'l', } ExprOptScope; +/// All possible assignment types: `=` and `{op}=`. +typedef enum { + kExprAsgnPlain = 0, ///< Plain assignment: `=`. + kExprAsgnAdd, ///< Assignment augmented with addition: `+=`. + kExprAsgnSubtract, ///< Assignment augmented with subtraction: `-=`. + kExprAsgnConcat, ///< Assignment augmented with concatenation: `.=`. +} ExprAssignmentType; + #define EXPR_OPT_SCOPE_LIST \ ((char[]){ kExprOptScopeGlobal, kExprOptScopeLocal }) @@ -147,6 +158,10 @@ typedef struct { uint8_t base; ///< Base: 2, 8, 10 or 16. bool is_float; ///< True if number is a floating-point. } num; ///< For kExprLexNumber + + struct { + ExprAssignmentType type; + } ass; ///< For kExprLexAssignment } data; ///< Additional data, if needed. } LexExprToken; @@ -170,8 +185,8 @@ typedef enum { /// “EOC” is something like "|". It is fine with emitting EOC at the end of /// string still, with or without this flag set. kELFlagForbidEOC = (1 << 4), - // WARNING: whenever you add a new flag, alter klee_assume() statement in - // viml_expressions_lexer.c. + // XXX Whenever you add a new flag, alter klee_assume() statement in + // viml_expressions_lexer.c. } LexExprFlags; /// Expression AST node type @@ -233,6 +248,10 @@ typedef enum { kExprNodeMod, kExprNodeOption, kExprNodeEnvironment, + kExprNodeAssignment, + // XXX When modifying this list also modify east_node_type_tab both in parser + // and in tests, and you most likely will also have to alter list of + // highlight groups stored in highlight_init_cmdline variable. } ExprASTNodeType; typedef struct expr_ast_node ExprASTNode; @@ -301,6 +320,9 @@ struct expr_ast_node { const char *ident; ///< Environment variable name start. size_t ident_len; ///< Environment variable name length. } env; ///< For kExprNodeEnvironment. + struct { + ExprAssignmentType type; + } ass; ///< For kExprNodeAssignment } data; }; @@ -314,8 +336,15 @@ enum { /// When parsing expressions input by user bar is assumed to be a binary /// operator and other two are spacings. kExprFlagsDisallowEOC = (1 << 1), - // WARNING: whenever you add a new flag, alter klee_assume() statement in - // viml_expressions_parser.c. + /// Parse :let argument + /// + /// That mean that top level node must be an assignment and first nodes + /// belong to lvalues. + kExprFlagsParseLet = (1 << 2), + // XXX whenever you add a new flag, alter klee_assume() statement in + // viml_expressions_parser.c, nvim_parse_expression() flags parsing + // alongside with its documentation and flag sets in check_parsing() + // function in expressions parser functional and unit tests. } ExprParserFlags; /// AST error definition @@ -350,6 +379,9 @@ extern const char *const eltkn_cmp_type_tab[]; /// Array mapping ExprCaseCompareStrategy values to their stringified versions extern const char *const ccs_tab[]; +/// Array mapping ExprAssignmentType values to their stringified versions +extern const char *const expr_asgn_type_tab[]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "viml/parser/expressions.h.generated.h" #endif diff --git a/test/symbolic/klee/viml_expressions_parser.c b/test/symbolic/klee/viml_expressions_parser.c index 9448937430..fd75e8d355 100644 --- a/test/symbolic/klee/viml_expressions_parser.c +++ b/test/symbolic/klee/viml_expressions_parser.c @@ -48,7 +48,8 @@ int main(const int argc, const char *const *const argv, klee_make_symbolic(&shift, sizeof(shift), "shift"); klee_make_symbolic(&flags, sizeof(flags), "flags"); klee_assume(shift < INPUT_SIZE); - klee_assume(flags <= (kExprFlagsMulti|kExprFlagsDisallowEOC)); + klee_assume( + flags <= (kExprFlagsMulti|kExprFlagsDisallowEOC|kExprFlagsParseLet)); #endif ParserLine plines[] = { diff --git a/test/unit/viml/expressions/lexer_spec.lua b/test/unit/viml/expressions/lexer_spec.lua index 75a641c48a..1b57a24ad5 100644 --- a/test/unit/viml/expressions/lexer_spec.lua +++ b/test/unit/viml/expressions/lexer_spec.lua @@ -13,6 +13,7 @@ local conv_ccs = viml_helpers.conv_ccs local new_pstate = viml_helpers.new_pstate local conv_cmp_type = viml_helpers.conv_cmp_type local pstate_set_str = viml_helpers.pstate_set_str +local conv_expr_asgn_type = viml_helpers.conv_expr_asgn_type local shallowcopy = global_helpers.shallowcopy local intchar2lua = global_helpers.intchar2lua @@ -52,6 +53,8 @@ child_call_once(function() [tonumber(lib.kExprLexParenthesis)] = 'Parenthesis', [tonumber(lib.kExprLexComma)] = 'Comma', [tonumber(lib.kExprLexArrow)] = 'Arrow', + + [tonumber(lib.kExprLexAssignment)] = 'Assignment', } eltkn_mul_type_tab = { @@ -118,6 +121,8 @@ local function eltkn2lua(pstate, tkn) ret.data.val = tonumber(tkn.data.num.is_float and tkn.data.num.val.floating or tkn.data.num.val.integer) + elseif ret.type == 'Assignment' then + ret.data = { type = conv_expr_asgn_type(tkn.data.ass.type) } elseif ret.type == 'Invalid' then ret.data = { error = ffi.string(tkn.data.err.msg) } end @@ -198,7 +203,9 @@ describe('Expressions lexer', function() singl_eltkn_test('Question', '?') singl_eltkn_test('Colon', ':') singl_eltkn_test('Dot', '.') + singl_eltkn_test('Assignment', '.=', {type='Concat'}) singl_eltkn_test('Plus', '+') + singl_eltkn_test('Assignment', '+=', {type='Add'}) singl_eltkn_test('Comma', ',') singl_eltkn_test('Multiplication', '*', {type='Mul'}) singl_eltkn_test('Multiplication', '/', {type='Div'}) @@ -266,12 +273,13 @@ describe('Expressions lexer', function() singl_eltkn_test('DoubleQuotedString', '"x\\"', {closed=false}) singl_eltkn_test('DoubleQuotedString', '"\\"x', {closed=false}) singl_eltkn_test('Not', '!') - singl_eltkn_test('Invalid', '=', {error='E15: Expected == or =~: %.*s'}) + singl_eltkn_test('Assignment', '=', {type='Plain'}) comparison_test('==', '!=', 'Equal') comparison_test('=~', '!~', 'Matches') comparison_test('>', '<=', 'Greater') comparison_test('>=', '<', 'GreaterOrEqual') singl_eltkn_test('Minus', '-') + singl_eltkn_test('Assignment', '-=', {type='Subtract'}) singl_eltkn_test('Arrow', '->') singl_eltkn_test('Invalid', '~', {error='E15: Unidentified character: %.*s'}) simple_test({{data=nil, size=0}}, 'EOC', 0, {error='start.col >= #pstr'}) diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index cfc9fe95ac..810d7bfbc6 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -18,6 +18,7 @@ local conv_ccs = viml_helpers.conv_ccs local new_pstate = viml_helpers.new_pstate local conv_cmp_type = viml_helpers.conv_cmp_type local pstate_set_str = viml_helpers.pstate_set_str +local conv_expr_asgn_type = viml_helpers.conv_expr_asgn_type local mergedicts_copy = global_helpers.mergedicts_copy local format_string = global_helpers.format_string @@ -109,6 +110,7 @@ make_enum_conv_tab(lib, { 'kExprNodeMod', 'kExprNodeOption', 'kExprNodeEnvironment', + 'kExprNodeAssignment', }, 'kExprNode', function(ret) east_node_type_tab = ret end) local function conv_east_node_type(typ) @@ -174,6 +176,8 @@ local function eastnode2lua(pstate, eastnode, checked_nodes) typ = ('%s(ident=%s)'):format( typ, ffi.string(eastnode.data.env.ident, eastnode.data.env.ident_len)) + elseif typ == 'Assignment' then + typ = ('%s(%s)'):format(typ, conv_expr_asgn_type(eastnode.data.ass.type)) end ret_str = typ .. ':' .. ret_str local can_simplify = not ret.children @@ -3976,27 +3980,20 @@ describe('Expressions parser', function() -- 012345 ast = { { - 'Comparison(type=Equal,inv=0,ccs=UseOption):0:3:=', + 'Assignment(Add):0:1: +=', children = { - { - 'BinaryPlus:0:1: +', - children = { - 'PlainIdentifier(scope=0,ident=a):0:0:a', - 'Missing:0:3:', - }, - }, + 'PlainIdentifier(scope=0,ident=a):0:0:a', 'PlainIdentifier(scope=0,ident=b):0:4: b', }, }, }, err = { - arg = '= b', - msg = 'E15: Expected == or =~: %.*s', + arg = '+= b', + msg = 'E15: Misplaced assignment: %.*s', }, }, { hl('IdentifierName', 'a'), - hl('BinaryPlus', '+', 1), - hl('InvalidComparison', '='), + hl('InvalidAssignmentWithAddition', '+=', 1), hl('IdentifierName', 'b', 1), }) check_parsing('a + b == c + d', { @@ -7347,4 +7344,6 @@ describe('Expressions parser', function() }, }) end) + -- FIXME: Test assignments thoroughly + -- FIXME: Test that parsing assignments can be used for `:for` pre-`in` part. end) diff --git a/test/unit/viml/helpers.lua b/test/unit/viml/helpers.lua index 9d2d7b61c7..9d8102e023 100644 --- a/test/unit/viml/helpers.lua +++ b/test/unit/viml/helpers.lua @@ -107,6 +107,18 @@ local function conv_ccs(ccs) return conv_enum(ccs_tab, ccs) end +local expr_asgn_type_tab +make_enum_conv_tab(lib, { + 'kExprAsgnPlain', + 'kExprAsgnAdd', + 'kExprAsgnSubtract', + 'kExprAsgnConcat', +}, 'kExprAsgn', function(ret) expr_asgn_type_tab = ret end) + +local function conv_expr_asgn_type(expr_asgn_type) + return conv_enum(expr_asgn_type_tab, expr_asgn_type) +end + return { conv_ccs = conv_ccs, pline2lua = pline2lua, @@ -114,4 +126,5 @@ return { new_pstate = new_pstate, conv_cmp_type = conv_cmp_type, pstate_set_str = pstate_set_str, + conv_expr_asgn_type = conv_expr_asgn_type, } |