lib/prism/parse_result.rb in prism-0.24.0 vs lib/prism/parse_result.rb in prism-0.25.0
- old
+ new
@@ -19,14 +19,20 @@
@source = source
@start_line = start_line # set after parsing is done
@offsets = offsets # set after parsing is done
end
+ # Returns the encoding of the source code, which is set by parameters to the
+ # parser or by the encoding magic comment.
+ def encoding
+ source.encoding
+ end
+
# Perform a byteslice on the source code using the given byte offset and
# byte length.
def slice(byte_offset, length)
- source.byteslice(byte_offset, length)
+ source.byteslice(byte_offset, length) or raise
end
# Binary search through the offsets to find the line number for the given
# byte offset.
def line(byte_offset)
@@ -44,11 +50,11 @@
byte_offset - line_start(byte_offset)
end
# Return the character offset for the given byte offset.
def character_offset(byte_offset)
- source.byteslice(0, byte_offset).length
+ (source.byteslice(0, byte_offset) or raise).length
end
# Return the column number in characters for the given byte offset.
def character_column(byte_offset)
character_offset(byte_offset) - character_offset(line_start(byte_offset))
@@ -59,11 +65,11 @@
#
# This method is tested with UTF-8, UTF-16, and UTF-32. If there is the
# concept of code units that differs from the number of characters in other
# encodings, it is not captured here.
def code_units_offset(byte_offset, encoding)
- byteslice = source.byteslice(0, byte_offset).encode(encoding)
+ byteslice = (source.byteslice(0, byte_offset) or raise).encode(encoding)
(encoding == Encoding::UTF_16LE || encoding == Encoding::UTF_16BE) ? (byteslice.bytesize / 2) : byteslice.length
end
# Returns the column number in code units for the given encoding for the
# given byte offset.
@@ -79,13 +85,13 @@
left = 0
right = offsets.length - 1
while left <= right
mid = left + (right - left) / 2
- return mid if offsets[mid] == byte_offset
+ return mid if (offset = offsets[mid]) == byte_offset
- if offsets[mid] < byte_offset
+ if offset < byte_offset
left = mid + 1
else
right = mid - 1
end
end
@@ -106,29 +112,55 @@
attr_reader :start_offset
# The length of this location in bytes.
attr_reader :length
- # The list of comments attached to this location
- attr_reader :comments
-
# Create a new location object with the given source, start byte offset, and
# byte length.
def initialize(source, start_offset, length)
@source = source
@start_offset = start_offset
@length = length
- @comments = []
+
+ # These are used to store comments that are associated with this location.
+ # They are initialized to `nil` to save on memory when there are no
+ # comments to be attached and/or the comment-related APIs are not used.
+ @leading_comments = nil
+ @trailing_comments = nil
end
+ # These are the comments that are associated with this location that exist
+ # before the start of this location.
+ def leading_comments
+ @leading_comments ||= []
+ end
+
+ # Attach a comment to the leading comments of this location.
+ def leading_comment(comment)
+ leading_comments << comment
+ end
+
+ # These are the comments that are associated with this location that exist
+ # after the end of this location.
+ def trailing_comments
+ @trailing_comments ||= []
+ end
+
+ # Attach a comment to the trailing comments of this location.
+ def trailing_comment(comment)
+ trailing_comments << comment
+ end
+
+ # Returns all comments that are associated with this location (both leading
+ # and trailing comments).
+ def comments
+ [*@leading_comments, *@trailing_comments]
+ end
+
# 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 }
- )
+ def copy(source: self.source, start_offset: self.start_offset, length: self.length)
+ Location.new(source, start_offset, length)
end
# Returns a string representation of this location.
def inspect
"#<Prism::Location @start_offset=#{@start_offset} @length=#{@length} start_line=#{start_line}>"
@@ -228,11 +260,11 @@
q.text("(#{start_line},#{start_column})-(#{end_line},#{end_column})")
end
# Returns true if the given other location is equal to this location.
def ==(other)
- other.is_a?(Location) &&
+ Location === other &&
other.start_offset == start_offset &&
other.end_offset == end_offset
end
# Returns a new location that stretches from this location to the given
@@ -242,17 +274,10 @@
raise "Incompatible sources" if source != other.source
raise "Incompatible locations" if start_offset > other.start_offset
Location.new(source, start_offset, other.end_offset - start_offset)
end
-
- # Returns a null location that does not correspond to a source and points to
- # the beginning of the file. Useful for when you want a location object but
- # do not care where it points.
- def self.null
- new(nil, 0, 0)
- end
end
# This represents a comment that was encountered during parsing. It is the
# base class for all comment types.
class Comment
@@ -266,10 +291,15 @@
# Implement the hash pattern matching interface for Comment.
def deconstruct_keys(keys)
{ location: location }
end
+
+ # Returns the content of the comment by slicing it from the source code.
+ def slice
+ location.slice
+ end
end
# InlineComment objects are the most common. They correspond to comments in
# the source file like this one that start with #.
class InlineComment < Comment
@@ -334,63 +364,73 @@
end
end
# This represents an error that was encountered during parsing.
class ParseError
+ # The type of error. This is an _internal_ symbol that is used for
+ # communicating with translation layers. It is not meant to be public API.
+ attr_reader :type
+
# The message associated with this error.
attr_reader :message
# A Location object representing the location of this error in the source.
attr_reader :location
# The level of this error.
attr_reader :level
# Create a new error object with the given message and location.
- def initialize(message, location, level)
+ def initialize(type, message, location, level)
+ @type = type
@message = message
@location = location
@level = level
end
# Implement the hash pattern matching interface for ParseError.
def deconstruct_keys(keys)
- { message: message, location: location, level: level }
+ { type: type, message: message, location: location, level: level }
end
# Returns a string representation of this error.
def inspect
- "#<Prism::ParseError @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level.inspect}>"
+ "#<Prism::ParseError @type=#{@type.inspect} @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level.inspect}>"
end
end
# This represents a warning that was encountered during parsing.
class ParseWarning
+ # The type of warning. This is an _internal_ symbol that is used for
+ # communicating with translation layers. It is not meant to be public API.
+ attr_reader :type
+
# The message associated with this warning.
attr_reader :message
# A Location object representing the location of this warning in the source.
attr_reader :location
# The level of this warning.
attr_reader :level
# Create a new warning object with the given message and location.
- def initialize(message, location, level)
+ def initialize(type, message, location, level)
+ @type = type
@message = message
@location = location
@level = level
end
# Implement the hash pattern matching interface for ParseWarning.
def deconstruct_keys(keys)
- { message: message, location: location, level: level }
+ { type: type, message: message, location: location, level: level }
end
# Returns a string representation of this warning.
def inspect
- "#<Prism::ParseWarning @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level.inspect}>"
+ "#<Prism::ParseWarning @type=#{@type.inspect} @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level.inspect}>"
end
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
@@ -435,10 +475,15 @@
# Implement the hash pattern matching interface for ParseResult.
def deconstruct_keys(keys)
{ value: value, comments: comments, magic_comments: magic_comments, data_loc: data_loc, errors: errors, warnings: warnings }
end
+ # Returns the encoding of the source code that was parsed.
+ def encoding
+ source.encoding
+ end
+
# Returns true if there were no errors during parsing and false if there
# were.
def success?
errors.empty?
end
@@ -475,12 +520,13 @@
{ type: type, value: value, location: location }
end
# A Location object representing the location of this token in the source.
def location
- return @location if @location.is_a?(Location)
- @location = Location.new(source, @location >> 32, @location & 0xFFFFFFFF)
+ location = @location
+ return location if location.is_a?(Location)
+ @location = Location.new(source, location >> 32, location & 0xFFFFFFFF)
end
# Implement the pretty print interface for Token.
def pretty_print(q)
q.group do
@@ -496,10 +542,10 @@
end
end
# Returns true if the given other token is equal to this token.
def ==(other)
- other.is_a?(Token) &&
+ Token === other &&
other.type == type &&
other.value == value
end
end
end