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
data/lib/core_ext.rb
ADDED
@@ -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
|
data/lib/wood.rb
ADDED
@@ -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
|
data/lib/wood/node.rb
ADDED
@@ -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
|