diff urogue/dict.c @ 256:c495a4f288c6

Import UltraRogue from the Roguelike Restoration Project (r1490)
author John "Elwin" Edwards
date Tue, 31 Jan 2017 19:56:04 -0500
parents
children 13b482bd9e66
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/urogue/dict.c	Tue Jan 31 19:56:04 2017 -0500
@@ -0,0 +1,1160 @@
+/*
+    dict.c
+
+    UltraRogue: The Ultimate Adventure in the Dungeons of Doom
+    Copyright (C) 1995 Herb Chong
+    All rights reserved.
+
+    See the file LICENSE.TXT for full copyright and licensing information.
+*/
+
+/******************
+**  Change history:
+**
+**  (AK:04/03/95) - In dict_create, initialize hook for extensions to dictionary structure.
+**  (AK:04/04/95) - In dict_insert, only set any_ptr when a new string entry is created.
+**  (AK:04/17/95) - Added dict_union and dict_merge.
+**  (AK:04/18/95) - In dict_create, added code to create signature,
+**                  table of contents, and parameter array.
+**  (AK:04/18/95) - Revised dict_load, dict_save
+**  (AK:04/18/95) - Added dict_import
+**
+******************/
+
+static char sccsid[] = "%W% %G%";
+
+#include <stdlib.h>
+#include <string.h>
+#if !defined(OS2) && !defined(_WIN32)
+   #include <unistd.h>
+#endif
+
+#include "dict.h"
+#include "dictutil.h"
+
+#define BUFLEN      300
+
+/*************************************************************************
+*       Generic dictionary and hash table functions for word and
+*       string storage.
+*
+*       Implements generic dictionary management by using a
+*       hash table with direct chaining.  All terms are stored in a
+*       separate, unsorted, string table. When inserting into the
+*       hash table exceeds the current size, allocate a new hash table
+*       and rehash the old contents into the new table. When the string
+*       table fills, append more empty entries onto the old table and
+*       continue adding. Inserting a string that already exists increments
+*       the count for the string. Deleting a string only decrements the
+*       count to no less than 0. This will allow a 0 occurence string to be
+*       retrieved. To remove the 0 occurence string table entries, you must
+*       rebuild the dictionary. Merging from one dictionary to a new one will
+*       do this because it only copies nonzero occurences.
+*
+*       Assumptions:
+*               unsigned long >= 32 bits
+*               int >= 16 bits
+*               char == 8 bits
+*               number of entries < 2^28
+*************************************************************************/
+
+/*************************************************************************
+*       hash_string: calculate hash value for string, modified
+*       version of hashpjw() from the new Dragon book.
+*
+*       s - string to calculate hash for
+*
+*       Returns: hash value
+*************************************************************************/
+static unsigned long hash_string(const char *s)
+{
+        unsigned long h = 0;
+        unsigned long g = 0;
+
+        while (*s) {
+                h <<= 4;
+                h += *s++;
+                if ((g = h & 0xf0000000) != 0)
+                        h ^= g >> 24;
+        }
+        return h ^ g;
+}
+
+/*************************************************************************
+*       dict_create: create a dictionary and initialize its structures
+*
+*       toc_size - number of entries in table of contents ( >= 4 )
+*       initial_string_count - number of strings to hold as initial allocation
+*       initial_hash_chains - size of hash table, must be power of 4
+*       max_chain_length - max number of elements in hash chain before signalling growth
+*
+*       Returns: pointer to dictionary
+*               NULL - error creating dictionary
+*               non-NULL - sucessful creation
+*************************************************************************/
+
+DICTIONARY *dict_create(
+        const long toc_size,
+        const long initial_string_count,
+        const long initial_hash_chains,
+        const long max_chain_length )
+{
+        DICTIONARY      *dtemp = NULL;
+        STRING_ENTRY    *stemp = NULL;
+        long    *ltemp = NULL;
+        char    *sltemp;
+        long    i, j, tocsize;
+
+        /* check for a power of 4 in number of initial hash entries */
+        switch(initial_hash_chains) {
+        case 0x00000004:
+        case 0x00000010:
+        case 0x00000040:
+        case 0x00000100:
+        case 0x00000400:
+        case 0x00001000:
+        case 0x00004000:
+        case 0x00010000:
+        case 0x00040000:
+        case 0x00100000:
+        case 0x00400000:
+        case 0x01000000:
+        case 0x04000000:
+                break;
+        default:
+                return NULL;
+        }
+
+        /* Allocate the dictionary structure */
+        if ((dtemp = (DICTIONARY *)malloc(sizeof(DICTIONARY))) == NULL)
+                goto err_exit;
+
+        /* string count has to be within range */
+        j = initial_string_count;
+        if (j > 0x04000000)
+                return NULL;
+
+        /* force a reasonable value */
+        if (j < 100)
+                j = 100;
+
+        /* Allocate the string table and string array */
+        if ((stemp = (STRING_ENTRY *)malloc(sizeof(STRING_ENTRY) * j)) == NULL)
+                goto err_exit;
+        if ((sltemp = (char *)malloc(8 * j)) == NULL)
+                goto err_exit;
+
+        /* Allocate the hash table */
+        if ((ltemp = (long *)malloc(sizeof(long) * initial_hash_chains)) == NULL)
+                goto err_exit;
+
+        /* Allocate the parameter array */
+        if ( (dtemp->parm = (DICT_PARM_ENTRY *)malloc(14*sizeof(DICT_PARM_ENTRY))) == NULL)
+                goto err_exit;
+
+        /* Allocate the signature structure */
+        if ( (dtemp->sig=(DICT_SIG*)malloc(sizeof(DICT_SIG))) == NULL )
+                goto err_exit;
+
+        /* Allocate the Table of Contents */
+        tocsize = toc_size;
+        if ( tocsize < 4 )
+           tocsize = 4;
+        dtemp->sig->toc_size = tocsize;
+        if ( (dtemp->toc=(DICT_TOC_ENTRY*)malloc(dtemp->sig->toc_size*sizeof(DICT_TOC_ENTRY))) == NULL )
+                goto err_exit;
+
+        dtemp->check_value = DICT_VALIDATE;                     /* validation value */
+        dtemp->flags = DICT_NONE;                               /* no flags set */
+        dtemp->entry_count = 0;                                 /* nothing in either table */
+
+        dtemp->string_table = stemp;                            /* connect string table */
+        dtemp->string_max = j;                                  /* size of string table */
+        dtemp->scan_string_index = DICT_ENTRY_NONE;             /* not pointing to anything in scan */
+        dtemp->string_growth_count = 0;                         /* haven't grown any */
+
+        dtemp->string_array = sltemp;                           /* character array for strings */
+        dtemp->array_size = j * 8;                              /* how many bytes available */
+        dtemp->array_used = 0;                                  /* nothing used yet */
+        dtemp->array_growth_count = 0;                          /* haven't grown any */
+
+        /* force maximum hash chain length to a reasonable value */
+        i = max_chain_length;
+        if (i < 1 || i > 200)
+                i = 200;
+        dtemp->chains = ltemp;                                  /* connect hash table */
+        dtemp->longest_chain_length = 0;                        /* nothing in table yet */
+        dtemp->allowable_chain_length = i;                      /* chain length limit */
+        dtemp->table_size = initial_hash_chains;                /* size of hash table */
+        dtemp->hash_mask = initial_hash_chains - 1;             /* mask for mod() function */
+        dtemp->hash_growth_count = 0;                           /* haven't grown any */
+
+        /* string table is empty */
+        for (i = 0 ; i < j ; i++) {
+                dtemp->string_table[i].string_offset = DICT_ENTRY_NONE;
+                dtemp->string_table[i].count = 0;
+                dtemp->string_table[i].next = DICT_ENTRY_NONE;
+                dtemp->string_table[i].flags = 0;
+                dtemp->string_table[i].hash_value = 0;
+        }
+
+        /* no entries chained */
+        for (i = 0; i < initial_hash_chains; i++)
+                dtemp->chains[i] = DICT_ENTRY_NONE;
+
+        /* Initialize hook to extended dictionary structure  */
+        dtemp->ext = NULL;
+
+        /* Set up the parameter array */
+        if ( dict_set_parm_ids(dtemp) == FALSE )
+                goto err_exit;
+        if ( dict_set_parm_values(dtemp) == FALSE )
+                goto err_exit;
+
+        /* Set up the Table of Contents */
+        strcpy( dtemp->toc[0].id , "PARM" );
+        dtemp->toc[0].size = dtemp->sig->nparms * sizeof(DICT_PARM_ENTRY);
+        dtemp->toc[0].ptr = dtemp->parm;
+        dtemp->toc[0].type = 0;
+
+        strcpy( dtemp->toc[1].id , "HASH" );
+        dtemp->toc[1].size = dtemp->table_size * sizeof(long);
+        dtemp->toc[1].ptr = dtemp->chains;
+        dtemp->toc[1].type = 0;
+
+        strcpy( dtemp->toc[2].id , "STTB" );
+        dtemp->toc[2].size = dtemp->string_max * sizeof(char);
+        dtemp->toc[2].ptr = dtemp->string_table;
+        dtemp->toc[2].type = 0;
+
+        strcpy( dtemp->toc[3].id , "STAR" );
+        dtemp->toc[3].size = dtemp->array_size * sizeof(STRING_ENTRY);
+        dtemp->toc[3].ptr = dtemp->string_array;
+        dtemp->toc[3].type = 0;
+
+        /* Set up the signature */
+        dtemp->sig->check_value = DICT_VALIDATE;
+        dtemp->sig->toc_size = tocsize;
+        dtemp->sig->nparms = 14;
+
+        return dtemp;
+
+        /* error exit - saves duplicate code */
+err_exit:
+
+        dict_destroy( dtemp );
+        return NULL;
+}
+
+/*************************************************************************
+*       dict_destroy: discard a dictionary and its contents
+*               multiple calls to destroy the same dictionary is safe
+*
+*       dict - pointer to a dictionary to discard
+*
+*       Returns: status code
+*               TRUE - dictionary destroyed
+*               FALSE - error when destroying dictionary
+*       Note: Does free the dictionary structure itself.
+*************************************************************************/
+BOOLEANC dict_destroy(DICTIONARY *dict)
+{
+        int  i;
+
+        /* have to point to something */
+        if (dict == NULL)
+                return FALSE;
+
+        /* check value has to be OK */
+        if (dict->check_value != DICT_VALIDATE)
+                return FALSE;
+
+        if ( (dict->sig==NULL) || (dict->toc==NULL) || (dict->parm==NULL) )
+                return FALSE;
+
+        /* Free the type=0 tables */
+        for ( i = 0 ; i < dict->sig->toc_size ; i++ ) {
+           if ( dict->toc[i].ptr != NULL && dict->toc[i].type == 0 )
+                free( dict->toc[i].ptr );
+        } /* endfor */
+
+        /* Free the Table of Contents and signature */
+        free( dict->toc );
+        free( dict->sig );
+
+        /* Free the dictionary structure itself */
+        free( dict );
+
+        return TRUE;
+}
+
+/*************************************************************************
+*       dict_insert: add entries into the dictionary, growing as necessary
+*
+*       dict - pointer to dictionary to insert into
+*       s - string to insert
+*       count - count of occurences of string
+*       flags - flag bits associated with the string
+*       number - pointer to long to return word number
+*
+*       Returns: pointer to new entry in dictionary
+*               NULL - error during insert
+*               non-NULL - pointer to inserted or updated entry
+*
+*       Notes: if the entry already exists, increment the count.
+*       If NULL is returned, the dictionary state can no longer
+*       be trusted. Terminating with an error code would be
+*       safest.
+*************************************************************************/
+STRING_ENTRY *dict_insert(DICTIONARY *dict, char *s,
+        const long occurences, const unsigned long flags,
+        void *any_ptr, long *number)
+{
+        unsigned long   hash_value;
+        long    hash_index;
+        long    string_index = -1;
+        long    he2;
+        int     chain_len;
+
+        /* have to point to something */
+        if (dict == NULL)
+                return FALSE;
+
+        /* check value has to be OK */
+        if (dict->check_value != DICT_VALIDATE)
+                return FALSE;
+
+        /* must have a string */
+        if (s == NULL) {
+                *number = -1;
+                return FALSE;
+        }
+
+        /* must be non-NULL */
+        if (s[0] == '\0') {
+                *number = -1;
+                return FALSE;
+        }
+
+        /* figure out which chain it should go into */
+        hash_value = hash_string(s);
+        hash_index = hash_value & dict->hash_mask;
+
+        /* look for the entry in the chain */
+        for (he2 = dict->chains[hash_index], chain_len = 0; he2 != DICT_ENTRY_NONE;
+                        he2 = dict->string_table[he2].next, chain_len++) {
+                char    *sa = &dict->string_array[dict->string_table[he2].string_offset];
+
+                /* compare hash_value and then string, in that order, for speed */
+                if (dict->string_table[he2].hash_value == hash_value && !strcmp(s, sa)) {
+                        string_index = he2;
+                        break;
+                }
+        }
+
+        /* string wasn't found */
+        if (string_index < 0) {
+                /* make sure there is room in string entry table */
+                if (dict->entry_count + 1 > dict->string_max) {
+                        /* make new table 20% bigger */
+                        dict->string_max = (dict->string_max * 6) / 5;
+                        dict->string_table = (STRING_ENTRY *)realloc(dict->string_table,
+                                        sizeof(STRING_ENTRY) * dict->string_max);
+                        if (dict->string_table == NULL)
+                                return NULL;
+                        dict->string_growth_count++;
+                }
+                string_index = dict->entry_count;
+                dict->entry_count++;
+
+                /* make sure there is room in the string array */
+                if (dict->array_used + (long)strlen(s) + 1 > dict->array_size) {
+                        dict->array_size = (dict->array_size * 6) / 5;
+                        dict->string_array = (char *) realloc(dict->string_array,
+                                dict->array_size);
+                        if (dict->string_array == NULL)
+                                return NULL;
+                }
+
+                /* fill in starting values */
+                strcpy(&dict->string_array[dict->array_used], s);
+                dict->string_table[string_index].string_offset = dict->array_used;
+                dict->array_used += (long) strlen(s) + 1;
+                dict->string_table[string_index].count = 0 ;
+                dict->string_table[string_index].flags = DICT_NONE;
+                dict->string_table[string_index].any_ptr = any_ptr; /* (AK:04/04/95) */
+                dict->string_table[string_index].hash_value = hash_value;
+
+                /* hook entry at beginning of chain */
+                dict->string_table[string_index].next = dict->chains[hash_index];
+                dict->chains[hash_index] = string_index;
+
+                /* record chain lengths */
+                chain_len++;
+                if (chain_len > dict->longest_chain_length)
+                        dict->longest_chain_length = chain_len;
+
+                /* if a chain is too long */
+                if (chain_len > dict->allowable_chain_length) {
+                        long    new_size = dict->table_size * 4;
+                        long    new_mask = new_size - 1;
+                        long    *hetemp;
+                        long    i;
+                        int     longest_chain;
+
+                        if ((hetemp = (long *)malloc(sizeof(long) * new_size)) == NULL)
+                                return NULL;
+
+                        /* hash table chains are empty */
+                        for (i = 0; i < new_size; i++)
+                                hetemp[i] = DICT_ENTRY_NONE;
+
+                        /* reset all chains */
+                        for (i = 0; i < dict->entry_count; i++)
+                                dict->string_table[i].next = DICT_ENTRY_NONE;
+
+                        /* recreate hash table entries by reinserting all strings */
+                        for (i = 0; i < dict->entry_count; i++) {
+                                long    he = dict->string_table[i].hash_value & new_mask;
+
+                                dict->string_table[i].next = hetemp[he];
+                                hetemp[he] = i;
+                        }
+
+                        /* find longest chain length */
+                        for (i = 0, longest_chain = 0; i < new_size; i++) {
+                                int     len;
+                                long    cur;
+
+                                for (cur = hetemp[i], len = 0; cur != DICT_ENTRY_NONE;
+                                                cur = dict->string_table[cur].next)
+                                    len++;
+                                        ;
+                                if (len > longest_chain)
+                                        longest_chain = len;
+                        }
+
+                        /* delete old table and attach new one */
+                        free(dict->chains);
+                        dict->chains = hetemp;
+                        dict->longest_chain_length = longest_chain;
+                        dict->table_size = new_size;
+                        dict->hash_mask = new_mask;
+
+                        /* keep track of growth */
+                        dict->hash_growth_count++;
+                }
+        }
+
+        dict->string_table[string_index].count += occurences;
+        dict->string_table[string_index].flags |= flags;
+        *number = string_index;
+
+        return &dict->string_table[string_index];
+}
+
+/*************************************************************************
+*       dict_delete: deletes an entry from the dictionary
+*       (Actually, only decrements the entry's count)
+*
+*       dict - pointer to dictionary to delete
+*       s - string to find and delete
+*       count - count to decrement entry by
+*
+*       Returns: status code
+*               TRUE - entry has been deleted
+*               FALSE - entry wasn't found or error occured
+*************************************************************************/
+BOOLEANC dict_delete(const DICTIONARY *dict, const char *s, const long count)
+{
+        STRING_ENTRY    *se;
+        long    n;
+
+        /* find the string */
+        if ((se = dict_search(dict, s, &n)) == NULL)
+                return FALSE;
+
+        /* decrement count and make sure it stays valid */
+        se->count -= count;
+        if (se->count < 0)
+                se->count = 0;
+        return TRUE;
+}
+
+/*************************************************************************
+*       dict_search: look for entries in the dictionary
+*
+*       dict - pointer to dictionary to search
+*       s - string to search for
+*       number - pointer to long to return string number
+*
+*       Returns: pointer to string entry
+*               NULL - entry not found
+*               non-NULL - pointer to string entry
+*************************************************************************/
+STRING_ENTRY *dict_search(const DICTIONARY *dict, const char *s,
+        long *number)
+{
+        unsigned long   hash_value;
+        long    hash_index;
+        long    string_index = -1;
+        long    he;
+
+        /* have to point to something */
+        if (dict == NULL)
+                return NULL;
+
+        /* check value has to be OK */
+        if (dict->check_value != DICT_VALIDATE)
+                return NULL;
+
+        /* must have a string */
+        if (s == NULL) {
+                *number = -1;
+                return NULL;
+        }
+
+        /* must be non-NULL */
+        if (s[0] == '\0') {
+                *number = -1;
+                return FALSE;
+        }
+
+        /* figure out which chain it should be in */
+        hash_value = hash_string(s);
+        hash_index = hash_value & dict->hash_mask;
+
+        /* look for the entry in the chain */
+        for (he = dict->chains[hash_index]; he != DICT_ENTRY_NONE;
+                        he = dict->string_table[he].next) {
+                char    *sa = (char*)(&dict->string_array[dict->string_table[he].string_offset]);
+
+                /* compare hash_value and then string, in that order, for speed */
+                if (dict->string_table[he].hash_value == hash_value && !strcmp(s, sa)) {
+                        string_index = he;
+                        break;
+                }
+        }
+        if (string_index < 0) {
+                *number = -1;
+                return NULL;
+        }
+
+        *number = string_index;
+        return dict->string_table+string_index;
+}
+
+/*************************************************************************
+*       dict_union: merges contents of 2 dictionaries into
+*       a third
+*
+*       dict1 - pointer to dictionary 1
+*       dict2 - pointer to dictionary 2
+*
+*       Returns: dictionary pointer
+*               NULL - failed to merge
+*               non-NULL - pointer to new dictionary
+*
+*       Notes: entries of the same string have their counts
+*       added and their flags ORed together.
+*************************************************************************/
+DICTIONARY *dict_union( const DICTIONARY *dict1 , const DICTIONARY *dict2 )
+{
+        DICTIONARY    *dict = NULL;
+        STRING_ENTRY  *se, *se2;
+        long          initial_string_count, initial_hash_chains, max_chain_length;
+        long          i, string_index;
+
+        /***********
+        **  Initialize the new dictionary.
+        ***********/
+
+        if ( dict1==NULL || dict2==NULL )
+                goto err_exit;
+        if ((dict=(DICTIONARY *)malloc(sizeof(DICTIONARY))) == NULL)
+                goto err_exit;
+
+        initial_string_count = dict1->string_max;
+        initial_hash_chains = dict1->table_size;
+        max_chain_length = dict1->allowable_chain_length;
+        dict = dict_create( 4,
+                            initial_string_count,
+                            initial_hash_chains,
+                            max_chain_length );
+
+        /***********
+        **  Copy the entries from dict1 into the new dictionary.
+        ***********/
+
+        for ( i = 0 ; i < dict1->entry_count ; i++ ) {
+                se = (STRING_ENTRY*)&dict1->string_table[i];
+                if ( se->count > 0 ) {
+                        se2 = dict_insert(
+                                  dict,
+                                  dict1->string_array+se->string_offset,
+                                  se->count,
+                                  se->flags,
+                                  NULL,
+                                  &string_index );
+                        if ( se2 == NULL )
+                                goto err_exit;
+                } /* endif */
+        } /* endfor */
+
+        /*  Merge the entries from dict2 into the new dictionary. */
+        if ( dict_merge(dict,dict2,FALSE) == FALSE )
+                goto err_exit;
+
+        /*  Success. Return a pointer to the new dictionary.  */
+        return( dict );
+
+        /*  Failure. Ignominiously erase our tracks and return NULL. */
+err_exit:
+        dict_destroy( dict );
+        return NULL;
+}
+
+/*************************************************************************
+*       dict_merge: merges the contents of a dictionary into
+*       another one, updating the contents of the destination
+*
+*       dst - dictionary to update
+*       src - dictionary to add
+*       move - boolean flag
+*               TRUE - move to dest
+*               FALSE - copy to dest
+*
+*       Returns: status code
+*               TRUE - merge completed
+*               FALSE - error on merge
+*
+*       Notes: entries of the same string have their counts
+*       added. At the end of a move, src is empty. If there
+*       is an error during a move, the contents of both
+*       dictionaries cannot be trusted.
+*************************************************************************/
+BOOLEANC dict_merge(const DICTIONARY *dst, const DICTIONARY *src, const BOOLEANC move)
+{
+        STRING_ENTRY  *se, *se2;
+        DICTIONARY    *dict1, *dict2;
+        long          i, string_index, index;
+
+        dict1 = (DICTIONARY*)src;
+        dict2 = (DICTIONARY*)dst;
+
+        /***********
+        **  Copy the dictionary entries into the new dictionary.
+        ***********/
+
+        for ( i = 0 ; i < dict1->entry_count ; i++ ) {
+                se = (STRING_ENTRY*)&dict1->string_table[i];
+                if ( se->count > 0 ) {
+                        se2 = dict_insert(
+                                  dict2,
+                                  dict1->string_array+se->string_offset,
+                                  se->count,
+                                  se->flags,
+                                  NULL,
+                                  &string_index );
+                        if ( se2 == NULL )
+                                goto err_exit;
+                } /* endif */
+        } /* endfor */
+
+        /***********
+        **  Set up the dictionary parameter vector.
+        ***********/
+
+        if ( dict_set_parm_values(dict2) == FALSE )
+                return( FALSE );
+
+        /***********
+        **  Update the table of contents.
+        **     PARM HASH STTB STAR
+        ***********/
+
+        if ( (index=dict_toc_index(dict2,"HASH")) == -1)
+                goto err_exit;
+        dict2->toc[index].size = dict2->table_size * sizeof(long);
+        dict2->toc[index].ptr = dict2->chains;
+
+        if ( (index=dict_toc_index(dict2,"STTB")) == -1)
+                goto err_exit;
+        dict2->toc[index].size = dict2->string_max * sizeof(char);
+        dict2->toc[index].ptr = dict2->string_table;
+
+        if ( (index=dict_toc_index(dict2,"STAR")) == -1)
+                goto err_exit;
+        dict2->toc[index].size = dict2->array_size * sizeof(STRING_ENTRY);
+        dict2->toc[index].ptr = dict2->string_array;
+
+        /***********
+        **  Update the signature
+        ***********/
+
+        dict2->sig->checksum =
+                compute_checksum( 4*sizeof(DICT_TOC_ENTRY) , (char*)(dict2->toc) );
+
+        /***********
+        **  If this is a move, destroy the source dictionary
+        ***********/
+
+        if ( move == TRUE )
+                if ( dict_destroy(dict1) == FALSE )
+                        goto err_exit;
+
+        /***********
+        **  Success
+        ***********/
+
+        return( TRUE );
+
+        /***********
+        **  Failure
+        ***********/
+
+err_exit:
+        dict_destroy( dict2 );
+        return FALSE;
+}
+
+/*************************************************************************
+*       dict_string_by_number: return string pointer for
+*       a given string number
+*
+*       number - string number
+*
+*       Returns: pointer to string entry
+*               NULL - entry not found
+*               non-NULL - pointer to string entry
+*************************************************************************/
+STRING_ENTRY *dict_string_by_number(const DICTIONARY *dict, const long number)
+{
+        if (dict == NULL)
+                return NULL;
+
+        /* check value has to be OK */
+        if (dict->check_value != DICT_VALIDATE)
+                return NULL;
+
+        /* string number has to be within range */
+        if (number < 0 || number >= dict->entry_count)
+                return NULL;
+
+        return dict->string_table+number;
+}
+
+/*************************************************************************
+*       dict_scan_begin: begin a scan of the dictionary
+*
+*       dict - pointer to dictionary to scan
+*
+*       Returns: status code
+*               TRUE - scan initialized
+*               FALSE - error
+*
+*       Notes: if a scan is already in progress, it is
+*       abandoned and the scan is reset to the
+*       beginning.
+*************************************************************************/
+BOOLEANC dict_scan_begin(DICTIONARY *dict)
+{
+        /* have to point to something */
+        if (dict == NULL)
+                return FALSE;
+
+        /* check value has to be OK */
+        if (dict->check_value != DICT_VALIDATE)
+                return FALSE;
+
+        /* point to first entry in string table */
+        dict->scan_string_index = 0;
+        return TRUE;
+}
+
+/*************************************************************************
+*       dict_scan_next: get the next entry in a scan
+*
+*       dict - pointer to dictionary to continue scanning
+*
+*       Returns: pointer to string entry
+*               NULL - no more entries
+*               non-NULL - next string entry
+*************************************************************************/
+STRING_ENTRY *dict_scan_next(DICTIONARY *dict)
+{
+        /* have to point to something */
+        if (dict == NULL)
+                return NULL;
+
+        /* check value has to be OK */
+        if (dict->check_value != DICT_VALIDATE)
+                return NULL;
+
+        /* scan index has to be within range */
+        if (dict->scan_string_index < 0
+                        || dict->scan_string_index > dict->entry_count)
+                return NULL;
+
+        /* for first non-empty table entry */
+        while (dict->scan_string_index < dict->entry_count
+                        && dict->string_table[dict->scan_string_index].count == 0)
+                dict->scan_string_index++;
+
+        /* past end of table? */
+        if (dict->scan_string_index >= dict->entry_count)
+                return NULL;
+
+        return &dict->string_table[dict->scan_string_index++];
+}
+
+
+/*************************************************************************
+*       dict_load - load a compiled dictionary into memory
+*                   creates a new dictionary
+*
+*       fname - fully qualified file name
+*
+*       Returns: pointer to created dictionary structure
+*                (NULL on failure)
+*************************************************************************/
+DICTIONARY *dict_load(const char *fname)
+{
+        DICTIONARY       *dict = NULL;
+        int              code, index;
+        FILE             *fi;
+        int              ntoc;
+
+        if ( (fi=fopen((char*)fname,"rb")) == NULL ) {
+                signal_error( "dict_load: could not open file" , (char*)fname , 0 );
+                goto err_exit;
+        } /* endif */
+
+        if ((dict = (DICTIONARY *)malloc(sizeof(DICTIONARY))) == NULL) {
+                /* signal_error( "dict_load: alloc failed" , "" , 0 ); */
+                goto err_exit;
+        } /* endif */
+
+        /* Read the dictionary signature record */
+        if ((dict->sig = (DICT_SIG *)malloc(sizeof(DICT_SIG))) == NULL) {
+                goto err_exit;
+        } /* endif */
+        code = block_read( fi , (char*)(dict->sig) , sizeof(DICT_SIG) , 0 );
+        if ( code == -1 ) {
+                signal_error( "dict_load: could not read signature" , (char*)fname , 0 );
+                goto err_exit;
+        } /* endif */
+
+        if ( dict->sig->check_value != DICT_VALIDATE ) {
+                signal_error( "dict_load: could not validate file" , (char*)fname , 0 );
+                goto err_exit;
+        } /* endif */
+        dict->check_value = dict->sig->check_value;
+
+        /* Read the dictionary Table of Contents */
+        ntoc = dict->sig->toc_size;
+        dict->toc = (DICT_TOC_ENTRY *) malloc( ntoc * sizeof(DICT_TOC_ENTRY) );
+        if ( dict->toc == NULL ) {
+                signal_error( "dict_load: alloc of TOC failed" , "" , 0 );
+                goto err_exit;
+        } /* endif */
+        code = block_read( fi ,
+                           (char*)(dict->toc) ,
+                           ntoc * sizeof(DICT_TOC_ENTRY) ,
+                           sizeof(DICT_SIG) );
+        if ( code == -1 ) {
+                signal_error( "dict_load: could not read Table of Contents" , (char*)fname , 0 );
+                goto err_exit;
+        } /* endif */
+
+        /* Read the dictionary parameters */
+        dict->parm = (DICT_PARM_ENTRY *)
+                         dict_load_block( dict , "PARM" , fi , NULL );
+        if ( dict->parm == NULL ) {
+                signal_error( "dict_load: could not load parameter table" , "" , 0 );
+                goto err_exit;
+        } /* endif */
+        index = dict_toc_index( dict , "PARM" );
+        dict->sig->nparms = dict->toc[index].size / sizeof(DICT_PARM_ENTRY);
+
+        /* Set the parameter values in the dictionary structure */
+        if ( dict_set_parm_variables(dict) == FALSE )
+                goto err_exit;
+
+        /* Load the string array */
+        dict->string_array = (char *)
+                dict_load_block( dict , "STAR" , fi , NULL );
+        if ( dict->string_array == NULL ) {
+                signal_error( "dict_load: could not load string array" , (char*)fname , 0 );
+                goto err_exit;
+        } /* endif */
+
+        /* Load the string table */
+        dict->string_table = (STRING_ENTRY *)
+                dict_load_block( dict , "STTB" , fi , NULL );
+        if ( dict->string_table == NULL ) {
+                signal_error( "dict_load: could not load string table" , (char*)fname , 0 );
+                goto err_exit;
+        } /* endif */
+
+        /* Load the hash table */
+        dict->chains = (long *)
+                dict_load_block( dict , "HASH" , fi , NULL );
+        if ( dict->chains == NULL ) {
+                signal_error( "dict_load: could not load hash table" , (char*)fname , 0 );
+                goto err_exit;
+        } /* endif */
+
+        /*  Initialize the hook for dictionary extensions  */
+        dict->ext = NULL;
+
+        /*  Success  */
+        fclose( fi );
+        return( dict );
+
+        /*  Failure  */
+err_exit:
+        if ( fi != NULL )
+                fclose( fi );
+        dict_destroy( dict );
+        return NULL;
+}
+
+
+/*************************************************************************
+*       dict_save - save a dictionary from memory into a file
+*
+*       dict - pointer to dictionary to save
+*       fname - full qualified file name prefix of dictionary
+*
+*       Returns: status code
+*               TRUE - dictionary was saved sucessfully
+*               FALSE - error during save
+*************************************************************************/
+BOOLEANC dict_save( DICTIONARY *dict, const char *fname )
+{
+        int   index, ret_code;
+        FILE  *fo = NULL;
+
+        /* Have to be pointing at a valid dictionary */
+        if ( dict == NULL || dict->sig->check_value != DICT_VALIDATE )
+                goto err_exit;
+
+        /* Open the file for output */
+        if ( (fo=fopen((char*)fname,"wb")) == NULL )
+                goto err_exit;
+
+        /*  Make the table of contents entries current  */
+        /*  Note: This will not be necessary once the data is stored in EVECTORs  */
+
+        if ( (index=dict_toc_index(dict,"PARM")) == -1 )
+                goto err_exit;
+        dict->toc[index].size = dict->sig->nparms * sizeof(DICT_PARM_ENTRY);
+        dict->toc[index].ptr = dict->parm;
+
+        if ( (index=dict_toc_index(dict,"STAR")) == -1 )
+                goto err_exit;
+        dict->toc[index].size = dict->array_size * sizeof(char);
+        dict->toc[index].ptr = dict->string_array;
+
+        if ( (index=dict_toc_index(dict,"STTB")) == -1 )
+                goto err_exit;
+        dict->toc[index].size = dict->string_max * sizeof(STRING_ENTRY);
+        dict->toc[index].ptr = dict->string_table;
+
+        if ( (index=dict_toc_index(dict,"HASH")) == -1 )
+                goto err_exit;
+        dict->toc[index].size = dict->table_size * sizeof(long);
+        dict->toc[index].ptr = dict->chains;
+
+        /*  Reset the TOC offsets and checksums for ALL tables  */
+        /*  (not just type=0 tables)                            */
+        dict_reset_toc_offsets( dict );
+
+        /* Set the dictionary parm structure from the parameter values */
+        if ( dict_set_parm_values(dict) == FALSE )
+                goto err_exit;
+
+        /* Save the signature */
+        dict->sig->checksum = compute_checksum( sizeof(DICT_SIG) , (char*)(dict->sig) );
+        ret_code = block_write( fo ,
+                                (char*)dict->sig ,
+                                sizeof(DICT_SIG) );
+        if ( ret_code == -1 )
+                goto err_exit;
+
+        /* Save the table of contents */
+        ret_code = block_write( fo,
+                                (char*)dict->toc,
+                                dict->sig->toc_size * sizeof(DICT_TOC_ENTRY) );
+        if ( ret_code == -1 )
+                goto err_exit;
+
+        /* Save the tables */
+        /* For now, only save type=0 tables */
+        for ( index = 0 ; index < dict->sig->toc_size ; index++ ) {
+                if ( dict->toc[index].type == 0 ) {  /* Ordinary table */
+                        ret_code = dict_save_block( dict , dict->toc[index].id , fo );
+                        if ( ret_code == FALSE )
+                                goto err_exit;
+                     } /* endif */
+        } /* endfor */
+
+        /*  Success  */
+        fclose( fo );
+        return TRUE;
+
+        /*  Failure  */
+err_exit:
+        if ( fo != NULL )
+                fclose( fo );
+        return FALSE;
+}
+
+
+/*************************************************************************
+*       dict_import: read in an ASCII dictionary.
+*
+*       dict_fname - name of dictionary file
+*       parameters to create a DICTIONARY structure (see dict_create)
+*
+*       Returns: pointer to created DICTIONARY structure
+*                (NULL on failure)
+*
+*************************************************************************/
+DICTIONARY *dict_import( const char *dict_fname ,
+                         const long initial_string_count ,
+                         const long initial_hash_entries ,
+                         const long max_chain_length )
+{
+        DICTIONARY     *dict;
+        char           buffer[BUFLEN], ch;
+        int            index, c, c0;
+        long           number;
+        FILE           *fi = NULL;
+
+        /***********
+        **  Dictionary setup.
+        ***********/
+
+        dict = dict_create( 4,
+                            initial_string_count ,
+                            initial_hash_entries ,
+                            max_chain_length );
+        if ( dict == NULL )
+           goto err_exit;
+
+        /***********
+        **  Read the dictionary file
+        **  Each line should have one word or a string delimited by '|'
+        ***********/
+
+        if ( (fi=fopen(dict_fname,"r")) == NULL )
+                goto err_exit;
+        while( fgets(buffer,BUFLEN,fi) != NULL ) {
+                c0 = 0;
+                   /*  Skip to non-blank  */
+                while ( (c0<BUFLEN-2) && (buffer[c0]==' ') ) ++c0;
+                if ( buffer[c0] == '|' ) {
+                        c = ++c0;
+                        ch = '|';
+                } else {
+                        c = c0;
+                        ch = ' ';
+                } /* endif */
+                   /*  Scan to blank or matching '|' */
+                while ( (c<BUFLEN-1) && (buffer[c]!='\0') &&
+                        (buffer[c]!='\n') && (buffer[c]!=ch)  )
+                        ++c;
+                buffer[c] = '\0';
+                   /*  Insert the word  */
+                if ( dict_insert(dict,buffer+c0,1,0,NULL,&number) == NULL )
+                        goto err_exit;
+        } /* endwhile */
+
+        /***********
+        **  Fill in the dictionary parameter vector.
+        ***********/
+
+        if ( dict_set_parm_values(dict) == FALSE )
+                goto err_exit;
+
+        /***********
+        **  Update the table of contents for HASH STTB STAR
+        ***********/
+
+        if ( (index=dict_toc_index(dict,"HASH")) == -1 )
+                goto err_exit;
+        dict->toc[index].size = dict->table_size * sizeof(long);
+
+        if ( (index=dict_toc_index(dict,"STTB")) == -1 )
+                goto err_exit;
+        dict->toc[index].size = dict->string_max * sizeof(char);
+
+        if ( (index=dict_toc_index(dict,"STAR")) == -1 )
+                goto err_exit;
+        dict->toc[index].size = dict->array_size * sizeof(STRING_ENTRY);
+
+           /*  Success. Return a pointer to the new dictionary.  */
+        fclose(fi);
+        return( dict );
+
+           /*  Failure. Ignominiously erase our tracks and return NULL.  */
+err_exit:
+        if ( fi != NULL )
+           fclose(fi);
+        dict_destroy( dict );
+        return NULL;
+}
+
+/*************************************************************************
+*       dict_export - save an extended dictionary from memory into
+*                     an ASCII file
+*
+*       dict - pointer to dictionary to save
+*       fname - full qualified file name prefix of dictionary
+*
+*       Returns: status code
+*               TRUE - dictionary was saved sucessfully
+*               FALSE - error during save
+*
+*************************************************************************/
+BOOLEANC dict_export( DICTIONARY *dict , const char *fname )
+{
+        FILE          *fp = NULL;
+        STRING_ENTRY  *se;
+        int       i;
+
+        /* have to point to something */
+        if (dict == NULL)
+                goto err_exit;
+
+        /* check value has to be OK */
+        if (dict->check_value != DICT_VALIDATE)
+                goto err_exit;
+
+        /* must have a filename */
+        if (fname == NULL)
+                goto err_exit;
+
+        fp = fopen( (char*)fname , "w" );
+        if ( fp == NULL )
+                goto err_exit;
+
+        for ( i = 0 ; i < dict->entry_count ; i++ ) {
+           se = &dict->string_table[i];
+           fprintf( fp , "|%s|\n" , dict->string_array + se->string_offset );
+        } /* endfor */
+        fclose( fp );
+
+        /*  Success.  */
+        fclose(fp);
+        return TRUE;
+
+        /*  Failure.  */
+err_exit:
+        if ( fp != NULL )
+           fclose(fp);
+        dict_destroy( dict );
+        return FALSE;
+}