2 $TESTING ||= false # unless defined $TESTING
5 # Sexps are the basic storage mechanism of SexpProcessor. Sexps have
6 # a +type+ (to be renamed +node_type+) which is the first element of
7 # the Sexp. The type is used by SexpProcessor to determine whom to
8 # dispatch the Sexp to for processing.
10 class Sexp < Array # ZenTest FULL
12 @@array_types = [ :array, :args, ]
15 # Create a new Sexp containing +args+.
22 # Creates a new Sexp for +klass+ or +method+ in +klass+.
24 # If +walk_ancestors+ is true and +method+ is provided, walks the ancestors
25 # of +klass+ until a method definition is found.
27 def self.for(klass, method = nil, walk_ancestors = false)
29 sexp = if walk_ancestors and method then
30 klass.ancestors.each do |klass|
31 sexp = ParseTree.translate klass, method
32 break sexp unless sexp == [nil]
35 ParseTree.translate klass, method
42 # Creates a new Sexp from Array +a+, typically from ParseTree::translate.
44 def self.from_array(a)
45 ary = Array === a ? a : [a]
54 result << self.from_array(x)
64 if obj.class == self.class then
72 # Returns true if this Sexp's pattern matches +sexp+.
75 return nil unless Sexp === sexp
76 pattern = self # this is just for my brain
78 return true if pattern == sexp
81 return true if pattern === subset
88 # Returns true if this Sexp matches +pattern+. (Opposite of #===.)
91 return pattern === self
95 # Returns true if the node_type is +array+ or +args+.
97 # REFACTOR: to TypedSexp - we only care when we have units.
101 @@array_types.include? type
105 # Enumeratates the sexp yielding to +b+ when the node_type == +t+.
107 def each_of_type(t, &b)
109 if Sexp === elem then
110 elem.each_of_type(t, &b)
111 b.call(elem) if elem.first == t
117 # Replaces all elements whose node_type is +from+ with +to+. Used
118 # only for the most trivial of rewrites.
120 def find_and_replace_all(from, to)
121 each_with_index do | elem, index |
122 if Sexp === elem then
123 elem.find_and_replace_all(from, to)
125 self[index] = to if elem == from
131 # Replaces all Sexps matching +pattern+ with Sexp +repl+.
133 def gsub(pattern, repl)
134 return repl if pattern == self
136 new = self.map do |subset|
139 subset.gsub(pattern, repl)
145 return Sexp.from_array(new)
148 def inspect # :nodoc:
149 sexp_str = self.map {|x|x.inspect}.join(', ')
150 return "s(#{sexp_str})"
154 # Returns the node named +node+, deleting it if +delete+ is true.
156 def method_missing(meth, delete=false)
157 matches = find_all { | sexp | Sexp === sexp and sexp.first == meth }
163 match = matches.first
164 delete match if delete
167 raise NoMethodError, "multiple nodes for #{meth} were found in #{inspect}"
171 def pretty_print(q) # :nodoc:
172 q.group(1, 's(', ')') do
173 q.seplist(self) {|v| q.pp v }
178 # Returns the Sexp without the node_type.
185 # If run with debug, Sexp will raise if you shift on an empty
186 # Sexp. Helps with debugging.
189 raise "I'm empty" if self.empty?
191 end if $DEBUG or $TESTING
194 # Returns the bare bones structure of the sexp.
195 # s(:a, :b, s(:c, :d), :e) => s(:a, s(:c))
198 result = self.class.new
199 if Array === self.first then
200 result = self.first.structure
203 self.grep(Array).each do |subexp|
204 result << subexp.structure
211 # Replaces the Sexp matching +pattern+ with +repl+.
213 def sub(pattern, repl)
214 return repl.dup if pattern == self
218 new = self.map do |subset|
224 if pattern == subset then
227 elsif pattern === subset then
229 subset.sub pattern, repl
239 return Sexp.from_array(new)
243 self.map { |o| Sexp === o ? o.to_a : o }
252 class SexpMatchSpecial < Sexp; end
254 class SexpAny < SexpMatchSpecial
268 module SexpMatchSpecials
269 def ANY(); return SexpAny.new; end
273 # This is just a stupid shortcut to make indentation much cleaner.