Skip to content

Commit 451f70e

Browse files
authored
Merge pull request #3839 from Earlopain/ripper-translator-untangle
Decouple ripper translator from ripper library
2 parents e0e5e68 + 2c0bea0 commit 451f70e

11 files changed

Lines changed: 151 additions & 83 deletions

File tree

Steepfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ target :lib do
1010
# TODO: Type-checking these files is still WIP
1111
ignore "lib/prism/desugar_compiler.rb"
1212
ignore "lib/prism/lex_compat.rb"
13+
ignore "lib/prism/lex_ripper.rb"
1314
ignore "lib/prism/serialize.rb"
1415
ignore "lib/prism/ffi.rb"
1516
ignore "lib/prism/translation"

lib/prism.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ module Prism
2020
autoload :DSL, "prism/dsl"
2121
autoload :InspectVisitor, "prism/inspect_visitor"
2222
autoload :LexCompat, "prism/lex_compat"
23-
autoload :LexRipper, "prism/lex_compat"
23+
autoload :LexRipper, "prism/lex_ripper"
2424
autoload :MutationCompiler, "prism/mutation_compiler"
2525
autoload :Pack, "prism/pack"
2626
autoload :Pattern, "prism/pattern"

lib/prism/lex_compat.rb

Lines changed: 69 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# :markup: markdown
33

44
require "delegate"
5-
require "ripper"
65

76
module Prism
87
# This class is responsible for lexing the source using prism and then
@@ -199,6 +198,58 @@ def deconstruct_keys(keys)
199198
"__END__": :on___end__
200199
}.freeze
201200

201+
# Pretty much a 1:1 copy of Ripper::Lexer::State. We list all the available states
202+
# to reimplement to_s without using Ripper.
203+
class State
204+
# Ripper-internal bitflags.
205+
ALL = %i[
206+
BEG END ENDARG ENDFN ARG CMDARG MID FNAME DOT CLASS LABEL LABELED FITEM
207+
].map.with_index.to_h { |name, i| [2 ** i, name] }
208+
ALL[0] = :NONE
209+
ALL.freeze
210+
ALL.each { |value, name| const_set(name, value) }
211+
212+
# :stopdoc:
213+
214+
attr_reader :to_int, :to_s
215+
216+
def initialize(i)
217+
@to_int = i
218+
@to_s = state_name(i)
219+
freeze
220+
end
221+
222+
def [](index)
223+
case index
224+
when 0, :to_int
225+
@to_int
226+
when 1, :to_s
227+
@to_s
228+
else
229+
nil
230+
end
231+
end
232+
233+
alias to_i to_int
234+
alias inspect to_s
235+
def pretty_print(q) q.text(to_s) end
236+
def ==(i) super or to_int == i end
237+
def &(i) self.class.new(to_int & i) end
238+
def |(i) self.class.new(to_int | i) end
239+
def allbits?(i) to_int.allbits?(i) end
240+
def anybits?(i) to_int.anybits?(i) end
241+
def nobits?(i) to_int.nobits?(i) end
242+
243+
# :startdoc:
244+
245+
private
246+
247+
# Convert the state flags into the format exposed by ripper.
248+
def state_name(bits)
249+
ALL.filter_map { |flag, name| name if bits & flag != 0 }.join("|")
250+
end
251+
end
252+
202253
# When we produce tokens, we produce the same arrays that Ripper does.
203254
# However, we add a couple of convenience methods onto them to make them a
204255
# little easier to work with. We delegate all other methods to the array.
@@ -249,8 +300,8 @@ def ==(other) # :nodoc:
249300
class IdentToken < Token
250301
def ==(other) # :nodoc:
251302
(self[0...-1] == other[0...-1]) && (
252-
(other[3] == Ripper::EXPR_LABEL | Ripper::EXPR_END) ||
253-
(other[3] & Ripper::EXPR_ARG_ANY != 0)
303+
(other[3] == State::LABEL | State::END) ||
304+
(other[3] & (State::ARG | State::CMDARG) != 0)
254305
)
255306
end
256307
end
@@ -261,8 +312,8 @@ class IgnoredNewlineToken < Token
261312
def ==(other) # :nodoc:
262313
return false unless self[0...-1] == other[0...-1]
263314

264-
if self[3] == Ripper::EXPR_ARG | Ripper::EXPR_LABELED
265-
other[3] & Ripper::EXPR_ARG | Ripper::EXPR_LABELED != 0
315+
if self[3] == State::ARG | State::LABELED
316+
other[3] & State::ARG | State::LABELED != 0
266317
else
267318
self[3] == other[3]
268319
end
@@ -280,8 +331,8 @@ def ==(other) # :nodoc:
280331
class ParamToken < Token
281332
def ==(other) # :nodoc:
282333
(self[0...-1] == other[0...-1]) && (
283-
(other[3] == Ripper::EXPR_END) ||
284-
(other[3] == Ripper::EXPR_END | Ripper::EXPR_LABEL)
334+
(other[3] == State::END) ||
335+
(other[3] == State::END | State::LABEL)
285336
)
286337
end
287338
end
@@ -615,6 +666,11 @@ def self.build(opening)
615666

