Class: ZombieKillerRewriter

Inherits:
Parser::Rewriter
  • Object
show all
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

Instance Method Summary collapse

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

#scopesObject (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

#scopeObject

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(message)
  $stderr.puts message 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