Skip to content

Commit 0f1c778

Browse files
committed
Introduce MatchLastLineNode and InterpolatedMatchLastLineNode
These are replacements for regular expressions when they are used alone as the predicate of a conditional. That's because they are significantly different from a regular expression because they are not evaluated for truthyness, but instead evaluated as a match against the last line read by an IO object.
1 parent 188d10c commit 0f1c778

File tree

5 files changed

+91
-20
lines changed

5 files changed

+91
-20
lines changed

config.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,6 +1488,25 @@ nodes:
14881488
14891489
1
14901490
^
1491+
- name: InterpolatedMatchLastLineNode
1492+
fields:
1493+
- name: opening_loc
1494+
type: location
1495+
- name: parts
1496+
type: node[]
1497+
- name: closing_loc
1498+
type: location
1499+
- name: flags
1500+
type: flags
1501+
kind: RegularExpressionFlags
1502+
newline: parts
1503+
comment: |
1504+
Represents a regular expression literal that contains interpolation that
1505+
is being used in the predicate of a conditional to implicitly match
1506+
against the last line read by an IO object.
1507+
1508+
if /foo #{bar} baz/ then end
1509+
^^^^^^^^^^^^^^^^
14911510
- name: InterpolatedRegularExpressionNode
14921511
fields:
14931512
- name: opening_loc
@@ -1702,6 +1721,26 @@ nodes:
17021721
17031722
foo = 1
17041723
^^^^^^^
1724+
- name: MatchLastLineNode
1725+
fields:
1726+
- name: opening_loc
1727+
type: location
1728+
- name: content_loc
1729+
type: location
1730+
- name: closing_loc
1731+
type: location
1732+
- name: unescaped
1733+
type: string
1734+
- name: flags
1735+
type: flags
1736+
kind: RegularExpressionFlags
1737+
comment: |
1738+
Represents a regular expression literal used in the predicate of a
1739+
conditional to implicitly match against the last line read by an IO
1740+
object.
1741+
1742+
if /foo/i then end
1743+
^^^^^^
17051744
- name: MatchPredicateNode
17061745
fields:
17071746
- name: value

src/yarp.c

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -450,48 +450,72 @@ yp_parser_optional_constant_id_token(yp_parser_t *parser, const yp_token_t *toke
450450
return token->type == YP_TOKEN_NOT_PROVIDED ? 0 : yp_parser_constant_id_token(parser, token);
451451
}
452452

