summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEarlopain <14981592+Earlopain@users.noreply.github.com>2026-01-12 14:21:42 +0100
committergit <svn-admin@ruby-lang.org>2026-01-12 22:08:25 +0000
commitee1aa78bee5f5c46ebcd75a3fe3eff03787b0b44 (patch)
treecd5878af549be8c6d462bf9bffb0d2d2454afd0d
parentd81a11d4e61f67b6fb0aaa44aaa7ead4022148dd (diff)
[ruby/prism] Correctly expose ripper state
It is for example used by `irb`, `rdoc`, `syntax_suggest` https://github.com/ruby/prism/commit/255aeb2485
-rw-r--r--lib/prism/lex_compat.rb72
-rw-r--r--lib/prism/prism.gemspec1
-rw-r--r--lib/prism/translation/ripper.rb25
-rw-r--r--lib/prism/translation/ripper/lexer.rb46
-rw-r--r--test/prism/ruby/ripper_test.rb11
5 files changed, 87 insertions, 68 deletions
diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb
index ebfb19e56d..46f6130357 100644
--- a/lib/prism/lex_compat.rb
+++ b/lib/prism/lex_compat.rb
@@ -198,58 +198,6 @@ module Prism
"__END__": :on___end__
}.freeze
- # Pretty much a 1:1 copy of Ripper::Lexer::State. We list all the available states
- # to reimplement to_s without using Ripper.
- class State
- # Ripper-internal bitflags.
- ALL = %i[
- BEG END ENDARG ENDFN ARG CMDARG MID FNAME DOT CLASS LABEL LABELED FITEM
- ].map.with_index.to_h { |name, i| [2 ** i, name] }
- ALL[0] = :NONE
- ALL.freeze
- ALL.each { |value, name| const_set(name, value) }
-
- # :stopdoc:
-
- attr_reader :to_int, :to_s
-
- def initialize(i)
- @to_int = i
- @to_s = state_name(i)
- freeze
- end
-
- def [](index)
- case index
- when 0, :to_int
- @to_int
- when 1, :to_s
- @to_s
- else
- nil
- end
- end
-
- alias to_i to_int
- alias inspect to_s
- def pretty_print(q) q.text(to_s) end
- def ==(i) super or to_int == i end
- def &(i) self.class.new(to_int & i) end
- def |(i) self.class.new(to_int | i) end
- def allbits?(i) to_int.allbits?(i) end
- def anybits?(i) to_int.anybits?(i) end
- def nobits?(i) to_int.nobits?(i) end
-
- # :startdoc:
-
- private
-
- # Convert the state flags into the format exposed by ripper.
- def state_name(bits)
- ALL.filter_map { |flag, name| name if bits & flag != 0 }.join("|")
- end
- end
-
# When we produce tokens, we produce the same arrays that Ripper does.
# However, we add a couple of convenience methods onto them to make them a
# little easier to work with. We delegate all other methods to the array.
@@ -300,8 +248,8 @@ module Prism
class IdentToken < Token
def ==(other) # :nodoc:
(self[0...-1] == other[0...-1]) && (
- (other[3] == State::LABEL | State::END) ||
- (other[3] & (State::ARG | State::CMDARG) != 0)
+ (other[3] == Translation::Ripper::EXPR_LABEL | Translation::Ripper::EXPR_END) ||
+ (other[3] & (Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_CMDARG) != 0)
)
end
end
@@ -312,8 +260,8 @@ module Prism
def ==(other) # :nodoc:
return false unless self[0...-1] == other[0...-1]
- if self[3] == State::ARG | State::LABELED
- other[3] & State::ARG | State::LABELED != 0
+ if self[3] == Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_LABELED
+ other[3] & Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_LABELED != 0
else
self[3] == other[3]
end
@@ -331,8 +279,8 @@ module Prism
class ParamToken < Token
def ==(other) # :nodoc:
(self[0...-1] == other[0...-1]) && (
- (other[3] == State::END) ||
- (other[3] == State::END | State::LABEL)
+ (other[3] == Translation::Ripper::EXPR_END) ||
+ (other[3] == Translation::Ripper::EXPR_END | Translation::Ripper::EXPR_LABEL)
)
end
end
@@ -727,7 +675,7 @@ module Prism
event = RIPPER.fetch(token.type)
value = token.value
- lex_state = State.new(lex_state)
+ lex_state = Translation::Ripper::Lexer::State.new(lex_state)
token =
case event
@@ -741,7 +689,7 @@ module Prism
last_heredoc_end = token.location.end_offset
IgnoreStateToken.new([[lineno, column], event, value, lex_state])
when :on_ident
- if lex_state == State::END
+ if lex_state == Translation::Ripper::EXPR_END
# If we have an identifier that follows a method name like:
#
# def foo bar
@@ -751,7 +699,7 @@ module Prism
# yet. We do this more accurately, so we need to allow comparing
# against both END and END|LABEL.
ParamToken.new([[lineno, column], event, value, lex_state])
- elsif lex_state == State::END | State::LABEL
+ elsif lex_state == Translation::Ripper::EXPR_END | Translation::Ripper::EXPR_LABEL
# In the event that we're comparing identifiers, we're going to
# allow a little divergence. Ripper doesn't account for local
# variables introduced through named captures in regexes, and we
@@ -791,7 +739,7 @@ module Prism
counter += { on_embexpr_beg: -1, on_embexpr_end: 1 }[current_event] || 0
end
- State.new(result_value[current_index][1])
+ Translation::Ripper::Lexer::State.new(result_value[current_index][1])
else
previous_state
end
diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec
index 6a3bed32d8..9f5d458d6c 100644
--- a/lib/prism/prism.gemspec
+++ b/lib/prism/prism.gemspec
@@ -104,6 +104,7 @@ Gem::Specification.new do |spec|
"lib/prism/translation/parser/compiler.rb",
"lib/prism/translation/parser/lexer.rb",
"lib/prism/translation/ripper.rb",
+ "lib/prism/translation/ripper/lexer.rb",
"lib/prism/translation/ripper/sexp.rb",
"lib/prism/translation/ripper/shim.rb",
"lib/prism/translation/ruby_parser.rb",
diff --git a/lib/prism/translation/ripper.rb b/lib/prism/translation/ripper.rb
index 00d5f80af4..a901a72692 100644
--- a/lib/prism/translation/ripper.rb
+++ b/lib/prism/translation/ripper.rb
@@ -424,9 +424,34 @@ module Prism
end
end
+ autoload :Lexer, "prism/translation/ripper/lexer"
autoload :SexpBuilder, "prism/translation/ripper/sexp"
autoload :SexpBuilderPP, "prism/translation/ripper/sexp"
+ # :stopdoc:
+ # This is not part of the public API but used by some gems.
+
+ # Ripper-internal bitflags.
+ LEX_STATE_NAMES = %i[
+ BEG END ENDARG ENDFN ARG CMDARG MID FNAME DOT CLASS LABEL LABELED FITEM
+ ].map.with_index.to_h { |name, i| [2 ** i, name] }.freeze
+ private_constant :LEX_STATE_NAMES
+
+ LEX_STATE_NAMES.each do |value, key|
+ const_set("EXPR_#{key}", value)
+ end
+ EXPR_NONE = 0
+ EXPR_VALUE = EXPR_BEG
+ EXPR_BEG_ANY = EXPR_BEG | EXPR_MID | EXPR_CLASS
+ EXPR_ARG_ANY = EXPR_ARG | EXPR_CMDARG
+ EXPR_END_ANY = EXPR_END | EXPR_ENDARG | EXPR_ENDFN
+
+ def self.lex_state_name(state)
+ LEX_STATE_NAMES.filter_map { |flag, name| name if state & flag != 0 }.join("|")
+ end
+
+ # :startdoc:
+
# The source that is being parsed.
attr_reader :source
diff --git a/lib/prism/translation/ripper/lexer.rb b/lib/prism/translation/ripper/lexer.rb
new file mode 100644
index 0000000000..ed02e96574
--- /dev/null
+++ b/lib/prism/translation/ripper/lexer.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+# :markup: markdown
+
+require_relative "../ripper"
+
+module Prism
+ module Translation
+ class Ripper
+ class Lexer # :nodoc:
+ # :stopdoc:
+ class State
+
+ attr_reader :to_int, :to_s
+
+ def initialize(i)
+ @to_int = i
+ @to_s = Ripper.lex_state_name(i)
+ freeze
+ end
+
+ def [](index)
+ case index
+ when 0, :to_int
+ @to_int
+ when 1, :to_s
+ @to_s
+ else
+ nil
+ end
+ end
+
+ alias to_i to_int
+ alias inspect to_s
+ def pretty_print(q) q.text(to_s) end
+ def ==(i) super or to_int == i end
+ def &(i) self.class.new(to_int & i) end
+ def |(i) self.class.new(to_int | i) end
+ def allbits?(i) to_int.allbits?(i) end
+ def anybits?(i) to_int.anybits?(i) end
+ def nobits?(i) to_int.nobits?(i) end
+ end
+ # :startdoc:
+ end
+ end
+ end
+end
diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb
index 9d64c5c70c..bbd85585a9 100644
--- a/test/prism/ruby/ripper_test.rb
+++ b/test/prism/ruby/ripper_test.rb
@@ -65,13 +65,12 @@ module Prism
# Check that the hardcoded values don't change without us noticing.
def test_internals
- actual = LexCompat::State::ALL
- expected = Ripper.constants.select { |name| name.start_with?("EXPR_") }
- expected -= %i[EXPR_VALUE EXPR_BEG_ANY EXPR_ARG_ANY EXPR_END_ANY]
+ actual = Translation::Ripper.constants.select { |name| name.start_with?("EXPR_") }.sort
+ expected = Ripper.constants.select { |name| name.start_with?("EXPR_") }.sort
- assert_equal(expected.size, actual.size)
- expected.each do |const_name|
- assert_equal(const_name.to_s.delete_prefix("EXPR_").to_sym, actual[Ripper.const_get(const_name)])
+ assert_equal(expected, actual)
+ expected.zip(actual).each do |ripper, prism|
+ assert_equal(Ripper.const_get(ripper), Translation::Ripper.const_get(prism))
end
end