File: map.rb

package info (click to toggle)
ruby-whitequark-parser 3.3.4.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,828 kB
  • sloc: yacc: 40,699; ruby: 20,395; makefile: 12; sh: 8
file content (186 lines) | stat: -rw-r--r-- 5,321 bytes parent folder | download | duplicates (3)
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