viml/parser/expressions: Add support for comparison operators

This commit is contained in:
ZyX 2017-10-02 02:41:55 +03:00
parent 6791c57420
commit 6168e1127c
6 changed files with 483 additions and 82 deletions

View File

@ -102,7 +102,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags)
if (ret.len < pline.size \ if (ret.len < pline.size \
&& strchr("?#", pline.data[ret.len]) != NULL) { \ && strchr("?#", pline.data[ret.len]) != NULL) { \
ret.data.cmp.ccs = \ ret.data.cmp.ccs = \
(CaseCompareStrategy)pline.data[ret.len]; \ (ExprCaseCompareStrategy)pline.data[ret.len]; \
ret.len++; \ ret.len++; \
} else { \ } else { \
ret.data.cmp.ccs = kCCStrategyUseOption; \ ret.data.cmp.ccs = kCCStrategyUseOption; \
@ -240,7 +240,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags)
&& ((ret.len == 2 && memcmp(pline.data, "is", 2) == 0) && ((ret.len == 2 && memcmp(pline.data, "is", 2) == 0)
|| (ret.len == 5 && memcmp(pline.data, "isnot", 5) == 0))) { || (ret.len == 5 && memcmp(pline.data, "isnot", 5) == 0))) {
ret.type = kExprLexComparison; ret.type = kExprLexComparison;
ret.data.cmp.type = kExprLexCmpIdentical; ret.data.cmp.type = kExprCmpIdentical;
ret.data.cmp.inv = (ret.len == 5); ret.data.cmp.inv = (ret.len == 5);
GET_CCS(ret, pline); GET_CCS(ret, pline);
// Scope: `s:`, etc. // Scope: `s:`, etc.
@ -381,10 +381,10 @@ viml_pexpr_next_token_invalid_comparison:
ret.type = kExprLexComparison; ret.type = kExprLexComparison;
ret.data.cmp.inv = (schar == '!'); ret.data.cmp.inv = (schar == '!');
if (pline.data[1] == '=') { if (pline.data[1] == '=') {
ret.data.cmp.type = kExprLexCmpEqual; ret.data.cmp.type = kExprCmpEqual;
ret.len++; ret.len++;
} else if (pline.data[1] == '~') { } else if (pline.data[1] == '~') {
ret.data.cmp.type = kExprLexCmpMatches; ret.data.cmp.type = kExprCmpMatches;
ret.len++; ret.len++;
} else { } else {
goto viml_pexpr_next_token_invalid_comparison; goto viml_pexpr_next_token_invalid_comparison;
@ -404,8 +404,8 @@ viml_pexpr_next_token_invalid_comparison:
GET_CCS(ret, pline); GET_CCS(ret, pline);
ret.data.cmp.inv = (schar == '<'); ret.data.cmp.inv = (schar == '<');
ret.data.cmp.type = ((ret.data.cmp.inv ^ haseqsign) ret.data.cmp.type = ((ret.data.cmp.inv ^ haseqsign)
? kExprLexCmpGreaterOrEqual ? kExprCmpGreaterOrEqual
: kExprLexCmpGreater); : kExprCmpGreater);
break; break;
} }
@ -503,11 +503,11 @@ static const char *const eltkn_type_tab[] = {
}; };
static const char *const eltkn_cmp_type_tab[] = { static const char *const eltkn_cmp_type_tab[] = {
[kExprLexCmpEqual] = "Equal", [kExprCmpEqual] = "Equal",
[kExprLexCmpMatches] = "Matches", [kExprCmpMatches] = "Matches",
[kExprLexCmpGreater] = "Greater", [kExprCmpGreater] = "Greater",
[kExprLexCmpGreaterOrEqual] = "GreaterOrEqual", [kExprCmpGreaterOrEqual] = "GreaterOrEqual",
[kExprLexCmpIdentical] = "Identical", [kExprCmpIdentical] = "Identical",
}; };
static const char *const ccs_tab[] = { static const char *const ccs_tab[] = {
@ -770,7 +770,8 @@ static inline void viml_pexpr_debug_print_token(
// NVimOperator -> Operator // NVimOperator -> Operator
// NVimUnaryOperator -> NVimOperator // NVimUnaryOperator -> NVimOperator
// NVimBinaryOperator -> NVimOperator // NVimBinaryOperator -> NVimOperator
// NVimComparisonOperator -> NVimOperator // NVimComparisonOperator -> NVimBinaryOperator
// NVimComparisonOperatorModifier -> NVimComparisonOperator
// NVimTernary -> NVimOperator // NVimTernary -> NVimOperator
// NVimTernaryColon -> NVimTernary // NVimTernaryColon -> NVimTernary
// //
@ -805,6 +806,8 @@ static inline void viml_pexpr_debug_print_token(
// NVimInvalidIdentifier -> NVimInvalidValue // NVimInvalidIdentifier -> NVimInvalidValue
// NVimInvalidIdentifierScope -> NVimInvalidValue // NVimInvalidIdentifierScope -> NVimInvalidValue
// NVimInvalidIdentifierScopeDelimiter -> NVimInvalidValue // NVimInvalidIdentifierScopeDelimiter -> NVimInvalidValue
// NVimInvalidComparisonOperator -> NVimInvalidOperator
// NVimInvalidComparisonOperatorModifier -> NVimInvalidComparisonOperator
// //
// NVimUnaryPlus -> NVimUnaryOperator // NVimUnaryPlus -> NVimUnaryOperator
// NVimBinaryPlus -> NVimBinaryOperator // NVimBinaryPlus -> NVimBinaryOperator
@ -849,6 +852,8 @@ static const ExprOpLvl node_type_to_op_lvl[] = {
[kExprNodeTernaryValue] = kEOpLvlTernaryValue, [kExprNodeTernaryValue] = kEOpLvlTernaryValue,
[kExprNodeComparison] = kEOpLvlComparison,
[kExprNodeBinaryPlus] = kEOpLvlAddition, [kExprNodeBinaryPlus] = kEOpLvlAddition,
[kExprNodeUnaryPlus] = kEOpLvlUnary, [kExprNodeUnaryPlus] = kEOpLvlUnary,
@ -892,6 +897,8 @@ static const ExprOpAssociativity node_type_to_op_ass[] = {
[kExprNodeTernaryValue] = kEOpAssRight, [kExprNodeTernaryValue] = kEOpAssRight,
[kExprNodeComparison] = kEOpAssRight,
[kExprNodeBinaryPlus] = kEOpAssLeft, [kExprNodeBinaryPlus] = kEOpAssLeft,
[kExprNodeUnaryPlus] = kEOpAssNo, [kExprNodeUnaryPlus] = kEOpAssNo,
@ -935,11 +942,23 @@ static inline ExprOpAssociativity node_ass(const ExprASTNode node)
/// 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.
static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack, ///
/// @param[in] pstate Parser state, used for error reporting.
/// @param ast_stack AST stack. May be popped of some values and will
/// definitely receive new ones.
/// @param bop_node New node to handle.
/// @param[out] want_node_p New value of want_node.
/// @param[out] ast_err Location where error is saved, if any.
///
/// @return True if no errors occurred, false otherwise.
static bool viml_pexpr_handle_bop(const ParserState *const pstate,
ExprASTStack *const ast_stack,
ExprASTNode *const bop_node, ExprASTNode *const bop_node,
ExprASTWantedNode *const want_node_p) ExprASTWantedNode *const want_node_p,
ExprASTError *const ast_err)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{ {
bool ret = true;
ExprASTNode **top_node_p = NULL; ExprASTNode **top_node_p = NULL;
ExprASTNode *top_node; ExprASTNode *top_node;
ExprOpLvl top_node_lvl; ExprOpLvl top_node_lvl;
@ -977,7 +996,6 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack,
break; break;
} }
} while (kv_size(*ast_stack)); } while (kv_size(*ast_stack));
// FIXME: Handle no associativity
if (top_node_ass == kEOpAssLeft || top_node_lvl != bop_node_lvl) { if (top_node_ass == kEOpAssLeft || top_node_lvl != bop_node_lvl) {
// outer(op(x,y)) -> outer(new_op(op(x,y),*)) // outer(op(x,y)) -> outer(new_op(op(x,y),*))
// //
@ -1008,10 +1026,18 @@ static void viml_pexpr_handle_bop(ExprASTStack *const ast_stack,
kvi_push(*ast_stack, top_node_p); kvi_push(*ast_stack, top_node_p);
kvi_push(*ast_stack, &top_node->children->next); kvi_push(*ast_stack, &top_node->children->next);
kvi_push(*ast_stack, &bop_node->children->next); kvi_push(*ast_stack, &bop_node->children->next);
// TODO(ZyX-I): Make this not error, but treat like Python does
if (bop_node->type == kExprNodeComparison) {
east_set_error(pstate, ast_err,
_("E15: Operator is not associative: %.*s"),
bop_node->start);
ret = false;
}
} }
*want_node_p = (*want_node_p == kENodeArgumentSeparator *want_node_p = (*want_node_p == kENodeArgumentSeparator
? kENodeArgument ? kENodeArgument
: kENodeValue); : kENodeValue);
return ret;
} }
/// ParserPosition literal based on ParserPosition pos with columns shifted /// ParserPosition literal based on ParserPosition pos with columns shifted
@ -1074,6 +1100,13 @@ static inline ParserPosition shifted_pos(const ParserPosition pos,
#define MAY_HAVE_NEXT_EXPR \ #define MAY_HAVE_NEXT_EXPR \
(kv_size(ast_stack) == 1) (kv_size(ast_stack) == 1)
/// Add operator node
///
/// @param[in] cur_node Node to add.
#define ADD_OP_NODE(cur_node) \
is_invalid |= !viml_pexpr_handle_bop(pstate, &ast_stack, cur_node, \
&want_node, &ast.err)
/// Record missing operator: for things like /// Record missing operator: for things like
/// ///
/// :echo @a @a /// :echo @a @a
@ -1094,7 +1127,7 @@ static inline ParserPosition shifted_pos(const ParserPosition pos,
ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Missing operator: %.*s")); \ ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Missing operator: %.*s")); \
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOpMissing); \ NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOpMissing); \
cur_node->len = 0; \ cur_node->len = 0; \
viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); \ ADD_OP_NODE(cur_node); \
goto viml_pexpr_parse_process_token; \ goto viml_pexpr_parse_process_token; \
} \ } \
} while (0) } while (0)
@ -1120,34 +1153,33 @@ static inline ParserPosition shifted_pos(const ParserPosition pos,
/// @param[in] msg Error message, assumed to be already translated and /// @param[in] msg Error message, assumed to be already translated and
/// containing a single %token "%.*s". /// containing a single %token "%.*s".
/// @param[in] start Position at which error occurred. /// @param[in] start Position at which error occurred.
static inline void east_set_error(ExprAST *const ret_ast, static inline void east_set_error(const ParserState *const pstate,
const ParserState *const pstate, ExprASTError *const ret_ast_err,
const char *const msg, const char *const msg,
const ParserPosition start) const ParserPosition start)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE
{ {
if (!ret_ast->correct) { if (ret_ast_err->msg != NULL) {
return; return;
} }
const ParserLine pline = pstate->reader.lines.items[start.line]; const ParserLine pline = pstate->reader.lines.items[start.line];
ret_ast->correct = false; ret_ast_err->msg = msg;
ret_ast->err.msg = msg; ret_ast_err->arg_len = (int)(pline.size - start.col);
ret_ast->err.arg_len = (int)(pline.size - start.col); ret_ast_err->arg = pline.data + start.col;
ret_ast->err.arg = pline.data + start.col;
} }
/// Set error from the given token and given message /// Set error from the given token and given message
#define ERROR_FROM_TOKEN_AND_MSG(cur_token, msg) \ #define ERROR_FROM_TOKEN_AND_MSG(cur_token, msg) \
do { \ do { \
is_invalid = true; \ is_invalid = true; \
east_set_error(&ast, pstate, msg, cur_token.start); \ east_set_error(pstate, &ast.err, msg, cur_token.start); \
} while (0) } while (0)
/// Like #ERROR_FROM_TOKEN_AND_MSG, but gets position from a node /// Like #ERROR_FROM_TOKEN_AND_MSG, but gets position from a node
#define ERROR_FROM_NODE_AND_MSG(node, msg) \ #define ERROR_FROM_NODE_AND_MSG(node, msg) \
do { \ do { \
is_invalid = true; \ is_invalid = true; \
east_set_error(&ast, pstate, msg, node->start); \ east_set_error(pstate, &ast.err, msg, node->start); \
} while (0) } while (0)
/// Set error from the given kExprLexInvalid token /// Set error from the given kExprLexInvalid token
@ -1231,7 +1263,6 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{ {
ExprAST ast = { ExprAST ast = {
.correct = true,
.err = { .err = {
.msg = NULL, .msg = NULL,
.arg_len = 0, .arg_len = 0,
@ -1359,12 +1390,38 @@ viml_pexpr_parse_process_token:
HL_CUR_TOKEN(UnaryPlus); HL_CUR_TOKEN(UnaryPlus);
} else { } else {
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinaryPlus); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinaryPlus);
viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); ADD_OP_NODE(cur_node);
HL_CUR_TOKEN(BinaryPlus); HL_CUR_TOKEN(BinaryPlus);
} }
want_node = kENodeValue; want_node = kENodeValue;
break; break;
} }
case kExprLexComparison: {
ADD_VALUE_IF_MISSING(
_("E15: Expected value, got comparison operator: %.*s"));
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComparison);
if (cur_token.type == kExprLexInvalid) {
cur_node->data.cmp.ccs = kCCStrategyUseOption;
cur_node->data.cmp.type = kExprCmpEqual;
cur_node->data.cmp.inv = false;
} else {
cur_node->data.cmp.ccs = cur_token.data.cmp.ccs;
cur_node->data.cmp.type = cur_token.data.cmp.type;
cur_node->data.cmp.inv = cur_token.data.cmp.inv;
}
ADD_OP_NODE(cur_node);
if (cur_token.data.cmp.ccs != kCCStrategyUseOption) {
viml_parser_highlight(pstate, cur_token.start, cur_token.len - 1,
HL(ComparisonOperator));
viml_parser_highlight(
pstate, shifted_pos(cur_token.start, cur_token.len - 1), 1,
HL(ComparisonOperatorModifier));
} else {
HL_CUR_TOKEN(ComparisonOperator);
}
want_node = kENodeValue;
break;
}
case kExprLexComma: { case kExprLexComma: {
assert(want_node != kENodeArgument); assert(want_node != kENodeArgument);
if (want_node == kENodeValue) { if (want_node == kENodeValue) {
@ -1415,7 +1472,7 @@ viml_pexpr_parse_invalid_comma:
} }
} }
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComma); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComma);
viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); ADD_OP_NODE(cur_node);
HL_CUR_TOKEN(Comma); HL_CUR_TOKEN(Comma);
break; break;
} }
@ -1474,7 +1531,7 @@ viml_pexpr_parse_invalid_colon:
HL_CUR_TOKEN(TernaryColon); HL_CUR_TOKEN(TernaryColon);
} else { } else {
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); ADD_OP_NODE(cur_node);
HL_CUR_TOKEN(Colon); HL_CUR_TOKEN(Colon);
} }
want_node = kENodeValue; want_node = kENodeValue;
@ -1646,7 +1703,7 @@ viml_pexpr_parse_figure_brace_closing_error:
ERROR_FROM_TOKEN_AND_MSG( ERROR_FROM_TOKEN_AND_MSG(
cur_token, _("E15: Arrow outside of lambda: %.*s")); cur_token, _("E15: Arrow outside of lambda: %.*s"));
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow);
viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); ADD_OP_NODE(cur_node);
} }
want_node = kENodeValue; want_node = kENodeValue;
HL_CUR_TOKEN(Arrow); HL_CUR_TOKEN(Arrow);
@ -1775,7 +1832,7 @@ viml_pexpr_parse_no_paren_closing_error: {}
} }
} }
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCall); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCall);
viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); ADD_OP_NODE(cur_node);
HL_CUR_TOKEN(CallingParenthesis); HL_CUR_TOKEN(CallingParenthesis);
} else { } else {
// Currently it is impossible to reach this. // Currently it is impossible to reach this.
@ -1788,7 +1845,7 @@ viml_pexpr_parse_no_paren_closing_error: {}
case kExprLexQuestion: { case kExprLexQuestion: {
ADD_VALUE_IF_MISSING(_("E15: Expected value, got question mark: %.*s")); ADD_VALUE_IF_MISSING(_("E15: Expected value, got question mark: %.*s"));
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeTernary); NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeTernary);
viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node); ADD_OP_NODE(cur_node);
HL_CUR_TOKEN(Ternary); HL_CUR_TOKEN(Ternary);
ExprASTNode *ter_val_node; ExprASTNode *ter_val_node;
NEW_NODE_WITH_CUR_POS(ter_val_node, kExprNodeTernaryValue); NEW_NODE_WITH_CUR_POS(ter_val_node, kExprNodeTernaryValue);
@ -1808,7 +1865,7 @@ viml_pexpr_parse_cycle_end:
} while (true); } while (true);
viml_pexpr_parse_end: viml_pexpr_parse_end:
if (want_node == kENodeValue) { if (want_node == kENodeValue) {
east_set_error(&ast, pstate, _("E15: Expected value, got EOC: %.*s"), east_set_error(pstate, &ast.err, _("E15: Expected value, got EOC: %.*s"),
pstate->pos); pstate->pos);
} else if (kv_size(ast_stack) != 1) { } else if (kv_size(ast_stack) != 1) {
// Something may be wrong, check whether it really is. // Something may be wrong, check whether it really is.
@ -1819,7 +1876,7 @@ viml_pexpr_parse_end:
// Topmost stack item must be a *finished* value, so it must not be // Topmost stack item must be a *finished* value, so it must not be
// analyzed. E.g. it may contain an already finished nested expression. // analyzed. E.g. it may contain an already finished nested expression.
kv_drop(ast_stack, 1); kv_drop(ast_stack, 1);
while (ast.correct && kv_size(ast_stack)) { while (ast.err.msg == NULL && kv_size(ast_stack)) {
const ExprASTNode *const cur_node = (*kv_pop(ast_stack)); const ExprASTNode *const cur_node = (*kv_pop(ast_stack));
// This should only happen when want_node == kENodeValue. // This should only happen when want_node == kENodeValue.
assert(cur_node != NULL); assert(cur_node != NULL);
@ -1832,14 +1889,14 @@ viml_pexpr_parse_end:
} }
case kExprNodeCall: { case kExprNodeCall: {
east_set_error( east_set_error(
&ast, pstate, pstate, &ast.err,
_("E116: Missing closing parenthesis for function call: %.*s"), _("E116: Missing closing parenthesis for function call: %.*s"),
cur_node->start); cur_node->start);
break; break;
} }
case kExprNodeNested: { case kExprNodeNested: {
east_set_error( east_set_error(
&ast, pstate, pstate, &ast.err,
_("E110: Missing closing parenthesis for nested expression" _("E110: Missing closing parenthesis for nested expression"
": %.*s"), ": %.*s"),
cur_node->start); cur_node->start);
@ -1855,7 +1912,7 @@ viml_pexpr_parse_end:
if (!cur_node->data.ter.got_colon) { if (!cur_node->data.ter.got_colon) {
// Actually Vim throws E109 in more cases. // Actually Vim throws E109 in more cases.
east_set_error( east_set_error(
&ast, pstate, _("E109: Missing ':' after '?': %.*s"), pstate, &ast.err, _("E109: Missing ':' after '?': %.*s"),
cur_node->start); cur_node->start);
} }
break; break;

View File

@ -16,7 +16,7 @@ typedef enum {
kCCStrategyUseOption = 0, // 0 for xcalloc kCCStrategyUseOption = 0, // 0 for xcalloc
kCCStrategyMatchCase = '#', kCCStrategyMatchCase = '#',
kCCStrategyIgnoreCase = '?', kCCStrategyIgnoreCase = '?',
} CaseCompareStrategy; } ExprCaseCompareStrategy;
/// Lexer token type /// Lexer token type
typedef enum { typedef enum {
@ -52,6 +52,14 @@ typedef enum {
kExprLexArrow, ///< Arrow, like from lambda expressions. kExprLexArrow, ///< Arrow, like from lambda expressions.
} LexExprTokenType; } LexExprTokenType;
typedef enum {
kExprCmpEqual, ///< Equality, unequality.
kExprCmpMatches, ///< Matches regex, not matches regex.
kExprCmpGreater, ///< `>` or `<=`
kExprCmpGreaterOrEqual, ///< `>=` or `<`.
kExprCmpIdentical, ///< `is` or `isnot`
} ExprComparisonType;
/// Lexer token /// Lexer token
typedef struct { typedef struct {
ParserPosition start; ParserPosition start;
@ -59,14 +67,8 @@ typedef struct {
LexExprTokenType type; LexExprTokenType type;
union { union {
struct { struct {
enum { ExprComparisonType type; ///< Comparison type.
kExprLexCmpEqual, ///< Equality, unequality. ExprCaseCompareStrategy ccs; ///< Case comparison strategy.
kExprLexCmpMatches, ///< Matches regex, not matches regex.
kExprLexCmpGreater, ///< `>` or `<=`
kExprLexCmpGreaterOrEqual, ///< `>=` or `<`.
kExprLexCmpIdentical, ///< `is` or `isnot`
} type; ///< Comparison type.
CaseCompareStrategy ccs; ///< Case comparison strategy.
bool inv; ///< True if comparison is to be inverted. bool inv; ///< True if comparison is to be inverted.
} cmp; ///< For kExprLexComparison. } cmp; ///< For kExprLexComparison.
@ -171,6 +173,7 @@ typedef enum {
kExprNodeComma = ',', ///< Comma “operator”. kExprNodeComma = ',', ///< Comma “operator”.
kExprNodeColon = ':', ///< Colon “operator”. kExprNodeColon = ':', ///< Colon “operator”.
kExprNodeArrow = '>', ///< Arrow “operator”. kExprNodeArrow = '>', ///< Arrow “operator”.
kExprNodeComparison = '=', ///< Various comparison operators.
} ExprASTNodeType; } ExprASTNodeType;
typedef struct expr_ast_node ExprASTNode; typedef struct expr_ast_node ExprASTNode;
@ -214,6 +217,11 @@ struct expr_ast_node {
struct { struct {
bool got_colon; ///< True if colon was seen. bool got_colon; ///< True if colon was seen.
} ter; ///< For kExprNodeTernaryValue. } ter; ///< For kExprNodeTernaryValue.
struct {
ExprComparisonType type; ///< Comparison type.
ExprCaseCompareStrategy ccs; ///< Case comparison strategy.
bool inv; ///< True if comparison is to be inverted.
} cmp; ///< For kExprNodeComparison.
} data; } data;
}; };
@ -235,19 +243,22 @@ enum {
// viml_expressions_parser.c. // viml_expressions_parser.c.
} ExprParserFlags; } ExprParserFlags;
/// AST error definition
typedef struct {
/// Error message. Must contain a single printf format atom: %.*s.
const char *msg;
/// Error message argument: points to the location of the error.
const char *arg;
/// Message argument length: length till the end of string.
int arg_len;
} ExprASTError;
/// Structure representing complety AST for one expression /// Structure representing complety AST for one expression
typedef struct { typedef struct {
/// True if represented AST is correct and can be executed. Incorrect ones may
/// still be used for completion, or in linters.
bool correct;
/// When AST is not correct this message will be printed. /// When AST is not correct this message will be printed.
/// ///
/// Uses `emsgf(msg, arg_len, arg);`, `msg` is assumed to contain only `%.*s`. /// Uses `emsgf(msg, arg_len, arg);`, `msg` is assumed to contain only `%.*s`.
struct { ExprASTError err;
const char *msg;
int arg_len;
const char *arg;
} err;
/// Root node of the AST. /// Root node of the AST.
ExprASTNode *root; ExprASTNode *root;
} ExprAST; } ExprAST;

View File

@ -91,12 +91,6 @@ int main(const int argc, const char *const *const argv,
const ExprAST ast = viml_pexpr_parse(&pstate, flags); const ExprAST ast = viml_pexpr_parse(&pstate, flags);
assert(ast.root != NULL assert(ast.root != NULL
|| plines[0].size == 0); || plines[0].size == 0);
assert(ast.root != NULL || !ast.correct); assert(ast.root != NULL || ast.err.msg);
assert(ast.correct
|| (ast.err.msg != NULL
&& ast.err.arg != NULL
&& ast.err.arg >= plines[0].data
&& ((size_t)(ast.err.arg - plines[0].data) + ast.err.arg_len
<= plines[0].size)));
// FIXME: free memory and assert no memory leaks // FIXME: free memory and assert no memory leaks
} }

View File

@ -1,7 +1,7 @@
local helpers = require('test.unit.helpers')(after_each) local helpers = require('test.unit.helpers')(after_each)
local viml_helpers = require('test.unit.viml.helpers')
local global_helpers = require('test.helpers') local global_helpers = require('test.helpers')
local itp = helpers.gen_itp(it) local itp = helpers.gen_itp(it)
local viml_helpers = require('test.unit.viml.helpers')
local child_call_once = helpers.child_call_once local child_call_once = helpers.child_call_once
local conv_enum = helpers.conv_enum local conv_enum = helpers.conv_enum
@ -9,17 +9,18 @@ local cimport = helpers.cimport
local ffi = helpers.ffi local ffi = helpers.ffi
local eq = helpers.eq local eq = helpers.eq
local conv_ccs = viml_helpers.conv_ccs
local pline2lua = viml_helpers.pline2lua local pline2lua = viml_helpers.pline2lua
local new_pstate = viml_helpers.new_pstate local new_pstate = viml_helpers.new_pstate
local intchar2lua = viml_helpers.intchar2lua local intchar2lua = viml_helpers.intchar2lua
local conv_cmp_type = viml_helpers.conv_cmp_type
local pstate_set_str = viml_helpers.pstate_set_str local pstate_set_str = viml_helpers.pstate_set_str
local shallowcopy = global_helpers.shallowcopy local shallowcopy = global_helpers.shallowcopy
local lib = cimport('./src/nvim/viml/parser/expressions.h') local lib = cimport('./src/nvim/viml/parser/expressions.h')
local eltkn_type_tab, eltkn_cmp_type_tab, ccs_tab, eltkn_mul_type_tab local eltkn_type_tab, eltkn_mul_type_tab, eltkn_opt_scope_tab
local eltkn_opt_scope_tab
child_call_once(function() child_call_once(function()
eltkn_type_tab = { eltkn_type_tab = {
[tonumber(lib.kExprLexInvalid)] = 'Invalid', [tonumber(lib.kExprLexInvalid)] = 'Invalid',
@ -54,20 +55,6 @@ child_call_once(function()
[tonumber(lib.kExprLexArrow)] = 'Arrow', [tonumber(lib.kExprLexArrow)] = 'Arrow',
} }
eltkn_cmp_type_tab = {
[tonumber(lib.kExprLexCmpEqual)] = 'Equal',
[tonumber(lib.kExprLexCmpMatches)] = 'Matches',
[tonumber(lib.kExprLexCmpGreater)] = 'Greater',
[tonumber(lib.kExprLexCmpGreaterOrEqual)] = 'GreaterOrEqual',
[tonumber(lib.kExprLexCmpIdentical)] = 'Identical',
}
ccs_tab = {
[tonumber(lib.kCCStrategyUseOption)] = 'UseOption',
[tonumber(lib.kCCStrategyMatchCase)] = 'MatchCase',
[tonumber(lib.kCCStrategyIgnoreCase)] = 'IgnoreCase',
}
eltkn_mul_type_tab = { eltkn_mul_type_tab = {
[tonumber(lib.kExprLexMulMul)] = 'Mul', [tonumber(lib.kExprLexMulMul)] = 'Mul',
[tonumber(lib.kExprLexMulDiv)] = 'Div', [tonumber(lib.kExprLexMulDiv)] = 'Div',
@ -101,8 +88,8 @@ local function eltkn2lua(pstate, tkn)
end end
if ret.type == 'Comparison' then if ret.type == 'Comparison' then
ret.data = { ret.data = {
type = conv_enum(eltkn_cmp_type_tab, tkn.data.cmp.type), type = conv_cmp_type(tkn.data.cmp.type),
ccs = conv_enum(ccs_tab, tkn.data.cmp.ccs), ccs = conv_ccs(tkn.data.cmp.ccs),
inv = (not not tkn.data.cmp.inv), inv = (not not tkn.data.cmp.inv),
} }
elseif ret.type == 'Multiplication' then elseif ret.type == 'Multiplication' then

View File

@ -1,7 +1,7 @@
local helpers = require('test.unit.helpers')(after_each) local helpers = require('test.unit.helpers')(after_each)
local viml_helpers = require('test.unit.viml.helpers')
local global_helpers = require('test.helpers') local global_helpers = require('test.helpers')
local itp = helpers.gen_itp(it) local itp = helpers.gen_itp(it)
local viml_helpers = require('test.unit.viml.helpers')
local make_enum_conv_tab = helpers.make_enum_conv_tab local make_enum_conv_tab = helpers.make_enum_conv_tab
local child_call_once = helpers.child_call_once local child_call_once = helpers.child_call_once
@ -11,9 +11,11 @@ local cimport = helpers.cimport
local ffi = helpers.ffi local ffi = helpers.ffi
local eq = helpers.eq local eq = helpers.eq
local conv_ccs = viml_helpers.conv_ccs
local pline2lua = viml_helpers.pline2lua local pline2lua = viml_helpers.pline2lua
local new_pstate = viml_helpers.new_pstate local new_pstate = viml_helpers.new_pstate
local intchar2lua = viml_helpers.intchar2lua local intchar2lua = viml_helpers.intchar2lua
local conv_cmp_type = viml_helpers.conv_cmp_type
local pstate_set_str = viml_helpers.pstate_set_str local pstate_set_str = viml_helpers.pstate_set_str
local format_string = global_helpers.format_string local format_string = global_helpers.format_string
@ -83,6 +85,7 @@ make_enum_conv_tab(lib, {
'kExprNodeComma', 'kExprNodeComma',
'kExprNodeColon', 'kExprNodeColon',
'kExprNodeArrow', 'kExprNodeArrow',
'kExprNodeComparison',
}, 'kExprNode', function(ret) east_node_type_tab = ret end) }, 'kExprNode', function(ret) east_node_type_tab = ret end)
local function conv_east_node_type(typ) local function conv_east_node_type(typ)
@ -121,6 +124,10 @@ local function eastnode2lua(pstate, eastnode, checked_nodes)
(eastnode.data.fig.type_guesses.allow_lambda and '\\' or '-') (eastnode.data.fig.type_guesses.allow_lambda and '\\' or '-')
.. (eastnode.data.fig.type_guesses.allow_dict and 'd' or '-') .. (eastnode.data.fig.type_guesses.allow_dict and 'd' or '-')
.. (eastnode.data.fig.type_guesses.allow_ident and 'i' or '-')) .. (eastnode.data.fig.type_guesses.allow_ident and 'i' or '-'))
elseif typ == 'Comparison' then
typ = typ .. ('(type=%s,inv=%u,ccs=%s)'):format(
conv_cmp_type(eastnode.data.cmp.type), eastnode.data.cmp.inv and 1 or 0,
conv_ccs(eastnode.data.cmp.ccs))
end end
ret_str = typ .. ':' .. ret_str ret_str = typ .. ':' .. ret_str
local can_simplify = true local can_simplify = true
@ -150,7 +157,7 @@ end
local function east2lua(pstate, east) local function east2lua(pstate, east)
local checked_nodes = {} local checked_nodes = {}
return { return {
err = (not east.correct) and { err = east.err.msg ~= nil and {
msg = ffi.string(east.err.msg), msg = ffi.string(east.err.msg),
arg = ('%s'):format( arg = ('%s'):format(
ffi.string(east.err.arg, east.err.arg_len)), ffi.string(east.err.arg, east.err.arg_len)),
@ -3328,4 +3335,318 @@ describe('Expressions parser', function()
hl('Identifier', 'h'), hl('Identifier', 'h'),
}) })
end) end)
itp('works with comparison operators', function()
check_parsing('a == b', 0, {
-- 012345
ast = {
{
'Comparison(type=Equal,inv=0,ccs=UseOption):0:1: ==',
children = {
'PlainIdentifier(scope=0,ident=a):0:0:a',
'PlainIdentifier(scope=0,ident=b):0:4: b',
},
},
},
}, {
hl('Identifier', 'a'),
hl('ComparisonOperator', '==', 1),
hl('Identifier', 'b', 1),
})
check_parsing('a ==? b', 0, {
-- 0123456
ast = {
{
'Comparison(type=Equal,inv=0,ccs=IgnoreCase):0:1: ==?',
children = {
'PlainIdentifier(scope=0,ident=a):0:0:a',
'PlainIdentifier(scope=0,ident=b):0:5: b',
},
},
},
}, {
hl('Identifier', 'a'),
hl('ComparisonOperator', '==', 1),
hl('ComparisonOperatorModifier', '?'),
hl('Identifier', 'b', 1),
})
check_parsing('a ==# b', 0, {
-- 0123456
ast = {
{
'Comparison(type=Equal,inv=0,ccs=MatchCase):0:1: ==#',
children = {
'PlainIdentifier(scope=0,ident=a):0:0:a',
'PlainIdentifier(scope=0,ident=b):0:5: b',
},
},
},
}, {
hl('Identifier', 'a'),
hl('ComparisonOperator', '==', 1),
hl('ComparisonOperatorModifier', '#'),
hl('Identifier', 'b', 1),
})
check_parsing('a !=# b', 0, {
-- 0123456
ast = {
{
'Comparison(type=Equal,inv=1,ccs=MatchCase):0:1: !=#',
children = {
'PlainIdentifier(scope=0,ident=a):0:0:a',
'PlainIdentifier(scope=0,ident=b):0:5: b',
},
},
},
}, {
hl('Identifier', 'a'),
hl('ComparisonOperator', '!=', 1),
hl('ComparisonOperatorModifier', '#'),
hl('Identifier', 'b', 1),
})
check_parsing('a <=# b', 0, {
-- 0123456
ast = {
{
'Comparison(type=Greater,inv=1,ccs=MatchCase):0:1: <=#',
children = {
'PlainIdentifier(scope=0,ident=a):0:0:a',
'PlainIdentifier(scope=0,ident=b):0:5: b',
},
},
},
}, {
hl('Identifier', 'a'),
hl('ComparisonOperator', '<=', 1),
hl('ComparisonOperatorModifier', '#'),
hl('Identifier', 'b', 1),
})
check_parsing('a >=# b', 0, {
-- 0123456
ast = {
{
'Comparison(type=GreaterOrEqual,inv=0,ccs=MatchCase):0:1: >=#',
children = {
'PlainIdentifier(scope=0,ident=a):0:0:a',
'PlainIdentifier(scope=0,ident=b):0:5: b',
},
},
},
}, {
hl('Identifier', 'a'),
hl('ComparisonOperator', '>=', 1),
hl('ComparisonOperatorModifier', '#'),
hl('Identifier', 'b', 1),
})
check_parsing('a ># b', 0, {
-- 012345
ast = {
{
'Comparison(type=Greater,inv=0,ccs=MatchCase):0:1: >#',
children = {
'PlainIdentifier(scope=0,ident=a):0:0:a',
'PlainIdentifier(scope=0,ident=b):0:4: b',
},
},
},
}, {
hl('Identifier', 'a'),
hl('ComparisonOperator', '>', 1),
hl('ComparisonOperatorModifier', '#'),
hl('Identifier', 'b', 1),
})
check_parsing('a <# b', 0, {
-- 012345
ast = {
{
'Comparison(type=GreaterOrEqual,inv=1,ccs=MatchCase):0:1: <#',
children = {
'PlainIdentifier(scope=0,ident=a):0:0:a',
'PlainIdentifier(scope=0,ident=b):0:4: b',
},
},
},
}, {
hl('Identifier', 'a'),
hl('ComparisonOperator', '<', 1),
hl('ComparisonOperatorModifier', '#'),
hl('Identifier', 'b', 1),
})
check_parsing('a is#b', 0, {
-- 012345
ast = {
{
'Comparison(type=Identical,inv=0,ccs=MatchCase):0:1: is#',
children = {
'PlainIdentifier(scope=0,ident=a):0:0:a',
'PlainIdentifier(scope=0,ident=b):0:5:b',
},
},
},
}, {
hl('Identifier', 'a'),
hl('ComparisonOperator', 'is', 1),
hl('ComparisonOperatorModifier', '#'),
hl('Identifier', 'b'),
})
check_parsing('a is?b', 0, {
-- 012345
ast = {
{
'Comparison(type=Identical,inv=0,ccs=IgnoreCase):0:1: is?',
children = {
'PlainIdentifier(scope=0,ident=a):0:0:a',
'PlainIdentifier(scope=0,ident=b):0:5:b',
},
},
},
}, {
hl('Identifier', 'a'),
hl('ComparisonOperator', 'is', 1),
hl('ComparisonOperatorModifier', '?'),
hl('Identifier', 'b'),
})
check_parsing('a isnot b', 0, {
-- 012345678
ast = {
{
'Comparison(type=Identical,inv=1,ccs=UseOption):0:1: isnot',
children = {
'PlainIdentifier(scope=0,ident=a):0:0:a',
'PlainIdentifier(scope=0,ident=b):0:7: b',
},
},
},
}, {
hl('Identifier', 'a'),
hl('ComparisonOperator', 'isnot', 1),
hl('Identifier', 'b', 1),
})
check_parsing('a < b < c', 0, {
-- 012345678
ast = {
{
'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:1: <',
children = {
'PlainIdentifier(scope=0,ident=a):0:0:a',
{
'Comparison(type=GreaterOrEqual,inv=1,ccs=UseOption):0:5: <',
children = {
'PlainIdentifier(scope=0,ident=b):0:3: b',
'PlainIdentifier(scope=0,ident=c):0:7: c',
},
},
},
},
},
err = {
arg = ' < c',
msg = 'E15: Operator is not associative: %.*s',
},
}, {
hl('Identifier', 'a'),
hl('ComparisonOperator', '<', 1),
hl('Identifier', 'b', 1),
hl('InvalidComparisonOperator', '<', 1),
hl('Identifier', 'c', 1),
})
check_parsing('a += b', 0, {
-- 012345
ast = {
{
'Comparison(type=Equal,inv=0,ccs=UseOption):0:3:=',
children = {
{
'BinaryPlus:0:1: +',
children = {
'PlainIdentifier(scope=0,ident=a):0:0:a',
'Missing:0:3:',
},
},
'PlainIdentifier(scope=0,ident=b):0:4: b',
},
},
},
err = {
arg = '= b',
msg = 'E15: Expected == or =~: %.*s',
},
}, {
hl('Identifier', 'a'),
hl('BinaryPlus', '+', 1),
hl('InvalidComparisonOperator', '='),
hl('Identifier', 'b', 1),
})
check_parsing('a + b == c + d', 0, {
-- 01234567890123
-- 0 1
ast = {
{
'Comparison(type=Equal,inv=0,ccs=UseOption):0:5: ==',
children = {
{
'BinaryPlus:0:1: +',
children = {
'PlainIdentifier(scope=0,ident=a):0:0:a',
'PlainIdentifier(scope=0,ident=b):0:3: b',
},
},
{
'BinaryPlus:0:10: +',
children = {
'PlainIdentifier(scope=0,ident=c):0:8: c',
'PlainIdentifier(scope=0,ident=d):0:12: d',
},
},
},
},
},
}, {
hl('Identifier', 'a'),
hl('BinaryPlus', '+', 1),
hl('Identifier', 'b', 1),
hl('ComparisonOperator', '==', 1),
hl('Identifier', 'c', 1),
hl('BinaryPlus', '+', 1),
hl('Identifier', 'd', 1),
})
check_parsing('+ a == + b', 0, {
-- 0123456789
ast = {
{
'Comparison(type=Equal,inv=0,ccs=UseOption):0:3: ==',
children = {
{
'UnaryPlus:0:0:+',
children = {
'PlainIdentifier(scope=0,ident=a):0:1: a',
},
},
{
'UnaryPlus:0:6: +',
children = {
'PlainIdentifier(scope=0,ident=b):0:8: b',
},
},
},
},
},
}, {
hl('UnaryPlus', '+'),
hl('Identifier', 'a', 1),
hl('ComparisonOperator', '==', 1),
hl('UnaryPlus', '+', 1),
hl('Identifier', 'b', 1),
})
end)
end) end)

View File

@ -1,8 +1,13 @@
local helpers = require('test.unit.helpers')(nil) local helpers = require('test.unit.helpers')(nil)
local ffi = helpers.ffi local ffi = helpers.ffi
local cimport = helpers.cimport
local kvi_new = helpers.kvi_new local kvi_new = helpers.kvi_new
local kvi_init = helpers.kvi_init local kvi_init = helpers.kvi_init
local conv_enum = helpers.conv_enum
local make_enum_conv_tab = helpers.make_enum_conv_tab
local lib = cimport('./src/nvim/viml/parser/expressions.h')
local function new_pstate(strings) local function new_pstate(strings)
local strings_idx = 0 local strings_idx = 0
@ -88,10 +93,36 @@ local function pstate_set_str(pstate, start, len, ret)
return ret return ret
end end
local eltkn_cmp_type_tab
make_enum_conv_tab(lib, {
'kExprCmpEqual',
'kExprCmpMatches',
'kExprCmpGreater',
'kExprCmpGreaterOrEqual',
'kExprCmpIdentical',
}, 'kExprCmp', function(ret) eltkn_cmp_type_tab = ret end)
local function conv_cmp_type(typ)
return conv_enum(eltkn_cmp_type_tab, typ)
end
local ccs_tab
make_enum_conv_tab(lib, {
'kCCStrategyUseOption',
'kCCStrategyMatchCase',
'kCCStrategyIgnoreCase',
}, 'kCCStrategy', function(ret) ccs_tab = ret end)
local function conv_ccs(ccs)
return conv_enum(ccs_tab, ccs)
end
return { return {
conv_ccs = conv_ccs,
pline2lua = pline2lua, pline2lua = pline2lua,
pstate_str = pstate_str, pstate_str = pstate_str,
new_pstate = new_pstate, new_pstate = new_pstate,
intchar2lua = intchar2lua, intchar2lua = intchar2lua,
conv_cmp_type = conv_cmp_type,
pstate_set_str = pstate_set_str, pstate_set_str = pstate_set_str,
} }