parser 2.6.5.0 → 2.7.2.0
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.
- checksums.yaml +4 -4
- data/lib/parser.rb +4 -1
- data/lib/parser/all.rb +1 -0
- data/lib/parser/ast/processor.rb +21 -0
- data/lib/parser/base.rb +25 -5
- data/lib/parser/builders/default.rb +394 -24
- data/lib/parser/context.rb +5 -0
- data/lib/parser/current.rb +16 -7
- data/lib/parser/current_arg_stack.rb +43 -0
- data/lib/parser/diagnostic.rb +1 -1
- data/lib/parser/diagnostic/engine.rb +1 -2
- data/lib/parser/lexer.rb +23770 -0
- data/lib/parser/lexer/dedenter.rb +52 -49
- data/lib/parser/macruby.rb +6149 -0
- data/lib/parser/{lexer/max_numparam_stack.rb → max_numparam_stack.rb} +10 -4
- data/lib/parser/messages.rb +52 -29
- data/lib/parser/meta.rb +10 -5
- data/lib/parser/ruby18.rb +5663 -0
- data/lib/parser/ruby19.rb +6092 -0
- data/lib/parser/ruby20.rb +6527 -0
- data/lib/parser/ruby21.rb +6578 -0
- data/lib/parser/ruby22.rb +6613 -0
- data/lib/parser/ruby23.rb +6624 -0
- data/lib/parser/ruby24.rb +6694 -0
- data/lib/parser/ruby25.rb +6662 -0
- data/lib/parser/ruby26.rb +6676 -0
- data/lib/parser/ruby27.rb +7803 -0
- data/lib/parser/ruby28.rb +8047 -0
- data/lib/parser/ruby30.rb +8052 -0
- data/lib/parser/rubymotion.rb +6086 -0
- data/lib/parser/runner.rb +26 -2
- data/lib/parser/runner/ruby_rewrite.rb +2 -2
- data/lib/parser/source/buffer.rb +3 -1
- data/lib/parser/source/comment.rb +1 -1
- data/lib/parser/source/comment/associator.rb +14 -4
- data/lib/parser/source/map/method_definition.rb +25 -0
- data/lib/parser/source/range.rb +19 -3
- data/lib/parser/source/tree_rewriter.rb +115 -12
- data/lib/parser/source/tree_rewriter/action.rb +137 -28
- data/lib/parser/static_environment.rb +10 -0
- data/lib/parser/tree_rewriter.rb +1 -2
- data/lib/parser/variables_stack.rb +32 -0
- data/lib/parser/version.rb +1 -1
- data/parser.gemspec +10 -18
- metadata +22 -99
- data/.gitignore +0 -33
- data/.travis.yml +0 -45
- data/.yardopts +0 -21
- data/CHANGELOG.md +0 -997
- data/CONTRIBUTING.md +0 -17
- data/Gemfile +0 -10
- data/LICENSE.txt +0 -25
- data/README.md +0 -301
- data/Rakefile +0 -166
- data/ci/run_rubocop_specs +0 -14
- data/doc/AST_FORMAT.md +0 -1816
- data/doc/CUSTOMIZATION.md +0 -37
- data/doc/INTERNALS.md +0 -21
- data/doc/css/.gitkeep +0 -0
- data/doc/css/common.css +0 -68
- data/lib/parser/lexer.rl +0 -2533
- data/lib/parser/macruby.y +0 -2198
- data/lib/parser/ruby18.y +0 -1934
- data/lib/parser/ruby19.y +0 -2175
- data/lib/parser/ruby20.y +0 -2353
- data/lib/parser/ruby21.y +0 -2357
- data/lib/parser/ruby22.y +0 -2364
- data/lib/parser/ruby23.y +0 -2370
- data/lib/parser/ruby24.y +0 -2408
- data/lib/parser/ruby25.y +0 -2405
- data/lib/parser/ruby26.y +0 -2413
- data/lib/parser/ruby27.y +0 -2470
- data/lib/parser/rubymotion.y +0 -2182
- data/test/bug_163/fixtures/input.rb +0 -5
- data/test/bug_163/fixtures/output.rb +0 -5
- data/test/bug_163/rewriter.rb +0 -20
- data/test/helper.rb +0 -59
- data/test/parse_helper.rb +0 -316
- data/test/racc_coverage_helper.rb +0 -133
- data/test/test_base.rb +0 -31
- data/test/test_current.rb +0 -29
- data/test/test_diagnostic.rb +0 -96
- data/test/test_diagnostic_engine.rb +0 -62
- data/test/test_encoding.rb +0 -99
- data/test/test_lexer.rb +0 -3667
- data/test/test_lexer_stack_state.rb +0 -78
- data/test/test_parse_helper.rb +0 -80
- data/test/test_parser.rb +0 -7644
- data/test/test_runner_parse.rb +0 -35
- data/test/test_runner_rewrite.rb +0 -47
- data/test/test_source_buffer.rb +0 -162
- data/test/test_source_comment.rb +0 -36
- data/test/test_source_comment_associator.rb +0 -367
- data/test/test_source_map.rb +0 -15
- data/test/test_source_range.rb +0 -172
- data/test/test_source_rewriter.rb +0 -541
- data/test/test_source_rewriter_action.rb +0 -46
- data/test/test_source_tree_rewriter.rb +0 -173
- data/test/test_static_environment.rb +0 -45
- data/test/using_tree_rewriter/fixtures/input.rb +0 -3
- data/test/using_tree_rewriter/fixtures/output.rb +0 -3
- 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].freeze
|
41
|
+
|
40
42
|
def runner_name
|
41
43
|
raise NotImplementedError, "implement #{self.class}##{__callee__}"
|
42
44
|
end
|
@@ -111,6 +113,11 @@ module Parser
|
|
111
113
|
@parser_class = Parser::Ruby27
|
112
114
|
end
|
113
115
|
|
116
|
+
opts.on '--30', 'Parse as Ruby 3.0 would' do
|
117
|
+
require 'parser/ruby30'
|
118
|
+
@parser_class = Parser::Ruby30
|
119
|
+
end
|
120
|
+
|
114
121
|
opts.on '--mac', 'Parse as MacRuby 0.12 would' do
|
115
122
|
require 'parser/macruby'
|
116
123
|
@parser_class = Parser::MacRuby
|
@@ -121,6 +128,17 @@ module Parser
|
|
121
128
|
@parser_class = Parser::RubyMotion
|
122
129
|
end
|
123
130
|
|
131
|
+
opts.on '--legacy', "Parse with all legacy modes" do
|
132
|
+
@legacy = Hash.new(true)
|
133
|
+
end
|
134
|
+
|
135
|
+
LEGACY_MODES.each do |mode|
|
136
|
+
opt_name = "--legacy-#{mode.to_s.gsub('_', '-')}"
|
137
|
+
opts.on opt_name, "Parse with legacy mode for emit_#{mode}" do
|
138
|
+
@legacy[mode] = true
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
124
142
|
opts.on '-w', '--warnings', 'Enable warnings' do |w|
|
125
143
|
@warnings = w
|
126
144
|
end
|
@@ -159,6 +177,12 @@ module Parser
|
|
159
177
|
end
|
160
178
|
end
|
161
179
|
|
180
|
+
def setup_builder_default
|
181
|
+
LEGACY_MODES.each do |mode|
|
182
|
+
Parser::Builders::Default.send(:"emit_#{mode}=", !@legacy[mode])
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
162
186
|
def prepare_parser
|
163
187
|
@parser = @parser_class.new
|
164
188
|
|
@@ -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
|
-
|
58
|
+
'|after ' + rewriter_class.name,
|
59
|
+
source: new_source)
|
60
60
|
|
61
61
|
@parser.reset
|
62
62
|
new_ast = @parser.parse(new_buffer)
|
data/lib/parser/source/buffer.rb
CHANGED
@@ -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
|
@@ -116,6 +116,8 @@ module Parser
|
|
116
116
|
# Cache for fast lookup
|
117
117
|
@line_for_position = {}
|
118
118
|
@column_for_position = {}
|
119
|
+
|
120
|
+
self.source = source if source
|
119
121
|
end
|
120
122
|
|
121
123
|
##
|
@@ -107,6 +107,19 @@ module Parser
|
|
107
107
|
|
108
108
|
private
|
109
109
|
|
110
|
+
POSTFIX_TYPES = Set[:if, :while, :while_post, :until, :until_post, :masgn].freeze
|
111
|
+
def children_in_source_order(node)
|
112
|
+
if POSTFIX_TYPES.include?(node.type)
|
113
|
+
# All these types have either nodes with expressions, or `nil`
|
114
|
+
# so a compact will do, but they need to be sorted.
|
115
|
+
node.children.compact.sort_by { |child| child.loc.expression.begin_pos }
|
116
|
+
else
|
117
|
+
node.children.select do |child|
|
118
|
+
child.is_a?(AST::Node) && child.loc && child.loc.expression
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
110
123
|
def do_associate
|
111
124
|
@mapping = Hash.new { |h, k| h[k] = [] }
|
112
125
|
@comment_num = -1
|
@@ -131,10 +144,7 @@ module Parser
|
|
131
144
|
node_loc = node.location
|
132
145
|
if @current_comment.location.line <= node_loc.last_line ||
|
133
146
|
node_loc.is_a?(Map::Heredoc)
|
134
|
-
node.
|
135
|
-
next unless child.is_a?(AST::Node) && child.loc && child.loc.expression
|
136
|
-
visit(child)
|
137
|
-
end
|
147
|
+
children_in_source_order(node).each { |child| visit(child) }
|
138
148
|
|
139
149
|
process_trailing_comments(node)
|
140
150
|
end
|
@@ -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
|
data/lib/parser/source/range.rb
CHANGED
@@ -13,7 +13,7 @@ module Parser
|
|
13
13
|
# ^^
|
14
14
|
#
|
15
15
|
# @!attribute [r] source_buffer
|
16
|
-
# @return [Parser::
|
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
|
115
|
+
if line != last_line
|
116
116
|
raise RangeError, "#{self.inspect} spans more than one line"
|
117
117
|
end
|
118
118
|
|
119
|
-
|
119
|
+
column...last_column
|
120
120
|
end
|
121
121
|
|
122
122
|
##
|
@@ -149,6 +149,13 @@ module Parser
|
|
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
|
#
|
@@ -117,6 +117,71 @@ module Parser
|
|
117
117
|
@action_root = TreeRewriter::Action.new(all_encompassing_range, @enforcer)
|
118
118
|
end
|
119
119
|
|
120
|
+
##
|
121
|
+
# Returns true iff no (non trivial) update has been recorded
|
122
|
+
#
|
123
|
+
# @return [Boolean]
|
124
|
+
#
|
125
|
+
def empty?
|
126
|
+
@action_root.empty?
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# Merges the updates of argument with the receiver.
|
131
|
+
# Policies of the receiver are used.
|
132
|
+
# This action is atomic in that it won't change the receiver
|
133
|
+
# unless it succeeds.
|
134
|
+
#
|
135
|
+
# @param [Rewriter] with
|
136
|
+
# @return [Rewriter] self
|
137
|
+
# @raise [ClobberingError] when clobbering is detected
|
138
|
+
#
|
139
|
+
def merge!(with)
|
140
|
+
raise 'TreeRewriter are not for the same source_buffer' unless
|
141
|
+
source_buffer == with.source_buffer
|
142
|
+
|
143
|
+
@action_root = @action_root.combine(with.action_root)
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
# Returns a new rewriter that consists of the updates of the received
|
149
|
+
# and the given argument. Policies of the receiver are used.
|
150
|
+
#
|
151
|
+
# @param [Rewriter] with
|
152
|
+
# @return [Rewriter] merge of receiver and argument
|
153
|
+
# @raise [ClobberingError] when clobbering is detected
|
154
|
+
#
|
155
|
+
def merge(with)
|
156
|
+
dup.merge!(with)
|
157
|
+
end
|
158
|
+
|
159
|
+
##
|
160
|
+
# For special cases where one needs to merge a rewriter attached to a different source_buffer
|
161
|
+
# or that needs to be offset. Policies of the receiver are used.
|
162
|
+
#
|
163
|
+
# @param [TreeRewriter] rewriter from different source_buffer
|
164
|
+
# @param [Integer] offset
|
165
|
+
# @return [Rewriter] self
|
166
|
+
# @raise [IndexError] if action ranges (once offset) don't fit the current buffer
|
167
|
+
#
|
168
|
+
def import!(foreign_rewriter, offset: 0)
|
169
|
+
return self if foreign_rewriter.empty?
|
170
|
+
|
171
|
+
contracted = foreign_rewriter.action_root.contract
|
172
|
+
merge_effective_range = ::Parser::Source::Range.new(
|
173
|
+
@source_buffer,
|
174
|
+
contracted.range.begin_pos + offset,
|
175
|
+
contracted.range.end_pos + offset,
|
176
|
+
)
|
177
|
+
check_range_validity(merge_effective_range)
|
178
|
+
|
179
|
+
merge_with = contracted.moved(@source_buffer, offset)
|
180
|
+
|
181
|
+
@action_root = @action_root.combine(merge_with)
|
182
|
+
self
|
183
|
+
end
|
184
|
+
|
120
185
|
##
|
121
186
|
# Replaces the code of the source range `range` with `content`.
|
122
187
|
#
|
@@ -185,28 +250,62 @@ module Parser
|
|
185
250
|
# @return [String]
|
186
251
|
#
|
187
252
|
def process
|
188
|
-
source = @source_buffer.source
|
189
|
-
adjustment = 0
|
253
|
+
source = @source_buffer.source
|
190
254
|
|
255
|
+
chunks = []
|
256
|
+
last_end = 0
|
191
257
|
@action_root.ordered_replacements.each do |range, replacement|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
source[begin_pos...end_pos] = replacement
|
196
|
-
|
197
|
-
adjustment += replacement.length - range.length
|
258
|
+
chunks << source[last_end...range.begin_pos] << replacement
|
259
|
+
last_end = range.end_pos
|
198
260
|
end
|
261
|
+
chunks << source[last_end...source.length]
|
262
|
+
chunks.join
|
263
|
+
end
|
199
264
|
|
200
|
-
|
265
|
+
##
|
266
|
+
# Returns a representation of the rewriter as an ordered list of replacements.
|
267
|
+
#
|
268
|
+
# rewriter.as_replacements # => [ [1...1, '('],
|
269
|
+
# [2...4, 'foo'],
|
270
|
+
# [5...6, ''],
|
271
|
+
# [6...6, '!'],
|
272
|
+
# [10...10, ')'],
|
273
|
+
# ]
|
274
|
+
#
|
275
|
+
# This representation is sufficient to recreate the result of `process` but it is
|
276
|
+
# not sufficient to recreate completely the rewriter for further merging/actions.
|
277
|
+
# See `as_nested_actions`
|
278
|
+
#
|
279
|
+
# @return [Array<Range, String>] an ordered list of pairs of range & replacement
|
280
|
+
#
|
281
|
+
def as_replacements
|
282
|
+
@action_root.ordered_replacements
|
283
|
+
end
|
284
|
+
|
285
|
+
##
|
286
|
+
# Returns a representation of the rewriter as nested insertions (:wrap) and replacements.
|
287
|
+
#
|
288
|
+
# rewriter.as_actions # =>[ [:wrap, 1...10, '(', ')'],
|
289
|
+
# [:wrap, 2...6, '', '!'], # aka "insert_after"
|
290
|
+
# [:replace, 2...4, 'foo'],
|
291
|
+
# [:replace, 5...6, ''], # aka "removal"
|
292
|
+
# ],
|
293
|
+
#
|
294
|
+
# Contrary to `as_replacements`, this representation is sufficient to recreate exactly
|
295
|
+
# the rewriter.
|
296
|
+
#
|
297
|
+
# @return [Array<(Symbol, Range, String{, String})>]
|
298
|
+
#
|
299
|
+
def as_nested_actions
|
300
|
+
@action_root.nested_actions
|
201
301
|
end
|
202
302
|
|
203
303
|
##
|
204
304
|
# Provides a protected block where a sequence of multiple rewrite actions
|
205
305
|
# are handled atomically. If any of the actions failed by clobbering,
|
206
|
-
# all the actions are rolled back.
|
306
|
+
# all the actions are rolled back. Transactions can be nested.
|
207
307
|
#
|
208
308
|
# @raise [RuntimeError] when no block is passed
|
209
|
-
# @raise [RuntimeError] when already in a transaction
|
210
309
|
#
|
211
310
|
def transaction
|
212
311
|
unless block_given?
|
@@ -256,6 +355,10 @@ module Parser
|
|
256
355
|
|
257
356
|
extend Deprecation
|
258
357
|
|
358
|
+
protected
|
359
|
+
|
360
|
+
attr_reader :action_root
|
361
|
+
|
259
362
|
private
|
260
363
|
|
261
364
|
ACTIONS = %i[accept warn raise].freeze
|
@@ -273,7 +376,7 @@ module Parser
|
|
273
376
|
|
274
377
|
def check_range_validity(range)
|
275
378
|
if range.begin_pos < 0 || range.end_pos > @source_buffer.source.size
|
276
|
-
raise IndexError, "The range #{range} is outside the bounds of the source"
|
379
|
+
raise IndexError, "The range #{range.to_range} is outside the bounds of the source"
|
277
380
|
end
|
278
381
|
range
|
279
382
|
end
|
@@ -7,7 +7,7 @@ module Parser
|
|
7
7
|
#
|
8
8
|
# Actions are arranged in a tree and get combined so that:
|
9
9
|
# children are strictly contained by their parent
|
10
|
-
# sibblings all disjoint from one another
|
10
|
+
# sibblings all disjoint from one another and ordered
|
11
11
|
# only actions with replacement==nil may have children
|
12
12
|
#
|
13
13
|
class TreeRewriter::Action
|
@@ -25,30 +25,77 @@ module Parser
|
|
25
25
|
freeze
|
26
26
|
end
|
27
27
|
|
28
|
-
# Assumes action.children.empty?
|
29
28
|
def combine(action)
|
30
|
-
return self
|
29
|
+
return self if action.empty? # Ignore empty action
|
31
30
|
do_combine(action)
|
32
31
|
end
|
33
32
|
|
33
|
+
def empty?
|
34
|
+
@insert_before.empty? &&
|
35
|
+
@insert_after.empty? &&
|
36
|
+
@children.empty? &&
|
37
|
+
(@replacement == nil || (@replacement.empty? && @range.empty?))
|
38
|
+
end
|
39
|
+
|
34
40
|
def ordered_replacements
|
35
41
|
reps = []
|
36
42
|
reps << [@range.begin, @insert_before] unless @insert_before.empty?
|
37
43
|
reps << [@range, @replacement] if @replacement
|
38
|
-
reps.concat(@children.
|
44
|
+
reps.concat(@children.flat_map(&:ordered_replacements))
|
39
45
|
reps << [@range.end, @insert_after] unless @insert_after.empty?
|
40
46
|
reps
|
41
47
|
end
|
42
48
|
|
49
|
+
def nested_actions
|
50
|
+
actions = []
|
51
|
+
actions << [:wrap, @range, @insert_before, @insert_after] if !@insert_before.empty? ||
|
52
|
+
!@insert_after.empty?
|
53
|
+
actions << [:replace, @range, @replacement] if @replacement
|
54
|
+
actions.concat(@children.flat_map(&:nested_actions))
|
55
|
+
end
|
56
|
+
|
43
57
|
def insertion?
|
44
58
|
!insert_before.empty? || !insert_after.empty? || (replacement && !replacement.empty?)
|
45
59
|
end
|
46
60
|
|
61
|
+
##
|
62
|
+
# A root action has its range set to the whole source range, even
|
63
|
+
# though it typically do not act on that range.
|
64
|
+
# This method returns the action as if it was a child action with
|
65
|
+
# its range contracted.
|
66
|
+
# @return [Action]
|
67
|
+
def contract
|
68
|
+
raise 'Empty actions can not be contracted' if empty?
|
69
|
+
return self if insertion?
|
70
|
+
range = @range.with(
|
71
|
+
begin_pos: children.first.range.begin_pos,
|
72
|
+
end_pos: children.last.range.end_pos,
|
73
|
+
)
|
74
|
+
with(range: range)
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# @return [Action] that has been moved to the given source_buffer and with the given offset
|
79
|
+
# No check is done on validity of resulting range.
|
80
|
+
def moved(source_buffer, offset)
|
81
|
+
moved_range = ::Parser::Source::Range.new(
|
82
|
+
source_buffer,
|
83
|
+
@range.begin_pos + offset,
|
84
|
+
@range.end_pos + offset
|
85
|
+
)
|
86
|
+
with(
|
87
|
+
range: moved_range,
|
88
|
+
children: children.map { |child| child.moved(source_buffer, offset) }
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
47
92
|
protected
|
48
93
|
|
49
|
-
|
94
|
+
attr_reader :children
|
95
|
+
|
96
|
+
def with(range: @range, enforcer: @enforcer, children: @children, insert_before: @insert_before, replacement: @replacement, insert_after: @insert_after)
|
50
97
|
children = swallow(children) if replacement
|
51
|
-
self.class.new(range,
|
98
|
+
self.class.new(range, enforcer, children: children, insert_before: insert_before, replacement: replacement, insert_after: insert_after)
|
52
99
|
end
|
53
100
|
|
54
101
|
# Assumes range.contains?(action.range) && action.children.empty?
|
@@ -56,24 +103,32 @@ module Parser
|
|
56
103
|
if action.range == @range
|
57
104
|
merge(action)
|
58
105
|
else
|
59
|
-
|
106
|
+
place_in_hierarchy(action)
|
60
107
|
end
|
61
108
|
end
|
62
109
|
|
63
|
-
def
|
64
|
-
family =
|
110
|
+
def place_in_hierarchy(action)
|
111
|
+
family = analyse_hierarchy(action)
|
65
112
|
|
66
113
|
if family[:fusible]
|
67
|
-
fuse_deletions(action, family[:fusible], [*family[:
|
114
|
+
fuse_deletions(action, family[:fusible], [*family[:sibbling_left], *family[:child], *family[:sibbling_right]])
|
68
115
|
else
|
69
116
|
extra_sibbling = if family[:parent] # action should be a descendant of one of the children
|
70
|
-
family[:parent]
|
117
|
+
family[:parent].do_combine(action)
|
71
118
|
elsif family[:child] # or it should become the parent of some of the children,
|
72
|
-
action.with(children: family[:child])
|
119
|
+
action.with(children: family[:child], enforcer: @enforcer)
|
120
|
+
.combine_children(action.children)
|
73
121
|
else # or else it should become an additional child
|
74
122
|
action
|
75
123
|
end
|
76
|
-
with(children: [*family[:
|
124
|
+
with(children: [*family[:sibbling_left], extra_sibbling, *family[:sibbling_right]])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Assumes `more_children` all contained within `@range`
|
129
|
+
def combine_children(more_children)
|
130
|
+
more_children.inject(self) do |parent, new_child|
|
131
|
+
parent.place_in_hierarchy(new_child)
|
77
132
|
end
|
78
133
|
end
|
79
134
|
|
@@ -84,22 +139,76 @@ module Parser
|
|
84
139
|
without_fusible.do_combine(fused_deletion)
|
85
140
|
end
|
86
141
|
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
def
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
142
|
+
# Similar to @children.bsearch_index || size
|
143
|
+
# except allows for a starting point
|
144
|
+
# and `bsearch_index` is only Ruby 2.3+
|
145
|
+
def bsearch_child_index(from = 0)
|
146
|
+
size = @children.size
|
147
|
+
(from...size).bsearch { |i| yield @children[i] } || size
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns the children in a hierarchy with respect to `action`:
|
151
|
+
# :sibbling_left, sibbling_right (for those that are disjoint from `action`)
|
152
|
+
# :parent (in case one of our children contains `action`)
|
153
|
+
# :child (in case `action` strictly contains some of our children)
|
154
|
+
# :fusible (in case `action` overlaps some children but they can be fused in one deletion)
|
155
|
+
# or raises a `CloberingError`
|
156
|
+
# In case a child has equal range to `action`, it is returned as `:parent`
|
157
|
+
# Reminder: an empty range 1...1 is considered disjoint from 1...10
|
158
|
+
def analyse_hierarchy(action)
|
159
|
+
r = action.range
|
160
|
+
# left_index is the index of the first child that isn't completely to the left of action
|
161
|
+
left_index = bsearch_child_index { |child| child.range.end_pos > r.begin_pos }
|
162
|
+
# right_index is the index of the first child that is completely on the right of action
|
163
|
+
start = left_index == 0 ? 0 : left_index - 1 # See "corner case" below for reason of -1
|
164
|
+
right_index = bsearch_child_index(start) { |child| child.range.begin_pos >= r.end_pos }
|
165
|
+
center = right_index - left_index
|
166
|
+
case center
|
167
|
+
when 0
|
168
|
+
# All children are disjoint from action, nothing else to do
|
169
|
+
when -1
|
170
|
+
# Corner case: if a child has empty range == action's range
|
171
|
+
# then it will appear to be both disjoint and to the left of action,
|
172
|
+
# as well as disjoint and to the right of action.
|
173
|
+
# Since ranges are equal, we return it as parent
|
174
|
+
left_index -= 1 # Fix indices, as otherwise this child would be
|
175
|
+
right_index += 1 # considered as a sibbling (both left and right!)
|
176
|
+
parent = @children[left_index]
|
100
177
|
else
|
101
|
-
@
|
178
|
+
overlap_left = @children[left_index].range.begin_pos <=> r.begin_pos
|
179
|
+
overlap_right = @children[right_index-1].range.end_pos <=> r.end_pos
|
180
|
+
|
181
|
+
# For one child to be the parent of action, we must have:
|
182
|
+
if center == 1 && overlap_left <= 0 && overlap_right >= 0
|
183
|
+
parent = @children[left_index]
|
184
|
+
else
|
185
|
+
# Otherwise consider all non disjoint elements (center) to be contained...
|
186
|
+
contained = @children[left_index...right_index]
|
187
|
+
fusible = check_fusible(action,
|
188
|
+
(contained.shift if overlap_left < 0), # ... but check first and last one
|
189
|
+
(contained.pop if overlap_right > 0) # ... for overlaps
|
190
|
+
)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
{
|
195
|
+
parent: parent,
|
196
|
+
sibbling_left: @children[0...left_index],
|
197
|
+
sibbling_right: @children[right_index...@children.size],
|
198
|
+
fusible: fusible,
|
199
|
+
child: contained,
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
# @param [Array(Action | nil)] fusible
|
204
|
+
def check_fusible(action, *fusible)
|
205
|
+
fusible.compact!
|
206
|
+
return if fusible.empty?
|
207
|
+
fusible.each do |child|
|
208
|
+
kind = action.insertion? || child.insertion? ? :crossing_insertions : :crossing_deletions
|
209
|
+
@enforcer.call(kind) { {range: action.range, conflict: child.range} }
|
102
210
|
end
|
211
|
+
fusible
|
103
212
|
end
|
104
213
|
|
105
214
|
# Assumes action.range == range && action.children.empty?
|
@@ -109,7 +218,7 @@ module Parser
|
|
109
218
|
insert_before: "#{action.insert_before}#{insert_before}",
|
110
219
|
replacement: action.replacement || @replacement,
|
111
220
|
insert_after: "#{insert_after}#{action.insert_after}",
|
112
|
-
)
|
221
|
+
).combine_children(action.children)
|
113
222
|
end
|
114
223
|
|
115
224
|
def call_enforcer_for_merge(action)
|