Skip to content

Commit 4f16613

Browse files
authored
Merge pull request #3849 from Earlopain/ripper-shim-irb-no-crash
Make irb work with the ripper shim
2 parents 0b2a318 + 2c5826b commit 4f16613

File tree

2 files changed

+131
-5
lines changed

2 files changed

+131
-5
lines changed

lib/prism/translation/ripper/lexer.rb

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
module Prism
77
module Translation
88
class Ripper
9-
class Lexer # :nodoc:
9+
class Lexer < Ripper # :nodoc:
1010
# :stopdoc:
1111
class State
1212

@@ -39,6 +39,92 @@ def allbits?(i) to_int.allbits?(i) end
3939
def anybits?(i) to_int.anybits?(i) end
4040
def nobits?(i) to_int.nobits?(i) end
4141
end
42+
43+
class Elem
44+
attr_accessor :pos, :event, :tok, :state, :message
45+
46+
def initialize(pos, event, tok, state, message = nil)
47+
@pos = pos
48+
@event = event
49+
@tok = tok
50+
@state = State.new(state)
51+
@message = message
52+
end
53+
54+
def [](index)
55+
case index
56+
when 0, :pos
57+
@pos
58+
when 1, :event
59+
@event
60+
when 2, :tok
61+
@tok
62+
when 3, :state
63+
@state
64+
when 4, :message
65+
@message
66+
else
67+
nil
68+
end
69+
end
70+
71+
def inspect
72+
"#<#{self.class}: #{event}@#{pos[0]}:#{pos[1]}:#{state}: #{tok.inspect}#{": " if message}#{message}>"
73+
end
74+
75+
alias to_s inspect
76+
77+
def pretty_print(q)
78+
q.group(2, "#<#{self.class}:", ">") {
79+
q.breakable
80+
q.text("#{event}@#{pos[0]}:#{pos[1]}")
81+
q.breakable
82+
state.pretty_print(q)
83+
q.breakable
84+
q.text("token: ")
85+
tok.pretty_print(q)
86+
if message
87+
q.breakable
88+
q.text("message: ")
89+
q.text(message)
90+
end
91+
}
92+
end
93+
94+
def to_a
95+
if @message
96+
[@pos, @event, @tok, @state, @message]
97+
else
98+
[@pos, @event, @tok, @state]
99+
end
100+
end
101+
end
102+
103+
def initialize(...)
104+
super
105+
@lex_compat = Prism.lex_compat(@source, filepath: filename, line: lineno)
106+
end
107+
108+
# Returns the lex_compat result wrapped in `Elem`. Errors are omitted.
109+
# Since ripper is a streaming parser, tokens are expected to be emitted in the order
110+
# that the parser encounters them. This is not implemented.
111+
def parse(raise_errors: false)
112+
if @lex_compat.failure? && raise_errors
113+
raise SyntaxError, @lex_compat.errors.first.message
114+
else
115+
@lex_compat.value.map do |position, event, token, state|
116+
Elem.new(position, event, token, state.to_int)
117+
end
118+
end
119+
end
120+
121+
# Similar to parse but ripper sorts the elements by position in the source. Also
122+
# includes errors. Since prism does error recovery, in cases of syntax errors
123+
# the result may differ greatly compared to ripper.
124+
def scan(...)
125+
parse(...)
126+
end
127+
42128
# :startdoc:
43129
end
44130
end

test/prism/ruby/ripper_test.rb

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class RipperTest < TestCase
3838
end
3939

4040
# Skip these tests that we haven't implemented yet.
41-
omitted = [
41+
omitted_sexp_raw = [
4242
"dos_endings.txt",
4343
"heredocs_with_fake_newlines.txt",
4444
"heredocs_with_ignored_newlines.txt",
@@ -59,8 +59,29 @@ class RipperTest < TestCase
5959
"whitequark/slash_newline_in_heredocs.txt"
6060
]
6161

62-
Fixture.each_for_current_ruby(except: incorrect | omitted) do |fixture|
63-
define_method(fixture.test_name) { assert_ripper(fixture.read) }
62+
omitted_lexer_parse = [
63+
"comments.txt",
64+
"heredoc_percent_q_newline_delimiter.txt",
65+
"heredoc_with_escaped_newline_at_start.txt",
66+
"heredocs_with_fake_newlines.txt",
67+
"indented_file_end.txt",
68+
"seattlerb/TestRubyParserShared.txt",
69+
"seattlerb/class_comments.txt",
70+
"seattlerb/module_comments.txt",
71+
"seattlerb/parse_line_block_inline_comment_leading_newlines.txt",
72+
"seattlerb/parse_line_block_inline_multiline_comment.txt",
73+
"spanning_heredoc_newlines.txt",
74+
"strings.txt",
75+
"whitequark/dedenting_heredoc.txt",
76+
"whitequark/procarg0.txt",
77+
]
78+
79+
Fixture.each_for_current_ruby(except: incorrect | omitted_sexp_raw) do |fixture|
80+
define_method("#{fixture.test_name}_sexp_raw") { assert_ripper_sexp_raw(fixture.read) }
81+
end
82+
83+
Fixture.each_for_current_ruby(except: incorrect | omitted_lexer_parse) do |fixture|
84+
define_method("#{fixture.test_name}_lexer_parse") { assert_ripper_lexer_parse(fixture.read) }
6485
end
6586

6687
# Check that the hardcoded values don't change without us noticing.
@@ -76,8 +97,27 @@ def test_internals
7697

7798
private
7899

79-
def assert_ripper(source)
100+
def assert_ripper_sexp_raw(source)
80101
assert_equal Ripper.sexp_raw(source), Prism::Translation::Ripper.sexp_raw(source)
81102
end
103+
104+
def assert_ripper_lexer_parse(source)
105+
prism = Translation::Ripper::Lexer.new(source).parse
106+
ripper = Ripper::Lexer.new(source).parse
107+
ripper.reject! { |elem| elem.event == :on_sp } # Prism doesn't emit on_sp
108+
ripper.sort_by!(&:pos) # Prism emits tokens by their order in the code, not in parse order
109+
110+
[prism.size, ripper.size].max.times do |i|
111+
expected = ripper[i].to_a
112+
actual = prism[i].to_a
113+
# Since tokens related to heredocs are not emitted in the same order,
114+
# the state also doesn't line up.
115+
if expected[1] == :on_heredoc_end && actual[1] == :on_heredoc_end
116+
expected[3] = actual[3] = nil
117+
end
118+
119+
assert_equal(expected, actual)
120+
end
121+
end
82122
end
83123
end

0 commit comments

Comments
 (0)