[ruby/yarp] Move debug into its own file
https://github.com/ruby/yarp/commit/139362c90a
This commit is contained in:
parent
aeb53cb50e
commit
978f91a10c
165
lib/yarp.rb
165
lib/yarp.rb
@ -358,171 +358,13 @@ module YARP
|
|||||||
end
|
end
|
||||||
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,
|
# 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
|
# 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
|
# our require speed faster since consuming libraries are unlikely to use all
|
||||||
# of these features.
|
# of these features.
|
||||||
autoload :BasicVisitor, "yarp/visitor"
|
autoload :BasicVisitor, "yarp/visitor"
|
||||||
autoload :Compiler, "yarp/compiler"
|
autoload :Compiler, "yarp/compiler"
|
||||||
|
autoload :Debug, "yarp/debug"
|
||||||
autoload :DesugarCompiler, "yarp/desugar_compiler"
|
autoload :DesugarCompiler, "yarp/desugar_compiler"
|
||||||
autoload :Dispatcher, "yarp/dispatcher"
|
autoload :Dispatcher, "yarp/dispatcher"
|
||||||
autoload :DSL, "yarp/dsl"
|
autoload :DSL, "yarp/dsl"
|
||||||
@ -533,6 +375,11 @@ module YARP
|
|||||||
autoload :Serialize, "yarp/serialize"
|
autoload :Serialize, "yarp/serialize"
|
||||||
autoload :Visitor, "yarp/visitor"
|
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.
|
# Load the serialized AST using the source as a reference into a tree.
|
||||||
def self.load(source, serialized)
|
def self.load(source, serialized)
|
||||||
Serialize.load(source, serialized)
|
Serialize.load(source, serialized)
|
||||||
|
157
lib/yarp/debug.rb
Normal file
157
lib/yarp/debug.rb
Normal 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
|
@ -60,6 +60,7 @@ Gem::Specification.new do |spec|
|
|||||||
"include/yarp/version.h",
|
"include/yarp/version.h",
|
||||||
"lib/yarp.rb",
|
"lib/yarp.rb",
|
||||||
"lib/yarp/compiler.rb",
|
"lib/yarp/compiler.rb",
|
||||||
|
"lib/yarp/debug.rb",
|
||||||
"lib/yarp/desugar_compiler.rb",
|
"lib/yarp/desugar_compiler.rb",
|
||||||
"lib/yarp/dispatcher.rb",
|
"lib/yarp/dispatcher.rb",
|
||||||
"lib/yarp/dsl.rb",
|
"lib/yarp/dsl.rb",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user