parser 2.5.1.0 → 3.0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/lib/parser.rb +4 -0
  3. data/lib/parser/all.rb +3 -0
  4. data/lib/parser/ast/processor.rb +49 -1
  5. data/lib/parser/base.rb +30 -6
  6. data/lib/parser/builders/default.rb +586 -29
  7. data/lib/parser/context.rb +17 -0
  8. data/lib/parser/current.rb +34 -7
  9. data/lib/parser/current_arg_stack.rb +46 -0
  10. data/lib/parser/diagnostic.rb +1 -1
  11. data/lib/parser/diagnostic/engine.rb +1 -2
  12. data/lib/parser/lexer.rb +23780 -0
  13. data/lib/parser/lexer/dedenter.rb +52 -49
  14. data/lib/parser/lexer/literal.rb +4 -0
  15. data/lib/parser/lexer/stack_state.rb +4 -0
  16. data/lib/parser/macruby.rb +6149 -0
  17. data/lib/parser/max_numparam_stack.rb +56 -0
  18. data/lib/parser/messages.rb +74 -44
  19. data/lib/parser/meta.rb +13 -3
  20. data/lib/parser/ruby18.rb +5667 -0
  21. data/lib/parser/ruby19.rb +6092 -0
  22. data/lib/parser/ruby20.rb +6527 -0
  23. data/lib/parser/ruby21.rb +6578 -0
  24. data/lib/parser/ruby22.rb +6613 -0
  25. data/lib/parser/ruby23.rb +6624 -0
  26. data/lib/parser/ruby24.rb +6694 -0
  27. data/lib/parser/ruby25.rb +6662 -0
  28. data/lib/parser/ruby26.rb +6676 -0
  29. data/lib/parser/ruby27.rb +7862 -0
  30. data/lib/parser/ruby28.rb +8047 -0
  31. data/lib/parser/ruby30.rb +8060 -0
  32. data/lib/parser/ruby31.rb +8075 -0
  33. data/lib/parser/rubymotion.rb +6086 -0
  34. data/lib/parser/runner.rb +36 -2
  35. data/lib/parser/runner/ruby_parse.rb +2 -2
  36. data/lib/parser/runner/ruby_rewrite.rb +2 -2
  37. data/lib/parser/source/buffer.rb +54 -29
  38. data/lib/parser/source/comment.rb +18 -5
  39. data/lib/parser/source/comment/associator.rb +34 -11
  40. data/lib/parser/source/map.rb +1 -1
  41. data/lib/parser/source/map/method_definition.rb +25 -0
  42. data/lib/parser/source/range.rb +20 -4
  43. data/lib/parser/source/tree_rewriter.rb +146 -16
  44. data/lib/parser/source/tree_rewriter/action.rb +137 -28
  45. data/lib/parser/static_environment.rb +14 -0
  46. data/lib/parser/tree_rewriter.rb +3 -3
  47. data/lib/parser/variables_stack.rb +36 -0
  48. data/lib/parser/version.rb +1 -1
  49. data/parser.gemspec +13 -21
  50. metadata +34 -98
  51. data/.gitignore +0 -32
  52. data/.travis.yml +0 -21
  53. data/.yardopts +0 -21
  54. data/CHANGELOG.md +0 -909
  55. data/CONTRIBUTING.md +0 -17
  56. data/Gemfile +0 -10
  57. data/README.md +0 -301
  58. data/Rakefile +0 -165
  59. data/doc/AST_FORMAT.md +0 -1718
  60. data/doc/CUSTOMIZATION.md +0 -37
  61. data/doc/INTERNALS.md +0 -21
  62. data/doc/css/.gitkeep +0 -0
  63. data/doc/css/common.css +0 -68
  64. data/lib/parser/lexer.rl +0 -2376
  65. data/lib/parser/macruby.y +0 -2198
  66. data/lib/parser/ruby18.y +0 -1934
  67. data/lib/parser/ruby19.y +0 -2175
  68. data/lib/parser/ruby20.y +0 -2353
  69. data/lib/parser/ruby21.y +0 -2357
  70. data/lib/parser/ruby22.y +0 -2364
  71. data/lib/parser/ruby23.y +0 -2370
  72. data/lib/parser/ruby24.y +0 -2395
  73. data/lib/parser/ruby25.y +0 -2392
  74. data/lib/parser/ruby26.y +0 -2392
  75. data/lib/parser/rubymotion.y +0 -2182
  76. data/test/bug_163/fixtures/input.rb +0 -5
  77. data/test/bug_163/fixtures/output.rb +0 -5
  78. data/test/bug_163/rewriter.rb +0 -20
  79. data/test/helper.rb +0 -52
  80. data/test/parse_helper.rb +0 -315
  81. data/test/racc_coverage_helper.rb +0 -133
  82. data/test/test_base.rb +0 -31
  83. data/test/test_current.rb +0 -27
  84. data/test/test_diagnostic.rb +0 -96
  85. data/test/test_diagnostic_engine.rb +0 -62
  86. data/test/test_encoding.rb +0 -99
  87. data/test/test_lexer.rb +0 -3537
  88. data/test/test_lexer_stack_state.rb +0 -78
  89. data/test/test_parse_helper.rb +0 -80
  90. data/test/test_parser.rb +0 -6968
  91. data/test/test_runner_rewrite.rb +0 -47
  92. data/test/test_source_buffer.rb +0 -162
  93. data/test/test_source_comment.rb +0 -36
  94. data/test/test_source_comment_associator.rb +0 -367
  95. data/test/test_source_map.rb +0 -15
  96. data/test/test_source_range.rb +0 -172
  97. data/test/test_source_rewriter.rb +0 -541
  98. data/test/test_source_rewriter_action.rb +0 -46
  99. data/test/test_source_tree_rewriter.rb +0 -173
  100. data/test/test_static_environment.rb +0 -45
  101. data/test/using_tree_rewriter/fixtures/input.rb +0 -3
  102. data/test/using_tree_rewriter/fixtures/output.rb +0 -3
  103. data/test/using_tree_rewriter/using_tree_rewriter.rb +0 -9
