virtual_keywords 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,34 @@
1
+ # Before translation
2
+ # Ok, so each s(:when) has 2 elements: the condition and the consequence
3
+ # They're not wrapped in an array or anything, so this is a lot more
4
+ # complicated...
5
+ s(:defn, :describe_value,
6
+ s(:scope,
7
+ s(:block, s(:args),
8
+ s(:case, s(:ivar, :@value),
9
+ s(:when,
10
+ s(:array, s(:lit, 1)),
11
+ s(:lit, :one)
12
+ ),
13
+ s(:when,
14
+ s(:array, s(:lit, 3..5)),
15
+ s(:lit, :three_to_five)
16
+ ),
17
+ s(:when,
18
+ s(:array, s(:lit, 7), s(:lit, 9)),
19
+ s(:lit, :seven_or_nine)
20
+ ),
21
+ s(:when,
22
+ s(:array,
23
+ s(:call,
24
+ s(:call, s(:vcall, :value), :*, s(:array, s(:lit, 10))),
25
+ :<, s(:array, s(:lit, 90))
26
+ )
27
+ ),
28
+ s(:lit, :passes_multiplication_test)
29
+ ),
30
+ s(:lit, :something_else)
31
+ )
32
+ )
33
+ )
34
+ )
@@ -0,0 +1,65 @@
1
+ # Structure of a while
2
+ # So the first element is the condition, and the second is the body, wrapped
3
+ # in a :block (TODO and then a "true", what is it for?)
4
+ s(:defn, :while_count_to_value,
5
+ s(:scope,
6
+ s(:block, s(:args),
7
+ s(:while,
8
+ # The condition
9
+ s(:call, s(:ivar, :@i), :<,
10
+ s(:array, s(:vcall, :value))
11
+ ),
12
+ # End condition
13
+ # The body
14
+ s(:block,
15
+ s(:call, s(:ivar, :@counts), :<<,
16
+ s(:array, s(:ivar, :@i))
17
+ ),
18
+ s(:iasgn, :@i,
19
+ s(:call, s(:ivar, :@i), :+,
20
+ s(:array, s(:lit, 1))
21
+ )
22
+ )
23
+ ),
24
+ # End body
25
+ true
26
+ )
27
+ )
28
+ )
29
+ )
30
+
31
+ # After
32
+ s(:defn, :while_result,
33
+ s(:scope,
34
+ s(:block, s(:args),
35
+ s(:fcall, :my_while,
36
+ s(:array,
37
+ s(:iter,
38
+ s(:fcall, :lambda), nil,
39
+ # The condition
40
+ s(:call, s(:ivar, :@i), :<,
41
+ s(:array, s(:vcall, :value))
42
+ )
43
+ # End condition
44
+ ),
45
+ s(:iter,
46
+ s(:fcall, :lambda), nil,
47
+ # The body
48
+ s(:block,
49
+ s(:call, s(:ivar, :@counts), :<<,
50
+ s(:array, s(:ivar, :@i))
51
+ ),
52
+ s(:iasgn, :@i,
53
+ s(:call, s(:ivar, :@i), :+,
54
+ s(:array, s(:lit, 1))
55
+ )
56
+ )
57
+ )
58
+ # End body
59
+ )
60
+ )
61
+ )
62
+ )
63
+ )
64
+ )
65
+
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'AndRewriter' do
4
+ include TrackIfs, TrackAnds, DoRewrite
5
+
6
+ before :each do
7
+ @and_user = AndUser.new false
8
+ @methods = sexpify_instance_methods AndUser
9
+ @if_rewriter = VirtualKeywords::IfRewriter.new
10
+ @and_rewriter = VirtualKeywords::AndRewriter.new
11
+
12
+ @my_and_calls = 0
13
+ @my_if_calls = 0
14
+
15
+ VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
16
+ @and_user, :and, my_and)
17
+ VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
18
+ @and_user, :if, my_if)
19
+ end
20
+
21
+ def rewriters
22
+ [@if_rewriter, @and_rewriter]
23
+ end
24
+
25
+ it 'rewrites "and" statements' do
26
+ do_rewrite(:method_with_and, @and_user)
27
+ @my_and_calls.should eql 1
28
+ end
29
+
30
+ it 'handles ifs with "and"s in the predicate' do
31
+ do_rewrite(:if_with_and, @and_user)
32
+ @my_and_calls.should eql 1
33
+ @my_if_calls.should eql 1
34
+ end
35
+ end
36
+
37
+ describe 'AndRewriter on &&' do
38
+ include TrackAnds, DoRewrite
39
+
40
+ before :each do
41
+ @operator_user = OperatorUser.new false
42
+ @methods = sexpify_instance_methods OperatorUser
43
+ @and_rewriter = VirtualKeywords::AndRewriter.new
44
+
45
+ @my_and_calls = 0
46
+
47
+ VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
48
+ @operator_user, :and, my_and)
49
+ end
50
+
51
+ def rewriters
52
+ [@and_rewriter]
53
+ end
54
+
55
+ it 'rewrites &&' do
56
+ do_rewrite(:symbolic_and, @operator_user)
57
+ @my_and_calls.should eql 1
58
+ end
59
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'IfRewriter' do
4
+ include TrackIfs, DoRewrite
5
+
6
+ before :each do
7
+ @greeter = Greeter.new true
8
+ @methods = sexpify_instance_methods Greeter
9
+ @if_rewriter = VirtualKeywords::IfRewriter.new
10
+
11
+ @my_if_calls = 0
12
+
13
+ VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
14
+ @greeter, :if, my_if)
15
+ end
16
+
17
+ def rewriters
18
+ [@if_rewriter]
19
+ end
20
+
21
+ def greeter_rewrite_should_work(method_name,
22
+ required_calls = 1, verbose = false)
23
+ do_rewrite(method_name, @greeter, verbose = verbose)
24
+ @my_if_calls.should eql required_calls
25
+ end
26
+
27
+ it 'rewrites greet with if and else' do
28
+ greeter_rewrite_should_work :greet_if_else
29
+ end
30
+
31
+ it 'rewrites greet with if without else' do
32
+ # We don't need to do anything special for if without else
33
+ # They use the same sexp as if with else, with an empty block for the
34
+ # else clause
35
+ greeter_rewrite_should_work :greet_if_without_else
36
+ end
37
+
38
+ it 'rewrites greet with postfix if' do
39
+ # Again, we don't need to do anything special - they turn into the same sexp
40
+ greeter_rewrite_should_work :greet_postfix_if
41
+ end
42
+
43
+ it 'rewrites greet with if then else on one line' do
44
+ greeter_rewrite_should_work :greet_if_then_else
45
+ end
46
+
47
+ it 'rewrites greet with if then but no else on one line' do
48
+ greeter_rewrite_should_work :greet_if_then_no_else
49
+ end
50
+
51
+ it 'rewrites greet with unless' do
52
+ greeter_rewrite_should_work :greet_unless
53
+ end
54
+
55
+ it 'rewrites greet with unless and else' do
56
+ greeter_rewrite_should_work :greet_unless_else
57
+ end
58
+
59
+ it 'rewrites greet with postfix unless' do
60
+ greeter_rewrite_should_work :greet_postfix_unless
61
+ end
62
+
63
+ it 'combines ifs without interference' do
64
+ greeter_rewrite_should_work(:greet_all, required_calls = 5)
65
+ end
66
+
67
+ it 'handles nested ifs' do
68
+ greeter_rewrite_should_work(:greet_nested, required_calls = 2)
69
+ end
70
+
71
+ it 'rewrites ifs with compound clauses' do
72
+ greeter_rewrite_should_work :greet_block
73
+ end
74
+ end
@@ -2,100 +2,8 @@ require 'spec_helper'
2
2
 
