[ruby/prism] Pass block exits up the tree

https://github.com/ruby/prism/commit/168f72b9fe
This commit is contained in:
Kevin Newton 2024-04-03 10:36:10 -04:00
Родитель 1fb11824f3
Коммит be48b733b6
4 изменённых файлов: 242 добавлений и 116 удалений

Просмотреть файл

@ -24,9 +24,10 @@
* this function returns false, otherwise it returns true.
*
* @param list The list to grow.
* @param size The number of nodes to grow the list by.
* @return True if the list was successfully grown, false otherwise.
*/
bool pm_node_list_grow(pm_node_list_t *list);
bool pm_node_list_grow(pm_node_list_t *list, size_t size);
/**
* Append a new node onto the end of the node list.
@ -42,8 +43,15 @@ void pm_node_list_append(pm_node_list_t *list, pm_node_t *node);
* @param list The list to prepend to.
* @param node The node to prepend.
*/
void
pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node);
void pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node);
/**
* Concatenate the given node list onto the end of the other node list.
*
* @param list The list to concatenate onto.
* @param other The list to concatenate.
*/
void pm_node_list_concat(pm_node_list_t *list, pm_node_list_t *other);
/**
* Free the internal memory associated with the given node list.

Просмотреть файл

@ -358,6 +358,9 @@ typedef enum {
/** a singleton class definition */
PM_CONTEXT_SCLASS,
/** a ternary expression */
PM_CONTEXT_TERNARY,
/** an unless statement */
PM_CONTEXT_UNLESS,

Просмотреть файл

