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,4 @@
1
+ require_relative "core_ext/string"
2
+ require_relative "core_ext/enumerable"
3
+ require_relative "core_ext/kernel"
4
+ require_relative "core_ext/class"
@@ -0,0 +1,13 @@
1
+ class Class
2
+ def delegate(methods:, to:)
3
+ code = methods.map do |method|
4
+ "
5
+ def #{method}(*args, &block)
6
+ #{to}.#{method}(*args, &block)
7
+ end
8
+ "
9
+ end.join("\n")
10
+
11
+ class_eval code
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ module Enumerable
2
+ # Helper class for iterating over elements in a {Enumerable} while
3
+ # calling a given block between each element.
4
+ class InBetweenEnum
5
+ include Enumerable
6
+ def initialize(enum, block)
7
+ @enum = enum
8
+ @block = block
9
+ end
10
+
11
+ # Calls a given block with each element while also calling `@block`
12
+ # in between each element during iteration.
13
+ def each
14
+ block = -> { block = @block }
15
+ @enum.each do |x|
16
+ block.call
17
+ yield x
18
+ end
19
+ end
20
+ end
21
+
22
+ # Creates a {InBetweenEnum} for iteration while calling `block` in between.
23
+ #
24
+ # @param block [Proc, #call] Block to be called in between each element
25
+ #
26
+ # @return [InBetweenEnum] Enumerable like object for iterating over elements
27
+ # in `self` while calling `block` in between.
28
+ def in_between(&block)
29
+ InBetweenEnum.new(self, block)
30
+ end
31
+
32
+ # Same as #map but passing along the index with each element to a given block.
33
+ #
34
+ # @return [Array] Mapped items based on block given.
35
+ # @example
36
+ # ["a","b","c"].map_with_index do |x, i|
37
+ # x + (i * 2).to_s
38
+ # end
39
+ # => ["a0", "b2", "c4"]
40
+ def map_with_index
41
+ arr = []
42
+ each_with_index do |x, i|
43
+ arr << yield(x, i)
44
+ end
45
+ arr
46
+ end
47
+ end
@@ -0,0 +1,16 @@
1
+ module Kernel
2
+ # Requires any ruby files in a given path.
3
+ #
4
+ # @param path [String] Path to require all ruby files from.
5
+ # @param relative_to [String] Path relative to where `path` exists.
6
+ #
7
+ # @example require_all "compile_stages", relative_to: __FILE__
8
+ def require_all(path, relative_to: nil)
9
+ if rt = relative_to
10
+ path = "#{File.dirname(rt)}/#{path}"
11
+ end
12
+ Dir.glob("#{path}/*.rb").each do |f|
13
+ require f
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ class String
2
+ # Returns a snake cases version of a {String}.
3
+ #
4
+ # @return [String] Snake cased version of `self`.
5
+ # @example "FooBarBaz".snake_cased # => "foo_bar_baz"
6
+ def snake_cased
7
+ r1 = /([A-Z]+)([A-Z][a-z])/
8
+ r2 = /([a-z\d])([A-Z])/
9
+
10
+ gsub(r1,'\1_\2').gsub(r2,'\1_\2').tr("-", "_").downcase
11
+ end
12
+
13
+ # Returns a camel cased version of a {String}.
14
+ #
15
+ # @return [String] Camel cased version of `self`.
16
+ # @example "foo_bar_baz".camel_cased # => "FooBarBaz"
17
+ def camel_cased
18
+ split("_").map(&:capitalize).join
19
+ end
20
+
21
+ # Returns `self` with all trailing whitespace removed.
22
+ #
23
+ # @return [String] `self` without any trailing whitespace.
24
+ def without_trailing_whitespace
25
+ lines.map(&:rstrip).join("\n")
26
+ end
27
+
28
+ def capitalized?
29
+ c = self[0]
30
+ c && c.upcase == c
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ require_relative "core_ext"
2
+
3
+ require_relative "wood/node"
4
+ require_relative "wood/node_rewriter"
5
+ require_relative "wood/node_visitor"
6
+ require_relative "wood/tree_pattern"
7
+ require_relative "wood/tree_rewriter"
8
+ require_relative "wood/nodes"
9
+
10
+ require_relative "wood/types"
11
+
12
+ require_relative "wood/indented_printer"
13
+ require_relative "wood/version"
@@ -0,0 +1,48 @@
1
+ module Wood
2
+ # Mixin used for indented pretty-printing of code.
3
+ # Expects `#io` and `#indentation` methods to work.
4
+ module IndentedPrinter
5
+ # Yields to a given block while indenting the output generated
6
+ # within the block.
7
+ def with_indentation
8
+ indent
9
+ newline
10
+ yield
11
+ unindent
12
+ newline
13
+ end
14
+
15
+ # Prints any arguments passed to `io`.
16
+ # @param args [Array] List of objects to print to `io`.
17
+ def print(*args)
18
+ args.each do |a|
19
+ io << a
20
+ end
21
+ end
22
+
23
+ # Prints a {String} to `io` followed by a line break.
24
+ # @param str [String] String to be printed to `io` followed by a newline.
25
+ def println(str)
26
+ print str, "\n"
27
+ end
28
+
29
+ # Inserts a line break into `io` together with correct reindentation on
30
+ # the next line.
31
+ def newline
32
+ io << "\n"
33
+ io << (" " * @__indent__.to_i)
34
+ end
35
+
36
+ # Increases the current indentation by `indentation` spaces.
37
+ def indent
38
+ @__indent__ ||= 0
39
+ @__indent__ += indentation
40
+ end
41
+
42
+ # Decreases the current indentation by `indentation` spaces.
43
+ def unindent
44
+ @__indent__ ||= 0
45
+ @__indent__ -= indentation
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,259 @@
1
+ module Wood
2
+ # Base node class.
3
+ # Represents a generic AST node to be visited, processed & rewritten
4
+ # in the compiler pipeline.
5
+ class Node
6
+ def self.with_type(type_pattern)
7
+ Wood::TreePattern::TypeMatcher.new(self, type_pattern)
8
+ end
9
+
10
+ def with_type(type_pattern)
11
+ Wood::TreePattern::TypeMatcher.new(self, type_pattern)
12
+ end
13
+
14
+ module ClassMethods
15
+ # @return [Array<Symbol>]
16
+ # List of child node names for a {Node} class.
17
+ def __child_nodes__
18
+ superclass.__child_nodes__ + (@__child_nodes__ || [])
19
+ end
20
+
21
+ # @param child_nodes [Array<Symbol>]
22
+ # Sets the list of child nodes for a {Node} class.
23
+ def child_nodes(*child_nodes)
24
+ @__child_nodes__ = child_nodes
25
+ attr_accessor *child_nodes
26
+ end
27
+
28
+ # Prefix to be ignored for #node_name
29
+ attr_accessor :node_name_prefix
30
+
31
+ # Overrides the default node name of a {Node} class.
32
+ #
33
+ # @param node_name [Symbol] New node name for {Node} class.
34
+ def node_name(node_name = nil)
35
+ @__node_name__ = node_name if node_name
36
+ unless @__node_name__
37
+ camel_cased = (name.split("::") - node_name_prefix.split("::")).join
38
+ @__node_name__ = camel_cased.snake_cased.to_sym
39
+ end
40
+ @__node_name__
41
+ end
42
+
43
+ # Instantiates a new instance of `self` with `options`.
44
+ def [](options = {})
45
+ new(options)
46
+ end
47
+ end
48
+
49
+ def self.__child_nodes__
50
+ []
51
+ end
52
+
53
+ def self.inherited(klass)
54
+ klass.extend ClassMethods
55
+ klass.node_name_prefix = "Wood::Nodes"
56
+ klass.__send__ :include, Wood::TreePattern::CombinatorialMatching
57
+ klass.extend Wood::TreePattern::CombinatorialMatching
58
+ end
59
+
60
+ Position = Struct.new(:line, :column)
61
+
62
+ # {Position} of node in the original source code.
63
+ attr_reader :pos
64
+ # Type of node, like they're defined in {Wood::Types}.
65
+ attr_accessor :type
66
+ # Parent of this node within the AST.
67
+ attr_accessor :parent_node
68
+
69
+ def initialize(options = {})
70
+ @pos = options[:pos] || Position.new
71
+ @type = options[:type]
72
+
73
+ self.class.__child_nodes__.each do |c|
74
+ node = options[c]
75
+ instance_variable_set("@#{c}", node)
76
+ if node.respond_to? :parent_node=
77
+ node.parent_node = self
78
+ end
79
+ end
80
+
81
+ setup
82
+ end
83
+
84
+ # Gets called on initialization, should be overwritten by subclass
85
+ # if any checking needs to be done on the child node data etc.
86
+ def setup
87
+ end
88
+
89
+ # @return [Symbol] Node name of this node.
90
+ def node_name
91
+ self.class.node_name
92
+ end
93
+
94
+ # @return [Array] S-expression version of `self`.
95
+ def sexp
96
+ [self.class.node_name, *child_nodes.map(&:sexp)]
97
+ end
98
+
99
+ # @param block [Proc, #call] Block to be called with each child node.
100
+ def each_child(&block)
101
+ self.class.__child_nodes__.each(&block)
102
+ end
103
+
104
+ def each_child_with_name(&block)
105
+ self.class.__child_nodes__.each do |child_name|
106
+ yield(get_child(child_name), child_name)
107
+ end
108
+ end
109
+
110
+ # @return [Array<Node>] List of child nodes for `self`.
111
+ def child_nodes
112
+ child_node_names.map do |c|
113
+ get_child(c)
114
+ end
115
+ end
116
+
117
+ # @return [Array<Symbol>] List of child node names for `self`.
118
+ def child_node_names
119
+ self.class.__child_nodes__
120
+ end
121
+
122
+ # Compares a node to another.
123
+ # @return [true, false] `true` if nodes equal, `false` otherwise.
124
+ def == other
125
+ case other
126
+ when self.class
127
+ each_child do |child|
128
+ return false unless self.__send__(child) == other.__send__(child)
129
+ end
130
+ return true
131
+ else
132
+ return false
133
+ end
134
+ end
135
+
136
+ # Matches a node with another.
137
+ # @return [true, false] `true` if nodes match, `false` otherwise.
138
+ def === other
139
+ case other
140
+ when self.class
141
+ each_child do |child|
142
+ child_val = get_child(child) || Wood::TreePattern::AnyMatcher.new
143
+ return false unless child_val === other.get_child(child)
144
+ end
145
+ return true
146
+ else
147
+ return false
148
+ end
149
+ end
150
+
151
+ def inspect
152
+ sexp.inspect
153
+ end
154
+
155
+ # @param child_name [Symbol] Name of child to get node value for.
156
+ # @return [Node] Child node named `child_name`.
157
+ def get_child(child_name)
158
+ __send__(child_name)
159
+ end
160
+
161
+ # @param child_name [Symbol] Name of child node to set value for.
162
+ # @param node_val [Node] Node value to set child node to.
163
+ def set_child(child_name, node_val)
164
+ __send__("#{child_name}=", node_val)
165
+ end
166
+
167
+ # Allows per-node custom pattern matchers & rewriters.
168
+ def __rewriter_class__
169
+ @rewriter_class ||= Class.new do
170
+ include Wood::TreePattern::Matcher
171
+ include Wood::TreeRewriter
172
+ patterns self.new
173
+ end
174
+ end
175
+
176
+ def pattern(&block)
177
+ __rewriter_class__.pattern(&block)
178
+ end
179
+
180
+ def rewrite!
181
+ __rewriter_class__.new.rewrite(self)
182
+ end
183
+
184
+ delegate methods: [
185
+ :find_parent_node, :find_parent_nodes,
186
+ :find_child_node, :find_child_nodes,
187
+ :delete_child_node, :delete_child_nodes
188
+ ], to: :__node_finder__
189
+
190
+ private
191
+
192
+ def __node_finder__
193
+ @node_finder ||= Wood::TreePattern::NodeFinder.new(self)
194
+ end
195
+ end
196
+ end
197
+
198
+ # sexp methods for core classes
199
+
200
+ BuiltinNodeClasses = [
201
+ Symbol, String, Regexp, Numeric,
202
+ NilClass, TrueClass, FalseClass
203
+ ]
204
+
205
+ BuiltinNodeClasses.each do |c|
206
+ c.class_eval do
207
+ def sexp
208
+ self
209
+ end
210
+
211
+ def each_child_with_name
212
+ self
213
+ end
214
+ end
215
+ end
216
+
217
+ class Array
218
+ def sexp
219
+ map(&:sexp)
220
+ end
221
+
222
+ def node_name
223
+ :array
224
+ end
225
+
226
+ def parent_node=(parent_node)
227
+ each do |node|
228
+ node.parent_node = parent_node if node.respond_to?(:parent_node=)
229
+ end
230
+ end
231
+
232
+ def === other
233
+ case other
234
+ when Array
235
+ self.each_with_index do |x, i|
236
+ return false unless x === other[i]
237
+ end
238
+ else
239
+ return false
240
+ end
241
+
242
+ return true
243
+ end
244
+
245
+ def child_nodes
246
+ self
247
+ end
248
+
249
+ alias_method :each_child_with_name, :each_with_index
250
+
251
+ def set_child(child_name, node_val)
252
+ case node_val
253
+ when nil
254
+ self.delete_at(child_name)
255
+ else
256
+ self[child_name] = node_val
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,29 @@
1
+ module Wood
2
+ class NodeRewriter
3
+ instance_methods.each do |m|
4
+ undef_method m unless m =~ /(^__|^send$|^object_id$)/
5
+ end
6
+
7
+ attr_reader :node
8
+
9
+ def initialize(node, name = nil, parent = nil)
10
+ @node = node
11
+ @parent = parent
12
+ @name = name
13
+ end
14
+
15
+ def method_missing(method, *args, &block)
16
+ NodeRewriter.new(@node.__send__(method, *args, &block), method, self)
17
+ end
18
+
19
+ def rewrite(new_node_val)
20
+ if new_node_val.respond_to? :node
21
+ new_node_val = new_node_val.node
22
+ end
23
+
24
+ if @parent && @name
25
+ @parent.set_child(@name, new_node_val)
26
+ end
27
+ end
28
+ end
29
+ end