3
3
  describe 'KeywordRewriter' do
4
4
  before :each do
5
- @sexp_processor = SexpProcessor.new
6
-
7
- def method_to_sexp(klass, method)
8
- @sexp_processor.process(ParseTree.translate(klass, method))
9
- end
10
-
11
- @greeter = Greeter.new true
12
- @and_user = AndUser.new false
13
- @or_user = OrUser.new true
14
- @operator_user = OperatorUser.new false
15
-
16
- @greet_if_else_sexp = method_to_sexp(Greeter, :greet_if_else)
17
- @greet_if_without_else_sexp = method_to_sexp(Greeter,
18
- :greet_if_without_else)
19
- @greet_postfix_if_sexp = method_to_sexp(Greeter, :greet_postfix_if)
20
- @greet_if_then_else_sexp = method_to_sexp(Greeter, :greet_if_then_else)
21
- @greet_if_then_no_else_sexp = method_to_sexp(Greeter,
22
- :greet_if_then_no_else)
23
- @greet_unless_sexp = method_to_sexp(Greeter, :greet_unless)
24
- @greet_unless_else_sexp = method_to_sexp(Greeter, :greet_unless_else)
25
- @greet_postfix_unless_sexp = method_to_sexp(Greeter, :greet_postfix_unless)
26
- @greet_all_sexp = method_to_sexp(Greeter, :greet_all)
27
- @greet_nested_sexp = method_to_sexp(Greeter, :greet_nested)
28
-
29
-
30
- @method_with_and_sexp = method_to_sexp(AndUser, :method_with_and)
31
- @if_with_and_sexp = method_to_sexp(AndUser, :if_with_and)
32
-
33
- @method_with_or_sexp = method_to_sexp(OrUser, :method_with_or)
34
- @if_with_or_sexp = method_to_sexp(OrUser, :if_with_or)
35
-
36
- @symbolic_and_sexp = method_to_sexp(OperatorUser, :symbolic_and)
37
-
38
- @greet_changed_sexp = method_to_sexp(Greeter, :greet_changed)
39
- @method_with_and_result_sexp = method_to_sexp(AndUser,
40
- :method_with_and_result)
41
- @symbolic_and_result_sexp = method_to_sexp(OperatorUser,
42
- :symbolic_and_result)
43
-
44
-
45
- @if_rewriter = VirtualKeywords::IfRewriter.new
46
- @and_rewriter = VirtualKeywords::AndRewriter.new
47
- @or_rewriter = VirtualKeywords::OrRewriter.new
48
-
49
- @my_if_calls = 0
50
- def increment_my_if_calls
51
- @my_if_calls += 1
52
- end
53
-
54
- @my_and_calls = 0
55
- def increment_my_and_calls
56
- @my_and_calls += 1
57
- end
58
- @my_or_calls = 0
59
- def increment_my_or_calls
60
- @my_or_calls += 1
61
- end
62
- $my_symbolic_and_calls = 0
63
-
64
- spec = self
65
- my_if = lambda { |condition, then_do, else_do|
66
- increment_my_if_calls()
67
- if condition.call
68
- then_do.call
69
- else
70
- else_do.call
71
- end
72
- }
73
- my_and = lambda { |first, second|
74
- increment_my_and_calls()
75
- first.call and second.call
76
- }
77
- my_or = lambda { |first, second|
78
- increment_my_or_calls()
79
- first.call or second.call
80
- }
81
-
82
- VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
83
- @greeter, :if, my_if)
84
-
85
- VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
86
- @and_user, :and, my_and)
87
-
88
- VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
89
- @and_user, :if, my_if)
90
-
91
- VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
92
- @or_user, :or, my_or)
93
-
94
- VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
95
- @or_user, :if, my_if)
96
-
97
- VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
98
- @operator_user, :and, my_and)
5
+ #@while_count_sexp = method_to_sexp(WhileUser, :while_count_to_value)
6
+ #@while_result_sexp = method_to_sexp(WhileUser, :while_result)
99
7
  end
