zombie-killer 0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,62 @@
1
+ # Tracks state for a variable
2
+ class VariableState
3
+ attr_accessor :nice
4
+ end
5
+
6
+ # Tracks state for local variables visible at certain point.
7
+ # Keys are symbols, values are VariableState
8
+ class VariableScope < Hash
9
+ def initialize
10
+ super do |hash, key|
11
+ hash[key] = VariableState.new
12
+ end
13
+ end
14
+
15
+ # Deep copy the VariableState values
16
+ def dup
17
+ copy = self.class.new
18
+ each do |k, v|
19
+ copy[k] = v.dup
20
+ end
21
+ copy
22
+ end
23
+
24
+ # @return [VariableState] state
25
+ def [](varname)
26
+ super
27
+ end
28
+
29
+ # Set state for a variable
30
+ def []=(varname, state)
31
+ super
32
+ end
33
+ end
34
+
35
+ # A stack of VariableScope
36
+ class VariableScopeStack
37
+ def initialize
38
+ outer_scope = VariableScope.new
39
+ @stack = [outer_scope]
40
+ end
41
+
42
+ # The innermost, or current VariableScope
43
+ def innermost
44
+ @stack.last
45
+ end
46
+
47
+ # Run *block* using a new clean scope
48
+ # @return the scope as the block left it, popped from the stack
49
+ def with_new(&block)
50
+ @stack.push VariableScope.new
51
+ block.call
52
+ @stack.pop
53
+ end
54
+
55
+ # Run *block* using a copy of the innermost scope
56
+ # @return the scope as the block left it, popped from the stack
57
+ def with_copy(&block)
58
+ @stack.push innermost.dup
59
+ block.call
60
+ @stack.pop
61
+ end
62
+ end
@@ -0,0 +1,3 @@
1
+ class ZombieKiller
2
+ VERSION = "0.2"
3
+ end
@@ -0,0 +1,172 @@
1
+ require "redcarpet"
2
+
3
+ require_relative "../lib/zombie_killer"
4
+
5
+ # Utility functions for manipulating code.
6
+ module Code
7
+ INDENT_STEP = 2
8
+
9
+ class << self
10
+ def join(lines)
11
+ lines.map { |l| "#{l}\n" }.join("")
12
+ end
13
+
14
+ def indent(s)
15
+ s.gsub(/^(?=.)/, " " * INDENT_STEP)
16
+ end
17
+ end
18
+ end
19
+
20
+ # Represents RSpec's "it" block.
21
+ class It
22
+ def initialize(attrs)
23
+ @description = attrs[:description]
24
+ @code = attrs[:code]
25
+ @skip = attrs[:skip]
26
+ end
27
+
28
+ def render
29
+ [
30
+ "#{@skip ? "xit" : "it"} #{@description.inspect} do",
31
+ Code.indent(@code),
32
+ "end"
33
+ ].join("\n")
34
+ end
35
+ end
36
+
37
+ # Represents RSpec's "describe" block.
38
+ class Describe
39
+ attr_reader :blocks
40
+
41
+ def initialize(attrs)
42
+ @description = attrs[:description]
43
+ @blocks = attrs[:blocks]
44
+ end
45
+
46
+ def render
47
+ parts = []
48
+ parts << "describe #{@description.inspect} do"
49
+ if !blocks.empty?
50
+ parts << Code.indent(@blocks.map(&:render).join("\n\n"))
51
+ end
52
+ parts << "end"
53
+ parts.join("\n")
54
+ end
55
+ end
56
+
57
+ class RSpecRenderer < Redcarpet::Render::Base
58
+ def initialize
59
+ super
60
+
61
+ @next_block_type = :unknown
62
+ @describe = Describe.new(description: "ZombieKiller:", blocks: [])
63
+ end
64
+
65
+ def header(text, header_level)
66
+ return nil if header_level == 1
67
+
68
+ if header_level > describes_depth + 1
69
+ raise "Missing higher level header: #{text}"
70
+ end
71
+
72
+ describe_at_level(header_level - 1).blocks << Describe.new(
73
+ description: text.downcase + ":",
74
+ blocks: []
75
+ )
76
+
77
+ nil
78
+ end
79
+
80
+ def paragraph(text)
81
+ if text =~ /^\*\*(.*)\*\*$/
82
+ @next_block_type = $1.downcase.to_sym
83
+ else
84
+ first_sentence = text.split(/\.(\s+|$)/).first
85
+ @description = first_sentence.sub(/^Zombie Killer /, "").sub(/\n/, " ")
86
+ end
87
+
88
+ nil
89
+ end
90
+
91
+ def block_code(code, language)
92
+ case @next_block_type
93
+ when :original
94
+ @original_code = code[0..-2]
95
+ when :translated
96
+ @translated_code = code[0..-2]
97
+ when :unchanged
98
+ @original_code = @translated_code = code[0..-2]
99
+ else
100
+ raise "Invalid next code block type: #@next_block_type.\n#{code}"
101
+ end
102
+ @next_block_type = :unknown
103
+
104
+ if @original_code && @translated_code
105
+ current_describe.blocks << It.new(
106
+ description: @description,
107
+ code: generate_test_code,
108
+ skip: @description =~ /XFAIL/
109
+ )
110
+
111
+ @original_code = nil
112
+ @translated_code = nil
113
+ end
114
+
115
+ nil
116
+ end
117
+
118
+ def doc_header
119
+ Code.join([
120
+ "# Generated from spec/zombie_killer_spec.md -- do not change!",
121
+ "",
122
+ "require \"spec_helper\"",
123
+ "",
124
+ ])
125
+ end
126
+
127
+ def doc_footer
128
+ "#{@describe.render}\n"
129
+ end
130
+
131
+ private
132
+
133
+ def describes_depth
134
+ describe = @describe
135
+ depth = 1
136
+ while describe.blocks.last.is_a?(Describe)
137
+ describe = describe.blocks.last
138
+ depth += 1
139
+ end
140
+ depth
141
+ end
142
+
143
+ def current_describe
144
+ describe = @describe
145
+ while describe.blocks.last.is_a?(Describe)
146
+ describe = describe.blocks.last
147
+ end
148
+ describe
149
+ end
150
+
151
+ def describe_at_level(level)
152
+ describe = @describe
153
+ 2.upto(level) do
154
+ describe = describe.blocks.last
155
+ end
156
+ describe
157
+ end
158
+
159
+ def generate_test_code
160
+ [
161
+ "original_code = cleanup(<" + "<-EOT)", # splitting un-confuses Emacs
162
+ Code.indent(@original_code),
163
+ "EOT",
164
+ "",
165
+ "translated_code = cleanup(<" + "<-EOT)", # splitting un-confuses Emacs
166
+ Code.indent(@translated_code),
167
+ "EOT",
168
+ "",
169
+ "expect(ZombieKiller.new.kill(original_code)).to eq(translated_code)"
170
+ ].join("\n")
171
+ end
172
+ end
@@ -0,0 +1,7 @@
1
+ $:.unshift File.expand_path("../../lib", __FILE__)
2
+ require "zombie_killer"
3
+
4
+ def cleanup(s)
5
+ s.split("\n").reject { |l| l =~ /^\s*$/ }.first =~ /^(\s*)/
6
+ s.gsub(Regexp.new("^#{$1}"), "")[0..-2]
7
+ end
@@ -0,0 +1,1101 @@
1
+ Zombie Killer Specification
2
+ ===========================
3
+
4
+ This document describes how [Zombie
5
+ Killer](https://github.com/yast/zombie-killer) kills various YCP zombies. It
6
+ serves both as a human-readable documentation and as an executable
7
+ specification. Technically, this is implemented by translating this document
8
+ from [Markdown](http://daringfireball.net/projects/markdown/) into
9
+ [RSpec](http://rspec.info/).
10
+
11
+ Table Of Contents
12
+ -----------------
13
+
14
+ 1. Concepts
15
+ 1. Literals
16
+ 1. Variables
17
+ 1. Assigments
18
+ 1. And-assignment
19
+ 1. Or-assignment
20
+ 1. Calls Preserving Niceness
21
+ 1. Calls Generating Niceness
22
+ 1. Translation Below Top Level
23
+ 1. Chained Translation
24
+ 1. If
25
+ 1. Case
26
+ 1. Loops
27
+ 1. While and Until
28
+ 1. For
29
+ 1. Exceptions
30
+ 1. Blocks
31
+ 1. Formatting
32
+
33
+ Concepts
34
+ --------
35
+
36
+ A **zombie** is a Ruby method call emulating a quirk of the YCP language that
37
+ YaST was formerly implemented in. `Ops.add` will serve as an example of a
38
+ simple zombie. The library implementation simply returns `nil` if any argument
39
+ is `nil`. Compare this to `+` which raises an exception if it gets
40
+ `nil`. Therefore `Ops.add` can be translated to the `+` operator, as long as
41
+ its arguments are not `nil`.
42
+
43
+ A **nice** value is one that cannot be `nil` and is therefore suitable as an
44
+ argument to a native operator.
45
+
46
+ An **ugly** value is one that may be `nil`.
47
+
48
+ Literals
49
+ --------
50
+
51
+ String and integer literals are obviously nice. `nil` is a literal too but it
52
+ is ugly.
53
+
54
+ Zombie Killer translates `Ops.add` of two string literals.
55
+
56
+ **Original**
57
+
58
+ ```ruby
59
+ Ops.add("Hello", "World")
60
+ ```
61
+
62
+ **Translated**
63
+
64
+ ```ruby
65
+ "Hello" + "World"
66
+ ```
67
+
68
+ Zombie Killer translates `Ops.add` of two integer literals.
69
+
70
+ **Original**
71
+
72
+ ```ruby
73
+ Ops.add(40, 2)
74
+ ```
75
+
76
+ **Translated**
77
+
78
+ ```ruby
79
+ 40 + 2
80
+ ```
81
+
82
+ Zombie Killer translates assignment of `Ops.add` of two string literals.
83
+ (Move this to "translate deeper than at top level")
84
+
85
+ **Original**
86
+
87
+ ```ruby
88
+ v = Ops.add("Hello", "World")
89
+ ```
90
+
91
+ **Translated**
92
+
93
+ ```ruby
94
+ v = "Hello" + "World"
95
+ ```
96
+
97
+ Zombie Killer does not translate Ops.add if any argument is ugly.
98
+
99
+ **Unchanged**
100
+
101
+ ```ruby
102
+ Ops.add("Hello", world)
103
+ ```
104
+
105
+ Zombie Killer does not translate Ops.add if any argument is the nil literal.
106
+
107
+ **Unchanged**
108
+
109
+ ```ruby
110
+ Ops.add("Hello", nil)
111
+ ```
112
+
113
+ Variables
114
+ ---------
115
+
116
+ If a local variable is assigned a nice value, we remember that.
117
+
118
+ Zombie Killer translates `Ops.add(nice_variable, literal)`.
119
+
120
+ **Original**
121
+
122
+ ```ruby
123
+ v = "Hello"
124
+ Ops.add(v, "World")
125
+ ```
126
+
127
+ **Translated**
128
+
129
+ ```ruby
130
+ v = "Hello"
131
+ v + "World"
132
+ ```
133
+
134
+ Zombie Killer doesn't translate `Ops.add(nice_variable, literal)` when the
135
+ variable got it's niceness via multiple assignemnt. We chose to ignore multiple
136
+ assigments for now because of their complicated semantics (especially in
137
+ presence of splats).
138
+
139
+ **Unchanged**
140
+
141
+ ```ruby
142
+ v1, v2 = "Hello", "World"
143
+ Ops.add(v1, v2)
144
+ ```
145
+
146
+ Zombie Killer translates `Ops.add(nontrivially_nice_variable, literal)`.
147
+
148
+ **Original**
149
+
150
+ ```ruby
151
+ v = "Hello"
152
+ v2 = v
153
+ v = uglify
154
+ Ops.add(v2, "World")
155
+ ```
156
+
157
+ **Translated**
158
+
159
+ ```ruby
160
+ v = "Hello"
161
+ v2 = v
162
+ v = uglify
163
+ v2 + "World"
164
+ ```
165
+
166
+ We have to take care to revoke a variable's niceness if appropriate.
167
+
168
+ Zombie Killer does not translate `Ops.add(mutated_variable, literal)`.
169
+
170
+ **Unchanged**
171
+
172
+ ```ruby
173
+ v = "Hello"
174
+ v = f(v)
175
+ Ops.add(v, "World")
176
+ ```
177
+
178
+ Zombie Killer does not confuse variables across `def`s.
179
+
180
+ **Unchanged**
181
+
182
+ ```ruby
183
+ def a
184
+ v = "literal"
185
+ end
186
+
187
+ def b(v)
188
+ Ops.add(v, "literal")
189
+ end
190
+ ```
191
+
192
+ Zombie Killer does not confuse variables across `def self.`s.
193
+
194
+ **Unchanged**
195
+
196
+ ```ruby
197
+ v = 1
198
+
199
+ def self.foo(v)
200
+ Ops.add(v, 1)
201
+ end
202
+ ```
203
+
204
+ Zombie Killer does not confuse variables across `module`s.
205
+
206
+ **Unchanged**
207
+
208
+ ```ruby
209
+ module A
210
+ v = "literal"
211
+ end
212
+
213
+ module B
214
+ # The assignment is needed to convince Ruby parser that the "v" reference in
215
+ # the "Ops.add" call later refers to a variable, not a method. This means it
216
+ # will be parsed as a "lvar" node (which can possibly be nice), not a "send"
217
+ # node (which can't be nice).
218
+
219
+ v = v
220
+ Ops.add(v, "literal")
221
+ end
222
+ ```
223
+
224
+ Zombie Killer does not confuse variables across `class`s.
225
+
226
+ **Unchanged**
227
+
228
+ ```ruby
229
+ class A
230
+ v = "literal"
231
+ end
232
+
233
+ class B
234
+ # The assignment is needed to convince Ruby parser that the "v" reference in
235
+ # the "Ops.add" call later refers to a variable, not a method. This means it
236
+ # will be parsed as a "lvar" node (which can possibly be nice), not a "send"
237
+ # node (which can't be nice).
238
+
239
+ v = v
240
+ Ops.add(v, "literal")
241
+ end
242
+ ```
243
+
244
+ Zombie Killer does not confuse variables across singleton `class`s.
245
+
246
+ **Unchanged**
247
+
248
+ ```ruby
249
+ class << self
250
+ v = "literal"
251
+ end
252
+
253
+ class << self
254
+ # The assignment is needed to convince Ruby parser that the "v" reference in
255
+ # the "Ops.add" call later refers to a variable, not a method. This means it
256
+ # will be parsed as a "lvar" node (which can possibly be nice), not a "send"
257
+ # node (which can't be nice).
258
+
259
+ v = v
260
+ Ops.add(v, "literal")
261
+ end
262
+ ```
263
+
264
+ Assignments
265
+ -----------
266
+
267
+ ### And-assignment
268
+
269
+ Zombie Killer manages niceness correctly in presence of `&&=`.
270
+
271
+ **Original**
272
+
273
+ ```ruby
274
+ nice1 = true
275
+ nice2 = true
276
+ ugly1 = nil
277
+ ugly2 = nil
278
+
279
+ nice1 &&= true
280
+ nice2 &&= nil
281
+ ugly1 &&= true
282
+ ugly2 &&= nil
283
+
284
+ Ops.add(nice1, 1)
285
+ Ops.add(nice2, 1)
286
+ Ops.add(ugly1, 1)
287
+ Ops.add(ugly2, 1)
288
+ ```
289
+
290
+ **Translated**
291
+
292
+ ```ruby
293
+ nice1 = true
294
+ nice2 = true
295
+ ugly1 = nil
296
+ ugly2 = nil
297
+
298
+ nice1 &&= true
299
+ nice2 &&= nil
300
+ ugly1 &&= true
301
+ ugly2 &&= nil
302
+
303
+ nice1 + 1
304
+ Ops.add(nice2, 1)
305
+ Ops.add(ugly1, 1)
306
+ Ops.add(ugly2, 1)
307
+ ```
308
+
309
+ ### Or-assignment
310
+
311
+ Zombie Killer manages niceness correctly in presence of `||=`.
312
+
313
+ **Original**
314
+
315
+ ```ruby
316
+ nice1 = true
317
+ nice2 = true
318
+ ugly1 = nil
319
+ ugly2 = nil
320
+
321
+ nice1 ||= true
322
+ nice2 ||= nil
323
+ ugly1 ||= true
324
+ ugly2 ||= nil
325
+
326
+ Ops.add(nice1, 1)
327
+ Ops.add(nice2, 1)
328
+ Ops.add(ugly1, 1)
329
+ Ops.add(ugly2, 1)
330
+ ```
331
+
332
+ **Translated**
333
+
334
+ ```ruby
335
+ nice1 = true
336
+ nice2 = true
337
+ ugly1 = nil
338
+ ugly2 = nil
339
+
340
+ nice1 ||= true
341
+ nice2 ||= nil
342
+ ugly1 ||= true
343
+ ugly2 ||= nil
344
+
345
+ nice1 + 1
346
+ nice2 + 1
347
+ ugly1 + 1
348
+ Ops.add(ugly2, 1)
349
+ ```
350
+
351
+ Calls Preserving Niceness
352
+ -------------------------
353
+
354
+ A localized string literal is nice.
355
+
356
+ **Original**
357
+
358
+ ```ruby
359
+ v = _("Hello")
360
+ Ops.add(v, "World")
361
+ ```
362
+
363
+ **Translated**
364
+
365
+ ```ruby
366
+ v = _("Hello")
367
+ v + "World"
368
+ ```
369
+
370
+ ### Calls Generating Niceness ###
371
+
372
+ `nil?` makes any value a nice value but unfortunately it seems of
373
+ little practical use. Even though there are two zombies that have
374
+ boolean arguments (`Builtins.find` and `Builtins.filter`), they are
375
+ just fine with `nil` since it is a falsey value.
376
+
377
+ Translation Below Top Level
378
+ ---------------------------
379
+
380
+ Zombie Killer translates a zombie nested in other calls.
381
+
382
+ **Original**
383
+
384
+ ```ruby
385
+ v = 1
386
+ foo(bar(Ops.add(v, 1), baz))
387
+ ```
388
+
389
+ **Translated**
390
+
391
+ ```ruby
392
+ v = 1
393
+ foo(bar(v + 1, baz))
394
+ ```
395
+
396
+ Chained Translation
397
+ -------------------
398
+
399
+ Zombie Killer translates a left-associative chain of nice zombies.
400
+
401
+ **Original**
402
+
403
+ ```ruby
404
+ Ops.add(Ops.add(1, 2), 3)
405
+ ```
406
+
407
+ **Translated**
408
+
409
+ ```ruby
410
+ (1 + 2) + 3
411
+ ```
412
+
413
+ Zombie Killer translates a right-associative chain of nice zombies.
414
+
415
+ **Original**
416
+
417
+ ```ruby
418
+ Ops.add(1, Ops.add(2, 3))
419
+ ```
420
+
421
+ **Translated**
422
+
423
+ ```ruby
424
+ 1 + (2 + 3)
425
+ ```
426
+
427
+ ### In case arguments are translated already
428
+
429
+ Zombie Killer translates `Ops.add` of plus and literal.
430
+
431
+ **Original**
432
+
433
+ ```ruby
434
+ Ops.add("Hello" + " ", "World")
435
+ ```
436
+
437
+ **Translated**
438
+
439
+ ```ruby
440
+ ("Hello" + " ") + "World"
441
+ ```
442
+
443
+ Zombie Killer translates `Ops.add` of parenthesized plus and literal.
444
+
445
+ **Original**
446
+
447
+ ```ruby
448
+ Ops.add(("Hello" + " "), "World")
449
+ ```
450
+
451
+ **Translated**
452
+
453
+ ```ruby
454
+ ("Hello" + " ") + "World"
455
+ ```
456
+
457
+ Zombie Killer translates `Ops.add` of literal and plus.
458
+
459
+ **Original**
460
+
461
+ ```ruby
462
+ Ops.add("Hello", " " + "World")
463
+ ```
464
+
465
+ **Translated**
466
+
467
+ ```ruby
468
+ "Hello" + (" " + "World")
469
+ ```
470
+
471
+ If
472
+ --
473
+
474
+ With a **single-pass top-down data flow analysis**, that we have been using,
475
+ we can process the `if` statement but not beyond it,
476
+ because we cannot know which branch was taken.
477
+
478
+ We can proceed after the `if` statement but must **start with a clean slate**.
479
+ More precisely we should remove knowledge of all variables affected in either
480
+ branch of the `if` statement, but we will first simplify the job and wipe all
481
+ state for the processed method.
482
+
483
+ Zombie Killer translates the `then` body of an `if` statement.
484
+
485
+ **Original**
486
+
487
+ ```ruby
488
+ if cond
489
+ Ops.add(1, 1)
490
+ end
491
+ ```
492
+
493
+ **Translated**
494
+
495
+ ```ruby
496
+ if cond
497
+ 1 + 1
498
+ end
499
+ ```
500
+
501
+ Zombie Killer translates the `then` body of an `unless` statement.
502
+
503
+ **Original**
504
+
505
+ ```ruby
506
+ unless cond
507
+ Ops.add(1, 1)
508
+ end
509
+ ```
510
+
511
+ **Translated**
512
+
513
+ ```ruby
514
+ unless cond
515
+ 1 + 1
516
+ end
517
+ ```
518
+
519
+ It translates both branches of an `if` statement, independently of each other.
520
+
521
+ **Original**
522
+
523
+ ```ruby
524
+ v = 1
525
+ if cond
526
+ Ops.add(v, 1)
527
+ v = nil
528
+ else
529
+ Ops.add(1, v)
530
+ v = nil
531
+ end
532
+ ```
533
+
534
+ **Translated**
535
+
536
+ ```ruby
537
+ v = 1
538
+ if cond
539
+ v + 1
540
+ v = nil
541
+ else
542
+ 1 + v
543
+ v = nil
544
+ end
545
+ ```
546
+
547
+ The condition also contributes to the data state.
548
+
549
+ **Original**
550
+
551
+ ```ruby
552
+ if cond(v = 1)
553
+ Ops.add(v, 1)
554
+ end
555
+ ```
556
+
557
+ **Translated**
558
+
559
+ ```ruby
560
+ if cond(v = 1)
561
+ v + 1
562
+ end
563
+ ```
564
+
565
+ ### A variable is not nice after its niceness was invalidated by an `if`
566
+
567
+ Plain `if`
568
+
569
+ **Unchanged**
570
+
571
+ ```ruby
572
+ v = 1
573
+ if cond
574
+ v = nil
575
+ end
576
+ Ops.add(v, 1)
577
+ ```
578
+
579
+ Trailing `if`.
580
+
581
+ **Unchanged**
582
+
583
+ ```ruby
584
+ v = 1
585
+ v = nil if cond
586
+ Ops.add(v, 1)
587
+ ```
588
+
589
+ Plain `unless`.
590
+
591
+ **Unchanged**
592
+
593
+ ```ruby
594
+ v = 1
595
+ unless cond
596
+ v = nil
597
+ end
598
+ Ops.add(v, 1)
599
+ ```
600
+
601
+ Trailing `unless`.
602
+
603
+ **Unchanged**
604
+
605
+ ```ruby
606
+ v = 1
607
+ v = nil unless cond
608
+ Ops.add(v, 1)
609
+ ```
610
+
611
+ ### Resuming with a clean slate after an `if`
612
+
613
+ It translates zombies whose arguments were found nice after an `if`.
614
+
615
+ **Original**
616
+
617
+ ```ruby
618
+ if cond
619
+ v = nil
620
+ end
621
+ v = 1
622
+ Ops.add(v, 1)
623
+ ```
624
+
625
+ **Translated**
626
+
627
+ ```ruby
628
+ if cond
629
+ v = nil
630
+ end
631
+ v = 1
632
+ v + 1
633
+ ```
634
+
635
+ Case
636
+ ----
637
+
638
+ With a **single-pass top-down data flow analysis**, that we have been using,
639
+ we can process the `case` statement but not beyond it,
640
+ because we cannot know which branch was taken.
641
+
642
+ We can proceed after the `case` statement but must **start with a clean slate**.
643
+ More precisely we should remove knowledge of all variables affected in either
644
+ branch of the `case` statement, but we will first simplify the job and wipe all
645
+ state for the processed method.
646
+
647
+ Zombie Killer translates the `when` body of a `case` statement.
648
+
649
+ **Original**
650
+
651
+ ```ruby
652
+ case expr
653
+ when 1
654
+ Ops.add(1, 1)
655
+ end
656
+ ```
657
+
658
+ **Translated**
659
+
660
+ ```ruby
661
+ case expr
662
+ when 1
663
+ 1 + 1
664
+ end
665
+ ```
666
+
667
+ It translates all branches of a `case` statement, independently of each other.
668
+
669
+ **Original**
670
+
671
+ ```ruby
672
+ v = 1
673
+ case expr
674
+ when 1
675
+ Ops.add(v, 1)
676
+ v = nil
677
+ when 2
678
+ Ops.add(v, 2)
679
+ v = nil
680
+ else
681
+ Ops.add(1, v)
682
+ v = nil
683
+ end
684
+ ```
685
+
686
+ **Translated**
687
+
688
+ ```ruby
689
+ v = 1
690
+ case expr
691
+ when 1
692
+ v + 1
693
+ v = nil
694
+ when 2
695
+ v + 2
696
+ v = nil
697
+ else
698
+ 1 + v
699
+ v = nil
700
+ end
701
+ ```
702
+
703
+ The expression also contributes to the data state.
704
+
705
+ **Original**
706
+
707
+ ```ruby
708
+ case v = 1
709
+ when 1
710
+ Ops.add(v, 1)
711
+ end
712
+ ```
713
+
714
+ **Translated**
715
+
716
+ ```ruby
717
+ case v = 1
718
+ when 1
719
+ v + 1
720
+ end
721
+ ```
722
+
723
+ The test also contributes to the data state.
724
+
725
+ **Original**
726
+
727
+ ```ruby
728
+ case expr
729
+ when v = 1
730
+ Ops.add(v, 1)
731
+ end
732
+ ```
733
+
734
+ **Translated**
735
+
736
+ ```ruby
737
+ case expr
738
+ when v = 1
739
+ v + 1
740
+ end
741
+ ```
742
+
743
+ ### A variable is not nice after its niceness was invalidated by a `case`
744
+
745
+ **Unchanged**
746
+
747
+ ```ruby
748
+ v = 1
749
+ case expr
750
+ when 1
751
+ v = nil
752
+ end
753
+ Ops.add(v, 1)
754
+ ```
755
+
756
+ ### Resuming with a clean slate after a `case`
757
+
758
+ It translates zombies whose arguments were found nice after a `case`.
759
+
760
+ **Original**
761
+
762
+ ```ruby
763
+ case expr
764
+ when 1
765
+ v = nil
766
+ end
767
+ v = 1
768
+ Ops.add(v, 1)
769
+ ```
770
+
771
+ **Translated**
772
+
773
+ ```ruby
774
+ case expr
775
+ when 1
776
+ v = nil
777
+ end
778
+ v = 1
779
+ v + 1
780
+ ```
781
+
782
+ Loops
783
+ -----
784
+
785
+ ### While and Until
786
+
787
+ `while` and its negated twin `until` are loops
788
+ which means assignments later in its body can affect values
789
+ earlier in its body and in the condition. Therefore we cannot process either
790
+ one and we must clear the state afterwards.
791
+
792
+ Zombie Killer does not translate anything in the outer scope
793
+ that contains a `while`.
794
+
795
+ **Unchanged**
796
+
797
+ ```ruby
798
+ v = 1
799
+ while Ops.add(v, 1)
800
+ Ops.add(1, 1)
801
+ end
802
+ Ops.add(v, 1)
803
+ ```
804
+
805
+ Zombie Killer does not translate anything in the outer scope
806
+ that contains an `until`.
807
+
808
+ **Unchanged**
809
+
810
+ ```ruby
811
+ v = 1
812
+ until Ops.add(v, 1)
813
+ Ops.add(1, 1)
814
+ end
815
+ Ops.add(v, 1)
816
+ ```
817
+
818
+ Zombie Killer can continue processing after a `while`. Pun!
819
+
820
+ **Original**
821
+
822
+ ```ruby
823
+ while cond
824
+ foo
825
+ end
826
+ v = 1
827
+ Ops.add(v, 1)
828
+ ```
829
+
830
+ **Translated**
831
+
832
+ ```ruby
833
+ while cond
834
+ foo
835
+ end
836
+ v = 1
837
+ v + 1
838
+ ```
839
+
840
+ Zombie Killer can continue processing after an `until`. No pun.
841
+
842
+ **Original**
843
+
844
+ ```ruby
845
+ until cond
846
+ foo
847
+ end
848
+ v = 1
849
+ Ops.add(v, 1)
850
+ ```
851
+
852
+ **Translated**
853
+
854
+ ```ruby
855
+ until cond
856
+ foo
857
+ end
858
+ v = 1
859
+ v + 1
860
+ ```
861
+
862
+ Zombie Killer can parse both the syntactic and semantic post-condition.
863
+
864
+ **Unchanged**
865
+
866
+ ```ruby
867
+ body_runs_after_condition while cond
868
+ body_runs_after_condition until cond
869
+
870
+ begin
871
+ body_runs_before_condition
872
+ end while cond
873
+
874
+ begin
875
+ body_runs_before_condition
876
+ end until cond
877
+ ```
878
+
879
+ ### For
880
+
881
+ `for` loops are just syntax sugar for an `each` call with a block. Thus, we need
882
+ to treat them as blocks.
883
+
884
+ Zombie Killer does not translate inside a `for` and resumes with a clean slate.
885
+
886
+ **Original**
887
+
888
+ ```ruby
889
+ v = 1
890
+ v = Ops.add(v, 1)
891
+
892
+ for i in [1, 2, 3]
893
+ v = Ops.add(v, 1)
894
+ v = uglify
895
+ end
896
+
897
+ v = Ops.add(v, 1)
898
+ w = 1
899
+ w = Ops.add(w, 1)
900
+ ```
901
+
902
+ **Translated**
903
+
904
+ ```ruby
905
+ v = 1
906
+ v = v + 1
907
+
908
+ for i in [1, 2, 3]
909
+ v = Ops.add(v, 1)
910
+ v = uglify
911
+ end
912
+
913
+ v = Ops.add(v, 1)
914
+ w = 1
915
+ w = w + 1
916
+ ```
917
+
918
+ Exceptions
919
+ ----------
920
+
921
+ Raising an exception is not a problem at the `raise` site. There it means
922
+ that all remaining code in a `def` is skipped. It is a problem at the `rescue`
923
+ or `ensure` site where it means that *some* of the preceding code was not
924
+ executed.
925
+
926
+ Zombie Killer translates the parts, joining else, rescue separately.
927
+
928
+ **Original**
929
+
930
+ ```ruby
931
+ def foo
932
+ v = 1
933
+ Ops.add(v, 1)
934
+ rescue
935
+ w = 1
936
+ Ops.add(w, 1)
937
+ v = nil
938
+ rescue
939
+ Ops.add(w, 1)
940
+ else
941
+ Ops.add(v, 1)
942
+ end
943
+ ```
944
+
945
+ **Translated**
946
+
947
+ ```ruby
948
+ def foo
949
+ v = 1
950
+ v + 1
951
+ rescue
952
+ w = 1
953
+ w + 1
954
+ v = nil
955
+ rescue
956
+ Ops.add(w, 1)
957
+ else
958
+ v + 1
959
+ end
960
+ ```
961
+
962
+ ### Skipping Code
963
+
964
+ Zombie Killer does not translate code that depends on niceness skipped
965
+ via an exception.
966
+
967
+ **Unchanged**
968
+
969
+ ```ruby
970
+ def a_problem
971
+ v = nil
972
+ w = 1 / 0
973
+ v = 1
974
+ rescue
975
+ puts "Oops", Ops.add(v, 1)
976
+ end
977
+ ```
978
+
979
+ ### Exception Syntax
980
+
981
+ Zombie Killer can parse the syntactic variants of exception handling.
982
+
983
+ **Unchanged**
984
+
985
+ ```ruby
986
+ begin
987
+ foo
988
+ raise "LOL"
989
+ foo
990
+ rescue Error
991
+ foo
992
+ rescue Bug, Blunder => b
993
+ foo
994
+ rescue => e
995
+ foo
996
+ rescue
997
+ foo
998
+ ensure
999
+ foo
1000
+ end
1001
+ yast rescue nil
1002
+ ```
1003
+
1004
+ ### Retry
1005
+
1006
+ The `retry` statement makes the begin-body effectively a loop which limits
1007
+ our translation possibilities.
1008
+
1009
+ Zombie Killer does not translate a begin-body when a rescue contains a retry.
1010
+
1011
+ **Unchanged**
1012
+
1013
+ ```ruby
1014
+ def foo
1015
+ v = 1
1016
+ begin
1017
+ Ops.add(v, 1)
1018
+ maybe_raise
1019
+ rescue
1020
+ v = nil
1021
+ retry
1022
+ end
1023
+ end
1024
+ ```
1025
+
1026
+ Blocks
1027
+ ------
1028
+
1029
+ Inside a block the data flow is more complex than we handle now.
1030
+ After it, we start anew.
1031
+
1032
+ Zombie Killer does not translate inside a block and resumes with a clean slate.
1033
+
1034
+ **Original**
1035
+
1036
+ ```ruby
1037
+ v = 1
1038
+ v = Ops.add(v, 1)
1039
+
1040
+ 2.times do
1041
+ v = Ops.add(v, 1)
1042
+ v = uglify
1043
+ end
1044
+
1045
+ v = Ops.add(v, 1)
1046
+ w = 1
1047
+ w = Ops.add(w, 1)
1048
+ ```
1049
+
1050
+ **Translated**
1051
+
1052
+ ```ruby
1053
+ v = 1
1054
+ v = v + 1
1055
+
1056
+ 2.times do
1057
+ v = Ops.add(v, 1)
1058
+ v = uglify
1059
+ end
1060
+
1061
+ v = Ops.add(v, 1)
1062
+ w = 1
1063
+ w = w + 1
1064
+ ```
1065
+
1066
+ Formatting
1067
+ ----------
1068
+
1069
+ Zombie Killer does not translate `Ops.add` if any argument has a comment.
1070
+
1071
+ **Unchanged**
1072
+
1073
+ ```ruby
1074
+ Ops.add(
1075
+ "Hello",
1076
+ # foo
1077
+ "World"
1078
+ )
1079
+ ```
1080
+
1081
+ Templates
1082
+ ---------
1083
+
1084
+ It translates.
1085
+
1086
+ **Original**
1087
+
1088
+ ```ruby
1089
+ ```
1090
+
1091
+ **Translated**
1092
+
1093
+ ```ruby
1094
+ ```
1095
+
1096
+ It does not translate.
1097
+
1098
+ **Unchanged**
1099
+
1100
+ ```ruby
1101
+ ```