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