ast 2.0.0 → 2.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0b74cff86763a30c7b0b92c2aae6467d904ad2c4
4
- data.tar.gz: f829d02411d598e5b470ad57cf726d342349ff07
2
+ SHA256:
3
+ metadata.gz: e1ea61250ad8d285c9857cb70e2529f827b561b673b01dc02e92cc7bcea81094
4
+ data.tar.gz: 88c407ffbff0293306bda599d2fe5146bb3504a389f2a69c2c43a8b5c2244446
5
5
  SHA512:
6
- metadata.gz: 51d6106feef1199a6ae5f347c9121303479d155d13291292a0c733884fd88579f56be461237fd8d21551305649e85c9d556791aab1deaf45bee66ad387bb954c
7
- data.tar.gz: a310e6c19a8b83477e855b8fa9d95c1c1de9398b2308d91a84cbf1b84a52004c5193a10ac522474e9ee20f058c469c4e4de38369df2d2d21114b4afc62d196ce
6
+ metadata.gz: dad932faea02614394a61c4075efb11a2932c6231f9cd11d59d8e7345d8f74d4e7602881fce0e77094017c137159466b92f13ebecaee17e4ee611a0091a3f17a
7
+ data.tar.gz: 59ea82e96f376f3dc4805a66baa37e577dec80bcad6485ba3a4bc440b27d0ba31ca5d789ca74e7594758f7cfbd279bc75195da8073af17b817e91f714192e874
@@ -8,5 +8,5 @@ This is a design choice. It does create some pressure on
8
8
  garbage collector, but completely eliminates all concurrency
9
9
  and aliasing problems.
10
10
 
11
- See also {AST::Node}, {AST::Processor} and {AST::Sexp} for additional
12
- recommendations and design patterns.
11
+ See also {AST::Node}, {AST::Processor::Mixin} and {AST::Sexp} for
12
+ additional recommendations and design patterns.
data/lib/ast.rb CHANGED
@@ -7,8 +7,8 @@
7
7
  # garbage collector, but completely eliminates all concurrency
8
8
  # and aliasing problems.
9
9
  #
10
- # See also {AST::Node}, {AST::Processor} and {AST::Sexp} for additional
11
- # recommendations and design patterns.
10
+ # See also {AST::Node}, {AST::Processor::Mixin} and {AST::Sexp} for
11
+ # additional recommendations and design patterns.
12
12
  #
13
13
  module AST
14
14
  require 'ast/node'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AST
2
4
  # Node is an immutable class, instances of which represent abstract
3
5
  # syntax tree nodes. It combines semantic information (i.e. anything
@@ -42,8 +44,17 @@ module AST
42
44
 
43
45
  # Returns the children of this node.
44
46
  # The returned value is frozen.
47
+ # The to_a alias is useful for decomposing nodes concisely.
48
+ # For example:
49
+ #
50
+ # node = s(:gasgn, :$foo, s(:integer, 1))
51
+ # var_name, value = *node
52
+ # p var_name # => :$foo
53
+ # p value # => (integer 1)
54
+ #
45
55
  # @return [Array]
46
56
  attr_reader :children
57
+ alias to_a children
47
58
 
48
59
  # Returns the precomputed hash value for this node
49
60
  # @return [Fixnum]
@@ -80,7 +91,7 @@ module AST
80
91
  # By default, each entry in the `properties` hash is assigned to
81
92
  # an instance variable in this instance of Node. A subclass should define
82
93
  # attribute readers for such variables. The values passed in the hash
83
- # are not frozen or whitelisted; such behavior can also be implemented\
94
+ # are not frozen or whitelisted; such behavior can also be implemented
84
95
  # by subclassing Node and overriding this method.
85
96
  #
86
97
  # @return [nil]
@@ -104,6 +115,7 @@ module AST
104
115
  def dup
105
116
  self
106
117
  end
118
+ alias :clone :dup
107
119
 
108
120
  # Returns a new instance of Node where non-nil arguments replace the
109
121
  # corresponding fields of `self`.
@@ -128,7 +140,9 @@ module AST
128
140
  properties.nil?
129
141
  self
130
142
  else
131
- original_dup.send :initialize, new_type, new_children, new_properties
143
+ copy = original_dup
144
+ copy.send :initialize, new_type, new_children, new_properties
145
+ copy
132
146
  end
133
147
  end
134
148
 
@@ -166,57 +180,71 @@ module AST
166
180
 
167
181
  alias << append
168
182
 
169
- # Converts `self` to a concise s-expression, omitting any children.
183
+ # Converts `self` to a pretty-printed s-expression.
170
184
  #
185
+ # @param [Integer] indent Base indentation level.
171
186
  # @return [String]
172
- def to_s
173
- "(#{fancy_type} ...)"
174
- end
187
+ def to_sexp(indent=0)
188
+ indented = " " * indent
189
+ sexp = "#{indented}(#{fancy_type}"
175
190
 
176
- # Returns {#children}. This is very useful in order to decompose nodes
177
- # concisely. For example:
178
- #
179
- # node = s(:gasgn, :$foo, s(:integer, 1))
180
- # s
181
- # var_name, value = *node
182
- # p var_name # => :$foo
183
- # p value # => (integer 1)
184
- #
185
- # @return [Array]
186
- def to_a
187
- children
191
+ children.each do |child|
192
+ if child.is_a?(Node)
193
+ sexp += "\n#{child.to_sexp(indent + 1)}"
194
+ else
195
+ sexp += " #{child.inspect}"
196
+ end
197
+ end
198
+
199
+ sexp += ")"
200
+
201
+ sexp
188
202
  end
