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 +5 -0
- data/lib/sexps/count_to_ten_sexp.txt +12 -0
- data/lib/sexps/sexps_and.txt +29 -0
- data/lib/sexps/sexps_greet.txt +63 -0
- data/lib/sexps/sexps_rewritten_keywords.txt +36 -0
- data/lib/sexps/sexps_symbolic_and.txt +25 -0
- data/lib/spec/class_mirrorer_spec.rb +18 -0
- data/lib/spec/keyword_rewriter_spec.rb +247 -0
- data/lib/spec/rewritten_keywords_spec.rb +28 -0
- data/lib/spec/spec_helper.rb +218 -0
- data/lib/spec/virtualizer_spec.rb +181 -0
- data/lib/virtual_keywords/class_mirrorer.rb +39 -0
- data/lib/virtual_keywords/keyword_rewriter.rb +108 -0
- data/lib/virtual_keywords/rewritten_keywords.rb +141 -0
- data/lib/virtual_keywords/sexp_stringifier.rb +30 -0
- data/lib/virtual_keywords/version.rb +3 -0
- data/lib/virtual_keywords/virtualizer.rb +241 -0
- data/lib/virtual_keywords.rb +33 -0
- metadata +137 -0
data/lib/Rakefile
ADDED
@@ -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
|