data/lib/parser/runner.rb CHANGED
@@ -14,9 +14,8 @@ module Parser
14
14
  end
15
15
 
16
16
  def initialize
17
- Parser::Builders::Default.modernize
18
-
19
17
  @option_parser = OptionParser.new { |opts| setup_option_parsing(opts) }
18
+ @legacy = {}
20
19
  @parser_class = nil
21
20
  @parser = nil
22
21
  @files = []
@@ -30,6 +29,7 @@ module Parser
30
29
 
31
30
  def execute(options)
32
31
  parse_options(options)
32
+ setup_builder_default
33
33
  prepare_parser
34
34
 
35
35
  process_all_input
@@ -37,6 +37,8 @@ module Parser
37
37
 
38
38
  private
39
39
 
40
+ LEGACY_MODES = %i[lambda procarg0 encoding index arg_inside_procarg0 forward_arg kwargs match_pattern].freeze
41
+
40
42
  def runner_name
41
43
  raise NotImplementedError, "implement #{self.class}##{__callee__}"
42
44
  end
@@ -106,6 +108,21 @@ module Parser
106
108
  @parser_class = Parser::Ruby26
107
109
  end
108
110
 
111
+ opts.on '--27', 'Parse as Ruby 2.7 would' do
112
+ require 'parser/ruby27'
113
+ @parser_class = Parser::Ruby27
114
+ end
115
+
116
+ opts.on '--30', 'Parse as Ruby 3.0 would' do
117
+ require 'parser/ruby30'
118
+ @parser_class = Parser::Ruby30
119
+ end
120
+
121
+ opts.on '--31', 'Parse as Ruby 3.1 would' do
122
+ require 'parser/ruby31'
123
+ @parser_class = Parser::Ruby31
124
+ end
125
+
109
126
  opts.on '--mac', 'Parse as MacRuby 0.12 would' do
110
127
  require 'parser/macruby'
111
128
  @parser_class = Parser::MacRuby
@@ -116,6 +133,17 @@ module Parser
116
133
  @parser_class = Parser::RubyMotion
117
134
  end
118
135
 
