Skip to content

Commit 31eb8b7

Browse files
committed
Simplify multi-target parsing
This simplifies how we handle multi-targets, and also fixes a bug we had where for loops were always getting multi-targets, even when there was only a single target.
1 parent ed87bc6 commit 31eb8b7

File tree

5 files changed

+82
-155
lines changed

5 files changed

+82
-155
lines changed

src/yarp.c

Lines changed: 56 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -8539,108 +8539,46 @@ parse_write(yp_parser_t *parser, yp_node_t *target, yp_token_t *operator, yp_nod
85398539
// target node or a multi-target node.
85408540
static yp_node_t *
85418541
parse_targets(yp_parser_t *parser, yp_node_t *first_target, yp_binding_power_t binding_power) {
8542-
yp_token_t operator = not_provided(parser);
8543-
8544-
// The first_target parameter can be NULL in the case that we're parsing a
8545-
// location that we know requires a multi write, as in the case of a for loop.
8546-
// In this case we will set up the parsing loop slightly differently.
8547-
if (first_target != NULL) {
8548-
first_target = parse_target(parser, first_target);
8549-
8550-
if (!match1(parser, YP_TOKEN_COMMA)) {
8551-
return first_target;
8552-
}
8553-
}
8542+
first_target = parse_target(parser, first_target);
8543+
if (!match1(parser, YP_TOKEN_COMMA)) return first_target;
85548544

85558545
yp_multi_target_node_t *result = yp_multi_target_node_create(parser);
8556-
if (first_target != NULL) {
8557-
yp_multi_target_node_targets_append(result, first_target);
8558-
}
8559-
8560-
bool has_splat = false;
8546+
yp_multi_target_node_targets_append(result, first_target);
8547+
bool has_splat = YP_NODE_TYPE_P(first_target, YP_SPLAT_NODE);
85618548

8562-
if (first_target == NULL || accept1(parser, YP_TOKEN_COMMA)) {
8563-
do {
8564-
if (accept1(parser, YP_TOKEN_USTAR)) {
8565-
// Here we have a splat operator. It can have a name or be anonymous. It
8566-
// can be the final target or be in the middle if there haven't been any
8567-
// others yet.
8568-
8569-
if (has_splat) {
8570-
yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_MULTI_ASSIGN_MULTI_SPLATS);
8571-
}
8572-
8573-
yp_token_t star_operator = parser->previous;
8574-
yp_node_t *name = NULL;
8575-
8576-
if (token_begins_expression_p(parser->current.type)) {
8577-
name = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_STAR);
8578-
name = parse_target(parser, name);
8579-
}
8580-
8581-
yp_node_t *splat = (yp_node_t *) yp_splat_node_create(parser, &star_operator, name);
8582-
yp_multi_target_node_targets_append(result, splat);
8583-
has_splat = true;
8584-
} else if (accept1(parser, YP_TOKEN_PARENTHESIS_LEFT)) {
8585-
// Here we have a parenthesized list of targets. We'll recurse down into
8586-
// the parentheses by calling parse_targets again and then finish out
8587-
// the node when it returns.
8588-
8589-
yp_token_t lparen = parser->previous;
8590-
yp_node_t *first_child_target = parse_expression(parser, YP_BINDING_POWER_STATEMENT, YP_ERR_EXPECT_EXPRESSION_AFTER_LPAREN);
8591-
yp_node_t *child_target = parse_targets(parser, first_child_target, YP_BINDING_POWER_STATEMENT);
8592-
8593-
expect1(parser, YP_TOKEN_PARENTHESIS_RIGHT, YP_ERR_EXPECT_RPAREN_AFTER_MULTI);
8594-
yp_token_t rparen = parser->previous;
8595-
8596-
if (YP_NODE_TYPE_P(child_target, YP_MULTI_TARGET_NODE) && first_target == NULL && result->targets.size == 0) {
8597-
yp_node_destroy(parser, (yp_node_t *) result);
8598-
result = (yp_multi_target_node_t *) child_target;
8599-
result->base.location.start = lparen.start;
8600-
result->base.location.end = rparen.end;
8601-
result->lparen_loc = YP_LOCATION_TOKEN_VALUE(&lparen);
8602-
result->rparen_loc = YP_LOCATION_TOKEN_VALUE(&rparen);
8603-
} else {
8604-
yp_multi_target_node_t *target;
8605-
8606-
if (YP_NODE_TYPE_P(child_target, YP_MULTI_TARGET_NODE)) {
8607-
target = (yp_multi_target_node_t *) child_target;
8608-
} else {
8609-
target = yp_multi_target_node_create(parser);
8610-
yp_multi_target_node_targets_append(target, child_target);
8611-
}
8612-
8613-
target->base.location.start = lparen.start;
8614-
target->base.location.end = rparen.end;
8615-
target->lparen_loc = YP_LOCATION_TOKEN_VALUE(&lparen);
8616-
target->rparen_loc = YP_LOCATION_TOKEN_VALUE(&rparen);
8549+
while (accept1(parser, YP_TOKEN_COMMA)) {
8550+
if (accept1(parser, YP_TOKEN_USTAR)) {
8551+
// Here we have a splat operator. It can have a name or be
8552+
// anonymous. It can be the final target or be in the middle if
8553+
// there haven't been any others yet.
8554+
if (has_splat) {
8555+
yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_MULTI_ASSIGN_MULTI_SPLATS);
8556+
}
86178557

8618-
yp_multi_target_node_targets_append(result, (yp_node_t *) target);
8619-
}
8620-
} else {
8621-
if (!token_begins_expression_p(parser->current.type) && !match1(parser, YP_TOKEN_USTAR)) {
8622-
if (first_target == NULL && result->targets.size == 0) {
8623-
// If we get here, then we weren't able to parse anything at all, so
8624-
// we need to return a missing node.
8625-
yp_node_destroy(parser, (yp_node_t *) result);
8626-
yp_diagnostic_list_append(&parser->error_list, operator.start, operator.end, YP_ERR_FOR_INDEX);
8627-
return (yp_node_t *) yp_missing_node_create(parser, operator.start, operator.end);
8628-
}
8558+
yp_token_t star_operator = parser->previous;
8559+
yp_node_t *name = NULL;
86298560

8630-
// If we get here, then we have a trailing , in a multi write node.
8631-
// We need to indicate this somehow in the tree, so we'll add an
8632-
// anonymous splat.
8633-
yp_node_t *splat = (yp_node_t *) yp_splat_node_create(parser, &parser->previous, NULL);
8634-
yp_multi_target_node_targets_append(result, splat);
8635-
return (yp_node_t *) result;
8636-
}
8561+
if (token_begins_expression_p(parser->current.type)) {
8562+
name = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_STAR);
8563+
name = parse_target(parser, name);
8564+
}
86378565

8638-
yp_node_t *target = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_COMMA);
8639-
target = parse_target(parser, target);
8566+
yp_node_t *splat = (yp_node_t *) yp_splat_node_create(parser, &star_operator, name);
8567+
yp_multi_target_node_targets_append(result, splat);
8568+
has_splat = true;
8569+
} else if (token_begins_expression_p(parser->current.type)) {
8570+
yp_node_t *target = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_COMMA);
8571+
target = parse_target(parser, target);
86408572

8641-
yp_multi_target_node_targets_append(result, target);
8642-
}
8643-
} while (accept1(parser, YP_TOKEN_COMMA));
8573+
yp_multi_target_node_targets_append(result, target);
8574+
} else {
8575+
// If we get here, then we have a trailing , in a multi target node.
8576+
// We need to indicate this somehow in the tree, so we'll add an
8577+
// anonymous splat.
8578+
yp_node_t *splat = (yp_node_t *) yp_splat_node_create(parser, &parser->previous, NULL);
8579+
yp_multi_target_node_targets_append(result, splat);
8580+
break;
8581+
}
86448582
}
86458583