616667
private_constant :Heredoc
617668

669+
# In previous versions of Ruby, Ripper wouldn't flush the bom before the
670+
# first token, so we had to have a hack in place to account for that.
671+
BOM_FLUSHED = RUBY_VERSION >= "3.3.0"
672+
private_constant :BOM_FLUSHED
673+
618674
attr_reader :source, :options
619675

620676
def initialize(source, **options)
@@ -630,13 +686,9 @@ def result
630686

631687
result = Prism.lex(source, **options)
632688
result_value = result.value
633-
previous_state = nil #: Ripper::Lexer::State?
689+
previous_state = nil #: State?
634690
last_heredoc_end = nil #: Integer?
635691

636-
# In previous versions of Ruby, Ripper wouldn't flush the bom before the
637-
# first token, so we had to have a hack in place to account for that. This
638-
# checks for that behavior.
639-
bom_flushed = Ripper.lex("\xEF\xBB\xBF# test")[0][0][1] == 0
640692
bom = source.byteslice(0..2) == "\xEF\xBB\xBF"
641693

642694
result_value.each_with_index do |(token, lex_state), index|
@@ -651,7 +703,7 @@ def result
651703
if bom && lineno == 1
652704
column -= 3
653705

654-
if index == 0 && column == 0 && !bom_flushed
706+
if index == 0 && column == 0 && !BOM_FLUSHED
655707
flushed =
656708
case token.type
657709
when :BACK_REFERENCE, :INSTANCE_VARIABLE, :CLASS_VARIABLE,
@@ -675,7 +727,7 @@ def result
675727

676728
event = RIPPER.fetch(token.type)
677729
value = token.value
678-
lex_state = Ripper::Lexer::State.new(lex_state)
730+
lex_state = State.new(lex_state)
679731

680732
token =
681733
case event
@@ -689,7 +741,7 @@ def result
689741
last_heredoc_end = token.location.end_offset
690742
IgnoreStateToken.new([[lineno, column], event, value, lex_state])
691743
when :on_ident
692-
if lex_state == Ripper::EXPR_END
744+
if lex_state == State::END
693745
# If we have an identifier that follows a method name like:
694746
#
695747
# def foo bar
@@ -699,7 +751,7 @@ def result
699751
# yet. We do this more accurately, so we need to allow comparing
700752
# against both END and END|LABEL.
701753
ParamToken.new([[lineno, column], event, value, lex_state])
702-
elsif lex_state == Ripper::EXPR_END | Ripper::EXPR_LABEL
754+
elsif lex_state == State::END | State::LABEL
703755
# In the event that we're comparing identifiers, we're going to
704756
# allow a little divergence. Ripper doesn't account for local
705757
# variables introduced through named captures in regexes, and we
@@ -739,7 +791,7 @@ def result
739791
counter += { on_embexpr_beg: -1, on_embexpr_end: 1 }[current_event] || 0
740792
end
741793

742-
Ripper::Lexer::State.new(result_value[current_index][1])
794+
State.new(result_value[current_index][1])
743795
else
744796
previous_state
745797
end
@@ -867,62 +919,4 @@ def result
867919
end
868920

869921
private_constant :LexCompat
870-
871-
# This is a class that wraps the Ripper lexer to produce almost exactly the
872-
# same tokens.
873-
class LexRipper # :nodoc:
874-
attr_reader :source
875-
876-
def initialize(source)
877-
@source = source
878-
end
879-
880-
def result
881-
previous = [] #: [[Integer, Integer], Symbol, String, untyped] | []
882-
results = [] #: Array[[[Integer, Integer], Symbol, String, untyped]]
883-
884-
lex(source).each do |token|
885-
case token[1]
886-
when :on_sp
887-
# skip
888-
when :on_tstring_content
889-
if previous[1] == :on_tstring_content && (token[2].start_with?("\#$") || token[2].start_with?("\#@"))
890-
previous[2] << token[2]
891-
else
892-
results << token
893-
previous = token
894-
end
895-
when :on_words_sep
896-
if previous[1] == :on_words_sep
897-
previous[2] << token[2]
898-
else
899-
results << token
900-
previous = token
901-
end
902-
else
903-
results << token
904-
previous = token
905-
end
906-
end
907-
908-
results
909-
end
910-
911-
private
912-
913-
if Ripper.method(:lex).parameters.assoc(:keyrest)
914-
def lex(source)
915-
Ripper.lex(source, raise_errors: true)
916-
end
917-
else
918-
def lex(source)
919-
ripper = Ripper::Lexer.new(source)
920-
ripper.lex.tap do |result|
921-
raise SyntaxError, ripper.errors.map(&:message).join(' ;') if ripper.errors.any?
922-
end
923-
end
924-
end
925-
end
926-
927-
private_constant :LexRipper
928922
end