136
+ opts.on '--legacy', "Parse with all legacy modes" do
137
+ @legacy = Hash.new(true)
138
+ end
139
+
140
+ LEGACY_MODES.each do |mode|
141
+ opt_name = "--legacy-#{mode.to_s.gsub('_', '-')}"
142
+ opts.on opt_name, "Parse with legacy mode for emit_#{mode}" do
143
+ @legacy[mode] = true
144
+ end
145
+ end
146
+
119
147
  opts.on '-w', '--warnings', 'Enable warnings' do |w|
120
148
  @warnings = w
121
149
  end
@@ -154,6 +182,12 @@ module Parser
154
182
  end
155
183
  end
156
184
 
185
+ def setup_builder_default
186
+ LEGACY_MODES.each do |mode|
187
+ Parser::Builders::Default.send(:"emit_#{mode}=", !@legacy[mode])
188
+ end
189
+ end
190
+
157
191
  def prepare_parser
158
192
  @parser = @parser_class.new
159
193
 
@@ -123,7 +123,7 @@ module Parser
123
123
  opts.on '--emit-ruby', 'Emit S-expressions as valid Ruby code' do
124
124
  @emit_ruby = true
125
125
  end
126
-
126
+
127
127
  opts.on '--emit-json', 'Emit S-expressions as valid JSON' do
128
128
  @emit_json = true
129
129
  end
@@ -146,7 +146,7 @@ module Parser
146
146
  if @emit_ruby
147
147
  puts ast.inspect
148
148
  elsif @emit_json
149
- puts JSON.generate(ast.to_sexp_array)
149
+ puts(ast ? JSON.generate(ast.to_sexp_array) : nil)
150
150
  else
151
151
  puts ast.to_s
152
152
  end
@@ -55,8 +55,8 @@ module Parser
55
55
  new_source = rewriter.rewrite(buffer, ast)
56
56
 
57
57
  new_buffer = Source::Buffer.new(initial_buffer.name +
58
- '|after ' + rewriter_class.name)
59
- new_buffer.source = new_source
58
+ '|after ' + rewriter_class.name,
59
+ source: new_source)
60
60
 
61
61
  @parser.reset
62
62
  new_ast = @parser.parse(new_buffer)
@@ -46,7 +46,7 @@ module Parser
46
46
  # magic encoding comment or UTF-8 BOM. `string` can be in any encoding.
47
47
  #
48
48
  # @param [String] string
49
- # @return [String|nil] encoding name, if recognized
49
+ # @return [String, nil] encoding name, if recognized
50
50
  #
51
51
  def self.recognize_encoding(string)
52
52
  return if string.empty?
@@ -102,7 +102,7 @@ module Parser
102
102
  end
103
103
  end
104
104
 
105
- def initialize(name, first_line = 1)
105
+ def initialize(name, first_line = 1, source: nil)
106
106
  @name = name.to_s
107
107
  @source = nil
108
108
  @first_line = first_line
@@ -114,8 +114,9 @@ module Parser
114
114
  @slice_source = nil
115
115
 
116
116
  # Cache for fast lookup
117
- @line_for_position = {}
118
- @column_for_position = {}
117
+ @line_index_for_position = {}
118
+
119
+ self.source = source if source
119
120
  end
120
121
 
121
122
  ##
@@ -205,9 +206,10 @@ module Parser
205
206
  # @return [[Integer, Integer]] `[line, column]`
206
207
  #
207
208
  def decompose_position(position)
208
- line_no, line_begin = line_for(position)
209
+ line_index = line_index_for_position(position)
210
+ line_begin = line_begins[line_index]
209
211
 
210
- [ @first_line + line_no, position - line_begin ]
212
+ [ @first_line + line_index , position - line_begin ]
211
213
  end
212
214
 
213
215
  ##
@@ -218,10 +220,7 @@ module Parser
218
220
  # @api private
219
221
  #
220
222
  def line_for_position(position)
221
- @line_for_position[position] ||= begin
222
- line_no, _ = line_for(position)
223
- @first_line + line_no
224
- end
223
+ line_index_for_position(position) + @first_line
225
224
  end
226
225
 
227
226
  ##
@@ -232,10 +231,8 @@ module Parser
232
231
  # @api private
233
232
  #
234
233
  def column_for_position(position)
