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
data/spec/spec_cov.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -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,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
|