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)

Parameters:
  • 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)

Raises:
  • (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)

Raises:
  • (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

Returns:
  • (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)

Raises:
  • (ClobberingError) - when clobbering is detected

Returns:
  • (Rewriter) - self

Parameters:
  • range (Range) --
def remove(range)
  append Rewriter::Action.new(range, '')
end

def replace(range, content)

Raises:
  • (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

Raises:
  • (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