Skip to content

Commit 6b78f53

Browse files
committed
Handle block exits under modifiers
1 parent 8d887c9 commit 6b78f53

File tree

3 files changed

+113
-70
lines changed

3 files changed

+113
-70
lines changed

bin/prism

+7-4
Original file line numberDiff line numberDiff line change
@@ -368,14 +368,17 @@ module Prism
368368

369369
# Parse the source code indicated by the command-line arguments.
370370
def parse_source(argv)
371+
command_line = +""
372+
command_line << argv.shift[1] while argv.first&.match?(/^-[alnpx]$/)
373+
371374
case argv.first
372375
when "-e"
373-
argv.shift
374-
Prism.parse(argv.shift, command_line: "e")
376+
command_line << argv.shift[1]
377+
Prism.parse(argv.shift, command_line: command_line)
375378
when nil
376-
Prism.parse_file("test.rb")
379+
Prism.parse_file("test.rb", command_line: command_line)
377380
else
378-
Prism.parse_file(argv.shift)
381+
Prism.parse_file(argv.shift, command_line: command_line)
379382
end
380383
end
381384

src/prism.c

+102-62
Original file line numberDiff line numberDiff line change
@@ -7538,6 +7538,29 @@ pm_unless_node_end_keyword_loc_set(pm_unless_node_t *node, const pm_token_t *end
75387538
node->base.location.end = end_keyword->end;
75397539
}
75407540

7541+
/**
7542+
* Loop modifiers could potentially modify an expression that contains block
7543+
* exits. In this case we need to loop through them and remove them from the
7544+
* list of block exits so that they do not later get marked as invalid.
7545+
*/
7546+
static void
7547+
pm_loop_modifier_block_exits(pm_parser_t *parser, pm_statements_node_t *statements) {
7548+
assert(parser->current_block_exits != NULL);
7549+
7550+
// All of the block exits that we want to remove should be within the
7551+
// statements, and since we are modifying the statements, we shouldn't have
7552+
// to check the end location.
7553+
const uint8_t *start = statements->base.location.start;
7554+
7555+
for (size_t index = parser->current_block_exits->size; index > 0; index--) {
7556+
pm_node_t *block_exit = parser->current_block_exits->nodes[index - 1];
7557+
if (block_exit->location.start < start) break;
7558+
7559+
// Implicitly remove from the list by lowering the size.
7560+
parser->current_block_exits->size--;
7561+
}
7562+
}
7563+
75417564
/**
75427565
* Allocate a new UntilNode node.
75437566
*/
@@ -7571,6 +7594,7 @@ static pm_until_node_t *
75717594
pm_until_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm_node_t *predicate, pm_statements_node_t *statements, pm_node_flags_t flags) {
75727595
pm_until_node_t *node = PM_ALLOC_NODE(parser, pm_until_node_t);
75737596
pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL);
7597+
pm_loop_modifier_block_exits(parser, statements);
75747598

