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;
+}
ae7 Tp2쎔[6r-01 ;*iwS]/[s ٌ7ᅦX ϧ6wA?Fs<_C;~S=QpqŽ[wO3]gzh##>y;Ь?V8C9OjhWUq[K_CJ,dn520dP`PF{%%;.v v$Cs*pʮmȓ&AÖBt[rDHI=LA֘Y!Ui^ehP b>R)3di׊7ECĽJT8`fmFM^^{432"L3lgi_6U:F5iCp :/'#d(aFKC1Nd{Yɹz㢝{u$amxĶBnH&w!BA8ӈ^I[FcI;̉圝C3y8"6M$OWS$c.9THw~sb$0ksrMDᅨ^lD2B"o^JGdrk˖Uvמ˽fRQI d)&PzGwfKdmJICi`z#A>MjV r-2 Zk(vBN~=~= XՇMǂ16؃n)l# jwSo9&LNH[Ҿ4$<yz;e ":%}dQ`5+T XwCWWa ,~ݳ`.Sˉ<n7A ,dp.,>PUC.& %Ms;5pd#\vyTF3Ʀ؇F:{seΧoCc=sq&pvsBXznoJf-=6B=k1=39d/\Hi?W[CqJ}$0۪5-EAywI|&fD݅M CE)A0RJSxDq$7Ѡ@:&0*!^̮-OAQm9"whtRD2(Ny0R kۦU~Aj:;'%̔փ|{.y2IkG†Q1]Yvpזa6== T ~~b Ԣac =i(`Rde;{Uόw(nAIij,g:Q,jH@ f"IN%6$n8n.LӸ+nSw]E n 1gΤxgCfHK*PJt)?~3g;HQWPĈGkĩo[hwC!Qwww30:}B$0z'7, AsFqzCT%5rE?z9!FQّ[{ z4oCw:xNq9p)cڢR 8iˏ#F2=gG/W~^vɽrdzEhw|ÜmwR~4r8& ZR-v'|*h2!Uۿ}؛kN!HPG{;3|eXExvtrgXQ_[ȵz/7 3x*ihmL{+L KһkrjMmh1b1t>jUJTOւR\!XMp՝櫓է6nSg( /&ImnySXͧc@\O.RoL$s'cK $kNvM>aUze瘟!9*ϒANSB[NR38wPvGA"MH?1+iNC<> ok c!Z 詙"Gi-&IⅣI8D . q8W!}4ebzM3Qֱ *:,v^Q6 f\Q;(Jtm3JҚG5We~{oo{DNdSv P*t$!dy'CC>2iOvSwohovbw=7>/ڇk-'(wgIGHs SrJ;Yڭ%[cnɫ\G v$da ߏB}C^>VkƊRF_ a i B7Q7CTQIJs=s8A"U/ RjLIvrdyw"̻r59|H} X%ѻ`U̎Yy܌]ۜ]py2(=+zJ|5̱ĎNu߿|W-n^%!Y<P/s٣;Ek.a6vIDImt zh??#UHA-$p ͪ[*\hrܖEsDYJ8re)6FMXiAt(,TԪѬ򍘤OK+$3ֱof1Xxؒ,P("H 8|L꣕kwQZj{ǢsȼI#3*PD1Kv((yNIvq(N!_ fG8ScvL`R=O4x+9Z n=x Úֵ6=Dwo<8@Yʉs#Yj{>QJ/wgbXi׬Жp~M~*YN !c/ RG>5`*Q*n5rƃho$ȂY'9Z\;Pgls)ߊ1ۚQ\9'>Tsnp;oSr̊ӕ F~$3T2)5/2R_J,CpDTC~ccϐn&vynT?vZM##/rGOONmt!S Rm~{N "JۤQe[AʈDPw 2<輑Sodž y,B7Rjw29?g(18L&XS?[;S k3U@u#CXj)rlP9"R &/Tr :O!Ñc#gh^<অ|l!Z?*ڻKNoc!& ܛ:k4U1c/ߘ~Dbhl Xt Llk6^zgcqHѓ,0iT^3}{T35lXIJ)fnI1k\$\Jj)-Ab uaazQk .K09OchvA,s 4·"JJjjJ m*ghfh2VoOĊKϗC%&(Ipj 𘌪X=_#7 mGj9SqɣǔMTX6#~|%,6y6FLD#,Vb5Kgc(8zvc˅ .ilXȧ\vSHNN#G sbU!m.GeRgYl#2vיa@??in]h%بWvAgGmIe:A[Tp~ќّ 3rOs=h= 1{#b[^.sBwH}4"ي9\%4 =R;0TAM '.rhbKl8qsjD.=ZЎ]X&`slRPvj8RKik\lR}.pNȇ'E9=.R-0NUw.vwYd\ # DԗS#C"I.2B׾.mEDž# +1J.)J5j in3,`E}Lqb UXȳ^ʙ% ֿ̎ x1>|Svaܫ~2r ?=HOi-z}$ZEn*\$XvZƕj@= vڶG8k蹫%KV:pi{f*ꠥ 蒇:8+ruf8Ϊ;qLg.H}|ljodv:8MtKCLBRoG `= fJYb.]*l EN<*\\ fN{&x.Gs,+ᅣֆKyףЬ.YerzE!0ʜ(ZK(lA)ԡ4J/J|#1̃c+p{wlüAvg޶n+E&CSƾ,Ѩv3!"dnW*|+c@޾ޯ*jܵMvrZnM3MFOj@HI~bÍ/G\CPk4g)٠:AݏbOʋrGs(R-h'J rU1cIrn!mK6C< E9 @`X)Czh2ਕ8 Ka,Ko$ifvpRPA B`4|]mvOڿf{UYǟ}6,K]Yy+' Q‡ДQ$/02ysPW.tmG0E}Dap]9k\q;6_ ӅnLJc|dau -oky "T ̒ݻ jd]H <9]XaA;@8aR9m'#1qfg9α @lq @Vİs{E/0kqXŊh9bp|sj(;*Hw9A9h%wx(XnsZxh>$@KiN9c7+e/ogJ`w[6-jr*eF 1324q)_h1!¶<pu?Lg~,^]堥z*t錴ru?̙rC@ G6bE$$-L1QUE,O* (` H"8lPA8,sJIIb"HBnGX@Oj`! ny$rVk H/p@R(%dC(uēE)/礼GmCcc͕O$v"6SNh@Z6͋ t?`5֜ijsq}Gbz(N&8wU(+^oő8 %xkcb1[R;~G,UE~_6?9bt.$9u[ڜ|l&el R\qbӳNi b")R`( &TPB p_€ D` ^o}u1s똝Vp~|'">Mgr.(d!??)!141- 0 j60_؇$=" @8GS0HPS0(]ܤr_8< 9Z2J}l_/#Wb脏@)J<hVlD+ov5P2)D9DDC .;\C;8b?]pi 6%1mU%s@kXߚޠMJvX iS'Ok^lb aizWv/9gf>"Kdzd橢]0<֟oJ&0fkryނhOfSkr=J/aٹc-T6Pl)uq3hm{N.QpN왘*)F鵭s:Da9sQ=30;ӀVvru.7z2Rbg+=Ɇ}ڂ^SC:wZk8C7+Ro5onKD;p%I:=s HҲdé-lM[KL le8ZY;fhz2aU 7qmvk#y{m=u'3ϊ]upI"*-{]%\:x,DDDav.0֝u/bCȣ~d't`OHbi~P9OOWo {yZdZܾ?&D|3<'-KC:pGix#1I">Il&KuZdE2'@ P10b*'y`iAI=1\^BPncdECtf! ϛ$YͬeJݠSX7l 75鎻_PLN\RNӭ]!t Ov}Cρְ 4 R-=2H$>7w{P|Z>ߢ!p6I E~:'LPAk:-?gNBHw2ICTAF' ψW-jcs?q:nH!SA Aq|5 λh| T}]j"jC6ۅ;KD۬r,3?[o ƅeyfyOHV?gѠO74cR\?K,3?[΄2B65O*_ @7Z}iYz39h.D/Pvl=a^5@Z*֖փYܽwh{ݮ@8ViePr4'Z9bS$#k f)\TMϟ!6A sy! 8eakR[Cz A2fy6FA!'K/}HgqV~TkGhiZ"V)Puu رxʙnh4w3u;h# a8;P#eW]m+a?;4$ ؐ((5%n,gdW!,· "̈́A @bc*;sޖ{nLsT[#\s_?I8=!۷~lOװdDK rg%  "dUˇZ1@\͝4IZ+DI23R)SNAnP)1_PP|qSQfJj94 tHS>d;)_ܯih8d$ #K4*NFVB y( CmTJPTI!:lZşwN޳ii#1MYdWEO@h|_=΁nws]Γuꔄ[=~_zEByCiR]aD~#?eTDoju2D }뽸APLu))HsFqo䀸DΘ)ҝqAl1ǹ*Rŭ>mgxrg➔vN1i[;+,1M^K晻`Maq+ddz?YD5T`Y2Ȥ6sy/F4dYMl`IJe*SfDy@kM$yz.Ӂ3Y6k^}5Vz}9U̬r<˓1SDN]$ՌF*{Pi kjqE.1Z?N;xu"Y2HXqU*JwB"yYl0zAwoVCP`s34 8Jy^<(elk+yǕ1`7p#T"VfJ{)Ex b;g5GptV0LCCf>8XǢ=~n04He uNI)&Yށ^5Y,oCɊ-A쵘Ee uz` SpbAzJ`xgj.s+L iX;7*'gY|cFҺ uɗMþ=<>K_D?#9gCB$k_ewq?kYF={#lWjRm~a#Om>_7|%3$>R7ɵHЀΰADTD*R4_vC+l)ƩM&տ_woGd{{?_}+Zmc4}D˷Q*!2sQCaMg3tK.O1#vO_Nz/4uӗ b.AY5NQΧ󌯿xJӰzJm'sn~קO{kzev8nɹ>iGl?,pX_HQ|}{};G88pL9Av}h³Z߲://tיfkSkݑ(S~Etǔ|gnwtԄD5{S}X> >1죥x~kz2L/AwB.I GOE=O*F:&)*Fux";P l [S=z7?x0`"gC/U ztء?ghhe{]siɄfRh}whh[c6u̻?Hn~ֽ 袏=]V~:i}aWn_ǖx>x-8=/2~>~8}N5zy{.ߋ3~o__̏[eF;dW0fV_\?ws?n9զх2> \dz~_w~Np_O/<~_OW*w|%yM0IctB,LssK)]0ÅICO/86}:Ӈf[ە鏟GZ _;em8w}GMn^ߗ{}6=`u=%AźAv [ 8>'z{#:d?J~ύ~dns~I"R·gџ?kS|/=9v]f3Vks#~!I5rZUc"Bꃼti_nr |i^(}_Lw_i: ]q.|R\q~_f>p_ q5O{sPk~&Ja:*񷿉E &/[wbH%8NlYʼnC =Yjvfz׷ft9~RߏǯƘ-L>|ukڱ~VCGH9/kjl?ώع7=wLs-)8;+ґެz[ga7s.~_ZU=<'왘?_>r@9D7ڪNA LBNq` h`!Nii0 k1B42S}=8Žh6eM0Kc4dFn"6Z7#Dk?ר$Spn#JτRG$ǔEr1Gީa݃Lk*Fx/ݮb{Cӱk1#4 VYЙPphc8SDagFv4]QpNUc^9v3!8"JETd`sZYbJ'wI.5YngpDhZG&N1Ƥcɱ}r |z2RcC C/r<_,0Z4hs7}h}]z~6 kq.VE|"2Rגt+9c^žfU2>f]KDbC OϦb;SxȚ8 bWN4ђ-"BXIz҃L`H*8s>2S,qn6FESzc5FykiYlHktJ?NGC q|-50ӥ~3 ao&dQ)2 (:1=*EH*B~!C_{{pɳ ^O5O`ȟ!=ő1DCcv&,sͩo {L9~Duo lH١TVԼ5!IgO>HKyA4@g\q>i_'n=a".3<2]h%!D;ɽ^U4io{1Zsw4v7x57k5PkQ@ЃB3=6g67A75 &y<CIj &㉧7כHsv~P|.^矨K%H@}<'On;,~?FEAs-]]%q5y ɥ.W3ZX+R)=L3ȁɯish,KwsO_CrYMaQ\{A߼EUo}Ur$SvУ9IZhy'ߦ[)4J9 I%bǯ)C^p,Y?`ӫnFO/ǻᚹ1̥wfBJQKIh}*"Os]wi?,QEQHOjv=K{g4vdn)ze& ށ2e {ƭT3=eCL܄P*h;zEHtU83MXz+VkVqMwuQXAA{"9⍞&3^dCeAL 2]Grvj!iqi.L,ѡ|?SAsT8"ΊdX5_jYK=ifsB]ԷzHHδy:,BZ(ii,z>F='EcI0 !h 1m /Q]$KrNTޏ'UG ׽-ٛђQV}sGH:e)BSht >֗iU'SPp6"Wv'h|9 qOzFщDBx_l,"H;f!L:|}fٚ.m:TmH!i4TzQ*%It'(ʤm$Vie5(FC'|XhuWgɬ,VȬ;ˠbhU`+{J^㌪KA=Ic2ucCL0$uܔ)Fx6Kn$LF {exoH_GVTc 9I4<2b괂xҹ3hB+xaTl`H@! J9+ ^?gos`2| DIM'5`JWけK=TJ4PHkGʿxZs'jgL>r KmЀ̫Le--8ʶxE*RbHB%@^uo˘'femB}"!"= nk[tUg\C~L;qLƸM1Nvf^\әF#f0[vyC#/GMF/ûuGr>Y8mqj^+6=1<֍Y@'x+qn#JٳSE)DOf!}TJbuhU*O=JO#?EE_8l(^=͙Xbw?CO$(xLcσL.g:䮃NXi8}m.e_pogO?c:ٴm!Xi;19O.B>Oɏw]IoE~ϑ_ U}YyF~ŧwm#twA~S^γ <1t hRO~~=m|5OT|3O2 #f2='W2gg>{HW #V[oU8#mC>vo>?8GFHQ*uIekR>s?+|}n,SBCw!()r+oguF5QLBg,e[jKVIݺвN 4 Btw[Yۙniw!h㾤cñmW:W|a xLv)~'Oc|Oulٱ}rcWxϷ:q>'Ƒsok]6{|_}0u~xs4;S(#.1]#o.5{ʍ Ƿ+?M9$W,yY̚W=?c|m\S5ƶO5YqeLLj}1޻ȦїVW|'q[\;k|>v^|xU\\X8Pœc~1ݫn҉QZME-+>S+[J>x;̸x5;44lѱ}=/,>45ES:g: svqE~;hL85sQU7 R n2**w)ց!&P(4M~/d!ԧqʌU%ozc,Wu[GHŻȲE=hW:<#wE,a*qx\V PTϳtk<646*9¼wL!b#Pg QVs^,}ZxBfxiEbŕ:<;tk'+wt7b` Zgei[/]6UAAs".sJWL5͐4B!5Ps\fZ <yU'V- Iۑj:EaDE4r՞)bts[Zisve^tߖFK@]\c*Z9+]rw/]f. Atߪ2qsk"wχW2G*<@1nN"$|#I?/$3_Cq\(BR-%*BPPS@P4BP@iZ)!(K h4)V*I) &ӡowW΃hB,