view urogue/dict.c @ 264:778938a5c21d

UltraRogue: add location for character files. When using the -n option, UltraRogue will look for character files in a single location, similar to save files. The location is chosen by defining CHRDIR in getplay.c, at least until UltraRogue gets integrated with the build systems.
author John "Elwin" Edwards
date Sun, 19 Feb 2017 19:47:09 -0500
parents c495a4f288c6
children 13b482bd9e66
line wrap: on
line source

/*
    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;
}