[ruby/prism] Remove Debug module

https://github.com/ruby/prism/commit/4d8929ff6a
This commit is contained in:
Kevin Newton 2024-05-24 12:48:41 -04:00 committed by git
parent 745a948b6d
commit f8b750370e
8 changed files with 187 additions and 214 deletions

View File

@ -13,7 +13,6 @@ module Prism
autoload :BasicVisitor, "prism/visitor"
autoload :Compiler, "prism/compiler"
autoload :Debug, "prism/debug"
autoload :DesugarCompiler, "prism/desugar_compiler"
autoload :Dispatcher, "prism/dispatcher"
autoload :DotVisitor, "prism/dot_visitor"
@ -32,7 +31,6 @@ module Prism
# Some of these constants are not meant to be exposed, so marking them as
# private here.
private_constant :Debug
private_constant :LexCompat
private_constant :LexRipper

View File

@ -1,206 +0,0 @@
# frozen_string_literal: true
module Prism
# This module is used for testing and debugging and is not meant to be used by
# consumers of this library.
module Debug
# A wrapper around a RubyVM::InstructionSequence that provides a more
# convenient interface for accessing parts of the iseq.
class ISeq # :nodoc:
attr_reader :parts
def initialize(parts)
@parts = parts
end
def type
parts[0]
end
def local_table
parts[10]
end
def instructions
parts[13]
end
def each_child
instructions.each do |instruction|
# Only look at arrays. Other instructions are line numbers or
# tracepoint events.
next unless instruction.is_a?(Array)
instruction.each do |opnd|
# Only look at arrays. Other operands are literals.
next unless opnd.is_a?(Array)
# Only look at instruction sequences. Other operands are literals.
next unless opnd[0] == "YARVInstructionSequence/SimpleDataFormat"
yield ISeq.new(opnd)
end
end
end
end
private_constant :ISeq
# :call-seq:
# Debug::cruby_locals(source) -> Array
#
# For the given source, compiles with CRuby and returns a list of all of the
# sets of local variables that were encountered.
def self.cruby_locals(source)
verbose, $VERBOSE = $VERBOSE, nil
begin
locals = [] #: Array[Array[Symbol | Integer]]
stack = [ISeq.new(RubyVM::InstructionSequence.compile(source).to_a)]
while (iseq = stack.pop)
names = [*iseq.local_table]
names.map!.with_index do |name, index|
# When an anonymous local variable is present in the iseq's local
# table, it is represented as the stack offset from the top.
# However, when these are dumped to binary and read back in, they
# are replaced with the symbol :#arg_rest. To consistently handle
# this, we replace them here with their index.
if name == :"#arg_rest"
names.length - index + 1
else
name
end
end
locals << names
iseq.each_child { |child| stack << child }
end
locals
ensure
$VERBOSE = verbose
end
end
# Used to hold the place of a local that will be in the local table but
# cannot be accessed directly from the source code. For example, the
# iteration variable in a for loop or the positional parameter on a method
# definition that is destructured.
AnonymousLocal = Object.new
private_constant :AnonymousLocal
# :call-seq:
# Debug::prism_locals(source) -> Array
#
# For the given source, parses with prism and returns a list of all of the
# sets of local variables that were encountered.
def self.prism_locals(source)
locals = [] #: Array[Array[Symbol | Integer]]
stack = [Prism.parse(source).value] #: Array[Prism::node]
while (node = stack.pop)
case node
when BlockNode, DefNode, LambdaNode
names = node.locals
params =
if node.is_a?(DefNode)
node.parameters
elsif node.parameters.is_a?(NumberedParametersNode)
nil
else
node.parameters&.parameters
end
# prism places parameters in the same order that they appear in the
# source. CRuby places them in the order that they need to appear
# according to their own internal calling convention. We mimic that
# order here so that we can compare properly.
if params
sorted = [
*params.requireds.map do |required|
if required.is_a?(RequiredParameterNode)
required.name
else
AnonymousLocal
end
end,
*params.optionals.map(&:name),
*((params.rest.name || :*) if params.rest && !params.rest.is_a?(ImplicitRestNode)),
*params.posts.map do |post|
if post.is_a?(RequiredParameterNode)
post.name
else
AnonymousLocal
end
end,
*params.keywords.grep(RequiredKeywordParameterNode).map(&:name),
*params.keywords.grep(OptionalKeywordParameterNode).map(&:name),
]
sorted << AnonymousLocal if params.keywords.any?
if params.keyword_rest.is_a?(ForwardingParameterNode)
sorted.push(:*, :**, :&, :"...")
elsif params.keyword_rest.is_a?(KeywordRestParameterNode)
sorted << (params.keyword_rest.name || :**)
end
# Recurse down the parameter tree to find any destructured
# parameters and add them after the other parameters.
param_stack = params.requireds.concat(params.posts).grep(MultiTargetNode).reverse
while (param = param_stack.pop)
case param
when MultiTargetNode
param_stack.concat(param.rights.reverse)
param_stack << param.rest if param.rest&.expression && !sorted.include?(param.rest.expression.name)
param_stack.concat(param.lefts.reverse)
when RequiredParameterNode
sorted << param.name
when SplatNode
sorted << param.expression.name
end
end
if params.block
sorted << (params.block.name || :&)
end
names = sorted.concat(names - sorted)
end
names.map!.with_index do |name, index|
if name == AnonymousLocal
names.length - index + 1
else
name
end
end
locals << names
when ClassNode, ModuleNode, ProgramNode, SingletonClassNode
locals << node.locals
when ForNode
locals << [2]
when PostExecutionNode
locals.push([], [])
when InterpolatedRegularExpressionNode
locals << [] if node.once?
end
stack.concat(node.compact_child_nodes)
end
locals
end
# :call-seq:
# Debug::newlines(source) -> Array
#
# For the given source string, return the byte offsets of every newline in
# the source.
def self.newlines(source)
Prism.parse(source).source.offsets
end
end
end

