summaryrefslogtreecommitdiffstats
path: root/libtecla-1.6.3/hash.c
diff options
context:
space:
mode:
Diffstat (limited to 'libtecla-1.6.3/hash.c')
-rw-r--r--libtecla-1.6.3/hash.c737
1 files changed, 737 insertions, 0 deletions
diff --git a/libtecla-1.6.3/hash.c b/libtecla-1.6.3/hash.c
new file mode 100644
index 0000000..e61c564
--- /dev/null
+++ b/libtecla-1.6.3/hash.c
@@ -0,0 +1,737 @@
+/*
+ * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2012 by Martin C. Shepherd.
+ *
+ * All rights reserved.
+ *
+ * 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, and/or sell copies of the Software, and to permit persons
+ * to whom the Software is furnished to do so, provided that the above
+ * copyright notice(s) and this permission notice appear in all copies of
+ * the Software and that both the above copyright notice(s) and this
+ * permission notice appear in supporting documentation.
+ *
+ * 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
+ * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
+ * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
+ * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+ * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Except as contained in this notice, the name of a copyright holder
+ * shall not be used in advertising or otherwise to promote the sale, use
+ * or other dealings in this Software without prior written authorization
+ * of the copyright holder.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "hash.h"
+#include "strngmem.h"
+#include "freelist.h"
+
+/*
+ * The following container object contains free-lists to be used
+ * for allocation of HashTable containers and nodes.
+ */
+struct HashMemory {
+ FreeList *hash_memory; /* HashTable free-list */
+ FreeList *node_memory; /* HashNode free-list */
+ StringMem *string_memory; /* Memory used to allocate hash strings */
+};
+
+/*
+ * Define a hash symbol-table entry.
+ * See symbol.h for the definition of the Symbol container type.
+ */
+typedef struct HashNode HashNode;
+struct HashNode {
+ Symbol symbol; /* The symbol stored in the hash-entry */
+ HashNode *next; /* The next hash-table entry in a bucket list */
+};
+
+/*
+ * Each hash-table bucket contains a linked list of entries that
+ * hash to the same bucket.
+ */
+typedef struct {
+ HashNode *head; /* The head of the bucket hash-node list */
+ int count; /* The number of entries in the list */
+} HashBucket;
+
+/*
+ * A hash-table consists of 'size' hash buckets.
+ * Note that the HashTable typedef for this struct is contained in hash.h.
+ */
+struct HashTable {
+ HashMemory *mem; /* HashTable free-list */
+ int internal_mem; /* True if 'mem' was allocated by _new_HashTable() */
+ int case_sensitive; /* True if case is significant in lookup keys */
+ int size; /* The number of hash buckets */
+ HashBucket *bucket; /* An array of 'size' hash buckets */
+ int (*keycmp)(const char *, const char *); /* Key comparison function */
+ void *app_data; /* Application-provided data */
+ HASH_DEL_FN(*del_fn); /* Application-provided 'app_data' destructor */
+};
+
+static HashNode *_del_HashNode(HashTable *hash, HashNode *node);
+static HashNode *_new_HashNode(HashTable *hash, const char *name, int code,
+ void (*fn)(void), void *data, SYM_DEL_FN(*del_fn));
+static HashNode *_find_HashNode(HashTable *hash, HashBucket *bucket,
+ const char *name, HashNode **prev);
+static HashBucket *_find_HashBucket(HashTable *hash, const char *name);
+static int _ht_lower_strcmp(const char *node_key, const char *look_key);
+static int _ht_strcmp(const char *node_key, const char *look_key);
+
+/*.......................................................................
+ * Allocate a free-list for use in allocating hash tables and their nodes.
+ *
+ * Input:
+ * list_count int The number of HashTable containers per free-list
+ * block.
+ * node_count int The number of HashTable nodes per free-list block.
+ * Output:
+ * return HashMemory * The new free-list for use in allocating hash tables
+ * and their nodes.
+ */
+HashMemory *_new_HashMemory(int hash_count, int node_count)
+{
+ HashMemory *mem;
+/*
+ * Allocate the free-list container.
+ */
+ mem = (HashMemory *) malloc(sizeof(HashMemory));
+ if(!mem) {
+ errno = ENOMEM;
+ return NULL;
+ };
+/*
+ * Initialize the container at least up to the point at which it can
+ * safely be passed to _del_HashMemory().
+ */
+ mem->hash_memory = NULL;
+ mem->node_memory = NULL;
+ mem->string_memory = NULL;
+/*
+ * Allocate the two free-lists.
+ */
+ mem->hash_memory = _new_FreeList(sizeof(HashTable), hash_count);
+ if(!mem->hash_memory)
+ return _del_HashMemory(mem, 1);
+ mem->node_memory = _new_FreeList(sizeof(HashNode), node_count);
+ if(!mem->node_memory)
+ return _del_HashMemory(mem, 1);
+ mem->string_memory = _new_StringMem(64);
+ if(!mem->string_memory)
+ return _del_HashMemory(mem, 1);
+/*
+ * Return the free-list container.
+ */
+ return mem;
+}
+
+/*.......................................................................
+ * Delete a HashTable free-list. An error will be displayed if the list is
+ * still in use and the deletion will be aborted.
+ *
+ * Input:
+ * mem HashMemory * The free-list container to be deleted.
+ * force int If force==0 then _del_HashMemory() will complain
+ * and refuse to delete the free-list if any
+ * of nodes have not been returned to the free-list.
+ * If force!=0 then _del_HashMemory() will not check
+ * whether any nodes are still in use and will
+ * always delete the list.
+ * Output:
+ * return HashMemory * Always NULL (even if the memory could not be
+ * deleted).
+ */
+HashMemory *_del_HashMemory(HashMemory *mem, int force)
+{
+ if(mem) {
+ if(!force && (_busy_FreeListNodes(mem->hash_memory) > 0 ||
+ _busy_FreeListNodes(mem->node_memory) > 0)) {
+ errno = EBUSY;
+ return NULL;
+ };
+ mem->hash_memory = _del_FreeList(mem->hash_memory, force);
+ mem->node_memory = _del_FreeList(mem->node_memory, force);
+ mem->string_memory = _del_StringMem(mem->string_memory, force);
+ free(mem);
+ };
+ return NULL;
+}
+
+/*.......................................................................
+ * Create a new hash table.
+ *
+ * Input:
+ * mem HashMemory * An optional free-list for use in allocating
+ * HashTable containers and nodes. See explanation
+ * in hash.h. If you are going to allocate more
+ * than one hash table, then it will be more
+ * efficient to allocate a single free-list for
+ * all of them than to force each hash table
+ * to allocate its own private free-list.
+ * size int The size of the hash table. Best performance
+ * will be acheived if this is a prime number.
+ * hcase HashCase Specify how symbol case is considered when
+ * looking up symbols, from:
+ * IGNORE_CASE - Upper and lower case versions
+ * of a letter are treated as
+ * being identical.
+ * HONOUR_CASE - Upper and lower case versions
+ * of a letter are treated as
+ * being distinct.
+ * characters in a lookup name is significant.
+ * app_data void * Optional application data to be registered
+ * to the table. This is presented to user
+ * provided SYM_DEL_FN() symbol destructors along
+ * with the symbol data.
+ * del_fn() HASH_DEL_FN(*) If you want app_data to be free'd when the
+ * hash-table is destroyed, register a suitable
+ * destructor function here.
+ * Output:
+ * return HashTable * The new hash table, or NULL on error.
+ */
+HashTable *_new_HashTable(HashMemory *mem, int size, HashCase hcase,
+ void *app_data, HASH_DEL_FN(*del_fn))
+{
+ HashTable *hash; /* The table to be returned */
+ int allocate_mem = !mem; /* True if mem should be internally allocated */
+ int i;
+/*
+ * Check arguments.
+ */
+ if(size <= 0) {
+ errno = EINVAL;
+ return NULL;
+ };
+/*
+ * Allocate an internal free-list?
+ */
+ if(allocate_mem) {
+ mem = _new_HashMemory(1, 100);
+ if(!mem)
+ return NULL;
+ };
+/*
+ * Allocate the container.
+ */
+ hash = (HashTable *) _new_FreeListNode(mem->hash_memory);
+ if(!hash) {
+ errno = ENOMEM;
+ if(allocate_mem)
+ mem = _del_HashMemory(mem, 1);
+ return NULL;
+ };
+/*
+ * Before attempting any operation that might fail, initialize
+ * the container at least up to the point at which it can safely
+ * be passed to _del_HashTable().
+ */
+ hash->mem = mem;
+ hash->internal_mem = allocate_mem;
+ hash->case_sensitive = hcase==HONOUR_CASE;
+ hash->size = size;
+ hash->bucket = NULL;
+ hash->keycmp = hash->case_sensitive ? _ht_strcmp : _ht_lower_strcmp;
+ hash->app_data = app_data;
+ hash->del_fn = del_fn;
+/*
+ * Allocate the array of 'size' hash buckets.
+ */
+ hash->bucket = (HashBucket *) malloc(sizeof(HashBucket) * size);
+ if(!hash->bucket) {
+ errno = ENOMEM;
+ return _del_HashTable(hash);
+ };
+/*
+ * Initialize the bucket array.
+ */
+ for(i=0; i<size; i++) {
+ HashBucket *b = hash->bucket + i;
+ b->head = NULL;
+ b->count = 0;
+ };
+/*
+ * The table is ready for use - albeit currently empty.
+ */
+ return hash;
+}
+
+/*.......................................................................
+ * Delete a hash-table.
+ *
+ * Input:
+ * hash HashTable * The hash table to be deleted.
+ * Output:
+ * return HashTable * The deleted hash table (always NULL).
+ */
+HashTable *_del_HashTable(HashTable *hash)
+{
+ if(hash) {
+/*
+ * Clear and delete the bucket array.
+ */
+ if(hash->bucket) {
+ _clear_HashTable(hash);
+ free(hash->bucket);
+ hash->bucket = NULL;
+ };
+/*
+ * Delete application data.
+ */
+ if(hash->del_fn)
+ hash->del_fn(hash->app_data);
+/*
+ * If the hash table was allocated from an internal free-list, delete
+ * it and the hash table by deleting the free-list. Otherwise just
+ * return the hash-table to the external free-list.
+ */
+ if(hash->internal_mem)
+ _del_HashMemory(hash->mem, 1);
+ else
+ hash = (HashTable *) _del_FreeListNode(hash->mem->hash_memory, hash);
+ };
+ return NULL;
+}
+
+/*.......................................................................
+ * Create and install a new entry in a hash table. If an entry with the
+ * same name already exists, replace its contents with the new data.
+ *
+ * Input:
+ * hash HashTable * The hash table to insert the symbol into.
+ * name const char * The name to tag the entry with.
+ * code int An application-specific code to be stored in
+ * the entry.
+ * fn void (*)(void) An application-specific function to be stored
+ * in the entry.
+ * data void * An application-specific pointer to data to be
+ * associated with the entry, or NULL if not
+ * relevant.
+ * del_fn SYM_DEL_FN(*) An optional destructor function. When the
+ * symbol is deleted this function will be called
+ * with the 'code' and 'data' arguments given
+ * above. Any application data that was registered
+ * to the table via the app_data argument of
+ * _new_HashTable() will also be passed.
+ * Output:
+ * return HashNode * The new entry, or NULL if there was insufficient
+ * memory or the arguments were invalid.
+ */
+Symbol *_new_HashSymbol(HashTable *hash, const char *name, int code,
+ void (*fn)(void), void *data, SYM_DEL_FN(*del_fn))
+{
+ HashBucket *bucket; /* The hash-bucket associated with the name */
+ HashNode *node; /* The new node */
+/*
+ * Check arguments.
+ */
+ if(!hash || !name) {
+ errno = EINVAL;
+ return NULL;
+ };
+/*
+ * Get the hash bucket of the specified name.
+ */
+ bucket = _find_HashBucket(hash, name);
+/*
+ * See if a node with the same name already exists.
+ */
+ node = _find_HashNode(hash, bucket, name, NULL);
+/*
+ * If found, delete its contents by calling the user-supplied
+ * destructor function, if provided.
+ */
+ if(node) {
+ if(node->symbol.data && node->symbol.del_fn) {
+ node->symbol.data = node->symbol.del_fn(hash->app_data, node->symbol.code,
+ node->symbol.data);
+ };
+/*
+ * Allocate a new node if necessary.
+ */
+ } else {
+ node = _new_HashNode(hash, name, code, fn, data, del_fn);
+ if(!node)
+ return NULL;
+ };
+/*
+ * Install the node at the head of the hash-bucket list.
+ */
+ node->next = bucket->head;
+ bucket->head = node;
+ bucket->count++;
+ return &node->symbol;
+}
+
+/*.......................................................................
+ * Remove and delete a given hash-table entry.
+ *
+ * Input:
+ * hash HashTable * The hash table to find the symbol in.
+ * name const char * The name of the entry.
+ * Output:
+ * return HashNode * The deleted hash node (always NULL).
+ */
+Symbol *_del_HashSymbol(HashTable *hash, const char *name)
+{
+ if(hash && name) {
+ HashBucket *bucket = _find_HashBucket(hash, name);
+ HashNode *prev; /* The node preceding the located node */
+ HashNode *node = _find_HashNode(hash, bucket, name, &prev);
+/*
+ * Node found?
+ */
+ if(node) {
+/*
+ * Remove the node from the bucket list.
+ */
+ if(prev) {
+ prev->next = node->next;
+ } else {
+ bucket->head = node->next;
+ };
+/*
+ * Record the loss of a node.
+ */
+ bucket->count--;
+/*
+ * Delete the node.
+ */
+ (void) _del_HashNode(hash, node);
+ };
+ };
+ return NULL;
+}
+
+/*.......................................................................
+ * Look up a symbol in the hash table.
+ *
+ * Input:
+ * hash HashTable * The table to look up the string in.
+ * name const char * The name of the symbol to look up.
+ * Output:
+ * return Symbol * The located hash-table symbol, or NULL if not
+ * found.
+ */
+Symbol *_find_HashSymbol(HashTable *hash, const char *name)
+{
+ HashBucket *bucket; /* The hash-table bucket associated with name[] */
+ HashNode *node; /* The hash-table node of the requested symbol */
+/*
+ * Check arguments.
+ */
+ if(!hash)
+ return NULL;
+/*
+ * Nothing to lookup?
+ */
+ if(!name)
+ return NULL;
+/*
+ * Hash the name to a hash-table bucket.
+ */
+ bucket = _find_HashBucket(hash, name);
+/*
+ * Find the bucket entry that exactly matches the name.
+ */
+ node = _find_HashNode(hash, bucket, name, NULL);
+ if(!node)
+ return NULL;
+ return &node->symbol;
+}
+
+/*.......................................................................
+ * Private function used to allocate a hash-table node.
+ * The caller is responsible for checking that the specified symbol
+ * is unique and for installing the returned entry in the table.
+ *
+ * Input:
+ * hash HashTable * The table to allocate the node for.
+ * name const char * The name of the new entry.
+ * code int A user-supplied context code.
+ * fn void (*)(void) A user-supplied function pointer.
+ * data void * A user-supplied data pointer.
+ * del_fn SYM_DEL_FN(*) An optional 'data' destructor function.
+ * Output:
+ * return HashNode * The new node, or NULL on error.
+ */
+static HashNode *_new_HashNode(HashTable *hash, const char *name, int code,
+ void (*fn)(void), void *data, SYM_DEL_FN(*del_fn))
+{
+ HashNode *node; /* The new node */
+/*
+ * Allocate the new node from the free list.
+ */
+ node = (HashNode *) _new_FreeListNode(hash->mem->node_memory);
+ if(!node)
+ return NULL;
+/*
+ * Before attempting any operation that might fail, initialize the
+ * contents of 'node' at least up to the point at which it can be
+ * safely passed to _del_HashNode().
+ */
+ node->symbol.name = NULL;
+ node->symbol.code = code;
+ node->symbol.fn = fn;
+ node->symbol.data = data;
+ node->symbol.del_fn = del_fn;
+ node->next = NULL;
+/*
+ * Allocate a copy of 'name'.
+ */
+ node->symbol.name = _new_StringMemString(hash->mem->string_memory,
+ strlen(name) + 1);
+ if(!node->symbol.name)
+ return _del_HashNode(hash, node);
+/*
+ * If character-case is insignificant in the current table, convert the
+ * name to lower case while copying it.
+ */
+ if(hash->case_sensitive) {
+ strcpy(node->symbol.name, name);
+ } else {
+ const char *src = name;
+ char *dst = node->symbol.name;
+ for( ; *src; src++,dst++)
+ *dst = tolower(*src);
+ *dst = '\0';
+ };
+ return node;
+}
+
+/*.......................................................................
+ * Private function used to delete a hash-table node.
+ * The node must have been removed from its list before calling this
+ * function.
+ *
+ * Input:
+ * hash HashTable * The table for which the node was originally
+ * allocated.
+ * node HashNode * The node to be deleted.
+ * Output:
+ * return HashNode * The deleted node (always NULL).
+ */
+static HashNode *_del_HashNode(HashTable *hash, HashNode *node)
+{
+ if(node) {
+ node->symbol.name = _del_StringMemString(hash->mem->string_memory,
+ node->symbol.name);
+/*
+ * Call the user-supplied data-destructor if provided.
+ */
+ if(node->symbol.data && node->symbol.del_fn)
+ node->symbol.data = node->symbol.del_fn(hash->app_data,
+ node->symbol.code,
+ node->symbol.data);
+/*
+ * Return the node to the free-list.
+ */
+ node->next = NULL;
+ node = (HashNode *) _del_FreeListNode(hash->mem->node_memory, node);
+ };
+ return NULL;
+}
+
+/*.......................................................................
+ * Private function to locate the hash bucket associated with a given
+ * name.
+ *
+ * This uses a hash-function described in the dragon-book
+ * ("Compilers - Principles, Techniques and Tools", by Aho, Sethi and
+ * Ullman; pub. Adison Wesley) page 435.
+ *
+ * Input:
+ * hash HashTable * The table to look up the string in.
+ * name const char * The name of the symbol to look up.
+ * Output:
+ * return HashBucket * The located hash-bucket.
+ */
+static HashBucket *_find_HashBucket(HashTable *hash, const char *name)
+{
+ unsigned const char *kp;
+ unsigned long h = 0L;
+ if(hash->case_sensitive) {
+ for(kp=(unsigned const char *) name; *kp; kp++)
+ h = 65599UL * h + *kp; /* 65599 is a prime close to 2^16 */
+ } else {
+ for(kp=(unsigned const char *) name; *kp; kp++)
+ h = 65599UL * h + tolower((int)*kp); /* 65599 is a prime close to 2^16 */
+ };
+ return hash->bucket + (h % hash->size);
+}
+
+/*.......................................................................
+ * Search for a given name in the entries of a given bucket.
+ *
+ * Input:
+ * hash HashTable * The hash-table being searched.
+ * bucket HashBucket * The bucket to search (use _find_HashBucket()).
+ * name const char * The name to search for.
+ * Output:
+ * prev HashNode ** If prev!=NULL then the pointer to the node
+ * preceding the located node in the list will
+ * be recorded in *prev. This will be NULL either
+ * if the name is not found or the located node is
+ * at the head of the list of entries.
+ * return HashNode * The located hash-table node, or NULL if not
+ * found.
+ */
+static HashNode *_find_HashNode(HashTable *hash, HashBucket *bucket,
+ const char *name, HashNode **prev)
+{
+ HashNode *last; /* The previously searched node */
+ HashNode *node; /* The node that is being searched */
+/*
+ * Search the list for a node containing the specified name.
+ */
+ for(last=NULL, node=bucket->head;
+ node && hash->keycmp(node->symbol.name, name)!=0;
+ last = node, node=node->next)
+ ;
+ if(prev)
+ *prev = node ? last : NULL;
+ return node;
+}
+
+/*.......................................................................
+ * When hash->case_sensitive is zero this function is called
+ * in place of strcmp(). In such cases the hash-table names are stored
+ * as lower-case versions of the original strings so this function
+ * performs the comparison against lower-case copies of the characters
+ * of the string being compared.
+ *
+ * Input:
+ * node_key const char * The lower-case hash-node key being compared
+ * against.
+ * look_key const char * The lookup key.
+ * Output:
+ * return int <0 if node_key < look_key.
+ * 0 if node_key == look_key.
+ * >0 if node_key > look_key.
+ */
+static int _ht_lower_strcmp(const char *node_key, const char *look_key)
+{
+ int cn; /* The latest character from node_key[] */
+ int cl; /* The latest character from look_key[] */
+ do {
+ cn = *node_key++;
+ cl = *look_key++;
+ } while(cn && cn==tolower(cl));
+ return cn - tolower(cl);
+}
+
+/*.......................................................................
+ * This is a wrapper around strcmp for comparing hash-keys in a case
+ * sensitive manner. The reason for having this wrapper, instead of using
+ * strcmp() directly, is to make some C++ compilers happy. The problem
+ * is that when the library is compiled with a C++ compiler, the
+ * declaration of the comparison function is a C++ declaration, whereas
+ * strcmp() is a pure C function and thus although it appears to have the
+ * same declaration, the compiler disagrees.
+ *
+ * Input:
+ * node_key char * The lower-case hash-node key being compared against.
+ * look_key char * The lookup key.
+ * Output:
+ * return int <0 if node_key < look_key.
+ * 0 if node_key == look_key.
+ * >0 if node_key > look_key.
+ */
+static int _ht_strcmp(const char *node_key, const char *look_key)
+{
+ return strcmp(node_key, look_key);
+}
+
+/*.......................................................................
+ * Empty a hash-table by deleting all of its entries.
+ *
+ * Input:
+ * hash HashTable * The hash table to clear.
+ * Output:
+ * return int 0 - OK.
+ * 1 - Invalid arguments.
+ */
+int _clear_HashTable(HashTable *hash)
+{
+ int i;
+/*
+ * Check the arguments.
+ */
+ if(!hash)
+ return 1;
+/*
+ * Clear the contents of the bucket array.
+ */
+ for(i=0; i<hash->size; i++) {
+ HashBucket *bucket = hash->bucket + i;
+/*
+ * Delete the list of active hash nodes from the bucket.
+ */
+ HashNode *node = bucket->head;
+ while(node) {
+ HashNode *next = node->next;
+ (void) _del_HashNode(hash, node);
+ node = next;
+ };
+/*
+ * Mark the bucket as empty.
+ */
+ bucket->head = NULL;
+ bucket->count = 0;
+ };
+ return 0;
+}
+
+/*.......................................................................
+ * Execute a given function on each entry of a hash table, returning
+ * before completion if the the specified function returns non-zero.
+ *
+ * Input:
+ * hash HashTable * The table to traverse.
+ * scan_fn HASH_SCAN_FN(*) The function to call.
+ * context void * Optional caller-specific context data
+ * to be passed to scan_fn().
+ * Output:
+ * return int 0 - OK.
+ * 1 - Either the arguments were invalid, or
+ * scan_fn() returned non-zero at some
+ * point.
+ */
+int _scan_HashTable(HashTable *hash, HASH_SCAN_FN(*scan_fn), void *context)
+{
+ int i;
+/*
+ * Check the arguments.
+ */
+ if(!hash || !scan_fn)
+ return 1;
+/*
+ * Iterate through the buckets of the table.
+ */
+ for(i=0; i<hash->size; i++) {
+ HashBucket *bucket = hash->bucket + i;
+ HashNode *node;
+/*
+ * Iterate through the list of symbols that fall into bucket i,
+ * passing each one to the caller-specified function.
+ */
+ for(node=bucket->head; node; node=node->next) {
+ if(scan_fn(&node->symbol, context))
+ return 1;
+ };
+ };
+ return 0;
+}