virtual_keywords 0.0.0 → 0.1.0

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,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: []