/*-
******************************************************************************
******************************************************************************
**
**  MODULE
**
**      file: utils.c
**
**  DESCRIPTION
**
**      A 32-bit implementation of BibTeX v0.99c for MS-DOS, OS/2 2.x, 
**      Unix and VMS.  This C language implementation is based on the 
**      original WEB source but it has been enhanced to support 8-bit input
**      characters and a very large processing capacity.
**
**      For documentation describing how to use and build this program, 
**      see the 00README.TXT file that accompanies this distribution.
**
**  MODULE CONTENTS
**
**      This module contains the new utility functions that will be used in
**      the program.  The functions are declared in alphabetical order.
**      Functions defined in this module are:
**
**          close_file
**          debug_msg
**          find_file
**          open_ip_file
**          open_op_file
**          mymalloc
**          myrealloc
**          parse_cmd_line
**          report_bibtex_capacity
**          report_search_paths
**          set_array_sizes
**          usage
**
**  8-bit support functions:
**
**          c8read_csf
**          c8initialise
**          c8read_lowercase
**          c8read_lowupcase
**          c8read_uppercase
**          c8read_order
**          c8report_8bit_handling
**
**  AUTHORS
**
**      Original WEB translation to C, conversion to "big" (32-bit) capacity,
**      addition of run-time selectable capacity and 8-bit support extensions
**      by:
**
**          Niel Kempson
**          Snowy Owl Systems Limited, Cheltenham, England
**          E-mail: kempson@snowyowl.co.uk
**      
**      8-bit support extensions also by:
**
**          Alejandro Aguilar-Sierra
**          Centro de Ciencias de la Atm\'osfera, 
**          Universidad Nacional Aut\'onoma de M\'exico, M\'exico
**          E-mail: asierra@servidor.unam.mx
**
**  COPYRIGHT
**
**      This implementation copyright (c) 1991-1995 by Niel Kempson
**           and copyright (c) 1995 by Alejandro Aguilar-Sierra.
**
**      This program is free software; you can redistribute it and/or
**      modify it under the terms of the GNU General Public License as
**      published by the Free Software Foundation; either version 1, or
**      (at your option) any later version.
**
**      This program is distributed in the hope that it will be useful,
**      but WITHOUT ANY WARRANTY; without even the implied warranty of
**      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
**      General Public License for more details.
**
**      You should have received a copy of the GNU General Public License
**      along with this program; if not, write to the Free Software
**      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
**      In other words, you are welcome to use, share and improve this
**      program.  You are forbidden to forbid anyone else to use, share
**      and improve what you give them.  Help stamp out software-hoarding!
**
**  ACKNOWLEDGEMENT
**      
**      The original BibTeX was written by Oren Patashnik using Donald 
**      Knuth's WEB system.  This format produces a PASCAL program for 
**      execution and a TeX documented version of the source code. This 
**      program started as a (manual) translation of the WEB source into C.
**  
**  CHANGE LOG
**
**      $Log: utils.c,v $
**      Revision 3.71  1996/08/18  20:37:06  kempson
**      Official release 3.71 (see HISTORY file for details).
**
**      Revision 3.70  1996/04/08  10:08:40  kempson
**      Final documentation & cosmetic changes for official release 3.70.
**
**      Revision 3.6  1995/10/21  22:20:42  kempson
**      Fixed numerous bugs, improved error reporting and added the --wolfgang
**      option.
**
**      Revision 3.5  1995/09/24  20:44:37  kempson
**      Many changes for final beta test version.
**
**      Revision 3.4  1995/04/09  22:15:43  kempson
**      Placed under RCS control
**
******************************************************************************
******************************************************************************
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdarg.h>
#ifdef WIN32
#include <getopt.h>
#else
#include <unistd.h>
#endif

#ifdef KPATHSEA
#include <kpathsea/config.h>
#include <kpathsea/c-fopen.h>
#include <kpathsea/tex-file.h>
#include <kpathsea/paths.h>
#include <kpathsea/variable.h>
#include <kpathsea/lib.h>
#ifndef WIN32
#include <kpathsea/getopt.h>
#endif
#endif

#include "sysdep.h"
#include "bibtex.h"
#include "datatype.h"
#include "gblprocs.h"
#include "gblvars.h"
#include "utils.h"
#include "version.h"


/*
** Useful macros to keep the code nicely formatted.
*/
#define FSE(_a)         fprintf (stderr, _a);
#define FSO(_a)         fprintf (TERM_OUT, _a);
#define LOG_CAPACITY(_a) \
        FPRINTF (log_file, "    %-15s = %7ld\n", #_a, (long) _a)
#define ISEMPTYSTR(_a)  ((_a == NULL) || (*_a == '\0'))
#define NULLCHECK(_a)   (ISEMPTYSTR(_a) ? "<undefined>" : _a)


/*-
** Options supported by the program and parsed by getopt().  Each option
** can be specified by a "traditional" single character argument
** preceeded by a dash (e.g. "-7") or a more descriptive parameter
** preceeded by two dashes (e.g. "--ascii").  Each option is also defined
** as either not taking a value, optinally taking a value or requiring a
** value.
*/
#define VALUE_NONE      0
#define VALUE_REQD      1
#define VALUE_OPT       2

static struct option long_options[] = {
#ifndef UTF_8
    {"8bit",            VALUE_NONE, 0, '8'},
    {"csfile",          VALUE_REQD, 0, 'c'},
#endif
    {"debug",           VALUE_REQD, 0, 'd'},
    {"help",            VALUE_NONE, 0, '?'},
    {"statistics",      VALUE_NONE, 0, 's'},
    {"trace",           VALUE_NONE, 0, 't'},
#ifndef UTF_8
    {"traditional",     VALUE_NONE, 0, '7'},
#endif
    {"version",         VALUE_NONE, 0, 'v'},

#ifdef UTF_8
    {"language",        VALUE_REQD, 0, 'l'},
    {"location",        VALUE_REQD, 0, 'o'},
#endif

    {"big",             VALUE_NONE, 0, 'B'},
    {"huge",            VALUE_NONE, 0, 'H'},
    {"wolfgang",        VALUE_NONE, 0, 'W'},
    {"min_crossrefs",   VALUE_REQD, 0, 'M'},

    {"mcites",          VALUE_REQD, 0, '\x0A'}, /* obsolete */
    {"mentints",        VALUE_REQD, 0, '\x0B'}, /* obsolete */
    {"mentstrs",        VALUE_REQD, 0, '\x0C'}, /* obsolete */
    {"mfields",         VALUE_REQD, 0, '\x0D'}, /* obsolete */
    {"mpool",           VALUE_REQD, 0, '\x0E'}, /* obsolete */
    {"mstrings",        VALUE_REQD, 0, '\x0F'},
    {"mwizfuns",        VALUE_REQD, 0, '\x10'}, /* obsolete */
    {0, 0, 0, 0}
};

static const char *getopt_str =
#ifndef UTF_8
  "78c:"
#endif
  "d:?stv"
#ifdef UTF_8
  "l:o:"
#endif
  "BHM:W";



/*
** Forward declaration of private functions and variables used in this
** module for 8-bit support.
*/
#ifdef SUPPORT_8BIT
static int              c8read_csf (void);
static void             c8read_lowupcase (void);
static void             c8read_lowercase (void);
static void             c8read_order (void);
static void             c8read_uppercase (void);
static void             c8report_8bit_handling (void);

static FILE            *c8_cs_file;
static int              c8_line;
static int              sort_weight;
#endif                          /* SUPPORT_8BIT */



/*-
**============================================================================
** allocate_arrays()
**
**  Allocate memory dynamically for the large arrays whose size is fixed,
**  regardless of the amount memory of memory available. If these arrays are
**  declared statically, the linker will complain about an overflowing text
**  segment.
**
**  Allocate memory dynamically for the large arrays whose size is set
**  dynamically, depending on the amount memory of memory available.
**
**	AlphaFile_T     bib_file[Max_Bib_Files + 1];
**	StrNumber_T     bib_list[Max_Bib_Files + 1];
**	ASCIICode_T     buffer[Buf_Size + 1];
**	StrNumber_T     cite_info[Max_Cites + 1];
**	StrNumber_T     cite_list[Max_Cites + 1];
**	Boolean_T       entry_exists[Max_Cites + 1];
**	Integer_T       entry_ints[Max_Ent_Ints + 1];
**	ASCIICode_T	entry_strs[Max_Ent_Strs + 1][Ent_Str_Size + 1];
**	ASCIICode_T     ex_buf[Buf_Size + 1];
**	StrNumber_T     field_info[Max_Fields + 1];
**	FnClass_T       fn_type[Hash_Size + 1];
**      Integer_T       glb_str_end[Max_Glob_Strs];
**      StrNumber_T     glb_str_ptr[Max_Glob_Strs];
**	ASCIICode_T     global_strs[Max_Glob_Strs][Glob_Str_Size + 1];;
**	StrIlk_T        hash_ilk[Hash_Size + 1];
**	HashPointer_T   hash_next[Hash_Size + 1];
**	StrNumber_T     hash_text[Hash_Size + 1];
**	Integer_T       ilk_info[Hash_Size + 1];
**      Integer_T       lit_stack[Lit_Stk_Size + 1];
**      StkType_T       lit_stk_type[Lit_Stk_Size + 1];
**	ASCIICode_T     name_sep_char[Buf_Size + 1];
**	BufPointer_T    name_tok[Buf_Size + 1];
**	ASCIICode_T     out_buf[Buf_Size + 1];
**	StrNumber_T     s_preamble[Max_Bib_Files + 1];
**	ASCIICode_T     str_pool[Pool_Size + 1];
**	PoolPointer_T   str_start[Max_Strings + 1];
**	ASCIICode_T     sv_buffer[Buf_Size + 1];
**	HashPtr2_T      type_list[Max_Cites + 1];
**	HashPtr2_T      wiz_functions[Wiz_Fn_Space + 1];
**============================================================================
*/
static void allocate_arrays (void)
{
    unsigned long           bytes_required;

    debug_msg (DBG_MEM, "Starting to allocate memory for arrays ... ");

    /*
    ** AlphaFile_T bib_file[Max_Bib_Files + 1];
    */
    bytes_required = (Max_Bib_Files + 1) * (unsigned long) sizeof (AlphaFile_T);
    bib_file = (AlphaFile_T *) mymalloc (bytes_required, "bib_file");

    /*
    ** StrNumber_T bib_list[Max_Bib_Files + 1];
    */
    bytes_required = (Max_Bib_Files + 1) * (unsigned long) sizeof (StrNumber_T);
    bib_list = (StrNumber_T *) mymalloc (bytes_required, "bib_list");

    /*
    ** ASCIICode_T buffer[Buf_Size + 1];
    */
    bytes_required = (Buf_Size + 1) * (unsigned long) sizeof (ASCIICode_T);
    buffer = (ASCIICode_T *) mymalloc (bytes_required, "buffer");

    /*
    ** StrNumber_T cite_info[Max_Cites + 1];
    */
    bytes_required = (Max_Cites + 1) * (unsigned long) sizeof (StrNumber_T);
    cite_info = (StrNumber_T *) mymalloc (bytes_required, "cite_info");

    /*
    ** StrNumber_T cite_list[Max_Cites + 1];
    */
    bytes_required = (Max_Cites + 1) * (unsigned long) sizeof (StrNumber_T);
    cite_list = (StrNumber_T *) mymalloc (bytes_required, "cite_list");

    /*
    ** Boolean_T entry_exists[Max_Cites + 1];
    */
    bytes_required = (Max_Cites + 1) * (unsigned long) sizeof (Boolean_T);
    entry_exists = (Boolean_T *) mymalloc (bytes_required, "entry_exists");

    /*
    ** Boolean_T entry_ints[Max_Ent_Ints + 1];
    ** allocated when num_ent_ints and num_cites are known
    */
    entry_ints = NULL;

    /*
    ** ASCIICode_T entry_strs[Max_Ent_Strs + 1][Ent_Str_Size + 1];
    ** allocated when num_ent_strs and num_cites are known
    */
    entry_strs = NULL;

    /*
    ** ASCIICode_T ex_buf[Buf_Size + 1];
    */
    bytes_required = (Buf_Size + 1) * (unsigned long) sizeof (ASCIICode_T);
    ex_buf = (ASCIICode_T *) mymalloc (bytes_required, "ex_buf");

    /*
    ** StrNumber_T field_info[Max_Fields + 1];
    */
    bytes_required = (Max_Fields + 1) * (unsigned long) sizeof (StrNumber_T);
    field_info = (StrNumber_T *) mymalloc (bytes_required, "field_info");

    /*
    ** FnClass_T fn_type[Hash_Size + 1];
    */
    bytes_required = (Hash_Size + 1) * (unsigned long) sizeof (FnClass_T);
    fn_type = (FnClass_T *) mymalloc (bytes_required, "fn_type");

    /*
    ** Integer_T glb_str_end[Max_Glob_Strs];
    */
    bytes_required = Max_Glob_Strs * (unsigned long) sizeof (Integer_T);
    glb_str_end = (Integer_T *) mymalloc (bytes_required, "glb_str_end");

    /*
    ** StrNumber_T glb_str_ptr[Max_Glob_Strs];
    */
    bytes_required = Max_Glob_Strs * (unsigned long) sizeof (StrNumber_T);
    glb_str_ptr = (StrNumber_T *) mymalloc (bytes_required, "glb_str_ptr");

    /*
    ** ASCIICode_T global_strs[Max_Glob_Strs][Glob_Str_Size + 1];
    */
    bytes_required = (unsigned long) (Max_Glob_Strs)
        * (Glob_Str_Size + 1)
        * (unsigned long) sizeof (ASCIICode_T);
    global_strs = (ASCIICode_T *) mymalloc (bytes_required, "global_strs");

    /*
    ** StrIlk_T hash_ilk[Hash_Size + 1];
    */
    bytes_required = (Hash_Size + 1) * (unsigned long) sizeof (StrIlk_T);
    hash_ilk = (StrIlk_T *) mymalloc (bytes_required, "hash_ilk");

    /*
    ** HashPointer_T hash_next[Hash_Size + 1];
    */
    bytes_required = (Hash_Size + 1) * (unsigned long) sizeof (HashPointer_T);
    hash_next = (HashPointer_T *) mymalloc (bytes_required, "hash_next");

    /*
    ** StrNumber_T hash_text[Hash_Size + 1];
    */
    bytes_required = (Hash_Size + 1) * (unsigned long) sizeof (StrNumber_T);
    hash_text = (StrNumber_T *) mymalloc (bytes_required, "hash_text");

    /*
    ** Integer_T ilk_info[Hash_Size + 1];
    */
    bytes_required = (Hash_Size + 1) * (unsigned long) sizeof (Integer_T);
    ilk_info = (Integer_T *) mymalloc (bytes_required, "ilk_info");

    /*
    ** Integer_T lit_stack[Lit_Stk_Size + 1];
    */
    bytes_required = (Lit_Stk_Size + 1) * (unsigned long) sizeof (Integer_T);
    lit_stack = (Integer_T *) mymalloc (bytes_required, "lit_stack");

    /*
    ** StkType_T lit_stk_type[Lit_Stk_Size + 1];
    */
    bytes_required = (Lit_Stk_Size + 1) * (unsigned long) sizeof (StkType_T);
    lit_stk_type = (StkType_T *) mymalloc (bytes_required, "lit_stk_type");

    /*
    ** ASCIICode_T name_sep_char[Buf_Size + 1];
    */
    bytes_required = (Buf_Size + 1) * (unsigned long) sizeof (ASCIICode_T);
    name_sep_char = (ASCIICode_T *) mymalloc (bytes_required, "name_sep_char");

    /*
    ** BufPointer_T name_tok[Buf_Size + 1];
    */
    bytes_required = (Buf_Size + 1) * (unsigned long) sizeof (BufPointer_T);
    name_tok = (BufPointer_T *) mymalloc (bytes_required, "name_tok");

    /*
    ** ASCIICode_T out_buf[Buf_Size + 1];
    */
    bytes_required = (Buf_Size + 1) * (unsigned long) sizeof (ASCIICode_T);
    out_buf = (ASCIICode_T *) mymalloc (bytes_required, "out_buf");

    /*
    ** StrNumber_T s_preamble[Max_Bib_Files + 1];
    */
    bytes_required = (Max_Bib_Files + 1) * (unsigned long) sizeof (StrNumber_T);
    s_preamble = (StrNumber_T *) mymalloc (bytes_required, "s_preamble");

    /*
    ** ASCIICode_T str_pool[Pool_Size + 1];
    */
    bytes_required = (Pool_Size + 1) * (unsigned long) sizeof (ASCIICode_T);
    str_pool = (ASCIICode_T *) mymalloc (bytes_required, "str_pool");

    /*
    ** PoolPointer_T str_start[Max_Strings + 1];
    */
    bytes_required = (Max_Strings + 1) * (unsigned long) sizeof (PoolPointer_T);
    str_start = (PoolPointer_T *) mymalloc (bytes_required, "str_start");

    /*
    ** ASCIICode_T sv_buffer[Buf_Size + 1];
    */
    bytes_required = (Buf_Size + 1) * (unsigned long) sizeof (ASCIICode_T);
    sv_buffer = (ASCIICode_T *) mymalloc (bytes_required, "sv_buffer");

    /*
    ** HashPtr2_T type_list[Max_Cites + 1];
    */
    bytes_required = (Max_Cites + 1) * (unsigned long) sizeof (HashPtr2_T);
    type_list = (HashPtr2_T *) mymalloc (bytes_required, "type_list");

    /*
    ** HashPtr2_T wiz_functions[Wiz_Fn_Space + 1];
    */
    bytes_required = (Wiz_Fn_Space + 1) * (unsigned long) sizeof (HashPtr2_T);
    wiz_functions = (HashPtr2_T *) mymalloc (bytes_required, "wiz_functions");

}                               /* allocate_arrays () */



/*-
**============================================================================
** checkdbg()
**
**  Examine a string representing different debug options, usually provided 
**  as a value to a command line option, and return the value as a long 
**  integer.  Return 0 (no debugging) if a valid option was not parsed.
**============================================================================
*/
static int checkdbg (char *str)
{
    int                 dbgval = 0;
    
    if ((str == NULL) || (*str == '\0'))
        return (0);

    if (strstr (str, "all") != NULL)
        dbgval |= DBG_ALL;
    if (strstr (str, "csf") != NULL)
        dbgval |= DBG_CSF;
    if (strstr (str, "io") != NULL)
        dbgval |= DBG_IO;
    if (strstr (str, "mem") != NULL)
        dbgval |= DBG_MEM;
    if (strstr (str, "misc") != NULL)
        dbgval |= DBG_MISC;
    if (strstr (str, "search") != NULL)
        dbgval |= DBG_SRCH;

    return (dbgval);
}                               /* checkdbg() */



/*-
**============================================================================
** checklong()
**
**  Examine a string representing an integer, usually provided as a value to
**  a command line option, and return the value as a long integer.
**  Return -1 if a valid integer was not parsed.
**============================================================================
*/
static long checklong (char *str)
{
    long                value;
    char               *endptr;
    int                 len;
    
    len = strlen (str);
    value = strtol (str, &endptr, 10);

    if (str == endptr)
        return (-1);
    if ((endptr - str) < len)
        return (-1);

    return (value);
}                               /* checklong() */



/*-
**============================================================================
** close_file()
**
**  Close a file - as long as it's open.
**============================================================================
*/
void close_file (const AlphaFile_T file_pointer)
{
    if (file_pointer != NULL)
        fclose (file_pointer);
}                               /* close_file() */



/*-
**============================================================================
** debug_msg()
**
**  Write a debugging message to the stderr stream, but only if the
**  appropriate debugging option has been selected.
**============================================================================
*/
void debug_msg (const int status, const char *printf_fmt, ...)
{
    va_list             printf_args;
    const char         *prefix;    

    switch (status) {
        case DBG_CSF:
            if ((Flag_debug & DBG_CSF) == 0) return;
            prefix = "D-CSF";
            break;
        case DBG_IO:
            if ((Flag_debug & DBG_IO) == 0) return;
            prefix = "D-I/O";
            break;
        case DBG_MEM:
            if ((Flag_debug & DBG_MEM) == 0) return;
            prefix = "D-MEM";
            break;
        case DBG_MISC:
            if ((Flag_debug & DBG_MISC) == 0) return;
            prefix = "D-MSC";
            break;
        case DBG_SRCH:
            if ((Flag_debug & DBG_SRCH) == 0) return;
            prefix = "D-SCH";
            break;
        default:
            return;
    }

    if (printf_fmt != NULL) {
        fprintf (stderr, "%s: ", prefix);
        va_start (printf_args, printf_fmt);
        vfprintf (stderr, printf_fmt, printf_args);
        va_end (printf_args);
        fprintf (stderr, "\n");
        fflush (stderr);
    }
}                               /* debug_msg () */


#ifndef KPATHSEA
/*-
**============================================================================
** find_file()
**
**  Search along a path for a file.  The search path is specified by the
**  value of an environment variable which can contain multiple directories,
**  separated by an appropriate delimiter.  The best known example is the
**  PATH environment variable found on DOS, OS/2 and Unix.
**
**  If the environment variable is not specified, or it has not been set,
**  a 'fallback' path will be used.  The current working directory will be
**  searched first, then directories along the search path define dby the
**  environment variable or fallback path.
**
**  If the file is found along the search path, a value of 0 is returned and 
**  the full file specification is stored in parameter full_file_spec.  If
**  the file was not found, -1 is returned.
**============================================================================
*/
int find_file (const char *envvar_name, const char *fallback_path,
               const char *filename, char *full_file_spec)
{
    char                c;
    int                 len = 0;
    char               *search_path;

    debug_msg (DBG_SRCH, "find_file: searching for file name `%s'",
               filename);
    debug_msg (DBG_SRCH,
               "find_file: environment variable for search path = `%s'", 
               (envvar_name == NULL) ? "NULL" : envvar_name);
    debug_msg (DBG_SRCH, "find_file: fallback path = `%s'",
               (fallback_path == NULL) ? "NULL" : fallback_path);

    if ((envvar_name == NULL) || (*envvar_name == '\0')) {
        search_path = NULL;
        debug_msg (DBG_SRCH, 
                   "find_file: no environment variable specified");    
    } else {
        search_path = getenv (envvar_name);
        debug_msg (DBG_SRCH, "find_file: %s = `%s'", envvar_name, 
                   (search_path == NULL) ? "NULL" : search_path);
    }

    /*
    ** If we have a NULL search path because an environment variable name was 
    ** not specified or specified but not defined, we use the fallback
    ** search path.
    */
    if (search_path == NULL) {
        debug_msg (DBG_SRCH, "find_file: using fallback search path `%s'", 
                   (fallback_path == NULL) ? "NULL" : fallback_path);
        search_path = (char *) fallback_path;
    }


    /*
    ** Work along the search path looking for the wanted file.  Stop when
    ** the first match is found.
    */
    while (1) {
        full_file_spec[len] = '\0';
        
        /*
        ** If the path is not empty, and it doesn't end in '/' or '\',
        ** append a trailing '/'.
        */
        if (len != 0) {
            c = full_file_spec[len - 1];
            if ((c != '/') && (c != '\\'))
                strcat (full_file_spec, "/");
        }

        /*
        ** Append the filename to the path and then test whether the
        ** file exists.  If not, return -1 and an empty string.
        */
        strcat (full_file_spec, filename);
        debug_msg (DBG_SRCH, "find_file: trying `%s'", full_file_spec);

        if (access (full_file_spec, 0) == 0)
            break;

        if ((search_path == NULL) || (*search_path == '\0')) {
            full_file_spec[0] = '\0';
            return (-1);
        }

        len = 0;

        for (;;) {
            if ((*search_path != PATH_DELIM) && (*search_path != '\0')) {
                if (*search_path == '\\')
                    full_file_spec[len] = '/';
                else
                    full_file_spec[len] = *search_path;

                ++len;
                ++search_path;    
            } else
                break;
        }                       /* end for (;;) */

        if (*search_path != '\0')
            ++search_path;
    }                           /* end while (1) */    

    debug_msg (DBG_SRCH, "find_file: returning `%s'", full_file_spec);
    return (0);
}                               /* find_file() */
#endif /* ! KPATHSEA */


/*-
**============================================================================
** mymalloc()
**
**  Allocate memory for a specified array and check whether the allocation 
**  was successful.  If not, issue an error message and cause the program
**  to stop with a fatal exit status.
**  Allocate at least 1 byte, otherwise malloc may return NULL.
**============================================================================
*/
void *mymalloc (const unsigned long bytes_required, const char *array_name)
{
    void               *ptr;

    ptr = malloc (bytes_required ? bytes_required : 1);

    if (ptr == NULL) {
        printf ("\nFatal error: couldn't allocate %lu bytes for array `%s'",
                bytes_required, array_name);
        mark_fatal ();
        debug_msg (DBG_MISC, "calling longjmp (Exit_Program_Flag) ... ");
        longjmp (Exit_Program_Flag, 1);
    } else {
        debug_msg (DBG_MEM, "allocated %7lu bytes for array `%s'",
	           bytes_required, array_name);
    }

    return (ptr);
}                               /* mymalloc () */



/*-
**============================================================================
** myrealloc()
**
**  Reallocate memory for a specified array and check whether the allocation 
**  was successful.  If not, issue an error message and cause the program
**  to stop with a fatal exit status.
**  Allocate at least 1 byte, otherwise realloc may return NULL.
**============================================================================
*/
void *myrealloc (void *old_ptr, const unsigned long bytes_required, const char *array_name)
{
    void               *ptr;

    if (old_ptr == NULL)
        return mymalloc (bytes_required, array_name);

    ptr = realloc (old_ptr, bytes_required ? bytes_required : 1);

    if (ptr == NULL) {
        printf ("\nFatal error: couldn't reallocate %lu bytes for array `%s'",
                bytes_required, array_name);
        mark_fatal ();
        debug_msg (DBG_MISC, "calling longjmp (Exit_Program_Flag) ... ");
        longjmp (Exit_Program_Flag, 1);
    } else {
        debug_msg (DBG_MEM, "reallocated %7lu bytes for array `%s'",
	           bytes_required, array_name);
    }

    return (ptr);
}                               /* myrealloc () */



/*-
**============================================================================
** open_ip_file()
**
**  Open a file for input after locating it using one of the five predefined
**  search strategies defined by the parameter "search_path".  This parameter
**  can take 5 values:
**
**  AUX_FILE  - used for locating .aux files.  Search for the file along the
**              path defined by the environment variable AUX_INPUT_ENVVAR or
**              if it is not defined, use the fallback path defined by 
**              AUX_INPUT_PATH.
**
**  BIB_FILE  - used for locating .bib files.  Search for the file along the
**              path defined by the environment variable BIB_INPUT_ENVVAR or
**              if it is not defined, use the fallback path defined by 
**              BIB_INPUT_PATH.
**
**  BST_FILE  - used for locating .bst files.  Search for the file along the
**              path defined by the environment variable BST_INPUT_ENVVAR or
**              if it is not defined, use the fallback path defined by 
**              BST_INPUT_PATH.
**
**  CSF_FILE  - used for locating .csf files.  Search for the file along the
**              path defined by the environment variable CSF_INPUT_ENVVAR or
**              if it is not defined, use the fallback path defined by 
**              CSF_INPUT_PATH.
**
**  NO_SEARCH_PATH  - only look for the file in the current directory.
**
**  Searching for the file is delegated to the function find_file(), and
**  only if it was successful, does this function attempt to open the file.
**============================================================================
*/
FILE *open_ip_file (Integer_T search_path)
{
    const char          *filename = (const char *) name_of_file;
#ifdef KPATHSEA
    string              full_filespec;
#else
    char                full_filespec[FILE_NAME_SIZE + 1];
#endif
    FILE               *fptr;
    int                 status;
      
    switch (search_path) {
        case AUX_FILE_SEARCH_PATH:
#ifdef KPATHSEA
	  status = ((full_filespec = kpse_find_file(filename, kpse_tex_format, false)) == NULL);
#else
            status = find_file (AUX_INPUT_ENVVAR, AUX_INPUT_PATH,
                                filename, full_filespec);
#endif
            break;
        case BIB_FILE_SEARCH_PATH:
#ifdef KPATHSEA
	  status = ((full_filespec = kpse_find_file(filename, kpse_bib_format, false)) == NULL);
#else
            status = find_file (BIB_INPUT_ENVVAR, BIB_INPUT_PATH,
                                filename, full_filespec);
#endif
            break;
        case BST_FILE_SEARCH_PATH:
#ifdef KPATHSEA
	  status = ((full_filespec = kpse_find_file(filename, kpse_bst_format, false)) == NULL);
#else
            status = find_file (BST_INPUT_ENVVAR, BST_INPUT_PATH,
                                filename, full_filespec);
#endif
            break;
        case CSF_FILE_SEARCH_PATH:
#ifdef KPATHSEA
	  status = ((full_filespec = kpse_find_file(filename, kpse_bst_format, false)) == NULL);
#else
            status = find_file (CSF_INPUT_ENVVAR, CSF_INPUT_PATH,
                                filename, full_filespec);
#endif
            break;
        case NO_SEARCH_PATH:
        default:
#ifdef KPATHSEA
	  status = ((full_filespec = kpse_find_file(filename, kpse_program_text_format, false)) == NULL);
#else
            status = find_file (NULL, NULL, filename, full_filespec);
#endif
            break;
    }                           /* end switch (search_path) */

    /*
    ** find_file() will return zero if the file was found somewhere
    ** along the search path.  If it does, try to open the file.
    */
    if (status == 0) {
        debug_msg (DBG_IO, "open_ip_file: trying to open `%s' ... ", 
                   full_filespec);
#ifdef KPATHSEA
	if (!kpse_in_name_ok(full_filespec))
	    goto not_ok;
	fptr = fopen (full_filespec, FOPEN_R_MODE);
	free (full_filespec);
#else
# if defined(MSDOS) || defined(OS2)
        fptr = fopen (full_filespec, "rt");
# endif

# if defined(UNIX) || defined(VMS)
        fptr = fopen (full_filespec, "r");
#endif
#endif /* ! KPATHSEA */
    }

    /*
    ** Otherwise, return a NULL pointer.
    */
    else {
not_ok:
        debug_msg (DBG_IO, "open_ip_file: unable to open `%s' ... ", 
                   full_filespec);
        fptr = NULL;
    }
    return (fptr);
}                               /* open_ip_file() */



/*-
**============================================================================
** open_op_file()
**
**  Open a file for output in the current working directory.  If the operating 
**  system makes a distinction between text and non-text files, make sure
**  that the file is opened in "text" mode.
**============================================================================
*/
FILE *open_op_file (void)
{
    const char         *tmp_file_name = (const char *) name_of_file;
    FILE               *fptr;
      
    debug_msg (DBG_IO, "open_op_file: trying to open `%s' ... ",
               tmp_file_name);

    /*
    ** Make sure that we open the output file as a "text" file.  This
    ** varies according to the operating system.
    */
#if defined(KPATHSEA)
    if (kpse_out_name_ok(tmp_file_name))
	fptr = fopen(tmp_file_name, FOPEN_W_MODE);
    else
	fptr = NULL;
#else
# if defined(MSDOS) || defined(OS2)
    fptr = fopen (tmp_file_name, "wt");
# endif

# ifdef UNIX
    fptr = fopen (tmp_file_name, "w");
# endif

# ifdef VMS
    fptr = fopen (tmp_file_name, "w", "rfm=var", "rat=cr");
# endif
#endif /* ! KPATHSEA */

    if (fptr == NULL) {
        printf ("open_op_file: error opening `%s'\n", tmp_file_name);
    }
    return (fptr);
}                               /* open_op_file() */



/*-
**============================================================================
** parse_cmd_line()
**
**  Parse the command line used to launch BibTeX and set options accordingly.
**  Most of the work is done by the GNU getopt() function to support these
**  long and short options:
**
**      -?  --help              display this help text
**      -7  --traditional       operate in the traditional 7-bit mode
**      -8  --8bit              force 8-bit mode, even if no CS file
**      -c  --csfile FILE       read FILE as the BibTeX character set
**                              and sort definition file
**      -d  --debug TYPE        report debugging information.  TYPE is one or
**                              more of all, csf, io, mem, misc, search.
**      -s  --statistics        report internal statistics
**      -t  --trace             report execution tracing
**      -v  --version           report BibTeX version\n
**      -B  --big               set large BibTeX capacity
**      -H  --huge              set huge BibTeX capacity
**      -W  --wolfgang          set really huge BibTeX capacity for Wolfgang
**      -M  --min_crossrefs ##  set ## as the minimum number of cross refs
**                              required for automatic inclusion of the cross
**                              referenced entry in the citation list
**                              (default = 2)
**          --mcites ##         ignored
**          --mentints ##       ignored
**          --mentstrs ##       ignored
**          --mfields ##        ignored
**          --mpool ##          ignored
**          --mstrings ##       allow ## unique strings
**          --mwizfuns ##       ignored
**============================================================================
*/
void parse_cmd_line (int argc, char **argv)
{
    int                 c;
    int                 no_files;

    Flag_7bit = FALSE;
#ifdef UTF_8
    Flag_8bit = TRUE;
#else
    Flag_8bit = FALSE;
#endif
    Flag_big = FALSE;
    Flag_debug = FALSE;
    Flag_huge = FALSE;
    Flag_wolfgang = FALSE;
    Flag_stats = FALSE;
    Flag_trace = FALSE;
    Str_auxfile = NULL;
    Str_csfile = NULL;
#ifdef UTF_8
    Flag_language = FALSE;
    Str_language = NULL;
    Flag_location = FALSE;
    Str_location = NULL;
#endif

    while (1) {
        int             option_index = 0;

        c = getopt_long (argc, argv, getopt_str, long_options, &option_index);

        if (c == EOF)
            break;

        switch (c) {
            case '?':       /**************** -?, --help ***************/
                usage (NULL);
                break;

#ifndef UTF_8
            case '7':       /**************** -7, --traditional ********/
                Flag_7bit = TRUE;
                break;

            case '8':       /**************** -8, --8bit ***************/
                Flag_8bit = TRUE;
                break;
#endif

            case 'B':       /**************** -B, --big ****************/
                Flag_big = TRUE;
                break;

#ifndef UTF_8
            case 'c':       /**************** -c, --csfile *************/
                Str_csfile = strdup (optarg);
                break;
#endif

            case 'd':       /**************** -d, --debug **************/
                if ((optarg == NULL) || (*optarg == '\0'))
                    Flag_debug = DBG_ALL;
                else
                    Flag_debug = checkdbg (optarg);
                break;

            case 'H':       /**************** -H, --huge ***************/
                Flag_huge = TRUE;
                break;

#ifdef UTF_8
            case 'l':       /**************** -l, --language ***********/
                Flag_language =TRUE;
                Str_language = optarg;
                break;
#endif

            case 'M':       /**************** -M, --min_crossrefs ******/
                M_min_crossrefs = checklong (optarg);
                if (M_min_crossrefs < 0) {
                    mark_fatal ();
                    usage ("invalid minimum cross references `%s'\n", optarg);
                }
                break;

#ifdef UTF_8
            case 'o':       /**************** -o, --location ***********/
                Flag_location =TRUE;
                Str_location = optarg;
                break;
#endif

            case 's':       /**************** -s, --statistics *********/
                Flag_stats = TRUE;
                break;

            case 't':       /**************** -t, --trace **************/
                Flag_trace = TRUE;
                break;

            case 'v':       /**************** -v, --version ************/
                FPRINTF (TERM_OUT, "%s-x%s (%s)\n", BANNER, EXT_VERSION, TL_VERSION);
                FPRINTF (TERM_OUT, "Implementation:  %s\n", IMPLEMENTATION);
                FPRINTF (TERM_OUT, "Release version: %s\n", VERSION);
#ifdef UTF_8
                {
                    UVersionInfo icuVersion;
                    char icu_version[U_MAX_VERSION_STRING_LENGTH] = "";
                    u_getVersion(icuVersion);
                    u_versionToString(icuVersion, icu_version);
                    FPRINTF (TERM_OUT, "Compiled with:   ICU version %s\n", icu_version);
                }
#endif

                debug_msg (DBG_MISC, 
                           "calling longjmp (Exit_Program_Flag) ... ");
                longjmp (Exit_Program_Flag, 1);
                break;

            case 'W':       /**************** -W, --wolfgang ***********/
                Flag_wolfgang = TRUE;
                break;

            case '\x0A':    /**************** --mcites *****************/
            case '\x0B':    /**************** --mentints ***************/
            case '\x0C':    /**************** --mentstrs ***************/
            case '\x0D':    /**************** --mfields ****************/
            case '\x0E':    /**************** --mpool *****************/
            case '\x10':    /**************** --mwizfuns ***************/
                (void) checklong (optarg);   /******** ignored ********/
                break;

            case '\x0F':    /**************** --mstrings ***************/
                M_strings = checklong (optarg);
                if (M_strings < 0) {
                    mark_fatal ();
                    usage ("invalid max number of strings `%s'\n", optarg);
                }
                break;

	    default:        /**************** Unknown argument ********/
                mark_fatal ();
                usage ("unknown option");
        }                       /* end switch (c) */
    }                           /* end while (1) */


    /*
    ** Check that a single .aux file was specified.
    */
    no_files = argc - optind;

    if (no_files == 1)
        Str_auxfile = argv[optind];
    else if (no_files < 1) {
        mark_fatal ();
        usage ("no aux file specified");
    } else {
        mark_fatal ();
        usage ("only one aux file may be specified");
    }

    /*
    ** Check for contradictory options
    */
    if (Flag_7bit && Flag_8bit) {
        mark_fatal ();
        usage ("can't specify --traditional and --8bit");
    }

    if ((Flag_big && Flag_huge) || (Flag_big && Flag_wolfgang)
                                || (Flag_huge && Flag_wolfgang)) {
        mark_fatal ();
        usage ("can only specify one of --big, --huge and --wolfgang");
    }

    if ((Str_csfile != NULL) && (Flag_7bit)) {
        mark_fatal ();
        usage ("can't specify --csfile and --traditional");
    }

    if ((Str_csfile != NULL) && (Flag_8bit)) {
        mark_fatal ();
        usage ("can't specify --csfile and --8bit");
    }
    
    /*
    ** Check for options which have not been #defined into the code
    */
#ifndef DEBUG
    if (Flag_debug)
        FSO ("BibTeX: debugging support was not compiled into this version\n");
#endif                          /* DEBUG */

#ifndef STAT
    if (Flag_stats)
        FSO ("BibTeX: statistics support was not compiled into this version\n");
#endif                          /* STAT */

#ifndef TRACE 
    if (Flag_trace)
        FSO ("BibTeX: tracing support was not compiled into this version\n");
#endif                          /* TRACE */


}                               /* parse_cmd_line() */



/*-
**============================================================================
** report_bibtex_capacity()
**
**  If the log file has been opened, indicate BibTeX's capacity by reporting
**  the size/values of some key values and arrays.  Anything in mixed case
**  has been set at run-time.
**============================================================================
*/
void report_bibtex_capacity (void)
{
    if (log_file != NULL) {
        FPRINTF (log_file, "BibTeX's capacity set as follows:\n\n");
        LOG_CAPACITY (AUX_STACK_SIZE);
        LOG_CAPACITY (Buf_Size);
        LOG_CAPACITY (Ent_Str_Size);
        LOG_CAPACITY (Glob_Str_Size);
        LOG_CAPACITY (Hash_Prime);
        LOG_CAPACITY (Hash_Size);
        LOG_CAPACITY (Lit_Stk_Size);
        LOG_CAPACITY (Max_Bib_Files);
        LOG_CAPACITY (Max_Cites);
        LOG_CAPACITY (Max_Fields);
        LOG_CAPACITY (Max_Glob_Strs);
        LOG_CAPACITY (MAX_PRINT_LINE);
        LOG_CAPACITY (Max_Strings);
        LOG_CAPACITY (Min_Crossrefs);
        LOG_CAPACITY (MIN_PRINT_LINE);
        LOG_CAPACITY (Pool_Size);
        LOG_CAPACITY (SINGLE_FN_SPACE);
        LOG_CAPACITY (Wiz_Fn_Space);
        FPRINTF (log_file, "\n");
    }
}                               /* report_bibtex_capacity() */



/*-
**============================================================================
** report_search_paths()
**
**  Report the environment variables and fallback search paths used by
**  BibTeX for locating input files.
**============================================================================
*/
void report_search_paths (void)
{
#ifndef KPATHSEA
    /*
    ** Define a couple of useful macros to simplify reporting of the desired
    ** information.
    */
#define REPORT_SEARCH_PATH(_a,_b,_c) \
    debug_msg (DBG_SRCH, "Search strategy for %s files:", _a); \
    debug_msg (DBG_SRCH, "  search path environment variable: %s", NULLCHECK (_b)); \
    if (! ISEMPTYSTR (_b)) { \
        char *ptr; \
        ptr = getenv (_b); \
        debug_msg (DBG_SRCH, "  %s value: %s", _b, NULLCHECK (ptr)); \
    } \
    debug_msg (DBG_SRCH, "  fallback search path: %s", NULLCHECK (_c));


#define REPORT_SEARCH_FILE(_a,_b,_c) \
    debug_msg (DBG_SRCH, "Default %s file:", _a); \
    debug_msg (DBG_SRCH, "  file name environment variable: %s", NULLCHECK (_b)); \
    if (! ISEMPTYSTR (_b)) { \
        char *ptr; \
        ptr = getenv (_b); \
        debug_msg (DBG_SRCH, "  %s value: %s", _b, NULLCHECK (ptr)); \
    } \
    debug_msg (DBG_SRCH, "  fallback file name: %s", NULLCHECK (_c));

    /*
    ** Now report the information.
    */
    REPORT_SEARCH_PATH (".aux", AUX_INPUT_ENVVAR, AUX_INPUT_PATH);
    REPORT_SEARCH_PATH (".bib", BIB_INPUT_ENVVAR, BIB_INPUT_PATH);
    REPORT_SEARCH_PATH (".bst", BST_INPUT_ENVVAR, BST_INPUT_PATH);
    REPORT_SEARCH_PATH (".csf", CSF_INPUT_ENVVAR, CSF_INPUT_PATH);

    REPORT_SEARCH_FILE (".csf", CSF_FILE_ENVVAR, CSF_FILE_NAME);
#endif /* ! KPATHSEA */
}                               /* report_search_paths() */


/*-
**============================================================================
** setup_bound_variable()
**
**  Obtain parameters from environment or configuration file,
**  or use the default value.
**============================================================================
*/
static void setup_bound_variable (Integer_T *var, const char *name,
                                  unsigned long def_value)
{
#ifdef KPATHSEA
    char *expansion = kpse_var_value (name);
    const char *me = kpse_invocation_name;
    const char *src = " or texmf.cnf";
#else
    char *expansion = getenv (name);
    const char *me = PROGNAME;
    const char *src = "";
#endif

    *var = def_value;
    if (expansion) {
        int conf_val = atoi (expansion);
        if (conf_val < def_value)
            fprintf (stderr,
            "%s: Bad value (%ld) in environment%s for %s, keeping %ld.\n",
            me, (long) conf_val, src, name, def_value);
        else
            *var = conf_val;
#ifdef KPATHSEA
        free (expansion);
#endif
    }
}                               /* setup_bound_variable() */

/*-
**============================================================================
** setup_params()
**
**  Determine |ent_str_size|, |glob_str_size|, and |max_strings| from the
**  environment, configuration file, or default value.  Set
**  |hash_size:=max_strings|, but not less than |HASH_SIZE|.
**============================================================================
*/
static void setup_params (void)
{
    setup_bound_variable (&Ent_Str_Size, "ent_str_size", ENT_STR_SIZE);
    setup_bound_variable (&Glob_Str_Size, "glob_str_size", GLOB_STR_SIZE);
    setup_bound_variable (&Max_Strings, "max_strings", MAX_STRINGS);

    /* Obsolete: Max_Strings specified via command line.  */
    if (Flag_big)
        Max_Strings = 10000;
    if (Flag_huge)
        Max_Strings = 19000;
    if (Flag_wolfgang)
        Max_Strings = 30000;
    if (M_strings > 0)
        Max_Strings = M_strings;

    if (Max_Strings < MAX_STRINGS)
        Max_Strings = MAX_STRINGS;
    Hash_Size = Max_Strings;
    if (Hash_Size < HASH_SIZE)
        Hash_Size = HASH_SIZE;
}                               /* setup_params() */


/*-
**============================================================================
** compute_hash_prime()
**
**  We use the algorithm from Knuth's \.{primes.web} to compute
**  |hash_prime| as the smallest prime number not less than 85\%
**  of |hash_size| (and |>=128|).
**
**  This algorithm uses two arrays |primes| and |mult|.  We use the
**  already allocated |hash_next| and |hash_text| for that purpose.
**============================================================================
*/
#define primes hash_next
#define mult hash_text
static void compute_hash_prime (void)
{
    Integer32_T Hash_Want = (Hash_Size / 20) * 17; /* 85\% of |hash_size| */
    Integer32_T k = 1; /*number of prime numbers $p_i$ in |primes| */
    Integer32_T j = 1; /* a prime number candidate */
    Integer32_T o = 2; /* number of odd multiples of primes in |mult| */
    Integer32_T square = 9; /* $p_o^2$ */
    Integer32_T n; /* loop index */
    Boolean_T j_prime; /* is |j| a prime? */

    debug_msg (DBG_MEM, "Computing Hash_Prime ... ");

    primes[k] = Hash_Prime = 2;
    while (Hash_Prime < Hash_Want) {
        do {
            j += 2;
            if (j == square) {
                mult[o++] = j;
                j += 2;
                square = primes[o] * primes[o];
            }
            n = 2;
            j_prime = TRUE;
            while ((n < o) && j_prime) {
                while (mult[n] < j)
                    mult[n] += 2 * primes[n];
                if (mult[n++] == j)
                    j_prime = FALSE;
            }
        } while (!j_prime);
        primes[++k] = Hash_Prime = j;
    }
}                               /* compute_hash_prime() */


/*-
**============================================================================
** set_array_sizes()
**
**  Allocate memory dynamically for the large arrays whose size is set
**  dynamically, depending on the amount memory of memory available.
**
**  On virtual memory operating systems, there is not normally a problem
**  allocating memory; the array sizes can be predetermined and memory
**  allocated at run-time.
**
**    Parameter       Cmd   Standard       --big      --huge  --wolfgang
**    ------------------------------------------------------------------
**    Buf_Size        *** initialy 20000, increased as required ***
**    Hash_Prime      *** computed from Hash_Size ***
**    Hash_Size       *** determined from Max_Strings ***
**    Max_Bib_Files   *** initialy 20, increased as required ***
**    Max_Cites       *** initialy 750, increased as required ***
**    Max_Ent_Ints    *** as required ***
**    Max_Ent_Strs    *** as required ***
**    Max_Fields      *** initialy 5000, increased as required ***
**    Max_Strings     Y        4,000      10,000      19,000      30,000
**    Pool_Size       *** initialy 65,000, increased as required ***
**    Wiz_Fn_Space    *** initialy 3000, increased as required ***
**    ------------------------------------------------------------------
**
**============================================================================
*/
void set_array_sizes (void)
{
    debug_msg (DBG_MEM, "Setting BibTeX's capacity ... ");

    Max_Strings = 4000;
    Min_Crossrefs = 2;

    setup_params ();
    Buf_Size = BUF_SIZE;

    Lit_Stk_Size = LIT_STK_SIZE;

    Max_Bib_Files = MAX_BIB_FILES;

    Max_Cites = MAX_CITES;

    Max_Fields = MAX_FIELDS;

    if (M_min_crossrefs > 0)
        Min_Crossrefs = M_min_crossrefs;

    Pool_Size = POOL_SIZE;

    Wiz_Fn_Space = WIZ_FN_SPACE;


    allocate_arrays ();
    compute_hash_prime ();


    debug_msg (DBG_MEM, "Hash_Prime = %d, Hash_Size = %d", 
               Hash_Prime, Hash_Size);
    debug_msg (DBG_MEM, "Buf_Size = %d, Max_Bib_Files = %d", 
               Buf_Size, Max_Bib_Files);
    debug_msg (DBG_MEM, "Max_Cites = %d, Max_Fields = %d", 
               Max_Cites, Max_Fields);
    debug_msg (DBG_MEM, "Max_Strings = %d, Pool_Size = %d",
               Max_Strings, Pool_Size);
    debug_msg (DBG_MEM, "Min_Crossrefs = %d, Wiz_Fn_Space = %d", 
               Min_Crossrefs, Wiz_Fn_Space);


    /*-
    ** Check that the key values are sensible.  These checks are stolen
    ** from initialize(), but the error messages are more informative.
    */
    debug_msg(DBG_MISC, "Sanity checking capacity values ... ");
    
    if (Hash_Prime < 128)
        usage ("Hash_Prime (%d) must be >= 128", Hash_Prime);
        
    if (Hash_Prime > Hash_Size)
        usage ("Hash_Prime (%d) must be <= Hash_Size (%d)",
               Hash_Prime, Hash_Size);

    /*
    ** The original WEB version of BibTeX imposed a maximum value of
    ** 16321 on Hash_Prime.  A WEB constant was defined:
    **
    **   MAX_HASH_VALUE = (Hash_Prime + Hash_Prime - 2 + 127)
    **
    ** but, because WEB constants are limited to 32767, Hash_Prime was
    ** consequently limited to a maximum of (32767 + 2 - 127) / 2 = 16321.
    **
    ** We're using C #defines, so that limit doesn't apply.  The
    ** following original sanity check is therefore commented out.
    **
    **   if (Hash_Prime >= (16384 - 64))
    **     usage ("Hash_Prime (%d) must be < %d", Hash_Prime, (16384-64));
    */

    if (Max_Strings > Hash_Size)
        usage ("Max_Strings (%d) must be <= Hash_Size (%d)",
               Max_Strings, Hash_Size);

    if (Max_Cites > Max_Strings)
        usage ("Max_Cites (%d) must be <= Max_Strings (%d)",
               Max_Cites, Max_Strings);

}                               /* set_array_sizes() */



/*-
**============================================================================
** usage()
**
**  Display a helpful message reporting the command line options
**  supported by this version of BibTeX.  If a message string is
**  provided, this is displayed first.
**============================================================================
*/
void usage (const char *printf_fmt, ...)
{
    va_list             printf_args;
    
    if (printf_fmt != NULL) {
#ifdef UTF_8
        fprintf (stderr, "BibTeXu: ");
#else
        fprintf (stderr, "BibTeX8: ");
#endif
        va_start (printf_args, printf_fmt);
        vfprintf (stderr, printf_fmt, printf_args);
        va_end (printf_args);
        fprintf (stderr, "\n");
    }

    FSO ("\nUsage: " PROGNAME " [options] aux-file\n\n");
    FSO ("  Valid options are:\n\n");
    FSO ("  -?  --help              display this help text\n");

#ifndef UTF_8
    FSO ("  -7  --traditional       operate in the original 7-bit mode\n");
    FSO ("  -8  --8bit              force 8-bit mode, no CS file used\n");
    FSO ("  -c  --csfile FILE       read FILE as the BibTeX character set\n");
    FSO ("                          and sort definition file\n");
#endif

#ifdef DEBUG
    FSO ("  -d  --debug TYPE        report debugging information.  TYPE is one\n");
    FSO ("                          or more of all, csf, io, mem, misc, search.\n");
#endif                          /* DEBUG */

#ifdef STAT
    FSO ("  -s  --statistics        report internal statistics\n");
#endif                          /* STAT */

#ifdef TRACE
    FSO ("  -t  --trace             report execution tracing\n");
#endif                          /* TRACE */

    FSO ("  -v  --version           report BibTeX version\n\n");

#ifdef UTF_8
    FSO ("  -l  --language LANG     use language LANG, ex: -l fr\n");
    FSO ("  -o  --location LOC      use location LOC, ex: -o fr\n\n");
#endif

    FSO ("  -B  --big               same as --mstrings 10000\n");
    FSO ("  -H  --huge              same as --mstrings 19000\n");
    FSO ("  -W  --wolfgang          same as --mstrings 30000\n");
    FSO ("  -M  --min_crossrefs ##  set min_crossrefs to ##\n");
    FSO ("      --mstrings ##       allow ## unique strings\n");
    FSO ("\nEmail bug reports to " PACKAGE_BUGREPORT ".\n");

    debug_msg (DBG_MISC, "calling longjmp (Exit_Program_Flag) ... ");
    longjmp (Exit_Program_Flag, 1);
}                               /* usage() */



/*-
******************************************************************************
******************************************************************************
**
**  Functions specifically for 8-bit support.
**
**      c8read_csf
**      c8initialise
**      c8read_lowercase
**      c8read_lowupcase
**      c8read_uppercase
**      c8read_order
**      c8report_8bit_handling
**
******************************************************************************
******************************************************************************
*/
#ifdef SUPPORT_8BIT

/*
** Useful definitions.
*/
#define c8is_alpha(c)	        (isalpha(c) || c > 127)
#define c8is_alphanumeric(c)	(isalnum(c) || c > 127)
#define SORT_LAST               4095



/*-
**============================================================================
** c8initialise ()
**
**  Initialise BibTeX's handling of 8-bit character sets.  The steps are:
**
**      o   initialise default behaviour for sorting and upper/lowercase
**      o   if appropriate, read and parse the CS file
**      o   use CS data to set behaviour for sorting and upper/lowercase
**      o   update xchr/xord arrays for 8-bit characters.  These arrays
**          are initialised for ASCII character sets in the original
**          initialize() function.  Further initialisation for 8-bit
**          characters is performed here to minimise "contamination" of
**          the original BibTeX code.
**
**  We have to deal with three modes of operation:
**
**      --traditional - the original BibTeX behaviour
**      --8bit        - every character with a code > 127 is treated as 
**                      a letter
**      default       - a CS file is read and parsed to define sorting order
**                      and how 8-bit characters should be handled
**============================================================================
*/
void c8initialise (void)
{
    int                 ascii_case_diff = 'a' - 'A';
    int                 c;
    int                 status;

    /*
    ** Traditional mode (--traditional, -7) results in identical behaviour 
    ** to the original BibTeX.
    **
    **  o   sort order by character code value ('A'=65 ... 'z'=122)
    **  o   upper/lowercase relationships for A..Za..z established
    **  o   no CS file read
    **  o   only characters < 128 allowed
    **  o   no need to update xchr/xord arrays
    */
    if (Flag_7bit) {

        for (c = 0; c <= 255; c++) {
            c8order[c] = c;
            c8lowcase[c] = (UChar_T) c;
            c8upcase[c] = (UChar_T) c;
        }

        for (c = 'A'; c <= 'Z'; c++) {
            c8lowcase[c] = (UChar_T) (c + ascii_case_diff);
            c8upcase[c + ascii_case_diff] = (UChar_T) c;
        }

        for (c = 128; c <= 255; c++)
            lex_class[c] = ILLEGAL;
    } 

    /*
    ** Raw 8-bit (--8bit, -8).
    ** 
    **  o   sort order by character code value ('A'=65 ... 'z'=122)
    **  o   upper/lowercase relationships for A..Za..z established
    **  o   no CS file read
    **  o   need to update xchr/xord arrays
    **  o   characters > 127 allowed and all treated as type ALPHA
    */
    else if (Flag_8bit) {

        for (c = 0; c <= 255; c++) {
            c8order[c] = c;
            c8lowcase[c] = (UChar_T) c;
            c8upcase[c] = (UChar_T) c;
        }

        for (c = 'A'; c <= 'Z'; c++) {
            c8lowcase[c] = (UChar_T) (c + ascii_case_diff);
            c8upcase[c + ascii_case_diff] = (UChar_T) c;
        }

        for (c = 128; c <= 255; c++)
            xchr[c] = (char) c;

        for (c = 128; c <= 255; c++)
            xord[xchr[c]] = (ASCIICode_T) c;

        for (c = 128; c <= 255; c++)
            lex_class[c] = ALPHA;
    }

    /*
    ** Default initialisation using CS file.
    ** 
    **  o   no sort order defined - all set to weight SORT_LAST
    **  o   upper/lowercase relationships for A..Za..z established
    **  o   only characters < 128 allowed initially
    **  o   a CS file is read and parsed by BibTeX to modify these defaults
    **  o   need to update xchr/xord/lex_class arrays after reading CS file
    */
    else {

        for (c = 0; c <= 255; c++) {
            c8order[c] = SORT_LAST;
            c8lowcase[c] = (UChar_T) c;
            c8upcase[c] = (UChar_T) c;
        }

        c8order[32] = 0;

        for (c = 'A'; c <= 'Z'; c++) {
            c8lowcase[c] = (UChar_T) (c + ascii_case_diff);
            c8upcase[c + ascii_case_diff] = (UChar_T) c;
        }

        for (c = 128; c <= 255; c++)
            xchr[c] = (char) c;

        for (c = 128; c <= 255; c++)
            xord[xchr[c]] = (ASCIICode_T) c;

        for (c = 128; c <= 255; c++)
            lex_class[c] = ILLEGAL;

        /*
        ** Read the CS file.  If it was read and parsed OK, update the
        ** definition of 8-bit characters.  If an 8-bit character is defined 
        ** in any section of the CS file, it is treated as type ALPHA.
        */
        if ((status = c8read_csf ()) == TRUE) {

            for (c = 128; c <= 255; c++)
                if ((c8order[c] != SORT_LAST)
                        || (c8lowcase[c] != c) 
                        || (c8upcase[c] != c))
                    lex_class[c] = ALPHA;                    
        }
    }                           /* end if (Flag_7bit) */

    /*
    ** If the CSF debugging option has been selected, report results.
    */
    if ((Flag_debug & DBG_CSF))
        c8report_8bit_handling ();

}                               /* c8initialise () */



/*-
**============================================================================
** c8read_csf()
**
**  Read the Case and Sorting definitions (CS) File.  Steps taken are:
**
**      o   determine which CS file should be opened
**      o   open the CS file
**      o   look for valid CS file sections (\lowupcase, \lowercase, 
**          \uppercase, \order) and delegate parsing of each section to the
**          appropriate function
**      o   close the CS file
**
**  Returns TRUE if a CS file was successfully read and parsed, FALSE
**  otherwise.
**============================================================================
*/
int c8read_csf (void)
{
    int                 c, i;
    int                 in_comment;
#define SX_BUF_SIZE 15
    /* a section name: "order", "uppercase", "lowercase", or "lowupcase".  */
    char                sx[SX_BUF_SIZE + 1];
#ifndef KPATHSEA
    char               *value;
#endif
    
    /*
    ** Construct the name of the CS file to be used:
    **
    **  1.  If the --csfile option was specified, use its value.
    **  or
    **  2.  If CSF_FILE_ENVVAR is defined to a non-empty value, it
    **      contains the name of an environment variable.  The value of 
    **      this environment variable will be used as the default CS file.
    */
    if (Str_csfile == NULL) {
#ifdef KPATHSEA
#ifndef CSF_FILE_ENVVAR
#define CSF_FILE_ENVVAR "BIBTEX_CSFILE"
#endif
      /* Default value from environment or texmf.cnf */
      Str_csfile = kpse_var_value (CSF_FILE_ENVVAR);
      if (Str_csfile == NULL)  /* Use fallback value */
          Str_csfile = xstrdup ("88591lat.csf");
#else
        if (strlen (CSF_FILE_ENVVAR) != 0) {
            debug_msg (DBG_CSF, "c8read_csf: --csfile not set, checking '%s'", 
                       CSF_FILE_ENVVAR);
            value = getenv (CSF_FILE_ENVVAR);            

            if (value == NULL)
                debug_msg (DBG_CSF, "c8read_csf: %s = `NULL'", CSF_FILE_ENVVAR);
            else
                debug_msg (DBG_CSF, "c8read_csf: %s = `%s'",
                           CSF_FILE_ENVVAR, value);
            /*
            ** If the environment variable has a non-empty value, use it
            ** as the file specifaction for the default CS file.
            */
            if ((value != NULL) && (*value != '\0'))
                Str_csfile = strdup (value);
            else {
                PRINT_LN ("Warning: cannot open CS file: --csfile not specfied");
                PRINT_LN2 ("and environment variable %s has no value.", 
                           CSF_FILE_ENVVAR);
                return FALSE;
            }
        } else {
            PRINT_LN ("Warning: cannot open CS file: --csfile not specified");
            PRINT_LN ("and default environment variable not specified.");
            return FALSE;
        }                       /* if (strlen (CSF_FILE_ENVVAR) != 0) */
#endif /* KPATHSEA */
    }                           /* if (Str_csfile == NULL) */
    /*
    ** Make sure that we have ended up with a CS file name.  If not,
    ** return FALSE;
    */
    if ((Str_csfile == NULL) || (*Str_csfile == '\0')) {
        PRINT_LN ("Warning: cannot open CS file: empty --csfile value specified.");
        return FALSE;
    }

    /*
    ** If the CS file name doesn't contain a '.', append ".csf".
    */
    free (name_of_file);
    name_of_file = (unsigned char *) mymalloc (strlen (Str_csfile) + 5, "name_of_file");
    strcpy ((char *)name_of_file, Str_csfile);

    if (strchr ((char *)name_of_file, '.') == NULL)
        strcat ((char *)name_of_file, ".csf");
    name_length = strlen ((char *)name_of_file);

    debug_msg (DBG_CSF, "c8read_csf: trying to open CS file `%s' ...",
               name_of_file);

    /*
    ** Attempt to open the CS file, using the CS file search path if
    ** necessary.  If the open fails, return FALSE.
    */
    if (! a_open_in (&c8_cs_file, CSF_FILE_SEARCH_PATH)) {
        PRINT_LN2 ("Error: cannot open CS file: %s", name_of_file);
        return FALSE;
    }
    PRINT_LN2 ("The 8-bit codepage and sorting file: %s", name_of_file);

    /*
    ** The main loop for reading the CS file.
    */
    c8_line = 1;
    while (! feof (c8_cs_file)) {

        /*
        ** Read the CS file until a '\\' character is found, denoting the
        ** start of a major section.  Comments begin with a '%' and
        ** anything else on the same line is ignored.
        */
        in_comment = FALSE;
        do {
            c = getc (c8_cs_file);

            if (c == '\n') {
                ++c8_line;
                in_comment = FALSE;
            }
            if (c == '%')
                in_comment = TRUE;
            if ((c == '\\') && (in_comment == FALSE))
                break;
        } while (!feof (c8_cs_file));

        /*
        ** If we break out of the do loop on EOF, there were no more 
        ** sections in the CS file.  Exit the main reading loop.
        */
        if (feof (c8_cs_file))
            break;

        /*
        ** After '\', a section name must be found.  This name is a word
        ** composed only of lower case ASCII letters.
        */
        i = 0;

        do {
            c = getc (c8_cs_file);
            if ((c >= 'a') && (c <= 'z'))
                sx[i++] = c;
            else
                break;
        } while (!feof (c8_cs_file) && (i < SX_BUF_SIZE));
        sx[i] = '\0';

        /*
        ** A '{' should follow the section name, but it could be preceeded 
        ** by whitespace, comments or newlines.
        */
	if (c != '{')
	    do {
		if (c == '\n') {
		    ++c8_line;
		    in_comment = FALSE;
		}
		if (c == '%')
		    in_comment = TRUE;
		if ((c == '{') && (in_comment == FALSE))
		    break;
		c = getc (c8_cs_file);
	    } while (!feof (c8_cs_file));


        /*
        ** The major sections supported in the CS file are: \order{},
        ** \uppercase{}, \lowercase{} and \lowupcase{}.  Anything else will
        ** be ignored.  Known sections are processed until they terminate
        ** and then the loop starts again.
        */
        if (strcmp (sx, "order") == 0)
            c8read_order ();
        else if (strcmp (sx, "uppercase") == 0)
            c8read_uppercase ();
        else if (strcmp (sx, "lowercase") == 0)
            c8read_lowercase ();
        else if (strcmp (sx, "lowupcase") == 0)
            c8read_lowupcase ();
        else {
            PRINT_LN2 ("Unknown section on line in CS file %s", Str_csfile);
            PRINT_LN3 ("%04d: \\%s", c8_line, buffer);
        }
    }				/* while (! feof (c8_cs_file)) */

    /*
    ** Finished reading, close the CS file then return TRUE.
    */
    close_file (c8_cs_file);
    free (Str_csfile);
    return TRUE;
}				/* c8read_csf () */



/*-
**============================================================================
** c8read_lowercase()
**
**  Read and parse the \lowercase section of the CS file.  This section is
**  used to define the upper -> lower case relationship of pairs of specified 
**  characters.  It should normally only be used if the relationship
**  isn't symmetrical - use \lowupcase if it is.
**
**  The syntax of the \lowercase section is:
**
**      \lowercase{
**          <UC-val-1> <LC-val-1>   % Comment begins with a percent sign
**          <UC-val-2> <LC-val-2>  <UC-val-3> <LC-val-3>
**          ...
**          <UC-val-N> <LC-val-N>
**      }
**
**  Notes:
**
**  o   characters can be entered normally, or in TeX notation for
**      character codes, i.e. ^^XX, where XX is the hexadecimal value.
**
**  o   reading of the \lowercase section ends when the first '}' character 
**      is reached, so '}' can't be included in a case relationship pair.
**      You can't use ^^7d either.
**
**  o   '%' is used to introduce a trailing comment, so '%' can't be included 
**      in the \lowercase section.  You can't use ^^25 either.
**
**  o   you cannot redefine the lower case equivalent of an ASCII character 
**      (code < 128).  In other words, all "left hand" characters in the
**      relationship pairs must have codes > 127.
**============================================================================
*/
static void c8read_lowercase (void)
{
    int                 c;
    int                 uppercase_val;

    debug_msg (DBG_CSF, "reading the \\lowercase section ... ");
    uppercase_val = -1;

    /*
    ** Keep reading characters until the end of the section is encountered 
    ** at a '}' or EOF is reached.
    */
    c = getc (c8_cs_file);

    while ((c != '}') && !feof (c8_cs_file)) {

        /*
        ** Characters may be specified using the TeX syntax for the
        ** 8-bit code, i.e. ^^XX, where XX is the hexadecimal value.
        ** We should really check the result of this fscanf().  Read the
        ** character code and then start the while loop again.
        */
        if (c == '^')
            fscanf (c8_cs_file, "^%x", &c);

        /*
        ** If we have reached the end of a line, simply increment the
        ** line count.
        */
        if (c == '\n') {
            c8_line++;
        }

        /*
        ** If we encounter a '%', it signifies the start of a comment.
        ** Ignore characters until the end of line is reached.
        */
        else if (c == '%') {
            do {
                c = getc(c8_cs_file);
            } while ((c != '\n') && (! feof( c8_cs_file)));
            continue;
        }

        /*
        ** The normal case is for alpha characters to be included in the
        ** case relationship.  Alpha characters are assumed to be ASCII
        ** alpha (A..Za..z) plus any character with a code > 127.
        */
        else if (c8is_alpha (c)) {

            /*
            ** The case relationship consists of a pair of alpha characters 
            ** separated by whitespace.  In the \lowercase section, the pair
            ** is in the order <uppercase-char> <lowercase-char>.  We
            ** must remember which character we are looking for - this is
            ** achieved by setting uppercase_val to -1 when we are
            ** looking for the first character of a pair.
            **
            ** If uppercase_val is < 0, we treat the current character as
            ** the upper case member of the pair and set uppercase_val
            ** accordingly.
            */
            if (uppercase_val < 0)
                uppercase_val = c;
            else {

                /*
                ** We are not allowed to redefine the lower case
                ** equivalent of an ASCII character. 
                */
                if (uppercase_val < 127) {
                    PRINT_LN2 ("Error: attempt to redefine lower case of "
                               "an ASCII character [0x%02x]", uppercase_val);
                    PRINT_LN3 ("Context: line %04d: of CS file %s", 
                               c8_line, Str_csfile);
                }

                /*
                ** All OK, so set the appropriate element of the c8lowcase 
                ** array and reset the value of uppercase_val to signify
                ** that we are looking for the next pair.
                */
                else {
                    c8lowcase[uppercase_val] = c;
                    uppercase_val = -1;
                }
            }
        }                       /* end if */

        /*
        ** Get the next character from the file and start the loop again.
        */
        c = getc (c8_cs_file);
    }				/* end while */

    /*
    ** The only normal reason for breaking out of the while loop is that
    ** we encountered '}' at the end of the \lowercase section.  Two
    ** conditions should be true:
    **
    **   (uppercase_val = -1)   - there is an unmatched character
    **   (c == '}')             - the terminating '}' has been read
    */
    if (uppercase_val != -1){
	PRINT_LN ("Error: unmatched character pair in \\lowercase section");
	PRINT_LN3 ("Context: line %04d: of CS file %s", c8_line, Str_csfile);
    }

    if (c != '}') {
        PRINT_LN ("Error: unexpected end of file in \\lowercase section");
        PRINT_LN3 ("Context: line %04d: of CS file %s", c8_line, Str_csfile);
    }

    debug_msg (DBG_CSF, "finished reading the \\lowercase section.");
}				/* c8read_lowercase () */



/*-
**============================================================================
** c8read_lowupcase()
**
**  Read and parse the \lowupcase section of the CS file.  This section is
**  used to define the lower -> upper and upper -> lower case relationship 
**  of pairs of specified characters.  It is only used if the relationship
**  is symmetrical - use \lowercase or \uppercase if it isn't.
**
**  The syntax of the \lowupcase section is:
**
**      \uppercase{
**          <LC-val-1> <UC-val-1>   % Comment begins with a percent sign
**          <LC-val-2> <UC-val-2>  <LC-val-3> <UC-val-3>
**          ...
**          <LC-val-N> <UC-val-N>
**      }
**
**  Notes:
**
**  o   characters can be entered normally, or in TeX notation for
**      character codes, i.e. ^^XX, where XX is the hexadecimal value.
**
**  o   reading of the \lowupcase section ends when the first '}' character 
**      is reached, so '}' can't be included in a case relationship pair.
**      You can't use ^^7d either.
**
**  o   '%' is used to introduce a trailing comment, so '%' can't be included 
**      in the \lowupcase section.  You can't use ^^25 either.
**
**  o   you cannot redefine the lower or upper case equivalent of an ASCII 
**      character (code < 128).  In other words, all characters in the
**      relationship pairs must have codes > 127.
**============================================================================
*/
static void c8read_lowupcase (void)
{
    int                 c;
    int                 lowercase_val;

    debug_msg (DBG_CSF, "reading the \\lowupcase section ... ");
    lowercase_val = -1;

    /*
    ** Keep reading characters until the end of the section is encountered 
    ** at a '}' or EOF is reached.
    */
    c = getc (c8_cs_file);

    while ((c != '}') && !feof (c8_cs_file)) {

        /*
        ** Characters may be specified using the TeX syntax for the
        ** 8-bit code, i.e. ^^XX, where XX is the hexadecimal value.
        ** We should really check the result of this fscanf().
        */
        if (c == '^')
            fscanf (c8_cs_file, "^%x", &c);

        /*
        ** If we have reached the end of a line, simply increment the
        ** line count.
        */
        if (c == '\n') {
            c8_line++;
        }

        /*
        ** If we encounter a '%', it signifies the start of a comment,
        ** unless it was translated from a ^^xx sequence.
        ** Ignore characters until the end of line is reached.
        */
        else if (c == '%') {
            do {
                c = getc(c8_cs_file);
            } while ((c != '\n') && (! feof( c8_cs_file)));
            continue;
        }

        /*
        ** The normal case is for alpha characters to be included in the
        ** case relationship.  Alpha characters are assumed to be ASCII
        ** alpha (A..Za..z) plus any character with a code > 127.
        */
        else if (c8is_alpha (c)) {

            /*
            ** The case relationship consists of a pair of alpha characters 
            ** separated by whitespace.  In the \lowupcase section, the pair
            ** is in the order <lowercase-char> <uppercase-char>.  We
            ** must remember which character we are looking for - this is
            ** achieved by setting lowercase_val to -1 when we are
            ** looking for the first character of a pair.
            **
            ** If lowercase_val is < 0, we treat the current character as
            ** the lower case member of the pair and set lowercase_val
            ** accordingly.
            */
            if (lowercase_val < 0)
                lowercase_val = c;
            else {

                /*
                ** We are not allowed to redefine the lower or upper case
                ** equivalent of an ASCII character. 
                */
                if (lowercase_val < 127) {
                    PRINT_LN2 ("Error: attempt to redefine upper case of "
                               "an ASCII character [0x%02x]", lowercase_val);
                    PRINT_LN3 ("Context: line %04d: of CS file %s", 
                               c8_line, Str_csfile);
                }
                else if (c < 127) {
                    PRINT_LN2 ("Error: attempt to redefine lower case of "
                               "an ASCII character [0x%02x]", c);
                    PRINT_LN3 ("Context: line %04d: of CS file %s", 
                               c8_line, Str_csfile);
                }

                /*
                ** All OK, so set the appropriate element of the c8lowcase 
                ** and c8upcase arrays, then reset the value of lowercase_val
                ** to signify that we are looking for the next pair.
                */
                else {
                    c8lowcase[c] = lowercase_val;
                    c8upcase[lowercase_val] = c;
                    lowercase_val = -1;
                }
            }
        }                       /* end if */

        /*
        ** Get the next character from the file and start the loop again.
        */
        c = getc (c8_cs_file);
    }				/* end while */

    /*
    ** The only normal reason for breaking out of the while loop is that
    ** we encountered '}' at the end of the \lowupcase section.  Two
    ** conditions should be true:
    **
    **   (lowercase_val = -1)   - there is an unmatched character
    **   (c == '}')             - the terminating '}' has been read
    */
    if (lowercase_val != -1){
	PRINT_LN ("Error: unmatched character pair in \\lowupcase section");
	PRINT_LN3 ("Context: line %04d: of CS file %s", c8_line, Str_csfile);
    }

    if (c != '}') {
        PRINT_LN ("Error: unexpected end of file in \\lowupcase section");
        PRINT_LN3 ("Context: line %04d: of CS file %s", c8_line, Str_csfile);
    }

    debug_msg (DBG_CSF, "finished reading the \\lowupcase section.");
}				/* c8read_lowupcase () */



/*-
**============================================================================
** c8read_order()
**
**  Read and parse the \order section of the CS file.  This section is
**  used to define the sorting order of characters in the 8-bit character
**  set.
**
**  The syntax of the \order section is:
**
**      \order{
**          <char-1>                % Comment begins with a percent sign
**          <char-2> <char-3> 
**          <char-4> - <char-5> 
**          <char-4> _ <char-5> 
**          ...
**          <char-n>
**      }
**
**  Notes:
**
**  o   characters can be entered normally, or in TeX notation for
**      character codes, i.e. ^^XX, where XX is the hexadecimal value.
**
**  o   reading of the \order section ends when the first '}' character 
**      is reached, so '}' can't be included in the \order section.  You
**      can't use ^^7d either.
**
**  o   '%' is used to introduce a trailing comment, so '%' can't be included
**      in the \order section.  You can't use ^^25 either.
**
**  o   All characters on the same line are given the same sorting weight.
**
**  o   The construct <char-1> <underscore> <char-2> is used to denote that 
**      all characters in the range <char-1> to <char-2> should be given the
**      same sorting weight.  For example, "A _ Z a _ z" would cause all
**      ASCII alphabetical characters to have the same sorting weight and
**      would be equivalent to placing all 26 characters on the same line.
**
**  o   The construct <char-1> <hyphen> <char-2> is used to denote that all
**      characters in the range <char-1> to <char-2> should be given an
**      ascending set of sorting weights, starting with <char-1> and
**      ending with <char-2>.  For example, "A - Z" would cause all
**      upper case ASCII alphabetical characters to be sorted in
**      ascending order and would be equivalent to placing 'A' on the
**      first line, 'B' on the second, through to 'Z' on the 26th line.
**
**  o   the characters at the beginning of the order section are given a
**      lower sorting weight than characters occuring later.  When
**      sorting alphabetically, characters with the lowest weight come
**      first.
**
**  o   all characters not in the \order section (including ASCII characters)
**      are given the same very high sorting weight to ensure that they
**      come last when sorting alphabetically.
**============================================================================
*/
static void c8read_order (void)
{
    int                 c, i;
    int                 c_previous = 0;
    int                 c_previous_previous = 0;
    int                 seen_chars;

    debug_msg (DBG_CSF, "reading the \\order section ... ");
    sort_weight = 1;
    seen_chars = FALSE;

    /*
    ** Keep reading characters until the end of the section is
    ** encountered at a '}' or EOF is reached.
    */
    c = getc (c8_cs_file);

    while ((c != '}') && (! feof (c8_cs_file))) {

        /*
        ** If we have reached the end of a line, the next line denotes
        ** characters with an increased sorting weight.  Any other
        ** characters with a value less than ' ' are ignored.
        */
        if (c == '\n') {
            if (seen_chars)
                sort_weight++;
            c8_line++;
            seen_chars = FALSE;
        }
        else if (c < ' ') {
            debug_msg (DBG_CSF, "ignoring char 0x%02X on line %d of CS file", 
                       c, c8_line);
        }        
        else if (c > ' ') {

            /*
            ** Characters may be specified using the TeX syntax for the
            ** 8-bit code, i.e. ^^XX, where XX is the hexadecimal value.
            ** We should really check the result of this fscanf().  Read the
            ** character code and then start the while loop again.
            */
            if (c == '^')
                fscanf (c8_cs_file, "^%x", &c);

            /*
            ** If we encounter a '%', it signifies the start of a comment.
            ** Ignore characters until the end of line is reached.
            */
            if (c == '%') {
                do {
                    c = getc(c8_cs_file);
                } while ((c != '\n') && (! feof( c8_cs_file)));
	        continue;
            }            

            /*
            ** The normal case is for alpha characters to be included
            ** in the sorting sequence.  Alpha characters are ASCII alpha
            ** plus any character with a code > 127.
            */
            else if (c8is_alphanumeric (c) || c == ' ') {

                /*
                ** If the previous char was '-', we expect this char to
                ** terminate a "X-Y" sequence.  The character before
                ** the '-' must also be an alpha type.  The characters in
                ** the sequence are assigned an increasing sort weight.
                */
	        if (c_previous == '-') {
	            if (c8is_alphanumeric (c_previous_previous)) {
	                for (i = c_previous_previous; i < c; i++)
	                    c8order[i] = sort_weight++;
                        seen_chars = TRUE;
	            } else {
                        PRINT_LN ("Error: non-alphanumeric character "
                                   "starts X-Y sequence");
                        PRINT_LN3 ("Context: line %04d: of CS file %s", 
                                   c8_line, Str_csfile);
                    }

                /*
                ** If the previous char was '_', we expect this char to
                ** terminate a "X_Y" sequence.  The character before
                ** the '_' must also be an alpha type.  The characters in
                ** the sequence are assigned the same sort weight.
                */
                } else if (c_previous == '_') {
	            if (c8is_alphanumeric (c_previous_previous)) {
	                for (i = c_previous_previous; i < c; i++)
	                    c8order[i] = sort_weight;
                        seen_chars = TRUE;
	            } else {
                        PRINT_LN ("Error: non-alphanumeric character "
                                   "starts X_Y sequence");
                        PRINT_LN3 ("Context: line %04d: of CS file %s", 
                                   c8_line, Str_csfile);
                    }
	        }
	        c8order[c] = sort_weight;
                seen_chars = TRUE;
            }
            
            /*
            ** The only other non-alpha characters allowed are '-' & '_'.
            */
            else if ((c != '-') && (c != '_')) {
                PRINT_LN ("Error: only '-' or '_' may follow an "
                          "alpha character");
                PRINT_LN3 ("Context: line %04d: of CS file %s", 
                           c8_line, Str_csfile);
            }

            /*
            ** Keep an up-to-date note of the two previous values of c.
            */
            c_previous_previous = c_previous;
            c_previous = c;
        }                       /* end if */

        /*
        ** Get the next character and start the loop again.
        */
        c = getc (c8_cs_file);
    }				/* end while */

    /*
    ** If c isn't '}', we ran into EOF.
    */
    if (c != '}') {
        PRINT_LN ("Error: unexpected end of file in \\order section");
        PRINT_LN3 ("Context: line %04d: of CS file %s", c8_line, Str_csfile);
    }
    debug_msg (DBG_CSF, "finished reading the \\order section.");
}				/* c8read_order () */



/*-
**============================================================================
** c8read_uppercase()
**
**  Read and parse the \uppercase section of the CS file.  This section is
**  used to define the lower -> upper case relationship of pairs of specified 
**  characters.  It should normally only be used if the relationship
**  isn't symmetrical - use \lowupcase if it is.
**
**  The syntax of the \uppercase section is:
**
**      \uppercase{
**          <LC-val-1> <UC-val-1>   % Comment begins with a percent sign
**          <LC-val-2> <UC-val-2>  <LC-val-3> <UC-val-3>
**          ...
**          <LC-val-N> <UC-val-N>
**      }
**
**  Notes:
**
**  o   characters can be entered normally, or in TeX notation for
**      character codes, i.e. ^^XX, where XX is the hexadecimal value.
**
**  o   reading of the \uppercase section ends when the first '}' character 
**      is reached, so '}' can't be included in a case relationship pair.
**      You can't use ^^7d either.
**
**  o   '%' is used to introduce a trailing comment, so '%' can't be included 
**      in the \uppercase section.  You can't use ^^25 either.
**
**  o   you cannot redefine the upper case equivalent of an ASCII character 
**      (code < 128).  In other words, all "left hand" characters in the
**      relationship pairs must have codes > 127.
**============================================================================
*/
static void c8read_uppercase (void)
{
    int                 c;
    int                 lowercase_val;

    debug_msg (DBG_CSF, "reading the \\uppercase section ... ");
    lowercase_val = -1;

    /*
    ** Keep reading characters until the end of the section is encountered 
    ** at a '}' or EOF is reached.
    */
    c = getc (c8_cs_file);

    while ((c != '}') && (! feof (c8_cs_file))) {

        /*
        ** Characters may be specified using the TeX syntax for the
        ** 8-bit code, i.e. ^^XX, where XX is the hexadecimal value.
        ** We should really check the result of this fscanf().
        */
        if (c == '^')
            fscanf (c8_cs_file, "^%x", &c);

        /*
        ** If we have reached the end of a line, simply increment the
        ** line count.
        */
        if (c == '\n') {
            c8_line++;
        }

        /*
        ** If we encounter a '%', it signifies the start of a comment.
        ** Ignore characters until the end of line is reached.
        */
        else if (c == '%') {
            do {
                c = getc(c8_cs_file);
            } while ((c != '\n') && (! feof( c8_cs_file)));
            continue;
        }

        /*
        ** The normal case is for alpha characters to be included in the
        ** case relationship.  Alpha characters are assumed to be ASCII
        ** alpha (A..Za..z) plus any character with a code > 127.
        */
        else if (c8is_alpha (c)) {

            /*
            ** The case relationship consists of a pair of alpha characters 
            ** separated by whitespace.  In the \uppercase section, the pair
            ** is in the order <lowercase-char> <uppercase-char>.  We
            ** must remember which character we are looking for - this is
            ** achieved by setting lowercase_val to -1 when we are
            ** looking for the first character of a pair.
            **
            ** If lowercase_val is < 0, we treat the current character as
            ** the lower case member of the pair and set lowercase_val
            ** accordingly.
            */
            if (lowercase_val < 0)
                lowercase_val = c;
            else {

                /*
                ** We are not allowed to redefine the upper case
                ** equivalent of an ASCII character. 
                */
                if (lowercase_val < 127) {
                    PRINT_LN2 ("Error: attempt to redefine upper case of "
                               "an ASCII character [0x%02x]", lowercase_val);
                    PRINT_LN3 ("Context: line %04d: of CS file %s", 
                               c8_line, Str_csfile);
                }

                /*
                ** All OK, so set the appropriate element of the c8lowcase 
                ** array and reset the value of lowercase_val to signify
                ** that we are looking for the next pair.
                */
                else {
                    c8upcase[lowercase_val] = c;
                    lowercase_val = -1;
                }
            }
        }                       /* end if */

        /*
        ** Get the next character from the file and start the loop again.
        */
        c = getc (c8_cs_file);
    }				/* end while */

    /*
    ** The only normal reason for breaking out of the while loop is that
    ** we encountered '}' at the end of the \uppercase section.  Two
    ** conditions should be true:
    **
    **   (lowercase_val = -1)   - there is an unmatched character
    **   (c == '}')             - the terminating '}' has been read
    */
    if (lowercase_val != -1){
	PRINT_LN ("Error: unmatched character pair in \\uppercase section");
	PRINT_LN3 ("Context: line %04d: of CS file %s", c8_line, Str_csfile);
    }

    if (c != '}') {
        PRINT_LN ("Error: unexpected end of file in \\uppercase section");
        PRINT_LN3 ("Context: line %04d: of CS file %s", c8_line, Str_csfile);
    }

    debug_msg (DBG_CSF, "finished reading the \\uppercase section.");
}				/* c8read_uppercase () */



/*
**============================================================================
** c8report_8bit_handling()
**
** Report the internal status of BibTeX's 8-bit character set handling.  The
** information reported is:
**
**  o   8-bit characters defined as type ALPHA
**  o   character codes with upper case equivalents
**  o   character codes with lower case equivalents
**  o   the character codes in sorting weight order
**============================================================================
*/
void c8report_8bit_handling (void)
{
    int                 c;
    int                 column;
    int                 weight;

    /*
    ** Exit immediately if the CSF debugging option hasn't been selected.
    */
    if ((Flag_debug & DBG_CSF) == 0)
        return;
    
    /*
    ** Report the 8-bit characters defined as type ALPHA, 16 to a row.
    */
    fprintf (stderr, "\nD-CSF: 8-bit ALPHA characters\n");
    fprintf (stderr, "D-CSF: ----------------------\n");

    for (c = 128; c <= 255; c += 16) {
        fprintf (stderr, "D-CSF: %02x:  ", c);
        
        for (column = 0; column < 16; column++) {
            if (lex_class[c + column] == ALPHA)
                fprintf (stderr, "%c  ", c + column);
            else
                fputs (".  ", stderr);
        }
        fputc ('\n', stderr);
    }

    /*
    ** Report the lower / upper case relationships, three pairs to a row.
    */
    fprintf (stderr, "\nD-CSF: Characters with upper case equivalents\n");
    fprintf (stderr, "D-CSF: --------------------------------------");
    column = 0;

    for (c = 1; c <= 255; c++)
        if (c8upcase[c] != c) {
            if ((column % 3) == 0)
                fprintf (stderr, "\nD-CSF: %c [%02x] <<< %c [%02x]",
                         c, (int) c, c8upcase[c], (int) c8upcase[c]);
            else
                fprintf (stderr, "      %c [%02x] <<< %c [%02x]",
                         c, (int) c, c8upcase[c], (int) c8upcase[c]);
            ++column;
        }

    /*
    ** Report the upper / lower case relationships, three pairs to a row.
    */
    fprintf (stderr, "\n\nD-CSF: Characters with lower case equivalents\n");
    fprintf (stderr, "D-CSF: --------------------------------------");
    column = 0;

    for (c = 1; c <= 255; c++)
        if (c8lowcase[c] != c) {
            if ((column % 3) == 0)
                fprintf (stderr, "\nD-CSF: %c [%02x] >>> %c [%02x]",
                         c, (int) c, c8lowcase[c], (int) c8lowcase[c]);
            else
                fprintf (stderr, "      %c [%02x] >>> %c [%02x]",
                         c, (int) c, c8lowcase[c], (int) c8lowcase[c]);
            ++column;
        }

    /*
    ** Report character codes in sorting weight order.  If BibTeX is
    ** operating in --traditional or --8bit mode, sorting order is simply
    ** character code order.  In that case, just give an abbreviated
    ** summary.
    */
    fprintf (stderr, "\n\nD-CSF: Characters in sorting order\n");
    fprintf (stderr, "D-CSF: ---------------------------\n");

    if (Flag_7bit || Flag_8bit) {
        fprintf (stderr, "D-CSF: 00:    [00]\n");
        fprintf (stderr, "D-CSF: 01:    [01]\n");
        fprintf (stderr, "D-CSF: ..     ....\n");
        fprintf (stderr, "D-CSF: 41:  A [41]\n");
        fprintf (stderr, "D-CSF: ..     ....\n");
        fprintf (stderr, "D-CSF: 5a:  Z [5a]\n");
        fprintf (stderr, "D-CSF: ..     ....\n");
        fprintf (stderr, "D-CSF: 61:  a [61]\n");
        fprintf (stderr, "D-CSF: ..     ....\n");
        fprintf (stderr, "D-CSF: 7a:  z [7a]\n");
        fprintf (stderr, "D-CSF: ..     ....\n");
        fprintf (stderr, "D-CSF: fe:    [fe]\n");
        fprintf (stderr, "D-CSF: ff:    [ff]\n\n");
    }

    /*
    ** When a CS file has been used, sort_weight will be set to the value of
    ** the highest sorting weight.  Report every character with a given
    ** sorting weight, for 0 <= weight < sort_weight.
    */
    else {
        for (weight = 0; weight < sort_weight; weight++) {
            fprintf (stderr, "D-CSF: %02x:  ", weight);

            for (c = 0; c <= 255; c++) {
                if (c8order[c] == weight)
                    fprintf (stderr, "%c [%02x] ", c, c);
            }
            fputc ('\n', stderr);
        }    
        fprintf (stderr, "D-CSF: (All other characters are sorted "
                         "equally after any of the above.)\n\n");
    }
}                       /* c8report_8bit_handling() */    


#endif                          /* SUPPORT_8BIT */
