Skip to content

Commit a346dec

Browse files
kddnewtonnobu
authored andcommitted
[ruby/prism] Support leading logical operators
ruby/prism@a10344cd01
1 parent 5c98b89 commit a346dec

7 files changed

Lines changed: 226 additions & 7 deletions

File tree

prism/prism.c

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10834,14 +10834,37 @@ parser_lex(pm_parser_t *parser) {
1083410834
following = next_newline(following, parser->end - following);
1083510835
}
1083610836

10837-
// If the lex state was ignored, or we hit a '.' or a '&.',
10838-
// we will lex the ignored newline
10837+
// If the lex state was ignored, we will lex the
10838+
// ignored newline.
10839+
if (lex_state_ignored_p(parser)) {
10840+
if (!lexed_comment) parser_lex_ignored_newline(parser);
10841+
lexed_comment = false;
10842+
goto lex_next_token;
10843+
}
10844+
10845+
// If we hit a '.' or a '&.' we will lex the ignored
10846+
// newline.
10847+
if (following && (
10848+
(peek_at(parser, following) == '.') ||
10849+
(peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '.')
10850+
)) {
10851+
if (!lexed_comment) parser_lex_ignored_newline(parser);
10852+
lexed_comment = false;
10853+
goto lex_next_token;
10854+
}
10855+
10856+
10857+
// If we are parsing as CRuby 3.5 or later and we
10858+
// hit a '&&' or a '||' then we will lex the ignored
10859+
// newline.
1083910860
if (
10840-
lex_state_ignored_p(parser) ||
10841-
(following && (
10842-
(peek_at(parser, following) == '.') ||
10843-
(peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '.')
10844-
))
10861+
(parser->version == PM_OPTIONS_VERSION_LATEST) &&
10862+
following && (
10863+
(peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '&') ||
10864+
(peek_at(parser, following) == '|' && peek_at(parser, following + 1) == '|') ||
10865+
(peek_at(parser, following) == 'a' && peek_at(parser, following + 1) == 'n' && peek_at(parser, following + 2) == 'd' && !char_is_identifier(parser, following + 3, parser->end - (following + 3))) ||
10866+
(peek_at(parser, following) == 'o' && peek_at(parser, following + 1) == 'r' && !char_is_identifier(parser, following + 2, parser->end - (following + 2)))
10867+
)
1084510868
) {
1084610869
if (!lexed_comment) parser_lex_ignored_newline(parser);
1084710870
lexed_comment = false;
@@ -10881,6 +10904,63 @@ parser_lex(pm_parser_t *parser) {
1088110904
parser->next_start = NULL;
1088210905
LEX(PM_TOKEN_AMPERSAND_DOT);
1088310906
}
10907+
10908+
if (parser->version == PM_OPTIONS_VERSION_LATEST) {
10909+
// If we hit an && then we are in a logical chain
10910+
// and we need to return the logical operator.
10911+
if (peek_at(parser, next_content) == '&' && peek_at(parser, next_content + 1) == '&') {
10912+
if (!lexed_comment) parser_lex_ignored_newline(parser);
10913+
lex_state_set(parser, PM_LEX_STATE_BEG);
10914+
parser->current.start = next_content;
10915+
parser->current.end = next_content + 2;
10916+
parser->next_start = NULL;
10917+
LEX(PM_TOKEN_AMPERSAND_AMPERSAND);
10918+
}
10919+
10920+
// If we hit a || then we are in a logical chain and
10921+
// we need to return the logical operator.
10922+
if (peek_at(parser, next_content) == '|' && peek_at(parser, next_content + 1) == '|') {
10923+
if (!lexed_comment) parser_lex_ignored_newline(parser);
10924+
lex_state_set(parser, PM_LEX_STATE_BEG);
10925+
parser->current.start = next_content;
10926+
parser->current.end = next_content + 2;
10927+
parser->next_start = NULL;
10928+
LEX(PM_TOKEN_PIPE_PIPE);
10929+
}
10930+
10931+
// If we hit an 'and' then we are in a logical chain
10932+
// and we need to return the logical operator.
10933+
if (
10934+
peek_at(parser, next_content) == 'a' &&
10935+
peek_at(parser, next_content + 1) == 'n' &&
10936+
peek_at(parser, next_content + 2) == 'd' &&
10937+
!char_is_identifier(parser, next_content + 3, parser->end - (next_content + 3))
10938+
) {
10939+
if (!lexed_comment) parser_lex_ignored_newline(parser);
10940+
lex_state_set(parser, PM_LEX_STATE_BEG);
10941+
parser->current.start = next_content;
10942+
parser->current.end = next_content + 3;
10943+
parser->next_start = NULL;
10944+
parser->command_start = true;
10945+
LEX(PM_TOKEN_KEYWORD_AND);
10946+
}
10947+
10948+
// If we hit a 'or' then we are in a logical chain
10949+
// and we need to return the logical operator.
10950+
if (
10951+
peek_at(parser, next_content) == 'o' &&
10952+
peek_at(parser, next_content + 1) == 'r' &&
10953+
!char_is_identifier(parser, next_content + 2, parser->end - (next_content + 2))
10954+
) {
10955+
if (!lexed_comment) parser_lex_ignored_newline(parser);
10956+
lex_state_set(parser, PM_LEX_STATE_BEG);
10957+
parser->current.start = next_content;
10958+
parser->current.end = next_content + 2;
10959+
parser->next_start = NULL;
10960+
parser->command_start = true;
10961+
LEX(PM_TOKEN_KEYWORD_OR);
10962+
}
10963+
}
1088410964
}
1088510965

