wood 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 06461ae1653d67b6313731b6cf748bbb42f2f56f
|
4
|
+
data.tar.gz: a70aa6de996d62eaec7a70b6e18fea232db71eb8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 144f8160f047702df5d83f7594440d88a7c7bdee35e89c55aa300a633bce941c113504df175963ba3c9a11bbedc27fe665ecb9a35c4b2cc2f1ee5dde06eb2675
|
7
|
+
data.tar.gz: 052a2acf8f7585575434e63b8a8b12f695ebf0613097c13960759d31bf22fb63df4dadedc4b5163640a7dbf016e4fe2afea3d3c329417d858a0f912ee93a84d7
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2013-2014 Christopher Bertels
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
5
|
+
|
6
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
7
|
+
disclaimer.
|
8
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
9
|
+
disclaimer in the documentation and/or other materials provided with the distribution.
|
10
|
+
* Neither the name of the original authors nor the names of its contributors may be used to endorse or promote
|
11
|
+
products derived from this software without specific prior written permission.
|
12
|
+
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
14
|
+
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
15
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
16
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
17
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
18
|
+
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
19
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Wood - Tree manipulation library
|
2
|
+
|
3
|
+
Wood is a library for creating, manipulating & rewriting trees,
|
4
|
+
in particular Abstract Syntax Trees (ASTs).
|
5
|
+
|
6
|
+
Wood was extracted from a source to source compiler I worked on for
|
7
|
+
Marft Inc. in 2013/14.
|
8
|
+
Marft has granted me the rights to this library, so I'm releasing it under the
|
9
|
+
3-clause BSD license (see LICENSE file).
|
10
|
+
|
11
|
+
The compiler that was built using this library (which was then called Forest)
|
12
|
+
translated a subset of ANSI C to multiple target languages, including Java, C#
|
13
|
+
and JavaScript.
|
14
|
+
|
15
|
+
Wood provides an easy to use DSL for searching & rewriting whole sub-trees
|
16
|
+
in place, which can be used for things like subsequently translating a parse tree
|
17
|
+
into a target language tree (in the compiler project's case to Java::AST,
|
18
|
+
CSharp::AST and JavaScript::AST trees).
|
19
|
+
|
20
|
+
You can find some example tree node definitions & rewriting rules in the
|
21
|
+
examples directory.
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require "rspec/core/rake_task"
|
2
|
+
|
3
|
+
RootDir = File.expand_path(File.dirname(__FILE__))
|
4
|
+
|
5
|
+
task :default => :spec
|
6
|
+
|
7
|
+
desc "Run RSpec tests"
|
8
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
9
|
+
t.rspec_opts = ["-r ./spec/spec_helper", "--format progress"]
|
10
|
+
t.verbose = false
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Run RSpec tests & generate test coverage report"
|
14
|
+
RSpec::Core::RakeTask.new(:spec_cov) do |t|
|
15
|
+
t.rspec_opts = ["-r ./spec/spec_cov", "-r ./spec/spec_helper", "--format progress"]
|
16
|
+
t.verbose = false
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "Generate & open test coverage report"
|
20
|
+
task :coverage => :spec_cov do
|
21
|
+
system "open coverage/index.html"
|
22
|
+
end
|
23
|
+
|
24
|
+
task "coverage/" => :coverage
|
25
|
+
|
26
|
+
desc "Cleanup generated files and stuff"
|
27
|
+
task :clean do
|
28
|
+
rm_rf "#{RootDir}/coverage/"
|
29
|
+
rm_rf "#{RootDir}/doc/"
|
30
|
+
Dir.glob("./**/*.class").each do |f|
|
31
|
+
rm f
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
LOC_EXCLUDE = []
|
37
|
+
def count_loc(dir, exclude_files = LOC_EXCLUDE)
|
38
|
+
source_files = Dir.glob("#{dir}/**/*") - exclude_files
|
39
|
+
source_files.reject!{ |f| File.directory?(f) }
|
40
|
+
`wc -l #{source_files.join(" ")}`.split("\n").last.split("total").first.to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "Show LOC"
|
44
|
+
task :loc do
|
45
|
+
source_dirs = ["lib", "spec"]
|
46
|
+
total = 0
|
47
|
+
source_dirs.each do |d|
|
48
|
+
lines = count_loc(d)
|
49
|
+
total += lines
|
50
|
+
printf("%5s : %i\n", d, lines)
|
51
|
+
end
|
52
|
+
total = "total : #{total}"
|
53
|
+
puts "-" * total.size
|
54
|
+
puts total
|
55
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require_relative "../lib/wood"
|
2
|
+
|
3
|
+
require "pp"
|
4
|
+
|
5
|
+
include Wood
|
6
|
+
include Wood::Nodes
|
7
|
+
|
8
|
+
# Let's define some node types
|
9
|
+
|
10
|
+
class BinaryOperator < Node
|
11
|
+
child_nodes :left, :right
|
12
|
+
end
|
13
|
+
|
14
|
+
class PlusOperator < BinaryOperator
|
15
|
+
end
|
16
|
+
|
17
|
+
class MinusOperator < BinaryOperator
|
18
|
+
end
|
19
|
+
|
20
|
+
class MultiplyOperator < BinaryOperator
|
21
|
+
end
|
22
|
+
|
23
|
+
class DivideOperator < BinaryOperator
|
24
|
+
end
|
25
|
+
|
26
|
+
class NumberLiteral < Wood::Node
|
27
|
+
child_nodes :value
|
28
|
+
end
|
29
|
+
|
30
|
+
RedundantBinaryOpsRewriter = TreeRewriter.new do
|
31
|
+
Zero = IntLiteral[0]
|
32
|
+
|
33
|
+
pattern {
|
34
|
+
opts = { left: l, right: Zero }
|
35
|
+
PlusOperator[opts] | MinusOperator[opts]
|
36
|
+
}.rewrite {
|
37
|
+
l
|
38
|
+
}
|
39
|
+
|
40
|
+
pattern {
|
41
|
+
op = { left: Zero, right: r }
|
42
|
+
PlusOperator[op] | MinusOperator[op]
|
43
|
+
}.rewrite {
|
44
|
+
r
|
45
|
+
}
|
46
|
+
|
47
|
+
pattern {
|
48
|
+
MultiplyOperator[left: Zero, right: _]
|
49
|
+
}.rewrite {
|
50
|
+
Zero
|
51
|
+
}
|
52
|
+
|
53
|
+
pattern {
|
54
|
+
MultiplyOperator[left: _, right: Zero]
|
55
|
+
}.rewrite {
|
56
|
+
Zero
|
57
|
+
}
|
58
|
+
|
59
|
+
pattern {
|
60
|
+
DivideOperator[left: _, right: Zero]
|
61
|
+
}.perform {
|
62
|
+
raise "Can't divide by zero. Fix this ASAP!"
|
63
|
+
}
|
64
|
+
|
65
|
+
pattern {
|
66
|
+
PlusOperator[left: IntLiteral[x], right: IntLiteral[y]]
|
67
|
+
}.rewrite {
|
68
|
+
if x == y
|
69
|
+
IntLiteral[x * 2]
|
70
|
+
end
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
# Now, let's use the rewriter:
|
75
|
+
|
76
|
+
add_zero = PlusOperator[
|
77
|
+
left: MultiplyOperator[
|
78
|
+
left: IntLiteral[2],
|
79
|
+
right: IntLiteral[0]
|
80
|
+
],
|
81
|
+
right: IntLiteral[0]
|
82
|
+
]
|
83
|
+
|
84
|
+
# rewrite once:
|
85
|
+
result1 = RedundantBinaryOpsRewriter.rewrite add_zero
|
86
|
+
|
87
|
+
# rewrite repeatedly until no more rewrites happen:
|
88
|
+
result2 = RedundantBinaryOpsRewriter.rewrite add_zero, true
|
89
|
+
|
90
|
+
puts "Original AST:"
|
91
|
+
pp add_zero
|
92
|
+
# => [:plus_operator,
|
93
|
+
# [:multiply_operator, [:int_literal, 2], [:int_literal, 0]],
|
94
|
+
# [:int_literal, 0]]
|
95
|
+
|
96
|
+
puts "\nAfter a single rewrite:"
|
97
|
+
pp result1
|
98
|
+
# => [:multiply_operator, [:int_literal, 2], [:int_literal, 0]]
|
99
|
+
|
100
|
+
puts "\nAfter all possible rewrites:"
|
101
|
+
pp result2
|
102
|
+
# => [:int_literal, 0]
|
103
|
+
|
104
|
+
# Let's let it crash by dividing by (static) 0 literal
|
105
|
+
|
106
|
+
divide_by_zero = DivideOperator[
|
107
|
+
left: IntLiteral[10],
|
108
|
+
right: MultiplyOperator[
|
109
|
+
left: IntLiteral[100],
|
110
|
+
right: IntLiteral[0]
|
111
|
+
]
|
112
|
+
]
|
113
|
+
|
114
|
+
puts "\nDivide by zero with 1 rewrite:"
|
115
|
+
pp RedundantBinaryOpsRewriter.rewrite(divide_by_zero)
|
116
|
+
# => [:divide_operator, [:int_literal, 10], [:int_literal, 0]]
|
117
|
+
|
118
|
+
puts "\nDivide by zero with repeated rewrites fails:"
|
119
|
+
begin
|
120
|
+
RedundantBinaryOpsRewriter.rewrite(divide_by_zero)
|
121
|
+
rescue => e
|
122
|
+
pp e
|
123
|
+
end
|
124
|
+
# => #<RuntimeError: Can't divide by zero. Fix this ASAP!>
|
125
|
+
|
126
|
+
puts "\nRewrite 2 + 2 => 4"
|
127
|
+
pp RedundantBinaryOpsRewriter.rewrite(PlusOperator[
|
128
|
+
left: IntLiteral[2],
|
129
|
+
right: IntLiteral[2]
|
130
|
+
])
|
131
|
+
# => [:int_literal, 4]
|
132
|
+
|
133
|
+
puts "\nDon't rewrite 3 + 2"
|
134
|
+
pp RedundantBinaryOpsRewriter.rewrite(PlusOperator[
|
135
|
+
left: IntLiteral[3],
|
136
|
+
right: IntLiteral[2]
|
137
|
+
])
|
138
|
+
# => [:plus_operator, [:int_literal, 3], [:int_literal, 2]]
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require_relative "../lib/wood"
|
2
|
+
|
3
|
+
require "pp"
|
4
|
+
|
5
|
+
include Wood
|
6
|
+
include Wood::Nodes
|
7
|
+
|
8
|
+
func_def = Function::Definition[
|
9
|
+
name: :print_while_positive,
|
10
|
+
type: Types::Int,
|
11
|
+
args: [
|
12
|
+
Function::Argument[name: :x, type: Types::Int]
|
13
|
+
],
|
14
|
+
body: CodeBlock[
|
15
|
+
WhileLoop[
|
16
|
+
condition: Operator[
|
17
|
+
name: :<,
|
18
|
+
left: Variable::Reference[:x],
|
19
|
+
right: IntLiteral[0]
|
20
|
+
],
|
21
|
+
body: CodeBlock[
|
22
|
+
Function::Call[
|
23
|
+
name: :print,
|
24
|
+
args: [
|
25
|
+
Variable::Reference[:x]
|
26
|
+
]
|
27
|
+
],
|
28
|
+
SubAssignment[
|
29
|
+
var: Variable::Reference[:x],
|
30
|
+
value: Operator[
|
31
|
+
name: :/,
|
32
|
+
left: Variable::Reference[:x],
|
33
|
+
right: IntLiteral[2]
|
34
|
+
]
|
35
|
+
],
|
36
|
+
IfElse[
|
37
|
+
condition: Operator[
|
38
|
+
name: :==,
|
39
|
+
left: Operator[
|
40
|
+
name: :%,
|
41
|
+
left: Variable::Reference[:x],
|
42
|
+
right: IntLiteral[2]
|
43
|
+
],
|
44
|
+
right: IntLiteral[0]
|
45
|
+
],
|
46
|
+
then_branch: Function::Call[
|
47
|
+
name: :print,
|
48
|
+
args: [
|
49
|
+
StringLiteral["even!"]
|
50
|
+
]
|
51
|
+
]
|
52
|
+
]
|
53
|
+
]
|
54
|
+
]
|
55
|
+
]
|
56
|
+
]
|
57
|
+
|
58
|
+
|
59
|
+
# the above should look something like this in C:
|
60
|
+
# int print_while_positive(int x) {
|
61
|
+
# while(x > 0) {
|
62
|
+
# print(x);
|
63
|
+
# x -= (x / 2);
|
64
|
+
# if(x % 2 == 0)
|
65
|
+
# print("even!");
|
66
|
+
# }
|
67
|
+
# }
|
68
|
+
|
69
|
+
# let's find the inner print with the string literal:
|
70
|
+
|
71
|
+
pp func_def.find_child_node Function::Call[
|
72
|
+
name: :print,
|
73
|
+
args: [Node.with_type(Wood::Types::String)]
|
74
|
+
]
|
75
|
+
# => [:call, :print, [[:string_literal, "even!"]]]
|
76
|
+
|
77
|
+
if_else = func_def.find_child_node IfElse
|
78
|
+
pp if_else
|
79
|
+
# => [:if,
|
80
|
+
# [:operator, :==,
|
81
|
+
# [:operator, :%,
|
82
|
+
# [:var_ref, :x],
|
83
|
+
# [:int_literal, 2]],
|
84
|
+
# [:int_literal, 0]],
|
85
|
+
# [:call, :print, [[:string_literal, "even!"]]],
|
86
|
+
# nil]
|
87
|
+
|
88
|
+
# we can also look up the tree:
|
89
|
+
pp func_def == if_else.find_parent_node(Function::Definition)
|
90
|
+
# => true
|
91
|
+
|
92
|
+
pp if_else == if_else.then_branch.find_parent_node(IfElse)
|
93
|
+
# => true
|
94
|
+
|
95
|
+
pp func_def.find_child_node(WhileLoop) == if_else.find_parent_node(WhileLoop)
|
96
|
+
# => true
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require_relative "../lib/wood"
|
2
|
+
|
3
|
+
require "pp"
|
4
|
+
|
5
|
+
include Wood
|
6
|
+
include Wood::Nodes
|
7
|
+
|
8
|
+
class SourcePrinter
|
9
|
+
include Wood::NodeVisitor
|
10
|
+
include Wood::IndentedPrinter
|
11
|
+
|
12
|
+
# We can keep custom context information available
|
13
|
+
# for visited nodes up the visit call stack
|
14
|
+
SourcePrinterContext = Struct.new(:ignored, :io_flushed)
|
15
|
+
|
16
|
+
def new_context
|
17
|
+
SourcePrinterContext.new(false, false)
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :io
|
21
|
+
|
22
|
+
def initialize(io)
|
23
|
+
@io = io
|
24
|
+
end
|
25
|
+
|
26
|
+
def indentation
|
27
|
+
2
|
28
|
+
end
|
29
|
+
|
30
|
+
def var_ref(var_ref)
|
31
|
+
print var_ref.name
|
32
|
+
end
|
33
|
+
|
34
|
+
def string_literal(sl)
|
35
|
+
print sl.value.inspect
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class JavaPrinter < SourcePrinter
|
40
|
+
def assign(assign)
|
41
|
+
visit_type assign.var
|
42
|
+
print " "
|
43
|
+
visit assign.var
|
44
|
+
print " = "
|
45
|
+
visit assign.value
|
46
|
+
print ";"
|
47
|
+
newline
|
48
|
+
end
|
49
|
+
|
50
|
+
def string_type(st)
|
51
|
+
print "String"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class RubyPrinter < SourcePrinter
|
56
|
+
def assign(assign)
|
57
|
+
visit assign.var
|
58
|
+
print " = "
|
59
|
+
visit assign.value
|
60
|
+
newline
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class JavaScriptPrinter < SourcePrinter
|
65
|
+
def assign(assign)
|
66
|
+
print "var "
|
67
|
+
visit assign.var
|
68
|
+
print " = "
|
69
|
+
visit(assign.value)
|
70
|
+
|
71
|
+
# # Context example:
|
72
|
+
# # visit returns the context object for that visit
|
73
|
+
# if visit(assign.value).io_flushed
|
74
|
+
# # do something here if context.io_flushed was set on visit of assign.value
|
75
|
+
# end
|
76
|
+
|
77
|
+
print ";"
|
78
|
+
newline
|
79
|
+
end
|
80
|
+
|
81
|
+
def string_literal(sl)
|
82
|
+
super
|
83
|
+
# # Example use of context:
|
84
|
+
# context.io_flushed = true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
assign_funcall = Assignment[
|
89
|
+
var: Variable::Reference[name: :foo, type: Types::String],
|
90
|
+
value: StringLiteral["Hello, World!"]
|
91
|
+
]
|
92
|
+
|
93
|
+
# Usage:
|
94
|
+
|
95
|
+
print "AST: "
|
96
|
+
pp assign_funcall
|
97
|
+
|
98
|
+
print "Java output: "
|
99
|
+
JavaPrinter.new($stdout).visit assign_funcall
|
100
|
+
|
101
|
+
print "Ruby output: "
|
102
|
+
RubyPrinter.new($stdout).visit assign_funcall
|
103
|
+
|
104
|
+
print "JS output: "
|
105
|
+
JavaScriptPrinter.new($stdout).visit assign_funcall
|