Change soft-fail to use the config, rather than env
[rbx.git] / kernel / core / struct.rb
blob1ca2a99f7d34cec77f49d886a02f70881030eb6d
1 # depends on: class.rb enumerable.rb hash.rb
3 class Struct
5   include Enumerable
7   class << self
8     alias subclass_new new
9   end
11   ##
12   # call-seq:
13   #   Struct.new( [aString] [, aSym]+> )    => StructClass
14   #   StructClass.new(arg, ...)             => obj
15   #   StructClass[arg, ...]                 => obj
16   #
17   # Creates a new class, named by <em>aString</em>, containing accessor
18   # methods for the given symbols. If the name <em>aString</em> is omitted,
19   # an anonymous structure class will be created. Otherwise, the name of
20   # this struct will appear as a constant in class <tt>Struct</tt>, so it
21   # must be unique for all <tt>Struct</tt>s in the system and should start
22   # with a capital letter. Assigning a structure class to a constant
23   # effectively gives the class the name of the constant.
24   #
25   # <tt>Struct::new</tt> returns a new <tt>Class</tt> object, which can then
26   # be used to create specific instances of the new structure. The number of
27   # actual parameters must be less than or equal to the number of attributes
28   # defined for this class; unset parameters default to \nil{}. Passing too
29   # many parameters will raise an \E{ArgumentError}.
30   #
31   # The remaining methods listed in this section (class and instance) are
32   # defined for this generated class.
33   #
34   #    # Create a structure with a name in Struct
35   #    Struct.new("Customer", :name, :address)    #=> Struct::Customer
36   #    Struct::Customer.new("Dave", "123 Main")   #=> #<Struct::Customer
37   # name="Dave", address="123 Main">
38   #    # Create a structure named by its constant
39   #    Customer = Struct.new(:name, :address)     #=> Customer
40   #    Customer.new("Dave", "123 Main")           #=> #<Customer
41   # name="Dave", address="123 Main">
43   def self.new(klass_name, *attrs, &block)
44     unless klass_name.nil? then
45       begin
46         klass_name = StringValue klass_name
47       rescue TypeError
48         attrs.unshift klass_name
49         klass_name = nil
50       end
51     end
53     begin
54       attrs = attrs.map { |attr| attr.to_sym }
55     rescue NoMethodError => e
56       raise TypeError, e.message
57     end
59     raise ArgumentError if attrs.any? { |attr| attr.nil? }
61     klass = Class.new self do
63       attr_accessor(*attrs)
65       def self.new(*args)
66         return subclass_new(*args)
67       end
69       def self.[](*args)
70         return new(*args)
71       end
73     end
75     Struct.const_set klass_name, klass if klass_name
77     klass.const_set :STRUCT_ATTRS, attrs
79     klass.module_eval(&block) if block
81     return klass
82   end
84   def self.allocate # :nodoc:
85     super
86   end
88   def _attrs # :nodoc:
89     return self.class.const_get(:STRUCT_ATTRS)
90   end
92   def instance_variables
93     # Hide the ivars used to store the struct fields
94     super() - _attrs.map { |a| "@#{a}" }
95   end
97   def initialize(*args)
98     raise ArgumentError unless args.length <= _attrs.length
99     _attrs.each_with_index do |attr, i|
100       instance_variable_set "@#{attr}", args[i]
101     end
102   end
104   private :initialize
106   ##
107   # call-seq:
108   #   struct == other_struct     => true or false
109   #
110   # Equality---Returns <tt>true</tt> if <em>other_struct</em> is equal to
111   # this one: they must be of the same class as generated by
112   # <tt>Struct::new</tt>, and the values of all instance variables must be
113   # equal (according to <tt>Object#==</tt>).
114   #
115   #    Customer = Struct.new(:name, :address, :zip)
116   #    joe   = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
117   #    joejr = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
118   #    jane  = Customer.new("Jane Doe", "456 Elm, Anytown NC", 12345)
119   #    joe == joejr   #=> true
120   #    joe == jane    #=> false
122   def ==(other)
123     return false if (self.class != other.class)
124     return false if (self.values.size != other.values.size)
125     
126     self.values.size.times { |i|
127       next if (RecursionGuard.inspecting?(self.values.at(i)))
128       next if (RecursionGuard.inspecting?(other.values.at(i)))
129       RecursionGuard.inspect(self.values.at(i)) do
130         RecursionGuard.inspect(other.values.at(i)) do
131           return false if (self.values.at(i) != other.values.at(i))
132         end
133       end
134     }
135     return true
136   end
138   ##
139   # call-seq:
140   #   struct[symbol]    => anObject
141   #   struct[fixnum]    => anObject 
142   #
143   # Attribute Reference---Returns the value of the instance variable named
144   # by <em>symbol</em>, or indexed (0..length-1) by <em>fixnum</em>. Will
145   # raise <tt>NameError</tt> if the named variable does not exist, or
146   # <tt>IndexError</tt> if the index is out of range.
147   #
148   #    Customer = Struct.new(:name, :address, :zip)
149   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
150   #    joe["name"]   #=> "Joe Smith"
151   #    joe[:name]    #=> "Joe Smith"
152   #    joe[0]        #=> "Joe Smith"
154   def [](var)
155     case var
156     when Numeric then
157       var = var.to_i
158       a_len = _attrs.length
159       if var > a_len - 1 then
160         raise IndexError, "offset #{var} too large for struct(size:#{a_len})"
161       end
162       if var < -a_len then
163         raise IndexError, "offset #{var + a_len} too small for struct(size:#{a_len})"
164       end
165       var = _attrs[var]
166     when Symbol, String then
167       42 # HACK
168       # ok
169     else
170       raise TypeError
171     end
173     unless _attrs.include? var.to_sym then
174       raise NameError, "no member '#{var}' in struct"
175     end
177     return instance_variable_get("@#{var}")
178   end
180   ##
181   # call-seq:
182   #   struct[symbol] = obj    => obj
183   #   struct[fixnum] = obj    => obj
184   #
185   # Attribute Assignment---Assigns to the instance variable named by
186   # <em>symbol</em> or <em>fixnum</em> the value <em>obj</em> and returns
187   # it. Will raise a <tt>NameError</tt> if the named variable does not
188   # exist, or an <tt>IndexError</tt> if the index is out of range.
189   #
190   #    Customer = Struct.new(:name, :address, :zip)
191   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
192   #    joe["name"] = "Luke"
193   #    joe[:zip]   = "90210"
194   #    joe.name   #=> "Luke"
195   #    joe.zip    #=> "90210"
197   def []=(var, obj)
198     case var
199     when Numeric then
200       var = var.to_i
201       a_len = _attrs.length
202       if var > a_len - 1 then
203         raise IndexError, "offset #{var} too large for struct(size:#{a_len})"
204       end
205       if var < -a_len then
206         raise IndexError, "offset #{var + a_len} too small for struct(size:#{a_len})"
207       end
208       var = _attrs[var]
209     when Symbol, String then
210       42 # HACK
211       # ok
212     else
213       raise TypeError
214     end
216     unless _attrs.include? var.to_s.intern then
217       raise NameError, "no member '#{var}' in struct"
218     end
220     return instance_variable_set("@#{var}", obj)
221   end
223   ##
224   # call-seq:
225   #   struct.each {|obj| block }  => struct
226   #
227   # Calls <em>block</em> once for each instance variable, passing the value
228   # as a parameter.
229   #
230   #    Customer = Struct.new(:name, :address, :zip)
231   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
232   #    joe.each {|x| puts(x) }
233   #
234   # <em>produces:</em>
235   #
236   #    Joe Smith
237   #    123 Maple, Anytown NC
238   #    12345
240   def each(&block)
241     return values.each(&block)
242   end
244   ##
245   # call-seq:
246   #   struct.each_pair {|sym, obj| block }     => struct
247   #
248   # Calls <em>block</em> once for each instance variable, passing the name
249   # (as a symbol) and the value as parameters.
250   #
251   #    Customer = Struct.new(:name, :address, :zip)
252   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
253   #    joe.each_pair {|name, value| puts("#{name} => #{value}") }
254   #
255   # <em>produces:</em>
256   #
257   #    name => Joe Smith
258   #    address => 123 Maple, Anytown NC
259   #    zip => 12345
261   def each_pair
262     raise LocalJumpError unless block_given? # HACK yield should do this
263     _attrs.map { |var| yield var, instance_variable_get("@#{var}") }
264   end
266   ##
267   # call-seq:
268   #   (p1)
269   #
270   # code-seq:
271   #
272   #   struct.eql?(other)   => true or false
273   #
274   # Two structures are equal if they are the same object, or if all their
275   # fields are equal (using <tt>eql?</tt>).
277   def eql?(other)
278     return true if self == other
279     return false if self.class != other.class
280     to_a.eql? other
281   end
283   ##
284   # call-seq:
285   #   struct.hash   => fixnum
286   #
287   # Return a hash value based on this struct's contents.
289   def hash
290     to_a.hash
291   end
293   ##
294   # call-seq:
295   #   struct.length    => fixnum
296   #   struct.size      => fixnum
297   #
298   # Returns the number of instance variables.
299   #
300   #    Customer = Struct.new(:name, :address, :zip)
301   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
302   #    joe.length   #=> 3
304   def length
305     return _attrs.length
306   end
308   alias size length
310   ##
311   # call-seq:
312   #   struct.members    => array
313   #
314   # Returns an array of strings representing the names of the instance
315   # variables.
316   #
317   #    Customer = Struct.new(:name, :address, :zip)
318   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
319   #    joe.members   #=> ["name", "address", "zip"]
321   def self.members
322     return const_get(:STRUCT_ATTRS).map { |member| member.to_s }
323   end
325   def members
326     return self.class.members
327   end
329   ##
330   # call-seq:
331   #   struct.select(fixnum, ... )   => array
332   #   struct.select {|i| block }    => array
333   #
334   # The first form returns an array containing the elements in
335   # <em>struct</em> corresponding to the given indices. The second form
336   # invokes the block passing in successive elements from <em>struct</em>,
337   # returning an array containing those elements for which the block returns
338   # a true value (equivalent to <tt>Enumerable#select</tt>).
339   #
340   #    Lots = Struct.new(:a, :b, :c, :d, :e, :f)
341   #    l = Lots.new(11, 22, 33, 44, 55, 66)
342   #    l.select(1, 3, 5)               #=> [22, 44, 66]
343   #    l.select(0, 2, 4)               #=> [11, 33, 55]
344   #    l.select(-1, -3, -5)            #=> [66, 44, 22]
345   #    l.select {|v| (v % 2).zero? }   #=> [22, 44, 66]
347   def select(&block)
348     to_a.select(&block)
349   end
351   ##
352   # call-seq:
353   #   struct.to_a     => array
354   #   struct.values   => array
355   #
356   # Returns the values for this instance as an array.
357   #
358   #    Customer = Struct.new(:name, :address, :zip)
359   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
360   #    joe.to_a[1]   #=> "123 Maple, Anytown NC"
362   def to_a
363     return _attrs.map { |var| instance_variable_get "@#{var}" }
364   end
366   ##
367   # call-seq:
368   #   struct.to_s      => string
369   #   struct.inspect   => string
370   #
371   # Describe the contents of this struct in a string.
373   def to_s
374     return "[...]" if RecursionGuard.inspecting?(self)
375   
376     RecursionGuard.inspect(self) do
377       "#<struct #{self.class.name} #{_attrs.zip(self.to_a).map{|o| o[1] = o[1].inspect; o.join('=')}.join(', ') }>"
378     end
379   end
381   alias inspect to_s
383   ##
384   # call-seq:
385   #   struct.to_a     => array
386   #   struct.values   => array
387   #
388   # Returns the values for this instance as an array.
389   #
390   #    Customer = Struct.new(:name, :address, :zip)
391   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
392   #    joe.to_a[1]   #=> "123 Maple, Anytown NC"
394   alias values to_a
396   ##
397   # call-seq:
398   #   struct.values_at(selector,... )  => an_array
399   #
400   # Returns an array containing the elements in <em>self</em> corresponding
401   # to the given selector(s). The selectors may be either integer indices or
402   # ranges. See also </code>.select<code>.
403   #
404   #    a = %w{ a b c d e f }
405   #    a.values_at(1, 3, 5)
406   #    a.values_at(1, 3, 5, 7)
407   #    a.values_at(-1, -3, -5, -7)
408   #    a.values_at(1..3, 2...5)
410   def values_at(*args)
411     to_a.values_at(*args)
412   end