453-
// Mark any range nodes in this subtree as flipflops.
453+
// The predicate of conditional nodes can change what would otherwise be regular
454+
// nodes into specialized nodes. For example:
455+
//
456+
// if foo .. bar => RangeNode becomes FlipFlopNode
457+
// if foo and bar .. baz => RangeNode becomes FlipFlopNode
458+
// if /foo/ => RegularExpressionNode becomes MatchLastLineNode
459+
// if /foo #{bar}/ => InterpolatedRegularExpressionNode becomes InterpolatedMatchLastLineNode
460+
//
454461
static void
455-
yp_flip_flop(yp_node_t *node) {
462+
yp_conditional_predicate(yp_node_t *node) {
456463
switch (YP_NODE_TYPE(node)) {
457464
case YP_AND_NODE: {
458465
yp_and_node_t *cast = (yp_and_node_t *) node;
459-
yp_flip_flop(cast->left);
460-
yp_flip_flop(cast->right);
466+
yp_conditional_predicate(cast->left);
467+
yp_conditional_predicate(cast->right);
461468
break;
462469
}
463470
case YP_OR_NODE: {
464471
yp_or_node_t *cast = (yp_or_node_t *) node;
465-
yp_flip_flop(cast->left);
466-
yp_flip_flop(cast->right);
472+
yp_conditional_predicate(cast->left);
473+
yp_conditional_predicate(cast->right);
467474
break;
468475
}
469476
case YP_PARENTHESES_NODE: {
470477
yp_parentheses_node_t *cast = (yp_parentheses_node_t *) node;
471478

472479
if ((cast->body != NULL) && YP_NODE_TYPE_P(cast->body, YP_STATEMENTS_NODE)) {
473480
yp_statements_node_t *statements = (yp_statements_node_t *) cast->body;
474-
if (statements->body.size == 1) yp_flip_flop(statements->body.nodes[0]);
481+
if (statements->body.size == 1) yp_conditional_predicate(statements->body.nodes[0]);
475482
}
476483

477484
break;
478485
}
479486
case YP_RANGE_NODE: {
480487
yp_range_node_t *cast = (yp_range_node_t *) node;
481488
if (cast->left) {
482-
yp_flip_flop(cast->left);
489+
yp_conditional_predicate(cast->left);
483490
}
484491
if (cast->right) {
485-
yp_flip_flop(cast->right);
492+
yp_conditional_predicate(cast->right);
486493
}
487494

488495
// Here we change the range node into a flip flop node. We can do
489496
// this since the nodes are exactly the same except for the type.
497+
// We're only asserting against the size when we should probably
498+
// assert against the entire layout, but we'll assume tests will
499+
// catch this.
490500
assert(sizeof(yp_range_node_t) == sizeof(yp_flip_flop_node_t));
491501
node->type = YP_FLIP_FLOP_NODE;
492502

493503
break;
494504
}
505+
case YP_REGULAR_EXPRESSION_NODE:
506+
// Here we change the regular expression node into a match last line
507+
// node. We can do this since the nodes are exactly the same except
508+
// for the type.
509+
assert(sizeof(yp_regular_expression_node_t) == sizeof(yp_match_last_line_node_t));
510+
node->type = YP_MATCH_LAST_LINE_NODE;
511+
break;
512+
case YP_INTERPOLATED_REGULAR_EXPRESSION_NODE:
513+
// Here we change the interpolated regular expression node into an
514+
// interpolated match last line node. We can do this since the nodes
515+
// are exactly the same except for the type.
516+
assert(sizeof(yp_interpolated_regular_expression_node_t) == sizeof(yp_interpolated_match_last_line_node_t));
517+
node->type = YP_INTERPOLATED_MATCH_LAST_LINE_NODE;
518+
break;
495519
default:
496520
break;
497521
}
@@ -2562,7 +2586,7 @@ yp_if_node_create(yp_parser_t *parser,
25622586
yp_node_t *consequent,
25632587
const yp_token_t *end_keyword
25642588
) {
2565-
yp_flip_flop(predicate);
2589+
yp_conditional_predicate(predicate);
25662590
yp_if_node_t *node = YP_ALLOC_NODE(parser, yp_if_node_t);
25672591

25682592
const uint8_t *end;
@@ -2598,7 +2622,7 @@ yp_if_node_create(yp_parser_t *parser,
25982622
// Allocate and initialize new IfNode node in the modifier form.
25992623
static yp_if_node_t *
26002624
yp_if_node_modifier_create(yp_parser_t *parser, yp_node_t *statement, const yp_token_t *if_keyword, yp_node_t *predicate) {
2601-
yp_flip_flop(predicate);
2625+
yp_conditional_predicate(predicate);
26022626
yp_if_node_t *node = YP_ALLOC_NODE(parser, yp_if_node_t);
26032627

26042628
yp_statements_node_t *statements = yp_statements_node_create(parser);
@@ -2626,7 +2650,7 @@ yp_if_node_modifier_create(yp_parser_t *parser, yp_node_t *statement, const yp_t
26262650
// Allocate and initialize an if node from a ternary expression.
26272651
static yp_if_node_t *
26282652
yp_if_node_ternary_create(yp_parser_t *parser, yp_node_t *predicate, yp_node_t *true_expression, const yp_token_t *colon, yp_node_t *false_expression) {
2629-
yp_flip_flop(predicate);
2653+
yp_conditional_predicate(predicate);
26302654

26312655
yp_statements_node_t *if_statements = yp_statements_node_create(parser);
26322656
yp_statements_node_body_append(if_statements, true_expression);
@@ -4326,7 +4350,7 @@ yp_undef_node_append(yp_undef_node_t *node, yp_node_t *name) {
43264350
// Allocate a new UnlessNode node.
43274351
static yp_unless_node_t *
43284352
yp_unless_node_create(yp_parser_t *parser, const yp_token_t *keyword, yp_node_t *predicate, yp_statements_node_t *statements) {
4329-
yp_flip_flop(predicate);
4353+
yp_conditional_predicate(predicate);
43304354
yp_unless_node_t *node = YP_ALLOC_NODE(parser, yp_unless_node_t);
43314355

43324356
const uint8_t *end;
@@ -4358,7 +4382,7 @@ yp_unless_node_create(yp_parser_t *parser, const yp_token_t *keyword, yp_node_t
43584382
// Allocate and initialize new UnlessNode node in the modifier form.
43594383
static yp_unless_node_t *
43604384
yp_unless_node_modifier_create(yp_parser_t *parser, yp_node_t *statement, const yp_token_t *unless_keyword, yp_node_t *predicate) {
4361-
yp_flip_flop(predicate);
4385+
yp_conditional_predicate(predicate);
43624386
yp_unless_node_t *node = YP_ALLOC_NODE(parser, yp_unless_node_t);
43634387

43644388
yp_statements_node_t *statements = yp_statements_node_create(parser);
@@ -12027,7 +12051,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
1202712051
arguments.closing_loc = YP_LOCATION_TOKEN_VALUE(&parser->previous);
1202812052
} else {
1202912053
receiver = parse_expression(parser, YP_BINDING_POWER_COMPOSITION, YP_ERR_NOT_EXPRESSION);
12030-
yp_flip_flop(receiver);
12054+
yp_conditional_predicate(receiver);
1203112055

1203212056
if (!parser->recovering) {
1203312057
accept(parser, YP_TOKEN_NEWLINE);
@@ -12037,7 +12061,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
1203712061
}
1203812062
} else {
1203912063
receiver = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_NOT_EXPRESSION);
12040-
yp_flip_flop(receiver);
12064+
yp_conditional_predicate(receiver);
1204112065
}
1204212066

1204312067
return (yp_node_t *) yp_call_node_not_create(parser, receiver, &message, &arguments);
@@ -12633,7 +12657,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
1263312657
yp_node_t *receiver = parse_expression(parser, yp_binding_powers[parser->previous.type].right, YP_ERR_UNARY_RECEIVER_BANG);
1263412658
yp_call_node_t *node = yp_call_node_unary_create(parser, &operator, receiver, "!");
1263512659

12636-
yp_flip_flop(receiver);
12660+
yp_conditional_predicate(receiver);
1263712661
return (yp_node_t *) node;
1263812662
}
1263912663
case YP_TOKEN_TILDE: {

test/yarp/location_test.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,10 @@ def test_IntegerNode
450450
assert_location(IntegerNode, "0o1_000")
451451
end
452452

453+
def test_InterpolatedMatchLastLineNode
454+
assert_location(InterpolatedMatchLastLineNode, "if /foo \#{bar}/ then end", 3...15, &:predicate)
455+
end
456+
453457
def test_InterpolatedRegularExpressionNode
454458
assert_location(InterpolatedRegularExpressionNode, "/\#{foo}/")
455459
end
@@ -525,6 +529,10 @@ def test_LocalVariableWriteNode
525529
assert_location(LocalVariableWriteNode, "foo = bar")
526530
end
527531

532+
def test_MatchLastLineNode
533+
assert_location(MatchLastLineNode, "if /foo/ then end", 3...8, &:predicate)
534+
end
535+
528536
def test_MatchPredicateNode
529537
assert_location(MatchPredicateNode, "foo in bar")
530538
end

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

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/yarp/snapshots/whitequark/cond_match_current_line.txt

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

0 commit comments

Comments
 (0)