86468584
return (yp_node_t *) result;
@@ -11301,7 +11239,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
1130111239
// If we have a single statement and are ending on a right
1130211240
// parenthesis, then we need to check if this is possibly a
1130311241
// multiple target node.
11304-
if (binding_power == YP_BINDING_POWER_STATEMENT && YP_NODE_TYPE_P(statement, YP_MULTI_TARGET_NODE)) {
11242+
if (YP_NODE_TYPE_P(statement, YP_MULTI_TARGET_NODE)) {
1130511243
yp_multi_target_node_t *multi_target;
1130611244
if (((yp_multi_target_node_t *) statement)->lparen_loc.start == NULL) {
1130711245
multi_target = (yp_multi_target_node_t *) statement;
@@ -12368,8 +12306,29 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
1236812306
case YP_TOKEN_KEYWORD_FOR: {
1236912307
parser_lex(parser);
1237012308
yp_token_t for_keyword = parser->previous;
12309+
yp_node_t *index;
12310+
12311+
// First, parse out the first index expression.
12312+
if (accept1(parser, YP_TOKEN_USTAR)) {
12313+
yp_token_t star_operator = parser->previous;
12314+
yp_node_t *name = NULL;
12315+
12316+
if (token_begins_expression_p(parser->current.type)) {
12317+
name = parse_expression(parser, YP_BINDING_POWER_INDEX, YP_ERR_EXPECT_EXPRESSION_AFTER_STAR);
12318+
name = parse_target(parser, name);
12319+
}
12320+
12321+
index = (yp_node_t *) yp_splat_node_create(parser, &star_operator, name);
12322+
} else if (token_begins_expression_p(parser->current.type)) {
12323+
index = parse_expression(parser, YP_BINDING_POWER_INDEX, YP_ERR_EXPECT_EXPRESSION_AFTER_COMMA);
12324+
} else {
12325+
yp_diagnostic_list_append(&parser->error_list, for_keyword.start, for_keyword.end, YP_ERR_FOR_INDEX);
12326+
index = (yp_node_t *) yp_missing_node_create(parser, for_keyword.start, for_keyword.end);
12327+
}
12328+
12329+
// Now, if there are multiple index expressions, parse them out.
12330+
index = parse_targets(parser, index, YP_BINDING_POWER_INDEX);
1237112331

12372-
yp_node_t *index = parse_targets(parser, NULL, YP_BINDING_POWER_INDEX);
1237312332
yp_do_loop_stack_push(parser, true);
1237412333

1237512334
expect1(parser, YP_TOKEN_KEYWORD_IN, YP_ERR_FOR_IN);

test/yarp/errors_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def test_for_loops_index_missing
4242
)
4343

4444
assert_errors expected, "for in 1..10\ni\nend", [
45-
["Expected an index after `for`", 0..0]
45+
["Expected an index after `for`", 0..3]
4646
]
4747
end
4848

@@ -58,7 +58,7 @@ def test_for_loops_only_end
5858
)
5959

6060
assert_errors expected, "for end", [
61-
["Expected an index after `for`", 0..0],
61+
["Expected an index after `for`", 0..3],
6262
["Expected an `in` after the index in a `for` statement", 3..3],
6363
["Expected a collection after the `in` in a `for` statement", 3..3]
6464
]

test/yarp/snapshots/for.txt

Lines changed: 12 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/yarp/snapshots/unparser/corpus/literal/for.txt

Lines changed: 6 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/yarp/snapshots/whitequark/for.txt

Lines changed: 6 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)