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,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
@@ -0,0 +1,8 @@
1
+ # Ruby Dependencies
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ group :development do
6
+ gem "rspec"
7
+ gem "simplecov"
8
+ end
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.
@@ -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.
@@ -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