189
203
 
190
- # Converts `self` to a pretty-printed s-expression.
204
+ alias to_s to_sexp
205
+
206
+ # Converts `self` to a s-expression ruby string.
207
+ # The code return will recreate the node, using the sexp module s()
191
208
  #
192
209
  # @param [Integer] indent Base indentation level.
193
210
  # @return [String]
194
- def to_sexp(indent=0)
211
+ def inspect(indent=0)
195
212
  indented = " " * indent
196
- sexp = "#{indented}(#{fancy_type}"
197
-
198
- first_node_child = children.index do |child|
199
- child.is_a?(Node) || child.is_a?(Array)
200
- end || children.count
213
+ sexp = "#{indented}s(:#{@type}"
201
214
 
202
- children.each_with_index do |child, idx|
203
- if child.is_a?(Node) && idx >= first_node_child
204
- sexp << "\n#{child.to_sexp(indent + 1)}"
215
+ children.each do |child|
216
+ if child.is_a?(Node)
217
+ sexp += ",\n#{child.inspect(indent + 1)}"
205
218
  else
206
- sexp << " #{child.inspect}"
219
+ sexp += ", #{child.inspect}"
207
220
  end
208
221
  end
209
222
 
210
- sexp << ")"
223
+ sexp += ")"
211
224
 
212
225
  sexp
213
226
  end
214
- alias :inspect :to_sexp
215
227
 
216
228
  # @return [AST::Node] self
217
229
  def to_ast
218
230
  self
219
231
  end
232
+
233
+ # Converts `self` to an Array where the first element is the type as a Symbol,
234
+ # and subsequent elements are the same representation of its children.
235
+ #
236
+ # @return [Array<Symbol, [...Array]>]
237
+ def to_sexp_array
238
+ children_sexp_arrs = children.map do |child|
239
+ if child.is_a?(Node)
240
+ child.to_sexp_array
241
+ else
242
+ child
243
+ end
244
+ end
245
+
246
+ [type, *children_sexp_arrs]
247
+ end
220
248
 
221
249
  protected
222
250
 
@@ -1,266 +1,12 @@
1
1
  module AST
2
- # Processor is a class which helps transforming one AST into another.
3
- # In a nutshell, the {#process} method accepts a {Node} and dispatches
4
- # it to a handler corresponding to its type, and returns a (possibly)
5
- # updated variant of the node.
2
+ # This class includes {AST::Processor::Mixin}; however, it is
3
+ # deprecated, since the module defines all of the behaviors that
4
+ # the processor includes. Any new libraries should use
5
+ # {AST::Processor::Mixin} instead of subclassing this.
6
6
  #
