wood 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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,2 @@
1
+ require "simplecov"
2
+ SimpleCov.start
@@ -0,0 +1,3 @@
1
+ require_relative "../lib/wood"
2
+
3
+ include Wood
@@ -0,0 +1,61 @@
1
+ class MyIndentedPrinter
2
+ include Wood::IndentedPrinter
3
+ attr_accessor :io, :indentation
4
+ def initialize(indentation = 2)
5
+ @io = StringIO.new
6
+ @indentation = indentation
7
+ end
8
+
9
+ def output
10
+ @io.string
11
+ end
12
+ end
13
+
14
+ describe Wood::IndentedPrinter do
15
+ before :each do
16
+ @p = MyIndentedPrinter.new
17
+ end
18
+
19
+ it "starts out with indentation = 0" do
20
+ @p.print "Hello"
21
+ @p.output.should == "Hello"
22
+ end
23
+
24
+ it "indents by the given amount" do
25
+ @p.print "Hello"
26
+ @p.with_indentation {
27
+ @p.print "World"
28
+ }
29
+ @p.output.should == "Hello\n World\n"
30
+ end
31
+
32
+ it "indents by a specific indentation" do
33
+ @p.indentation = 5
34
+ @p.print "Hello"
35
+ @p.with_indentation {
36
+ @p.print "yo"
37
+ @p.newline
38
+ @p.print "done"
39
+ }
40
+ @p.output.should == "Hello\n yo\n done\n"
41
+ end
42
+
43
+ it "indents and unindents the output explicitly" do
44
+ @p.print "ok"
45
+ @p.indent
46
+ @p.newline
47
+ @p.print "what"
48
+ @p.unindent
49
+ @p.newline
50
+ @p.print "done"
51
+
52
+ @p.output.should == "ok\n what\ndone"
53
+ end
54
+
55
+ it "prints followed by a newline" do
56
+ @p.println "start"
57
+ @p.println "done"
58
+
59
+ @p.output.should == "start\ndone\n"
60
+ end
61
+ end
@@ -0,0 +1,258 @@
1
+ class MyNode < Node
2
+ child_nodes :a, :b, :c
3
+ end
4
+
5
+ include Wood::Nodes
6
+
7
+ describe Node do
8
+ it "has no default child nodes" do
9
+ Node.__child_nodes__.should == []
10
+ end
11
+
12
+ it "includes the ClassMethods on subclasses" do
13
+ MyNode.__child_nodes__.should == [:a, :b, :c]
14
+ MyNode.node_name.should == :my_node
15
+ end
16
+
17
+ it "returns the correct node_name" do
18
+ module MyModule
19
+ class MyNode < Node
20
+ end
21
+ end
22
+
23
+ MyNode.node_name.should == :my_node
24
+ MyModule::MyNode.node_name.should == :my_module_my_node
25
+ end
26
+
27
+ it "provides a default sexp implementation" do
28
+ class EmptyNode < Node
29
+ end
30
+
31
+ class ValueNode < Node
32
+ child_nodes :value
33
+ def sexp
34
+ [:value_node, value]
35
+ end
36
+ end
37
+
38
+ e = EmptyNode.new
39
+ n = MyNode.new a: e, b: e, c: e
40
+ n.sexp.should == [:my_node, [:empty_node], [:empty_node], [:empty_node]]
41
+
42
+ n = MyNode.new a: e, b: ValueNode.new(value: "foo"), c: e
43
+ n.sexp.should == [
44
+ :my_node, [:empty_node], [:value_node, "foo"], [:empty_node]
45
+ ]
46
+ end
47
+
48
+ it "compares nodes correctly for equality" do
49
+ n1 = MyNode.new a: EmptyNode.new, b: MyNode.new, c: EmptyNode.new
50
+ n2 = MyNode.new a: EmptyNode.new, b: MyNode.new, c: EmptyNode.new
51
+ n3 = MyNode.new a: MyNode.new, b: EmptyNode.new, c: EmptyNode.new
52
+
53
+ n1.should == n1
54
+ n2.should == n2
55
+ n3.should == n3
56
+
57
+ n1.should == n2
58
+ n2.should == n1
59
+
60
+ n1.should_not == n3
61
+ n2.should_not == n3
62
+
63
+ n3.should_not == n1
64
+ n3.should_not == n2
65
+ end
66
+
67
+ it "compares nodes correctly for matching" do
68
+ any = Wood::TreePattern::AnyMatcher.new
69
+ IntLiteral[10].should === IntLiteral[10]
70
+ IntLiteral[10].should === IntLiteral[any]
71
+ IntLiteral[any].should === IntLiteral[10]
72
+
73
+ Operator[
74
+ name: :*,
75
+ left: any,
76
+ right: any
77
+ ].should === Operator[
78
+ name: :*,
79
+ left: IntLiteral[-10],
80
+ right: IntLiteral[10]
81
+ ]
82
+
83
+ Operator[name: :*].should === Operator[
84
+ name: :*,
85
+ left: IntLiteral[10],
86
+ right: IntLiteral[10]
87
+ ]
88
+
89
+ Operator[
90
+ name: :*,
91
+ left: IntLiteral[10],
92
+ right: IntLiteral[10]
93
+ ].should_not === Operator[name: :*]
94
+ end
95
+
96
+ it "supports node construction via Node##[]" do
97
+ a = MyNode[a: EmptyNode[], b: MyNode[], c: EmptyNode[]]
98
+ b = MyNode.new a: EmptyNode.new, b: MyNode.new, c: EmptyNode.new
99
+
100
+ a.should == b
101
+ end
102
+
103
+ it "returns its child nodes" do
104
+ node = MyNode[
105
+ a: StringLiteral["foo"],
106
+ b: IntLiteral[10],
107
+ c: TrueLiteral.new
108
+ ]
109
+
110
+ node.child_nodes.should == [
111
+ StringLiteral["foo"],
112
+ IntLiteral[10],
113
+ TrueLiteral.new
114
+ ]
115
+
116
+ MyNode.new.child_nodes.should == [nil, nil, nil]
117
+ end
118
+
119
+ it "returns the names of its child nodes" do
120
+ MyNode.new.child_node_names.should == [:a, :b, :c]
121
+
122
+ class EmptyNode < Node
123
+ end
124
+ EmptyNode.new.child_node_names.should be_empty
125
+ end
126
+
127
+ it "sets a child value" do
128
+ n = MyNode[
129
+ a: nil,
130
+ b: IntLiteral[10],
131
+ c: StringLiteral["foo"]
132
+ ]
133
+ n.set_child(:a, IntLiteral[42])
134
+ n.should == MyNode[
135
+ a: IntLiteral[42],
136
+ b: IntLiteral[10],
137
+ c: StringLiteral["foo"]
138
+ ]
139
+ end
140
+
141
+ it "gets a child value" do
142
+ n = MyNode[
143
+ a: IntLiteral[42],
144
+ b: IntLiteral[10],
145
+ c: StringLiteral["foo"]
146
+ ]
147
+ n.get_child(:a).should == IntLiteral[42]
148
+ n.get_child(:b).should == IntLiteral[10]
149
+ n.get_child(:c).should == StringLiteral["foo"]
150
+ end
151
+
152
+ it "returns a default sexp implementation for basic ruby types" do
153
+ :foo.sexp.should == :foo
154
+ "foo".sexp.should == "foo"
155
+ 1.sexp.should == 1
156
+ 1.5.sexp.should == 1.5
157
+ nil.sexp.should == nil
158
+ true.sexp.should == true
159
+ false.sexp.should == false
160
+ [1,2,3].sexp.should == [1,2,3]
161
+ end
162
+
163
+ context "finds node up & down the tree" do
164
+ before :each do
165
+ @outer_while = WhileLoop[
166
+ condition: Operator[
167
+ name: :<,
168
+ left: Variable::Reference[:x],
169
+ right: Variable::Reference[:y]
170
+ ],
171
+ body: CodeBlock[
172
+ Assignment[
173
+ var: Variable::Reference[:x],
174
+ value: Function::Call[name: :foo, args: [Variable::Reference[:y]]]
175
+ ],
176
+ WhileLoop[
177
+ condition: Operator[
178
+ name: :<,
179
+ left: Variable::Reference[:y],
180
+ right: Variable::Reference[:z]
181
+ ],
182
+ body: CodeBlock[
183
+ Assignment[
184
+ var: Variable::Reference[:y],
185
+ value: Function::Call[name: :bar, args: [Variable::Reference[:z]]]
186
+ ],
187
+ Assignment[
188
+ var: Variable::Reference[:z],
189
+ value: IntLiteral[10]
190
+ ]
191
+ ]
192
+ ]
193
+ ]
194
+ ]
195
+
196
+ @x_assign = @outer_while.body[0]
197
+ @inner_while = @outer_while.body[1]
198
+ @y_assign = @inner_while.body[0]
199
+ @z_assign = @inner_while.body[1]
200
+ end
201
+
202
+ it "finds nodes up the tree" do
203
+ @x_assign.find_parent_node(WhileLoop).should == @outer_while
204
+ @y_assign.find_parent_node(WhileLoop).should == @inner_while
205
+ @inner_while.find_parent_node(WhileLoop).should == @outer_while
206
+
207
+ @x_assign.find_parent_nodes(WhileLoop).should == [@outer_while]
208
+ @y_assign.find_parent_nodes(WhileLoop).should == [@inner_while, @outer_while]
209
+ @inner_while.find_parent_nodes(WhileLoop).should == [@outer_while]
210
+
211
+ @x_assign.find_parent_node(Assignment).should be_nil
212
+ @y_assign.find_parent_node(Assignment).should be_nil
213
+ @inner_while.find_parent_node(Assignment).should be_nil
214
+ end
215
+
216
+ it "finds nodes down the tree" do
217
+ @outer_while.find_child_node(WhileLoop).should == @inner_while
218
+ @outer_while.find_child_node(Assignment).should == @x_assign
219
+ @inner_while.find_child_node(WhileLoop).should be_nil
220
+ @inner_while.find_child_node(Assignment).should == @y_assign
221
+
222
+ @outer_while.find_child_nodes(WhileLoop).should == [@inner_while]
223
+ @outer_while.find_child_nodes(Assignment).should == [@x_assign, @y_assign, @z_assign]
224
+ @inner_while.find_child_nodes(WhileLoop).should == []
225
+ @inner_while.find_child_nodes(Assignment).should == [@y_assign, @z_assign]
226
+ end
227
+
228
+ it "deletes nodes down the tree" do
229
+ @outer_while.delete_child_node(
230
+ Assignment[
231
+ var: Variable::Reference[name: :y],
232
+ value: Function::Call[
233
+ name: :bar,
234
+ args: [ Variable::Reference[name: :z] ]
235
+ ]
236
+ ]
237
+ ).should == @y_assign
238
+ @inner_while.body.size.should == 1
239
+ @inner_while.body[0].should == @z_assign
240
+ end
241
+
242
+ it "deleted all child nodes that match down the tree" do
243
+ any = Wood::TreePattern::AnyMatcher.new
244
+
245
+ @outer_while.delete_child_nodes(
246
+ Assignment[
247
+ name: any,
248
+ value: Function::Call[
249
+ name: any,
250
+ args: any
251
+ ]
252
+ ]
253
+ ).should == [@x_assign, @y_assign]
254
+ @inner_while.body.size.should == 1
255
+ @inner_while.body[0].should == @z_assign
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,43 @@
1
+ class MyVisitor
2
+ include Wood::NodeVisitor
3
+
4
+ ignore_nodes :node_c, :node_d
5
+
6
+ attr_reader :value
7
+
8
+ def node_a(node)
9
+ @value = "in node_a"
10
+ end
11
+
12
+ def node_b(node)
13
+ @value = "in node_b"
14
+ end
15
+ end
16
+
17
+ describe Wood::NodeVisitor do
18
+ before :each do
19
+ @v = MyVisitor.new
20
+ @node = Struct.new(:node_name)
21
+ end
22
+
23
+ it "dispatches based on node_name" do
24
+ a = @node.new("node_a")
25
+ b = @node.new("node_b")
26
+
27
+ @v.visit(a)
28
+ @v.value.should == "in node_a"
29
+ @v.visit(b)
30
+ @v.value.should == "in node_b"
31
+ end
32
+
33
+ it "raises an exception when trying to visit an undefined node" do
34
+ expect {
35
+ @v.visit @node.new("foo")
36
+ }.to raise_error(NoMethodError)
37
+ end
38
+
39
+ it "ignores specified nodes" do
40
+ @v.visit(@node.new("node_c")).ignored.should be_truthy
41
+ @v.visit(@node.new("node_d")).ignored.should be_truthy
42
+ end
43
+ end
@@ -0,0 +1,32 @@
1
+ describe Wood::Nodes::CodeBlock do
2
+ it "defaults to an empty array of expressions" do
3
+ CodeBlock[].expressions.should be_empty
4
+ CodeBlock[].should be_empty
5
+ CodeBlock[].empty?.should be_truthy
6
+ end
7
+
8
+ it "behaves as an Enumerable and forward delegates #each" do
9
+ expressions = [
10
+ Function::Call[name: :foo],
11
+ Function::Call[name: :bar],
12
+ Return[]
13
+ ]
14
+ cb = CodeBlock[expressions: expressions]
15
+ cb.to_a.should == expressions
16
+ expressions.size.times do |i|
17
+ cb[i].should == expressions[i]
18
+ end
19
+ cb << Function::Call[name: :final]
20
+ cb.size.should == 4
21
+ cb[-1].should == Function::Call[name: :final]
22
+ end
23
+
24
+ it "returns an array of the body's sexps" do
25
+ expressions = [
26
+ Function::Call[name: :foo],
27
+ Function::Call[name: :bar],
28
+ Return[]
29
+ ]
30
+ CodeBlock[expressions: expressions].sexp.should == expressions.map(&:sexp)
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ describe Wood::Nodes::NoOp do
2
+ it "has type void" do
3
+ Wood::Nodes::NoOp[].type.should == Wood::Types::Void
4
+ end
5
+ end
@@ -0,0 +1,31 @@
1
+ describe Wood::Nodes::Operator do
2
+ Operator = Wood::Nodes::Operator
3
+
4
+ it "is boolean" do
5
+ Operator::BOOL_OPS.each do |name|
6
+ Operator[name: name].boolean?.should be_truthy
7
+ end
8
+ end
9
+
10
+ it "it is not boolean" do
11
+ Operator::NON_BOOL_OPS.each do |name|
12
+ Operator[name: name].boolean?.should be_falsey
13
+ end
14
+ end
15
+
16
+ it "returns itself as a boolean operator" do
17
+ Operator::NON_BOOL_OPS.each do |name|
18
+ op = Operator[name: name]
19
+ op.to_boolean.should == Operator[
20
+ name: :"!=",
21
+ left: op,
22
+ right: IntLiteral[0]
23
+ ]
24
+ end
25
+
26
+ Operator::BOOL_OPS.each do |name|
27
+ op = Operator[name: name]
28
+ op.to_boolean.should == op
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ describe Wood::TreePattern::AnyMatcher do
2
+ before do
3
+ @m = Wood::TreePattern::AnyMatcher.new
4
+ end
5
+
6
+ it "matches any node" do
7
+ @m.should === Function::Call[name: :foo]
8
+ @m.should === Variable::Declaration[
9
+ name: :x,
10
+ type: Wood::Types::Int,
11
+ value: IntLiteral[10]
12
+ ]
13
+ @m.should === CharLiteral["c"]
14
+ @m.should === IntLiteral[10]
15
+
16
+ IntLiteral[@m].should === IntLiteral[10]
17
+ end
18
+
19
+ it "is equal to any node" do
20
+ @m.should == IntLiteral[10]
21
+ @m.should == StringLiteral["foo"]
22
+ @m.should == @m
23
+ end
24
+
25
+ it "returns a sexp notation" do
26
+ @m.sexp.should == [:any_matcher]
27
+ end
28
+
29
+ it "returns a string notation of self" do
30
+ @m.inspect.should == @m.sexp.inspect
31
+ end
32
+ end