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