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,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