# 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 (`< 2]').children[0].loc # # => > # # 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 # # => #, # # @begin=#, # # @expression=#> # # @!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 => #, # # :end => #, # # :expression => # # # } # # @return [Hash] # 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