Class: ZombieKillerRewriter
- Inherits:
-
Parser::Rewriter
- Object
- Parser::Rewriter
- ZombieKillerRewriter
- Includes:
- Niceness
- Defined in:
- lib/zombie_killer/rewriter.rb
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 :kwbegin, # A variant of begin; for rescue and while_post :kwoptarg, # Keyword optional argument, def m(a: 1) :lvar, # Local variable value :lvasgn, # Local variable assignment :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.
19 20 21 22 |
# File 'lib/zombie_killer/rewriter.rb', line 19 def initialize(unsafe: false) @scopes = VariableScopeStack.new @unsafe = unsafe end |
Instance Attribute Details
#scopes ⇒ Object (readonly)
Returns the value of attribute scopes.
17 18 19 |
# File 'lib/zombie_killer/rewriter.rb', line 17 def scopes @scopes end |
Instance Method Details
#on_and_asgn(node) ⇒ Object
188 189 190 191 192 193 194 195 |
# File 'lib/zombie_killer/rewriter.rb', line 188 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
218 219 220 221 |
# File 'lib/zombie_killer/rewriter.rb', line 218 def on_block(node) # ignore body, clean slate scope.clear end |
#on_case(node) ⇒ Object
end
167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/zombie_killer/rewriter.rb', line 167 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
136 137 138 |
# File 'lib/zombie_killer/rewriter.rb', line 136 def on_class(node) with_new_scope_rescuing_oops { super } end |
#on_def(node) ⇒ Object
124 125 126 |
# File 'lib/zombie_killer/rewriter.rb', line 124 def on_def(node) with_new_scope_rescuing_oops { super } end |
#on_defs(node) ⇒ Object
128 129 130 |
# File 'lib/zombie_killer/rewriter.rb', line 128 def on_defs(node) with_new_scope_rescuing_oops { super } end |
#on_ensure(node) ⇒ Object
265 266 267 268 269 270 |
# File 'lib/zombie_killer/rewriter.rb', line 265 def on_ensure(node) # (:ensure, guarded-code, ensuring-code) # guarded-code may be a :rescue or not scope.clear end |
#on_if(node) ⇒ Object
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/zombie_killer/rewriter.rb', line 144 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
181 182 183 184 185 186 |
# File 'lib/zombie_killer/rewriter.rb', line 181 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
132 133 134 |
# File 'lib/zombie_killer/rewriter.rb', line 132 def on_module(node) with_new_scope_rescuing_oops { super } end |
#on_or_asgn(node) ⇒ Object
197 198 199 200 201 202 203 204 |
# File 'lib/zombie_killer/rewriter.rb', line 197 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
251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/zombie_killer/rewriter.rb', line 251 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
236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/zombie_killer/rewriter.rb', line 236 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
272 273 274 275 |
# File 'lib/zombie_killer/rewriter.rb', line 272 def on_retry(node) # that makes the :rescue a loop, top-down data-flow fails raise TooComplexToTranslateError end |
#on_sclass(node) ⇒ Object
140 141 142 |
# File 'lib/zombie_killer/rewriter.rb', line 140 def on_sclass(node) with_new_scope_rescuing_oops { super } end |
#on_send(node) ⇒ Object
206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/zombie_killer/rewriter.rb', line 206 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
224 225 226 227 228 229 230 |
# File 'lib/zombie_killer/rewriter.rb', line 224 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
102 103 104 105 106 107 108 109 |
# File 'lib/zombie_killer/rewriter.rb', line 102 def process(node) return if node.nil? if ! @unsafe oops(node, RuntimeError.new("Unknown node type #{node.type}")) unless HANDLED_NODE_TYPES.include? node.type end super end |
#rewrite(buffer, ast) ⇒ Object
28 29 30 31 32 33 |
# File 'lib/zombie_killer/rewriter.rb', line 28 def rewrite(buffer, ast) super rescue TooComplexToTranslateError warning "(outer scope) is too complex to translate" buffer.source end |
#scope ⇒ Object
currently visible scope
112 113 114 |
# File 'lib/zombie_killer/rewriter.rb', line 112 def scope scopes.innermost end |
#warning(message) ⇒ Object
24 25 26 |
# File 'lib/zombie_killer/rewriter.rb', line 24 def warning() $stderr.puts if $VERBOSE end |
#with_new_scope_rescuing_oops(&block) ⇒ Object
116 117 118 119 120 121 122 |
# File 'lib/zombie_killer/rewriter.rb', line 116 def with_new_scope_rescuing_oops(&block) scopes.with_new do block.call end rescue => e oops(node, e) end |