new world
[rubydium.git] / ruby_parser-1.0.0 / lib / sexp.rb
blobee1c7ddd5e8d3df990ad9c0186f9efa132a3c39c
2 $TESTING ||= false # unless defined $TESTING
4 ##
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, ]
14   ##
15   # Create a new Sexp containing +args+.
17   def initialize(*args)
18     super(args)
19   end
21   ##
22   # Creates a new Sexp for +klass+ or +method+ in +klass+.
23   #
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)
28     require 'parse_tree'
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]
33              end
34            else
35              ParseTree.translate klass, method
36            end
38     Sexp.from_array sexp
39   end
41   ##
42   # Creates a new Sexp from Array +a+, typically from ParseTree::translate.
44   def self.from_array(a)
45     ary = Array === a ? a : [a]
47     result = self.new
49     ary.each do |x|
50       case x
51       when Sexp
52         result << x
53       when Array
54         result << self.from_array(x)
55       else
56         result << x
57       end
58     end
60     result
61   end
63   def ==(obj) # :nodoc:
64     if obj.class == self.class then
65       super
66     else
67       false
68     end
69   end
71   ##
72   # Returns true if this Sexp's pattern matches +sexp+.
74   def ===(sexp)
75     return nil unless Sexp === sexp
76     pattern = self # this is just for my brain
78     return true if pattern == sexp
80     sexp.each do |subset|
81       return true if pattern === subset
82     end
84     return nil
85   end
87   ##
88   # Returns true if this Sexp matches +pattern+.  (Opposite of #===.)
90   def =~(pattern)
91     return pattern === self
92   end
94   ##
95   # Returns true if the node_type is +array+ or +args+.
96   #
97   # REFACTOR: to TypedSexp - we only care when we have units.
99   def array_type?
100     type = self.first
101     @@array_types.include? type
102   end
104   ##
105   # Enumeratates the sexp yielding to +b+ when the node_type == +t+.
107   def each_of_type(t, &b)
108     each do | elem |
109       if Sexp === elem then
110         elem.each_of_type(t, &b)
111         b.call(elem) if elem.first == t
112       end
113     end
114   end
116   ##
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)
124       else
125         self[index] = to if elem == from
126       end
127     end
128   end
130   ##
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|
137       case subset
138       when Sexp then
139         subset.gsub(pattern, repl)
140       else
141         subset
142       end
143     end
145     return Sexp.from_array(new)
146   end
148   def inspect # :nodoc:
149     sexp_str = self.map {|x|x.inspect}.join(', ')
150     return "s(#{sexp_str})"
151   end
153   ##
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 }
159     case matches.size
160     when 0 then
161       nil
162     when 1 then
163       match = matches.first
164       delete match if delete
165       match
166     else
167       raise NoMethodError, "multiple nodes for #{meth} were found in #{inspect}"
168     end
169   end
171   def pretty_print(q) # :nodoc:
172     q.group(1, 's(', ')') do
173       q.seplist(self) {|v| q.pp v }
174     end
175   end
177   ##
178   # Returns the Sexp without the node_type.
180   def sexp_body
181     self[1..-1]
182   end
184   ##
185   # If run with debug, Sexp will raise if you shift on an empty
186   # Sexp. Helps with debugging.
188   def shift
189     raise "I'm empty" if self.empty?
190     super
191   end if $DEBUG or $TESTING
193   ##
194   # Returns the bare bones structure of the sexp.
195   # s(:a, :b, s(:c, :d), :e) => s(:a, s(:c))
197   def structure
198     result = self.class.new
199     if Array === self.first then
200       result = self.first.structure
201     else
202       result << self.first
203       self.grep(Array).each do |subexp|
204         result << subexp.structure
205       end
206     end
207     result
208   end
210   ##
211   # Replaces the Sexp matching +pattern+ with +repl+.
213   def sub(pattern, repl)
214     return repl.dup if pattern == self
216     done = false
218     new = self.map do |subset|
219       if done then
220         subset
221       else
222         case subset
223         when Sexp then
224           if pattern == subset then
225             done = true
226             repl.dup
227           elsif pattern === subset then
228             done = true
229             subset.sub pattern, repl
230           else
231             subset
232           end
233         else
234           subset
235         end
236       end
237     end
239     return Sexp.from_array(new)
240   end
242   def to_a # :nodoc:
243     self.map { |o| Sexp === o ? o.to_a : o }
244   end
246   def to_s # :nodoc:
247     inspect
248   end
252 class SexpMatchSpecial < Sexp; end
254 class SexpAny < SexpMatchSpecial
255   def ==(o)
256     Sexp === o
257   end
259   def ===(o)
260     return Sexp === o
261   end
263   def inspect
264     "ANY"
265   end
268 module SexpMatchSpecials
269   def ANY(); return SexpAny.new; end
273 # This is just a stupid shortcut to make indentation much cleaner.
275 def s(*args)
276   Sexp.new(*args)