7
- # Processor has a set of associated design patterns. They are best
8
- # explained with a concrete example. Let's define a simple arithmetic
9
- # language and an AST format for it:
10
- #
11
- # Terminals (AST nodes which do not have other AST nodes inside):
12
- #
13
- # * `(integer <int-literal>)`,
14
- #
15
- # Nonterminals (AST nodes with other nodes as children):
16
- #
17
- # * `(add <node> <node>)`,
18
- # * `(multiply <node> <node>)`,
19
- # * `(divide <node> <node>)`,
20
- # * `(negate <node>)`,
21
- # * `(store <node> <string-literal>)`: stores value of `<node>` into a variable named `<string-literal>`,
22
- # * `(load <string-literal>)`: loads value of a variable named `<string-literal>`,
23
- # * `(each <node> ...): computes each of the `<node>`s and prints the result.
24
- #
25
- # All AST nodes have the same Ruby class, and therefore they don't
26
- # know how to traverse themselves. (A solution which dynamically checks the
27
- # type of children is possible, but is slow and error-prone.) So, a subclass
28
- # of Processor which knows how to traverse the entire tree should be defined.
29
- # Such subclass has a handler for each nonterminal node which recursively
30
- # processes children nodes:
31
- #
32
- # require 'ast'
33
- #
34
- # class ArithmeticsProcessor < AST::Processor
35
- # # This method traverses any binary operators such as (add) or (multiply).
36
- # def process_binary_op(node)
37
- # # Children aren't decomposed automatically; it is suggested to use Ruby
38
- # # multiple assignment expansion, as it is very convenient here.
39
- # left_expr, right_expr = *node
40
- #
41
- # # AST::Node#updated won't change node type if nil is passed as a first
42
- # # argument, which allows to reuse the same handler for multiple node types
43
- # # using `alias' (below).
44
- # node.updated(nil, [
45
- # process(left_expr),
46
- # process(right_expr)
47
- # ])
48
- # end
49
- # alias on_add process_binary_op
50
- # alias on_multiply process_binary_op
51
- # alias on_divide process_binary_op
52
- #
53
- # def on_negate(node)
54
- # # It is also possible to use #process_all for more compact code
55
- # # if every child is a Node.
56
- # node.updated(nil, process_all(node))
57
- # end
58
- #
59
- # def on_store(node)
60
- # expr, variable_name = *node
61
- #
62
- # # Note that variable_name is not a Node and thus isn't passed to #process.
63
- # node.updated(nil, [
64
- # process(expr),
65
- # variable_name
66
- # ])
67
- # end
68
- #
69
- # # (load) is effectively a terminal node, and so it does not need
70
- # # an explicit handler, as the following is the default behavior.
71
- # def on_load(node)
72
- # nil
73
- # end
74
- #
75
- # def on_each(node)
76
- # node.updated(nil, process_all(node))
77
- # end
78
- # end
79
- #
80
- # Let's test our ArithmeticsProcessor:
81
- #
82
- # include AST::Sexp
83
- # expr = s(:add, s(:integer, 2), s(:integer, 2))
84
- #
85
- # p ArithmeticsProcessor.new.process(expr) == expr # => true
86
- #
87
- # As expected, it does not change anything at all. This isn't actually
88
- # very useful, so let's now define a Calculator, which will compute the
89
- # expression values:
90
- #
91
- # # This Processor folds nonterminal nodes and returns an (integer)
92
- # # terminal node.
93
- # class ArithmeticsCalculator < ArithmeticsProcessor
94
- # def compute_op(node)
95
- # # First, node children are processed and then unpacked to local
96
- # # variables.
97
- # nodes = process_all(node)
98
- #
99
- # if nodes.all? { |node| node.type == :integer }
100
- # # If each of those nodes represents a literal, we can fold this
101
- # # node!
102
- # values = nodes.map { |node| node.children.first }
103
- # AST::Node.new(:integer, [
104
- # yield(values)
105
- # ])
106
- # else
107
- # # Otherwise, we can just leave the current node in the tree and
108
- # # only update it with processed children nodes, which can be
109
- # # partially folded.
110
- # node.updated(nil, nodes)
111
- # end
112
- # end
113
- #
114
- # def on_add(node)
115
- # compute_op(node) { |left, right| left + right }
116
- # end
117
- #
118
- # def on_multiply(node)
119
- # compute_op(node) { |left, right| left * right }
120
- # end
121
- # end
122
- #
123
- # Let's check:
124
- #
125
- # p ArithmeticsCalculator.new.process(expr) # => (integer 4)
126
- #
127
- # Excellent, the calculator works! Now, a careful reader could notice that
128
- # the ArithmeticsCalculator does not know how to divide numbers. What if we
129
- # pass an expression with division to it?
130
- #
131
- # expr_with_division = \
132
- # s(:add,
133
- # s(:integer, 1),
134
- # s(:divide,
135
- # s(:add, s(:integer, 8), s(:integer, 4)),
136
- # s(:integer, 3))) # 1 + (8 + 4) / 3
137
- #
138
- # folded_expr_with_division = ArithmeticsCalculator.new.process(expr_with_division)
139
- # p folded_expr_with_division
140
- # # => (add
141
- # # (integer 1)
142
- # # (divide
143
- # # (integer 12)
144
- # # (integer 3)))
145
- #
146
- # As you can see, the expression was folded _partially_: the inner `(add)` node which
147
- # could be computed was folded to `(integer 12)`, the `(divide)` node is left as-is
148
- # because there is no computing handler for it, and the root `(add)` node was also left
149
- # as it is because some of its children were not literals.
150
- #
151
- # Note that this partial folding is only possible because the _data_ format, i.e.
152
- # the format in which the computed values of the nodes are represented, is the same as
153
- # the AST itself.
154
- #
155
- # Let's extend our ArithmeticsCalculator class further.
156
- #
157
- # class ArithmeticsCalculator
158
- # def on_divide(node)
159
- # compute_op(node) { |left, right| left / right }
160
- # end
161
- #
162
- # def on_negate(node)
163
- # # Note how #compute_op works regardless of the operator arity.
164
- # compute_op(node) { |value| -value }
165
- # end
166
- # end
167
- #
168
- # Now, let's apply our renewed ArithmeticsCalculator to a partial result of previous
169
- # evaluation:
170
- #
171
- # p ArithmeticsCalculator.new.process(expr_with_division) # => (integer 5)
172
- #
173
- # Five! Excellent. This is also pretty much how CRuby 1.8 executed its programs.
174
- #
175
- # Now, let's do some automated bug searching. Division by zero is an error, right?
176
- # So if we could detect that someone has divided by zero before the program is even
177
- # run, that could save some debugging time.
178
- #
179
- # class DivisionByZeroVerifier < ArithmeticsProcessor
180
- # class VerificationFailure < Exception; end
181
- #
182
- # def on_divide(node)
183
- # # You need to process the children to handle nested divisions
184
- # # such as:
185
- # # (divide
186
- # # (integer 1)
187
- # # (divide (integer 1) (integer 0))
188
- # left, right = process_all(node)
189
- #
190
- # if right.type == :integer &&
191
- # right.children.first == 0
192
- # raise VerificationFailure, "Ouch! This code divides by zero."
193
- # end
194
- # end
195
- #
196
- # def divides_by_zero?(ast)
197
- # process(ast)
198
- # false
199
- # rescue VerificationFailure
200
- # true
201
- # end
202
- # end
203
- #
204
- # nice_expr = \
205
- # s(:divide,
206
- # s(:add, s(:integer, 10), s(:integer, 2)),
207
- # s(:integer, 4))
208
- #
209
- # p DivisionByZeroVerifier.new.divides_by_zero?(nice_expr)
210
- # # => false. Good.
211
- #
212
- # bad_expr = \
213
- # s(:add, s(:integer, 10),
214
- # s(:divide, s(:integer, 1), s(:integer, 0)))
215
- #
216
- # p DivisionByZeroVerifier.new.divides_by_zero?(bad_expr)
217
- # # => true. WHOOPS. DO NOT RUN THIS.
218
- #
219
- # Of course, this won't detect more complex cases... unless you use some partial
220
- # evaluation before! The possibilites are endless. Have fun.
7
+ # @deprecated Use {AST::Processor::Mixin} instead.
221
8
  class Processor
