zombie-killer 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ ```