Class: ZombieKillerRewriter
- Inherits:
-
Parser::Rewriter
- Object
- Parser::Rewriter
- ZombieKillerRewriter
- Includes:
- Niceness
- Defined in:
- lib/zombie_killer/rewriter.rb
Overview
The main rewriter
Constant Summary collapse
- HANDLED_NODE_TYPES =
FIXME How can we ensure that code modifications do not make some unhandled again?
[ :alias, # Method alias :and, # && :and_asgn, # &&= :arg, # One argument :args, # All arguments :back_ref, # Regexp backreference, $`; $&; $' :begin, # A simple sequence :block, # A closure, not just any scope :block_pass, # Pass &foo as an arg which is a block, &:foo :blockarg, # An argument initialized with a block def m(&b) :break, # Break statement :case, # Case statement :casgn, # Constant assignment/definition :cbase, # Base/root of constant tree, ::Foo :class, # Class body :cvar, # Class @@variable :cvasgn, # Class @@variable = assignment :const, # Name of a class/module or name of a value :def, # Method definition :defined?, # defined? statement :defs, # Method definition on self :ensure, # Exception ensuring :for, # For v in enum; :gvar, # Global $variable :gvasgn, # Global $variable = assignment :if, # If and Unless :ivar, # Instance variable value :ivasgn, # Instance variable assignment :kwarg, # Keyword argument, def m(a:) :kwbegin, # A variant of begin; for rescue and while_post :kwoptarg, # Keyword optional argument, def m(a: 1) :kwrestarg, # Rest of keyword arguments, def m(**kwargs) :kwsplat, # Hash **splatting :lvar, # Local variable value :lvasgn, # Local variable assignment :match_with_lvasgn, # /regex/ =~ value :masgn, # Multiple assigment: a, b = c, d :mlhs, # Left-hand side of a multiple assigment: a, b = c, d :module, # Module body :next, # Next statement :nil, # nil literal :nth_ref, # Regexp back references: $1, $2... :op_asgn, # a %= b where % is any operator except || && :optarg, # Optional argument :or, # || :or_asgn, # ||= :postexe, # END { } :regopt, # options tacked on a :regexp :resbody, # One rescue clause in a :rescue construct :rescue, # Groups the begin and :resbody :restarg, # Rest of arguments, (..., *args) :retry, # Retry a begin-rescue block :return, # Method return :sclass, # Singleton class, class << foo :send, # Send a message AKA Call a method :splat, # Array *splatting :super, # Call the ancestor method :unless, # Unless AKA If-Not :until, # Until AKA While-Not :until_post, # Until with post-condtion :when, # When branch of an Case statement :while, # While loop :while_post, # While loop with post-condition :xstr, # Executed `string`, backticks :yield, # Call the unnamed block :zsuper # Zero argument :super ].to_set + NICE_LITERAL_NODE_TYPES
Constants included from Niceness
Niceness::NICE_GLOBAL_METHODS, Niceness::NICE_LITERAL_NODE_TYPES, Niceness::NICE_OPERATORS
Instance Attribute Summary collapse
-
#scopes ⇒ Object
readonly
Returns the value of attribute scopes.
Instance Method Summary collapse
-
#initialize(unsafe: false) ⇒ ZombieKillerRewriter
constructor
A new instance of ZombieKillerRewriter.
- #on_and_asgn(node) ⇒ Object
- #on_block(node) ⇒ Object (also: #on_for)
-
#on_case(node) ⇒ Object
end.
- #on_class(node) ⇒ Object
- #on_def(node) ⇒ Object
- #on_defs(node) ⇒ Object
- #on_ensure(node) ⇒ Object
- #on_if(node) ⇒ Object
- #on_lvasgn(node) ⇒ Object
- #on_module(node) ⇒ Object
- #on_or_asgn(node) ⇒ Object
- #on_resbody(node) ⇒ Object
-
#on_rescue(node) ⇒ Object
Exceptions: ‘raise` is an ordinary :send for the parser.
- #on_retry(node) ⇒ Object
- #on_sclass(node) ⇒ Object
- #on_send(node) ⇒ Object
- #on_while(node) ⇒ Object (also: #on_until)
- #process(node) ⇒ Object
- #rewrite(buffer, ast) ⇒ Object
-
#scope ⇒ Object
currently visible scope.
- #warning(message) ⇒ Object
- #with_new_scope_rescuing_oops(&block) ⇒ Object
Methods included from Niceness
#nice, #nice_begin, #nice_literal, #nice_send, #nice_variable
Constructor Details
#initialize(unsafe: false) ⇒ ZombieKillerRewriter
Returns a new instance of ZombieKillerRewriter.
30 31 32 33 |
# File 'lib/zombie_killer/rewriter.rb', line 30 def initialize(unsafe: false) @scopes = VariableScopeStack.new @unsafe = unsafe end |
Instance Attribute Details
#scopes ⇒ Object (readonly)
Returns the value of attribute scopes.
28 29 30 |
# File 'lib/zombie_killer/rewriter.rb', line 28 def scopes @scopes end |
Instance Method Details
#on_and_asgn(node) ⇒ Object
205 206 207 208 209 210 211 212 |
# File 'lib/zombie_killer/rewriter.rb', line 205 def on_and_asgn(node) super var, value = * node return if var.type != :lvasgn name = var.children[0] scope[name].nice &&= nice(value) end |
#on_block(node) ⇒ Object Also known as: on_for
235 236 237 238 |
# File 'lib/zombie_killer/rewriter.rb', line 235 def on_block(node) # ignore body, clean slate scope.clear end |
#on_case(node) ⇒ Object
end
184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/zombie_killer/rewriter.rb', line 184 def on_case(node) expr, *cases = *node process(expr) cases.each do |case_| scopes.with_copy do process(case_) end end # clean slate scope.clear end |
#on_class(node) ⇒ Object
153 154 155 |
# File 'lib/zombie_killer/rewriter.rb', line 153 def on_class(node) with_new_scope_rescuing_oops { super } end |
#on_def(node) ⇒ Object
141 142 143 |
# File 'lib/zombie_killer/rewriter.rb', line 141 def on_def(node) with_new_scope_rescuing_oops { super } end |
#on_defs(node) ⇒ Object
145 146 147 |
# File 'lib/zombie_killer/rewriter.rb', line 145 def on_defs(node) with_new_scope_rescuing_oops { super } end |
#on_ensure(node) ⇒ Object
282 283 284 285 286 287 |
# File 'lib/zombie_killer/rewriter.rb', line 282 def on_ensure(node) # (:ensure, guarded-code, ensuring-code) # guarded-code may be a :rescue or not scope.clear end |
#on_if(node) ⇒ Object
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/zombie_killer/rewriter.rb', line 161 def on_if(node) cond, then_body, else_body = *node process(cond) scopes.with_copy do process(then_body) end scopes.with_copy do process(else_body) end # clean slate scope.clear end |
#on_lvasgn(node) ⇒ Object
198 199 200 201 202 203 |
# File 'lib/zombie_killer/rewriter.rb', line 198 def on_lvasgn(node) super name, value = * node return if value.nil? # and-asgn, or-asgn, resbody do this scope[name].nice = nice(value) end |
#on_module(node) ⇒ Object
149 150 151 |
# File 'lib/zombie_killer/rewriter.rb', line 149 def on_module(node) with_new_scope_rescuing_oops { super } end |
#on_or_asgn(node) ⇒ Object
214 215 216 217 218 219 220 221 |
# File 'lib/zombie_killer/rewriter.rb', line 214 def on_or_asgn(node) super var, value = * node return if var.type != :lvasgn name = var.children[0] scope[name].nice ||= nice(value) end |
#on_resbody(node) ⇒ Object
268 269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'lib/zombie_killer/rewriter.rb', line 268 def on_resbody(node) # How it is parsed: # (:resbody, exception-types-or-nil, exception-variable-or-nil, body) # exception-types is an :array # exception-variable is a (:lvasgn, name), without a value # A rescue means that *some* previous code was skipped. We know nothing. # We could process the resbodies individually, # and join begin-block with else-block, but it is little worth # because they will contain few zombies. scope.clear super end |
#on_rescue(node) ⇒ Object
Exceptions: ‘raise` is an ordinary :send for the parser
253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/zombie_killer/rewriter.rb', line 253 def on_rescue(node) # (:rescue, begin-block, resbody..., else-block-or-nil) begin_body, *rescue_bodies, else_body = *node @source_rewriter.transaction do process(begin_body) process(else_body) rescue_bodies.each do |r| process(r) end end rescue TooComplexToTranslateError warning "begin-rescue is too complex to translate due to a retry" end |
#on_retry(node) ⇒ Object
289 290 291 292 |
# File 'lib/zombie_killer/rewriter.rb', line 289 def on_retry(node) # that makes the :rescue a loop, top-down data-flow fails raise TooComplexToTranslateError end |
#on_sclass(node) ⇒ Object
157 158 159 |
# File 'lib/zombie_killer/rewriter.rb', line 157 def on_sclass(node) with_new_scope_rescuing_oops { super } end |
#on_send(node) ⇒ Object
223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/zombie_killer/rewriter.rb', line 223 def on_send(node) super if is_call(node, :Ops, :add) new_op = :+ _ops, _add, a, b = *node if nice(a) && nice(b) replace_node node, Parser::AST::Node.new(:send, [a, new_op, b]) end end end |
#on_while(node) ⇒ Object Also known as: on_until
241 242 243 244 245 246 247 |
# File 'lib/zombie_killer/rewriter.rb', line 241 def on_while(node) # ignore both condition and body, # with a simplistic scope we cannot handle them # clean slate scope.clear end |
#process(node) ⇒ Object
117 118 119 120 121 122 123 124 |
# File 'lib/zombie_killer/rewriter.rb', line 117 def process(node) return if node.nil? unless @unsafe raise NodeError.new("Unknown node type #{node.type}", node) unless HANDLED_NODE_TYPES.include? node.type end super end |
#rewrite(buffer, ast) ⇒ Object
39 40 41 42 43 44 |
# File 'lib/zombie_killer/rewriter.rb', line 39 def rewrite(buffer, ast) super rescue TooComplexToTranslateError warning "(outer scope) is too complex to translate" buffer.source end |
#scope ⇒ Object
currently visible scope
127 128 129 |
# File 'lib/zombie_killer/rewriter.rb', line 127 def scope scopes.innermost end |
#warning(message) ⇒ Object
35 36 37 |
# File 'lib/zombie_killer/rewriter.rb', line 35 def warning() $stderr.puts if $VERBOSE end |
#with_new_scope_rescuing_oops(&block) ⇒ Object
131 132 133 134 135 136 137 138 139 |
# File 'lib/zombie_killer/rewriter.rb', line 131 def with_new_scope_rescuing_oops(&block) scopes.with_new do block.call end rescue NodeError => e puts e puts "Node exception @ #{e.node.loc.expression}" puts "Offending node: #{e.node.inspect}" end |