222
- # Dispatches `node`. If a node has type `:foo`, then a handler named
223
- # `on_foo` is invoked with one argument, the `node`; if there isn't
224
- # such a handler, {#handler_missing} is invoked with the same argument.
225
- #
226
- # If the handler returns `nil`, `node` is returned; otherwise, the return
227
- # value of the handler is passed along.
228
- #
229
- # @param [AST::Node, nil] node
230
- # @return [AST::Node, nil]
231
- def process(node)
232
- return if node.nil?
233
-
234
- node = node.to_ast
235
-
236
- # Invoke a specific handler
237
- on_handler = :"on_#{node.type}"
238
- if respond_to? on_handler
239
- new_node = send on_handler, node
240
- else
241
- new_node = handler_missing(node)
242
- end
243
-
244
- node = new_node if new_node
245
-
246
- node
247
- end
248
-
249
- # {#process}es each node from `nodes` and returns an array of results.
250
- #
251
- # @param [Array<AST::Node>] nodes
252
- # @return [Array<AST::Node>]
253
- def process_all(nodes)
254
- nodes.to_a.map do |node|
255
- process node
256
- end
257
- end
258
-
259
- # Default handler. Does nothing.
260
- #
261
- # @param [AST::Node] node
262
- # @return [AST::Node, nil]
263
- def handler_missing(node)
264
- end
9
+ require 'ast/processor/mixin'
10
+ include Mixin
265
11
  end
266
12
  end
