require 'corelib/enumerable' class Hash include Enumerable # Mark all hash instances as valid hashes (used to check keyword args, etc) `def.$$is_hash = true` def self.[](*objs) `Opal.hash.apply(null, objs)` end def self.allocate %x{ var hash = new self.$$alloc; hash.map = {}; hash.smap = {}; hash.keys = []; hash.none = nil; hash.proc = nil; return hash; } end def initialize(defaults = undefined, &block) %x{ self.none = (defaults === undefined ? nil : defaults); self.proc = block; } self end def ==(other) %x{ if (self === other) { return true; } if (!other.keys || !other.smap || !other.map) { return false; } if (self.keys.length !== other.keys.length) { return false; } var _map = self.map, smap = self.smap, _map2 = other.map, smap2 = other.smap, map, map2, key, khash, value, value2; for (var i = 0, length = self.keys.length; i < length; i++) { key = self.keys[i]; if (key.$$is_string) { khash = key; map = smap; map2 = smap2; } else { khash = key.$hash(); map = _map; map2 = _map2; } value = map[khash]; if (value === undefined) console.log('==', key, self); value2 = map2[khash]; if (value2 === undefined || #{not(`value` == `value2`)}) { return false; } } return true; } end def [](key) %x{ var map, khash; if (key.$$is_string) { map = self.smap; khash = key; } else { map = self.map; khash = key.$hash(); } if (map === undefined) { console.log(self, '[] --> key:', key, khash, map) } if (Opal.hasOwnProperty.call(map, khash)) { return map[khash]; } var proc = #@proc; if (proc !== nil) { return #{ `proc`.call self, key }; } return #@none; } end def []=(key, value) %x{ var map, khash, value; if (key.$$is_string) { map = self.smap; khash = key; } else { map = self.map; khash = key.$hash(); } if (!Opal.hasOwnProperty.call(map, khash)) { self.keys.push(key); } map[khash] = value; return value; } end def assoc(object) %x{ var keys = self.keys, map, key, khash; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (#{`key` == object}) { if (key.$$is_string) { map = self.smap; khash = key; } else { map = self.map; khash = key.$hash(); } return [key, map[khash]]; } } return nil; } end def clear %x{ self.map = {}; self.smap = {}; self.keys = []; return self; } end def clone %x{ var _map = {}, smap = {}, _map2 = self.map, smap2 = self.smap, keys = [], map, map2, key, khash, value; for (var i = 0, length = self.keys.length; i < length; i++) { key = self.keys[i]; if (key.$$is_string) { khash = key; map = smap; map2 = smap2; } else { khash = key.$hash(); map = _map; map2 = _map2; } value = map2[khash]; keys.push(key); map[khash] = value; } var clone = new self.$$class.$$alloc(); clone.map = _map; clone.smap = smap; clone.keys = keys; clone.none = self.none; clone.proc = self.proc; return clone; } end def default(val = undefined) %x{ if (val !== undefined && self.proc !== nil) { return #{@proc.call(self, val)}; } return self.none; } end def default=(object) %x{ self.proc = nil; return (self.none = object); } end def default_proc @proc end def default_proc=(proc) %x{ if (proc !== nil) { proc = #{Opal.coerce_to!(proc, Proc, :to_proc)}; if (#{proc.lambda?} && #{proc.arity.abs} != 2) { #{raise TypeError, "default_proc takes two arguments"}; } } self.none = nil; return (self.proc = proc); } end def delete(key, &block) %x{ var result, map, khash; if (key.$$is_string) { map = self.smap; khash = key; } else { map = self.map; khash = key.$hash(); } result = map[khash]; if (result != null) { delete map[khash]; self.keys.$delete(key); return result; } if (block !== nil) { return #{block.call(key)}; } return nil; } end def delete_if(&block) return enum_for :delete_if unless block %x{ var _map = self.map, smap = self.smap, keys = self.keys, map, key, value, obj, khash; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (key.$$is_string) { map = smap; khash = key; } else { map = _map; khash = key.$hash(); } obj = map[khash]; value = block(key, obj); if (value === $breaker) { return $breaker.$v; } if (value !== false && value !== nil) { keys.splice(i, 1); delete map[khash]; length--; i--; } } return self; } end alias dup clone def each(&block) return enum_for :each unless block %x{ var _map = self.map, smap = self.smap, keys = self.keys, map, key, khash, value; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (key.$$is_string) { map = smap; khash = key; } else { map = _map; khash = key.$hash(); } value = Opal.yield1(block, [key, map[khash]]); if (value === $breaker) { return $breaker.$v; } } return self; } end def each_key(&block) return enum_for :each_key unless block # @keys.each(&block) %x{ var keys = self.keys, key; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (block(key) === $breaker) { return $breaker.$v; } } return self; } end alias each_pair each def each_value(&block) return enum_for :each_value unless block %x{ var _map = self.map, smap = self.smap, keys = self.keys, key, map, khash; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (key.$$is_string) { map = smap; khash = key; } else { map = _map; khash = key.$hash(); } if (block(map[khash]) === $breaker) { return $breaker.$v; } } return self; } end def empty? `self.keys.length === 0` end alias eql? == def fetch(key, defaults = undefined, &block) %x{ var map, khash, value; if (key.$$is_string) { khash = key; map = self.smap; } else { khash = key.$hash(); map = self.map; } value = map[khash]; if (value != null) { return value; } if (block !== nil) { var value; if ((value = block(key)) === $breaker) { return $breaker.$v; } return value; } if (defaults != null) { return defaults; } #{ raise KeyError, "key not found: #{key.inspect}" }; } end def flatten(level=undefined) %x{ var _map = self.map, smap = self.smap, keys = self.keys, result = [], map, key, khash, value; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (key.$$is_string) { khash = key; map = smap; } else { khash = key.$hash(); map = _map; } value = map[khash]; result.push(key); if (value.$$is_array) { if (level == null || level === 1) { result.push(value); } else { result = result.concat(#{`value`.flatten(`level - 1`)}); } } else { result.push(value); } } return result; } end def has_key?(key) %x{ var keys = self.keys, map, khash; if (key.$$is_string) { khash = key; map = self.smap; } else { khash = key.$hash(); map = self.map; } if (Opal.hasOwnProperty.call(map, khash)) { for (var i = 0, length = keys.length; i < length; i++) { if (!#{not(key.eql?(`keys[i]`))}) { return true; } } } return false; } end def has_value?(value) %x{ for (var khash in self.map) { if (#{`self.map[khash]` == value}) { return true; } } return false; } end `var hash_ids = null;` def hash %x{ var top = (hash_ids === null); try { var key, value, hash = ['Hash'], keys = self.keys, id = self.$object_id(), counter = 0; if (top) { hash_ids = {} } if (hash_ids.hasOwnProperty(id)) { return 'self'; } hash_ids[id] = true; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; value = key.$$is_string ? self.smap[key] : self.map[key.$hash()]; key = key.$hash(); value = (typeof(value) === 'undefined') ? '' : value.$hash(); hash.push([key,value]); } return hash.sort().join(); } finally { if (top) { hash_ids = null; } } } end alias include? has_key? def index(object) %x{ var _map = self.map, smap = self.smap, keys = self.keys, map, khash, key; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (key.$$is_string) { map = smap; khash = key; } else { map = _map; khash = key.$hash(); } if (#{`map[khash]` == object}) { return key; } } return nil; } end def indexes(*keys) %x{ var result = [], _map = self.map, smap = self.smap, map, key, khash, value; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (key.$$is_string) { khash = key; map = smap; } else { khash = key.$hash(); map = _map; } value = map[khash]; if (value != null) { result.push(value); } else { result.push(self.none); } } return result; } end alias indices indexes `var inspect_ids = null;` def inspect %x{ var top = (inspect_ids === null); try { var key, value, inspect = [], keys = self.keys, id = self.$object_id(), counter = 0; if (top) { inspect_ids = {} } if (inspect_ids.hasOwnProperty(id)) { return '{...}'; } inspect_ids[id] = true; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; value = key.$$is_string ? self.smap[key] : self.map[key.$hash()]; key = key.$inspect(); value = value.$inspect(); inspect.push(key + '=>' + value); } return '{' + inspect.join(', ') + '}'; } finally { if (top) { inspect_ids = null; } } } end def invert %x{ var result = Opal.hash(), keys = self.keys, _map = self.map, smap = self.smap, keys2 = result.keys, _map2 = result.map, smap2 = result.smap, map, map2, key, khash, value; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (key.$$is_string) { map = smap; khash = key; } else { map = _map; khash = key.$hash(); } value = map[khash]; keys2.push(value); if (value.$$is_string) { map2 = smap2; khash = value; } else { map2 = _map2; khash = value.$hash(); } map2[khash] = key; } return result; } end def keep_if(&block) return enum_for :keep_if unless block %x{ var _map = self.map, smap = self.smap, keys = self.keys, map, key, khash, value, keep; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (key.$$is_string) { khash = key; map = smap; } else { khash = key.$hash(); map = _map; } value = map[khash]; keep = block(key, value); if (keep === $breaker) { return $breaker.$v; } if (keep === false || keep === nil) { keys.splice(i, 1); delete map[khash]; length--; i--; } } return self; } end alias key index alias key? has_key? def keys `self.keys.slice(0)` end def length `self.keys.length` end alias member? has_key? def merge(other, &block) unless Hash === other other = Opal.coerce_to!(other, Hash, :to_hash) end cloned = clone cloned.merge!(other, &block) cloned end def merge!(other, &block) %x{ if (! #{Hash === other}) { other = #{Opal.coerce_to!(other, Hash, :to_hash)}; } var keys = self.keys, _map = self.map, smap = self.smap, keys2 = other.keys, _map2 = other.map, smap2 = other.smap, map, map2, key, khash, value, value2; if (block === nil) { for (var i = 0, length = keys2.length; i < length; i++) { key = keys2[i]; if (key.$$is_string) { khash = key; map = smap; map2 = smap2; } else { khash = key.$hash(); map = _map; map2 = _map2; } if (map[khash] == null) { keys.push(key); } map[khash] = map2[khash]; } } else { for (var i = 0, length = keys2.length; i < length; i++) { key = keys2[i]; if (key.$$is_string) { khash = key; map = smap; map2 = smap2; } else { khash = key.$hash(); map = _map; map2 = _map2; } value = map[khash]; value2 = map2[khash]; if (value == null) { keys.push(key); map[khash] = value2; } else { map[khash] = block(key, value, value2); } } } return self; } end def rassoc(object) %x{ var keys = self.keys, _map = self.map, smap = self.smap, key, khash, value, map; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i] if (key.$$is_string) { khash = key; map = smap; } else { khash = key.$hash(); map = _map; } value = map[khash]; if (#{`value` == object}) { return [key, value]; } } return nil; } end def reject(&block) return enum_for :reject unless block %x{ var keys = self.keys, _map = self.map, smap = self.smap, result = Opal.hash(), _map2 = result.map, smap2 = result.smap, keys2 = result.keys, map, map2, key, khash, object, value; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (key.$$is_string) { khash = key; map = smap; map2 = smap2; } else { khash = key.$hash(); map = _map; map2 = _map2; } object = map[khash]; if ((value = block(key, object)) === $breaker) { return $breaker.$v; } if (value === false || value === nil) { keys2.push(key); map2[khash] = object; } } return result; } end def replace(other) %x{ var keys = self.keys = [], _map = self.map = {}, smap = self.smap = {}, _map2 = other.map, smap2 = other.smap, key, khash, map, map2; for (var i = 0, length = other.keys.length; i < length; i++) { key = other.keys[i]; if (key.$$is_string) { khash = key; map = smap; map2 = smap2; } else { khash = key.$hash(); map = _map; map2 = _map2; } keys.push(key); map[khash] = map2[khash]; } return self; } end def select(&block) return enum_for :select unless block %x{ var keys = self.keys, _map = self.map, smap = self.smap, result = Opal.hash(), _map2 = result.map, smap2 = result.smap, keys2 = result.keys, map, map2, key, khash, value, object; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (key.$$is_string) { khash = key; map = smap; map2 = smap2; } else { khash = key.$hash(); map = _map; map2 = _map2; } value = map[khash]; object = block(key, value); if (object === $breaker) { return $breaker.$v; } if (object !== false && object !== nil) { keys2.push(key); map2[khash] = value; } } return result; } end def select!(&block) return enum_for :select! unless block %x{ var _map = self.map, smap = self.smap, keys = self.keys, result = nil, key, khash, value, object, map; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (key.$$is_string) { khash = key; map = smap; } else { khash = key.$hash(); map = _map; } value = map[khash]; object = block(key, value); if (object === $breaker) { return $breaker.$v; } if (object === false || object === nil) { keys.splice(i, 1); delete map[khash]; length--; i--; result = self } } return result; } end def shift %x{ var keys = self.keys, _map = self.map, smap = self.smap, map, key, khash, value; if (keys.length) { key = keys[0]; if (key.$$is_string) { khash = key; map = smap; } else { khash = key.$hash(); map = _map; } value = map[khash]; delete map[khash]; keys.splice(0, 1); return [key, value]; } return nil; } end alias size length alias_method :store, :[]= def to_a %x{ var keys = self.keys, _map = self.map, smap = self.smap, result = [], map, key, khash; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (key.$$is_string) { khash = key; map = smap; } else { khash = key.$hash(); map = _map; } result.push([key, map[khash]]); } return result; } end def to_h %x{ if (self.$$class === Opal.Hash) { return self } var hash = new Opal.Hash.$$alloc, cloned = #{clone}; hash.map = cloned.map; hash.smap = cloned.smap; hash.keys = cloned.keys; hash.none = cloned.none; hash.proc = cloned.proc; return hash; } end def to_hash self end alias to_s inspect alias update merge! alias value? has_value? alias values_at indexes def values %x{ var _map = self.map, smap = self.smap, keys = self.keys, result = [], map, khash, key; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (key.$$is_string) { khash = key; map = smap; } else { khash = key.$hash(); map = _map; } result.push(map[khash]); } return result; } end end