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
@@ -0,0 +1,241 @@
|
|
1
|
+
# Parent module containing all variables defined as part of virtual_keywords
|
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
|
+
# Object that virtualizes keywords.
|
99
|
+
class Virtualizer
|
100
|
+
# Initialize a Virtualizer
|
101
|
+
#
|
102
|
+
# Arguments:
|
103
|
+
# A Hash with the following arguments (all optional):
|
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).
|
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).
|
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.
|
114
|
+
# if_rewriter: (IfRewriter) the SexpProcessor descendant that
|
115
|
+
# rewrites "if"s in methods (optional, the default is
|
116
|
+
# IfRewriter.new).
|
117
|
+
# and_rewriter: (AndRewriter) the SexpProcessor descendant that
|
118
|
+
# rewrites "and"s in methods (optional, the default is
|
119
|
+
# AndRewriter.new).
|
120
|
+
# or_rewriter: (OrRewriter) the SexpProcessor descendant that
|
121
|
+
# rewrites "or"s in methods (optional, the default is
|
122
|
+
# OrRewriter.new).
|
123
|
+
# sexp_processor: (SexpProcessor) the sexp_processor that can turn
|
124
|
+
# ParseTree results into sexps (optional, the default is
|
125
|
+
# SexpProcessor.new).
|
126
|
+
# sexp_stringifier: (SexpStringifier) an object that can turn sexps
|
127
|
+
# back into Ruby code (optional, the default is
|
128
|
+
# SexpStringifier.new).
|
129
|
+
# rewritten_keywords: (RewrittenKeywords) a repository for keyword
|
130
|
+
# replacement lambdas (optional, the default is
|
131
|
+
# REWRITTEN_KEYWORDS).
|
132
|
+
def initialize(input_hash)
|
133
|
+
@for_classes = input_hash[:for_classes] || []
|
134
|
+
@for_instances = input_hash[:for_instances] || []
|
135
|
+
@for_subclasses_of = input_hash[:for_subclasses_of] || []
|
136
|
+
@if_rewriter = input_hash[:if_rewriter] || IfRewriter.new
|
137
|
+
@and_rewriter = input_hash[:and_rewriter] || AndRewriter.new
|
138
|
+
@or_rewriter = input_hash[:or_rewriter] || OrRewriter.new
|
139
|
+
@sexp_processor = input_hash[:sexp_processor] || SexpProcessor.new
|
140
|
+
@sexp_stringifier = input_hash[:sexp_stringifier] || SexpStringifier.new
|
141
|
+
@rewritten_keywords =
|
142
|
+
input_hash[:rewritten_keywords] || REWRITTEN_KEYWORDS
|
143
|
+
end
|
144
|
+
|
145
|
+
# Helper method to rewrite code.
|
146
|
+
#
|
147
|
+
# Arguments:
|
148
|
+
# translated: (Array) the output of ParseTree.translate on the original
|
149
|
+
# code
|
150
|
+
# rewriter: (SexpProcessor) the object that will rewrite the sexp, to
|
151
|
+
# virtualize the keywords.
|
152
|
+
def rewritten_code(translated, rewriter)
|
153
|
+
sexp = @sexp_processor.process(
|
154
|
+
VirtualKeywords.deep_copy_array(translated))
|
155
|
+
new_code = @sexp_stringifier.stringify(
|
156
|
+
rewriter.process(sexp))
|
157
|
+
end
|
158
|
+
|
159
|
+
# Helper method to rewrite all methods of an object.
|
160
|
+
#
|
161
|
+
# Arguments:
|
162
|
+
# instance: (Object) the object whose methods will be rewritten.
|
163
|
+
# keyword: (Symbol) the keyword to virtualize.
|
164
|
+
# rewriter: (SexpProcessor) the object that will do the rewriting.
|
165
|
+
# block: (Proc) the lambda that will replace the keyword.
|
166
|
+
def rewrite_methods_of_instance(instance, keyword, rewriter, block)
|
167
|
+
@rewritten_keywords.register_lambda_for_object(instance, keyword, block)
|
168
|
+
|
169
|
+
methods = ClassReflection.instance_methods_of instance.class
|
170
|
+
methods.each do |name, translated|
|
171
|
+
new_code = rewritten_code(translated, rewriter)
|
172
|
+
ClassReflection.install_method_on_instance(instance, new_code)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Helper method to rewrite all methods of objects from a class.
|
177
|
+
#
|
178
|
+
# Arguments:
|
179
|
+
# klass: (Class) the class whose methods will be rewritten.
|
180
|
+
# keyword: (Symbol) the keyword to virtualize.
|
181
|
+
# rewriter: (SexpProcessor) the object that will do the rewriting.
|
182
|
+
# block: (Proc) the lambda that will replace the keyword.
|
183
|
+
def rewrite_methods_of_class(klass, keyword, rewriter, block)
|
184
|
+
@rewritten_keywords.register_lambda_for_class(klass, keyword, block)
|
185
|
+
|
186
|
+
methods = ClassReflection.instance_methods_of klass
|
187
|
+
methods.each do |name, translated|
|
188
|
+
new_code = rewritten_code(translated, rewriter)
|
189
|
+
ClassReflection.install_method_on_class(klass, new_code)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Helper method to virtualize a keyword (rewrite with the given block)
|
194
|
+
#
|
195
|
+
# Arguments:
|
196
|
+
# keyword: (Symbol) the keyword to virtualize.
|
197
|
+
# rewriter: (SexpProcessor) the object that will do the rewriting.
|
198
|
+
# block: (Proc) the lambda that will replace the keyword.
|
199
|
+
def virtualize_keyword(keyword, rewriter, block)
|
200
|
+
@for_instances.each do |instance|
|
201
|
+
rewrite_methods_of_instance(instance, keyword, rewriter, block)
|
202
|
+
end
|
203
|
+
|
204
|
+
@for_classes.each do |klass|
|
205
|
+
rewrite_methods_of_class(klass, keyword, rewriter, block)
|
206
|
+
end
|
207
|
+
|
208
|
+
subclasses = ClassReflection.subclasses_of_classes @for_subclasses_of
|
209
|
+
subclasses.each do |subclass|
|
210
|
+
rewrite_methods_of_class(subclass, keyword, rewriter, block)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Rewrite "if" expressions.
|
215
|
+
#
|
216
|
+
# Arguments:
|
217
|
+
# &block: The block that will replace "if"s in the objects being
|
218
|
+
# virtualized
|
219
|
+
def virtual_if(&block)
|
220
|
+
virtualize_keyword(:if, @if_rewriter, block)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Rewrite "and" expressions.
|
224
|
+
#
|
225
|
+
# Arguments:
|
226
|
+
# &block: The block that will replace "and"s in the objects being
|
227
|
+
# virtualized
|
228
|
+
def virtual_and(&block)
|
229
|
+
virtualize_keyword(:and, @and_rewriter, block)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Rewrite "or" expressions.
|
233
|
+
#
|
234
|
+
# Arguments:
|
235
|
+
# &block: The block that will replace "or"s in the objects being
|
236
|
+
# virtualized
|
237
|
+
def virtual_or(&block)
|
238
|
+
virtualize_keyword(:or, @or_rewriter, block)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'virtual_keywords/sexp_stringifier'
|
2
|
+
require 'virtual_keywords/class_mirrorer'
|
3
|
+
require 'virtual_keywords/virtualizer'
|
4
|
+
require 'virtual_keywords/keyword_rewriter'
|
5
|
+
require 'virtual_keywords/rewritten_keywords'
|
6
|
+
|
7
|
+
module VirtualKeywords
|
8
|
+
class Foo
|
9
|
+
def hi
|
10
|
+
if true
|
11
|
+
:hi
|
12
|
+
else
|
13
|
+
:bye
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.sanity_test
|
19
|
+
virtualizer = Virtualizer.new(
|
20
|
+
:for_classes => [Foo]
|
21
|
+
)
|
22
|
+
virtualizer.virtual_if do |condition, then_do, else_do|
|
23
|
+
:pwned
|
24
|
+
end
|
25
|
+
|
26
|
+
foo = Foo.new
|
27
|
+
if foo.hi == :pwned
|
28
|
+
:success
|
29
|
+
else
|
30
|
+
:failure
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: virtual_keywords
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 31
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 0.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Rahul Rajagopalan
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-04-21 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: sexp_processor
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: ParseTree
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :runtime
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: ruby2ruby
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
type: :runtime
|
75
|
+
version_requirements: *id004
|
76
|
+
description: Replace keyword implementations with your own functions, for DSLs
|
77
|
+
email: rahulrajago@gmail.com
|
78
|
+
executables: []
|
79
|
+
|
80
|
+
extensions: []
|
81
|
+
|
82
|
+
extra_rdoc_files: []
|
83
|
+
|
84
|
+
files:
|
85
|
+
- lib/virtual_keywords/class_mirrorer.rb
|
86
|
+
- lib/virtual_keywords/sexp_stringifier.rb
|
87
|
+
- lib/virtual_keywords/rewritten_keywords.rb
|
88
|
+
- lib/virtual_keywords/keyword_rewriter.rb
|
89
|
+
- lib/virtual_keywords/version.rb
|
90
|
+
- lib/virtual_keywords/virtualizer.rb
|
91
|
+
- lib/sexps/count_to_ten_sexp.txt
|
92
|
+
- lib/sexps/sexps_and.txt
|
93
|
+
- lib/sexps/sexps_symbolic_and.txt
|
94
|
+
- lib/sexps/sexps_rewritten_keywords.txt
|
95
|
+
- lib/sexps/sexps_greet.txt
|
96
|
+
- lib/virtual_keywords.rb
|
97
|
+
- lib/spec/virtualizer_spec.rb
|
98
|
+
- lib/spec/rewritten_keywords_spec.rb
|
99
|
+
- lib/spec/keyword_rewriter_spec.rb
|
100
|
+
- lib/spec/class_mirrorer_spec.rb
|
101
|
+
- lib/spec/spec_helper.rb
|
102
|
+
- lib/Rakefile
|
103
|
+
homepage: http://github.com/rahulraj/virtual_keywords
|
104
|
+
licenses: []
|
105
|
+
|
106
|
+
post_install_message:
|
107
|
+
rdoc_options: []
|
108
|
+
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
hash: 3
|
117
|
+
segments:
|
118
|
+
- 0
|
119
|
+
version: "0"
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
hash: 3
|
126
|
+
segments:
|
127
|
+
- 0
|
128
|
+
version: "0"
|
129
|
+
requirements: []
|
130
|
+
|
131
|
+
rubyforge_project:
|
132
|
+
rubygems_version: 1.8.23
|
133
|
+
signing_key:
|
134
|
+
specification_version: 3
|
135
|
+
summary: Virtualize keywords, like "if", "and", and "or"
|
136
|
+
test_files: []
|
137
|
+
|