@@ -0,0 +1,288 @@
1
+ module AST
2
+ class Processor
3
+ # The processor module is a module which helps transforming one
4
+ # AST into another. In a nutshell, the {#process} method accepts
5
+ # a {Node} and dispatches it to a handler corresponding to its
6
+ # type, and returns a (possibly) updated variant of the node.
7
+ #
8
+ # The processor module has a set of associated design patterns.
9
+ # They are best explained with a concrete example. Let's define a
10
+ # simple arithmetic language and an AST format for it:
11
+ #
12
+ # Terminals (AST nodes which do not have other AST nodes inside):
13
+ #
14
+ # * `(integer <int-literal>)`,
15
+ #
16
+ # Nonterminals (AST nodes with other nodes as children):
17
+ #
18
+ # * `(add <node> <node>)`,
19
+ # * `(multiply <node> <node>)`,
20
+ # * `(divide <node> <node>)`,
21
+ # * `(negate <node>)`,
22
+ # * `(store <node> <string-literal>)`: stores value of `<node>`
23
+ # into a variable named `<string-literal>`,
24
+ # * `(load <string-literal>)`: loads value of a variable named
25
+ # `<string-literal>`,
26
+ # * `(each <node> ...)`: computes each of the `<node>`s and
27
+ # prints the result.
28
+ #
29
+ # All AST nodes have the same Ruby class, and therefore they don't
30
+ # know how to traverse themselves. (A solution which dynamically
31
+ # checks the type of children is possible, but is slow and
32
+ # error-prone.) So, a class including the module which knows how
33
+ # to traverse the entire tree should be defined. Such classes
34
+ # have a handler for each nonterminal node which recursively
35
+ # processes children nodes:
36
+ #
37
+ # require 'ast'
38
+ #
39
+ # class ArithmeticsProcessor
40
+ # include AST::Processor::Mixin
41
+ # # This method traverses any binary operators such as (add)
42
+ # # or (multiply).
43
+ # def process_binary_op(node)
44
+ # # Children aren't decomposed automatically; it is
45
+ # # suggested to use Ruby multiple assignment expansion,
46
+ # # as it is very convenient here.
47
+ # left_expr, right_expr = *node
48
+ #
49
+ # # AST::Node#updated won't change node type if nil is
50
+ # # passed as a first argument, which allows to reuse the
51
+ # # same handler for multiple node types using `alias'
52
+ # # (below).
53
+ # node.updated(nil, [
54
+ # process(left_expr),
55
+ # process(right_expr)
56
+ # ])
57
+ # end
58
+ # alias_method :on_add, :process_binary_op
59
+ # alias_method :on_multiply, :process_binary_op
60
+ # alias_method :on_divide, :process_binary_op
61
+ #
62
+ # def on_negate(node)
63
+ # # It is also possible to use #process_all for more
64
+ # # compact code if every child is a Node.
65
+ # node.updated(nil, process_all(node))
66
+ # end
67
+ #
68
+ # def on_store(node)
69
+ # expr, variable_name = *node
70
+ #
71
+ # # Note that variable_name is not a Node and thus isn't
72
+ # # passed to #process.
73
+ # node.updated(nil, [
74
+ # process(expr),
75
+ # variable_name
76
+ # ])
77
+ # end
78
+ #
79
+ # # (load) is effectively a terminal node, and so it does
80
+ # # not need an explicit handler, as the following is the
81
+ # # default behavior. Essentially, for any nodes that don't
82
+ # # have a defined handler, the node remains unchanged.
83
+ # def on_load(node)
84
+ # nil
85
+ # end
86
+ #
87
+ # def on_each(node)
88
+ # node.updated(nil, process_all(node))
89
+ # end
90
+ # end
91
+ #
92
+ # Let's test our ArithmeticsProcessor:
93
+ #
94
+ # include AST::Sexp
95
+ # expr = s(:add, s(:integer, 2), s(:integer, 2))
96
+ #
97
+ # p ArithmeticsProcessor.new.process(expr) == expr # => true
98
+ #
99
+ # As expected, it does not change anything at all. This isn't
100
+ # actually very useful, so let's now define a Calculator, which
101
+ # will compute the expression values:
102
+ #
103
+ # # This Processor folds nonterminal nodes and returns an
104
+ # # (integer) terminal node.
105
+ # class ArithmeticsCalculator < ArithmeticsProcessor
106
+ # def compute_op(node)
107
+ # # First, node children are processed and then unpacked
108
+ # # to local variables.
109
+ # nodes = process_all(node)
110
+ #
111
+ # if nodes.all? { |node| node.type == :integer }
112
+ # # If each of those nodes represents a literal, we can
113
+ # # fold this node!
114
+ # values = nodes.map { |node| node.children.first }
115
+ # AST::Node.new(:integer, [
116
+ # yield(values)
117
+ # ])
118
+ # else
119
+ # # Otherwise, we can just leave the current node in the
120
+ # # tree and only update it with processed children
121
+ # # nodes, which can be partially folded.
122
+ # node.updated(nil, nodes)
123
+ # end
124
+ # end
125
+ #
126
+ # def on_add(node)
127
+ # compute_op(node) { |left, right| left + right }
128
+ # end
129
+ #
130
+ # def on_multiply(node)
131
+ # compute_op(node) { |left, right| left * right }
132
+ # end
133
+ # end
134
+ #
135
+ # Let's check:
136
+ #
137
+ # p ArithmeticsCalculator.new.process(expr) # => (integer 4)
138
+ #
139
+ # Excellent, the calculator works! Now, a careful reader could
140
+ # notice that the ArithmeticsCalculator does not know how to
141
+ # divide numbers. What if we pass an expression with division to
142
+ # it?
143
+ #
144
+ # expr_with_division = \
145
+ # s(:add,
146
+ # s(:integer, 1),
147
+ # s(:divide,
148
+ # s(:add, s(:integer, 8), s(:integer, 4)),
149
+ # s(:integer, 3))) # 1 + (8 + 4) / 3
150
+ #
151
+ # folded_expr_with_division = ArithmeticsCalculator.new.process(expr_with_division)
152
+ # p folded_expr_with_division
153
+ # # => (add
154
+ # # (integer 1)
155
+ # # (divide
156
+ # # (integer 12)
157
+ # # (integer 3)))
158
+ #
159
+ # As you can see, the expression was folded _partially_: the inner
160
+ # `(add)` node which could be computed was folded to
161
+ # `(integer 12)`, the `(divide)` node is left as-is because there
162
+ # is no computing handler for it, and the root `(add)` node was
163
+ # also left as it is because some of its children were not
164
+ # literals.
165
+ #
166
+ # Note that this partial folding is only possible because the
167
+ # _data_ format, i.e. the format in which the computed values of
168
+ # the nodes are represented, is the same as the AST itself.
169
+ #
170
+ # Let's extend our ArithmeticsCalculator class further.
171
+ #
172
+ # class ArithmeticsCalculator
173
+ # def on_divide(node)
174
+ # compute_op(node) { |left, right| left / right }
175
+ # end
176
+ #
177
+ # def on_negate(node)
178
+ # # Note how #compute_op works regardless of the operator
179
+ # # arity.
180
+ # compute_op(node) { |value| -value }
181
+ # end
182
+ # end
183
+ #
184
+ # Now, let's apply our renewed ArithmeticsCalculator to a partial
185
+ # result of previous evaluation:
186
+ #
187
+ # p ArithmeticsCalculator.new.process(expr_with_division) # => (integer 5)
188
+ #
189
+ # Five! Excellent. This is also pretty much how CRuby 1.8 executed
190
+ # its programs.
191
+ #
192
+ # Now, let's do some automated bug searching. Division by zero is
193
+ # an error, right? So if we could detect that someone has divided
194
+ # by zero before the program is even run, that could save some
195
+ # debugging time.
196
+ #
197
+ # class DivisionByZeroVerifier < ArithmeticsProcessor
198
+ # class VerificationFailure < Exception; end
199
+ #
200
+ # def on_divide(node)
201
+ # # You need to process the children to handle nested divisions
202
+ # # such as:
203
+ # # (divide
204
+ # # (integer 1)
205
+ # # (divide (integer 1) (integer 0))
206
+ # left, right = process_all(node)
207
+ #
208
+ # if right.type == :integer &&
209
+ # right.children.first == 0
210
+ # raise VerificationFailure, "Ouch! This code divides by zero."
211
+ # end
212
+ # end
213
+ #
214
+ # def divides_by_zero?(ast)
215
+ # process(ast)
216
+ # false
217
+ # rescue VerificationFailure
218
+ # true
219
+ # end
220
+ # end
221
+ #
222
+ # nice_expr = \
223
+ # s(:divide,
224
+ # s(:add, s(:integer, 10), s(:integer, 2)),
225
+ # s(:integer, 4))
226
+ #
227
+ # p DivisionByZeroVerifier.new.divides_by_zero?(nice_expr)
228
+ # # => false. Good.
229
+ #
230
+ # bad_expr = \
231
+ # s(:add, s(:integer, 10),
232
+ # s(:divide, s(:integer, 1), s(:integer, 0)))
233
+ #
234
+ # p DivisionByZeroVerifier.new.divides_by_zero?(bad_expr)
235
+ # # => true. WHOOPS. DO NOT RUN THIS.
236
+ #
237
+ # Of course, this won't detect more complex cases... unless you
238
+ # use some partial evaluation before! The possibilites are
239
+ # endless. Have fun.
240
+ module Mixin
241
+ # Dispatches `node`. If a node has type `:foo`, then a handler
242
+ # named `on_foo` is invoked with one argument, the `node`; if
243
+ # there isn't such a handler, {#handler_missing} is invoked
244
+ # with the same argument.
245
+ #
246
+ # If the handler returns `nil`, `node` is returned; otherwise,
247
+ # the return value of the handler is passed along.
248
+ #
249
+ # @param [AST::Node, nil] node
250
+ # @return [AST::Node, nil]
251
+ def process(node)
252
+ return if node.nil?
253
+
254
+ node = node.to_ast
255
+
256
+ # Invoke a specific handler
257
+ on_handler = :"on_#{node.type}"
258
+ if respond_to? on_handler
259
+ new_node = send on_handler, node
260
+ else
261
+ new_node = handler_missing(node)
262
+ end
263
+
264
+ node = new_node if new_node
265
+
266
+ node
267
+ end
268
+
269
+ # {#process}es each node from `nodes` and returns an array of
270
+ # results.
271
+ #
272
+ # @param [Array<AST::Node>] nodes
273
+ # @return [Array<AST::Node>]
274
+ def process_all(nodes)
275
+ nodes.to_a.map do |node|
276
+ process node
277
+ end
278
+ end
279
+
280
+ # Default handler. Does nothing.
281
+ #
282
+ # @param [AST::Node] node
283
+ # @return [AST::Node, nil]
284
+ def handler_missing(node)
285
+ end
286
+ end
287
+ end
288
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ast
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.4.1
5
5
  platform: ruby
