wood 0.1.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.
- checksums.yaml +7 -0
- data/Gemfile +8 -0
- data/LICENSE +19 -0
- data/README.md +21 -0
- data/Rakefile +55 -0
- data/examples/binary_ops.rb +138 -0
- data/examples/node_finder.rb +96 -0
- data/examples/visitors.rb +105 -0
- data/lib/core_ext.rb +4 -0
- data/lib/core_ext/class.rb +13 -0
- data/lib/core_ext/enumerable.rb +47 -0
- data/lib/core_ext/kernel.rb +16 -0
- data/lib/core_ext/string.rb +32 -0
- data/lib/wood.rb +13 -0
- data/lib/wood/indented_printer.rb +48 -0
- data/lib/wood/node.rb +259 -0
- data/lib/wood/node_rewriter.rb +29 -0
- data/lib/wood/node_visitor.rb +60 -0
- data/lib/wood/nodes.rb +22 -0
- data/lib/wood/nodes/assignment.rb +34 -0
- data/lib/wood/nodes/break.rb +4 -0
- data/lib/wood/nodes/code_block.rb +44 -0
- data/lib/wood/nodes/continue.rb +4 -0
- data/lib/wood/nodes/for_loop.rb +11 -0
- data/lib/wood/nodes/function.rb +45 -0
- data/lib/wood/nodes/if_else.rb +6 -0
- data/lib/wood/nodes/literals.rb +68 -0
- data/lib/wood/nodes/nested_node.rb +5 -0
- data/lib/wood/nodes/no_op.rb +7 -0
- data/lib/wood/nodes/null.rb +4 -0
- data/lib/wood/nodes/operator.rb +28 -0
- data/lib/wood/nodes/return.rb +15 -0
- data/lib/wood/nodes/switch.rb +13 -0
- data/lib/wood/nodes/variable.rb +24 -0
- data/lib/wood/nodes/while_loop.rb +10 -0
- data/lib/wood/tree_pattern.rb +14 -0
- data/lib/wood/tree_pattern/any_matcher.rb +20 -0
- data/lib/wood/tree_pattern/matcher.rb +40 -0
- data/lib/wood/tree_pattern/node_finder.rb +164 -0
- data/lib/wood/tree_pattern/or_matcher.rb +33 -0
- data/lib/wood/tree_pattern/pattern_builder.rb +57 -0
- data/lib/wood/tree_pattern/pattern_callback.rb +5 -0
- data/lib/wood/tree_pattern/replacement_builder.rb +27 -0
- data/lib/wood/tree_pattern/type_matcher.rb +59 -0
- data/lib/wood/tree_pattern/variable_matcher.rb +31 -0
- data/lib/wood/tree_rewriter.rb +67 -0
- data/lib/wood/types.rb +318 -0
- data/lib/wood/version.rb +3 -0
- data/spec/core_ext/enumerable_spec.rb +34 -0
- data/spec/core_ext/string_spec.rb +25 -0
- data/spec/spec_cov.rb +2 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/wood/indented_printer_spec.rb +61 -0
- data/spec/wood/node_spec.rb +258 -0
- data/spec/wood/node_visitor_spec.rb +43 -0
- data/spec/wood/nodes/code_block_spec.rb +32 -0
- data/spec/wood/nodes/no_op_spec.rb +5 -0
- data/spec/wood/nodes/operator_spec.rb +31 -0
- data/spec/wood/tree_pattern/any_matcher_spec.rb +32 -0
- data/spec/wood/tree_pattern/matcher_spec.rb +316 -0
- data/spec/wood/tree_pattern/node_finder_spec.rb +104 -0
- data/spec/wood/tree_pattern/or_matcher_spec.rb +43 -0
- data/spec/wood/tree_pattern/type_matcher_spec.rb +69 -0
- data/spec/wood/tree_pattern/variable_matcher_spec.rb +37 -0
- data/spec/wood/tree_rewriter_spec.rb +351 -0
- metadata +114 -0
@@ -0,0 +1,316 @@
|
|
1
|
+
describe Wood::TreePattern::Matcher do
|
2
|
+
it "matches any node" do
|
3
|
+
class MatchAll
|
4
|
+
include Wood::TreePattern::Matcher
|
5
|
+
pattern { _ }
|
6
|
+
end
|
7
|
+
|
8
|
+
MatchAll.pattern_builders.should_not be_empty
|
9
|
+
|
10
|
+
m = MatchAll.new
|
11
|
+
m.should === Operator[
|
12
|
+
name: :*,
|
13
|
+
left: IntLiteral[3],
|
14
|
+
right: IntLiteral[2]
|
15
|
+
]
|
16
|
+
end
|
17
|
+
|
18
|
+
it "matches string literal nodes" do
|
19
|
+
class MatchString
|
20
|
+
include Wood::TreePattern::Matcher
|
21
|
+
pattern {
|
22
|
+
StringLiteral[val]
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
m = MatchString.new
|
27
|
+
m.should === StringLiteral["Hello, World!"]
|
28
|
+
end
|
29
|
+
|
30
|
+
it "matches a specific int literal node" do
|
31
|
+
class MatchIntLit3
|
32
|
+
include Wood::TreePattern::Matcher
|
33
|
+
pattern {
|
34
|
+
IntLiteral[3]
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
m = MatchIntLit3.new
|
39
|
+
m.should === IntLiteral[3]
|
40
|
+
m.should_not === IntLiteral[4]
|
41
|
+
end
|
42
|
+
|
43
|
+
it "rewrites a int literal to a string literal node" do
|
44
|
+
class RewriteIntLiteral
|
45
|
+
include Wood::TreePattern::Matcher
|
46
|
+
pattern {
|
47
|
+
IntLiteral[val]
|
48
|
+
}.rewrite {
|
49
|
+
StringLiteral[val.to_s]
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
rw = RewriteIntLiteral.new
|
54
|
+
rw.replacement_for(IntLiteral[10]).should == StringLiteral["10"]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "rewrites a nested node" do
|
58
|
+
class RewriteNestedNode
|
59
|
+
include Wood::TreePattern::Matcher
|
60
|
+
pattern {
|
61
|
+
Operator[
|
62
|
+
name: :*,
|
63
|
+
left: left,
|
64
|
+
right: IntLiteral[10]
|
65
|
+
]
|
66
|
+
}.rewrite {
|
67
|
+
Operator[
|
68
|
+
name: :*,
|
69
|
+
left: left,
|
70
|
+
right: IntLiteral[100]
|
71
|
+
]
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
rw = RewriteNestedNode.new
|
76
|
+
# no change if no match:
|
77
|
+
rw.replacement_for(StringLiteral[10]).should == StringLiteral[10]
|
78
|
+
|
79
|
+
1.upto(10).each do |i|
|
80
|
+
original = Operator[name: :*, left: IntLiteral[i], right: IntLiteral[10]]
|
81
|
+
replacement = Operator[name: :*, left: IntLiteral[i], right: IntLiteral[100]]
|
82
|
+
|
83
|
+
rw.replacement_for(original).should == replacement
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it "defines VariableMatchers via method_missing" do
|
88
|
+
class VarMatcherWithMethodMissing
|
89
|
+
include Wood::TreePattern::Matcher
|
90
|
+
pattern {
|
91
|
+
StringLiteral[val]
|
92
|
+
}.rewrite {
|
93
|
+
StringLiteral[val * 2]
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
VarMatcherWithMethodMissing.new.replacement_for(StringLiteral["foo"]).should == StringLiteral["foofoo"]
|
98
|
+
end
|
99
|
+
|
100
|
+
it "defines VariableMatchers via _" do
|
101
|
+
class VarMatcherWith_
|
102
|
+
include Wood::TreePattern::Matcher
|
103
|
+
pattern {
|
104
|
+
StringLiteral[_(:val)]
|
105
|
+
}.rewrite {
|
106
|
+
StringLiteral[val * 2]
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
VarMatcherWith_.new.replacement_for(StringLiteral["foo"]).should == StringLiteral["foofoo"]
|
111
|
+
end
|
112
|
+
|
113
|
+
it "returns the node itself if no ReplacementBuilder has been defined" do
|
114
|
+
class NoReplacement
|
115
|
+
include Wood::TreePattern::Matcher
|
116
|
+
pattern { StringLiteral[val] }
|
117
|
+
end
|
118
|
+
|
119
|
+
sl = StringLiteral["foo"]
|
120
|
+
nr = NoReplacement.new
|
121
|
+
|
122
|
+
nr.should === sl
|
123
|
+
nr.replacement_for(sl).should == sl
|
124
|
+
end
|
125
|
+
|
126
|
+
it "returns a matched child node" do
|
127
|
+
class ChildNodeReplacement
|
128
|
+
include Wood::TreePattern::Matcher
|
129
|
+
pattern {
|
130
|
+
Operator[name: :*, left: left, right: _]
|
131
|
+
}.rewrite {
|
132
|
+
left
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
left = Operator[name: :+, left: IntLiteral[10], right: IntLiteral[42]]
|
137
|
+
op = Operator[name: :*, left: left, right: IntLiteral[10]]
|
138
|
+
ChildNodeReplacement.new.replacement_for(op).should == left
|
139
|
+
end
|
140
|
+
|
141
|
+
it "has node defined (apart from variables) in the rewrite block" do
|
142
|
+
class NodeDefinedInRewrite
|
143
|
+
include Wood::TreePattern::Matcher
|
144
|
+
pattern {
|
145
|
+
StringLiteral["foo"]
|
146
|
+
}.rewrite {
|
147
|
+
node.should == StringLiteral["foo"]
|
148
|
+
StringLiteral["bar"]
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
match = StringLiteral["foo"]
|
153
|
+
no_match = StringLiteral["nope"]
|
154
|
+
NodeDefinedInRewrite.new.replacement_for(match).should == StringLiteral["bar"]
|
155
|
+
NodeDefinedInRewrite.new.replacement_for(no_match).should == no_match
|
156
|
+
NodeDefinedInRewrite.new.should === match
|
157
|
+
NodeDefinedInRewrite.new.should_not === no_match
|
158
|
+
end
|
159
|
+
|
160
|
+
it "calls a block if a pattern is matched" do
|
161
|
+
MatchedNodes = []
|
162
|
+
class CallBlockMatcher
|
163
|
+
include Wood::TreePattern::Matcher
|
164
|
+
pattern {
|
165
|
+
StringLiteral["foo"]
|
166
|
+
}.perform {
|
167
|
+
MatchedNodes << node
|
168
|
+
}
|
169
|
+
|
170
|
+
pattern {
|
171
|
+
Operator[name: :*, left: IntLiteral[left], right: IntLiteral[right]]
|
172
|
+
}.perform {
|
173
|
+
MatchedNodes << IntLiteral[left * right]
|
174
|
+
}
|
175
|
+
end
|
176
|
+
|
177
|
+
cbm = CallBlockMatcher.new
|
178
|
+
|
179
|
+
MatchedNodes.should be_empty
|
180
|
+
|
181
|
+
cbm.should === StringLiteral["foo"]
|
182
|
+
MatchedNodes.should == [StringLiteral["foo"]]
|
183
|
+
|
184
|
+
cbm.should_not === StringLiteral["foobar"]
|
185
|
+
MatchedNodes.should == [StringLiteral["foo"]]
|
186
|
+
|
187
|
+
cbm.should === Operator[
|
188
|
+
name: :*,
|
189
|
+
left: IntLiteral[2],
|
190
|
+
right: IntLiteral[3]
|
191
|
+
]
|
192
|
+
MatchedNodes.should == [
|
193
|
+
StringLiteral["foo"], IntLiteral[6]
|
194
|
+
]
|
195
|
+
end
|
196
|
+
|
197
|
+
it "names the root node in a pattern" do
|
198
|
+
class MyRootNodeMatcher
|
199
|
+
include Wood::TreePattern::Matcher
|
200
|
+
pattern {
|
201
|
+
my_string StringLiteral[val]
|
202
|
+
}.perform {
|
203
|
+
my_string.should == node
|
204
|
+
my_string.value.should == node.value
|
205
|
+
my_string.value.should == val
|
206
|
+
}
|
207
|
+
end
|
208
|
+
|
209
|
+
MyRootNodeMatcher.new.should === StringLiteral["foo"]
|
210
|
+
end
|
211
|
+
|
212
|
+
it "names child nodes in a nested pattern" do
|
213
|
+
class MyNestedNodeMatcher
|
214
|
+
include Wood::TreePattern::Matcher
|
215
|
+
pattern {
|
216
|
+
Operator[
|
217
|
+
name: opname,
|
218
|
+
left: outer_left(Operator[
|
219
|
+
name: :*,
|
220
|
+
left: inner_left(Operator[
|
221
|
+
name: _,
|
222
|
+
left: IntLiteral[_],
|
223
|
+
right: IntLiteral[_]
|
224
|
+
]),
|
225
|
+
right: inner_right(_)
|
226
|
+
]),
|
227
|
+
right: outer_right(IntLiteral[42])
|
228
|
+
]
|
229
|
+
}.perform {
|
230
|
+
opname.should == node.name
|
231
|
+
outer_left.should == node.left
|
232
|
+
outer_right.should == node.right
|
233
|
+
inner_left.should == node.left.left
|
234
|
+
inner_right.should == node.left.right
|
235
|
+
}
|
236
|
+
end
|
237
|
+
|
238
|
+
MyNestedNodeMatcher.new.should === Operator[
|
239
|
+
name: :+,
|
240
|
+
left: Operator[
|
241
|
+
name: :*,
|
242
|
+
left: Operator[
|
243
|
+
name: :+,
|
244
|
+
left: IntLiteral[3],
|
245
|
+
right: IntLiteral[5]
|
246
|
+
],
|
247
|
+
right: IntLiteral[2]
|
248
|
+
],
|
249
|
+
right: IntLiteral[42]
|
250
|
+
]
|
251
|
+
end
|
252
|
+
|
253
|
+
it "matches a nested node via a within-pattern" do
|
254
|
+
MatchedInts = []
|
255
|
+
class MyWithinMatcher
|
256
|
+
include Wood::TreePattern::Matcher
|
257
|
+
pattern {
|
258
|
+
CodeBlock
|
259
|
+
}.perform {
|
260
|
+
within(node) {
|
261
|
+
pattern {
|
262
|
+
IntLiteral[v]
|
263
|
+
}.perform {
|
264
|
+
MatchedInts << v
|
265
|
+
}
|
266
|
+
}
|
267
|
+
}
|
268
|
+
end
|
269
|
+
|
270
|
+
m = MyWithinMatcher.new
|
271
|
+
m.should === CodeBlock[
|
272
|
+
expressions: [
|
273
|
+
StringLiteral["foo"],
|
274
|
+
IntLiteral[10],
|
275
|
+
FloatLiteral[1.5],
|
276
|
+
IntLiteral[100]
|
277
|
+
]
|
278
|
+
]
|
279
|
+
MatchedInts.should == [10, 100]
|
280
|
+
end
|
281
|
+
|
282
|
+
it "matches nodes with a given type" do
|
283
|
+
MyType = Struct.new(:name).new(:my_type)
|
284
|
+
class MyTypedMatcher
|
285
|
+
include Wood::TreePattern::Matcher
|
286
|
+
pattern {
|
287
|
+
Variable::Reference.with_type Wood::Types::Array[MyType]
|
288
|
+
}.perform {
|
289
|
+
node.type = MyType
|
290
|
+
}
|
291
|
+
|
292
|
+
pattern {
|
293
|
+
Variable::Reference[name: /foo_(\d+)/].with_type(Wood::Types::U8)
|
294
|
+
}.perform {
|
295
|
+
node.name = :"__gen__#{node.name}"
|
296
|
+
}
|
297
|
+
end
|
298
|
+
|
299
|
+
m = MyTypedMatcher.new
|
300
|
+
ref = Variable::Reference[
|
301
|
+
name: :foo,
|
302
|
+
type: Wood::Types::Array[MyType]
|
303
|
+
]
|
304
|
+
|
305
|
+
m.should === ref
|
306
|
+
ref.type.should == MyType
|
307
|
+
|
308
|
+
ref = Variable::Reference[
|
309
|
+
name: :foo_123,
|
310
|
+
type: Wood::Types::U8
|
311
|
+
]
|
312
|
+
|
313
|
+
m.should === ref
|
314
|
+
ref.name.should == :__gen__foo_123
|
315
|
+
end
|
316
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
describe Wood::TreePattern::NodeFinder do
|
2
|
+
before do
|
3
|
+
@nf = Wood::TreePattern::NodeFinder.new
|
4
|
+
end
|
5
|
+
|
6
|
+
it "finds a node up the tree" do
|
7
|
+
ast = WhileLoop[
|
8
|
+
condition: TrueLiteral[],
|
9
|
+
body: CodeBlock[
|
10
|
+
IfElse[
|
11
|
+
condition: Operator[
|
12
|
+
name: :<,
|
13
|
+
left: Variable::Reference[:x],
|
14
|
+
right: IntLiteral[10]
|
15
|
+
],
|
16
|
+
then_branch: CodeBlock[Return[CharLiteral["a"]]],
|
17
|
+
else_branch: CodeBlock[Return[CharLiteral["b"]]]
|
18
|
+
]
|
19
|
+
]
|
20
|
+
]
|
21
|
+
|
22
|
+
@nf.ast = ast.body.first.else_branch.first
|
23
|
+
@nf.find_parent_node(WhileLoop).should == ast
|
24
|
+
@nf.find_parent_node(IfElse).should == ast.body.first
|
25
|
+
@nf.find_parent_node(Operator).should be_nil
|
26
|
+
@nf.find_parent_node(Function::Call).should be_nil
|
27
|
+
@nf.find_parent_node(Return).should be_nil
|
28
|
+
end
|
29
|
+
|
30
|
+
it "finds multiple nodes up the tree" do
|
31
|
+
ast = WhileLoop[
|
32
|
+
condition: Operator[
|
33
|
+
name: :<,
|
34
|
+
left: Variable::Reference[:x],
|
35
|
+
right: Variable::Reference[:y]
|
36
|
+
],
|
37
|
+
body: CodeBlock[
|
38
|
+
Assignment[
|
39
|
+
var: Variable::Reference[:x],
|
40
|
+
value: Function::Call[name: :foo, args: [Variable::Reference[:y]]]
|
41
|
+
],
|
42
|
+
WhileLoop[
|
43
|
+
condition: Operator[
|
44
|
+
name: :<,
|
45
|
+
left: Variable::Reference[:y],
|
46
|
+
right: Variable::Reference[:z]
|
47
|
+
],
|
48
|
+
body: CodeBlock[
|
49
|
+
Assignment[
|
50
|
+
var: Variable::Reference[:y],
|
51
|
+
value: Function::Call[name: :foo, args: [Variable::Reference[:z]]]
|
52
|
+
]
|
53
|
+
]
|
54
|
+
]
|
55
|
+
]
|
56
|
+
]
|
57
|
+
|
58
|
+
@nf.ast = ast.body[1].body.first
|
59
|
+
@nf.find_parent_nodes(WhileLoop).should == [ast.body[1], ast]
|
60
|
+
@nf.find_parent_nodes(Function::Declaration).should == []
|
61
|
+
@nf.find_parent_nodes(Assignment).should == []
|
62
|
+
end
|
63
|
+
|
64
|
+
it "finds a node down the tree" do
|
65
|
+
ast = WhileLoop[
|
66
|
+
condition: Variable::Reference[:x],
|
67
|
+
body: CodeBlock[
|
68
|
+
Switch[
|
69
|
+
expression: Variable::Reference[:x],
|
70
|
+
cases: [
|
71
|
+
Switch::Case[
|
72
|
+
expression: Variable::Reference[:FOO_1],
|
73
|
+
body: CodeBlock[Return[StringLiteral["foo1"]]]
|
74
|
+
],
|
75
|
+
Switch::Case[
|
76
|
+
expression: Variable::Reference[:FOO_2],
|
77
|
+
body: CodeBlock[Return[StringLiteral["foo2"]]]
|
78
|
+
],
|
79
|
+
Switch::Case[
|
80
|
+
expression: Variable::Reference[:FOO_3],
|
81
|
+
body: CodeBlock[Return[StringLiteral["foo3"]]]
|
82
|
+
],
|
83
|
+
Switch::DefaultCase[
|
84
|
+
body: CodeBlock[Return[Variable::Reference[:x]]]
|
85
|
+
]
|
86
|
+
]
|
87
|
+
]
|
88
|
+
]
|
89
|
+
]
|
90
|
+
|
91
|
+
switch = ast.body.first
|
92
|
+
@nf.ast = ast
|
93
|
+
|
94
|
+
@nf.find_child_node(Switch).should == switch
|
95
|
+
@nf.find_child_node(Switch::Case).should == switch.cases.first
|
96
|
+
@nf.find_child_node(Switch::DefaultCase).should == switch.cases[3]
|
97
|
+
@nf.find_child_nodes(IfElse).should == []
|
98
|
+
|
99
|
+
@nf.find_child_nodes(Switch::Case).should ==
|
100
|
+
switch.cases.grep(Switch::Case)
|
101
|
+
@nf.find_child_nodes(Return).should ==
|
102
|
+
switch.cases.map{|c| c.body.first }.grep(Return)
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
describe Wood::TreePattern::OrMatcher do
|
2
|
+
OrMatcher = Wood::TreePattern::OrMatcher
|
3
|
+
|
4
|
+
it "matches either a or b but not c" do
|
5
|
+
a = Operator[name: :*, left: IntLiteral[10], right: IntLiteral[0]]
|
6
|
+
b = Operator[name: :*, left: IntLiteral[2], right: IntLiteral[3]]
|
7
|
+
c = Operator[name: :+, left: a, right: b]
|
8
|
+
om = OrMatcher.new(a, b)
|
9
|
+
|
10
|
+
om.should === a
|
11
|
+
om.should === b
|
12
|
+
om.should_not === c
|
13
|
+
end
|
14
|
+
|
15
|
+
it "is equal to a or b" do
|
16
|
+
a = Operator[name: :*, left: IntLiteral[3], right: IntLiteral[2]]
|
17
|
+
b = Operator[name: :+, left: IntLiteral[5], right: IntLiteral[4]]
|
18
|
+
c = Operator[name: :+, left: IntLiteral[1], right: IntLiteral[2]]
|
19
|
+
|
20
|
+
om = OrMatcher.new(a, b)
|
21
|
+
|
22
|
+
om.should == a
|
23
|
+
om.should == b
|
24
|
+
|
25
|
+
om.should_not == c
|
26
|
+
end
|
27
|
+
|
28
|
+
context "debugging output" do
|
29
|
+
before do
|
30
|
+
@a = Operator[name: :*, left: IntLiteral[3], right: IntLiteral[2]]
|
31
|
+
@b = Null[]
|
32
|
+
end
|
33
|
+
|
34
|
+
it "returns a sexp" do
|
35
|
+
OrMatcher.new(@a, @b).sexp.should == [:or_matcher, @a.sexp, @b.sexp]
|
36
|
+
end
|
37
|
+
|
38
|
+
it "returns an inspect string" do
|
39
|
+
om = OrMatcher.new(@a, @b)
|
40
|
+
om.inspect.should == om.sexp.inspect
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|