100
8
 
101
9
  # These two "specs" produce sexps that I used to figure out how
@@ -140,108 +48,14 @@ describe 'KeywordRewriter' do
140
48
  #p @symbolic_and_result_sexp
141
49
  #puts ''
142
50
  #end
143
- #
144
- def do_rewrite(sexp, method_name, object, verbose = false)
145
- result1 = @if_rewriter.process sexp
146
- result2 = @and_rewriter.process result1
147
- result = @or_rewriter.process result2
148
- stringifier = VirtualKeywords::SexpStringifier.new
149
-
150
- # Visually inspecting this result, it appears to be right
151
- code_result = stringifier.stringify result
152
- if verbose
153
- puts code_result
154
- end
155
-
156
- # my_* are dummy methods that do not change behavior, so both the
157
- # old and new code should produce the same result,
158
- # except that @my_*_calls is incremented
159
- old_result = object.send method_name
160
- VirtualKeywords::ClassReflection.install_method_on_instance(object, code_result)
161
- new_result = object.send method_name
162
-
163
- new_result.should eql old_result
164
- end
165
-
166
- def greeter_rewrite_should_work(sexp, method_name,
167
- required_calls = 1, verbose = false)
168
- do_rewrite(sexp, method_name, @greeter, verbose = verbose)
169
- @my_if_calls.should eql required_calls
170
- end
171
-
172
- it 'rewrites greet with if and else' do
173
- greeter_rewrite_should_work(@greet_if_else_sexp, :greet_if_else)
174
- end
175
-
176
- it 'rewrites greet with if without else' do
177
- # We don't need to do anything special for if without else
178
- # They use the same sexp as if with else, with an empty block for the
179
- # else clause
180
- greeter_rewrite_should_work(@greet_if_without_else_sexp,
181
- :greet_if_without_else)
182
- end
183
51
 
184
- it 'rewrites greet with postfix if' do
185
- # Again, we don't need to do anything special - they turn into the same sexp
186
- greeter_rewrite_should_work(@greet_postfix_if_sexp, :greet_postfix_if)
187
- end
188
-
189
- it 'rewrites greet with if then else on one line' do
190
- greeter_rewrite_should_work(@greet_if_then_else_sexp,
191
- :greet_if_then_else)
192
- end
193
-
194
- it 'rewrites greet with if then but no else on one line' do
195
- greeter_rewrite_should_work(@greet_if_then_no_else_sexp,
196
- :greet_if_then_no_else)
197
- end
198
-
199
- it 'rewrites greet with unless' do
200
- greeter_rewrite_should_work(@greet_unless_sexp, :greet_unless)
201
- end
202
-
203
- it 'rewrites greet with unless and else' do
204
- greeter_rewrite_should_work(@greet_unless_else_sexp, :greet_unless_else)
205
- end
206
-
207
- it 'rewrites greet with postfix unless' do
208
- greeter_rewrite_should_work(@greet_postfix_unless_sexp,
209
- :greet_postfix_unless)
210
- end
211
-
212
- it 'combines ifs without interference' do
213
- greeter_rewrite_should_work(@greet_all_sexp, :greet_all, required_calls = 5)
214
- end
215
-
216
- it 'handles nested ifs' do
217
- greeter_rewrite_should_work(@greet_nested_sexp, :greet_nested,
218
- required_calls = 2)
219
- end
220
-
221
- it 'rewrites "and" statements' do
222
- do_rewrite(@method_with_and_sexp, :method_with_and, @and_user)
223
- @my_and_calls.should eql 1
224
- end
225
-
226
- it 'handles ifs with "and"s in the predicate' do
227
- do_rewrite(@if_with_and_sexp, :if_with_and, @and_user)
228
- @my_and_calls.should eql 1
229
- @my_if_calls.should eql 1
230
- end
231
-
232
- it 'rewrites "or" statements' do
233
- do_rewrite(@method_with_or_sexp, :method_with_or, @or_user)
234
- @my_or_calls.should eql 1
235
- end
236
-
237
- it 'handles ifs with "or"s in the predicate' do
238
- do_rewrite(@if_with_or_sexp, :if_with_or, @or_user)
239
- @my_or_calls.should eql 1
240
- @my_if_calls.should eql 1
241
- end
242
-
243
- it 'rewrites &&' do
244
- do_rewrite(@symbolic_and_sexp, :symbolic_and, @operator_user)
245
- @my_and_calls.should eql 1
246
- end
52
+ #it 'turns a case-when into a sexp' do
53
+ #p @describe_value_sexp
54
+ #end
55
+
56
+ #it 'turns a while into a sexp' do
57
+ #p @while_count_sexp
58
+ #puts ''
59
+ #p @while_result_sexp
60
+ #end
247
61
  end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'OrRewriter' do
