2023-06-20 11:53:02 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module YARP
|
2023-06-30 14:30:24 -04:00
|
|
|
# This represents a source of Ruby code that has been parsed. It is used in
|
|
|
|
# conjunction with locations to allow them to resolve line numbers and source
|
|
|
|
# ranges.
|
|
|
|
class Source
|
|
|
|
attr_reader :source, :offsets
|
|
|
|
|
2023-08-15 10:00:54 -07:00
|
|
|
def initialize(source, offsets = compute_offsets(source))
|
2023-06-30 14:30:24 -04:00
|
|
|
@source = source
|
|
|
|
@offsets = offsets
|
|
|
|
end
|
|
|
|
|
|
|
|
def slice(offset, length)
|
|
|
|
source.byteslice(offset, length)
|
|
|
|
end
|
|
|
|
|
|
|
|
def line(value)
|
|
|
|
offsets.bsearch_index { |offset| offset > value } || offsets.length
|
|
|
|
end
|
|
|
|
|
2023-09-07 13:44:35 -04:00
|
|
|
def line_offset(value)
|
|
|
|
offsets[line(value) - 1]
|
|
|
|
end
|
|
|
|
|
2023-06-30 14:30:24 -04:00
|
|
|
def column(value)
|
|
|
|
value - offsets[line(value) - 1]
|
|
|
|
end
|
2023-08-15 10:00:54 -07:00
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def compute_offsets(code)
|
|
|
|
offsets = [0]
|
|
|
|
code.b.scan("\n") { offsets << $~.end(0) }
|
|
|
|
offsets
|
|
|
|
end
|
2023-06-30 14:30:24 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# This represents a location in the source.
|
2023-06-20 11:53:02 -04:00
|
|
|
class Location
|
2023-06-30 14:30:24 -04:00
|
|
|
# A Source object that is used to determine more information from the given
|
|
|
|
# offset and length.
|
2023-08-25 14:32:32 -04:00
|
|
|
protected attr_reader :source
|
2023-06-30 14:30:24 -04:00
|
|
|
|
|
|
|
# The byte offset from the beginning of the source where this location
|
|
|
|
# starts.
|
|
|
|
attr_reader :start_offset
|
2023-06-20 11:53:02 -04:00
|
|
|
|
2023-06-30 14:30:24 -04:00
|
|
|
# The length of this location in bytes.
|
|
|
|
attr_reader :length
|
|
|
|
|
2023-09-07 13:44:35 -04:00
|
|
|
# The list of comments attached to this location
|
|
|
|
attr_reader :comments
|
|
|
|
|
2023-06-30 14:30:24 -04:00
|
|
|
def initialize(source, start_offset, length)
|
|
|
|
@source = source
|
2023-06-20 11:53:02 -04:00
|
|
|
@start_offset = start_offset
|
|
|
|
@length = length
|
2023-09-07 13:44:35 -04:00
|
|
|
@comments = []
|
2023-06-20 11:53:02 -04:00
|
|
|
end
|
|
|
|
|
2023-08-25 11:22:05 -04:00
|
|
|
# Create a new location object with the given options.
|
|
|
|
def copy(**options)
|
|
|
|
Location.new(
|
|
|
|
options.fetch(:source) { source },
|
|
|
|
options.fetch(:start_offset) { start_offset },
|
|
|
|
options.fetch(:length) { length }
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Returns a string representation of this location.
|
2023-07-06 13:26:26 -04:00
|
|
|
def inspect
|
2023-09-19 11:46:04 -04:00
|
|
|
"#<YARP::Location @start_offset=#{@start_offset} @length=#{@length} start_line=#{start_line}>"
|
2023-07-06 13:26:26 -04:00
|
|
|
end
|
|
|
|
|
2023-06-30 14:30:24 -04:00
|
|
|
# The source code that this location represents.
|
|
|
|
def slice
|
|
|
|
source.slice(start_offset, length)
|
|
|
|
end
|
|
|
|
|
|
|
|
# The byte offset from the beginning of the source where this location ends.
|
2023-06-20 11:53:02 -04:00
|
|
|
def end_offset
|
2023-06-30 14:30:24 -04:00
|
|
|
start_offset + length
|
|
|
|
end
|
|
|
|
|
|
|
|
# The line number where this location starts.
|
|
|
|
def start_line
|
|
|
|
source.line(start_offset)
|
|
|
|
end
|
|
|
|
|
2023-09-07 13:44:35 -04:00
|
|
|
# The content of the line where this location starts before this location.
|
|
|
|
def start_line_slice
|
|
|
|
offset = source.line_offset(start_offset)
|
|
|
|
source.slice(offset, start_offset - offset)
|
|
|
|
end
|
|
|
|
|
2023-06-30 14:30:24 -04:00
|
|
|
# The line number where this location ends.
|
|
|
|
def end_line
|
|
|
|
source.line(end_offset - 1)
|
|
|
|
end
|
|
|
|
|
|
|
|
# The column number in bytes where this location starts from the start of
|
|
|
|
# the line.
|
|
|
|
def start_column
|
|
|
|
source.column(start_offset)
|
|
|
|
end
|
|
|
|
|
|
|
|
# The column number in bytes where this location ends from the start of the
|
|
|
|
# line.
|
|
|
|
def end_column
|
2023-08-29 16:42:43 -04:00
|
|
|
source.column(end_offset)
|
2023-06-20 11:53:02 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def deconstruct_keys(keys)
|
|
|
|
{ start_offset: start_offset, end_offset: end_offset }
|
|
|
|
end
|
|
|
|
|
|
|
|
def pretty_print(q)
|
2023-09-19 11:46:04 -04:00
|
|
|
q.text("(#{start_line},#{start_column})-(#{end_line},#{end_column}))")
|
2023-06-20 11:53:02 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def ==(other)
|
2023-07-14 16:40:09 -04:00
|
|
|
other.is_a?(Location) &&
|
|
|
|
other.start_offset == start_offset &&
|
|
|
|
other.end_offset == end_offset
|
2023-06-20 11:53:02 -04:00
|
|
|
end
|
|
|
|
|
2023-08-25 14:32:32 -04:00
|
|
|
# Returns a new location that stretches from this location to the given
|
|
|
|
# other location. Raises an error if this location is not before the other
|
|
|
|
# location or if they don't share the same source.
|
|
|
|
def join(other)
|
|
|
|
raise "Incompatible sources" if source != other.source
|
|
|
|
raise "Incompatible locations" if start_offset > other.start_offset
|
|
|
|
|
2023-08-22 12:21:35 -04:00
|
|
|
Location.new(source, start_offset, other.end_offset - start_offset)
|
|
|
|
end
|
|
|
|
|
2023-06-20 11:53:02 -04:00
|
|
|
def self.null
|
|
|
|
new(0, 0)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# This represents a comment that was encountered during parsing.
|
|
|
|
class Comment
|
2023-08-15 10:00:54 -07:00
|
|
|
TYPES = [:inline, :embdoc, :__END__]
|
|
|
|
|
2023-06-20 11:53:02 -04:00
|
|
|
attr_reader :type, :location
|
|
|
|
|
|
|
|
def initialize(type, location)
|
|
|
|
@type = type
|
|
|
|
@location = location
|
|
|
|
end
|
|
|
|
|
|
|
|
def deconstruct_keys(keys)
|
|
|
|
{ type: type, location: location }
|
|
|
|
end
|
2023-09-01 14:51:50 -04:00
|
|
|
|
2023-09-07 13:44:35 -04:00
|
|
|
# Returns true if the comment happens on the same line as other code and false if the comment is by itself
|
|
|
|
def trailing?
|
|
|
|
type == :inline && !location.start_line_slice.strip.empty?
|
|
|
|
end
|
|
|
|
|
2023-09-01 14:51:50 -04:00
|
|
|
def inspect
|
|
|
|
"#<YARP::Comment @type=#{@type.inspect} @location=#{@location.inspect}>"
|
|
|
|
end
|
2023-06-20 11:53:02 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# This represents an error that was encountered during parsing.
|
|
|
|
class ParseError
|
|
|
|
attr_reader :message, :location
|
|
|
|
|
|
|
|
def initialize(message, location)
|
|
|
|
@message = message
|
|
|
|
@location = location
|
|
|
|
end
|
|
|
|
|
|
|
|
def deconstruct_keys(keys)
|
|
|
|
{ message: message, location: location }
|
|
|
|
end
|
2023-09-01 14:51:50 -04:00
|
|
|
|
|
|
|
def inspect
|
|
|
|
"#<YARP::ParseError @message=#{@message.inspect} @location=#{@location.inspect}>"
|
|
|
|
end
|
2023-06-20 11:53:02 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# This represents a warning that was encountered during parsing.
|
|
|
|
class ParseWarning
|
|
|
|
attr_reader :message, :location
|
|
|
|
|
|
|
|
def initialize(message, location)
|
|
|
|
@message = message
|
|
|
|
@location = location
|
|
|
|
end
|
|
|
|
|
|
|
|
def deconstruct_keys(keys)
|
|
|
|
{ message: message, location: location }
|
|
|
|
end
|
2023-09-01 14:51:50 -04:00
|
|
|
|
|
|
|
def inspect
|
|
|
|
"#<YARP::ParseWarning @message=#{@message.inspect} @location=#{@location.inspect}>"
|
|
|
|
end
|
2023-06-20 11:53:02 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# This represents the result of a call to ::parse or ::parse_file. It contains
|
|
|
|
# the AST, any comments that were encounters, and any errors that were
|
|
|
|
# encountered.
|
|
|
|
class ParseResult
|
2023-07-07 12:54:18 -04:00
|
|
|
attr_reader :value, :comments, :errors, :warnings, :source
|
2023-06-20 11:53:02 -04:00
|
|
|
|
2023-07-07 12:54:18 -04:00
|
|
|
def initialize(value, comments, errors, warnings, source)
|
2023-06-20 11:53:02 -04:00
|
|
|
@value = value
|
|
|
|
@comments = comments
|
|
|
|
@errors = errors
|
|
|
|
@warnings = warnings
|
2023-07-07 12:54:18 -04:00
|
|
|
@source = source
|
2023-06-20 11:53:02 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def deconstruct_keys(keys)
|
|
|
|
{ value: value, comments: comments, errors: errors, warnings: warnings }
|
|
|
|
end
|
|
|
|
|
|
|
|
def success?
|
|
|
|
errors.empty?
|
|
|
|
end
|
|
|
|
|
|
|
|
def failure?
|
|
|
|
!success?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# This represents a token from the Ruby source.
|
|
|
|
class Token
|
2023-06-30 14:30:24 -04:00
|
|
|
attr_reader :type, :value, :location
|
2023-06-20 11:53:02 -04:00
|
|
|
|
2023-06-30 14:30:24 -04:00
|
|
|
def initialize(type, value, location)
|
2023-06-20 11:53:02 -04:00
|
|
|
@type = type
|
|
|
|
@value = value
|
2023-06-30 14:30:24 -04:00
|
|
|
@location = location
|
2023-06-20 11:53:02 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def deconstruct_keys(keys)
|
|
|
|
{ type: type, value: value, location: location }
|
|
|
|
end
|
|
|
|
|
|
|
|
def pretty_print(q)
|
|
|
|
q.group do
|
|
|
|
q.text(type.to_s)
|
|
|
|
self.location.pretty_print(q)
|
|
|
|
q.text("(")
|
|
|
|
q.nest(2) do
|
|
|
|
q.breakable("")
|
|
|
|
q.pp(value)
|
|
|
|
end
|
|
|
|
q.breakable("")
|
|
|
|
q.text(")")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def ==(other)
|
2023-07-14 16:40:09 -04:00
|
|
|
other.is_a?(Token) &&
|
|
|
|
other.type == type &&
|
|
|
|
other.value == value
|
2023-06-20 11:53:02 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# This represents a node in the tree.
|
|
|
|
class Node
|
2023-06-30 14:30:24 -04:00
|
|
|
attr_reader :location
|
2023-06-20 11:53:02 -04:00
|
|
|
|
2023-08-15 10:00:54 -07:00
|
|
|
def newline?
|
|
|
|
@newline ? true : false
|
|
|
|
end
|
|
|
|
|
|
|
|
def set_newline_flag(newline_marked)
|
|
|
|
line = location.start_line
|
|
|
|
unless newline_marked[line]
|
|
|
|
newline_marked[line] = true
|
|
|
|
@newline = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Slice the location of the node from the source.
|
|
|
|
def slice
|
|
|
|
location.slice
|
|
|
|
end
|
|
|
|
|
2023-06-20 11:53:02 -04:00
|
|
|
def pretty_print(q)
|
2023-09-19 12:30:48 -04:00
|
|
|
q.seplist(inspect.chomp.each_line, -> { q.breakable }) do |line|
|
|
|
|
q.text(line.chomp)
|
|
|
|
end
|
|
|
|
q.current_group.break
|
2023-06-20 11:53:02 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-09-01 14:51:50 -04:00
|
|
|
# This object is responsible for generating the output for the inspect method
|
|
|
|
# implementations of child nodes.
|
|
|
|
class NodeInspector
|
|
|
|
attr_reader :prefix, :output
|
|
|
|
|
|
|
|
def initialize(prefix = "")
|
|
|
|
@prefix = prefix
|
|
|
|
@output = +""
|
|
|
|
end
|
|
|
|
|
|
|
|
# Appends a line to the output with the current prefix.
|
|
|
|
def <<(line)
|
|
|
|
output << "#{prefix}#{line}"
|
|
|
|
end
|
|
|
|
|
|
|
|
# This generates a string that is used as the header of the inspect output
|
|
|
|
# for any given node.
|
|
|
|
def header(node)
|
|
|
|
output = +"@ #{node.class.name.split("::").last} ("
|
2023-09-19 11:46:04 -04:00
|
|
|
output << "location: (#{node.location.start_line},#{node.location.start_column})-(#{node.location.end_line},#{node.location.end_column})"
|
2023-09-01 14:51:50 -04:00
|
|
|
output << ", newline: true" if node.newline?
|
|
|
|
output << ")\n"
|
|
|
|
output
|
|
|
|
end
|
|
|
|
|
|
|
|
# Generates a string that represents a list of nodes. It handles properly
|
|
|
|
# using the box drawing characters to make the output look nice.
|
|
|
|
def list(prefix, nodes)
|
|
|
|
output = +"(length: #{nodes.length})\n"
|
|
|
|
last_index = nodes.length - 1
|
|
|
|
|
|
|
|
nodes.each_with_index do |node, index|
|
|
|
|
pointer, preadd = (index == last_index) ? ["└── ", " "] : ["├── ", "│ "]
|
|
|
|
node_prefix = "#{prefix}#{preadd}"
|
|
|
|
output << node.inspect(NodeInspector.new(node_prefix)).sub(node_prefix, "#{prefix}#{pointer}")
|
|
|
|
end
|
|
|
|
|
|
|
|
output
|
|
|
|
end
|
|
|
|
|
|
|
|
# Generates a string that represents a location field on a node.
|
|
|
|
def location(value)
|
|
|
|
if value
|
2023-09-19 11:46:04 -04:00
|
|
|
"(#{value.start_line},#{value.start_column})-(#{value.end_line},#{value.end_column}) = #{value.slice.inspect}"
|
2023-09-01 14:51:50 -04:00
|
|
|
else
|
|
|
|
"∅"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Generates a string that represents a child node.
|
|
|
|
def child_node(node, append)
|
|
|
|
node.inspect(child_inspector(append)).delete_prefix(prefix)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Returns a new inspector that can be used to inspect a child node.
|
|
|
|
def child_inspector(append)
|
|
|
|
NodeInspector.new("#{prefix}#{append}")
|
|
|
|
end
|
|
|
|
|
|
|
|
# Returns the output as a string.
|
|
|
|
def to_str
|
|
|
|
output
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-08-02 13:17:31 -04:00
|
|
|
# This module is used for testing and debugging and is not meant to be used by
|
|
|
|
# consumers of this library.
|
|
|
|
module Debug
|
2023-08-15 10:00:54 -07:00
|
|
|
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)
|
|
|
|
|
2023-08-17 20:03:06 -04:00
|
|
|
# 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")
|
|
|
|
|
2023-08-15 10:00:54 -07:00
|
|
|
# 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 = [
|
2023-08-27 17:56:53 +02:00
|
|
|
*params.requireds.grep(RequiredParameterNode).map(&:name),
|
|
|
|
*params.optionals.map(&:name),
|
2023-09-06 11:22:27 -04:00
|
|
|
*((params.rest.name || :*) if params.rest && params.rest.operator != ","),
|
2023-08-27 17:56:53 +02:00
|
|
|
*params.posts.grep(RequiredParameterNode).map(&:name),
|
2023-09-06 11:06:26 -04:00
|
|
|
*params.keywords.reject(&:value).map(&:name),
|
|
|
|
*params.keywords.select(&:value).map(&:name)
|
2023-08-15 10:00:54 -07:00
|
|
|
]
|
|
|
|
|
|
|
|
# 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
|
2023-08-27 17:56:53 +02:00
|
|
|
sorted << param.name
|
2023-08-15 10:00:54 -07:00
|
|
|
when SplatNode
|
2023-08-27 17:56:53 +02:00
|
|
|
sorted << param.expression.name if param.expression
|
2023-08-15 10:00:54 -07:00
|
|
|
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([], [])
|
2023-08-18 09:16:10 -04:00
|
|
|
when InterpolatedRegularExpressionNode
|
|
|
|
locals << [] if node.once?
|
2023-08-15 10:00:54 -07:00
|
|
|
end
|
|
|
|
|
2023-09-15 18:38:31 -04:00
|
|
|
stack.concat(node.compact_child_nodes)
|
2023-08-15 10:00:54 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
locals
|
|
|
|
end
|
|
|
|
|
2023-08-02 13:17:31 -04:00
|
|
|
def self.newlines(source)
|
|
|
|
YARP.parse(source).source.offsets
|
|
|
|
end
|
2023-08-15 10:00:54 -07:00
|
|
|
|
|
|
|
def self.parse_serialize_file(filepath)
|
|
|
|
parse_serialize_file_metadata(filepath, [filepath.bytesize, filepath.b, 0].pack("LA*L"))
|
|
|
|
end
|
2023-07-07 12:54:18 -04:00
|
|
|
end
|
2023-08-02 13:17:31 -04:00
|
|
|
|
|
|
|
# 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
|
2023-09-22 10:32:34 -04:00
|
|
|
|
|
|
|
# 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.
|
2023-09-22 11:31:45 -04:00
|
|
|
autoload :BasicVisitor, "yarp/visitor"
|
|
|
|
autoload :Compiler, "yarp/compiler"
|
|
|
|
autoload :DesugarCompiler, "yarp/desugar_compiler"
|
2023-09-22 10:32:34 -04:00
|
|
|
autoload :Dispatcher, "yarp/dispatcher"
|
2023-09-22 10:57:00 -04:00
|
|
|
autoload :DSL, "yarp/dsl"
|
2023-09-22 11:31:45 -04:00
|
|
|
autoload :MutationCompiler, "yarp/mutation_compiler"
|
2023-09-22 10:38:48 -04:00
|
|
|
autoload :RipperCompat, "yarp/ripper_compat"
|
|
|
|
autoload :Pack, "yarp/pack"
|
|
|
|
autoload :Pattern, "yarp/pattern"
|
|
|
|
autoload :Serialize, "yarp/serialize"
|
2023-09-22 11:31:45 -04:00
|
|
|
autoload :Visitor, "yarp/visitor"
|
2023-09-22 10:50:08 -04:00
|
|
|
|
|
|
|
# Load the serialized AST using the source as a reference into a tree.
|
|
|
|
def self.load(source, serialized)
|
|
|
|
Serialize.load(source, serialized)
|
|
|
|
end
|
2023-06-20 11:53:02 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
require_relative "yarp/lex_compat"
|
|
|
|
require_relative "yarp/node"
|
2023-07-18 16:25:52 -04:00
|
|
|
|
2023-09-08 14:27:17 -04:00
|
|
|
require_relative "yarp/parse_result/comments"
|
|
|
|
require_relative "yarp/parse_result/newlines"
|
|
|
|
|
2023-09-22 10:38:48 -04:00
|
|
|
# This is a Ruby implementation of the YARP parser. If we're running on CRuby
|
|
|
|
# and we haven't explicitly set the YARP_FFI_BACKEND environment variable, then
|
|
|
|
# it's going to require the built library. Otherwise, it's going to require a
|
|
|
|
# module that uses FFI to call into the library.
|
2023-08-15 10:00:54 -07:00
|
|
|
if RUBY_ENGINE == "ruby" and !ENV["YARP_FFI_BACKEND"]
|
|
|
|
require "yarp/yarp"
|
|
|
|
else
|
2023-08-22 16:14:21 +02:00
|
|
|
require_relative "yarp/ffi"
|
2023-08-15 10:00:54 -07:00
|
|
|
end
|
2023-09-11 11:31:52 -04:00
|
|
|
|
|
|
|
# Reopening the YARP module after yarp/node is required so that constant
|
|
|
|
# reflection APIs will find the constants defined in the node file before these.
|
|
|
|
# This block is meant to contain extra APIs we define on YARP nodes that aren't
|
|
|
|
# templated and are meant as convenience methods.
|
|
|
|
module YARP
|
|
|
|
class FloatNode < Node
|
|
|
|
# Returns the value of the node as a Ruby Float.
|
|
|
|
def value
|
|
|
|
Float(slice)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class ImaginaryNode < Node
|
|
|
|
# Returns the value of the node as a Ruby Complex.
|
|
|
|
def value
|
|
|
|
Complex(0, numeric.value)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class IntegerNode < Node
|
|
|
|
# Returns the value of the node as a Ruby Integer.
|
|
|
|
def value
|
|
|
|
Integer(slice)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class InterpolatedRegularExpressionNode < Node
|
|
|
|
# Returns a numeric value that represents the flags that were used to create
|
2023-09-15 11:26:13 -05:00
|
|
|
# the regular expression.
|
2023-09-11 11:31:52 -04:00
|
|
|
def options
|
2023-09-22 11:41:34 -04:00
|
|
|
o = flags & (RegularExpressionFlags::IGNORE_CASE | RegularExpressionFlags::EXTENDED | RegularExpressionFlags::MULTI_LINE)
|
2023-09-15 13:17:51 -05:00
|
|
|
o |= Regexp::FIXEDENCODING if flags.anybits?(RegularExpressionFlags::EUC_JP | RegularExpressionFlags::WINDOWS_31J | RegularExpressionFlags::UTF_8)
|
|
|
|
o |= Regexp::NOENCODING if flags.anybits?(RegularExpressionFlags::ASCII_8BIT)
|
2023-09-15 11:26:13 -05:00
|
|
|
o
|
2023-09-11 11:31:52 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class RationalNode < Node
|
|
|
|
# Returns the value of the node as a Ruby Rational.
|
|
|
|
def value
|
|
|
|
Rational(slice.chomp("r"))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class RegularExpressionNode < Node
|
|
|
|
# Returns a numeric value that represents the flags that were used to create
|
2023-09-15 11:26:13 -05:00
|
|
|
# the regular expression.
|
2023-09-11 11:31:52 -04:00
|
|
|
def options
|
2023-09-22 11:41:34 -04:00
|
|
|
o = flags & (RegularExpressionFlags::IGNORE_CASE | RegularExpressionFlags::EXTENDED | RegularExpressionFlags::MULTI_LINE)
|
2023-09-15 13:17:51 -05:00
|
|
|
o |= Regexp::FIXEDENCODING if flags.anybits?(RegularExpressionFlags::EUC_JP | RegularExpressionFlags::WINDOWS_31J | RegularExpressionFlags::UTF_8)
|
|
|
|
o |= Regexp::NOENCODING if flags.anybits?(RegularExpressionFlags::ASCII_8BIT)
|
2023-09-15 11:26:13 -05:00
|
|
|
o
|
2023-09-11 11:31:52 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|