[ruby/yarp] Move debug into its own file

https://github.com/ruby/yarp/commit/139362c90a
This commit is contained in:
Kevin Newton 2023-09-22 11:40:14 -04:00
parent aeb53cb50e
commit 978f91a10c
3 changed files with 164 additions and 159 deletions

View File

@ -358,171 +358,13 @@ module YARP
end
end
# This module is used for testing and debugging and is not meant to be used by
# consumers of this library.
module Debug
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
# 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 = []
stack = [ISeq.new(RubyVM::InstructionSequence.compile(source).to_a)]
while (iseq = stack.pop)
if iseq.type != :once
names = iseq.local_table
# CRuby will push on a special local variable when there are keyword
# arguments. We get rid of that here.
names = names.grep_v(Integer)
# For some reason, CRuby occasionally pushes this special local
# variable when there are splat arguments. We get rid of that here.
names = names.grep_v(:"#arg_rest")
# Now push them onto the list of locals.
locals << names
end
iseq.each_child { |child| stack << child }
end
locals
ensure
$VERBOSE = verbose
end
end
# For the given source, parses with YARP and returns a list of all of the
# sets of local variables that were encountered.
def self.yarp_locals(source)
locals = []
stack = [YARP.parse(source).value]
while (node = stack.pop)
case node
when BlockNode, DefNode, LambdaNode
names = node.locals
params = node.parameters
params = params&.parameters unless node.is_a?(DefNode)
# YARP 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.grep(RequiredParameterNode).map(&:name),
*params.optionals.map(&:name),
*((params.rest.name || :*) if params.rest && params.rest.operator != ","),
*params.posts.grep(RequiredParameterNode).map(&:name),
*params.keywords.reject(&:value).map(&:name),
*params.keywords.select(&:value).map(&:name)
]
# TODO: When we get a ... parameter, we should be pushing * and &
# onto the local list. We don't do that yet, so we need to add them
# in here.
if params.keyword_rest.is_a?(ForwardingParameterNode)
sorted.push(:*, :&, :"...")
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(RequiredDestructuredParameterNode).reverse
while (param = param_stack.pop)
case param
when RequiredDestructuredParameterNode
param_stack.concat(param.parameters.reverse)
when RequiredParameterNode
sorted << param.name
when SplatNode
sorted << param.expression.name if param.expression
end
end
names = sorted.concat(names - sorted)
end
locals << names
when ClassNode, ModuleNode, ProgramNode, SingletonClassNode
locals << node.locals
when ForNode
locals << []
when PostExecutionNode
locals.push([], [])
when InterpolatedRegularExpressionNode
locals << [] if node.once?
end
stack.concat(node.compact_child_nodes)
end
locals
end
def self.newlines(source)
YARP.parse(source).source.offsets
end
def self.parse_serialize_file(filepath)
parse_serialize_file_metadata(filepath, [filepath.bytesize, filepath.b, 0].pack("LA*L"))
end
end
# Marking this as private so that consumers don't see it. It makes it a little
# annoying for testing since you have to const_get it to access the methods,
# but at least this way it's clear it's not meant for consumers.
private_constant :Debug
# There are many files in YARP that are templated to handle every node type,
# which means the files can end up being quite large. We autoload them to make
# our require speed faster since consuming libraries are unlikely to use all
# of these features.
autoload :BasicVisitor, "yarp/visitor"
autoload :Compiler, "yarp/compiler"
autoload :Debug, "yarp/debug"
autoload :DesugarCompiler, "yarp/desugar_compiler"
autoload :Dispatcher, "yarp/dispatcher"
autoload :DSL, "yarp/dsl"
@ -533,6 +375,11 @@ module YARP
autoload :Serialize, "yarp/serialize"
autoload :Visitor, "yarp/visitor"
# Marking this as private so that consumers don't see it. It makes it a little
# annoying for testing since you have to const_get it to access the methods,
# but at least this way it's clear it's not meant for consumers.
private_constant :Debug
# Load the serialized AST using the source as a reference into a tree.
def self.load(source, serialized)
Serialize.load(source, serialized)

157
lib/yarp/debug.rb Normal file
View File

@ -0,0 +1,157 @@
# frozen_string_literal: true
module YARP
# This module is used for testing and debugging and is not meant to be used by
# consumers of this library.
module Debug
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
# 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 = []
stack = [ISeq.new(RubyVM::InstructionSequence.compile(source).to_a)]
while (iseq = stack.pop)
if iseq.type != :once
names = iseq.local_table
# CRuby will push on a special local variable when there are keyword
# arguments. We get rid of that here.
names = names.grep_v(Integer)
# For some reason, CRuby occasionally pushes this special local
# variable when there are splat arguments. We get rid of that here.
names = names.grep_v(:"#arg_rest")
# Now push them onto the list of locals.
locals << names
end
iseq.each_child { |child| stack << child }
end
locals
ensure
$VERBOSE = verbose
end
end
# For the given source, parses with YARP and returns a list of all of the
# sets of local variables that were encountered.
def self.yarp_locals(source)
locals = []
stack = [YARP.parse(source).value]
while (node = stack.pop)
case node
when BlockNode, DefNode, LambdaNode
names = node.locals
params = node.parameters
params = params&.parameters unless node.is_a?(DefNode)
# YARP 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.grep(RequiredParameterNode).map(&:name),
*params.optionals.map(&:name),
*((params.rest.name || :*) if params.rest && params.rest.operator != ","),
*params.posts.grep(RequiredParameterNode).map(&:name),
*params.keywords.reject(&:value).map(&:name),
*params.keywords.select(&:value).map(&:name)
]
# TODO: When we get a ... parameter, we should be pushing * and &
# onto the local list. We don't do that yet, so we need to add them
# in here.
if params.keyword_rest.is_a?(ForwardingParameterNode)
sorted.push(:*, :&, :"...")
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(RequiredDestructuredParameterNode).reverse
while (param = param_stack.pop)
case param
when RequiredDestructuredParameterNode
param_stack.concat(param.parameters.reverse)
when RequiredParameterNode
sorted << param.name
when SplatNode
sorted << param.expression.name if param.expression
end
end
names = sorted.concat(names - sorted)
end
locals << names
when ClassNode, ModuleNode, ProgramNode, SingletonClassNode
locals << node.locals
when ForNode
locals << []
when PostExecutionNode
locals.push([], [])
when InterpolatedRegularExpressionNode
locals << [] if node.once?
end
stack.concat(node.compact_child_nodes)
end
locals
end
def self.newlines(source)
YARP.parse(source).source.offsets
end
def self.parse_serialize_file(filepath)
parse_serialize_file_metadata(filepath, [filepath.bytesize, filepath.b, 0].pack("LA*L"))
end
end
end

View File

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