diff options
Diffstat (limited to '')
-rw-r--r-- | hashlib.c | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/hashlib.c b/hashlib.c new file mode 100644 index 0000000..4a7e813 --- /dev/null +++ b/hashlib.c @@ -0,0 +1,545 @@ +/* hashlib.c -- functions to manage and access hash tables for bash. */ + +/* Copyright (C) 1987,1989,1991,1995,1998,2001,2003,2005,2006,2008,2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <config.h> + +#include "bashansi.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include <sys/types.h> +# endif +# include <unistd.h> +#endif + +#include <stdio.h> + +#include "shell.h" +#include "hashlib.h" + +/* tunable constants for rehashing */ +#define HASH_REHASH_MULTIPLIER 4 +#define HASH_REHASH_FACTOR 2 + +#define HASH_SHOULDGROW(table) \ + ((table)->nentries >= (table)->nbuckets * HASH_REHASH_FACTOR) + +/* an initial approximation */ +#define HASH_SHOULDSHRINK(table) \ + (((table)->nbuckets > DEFAULT_HASH_BUCKETS) && \ + ((table)->nentries < (table)->nbuckets / HASH_REHASH_MULTIPLIER)) + +/* Rely on properties of unsigned division (unsigned/int -> unsigned) and + don't discard the upper 32 bits of the value, if present. */ +#define HASH_BUCKET(s, t, h) (((h) = hash_string (s)) & ((t)->nbuckets - 1)) + +static BUCKET_CONTENTS *copy_bucket_array PARAMS((BUCKET_CONTENTS *, sh_string_func_t *)); + +static void hash_rehash PARAMS((HASH_TABLE *, int)); +static void hash_grow PARAMS((HASH_TABLE *)); +static void hash_shrink PARAMS((HASH_TABLE *)); + +/* Make a new hash table with BUCKETS number of buckets. Initialize + each slot in the table to NULL. */ +HASH_TABLE * +hash_create (buckets) + int buckets; +{ + HASH_TABLE *new_table; + register int i; + + new_table = (HASH_TABLE *)xmalloc (sizeof (HASH_TABLE)); + if (buckets == 0) + buckets = DEFAULT_HASH_BUCKETS; + + new_table->bucket_array = + (BUCKET_CONTENTS **)xmalloc (buckets * sizeof (BUCKET_CONTENTS *)); + new_table->nbuckets = buckets; + new_table->nentries = 0; + + for (i = 0; i < buckets; i++) + new_table->bucket_array[i] = (BUCKET_CONTENTS *)NULL; + + return (new_table); +} + +int +hash_size (table) + HASH_TABLE *table; +{ + return (HASH_ENTRIES(table)); +} + +static BUCKET_CONTENTS * +copy_bucket_array (ba, cpdata) + BUCKET_CONTENTS *ba; + sh_string_func_t *cpdata; /* data copy function */ +{ + BUCKET_CONTENTS *new_bucket, *n, *e; + + if (ba == 0) + return ((BUCKET_CONTENTS *)0); + + for (n = (BUCKET_CONTENTS *)0, e = ba; e; e = e->next) + { + if (n == 0) + { + new_bucket = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); + n = new_bucket; + } + else + { + n->next = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); + n = n->next; + } + + n->key = savestring (e->key); + n->data = e->data ? (cpdata ? (*cpdata) (e->data) : savestring (e->data)) + : NULL; + n->khash = e->khash; + n->times_found = e->times_found; + n->next = (BUCKET_CONTENTS *)NULL; + } + + return new_bucket; +} + +static void +hash_rehash (table, nsize) + HASH_TABLE *table; + int nsize; +{ + int osize, i, j; + BUCKET_CONTENTS **old_bucket_array, *item, *next; + + if (table == NULL || nsize == table->nbuckets) + return; + + osize = table->nbuckets; + old_bucket_array = table->bucket_array; + + table->nbuckets = nsize; + table->bucket_array = (BUCKET_CONTENTS **)xmalloc (table->nbuckets * sizeof (BUCKET_CONTENTS *)); + for (i = 0; i < table->nbuckets; i++) + table->bucket_array[i] = (BUCKET_CONTENTS *)NULL; + + for (j = 0; j < osize; j++) + { + for (item = old_bucket_array[j]; item; item = next) + { + next = item->next; + i = item->khash & (table->nbuckets - 1); + item->next = table->bucket_array[i]; + table->bucket_array[i] = item; + } + } + + free (old_bucket_array); +} + +static void +hash_grow (table) + HASH_TABLE *table; +{ + int nsize; + + nsize = table->nbuckets * HASH_REHASH_MULTIPLIER; + if (nsize > 0) /* overflow */ + hash_rehash (table, nsize); +} + +static void +hash_shrink (table) + HASH_TABLE *table; +{ + int nsize; + + nsize = table->nbuckets / HASH_REHASH_MULTIPLIER; + hash_rehash (table, nsize); +} + +HASH_TABLE * +hash_copy (table, cpdata) + HASH_TABLE *table; + sh_string_func_t *cpdata; +{ + HASH_TABLE *new_table; + int i; + + if (table == 0) + return ((HASH_TABLE *)NULL); + + new_table = hash_create (table->nbuckets); + + for (i = 0; i < table->nbuckets; i++) + new_table->bucket_array[i] = copy_bucket_array (table->bucket_array[i], cpdata); + + new_table->nentries = table->nentries; + return new_table; +} + +/* This is the best 32-bit string hash function I found. It's one of the + Fowler-Noll-Vo family (FNV-1). + + The magic is in the interesting relationship between the special prime + 16777619 (2^24 + 403) and 2^32 and 2^8. */ + +#define FNV_OFFSET 2166136261 +#define FNV_PRIME 16777619 + +/* If you want to use 64 bits, use +FNV_OFFSET 14695981039346656037 +FNV_PRIME 1099511628211 +*/ + +/* The `khash' check below requires that strings that compare equally with + strcmp hash to the same value. */ +unsigned int +hash_string (s) + const char *s; +{ + register unsigned int i; + + for (i = FNV_OFFSET; *s; s++) + { + /* FNV-1a has the XOR first, traditional FNV-1 has the multiply first */ + + /* was i *= FNV_PRIME */ + i += (i<<1) + (i<<4) + (i<<7) + (i<<8) + (i<<24); + i ^= *s; + } + + return i; +} + +/* Return the location of the bucket which should contain the data + for STRING. TABLE is a pointer to a HASH_TABLE. */ + +int +hash_bucket (string, table) + const char *string; + HASH_TABLE *table; +{ + unsigned int h; + + return (HASH_BUCKET (string, table, h)); +} + +/* Return a pointer to the hashed item. If the HASH_CREATE flag is passed, + create a new hash table entry for STRING, otherwise return NULL. */ +BUCKET_CONTENTS * +hash_search (string, table, flags) + const char *string; + HASH_TABLE *table; + int flags; +{ + BUCKET_CONTENTS *list; + int bucket; + unsigned int hv; + + if (table == 0 || ((flags & HASH_CREATE) == 0 && HASH_ENTRIES (table) == 0)) + return (BUCKET_CONTENTS *)NULL; + + bucket = HASH_BUCKET (string, table, hv); + + for (list = table->bucket_array ? table->bucket_array[bucket] : 0; list; list = list->next) + { + /* This is the comparison function */ + if (hv == list->khash && STREQ (list->key, string)) + { + list->times_found++; + return (list); + } + } + + if (flags & HASH_CREATE) + { + if (HASH_SHOULDGROW (table)) + { + hash_grow (table); + bucket = HASH_BUCKET (string, table, hv); + } + + list = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); + list->next = table->bucket_array[bucket]; + table->bucket_array[bucket] = list; + + list->data = NULL; + list->key = (char *)string; /* XXX fix later */ + list->khash = hv; + list->times_found = 0; + + table->nentries++; + return (list); + } + + return (BUCKET_CONTENTS *)NULL; +} + +/* Remove the item specified by STRING from the hash table TABLE. + The item removed is returned, so you can free its contents. If + the item isn't in this table NULL is returned. */ +BUCKET_CONTENTS * +hash_remove (string, table, flags) + const char *string; + HASH_TABLE *table; + int flags; +{ + int bucket; + BUCKET_CONTENTS *prev, *temp; + unsigned int hv; + + if (table == 0 || HASH_ENTRIES (table) == 0) + return (BUCKET_CONTENTS *)NULL; + + bucket = HASH_BUCKET (string, table, hv); + prev = (BUCKET_CONTENTS *)NULL; + for (temp = table->bucket_array[bucket]; temp; temp = temp->next) + { + if (hv == temp->khash && STREQ (temp->key, string)) + { + if (prev) + prev->next = temp->next; + else + table->bucket_array[bucket] = temp->next; + + table->nentries--; + return (temp); + } + prev = temp; + } + return ((BUCKET_CONTENTS *) NULL); +} + +/* Create an entry for STRING, in TABLE. If the entry already + exists, then return it (unless the HASH_NOSRCH flag is set). */ +BUCKET_CONTENTS * +hash_insert (string, table, flags) + char *string; + HASH_TABLE *table; + int flags; +{ + BUCKET_CONTENTS *item; + int bucket; + unsigned int hv; + + if (table == 0) + table = hash_create (0); + + item = (flags & HASH_NOSRCH) ? (BUCKET_CONTENTS *)NULL + : hash_search (string, table, 0); + + if (item == 0) + { + if (HASH_SHOULDGROW (table)) + hash_grow (table); + + bucket = HASH_BUCKET (string, table, hv); + + item = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); + item->next = table->bucket_array[bucket]; + table->bucket_array[bucket] = item; + + item->data = NULL; + item->key = string; + item->khash = hv; + item->times_found = 0; + + table->nentries++; + } + + return (item); +} + +/* Remove and discard all entries in TABLE. If FREE_DATA is non-null, it + is a function to call to dispose of a hash item's data. Otherwise, + free() is called. */ +void +hash_flush (table, free_data) + HASH_TABLE *table; + sh_free_func_t *free_data; +{ + int i; + register BUCKET_CONTENTS *bucket, *item; + + if (table == 0 || HASH_ENTRIES (table) == 0) + return; + + for (i = 0; i < table->nbuckets; i++) + { + bucket = table->bucket_array[i]; + + while (bucket) + { + item = bucket; + bucket = bucket->next; + + if (free_data) + (*free_data) (item->data); + else + free (item->data); + free (item->key); + free (item); + } + table->bucket_array[i] = (BUCKET_CONTENTS *)NULL; + } + + table->nentries = 0; +} + +/* Free the hash table pointed to by TABLE. */ +void +hash_dispose (table) + HASH_TABLE *table; +{ + free (table->bucket_array); + free (table); +} + +void +hash_walk (table, func) + HASH_TABLE *table; + hash_wfunc *func; +{ + register int i; + BUCKET_CONTENTS *item; + + if (table == 0 || HASH_ENTRIES (table) == 0) + return; + + for (i = 0; i < table->nbuckets; i++) + { + for (item = hash_items (i, table); item; item = item->next) + if ((*func) (item) < 0) + return; + } +} + +#if defined (DEBUG) || defined (TEST_HASHING) +void +hash_pstats (table, name) + HASH_TABLE *table; + char *name; +{ + register int slot, bcount; + register BUCKET_CONTENTS *bc; + + if (name == 0) + name = "unknown hash table"; + + fprintf (stderr, "%s: %d buckets; %d items\n", name, table->nbuckets, table->nentries); + + /* Print out a count of how many strings hashed to each bucket, so we can + see how even the distribution is. */ + for (slot = 0; slot < table->nbuckets; slot++) + { + bc = hash_items (slot, table); + + fprintf (stderr, "\tslot %3d: ", slot); + for (bcount = 0; bc; bc = bc->next) + bcount++; + + fprintf (stderr, "%d\n", bcount); + } +} +#endif + +#ifdef TEST_HASHING + +/* link with xmalloc.o and lib/malloc/libmalloc.a */ +#undef NULL +#include <stdio.h> + +#ifndef NULL +#define NULL 0 +#endif + +HASH_TABLE *table, *ntable; + +int interrupt_immediately = 0; +int running_trap = 0; + +int +signal_is_trapped (s) + int s; +{ + return (0); +} + +void +programming_error (const char *format, ...) +{ + abort(); +} + +void +fatal_error (const char *format, ...) +{ + abort(); +} + +void +internal_warning (const char *format, ...) +{ +} + +int +main () +{ + char string[256]; + int count = 0; + BUCKET_CONTENTS *tt; + +#if defined (TEST_NBUCKETS) + table = hash_create (TEST_NBUCKETS); +#else + table = hash_create (0); +#endif + + for (;;) + { + char *temp_string; + if (fgets (string, sizeof (string), stdin) == 0) + break; + if (!*string) + break; + temp_string = savestring (string); + tt = hash_insert (temp_string, table, 0); + if (tt->times_found) + { + fprintf (stderr, "You have already added item `%s'\n", string); + free (temp_string); + } + else + { + count++; + } + } + + hash_pstats (table, "hash test"); + + ntable = hash_copy (table, (sh_string_func_t *)NULL); + hash_flush (table, (sh_free_func_t *)NULL); + hash_pstats (ntable, "hash copy test"); + + exit (0); +} + +#endif /* TEST_HASHING */ |