opal/corelib/struct.rb
# backtick_javascript: true
require 'corelib/enumerable'
class ::Struct
include ::Enumerable
def self.new(const_name, *args, keyword_init: false, &block)
if const_name
if const_name.class == ::String && const_name[0].upcase != const_name[0]
# Fast track so that we skip needlessly going thru exceptions
# in most cases.
args.unshift(const_name)
const_name = nil
else
begin
const_name = ::Opal.const_name!(const_name)
rescue ::TypeError, ::NameError
args.unshift(const_name)
const_name = nil
end
end
end
args.map do |arg|
::Opal.coerce_to!(arg, ::String, :to_str)
end
klass = ::Class.new(self) do
args.each { |arg| define_struct_attribute(arg) }
class << self
def new(*args)
instance = allocate
`#{instance}.$$data = {}`
instance.initialize(*args)
instance
end
alias_method :[], :new
end
end
klass.module_eval(&block) if block
`klass.$$keyword_init = keyword_init`
if const_name
::Struct.const_set(const_name, klass)
end
klass
end
def self.define_struct_attribute(name)
if self == ::Struct
::Kernel.raise ::ArgumentError, 'you cannot define attributes to the Struct class'
end
members << name
define_method name do
`self.$$data[name]`
end
define_method "#{name}=" do |value|
`self.$$data[name] = value`
end
end
def self.members
if self == ::Struct
::Kernel.raise ::ArgumentError, 'the Struct class has no members'
end
@members ||= []
end
def self.inherited(klass)
members = @members
klass.instance_eval do
@members = members
end
end
def initialize(*args)
if `#{self.class}.$$keyword_init`
kwargs = args.last || {}
if args.length > 1 || `(args.length === 1 && !kwargs.$$is_hash)`
::Kernel.raise ::ArgumentError, "wrong number of arguments (given #{args.length}, expected 0)"
end
extra = kwargs.keys - self.class.members
if extra.any?
::Kernel.raise ::ArgumentError, "unknown keywords: #{extra.join(', ')}"
end
self.class.members.each do |name|
self[name] = kwargs[name]
end
else
if args.length > self.class.members.length
::Kernel.raise ::ArgumentError, 'struct size differs'
end
self.class.members.each_with_index do |name, index|
self[name] = args[index]
end
end
end
def initialize_copy(from)
%x{
self.$$data = {}
var keys = Object.keys(from.$$data), i, max, name;
for (i = 0, max = keys.length; i < max; i++) {
name = keys[i];
self.$$data[name] = from.$$data[name];
}
}
end
def self.keyword_init?
`self.$$keyword_init`
end
def members
self.class.members
end
def hash
[self.class, to_a].hash
end
def [](name)
if ::Integer === name
::Kernel.raise ::IndexError, "offset #{name} too small for struct(size:#{self.class.members.size})" if name < -self.class.members.size
::Kernel.raise ::IndexError, "offset #{name} too large for struct(size:#{self.class.members.size})" if name >= self.class.members.size
name = self.class.members[name]
elsif ::String === name
%x{
if(!self.$$data.hasOwnProperty(name)) {
#{::Kernel.raise ::NameError.new("no member '#{name}' in struct", name)}
}
}
else
::Kernel.raise ::TypeError, "no implicit conversion of #{name.class} into Integer"
end
name = ::Opal.coerce_to!(name, ::String, :to_str)
`self.$$data[name]`
end
def []=(name, value)
if ::Integer === name
::Kernel.raise ::IndexError, "offset #{name} too small for struct(size:#{self.class.members.size})" if name < -self.class.members.size
::Kernel.raise ::IndexError, "offset #{name} too large for struct(size:#{self.class.members.size})" if name >= self.class.members.size
name = self.class.members[name]
elsif ::String === name
::Kernel.raise ::NameError.new("no member '#{name}' in struct", name) unless self.class.members.include?(name.to_sym)
else
::Kernel.raise ::TypeError, "no implicit conversion of #{name.class} into Integer"
end
name = ::Opal.coerce_to!(name, ::String, :to_str)
`self.$$data[name] = value`
end
def ==(other)
return false unless other.instance_of?(self.class)
%x{
var recursed1 = {}, recursed2 = {};
function _eqeq(struct, other) {
var key, a, b;
recursed1[#{`struct`.__id__}] = true;
recursed2[#{`other`.__id__}] = true;
for (key in struct.$$data) {
a = struct.$$data[key];
b = other.$$data[key];
if (#{::Struct === `a`}) {
if (!recursed1.hasOwnProperty(#{`a`.__id__}) || !recursed2.hasOwnProperty(#{`b`.__id__})) {
if (!_eqeq(a, b)) {
return false;
}
}
} else {
if (!#{`a` == `b`}) {
return false;
}
}
}
return true;
}
return _eqeq(self, other);
}
end
def eql?(other)
return false unless other.instance_of?(self.class)
%x{
var recursed1 = {}, recursed2 = {};
function _eqeq(struct, other) {
var key, a, b;
recursed1[#{`struct`.__id__}] = true;
recursed2[#{`other`.__id__}] = true;
for (key in struct.$$data) {
a = struct.$$data[key];
b = other.$$data[key];
if (#{::Struct === `a`}) {
if (!recursed1.hasOwnProperty(#{`a`.__id__}) || !recursed2.hasOwnProperty(#{`b`.__id__})) {
if (!_eqeq(a, b)) {
return false;
}
}
} else {
if (!#{`a`.eql?(`b`)}) {
return false;
}
}
}
return true;
}
return _eqeq(self, other);
}
end
def each
return enum_for(:each) { size } unless block_given?
self.class.members.each { |name| yield self[name] }
self
end
def each_pair
return enum_for(:each_pair) { size } unless block_given?
self.class.members.each { |name| yield [name, self[name]] }
self
end
def length
self.class.members.length
end
def to_a
self.class.members.map { |name| self[name] }
end
`var inspect_stack = []`
def inspect
result = '#<struct '
if `inspect_stack`.include? __id__
result + ':...>'
else
`inspect_stack` << __id__
pushed = true
if ::Struct === self && self.class.name
result += "#{self.class} "
end
result += each_pair.map do |name, value|
"#{name}=#{Opal.inspect(value)}"
end.join ', '
result += '>'
result
end
ensure
`inspect_stack.pop()` if pushed
end
def to_h(*args, &block)
return map(&block).to_h(*args) if block_given?
self.class.members.each_with_object({}) { |name, h| h[name] = self[name] }
end
def values_at(*args)
args = args.map { |arg| `arg.$$is_range ? #{arg.to_a} : arg` }.flatten
%x{
var result = [];
for (var i = 0, len = args.length; i < len; i++) {
if (!args[i].$$is_number) {
#{::Kernel.raise ::TypeError, "no implicit conversion of #{`args[i]`.class} into Integer"}
}
result.push(#{self[`args[i]`]});
}
return result;
}
end
def dig(key, *keys)
item = if `key.$$is_string && self.$$data.hasOwnProperty(key)`
`self.$$data[key] || nil`
end
%x{
if (item === nil || keys.length === 0) {
return item;
}
}
unless item.respond_to?(:dig)
::Kernel.raise ::TypeError, "#{item.class} does not have #dig method"
end
item.dig(*keys)
end
alias size length
alias to_s inspect
alias values to_a
end