Class: Parser::Source::Rewriter
- Inherits:
-
Object
- Object
- Parser::Source::Rewriter
- Defined in:
- lib/parser/source/rewriter.rb
Overview
Rewriter performs the heavy lifting in the source rewriting process. It schedules code updates to be performed in the correct order and verifies that no two updates clobber each other, that is, attempt to modify the same part of code.
If it is detected that one update clobbers another one, an :error
and
a :note
diagnostics describing both updates are generated and passed to
the diagnostic engine. After that, an exception is raised.
The default diagnostic engine consumer simply prints the diagnostics to stderr
.
Instance Attribute Summary collapse
- #diagnostics ⇒ Diagnostic::Engine readonly
- #source_buffer ⇒ Source::Buffer readonly
Instance Method Summary collapse
-
#initialize(source_buffer) ⇒ Rewriter
constructor
A new instance of Rewriter.
-
#insert_after(range, content) ⇒ Rewriter
Inserts new code after the given source range.
-
#insert_before(range, content) ⇒ Rewriter
Inserts new code before the given source range.
-
#process ⇒ String
Applies all scheduled changes to the
source_buffer
and returns modified source as a new string. -
#remove(range) ⇒ Rewriter
Removes the source range.
-
#replace(range, content) ⇒ Rewriter
Replaces the code of the source range
range
withcontent
.
Constructor Details
#initialize(source_buffer) ⇒ Rewriter
Returns a new instance of Rewriter.
31 32 33 34 35 36 37 38 39 40 |
# File 'lib/parser/source/rewriter.rb', line 31 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 |
Instance Attribute Details
#diagnostics ⇒ Diagnostic::Engine (readonly)
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/parser/source/rewriter.rb', line 24 class Rewriter attr_reader :source_buffer attr_reader :diagnostics ## # @param [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 ## # Removes the source range. # # @param [Range] range # @return [Rewriter] self # @raise [RuntimeError] when clobbering is detected # def remove(range) append Rewriter::Action.new(range, '') end ## # Inserts new code before the given source range. # # @param [Range] range # @param [String] content # @return [Rewriter] self # @raise [RuntimeError] when clobbering is detected # def insert_before(range, content) append Rewriter::Action.new(range.begin, content) end ## # Inserts new code after the given source range. # # @param [Range] range # @param [String] content # @return [Rewriter] self # @raise [RuntimeError] when clobbering is detected # def insert_after(range, content) append Rewriter::Action.new(range.end, content) end ## # Replaces the code of the source range `range` with `content`. # # @param [Range] range # @param [String] content # @return [Rewriter] self # @raise [RuntimeError] when clobbering is detected # def replace(range, content) append Rewriter::Action.new(range, content) end ## # Applies all scheduled changes to the `source_buffer` and returns # modified source as a new string. # # @return [String] # def process 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 private 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 RuntimeError, "Parser::Source::Rewriter detected clobbering" else clobber(action.range) @queue << action end self end def clobber(range) @clobber |= (2 ** range.size - 1) << range.begin_pos end def clobbered?(range) if @clobber & ((2 ** range.size - 1) << range.begin_pos) != 0 @queue.find do |action| action.range.to_a & range.to_a end end end end |
#source_buffer ⇒ Source::Buffer (readonly)
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/parser/source/rewriter.rb', line 24 class Rewriter attr_reader :source_buffer attr_reader :diagnostics ## # @param [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 ## # Removes the source range. # # @param [Range] range # @return [Rewriter] self # @raise [RuntimeError] when clobbering is detected # def remove(range) append Rewriter::Action.new(range, '') end ## # Inserts new code before the given source range. # # @param [Range] range # @param [String] content # @return [Rewriter] self # @raise [RuntimeError] when clobbering is detected # def insert_before(range, content) append Rewriter::Action.new(range.begin, content) end ## # Inserts new code after the given source range. # # @param [Range] range # @param [String] content # @return [Rewriter] self # @raise [RuntimeError] when clobbering is detected # def insert_after(range, content) append Rewriter::Action.new(range.end, content) end ## # Replaces the code of the source range `range` with `content`. # # @param [Range] range # @param [String] content # @return [Rewriter] self # @raise [RuntimeError] when clobbering is detected # def replace(range, content) append Rewriter::Action.new(range, content) end ## # Applies all scheduled changes to the `source_buffer` and returns # modified source as a new string. # # @return [String] # def process 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 private 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 RuntimeError, "Parser::Source::Rewriter detected clobbering" else clobber(action.range) @queue << action end self end def clobber(range) @clobber |= (2 ** range.size - 1) << range.begin_pos end def clobbered?(range) if @clobber & ((2 ** range.size - 1) << range.begin_pos) != 0 @queue.find do |action| action.range.to_a & range.to_a end end end end |
Instance Method Details
#insert_after(range, content) ⇒ Rewriter
Inserts new code after the given source range.
73 74 75 |
# File 'lib/parser/source/rewriter.rb', line 73 def insert_after(range, content) append Rewriter::Action.new(range.end, content) end |
#insert_before(range, content) ⇒ Rewriter
Inserts new code before the given source range.
61 62 63 |
# File 'lib/parser/source/rewriter.rb', line 61 def insert_before(range, content) append Rewriter::Action.new(range.begin, content) end |
#process ⇒ String
Applies all scheduled changes to the source_buffer
and returns
modified source as a new string.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/parser/source/rewriter.rb', line 95 def process 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 |