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 877f497
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 7 deletions.
94 changes: 87 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' && !char_is_identifier(parser, following + 3)) ||
(peek_at(parser, following) == 'o' && peek_at(parser, following + 1) == 'r' && !char_is_identifier(parser, following + 2))
)
) {
if (!lexed_comment) parser_lex_ignored_newline(parser);
lexed_comment = false;
Expand Down Expand Up @@ -10876,6 +10899,63 @@ 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 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 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' &&
!char_is_identifier(parser, next_content + 3)
) {
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;
parser->command_start = true;
LEX(PM_TOKEN_KEYWORD_AND);
}

// 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' &&
!char_is_identifier(parser, next_content + 2)
) {
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;
parser->command_start = true;
LEX(PM_TOKEN_KEYWORD_OR);
}
}
}

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

1
|| 2
|| 3

1
and 2
and 3

1
or 2
or 3

1
andfoo

2
orfoo
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
109 changes: 109 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 877f497

Please sign in to comment.