4
+ include TrackIfs, TrackOrs, DoRewrite
5
+
6
+ before :each do
7
+ @or_user = OrUser.new true
8
+ @methods = sexpify_instance_methods OrUser
9
+ @if_rewriter = VirtualKeywords::IfRewriter.new
10
+ @or_rewriter = VirtualKeywords::OrRewriter.new
11
+
12
+ @my_if_calls = 0
13
+ @my_or_calls = 0
14
+
15
+ VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
16
+ @or_user, :or, my_or)
17
+ VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
18
+ @or_user, :if, my_if)
19
+ end
20
+
21
+ def rewriters
22
+ [@if_rewriter, @or_rewriter]
23
+ end
24
+
25
+ it 'rewrites "or" statements' do
26
+ do_rewrite(:method_with_or, @or_user)
27
+ @my_or_calls.should eql 1
28
+ end
29
+
30
+ it 'handles ifs with "or"s in the predicate' do
31
+ do_rewrite(:if_with_or, @or_user)
32
+ @my_or_calls.should eql 1
33
+ @my_if_calls.should eql 1
34
+ end
35
+ end
@@ -149,6 +149,20 @@ class Greeter < ApplicationController
149
149
  end
150
150
  end
151
151
 
152
+ def greet_block
153
+ # Multiple statements in the if/else clauses
154
+ if @hello
155
+ value = 5
156
+ value += 5
157
+
158
+ value
159
+ else
160
+ thing = 9
161
+
162
+ thing
163
+ end
164
+ end
165
+
152
166
  def count_to_ten
153
167
  [1..10].each do |index|
154
168
  puts index
@@ -212,7 +226,176 @@ class OperatorUser < ActiveRecord::Base
212
226
  end
213
227
  end
214
228
 
215
- RSpec.configure do |config|
216
- config.color_enabled = true
217
- config.formatter = 'documentation'
229
+ class WhileUser < ApplicationController
230
+ def initialize(value)
231
+ @value = value
232
+ @i = 0
233
+ @counts = []
234
+ end
235
+
236
+ def while_count_to_value
237
+ while @i < @value
238
+ @counts << @i
239
+ @i += 1
240
+ end
241
+
242
+ @counts
243
+ end
244
+
245
+ def while_result
246
+ my_while(
247
+ lambda { @i < value },
248
+ lambda do
249
+ @counts << @i
250
+ @i += 1
251
+ end
252
+ )
253
+ end
254
+ end
255
+
256
+ class CaseWhenUser < ApplicationController
257
+ def initialize(value)
258
+ @value = value
259
+ end
260
+
261
+ def describe_value
262
+ case @value
263
+ when 1
264
+ :one
265
+ when 3..5
266
+ :three_to_five
267
+ when 7, 9
268
+ :seven_or_nine
269
+ when value * 10 < 90
270
+ :passes_multiplication_test
271
+ else
272
+ :something_else
273
+ end
274
+ end
275
+ end
276
+
277
+ # Helper classes and functions for rewriter specs
278
+
279
+ # Given a class and a method name, return a sexpified method.
280
+ def method_to_sexp(klass, method)
281
+ SexpProcessor.new.process(ParseTree.translate(klass, method))
282
+ end
283
+
284
+ # Sexpify all non-inherited instance methods of a class and return them in
285
+ # a hash mapping names to sexps.
286
+ def sexpify_instance_methods klass
287
+ sexps = {}
288
+ klass.instance_methods(false).each do |method_name|
289
+ sexps[method_name.to_sym] = method_to_sexp(klass, method_name.to_sym)
290
+ end
291
+
292
+ sexps
293
+ end
294
+
295
+ module TrackIfs
296
+ @my_if_calls = 0 # Don't forget to reset this before each spec!
297
+
298
+ def increment_my_if_calls
299
+ @my_if_calls += 1
300
+ end
301
+
302
+ def my_if
303
+ # Dummy if that increments @my_if_calls, then runs as normal
304
+ @my_if ||= lambda { |condition, then_do, else_do|
305
+ increment_my_if_calls
306
+ if condition.call
307
+ then_do.call
308
+ else
309
+ else_do.call
310
+ end
311
+ }
312
+ end
313
+ end
314
+
315
+ module TrackAnds
316
+ @my_and_calls = 0
317
+
318
+ def increment_my_and_calls
319
+ @my_and_calls += 1
320
+ end
321
+
322
+ def my_and
323
+ # Dummy if that increments @my_if_calls, then runs as normal
324
+ @my_and ||= lambda { |first, second|
325
+ increment_my_and_calls
326
+ first.call and second.call
327
+ }
328
+ end
329
+ end
330
+
331
+ module TrackOrs
332
+ @my_or_calls = 0
333
+
334
+ def increment_my_or_calls
335
+ @my_or_calls += 1
336
+ end
337
+
338
+ def my_or
339
+ @my_or ||= lambda { |first, second|
340
+ increment_my_or_calls
341
+ first.call or second.call
342
+ }
343
+ end
344
+ end
345
+
346
+ module TrackWhiles
347
+ @my_while_calls = 0
348
+
349
+ def increment_my_while_calls
350
+ @my_while_calls += 1
351
+ end
352
+
353
+ def my_while
354
+ @my_while ||= lambda { |condition, body|
355
+ increment_my_while_calls
356
+ while condition.call
357
+ body.call
358
+ end
359
+ }
360
+ end
361
+ end
362
+
363
+ class Abstract < StandardError
364
+ end
365
+
366
+ module DoRewrite
367
+ # Override this and return a list of rewriters, in order, so do_rewrite
368
+ # can call them
369
+ def rewriters
370
+ raise Abstract
371
+ end
372
+
373
+ def do_rewrite(method_name, object, verbose = false)
374
+ sexp = @methods[method_name]
375
+ result = sexp
376
+ rewriters.each do |rewriter|
377
+ result = rewriter.process result
378
+ end
379
+ stringifier = VirtualKeywords::SexpStringifier.new
380
+
381
+ # Visually inspecting this result, it appears to be right
382
+ code_result = stringifier.stringify result
383
+ if verbose
384
+ puts code_result
385
+ end
386
+
387
+ # my_* are dummy methods that do not change behavior, so both the
388
+ # old and new code should produce the same result,
389
+ # except that @my_*_calls is incremented
390
+ old_result = object.send method_name
391
+ VirtualKeywords::ClassReflection.install_method_on_instance(object, code_result)
392
+ new_result = object.send method_name
393
+
394
+ new_result.should eql old_result
395
+ end
396
+ end
397
+
398
+ RSpec.configure do |configuration|
399
+ configuration.color_enabled = true
400
+ configuration.formatter = 'documentation'
218
401
  end