lib/prism/lex_ripper.rb

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# frozen_string_literal: true
2+
# :markup: markdown
3+
4+
require "ripper"
5+
6+
module Prism
7+
# This is a class that wraps the Ripper lexer to produce almost exactly the
8+
# same tokens.
9+
class LexRipper # :nodoc:
10+
attr_reader :source
11+
12+
def initialize(source)
13+
@source = source
14+
end
15+
16+
def result
17+
previous = [] #: [[Integer, Integer], Symbol, String, untyped] | []
18+
results = [] #: Array[[[Integer, Integer], Symbol, String, untyped]]
19+
20+
lex(source).each do |token|
21+
case token[1]
22+
when :on_sp
23+
# skip
24+
when :on_tstring_content
25+
if previous[1] == :on_tstring_content && (token[2].start_with?("\#$") || token[2].start_with?("\#@"))
26+
previous[2] << token[2]
27+
else
28+
results << token
29+
previous = token
30+
end
31+
when :on_words_sep
32+
if previous[1] == :on_words_sep
33+
previous[2] << token[2]
34+
else
35+
results << token
36+
previous = token
37+
end
38+
else
39+
results << token
40+
previous = token
41+
end
42+
end
43+
44+
results
45+
end
46+
47+
private
48+
49+
if Ripper.method(:lex).parameters.assoc(:keyrest)
50+
def lex(source)
51+
Ripper.lex(source, raise_errors: true)
52+
end
53+
else
54+
def lex(source)
55+
ripper = Ripper::Lexer.new(source)
56+
ripper.lex.tap do |result|
57+
raise SyntaxError, ripper.errors.map(&:message).join(' ;') if ripper.errors.any?
58+
end
59+
end
60+
end
61+
end
62+
63+
private_constant :LexRipper
64+
end

lib/prism/translation/ripper.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# frozen_string_literal: true
22
# :markup: markdown
33

4-
require "ripper"
5-
64
module Prism
75
module Translation
86
# This class provides a compatibility layer between prism and Ripper. It

prism.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ Gem::Specification.new do |spec|
7777
"lib/prism/ffi.rb",
7878
"lib/prism/inspect_visitor.rb",
7979
"lib/prism/lex_compat.rb",
80+
"lib/prism/lex_ripper.rb",
8081
"lib/prism/mutation_compiler.rb",
8182
"lib/prism/node_ext.rb",
8283
"lib/prism/node.rb",

rakelib/lex.rake

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ TARGETS.each do |name, target|
126126
desc "Lex #{repo} and compare with lex_compat"
127127
task "lex:#{name}" => [dirpath, :compile] do
128128
$:.unshift(File.expand_path("../lib", __dir__))
129-
require "ripper"
130129
require "prism"
131130

132131
plain_text = ENV.fetch("CI", false)
@@ -169,7 +168,6 @@ end
169168
desc "Lex files and compare with lex_compat"
170169
task lex: :compile do
171170
$:.unshift(File.expand_path("../lib", __dir__))
172-
require "ripper"
173171
require "prism"
174172

175173
plain_text = ENV.fetch("CI", false)
@@ -201,7 +199,6 @@ desc "Lex against the most recent version of various rubygems"
201199
task "lex:rubygems": [:compile, "tmp/failing"] do
202200
$:.unshift(File.expand_path("../lib", __dir__))
203201
require "net/http"
204-
require "ripper"
205202
require "rubygems/package"
206203
require "tmpdir"
207204
require "prism"
@@ -333,7 +330,6 @@ desc "Lex against the top 100 rubygems"
333330
task "lex:topgems": ["download:topgems", :compile] do
334331
$:.unshift(File.expand_path("../lib", __dir__))
335332
require "net/http"
336-
require "ripper"
337333
require "rubygems/package"
338334
require "tmpdir"
339335
require "prism"

rakelib/typecheck.rake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ namespace :typecheck do
2020
File.write("sorbet/typed_overrides.yml", ERB.new(<<~YAML, trim_mode: "-").result_with_hash(locals))
2121
false:
2222
- ./lib/prism/lex_compat.rb
23+
- ./lib/prism/lex_ripper.rb
2324
- ./lib/prism/node_ext.rb
2425
- ./lib/prism/parse_result.rb
2526
- ./lib/prism/visitor.rb

test/prism/magic_comment_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require_relative "test_helper"
4+
require "ripper"
45

56
module Prism
67
class MagicCommentTest < TestCase

0 commit comments

Comments
 (0)