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)
-
- (Diagnostic::Engine) diagnostics
readonly
-
- (Source::Buffer) source_buffer
readonly
Instance Method Summary (collapse)
-
- (Rewriter) initialize(source_buffer)
constructor
A new instance of Rewriter.
-
- (Rewriter) insert_after(range, content)
Inserts new code after the given source range.
-
- (Rewriter) insert_before(range, content)
Inserts new code before the given source range.
-
- (String) process
Applies all scheduled changes to the
source_buffer
and returns modified source as a new string. -
- (Rewriter) remove(range)
Removes the source range.
-
- (Rewriter) replace(range, content)
Replaces the code of the source range
range
withcontent
. -
- (Object) transaction
Provides a protected block where a sequence of multiple rewrite actions are handled atomic.
Constructor Details
- (Rewriter) initialize(source_buffer)
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
- (Diagnostic::Engine) diagnostics (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 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# 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 [ClobberingError] 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 [ClobberingError] 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 [ClobberingError] 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 [ClobberingError] 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 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 ## # Provides a protected block where a sequence of multiple rewrite actions # are handled atomic. If any of the action failed by clobbering, # all the actions are rolled back. # # @example # begin # rewriter.transaction do # rewriter.insert_before(range_of_something, '(') # rewriter.insert_after(range_of_something, ')') # end # rescue Parser::ClobberingError # end # # @raise [RuntimeError] when no block is passed # @raise [RuntimeError] when already in a transaction # 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 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 ClobberingError, "Parser::Source::Rewriter detected clobbering" else clobber(action.range) active_queue << action end self end def clobber(range) self.active_clobber = active_clobber | (2 ** range.size - 1) << range.begin_pos end 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? !@pending_queue.nil? end def active_queue @pending_queue || @queue end def active_clobber @pending_clobber || @clobber end def active_clobber=(value) if @pending_clobber @pending_clobber = value else @clobber = value 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 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# 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 [ClobberingError] 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 [ClobberingError] 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 [ClobberingError] 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 [ClobberingError] 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 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 ## # Provides a protected block where a sequence of multiple rewrite actions # are handled atomic. If any of the action failed by clobbering, # all the actions are rolled back. # # @example # begin # rewriter.transaction do # rewriter.insert_before(range_of_something, '(') # rewriter.insert_after(range_of_something, ')') # end # rescue Parser::ClobberingError # end # # @raise [RuntimeError] when no block is passed # @raise [RuntimeError] when already in a transaction # 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 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 ClobberingError, "Parser::Source::Rewriter detected clobbering" else clobber(action.range) active_queue << action end self end def clobber(range) self.active_clobber = active_clobber | (2 ** range.size - 1) << range.begin_pos end 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? !@pending_queue.nil? end def active_queue @pending_queue || @queue end def active_clobber @pending_clobber || @clobber end def active_clobber=(value) if @pending_clobber @pending_clobber = value else @clobber = value end end end |
Instance Method Details
- (Rewriter) insert_after(range, content)
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 |
- (Rewriter) insert_before(range, content)
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 |
- (String) process
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 114 115 116 117 |
# File 'lib/parser/source/rewriter.rb', line 95 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 |
- (Rewriter) remove(range)
Removes the source range.
49 50 51 |
# File 'lib/parser/source/rewriter.rb', line 49 def remove(range) append Rewriter::Action.new(range, '') end |
- (Rewriter) replace(range, content)
Replaces the code of the source range range
with content
.
85 86 87 |
# File 'lib/parser/source/rewriter.rb', line 85 def replace(range, content) append Rewriter::Action.new(range, content) end |
- (Object) transaction
Provides a protected block where a sequence of multiple rewrite actions are handled atomic. If any of the action failed by clobbering, all the actions are rolled back.
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/parser/source/rewriter.rb', line 136 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 |