Skip to content

Commit be41417

Browse files
committed
Correctly expose ripper state
It is for example used by `irb`, `rdoc`, `syntax_suggest`
1 parent 2c0bea0 commit be41417

File tree

6 files changed

+88
-68
lines changed

6 files changed

+88
-68
lines changed

lib/prism/lex_compat.rb

Lines changed: 10 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -198,58 +198,6 @@ def deconstruct_keys(keys)
198198
"__END__": :on___end__
199199
}.freeze
200200

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-
253201
# When we produce tokens, we produce the same arrays that Ripper does.
254202
# However, we add a couple of convenience methods onto them to make them a
255203
# little easier to work with. We delegate all other methods to the array.
@@ -300,8 +248,8 @@ def ==(other) # :nodoc:
300248
class IdentToken < Token
301249
def ==(other) # :nodoc:
302250
(self[0...-1] == other[0...-1]) && (
303-
(other[3] == State::LABEL | State::END) ||
304-
(other[3] & (State::ARG | State::CMDARG) != 0)
251+
(other[3] == Ripper::EXPR_LABEL | Ripper::EXPR_END) ||
252+
(other[3] & (Ripper::EXPR_ARG | Ripper::EXPR_CMDARG) != 0)
305253
)
306254
end
307255
end
@@ -312,8 +260,8 @@ class IgnoredNewlineToken < Token
312260
def ==(other) # :nodoc:
313261
return false unless self[0...-1] == other[0...-1]
314262

315-
if self[3] == State::ARG | State::LABELED
316-
other[3] & State::ARG | State::LABELED != 0
263+
if self[3] == Ripper::EXPR_ARG | Ripper::EXPR_LABELED
264+
other[3] & Ripper::EXPR_ARG | Ripper::EXPR_LABELED != 0
317265
else
318266
self[3] == other[3]
319267
end
@@ -331,8 +279,8 @@ def ==(other) # :nodoc:
331279
class ParamToken < Token
332280
def ==(other) # :nodoc:
333281
(self[0...-1] == other[0...-1]) && (
334-
(other[3] == State::END) ||
335-
(other[3] == State::END | State::LABEL)
282+
(other[3] == Ripper::EXPR_END) ||
283+
(other[3] == Ripper::EXPR_END | Ripper::EXPR_LABEL)
336284
)
337285
end
338286
end
@@ -727,7 +675,7 @@ def result
727675

728676
event = RIPPER.fetch(token.type)
729677
value = token.value
730-
lex_state = State.new(lex_state)
678+
lex_state = Ripper::Lexer::State.new(lex_state)
731679

732680
token =
733681
case event
@@ -741,7 +689,7 @@ def result
741689
last_heredoc_end = token.location.end_offset
742690
IgnoreStateToken.new([[lineno, column], event, value, lex_state])
743691
when :on_ident
744-
if lex_state == State::END
692+
if lex_state == Ripper::EXPR_END
745693
# If we have an identifier that follows a method name like:
746694
#
747695
# def foo bar
@@ -751,7 +699,7 @@ def result
751699
# yet. We do this more accurately, so we need to allow comparing
752700
# against both END and END|LABEL.
753701
ParamToken.new([[lineno, column], event, value, lex_state])
754-
elsif lex_state == State::END | State::LABEL
702+
elsif lex_state == Ripper::EXPR_END | Ripper::EXPR_LABEL
755703
# In the event that we're comparing identifiers, we're going to
756704
# allow a little divergence. Ripper doesn't account for local
757705
# variables introduced through named captures in regexes, and we
@@ -791,7 +739,7 @@ def result
791739
counter += { on_embexpr_beg: -1, on_embexpr_end: 1 }[current_event] || 0
792740
end
793741

794-
State.new(result_value[current_index][1])
742+
Ripper::Lexer::State.new(result_value[current_index][1])
795743
else
796744
previous_state
797745
end

lib/prism/translation/ripper.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,9 +424,34 @@ def self.sexp_raw(src, filename = "-", lineno = 1, raise_errors: false)
424424
end
425425
end
426426

427+
autoload :Lexer, "prism/translation/ripper/lexer"
427428
autoload :SexpBuilder, "prism/translation/ripper/sexp"
428429
autoload :SexpBuilderPP, "prism/translation/ripper/sexp"
429430

