virtual_keywords 0.0.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.
data/lib/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec)
4
+
5
+ task :default => :spec
@@ -0,0 +1,12 @@
1
+ # Sexp for Greeter#count_to_ten
2
+ s(:defn, :count_to_ten,
3
+ s(:scope,
4
+ s(:block, s(:args),
5
+ s(:iter,
6
+ s(:call, s(:array, s(:lit, 1..10)), :each),
7
+ s(:dasgn_curr, :index),
8
+ s(:fcall, :puts, s(:array, s(:dvar, :index)))
9
+ )
10
+ )
11
+ )
12
+ )
@@ -0,0 +1,29 @@
1
+ # Sexps from translating Greeter#method_with_and and
2
+ # Greeter#method_with_and_result (example_classes.rb)
3
+
4
+ # before
5
+ s(:defn, :method_with_and,
6
+ s(:scope,
7
+ s(:block, s(:args),
8
+ s(:and, s(:ivar, :@hello), s(:true))
9
+ )
10
+ )
11
+ )
12
+
13
+ # after
14
+ s(:defn, :method_with_and_result,
15
+ s(:scope,
16
+ s(:block, s(:args),
17
+ s(:fcall, :my_and,
18
+ s(:array,
19
+ s(:iter,
20
+ s(:fcall, :lambda), nil, s(:ivar, :@hello)
21
+ ),
22
+ s(:iter,
23
+ s(:fcall, :lambda), nil, s(:true)
24
+ )
25
+ )
26
+ )
27
+ )
28
+ )
29
+ )
@@ -0,0 +1,63 @@
1
+ # Sexps gotten from translating Greeter#greet and Greeter#greet_changed
2
+ # from example_classes.rb
3
+
4
+ # before translation (formatted manually)
5
+ s(:defn, :greet,
6
+ s(:scope,
7
+ s(:block, s(:args),
8
+ s(:if, s(:ivar, :@hello),
9
+ s(:str, "Hello World!"),
10
+ s(:str, "Goodbye")
11
+ )
12
+ )
13
+ )
14
+ )
15
+
16
+ # after translation
17
+ s(:defn, :greet_changed,
18
+ s(:scope,
19
+ s(:block, s(:args),
20
+ s(:fcall, :my_if,
21
+ s(:array,
22
+ s(:iter,
23
+ s(:fcall, :lambda), nil, s(:ivar, :@hello)
24
+ ),
25
+ s(:iter,
26
+ s(:fcall, :lambda), nil, s(:str, "Hello World!")
27
+ ),
28
+ s(:iter,
29
+ s(:fcall, :lambda), nil, s(:str, "Goodbye")
30
+ )
31
+ )
32
+ )
33
+ )
34
+ )
35
+ )
36
+
37
+ # OK, so here's the rule that I'm inferring from this example:
38
+
39
+ # We start with
40
+ s(:if, condition, then_do, else_do)
41
+ # Where condition, then_do, and else_do are "variables" containing
42
+ # arbitrary sexps
43
+
44
+ # Our rewriter should turn this into
45
+ s(:fcall, :my_if,
46
+ s(:array,
47
+ s(:iter,
48
+ s(:fcall, :lambda), nil, ~condition
49
+ ),
50
+ s(:iter,
51
+ s(:fcall, :lambda), nil, ~then_do
52
+ ),
53
+ s(:iter,
54
+ s(:fcall, :lambda), nil, ~else_do
55
+ )
56
+ )
57
+ )
58
+ # Where ~condition is the expansion of condition, inserting the
59
+ # sexp it points to, and similar for then_do and else_do
60
+
61
+ # Question: What does the :iter part mean exactly?
62
+ # Also, how exactly does the call to lambda map to the sexp?
63
+ # It takes a block, so that complicates the syntax.
@@ -0,0 +1,36 @@
1
+ # before
2
+ s(:defn, :greet_if_else,
3
+ s(:scope,
4
+ s(:block, s(:args),
5
+ s(:if,
6
+ s(:ivar, :@hello),
7
+ s(:str, "Hello World! (if else)"),
8
+ s(:call, s(:str, "Good"), :+, s(:array, s(:str, "bye (if else)")))
9
+ )
10
+ )
11
+ )
12
+ )
13
+
14
+ #after
15
+ s(:defn, :greet_changed,
16
+ s(:scope,
17
+ s(:block, s(:args),
18
+ s(:call,
19
+ s(:colon2,
20
+ s(:const, :VirtualKeywords),
21
+ :REWRITTEN_KEYWORDS
22
+ ),
23
+ :call_if,
24
+ s(:array,
25
+ s(:self),
26
+ s(:iter, s(:fcall, :lambda), nil, s(:ivar, :@hello)),
27
+ s(:iter, s(:fcall, :lambda), nil, s(:str, "Hello World! (if else)")),
28
+ s(:iter,
29
+ s(:fcall, :lambda),
30
+ nil, s(:call, s(:str, "Good"), :+, s(:array, s(:str, "bye (if else)")))
31
+ )
32
+ )
33
+ )
34
+ )
35
+ )
36
+ )
@@ -0,0 +1,25 @@
1
+ s(:defn, :symbolic_and,
2
+ s(:scope,
3
+ s(:block, s(:args),
4
+ s(:and, s(:ivar, :@value), s(:false))
5
+ )
6
+ )
7
+ )
8
+
9
+ # after
10
+ s(:defn, :symbolic_and_result,
11
+ s(:scope,
12
+ s(:block, s(:args),
13
+ s(:fcall, :my_conditional_and,
14
+ s(:array,
15
+ s(:iter,
16
+ s(:fcall, :lambda), nil, s(:ivar, :@value)
17
+ ),
18
+ s(:iter,
19
+ s(:fcall, :lambda), nil, s(:false)
20
+ )
21
+ )
22
+ )
23
+ )
24
+ )
25
+ )
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ClassMirrorer' do
4
+ before :each do
5
+ @stub_parser = double('parser')
6
+ end
7
+
8
+ it 'mirrors given classes' do
9
+ @stub_parser.stub(:translate).and_return('translated')
10
+ mirrorer = VirtualKeywords::ClassMirrorer.new @stub_parser
11
+ result = mirrorer.mirror [Fizzbuzzer]
12
+
13
+ class_and_method = VirtualKeywords::ClassAndMethodName.new(
14
+ Fizzbuzzer, 'fizzbuzz')
15
+ result.keys.should include class_and_method
16
+ result[class_and_method].should eql 'translated'
17
+ end
18
+ end
@@ -0,0 +1,247 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'KeywordRewriter' do
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)
99
+ end
100
+
101
+ # These two "specs" produce sexps that I used to figure out how
102
+ # to do the rewrite. Their outputs are in sexps_greet.txt and
103
+ # count_to_ten_sexp.txt
104
+
105
+ #it 'compares sexps of manually translated if' do
106
+ #puts 'before translation'
107
+ #p @greet_if_else_sexp
108
+ #puts ''
109
+
110
+ #puts 'after translation'
111
+ #p @greet_changed_sexp
112
+ #puts ''
113
+ #end
114
+
115
+ #it 'turns a method with block code into a sexp' do
116
+ #count_sexp = method_to_sexp(Greeter, :count_to_ten)
117
+ #p count_sexp
118
+ #end
119
+
120
+ # Spec used to see how "and" should be translated
121
+ #it 'compares sexps of manually translated and' do
122
+ #puts 'before'
123
+ #p @method_with_and_sexp
124
+ #puts ''
125
+
126
+ #puts 'after'
127
+ #p @method_with_and_result_sexp
128
+ #puts ''
129
+ #end
130
+
131
+ # Spec used to see how && should be translated
132
+ # Looks like it uses :and same as the other one
133
+ # Aren't they different semantically though?
134
+ #it 'compares sexps of manually translated &&' do
135
+ #puts 'before'
136
+ #p @symbolic_and_sexp
137
+ #puts ''
138
+
139
+ #puts 'after'
140
+ #p @symbolic_and_result_sexp
141
+ #puts ''
142
+ #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
+
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
247
+ end
@@ -0,0 +1,28 @@
1
+ describe 'RewrittenKeywords' do
2
+ before :each do
3
+ @rewritten_keywords = VirtualKeywords::RewrittenKeywords.new({})
4
+ end
5
+
6
+ it 'has no lambdas initially' do
7
+ lambda { @rewritten_keywords.lambda_or_raise(4, :if) }.
8
+ should raise_error VirtualKeywords::RewriteLambdaNotProvided
9
+ end
10
+
11
+ it 'registers lambdas for objects' do
12
+ number = 5
13
+ keyword = :if
14
+ the_lambda = lambda {}
15
+ @rewritten_keywords.register_lambda_for_object(number, keyword, the_lambda)
16
+ @rewritten_keywords.lambda_or_raise(number, keyword).should eql the_lambda
17
+ end
18
+
19
+ it 'registers lambdas for classes' do
20
+ class MyClass
21
+ end
22
+ my_class = MyClass.new
23
+ keyword = :if
24
+ the_lambda = lambda {}
25
+ @rewritten_keywords.register_lambda_for_class(MyClass, keyword, the_lambda)
26
+ @rewritten_keywords.lambda_or_raise(my_class, keyword).should eql the_lambda
27
+ end
28
+ end
@@ -0,0 +1,218 @@
1
+ require 'sexp_processor'
2
+ require 'parse_tree'
3
+ require 'ruby2ruby'
4
+
5
+ require 'virtual_keywords/sexp_stringifier'
6
+ require 'virtual_keywords/class_mirrorer'
7
+ require 'virtual_keywords/virtualizer'
8
+ require 'virtual_keywords/keyword_rewriter'
9
+ require 'virtual_keywords/rewritten_keywords'
10
+
11
+ require 'rspec'
12
+
13
+ # Classes the specs should use.
14
+ module ActiveRecord
15
+ class Base
16
+ end
17
+ end
18
+
19
+ class ApplicationController
20
+ end
21
+
22
+ class Fizzbuzzer < ActiveRecord::Base
23
+ def fizzbuzz(n)
24
+ (1..n).map { |i|
25
+ if i % 3 == 0 and i % 5 == 0
26
+ "fizzbuzz"
27
+ elsif i % 3 == 0
28
+ "fizz"
29
+ elsif i % 5 == 0
30
+ "buzz"
31
+ else
32
+ i.to_s
33
+ end
34
+ }
35
+ end
36
+ end
37
+
38
+ class Greeter < ApplicationController
39
+ def initialize(hello)
40
+ @hello = hello
41
+ end
42
+
43
+ # The following two methods are before/after examples. The rewriter
44
+ # should turn greet's body into greet_changed's. Running ParseTree over
45
+ # them, I got two sexps (in sexps_greet.txt), which I read to
46
+ # reverse-engineer the format. There may be edge cases (Ruby's grammar
47
+ # is much more complex than Lisp's!)
48
+ #
49
+ # spec/if_processor_spec runs a test over greet
50
+
51
+ # An example conditional: if and else
52
+ def greet_if_else
53
+ if @hello
54
+ 'Hello World! (if else)'
55
+ else
56
+ # Compound expressions should be preserved
57
+ # (i.e. not evaluated too early or in the wrong context)
58
+ 'Good' + 'bye (if else)'
59
+ end
60
+ end
61
+
62
+ # If without else
63
+ def greet_if_without_else
64
+ if @hello
65
+ 'Hello World! (if without else)'
66
+ end
67
+ end
68
+
69
+ # Postfix if
70
+ def greet_postfix_if
71
+ 'Hello World! (postfix if)' if @hello
72
+ end
73
+
74
+ # If, then, else
75
+ def greet_if_then_else
76
+ if @hello then 'Hello World! (if then else)' else 'Goodbye (if then else)' end
77
+ end
78
+
79
+ # If, then, no else
80
+ def greet_if_then_no_else
81
+ if @hello then 'Hello World! (if then)' end
82
+ end
83
+
84
+ # Unless
85
+ def greet_unless
86
+ unless @hello
87
+ 'Goodbye (unless)'
88
+ end
89
+ end
90
+
91
+ # Unless, then else
92
+ def greet_unless_else
93
+ unless @hello
94
+ 'Goodbye (unless else)'
95
+ else
96
+ 'Hello World! (unless else)'
97
+ end
98
+ end
99
+
100
+ # Postfix unless
101
+ def greet_postfix_unless
102
+ 'Goodbye (postfix unless)' unless @hello
103
+ end
104
+
105
+ # What the conditional in greet should look like after processing
106
+ def greet_changed
107
+ VirtualKeywords::REWRITTEN_KEYWORDS.call_if(
108
+ self, lambda { @hello }, lambda { 'Hello World! (if else)' },
109
+ lambda { 'Good' + 'bye (if else)' })
110
+ end
111
+
112
+ # All together now
113
+ def greet_all
114
+ result = ''
115
+ # 1
116
+ if @hello
117
+ result = 'Hello'
118
+ else
119
+ result = 'Goodbye'
120
+ end
121
+ # 2
122
+ if 2 + 2 == 4
123
+ result += '\nMath is right'
124
+ end
125
+ # 3
126
+ result += '\nThis is supposed to look like English' if false
127
+ # 4
128
+ unless 2 + 9 == 10
129
+ result += '\nMath should work in unless too'
130
+ end
131
+ # 5
132
+ result += '\nWorld!' unless true
133
+
134
+ result
135
+ end
136
+
137
+ def greet_nested
138
+ if true
139
+ # This if should be processed, even though it never happens!
140
+ if 2 + 2 == 4
141
+ 'Math is right'
142
+ else
143
+ 'Weird'
144
+ end
145
+ else
146
+ # This if should be expanded, but NOT evaluated!
147
+ puts 'hi there' if true
148
+ 'The false case'
149
+ end
150
+ end
151
+
152
+ def count_to_ten
153
+ [1..10].each do |index|
154
+ puts index
155
+ end
156
+ end
157
+ end
158
+
159
+ class AndUser < ActiveRecord::Base
160
+ def initialize(value)
161
+ @value = value
162
+ end
163
+
164
+ def method_with_and
165
+ @value and true
166
+ end
167
+
168
+ def if_with_and
169
+ if @value and true
170
+ 'Both @value and true were true (the latter is no surprise)'
171
+ else
172
+ '@value must have been false, I doubt true was false!'
173
+ end
174
+ end
175
+
176
+ def method_with_and_result
177
+ my_and(lambda { @value }, lambda { true })
178
+ end
179
+ end
180
+
181
+ class OrUser < ApplicationController
182
+ def initialize(value)
183
+ @value = value
184
+ end
185
+
186
+ def method_with_or
187
+ @value or false
188
+ end
189
+
190
+ def if_with_or
191
+ if @value or true
192
+ 'Both @value or true were true (the latter is no surprise)'
193
+ else
194
+ "This can't happen!"
195
+ end
196
+ end
197
+ end
198
+
199
+ # Ruby also lets you use the && and || operators
200
+ # I think they have different precedence rules
201
+ class OperatorUser < ActiveRecord::Base
202
+ def initialize(value)
203
+ @value = value
204
+ end
205
+
206
+ def symbolic_and
207
+ @value && false
208
+ end
209
+
210
+ def symbolic_and_result
211
+ my_conditional_and(lambda { @value }, lambda { false })
212
+ end
213
+ end
214
+
215
+ RSpec.configure do |config|
216
+ config.color_enabled = true
217
+ config.formatter = 'documentation'
218
+ end