ast 2.0.0 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
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