diff options
author | ZyX <kp-pav@yandex.ru> | 2017-09-26 01:22:13 +0300 |
---|---|---|
committer | ZyX <kp-pav@yandex.ru> | 2017-10-08 22:25:07 +0300 |
commit | 0987d3b10f36202e9f0289b50298e69aaf2fa4d2 (patch) | |
tree | b50f26bf3d35bdbc3a60e674150d36231404665d | |
parent | 3cc65ac054976ef7520f0247b430ebef2f9537b7 (diff) | |
download | rneovim-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.c | 116 | ||||
-rw-r--r-- | test/unit/viml/expressions/parser_spec.lua | 384 |
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) |