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