Skip to content

Commit

Permalink
viml/parser/expressions: Make curly braces name actually work
Browse files Browse the repository at this point in the history
  • Loading branch information
ZyX-I committed Sep 26, 2017
1 parent 5c7b0c5 commit 549be54
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 49 deletions.
109 changes: 72 additions & 37 deletions src/nvim/viml/parser/expressions.c
Expand Up @@ -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.
Expand Down Expand Up @@ -1273,40 +1322,14 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags)
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);
kvi_push(ast_stack, &cur_node->children);
want_node = kENodeValue;
} while (0),
Curly);
}
}
break;
Expand Down Expand Up @@ -1352,8 +1375,6 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags)
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
Expand All @@ -1375,8 +1396,22 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags)
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;
}
Expand Down
100 changes: 88 additions & 12 deletions test/unit/viml/expressions/parser_spec.lua
Expand Up @@ -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',
Expand All @@ -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 = {
Expand Down Expand Up @@ -1167,6 +1157,93 @@ 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(\\di):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(\\di):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'),
})
-- FIXME test calling complex identifiers
-- FIXME test calling complex identifiers, separated by whitespace from call
-- FIXME test complex identifiers, including mixed ones
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 = {
{
Expand Down Expand Up @@ -1973,6 +2050,5 @@ describe('Expressions parser', function()
})
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)

0 comments on commit 549be54

Please sign in to comment.