Skip to content

Commit 1e0aed5

Browse files
committed
wip
1 parent 07ab97e commit 1e0aed5

File tree

4 files changed

+129
-46
lines changed

4 files changed

+129
-46
lines changed

lib/parser.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ module Source
3838
require 'parser/source/rewriter/action'
3939
require 'parser/source/tree_rewriter'
4040
require 'parser/source/tree_rewriter/action'
41+
require 'parser/source/tree_rewriter/enforcer'
4142

4243
require 'parser/source/map'
4344
require 'parser/source/map/operator'

lib/parser/source/tree_rewriter.rb

Lines changed: 11 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -90,27 +90,19 @@ module Source
9090
#
9191
class TreeRewriter
9292
attr_reader :source_buffer
93-
attr_reader :diagnostics
9493

94+
attr_reader :enforcer
9595
##
9696
# @param [Source::Buffer] source_buffer
9797
#
9898
def initialize(source_buffer,
99-
crossing_deletions: :accept,
100-
different_replacements: :accept,
101-
swallowed_insertions: :accept)
102-
@diagnostics = Diagnostic::Engine.new
103-
@diagnostics.consumer = -> diag { $stderr.puts diag.render }
99+
enforcer: nil,
100+
**policy)
101+
@enforcer = enforcer || Enforcer::WithPolicy.new(**policy)
104102

105103
@source_buffer = source_buffer
106104
@in_transaction = false
107105

108-
@policy = {crossing_deletions: crossing_deletions,
109-
different_replacements: different_replacements,
110-
swallowed_insertions: swallowed_insertions}.freeze
111-
check_policy_validity
112-
113-
@enforcer = method(:enforce_policy)
114106
# We need a range that would be jugded as containing all other ranges,
115107
# including 0...0 and size...size:
116108
all_encompassing_range = @source_buffer.source_range.adjust(begin_pos: -1, end_pos: +1)
@@ -330,6 +322,13 @@ def in_transaction?
330322
@in_transaction
331323
end
332324

325+
##
326+
# @deprecated Provide different enforcer
327+
#
328+
def diagnostics
329+
enforcer.diagnostics
330+
end
331+
333332
##
334333
# @api private
335334
# @deprecated Use insert_after or wrap
@@ -361,12 +360,6 @@ def insert_after_multi(range, text)
361360

362361
private
363362

364-
ACTIONS = %i[accept warn raise].freeze
365-
def check_policy_validity
366-
invalid = @policy.values - ACTIONS
367-
raise ArgumentError, "Invalid policy: #{invalid.join(', ')}" unless invalid.empty?
368-
end
369-
370363
def combine(range, attributes)
371364
range = check_range_validity(range)
372365
action = TreeRewriter::Action.new(range, @enforcer, **attributes)
@@ -380,25 +373,6 @@ def check_range_validity(range)
380373
end
381374
range
382375
end
383-
384-
def enforce_policy(event)
385-
return if @policy[event] == :accept
386-
return unless (values = yield)
387-
trigger_policy(event, **values)
388-
end
389-
390-
POLICY_TO_LEVEL = {warn: :warning, raise: :error}.freeze
391-
def trigger_policy(event, range: raise, conflict: nil, **arguments)
392-
action = @policy[event] || :raise
393-
diag = Parser::Diagnostic.new(POLICY_TO_LEVEL[action], event, arguments, range)
394-
@diagnostics.process(diag)
395-
if conflict
396-
range, *highlights = conflict
397-
diag = Parser::Diagnostic.new(POLICY_TO_LEVEL[action], :"#{event}_conflict", arguments, range, highlights)
398-
@diagnostics.process(diag)
399-
end
400-
raise Parser::ClobberingError, "Parser::Source::TreeRewriter detected clobbering" if action == :raise
401-
end
402376
end
403377
end
404378
end

lib/parser/source/tree_rewriter/action.rb

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -205,15 +205,19 @@ def check_fusible(action, *fusible)
205205
fusible.compact!
206206
return if fusible.empty?
207207
fusible.each do |child|
208-
kind = action.insertion? || child.insertion? ? :crossing_insertions : :crossing_deletions
209-
@enforcer.call(kind) { {range: action.range, conflict: child.range} }
208+
if action.insertion? || child.insertion?
209+
@enforcer.on_crossing_insertions(action.range, child.range) unless @enforcer.ignore?(:crossing_insertions)
210+
else
211+
@enforcer.on_crossing_deletions(action.range, child.range) unless @enforcer.ignore?(:crossing_deletions)
212+
end
210213
end
211214
fusible
212215
end
213216