6
6
  authors:
7
- - Peter Zotov
7
+ - whitequark
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-21 00:00:00.000000000 Z
11
+ date: 2020-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '10.0'
19
+ version: '12.3'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '10.0'
26
+ version: '12.3'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bacon
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -68,46 +68,18 @@ dependencies:
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: coveralls
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: json_pure
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: mime-types
99
71
  requirement: !ruby/object:Gem::Requirement
100
72
  requirements:
101
73
  - - "~>"
102
74
  - !ruby/object:Gem::Version
103
- version: '1.25'
75
+ version: 0.8.23
104
76
  type: :development
105
77
  prerelease: false
106
78
  version_requirements: !ruby/object:Gem::Requirement
107
79
  requirements:
108
80
  - - "~>"
109
81
  - !ruby/object:Gem::Version
110
- version: '1.25'
82
+ version: 0.8.23
111
83
  - !ruby/object:Gem::Dependency
112
84
  name: yard
113
85
  requirement: !ruby/object:Gem::Requirement
@@ -143,22 +115,13 @@ executables: []
143
115
  extensions: []
144
116
  extra_rdoc_files: []
145
117
  files:
146
- - ".gitignore"
147
- - ".travis.yml"
148
- - ".yardopts"
149
- - CHANGELOG.md
150
- - Gemfile
151
118
  - LICENSE.MIT
152
119
  - README.YARD.md
153
- - README.md
154
- - Rakefile
155
- - ast.gemspec
156
120
  - lib/ast.rb
157
121
  - lib/ast/node.rb
158
122
  - lib/ast/processor.rb
123
+ - lib/ast/processor/mixin.rb
159
124
  - lib/ast/sexp.rb
160
- - test/helper.rb
161
- - test/test_ast.rb
162
125
  homepage: https://whitequark.github.io/ast/
163
126
  licenses:
164
127
  - MIT
@@ -178,10 +141,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
178
141
  - !ruby/object:Gem::Version
179
142
  version: '0'
180
143
  requirements: []
181
- rubyforge_project:
182
- rubygems_version: 2.2.2
144
+ rubygems_version: 3.1.2
183
145
  signing_key:
184
146
  specification_version: 4
185
147
  summary: A library for working with Abstract Syntax Trees.
186
148
  test_files: []
