virtual_keywords 0.1.0 → 0.3.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/parsetree/History.txt +477 -0
- data/lib/parsetree/Manifest.txt +19 -0
- data/lib/parsetree/README.txt +97 -0
- data/lib/parsetree/Rakefile +37 -0
- data/lib/parsetree/bin/parse_tree_abc +89 -0
- data/lib/parsetree/bin/parse_tree_audit +28 -0
- data/lib/parsetree/bin/parse_tree_deps +62 -0
- data/lib/parsetree/bin/parse_tree_show +63 -0
- data/lib/parsetree/demo/printer.rb +20 -0
- data/lib/parsetree/lib/gauntlet_parsetree.rb +121 -0
- data/lib/parsetree/lib/parse_tree.rb +1202 -0
- data/lib/parsetree/lib/parse_tree_extensions.rb +59 -0
- data/lib/parsetree/lib/unified_ruby.rb +421 -0
- data/lib/parsetree/test/something.rb +53 -0
- data/lib/parsetree/test/test_parse_tree.rb +2567 -0
- data/lib/parsetree/test/test_parse_tree_extensions.rb +107 -0
- data/lib/parsetree/test/test_unified_ruby.rb +57 -0
- data/lib/parsetree/validate.sh +31 -0
- data/lib/sexps/{count_to_ten_sexp.txt → count_to_ten_sexp.rb} +0 -0
- data/lib/sexps/{sexps_and.txt → sexps_and.rb} +0 -0
- data/lib/sexps/{sexps_case_when.txt → sexps_case_when.rb} +0 -0
- data/lib/sexps/{sexps_greet.txt → sexps_greet.rb} +0 -0
- data/lib/sexps/sexps_not.rb +24 -0
- data/lib/sexps/{sexps_rewritten_keywords.txt → sexps_rewritten_keywords.rb} +0 -0
- data/lib/sexps/{sexps_symbolic_and.txt → sexps_symbolic_and.rb} +0 -0
- data/lib/sexps/sexps_until.rb +60 -0
- data/lib/sexps/{sexps_while.txt → sexps_while.rb} +0 -0
- data/lib/spec/class_reflection_spec.rb +64 -0
- data/lib/spec/keyword_rewriter_spec.rb +7 -54
- data/lib/spec/not_rewriter_spec.rb +25 -0
- data/lib/spec/parser_strategy_spec.rb +23 -0
- data/lib/spec/sexp_stringifier_spec.rb +19 -0
- data/lib/spec/spec_helper.rb +61 -307
- data/lib/spec/until_rewriter_spec.rb +26 -0
- data/lib/spec/virtualizees/and_user.rb +17 -0
- data/lib/spec/virtualizees/case_when_user.rb +20 -0
- data/lib/spec/virtualizees/fizzbuzzer.rb +15 -0
- data/lib/spec/virtualizees/greeter.rb +127 -0
- data/lib/spec/virtualizees/not_user.rb +9 -0
- data/lib/spec/virtualizees/operator_user.rb +15 -0
- data/lib/spec/virtualizees/or_user.rb +17 -0
- data/lib/spec/virtualizees/parents.rb +8 -0
- data/lib/spec/virtualizees/until_user.rb +16 -0
- data/lib/spec/virtualizees/while_user.rb +16 -0
- data/lib/spec/virtualizer_spec.rb +46 -83
- data/lib/virtual_keywords/class_reflection.rb +88 -0
- data/lib/virtual_keywords/deep_copy_array.rb +12 -0
- data/lib/virtual_keywords/keyword_rewriter.rb +54 -0
- data/lib/virtual_keywords/parser_strategy.rb +40 -0
- data/lib/virtual_keywords/rewritten_keywords.rb +15 -0
- data/lib/virtual_keywords/sexp_stringifier.rb +1 -5
- data/lib/virtual_keywords/version.rb +1 -1
- data/lib/virtual_keywords/virtualizer.rb +30 -115
- data/lib/virtual_keywords.rb +31 -5
- metadata +117 -90
- data/lib/spec/class_mirrorer_spec.rb +0 -18
- data/lib/virtual_keywords/class_mirrorer.rb +0 -39
@@ -1,84 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe 'instance_methods_of' do
|
4
|
-
it 'retrieves the instance methods of a class' do
|
5
|
-
method_names = VirtualKeywords::ClassReflection.instance_methods_of(
|
6
|
-
Fizzbuzzer).keys
|
7
|
-
method_names.should include 'fizzbuzz'
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
describe 'subclasses_of_classes' do
|
12
|
-
it 'finds the subclasses of classes and flattens the result' do
|
13
|
-
rails_classes = [ActiveRecord::Base, ApplicationController]
|
14
|
-
subclasses = VirtualKeywords::ClassReflection.
|
15
|
-
subclasses_of_classes rails_classes
|
16
|
-
|
17
|
-
subclasses.should include Fizzbuzzer
|
18
|
-
subclasses.should include Greeter
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
describe 'install_method_on_class' do
|
23
|
-
before :each do
|
24
|
-
class MyClass
|
25
|
-
def foo
|
26
|
-
:hello
|
27
|
-
end
|
28
|
-
end
|
29
|
-
@object = MyClass.new
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'installs methods on classes' do
|
33
|
-
VirtualKeywords::ClassReflection.install_method_on_class(
|
34
|
-
MyClass, 'def foo; :goodbye; end')
|
35
|
-
@object.foo.should eql :goodbye
|
36
|
-
end
|
37
|
-
|
38
|
-
it 'installs methods that change instance fields' do
|
39
|
-
class MyClass
|
40
|
-
def foo
|
41
|
-
:hello
|
42
|
-
end
|
43
|
-
end
|
44
|
-
VirtualKeywords::ClassReflection.install_method_on_class(
|
45
|
-
MyClass, 'def foo; @bar = :bar; :goodbye; end')
|
46
|
-
VirtualKeywords::ClassReflection.install_method_on_class(
|
47
|
-
MyClass, 'def bar; @bar; end')
|
48
|
-
|
49
|
-
@object.foo.should eql :goodbye
|
50
|
-
@object.bar.should eql :bar
|
51
|
-
end
|
52
|
-
|
53
|
-
it 'installs methods that mutate globals' do
|
54
|
-
$thing = :old
|
55
|
-
VirtualKeywords::ClassReflection.install_method_on_class(
|
56
|
-
MyClass, 'def foo; $thing = :new; end')
|
57
|
-
|
58
|
-
@object.foo()
|
59
|
-
$thing.should eql :new
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
describe 'install_method_on_instance' do
|
64
|
-
before :each do
|
65
|
-
class MyClass
|
66
|
-
def foo
|
67
|
-
:hello
|
68
|
-
end
|
69
|
-
end
|
70
|
-
@object1 = MyClass.new
|
71
|
-
@object2 = MyClass.new
|
72
|
-
end
|
73
|
-
|
74
|
-
it 'installs methods on specific instances' do
|
75
|
-
VirtualKeywords::ClassReflection.install_method_on_instance(
|
76
|
-
@object1, 'def foo; :goodbye; end')
|
77
|
-
@object1.foo.should eql :goodbye
|
78
|
-
@object2.foo.should eql :hello
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
3
|
describe 'Virtualizer' do
|
83
4
|
before :each do
|
84
5
|
@greeter = Greeter.new false
|
@@ -113,7 +34,7 @@ describe 'Virtualizer' do
|
|
113
34
|
end
|
114
35
|
end
|
115
36
|
|
116
|
-
class
|
37
|
+
class WhileCounter
|
117
38
|
def run
|
118
39
|
a = []
|
119
40
|
i = 0
|
@@ -125,13 +46,34 @@ describe 'Virtualizer' do
|
|
125
46
|
end
|
126
47
|
end
|
127
48
|
|
49
|
+
class UntilCounter
|
50
|
+
def run
|
51
|
+
a = []
|
52
|
+
i = 0
|
53
|
+
until i > 10
|
54
|
+
a << i
|
55
|
+
end
|
56
|
+
|
57
|
+
a
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Inverter
|
62
|
+
def run value
|
63
|
+
not value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
128
67
|
@my_class = MyClass.new
|
129
68
|
@another_class = AnotherClass.new
|
130
69
|
@yet_another_class = YetAnotherClass.new
|
131
70
|
@operator_user = OperatorUser.new false
|
132
|
-
@
|
71
|
+
@while_counter = WhileCounter.new
|
72
|
+
@until_counter = UntilCounter.new
|
73
|
+
@inverter = Inverter.new
|
133
74
|
@virtualizer = VirtualKeywords::Virtualizer.new(
|
134
|
-
:for_instances => [@greeter, @my_class,
|
75
|
+
:for_instances => [@greeter, @my_class,
|
76
|
+
@while_counter, @until_counter, @inverter]
|
135
77
|
)
|
136
78
|
@class_virtualizer = VirtualKeywords::Virtualizer.new(
|
137
79
|
:for_classes => [AnotherClass]
|
@@ -198,7 +140,28 @@ describe 'Virtualizer' do
|
|
198
140
|
body.call
|
199
141
|
end
|
200
142
|
|
201
|
-
result = @
|
143
|
+
result = @while_counter.run
|
202
144
|
result.should eql [0]
|
203
145
|
end
|
146
|
+
|
147
|
+
it 'virtualizes "until" on instances' do
|
148
|
+
@virtualizer.virtual_until do |condition, body|
|
149
|
+
# flip it into a while
|
150
|
+
while condition.call
|
151
|
+
body.call
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
result = @until_counter.run
|
156
|
+
result.should eql []
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'virtualizes "not" on instances' do
|
160
|
+
@virtualizer.virtual_not do |value|
|
161
|
+
value.call
|
162
|
+
end
|
163
|
+
|
164
|
+
result = @inverter.run true
|
165
|
+
result.should be_true
|
166
|
+
end
|
204
167
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module VirtualKeywords
|
2
|
+
# Object used to inspect the class hierarchy, and to view
|
3
|
+
# and modify methods of classes.
|
4
|
+
class ClassReflection
|
5
|
+
def initialize(parser_strategy = ParserStrategy.new)
|
6
|
+
@parser_strategy = parser_strategy
|
7
|
+
end
|
8
|
+
|
9
|
+
# Get the subclasses of a given class.
|
10
|
+
#
|
11
|
+
# Arguments:
|
12
|
+
# parent: (Class) the class whose subclasses to find.
|
13
|
+
#
|
14
|
+
# Returns:
|
15
|
+
# (Array) all classes which are subclasses of parent.
|
16
|
+
def subclasses_of_class(parent)
|
17
|
+
ObjectSpace.each_object(Class).select { |klass|
|
18
|
+
klass < parent
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Given an array of base classes, return a flat array of all their
|
23
|
+
# subclasses.
|
24
|
+
#
|
25
|
+
# Arguments:
|
26
|
+
# klasses: (Array[Class]) an array of classes
|
27
|
+
#
|
28
|
+
# Returns:
|
29
|
+
# (Array) All classes that are subclasses of one of the classes in klasses,
|
30
|
+
# in a flattened array.
|
31
|
+
def subclasses_of_classes(klasses)
|
32
|
+
klasses.map { |klass|
|
33
|
+
subclasses_of_class klass
|
34
|
+
}.flatten
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get the instance_methods of a class.
|
38
|
+
#
|
39
|
+
# Arguments:
|
40
|
+
# klass: (Class) the class.
|
41
|
+
#
|
42
|
+
# Returns:
|
43
|
+
# (Hash[Symbol, Array]) A hash, mapping method names to the results of
|
44
|
+
# ParseTree.translate.
|
45
|
+
def instance_methods_of(klass)
|
46
|
+
methods = {}
|
47
|
+
klass.instance_methods(false).each do |method_name|
|
48
|
+
translated = @parser_strategy.translate_instance_method(klass, method_name)
|
49
|
+
methods[method_name.to_sym] = translated
|
50
|
+
end
|
51
|
+
|
52
|
+
methods
|
53
|
+
end
|
54
|
+
|
55
|
+
# Install a method on a class. When object.method_name is called
|
56
|
+
# (for objects in the class), have them run the given code.
|
57
|
+
# TODO Should it be possible to recover the old method?
|
58
|
+
# How would that API look?
|
59
|
+
#
|
60
|
+
# Arguments:
|
61
|
+
# klass: (Class) the class which should be modified.
|
62
|
+
# method_code: (String) the code for the method to install, of the format:
|
63
|
+
# def method_name(args)
|
64
|
+
# ...
|
65
|
+
# end
|
66
|
+
def install_method_on_class(klass, method_code)
|
67
|
+
klass.class_eval method_code
|
68
|
+
end
|
69
|
+
|
70
|
+
# Install a method on an object. When object.method_name is called,
|
71
|
+
# runs the given code.
|
72
|
+
#
|
73
|
+
# This function can also be used for classmethods. For example, if you want
|
74
|
+
# to rewrite Klass.method_name (a method on Klass, a singleton Class),
|
75
|
+
# call this method (NOT install_method_on_class, that will modifiy objects
|
76
|
+
# created through Klass.new!)
|
77
|
+
#
|
78
|
+
# Arguments:
|
79
|
+
# object: (Object) the object instance that should be modified.
|
80
|
+
# method_code: (String) the code for the method to install, of the format:
|
81
|
+
# def method_name(args)
|
82
|
+
# ...
|
83
|
+
# end
|
84
|
+
def install_method_on_instance(object, method_code)
|
85
|
+
object.instance_eval method_code
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module VirtualKeywords
|
2
|
+
# Deeply copy an array.
|
3
|
+
#
|
4
|
+
# Arguments:
|
5
|
+
# array: (Array[A]) the array to copy. A is any arbitrary type.
|
6
|
+
#
|
7
|
+
# Returns:
|
8
|
+
# (Array[A]) a deep copy of the original array.
|
9
|
+
def self.deep_copy_array(array)
|
10
|
+
Marshal.load(Marshal.dump(array))
|
11
|
+
end
|
12
|
+
end
|
@@ -114,6 +114,28 @@ module VirtualKeywords
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
+
class NotRewriter < SexpProcessor
|
118
|
+
def initialize
|
119
|
+
super
|
120
|
+
self.strict = false
|
121
|
+
end
|
122
|
+
|
123
|
+
def rewrite_not(expression)
|
124
|
+
value = expression[1]
|
125
|
+
|
126
|
+
s(:call,
|
127
|
+
s(:colon2,
|
128
|
+
s(:const, :VirtualKeywords),
|
129
|
+
:REWRITTEN_KEYWORDS
|
130
|
+
), :call_not,
|
131
|
+
s(:array,
|
132
|
+
s(:self),
|
133
|
+
s(:iter, s(:fcall, :lambda), nil, value)
|
134
|
+
)
|
135
|
+
)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
117
139
|
class UnexpectedSexp < StandardError
|
118
140
|
end
|
119
141
|
|
@@ -148,4 +170,36 @@ module VirtualKeywords
|
|
148
170
|
)
|
149
171
|
end
|
150
172
|
end
|
173
|
+
|
174
|
+
class UntilRewriter < SexpProcessor
|
175
|
+
def initialize
|
176
|
+
super
|
177
|
+
self.strict = false
|
178
|
+
end
|
179
|
+
|
180
|
+
def rewrite_until(expression)
|
181
|
+
condition = expression[1]
|
182
|
+
body = expression[2]
|
183
|
+
|
184
|
+
# This was a true in the example I checked (in sexps_while.txt)
|
185
|
+
# but I'm not sure what it's for.
|
186
|
+
third = expression[3]
|
187
|
+
if third != true # Should be true, not just a truthy object
|
188
|
+
raise UnexpectedSexp, 'Expected true as the 3rd element in a while, ' +
|
189
|
+
"but got #{third}, this is probably a bug."
|
190
|
+
end
|
191
|
+
|
192
|
+
s(:call,
|
193
|
+
s(:colon2,
|
194
|
+
s(:const, :VirtualKeywords),
|
195
|
+
:REWRITTEN_KEYWORDS
|
196
|
+
), :call_until,
|
197
|
+
s(:array,
|
198
|
+
s(:self),
|
199
|
+
s(:iter, s(:fcall, :lambda), nil, condition),
|
200
|
+
s(:iter, s(:fcall, :lambda), nil, body)
|
201
|
+
)
|
202
|
+
)
|
203
|
+
end
|
204
|
+
end
|
151
205
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module VirtualKeywords
|
2
|
+
class ParserStrategy
|
3
|
+
# Factory method
|
4
|
+
# Create the appropriate strategy object for the current Ruby version.
|
5
|
+
def self.new
|
6
|
+
if RUBY_VERSION.start_with? '1.8'
|
7
|
+
ParseTreeStrategy.new(ParseTree, SexpProcessor.new)
|
8
|
+
else
|
9
|
+
RubyParserStrategy.new(RubyParser.new)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
# One problem that needs to be solved is that of converting source code form
|
14
|
+
# files into sexps.
|
15
|
+
|
16
|
+
# In Ruby 1.8, we use ParseTree and then SexpProcessor.
|
17
|
+
# In Ruby 1.9, we use RubyParser.
|
18
|
+
# Note that neither set of libraries seems to work in the other version.
|
19
|
+
# Use the strategy pattern, and initialize whichever class is appropriate.
|
20
|
+
class ParseTreeStrategy
|
21
|
+
def initialize(parse_tree, sexp_processor)
|
22
|
+
@parse_tree = parse_tree
|
23
|
+
@sexp_processor = sexp_processor
|
24
|
+
end
|
25
|
+
|
26
|
+
def translate_instance_method(klass, method_name)
|
27
|
+
@sexp_processor.process(@parse_tree.translate(klass, method_name))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class RubyParserStrategy
|
32
|
+
def initialize ruby_parser
|
33
|
+
@ruby_parser = ruby_parser
|
34
|
+
end
|
35
|
+
|
36
|
+
def translate_instance_method(klass, method_name)
|
37
|
+
@ruby_parser.parse(klass.instance_method(method_name).source)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -144,6 +144,21 @@ module VirtualKeywords
|
|
144
144
|
while_lambda = lambda_or_raise(caller_object, :while)
|
145
145
|
while_lambda.call(condition, body)
|
146
146
|
end
|
147
|
+
|
148
|
+
# Unlike unless, until IS a node in the AST
|
149
|
+
# (it doesn't turn into while not)
|
150
|
+
# For now, I'm passing this inconsistency through to the client.
|
151
|
+
# A later modification of this gem may fold while and until into one thing
|
152
|
+
# for consistency.
|
153
|
+
def call_until(caller_object, condition, body)
|
154
|
+
until_lambda = lambda_or_raise(caller_object, :until)
|
155
|
+
until_lambda.call(condition, body)
|
156
|
+
end
|
157
|
+
|
158
|
+
def call_not(caller_object, value)
|
159
|
+
not_lambda = lambda_or_raise(caller_object, :not)
|
160
|
+
not_lambda.call value
|
161
|
+
end
|
147
162
|
end
|
148
163
|
|
149
164
|
|
@@ -1,8 +1,4 @@
|
|
1
|
-
require 'parse_tree'
|
2
|
-
require 'ruby2ruby'
|
3
|
-
|
4
1
|
module VirtualKeywords
|
5
|
-
|
6
2
|
# Class that turns a sexp back into a string of Ruby code.
|
7
3
|
class SexpStringifier
|
8
4
|
# Initialize the SexpStringifier
|
@@ -23,7 +19,7 @@ module VirtualKeywords
|
|
23
19
|
#
|
24
20
|
# Returns:
|
25
21
|
# (String) Ruby code equivalent to the sexp.
|
26
|
-
def stringify
|
22
|
+
def stringify sexp
|
27
23
|
@ruby2ruby.process(@unifier.process(sexp))
|
28
24
|
end
|
29
25
|
end
|
@@ -1,100 +1,5 @@
|
|
1
1
|
# Parent module containing all variables defined as part of virtual_keywords
|
2
2
|
module VirtualKeywords
|
3
|
-
|
4
|
-
# Utility functions used to inspect the class hierarchy, and to view
|
5
|
-
# and modify methods of classes.
|
6
|
-
class ClassReflection
|
7
|
-
# Get the subclasses of a given class.
|
8
|
-
#
|
9
|
-
# Arguments:
|
10
|
-
# parent: (Class) the class whose subclasses to find.
|
11
|
-
#
|
12
|
-
# Returns:
|
13
|
-
# (Array) all classes which are subclasses of parent.
|
14
|
-
def self.subclasses_of_class(parent)
|
15
|
-
ObjectSpace.each_object(Class).select { |klass|
|
16
|
-
klass < parent
|
17
|
-
}
|
18
|
-
end
|
19
|
-
|
20
|
-
# Given an array of base classes, return a flat array of all their
|
21
|
-
# subclasses.
|
22
|
-
#
|
23
|
-
# Arguments:
|
24
|
-
# klasses: (Array[Class]) an array of classes
|
25
|
-
#
|
26
|
-
# Returns:
|
27
|
-
# (Array) All classes that are subclasses of one of the classes in klasses,
|
28
|
-
# in a flattened array.
|
29
|
-
def self.subclasses_of_classes(klasses)
|
30
|
-
klasses.map { |klass|
|
31
|
-
subclasses_of_class klass
|
32
|
-
}.flatten
|
33
|
-
end
|
34
|
-
|
35
|
-
# Get the instance_methods of a class.
|
36
|
-
#
|
37
|
-
# Arguments:
|
38
|
-
# klass: (Class) the class.
|
39
|
-
#
|
40
|
-
# Returns:
|
41
|
-
# (Hash[Symbol, Array]) A hash, mapping method names to the results of
|
42
|
-
# ParseTree.translate.
|
43
|
-
def self.instance_methods_of(klass)
|
44
|
-
methods = {}
|
45
|
-
klass.instance_methods(false).each do |method_name|
|
46
|
-
translated = ParseTree.translate(klass, method_name)
|
47
|
-
methods[method_name] = translated
|
48
|
-
end
|
49
|
-
|
50
|
-
methods
|
51
|
-
end
|
52
|
-
|
53
|
-
# Install a method on a class. When object.method_name is called
|
54
|
-
# (for objects in the class), have them run the given code.
|
55
|
-
# TODO Should it be possible to recover the old method?
|
56
|
-
# How would that API look?
|
57
|
-
#
|
58
|
-
# Arguments:
|
59
|
-
# klass: (Class) the class which should be modified.
|
60
|
-
# method_code: (String) the code for the method to install, of the format:
|
61
|
-
# def method_name(args)
|
62
|
-
# ...
|
63
|
-
# end
|
64
|
-
def self.install_method_on_class(klass, method_code)
|
65
|
-
klass.class_eval method_code
|
66
|
-
end
|
67
|
-
|
68
|
-
# Install a method on an object. When object.method_name is called,
|
69
|
-
# runs the given code.
|
70
|
-
#
|
71
|
-
# This function can also be used for classmethods. For example, if you want
|
72
|
-
# to rewrite Klass.method_name (a method on Klass, a singleton Class),
|
73
|
-
# call this method (NOT install_method_on_class, that will modifiy objects
|
74
|
-
# created through Klass.new!)
|
75
|
-
#
|
76
|
-
# Arguments:
|
77
|
-
# object: (Object) the object instance that should be modified.
|
78
|
-
# method_code: (String) the code for the method to install, of the format:
|
79
|
-
# def method_name(args)
|
80
|
-
# ...
|
81
|
-
# end
|
82
|
-
def self.install_method_on_instance(object, method_code)
|
83
|
-
object.instance_eval method_code
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
# Deeply copy an array.
|
88
|
-
#
|
89
|
-
# Arguments:
|
90
|
-
# array: (Array[A]) the array to copy. A is any arbitrary type.
|
91
|
-
#
|
92
|
-
# Returns:
|
93
|
-
# (Array[A]) a deep copy of the original array.
|
94
|
-
def self.deep_copy_array(array)
|
95
|
-
Marshal.load(Marshal.dump(array))
|
96
|
-
end
|
97
|
-
|
98
3
|
# Object that virtualizes keywords.
|
99
4
|
class Virtualizer
|
100
5
|
# Initialize a Virtualizer
|
@@ -119,12 +24,15 @@ module VirtualKeywords
|
|
119
24
|
# or_rewriter: (OrRewriter) the SexpProcessor descendant that
|
120
25
|
# rewrites "or"s in methods (optional, the default is
|
121
26
|
# OrRewriter.new).
|
27
|
+
# not_rewriter: (NotRewriter) the SexpProcessor descendant that
|
28
|
+
# rewrites "not"s in methods (optional, the default is
|
29
|
+
# NotRewriter.new).
|
122
30
|
# while_rewriter: (WhileRewriter) the SexpProcessor descendant that
|
123
31
|
# rewrites "while"s in methods (optional, the default is
|
124
32
|
# WhileRewriter.new).
|
125
|
-
#
|
126
|
-
#
|
127
|
-
#
|
33
|
+
# until_rewriter: (UntilRewriter) the SexpProcessor descendant that
|
34
|
+
# rewrites "until"s in methods (optional, the default is
|
35
|
+
# UntilRewriter.new).
|
128
36
|
# sexp_stringifier: (SexpStringifier) an object that can turn sexps
|
129
37
|
# back into Ruby code (optional, the default is
|
130
38
|
# SexpStringifier.new).
|
@@ -139,26 +47,25 @@ module VirtualKeywords
|
|
139
47
|
@if_rewriter = input_hash[:if_rewriter] || IfRewriter.new
|
140
48
|
@and_rewriter = input_hash[:and_rewriter] || AndRewriter.new
|
141
49
|
@or_rewriter = input_hash[:or_rewriter] || OrRewriter.new
|
50
|
+
@not_rewriter = input_hash[:or_rewriter] || NotRewriter.new
|
142
51
|
@while_rewriter = input_hash[:while_rewriter] || WhileRewriter.new
|
143
|
-
@
|
52
|
+
@until_rewriter = input_hash[:until_rewriter] || UntilRewriter.new
|
144
53
|
@sexp_stringifier = input_hash[:sexp_stringifier] || SexpStringifier.new
|
145
54
|
@rewritten_keywords =
|
146
55
|
input_hash[:rewritten_keywords] || REWRITTEN_KEYWORDS
|
147
|
-
@class_reflection = input_hash[:class_reflection] || ClassReflection
|
56
|
+
@class_reflection = input_hash[:class_reflection] || ClassReflection.new
|
148
57
|
end
|
149
58
|
|
150
59
|
# Helper method to rewrite code.
|
151
60
|
#
|
152
61
|
# Arguments:
|
153
|
-
# translated: (
|
154
|
-
# code
|
62
|
+
# translated: (Sexp) the sexp for code
|
155
63
|
# rewriter: (SexpProcessor) the object that will rewrite the sexp, to
|
156
64
|
# virtualize the keywords.
|
157
|
-
def rewritten_code(
|
158
|
-
|
159
|
-
VirtualKeywords.deep_copy_array(translated))
|
65
|
+
def rewritten_code(sexp, rewriter)
|
66
|
+
sexp_copy = VirtualKeywords.deep_copy_array sexp
|
160
67
|
new_code = @sexp_stringifier.stringify(
|
161
|
-
rewriter.process(
|
68
|
+
rewriter.process(sexp_copy))
|
162
69
|
end
|
163
70
|
|
164
71
|
# Helper method to rewrite all methods of an object.
|
@@ -168,12 +75,12 @@ module VirtualKeywords
|
|
168
75
|
# keyword: (Symbol) the keyword to virtualize.
|
169
76
|
# rewriter: (SexpProcessor) the object that will do the rewriting.
|
170
77
|
# block: (Proc) the lambda that will replace the keyword.
|
171
|
-
def
|
78
|
+
def rewrite_all_methods_of_instance(instance, keyword, rewriter, block)
|
172
79
|
@rewritten_keywords.register_lambda_for_object(instance, keyword, block)
|
173
80
|
|
174
81
|
methods = @class_reflection.instance_methods_of instance.class
|
175
|
-
methods.each do |name,
|
176
|
-
new_code = rewritten_code(
|
82
|
+
methods.each do |name, sexp|
|
83
|
+
new_code = rewritten_code(sexp, rewriter)
|
177
84
|
@class_reflection.install_method_on_instance(instance, new_code)
|
178
85
|
end
|
179
86
|
end
|
@@ -185,12 +92,12 @@ module VirtualKeywords
|
|
185
92
|
# keyword: (Symbol) the keyword to virtualize.
|
186
93
|
# rewriter: (SexpProcessor) the object that will do the rewriting.
|
187
94
|
# block: (Proc) the lambda that will replace the keyword.
|
188
|
-
def
|
95
|
+
def rewrite_all_methods_of_class(klass, keyword, rewriter, block)
|
189
96
|
@rewritten_keywords.register_lambda_for_class(klass, keyword, block)
|
190
97
|
|
191
98
|
methods = @class_reflection.instance_methods_of klass
|
192
|
-
methods.each do |name,
|
193
|
-
new_code = rewritten_code(
|
99
|
+
methods.each do |name, sexp|
|
100
|
+
new_code = rewritten_code(sexp, rewriter)
|
194
101
|
@class_reflection.install_method_on_class(klass, new_code)
|
195
102
|
end
|
196
103
|
end
|
@@ -203,16 +110,16 @@ module VirtualKeywords
|
|
203
110
|
# block: (Proc) the lambda that will replace the keyword.
|
204
111
|
def virtualize_keyword(keyword, rewriter, block)
|
205
112
|
@for_instances.each do |instance|
|
206
|
-
|
113
|
+
rewrite_all_methods_of_instance(instance, keyword, rewriter, block)
|
207
114
|
end
|
208
115
|
|
209
116
|
@for_classes.each do |klass|
|
210
|
-
|
117
|
+
rewrite_all_methods_of_class(klass, keyword, rewriter, block)
|
211
118
|
end
|
212
119
|
|
213
120
|
subclasses = @class_reflection.subclasses_of_classes @for_subclasses_of
|
214
121
|
subclasses.each do |subclass|
|
215
|
-
|
122
|
+
rewrite_all_methods_of_class(subclass, keyword, rewriter, block)
|
216
123
|
end
|
217
124
|
end
|
218
125
|
|
@@ -251,5 +158,13 @@ module VirtualKeywords
|
|
251
158
|
def virtual_while(&block)
|
252
159
|
virtualize_keyword(:while, @while_rewriter, block)
|
253
160
|
end
|
161
|
+
|
162
|
+
def virtual_until(&block)
|
163
|
+
virtualize_keyword(:until, @until_rewriter, block)
|
164
|
+
end
|
165
|
+
|
166
|
+
def virtual_not(&block)
|
167
|
+
virtualize_keyword(:not, @not_rewriter, block)
|
168
|
+
end
|
254
169
|
end
|
255
170
|
end
|
data/lib/virtual_keywords.rb
CHANGED
@@ -1,8 +1,32 @@
|
|
1
|
-
|
2
|
-
require '
|
3
|
-
|
4
|
-
|
5
|
-
require '
|
1
|
+
begin
|
2
|
+
require 'parse_tree' # 1.8
|
3
|
+
rescue LoadError
|
4
|
+
# 1.9
|
5
|
+
require 'method_source'
|
6
|
+
require 'ruby_parser'
|
7
|
+
# HACK: parse_tree complains if I try to require it, but I do need the Unifier
|
8
|
+
# class from it. So, grab it from a local copy.
|
9
|
+
require_relative 'parsetree/lib/unified_ruby'
|
10
|
+
end
|
11
|
+
require 'ruby2ruby'
|
12
|
+
|
13
|
+
if RUBY_VERSION.start_with? '1.8'
|
14
|
+
require 'virtual_keywords/deep_copy_array'
|
15
|
+
require 'virtual_keywords/parser_strategy'
|
16
|
+
require 'virtual_keywords/sexp_stringifier'
|
17
|
+
require 'virtual_keywords/class_reflection'
|
18
|
+
require 'virtual_keywords/virtualizer'
|
19
|
+
require 'virtual_keywords/keyword_rewriter'
|
20
|
+
require 'virtual_keywords/rewritten_keywords'
|
21
|
+
else
|
22
|
+
require_relative 'virtual_keywords/deep_copy_array'
|
23
|
+
require_relative 'virtual_keywords/parser_strategy'
|
24
|
+
require_relative 'virtual_keywords/sexp_stringifier'
|
25
|
+
require_relative 'virtual_keywords/class_reflection'
|
26
|
+
require_relative 'virtual_keywords/virtualizer'
|
27
|
+
require_relative 'virtual_keywords/keyword_rewriter'
|
28
|
+
require_relative 'virtual_keywords/rewritten_keywords'
|
29
|
+
end
|
6
30
|
|
7
31
|
module VirtualKeywords
|
8
32
|
class Foo
|
@@ -16,6 +40,8 @@ module VirtualKeywords
|
|
16
40
|
end
|
17
41
|
|
18
42
|
def self.sanity_test
|
43
|
+
# TODO See if there's a way to run the specs instead of this, when
|
44
|
+
# building the gem and requiring it
|
19
45
|
virtualizer = Virtualizer.new(
|
20
46
|
:for_classes => [Foo]
|
21
47
|
)
|