1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
|
# frozen_string_literal: true
module Parser
module Source
##
# {Map} relates AST nodes to the source code they were parsed from.
# More specifically, a {Map} or its subclass contains a set of ranges:
#
# * `expression`: smallest range which includes all source corresponding
# to the node and all `expression` ranges of its children.
# * other ranges (`begin`, `end`, `operator`, ...): node-specific ranges
# pointing to various interesting tokens corresponding to the node.
#
# Note that the {Map::Heredoc} map is the only one whose `expression` does
# not include other ranges. It only covers the heredoc marker (`<<HERE`),
# not the here document itself.
#
# All ranges except `expression` are defined by {Map} subclasses.
#
# Ranges (except `expression`) can be `nil` if the corresponding token is
# not present in source. For example, a hash may not have opening/closing
# braces, and so would its source map.
#
# p Parser::CurrentRuby.parse('[1 => 2]').children[0].loc
# # => <Parser::Source::Map::Collection:0x007f5492b547d8
# # @end=nil, @begin=nil,
# # @expression=#<Source::Range (string) 1...7>>
#
# The {file:doc/AST_FORMAT.md} document describes how ranges associated to source
# code tokens. For example, the entry
#
# (array (int 1) (int 2))
#
# "[1, 2]"
# ^ begin
# ^ end
# ~~~~~~ expression
#
# means that if `node` is an {Parser::AST::Node} `(array (int 1) (int 2))`,
# then `node.loc` responds to `begin`, `end` and `expression`, and
# `node.loc.begin` returns a range pointing at the opening bracket, and so on.
#
# If you want to write code polymorphic by the source map (i.e. accepting
# several subclasses of {Map}), use `respond_to?` instead of `is_a?` to
# check whether the map features the range you need. Concrete {Map}
# subclasses may not be preserved between versions, but their interfaces
# will be kept compatible.
#
# You can visualize the source maps with `ruby-parse -E` command-line tool.
#
# @example
# require 'parser/current'
#
# p Parser::CurrentRuby.parse('[1, 2]').loc
# # => #<Parser::Source::Map::Collection:0x007f14b80eccd8
# # @end=#<Source::Range (string) 5...6>,
# # @begin=#<Source::Range (string) 0...1>,
# # @expression=#<Source::Range (string) 0...6>>
#
# @!attribute [r] node
# The node that is described by this map. Nodes and maps have 1:1 correspondence.
# @return [Parser::AST::Node]
#
# @!attribute [r] expression
# @return [Range]
#
# @api public
#
class Map
attr_reader :node
attr_reader :expression
##
# @param [Range] expression
def initialize(expression)
@expression = expression
end
##
# @api private
def initialize_copy(other)
super
@node = nil
end
##
# @api private
def node=(node)
@node = node
freeze
@node
end
##
# A shortcut for `self.expression.line`.
# @return [Integer]
#
def line
@expression.line
end
alias_method :first_line, :line
##
# A shortcut for `self.expression.column`.
# @return [Integer]
#
def column
@expression.column
end
##
# A shortcut for `self.expression.last_line`.
# @return [Integer]
#
def last_line
@expression.last_line
end
##
# A shortcut for `self.expression.last_column`.
# @return [Integer]
#
def last_column
@expression.last_column
end
##
# @api private
#
def with_expression(expression_l)
with { |map| map.update_expression(expression_l) }
end
##
# Compares source maps.
# @return [Boolean]
#
def ==(other)
other.class == self.class &&
instance_variables.map do |ivar|
instance_variable_get(ivar) ==
other.send(:instance_variable_get, ivar)
end.reduce(:&)
end
##
# Converts this source map to a hash with keys corresponding to
# ranges. For example, if called on an instance of {Collection},
# which adds the `begin` and `end` ranges, the resulting hash
# will contain keys `:expression`, `:begin` and `:end`.
#
# @example
# require 'parser/current'
#
# p Parser::CurrentRuby.parse('[1, 2]').loc.to_hash
# # => {
# # :begin => #<Source::Range (string) 0...1>,
# # :end => #<Source::Range (string) 5...6>,
# # :expression => #<Source::Range (string) 0...6>
# # }
#
# @return [Hash<Symbol, Parser::Source::Range>]
#
def to_hash
instance_variables.inject({}) do |hash, ivar|
next hash if ivar.to_sym == :@node
hash[ivar[1..-1].to_sym] = instance_variable_get(ivar)
hash
end
end
protected
def with(&block)
dup.tap(&block)
end
def update_expression(expression_l)
@expression = expression_l
end
end
end
end
|