187
- has_rdoc:
data/.gitignore DELETED
@@ -1,8 +0,0 @@
1
- .bundle
2
- Gemfile.lock
3
- pkg/*
4
- .rbx/
5
- *.sublime-*
6
- doc/
7
- .yardoc/
8
- coverage/
@@ -1,9 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 1.8.7
4
- - 1.9.2
5
- - 1.9.3
6
- - 2.0
7
- - jruby-18mode
8
- - jruby-19mode
9
- - rbx-2
data/.yardopts DELETED
@@ -1 +0,0 @@
1
- -r README.YARD.md -m markdown --protected
@@ -1,9 +0,0 @@
1
- Changelog
2
- =========
3
-
4
- v1.1.0 (2013-06-17)
5
- -------------------
6
-
7
- API changes:
8
-
9
- * AST::Processor#process will return nil if passed nil, instead of raising NoMethodError.
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source "http://rubygems.org"
2
-
3
- # Specify your gem's dependencies in furnace.gemspec
4
- gemspec
data/README.md DELETED
@@ -1,23 +0,0 @@
1
- # AST
2
-
3
- [![Build Status](https://travis-ci.org/whitequark/ast.png?branch=master)](https://travis-ci.org/whitequark/ast)
4
- [![Code Climate](https://codeclimate.com/github/whitequark/ast.png)](https://codeclimate.com/github/whitequark/ast)
5
- [![Coverage Status](https://coveralls.io/repos/whitequark/ast/badge.png?branch=master)](https://coveralls.io/r/whitequark/ast)
6
-
7
- AST is a small library for working with immutable abstract syntax trees.
8
-
9
- ## Installation
10
-
11
- $ gem install ast
12
-
13
- ## Usage
14
-
15
- See the documentation at [GitHub](http://whitequark.github.com/ast/frames.html) or [rdoc.info](http://rdoc.info/gems/ast).
16
-
17
- ## Contributing
18
-
19
- 1. Fork it
20
- 2. Create your feature branch (`git checkout -b my-new-feature`)
21
- 3. Commit your changes (`git commit -am 'Add some feature'`)
22
- 4. Push to the branch (`git push origin my-new-feature`)
23
- 5. Create new Pull Request
data/Rakefile DELETED
@@ -1,19 +0,0 @@
1
- require 'bundler/gem_tasks'
2
- require 'bundler/setup'
3
-
4
- task :default => :test
5
-
6
- desc "Run test suite"
7
- task :test do
8
- sh "bacon -Itest -a"
9
- end
10
-
11
- PAGES_REPO = 'git@github.com:whitequark/ast'
12
-
13
- desc "Build and deploy documentation to GitHub pages"
14
- task :pages do
15
- system "git clone #{PAGES_REPO} gh-temp/ -b gh-pages; rm gh-temp/* -rf; touch gh-temp/.nojekyll" or abort
16
- system "yardoc -o gh-temp/;" or abort
17
- system "cd gh-temp/; git add -A; git commit -m 'Updated pages.'; git push -f origin gh-pages" or abort
18
- FileUtils.rm_rf 'gh-temp'
19
- end
@@ -1,28 +0,0 @@
1
- Gem::Specification.new do |s|
2
- s.name = 'ast'
3
- s.version = '2.0.0'
4
- s.license = 'MIT'
5
- s.authors = ["Peter Zotov"]
6
- s.email = ["whitequark@whitequark.org"]
7
- s.homepage = "https://whitequark.github.io/ast/"
8
- s.summary = %q{A library for working with Abstract Syntax Trees.}
9
- s.description = s.summary
10
-
11
- s.files = `git ls-files`.split("\n")
12
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
13
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
14
- s.require_paths = ["lib"]
15
-
16
- s.add_development_dependency 'rake', '~> 10.0'
17
-
18
- s.add_development_dependency 'bacon', '~> 1.2'
19
- s.add_development_dependency 'bacon-colored_output'
20
- s.add_development_dependency 'simplecov'
21
-
22
- s.add_development_dependency 'coveralls'
23
- s.add_development_dependency 'json_pure' # for coveralls on 1.9.2
24
- s.add_development_dependency 'mime-types', '~> 1.25' # for coveralls on 1.8.7
25
-
26
- s.add_development_dependency 'yard'
27
- s.add_development_dependency 'kramdown'
28
- end
@@ -1,17 +0,0 @@
1
- require 'bacon'
2
- require 'bacon/colored_output'
3
-
4
- require 'simplecov'
5
- require 'coveralls'
6
-
7
- SimpleCov.start do
8
- self.formatter = SimpleCov::Formatter::MultiFormatter[
9
- SimpleCov::Formatter::HTMLFormatter,
10
- Coveralls::SimpleCov::Formatter
11
- ]
12
-
13
- # Exclude the testsuite itself.
14
- add_filter "/test/"
15
- end
16
-
17
- require 'ast'
@@ -1,243 +0,0 @@
1
- require 'helper'
2
-
3
- describe AST::Node do
4
- extend AST::Sexp
5
-
6
- class MetaNode < AST::Node
7
- attr_reader :meta
8
- end
9
-
10
- before do
11
- @node = AST::Node.new(:node, [ 0, 1 ])
12
- @metanode = MetaNode.new(:node, [ 0, 1 ], :meta => 'value')
13
- end
14
-
15
- it 'should have accessors for type and children' do
16
- @node.type.should.equal :node
17
- @node.children.should.equal [0, 1]
18
- end
19
-
20
- it 'should set metadata' do
21
- @metanode.meta.should.equal 'value'
22
- end
23
-
24
- it 'should be frozen' do
25
- @node.frozen?.should.be.true
26
- @node.children.frozen?.should.be.true
27
- end
28
-
29
- it 'should return self when duping' do
30
- @node.dup.should.equal? @node
31
- end
32
-
33
- it 'should return an updated node, but only if needed' do
34
- @node.updated().should.be.identical_to @node
35
- @node.updated(:node).should.be.identical_to @node
36
- @node.updated(nil, [0, 1]).should.be.identical_to @node
37
-
38
- updated = @node.updated(:other_node)
39
- updated.should.not.be.identical_to @node
40
- updated.type.should.equal :other_node
41
- updated.children.should.equal @node.children
42
-
43
- updated.frozen?.should.be.true
44
-
45
- updated = @node.updated(nil, [1, 1])
46
- updated.should.not.be.identical_to @node
47
- updated.type.should.equal @node.type
48
- updated.children.should.equal [1, 1]
49
-
50
- updated = @metanode.updated(nil, nil, :meta => 'other_value')
51
- updated.meta.should.equal 'other_value'
52
- end
53
-
54
- it 'should use fancy type in to_s' do
55
- node = AST::Node.new(:ast_node)
56
- node.to_s.should.equal '(ast-node ...)'
57
- end
58
-
59
- it 'should format to_sexp correctly' do
60
- AST::Node.new(:a, [ :sym, [ 1, 2 ] ]).to_sexp.should.equal '(a :sym [1, 2])'
61
- AST::Node.new(:a, [ :sym, @node ]).to_sexp.should.equal "(a :sym\n (node 0 1))"
62
- AST::Node.new(:a, [ :sym,
63
- AST::Node.new(:b, [ @node, @node ])
64
- ]).to_sexp.should.equal "(a :sym\n (b\n (node 0 1)\n (node 0 1)))"
65
- end
66
-
67
- it 'should return self in to_ast' do
68
- @node.to_ast.should.be.identical_to @node
69
- end
70
-
71
- it 'should only use type and children to compute #hash' do
72
- @node.hash.should.equal([@node.type, @node.children, @node.class].hash)
73
- end
74
-
75
- it 'should only use type and children in #eql? comparisons' do
76
- # Not identical but equivalent
77
- @node.eql?(AST::Node.new(:node, [0, 1])).should.be.true
78
- # Not identical and not equivalent
79
- @node.eql?(AST::Node.new(:other, [0, 1])).should.be.false
80
- # Not identical and not equivalent because of differend class
81
- @node.eql?(@metanode).should.be.false
82
- end
83
-
84
- it 'should only use type and children in #== comparisons' do
85
- @node.should.equal @node
86
- @node.should.equal @metanode
87
- @node.should.not.equal :foo
88
-
89
- mock_node = Object.new.tap do |obj|
90
- def obj.to_ast
91
- self
92
- end
93
-
94
- def obj.type
95
- :node
96
- end
97
-
98
- def obj.children
99
- [ 0, 1 ]
100
- end
101
- end
102
- @node.should.equal mock_node
103
- end
104
-
105
- it 'should allow to decompose nodes with a, b = *node' do
106
- node = s(:gasgn, :$foo, s(:integer, 1))
107
-
108
- var_name, value = *node
109
- var_name.should.equal :$foo
110
- value.should.equal s(:integer, 1)
111
- end
112
-
113
- it 'should concatenate with arrays' do
114
- node = s(:gasgn, :$foo)
115
- (node + [s(:integer, 1)]).
116
- should.equal s(:gasgn, :$foo, s(:integer, 1))
117
- end
118
-
119
- it 'should append elements' do
120
- node = s(:array)
121
- (node << s(:integer, 1) << s(:string, "foo")).
122
- should.equal s(:array, s(:integer, 1), s(:string, "foo"))
123
- end
124
-
125
- begin
126
- eval <<-CODE
127
- it 'should not trigger a rubinius bug' do
128
- bar = [ s(:bar, 1) ]
129
- baz = s(:baz, 2)
130
- s(:foo, *bar, baz).should.equal s(:foo, s(:bar, 1), s(:baz, 2))
131
- end
132
- CODE
133
- rescue SyntaxError
134
- # Running on 1.8, ignore.
135
- end
136
- end
137
-
138
- describe AST::Processor do
139
- extend AST::Sexp
140
-
141
- def have_sexp(text)
142
- text = text.lines.map { |line| line.sub /^ +\|(.+)/, '\1' }.join.rstrip
143
- lambda { |ast| ast.to_sexp == text }
144
- end
145
-
146
- class MockProcessor < AST::Processor
147
- attr_reader :counts
148
-
149
- def initialize
150
- @counts = Hash.new(0)
151
- end
152
-
153
- def on_root(node)
154
- count_node(node)
155
- node.updated(nil, process_all(node.children))
156
- end
157
- alias on_body on_root
158
-
159
- def on_def(node)
160
- count_node(node)
161
- name, arglist, body = node.children
162
- node.updated(:def, [ name, process(arglist), process(body) ])
163
- end
164
-
165
- def handler_missing(node)
166
- count_node(node)
167
- end
168
-
169
- def count_node(node)
170
- @counts[node.type] += 1; nil
171
- end
172
- end
173
-
174
- before do
175
- @ast = AST::Node.new(:root, [
176
- AST::Node.new(:def, [ :func,
177
- AST::Node.new(:arglist, [ :foo, :bar ]),
178
- AST::Node.new(:body, [
179
- AST::Node.new(:invoke, [ :puts, "Hello world" ])
180
- ])
181
- ]),
182
- AST::Node.new(:invoke, [ :func ])
183
- ])
184
-
185
- @processor = MockProcessor.new
186
- end
187
-
188
- it 'should visit every node' do
189
- @processor.process(@ast).should.equal @ast
190
- @processor.counts.should.equal({
191
- :root => 1,
192
- :def => 1,
193
- :arglist => 1,
194
- :body => 1,
195
- :invoke => 2,
196
- })
197
- end
198
-
199
- it 'should be able to replace inner nodes' do
200
- def @processor.on_arglist(node)
201
- node.updated(:new_fancy_arglist)
202
- end
203
-
204
- @processor.process(@ast).should have_sexp(<<-SEXP)
205
- |(root
206
- | (def :func
207
- | (new-fancy-arglist :foo :bar)
208
- | (body
209
- | (invoke :puts "Hello world")))
210
- | (invoke :func))
211
- SEXP
212
- end
213
-
214
- it 'should build sexps' do
215
- s(:add,
216
- s(:integer, 1),
217
- s(:multiply,
218
- s(:integer, 2),
219
- s(:integer, 3))).should have_sexp(<<-SEXP)
220
- |(add
221
- | (integer 1)
222
- | (multiply
223
- | (integer 2)
224
- | (integer 3)))
225
- SEXP
226
- end
227
-
228
- it 'should return nil if passed nil' do
229
- @processor.process(nil).should == nil
230
- end
231
-
232
- it 'should refuse to process non-nodes' do
233
- lambda { @processor.process([]) }.should.raise NoMethodError, %r|to_ast|
234
- end
235
-
236
- it 'should allow to visit nodes with process_all(node)' do
237
- @processor.process_all s(:foo, s(:bar), s(:integer, 1))
238
- @processor.counts.should.equal({
239
- :bar => 1,
240
- :integer => 1,
241
- })
242
- end
243
- end