235
- @column_for_position[position] ||= begin
236
- _, line_begin = line_for(position)
237
- position - line_begin
238
- end
234
+ line_index = line_index_for_position(position)
235
+ position - line_begins[line_index]
239
236
  end
240
237
 
241
238
  ##
@@ -276,15 +273,13 @@ module Parser
276
273
  # @raise [IndexError] if `lineno` is out of bounds
277
274
  #
278
275
  def line_range(lineno)
279
- index = lineno - @first_line + 1
280
- if index <= 0 || index > line_begins.size
276
+ index = lineno - @first_line
277
+ if index < 0 || index + 1 >= line_begins.size
281
278
  raise IndexError, 'Parser::Source::Buffer: range for line ' \
282
279
  "#{lineno} requested, valid line numbers are #{@first_line}.." \
283
- "#{@first_line + line_begins.size - 1}"
284
- elsif index == line_begins.size
285
- Range.new(self, line_begins[-index][1], @source.size)
280
+ "#{@first_line + line_begins.size - 2}"
286
281
  else
287
- Range.new(self, line_begins[-index][1], line_begins[-index - 1][1] - 1)
282
+ Range.new(self, line_begins[index], line_begins[index + 1] - 1)
288
283
  end
289
284
  end
290
285
 
@@ -301,27 +296,57 @@ module Parser
301
296
  # @return [Integer]
302
297
  #
303
298
  def last_line
304
- line_begins.size + @first_line - 1
299
+ line_begins.size + @first_line - 2
300
+ end
301
+
302
+ # :nodoc:
303
+ def freeze
304
+ source_lines; line_begins; source_range # build cache
305
+ super
306
+ end
307
+
308
+ # :nodoc:
309
+ def inspect
310
+ "#<#{self.class} #{name}>"
305
311
  end
306
312
 
307
313
  private
308
314
 
315
+ # @returns [0, line_begin_of_line_1, ..., source.size + 1]
309
316
  def line_begins
310
- unless @line_begins
311
- @line_begins, index = [ [ 0, 0 ] ], 0
312
-
317
+ @line_begins ||= begin
318
+ begins = [0]
319
+ index = 0
313
320
  while index = @source.index("\n".freeze, index)
314
321
  index += 1
315
- @line_begins.unshift [ @line_begins.length, index ]
322
+ begins << index
316
323
  end
324
+ begins << @source.size + 1
325
+ begins
317
326
  end
327
+ end
318
328
 
319
- @line_begins
329
+ # @returns 0-based line index of position
330
+ def line_index_for_position(position)
331
+ @line_index_for_position[position] || begin
332
+ index = bsearch(line_begins, position) - 1
333
+ @line_index_for_position[position] = index unless @line_index_for_position.frozen?
334
+ index
335
+ end
320
336
  end
321
337
 
322
- def line_for(position)
323
- line_begins.bsearch do |line, line_begin|
324
- line_begin <= position
338
+ if Array.method_defined?(:bsearch_index) # RUBY_VERSION >= 2.3
339
+ def bsearch(line_begins, position)
340
+ line_begins.bsearch_index do |line_begin|
341
+ position < line_begin
342
+ end || line_begins.size - 1 # || only for out of bound values
343
+ end
344
+ else
345
+ def bsearch(line_begins, position)
346
+ @line_range ||= 0...line_begins.size
347
+ @line_range.bsearch do |i|
348
+ position < line_begins[i]
349
+ end || line_begins.size - 1 # || only for out of bound values
325
350
  end
326
351
  end
327
352
  end
@@ -10,7 +10,7 @@ module Parser
10
10
  # @return [String]
11
11
  #
12
12
  # @!attribute [r] location
13
- # @return [Parser::Source::Map]
13
+ # @return [Parser::Source::Range]
14
14
  #
15
15
  # @api public
16
16
  #
@@ -24,8 +24,8 @@ module Parser
24
24
  # Associate `comments` with `ast` nodes by their corresponding node.
25
25
  #
26
26
  # @param [Parser::AST::Node] ast
