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