View File

@ -394,7 +394,7 @@ module Prism
template << "L"
if (encoding = options[:encoding])
name = encoding.name
name = encoding.is_a?(Encoding) ? encoding.name : encoding
values.push(name.bytesize, name.b)
template << "A*"
else

View File

@ -70,7 +70,6 @@ Gem::Specification.new do |spec|
"include/prism/version.h",
"lib/prism.rb",
"lib/prism/compiler.rb",
"lib/prism/debug.rb",
"lib/prism/desugar_compiler.rb",
"lib/prism/dispatcher.rb",
"lib/prism/dot_visitor.rb",

View File

@ -6,7 +6,7 @@ module Prism
class CommentsTest < TestCase
def test_comment_inline
source = "# comment"
assert_equal [0], Debug.newlines(source)
assert_equal [0], Prism.parse(source).source.offsets
assert_comment(
source,

View File

@ -41,8 +41,8 @@ module Prism
def assert_locals(filepath)
source = File.read(filepath)
expected = Debug.cruby_locals(source)
actual = Debug.prism_locals(source)
expected = cruby_locals(source)
actual = prism_locals(source)
assert_equal(expected, actual)
end
@ -54,5 +54,186 @@ module Prism
ensure
$VERBOSE = previous_verbosity
end
# A wrapper around a RubyVM::InstructionSequence that provides a more
# convenient interface for accessing parts of the iseq.
class ISeq
attr_reader :parts
def initialize(parts)
@parts = parts
end
def type
parts[0]
end
def local_table
parts[10]
end
def instructions
parts[13]
end
def each_child
instructions.each do |instruction|
# Only look at arrays. Other instructions are line numbers or
# tracepoint events.
next unless instruction.is_a?(Array)
instruction.each do |opnd|
# Only look at arrays. Other operands are literals.
next unless opnd.is_a?(Array)
# Only look at instruction sequences. Other operands are literals.
next unless opnd[0] == "YARVInstructionSequence/SimpleDataFormat"
yield ISeq.new(opnd)
end
end
end
end
# Used to hold the place of a local that will be in the local table but
# cannot be accessed directly from the source code. For example, the
# iteration variable in a for loop or the positional parameter on a method
# definition that is destructured.
AnonymousLocal = Object.new
# For the given source, compiles with CRuby and returns a list of all of the
# sets of local variables that were encountered.
def cruby_locals(source)
verbose, $VERBOSE = $VERBOSE, nil
begin
locals = [] #: Array[Array[Symbol | Integer]]
stack = [ISeq.new(RubyVM::InstructionSequence.compile(source).to_a)]
while (iseq = stack.pop)
names = [*iseq.local_table]
names.map!.with_index do |name, index|
# When an anonymous local variable is present in the iseq's local
# table, it is represented as the stack offset from the top.
# However, when these are dumped to binary and read back in, they
# are replaced with the symbol :#arg_rest. To consistently handle
# this, we replace them here with their index.
if name == :"#arg_rest"
names.length - index + 1
else
name
end
end
locals << names
iseq.each_child { |child| stack << child }
end
locals
ensure
$VERBOSE = verbose
end
end
# For the given source, parses with prism and returns a list of all of the
# sets of local variables that were encountered.
def prism_locals(source)
locals = [] #: Array[Array[Symbol | Integer]]
stack = [Prism.parse(source).value] #: Array[Prism::node]
while (node = stack.pop)
case node
when BlockNode, DefNode, LambdaNode
names = node.locals
params =
if node.is_a?(DefNode)
node.parameters
elsif node.parameters.is_a?(NumberedParametersNode)
nil
else
node.parameters&.parameters
end
# prism places parameters in the same order that they appear in the
# source. CRuby places them in the order that they need to appear
# according to their own internal calling convention. We mimic that
# order here so that we can compare properly.
if params
sorted = [
*params.requireds.map do |required|
if required.is_a?(RequiredParameterNode)
required.name
else
AnonymousLocal
end
end,
*params.optionals.map(&:name),
*((params.rest.name || :*) if params.rest && !params.rest.is_a?(ImplicitRestNode)),
*params.posts.map do |post|
if post.is_a?(RequiredParameterNode)
post.name
else
AnonymousLocal
end
end,
*params.keywords.grep(RequiredKeywordParameterNode).map(&:name),
*params.keywords.grep(OptionalKeywordParameterNode).map(&:name),
]
sorted << AnonymousLocal if params.keywords.any?
if params.keyword_rest.is_a?(ForwardingParameterNode)
sorted.push(:*, :**, :&, :"...")
elsif params.keyword_rest.is_a?(KeywordRestParameterNode)
sorted << (params.keyword_rest.name || :**)
end
# Recurse down the parameter tree to find any destructured
# parameters and add them after the other parameters.
param_stack = params.requireds.concat(params.posts).grep(MultiTargetNode).reverse
while (param = param_stack.pop)
case param
when MultiTargetNode
param_stack.concat(param.rights.reverse)
param_stack << param.rest if param.rest&.expression && !sorted.include?(param.rest.expression.name)
param_stack.concat(param.lefts.reverse)
when RequiredParameterNode
sorted << param.name
when SplatNode
sorted << param.expression.name
end
end
if params.block
sorted << (params.block.name || :&)
end
names = sorted.concat(names - sorted)
end
names.map!.with_index do |name, index|
if name == AnonymousLocal
names.length - index + 1
else
name
end
end
locals << names
when ClassNode, ModuleNode, ProgramNode, SingletonClassNode
locals << node.locals
when ForNode
locals << [2]
when PostExecutionNode
locals.push([], [])
when InterpolatedRegularExpressionNode
locals << [] if node.once?
end
stack.concat(node.compact_child_nodes)
end
locals
end
end
end

View File

@ -10,6 +10,7 @@ module Prism
filepaths = Dir["*.rb", base: base] - %w[
encoding_test.rb
errors_test.rb
locals_test.rb
parser_test.rb
regexp_test.rb
static_literals_test.rb

View File

@ -265,7 +265,7 @@ module Prism
# Next, assert that the newlines are in the expected places.
expected_newlines = [0]
source.b.scan("\n") { expected_newlines << $~.offset(0)[0] + 1 }
assert_equal expected_newlines, Debug.newlines(source)
assert_equal expected_newlines, Prism.parse(source).source.offsets
if ripper_should_match
# Finally, assert that we can lex the source and get the same tokens as