virtual_keywords 0.3.0 → 0.3.1
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/spec/class_mirrorer_spec.rb +18 -0
- data/lib/spec/micro_sql_dsl_spec.rb +76 -0
- data/lib/spec/spec_helper.rb +10 -5
- data/lib/spec/virtualizees/micro_sql_dsl.rb +110 -0
- data/lib/spec/virtualizer_spec.rb +16 -0
- data/lib/virtual_keywords.rb +2 -0
- data/lib/virtual_keywords/class_mirrorer.rb +37 -0
- data/lib/virtual_keywords/class_reflection.rb +5 -1
- data/lib/virtual_keywords/keyword_rewriter.rb +25 -0
- data/lib/virtual_keywords/parser_strategy.rb +27 -0
- data/lib/virtual_keywords/rewritten_keywords.rb +20 -1
- data/lib/virtual_keywords/sexp_stringifier.rb +3 -1
- data/lib/virtual_keywords/version.rb +1 -1
- data/lib/virtual_keywords/virtualizer.rb +73 -11
- metadata +17 -13
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'ClassMirrorer' do
|
4
|
+
before :each do
|
5
|
+
@stub_parser = double 'parser'
|
6
|
+
@mirrorer = VirtualKeywords::ClassMirrorer.new :parser => @stub_parser
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'mirrors given classes' do
|
10
|
+
@stub_parser.stub(:translate_instance_method).and_return :translated
|
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,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'micro SQL DSL rewriting' do
|
4
|
+
include TrackIfs, TrackAnds, TrackOrs, DoRewrite
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
# Here, MicroSqlUser is the consumer of the DSL.
|
8
|
+
@user = MicroSqlUser.new
|
9
|
+
|
10
|
+
@methods = sexpify_instance_methods MicroSqlUser
|
11
|
+
@if_rewriter = VirtualKeywords::IfRewriter.new
|
12
|
+
@and_rewriter = VirtualKeywords::AndRewriter.new
|
13
|
+
@or_rewriter = VirtualKeywords::OrRewriter.new
|
14
|
+
|
15
|
+
@my_if_calls = 0
|
16
|
+
@my_and_calls = 0
|
17
|
+
@my_or_calls = 0
|
18
|
+
|
19
|
+
VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
|
20
|
+
@user, :if, my_if)
|
21
|
+
VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
|
22
|
+
@user, :and, my_and)
|
23
|
+
VirtualKeywords::REWRITTEN_KEYWORDS.register_lambda_for_object(
|
24
|
+
@user, :or, my_or)
|
25
|
+
end
|
26
|
+
|
27
|
+
def rewriters
|
28
|
+
[@if_rewriter, @and_rewriter, @or_rewriter]
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'rewrites postfix if in the SQL DSL' do
|
32
|
+
do_rewrite(:select_with_where, @user, verbose = false,
|
33
|
+
old_and_new_are_same = false)
|
34
|
+
@my_if_calls.should eql 1
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'rewrites if with or in the SQL DSL' do
|
38
|
+
do_rewrite(:select_with_or, @user, verbose = false,
|
39
|
+
old_and_new_are_same = false)
|
40
|
+
@my_if_calls.should eql 1
|
41
|
+
@my_or_calls.should eql 1
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'rewrites complex conditionals in the SQL DSL' do
|
45
|
+
do_rewrite(:select_complex, @user, verbose = false,
|
46
|
+
old_and_new_are_same = false)
|
47
|
+
@my_if_calls.should eql 1
|
48
|
+
@my_and_calls.should eql 1
|
49
|
+
@my_or_calls.should eql 1
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'micro SQL DSL virtualizing' do
|
54
|
+
before :each do
|
55
|
+
# Here, MicroSqlUser is the consumer of the DSL.
|
56
|
+
@user = MicroSqlUser.new
|
57
|
+
Sql.dslify @user
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'generates basic select statements' do
|
61
|
+
result_sql = @user.simple_select
|
62
|
+
result_sql.should eql 'select (name,post_ids) from users'
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'generates select-where statements' do
|
66
|
+
result_sql = @user.select_with_where
|
67
|
+
|
68
|
+
result_sql.should eql 'select (name,post_ids) from users where name="rahul"'
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'generates select-where with "or" statements' do
|
72
|
+
result_sql = @user.select_with_or
|
73
|
+
result_sql.should eql 'select (name,post_ids) from users where ' +
|
74
|
+
'name="rahul" or name="Rahul Rajagopalan"'
|
75
|
+
end
|
76
|
+
end
|
data/lib/spec/spec_helper.rb
CHANGED
@@ -16,6 +16,7 @@ require 'virtualizees/operator_user'
|
|
16
16
|
require 'virtualizees/while_user'
|
17
17
|
require 'virtualizees/until_user'
|
18
18
|
require 'virtualizees/case_when_user'
|
19
|
+
require 'virtualizees/micro_sql_dsl'
|
19
20
|
|
20
21
|
require 'rspec'
|
21
22
|
|
@@ -120,10 +121,11 @@ module DoRewrite
|
|
120
121
|
# Override this and return a list of rewriters, in order, so do_rewrite
|
121
122
|
# can call them
|
122
123
|
def rewriters
|
123
|
-
raise Abstract
|
124
|
+
raise Abstract, 'Must provide rewriters!'
|
124
125
|
end
|
125
126
|
|
126
|
-
def do_rewrite(method_name, object, verbose = false
|
127
|
+
def do_rewrite(method_name, object, verbose = false,
|
128
|
+
old_and_new_are_same = true)
|
127
129
|
sexp = @methods[method_name]
|
128
130
|
# Run all rewriters on the sexp
|
129
131
|
result = rewriters.reduce(sexp) { |rewritee, rewriter|
|
@@ -141,11 +143,14 @@ module DoRewrite
|
|
141
143
|
# old and new code should produce the same result,
|
142
144
|
# except that @my_*_calls is incremented
|
143
145
|
old_result = object.send method_name
|
144
|
-
|
145
|
-
|
146
|
+
object.instance_eval code_result
|
147
|
+
#VirtualKeywords::ClassReflection.new.install_method_on_instance(
|
148
|
+
#object, code_result)
|
146
149
|
new_result = object.send method_name
|
147
150
|
|
148
|
-
|
151
|
+
if old_and_new_are_same
|
152
|
+
new_result.should eql old_result
|
153
|
+
end
|
149
154
|
end
|
150
155
|
end
|
151
156
|
|
@@ -0,0 +1,110 @@
|
|
1
|
+
class Sql
|
2
|
+
def self.select(columns, options)
|
3
|
+
column_names = columns.join ','
|
4
|
+
table_name = options[:from]
|
5
|
+
|
6
|
+
"select (#{column_names}) from #{table_name}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.dslify object
|
10
|
+
virtualizer = VirtualKeywords::Virtualizer.new :for_instances => [object]
|
11
|
+
|
12
|
+
virtualizer.virtual_if do |condition, then_do, else_do|
|
13
|
+
# In this DSL, all "if"s are postfix and have no else clause.
|
14
|
+
"#{then_do.call} where #{condition.call}"
|
15
|
+
end
|
16
|
+
|
17
|
+
virtualizer.virtual_or do |first, second|
|
18
|
+
"#{first.call} or #{second.call}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class MicroSqlUser
|
24
|
+
# First version, just using built-in syntatic sugar: optional parentheses
|
25
|
+
# and braces around hashes.
|
26
|
+
def simple_select
|
27
|
+
# Limitation: the parser can't handle the new hash syntax
|
28
|
+
# (so here we use the old one)
|
29
|
+
Sql::select [:name, :post_ids], :from => :users
|
30
|
+
end
|
31
|
+
|
32
|
+
# Ok, now mix in some VirtualKeywords!
|
33
|
+
# Use postfix "if" to stand in for "where" clauses. It should look
|
34
|
+
# gramatically similar to SQL, just with "if" instead of "where".
|
35
|
+
#
|
36
|
+
# Calling virtual_if should turn this method into:
|
37
|
+
#
|
38
|
+
# name_is_rahul = 'name="rahul"'
|
39
|
+
# VirtualKeywords::REWRITTEN_KEYWORDS.call_if(
|
40
|
+
# self,
|
41
|
+
# lambda { Sql::select [:name, :post_ids], :from=> :users },
|
42
|
+
# lambda { name_is_rahul },
|
43
|
+
# lambda {}
|
44
|
+
# )
|
45
|
+
def select_with_where
|
46
|
+
# Limitation: 'name="rahul"' is in quotes (we can't actually get
|
47
|
+
# AST nodes because virtual_keywords hides them.)
|
48
|
+
|
49
|
+
# Using the string literal directly is valid Ruby, but produces
|
50
|
+
# an annoying warning.
|
51
|
+
name_is_rahul = 'name="rahul"'
|
52
|
+
Sql::select [:name, :post_ids], :from => :users if name_is_rahul
|
53
|
+
end
|
54
|
+
|
55
|
+
# Should turn into:
|
56
|
+
# def select_with_or
|
57
|
+
# name_is_rahul = "name=\"rahul\""
|
58
|
+
# is_full_name = "name=\"Rahul Rajagopalan\""
|
59
|
+
# VirtualKeywords::REWRITTEN_KEYWORDS.call_if(
|
60
|
+
# self,
|
61
|
+
# lambda do
|
62
|
+
# VirtualKeywords::REWRITTEN_KEYWORDS.call_or(
|
63
|
+
# self,
|
64
|
+
# lambda { name_is_rahul },
|
65
|
+
# lambda { is_full_name }
|
66
|
+
# )
|
67
|
+
# end,
|
68
|
+
# lambda { Sql.select([:name, :post_ids], :from => :users) },
|
69
|
+
# lambda { }
|
70
|
+
# )
|
71
|
+
# end
|
72
|
+
def select_with_or
|
73
|
+
name_is_rahul = 'name="rahul"'
|
74
|
+
is_full_name = 'name="Rahul Rajagopalan"'
|
75
|
+
Sql::select [:name, :post_ids], :from => :users if
|
76
|
+
name_is_rahul or is_full_name
|
77
|
+
end
|
78
|
+
|
79
|
+
# Should turn into:
|
80
|
+
# def select_complex
|
81
|
+
# name_is_rahul = "name=\"rahul\""
|
82
|
+
# right_id = "id=5"
|
83
|
+
# is_full_name = "name=\"Rahul Rajagopalan\""
|
84
|
+
# VirtualKeywords::REWRITTEN_KEYWORDS.call_if(
|
85
|
+
# self,
|
86
|
+
# lambda do
|
87
|
+
# VirtualKeywords::REWRITTEN_KEYWORDS.call_or(
|
88
|
+
# self,
|
89
|
+
# lambda do
|
90
|
+
# VirtualKeywords::REWRITTEN_KEYWORDS.call_and(
|
91
|
+
# self,
|
92
|
+
# lambda { name_is_rahul },
|
93
|
+
# lambda { right_id }
|
94
|
+
# )
|
95
|
+
# end,
|
96
|
+
# lambda { is_full_name }
|
97
|
+
# )
|
98
|
+
# end,
|
99
|
+
# lambda { Sql.select([:name, :post_ids], :from => :users) },
|
100
|
+
# lambda { }
|
101
|
+
# )
|
102
|
+
# end
|
103
|
+
def select_complex
|
104
|
+
name_is_rahul = 'name="rahul"'
|
105
|
+
right_id = 'id=5'
|
106
|
+
is_full_name = 'name="Rahul Rajagopalan"'
|
107
|
+
Sql::select [:name, :post_ids], :from => :users if
|
108
|
+
name_is_rahul and right_id or is_full_name
|
109
|
+
end
|
110
|
+
end
|
@@ -164,4 +164,20 @@ describe 'Virtualizer' do
|
|
164
164
|
result = @inverter.run true
|
165
165
|
result.should be_true
|
166
166
|
end
|
167
|
+
|
168
|
+
it 'virtualizes multiple keywords in the same method' do
|
169
|
+
if_calls = 0
|
170
|
+
and_calls = 0
|
171
|
+
@virtualizer.virtual_if do |condition, then_do, else_do|
|
172
|
+
if_calls += 1
|
173
|
+
if condition.call then then_do.call else else_do.call end
|
174
|
+
end
|
175
|
+
@virtualizer.virtual_and do |first, second|
|
176
|
+
and_calls += 1
|
177
|
+
first.call and second.call
|
178
|
+
end
|
179
|
+
@my_class.foo
|
180
|
+
if_calls.should eql 1
|
181
|
+
and_calls.should eql 1
|
182
|
+
end
|
167
183
|
end
|
data/lib/virtual_keywords.rb
CHANGED
@@ -12,6 +12,7 @@ require 'ruby2ruby'
|
|
12
12
|
|
13
13
|
if RUBY_VERSION.start_with? '1.8'
|
14
14
|
require 'virtual_keywords/deep_copy_array'
|
15
|
+
require 'virtual_keywords/class_mirrorer'
|
15
16
|
require 'virtual_keywords/parser_strategy'
|
16
17
|
require 'virtual_keywords/sexp_stringifier'
|
17
18
|
require 'virtual_keywords/class_reflection'
|
@@ -20,6 +21,7 @@ if RUBY_VERSION.start_with? '1.8'
|
|
20
21
|
require 'virtual_keywords/rewritten_keywords'
|
21
22
|
else
|
22
23
|
require_relative 'virtual_keywords/deep_copy_array'
|
24
|
+
require_relative 'virtual_keywords/class_mirrorer'
|
23
25
|
require_relative 'virtual_keywords/parser_strategy'
|
24
26
|
require_relative 'virtual_keywords/sexp_stringifier'
|
25
27
|
require_relative 'virtual_keywords/class_reflection'
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module VirtualKeywords
|
2
|
+
# Simple data object holding a Class and the name of one of its methods
|
3
|
+
ClassAndMethodName = Struct.new(:klass, :method_name)
|
4
|
+
|
5
|
+
# ClassMirrorer that uses ParseTree (Ruby 1.8)
|
6
|
+
class ClassMirrorer
|
7
|
+
# Initialize a ParseTreeClassMirrorer
|
8
|
+
#
|
9
|
+
# Arguments:
|
10
|
+
# An options Hash with the following key
|
11
|
+
# parser: (ParserStrategy) an object with a method translate, that takes
|
12
|
+
# a class and method name, and returns a syntax tree that can be
|
13
|
+
# sexpified (optional, the default is ParserStrategy.new)
|
14
|
+
def initialize options
|
15
|
+
@parser = options[:parser] || ParserStrategy.new
|
16
|
+
end
|
17
|
+
|
18
|
+
# Map ClassAndMethodNames to sexps
|
19
|
+
#
|
20
|
+
# Arguments:
|
21
|
+
# klass: (Class) the class to mirror.
|
22
|
+
#
|
23
|
+
# Returns:
|
24
|
+
# (Hash[ClassAndMethodName, Sexp]) a hash mapping every method of the
|
25
|
+
# class to parsed output.
|
26
|
+
def mirror klass
|
27
|
+
methods = {}
|
28
|
+
klass.instance_methods(false).each do |method_name|
|
29
|
+
key = ClassAndMethodName.new(klass, method_name)
|
30
|
+
translated = @parser.translate_instance_method(klass, method_name)
|
31
|
+
methods[key] = translated
|
32
|
+
end
|
33
|
+
|
34
|
+
methods
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -2,6 +2,10 @@ module VirtualKeywords
|
|
2
2
|
# Object used to inspect the class hierarchy, and to view
|
3
3
|
# and modify methods of classes.
|
4
4
|
class ClassReflection
|
5
|
+
# Create a new ClassReflection
|
6
|
+
# Arguments:
|
7
|
+
# parser_strategy: (ParserStrategy) the strategy object to use to parse
|
8
|
+
# methods. (Optional, the default value is ParserStrategy.new)
|
5
9
|
def initialize(parser_strategy = ParserStrategy.new)
|
6
10
|
@parser_strategy = parser_strategy
|
7
11
|
end
|
@@ -27,7 +31,7 @@ module VirtualKeywords
|
|
27
31
|
#
|
28
32
|
# Returns:
|
29
33
|
# (Array) All classes that are subclasses of one of the classes in klasses,
|
30
|
-
#
|
34
|
+
# in a flattened array.
|
31
35
|
def subclasses_of_classes(klasses)
|
32
36
|
klasses.map { |klass|
|
33
37
|
subclasses_of_class klass
|
@@ -114,12 +114,20 @@ module VirtualKeywords
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
+
# SexpProcessor subclass that rewrites "not" expressions.
|
117
118
|
class NotRewriter < SexpProcessor
|
118
119
|
def initialize
|
119
120
|
super
|
120
121
|
self.strict = false
|
121
122
|
end
|
122
123
|
|
124
|
+
# Rewrite "not" expressions (automatically called by SexpProcessor#process)
|
125
|
+
#
|
126
|
+
# Arguments:
|
127
|
+
# expression: (Sexp) the :not sexp to rewrite.
|
128
|
+
#
|
129
|
+
# Returns:
|
130
|
+
# (Sexp): a sexp that instead calls REWRITTEN_KEYWORDS.call_not
|
123
131
|
def rewrite_not(expression)
|
124
132
|
value = expression[1]
|
125
133
|
|
@@ -136,15 +144,24 @@ module VirtualKeywords
|
|
136
144
|
end
|
137
145
|
end
|
138
146
|
|
147
|
+
# Raised if a rewriter encounters an unexpected sexp, indicating a bug.
|
139
148
|
class UnexpectedSexp < StandardError
|
140
149
|
end
|
141
150
|
|
151
|
+
# SexpProcessor subclass that rewrites "while" expressions.
|
142
152
|
class WhileRewriter < SexpProcessor
|
143
153
|
def initialize
|
144
154
|
super
|
145
155
|
self.strict = false
|
146
156
|
end
|
147
157
|
|
158
|
+
# Rewrite "while" expressions (automatically called by SexpProcessor#process)
|
159
|
+
#
|
160
|
+
# Arguments:
|
161
|
+
# expression: (Sexp) the :while sexp to rewrite.
|
162
|
+
#
|
163
|
+
# Returns:
|
164
|
+
# (Sexp): a sexp that instead calls REWRITTEN_KEYWORDS.call_while
|
148
165
|
def rewrite_while(expression)
|
149
166
|
condition = expression[1]
|
150
167
|
body = expression[2]
|
@@ -171,12 +188,20 @@ module VirtualKeywords
|
|
171
188
|
end
|
172
189
|
end
|
173
190
|
|
191
|
+
# SexpProcessor subclass that rewrites "until" expressions.
|
174
192
|
class UntilRewriter < SexpProcessor
|
175
193
|
def initialize
|
176
194
|
super
|
177
195
|
self.strict = false
|
178
196
|
end
|
179
197
|
|
198
|
+
# Rewrite "until" expressions (automatically called by SexpProcessor#process)
|
199
|
+
#
|
200
|
+
# Arguments:
|
201
|
+
# expression: (Sexp) the :until sexp to rewrite.
|
202
|
+
#
|
203
|
+
# Returns:
|
204
|
+
# (Sexp): a sexp that instead calls REWRITTEN_KEYWORDS.call_until
|
180
205
|
def rewrite_until(expression)
|
181
206
|
condition = expression[1]
|
182
207
|
body = expression[2]
|
@@ -10,6 +10,7 @@ module VirtualKeywords
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
13
|
+
|
13
14
|
# One problem that needs to be solved is that of converting source code form
|
14
15
|
# files into sexps.
|
15
16
|
|
@@ -17,22 +18,48 @@ module VirtualKeywords
|
|
17
18
|
# In Ruby 1.9, we use RubyParser.
|
18
19
|
# Note that neither set of libraries seems to work in the other version.
|
19
20
|
# Use the strategy pattern, and initialize whichever class is appropriate.
|
21
|
+
|
22
|
+
# Parser strategy that uses ParseTree
|
20
23
|
class ParseTreeStrategy
|
24
|
+
# Initialize a ParseTreeStrategy
|
25
|
+
#
|
26
|
+
# Arguments:
|
27
|
+
# parse_tree: (Class) an instance of the ParseTree singleton, that
|
28
|
+
# translates code into ParseTree output.
|
29
|
+
# sexp_processor: (SexpProcessor) an object that translates ParseTree
|
30
|
+
# output into rewriteable sexps.
|
21
31
|
def initialize(parse_tree, sexp_processor)
|
22
32
|
@parse_tree = parse_tree
|
23
33
|
@sexp_processor = sexp_processor
|
24
34
|
end
|
25
35
|
|
36
|
+
# Translate an instance method of a class.
|
37
|
+
#
|
38
|
+
# Arguments:
|
39
|
+
# klass: (Class) the class.
|
40
|
+
# method_name: (String) the name of the method to translate.
|
41
|
+
#
|
42
|
+
# Returns:
|
43
|
+
# (Sexp) the method, turned into a sexp.
|
26
44
|
def translate_instance_method(klass, method_name)
|
27
45
|
@sexp_processor.process(@parse_tree.translate(klass, method_name))
|
28
46
|
end
|
29
47
|
end
|
30
48
|
|
49
|
+
# Parser strategy that uses ruby_parser
|
31
50
|
class RubyParserStrategy
|
32
51
|
def initialize ruby_parser
|
33
52
|
@ruby_parser = ruby_parser
|
34
53
|
end
|
35
54
|
|
55
|
+
# Translate an instance method of a class.
|
56
|
+
#
|
57
|
+
# Arguments:
|
58
|
+
# klass: (Class) the class.
|
59
|
+
# method_name: (String) the name of the method to translate.
|
60
|
+
#
|
61
|
+
# Returns:
|
62
|
+
# (Sexp) the method, turned into a sexp.
|
36
63
|
def translate_instance_method(klass, method_name)
|
37
64
|
@ruby_parser.parse(klass.instance_method(method_name).source)
|
38
65
|
end
|
@@ -130,7 +130,7 @@ module VirtualKeywords
|
|
130
130
|
or_lambda.call(first, second)
|
131
131
|
end
|
132
132
|
|
133
|
-
# Call a "while" virtual block in place of
|
133
|
+
# Call a "while" virtual block in place of a "while" expression.
|
134
134
|
#
|
135
135
|
# Arguments:
|
136
136
|
# caller_object: (Object) the object whose method this is being called in.
|
@@ -145,16 +145,35 @@ module VirtualKeywords
|
|
145
145
|
while_lambda.call(condition, body)
|
146
146
|
end
|
147
147
|
|
148
|
+
# Call an "until" virtual block in place of an "until" expression.
|
148
149
|
# Unlike unless, until IS a node in the AST
|
149
150
|
# (it doesn't turn into while not)
|
150
151
|
# For now, I'm passing this inconsistency through to the client.
|
151
152
|
# A later modification of this gem may fold while and until into one thing
|
152
153
|
# for consistency.
|
154
|
+
#
|
155
|
+
# Arguments:
|
156
|
+
# caller_object: (Object) the object whose method this is being called in.
|
157
|
+
# condition: (Proc) The condition of the until expression.
|
158
|
+
# body: (Proc) The body of the until expression (which is normally
|
159
|
+
# executed repeatedly)
|
160
|
+
#
|
161
|
+
# Raises:
|
162
|
+
# RewriteLambdaNotProvided if no "until" lambda is available.
|
153
163
|
def call_until(caller_object, condition, body)
|
154
164
|
until_lambda = lambda_or_raise(caller_object, :until)
|
155
165
|
until_lambda.call(condition, body)
|
156
166
|
end
|
157
167
|
|
168
|
+
# Call a "not" virtual block in place of a "not" expression.
|
169
|
+
#
|
170
|
+
# Arguments:
|
171
|
+
# caller_object: (Object) the object whose method this is being called in.
|
172
|
+
# value: (Proc) The operand of the not operator, which would normally be
|
173
|
+
# inverted.
|
174
|
+
#
|
175
|
+
# Raises:
|
176
|
+
# RewriteLambdaNotProvided if no "not" lambda is available.
|
158
177
|
def call_not(caller_object, value)
|
159
178
|
not_lambda = lambda_or_raise(caller_object, :not)
|
160
179
|
not_lambda.call value
|
@@ -13,6 +13,7 @@ module VirtualKeywords
|
|
13
13
|
end
|
14
14
|
|
15
15
|
# Turn a sexp into a string of Ruby code.
|
16
|
+
# Makes a copy first so the inputted sexp is not emptied.
|
16
17
|
#
|
17
18
|
# Arguments:
|
18
19
|
# sexp: (Sexp) the sexp to be stringified.
|
@@ -20,7 +21,8 @@ module VirtualKeywords
|
|
20
21
|
# Returns:
|
21
22
|
# (String) Ruby code equivalent to the sexp.
|
22
23
|
def stringify sexp
|
23
|
-
|
24
|
+
sexp_copy = VirtualKeywords.deep_copy_array sexp
|
25
|
+
@ruby2ruby.process(@unifier.process(sexp_copy))
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
@@ -1,11 +1,18 @@
|
|
1
1
|
# Parent module containing all variables defined as part of virtual_keywords
|
2
2
|
module VirtualKeywords
|
3
|
+
|
4
|
+
class NoSuchInstance < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class NoSuchClass < StandardError
|
8
|
+
end
|
9
|
+
|
3
10
|
# Object that virtualizes keywords.
|
4
11
|
class Virtualizer
|
5
12
|
# Initialize a Virtualizer
|
6
13
|
#
|
7
14
|
# Arguments:
|
8
|
-
# A Hash with the following
|
15
|
+
# A Hash with the following key-value pairs (all optional):
|
9
16
|
# for_classes: (Array[Class]) an array of classes. All methods of objects
|
10
17
|
# created from the given classes will be virtualized (optional, the
|
11
18
|
# default is an empty Array).
|
@@ -54,6 +61,34 @@ module VirtualKeywords
|
|
54
61
|
@rewritten_keywords =
|
55
62
|
input_hash[:rewritten_keywords] || REWRITTEN_KEYWORDS
|
56
63
|
@class_reflection = input_hash[:class_reflection] || ClassReflection.new
|
64
|
+
@class_mirrorer = input_hash[:class_mirrorer] || ClassMirrorer.new({})
|
65
|
+
|
66
|
+
@sexps_for_classes = {}
|
67
|
+
@sexps_for_instances = {}
|
68
|
+
# TODO Refactor out work in the constructor
|
69
|
+
# and maybe move the sexp storage elsewhere.
|
70
|
+
parse_all_the_classes
|
71
|
+
end
|
72
|
+
|
73
|
+
def parse_all_the_classes
|
74
|
+
# Well, not _all_, just the ones provided in the constructor..
|
75
|
+
@for_classes.each do |klass|
|
76
|
+
@sexps_for_classes[klass] = @class_mirrorer.mirror klass
|
77
|
+
end
|
78
|
+
@for_instances.each do |instance|
|
79
|
+
@sexps_for_instances[instance] = @class_mirrorer.mirror instance.class
|
80
|
+
end
|
81
|
+
@for_subclasses_of.each do |klass|
|
82
|
+
subclasses = @class_reflection.subclasses_of_class klass
|
83
|
+
subclasses.each do |subclass|
|
84
|
+
@sexps_for_classes[subclass] = @class_mirrorer.mirror subclass
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def rewritten_sexp(sexp, rewriter)
|
90
|
+
sexp_copy = VirtualKeywords.deep_copy_array sexp
|
91
|
+
rewriter.process sexp_copy
|
57
92
|
end
|
58
93
|
|
59
94
|
# Helper method to rewrite code.
|
@@ -78,11 +113,20 @@ module VirtualKeywords
|
|
78
113
|
def rewrite_all_methods_of_instance(instance, keyword, rewriter, block)
|
79
114
|
@rewritten_keywords.register_lambda_for_object(instance, keyword, block)
|
80
115
|
|
81
|
-
|
82
|
-
|
83
|
-
|
116
|
+
if not @sexps_for_instances.include? instance
|
117
|
+
raise NoSuchInstance, "Tried to rewrite methods of #{instance}," +
|
118
|
+
"but it's not there!"
|
119
|
+
end
|
120
|
+
mirror_hash = @sexps_for_instances[instance]
|
121
|
+
new_mirror_hash = {}
|
122
|
+
mirror_hash.each do |class_and_method_name, sexp|
|
123
|
+
new_sexp = rewritten_sexp(sexp, rewriter)
|
124
|
+
new_code = @sexp_stringifier.stringify new_sexp
|
84
125
|
@class_reflection.install_method_on_instance(instance, new_code)
|
126
|
+
# Save the modified sexp for the next go
|
127
|
+
new_mirror_hash[class_and_method_name] = new_sexp
|
85
128
|
end
|
129
|
+
@sexps_for_instances[instance] = new_mirror_hash
|
86
130
|
end
|
87
131
|
|
88
132
|
# Helper method to rewrite all methods of objects from a class.
|
@@ -95,11 +139,19 @@ module VirtualKeywords
|
|
95
139
|
def rewrite_all_methods_of_class(klass, keyword, rewriter, block)
|
96
140
|
@rewritten_keywords.register_lambda_for_class(klass, keyword, block)
|
97
141
|
|
98
|
-
|
99
|
-
|
100
|
-
|
142
|
+
if not @sexps_for_classes.include? klass
|
143
|
+
raise NoSuchInstance, "Tried to rewrite methods of #{klass}," +
|
144
|
+
"but it's not there!"
|
145
|
+
end
|
146
|
+
mirror_hash = @sexps_for_classes[klass]
|
147
|
+
new_mirror_hash = {}
|
148
|
+
mirror_hash.each do |class_and_method_name, sexp|
|
149
|
+
new_sexp = rewritten_sexp(sexp, rewriter)
|
150
|
+
new_code = @sexp_stringifier.stringify new_sexp
|
101
151
|
@class_reflection.install_method_on_class(klass, new_code)
|
152
|
+
new_mirror_hash[class_and_method_name] = new_sexp
|
102
153
|
end
|
154
|
+
@sexps_for_classes[klass] = new_mirror_hash
|
103
155
|
end
|
104
156
|
|
105
157
|
# Helper method to virtualize a keyword (rewrite with the given block)
|
@@ -150,6 +202,15 @@ module VirtualKeywords
|
|
150
202
|
virtualize_keyword(:or, @or_rewriter, block)
|
151
203
|
end
|
152
204
|
|
205
|
+
# Rewrite "not" expressions.
|
206
|
+
#
|
207
|
+
# Arguments:
|
208
|
+
# &block: The block that will replace "not"s in the objects being
|
209
|
+
# virtualized
|
210
|
+
def virtual_not(&block)
|
211
|
+
virtualize_keyword(:not, @not_rewriter, block)
|
212
|
+
end
|
213
|
+
|
153
214
|
# Rewrite "while" expressions.
|
154
215
|
#
|
155
216
|
# Arguments:
|
@@ -159,12 +220,13 @@ module VirtualKeywords
|
|
159
220
|
virtualize_keyword(:while, @while_rewriter, block)
|
160
221
|
end
|
161
222
|
|
223
|
+
# Rewrite "until" expressions.
|
224
|
+
#
|
225
|
+
# Arguments:
|
226
|
+
# &block: The block that will replace "until"s in the objects being
|
227
|
+
# virtualized
|
162
228
|
def virtual_until(&block)
|
163
229
|
virtualize_keyword(:until, @until_rewriter, block)
|
164
230
|
end
|
165
|
-
|
166
|
-
def virtual_not(&block)
|
167
|
-
virtualize_keyword(:not, @not_rewriter, block)
|
168
|
-
end
|
169
231
|
end
|
170
232
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: virtual_keywords
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ date: 2012-04-21 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &10217980 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *10217980
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: sexp_processor
|
27
|
-
requirement: &
|
27
|
+
requirement: &10216740 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *10216740
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: ParseTree
|
38
|
-
requirement: &
|
38
|
+
requirement: &10216300 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *10216300
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: ruby2ruby
|
49
|
-
requirement: &
|
49
|
+
requirement: &10215780 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *10215780
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: method_source
|
60
|
-
requirement: &
|
60
|
+
requirement: &10215360 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :runtime
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *10215360
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: ruby_parser
|
71
|
-
requirement: &
|
71
|
+
requirement: &10214920 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,13 +76,14 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *10214920
|
80
80
|
description: Replace keyword implementations with your own functions, for DSLs
|
81
81
|
email: rahulrajago@gmail.com
|
82
82
|
executables: []
|
83
83
|
extensions: []
|
84
84
|
extra_rdoc_files: []
|
85
85
|
files:
|
86
|
+
- lib/virtual_keywords/class_mirrorer.rb
|
86
87
|
- lib/virtual_keywords/class_reflection.rb
|
87
88
|
- lib/virtual_keywords/sexp_stringifier.rb
|
88
89
|
- lib/virtual_keywords/rewritten_keywords.rb
|
@@ -137,11 +138,14 @@ files:
|
|
137
138
|
- lib/spec/virtualizees/until_user.rb
|
138
139
|
- lib/spec/virtualizees/not_user.rb
|
139
140
|
- lib/spec/virtualizees/greeter.rb
|
141
|
+
- lib/spec/virtualizees/micro_sql_dsl.rb
|
140
142
|
- lib/spec/not_rewriter_spec.rb
|
141
143
|
- lib/spec/keyword_rewriter_spec.rb
|
144
|
+
- lib/spec/class_mirrorer_spec.rb
|
142
145
|
- lib/spec/spec_helper.rb
|
143
146
|
- lib/spec/class_reflection_spec.rb
|
144
147
|
- lib/spec/or_rewriter_spec.rb
|
148
|
+
- lib/spec/micro_sql_dsl_spec.rb
|
145
149
|
- lib/Rakefile
|
146
150
|
homepage: http://github.com/rahulraj/virtual_keywords
|
147
151
|
licenses: []
|