aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZyX <kp-pav@yandex.ru>2017-09-26 01:22:13 +0300
committerZyX <kp-pav@yandex.ru>2017-10-08 22:25:07 +0300
commit0987d3b10f36202e9f0289b50298e69aaf2fa4d2 (patch)
treeb50f26bf3d35bdbc3a60e674150d36231404665d
parent3cc65ac054976ef7520f0247b430ebef2f9537b7 (diff)
downloadrneovim-0987d3b10f36202e9f0289b50298e69aaf2fa4d2.tar.gz
rneovim-0987d3b10f36202e9f0289b50298e69aaf2fa4d2.tar.bz2
rneovim-0987d3b10f36202e9f0289b50298e69aaf2fa4d2.zip
viml/parser/expressions: Make curly braces name actually work
-rw-r--r--src/nvim/viml/parser/expressions.c116
-rw-r--r--test/unit/viml/expressions/parser_spec.lua384
2 files changed, 450 insertions, 50 deletions
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index 7bee779c49..cabf2dac58 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -909,6 +909,55 @@ static inline void east_set_error(ExprAST *const ret_ast,
} \
} while (0)
+/// Add identifier which should constitute complex identifier node
+///
+/// This one is to be called only in case want_node is kENodeOperator.
+///
+/// @param new_ident_node_code Code used to create a new identifier node and
+/// update want_node and ast_stack, without
+/// a trailing semicolon.
+/// @param hl Highlighting name to use, passed as an argument to #HL.
+#define ADD_IDENT(new_ident_node_code, hl) \
+ do { \
+ assert(want_node == kENodeOperator); \
+ /* Operator: may only be curly braces name, but only under certain */ \
+ /* conditions. */ \
+\
+ /* First condition is that there is no space before a part of complex */ \
+ /* identifier. */ \
+ if (prev_token.type == kExprLexSpacing) { \
+ OP_MISSING; \
+ } \
+ switch ((*top_node_p)->type) { \
+ /* Second is that previous node is one of the identifiers: */ \
+ /* complex, plain, curly braces. */ \
+\
+ /* TODO(ZyX-I): Extend syntax to allow ${expr}. This is needed to */ \
+ /* handle environment variables like those bash uses for */ \
+ /* `export -f`: their names consist not only of alphanumeric */ \
+ /* characetrs. */ \
+ case kExprNodeComplexIdentifier: \
+ case kExprNodePlainIdentifier: \
+ case kExprNodeCurlyBracesIdentifier: { \
+ NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComplexIdentifier); \
+ cur_node->len = 0; \
+ cur_node->children = *top_node_p; \
+ *top_node_p = cur_node; \
+ kvi_push(ast_stack, &cur_node->children->next); \
+ ExprASTNode **const new_top_node_p = kv_last(ast_stack); \
+ assert(*new_top_node_p == NULL); \
+ new_ident_node_code; \
+ *new_top_node_p = cur_node; \
+ HL_CUR_TOKEN(hl); \
+ break; \
+ } \
+ default: { \
+ OP_MISSING; \
+ break; \
+ } \
+ } \
+ } while (0)
+
/// Parse one VimL expression
///
/// @param pstate Parser state.
@@ -1272,40 +1321,18 @@ viml_pexpr_parse_figure_brace_closing_error:
want_node = kENodeArgument;
lambda_node = cur_node;
} else {
- // Operator: may only be curly braces name, but only under certain
- // conditions.
-
- // First condition is that there is no space before {.
- if (prev_token.type == kExprLexSpacing) {
- OP_MISSING;
- }
- switch ((*top_node_p)->type) {
- // Second is that previous node is one of the identifiers:
- // complex, plain, curly braces.
-
- // TODO(ZyX-I): Extend syntax to allow ${expr}. This is needed to
- // handle environment variables like those bash uses for
- // `export -f`: their names consist not only of alphanumeric
- // characetrs.
- case kExprNodeComplexIdentifier:
- case kExprNodePlainIdentifier:
- case kExprNodeCurlyBracesIdentifier: {
- NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComplexIdentifier);
- cur_node->len = 0;
- viml_pexpr_handle_bop(&ast_stack, cur_node, &want_node);
- ExprASTNode *const new_top_node = *kv_last(ast_stack);
- assert(new_top_node->next == NULL);
- NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCurlyBracesIdentifier);
- new_top_node->next = cur_node;
- kvi_push(ast_stack, &cur_node->children);
- HL_CUR_TOKEN(Curly);
- break;
- }
- default: {
- OP_MISSING;
- break;
- }
- }
+ ADD_IDENT(
+ do {
+ NEW_NODE_WITH_CUR_POS(cur_node,
+ kExprNodeCurlyBracesIdentifier);
+ cur_node->data.fig.opening_hl_idx = kv_size(*pstate->colors);
+ 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(ast_stack, &cur_node->children);
+ want_node = kENodeValue;
+ } while (0),
+ Curly);
}
}
break;
@@ -1351,8 +1378,6 @@ viml_pexpr_parse_figure_brace_closing_error:
want_node = (want_node == kENodeArgument
? kENodeArgumentSeparator
: kENodeOperator);
- // FIXME: It is not valid to have scope inside complex identifier,
- // check that.
NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier);
cur_node->data.var.scope = cur_token.data.var.scope;
const size_t scope_shift = (cur_token.data.var.scope == 0
@@ -1374,8 +1399,22 @@ 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 {
- OP_MISSING;
+ if (cur_token.data.var.scope == 0) {
+ ADD_IDENT(
+ do {
+ NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier);
+ cur_node->data.var.scope = cur_token.data.var.scope;
+ cur_node->data.var.ident = pline.data + cur_token.start.col;
+ cur_node->data.var.ident_len = cur_token.len;
+ want_node = kENodeOperator;
+ } while (0),
+ Identifier);
+ } else {
+ OP_MISSING;
+ }
}
break;
}
@@ -1453,7 +1492,8 @@ viml_pexpr_parse_no_paren_closing_error: {}
// intentionally inconsistent and he is not very happy with the
// situation himself.
if ((*top_node_p)->type != kExprNodePlainIdentifier
- && (*top_node_p)->type != kExprNodeComplexIdentifier) {
+ && (*top_node_p)->type != kExprNodeComplexIdentifier
+ && (*top_node_p)->type != kExprNodeCurlyBracesIdentifier) {
OP_MISSING;
}
}
diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua
index b747a40e27..eec4cb5bd9 100644
--- a/test/unit/viml/expressions/parser_spec.lua
+++ b/test/unit/viml/expressions/parser_spec.lua
@@ -1059,7 +1059,7 @@ describe('Expressions parser', function()
hl('CallingParenthesis', ')'),
})
end)
- itp('works with identifiers', function()
+ itp('works with variable names, including curly braces ones', function()
check_parsing('var', 0, {
ast = {
'PlainIdentifier(scope=0,ident=var):0:0:var',
@@ -1084,16 +1084,6 @@ describe('Expressions parser', function()
hl('IdentifierScope', 'g'),
hl('IdentifierScopeDelimiter', ':'),
})
- end)
- itp('works with curly braces', function()
- check_parsing('{}', 0, {
- ast = {
- 'DictLiteral(-di):0:0:{',
- },
- }, {
- hl('Dict', '{'),
- hl('Dict', '}'),
- })
check_parsing('{a}', 0, {
-- 012
ast = {
@@ -1167,6 +1157,209 @@ describe('Expressions parser', function()
hl('Register', '@a'),
hl('Curly', '}'),
})
+ check_parsing('{@a}{@b}', 0, {
+ -- 01234567
+ ast = {
+ {
+ 'ComplexIdentifier:0:4:',
+ children = {
+ {
+ 'CurlyBracesIdentifier(-di):0:0:{',
+ children = {
+ 'Register(name=a):0:1:@a',
+ },
+ },
+ {
+ 'CurlyBracesIdentifier(--i):0:4:{',
+ children = {
+ 'Register(name=b):0:5:@b',
+ },
+ },
+ },
+ },
+ },
+ }, {
+ hl('Curly', '{'),
+ hl('Register', '@a'),
+ hl('Curly', '}'),
+ hl('Curly', '{'),
+ hl('Register', '@b'),
+ hl('Curly', '}'),
+ })
+ check_parsing('g:{@a}', 0, {
+ -- 01234567
+ ast = {
+ {
+ 'ComplexIdentifier:0:2:',
+ children = {
+ 'PlainIdentifier(scope=g,ident=):0:0:g:',
+ {
+ 'CurlyBracesIdentifier(--i):0:2:{',
+ children = {
+ 'Register(name=a):0:3:@a',
+ },
+ },
+ },
+ },
+ },
+ }, {
+ hl('IdentifierScope', 'g'),
+ hl('IdentifierScopeDelimiter', ':'),
+ hl('Curly', '{'),
+ hl('Register', '@a'),
+ hl('Curly', '}'),
+ })
+ check_parsing('{@a}_test', 0, {
+ -- 012345678
+ ast = {
+ {
+ 'ComplexIdentifier:0:4:',
+ children = {
+ {
+ 'CurlyBracesIdentifier(-di):0:0:{',
+ children = {
+ 'Register(name=a):0:1:@a',
+ },
+ },
+ 'PlainIdentifier(scope=0,ident=_test):0:4:_test',
+ },
+ },
+ },
+ }, {
+ hl('Curly', '{'),
+ hl('Register', '@a'),
+ hl('Curly', '}'),
+ hl('Identifier', '_test'),
+ })
+ check_parsing('g:{@a}_test', 0, {
+ -- 01234567890
+ ast = {
+ {
+ 'ComplexIdentifier:0:2:',
+ children = {
+ 'PlainIdentifier(scope=g,ident=):0:0:g:',
+ {
+ 'ComplexIdentifier:0:6:',
+ children = {
+ {
+ 'CurlyBracesIdentifier(--i):0:2:{',
+ children = {
+ 'Register(name=a):0:3:@a',
+ },
+ },
+ 'PlainIdentifier(scope=0,ident=_test):0:6:_test',
+ },
+ },
+ },
+ },
+ },
+ }, {
+ hl('IdentifierScope', 'g'),
+ hl('IdentifierScopeDelimiter', ':'),
+ hl('Curly', '{'),
+ hl('Register', '@a'),
+ hl('Curly', '}'),
+ hl('Identifier', '_test'),
+ })
+ check_parsing('g:{@a}_test()', 0, {
+ -- 0123456789012
+ ast = {
+ {
+ 'Call:0:11:(',
+ children = {
+ {
+ 'ComplexIdentifier:0:2:',
+ children = {
+ 'PlainIdentifier(scope=g,ident=):0:0:g:',
+ {
+ 'ComplexIdentifier:0:6:',
+ children = {
+ {
+ 'CurlyBracesIdentifier(--i):0:2:{',
+ children = {
+ 'Register(name=a):0:3:@a',
+ },
+ },
+ 'PlainIdentifier(scope=0,ident=_test):0:6:_test',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }, {
+ hl('IdentifierScope', 'g'),
+ hl('IdentifierScopeDelimiter', ':'),
+ hl('Curly', '{'),
+ hl('Register', '@a'),
+ hl('Curly', '}'),
+ hl('Identifier', '_test'),
+ hl('CallingParenthesis', '('),
+ hl('CallingParenthesis', ')'),
+ })
+ check_parsing('{@a} ()', 0, {
+ -- 0123456789012
+ ast = {
+ {
+ 'Call:0:4: (',
+ children = {
+ {
+ 'CurlyBracesIdentifier(-di):0:0:{',
+ children = {
+ 'Register(name=a):0:1:@a',
+ },
+ },
+ },
+ },
+ },
+ }, {
+ hl('Curly', '{'),
+ hl('Register', '@a'),
+ hl('Curly', '}'),
+ hl('CallingParenthesis', '(', 1),
+ hl('CallingParenthesis', ')'),
+ })
+ check_parsing('g:{@a} ()', 0, {
+ -- 0123456789012
+ ast = {
+ {
+ 'Call:0:6: (',
+ children = {
+ {
+ 'ComplexIdentifier:0:2:',
+ children = {
+ 'PlainIdentifier(scope=g,ident=):0:0:g:',
+ {
+ 'CurlyBracesIdentifier(--i):0:2:{',
+ children = {
+ 'Register(name=a):0:3:@a',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }, {
+ hl('IdentifierScope', 'g'),
+ hl('IdentifierScopeDelimiter', ':'),
+ hl('Curly', '{'),
+ hl('Register', '@a'),
+ hl('Curly', '}'),
+ hl('CallingParenthesis', '(', 1),
+ hl('CallingParenthesis', ')'),
+ })
+ end)
+ itp('works with lambdas and dictionaries', function()
+ check_parsing('{}', 0, {
+ ast = {
+ 'DictLiteral(-di):0:0:{',
+ },
+ }, {
+ hl('Dict', '{'),
+ hl('Dict', '}'),
+ })
check_parsing('{->@a}', 0, {
ast = {
{
@@ -1971,8 +2164,175 @@ describe('Expressions parser', function()
hl('Comma', ','),
hl('Dict', '}'),
})
+ check_parsing('{({f -> g})(@h)(@i)}', 0, {
+ -- 01234567890123456789
+ -- 0 1
+ ast = {
+ {
+ 'CurlyBracesIdentifier(-di):0:0:{',
+ children = {
+ {
+ 'Call:0:15:(',
+ children = {
+ {
+ 'Call:0:11:(',
+ children = {
+ {
+ 'Nested:0:1:(',
+ children = {
+ {
+ 'Lambda(\\di):0:2:{',
+ children = {
+ 'PlainIdentifier(scope=0,ident=f):0:3:f',
+ {
+ 'Arrow:0:4: ->',
+ children = {
+ 'PlainIdentifier(scope=0,ident=g):0:7: g',
+ },
+ },
+ },
+ },
+ },
+ },
+ 'Register(name=h):0:12:@h',
+ },
+ },
+ 'Register(name=i):0:16:@i',
+ },
+ },
+ },
+ },
+ },
+ }, {
+ hl('Curly', '{'),
+ hl('NestingParenthesis', '('),
+ hl('Lambda', '{'),
+ hl('Identifier', 'f'),
+ hl('Arrow', '->', 1),
+ hl('Identifier', 'g', 1),
+ hl('Lambda', '}'),
+ hl('NestingParenthesis', ')'),
+ hl('CallingParenthesis', '('),
+ hl('Register', '@h'),
+ hl('CallingParenthesis', ')'),
+ hl('CallingParenthesis', '('),
+ hl('Register', '@i'),
+ hl('CallingParenthesis', ')'),
+ hl('Curly', '}'),
+ })
+ -- FIXME the below should not crash
+ check_parsing('a:{{b, c -> @d + @e + ({f -> g})(@h)}(@i)}j', 0, {
+ -- 01234567890123456789012345678901234567890123456
+ -- 0 1 2 3 4
+ ast = {
+ {
+ 'ComplexIdentifier:0:2:',
+ children = {
+ 'PlainIdentifier(scope=a,ident=):0:0:g:',
+ {
+ 'ComplexIdentifier:0:6:',
+ children = {
+ {
+ 'CurlyBracesIdentifier(--i):0:2:{',
+ children = {
+ {
+ 'Call:0:37:(',
+ children = {
+ {
+ 'Lambda(\\di):0:3:{',
+ children = {
+ {
+ 'Comma:0:5:,',
+ children = {
+ 'PlainIdentifier(scope=0,ident=b):0:4:b',
+ 'PlainIdentifier(scope=0,ident=c):0:6: c',
+ },
+ },
+ {
+ 'Arrow:0:8: ->',
+ children = {
+ {
+ 'BinaryPlus:0:19: +',
+ children = {
+ {
+ 'BinaryPlus:0:14: +',
+ children = {
+ 'Register(name=d):0:11: @d',
+ 'Register(name=e):0:16: @e',
+ },
+ },
+ {
+ 'Call:0:32:(',
+ children = {
+ {
+ 'NestingParenthesis:0:21: (',
+ children = {
+ {
+ 'Lambda(\\di):0:23:{',
+ children = {
+ 'PlainIdentifier(scope=0,ident=f):0:24:f',
+ {
+ 'Arrow:0:25: ->',
+ children = {
+ 'PlainIdentifier(scope=0,ident=g):0:28: g',
+ },
+ },
+ },
+ },
+ },
+ },
+ 'Register(name=h):0:33:@h',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ 'Register(name=i):0:38:@i',
+ },
+ },
+ 'PlainIdentifier(scope=0,ident=j):0:42:j',
+ },
+ },
+ 'PlainIdentifier(scope=0,ident=_test):0:42:_test',
+ },
+ },
+ },
+ },
+ },
+ }, {
+ hl('IdentifierScope', 'a'),
+ hl('IdentifierScopeDelimiter', ':'),
+ hl('Curly', '{'),
+ hl('Lambda', '{'),
+ hl('Identifier', 'b'),
+ hl('Comma', ','),
+ hl('Identifier', 'c', 1),
+ hl('Arrow', '->', 1),
+ hl('Register', '@d', 1),
+ hl('BinaryPlus', '+', 1),
+ hl('Register', '@e', 1),
+ hl('BinaryPlus', '+', 1),
+ hl('NestingParenthesis', '('),
+ hl('Lambda', '{'),
+ hl('Identifier', 'f'),
+ hl('Arrow', '->', 1),
+ hl('Identifier', 'g', 1),
+ hl('Lambda', '}'),
+ hl('NestingParenthesis', ')'),
+ hl('CallingParenthesis', '('),
+ hl('Register', '@h'),
+ hl('CallingParenthesis', ')'),
+ hl('Lambda', '}'),
+ hl('CallingParenthesis', '('),
+ hl('Register', '@i'),
+ hl('CallingParenthesis', ')'),
+ hl('Curly', '}'),
+ hl('Identifier', 'j'),
+ })
end)
-- FIXME: Test sequence of arrows inside and outside lambdas.
- -- FIXME: Test multiple arguments calling.
-- FIXME: Test autoload character and scope in lambda arguments.
end)