@@ -113,12 +113,25 @@ describe 'Virtualizer' do
113
113
  end
114
114
  end
115
115
 
116
+ class Counter
117
+ def run
118
+ a = []
119
+ i = 0
120
+ while i <= 10
121
+ a << i
122
+ end
123
+
124
+ a
125
+ end
126
+ end
127
+
116
128
  @my_class = MyClass.new
117
129
  @another_class = AnotherClass.new
118
130
  @yet_another_class = YetAnotherClass.new
119
131
  @operator_user = OperatorUser.new false
132
+ @counter = Counter.new
120
133
  @virtualizer = VirtualKeywords::Virtualizer.new(
121
- :for_instances => [@greeter, @my_class]
134
+ :for_instances => [@greeter, @my_class, @counter]
122
135
  )
123
136
  @class_virtualizer = VirtualKeywords::Virtualizer.new(
124
137
  :for_classes => [AnotherClass]
@@ -178,4 +191,14 @@ describe 'Virtualizer' do
178
191
  @another_class.quux.should eql :right
179
192
  @yet_another_class.quux.should eql :if_modified
180
193
  end
194
+
195
+ it 'virtualizes "while" on instances' do
196
+ @virtualizer.virtual_while do |condition, body|
197
+ # call the body once, regardless of condition
198
+ body.call
199
+ end
200
+
201
+ result = @counter.run
202
+ result.should eql [0]
203
+ end
181
204
  end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'WhileRewriter' do
4
+ include TrackWhiles, DoRewrite
5
+
6
+ before :each do
7
+ @while_user = WhileUser.new 10
8
+ @methods = sexpify_instance_methods WhileUser
9
+ @while_rewriter = VirtualKeywords::WhileRewriter.new
10
+
11
+ @my_while_calls = 0
12
+
13
+ VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
14
+ @while_user, :while, my_while)
15
+ end
16
+
17
+ def rewriters
18
+ [@while_rewriter]
19
+ end
20
+
21
+ it 'rewrites "while" expressions' do
22
+ do_rewrite(:while_count_to_value, @while_user)
23
+ @my_while_calls.should eql 1
24
+ end
25
+ end
@@ -1,4 +1,7 @@
1
1
  module VirtualKeywords
2
+ # SexpProcessor subclass that rewrites "if" expressions.
3
+ # Calls VirtualKeywords::REWRITTEN_KEYWORDS.call_if and lets it pick a
4
+ # virtual implementation.
2
5
  class IfRewriter < SexpProcessor
3
6
  # Initialize an IfRewriter (self.strict is false)
4
7
  def initialize
@@ -25,6 +28,9 @@ module VirtualKeywords
25
28
  then_do = expression[2]
26
29
  else_do = expression[3]
27
30
 