1088610966
// At this point we know this is a regular newline, and we can set the
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
1
2+
&& 2
3+
&& 3
4+
5+
1
6+
|| 2
7+
|| 3
8+
9+
1
10+
and 2
11+
and 3
12+
13+
1
14+
or 2
15+
or 3
16+
17+
1
18+
andfoo
19+
20+
2
21+
orfoo

test/prism/fixtures_test.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class FixturesTest < TestCase
2525
except << "whitequark/ruby_bug_19281.txt"
2626
end
2727

28+
except << "leading_logical.txt" if RUBY_VERSION < "3.5.0"
29+
2830
Fixture.each(except: except) do |fixture|
2931
define_method(fixture.test_name) { assert_valid_syntax(fixture.read) }
3032
end

test/prism/lex_test.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ class LexTest < TestCase
4242
except << "whitequark/ruby_bug_19281.txt"
4343
end
4444

45+
# https://bugs.ruby-lang.org/issues/20925
46+
except << "leading_logical.txt"
47+
4548
Fixture.each(except: except) do |fixture|
4649
define_method(fixture.test_name) { assert_lex(fixture) }
4750
end

test/prism/ruby/ripper_test.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ module Prism
88
class RipperTest < TestCase
99
# Skip these tests that Ripper is reporting the wrong results for.
1010
incorrect = [
11+
# Not yet supported.
12+
"leading_logical.txt",
13+
1114
# Ripper incorrectly attributes the block to the keyword.
1215
"seattlerb/block_break.txt",
1316
"seattlerb/block_next.txt",

test/prism/ruby/ruby_parser_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class RubyParserTest < TestCase
3838
"dos_endings.txt",
3939
"heredocs_with_fake_newlines.txt",
4040
"heredocs_with_ignored_newlines.txt",
41+
"leading_logical.txt",
4142
"method_calls.txt",
4243
"methods.txt",
4344
"multi_write.txt",
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
@ ProgramNode (location: (1,0)-(21,5))
2+
├── flags: ∅
3+
├── locals: []
4+
└── statements:
5+
@ StatementsNode (location: (1,0)-(21,5))
6+
├── flags: ∅
7+
└── body: (length: 8)
8+
├── @ AndNode (location: (1,0)-(3,4))
9+
│ ├── flags: newline
10+
│ ├── left:
11+
│ │ @ AndNode (location: (1,0)-(2,4))
12+
│ │ ├── flags: ∅
13+
│ │ ├── left:
14+
│ │ │ @ IntegerNode (location: (1,0)-(1,1))
15+
│ │ │ ├── flags: static_literal, decimal
16+
│ │ │ └── value: 1
17+
│ │ ├── right:
18+
│ │ │ @ IntegerNode (location: (2,3)-(2,4))
19+
│ │ │ ├── flags: static_literal, decimal
20+
│ │ │ └── value: 2
21+
│ │ └── operator_loc: (2,0)-(2,2) = "&&"
22+
│ ├── right:
23+
│ │ @ IntegerNode (location: (3,3)-(3,4))
24+
│ │ ├── flags: static_literal, decimal
25+
│ │ └── value: 3
26+
│ └── operator_loc: (3,0)-(3,2) = "&&"
27+
├── @ OrNode (location: (5,0)-(7,4))
28+
│ ├── flags: newline
29+
│ ├── left:
30+
│ │ @ OrNode (location: (5,0)-(6,4))
31+
│ │ ├── flags: ∅
32+
│ │ ├── left:
33+
│ │ │ @ IntegerNode (location: (5,0)-(5,1))
34+
│ │ │ ├── flags: static_literal, decimal
35+
│ │ │ └── value: 1
36+
│ │ ├── right:
37+
│ │ │ @ IntegerNode (location: (6,3)-(6,4))
38+
│ │ │ ├── flags: static_literal, decimal
39+
│ │ │ └── value: 2
40+
│ │ └── operator_loc: (6,0)-(6,2) = "||"
41+
│ ├── right:
42+
│ │ @ IntegerNode (location: (7,3)-(7,4))
43+
│ │ ├── flags: static_literal, decimal
44+
│ │ └── value: 3
45+
│ └── operator_loc: (7,0)-(7,2) = "||"
46+
├── @ AndNode (location: (9,0)-(11,5))
47+
│ ├── flags: newline
48+
│ ├── left:
49+
│ │ @ AndNode (location: (9,0)-(10,5))
50+
│ │ ├── flags: ∅
51+
│ │ ├── left:
52+
│ │ │ @ IntegerNode (location: (9,0)-(9,1))
53+
│ │ │ ├── flags: static_literal, decimal
54+
│ │ │ └── value: 1
55+
│ │ ├── right:
56+
│ │ │ @ IntegerNode (location: (10,4)-(10,5))
57+
│ │ │ ├── flags: static_literal, decimal
58+
│ │ │ └── value: 2
59+
│ │ └── operator_loc: (10,0)-(10,3) = "and"
60+
│ ├── right:
61+
│ │ @ IntegerNode (location: (11,4)-(11,5))
62+
│ │ ├── flags: static_literal, decimal
63+
│ │ └── value: 3
64+
│ └── operator_loc: (11,0)-(11,3) = "and"
65+
├── @ OrNode (location: (13,0)-(15,4))
66+
│ ├── flags: newline
67+
│ ├── left:
68+
│ │ @ OrNode (location: (13,0)-(14,4))
69+
│ │ ├── flags: ∅
70+
│ │ ├── left:
71+
│ │ │ @ IntegerNode (location: (13,0)-(13,1))
72+
│ │ │ ├── flags: static_literal, decimal
73+
│ │ │ └── value: 1
74+
│ │ ├── right:
75+
│ │ │ @ IntegerNode (location: (14,3)-(14,4))
76+
│ │ │ ├── flags: static_literal, decimal
77+
│ │ │ └── value: 2
78+
│ │ └── operator_loc: (14,0)-(14,2) = "or"
79+
│ ├── right:
80+
│ │ @ IntegerNode (location: (15,3)-(15,4))
81+
│ │ ├── flags: static_literal, decimal
82+
│ │ └── value: 3
83+
│ └── operator_loc: (15,0)-(15,2) = "or"
84+
├── @ IntegerNode (location: (17,0)-(17,1))
85+
│ ├── flags: newline, static_literal, decimal
86+
│ └── value: 1
87+
├── @ CallNode (location: (18,0)-(18,6))
88+
│ ├── flags: newline, variable_call, ignore_visibility
89+
│ ├── receiver: ∅
90+
│ ├── call_operator_loc: ∅
91+
│ ├── name: :andfoo
92+
│ ├── message_loc: (18,0)-(18,6) = "andfoo"
93+
│ ├── opening_loc: ∅
94+
│ ├── arguments: ∅
95+
│ ├── closing_loc: ∅
96+
│ └── block: ∅
97+
├── @ IntegerNode (location: (20,0)-(20,1))
98+
│ ├── flags: newline, static_literal, decimal
99+
│ └── value: 2
100+
└── @ CallNode (location: (21,0)-(21,5))
101+
├── flags: newline, variable_call, ignore_visibility
102+
├── receiver: ∅
103+
├── call_operator_loc: ∅
104+
├── name: :orfoo
105+
├── message_loc: (21,0)-(21,5) = "orfoo"
106+
├── opening_loc: ∅
107+
├── arguments: ∅
108+
├── closing_loc: ∅
109+
└── block: ∅

0 commit comments

Comments
 (0)