27
- # @param [Array(Comment)] comments
28
- # @return [Hash(Parser::AST::Node, Array(Comment))]
27
+ # @param [Array<Comment>] comments
28
+ # @return [Hash<Parser::AST::Node, Array<Comment>>]
29
29
  # @see Parser::Source::Comment::Associator#associate
30
30
  # @deprecated Use {associate_locations}.
31
31
  #
@@ -39,8 +39,8 @@ module Parser
39
39
  # source.
40
40
  #
41
41
  # @param [Parser::AST::Node] ast
42
- # @param [Array(Comment)] comments
43
- # @return [Hash(Parser::Source::Map, Array(Comment))]
42
+ # @param [Array<Comment>] comments
43
+ # @return [Hash<Parser::Source::Map, Array<Comment>>]
44
44
  # @see Parser::Source::Comment::Associator#associate_locations
45
45
  #
46
46
  def self.associate_locations(ast, comments)
@@ -48,6 +48,19 @@ module Parser
48
48
  associator.associate_locations
49
49
  end
50
50
 
51
+ ##
52
+ # Associate `comments` with `ast` nodes using identity.
53
+ #
54
+ # @param [Parser::AST::Node] ast
55
+ # @param [Array<Comment>] comments
56
+ # @return [Hash<Parser::Source::Node, Array<Comment>>]
57
+ # @see Parser::Source::Comment::Associator#associate_by_identity
58
+ #
59
+ def self.associate_by_identity(ast, comments)
60
+ associator = Associator.new(ast, comments)
61
+ associator.associate_by_identity
62
+ end
63
+
51
64
  ##
52
65
  # @param [Parser::Source::Range] range
53
66
  #
@@ -47,7 +47,7 @@ module Parser
47
47
 
48
48
  ##
49
49
  # @param [Parser::AST::Node] ast
50
- # @param [Array(Parser::Source::Comment)] comments
50
+ # @param [Array<Parser::Source::Comment>] comments
51
51
  def initialize(ast, comments)
52
52
  @ast = ast
53
53
  @comments = comments
@@ -84,12 +84,24 @@ module Parser
84
84
  #
85
85
  # Note that {associate} produces unexpected result for nodes which are
86
86
  # equal but have distinct locations; comments for these nodes are merged.
87
+ # You may prefer using {associate_by_identity} or {associate_locations}.
87
88
  #
88
- # @return [Hash(Parser::AST::Node, Array(Parser::Source::Comment))]
89
+ # @return [Hash<Parser::AST::Node, Array<Parser::Source::Comment>>]
89
90
  # @deprecated Use {associate_locations}.
90
91
  #
91
92
  def associate
92
- @map_using_locations = false
93
+ @map_using = :eql
94
+ do_associate
95
+ end
96
+
97
+ ##
98
+ # Same as {associate}, but compares by identity, thus producing an unambiguous
99
+ # result even in presence of equal nodes.
100
+ #
101
+ # @return [Hash<Parser::Source::Node, Array<Parser::Source::Comment>>]
102
+ #
103
+ def associate_locations
104
+ @map_using = :location
93
105
  do_associate
94
106
  end
95
107
 
@@ -98,17 +110,31 @@ module Parser
98
110
  # the hash key, thus producing an unambiguous result even in presence
99
111
  # of equal nodes.
100
112
  #
101
- # @return [Hash(Parser::Source::Map, Array(Parser::Source::Comment))]
113
+ # @return [Hash<Parser::Source::Map, Array<Parser::Source::Comment>>]
102
114
  #
103
- def associate_locations
104
- @map_using_locations = true
115
+ def associate_by_identity
116
+ @map_using = :identity
105
117
  do_associate
106
118
  end
107
119
 
108
120
  private
109
121
 
122
+ POSTFIX_TYPES = Set[:if, :while, :while_post, :until, :until_post, :masgn].freeze
123
+ def children_in_source_order(node)
124
+ if POSTFIX_TYPES.include?(node.type)
125
+ # All these types have either nodes with expressions, or `nil`
126
+ # so a compact will do, but they need to be sorted.
127
+ node.children.compact.sort_by { |child| child.loc.expression.begin_pos }
128
+ else
129
+ node.children.select do |child|
130
+ child.is_a?(AST::Node) && child.loc && child.loc.expression
131
+ end
132
+ end
133
+ end
134
+
110
135
  def do_associate