75757599
*node = (pm_until_node_t) {
75767600
{
@@ -7677,6 +7701,7 @@ static pm_while_node_t *
76777701
pm_while_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm_node_t *predicate, pm_statements_node_t *statements, pm_node_flags_t flags) {
76787702
pm_while_node_t *node = PM_ALLOC_NODE(parser, pm_while_node_t);
76797703
pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL);
7704+
pm_loop_modifier_block_exits(parser, statements);
76807705

76817706
*node = (pm_while_node_t) {
76827707
{
@@ -15052,11 +15077,8 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept
1505215077
* context. If it isn't, add an error to the parser.
1505315078
*/
1505415079
static void
15055-
parse_block_exit(pm_parser_t *parser, pm_node_t *node, const char *type) {
15056-
pm_context_node_t *context_node = parser->current_context;
15057-
bool through_expression = false;
15058-
15059-
while (context_node != NULL) {
15080+
parse_block_exit(pm_parser_t *parser, pm_node_t *node) {
15081+
for (pm_context_node_t *context_node = parser->current_context; context_node != NULL; context_node = context_node->prev) {
1506015082
switch (context_node->context) {
1506115083
case PM_CONTEXT_BLOCK_BRACES:
1506215084
case PM_CONTEXT_BLOCK_KEYWORDS:
@@ -15090,65 +15112,49 @@ parse_block_exit(pm_parser_t *parser, pm_node_t *node, const char *type) {
1509015112
case PM_CONTEXT_SCLASS_RESCUE:
1509115113
// These are the bad cases. We're not allowed to have a block
1509215114
// exit in these contexts.
15093-
15094-
if (through_expression) {
15095-
// If we get here, then we're about to mark this block exit
15096-
// as invalid. However, it could later _become_ valid if we
15097-
// find a trailing while/until on the expression. In this
15098-
// case instead of adding the error here, we'll add the
15099-
// block exit to the list of exits for the expression, and
15100-
// the node parsing will handle validating it instead.
15101-
assert(parser->current_block_exits != NULL);
15102-
pm_node_list_append(parser->current_block_exits, node);
15103-
} else {
15104-
// Otherwise, if we haven't gone through an expression
15105-
// context, then this is just invalid and we'll add the
15106-
// error here.
15107-
PM_PARSER_ERR_NODE_FORMAT(parser, node, PM_ERR_INVALID_BLOCK_EXIT, type);
15108-
}
15109-
15115+
//
15116+
// If we get here, then we're about to mark this block exit
15117+
// as invalid. However, it could later _become_ valid if we
15118+
// find a trailing while/until on the expression. In this
15119+
// case instead of adding the error here, we'll add the
15120+
// block exit to the list of exits for the expression, and
15121+
// the node parsing will handle validating it instead.
15122+
assert(parser->current_block_exits != NULL);
15123+
pm_node_list_append(parser->current_block_exits, node);
1511015124
return;
15111-
case PM_CONTEXT_NONE:
15112-
// This case should never happen.
15113-
assert(false && "unreachable");
15114-
break;
15115-
case PM_CONTEXT_BEGIN:
1511615125
case PM_CONTEXT_BEGIN_ELSE:
1511715126
case PM_CONTEXT_BEGIN_ENSURE:
1511815127
case PM_CONTEXT_BEGIN_RESCUE:
15128+
case PM_CONTEXT_BEGIN:
1511915129
case PM_CONTEXT_CASE_IN:
1512015130
case PM_CONTEXT_CASE_WHEN:
15121-
case PM_CONTEXT_CLASS:
1512215131
case PM_CONTEXT_CLASS_ELSE:
1512315132
case PM_CONTEXT_CLASS_ENSURE:
1512415133
case PM_CONTEXT_CLASS_RESCUE:
15134+
case PM_CONTEXT_CLASS:
15135+
case PM_CONTEXT_DEFAULT_PARAMS:
1512515136
case PM_CONTEXT_ELSE:
1512615137
case PM_CONTEXT_ELSIF:
15138+
case PM_CONTEXT_EMBEXPR:
15139+
case PM_CONTEXT_FOR_INDEX:
1512715140
case PM_CONTEXT_IF:
15128-
case PM_CONTEXT_MODULE:
1512915141
case PM_CONTEXT_MODULE_ELSE:
1513015142
case PM_CONTEXT_MODULE_ENSURE:
1513115143
case PM_CONTEXT_MODULE_RESCUE:
15144+
case PM_CONTEXT_MODULE:
1513215145
case PM_CONTEXT_PARENS:
15146+
case PM_CONTEXT_PREDICATE:
1513315147
case PM_CONTEXT_RESCUE_MODIFIER:
1513415148
case PM_CONTEXT_TERNARY:
1513515149
case PM_CONTEXT_UNLESS:
15136-
// If we got to an expression that could be modified by a
15137-
// trailing while/until, then we'll track that we have gotten
15138-
// here because we need to know it if this block exit is later
15139-
// marked as invalid.
15140-
through_expression = true;
15141-
break;
15142-
case PM_CONTEXT_EMBEXPR:
15143-
case PM_CONTEXT_DEFAULT_PARAMS:
15144-
case PM_CONTEXT_FOR_INDEX:
15145-
case PM_CONTEXT_PREDICATE:
1514615150
// In these contexts we should continue walking up the list of
1514715151
// contexts.
1514815152
break;
15153+
case PM_CONTEXT_NONE:
15154+
// This case should never happen.
15155+
assert(false && "unreachable");
15156+
break;
1514915157
}
15150-
15151-
context_node = context_node->prev;
1515215158
}
1515315159
}
1515415160

@@ -15163,6 +15169,30 @@ push_block_exits(pm_parser_t *parser, pm_node_list_t *current_block_exits) {
1516315169
return previous_block_exits;
1516415170
}
1516515171

15172+
/**
15173+
* If we did not match a trailing while/until and this was the last chance to do
15174+
* so, then all of the block exits in the list are invalid and we need to add an
15175+
* error for each of them.
15176+
*/
15177+
static void
15178+
flush_block_exits(pm_parser_t *parser, pm_node_list_t *previous_block_exits) {
15179+
pm_node_t *block_exit;
15180+
PM_NODE_LIST_FOREACH(parser->current_block_exits, index, block_exit) {
15181+
const char *type;
15182+
15183+
switch (PM_NODE_TYPE(block_exit)) {
15184+
case PM_BREAK_NODE: type = "break"; break;
15185+
case PM_NEXT_NODE: type = "next"; break;
15186+
case PM_REDO_NODE: type = "redo"; break;
15187+
default: assert(false && "unreachable"); type = ""; break;
15188+
}
15189+
15190+
PM_PARSER_ERR_NODE_FORMAT(parser, block_exit, PM_ERR_INVALID_BLOCK_EXIT, type);
15191+
}
15192+
15193+
parser->current_block_exits = previous_block_exits;
15194+
}
15195+
1516615196
/**
1516715197
* Pop the current level of block exits from the parser, and add errors to the
1516815198
* parser if any of them are deemed to be invalid.
@@ -15173,33 +15203,21 @@ pop_block_exits(pm_parser_t *parser, pm_node_list_t *previous_block_exits) {
1517315203
// If we matched a trailing while/until, then all of the block exits in
1517415204
// the contained list are valid. In this case we do not need to do
1517515205
// anything.
15206+
parser->current_block_exits = previous_block_exits;
1517615207
} else if (previous_block_exits != NULL) {
1517715208
// If we did not matching a trailing while/until, then all of the block
1517815209
// exits contained in the list are invalid for this specific context.
1517915210
// However, they could still become valid in a higher level context if
1518015211
// there is another list above this one. In this case we'll push all of
1518115212
// the block exits up to the previous list.
1518215213
pm_node_list_concat(previous_block_exits, parser->current_block_exits);
15214+
parser->current_block_exits = previous_block_exits;
1518315215
} else {
1518415216
// If we did not match a trailing while/until and this was the last
1518515217
// chance to do so, then all of the block exits in the list are invalid
1518615218
// and we need to add an error for each of them.
15187-
pm_node_t *block_exit;
15188-
PM_NODE_LIST_FOREACH(parser->current_block_exits, index, block_exit) {
15189-
const char *type;
15190-
15191-
switch (PM_NODE_TYPE(block_exit)) {
15192-
case PM_BREAK_NODE: type = "break"; break;
15193-
case PM_NEXT_NODE: type = "next"; break;
15194-
case PM_REDO_NODE: type = "redo"; break;
15195-
default: assert(false && "unreachable"); type = ""; break;
15196-
}
15197-
15198-
PM_PARSER_ERR_NODE_FORMAT(parser, block_exit, PM_ERR_INVALID_BLOCK_EXIT, type);
15199-
}
15219+
flush_block_exits(parser, previous_block_exits);
1520015220
}
15201-
15202-
parser->current_block_exits = previous_block_exits;
1520315221
}
1520415222

1520515223
static inline pm_node_t *
@@ -18309,6 +18327,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1830918327
return (pm_node_t *) begin_node;
1831018328
}
1831118329
case PM_TOKEN_KEYWORD_BEGIN_UPCASE: {
18330+
pm_node_list_t current_block_exits = { 0 };
18331+
pm_node_list_t *previous_block_exits = push_block_exits(parser, &current_block_exits);
18332+
1831218333
if (binding_power != PM_BINDING_POWER_STATEMENT) {
1831318334
pm_parser_err_current(parser, PM_ERR_STATEMENT_PREEXE_BEGIN);
1831418335
}
@@ -18325,6 +18346,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1832518346
if ((context != PM_CONTEXT_MAIN) && (context != PM_CONTEXT_PREEXE)) {
1832618347
pm_parser_err_token(parser, &keyword, PM_ERR_BEGIN_UPCASE_TOPLEVEL);
1832718348
}
18349+
18350+
flush_block_exits(parser, previous_block_exits);
18351+
pm_node_list_free(&current_block_exits);
18352+
1832818353
return (pm_node_t *) pm_pre_execution_node_create(parser, &keyword, &opening, statements, &parser->previous);
1832918354
}
1833018355
case PM_TOKEN_KEYWORD_BREAK:
@@ -18349,12 +18374,12 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1834918374
switch (keyword.type) {
1835018375
case PM_TOKEN_KEYWORD_BREAK: {
1835118376
pm_node_t *node = (pm_node_t *) pm_break_node_create(parser, &keyword, arguments.arguments);
18352-
if (!parser->parsing_eval) parse_block_exit(parser, node, "break");
18377+
if (!parser->parsing_eval) parse_block_exit(parser, node);
1835318378
return node;
1835418379
}
1835518380
case PM_TOKEN_KEYWORD_NEXT: {
1835618381
pm_node_t *node = (pm_node_t *) pm_next_node_create(parser, &keyword, arguments.arguments);
18357-
if (!parser->parsing_eval) parse_block_exit(parser, node, "next");
18382+
if (!parser->parsing_eval) parse_block_exit(parser, node);
1835818383
return node;
1835918384
}
1836018385
case PM_TOKEN_KEYWORD_RETURN: {
@@ -18415,6 +18440,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1841518440
pm_token_t class_keyword = parser->previous;
1841618441
pm_do_loop_stack_push(parser, false);
1841718442

18443+
pm_node_list_t current_block_exits = { 0 };
18444+
pm_node_list_t *previous_block_exits = push_block_exits(parser, &current_block_exits);
18445+
1841818446
if (accept1(parser, PM_TOKEN_LESS_LESS)) {
1841918447
pm_token_t operator = parser->previous;
1842018448
pm_node_t *expression = parse_value_expression(parser, PM_BINDING_POWER_COMPOSITION, true, PM_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS);
@@ -18442,12 +18470,12 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1844218470
pm_parser_scope_pop(parser);
1844318471
pm_do_loop_stack_pop(parser);
1844418472

18473+
flush_block_exits(parser, previous_block_exits);
18474+
pm_node_list_free(&current_block_exits);
18475+
1844518476
return (pm_node_t *) pm_singleton_class_node_create(parser, &locals, &class_keyword, &operator, expression, statements, &parser->previous);
1844618477
}
1844718478

18448-
pm_node_list_t current_block_exits = { 0 };
18449-
pm_node_list_t *previous_block_exits = push_block_exits(parser, &current_block_exits);
18450-
1845118479
pm_node_t *constant_path = parse_expression(parser, PM_BINDING_POWER_INDEX, false, PM_ERR_CLASS_NAME);
1845218480
pm_token_t name = parser->previous;
1845318481
if (name.type != PM_TOKEN_CONSTANT) {
@@ -18512,6 +18540,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1851218540
return (pm_node_t *) pm_class_node_create(parser, &locals, &class_keyword, constant_path, &name, &inheritance_operator, superclass, statements, &parser->previous);
1851318541
}
1851418542
case PM_TOKEN_KEYWORD_DEF: {
18543+
pm_node_list_t current_block_exits = { 0 };
18544+
pm_node_list_t *previous_block_exits = push_block_exits(parser, &current_block_exits);
18545+
1851518546
pm_token_t def_keyword = parser->current;
1851618547

1851718548
pm_node_t *receiver = NULL;
@@ -18772,6 +18803,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1877218803
*/
1877318804
pm_constant_id_t name_id = pm_parser_constant_id_location(parser, name.start, parse_operator_symbol_name(&name));
1877418805

18806+
flush_block_exits(parser, previous_block_exits);
18807+
pm_node_list_free(&current_block_exits);
18808+
1877518809
return (pm_node_t *) pm_def_node_create(
1877618810
parser,
1877718811
name_id,
@@ -19043,7 +19077,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
1904319077
parser_lex(parser);
1904419078

1904519079
pm_node_t *node = (pm_node_t *) pm_redo_node_create(parser, &parser->previous);
19046-
if (!parser->parsing_eval) parse_block_exit(parser, node, "redo");
19080+
if (!parser->parsing_eval) parse_block_exit(parser, node);
1904719081

1904819082
return node;
1904919083
}
@@ -21203,6 +21237,9 @@ parse_program(pm_parser_t *parser) {
2120321237
pm_parser_scope_push(parser, true);
2120421238
}
2120521239

21240+
pm_node_list_t current_block_exits = { 0 };
21241+
pm_node_list_t *previous_block_exits = push_block_exits(parser, &current_block_exits);
21242+
2120621243
parser_lex(parser);
2120721244
pm_statements_node_t *statements = parse_statements(parser, PM_CONTEXT_MAIN);
2120821245

@@ -21231,6 +21268,9 @@ parse_program(pm_parser_t *parser) {
2123121268
// node with a while loop based on the options.
2123221269
if (parser->command_line & (PM_OPTIONS_COMMAND_LINE_P | PM_OPTIONS_COMMAND_LINE_N)) {
2123321270
statements = wrap_statements(parser, statements);
21271+
} else {
21272+
flush_block_exits(parser, previous_block_exits);
21273+
pm_node_list_free(&current_block_exits);
2123421274
}
2123521275

2123621276
return (pm_node_t *) pm_program_node_create(parser, &locals, statements);

test/prism/errors_test.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,9 @@ def test_next_1_2_3
261261
["unexpected ',', expecting end-of-input", 6..7],
262262
["unexpected ',', ignoring it", 6..7],
263263
["expected a matching `)`", 6..6],
264-
["Invalid next", 0..12],
265264
["unexpected ')', expecting end-of-input", 12..13],
266-
["unexpected ')', ignoring it", 12..13]
265+
["unexpected ')', ignoring it", 12..13],
266+
["Invalid next", 0..12]
267267
]
268268
end
269269

@@ -279,9 +279,9 @@ def test_break_1_2_3
279279
["unexpected ',', expecting end-of-input", 7..8],
280280
["unexpected ',', ignoring it", 7..8],
281281
["expected a matching `)`", 7..7],
282-
["Invalid break", 0..13],
283282
["unexpected ')', expecting end-of-input", 13..14],
284-
["unexpected ')', ignoring it", 13..14]
283+
["unexpected ')', ignoring it", 13..14],
284+
["Invalid break", 0..13]
285285
]
286286
end
287287

0 commit comments

Comments
 (0)