Skip to content

Commit a10344c

Browse files
committed
Support leading logical operators
1 parent a1e7c89 commit a10344c

File tree

7 files changed

+226
-7
lines changed

7 files changed

+226
-7
lines changed

src/prism.c

+87-7
Original file line numberDiff line numberDiff line change
@@ -10842,14 +10842,37 @@ parser_lex(pm_parser_t *parser) {
1084210842
following = next_newline(following, parser->end - following);
1084310843
}
1084410844

10845-
// If the lex state was ignored, or we hit a '.' or a '&.',
10846-
// we will lex the ignored newline
10845+
// If the lex state was ignored, we will lex the
10846+
// ignored newline.
10847+
if (lex_state_ignored_p(parser)) {
10848+
if (!lexed_comment) parser_lex_ignored_newline(parser);
10849+
lexed_comment = false;
10850+
goto lex_next_token;
10851+
}
10852+
10853+
// If we hit a '.' or a '&.' we will lex the ignored
10854+
// newline.
10855+
if (following && (
10856+
(peek_at(parser, following) == '.') ||
10857+
(peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '.')
10858+
)) {
10859+
if (!lexed_comment) parser_lex_ignored_newline(parser);
10860+
lexed_comment = false;
10861+
goto lex_next_token;
10862+
}
10863+
10864+
10865+
// If we are parsing as CRuby 3.5 or later and we
10866+
// hit a '&&' or a '||' then we will lex the ignored
10867+
// newline.
1084710868
if (
10848-
lex_state_ignored_p(parser) ||
10849-
(following && (
10850-
(peek_at(parser, following) == '.') ||
10851-
(peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '.')
10852-
))
10869+
(parser->version == PM_OPTIONS_VERSION_LATEST) &&
10870+
following && (
10871+
(peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '&') ||
10872+
(peek_at(parser, following) == '|' && peek_at(parser, following + 1) == '|') ||
10873+
(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))) ||
10874+
(peek_at(parser, following) == 'o' && peek_at(parser, following + 1) == 'r' && !char_is_identifier(parser, following + 2, parser->end - (following + 2)))
10875+
)
1085310876
) {
1085410877
if (!lexed_comment) parser_lex_ignored_newline(parser);
1085510878
lexed_comment = false;
@@ -10889,6 +10912,63 @@ parser_lex(pm_parser_t *parser) {
1088910912
parser->next_start = NULL;
1089010913
LEX(PM_TOKEN_AMPERSAND_DOT);
1089110914
}
10915+
10916+
if (parser->version == PM_OPTIONS_VERSION_LATEST) {
10917+
// If we hit an && then we are in a logical chain
10918+
// and we need to return the logical operator.
10919+
if (peek_at(parser, next_content) == '&' && peek_at(parser, next_content + 1) == '&') {
10920+
if (!lexed_comment) parser_lex_ignored_newline(parser);
10921+
lex_state_set(parser, PM_LEX_STATE_BEG);
10922+
parser->current.start = next_content;
10923+
parser->current.end = next_content + 2;
10924+
parser->next_start = NULL;
10925+
LEX(PM_TOKEN_AMPERSAND_AMPERSAND);
10926+
}
10927+
10928+
// If we hit a || then we are in a logical chain and
10929+
// we need to return the logical operator.
10930+
if (peek_at(parser, next_content) == '|' && peek_at(parser, next_content + 1) == '|') {
10931+
if (!lexed_comment) parser_lex_ignored_newline(parser);
10932+
lex_state_set(parser, PM_LEX_STATE_BEG);
10933+
parser->current.start = next_content;
10934+
parser->current.end = next_content + 2;
10935+
parser->next_start = NULL;
10936+
LEX(PM_TOKEN_PIPE_PIPE);
10937+
}
10938+
10939+
// If we hit an 'and' then we are in a logical chain
10940+
// and we need to return the logical operator.
10941+
if (
10942+
peek_at(parser, next_content) == 'a' &&
10943+
peek_at(parser, next_content + 1) == 'n' &&
10944+
peek_at(parser, next_content + 2) == 'd' &&
10945+
!char_is_identifier(parser, next_content + 3, parser->end - (next_content + 3))
10946+
) {
10947+
if (!lexed_comment) parser_lex_ignored_newline(parser);
10948+
lex_state_set(parser, PM_LEX_STATE_BEG);
10949+
parser->current.start = next_content;
10950+
parser->current.end = next_content + 3;
10951+
parser->next_start = NULL;
10952+
parser->command_start = true;
10953+
LEX(PM_TOKEN_KEYWORD_AND);
10954+
}
10955+
10956+
// If we hit a 'or' then we are in a logical chain
10957+
// and we need to return the logical operator.
10958+
if (
10959+
peek_at(parser, next_content) == 'o' &&
10960+
peek_at(parser, next_content + 1) == 'r' &&
10961+
!char_is_identifier(parser, next_content + 2, parser->end - (next_content + 2))
10962+
) {
10963+
if (!lexed_comment) parser_lex_ignored_newline(parser);
10964+
lex_state_set(parser, PM_LEX_STATE_BEG);
10965+
parser->current.start = next_content;
10966+
parser->current.end = next_content + 2;
10967+
parser->next_start = NULL;
10968+
parser->command_start = true;
10969+
LEX(PM_TOKEN_KEYWORD_OR);
10970+
}
10971+
}
1089210972
}
1089310973

1089410974
// At this point we know this is a regular newline, and we can set the
+21
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

+2
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

+3
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

+3
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

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class RubyParserTest < TestCase
5555
"dos_endings.txt",
5656
"heredocs_with_fake_newlines.txt",
5757
"heredocs_with_ignored_newlines.txt",
58+
"leading_logical.txt",
5859
"method_calls.txt",
5960
"methods.txt",
6061
"multi_write.txt",

test/prism/snapshots/leading_logical.txt

+109
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)