@ -62,6 +62,7 @@ debug_context(pm_context_t context) {
case PM_CONTEXT_RESCUE_ELSE_DEF: return "RESCUE_ELSE_DEF";
case PM_CONTEXT_RESCUE_DEF: return "RESCUE_DEF";
case PM_CONTEXT_SCLASS: return "SCLASS";
case PM_CONTEXT_TERNARY: return "TERNARY";
case PM_CONTEXT_UNLESS: return "UNLESS";
case PM_CONTEXT_UNTIL: return "UNTIL";
case PM_CONTEXT_WHILE: return "WHILE";
@ -7598,6 +7599,7 @@ context_terminator(pm_context_t context, pm_token_t *token) {
case PM_CONTEXT_MAIN:
case PM_CONTEXT_DEF_PARAMS:
case PM_CONTEXT_DEFINED:
case PM_CONTEXT_TERNARY:
return token->type == PM_TOKEN_EOF;
case PM_CONTEXT_DEFAULT_PARAMS:
return token->type == PM_TOKEN_COMMA || token->type == PM_TOKEN_PARENTHESIS_RIGHT;
@ -7770,6 +7772,7 @@ context_human(pm_context_t context) {
case PM_CONTEXT_RESCUE: return "'rescue' clause";
case PM_CONTEXT_RESCUE_DEF: return "'rescue' clause";
case PM_CONTEXT_SCLASS: return "singleton class definition";
case PM_CONTEXT_TERNARY: return "ternary expression";
case PM_CONTEXT_UNLESS: return "unless statement";
case PM_CONTEXT_UNTIL: return "until statement";
case PM_CONTEXT_WHILE: return "while statement";
@ -13826,6 +13829,144 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept
return found;
}
/**
* Check that the block exit (next, break, redo) is allowed in the current
* context. If it isn't, add an error to the parser.
*/
static void
parse_block_exit(pm_parser_t *parser, pm_node_t *node, const char *type) {
pm_context_node_t *context_node = parser->current_context;
bool through_expression = false;
while (context_node != NULL) {
switch (context_node->context) {
case PM_CONTEXT_BLOCK_BRACES:
case PM_CONTEXT_BLOCK_KEYWORDS:
case PM_CONTEXT_DEFINED:
case PM_CONTEXT_FOR:
case PM_CONTEXT_LAMBDA_BRACES:
case PM_CONTEXT_LAMBDA_DO_END:
case PM_CONTEXT_POSTEXE:
case PM_CONTEXT_UNTIL:
case PM_CONTEXT_WHILE:
// These are the good cases. We're allowed to have a block exit
// in these contexts.
return;
case PM_CONTEXT_CLASS:
case PM_CONTEXT_DEF:
case PM_CONTEXT_DEF_PARAMS:
case PM_CONTEXT_ENSURE_DEF:
case PM_CONTEXT_MAIN:
case PM_CONTEXT_MODULE:
case PM_CONTEXT_PREEXE:
case PM_CONTEXT_RESCUE_DEF:
case PM_CONTEXT_RESCUE_ELSE_DEF:
case PM_CONTEXT_SCLASS:
// These are the bad cases. We're not allowed to have a block
// exit in these contexts.
if (through_expression) {
// If we get here, then we're about to mark this block exit
// as invalid. However, it could later _become_ valid if we
// find a trailing while/until on the expression. In this
// case instead of adding the error here, we'll add the
// block exit to the list of exits for the expression, and
// the node parsing will handle validating it instead.
assert(parser->current_block_exits != NULL);
pm_node_list_append(parser->current_block_exits, node);
} else {
// Otherwise, if we haven't gone through an expression
// context, then this is just invalid and we'll add the
// error here.
PM_PARSER_ERR_NODE_FORMAT(parser, node, PM_ERR_INVALID_BLOCK_EXIT, type);
}
return;
case PM_CONTEXT_NONE:
// This case should never happen.
assert(false && "unreachable");
break;
case PM_CONTEXT_BEGIN:
case PM_CONTEXT_CASE_IN:
case PM_CONTEXT_CASE_WHEN:
case PM_CONTEXT_ELSE:
case PM_CONTEXT_ELSIF:
case PM_CONTEXT_ENSURE:
case PM_CONTEXT_IF:
case PM_CONTEXT_PARENS:
case PM_CONTEXT_RESCUE_ELSE:
case PM_CONTEXT_RESCUE:
case PM_CONTEXT_TERNARY:
case PM_CONTEXT_UNLESS:
// If we got to an expression that could be modified by a
// trailing while/until, then we'll track that we have gotten
// here because we need to know it if this block exit is later
// marked as invalid.
through_expression = true;
break;
case PM_CONTEXT_EMBEXPR:
case PM_CONTEXT_DEFAULT_PARAMS:
case PM_CONTEXT_FOR_INDEX:
case PM_CONTEXT_PREDICATE:
// In these contexts we should continue walking up the list of
// contexts.
break;
}
context_node = context_node->prev;
}
}
/**
* When we hit an expression that could contain block exits, we need to stash
* the previous set and create a new one.
*/
static pm_node_list_t *
push_block_exits(pm_parser_t *parser, pm_node_list_t *current_block_exits) {
pm_node_list_t *previous_block_exits = parser->current_block_exits;
parser->current_block_exits = current_block_exits;
return previous_block_exits;
}
/**
* Pop the current level of block exits from the parser, and add errors to the
* parser if any of them are deemed to be invalid.
*/
static void
pop_block_exits(pm_parser_t *parser, pm_node_list_t *previous_block_exits) {
if (match2(parser, PM_TOKEN_KEYWORD_WHILE_MODIFIER, PM_TOKEN_KEYWORD_UNTIL_MODIFIER)) {
// If we matched a trailing while/until, then all of the block exits in
// the contained list are valid. In this case we do not need to do
// anything.
} else if (previous_block_exits != NULL) {
// If we did not matching a trailing while/until, then all of the block
// exits contained in the list are invalid for this specific context.
// However, they could still become valid in a higher level context if
// there is another list above this one. In this case we'll push all of
// the block exits up to the previous list.
pm_node_list_concat(previous_block_exits, parser->current_block_exits);
} else {
// If we did not match a trailing while/until and this was the last
// chance to do so, then all of the block exits in the list are invalid
// and we need to add an error for each of them.
pm_node_t *block_exit;
PM_NODE_LIST_FOREACH(parser->current_block_exits, index, block_exit) {
const char *type;
switch (PM_NODE_TYPE(block_exit)) {
case PM_BREAK_NODE: type = "break"; break;
case PM_NEXT_NODE: type = "next"; break;
case PM_REDO_NODE: type = "redo"; break;
default: assert(false && "unreachable"); type = ""; break;
}
PM_PARSER_ERR_NODE_FORMAT(parser, block_exit, PM_ERR_INVALID_BLOCK_EXIT, type);
}
}
parser->current_block_exits = previous_block_exits;
}
static inline pm_node_t *
parse_predicate(pm_parser_t *parser, pm_binding_power_t binding_power, pm_context_t context, pm_token_t *then_keyword) {
context_push(parser, PM_CONTEXT_PREDICATE);
@ -13850,6 +13991,9 @@ parse_predicate(pm_parser_t *parser, pm_binding_power_t binding_power, pm_contex
static inline pm_node_t *
parse_conditional(pm_parser_t *parser, pm_context_t context) {
pm_node_list_t current_block_exits = { 0 };
pm_node_list_t *previous_block_exits = push_block_exits(parser, &current_block_exits);
pm_token_t keyword = parser->previous;
pm_token_t then_keyword = not_provided(parser);
@ -13929,7 +14073,8 @@ parse_conditional(pm_parser_t *parser, pm_context_t context) {
break;
}
} else {
// We should specialize this error message to refer to 'if' or 'unless' explicitly.
// We should specialize this error message to refer to 'if' or 'unless'
// explicitly.
expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CONDITIONAL_TERM);
}
@ -13966,6 +14111,9 @@ parse_conditional(pm_parser_t *parser, pm_context_t context) {
break;
}
pop_block_exits(parser, previous_block_exits);
pm_node_list_free(&current_block_exits);
return parent;
}
@ -15707,88 +15855,6 @@ pm_parser_err_prefix(pm_parser_t *parser, pm_diagnostic_id_t diag_id) {
}
}
/**
* Check that the block exit (next, break, redo) is allowed in the current
* context. If it isn't, add an error to the parser.
*/
static void
parse_block_exit(pm_parser_t *parser, pm_node_t *node, const char *type) {
pm_context_node_t *context_node = parser->current_context;
bool through_begin = false;
while (context_node != NULL) {
switch (context_node->context) {
case PM_CONTEXT_BLOCK_BRACES:
case PM_CONTEXT_BLOCK_KEYWORDS:
case PM_CONTEXT_DEFINED:
case PM_CONTEXT_FOR:
case PM_CONTEXT_LAMBDA_BRACES:
case PM_CONTEXT_LAMBDA_DO_END:
case PM_CONTEXT_POSTEXE:
case PM_CONTEXT_UNTIL:
case PM_CONTEXT_WHILE:
// These are the good cases. We're allowed to have a block exit
// in these contexts.
return;
case PM_CONTEXT_CLASS:
case PM_CONTEXT_DEF:
case PM_CONTEXT_DEF_PARAMS:
case PM_CONTEXT_ENSURE_DEF:
case PM_CONTEXT_MAIN:
case PM_CONTEXT_MODULE:
case PM_CONTEXT_PREEXE:
case PM_CONTEXT_RESCUE_DEF:
case PM_CONTEXT_RESCUE_ELSE_DEF:
case PM_CONTEXT_SCLASS:
if (through_begin) {
// If we get here, then we're about to mark this block exit
// as invalid. However, it could later _become_ valid if we
// find a trailing while/until on the begin. In this case
// instead of adding the error here, we'll add the block
// exit to the list of exits for the begin node, and the
// begin node parsing will handle validating it instead.
assert(parser->current_block_exits != NULL);
pm_node_list_append(parser->current_block_exits, node);
} else {
// These are the bad cases. We're not allowed to have a
// block exit in these contexts.
PM_PARSER_ERR_NODE_FORMAT(parser, node, PM_ERR_INVALID_BLOCK_EXIT, type);
}
return;
case PM_CONTEXT_NONE:
// This case should never happen.
assert(false && "unreachable");
break;
case PM_CONTEXT_BEGIN:
case PM_CONTEXT_RESCUE_ELSE:
case PM_CONTEXT_RESCUE:
// If we got to a begin node, then we'll track that we have
// gotten here because we need to know it if this block exit is
// later marked as invalid.
through_begin = true;
/* fallthrough */
case PM_CONTEXT_CASE_WHEN:
case PM_CONTEXT_CASE_IN:
case PM_CONTEXT_DEFAULT_PARAMS:
case PM_CONTEXT_ELSE:
case PM_CONTEXT_ELSIF:
case PM_CONTEXT_EMBEXPR:
case PM_CONTEXT_ENSURE:
case PM_CONTEXT_FOR_INDEX:
case PM_CONTEXT_IF:
case PM_CONTEXT_PARENS:
case PM_CONTEXT_PREDICATE:
case PM_CONTEXT_UNLESS:
// In these contexts we should continue walking up the list of
// contexts.
break;
}
context_node = context_node->prev;
}
}
/**
* Ensures that the current retry token is valid in the current context.
*/
@ -15847,6 +15913,7 @@ parse_retry(pm_parser_t *parser, const pm_node_t *node) {
case PM_CONTEXT_PARENS:
case PM_CONTEXT_POSTEXE:
case PM_CONTEXT_PREDICATE:
case PM_CONTEXT_TERNARY:
case PM_CONTEXT_UNLESS:
case PM_CONTEXT_UNTIL:
case PM_CONTEXT_WHILE:
@ -15910,6 +15977,7 @@ parse_yield(pm_parser_t *parser, const pm_node_t *node) {
case PM_CONTEXT_RESCUE_ELSE:
case PM_CONTEXT_RESCUE:
case PM_CONTEXT_SCLASS:
case PM_CONTEXT_TERNARY:
case PM_CONTEXT_UNLESS:
case PM_CONTEXT_UNTIL:
case PM_CONTEXT_WHILE:
@ -16025,6 +16093,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
case PM_TOKEN_PARENTHESIS_LEFT:
case PM_TOKEN_PARENTHESIS_LEFT_PARENTHESES: {
pm_token_t opening = parser->current;
pm_node_list_t current_block_exits = { 0 };
pm_node_list_t *previous_block_exits = push_block_exits(parser, &current_block_exits);
parser_lex(parser);
while (accept2(parser, PM_TOKEN_SEMICOLON, PM_TOKEN_NEWLINE));
@ -16032,6 +16104,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
// we have an empty parentheses node, and we can immediately return.
if (match2(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_EOF)) {
expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_EXPECT_RPAREN);
pop_block_exits(parser, previous_block_exits);
pm_node_list_free(&current_block_exits);
return (pm_node_t *) pm_parentheses_node_create(parser, &opening, NULL, &parser->previous);
}
@ -16057,9 +16133,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
if (opening.type == PM_TOKEN_PARENTHESIS_LEFT_PARENTHESES) {
lex_state_set(parser, PM_LEX_STATE_ENDARG);
}
parser_lex(parser);
pm_accepts_block_stack_pop(parser);
pop_block_exits(parser, previous_block_exits);
pm_node_list_free(&current_block_exits);
if (PM_NODE_TYPE_P(statement, PM_MULTI_TARGET_NODE) || PM_NODE_TYPE_P(statement, PM_SPLAT_NODE)) {
// If we have a single statement and are ending on a right
// parenthesis, then we need to check if this is possibly a
@ -16147,6 +16227,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
pm_accepts_block_stack_pop(parser);
expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_EXPECT_RPAREN);
pop_block_exits(parser, previous_block_exits);
pm_node_list_free(&current_block_exits);
return (pm_node_t *) pm_parentheses_node_create(parser, &opening, (pm_node_t *) statements, &parser->previous);
}
case PM_TOKEN_BRACE_LEFT: {
@ -16578,6 +16661,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
pm_token_t case_keyword = parser->previous;
pm_node_t *predicate = NULL;
pm_node_list_t current_block_exits = { 0 };
pm_node_list_t *previous_block_exits = push_block_exits(parser, &current_block_exits);
if (accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) {
while (accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON));
predicate = NULL;
@ -16591,6 +16677,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
}
if (accept1(parser, PM_TOKEN_KEYWORD_END)) {
pop_block_exits(parser, previous_block_exits);
pm_node_list_free(&current_block_exits);
pm_parser_err_token(parser, &case_keyword, PM_ERR_CASE_MISSING_CONDITIONS);
return (pm_node_t *) pm_case_node_create(parser, &case_keyword, predicate, &parser->previous);
}
@ -16770,6 +16859,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
pm_case_match_node_end_keyword_loc_set((pm_case_match_node_t *) node, &parser->previous);
}
pop_block_exits(parser, previous_block_exits);
pm_node_list_free(&current_block_exits);
return node;
}
case PM_TOKEN_KEYWORD_BEGIN: {
@ -16778,10 +16870,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
pm_token_t begin_keyword = parser->previous;
accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON);
pm_node_list_t *previous_block_exits = parser->current_block_exits;
pm_node_list_t current_block_exits = { 0 };
parser->current_block_exits = &current_block_exits;
pm_node_list_t *previous_block_exits = push_block_exits(parser, &current_block_exits);
pm_statements_node_t *begin_statements = NULL;
if (!match3(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE, PM_TOKEN_KEYWORD_END)) {
@ -16802,25 +16892,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
pm_parser_err_node(parser, (pm_node_t *) begin_node->else_clause, PM_ERR_BEGIN_LONELY_ELSE);
}
if (!match2(parser, PM_TOKEN_KEYWORD_WHILE_MODIFIER, PM_TOKEN_KEYWORD_UNTIL_MODIFIER)) {
// If we didn't find a while or until modifier, then we need to
// go back in and mark all of the block exits as invalid.
pm_node_t *block_exit;
PM_NODE_LIST_FOREACH(&current_block_exits, block_exit_index, block_exit) {
const char *type;
switch (PM_NODE_TYPE(block_exit)) {
case PM_BREAK_NODE: type = "break"; break;
case PM_NEXT_NODE: type = "next"; break;
case PM_REDO_NODE: type = "redo"; break;
default: assert(false && "unreachable"); type = ""; break;
}
PM_PARSER_ERR_NODE_FORMAT(parser, block_exit, PM_ERR_INVALID_BLOCK_EXIT, type);
}
}
parser->current_block_exits = previous_block_exits;
pop_block_exits(parser, previous_block_exits);
pm_node_list_free(&current_block_exits);
return (pm_node_t *) begin_node;
@ -19146,8 +19218,13 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
return (pm_node_t *) pm_while_node_modifier_create(parser, &token, predicate, statements, PM_NODE_TYPE_P(node, PM_BEGIN_NODE) ? PM_LOOP_FLAGS_BEGIN_MODIFIER : 0);
}
case PM_TOKEN_QUESTION_MARK: {
context_push(parser, PM_CONTEXT_TERNARY);
pm_node_list_t current_block_exits = { 0 };
pm_node_list_t *previous_block_exits = push_block_exits(parser, &current_block_exits);
pm_token_t qmark = parser->current;
parser_lex(parser);
pm_node_t *true_expression = parse_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_TERNARY_EXPRESSION_TRUE);
if (parser->recovering) {
@ -19160,6 +19237,10 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
pm_token_t colon = (pm_token_t) { .type = PM_TOKEN_MISSING, .start = parser->previous.end, .end = parser->previous.end };
pm_node_t *false_expression = (pm_node_t *) pm_missing_node_create(parser, colon.start, colon.end);
context_pop(parser);
pop_block_exits(parser, previous_block_exits);
pm_node_list_free(&current_block_exits);
return (pm_node_t *) pm_if_node_ternary_create(parser, node, &qmark, true_expression, &colon, false_expression);
}
@ -19169,6 +19250,10 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
pm_token_t colon = parser->previous;
pm_node_t *false_expression = parse_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_TERNARY_EXPRESSION_FALSE);
context_pop(parser);
pop_block_exits(parser, previous_block_exits);
pm_node_list_free(&current_block_exits);
return (pm_node_t *) pm_if_node_ternary_create(parser, node, &qmark, true_expression, &colon, false_expression);
}
case PM_TOKEN_COLON_COLON: {

Просмотреть файл

@ -21,13 +21,32 @@ pm_node_list_memsize(pm_node_list_t *node_list, pm_memsize_t *memsize) {
* this function returns false, otherwise it returns true.
*/
bool
pm_node_list_grow(pm_node_list_t *list) {
if (list->size == list->capacity) {
list->capacity = list->capacity == 0 ? 4 : list->capacity * 2;
list->nodes = (pm_node_t **) xrealloc(list->nodes, sizeof(pm_node_t *) * list->capacity);
return list->nodes != NULL;
pm_node_list_grow(pm_node_list_t *list, size_t size) {
size_t requested_size = list->size + size;
// If the requested size caused overflow, return false.
if (requested_size < list->size) return false;
// If the requested size is within the existing capacity, return true.
if (requested_size < list->capacity) return true;
// Otherwise, reallocate the list to be twice as large as it was before.
size_t next_capacity = list->capacity == 0 ? 4 : list->capacity * 2;
// If multiplying by 2 caused overflow, return false.
if (next_capacity < list->capacity) return false;
// If we didn't get enough by doubling, keep doubling until we do.
while (requested_size > next_capacity) {
size_t double_capacity = next_capacity * 2;
// Ensure we didn't overflow by multiplying by 2.
if (double_capacity < next_capacity) return false;
next_capacity = double_capacity;
}
return true;
list->nodes = (pm_node_t **) xrealloc(list->nodes, sizeof(pm_node_t *) * next_capacity);
return list->nodes != NULL;
}
/**
@ -35,7 +54,7 @@ pm_node_list_grow(pm_node_list_t *list) {
*/
void
pm_node_list_append(pm_node_list_t *list, pm_node_t *node) {
if (pm_node_list_grow(list)) {
if (pm_node_list_grow(list, 1)) {
list->nodes[list->size++] = node;
}
}
@ -45,13 +64,24 @@ pm_node_list_append(pm_node_list_t *list, pm_node_t *node) {
*/
void
pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node) {
if (pm_node_list_grow(list)) {
if (pm_node_list_grow(list, 1)) {
memmove(list->nodes + 1, list->nodes, list->size * sizeof(pm_node_t *));
list->nodes[0] = node;
list->size++;
}
}
/**
* Concatenate the given node list onto the end of the other node list.
*/
void
pm_node_list_concat(pm_node_list_t *list, pm_node_list_t *other) {
if (other->size > 0 && pm_node_list_grow(list, other->size)) {
memcpy(list->nodes + list->size, other->nodes, other->size * sizeof(pm_node_t *));
list->size += other->size;
}
}
/**
* Free the internal memory associated with the given node list.
*/