virtual_keywords 0.0.0

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