#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include "dict.h"
#define HASH_64_INIT (0xcbf29ce484222325ULL)
#define HASH_64_PRIME (0x100000001b3ULL)
#define DICT_SIZE 509
typedef struct dict_bucket
{
const void *db_key;
void *db_value;
struct dict_bucket *db_next;
} dict_bucket_t;
struct dict_hdl {
uint64_t dh_change;
int (*dh_cmp)(const void *, const void *);
uint64_t (*dh_hash)(const void *);
uint64_t dh_length;
dict_bucket_t **dh_buckets;
uint64_t dh_size;
};
#if defined(DEBUG)
static void bit_print_32(unsigned int);
static void bit_print_64(unsigned long long);
#endif
static int cmp_addr(const void *, const void *);
static uint64_t hash_addr(const void *);
#if defined(DEBUG)
void
bit_print_32(unsigned int v)
{
int i, mask = 1 << 31;
for (i = 1; i <= 32; i++) {
(void) putchar(((v & mask) == 0) ? '0' : '1');
v <<= 1;
if (i % 8 == 0 && i != 32)
(void) putchar(' ');
}
(void) putchar('\n');
}
void
bit_print_64(unsigned long long v)
{
long long mask = 1ll << 63;
int i;
for (i = 1; i <= 64; i++) {
(void) putchar(((v & mask) == 0) ? '0' : '1');
v <<= 1;
if (i % 8 == 0 && i != 64)
(void) putchar(' ');
}
(void) putchar('\n');
}
#endif
int
cmp_addr(const void *x, const void *y)
{
return (x != y);
}
uint64_t
hash_addr(const void *key)
{
return (hash_buf(&key, sizeof (key)));
}
uint64_t
hash_buf(const void *buf, size_t len)
{
uchar_t *start = (uchar_t *)buf;
uchar_t *end = start + len;
uint64_t hash = HASH_64_INIT;
while (start < end) {
hash *= HASH_64_PRIME;
hash ^= (uint64_t)*start++;
}
return (hash);
}
uint64_t
hash_str(const char *str)
{
uchar_t *p = (uchar_t *)str;
uint64_t hash = HASH_64_INIT;
while (*p) {
hash *= HASH_64_PRIME;
hash ^= (uint64_t)*p++;
}
return (hash);
}
uint64_t
dict_length(dict_hdl_t *hdl)
{
return (hdl->dh_length);
}
void
dict_free(dict_hdl_t **hdl)
{
if ((*hdl)->dh_length > 0) {
uint64_t i;
for (i = 0; i < (*hdl)->dh_size; i++) {
dict_bucket_t *this, *next;
for (this = (*hdl)->dh_buckets[i]; this != NULL;
this = next) {
next = this->db_next;
free(this);
}
}
}
free((*hdl)->dh_buckets);
free((*hdl));
*hdl = NULL;
}
dict_hdl_t *
dict_new(int (*cmp)(const void *, const void *),
uint64_t (*hash)(const void *))
{
dict_hdl_t *hdl;
if ((hdl = calloc(1, sizeof (dict_hdl_t))) == NULL)
return (NULL);
hdl->dh_size = DICT_SIZE;
if ((hdl->dh_buckets = calloc(hdl->dh_size, sizeof (dict_bucket_t *)))
== NULL) {
free(hdl);
return (NULL);
}
hdl->dh_cmp = cmp ? cmp : cmp_addr;
hdl->dh_hash = hash ? hash : hash_addr;
return (hdl);
}
void *
dict_get(dict_hdl_t *hdl, const void *key)
{
uint64_t i;
dict_bucket_t *bucket;
i = (*hdl->dh_hash)(key)%hdl->dh_size;
for (bucket = hdl->dh_buckets[i]; bucket != NULL;
bucket = bucket->db_next)
if ((*hdl->dh_cmp)(key, bucket->db_key) == 0)
break;
return (bucket ? bucket->db_value : NULL);
}
void *
dict_put(dict_hdl_t *hdl, const void *key, void *value)
{
uint64_t i;
dict_bucket_t *bucket;
void *prev = NULL;
i = (*hdl->dh_hash)(key)%hdl->dh_size;
for (bucket = hdl->dh_buckets[i]; bucket != NULL;
bucket = bucket->db_next)
if ((*hdl->dh_cmp)(key, bucket->db_key) == 0)
break;
if (bucket) {
prev = bucket->db_value;
} else {
bucket = malloc(sizeof (dict_bucket_t));
bucket->db_key = key;
bucket->db_next = hdl->dh_buckets[i];
hdl->dh_buckets[i] = bucket;
hdl->dh_length++;
}
hdl->dh_change++;
bucket->db_value = value;
return (prev);
}
void *
dict_remove(dict_hdl_t *hdl, const void *key)
{
uint64_t i;
dict_bucket_t **pbucket;
hdl->dh_change++;
i = (*hdl->dh_hash)(key)%hdl->dh_size;
for (pbucket = &hdl->dh_buckets[i]; *pbucket != NULL;
pbucket = &(*pbucket)->db_next) {
if ((*hdl->dh_cmp)(key, (*pbucket)->db_key) == 0) {
dict_bucket_t *bucket = *pbucket;
void *value = bucket->db_value;
*pbucket = bucket->db_next;
free(bucket);
hdl->dh_length--;
return (value);
}
}
return (NULL);
}
void
dict_map(dict_hdl_t *hdl, void (*apply)(const void *, void **, void *),
void *cl)
{
uint64_t i;
dict_bucket_t *bucket = NULL;
uint64_t change_stamp = hdl->dh_change;
for (i = 0; i < hdl->dh_size; i++) {
for (bucket = hdl->dh_buckets[i]; bucket != NULL;
bucket = bucket->db_next) {
apply(bucket->db_key, &bucket->db_value, cl);
if (hdl->dh_change != change_stamp)
assert(!"table modified illegally");
}
}
}