431+
# :stopdoc:
432+
# This is not part of the public API but used by some gems.
433+
434+
# Ripper-internal bitflags.
435+
LEX_STATE_NAMES = %i[
436+
BEG END ENDARG ENDFN ARG CMDARG MID FNAME DOT CLASS LABEL LABELED FITEM
437+
].map.with_index.to_h { |name, i| [2 ** i, name] }.freeze
438+
private_constant :LEX_STATE_NAMES
439+
440+
LEX_STATE_NAMES.each do |value, key|
441+
const_set("EXPR_#{key}", value)
442+
end
443+
EXPR_NONE = 0
444+
EXPR_VALUE = EXPR_BEG
445+
EXPR_BEG_ANY = EXPR_BEG | EXPR_MID | EXPR_CLASS
446+
EXPR_ARG_ANY = EXPR_ARG | EXPR_CMDARG
447+
EXPR_END_ANY = EXPR_END | EXPR_ENDARG | EXPR_ENDFN
448+
449+
def self.lex_state_name(state)
450+
LEX_STATE_NAMES.filter_map { |flag, name| name if bits & flag != 0 }.join("|")
451+
end
452+
453+
# :startdoc:
454+
430455
# The source that is being parsed.
431456
attr_reader :source
432457

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
# :markup: markdown
3+
4+
require_relative "../ripper"
5+
6+
module Prism
7+
module Translation
8+
class Ripper
9+
class Lexer # :nodoc:
10+
# :stopdoc:
11+
class State
12+
13+
attr_reader :to_int, :to_s
14+
15+
def initialize(i)
16+
@to_int = i
17+
@to_s = Ripper.lex_state_name(i)
18+
freeze
19+
end
20+
21+
def [](index)
22+
case index
23+
when 0, :to_int
24+
@to_int
25+
when 1, :to_s
26+
@to_s
27+
else
28+
nil
29+
end
30+
end
31+
32+
alias to_i to_int
33+
alias inspect to_s
34+
def pretty_print(q) q.text(to_s) end
35+
def ==(i) super or to_int == i end
36+
def &(i) self.class.new(to_int & i) end
37+
def |(i) self.class.new(to_int | i) end
38+
def allbits?(i) to_int.allbits?(i) end
39+
def anybits?(i) to_int.anybits?(i) end
40+
def nobits?(i) to_int.nobits?(i) end
41+
end
42+
# :startdoc:
43+
end
44+
end
45+
end
46+
end

prism.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ Gem::Specification.new do |spec|
108108
"lib/prism/translation/parser/compiler.rb",
109109
"lib/prism/translation/parser/lexer.rb",
110110
"lib/prism/translation/ripper.rb",
111+
"lib/prism/translation/ripper/lexer.rb",
111112
"lib/prism/translation/ripper/sexp.rb",
112113
"lib/prism/translation/ripper/shim.rb",
113114
"lib/prism/translation/ruby_parser.rb",

rakelib/typecheck.rake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ namespace :typecheck do
2626
- ./lib/prism/visitor.rb
2727
- ./lib/prism/translation/parser/lexer.rb
2828
- ./lib/prism/translation/ripper.rb
29+
- ./lib/prism/translation/ripper/lexer.rb
2930
- ./lib/prism/translation/ripper/sexp.rb
3031
- ./lib/prism/translation/ruby_parser.rb
3132
- ./lib/prism/inspect_visitor.rb

test/prism/ruby/ripper_test.rb

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,12 @@ class RipperTest < TestCase
6565

6666
# Check that the hardcoded values don't change without us noticing.
6767
def test_internals
68-
actual = LexCompat::State::ALL
69-
expected = Ripper.constants.select { |name| name.start_with?("EXPR_") }
70-
expected -= %i[EXPR_VALUE EXPR_BEG_ANY EXPR_ARG_ANY EXPR_END_ANY]
68+
actual = Translation::Ripper.constants.select { |name| name.start_with?("EXPR_") }.sort
69+
expected = Ripper.constants.select { |name| name.start_with?("EXPR_") }.sort
7170

72-
assert_equal(expected.size, actual.size)
73-
expected.each do |const_name|
74-
assert_equal(const_name.to_s.delete_prefix("EXPR_").to_sym, actual[Ripper.const_get(const_name)])
71+
assert_equal(expected, actual)
72+
expected.zip(actual).each do |ripper, prism|
73+
assert_equal(Ripper.const_get(ripper), Translation::Ripper.const_get(prism))
7574
end
7675
end
7776

0 commit comments

Comments
 (0)