214217
# Assumes action.range == range && action.children.empty?
215218
def merge(action)
216219
call_enforcer_for_merge(action)
220+
217221
with(
218222
insert_before: "#{action.insert_before}#{insert_before}",
219223
replacement: action.replacement || @replacement,
@@ -222,19 +226,20 @@ def merge(action)
222226
end
223227

224228
def call_enforcer_for_merge(action)
225-
@enforcer.call(:different_replacements) do
226-
if @replacement && action.replacement && @replacement != action.replacement
227-
{range: @range, replacement: action.replacement, other_replacement: @replacement}
228-
end
229+
if @replacement && action.replacement && !@enforcer.ignore?(:different_replacements) &&
230+
@replacement != action.replacement
231+
@enforcer.on_different_replacements(@range, action.replacement, @replacement)
229232
end
230233
end
231234

232235
def swallow(children)
233-
@enforcer.call(:swallowed_insertions) do
236+
unless @enforcer.ignore?(:swallowed_insertions)
234237
insertions = children.select(&:insertion?)
235-
236-
{range: @range, conflict: insertions.map(&:range)} unless insertions.empty?
238+
unless insertions.empty?
239+
@enforcer.on_swallowed_insertions @range, insertions.map(&:range)
240+
end
237241
end
242+
238243
[]
239244
end
240245
end
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# frozen_string_literal: true
2+
3+
module Parser
4+
module Source
5+
class TreeRewriter
6+
##
7+
# Base class for objects responsible to handle rewriting conflicts
8+
# At a minimum, `ignore?` must be defined.
9+
# Other methods may be overwritten.
10+
#
11+
class Enforcer
12+
#
13+
# @param [Symbol] one of :crossing_deletions, :different_replacements, :swallowed_insertions,
14+
# For future-proofing, `ignore?` must accept other events and return `true`.
15+
def ignore?(event)
16+
raise NotImplementedError
17+
end
18+
19+
def on_crossing_insertions(range, conflict_range)
20+
on(:crossing_insertions, range, conflict: conflict_range)
21+
end
22+
23+
def on_crossing_deletions(range, conflict_range)
24+
on(:crossing_deletions, range, conflict: conflict_range)
25+
end
26+
27+
def on_swallowed_insertions(range, conflict_range)
28+
on(:swallowed_insertions, range, conflict: conflict_range)
29+
end
30+
31+
def on_different_replacements(range, replacement, other_replacement)
32+
on(:different_replacements, range, replacement: replacement, other_replacement: other_replacement)
33+
end
34+
35+
protected
36+
37+
def on(_event, _range, **_args)
38+
reject
39+
end
40+
41+
def reject
42+
raise Parser::ClobberingError, "Parser::Source::TreeRewriter detected clobbering"
43+
end
44+
45+
class WithPolicy < Enforcer
46+
ACTIONS = %i[accept warn raise].freeze
47+
48+
def initialize(
49+
crossing_deletions: :accept,
50+
different_replacements: :accept,
51+
swallowed_insertions: :accept
52+
)
53+
@crossing_deletions = validate_policy(crossing_deletions )
54+
@different_replacements = validate_policy(different_replacements)
55+
@swallowed_insertions = validate_policy(swallowed_insertions )
56+
end
57+
58+
def ignore?(event)
59+
policy(event) == :accept
60+
end
61+
62+
def diagnostics
63+
@diagnostics ||= Diagnostic::Engine.new(-> diag { $stderr.puts diag.render })
64+
end
65+
66+
protected
67+
68+
def validate_policy(action)
69+
raise ArgumentError, "Invalid policy value: #{action}" unless ACTIONS.include?(action)
70+
71+
action
72+
end
73+
74+
def policy(event)
75+
case event
76+
when :crossing_insertions then :raise
77+
when :crossing_deletions then @crossing_deletions
78+
when :different_replacements then @different_replacements
79+
when :swallowed_insertions then @swallowed_insertions
80+
else :accept # Example of future proofing
81+
end
82+
end
83+
84+
POLICY_TO_LEVEL = {warn: :warning, raise: :error}.freeze
85+
def on(event, range, conflict: nil, **arguments)
86+
action = policy(event)
87+
severity = POLICY_TO_LEVEL.fetch(action)
88+
diag = Parser::Diagnostic.new(severity, event, arguments, range)
89+
diagnostics.process(diag)
90+
if conflict
91+
range, *highlights = conflict
92+
diag = Parser::Diagnostic.new(severity, :"#{event}_conflict", arguments, range, highlights)
93+
diagnostics.process(diag)
94+
end
95+
raise Parser::ClobberingError, "Parser::Source::TreeRewriter detected clobbering" if action == :raise
96+
end
97+
end
98+
99+
DEFAULT = WithPolicy.new.freeze
100+
end
101+
end
102+
end
103+
end

0 commit comments

Comments
 (0)