You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
693 lines
20 KiB
693 lines
20 KiB
/* -*- coding: utf-8 -*-
|
|
* ----------------------------------------------------------------------
|
|
* Copyright © 2011-2013, RedJack, LLC.
|
|
* All rights reserved.
|
|
*
|
|
* Please see the COPYING file in this distribution for license
|
|
* details.
|
|
* ----------------------------------------------------------------------
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "libcork/core/callbacks.h"
|
|
#include "libcork/core/hash.h"
|
|
#include "libcork/core/mempool.h"
|
|
#include "libcork/core/types.h"
|
|
#include "libcork/ds/dllist.h"
|
|
#include "libcork/ds/hash-table.h"
|
|
#include "libcork/helpers/errors.h"
|
|
|
|
#ifndef CORK_HASH_TABLE_DEBUG
|
|
#define CORK_HASH_TABLE_DEBUG 0
|
|
#endif
|
|
|
|
#if CORK_HASH_TABLE_DEBUG
|
|
#include <stdio.h>
|
|
#define DEBUG(...) \
|
|
do { \
|
|
fprintf(stderr, __VA_ARGS__); \
|
|
fprintf(stderr, "\n"); \
|
|
} while (0)
|
|
#else
|
|
#define DEBUG(...) /* nothing */
|
|
#endif
|
|
|
|
|
|
/*-----------------------------------------------------------------------
|
|
* Hash tables
|
|
*/
|
|
|
|
struct cork_hash_table_entry_priv {
|
|
struct cork_hash_table_entry public;
|
|
struct cork_dllist_item in_bucket;
|
|
struct cork_dllist_item insertion_order;
|
|
};
|
|
|
|
struct cork_hash_table {
|
|
struct cork_dllist *bins;
|
|
struct cork_dllist insertion_order;
|
|
size_t bin_count;
|
|
size_t bin_mask;
|
|
size_t entry_count;
|
|
struct cork_mempool *pool;
|
|
void *user_data;
|
|
cork_free_f free_user_data;
|
|
cork_hash_f hash;
|
|
cork_equals_f equals;
|
|
cork_free_f free_key;
|
|
cork_free_f free_value;
|
|
};
|
|
|
|
static cork_hash
|
|
cork_hash_table__default_hash(void *user_data, const void *key)
|
|
{
|
|
return (cork_hash) (uintptr_t) key;
|
|
}
|
|
|
|
static bool
|
|
cork_hash_table__default_equals(void *user_data,
|
|
const void *key1, const void *key2)
|
|
{
|
|
return key1 == key2;
|
|
}
|
|
|
|
|
|
/* The default initial number of bins to allocate in a new table. */
|
|
#define CORK_HASH_TABLE_DEFAULT_INITIAL_SIZE 8
|
|
|
|
/* The default number of entries per bin to allow before increasing the
|
|
* number of bins. */
|
|
#define CORK_HASH_TABLE_MAX_DENSITY 5
|
|
|
|
/* Return a power-of-2 bin count that's at least as big as the given requested
|
|
* size. */
|
|
static inline size_t
|
|
cork_hash_table_new_size(size_t desired_count)
|
|
{
|
|
size_t v = desired_count;
|
|
size_t r = 1;
|
|
while (v >>= 1) {
|
|
r <<= 1;
|
|
}
|
|
if (r != desired_count) {
|
|
r <<= 1;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
#define bin_index(table, hash) ((hash) & (table)->bin_mask)
|
|
|
|
/* Allocates a new bins array in a hash table. We overwrite the old
|
|
* array, so make sure to stash it away somewhere safe first. */
|
|
static void
|
|
cork_hash_table_allocate_bins(struct cork_hash_table *table,
|
|
size_t desired_count)
|
|
{
|
|
size_t i;
|
|
|
|
table->bin_count = cork_hash_table_new_size(desired_count);
|
|
table->bin_mask = table->bin_count - 1;
|
|
DEBUG("Allocate %zu bins", table->bin_count);
|
|
table->bins = cork_calloc(table->bin_count, sizeof(struct cork_dllist));
|
|
for (i = 0; i < table->bin_count; i++) {
|
|
cork_dllist_init(&table->bins[i]);
|
|
}
|
|
}
|
|
|
|
|
|
static struct cork_hash_table_entry_priv *
|
|
cork_hash_table_new_entry(struct cork_hash_table *table,
|
|
cork_hash hash, void *key, void *value)
|
|
{
|
|
struct cork_hash_table_entry_priv *entry =
|
|
cork_mempool_new_object(table->pool);
|
|
cork_dllist_add(&table->insertion_order, &entry->insertion_order);
|
|
entry->public.hash = hash;
|
|
entry->public.key = key;
|
|
entry->public.value = value;
|
|
return entry;
|
|
}
|
|
|
|
static void
|
|
cork_hash_table_free_entry(struct cork_hash_table *table,
|
|
struct cork_hash_table_entry_priv *entry)
|
|
{
|
|
if (table->free_key != NULL) {
|
|
table->free_key(entry->public.key);
|
|
}
|
|
if (table->free_value != NULL) {
|
|
table->free_value(entry->public.value);
|
|
}
|
|
cork_dllist_remove(&entry->insertion_order);
|
|
cork_mempool_free_object(table->pool, entry);
|
|
}
|
|
|
|
|
|
struct cork_hash_table *
|
|
cork_hash_table_new(size_t initial_size, unsigned int flags)
|
|
{
|
|
struct cork_hash_table *table = cork_new(struct cork_hash_table);
|
|
table->entry_count = 0;
|
|
table->user_data = NULL;
|
|
table->free_user_data = NULL;
|
|
table->hash = cork_hash_table__default_hash;
|
|
table->equals = cork_hash_table__default_equals;
|
|
table->free_key = NULL;
|
|
table->free_value = NULL;
|
|
table->pool = cork_mempool_new(struct cork_hash_table_entry_priv);
|
|
cork_dllist_init(&table->insertion_order);
|
|
if (initial_size < CORK_HASH_TABLE_DEFAULT_INITIAL_SIZE) {
|
|
initial_size = CORK_HASH_TABLE_DEFAULT_INITIAL_SIZE;
|
|
}
|
|
cork_hash_table_allocate_bins(table, initial_size);
|
|
return table;
|
|
}
|
|
|
|
void
|
|
cork_hash_table_clear(struct cork_hash_table *table)
|
|
{
|
|
size_t i;
|
|
struct cork_dllist_item *curr;
|
|
struct cork_dllist_item *next;
|
|
|
|
DEBUG("(clear) Remove all entries");
|
|
for (curr = cork_dllist_start(&table->insertion_order);
|
|
!cork_dllist_is_end(&table->insertion_order, curr);
|
|
curr = next) {
|
|
struct cork_hash_table_entry_priv *entry =
|
|
cork_container_of
|
|
(curr, struct cork_hash_table_entry_priv, insertion_order);
|
|
next = curr->next;
|
|
cork_hash_table_free_entry(table, entry);
|
|
}
|
|
cork_dllist_init(&table->insertion_order);
|
|
|
|
DEBUG("(clear) Clear bins");
|
|
for (i = 0; i < table->bin_count; i++) {
|
|
DEBUG(" Bin %zu", i);
|
|
cork_dllist_init(&table->bins[i]);
|
|
}
|
|
|
|
table->entry_count = 0;
|
|
}
|
|
|
|
void
|
|
cork_hash_table_free(struct cork_hash_table *table)
|
|
{
|
|
cork_hash_table_clear(table);
|
|
cork_mempool_free(table->pool);
|
|
free(table->bins);
|
|
free(table);
|
|
}
|
|
|
|
size_t
|
|
cork_hash_table_size(const struct cork_hash_table *table)
|
|
{
|
|
return table->entry_count;
|
|
}
|
|
|
|
void
|
|
cork_hash_table_set_user_data(struct cork_hash_table *table,
|
|
void *user_data, cork_free_f free_user_data)
|
|
{
|
|
table->user_data = user_data;
|
|
table->free_user_data = free_user_data;
|
|
}
|
|
|
|
void
|
|
cork_hash_table_set_hash(struct cork_hash_table *table, cork_hash_f hash)
|
|
{
|
|
table->hash = hash;
|
|
}
|
|
|
|
void
|
|
cork_hash_table_set_equals(struct cork_hash_table *table, cork_equals_f equals)
|
|
{
|
|
table->equals = equals;
|
|
}
|
|
|
|
void
|
|
cork_hash_table_set_free_key(struct cork_hash_table *table, cork_free_f free)
|
|
{
|
|
table->free_key = free;
|
|
}
|
|
|
|
void
|
|
cork_hash_table_set_free_value(struct cork_hash_table *table, cork_free_f free)
|
|
{
|
|
table->free_value = free;
|
|
}
|
|
|
|
|
|
void
|
|
cork_hash_table_ensure_size(struct cork_hash_table *table, size_t desired_count)
|
|
{
|
|
if (desired_count > table->bin_count) {
|
|
struct cork_dllist *old_bins = table->bins;
|
|
size_t old_bin_count = table->bin_count;
|
|
|
|
cork_hash_table_allocate_bins(table, desired_count);
|
|
|
|
if (old_bins != NULL) {
|
|
size_t i;
|
|
for (i = 0; i < old_bin_count; i++) {
|
|
struct cork_dllist *bin = &old_bins[i];
|
|
struct cork_dllist_item *curr = cork_dllist_start(bin);
|
|
while (!cork_dllist_is_end(bin, curr)) {
|
|
struct cork_hash_table_entry_priv *entry =
|
|
cork_container_of
|
|
(curr, struct cork_hash_table_entry_priv, in_bucket);
|
|
struct cork_dllist_item *next = curr->next;
|
|
|
|
size_t bin_index = bin_index(table, entry->public.hash);
|
|
DEBUG(" Rehash %p from bin %zu to bin %zu",
|
|
entry, i, bin_index);
|
|
cork_dllist_add(&table->bins[bin_index], curr);
|
|
|
|
curr = next;
|
|
}
|
|
}
|
|
|
|
free(old_bins);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
cork_hash_table_rehash(struct cork_hash_table *table)
|
|
{
|
|
DEBUG(" Reached maximum density; rehash");
|
|
cork_hash_table_ensure_size(table, table->bin_count + 1);
|
|
}
|
|
|
|
|
|
struct cork_hash_table_entry *
|
|
cork_hash_table_get_entry_hash(const struct cork_hash_table *table,
|
|
cork_hash hash, const void *key)
|
|
{
|
|
size_t bin_index;
|
|
struct cork_dllist *bin;
|
|
struct cork_dllist_item *curr;
|
|
|
|
if (table->bin_count == 0) {
|
|
DEBUG("(get) Empty table when searching for key %p "
|
|
"(hash 0x%08" PRIx32 ")",
|
|
key, hash);
|
|
return NULL;
|
|
}
|
|
|
|
bin_index = bin_index(table, hash);
|
|
DEBUG("(get) Search for key %p (hash 0x%08" PRIx32 ", bin %zu)",
|
|
key, hash, bin_index);
|
|
|
|
bin = &table->bins[bin_index];
|
|
curr = cork_dllist_start(bin);
|
|
while (!cork_dllist_is_end(bin, curr)) {
|
|
struct cork_hash_table_entry_priv *entry =
|
|
cork_container_of
|
|
(curr, struct cork_hash_table_entry_priv, in_bucket);
|
|
|
|
DEBUG(" Check entry %p", entry);
|
|
if (table->equals(table->user_data, key, entry->public.key)) {
|
|
DEBUG(" Match");
|
|
return &entry->public;
|
|
}
|
|
|
|
curr = curr->next;
|
|
}
|
|
|
|
DEBUG(" Entry not found");
|
|
return NULL;
|
|
}
|
|
|
|
struct cork_hash_table_entry *
|
|
cork_hash_table_get_entry(const struct cork_hash_table *table, const void *key)
|
|
{
|
|
cork_hash hash = table->hash(table->user_data, key);
|
|
return cork_hash_table_get_entry_hash(table, hash, key);
|
|
}
|
|
|
|
void *
|
|
cork_hash_table_get_hash(const struct cork_hash_table *table,
|
|
cork_hash hash, const void *key)
|
|
{
|
|
struct cork_hash_table_entry *entry =
|
|
cork_hash_table_get_entry_hash(table, hash, key);
|
|
if (entry == NULL) {
|
|
return NULL;
|
|
} else {
|
|
DEBUG(" Extract value pointer %p", entry->value);
|
|
return entry->value;
|
|
}
|
|
}
|
|
|
|
void *
|
|
cork_hash_table_get(const struct cork_hash_table *table, const void *key)
|
|
{
|
|
struct cork_hash_table_entry *entry =
|
|
cork_hash_table_get_entry(table, key);
|
|
if (entry == NULL) {
|
|
return NULL;
|
|
} else {
|
|
DEBUG(" Extract value pointer %p", entry->value);
|
|
return entry->value;
|
|
}
|
|
}
|
|
|
|
|
|
struct cork_hash_table_entry *
|
|
cork_hash_table_get_or_create_hash(struct cork_hash_table *table,
|
|
cork_hash hash, void *key, bool *is_new)
|
|
{
|
|
struct cork_hash_table_entry_priv *entry;
|
|
size_t bin_index;
|
|
|
|
if (table->bin_count > 0) {
|
|
struct cork_dllist *bin;
|
|
struct cork_dllist_item *curr;
|
|
|
|
bin_index = bin_index(table, hash);
|
|
DEBUG("(get_or_create) Search for key %p "
|
|
"(hash 0x%08" PRIx32 ", bin %zu)",
|
|
key, hash, bin_index);
|
|
|
|
bin = &table->bins[bin_index];
|
|
curr = cork_dllist_start(bin);
|
|
while (!cork_dllist_is_end(bin, curr)) {
|
|
struct cork_hash_table_entry_priv *entry =
|
|
cork_container_of
|
|
(curr, struct cork_hash_table_entry_priv, in_bucket);
|
|
|
|
DEBUG(" Check entry %p", entry);
|
|
if (table->equals(table->user_data, key, entry->public.key)) {
|
|
DEBUG(" Match");
|
|
DEBUG(" Return value pointer %p", entry->public.value);
|
|
*is_new = false;
|
|
return &entry->public;
|
|
}
|
|
|
|
curr = curr->next;
|
|
}
|
|
|
|
/* create a new entry */
|
|
DEBUG(" Entry not found");
|
|
|
|
if ((table->entry_count / table->bin_count) >
|
|
CORK_HASH_TABLE_MAX_DENSITY) {
|
|
cork_hash_table_rehash(table);
|
|
bin_index = bin_index(table, hash);
|
|
}
|
|
} else {
|
|
DEBUG("(get_or_create) Search for key %p (hash 0x%08" PRIx32 ")",
|
|
key, hash);
|
|
DEBUG(" Empty table");
|
|
cork_hash_table_rehash(table);
|
|
bin_index = bin_index(table, hash);
|
|
}
|
|
|
|
DEBUG(" Allocate new entry");
|
|
entry = cork_hash_table_new_entry(table, hash, key, NULL);
|
|
DEBUG(" Created new entry %p", entry);
|
|
|
|
DEBUG(" Add entry into bin %zu", bin_index);
|
|
cork_dllist_add(&table->bins[bin_index], &entry->in_bucket);
|
|
|
|
table->entry_count++;
|
|
*is_new = true;
|
|
return &entry->public;
|
|
}
|
|
|
|
struct cork_hash_table_entry *
|
|
cork_hash_table_get_or_create(struct cork_hash_table *table,
|
|
void *key, bool *is_new)
|
|
{
|
|
cork_hash hash = table->hash(table->user_data, key);
|
|
return cork_hash_table_get_or_create_hash(table, hash, key, is_new);
|
|
}
|
|
|
|
|
|
void
|
|
cork_hash_table_put_hash(struct cork_hash_table *table,
|
|
cork_hash hash, void *key, void *value,
|
|
bool *is_new, void **old_key, void **old_value)
|
|
{
|
|
struct cork_hash_table_entry_priv *entry;
|
|
size_t bin_index;
|
|
|
|
if (table->bin_count > 0) {
|
|
struct cork_dllist *bin;
|
|
struct cork_dllist_item *curr;
|
|
|
|
bin_index = bin_index(table, hash);
|
|
DEBUG("(put) Search for key %p (hash 0x%08" PRIx32 ", bin %zu)",
|
|
key, hash, bin_index);
|
|
|
|
bin = &table->bins[bin_index];
|
|
curr = cork_dllist_start(bin);
|
|
while (!cork_dllist_is_end(bin, curr)) {
|
|
struct cork_hash_table_entry_priv *entry =
|
|
cork_container_of
|
|
(curr, struct cork_hash_table_entry_priv, in_bucket);
|
|
|
|
DEBUG(" Check entry %p", entry);
|
|
if (table->equals(table->user_data, key, entry->public.key)) {
|
|
DEBUG(" Found existing entry; overwriting");
|
|
DEBUG(" Return old key %p", entry->public.key);
|
|
if (old_key != NULL) {
|
|
*old_key = entry->public.key;
|
|
}
|
|
DEBUG(" Return old value %p", entry->public.value);
|
|
if (old_value != NULL) {
|
|
*old_value = entry->public.value;
|
|
}
|
|
DEBUG(" Copy key %p into entry", key);
|
|
entry->public.key = key;
|
|
DEBUG(" Copy value %p into entry", value);
|
|
entry->public.value = value;
|
|
if (is_new != NULL) {
|
|
*is_new = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
curr = curr->next;
|
|
}
|
|
|
|
/* create a new entry */
|
|
DEBUG(" Entry not found");
|
|
if ((table->entry_count / table->bin_count) >
|
|
CORK_HASH_TABLE_MAX_DENSITY) {
|
|
cork_hash_table_rehash(table);
|
|
bin_index = bin_index(table, hash);
|
|
}
|
|
} else {
|
|
DEBUG("(put) Search for key %p (hash 0x%08" PRIx32 ")",
|
|
key, hash);
|
|
DEBUG(" Empty table");
|
|
cork_hash_table_rehash(table);
|
|
bin_index = bin_index(table, hash);
|
|
}
|
|
|
|
DEBUG(" Allocate new entry");
|
|
entry = cork_hash_table_new_entry(table, hash, key, value);
|
|
DEBUG(" Created new entry %p", entry);
|
|
|
|
DEBUG(" Add entry into bin %zu", bin_index);
|
|
cork_dllist_add(&table->bins[bin_index], &entry->in_bucket);
|
|
|
|
table->entry_count++;
|
|
if (old_key != NULL) {
|
|
*old_key = NULL;
|
|
}
|
|
if (old_value != NULL) {
|
|
*old_value = NULL;
|
|
}
|
|
if (is_new != NULL) {
|
|
*is_new = true;
|
|
}
|
|
}
|
|
|
|
void
|
|
cork_hash_table_put(struct cork_hash_table *table,
|
|
void *key, void *value,
|
|
bool *is_new, void **old_key, void **old_value)
|
|
{
|
|
cork_hash hash = table->hash(table->user_data, key);
|
|
cork_hash_table_put_hash
|
|
(table, hash, key, value, is_new, old_key, old_value);
|
|
}
|
|
|
|
|
|
void
|
|
cork_hash_table_delete_entry(struct cork_hash_table *table,
|
|
struct cork_hash_table_entry *ventry)
|
|
{
|
|
struct cork_hash_table_entry_priv *entry =
|
|
cork_container_of(ventry, struct cork_hash_table_entry_priv, public);
|
|
cork_dllist_remove(&entry->in_bucket);
|
|
table->entry_count--;
|
|
cork_hash_table_free_entry(table, entry);
|
|
}
|
|
|
|
|
|
bool
|
|
cork_hash_table_delete_hash(struct cork_hash_table *table,
|
|
cork_hash hash, const void *key,
|
|
void **deleted_key, void **deleted_value)
|
|
{
|
|
size_t bin_index;
|
|
struct cork_dllist *bin;
|
|
struct cork_dllist_item *curr;
|
|
|
|
if (table->bin_count == 0) {
|
|
DEBUG("(delete) Empty table when searching for key %p "
|
|
"(hash 0x%08" PRIx32 ")",
|
|
key, hash);
|
|
return false;
|
|
}
|
|
|
|
bin_index = bin_index(table, hash);
|
|
DEBUG("(delete) Search for key %p (hash 0x%08" PRIx32 ", bin %zu)",
|
|
key, hash, bin_index);
|
|
|
|
bin = &table->bins[bin_index];
|
|
curr = cork_dllist_start(bin);
|
|
while (!cork_dllist_is_end(bin, curr)) {
|
|
struct cork_hash_table_entry_priv *entry =
|
|
cork_container_of
|
|
(curr, struct cork_hash_table_entry_priv, in_bucket);
|
|
|
|
DEBUG(" Check entry %p", entry);
|
|
if (table->equals(table->user_data, key, entry->public.key)) {
|
|
DEBUG(" Match");
|
|
if (deleted_key != NULL) {
|
|
*deleted_key = entry->public.key;
|
|
}
|
|
if (deleted_value != NULL) {
|
|
*deleted_value = entry->public.value;
|
|
}
|
|
|
|
DEBUG(" Remove entry from hash bin %zu", bin_index);
|
|
cork_dllist_remove(curr);
|
|
table->entry_count--;
|
|
|
|
DEBUG(" Free entry %p", entry);
|
|
cork_hash_table_free_entry(table, entry);
|
|
return true;
|
|
}
|
|
|
|
curr = curr->next;
|
|
}
|
|
|
|
DEBUG(" Entry not found");
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
cork_hash_table_delete(struct cork_hash_table *table, const void *key,
|
|
void **deleted_key, void **deleted_value)
|
|
{
|
|
cork_hash hash = table->hash(table->user_data, key);
|
|
return cork_hash_table_delete_hash
|
|
(table, hash, key, deleted_key, deleted_value);
|
|
}
|
|
|
|
|
|
void
|
|
cork_hash_table_map(struct cork_hash_table *table, void *user_data,
|
|
cork_hash_table_map_f map)
|
|
{
|
|
struct cork_dllist_item *curr;
|
|
DEBUG("Map across hash table");
|
|
|
|
curr = cork_dllist_start(&table->insertion_order);
|
|
while (!cork_dllist_is_end(&table->insertion_order, curr)) {
|
|
struct cork_hash_table_entry_priv *entry =
|
|
cork_container_of
|
|
(curr, struct cork_hash_table_entry_priv, insertion_order);
|
|
struct cork_dllist_item *next = curr->next;
|
|
enum cork_hash_table_map_result result;
|
|
|
|
DEBUG(" Apply function to entry %p", entry);
|
|
result = map(user_data, &entry->public);
|
|
|
|
if (result == CORK_HASH_TABLE_MAP_ABORT) {
|
|
return;
|
|
} else if (result == CORK_HASH_TABLE_MAP_DELETE) {
|
|
DEBUG(" Delete requested");
|
|
cork_dllist_remove(curr);
|
|
table->entry_count--;
|
|
cork_hash_table_free_entry(table, entry);
|
|
}
|
|
|
|
curr = next;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
cork_hash_table_iterator_init(struct cork_hash_table *table,
|
|
struct cork_hash_table_iterator *iterator)
|
|
{
|
|
DEBUG("Iterate through hash table");
|
|
iterator->table = table;
|
|
iterator->priv = cork_dllist_start(&table->insertion_order);
|
|
}
|
|
|
|
|
|
struct cork_hash_table_entry *
|
|
cork_hash_table_iterator_next(struct cork_hash_table_iterator *iterator)
|
|
{
|
|
struct cork_hash_table *table = iterator->table;
|
|
struct cork_dllist_item *curr = iterator->priv;
|
|
struct cork_hash_table_entry_priv *entry;
|
|
|
|
if (cork_dllist_is_end(&table->insertion_order, curr)) {
|
|
return NULL;
|
|
}
|
|
|
|
entry = cork_container_of
|
|
(curr, struct cork_hash_table_entry_priv, insertion_order);
|
|
DEBUG(" Return entry %p", entry);
|
|
iterator->priv = curr->next;
|
|
return &entry->public;
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------
|
|
* Built-in key types
|
|
*/
|
|
|
|
static cork_hash
|
|
string_hash(void *user_data, const void *vk)
|
|
{
|
|
const char *k = vk;
|
|
size_t len = strlen(k);
|
|
return cork_hash_buffer(0, k, len);
|
|
}
|
|
|
|
static bool
|
|
string_equals(void *user_data, const void *vk1, const void *vk2)
|
|
{
|
|
const char *k1 = vk1;
|
|
const char *k2 = vk2;
|
|
return strcmp(k1, k2) == 0;
|
|
}
|
|
|
|
struct cork_hash_table *
|
|
cork_string_hash_table_new(size_t initial_size, unsigned int flags)
|
|
{
|
|
struct cork_hash_table *table = cork_hash_table_new(initial_size, flags);
|
|
cork_hash_table_set_hash(table, string_hash);
|
|
cork_hash_table_set_equals(table, string_equals);
|
|
return table;
|
|
}
|
|
|
|
struct cork_hash_table *
|
|
cork_pointer_hash_table_new(size_t initial_size, unsigned int flags)
|
|
{
|
|
return cork_hash_table_new(initial_size, flags);
|
|
}
|