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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +8 -0
  3. data/LICENSE +19 -0
  4. data/README.md +21 -0
  5. data/Rakefile +55 -0
  6. data/examples/binary_ops.rb +138 -0
  7. data/examples/node_finder.rb +96 -0
  8. data/examples/visitors.rb +105 -0
  9. data/lib/core_ext.rb +4 -0
  10. data/lib/core_ext/class.rb +13 -0
  11. data/lib/core_ext/enumerable.rb +47 -0
  12. data/lib/core_ext/kernel.rb +16 -0
  13. data/lib/core_ext/string.rb +32 -0
  14. data/lib/wood.rb +13 -0
  15. data/lib/wood/indented_printer.rb +48 -0
  16. data/lib/wood/node.rb +259 -0
  17. data/lib/wood/node_rewriter.rb +29 -0
  18. data/lib/wood/node_visitor.rb +60 -0
  19. data/lib/wood/nodes.rb +22 -0
  20. data/lib/wood/nodes/assignment.rb +34 -0
  21. data/lib/wood/nodes/break.rb +4 -0
  22. data/lib/wood/nodes/code_block.rb +44 -0
  23. data/lib/wood/nodes/continue.rb +4 -0
  24. data/lib/wood/nodes/for_loop.rb +11 -0
  25. data/lib/wood/nodes/function.rb +45 -0
  26. data/lib/wood/nodes/if_else.rb +6 -0
  27. data/lib/wood/nodes/literals.rb +68 -0
  28. data/lib/wood/nodes/nested_node.rb +5 -0
  29. data/lib/wood/nodes/no_op.rb +7 -0
  30. data/lib/wood/nodes/null.rb +4 -0
  31. data/lib/wood/nodes/operator.rb +28 -0
  32. data/lib/wood/nodes/return.rb +15 -0
  33. data/lib/wood/nodes/switch.rb +13 -0
  34. data/lib/wood/nodes/variable.rb +24 -0
  35. data/lib/wood/nodes/while_loop.rb +10 -0
  36. data/lib/wood/tree_pattern.rb +14 -0
  37. data/lib/wood/tree_pattern/any_matcher.rb +20 -0
  38. data/lib/wood/tree_pattern/matcher.rb +40 -0
  39. data/lib/wood/tree_pattern/node_finder.rb +164 -0
  40. data/lib/wood/tree_pattern/or_matcher.rb +33 -0
  41. data/lib/wood/tree_pattern/pattern_builder.rb +57 -0
  42. data/lib/wood/tree_pattern/pattern_callback.rb +5 -0
  43. data/lib/wood/tree_pattern/replacement_builder.rb +27 -0
  44. data/lib/wood/tree_pattern/type_matcher.rb +59 -0
  45. data/lib/wood/tree_pattern/variable_matcher.rb +31 -0
  46. data/lib/wood/tree_rewriter.rb +67 -0
  47. data/lib/wood/types.rb +318 -0
  48. data/lib/wood/version.rb +3 -0
  49. data/spec/core_ext/enumerable_spec.rb +34 -0
  50. data/spec/core_ext/string_spec.rb +25 -0
  51. data/spec/spec_cov.rb +2 -0
  52. data/spec/spec_helper.rb +3 -0
  53. data/spec/wood/indented_printer_spec.rb +61 -0
  54. data/spec/wood/node_spec.rb +258 -0
  55. data/spec/wood/node_visitor_spec.rb +43 -0
  56. data/spec/wood/nodes/code_block_spec.rb +32 -0
  57. data/spec/wood/nodes/no_op_spec.rb +5 -0
  58. data/spec/wood/nodes/operator_spec.rb +31 -0
  59. data/spec/wood/tree_pattern/any_matcher_spec.rb +32 -0
  60. data/spec/wood/tree_pattern/matcher_spec.rb +316 -0
  61. data/spec/wood/tree_pattern/node_finder_spec.rb +104 -0
  62. data/spec/wood/tree_pattern/or_matcher_spec.rb +43 -0
  63. data/spec/wood/tree_pattern/type_matcher_spec.rb +69 -0
  64. data/spec/wood/tree_pattern/variable_matcher_spec.rb +37 -0
  65. data/spec/wood/tree_rewriter_spec.rb +351 -0
  66. 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