Updated RubySpec submodule to 9f66d0b1.
[rbx.git] / kernel / core / struct.rb
blobbf76e2a6bff2405280af67ca867a298d1f1087c8
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 self.class == other.class && self.values == other.values
124   end
126   ##
127   # call-seq:
128   #   struct[symbol]    => anObject
129   #   struct[fixnum]    => anObject 
130   #
131   # Attribute Reference---Returns the value of the instance variable named
132   # by <em>symbol</em>, or indexed (0..length-1) by <em>fixnum</em>. Will
133   # raise <tt>NameError</tt> if the named variable does not exist, or
134   # <tt>IndexError</tt> if the index is out of range.
135   #
136   #    Customer = Struct.new(:name, :address, :zip)
137   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
138   #    joe["name"]   #=> "Joe Smith"
139   #    joe[:name]    #=> "Joe Smith"
140   #    joe[0]        #=> "Joe Smith"
142   def [](var)
143     case var
144     when Numeric then
145       var = var.to_i
146       a_len = _attrs.length
147       if var > a_len - 1 then
148         raise IndexError, "offset #{var} too large for struct(size:#{a_len})"
149       end
150       if var < -a_len then
151         raise IndexError, "offset #{var + a_len} too small for struct(size:#{a_len})"
152       end
153       var = _attrs[var]
154     when Symbol, String then
155       42 # HACK
156       # ok
157     else
158       raise TypeError
159     end
161     unless _attrs.include? var.to_sym then
162       raise NameError, "no member '#{var}' in struct"
163     end
165     return instance_variable_get("@#{var}")
166   end
168   ##
169   # call-seq:
170   #   struct[symbol] = obj    => obj
171   #   struct[fixnum] = obj    => obj
172   #
173   # Attribute Assignment---Assigns to the instance variable named by
174   # <em>symbol</em> or <em>fixnum</em> the value <em>obj</em> and returns
175   # it. Will raise a <tt>NameError</tt> if the named variable does not
176   # exist, or an <tt>IndexError</tt> if the index is out of range.
177   #
178   #    Customer = Struct.new(:name, :address, :zip)
179   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
180   #    joe["name"] = "Luke"
181   #    joe[:zip]   = "90210"
182   #    joe.name   #=> "Luke"
183   #    joe.zip    #=> "90210"
185   def []=(var, obj)
186     case var
187     when Numeric then
188       var = var.to_i
189       a_len = _attrs.length
190       if var > a_len - 1 then
191         raise IndexError, "offset #{var} too large for struct(size:#{a_len})"
192       end
193       if var < -a_len then
194         raise IndexError, "offset #{var + a_len} too small for struct(size:#{a_len})"
195       end
196       var = _attrs[var]
197     when Symbol, String then
198       42 # HACK
199       # ok
200     else
201       raise TypeError
202     end
204     unless _attrs.include? var.to_s.intern then
205       raise NameError, "no member '#{var}' in struct"
206     end
208     return instance_variable_set("@#{var}", obj)
209   end
211   ##
212   # call-seq:
213   #   struct.each {|obj| block }  => struct
214   #
215   # Calls <em>block</em> once for each instance variable, passing the value
216   # as a parameter.
217   #
218   #    Customer = Struct.new(:name, :address, :zip)
219   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
220   #    joe.each {|x| puts(x) }
221   #
222   # <em>produces:</em>
223   #
224   #    Joe Smith
225   #    123 Maple, Anytown NC
226   #    12345
228   def each(&block)
229     return values.each(&block)
230   end
232   ##
233   # call-seq:
234   #   struct.each_pair {|sym, obj| block }     => struct
235   #
236   # Calls <em>block</em> once for each instance variable, passing the name
237   # (as a symbol) and the value as parameters.
238   #
239   #    Customer = Struct.new(:name, :address, :zip)
240   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
241   #    joe.each_pair {|name, value| puts("#{name} => #{value}") }
242   #
243   # <em>produces:</em>
244   #
245   #    name => Joe Smith
246   #    address => 123 Maple, Anytown NC
247   #    zip => 12345
249   def each_pair
250     raise LocalJumpError unless block_given? # HACK yield should do this
251     _attrs.map { |var| yield var, instance_variable_get("@#{var}") }
252   end
254   ##
255   # call-seq:
256   #   (p1)
257   #
258   # code-seq:
259   #
260   #   struct.eql?(other)   => true or false
261   #
262   # Two structures are equal if they are the same object, or if all their
263   # fields are equal (using <tt>eql?</tt>).
265   def eql?(other)
266     return true if self == other
267     return false if self.class != other.class
268     to_a.eql? other
269   end
271   ##
272   # call-seq:
273   #   struct.hash   => fixnum
274   #
275   # Return a hash value based on this struct's contents.
277   def hash
278     to_a.hash
279   end
281   ##
282   # call-seq:
283   #   struct.length    => fixnum
284   #   struct.size      => fixnum
285   #
286   # Returns the number of instance variables.
287   #
288   #    Customer = Struct.new(:name, :address, :zip)
289   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
290   #    joe.length   #=> 3
292   def length
293     return _attrs.length
294   end
296   alias size length
298   ##
299   # call-seq:
300   #   struct.members    => array
301   #
302   # Returns an array of strings representing the names of the instance
303   # variables.
304   #
305   #    Customer = Struct.new(:name, :address, :zip)
306   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
307   #    joe.members   #=> ["name", "address", "zip"]
309   def self.members
310     return const_get(:STRUCT_ATTRS).map { |member| member.to_s }
311   end
313   def members
314     return self.class.members
315   end
317   ##
318   # call-seq:
319   #   struct.select(fixnum, ... )   => array
320   #   struct.select {|i| block }    => array
321   #
322   # The first form returns an array containing the elements in
323   # <em>struct</em> corresponding to the given indices. The second form
324   # invokes the block passing in successive elements from <em>struct</em>,
325   # returning an array containing those elements for which the block returns
326   # a true value (equivalent to <tt>Enumerable#select</tt>).
327   #
328   #    Lots = Struct.new(:a, :b, :c, :d, :e, :f)
329   #    l = Lots.new(11, 22, 33, 44, 55, 66)
330   #    l.select(1, 3, 5)               #=> [22, 44, 66]
331   #    l.select(0, 2, 4)               #=> [11, 33, 55]
332   #    l.select(-1, -3, -5)            #=> [66, 44, 22]
333   #    l.select {|v| (v % 2).zero? }   #=> [22, 44, 66]
335   def select(&block)
336     to_a.select(&block)
337   end
339   ##
340   # call-seq:
341   #   struct.to_a     => array
342   #   struct.values   => array
343   #
344   # Returns the values for this instance as an array.
345   #
346   #    Customer = Struct.new(:name, :address, :zip)
347   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
348   #    joe.to_a[1]   #=> "123 Maple, Anytown NC"
350   def to_a
351     return _attrs.map { |var| instance_variable_get "@#{var}" }
352   end
354   ##
355   # call-seq:
356   #   struct.to_s      => string
357   #   struct.inspect   => string
358   #
359   # Describe the contents of this struct in a string.
361   def to_s
362     "#<struct #{self.class.name} #{_attrs.zip(self.to_a).map{|o| o[1] = o[1].inspect; o.join('=')}.join(', ') }>"
363   end
365   alias inspect to_s
367   ##
368   # call-seq:
369   #   struct.to_a     => array
370   #   struct.values   => array
371   #
372   # Returns the values for this instance as an array.
373   #
374   #    Customer = Struct.new(:name, :address, :zip)
375   #    joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
376   #    joe.to_a[1]   #=> "123 Maple, Anytown NC"
378   alias values to_a
380   ##
381   # call-seq:
382   #   struct.values_at(selector,... )  => an_array
383   #
384   # Returns an array containing the elements in <em>self</em> corresponding
385   # to the given selector(s). The selectors may be either integer indices or
386   # ranges. See also </code>.select<code>.
387   #
388   #    a = %w{ a b c d e f }
389   #    a.values_at(1, 3, 5)
390   #    a.values_at(1, 3, 5, 7)
391   #    a.values_at(-1, -3, -5, -7)
392   #    a.values_at(1..3, 2...5)
394   def values_at(*args)
395     to_a.values_at(*args)
396   end