core/struct.rb
class Struct
include Enumerable
class << self
alias_method :subclass_new, :new
end
def self.new(klass_name, *attrs, &block)
if klass_name
begin
klass_name = StringValue klass_name
rescue TypeError
attrs.unshift klass_name
klass_name = nil
end
end
attrs = attrs.map do |a|
case a
when Symbol
a
when String
sym = a.to_sym
unless sym.kind_of? Symbol
raise TypeError, "#to_sym didn't return a symbol"
end
sym
else
raise TypeError, "#{a.inspect} is not a symbol"
end
end
klass = Class.new self do
_specialize attrs
attr_accessor(*attrs)
def self.new(*args, &block)
return subclass_new(*args, &block)
end
def self.[](*args)
return new(*args)
end
const_set :STRUCT_ATTRS, attrs
end
Struct.const_set klass_name, klass if klass_name
klass.module_eval(&block) if block
return klass
end
# Don't specialize any thing created in the core library. We hook up better
# form of this in core/zed.rb.
def self._specialize(attrs)
end
def self.make_struct(name, attrs)
new name, *attrs
end
def _attrs # :nodoc:
return self.class::STRUCT_ATTRS
end
private :_attrs
def select
return to_enum(:select) { size } unless block_given?
to_a.select do |v|
yield v
end
end
def to_h
Hash[each_pair.to_a]
end
def to_s
return "[...]" if Thread.guarding? self
Thread.recursion_guard self do
values = []
_attrs.each do |var|
val = instance_variable_get :"@#{var}"
values << "#{var}=#{val.inspect}"
end
name = self.class.name
if name.nil? || name.empty?
"#<struct #{values.join(', ')}>"
else
"#<struct #{self.class.name} #{values.join(', ')}>"
end
end
end
alias_method :inspect, :to_s
def instance_variables
# Hide the ivars used to store the struct fields
super() - _attrs.map { |a| "@#{a}".to_sym }
end
def initialize(*args)
attrs = _attrs
unless args.length <= attrs.length
raise ArgumentError, "Expected #{attrs.size}, got #{args.size}"
end
attrs.each_with_index do |attr, i|
instance_variable_set :"@#{attr}", args[i]
end
end
private :initialize
def ==(other)
return false if self.class != other.class
Thread.detect_recursion self, other do
return self.values == other.values
end
# Subtle: if we are here, we are recursing and haven't found any difference, so:
true
end
def [](var)
case var
when Symbol, String
# ok
else
var = Integer(var)
a_len = _attrs.length
if var > a_len - 1
raise IndexError, "offset #{var} too large for struct(size:#{a_len})"
end
if var < -a_len
raise IndexError, "offset #{var + a_len} too small for struct(size:#{a_len})"
end
var = _attrs[var]
end
unless _attrs.include? var.to_sym
raise NameError, "no member '#{var}' in struct"
end
return instance_variable_get(:"@#{var}")
end
def []=(var, obj)
case var
when Symbol
unless _attrs.include? var
raise NameError, "no member '#{var}' in struct"
end
when String
var = var.to_sym
unless _attrs.include? var
raise NameError, "no member '#{var}' in struct"
end
else
var = Integer(var)
a_len = _attrs.length
if var > a_len - 1
raise IndexError, "offset #{var} too large for struct(size:#{a_len})"
end
if var < -a_len
raise IndexError, "offset #{var + a_len} too small for struct(size:#{a_len})"
end
var = _attrs[var]
end
return instance_variable_set(:"@#{var}", obj)
end
def dig(key, *remaining_keys)
item = begin
self[key]
rescue NameError
nil
end
return item if remaining_keys.empty? || item.nil?
raise TypeError, "#{item.class} does not have #dig method" unless item.respond_to?(:dig)
item.dig(*remaining_keys)
end
def eql?(other)
return true if equal? other
return false if self.class != other.class
Thread.detect_recursion self, other do
_attrs.each do |var|
mine = instance_variable_get(:"@#{var}")
theirs = other.instance_variable_get(:"@#{var}")
return false unless mine.eql? theirs
end
end
# Subtle: if we are here, then no difference was found, or we are recursing
# In either case, return
true
end
def each
return to_enum(:each) { size } unless block_given?
values.each do |v|
yield v
end
self
end
def each_pair
return to_enum(:each_pair) { size } unless block_given?
_attrs.each { |var| yield [var, instance_variable_get(:"@#{var}")] }
self
end
def hash
hash_val = size
return _attrs.size if Thread.detect_outermost_recursion self do
_attrs.each { |var| hash_val ^= instance_variable_get(:"@#{var}").hash }
end
return hash_val
end
def length
return _attrs.length
end
alias_method :size, :length
def self.length
return self::STRUCT_ATTRS.size
end
def self.members
self::STRUCT_ATTRS.dup
end
def members
return self.class.members
end
def to_a
return _attrs.map { |var| instance_variable_get :"@#{var}" }
end
alias_method :values, :to_a
def values_at(*args)
to_a.values_at(*args)
end
end