Skip to content

Commit

Permalink
Support leading logical operators
Browse files Browse the repository at this point in the history
  • Loading branch information
kddnewton committed Dec 21, 2024
1 parent a08cde8 commit 177f973
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 7 deletions.
83 changes: 76 additions & 7 deletions src/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -10829,14 +10829,37 @@ parser_lex(pm_parser_t *parser) {
following = next_newline(following, parser->end - following);
}

// If the lex state was ignored, or we hit a '.' or a '&.',
// we will lex the ignored newline
// If the lex state was ignored, we will lex the
// ignored newline.
if (lex_state_ignored_p(parser)) {
if (!lexed_comment) parser_lex_ignored_newline(parser);
lexed_comment = false;
goto lex_next_token;
}

// If we hit a '.' or a '&.' we will lex the ignored
// newline.
if (following && (
(peek_at(parser, following) == '.') ||
(peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '.')
)) {
if (!lexed_comment) parser_lex_ignored_newline(parser);
lexed_comment = false;
goto lex_next_token;
}


// If we are parsing as CRuby 3.5 or later and we
// hit a '&&' or a '||' then we will lex the ignored
// newline.
if (
lex_state_ignored_p(parser) ||
(following && (
(peek_at(parser, following) == '.') ||
(peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '.')
))
(parser->version == PM_OPTIONS_VERSION_LATEST) &&
following && (
(peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '&') ||
(peek_at(parser, following) == '|' && peek_at(parser, following + 1) == '|') ||
(peek_at(parser, following) == 'a' && peek_at(parser, following + 1) == 'n' && peek_at(parser, following + 2) == 'd') ||
(peek_at(parser, following) == 'o' && peek_at(parser, following + 1) == 'r')
)
) {
if (!lexed_comment) parser_lex_ignored_newline(parser);
lexed_comment = false;
Expand Down Expand Up @@ -10876,6 +10899,52 @@ parser_lex(pm_parser_t *parser) {
parser->next_start = NULL;
LEX(PM_TOKEN_AMPERSAND_DOT);
}

if (parser->version == PM_OPTIONS_VERSION_LATEST) {
// If we hit an && then we are in a logical chain
// and we need to return the logical operator.
if (peek_at(parser, next_content) == '&' && peek_at(parser, next_content + 1) == '&') {
if (!lexed_comment) parser_lex_ignored_newline(parser);
lex_state_set(parser, PM_LEX_STATE_BEG);
parser->current.start = next_content;
parser->current.end = next_content + 2;
parser->next_start = NULL;
LEX(PM_TOKEN_AMPERSAND_AMPERSAND);
}

// If we hit an 'and' then we are in a logical chain
// and we need to return the logical operator.
if (peek_at(parser, next_content) == 'a' && peek_at(parser, next_content + 1) == 'n' && peek_at(parser, next_content + 2) == 'd') {
if (!lexed_comment) parser_lex_ignored_newline(parser);
lex_state_set(parser, PM_LEX_STATE_BEG);
parser->current.start = next_content;
parser->current.end = next_content + 3;
parser->next_start = NULL;
LEX(PM_TOKEN_KEYWORD_AND);
}

// If we hit a || then we are in a logical chain and
// we need to return the logical operator.
if (peek_at(parser, next_content) == '|' && peek_at(parser, next_content + 1) == '|') {
if (!lexed_comment) parser_lex_ignored_newline(parser);
lex_state_set(parser, PM_LEX_STATE_BEG);
parser->current.start = next_content;
parser->current.end = next_content + 2;
parser->next_start = NULL;
LEX(PM_TOKEN_PIPE_PIPE);
}

// If we hit a 'or' then we are in a logical chain
// and we need to return the logical operator.
if (peek_at(parser, next_content) == 'o' && peek_at(parser, next_content + 1) == 'r') {
if (!lexed_comment) parser_lex_ignored_newline(parser);
lex_state_set(parser, PM_LEX_STATE_BEG);
parser->current.start = next_content;
parser->current.end = next_content + 2;
parser->next_start = NULL;
LEX(PM_TOKEN_KEYWORD_OR);
}
}
}

// At this point we know this is a regular newline, and we can set the
Expand Down
15 changes: 15 additions & 0 deletions test/prism/fixtures/leading_logical.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
1
&& 2
&& 3

1
|| 2
|| 3

1
and 2
and 3

1
or 2
or 3
2 changes: 2 additions & 0 deletions test/prism/fixtures_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class FixturesTest < TestCase
# https://bugs.ruby-lang.org/issues/19539
except << "heredocs_leading_whitespace.txt" if RUBY_VERSION < "3.3.0"

except << "leading_logical.txt" if RUBY_VERSION < "3.5.0"

Fixture.each(except: except) do |fixture|
define_method(fixture.test_name) { assert_valid_syntax(fixture.read) }
end
Expand Down
4 changes: 4 additions & 0 deletions test/prism/lex_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class LexTest < TestCase
except << "heredocs_leading_whitespace.txt"
end

if RUBY_VERSION < "3.5.0"
except << "leading_logical.txt"
end

Fixture.each(except: except) do |fixture|
define_method(fixture.test_name) { assert_lex(fixture) }
end
Expand Down
3 changes: 3 additions & 0 deletions test/prism/ruby/ripper_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ module Prism
class RipperTest < TestCase
# Skip these tests that Ripper is reporting the wrong results for.
incorrect = [
# Not yet supported.
"leading_logical.txt",

# Ripper incorrectly attributes the block to the keyword.
"seattlerb/block_break.txt",
"seattlerb/block_next.txt",
Expand Down
1 change: 1 addition & 0 deletions test/prism/ruby/ruby_parser_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class RubyParserTest < TestCase
"alias.txt",
"dos_endings.txt",
"heredocs_with_ignored_newlines.txt",
"leading_logical.txt",
"method_calls.txt",
"methods.txt",
"multi_write.txt",
Expand Down
83 changes: 83 additions & 0 deletions test/prism/snapshots/leading_logical.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 177f973

Please sign in to comment.