31
+ # This ugly sexp turns into the following Ruby code:
32
+ # VirtualKeywords::REWRITTEN_KEYWORDS.call_if(
33
+ # self, lambda { condition }, lambda { then_do }, lambda { else_do })
28
34
  s(:call,
29
35
  s(:colon2,
30
36
  s(:const, :VirtualKeywords),
@@ -45,11 +51,11 @@ module VirtualKeywords
45
51
  #
46
52
  # Arguments:
47
53
  # method_name: (Symbol) the name of the REWRITTEN_KEYWORDS method that
48
- # should be called in the sexp.
54
+ # should be called in the sexp.
49
55
  # first: (Sexp) the first argument to the method, which should be
50
- # wrapped in a lambda then passed to REWRITTEN_KEYWORDS.
56
+ # wrapped in a lambda then passed to REWRITTEN_KEYWORDS.
51
57
  # second: (Sexp) the second argument to the method, which should be
52
- # wrapped in a lambda then passed to REWRITTEN_KEYWORDS.
58
+ # wrapped in a lambda then passed to REWRITTEN_KEYWORDS.
53
59
  def self.call_operator_replacement(function_name, first, second)
54
60
  s(:call,
55
61
  s(:colon2,
@@ -64,6 +70,7 @@ module VirtualKeywords
64
70
  )
65
71
  end
66
72
 
73
+ # SexpProcessor subclass that rewrites "and" expressions.
67
74
  class AndRewriter < SexpProcessor
68
75
  def initialize
69
76
  super
@@ -85,6 +92,7 @@ module VirtualKeywords
85
92
  end
86
93
  end
87
94
 
95
+ # SexpProcessor subclass that rewrites "or" expressions.
88
96
  class OrRewriter < SexpProcessor
89
97
  def initialize
90
98
  super
@@ -105,4 +113,39 @@ module VirtualKeywords
105
113
  VirtualKeywords.call_operator_replacement(:call_or, first, second)
106
114
  end
107
115
  end
116
+
117
+ class UnexpectedSexp < StandardError
118
+ end
119
+
120
+ class WhileRewriter < SexpProcessor
121
+ def initialize
122
+ super
123
+ self.strict = false
124
+ end
125
+
126
+ def rewrite_while(expression)
127
+ condition = expression[1]
128
+ body = expression[2]
129
+
130
+ # This was a true in the example I checked (in sexps_while.txt)
131
+ # but I'm not sure what it's for.
132
+ third = expression[3]
133
+ if third != true # Should be true, not just a truthy object
134
+ raise UnexpectedSexp, 'Expected true as the 3rd element in a while, ' +
135
+ "but got #{third}, this is probably a bug."
136
+ end
137
+
138
+ s(:call,
139
+ s(:colon2,
140
+ s(:const, :VirtualKeywords),
141
+ :REWRITTEN_KEYWORDS
142
+ ), :call_while,
143
+ s(:array,
144
+ s(:self),
145
+ s(:iter, s(:fcall, :lambda), nil, condition),
146
+ s(:iter, s(:fcall, :lambda), nil, body)
147
+ )
148
+ )
149
+ end
150
+ end
108
151
  end
@@ -17,10 +17,9 @@ module VirtualKeywords
17
17
  # Arguments:
18
18
  # A Hash with the following key:
19
19
  # predicates_to_blocks: (Hash[Proc, Proc]) a hash mapping predicates that
20
- # take ObjectAndKeywords and return true for matches
21
- # to the lambdas that should be called in place of
22
- # the keyword in the object's methods
23
- # (optional, an empty Hash is the default).
20
+ # take ObjectAndKeywords and return true for matches to the lambdas
21
+ # that should be called in place of the keyword in the object's
22
+ # methods (optional, an empty Hash is the default).
24
23
  def initialize(input)
25
24
  @predicates_to_blocks = input[:predicates_to_blocks] || {}
26
25
  end
@@ -72,7 +71,7 @@ module VirtualKeywords
72
71
  def lambda_or_raise(caller_object, keyword)
73
72
  object_and_keyword = ObjectAndKeyword.new(caller_object, keyword)
74
73
  matching = @predicates_to_blocks.keys.find { |predicate|
75
- predicate.call(object_and_keyword)
74
+ predicate.call object_and_keyword
76
75
  }
77
76
 
78
77
  if matching.nil?
@@ -90,11 +89,11 @@ module VirtualKeywords
90
89
  # Arguments:
91
90
  # caller_object: (Object) the object whose method this is being called in.
92
91
  # condition: (Proc) The condition of the if statement, wrapped in a
93
- # lambda.
92
+ # lambda.
94
93
  # then_do: (Proc) the lambda to execute if the condition is true (but
95
- # the user-supplied block may do something else)
94
+ # the user-supplied block may do something else)
96
95
  # else_do: (Proc) the lambda to execute if the condition is false (but
97
- # the user-supplied block may do something else)
96
+ # the user-supplied block may do something else)
98
97
  #
99
98
  # Raises:
100
99
  # RewriteLambdaNotProvided if no "if" lambda is available.
@@ -130,6 +129,21 @@ module VirtualKeywords
130
129
  or_lambda = lambda_or_raise(caller_object, :or)
131
130
  or_lambda.call(first, second)
132
131
  end
132
+
133
+ # Call a "while" virtual block in place of an "while" expression.
134
+ #
135
+ # Arguments:
136
+ # caller_object: (Object) the object whose method this is being called in.
137
+ # condition: (Proc) The condition of the while expression.
138
+ # body: (Proc) The body of the while expression (which is normally
139
+ # executed repeatedly)
140
+ #
141
+ # Raises:
142
+ # RewriteLambdaNotProvided if no "while" lambda is available.
143
+ def call_while(caller_object, condition, body)
144
+ while_lambda = lambda_or_raise(caller_object, :while)
145
+ while_lambda.call(condition, body)
146
+ end
133
147
  end
134
148
 
135
149
 
@@ -1,3 +1,3 @@
1
1
  module VirtualKeywords
2
- VERSION = '0.0.0'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -58,9 +58,9 @@ module VirtualKeywords
58
58
  # Arguments:
59
59
  # klass: (Class) the class which should be modified.
60
60
  # method_code: (String) the code for the method to install, of the format:
61
- # def method_name(args)
62
- # ...
63
- # end
61
+ # def method_name(args)
62
+ # ...
63
+ # end
64
64
  def self.install_method_on_class(klass, method_code)
65
65
  klass.class_eval method_code
66
66
  end
@@ -76,9 +76,9 @@ module VirtualKeywords
76
76
  # Arguments:
77
77
  # object: (Object) the object instance that should be modified.
78
78
  # method_code: (String) the code for the method to install, of the format:
79
- # def method_name(args)
80
- # ...
81
- # end
79
+ # def method_name(args)
80
+ # ...
81
+ # end
82
82
  def self.install_method_on_instance(object, method_code)
83
83
  object.instance_eval method_code
84
84
  end
@@ -102,33 +102,36 @@ module VirtualKeywords
102
102
  # Arguments:
103
103
  # A Hash with the following arguments (all optional):
104
104
  # for_classes: (Array[Class]) an array of classes. All methods of objects
105
- # created from the given classes will be virtualized
106
- # (optional, the default is an empty Array).
105
+ # created from the given classes will be virtualized (optional, the
106
+ # default is an empty Array).
107
107
  # for_instances: (Array[Object]) an array of object. All of these objects'
108
- # methods will be virtualized
109
- # (optional, the default is an empty Array).
108
+ # methods will be virtualized
109
+ # (optional, the default is an empty Array).
110
110
  # for subclasses_of: (Array[Class]) an array of classes. All methods of
111
- # objects created from the given classes' subclasses
112
- # (but NOT those from the given classes) will be
113
- # virtualized.
111
+ # objects created from the given classes' subclasses (but NOT those
112
+ # from the given classes) will be virtualized.
114
113
  # if_rewriter: (IfRewriter) the SexpProcessor descendant that
115
- # rewrites "if"s in methods (optional, the default is
116
- # IfRewriter.new).
114
+ # rewrites "if"s in methods (optional, the default is
115
+ # IfRewriter.new).
117
116
  # and_rewriter: (AndRewriter) the SexpProcessor descendant that
118
- # rewrites "and"s in methods (optional, the default is
119
- # AndRewriter.new).
117
+ # rewrites "and"s in methods (optional, the default is
118
+ # AndRewriter.new).
120
119
  # or_rewriter: (OrRewriter) the SexpProcessor descendant that
121
- # rewrites "or"s in methods (optional, the default is
122
- # OrRewriter.new).
120
+ # rewrites "or"s in methods (optional, the default is
121
+ # OrRewriter.new).
122
+ # while_rewriter: (WhileRewriter) the SexpProcessor descendant that
123
+ # rewrites "while"s in methods (optional, the default is
124
+ # WhileRewriter.new).
123
125
  # sexp_processor: (SexpProcessor) the sexp_processor that can turn
124
- # ParseTree results into sexps (optional, the default is
125
- # SexpProcessor.new).
126
+ # ParseTree results into sexps (optional, the default is
127
+ # SexpProcessor.new).
126
128
  # sexp_stringifier: (SexpStringifier) an object that can turn sexps
127
- # back into Ruby code (optional, the default is
128
- # SexpStringifier.new).
129
+ # back into Ruby code (optional, the default is
130
+ # SexpStringifier.new).
129
131
  # rewritten_keywords: (RewrittenKeywords) a repository for keyword
130
- # replacement lambdas (optional, the default is
131
- # REWRITTEN_KEYWORDS).
132
+ # replacement lambdas (optional, the default is REWRITTEN_KEYWORDS).
133
+ # class_reflection: (Class) an object that provides methods to modify the
134
+ # methods of classes (optional, the default is ClassReflection).
132
135
  def initialize(input_hash)
133
136
  @for_classes = input_hash[:for_classes] || []
134
137
  @for_instances = input_hash[:for_instances] || []
@@ -136,19 +139,21 @@ module VirtualKeywords
136
139
  @if_rewriter = input_hash[:if_rewriter] || IfRewriter.new
137
140
  @and_rewriter = input_hash[:and_rewriter] || AndRewriter.new
138
141
  @or_rewriter = input_hash[:or_rewriter] || OrRewriter.new
142
+ @while_rewriter = input_hash[:while_rewriter] || WhileRewriter.new
139
143
  @sexp_processor = input_hash[:sexp_processor] || SexpProcessor.new
140
144
  @sexp_stringifier = input_hash[:sexp_stringifier] || SexpStringifier.new
141
145
  @rewritten_keywords =
142
146
  input_hash[:rewritten_keywords] || REWRITTEN_KEYWORDS
147
+ @class_reflection = input_hash[:class_reflection] || ClassReflection
143
148
  end
144
149
 
145
150
  # Helper method to rewrite code.
146
151
  #
147
152
  # Arguments:
148
153
  # translated: (Array) the output of ParseTree.translate on the original
149
- # code
154
+ # code
150
155
  # rewriter: (SexpProcessor) the object that will rewrite the sexp, to
151
- # virtualize the keywords.
156
+ # virtualize the keywords.
152
157
  def rewritten_code(translated, rewriter)
153
158
  sexp = @sexp_processor.process(
154
159
  VirtualKeywords.deep_copy_array(translated))
@@ -166,10 +171,10 @@ module VirtualKeywords
166
171
  def rewrite_methods_of_instance(instance, keyword, rewriter, block)
167
172
  @rewritten_keywords.register_lambda_for_object(instance, keyword, block)
168
173
 
169
- methods = ClassReflection.instance_methods_of instance.class
174
+ methods = @class_reflection.instance_methods_of instance.class
170
175
  methods.each do |name, translated|
171
176
  new_code = rewritten_code(translated, rewriter)
172
- ClassReflection.install_method_on_instance(instance, new_code)
177
+ @class_reflection.install_method_on_instance(instance, new_code)
173
178
  end
174
179
  end
175
180
 
@@ -181,12 +186,12 @@ module VirtualKeywords
181
186
  # rewriter: (SexpProcessor) the object that will do the rewriting.
182
187
  # block: (Proc) the lambda that will replace the keyword.
183
188
  def rewrite_methods_of_class(klass, keyword, rewriter, block)
184
- @rewritten_keywords.register_lambda_for_class(klass, keyword, block)
189
+ @rewritten_keywords.register_lambda_for_class(klass, keyword, block)
185
190
 
186
- methods = ClassReflection.instance_methods_of klass
191
+ methods = @class_reflection.instance_methods_of klass
187
192
  methods.each do |name, translated|
188
193
  new_code = rewritten_code(translated, rewriter)
189
- ClassReflection.install_method_on_class(klass, new_code)
194
+ @class_reflection.install_method_on_class(klass, new_code)
190
195
  end
191
196
  end
192
197
 
@@ -205,7 +210,7 @@ module VirtualKeywords
205
210
  rewrite_methods_of_class(klass, keyword, rewriter, block)
206
211
  end
207
212
 
208
- subclasses = ClassReflection.subclasses_of_classes @for_subclasses_of
213
+ subclasses = @class_reflection.subclasses_of_classes @for_subclasses_of
209
214
  subclasses.each do |subclass|
210
215
  rewrite_methods_of_class(subclass, keyword, rewriter, block)
211
216
  end
@@ -215,7 +220,7 @@ module VirtualKeywords
215
220
  #
216
221
  # Arguments:
217
222
  # &block: The block that will replace "if"s in the objects being
218
- # virtualized
223
+ # virtualized
219
224
  def virtual_if(&block)
220
225
  virtualize_keyword(:if, @if_rewriter, block)
221
226
  end
@@ -224,7 +229,7 @@ module VirtualKeywords
224
229
  #
225
230
  # Arguments:
226
231
  # &block: The block that will replace "and"s in the objects being
227
- # virtualized
232
+ # virtualized
228
233
  def virtual_and(&block)
229
234
  virtualize_keyword(:and, @and_rewriter, block)
230
235
  end
@@ -233,9 +238,18 @@ module VirtualKeywords
233
238
  #
234
239
  # Arguments:
235
240
  # &block: The block that will replace "or"s in the objects being
236
- # virtualized
241
+ # virtualized
237
242
  def virtual_or(&block)
238
243
  virtualize_keyword(:or, @or_rewriter, block)
239
244
  end
245
+
246
+ # Rewrite "while" expressions.
247
+ #
248
+ # Arguments:
249
+ # &block: The block that will replace "while"s in the objects being
250
+ # virtualized
251
+ def virtual_while(&block)
252
+ virtualize_keyword(:while, @while_rewriter, block)
253
+ end
240
254
  end
241
255
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: virtual_keywords
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 0
10
- version: 0.0.0
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Rahul Rajagopalan
@@ -88,17 +88,23 @@ files:
88
88
  - lib/virtual_keywords/keyword_rewriter.rb
89
89
  - lib/virtual_keywords/version.rb
90
90
  - lib/virtual_keywords/virtualizer.rb
91
+ - lib/sexps/sexps_while.txt
91
92
  - lib/sexps/count_to_ten_sexp.txt
92
93
  - lib/sexps/sexps_and.txt
93
94
  - lib/sexps/sexps_symbolic_and.txt
95
+ - lib/sexps/sexps_case_when.txt
94
96
  - lib/sexps/sexps_rewritten_keywords.txt
95
97
  - lib/sexps/sexps_greet.txt
96
98
  - lib/virtual_keywords.rb
97
99
  - lib/spec/virtualizer_spec.rb
100
+ - lib/spec/while_rewriter_spec.rb
101
+ - lib/spec/and_rewriter_spec.rb
102
+ - lib/spec/if_rewriter_spec.rb
98
103
  - lib/spec/rewritten_keywords_spec.rb
99
104
  - lib/spec/keyword_rewriter_spec.rb
100
105
  - lib/spec/class_mirrorer_spec.rb
101
106
  - lib/spec/spec_helper.rb
107
+ - lib/spec/or_rewriter_spec.rb
102
108
  - lib/Rakefile
103
109
  homepage: http://github.com/rahulraj/virtual_keywords
104
110
  licenses: []