package SPVM::Hash : precompile {
use SPVM::Hash::Entry;
use SPVM::NumberUtil (INT32_MAX);
has bucket_count : int;
has count : ro int;
has entries : SPVM::Hash::Entry[];
private enum {
_DEFALUT_HASH_TABLE_LENGTH = 16,
}
private sub _MAX_LOAD_FACTOR : double () { return 0.75; }
sub new : SPVM::Hash ($key_values : oarray) {
my $self = new SPVM::Hash;
my $default_capacity = 32;
$self->{bucket_count} = _DEFALUT_HASH_TABLE_LENGTH();
$self->{entries} = new SPVM::Hash::Entry[_DEFALUT_HASH_TABLE_LENGTH()];
$self->{count} = 0;
my $length : int;
if ($key_values) {
$length = @$key_values;
}
else {
$length = 0;
}
if ($length % 2 != 0) {
die "Odd number array is invalid";
}
if ($key_values) {
for (my $i = 0; $i < @$key_values; $i += 2) {
my $key = $key_values->[$i];
my $value = $key_values->[$i + 1];
unless ($key isa string) {
die "Key must be string";
}
$self->set((string)$key => $value);
}
}
return $self;
}
sub set : void ($self : self, $key : string, $val : object) {
unless ($key) {
die "key must not be undef";
}
# Rehash
if (((double)$self->{count} / $self->{bucket_count}) > _MAX_LOAD_FACTOR()) {
$self->_rehash;
}
my $count = $self->{count};
_set_to_container($self->{entries}, $self->{bucket_count}, \$count, $key, $val);
$self->{count} = $count;
}
sub get : object ($self : self, $key : string) {
my $index = _index_by_key($key, $self->{bucket_count});
my $ref = $self->{entries}->[$index];
unless ($ref) {
return undef;
}
while (1) {
if ($ref->{key} eq $key) {
return $ref->{val};
}
unless ($ref->{next_entry}) {
return undef;
}
$ref = $ref->{next_entry};
}
}
sub exists : int ($self : self, $key : string) {
my $index = _index_by_key($key, $self->{bucket_count});
my $ref = $self->{entries}->[$index];
unless ($ref) {
return 0;
}
while (1) {
if ($ref->{key} eq $key) {
return 1;
}
unless ($ref->{next_entry}) {
return 0;
}
$ref = $ref->{next_entry};
}
}
sub delete : object ($self : self, $key : string) {
my $index = _index_by_key($key, $self->{bucket_count});
my $ref = $self->{entries}->[$index];
my $prev : SPVM::Hash::Entry = undef;
unless ($ref) {
return undef;
}
while (1) {
if ($ref->{key} eq $key) {
my $ret = $ref->{val};
if ($prev) {
$prev->{next_entry} = $ref->{next_entry};
}
else {
$self->{entries}->[$index] = $ref->{next_entry};
}
$self->{count}--;
return $ret;
}
unless ($ref->{next_entry}) {
return undef;
}
$prev = $ref;
$ref = $ref->{next_entry};
}
}
sub keys : string[] ($self : self) {
my $retval = new string[$self->{count}];
# iterate entries
my $count = 0;
for (my $i = 0; $i < $self->{bucket_count}; ++$i) {
my $ref = $self->{entries}->[$i];
while ($ref) {
$retval->[$count++] = $ref->{key};
$ref = $ref->{next_entry};
}
}
return $retval;
}
sub values : object[] ($self : self) {
my $retval = new object[$self->{count}];
# iterate entries
my $count = 0;
for (my $i = 0; $i < $self->{bucket_count}; ++$i) {
my $ref = $self->{entries}->[$i];
while ($ref) {
$retval->[$count++] = $ref->{val};
$ref = $ref->{next_entry};
}
}
return $retval;
}
sub copy : SPVM::Hash ($self : self) {
my $ret = SPVM::Hash->new({});
my $keys = $self->keys;
for (my $i = 0; $i < @$keys; ++$i) {
$ret->set($keys->[$i], $self->get($keys->[$i]));
}
return $ret;
}
native sub _murmur_hash : long ($string : string, $seed : int);
sub set_byte : void ($self : self, $name : string, $value : byte) {
$self->set($name => SPVM::Byte->new($value));
}
sub set_short : void ($self : self, $name : string, $value : short) {
$self->set($name => SPVM::Short->new($value));
}
sub set_int : void ($self : self, $name : string, $value : int) {
$self->set($name => SPVM::Int->new($value));
}
sub set_string : void ($self : self, $name : string, $value : string) {
$self->set($name => $value);
}
sub set_long : void ($self : self, $name : string, $value : long) {
$self->set($name => SPVM::Long->new($value));
}
sub set_float : void ($self : self, $name : string, $value : float) {
$self->set($name => SPVM::Float->new($value));
}
sub set_double : void ($self : self, $name : string, $value : double) {
$self->set($name => SPVM::Double->new($value));
}
sub get_byte : byte ($self : self, $name : string) {
return (byte)$self->get($name);
}
sub get_short : short ($self : self, $name : string) {
return (short)$self->get($name);
}
sub get_int : int ($self : self, $name : string) {
return (int)$self->get($name);
}
sub get_string : string ($self : self, $name : string) {
return (string)$self->get($name);
}
sub get_long : long ($self : self, $name : string) {
return (long)$self->get($name);
}
sub get_float : float ($self : self, $name : string) {
return (float)$self->get($name);
}
sub get_double : double ($self : self, $name : string) {
return (double)$self->get($name);
}
sub _bucket_count : int ($self : self) {
return $self->{bucket_count};
}
sub _entries : SPVM::Hash::Entry [] ($self : self) {
return $self->{entries};
}
sub _index_by_key : int ($key : string, $bucket_count : int) {
my $default_seed = 123456789;
return (int)(_murmur_hash($key, $default_seed) % $bucket_count);
}
sub _set_to_container : void ($entries : SPVM::Hash::Entry[], $bucket_count : int, $count_ref : int&,
$key : string, $val : object) {
my $index = _index_by_key($key, $bucket_count);
my $ref = $entries->[$index];
unless ($ref) {
$entries->[$index] = new SPVM::Hash::Entry;
$entries->[$index]->{key} = $key;
$entries->[$index]->{val} = $val;
$entries->[$index]->{next_entry} = undef;
++$$count_ref;
return;
}
while (1) {
if ($ref->{key} eq $key) {
$ref->{val} = $val;
return;
}
unless ($ref->{next_entry}) {
$ref->{next_entry} = new SPVM::Hash::Entry;
$ref->{next_entry}->{key} = $key;
$ref->{next_entry}->{val} = $val;
$ref->{next_entry}->{next_entry} = undef;
++$$count_ref;
return;
}
$ref = $ref->{next_entry};
}
}
sub _rehash : void ($self : self) {
my $new_bucket_count : int;
$new_bucket_count = $self->{bucket_count} * 2;
my $new_entries = new SPVM::Hash::Entry [$new_bucket_count];
my $new_count = 0;
# iterate entries
for (my $i = 0; $i < $self->{bucket_count}; ++$i) {
my $ref = $self->{entries}->[$i];
while ($ref) {
_set_to_container($new_entries, $new_bucket_count, \$new_count, $ref->{key}, $ref->{val});
$ref = $ref->{next_entry};
}
}
$self->{bucket_count} = $new_bucket_count;
$self->{entries} = $new_entries;
}
}