Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
917 lines (719 sloc) 18.659 kb
if Rubinius::Config['hash.hamt']
class Hash
include Enumerable
Vector = Rubinius::Tuple
class State
attr_accessor :head
attr_accessor :tail
attr_accessor :size
def self.from(state)
new_state = new
new_state.compare_by_identity if state and state.compare_by_identity?
new_state
end
def initialize
@size = 0
@head = nil
@tail = nil
@compare_by_identity = false
end
def compare_by_identity?
@compare_by_identity
end
def compare_by_identity
@compare_by_identity = true
class << self
def match?(this_key, this_hash, other_key, other_hash)
Rubinius::Type.object_equal other_key, this_key
end
end
end
def match?(this_key, this_hash, other_key, other_hash)
other_hash == this_hash and
(Rubinius::Type::object_equal(other_key, this_key) or
other_key.eql?(this_key))
end
end
class Item
attr_accessor :state
attr_accessor :previous
attr_accessor :next
attr_accessor :key
attr_accessor :key_hash
attr_accessor :value
def initialize(key, key_hash, value, state)
if key.kind_of?(String) and !key.frozen? and !state.compare_by_identity?
key = key.dup
key.freeze
end
@key = key
@key_hash = key_hash
@value = value
@state = state
if tail = state.tail
@previous = tail
state.tail = tail.next = self
else
state.head = state.tail = self
end
state.size += 1
end
def empty?
not @state
end
def lookup(key, key_hash)
return self if @state.match? @key, @key_hash, key, key_hash
end
def insert(key, key_hash, value)
return unless @state.match? @key, @key_hash, key, key_hash
@value = value
self
end
def delete(key, key_hash)
if @state.match? @key, @key_hash, key, key_hash
if @previous
@previous.next = @next
else
@state.head = @next
end
if @next
@next.previous = @previous
else
@state.tail = @previous
end
@state.size -= 1
@state = nil
self
end
end
end
class List
attr_accessor :state
attr_accessor :key_hash
attr_accessor :entries
def initialize(state, key_hash)
@state = state
@key_hash = key_hash
@entries = Vector.new 2
end
def empty?
not @entries
end
def lookup(key, key_hash)
@entries.each { |e| return e if e.lookup(key, key_hash) }
nil
end
def insert(key, key_hash, value)
@entries.each { |e| return e if e.insert(key, key_hash, value) }
item = Item.new key, key_hash, value, @state
@entries = @entries.insert_at_index @entries.size, item
item
end
def add(item, key, key_hash, value)
@entries[0] = item
@entries[1] = Item.new key, key_hash, value, @state
end
def delete(key, key_hash)
@entries.each_with_index do |e, i|
if e.delete key, key_hash
@entries = @entries.delete_at_index i
return e
end
end
nil
end
end
# An Array Mapped Trie
class Trie
attr_accessor :bmp
attr_accessor :entries
attr_accessor :level
attr_accessor :state
def initialize(state, level)
@state = state
@level = level
@bmp = 0
@entries = nil
end
def empty?
not @entries
end
def lookup(key, key_hash)
return unless index = item_index(key_hash)
@entries[index].lookup key, key_hash
end
def insert(key, key_hash, value)
if index = item_index(key_hash)
item = @entries[index]
unless item.insert key, key_hash, value
trie = Trie.new @state, @level + 1
trie.add item, key, key_hash, value
@entries[index] = trie
return trie
else
return item
end
else
@bmp = set_bitmap key_hash
index = item_index key_hash
item = Item.new key, key_hash, value, @state
@entries = @entries.insert_at_index index, item
return item
end
end
def add(item, key, key_hash, value)
item_hash = item.key_hash
bmp = @bmp = set_bitmap(item_hash)
@bmp = set_bitmap key_hash
if bmp == @bmp
if item_hash == key_hash
list = List.new @state, key_hash
list.add item, key, key_hash, value
@entries = Vector[list]
else
trie = Trie.new @state, @level + 1
trie.add item, key, key_hash, value
@entries = Vector[trie]
end
else
@entries = Vector.new 2
@entries[item_index(item_hash)] = item
@entries[item_index(key_hash)] = Item.new key, key_hash, value, @state
end
end
def delete(key, key_hash)
return unless index = item_index(key_hash)
item = @entries[index]
if de = item.delete(key, key_hash)
if item.empty?
@bmp = unset_bitmap key_hash
@entries = @entries.delete_at_index index
end
return de
end
end
def item_index(key_hash)
Rubinius.invoke_primitive :vm_hash_trie_item_index, key_hash, @level, @bmp
end
def set_bitmap(key_hash)
Rubinius.invoke_primitive :vm_hash_trie_set_bitmap, key_hash, @level, @bmp
end
def unset_bitmap(key_hash)
Rubinius.invoke_primitive :vm_hash_trie_unset_bitmap, key_hash, @level, @bmp
end
end
class Table
attr_accessor :entries
attr_accessor :state
def initialize(state)
@state = state
@entries = Vector.new 64
end
def item_index(key_hash)
key_hash & 0b111111
end
def lookup(key, key_hash)
if item = @entries[item_index(key_hash)]
item.lookup key, key_hash
end
end
def insert(key, key_hash, value)
index = item_index key_hash
if item = @entries[index]
unless item.insert key, key_hash, value
trie = Trie.new @state, 0
trie.add item, key, key_hash, value
@entries[index] = trie
end
else
item = Item.new key, key_hash, value, @state
@entries[index] = item
end
end
def delete(key, key_hash)
index = item_index key_hash
return unless item = @entries[index]
if de = item.delete(key, key_hash)
if item.empty?
@entries[index] = nil
end
return de
end
end
end
# An external iterator that returns entries in insertion order. While
# somewhat following the API of Enumerator, it is named Iterator because it
# does not provide <code>#each</code> and should not conflict with
# +Enumerator+ in MRI 1.8.7+. Returned by <code>Hash#to_iter</code>.
class Iterator
def initialize(state)
@state = state
end
# Returns the next object or +nil+.
def next(item)
if item
return item if item = item.next
else
return @state.head
end
end
end
# Hash methods
def self.[](*args)
total = args.size
if total == 1
obj = args.first
if hash = Rubinius::Type.check_convert_type(obj, Hash, :to_hash)
new_hash = allocate.replace(hash)
new_hash.default = nil
return new_hash
elsif array = Rubinius::Type.check_convert_type(obj, Array, :to_ary)
hash = allocate
array.each do |a|
a = Rubinius::Type.check_convert_type a, Array, :to_ary
next unless a
size = a.size
unless size >= 1 and size <= 2
raise ArgumentError, "invalid number of elements (#{size} for 1..2)"
end
hash[a[0]] = a[1]
end
return hash
end
end
return allocate if total == 0
if total & 1 == 1
raise ArgumentError, "Expected an even number, got #{total}"
end
hash = allocate
i = 0
while i < total
hash[args[i]] = args[i+1]
i += 2
end
hash
end
def self.try_convert(obj)
Rubinius::Type.try_convert obj, Hash, :to_hash
end
def self.new_from_literal(size)
allocate
end
def initialize(default=undefined, &block)
Rubinius.check_frozen
if !undefined.equal?(default) and block
raise ArgumentError, "Specify a default value or a block, not both"
end
if block
@default = nil
@default_proc = block
elsif !undefined.equal?(default)
@default = default
@default_proc = nil
end
self
end
private :initialize
def hash
val = size
Thread.detect_outermost_recursion self do
each_item do |item|
Rubinius.privately { val ^= item.key.hash }
Rubinius.privately { val ^= item.value.hash }
end
end
return val
end
def initialize_copy(other)
replace other
end
private :initialize_copy
def [](key)
Rubinius.privately { key_hash = key.hash }
if @table and item = @table.lookup(key, key_hash)
return item.value
end
default key
end
def []=(key, value)
Rubinius.check_frozen
unless @table
@state = State.new unless @state
@table = Table.new @state
end
Rubinius.privately { key_hash = key.hash }
@table.insert key, key_hash, value
value
end
alias_method :store, :[]=
def assoc(key)
each_item { |e| return e.key, e.value if key == e.key }
nil
end
def clear
@state = State.from @state
@table = nil
self
end
def compare_by_identity
Rubinius.check_frozen
@state = State.new unless @state
@state.compare_by_identity
self
end
def compare_by_identity?
return false unless @state
@state.compare_by_identity?
end
def default(key=undefined)
if @default_proc and !undefined.equal?(key)
@default_proc.call(self, key)
else
@default
end
end
def default=(value)
@default_proc = nil
@default = value
end
def default_proc
@default_proc
end
def default_proc=(prc)
Rubinius.check_frozen
unless prc.nil?
prc = Rubinius::Type.coerce_to prc, Proc, :to_proc
if prc.lambda? and prc.arity != 2
raise TypeError, "default proc must have arity 2"
end
end
@default = nil
@default_proc = prc
end
def delete(key)
Rubinius.check_frozen
if @table
Rubinius.privately { key_hash = key.hash }
if item = @table.delete(key, key_hash)
return item.value
end
end
return yield(key) if block_given?
nil
end
def delete_if
return to_enum(:delete_if) { size } unless block_given?
Rubinius.check_frozen
each_item { |e| delete e.key if yield(e.key, e.value) }
self
end
def each
return to_enum(:each) { size } unless block_given?
if @state
item = @state.head
while item
yield [item.key, item.value]
item = item.next
end
end
self
end
alias_method :each_pair, :each
def each_item
return unless @state
item = @state.head
while item
yield item
item = item.next
end
end
def each_key
return to_enum(:each_key) { size } unless block_given?
each_item { |e| yield e.key }
self
end
def each_value
return to_enum(:each_value) { size } unless block_given?
each_item { |e| yield e.value }
self
end
def empty?
size == 0
end
def eql?(other)
return true if self.equal? other
unless other.kind_of? Hash
return false unless other.respond_to? :to_hash
return other.eql?(self)
end
return false unless other.size == size
Thread.detect_recursion self, other do
other.each_item do |e|
Rubinius.privately { key_hash = e.key.hash }
return false unless item = @table.lookup(e.key, key_hash)
# Order of the comparison matters! We must compare our value with
# the other Hash's value and not the other way around.
return false unless item.value.eql?(e.value)
end
end
true
end
def ==(other)
return true if self.equal? other
unless other.kind_of? Hash
return false unless other.respond_to? :to_hash
return other == self
end
return false unless other.size == size
Thread.detect_recursion self, other do
other.each_item do |e|
Rubinius.privately { key_hash = e.key.hash }
item = @table.lookup(e.key, key_hash)
return false unless item
# Order of the comparison matters! We must compare our value with
# the other Hash's value and not the other way around.
unless Rubinius::Type::object_equal(item.value, e.value) or
item.value == e.value
return false
end
end
end
true
end
def fetch(key, default=undefined)
unless empty?
Rubinius.privately { key_hash = key.hash }
if item = @table.lookup(key, key_hash)
return item.value
end
end
return yield(key) if block_given?
return default unless undefined.equal?(default)
raise KeyError, 'key #{key} not found'
end
# Searches for an item matching +key+. Returns the item
# if found. Otherwise returns +nil+. Called from the C-API.
def find_item(key)
unless empty?
Rubinius.privately { key_hash = key.hash }
if item = @table.lookup(key, key_hash)
return item
end
end
nil
end
def flatten(level=1)
to_a.flatten(level)
end
def initialize_copy(other)
replace other
end
private :initialize_copy
def inspect
out = []
return '{...}' if Thread.detect_recursion self do
each_item do |e|
str = e.key.inspect
str << '=>'
str << e.value.inspect
out << str
end
end
ret = "{#{out.join ', '}}"
Rubinius::Type.infect(ret, self) unless empty?
ret
end
alias_method :to_s, :inspect
def invert
h = Hash.allocate
each_item { |e| h[e.value] = e.key }
h
end
def keep_if
return to_enum(:keep_if) { size } unless block_given?
Rubinius.check_frozen
each_item { |e| delete e.key unless yield(e.key, e.value) }
self
end
def key(value)
each_item { |e| return e.key if e.value == value }
nil
end
alias_method :index, :key
def key?(key)
Rubinius.privately { key_hash = key.hash }
if @table and @table.lookup(key, key_hash)
true
else
false
end
end
alias_method :has_key?, :key?
alias_method :include?, :key?
alias_method :member?, :key?
def keys
ary = []
each_item { |e| ary << e.key }
ary
end
def merge(other, &block)
dup.merge!(other, &block)
end
def merge!(other)
Rubinius.check_frozen
other = Rubinius::Type.coerce_to other, Hash, :to_hash
return self if other.empty?
unless @table
@state = State.new unless @state
@table = Table.new @state
end
if block_given?
other.each_item do |e|
key = e.key
Rubinius.privately { key_hash = key.hash }
if item = @table.lookup(key, key_hash)
item.value = yield(key, item.value, e.value)
else
@table.insert key, key_hash, e.value
end
end
else
other.each_item do |e|
key = e.key
Rubinius.privately { key_hash = key.hash }
@table.insert key, key_hash, e.value
end
end
self
end
alias_method :update, :merge!
def rassoc(value)
each_item { |e| return e.key, e.value if value == e.value }
nil
end
def rehash
Rubinius.check_frozen
return if empty?
item = @state.head
@state = State.from @state
table = Table.new @state
while item
Rubinius.privately { key_hash = item.key.hash }
table.insert item.key, key_hash, item.value
item = item.next
end
@table = table
self
end
def reject(&block)
return to_enum(:reject) { size } unless block_given?
hsh = dup.delete_if(&block)
hsh.taint if tainted?
hsh
end
def reject!(&block)
return to_enum(:reject!) { size } unless block_given?
Rubinius.check_frozen
unless empty?
size = @state.size
delete_if(&block)
return self if size != @state.size
end
nil
end
def replace(other)
Rubinius.check_frozen
unless other.kind_of? Hash
other = Rubinius::Type.coerce_to other, Hash, :to_hash
end
return self if self.equal? other
@state = State.from @state
@state.compare_by_identity if other.compare_by_identity?
@table = Table.new @state
other.each_item do |e|
Rubinius.privately { key_hash = e.key.hash }
@table.insert e.key, key_hash, e.value
end
@default = other.default
@default_proc = other.default_proc
self
end
def select
return to_enum(:select) { size } unless block_given?
hsh = Hash.allocate
each_item do |e|
key = e.key
value = e.value
hsh[key] = value if yield(key, value)
end
hsh
end
def select!
return to_enum(:select!) { size } unless block_given?
Rubinius.check_frozen
return nil if empty?
size = @state.size
each_item { |e| delete e.key unless yield(e.key, e.value) }
return nil if size == @state.size
self
end
def shift
Rubinius.check_frozen
return default(nil) if empty?
item = @state.head
Rubinius.privately { key_hash = item.key.hash }
@table.delete item.key, key_hash
return item.key, item.value
end
def size
@state ? @state.size : 0
end
alias_method :length, :size
def to_a
ary = []
each_item { |e| ary << [e.key, e.value] }
Rubinius::Type.infect ary, self
ary
end
# Returns an external iterator for the bins. See +Iterator+
def to_iter
Iterator.new @state
end
def to_h
if instance_of? Hash
self
else
Hash.allocate.replace(to_hash)
end
end
def to_hash
self
end
def value?(value)
each_item { |e| return true if value == e.value }
false
end
alias_method :has_value?, :value?
def values
ary = []
each_item { |e| ary << e.value }
ary
end
def values_at(*args)
if empty?
args.map { |key| default key }
else
args.map do |key|
Rubinius.privately { key_hash = key.hash }
if item = @table.lookup(key, key_hash)
item.value
else
default key
end
end
end
end
# Used internally in Rubinius to get around subclasses redefining #[]=
alias_method :__store__, :[]=
end
end
Jump to Line
Something went wrong with that request. Please try again.