111
136
  @mapping = Hash.new { |h, k| h[k] = [] }
137
+ @mapping.compare_by_identity if @map_using == :identity
112
138
  @comment_num = -1
113
139
  advance_comment
114
140
 
@@ -131,10 +157,7 @@ module Parser
131
157
  node_loc = node.location
132
158
  if @current_comment.location.line <= node_loc.last_line ||
133
159
  node_loc.is_a?(Map::Heredoc)
134
- node.children.each do |child|
135
- next unless child.is_a?(AST::Node) && child.loc && child.loc.expression
136
- visit(child)
137
- end
160
+ children_in_source_order(node).each { |child| visit(child) }
138
161
 
139
162
  process_trailing_comments(node)
140
163
  end
@@ -181,7 +204,7 @@ module Parser
181
204
  end
182
205
 
183
206
  def associate_and_advance_comment(node)
184
- key = @map_using_locations ? node.location : node
207
+ key = @map_using == :location ? node.location : node
185
208
  @mapping[key] << @current_comment
186
209
  advance_comment
187
210
  end
@@ -161,7 +161,7 @@ module Parser
161
161
  # # :expression => #<Source::Range (string) 0...6>
162
162
  # # }
163
163
  #
164
- # @return [Hash(Symbol, Parser::Source::Range)]
164
+ # @return [Hash<Symbol, Parser::Source::Range>]
165
165
  #
166
166
  def to_hash
167
167
  instance_variables.inject({}) do |hash, ivar|
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Parser
4
+ module Source
5
+
6
+ class Map::MethodDefinition < Map
7
+ attr_reader :keyword
8
+ attr_reader :operator
9
+ attr_reader :name
10
+ attr_reader :end
11
+ attr_reader :assignment
12
+
13
+ def initialize(keyword_l, operator_l, name_l, end_l, assignment_l, body_l)
14
+ @keyword = keyword_l
15
+ @operator = operator_l
16
+ @name = name_l
17
+ @end = end_l
18
+ @assignment = assignment_l
19
+
20
+ super(@keyword.join(end_l || body_l))
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -13,7 +13,7 @@ module Parser
13
13
  # ^^
14
14
  #
15
15
  # @!attribute [r] source_buffer
16
- # @return [Parser::Diagnostic::Engine]
16
+ # @return [Parser::Source::Buffer]
17
17
  #
18
18
  # @!attribute [r] begin_pos
19
19
  # @return [Integer] index of the first character in the range
@@ -112,11 +112,11 @@ module Parser
112
112
  # @raise RangeError
113
113
  #
114
114
  def column_range
115
- if self.begin.line != self.end.line
115
+ if line != last_line
116
116
  raise RangeError, "#{self.inspect} spans more than one line"
117
117
  end
118
118
 
119
- self.begin.column...self.end.column
119
+ column...last_column
120
120
  end
121
121
 
122
122
  ##
@@ -143,12 +143,19 @@ module Parser
143
143
  end
144
144
 
145
145
  ##
146
- # @return [Array(Integer)] a set of character indexes contained in this range.
146
+ # @return [Array<Integer>] a set of character indexes contained in this range.
147
147
  #
148
148
  def to_a
149
149
  (@begin_pos...@end_pos).to_a
150
150
  end
151
151
 
152
+ ##
153
+ # @return [Range] a Ruby range with the same `begin_pos` and `end_pos`
154
+ #
155
+ def to_range
156
+ self.begin_pos...self.end_pos
157
+ end
158
+
152
159
  ##
153
160
  # Composes a GNU/Clang-style string representation of the beginning of this
154
161
  # range.
@@ -298,6 +305,15 @@ module Parser
298
305
  (@end_pos <=> other.end_pos)
299
306
  end
300
307
 
308
+ alias_method :eql?, :==
309
+
310
+ ##
311
+ # Support for Ranges be used in as Hash indices and in Sets.
312
+ #
313
+ def hash
314
+ [@source_buffer, @begin_pos, @end_pos].hash
315
+ end
316
+
301
317
  ##
302
318
  # @return [String] a human-readable representation of this range.
303
319
  #