viml/parser/expressions: Add support for parsing assignments

This commit is contained in:
ZyX 2017-11-12 02:18:43 +03:00
parent 1aa6276c29
commit c7495ebcc0
8 changed files with 325 additions and 58 deletions

View File

@ -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).
/// @param[in] flags Flags:
///
/// Use only "m" to parse like for "<C-r>=", only "E" to
/// parse like for ":echo", empty string for ":let".
/// - "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:

View File

@ -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",

View File

@ -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.
&& 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);
// 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:

View File

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

View File

@ -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[] = {

View File

@ -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'})

View File

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

View File

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