class Parser::Source::Rewriter
@api public
@return [Diagnostic::Engine]
@!attribute [r] diagnostics
@return [Source::Buffer]
@!attribute [r] source_buffer
The default diagnostic engine consumer simply prints the diagnostics to ‘stderr`.
the diagnostic engine. After that, an exception is raised.
a `:note` diagnostics describing both updates are generated and passed to
If it is detected that one update clobbers another one, an `:error` and
modify the same part of code.
verifies that no two updates clobber each other, that is, attempt to
It schedules code updates to be performed in the correct order and
{Rewriter} performs the heavy lifting in the source rewriting process.
#
def active_clobber
def active_clobber @pending_clobber || @clobber end
def active_clobber=(value)
def active_clobber=(value) if @pending_clobber @pending_clobber = value else @clobber = value end end
def active_queue
def active_queue @pending_queue || @queue end
def append(action)
def append(action) if (clobber_action = clobbered?(action.range)) # cannot replace 3 characters with "foobar" diagnostic = Diagnostic.new(:error, :invalid_action, { :action => action }, action.range) @diagnostics.process(diagnostic) # clobbered by: remove 3 characters diagnostic = Diagnostic.new(:note, :clobbered, { :action => clobber_action }, clobber_action.range) @diagnostics.process(diagnostic) raise ClobberingError, "Parser::Source::Rewriter detected clobbering" else clobber(action.range) active_queue << action end self end
def clobber(range)
def clobber(range) self.active_clobber = active_clobber | (2 ** range.size - 1) << range.begin_pos end
def clobbered?(range)
def clobbered?(range) if active_clobber & ((2 ** range.size - 1) << range.begin_pos) != 0 active_queue.find do |action| action.range.to_a & range.to_a end end end
def in_transaction?
def in_transaction? !@pending_queue.nil? end
def initialize(source_buffer)
-
source_buffer
(Source::Buffer
) --
def initialize(source_buffer) @diagnostics = Diagnostic::Engine.new @diagnostics.consumer = lambda do |diag| $stderr.puts diag.render end @source_buffer = source_buffer @queue = [] @clobber = 0 end
def insert_after(range, content)
-
(ClobberingError)
- when clobbering is detected
Returns:
-
(Rewriter)
- self
Parameters:
-
content
(String
) -- -
range
(Range
) --
def insert_after(range, content) append Rewriter::Action.new(range.end, content) end
def insert_before(range, content)
-
(ClobberingError)
- when clobbering is detected
Returns:
-
(Rewriter)
- self
Parameters:
-
content
(String
) -- -
range
(Range
) --
def insert_before(range, content) append Rewriter::Action.new(range.begin, content) end
def process
-
(String)
-
def process if in_transaction? raise "Do not call #{self.class}##{__method__} inside a transaction" end adjustment = 0 source = @source_buffer.source.dup sorted_queue = @queue.sort_by.with_index do |action, index| [action.range.begin_pos, index] end sorted_queue.each do |action| begin_pos = action.range.begin_pos + adjustment end_pos = begin_pos + action.range.length source[begin_pos...end_pos] = action.replacement adjustment += (action.replacement.length - action.range.length) end source end
def remove(range)
-
(ClobberingError)
- when clobbering is detected
Returns:
-
(Rewriter)
- self
Parameters:
-
range
(Range
) --
def remove(range) append Rewriter::Action.new(range, '') end
def replace(range, content)
-
(ClobberingError)
- when clobbering is detected
Returns:
-
(Rewriter)
- self
Parameters:
-
content
(String
) -- -
range
(Range
) --
def replace(range, content) append Rewriter::Action.new(range, content) end
def transaction
-
(RuntimeError)
- when already in a transaction -
(RuntimeError)
- when no block is passed
def transaction unless block_given? raise "#{self.class}##{__method__} requires block" end if in_transaction? raise 'Nested transaction is not supported' end @pending_queue = @queue.dup @pending_clobber = @clobber yield @queue = @pending_queue @clobber = @pending_clobber self ensure @pending_queue = nil @pending_clobber = nil end