/* * Copyright (c) 2002 by The XFree86 Project, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Except as contained in this notice, the name of the XFree86 Project shall * not be used in advertising or otherwise to promote the sale, use or other * dealings in this Software without prior written authorization from the * XFree86 Project. * * Author: Paulo César Pereira de Andrade */ /* $XFree86: xc/programs/xedit/lisp/hash.c,v 1.5 2003/04/27 18:17:32 tsi Exp $ */ #include "lisp/hash.h" /* A simple hash-table implementation * TODO: implement SXHASH and WITH-HASH-TABLE-ITERATOR * May need a rewrite for better performance, and will * need a rewrite if images/bytecode saved on disk. */ #define GET_HASH 1 #define PUT_HASH 2 #define REM_HASH 3 /* * Prototypes */ static unsigned long LispHashKey(LispObj*, int); static LispObj *LispHash(LispBuiltin*, int); static void LispRehash(LispHashTable*); static void LispFreeHashEntries(LispHashEntry*, long); /* * Initialization */ extern LispObj *Oeq, *Oeql, *Oequal, *Oequalp; /* Hash tables will have one of these sizes, unless the user * specified a very large size */ static long some_primes[] = { 5, 11, 17, 23, 31, 47, 71, 97, 139, 199, 307, 401, 607, 809, 1213, 1619, 2437, 3251, 4889, 6521 }; /* * Implementation */ static unsigned long LispHashKey(LispObj *object, int function) { mpi *bigi; char *string; long i, length; unsigned long key = ((unsigned long)object) >> 4; /* Must be the same object for EQ */ if (function == FEQ) goto hash_key_done; if (function == FEQUALP) { switch (OBJECT_TYPE(object)) { case LispSChar_t: key = (unsigned long)toupper(SCHAR_VALUE(object)); goto hash_key_done; case LispString_t: string = THESTR(object); length = STRLEN(object); if (length > 32) length = 32; for (i = 0, key = 0; i < length; i++) key = (key << 1) ^ toupper(string[i]); goto hash_key_done; default: break; } } /* Function is EQL, EQUAL or EQUALP */ switch (OBJECT_TYPE(object)) { case LispFixnum_t: case LispSChar_t: key = (unsigned long)FIXNUM_VALUE(object); goto hash_key_done; case LispInteger_t: key = (unsigned long)INT_VALUE(object); goto hash_key_done; case LispRatio_t: key = (object->data.ratio.numerator << 16) ^ object->data.ratio.denominator; goto hash_key_done; case LispDFloat_t: key = (unsigned long)DFLOAT_VALUE(object); break; case LispComplex_t: key = (LispHashKey(object->data.complex.imag, function) << 16) ^ LispHashKey(object->data.complex.real, function); goto hash_key_done; case LispBignum_t: bigi = object->data.mp.integer; length = bigi->size; if (length > 8) length = 8; key = bigi->sign; for (i = 0; i < length; i++) key = (key << 8) ^ bigi->digs[i]; goto hash_key_done; case LispBigratio_t: bigi = mpr_num(object->data.mp.ratio); length = bigi->size; if (length > 4) length = 4; key = bigi->sign; for (i = 0; i < length; i++) key = (key << 4) ^ bigi->digs[i]; bigi = mpr_den(object->data.mp.ratio); length = bigi->size; if (length > 4) length = 4; for (i = 0; i < length; i++) key = (key << 4) ^ bigi->digs[i]; goto hash_key_done; default: break; } /* Anything else must be the same object for EQL */ if (function == FEQL) goto hash_key_done; switch (OBJECT_TYPE(object)) { case LispString_t: string = THESTR(object); length = STRLEN(object); for (i = 0, key = 0; i < length; i++) key = (key << 1) ^ string[i]; break; case LispCons_t: key = (LispHashKey(CAR(object), function) << 16) ^ LispHashKey(CDR(object), function); break; case LispQuote_t: case LispBackquote_t: case LispPathname_t: key = LispHashKey(object->data.pathname, function); break; case LispRegex_t: key = LispHashKey(object->data.regex.pattern, function); break; default: break; } hash_key_done: return (key); } static LispObj * LispHash(LispBuiltin *builtin, int code) { LispHashEntry *entry; LispHashTable *hash; unsigned long key; LispObj *result; int found; long i; LispObj *okey, *hash_table, *value; if (code == REM_HASH) value = NIL; else { value = ARGUMENT(2); if (value == UNSPEC) value = NIL; } hash_table = ARGUMENT(1); okey = ARGUMENT(0); CHECK_HASHTABLE(hash_table); /* get hash entry */ hash = hash_table->data.hash.table; key = LispHashKey(okey, hash->function) % hash->num_entries; entry = hash->entries + key; /* search entry in the hash table */ if (entry->count == 0) i = 0; else { if (hash->function == FEQ) { for (i = entry->cache; i >= 0; i--) { if (entry->keys[i] == okey) goto found_key; } for (i = entry->cache + 1; i < entry->count; i++) { if (entry->keys[i] == okey) break; } } else { for (i = entry->cache; i >= 0; i--) { if (LispObjectCompare(entry->keys[i], okey, hash->function) == T) goto found_key; } for (i = entry->cache + 1; i < entry->count; i++) { if (LispObjectCompare(entry->keys[i], okey, hash->function) == T) break; } } } found_key: result = value; if ((found = i < entry->count) == 0) i = entry->count; switch (code) { case GET_HASH: RETURN_COUNT = 1; if (found) { RETURN(0) = T; entry->cache = i; result = entry->values[i]; } else RETURN(0) = NIL; break; case PUT_HASH: entry->cache = i; if (found) /* Just replace current entry */ entry->values[i] = value; else { if ((i % 4) == 0) { LispObj **keys, **values; keys = realloc(entry->keys, sizeof(LispObj*) * (i + 4)); if (keys == NULL) LispDestroy("out of memory"); values = realloc(entry->values, sizeof(LispObj*) * (i + 4)); if (values == NULL) { free(keys); LispDestroy("out of memory"); } entry->keys = keys; entry->values = values; } entry->keys[i] = okey; entry->values[i] = value; ++entry->count; ++hash->count; if (hash->count > hash->rehash_threshold * hash->num_entries) LispRehash(hash); } break; case REM_HASH: if (found) { result = T; --entry->count; --hash->count; if (i < entry->count) { memmove(entry->keys + i, entry->keys + i + 1, (entry->count - i) * sizeof(LispObj*)); memmove(entry->values + i, entry->values + i + 1, (entry->count - i) * sizeof(LispObj*)); } if (entry->cache && entry->cache == entry->count) --entry->cache; } break; } return (result); } static void LispRehash(LispHashTable *hash) { unsigned long key; LispHashEntry *entries, *nentry, *entry, *last; long i, size = hash->num_entries * hash->rehash_size; for (i = 0; i < sizeof(some_primes) / sizeof(some_primes[0]); i++) if (some_primes[i] >= size) { size = some_primes[i]; break; } entries = calloc(1, sizeof(LispHashEntry) * size); if (entries == NULL) goto out_of_memory; for (entry = hash->entries, last = entry + hash->num_entries; entry < last; entry++) { for (i = 0; i < entry->count; i++) { key = LispHashKey(entry->keys[i], hash->function) % size; nentry = entries + key; if ((nentry->count % 4) == 0) { LispObj **keys, **values; keys = realloc(nentry->keys, sizeof(LispObj*) * (nentry->count + 4)); if (keys == NULL) goto out_of_memory; values = realloc(nentry->values, sizeof(LispObj*) * (nentry->count + 4)); if (values == NULL) { free(keys); goto out_of_memory; } nentry->keys = keys; nentry->values = values; } nentry->keys[nentry->count] = entry->keys[i]; nentry->values[nentry->count] = entry->values[i]; ++nentry->count; } } LispFreeHashEntries(hash->entries, hash->num_entries); hash->entries = entries; hash->num_entries = size; return; out_of_memory: if (entries) LispFreeHashEntries(entries, size); LispDestroy("out of memory"); } static void LispFreeHashEntries(LispHashEntry *entries, long num_entries) { LispHashEntry *entry, *last; for (entry = entries, last = entry + num_entries; entry < last; entry++) { free(entry->keys); free(entry->values); } free(entries); } void LispFreeHashTable(LispHashTable *hash) { LispFreeHashEntries(hash->entries, hash->num_entries); free(hash); } LispObj * Lisp_Clrhash(LispBuiltin *builtin) /* clrhash hash-table */ { LispHashTable *hash; LispHashEntry *entry, *last; LispObj *hash_table = ARGUMENT(0); CHECK_HASHTABLE(hash_table); hash = hash_table->data.hash.table; for (entry = hash->entries, last = entry + hash->num_entries; entry < last; entry++) { free(entry->keys); free(entry->values); entry->keys = entry->values = NULL; entry->count = entry->cache = 0; } hash->count = 0; return (hash_table); } LispObj * Lisp_Gethash(LispBuiltin *builtin) /* gethash key hash-table &optional default */ { return (LispHash(builtin, GET_HASH)); } LispObj * Lisp_HashTableP(LispBuiltin *builtin) /* hash-table-p object */ { LispObj *object = ARGUMENT(0); return (HASHTABLEP(object) ? T : NIL); } LispObj * Lisp_HashTableCount(LispBuiltin *builtin) /* hash-table-count hash-table */ { LispObj *hash_table = ARGUMENT(0); CHECK_HASHTABLE(hash_table); return (FIXNUM(hash_table->data.hash.table->count)); } LispObj * Lisp_HashTableRehashSize(LispBuiltin *builtin) /* hash-table-rehash-size hash-table */ { LispObj *hash_table = ARGUMENT(0); CHECK_HASHTABLE(hash_table); return (DFLOAT(hash_table->data.hash.table->rehash_size)); } LispObj * Lisp_HashTableRehashThreshold(LispBuiltin *builtin) /* hash-table-rehash-threshold hash-table */ { LispObj *hash_table = ARGUMENT(0); CHECK_HASHTABLE(hash_table); return (DFLOAT(hash_table->data.hash.table->rehash_threshold)); } LispObj * Lisp_HashTableSize(LispBuiltin *builtin) /* hash-table-size hash-table */ { LispObj *hash_table = ARGUMENT(0); CHECK_HASHTABLE(hash_table); return (FIXNUM(hash_table->data.hash.table->num_entries)); } LispObj * Lisp_HashTableTest(LispBuiltin *builtin) /* hash-table-test hash-table */ { LispObj *hash_table = ARGUMENT(0); CHECK_HASHTABLE(hash_table); return (hash_table->data.hash.test); } LispObj * Lisp_Maphash(LispBuiltin *builtin) /* maphash function hash-table */ { long i; LispHashEntry *entry, *last; LispObj *function, *hash_table; hash_table = ARGUMENT(1); function = ARGUMENT(0); CHECK_HASHTABLE(hash_table); for (entry = hash_table->data.hash.table->entries, last = entry + hash_table->data.hash.table->num_entries; entry < last; entry++) { for (i = 0; i < entry->count; i++) APPLY2(function, entry->keys[i], entry->values[i]); } return (NIL); } LispObj * Lisp_MakeHashTable(LispBuiltin *builtin) /* make-hash-table &key test size rehash-size rehash-threshold initial-contents */ { int function = FEQL; unsigned long i, isize, xsize; double drsize, drthreshold; LispHashTable *hash_table; LispObj *cons, *result; LispObj *test, *size, *rehash_size, *rehash_threshold, *initial_contents; initial_contents = ARGUMENT(4); rehash_threshold = ARGUMENT(3); rehash_size = ARGUMENT(2); size = ARGUMENT(1); test = ARGUMENT(0); if (test != UNSPEC) { if (FUNCTIONP(test)) test = test->data.atom->object; if (test == Oeq) function = FEQ; else if (test == Oeql) function = FEQL; else if (test == Oequal) function = FEQUAL; else if (test == Oequalp) function = FEQUALP; else LispDestroy("%s: :TEST must be EQ, EQL, EQUAL, " "or EQUALP, not %s", STRFUN(builtin), STROBJ(test)); } else test = Oeql; if (size != UNSPEC) { CHECK_INDEX(size); isize = FIXNUM_VALUE(size); } else isize = 1; if (rehash_size != UNSPEC) { CHECK_DFLOAT(rehash_size); if (DFLOAT_VALUE(rehash_size) <= 1.0) LispDestroy("%s: :REHASH-SIZE must a float > 1, not %s", STRFUN(builtin), STROBJ(rehash_size)); drsize = DFLOAT_VALUE(rehash_size); } else drsize = 1.5; if (rehash_threshold != UNSPEC) { CHECK_DFLOAT(rehash_threshold); if (DFLOAT_VALUE(rehash_threshold) < 0.0 || DFLOAT_VALUE(rehash_threshold) > 1.0) LispDestroy("%s: :REHASH-THRESHOLD must a float " "in the range 0.0 - 1.0, not %s", STRFUN(builtin), STROBJ(rehash_threshold)); drthreshold = DFLOAT_VALUE(rehash_threshold); } else drthreshold = 0.75; if (initial_contents == UNSPEC) initial_contents = NIL; CHECK_LIST(initial_contents); for (xsize = 0, cons = initial_contents; CONSP(cons); xsize++, cons = CDR(cons)) CHECK_CONS(CAR(cons)); if (xsize > isize) isize = xsize; for (i = 0; i < sizeof(some_primes) / sizeof(some_primes[0]); i++) if (some_primes[i] >= isize) { isize = some_primes[i]; break; } hash_table = LispMalloc(sizeof(LispHashTable)); hash_table->entries = LispCalloc(1, sizeof(LispHashEntry) * isize); hash_table->num_entries = isize; hash_table->count = 0; hash_table->function = function; hash_table->rehash_size = drsize; hash_table->rehash_threshold = drthreshold; result = LispNew(NIL, NIL); result->type = LispHashTable_t; result->data.hash.table = hash_table; result->data.hash.test = test; LispMused(hash_table); LispMused(hash_table->entries); if (initial_contents != UNSPEC) { unsigned long key; LispHashEntry *entry; for (cons = initial_contents; CONSP(cons); cons = CDR(cons)) { key = LispHashKey(CAAR(cons), function) % isize; entry = hash_table->entries + key; if ((entry->count % 4) == 0) { LispObj **keys, **values; keys = realloc(entry->keys, sizeof(LispObj*) * (i + 4)); if (keys == NULL) LispDestroy("out of memory"); values = realloc(entry->values, sizeof(LispObj*) * (i + 4)); if (values == NULL) { free(keys); LispDestroy("out of memory"); } entry->keys = keys; entry->values = values; } entry->keys[entry->count] = CAAR(cons); entry->values[entry->count] = CDAR(cons); ++entry->count; } hash_table->count = xsize; } return (result); } LispObj * Lisp_Remhash(LispBuiltin *builtin) /* remhash key hash-table */ { return (LispHash(builtin, REM_HASH)); } LispObj * Lisp_XeditPuthash(LispBuiltin *builtin) /* lisp::puthash key hash-table value */ { return (LispHash(builtin, PUT_HASH)); }