%{
/* -*- Mode:Text -*- */
#ifndef lint
static char Rcs_Id[] =
    "$Id: parse.y,v 1.25 91/09/11 23:22:56 geoff Exp $";
#endif

/*
 * Copyright 1987, by Geoff Kuenning, Manhattan Beach, CA
 * Permission for non-profit use is hereby granted.
 * All other rights reserved.
 */

/*
 * $Log:	parse.y,v $
 * Revision 1.25  91/09/11  23:22:56  geoff
 * Add the "stringcharcmp" routine for getting around stupidity on Sun 4's.
 * Fix a number of places where the check for too many string characters
 * was off by 1.
 * 
 * Revision 1.24  91/08/10  14:09:46  geoff
 * Fix all of the stringchar length tests to allow single-character
 * stringchars.
 * 
 * Revision 1.23  91/07/15  19:27:11  geoff
 * Provide the "canonical" parameter to all *isstringch, strtoichar, and
 * strtosichar calls.
 * 
 * Revision 1.22  91/07/11  19:52:21  geoff
 * Remove the include of stdio.h, since ispell.h now does this.
 * 
 * Revision 1.21  91/07/05  19:51:51  geoff
 * Fix some lint complaints.
 * 
 * Revision 1.20  91/07/04  00:04:24  geoff
 * Put new string characters at the end of the list, so stringcharlen's
 * binary search will work right.
 * 
 * Revision 1.19  91/06/23  22:14:38  geoff
 * Fix a typo that could cause core dumps
 * 
 * Revision 1.18  91/06/23  20:53:46  geoff
 * Change the name of infile to prevent name conflicts with ispell.h
 * 
 * Revision 1.17  91/05/27  21:48:09  geoff
 * Add support for the altstringtype and defstringtype statements, and
 * introduce some statement ordering restrictions (mainly to make the
 * grammar simpler.)
 * 
 * Revision 1.16  90/12/31  01:04:08  geoff
 * Add a text-mode line so emacs will edit consistently.
 * 
 * Revision 1.15  90/04/26  22:44:20  geoff
 * Add the altstringchar statement and other support for alternate string
 * characters.
 * 
 * Revision 1.14  90/04/17  15:34:39  geoff
 * Fix some incorrect declarations caught by gcc
 * 
 * Revision 1.13  89/12/27  03:20:15  geoff
 * Move all messages to msgs.h so they can be reconfigured
 * 
 * Revision 1.12  89/06/09  15:55:57  geoff
 * Add support for the internal "character" type, ichar_t.
 * 
 * Revision 1.11  89/04/27  23:33:41  geoff
 * Fix some bugs introduced in the last revision, when we switched to a
 * malloc'ed yylval and added string characters.
 * 
 * Revision 1.10  89/04/03  01:57:50  geoff
 * Remove the distinction between "casechars" and "otherchars", figuring
 * out which is which from the syntax.  Add support for "boundarychars,"
 * string characters, and the selectable flag marker.  Fix all the code
 * that returns strings and character sets to return a malloc'ed yylval, for
 * reliability and robustness.  Make "option_stmt" a separate entity from
 * 'charset_stmt," just for readability.  Fix a bunch of lint complaints.  Add
 * support for strings in double quotes.
 * 
 * Revision 1.9  88/11/25  19:53:42  geoff
 * Get rid of some unneeded declarations, and add a few strategic blank
 * lines to make the formatting look a bit nicer.
 * 
 * Revision 1.8  88/06/25  17:50:24  geoff
 * Add statements to control various options:  nroff/TeX control characters,
 * compound words, and "all affix" generation.
 * 
 * Revision 1.7  88/04/30  22:15:13  geoff
 * Fix some lint complaints.
 * 
 * Revision 1.6  88/02/20  23:14:04  geoff
 * Use upcase instead of forceupper.  Allow affixes that delete something
 * without adding anything in its place.
 * 
 * Revision 1.5  87/09/30  23:31:17  geoff
 * Make the default sort order be ASCII but after all word characters.
 * Also rename a variable more mnemonically.
 * 
 * Revision 1.4  87/09/14  22:39:02  geoff
 * Add copyright comments
 * 
 * Revision 1.3  87/09/09  00:19:03  geoff
 * Add code to record the collating order in hashheader.sortorder.
 * 
 * Revision 1.2  87/08/02  17:17:54  geoff
 * Add (untested) support for the \x construct in the lexical analyzer.
 * 
 * Revision 1.1  87/07/20  22:57:45  geoff
 * Initial revision
 * 
 */

#include <ctype.h>
#include "config.h"
#include "ispell.h"
#include "msgs.h"

%}

%union
    {
    int			simple;		/* Simple char or lval from yylex */
    struct
	{
	char *		set;		/* Character set */
	int		complement;	/* NZ if it is a complement set */
	}
			charset;
    unsigned char *	string;		/* String */
    ichar_t *		istr;		/* Internal string */
    struct flagent *	entry;		/* Flag entry */
    }

%{

static int	yylex ();	/* Lexical analyzer */
static int	kwanalyze ();	/* Analyze a possible keyword */
static void	getqstring ();	/* Get (double-)quoted string */
static void	getrange ();	/* Get a lexical character range */
static int	backch ();	/* Process a backslashed character */
static void	yyerror ();	/* Print out an error message */
int		yyopen ();	/* Open a table file */
void		yyinit ();	/* Initialize for parsing */
static int	grabchar ();	/* Get a character and track line number */
static void	ungrabchar ();	/* Unget a character, tracking line numbers */
static int	sufcmp ();	/* Compare suffix flags for qsort */
static int	precmp ();	/* Compare prefix flags for qsort */
static int	addstringchar (); /* Add a string character to the table */
#ifdef SUN4_IS_STUPID
static int	stringcharcmp (); /* Strcmp() done right, for Sun 4's */
#else /* SUN4_IS_STUPID */
#define stringcharcmp	strcmp	/* Everybody else knows how to do strcmp() */
#endif /* SUN4_IS_STUPID */
#ifdef TBLDEBUG
static void	tbldump ();	/* Dump a flag table */
static void	entdump ();	/* Dump one flag entry */
static void	setdump ();	/* Dump a set specification */
static void	subsetdump ();	/* Dump part of a set spec */
#endif

struct kwtab
    {
    char *	kw;		/* Syntactic keyword */
    int		val;		/* Lexical value */
    };

#define TBLINC		10		/* Size to allocate table by */

static FILE *		aff_file = stdin; /* Input file pointer */
static int		centnum;	/* Number of entries in curents */
static int		centsize = 0;	/* Size of alloc'ed space in curents */
static int		ctypechars;	/* Size of string in current strtype */
static int		ctypenum = 0;	/* Number of entries in chartypes */
static int		ctypesize = 0;	/* Size of alloc'ed spc in chartypes */
static struct flagent * curents;	/* Current flag entry collection */
static char *		fname = "(stdin)"; /* Current file name */
static char		lexungrab[MAXSTRINGCHARLEN * 2]; /* Spc for ungrabch */
static int		lineno = 1;	/* Current line number in file */
static struct flagent * table;		/* Current table being built */
static int		tblnum;		/* Numer of entries in table */
static int		tblsize = 0;	/* Size of the flag table */
static int		ungrablen;	/* Size of ungrab area */

extern void		upcase ();	/* Make a string uppercase */

extern void		exit ();	/* Terminate program */
extern void		free ();	/* Free memory */
extern char *		malloc ();	/* Memory allocator */
extern void		perror ();	/* Print a system error message */
extern void		qsort ();	/* Quicksort routine */
extern char *		realloc ();	/* Memory reallocator */
extern char *		strcpy ();	/* String copier */
%}

%token <simple> '-'
%token <simple> '>'
%token <simple> ','
%token <simple> ':'
%token <simple> '.'
%token <simple> '*'
%token <simple> ALLAFFIXES
%token <simple> ALTSTRINGCHAR
%token <simple> ALTSTRINGTYPE
%token <simple> BOUNDARYCHARS
%token <simple> COMPOUNDWORDS
%token <simple> DEFSTRINGTYPE
%token <simple> FLAG
%token <simple> FLAGMARKER
%token <simple> NROFFCHARS
%token <simple> OFF
%token <simple> ON
%token <simple> PREFIXES
%token <charset> RANGE
%token <simple> SUFFIXES
%token <string> STRING
%token <simple> STRINGCHAR
%token <simple> TEXCHARS
%token <simple> WORDCHARS

%type <simple> file
%type <simple> headers
%type <simple> option_group
%type <simple> charset_group
%type <simple> altchar_group
%type <simple> charset_stmt
%type <simple> option_stmt
%type <simple> altchar_stmt
%type <simple> altchar_spec_group
%type <simple> altchar_spec
%type <simple> deftype_stmt
%type <string> stringtype_info
%type <simple> filesuf_list
%type <string> filesuf
%type <charset> char_set
%type <simple> tables
%type <simple> prefix_table
%type <simple> suffix_table
%type <simple> table
%type <simple> flagdef
%type <simple> error
%type <simple> on_or_off
%type <simple> rules
%type <entry> affix_rule
%type <entry> cond_or_null
%type <entry> conditions
%type <istr> ichar_string
%{
static struct kwtab			/* Table of built-in keywords */
		keywords[] =
    {
    {"allaffixes", ALLAFFIXES},
    {"altstringchar", ALTSTRINGCHAR},
    {"altstringtype", ALTSTRINGTYPE},
    {"boundarychars", BOUNDARYCHARS},
    {"compoundwords", COMPOUNDWORDS},
    {"defstringtype", DEFSTRINGTYPE},
    {"flag", FLAG},
    {"flagmarker", FLAGMARKER},
    {"nroffchars", NROFFCHARS},
    {"troffchars", NROFFCHARS},
    {"on", ON},
    {"off", OFF},
    {"prefixes", PREFIXES},
    {"stringchar", STRINGCHAR},
    {"suffixes", SUFFIXES},
    {"TeXchars", TEXCHARS},
    {"texchars", TEXCHARS},
    {"wordchars", WORDCHARS},
    {NULL, 0}
    };
%}
%%
file		:	headers tables
		|	tables
		;

headers		:	option_group charset_group
		|	option_group charset_group altchar_group
		|	charset_group
		|	charset_group altchar_group
		;

option_group	:	option_stmt
		|	option_group option_stmt
		;

charset_group	:	deftype_stmt charset_stmt
		|	charset_stmt
		|	charset_group charset_stmt
		;

deftype_stmt	:	DEFSTRINGTYPE stringtype_info
		;

altchar_group	:	altchar_stmt
		|	altchar_group altchar_stmt
		;

charset_stmt 	:	WORDCHARS char_set char_set
			    {
			    int nextlower;
			    int nextupper;

			    for (nextlower = SET_SIZE + hashheader.nstrchars;
			      --nextlower > SET_SIZE;
			      )
				{
				if ($2.set[nextlower] != 0
				  ||  $3.set[nextlower] != 0)
				    {
				    yyerror (PARSE_Y_NO_WORD_STRINGS);
				    break;
				    }
				}
			    for (nextlower = 0;
			      nextlower < SET_SIZE;
			      nextlower++)
				{
				hashheader.wordchars[nextlower]
				  |= $2.set[nextlower] | $3.set[nextlower];
				hashheader.lowerchars[nextlower]
				  |= $2.set[nextlower];
				hashheader.upperchars[nextlower]
				  |= $3.set[nextlower];
				}
			    for (nextlower = nextupper = 0;
			      nextlower < SET_SIZE;
			      nextlower++)
				{
				if ($2.set[nextlower])
				    {
				    for (  ;
				      nextupper < SET_SIZE
					&&  !$3.set[nextupper];
				      nextupper++)
					;
				    if (nextupper == SET_SIZE)
					yyerror (PARSE_Y_UNMATCHED);
				    else
					{
					hashheader.lowerconv[nextupper]
					  = nextlower;
					hashheader.upperconv[nextlower]
					  = nextupper;
					hashheader.sortorder[nextupper]
					  = hashheader.sortval++;
					hashheader.sortorder[nextlower]
					  = hashheader.sortval++;
					nextupper++;
					}
				    }
				}
			    for (  ;  nextupper < SET_SIZE;  nextupper++)
				{
				if ($3.set[nextupper])
				    yyerror (PARSE_Y_UNMATCHED);
				}
			    free ($2.set);
			    free ($3.set);
			    }
		|	WORDCHARS char_set
			    {
			    int i;

			    for (i = SET_SIZE + hashheader.nstrchars;
			      --i > SET_SIZE;
			      )
				{
				if ($2.set[i] != 0)
				    {
				    yyerror (PARSE_Y_NO_WORD_STRINGS);
				    break;
				    }
				}
			    for (i = 0;  i < SET_SIZE;  i++)
				{
				if ($2.set[i])
				    {
				    hashheader.wordchars[i] = 1;
				    hashheader.sortorder[i]
				      = hashheader.sortval++;
				    }
				}
			    free ($2.set);
			    }
		|	BOUNDARYCHARS char_set char_set
			    {
			    int nextlower;
			    int nextupper;

			    for (nextlower = SET_SIZE + hashheader.nstrchars;
			      --nextlower > SET_SIZE;
			      )
				{
				if ($2.set[nextlower] != 0
				  ||  $3.set[nextlower] != 0)
				    {
				    yyerror (PARSE_Y_NO_BOUNDARY_STRINGS);
				    break;
				    }
				}
			    for (nextlower = 0;
			      nextlower < SET_SIZE;
			      nextlower++)
				{
				hashheader.boundarychars[nextlower]
				  |= $2.set[nextlower] | $3.set[nextlower];
				hashheader.lowerchars[nextlower]
				  |= $2.set[nextlower];
				hashheader.upperchars[nextlower]
				  |= $3.set[nextlower];
				}
			    for (nextlower = nextupper = 0;
			      nextlower < SET_SIZE;
			      nextlower++)
				{
				if ($2.set[nextlower])
				    {
				    for (  ;
				      nextupper < SET_SIZE
					&&  !$3.set[nextupper];
				      nextupper++)
					;
				    if (nextupper == SET_SIZE)
					yyerror (PARSE_Y_UNMATCHED);
				    else
					{
					hashheader.lowerconv[nextupper]
					  = nextlower;
					hashheader.upperconv[nextlower]
					  = nextupper;
					hashheader.sortorder[nextupper]
					  = hashheader.sortval++;
					hashheader.sortorder[nextlower]
					  = hashheader.sortval++;
					nextupper++;
					}
				    }
				}
			    for (  ;  nextupper < SET_SIZE;  nextupper++)
				{
				if ($3.set[nextupper])
				    yyerror (PARSE_Y_UNMATCHED);
				}
			    free ($2.set);
			    free ($3.set);
			    }
		|	BOUNDARYCHARS char_set
			    {
			    int i;

			    for (i = SET_SIZE + hashheader.nstrchars;
			      --i > SET_SIZE;
			      )
				{
				if ($2.set[i] != 0)
				    {
				    yyerror (PARSE_Y_NO_BOUNDARY_STRINGS);
				    break;
				    }
				}
			    for (i = 0;  i < SET_SIZE;  i++)
				{
				if ($2.set[i])
				    {
				    hashheader.boundarychars[i] = 1;
				    hashheader.sortorder[i]
				      = hashheader.sortval++;
				    }
				}
			    free ($2.set);
			    }
		|	STRINGCHAR STRING
			    {
			    int len;

			    len = strlen ((char *) $2);
			    if (len > MAXSTRINGCHARLEN)
				yyerror (PARSE_Y_LONG_STRING);
			    else if (len == 0)
				yyerror (PARSE_Y_NULL_STRING);
			    else if (hashheader.nstrchars >= MAXSTRINGCHARS)
				yyerror (PARSE_Y_MANY_STRINGS);
			    else
				(void) addstringchar ($2, 0, 0);
			    free ((char *) $2);
			    }
		|	STRINGCHAR STRING STRING
			    {
			    int lcslot;
			    int len;
			    int ucslot;

			    len = strlen ((char *) $2);
			    if (strlen ((char *) $3) != len)
				yyerror (PARSE_Y_LENGTH_MISMATCH);
			    else if (len > MAXSTRINGCHARLEN)
				yyerror (PARSE_Y_LONG_STRING);
			    else if (len == 0)
				yyerror (PARSE_Y_NULL_STRING);
			    else if (hashheader.nstrchars >= MAXSTRINGCHARS)
				yyerror (PARSE_Y_MANY_STRINGS);
			    else
				{
				/*
				 * Add the uppercase character first, so that
				 * it will sort first.
				 */
				ucslot = addstringchar ($3, 0, 1);
				if (ucslot >= 0)
				    lcslot = addstringchar ($2, 1, 0);
				if (ucslot >= 0  &&  lcslot >= 0)
				    {
				    if (ucslot >= lcslot)
					ucslot++;
				    hashheader.lowerconv[ucslot] = lcslot;
				    hashheader.upperconv[lcslot] = ucslot;
				    }
				}
			    free ((char *) $2);
			    free ((char *) $3);
			    }
		;

altchar_stmt	:	ALTSTRINGTYPE stringtype_info altchar_spec_group
		;

stringtype_info	:	STRING filesuf_list
			    {
			    chartypes[ctypenum].name = (char *) $1;
			    ctypenum++;
			    hashheader.nstrchartype = ctypenum;
			    }
		;

filesuf_list	:	filesuf
			    {
			    if (ctypenum >= ctypesize)
				{
				if (ctypesize == 0)
				    chartypes = (struct strchartype *)
				      malloc (TBLINC
					* sizeof (struct strchartype));
				else
				    chartypes = (struct strchartype *)
				      realloc ((char *) chartypes,
					(ctypesize + TBLINC)
					  * sizeof (struct strchartype));
				if (chartypes == NULL)
				    {
				    yyerror (PARSE_Y_NO_SPACE);
				    exit (1);
				    }
				ctypesize += TBLINC;
				}
			    ctypechars =
			      TBLINC * (strlen ((char *) $1) + 1) + 1;
			    chartypes[ctypenum].suffixes = malloc (ctypechars);
			    if (chartypes[ctypenum].suffixes == NULL)
				{
				yyerror (PARSE_Y_NO_SPACE);
				exit (1);
				}
			    (void) strcpy (chartypes[ctypenum].suffixes,
			     (char *) $1);
			    chartypes[ctypenum].suffixes
				[strlen ((char *) $1) + 1]
			      = '\0';
			    free ((char *) $1);
			    }
		|	filesuf_list filesuf
			    {
			    char *	nexttype;
			    int		offset;

			    for (nexttype = chartypes[ctypenum].suffixes;
			      *nexttype != '\0';
			      nexttype += strlen (nexttype) + 1)
				;
			    offset = nexttype - chartypes[ctypenum].suffixes;
			    if (offset + strlen ((char *) $2) + 1
			       >= ctypechars)
				{
				ctypechars +=
				  TBLINC * (strlen ((char *) $2) + 1);
				chartypes[ctypenum].suffixes =
				  realloc (chartypes[ctypenum].suffixes,
				    ctypechars);
				if (chartypes[ctypenum].suffixes == NULL)
				    {
				    yyerror (PARSE_Y_NO_SPACE);
				    exit (1);
				    }
				nexttype =
				  chartypes[ctypenum].suffixes + offset;
				}
			    (void) strcpy (nexttype, (char *) $2);
			    nexttype[strlen ((char *) $2) + 1] = '\0';
			    free ((char *) $2);
			    }
		;

filesuf		:	STRING
		;

altchar_spec_group
		:	altchar_spec
		|	altchar_spec_group altchar_spec
		;

altchar_spec	:	ALTSTRINGCHAR STRING STRING
			    {
			    int i;
			    int len;
			    int slot;

			    len = strlen ((char *) $2);
			    if (len > MAXSTRINGCHARLEN)
				yyerror (PARSE_Y_LONG_STRING);
			    else if (len == 0)
				yyerror (PARSE_Y_NULL_STRING);
			    else if (hashheader.nstrchars >= MAXSTRINGCHARS)
				yyerror (PARSE_Y_MANY_STRINGS);
			    else if (!isstringch ((char *) $3, 1))
				yyerror (PARSE_Y_NO_SUCH_STRING);
			    else
				{
				slot = addstringchar ($2, 0, 0) - SET_SIZE;
				if (laststringch >= slot)
				    laststringch++;
				hashheader.stringdups[slot] = laststringch;
				for (i = hashheader.nstrchars;  --i >= 0;  )
				    {
				    if (hashheader.stringdups[i]
				      == laststringch)
					hashheader.dupnos[slot]++;
				    }
				/*
				 * The above code sets dupnos one too high,
				 * because it counts the character itself.
				 */
				if (hashheader.dupnos[slot]
				  != hashheader.nstrchartype)
				    yyerror (PARSE_Y_MULTIPLE_STRINGS);
				hashheader.dupnos[slot]--;
				}
			    free ((char *) $2);
			    free ((char *) $3);
			    }
		;

option_stmt	:	NROFFCHARS STRING
			    {
			    if (strlen ((char *) $2)
			      == sizeof (hashheader.nrchars))
				(void) bcopy ((char *) $2, hashheader.nrchars,
				  sizeof (hashheader.nrchars));
			    else
				yyerror (PARSE_Y_WRONG_NROFF);
			    free ((char *) $2);
			    }
		;
		|	TEXCHARS STRING
			    {
			    if (strlen ((char *) $2)
			      == sizeof (hashheader.texchars))
				(void) bcopy ((char *) $2, hashheader.texchars,
				  sizeof (hashheader.texchars));
			    else
				yyerror (PARSE_Y_WRONG_TEX);
			    free ((char *) $2);
			    }
		|	COMPOUNDWORDS on_or_off
			    {
			    hashheader.defspaceflag = !$2;
			    }
		|	ALLAFFIXES on_or_off
			    {
			    hashheader.defhardflag = $2;
			    }
		|	FLAGMARKER STRING
			    {
			    if (strlen ((char *) $2) != 1)
				yyerror (PARSE_Y_LONG_FLAG);
			    else
				hashheader.flagmarker = $2[0];
			    free ((char *) $2);
			    }
		;

char_set	:	'.'
			    {
			    int		i;
			    char *	set;

			    set = malloc (SET_SIZE + MAXSTRINGCHARS);
			    if (set == NULL)
				{
				yyerror (PARSE_Y_NO_SPACE);
				exit (1);
				}
			    $$.set = set;
			    for (i = SET_SIZE + MAXSTRINGCHARS;  --i >= 0;  )
				*set++ = 1;
			    $$.complement = 0;
			    }
		|	STRING
			    {
			    int		setlen;

			    $$.set = malloc (SET_SIZE + MAXSTRINGCHARS);
			    if ($$.set == NULL)
				{
				yyerror (PARSE_Y_NO_SPACE);
				exit (1);
				}
			    (void) bzero ($$.set, SET_SIZE + MAXSTRINGCHARS);
			    if (l1_isstringch ((char *) $1, setlen, 1))
				{
				if (setlen != strlen ((char *) $1))
				    yyerror (PARSE_Y_NEED_BLANK);
				$$.set[SET_SIZE + laststringch] = 1;
				}
			    else
				{
				if (strlen ((char *) $1) != 1)
				    yyerror (PARSE_Y_NEED_BLANK);
				$$.set[*$1] = 1;
				}
			    free ((char *) $1);
			    $$.complement = 0;
			    }
		|	RANGE
		;

on_or_off	:	ON
			    {
			    $$ = 1;
			    }
		|	OFF
			    {
			    $$ = 0;
			    }
		;

tables		:	prefix_table suffix_table
		|	suffix_table prefix_table
		|	prefix_table
		|	suffix_table
		;

prefix_table	:	PREFIXES table
			    {
			    pflaglist = table;
			    numpflags = tblnum;
			    /*
			     * Sort the flag table.  This is critical so
			     * that ispell can build a correct index
			     * table.  The idea is to put similar affixes
			     * together.
			     */
			    qsort ((char *) table, (unsigned) tblnum,
			      sizeof (*table), precmp);
#ifdef TBLDEBUG
			    (void) fprintf (stderr, "prefixes\n");
			    tbldump (table, tblnum);
#endif
			    tblsize = 0;
			    }
		;

suffix_table	:	SUFFIXES table
			    {
			    sflaglist = table;
			    numsflags = tblnum;
			    /*
			     * See comments on the prefix sort.
			     */
			    qsort ((char *) table, (unsigned) tblnum,
			      sizeof (*table), sufcmp);
#ifdef TBLDEBUG
			    (void) fprintf (stderr, "suffixes\n");
			    tbldump (table, tblnum);
#endif
			    tblsize = 0;
			    }
		;

table		:	flagdef
			    {
			    if (tblsize == 0)
				{
				tblsize = centnum + TBLINC;
				tblnum = 0;
				table = (struct flagent *)
				  malloc (tblsize * (sizeof (struct flagent)));
				if (table == NULL)
				    {
				    yyerror (PARSE_Y_NO_SPACE);
				    exit (1);
				    }
				}
			    else if (tblnum + centnum >= tblsize)
				{
				tblsize = tblnum + centnum + TBLINC;
				table = (struct flagent *)
				  realloc ((char *) table,
				    tblsize * (sizeof (struct flagent)));
				if (table == NULL)
				    {
				    yyerror (PARSE_Y_NO_SPACE);
				    exit (1);
				    }
				}
			    for (tblnum = 0;  tblnum < centnum;  tblnum++)
				table[tblnum] = curents[tblnum];
			    centnum = 0;
			    }
		|	table flagdef
			    {
			    int i;

			    if (tblnum + centnum >= tblsize)
				{
				tblsize = tblnum + centnum + TBLINC;
				table = (struct flagent *)
				  realloc ((char *) table,
				    tblsize * (sizeof (struct flagent)));
				if (table == NULL)
				    {
				    yyerror (PARSE_Y_NO_SPACE);
				    exit (1);
				    }
				}
			    for (i = 0;  i < centnum;  i++)
				table[tblnum + i] = curents[i];
			    tblnum += centnum;
			    centnum = 0;
			    }
		;

flagdef		:	FLAG STRING ':' rules
			    {
			    int i;

			    if (strlen ((char *) $2) != 1)
				yyerror (PARSE_Y_LONG_FLAG);
			    for (i = 0;  i < centnum;  i++)
				{
				curents[i].flagbit = CHARTOBIT ($2[0]);
				curents[i].flagflags = 0;
				}
			    free ((char *) $2);
			    }
		|	FLAG '*' STRING ':' rules
			    {
			    int i;

			    if (strlen ((char *) $3) != 1)
				yyerror (PARSE_Y_LONG_FLAG);
			    for (i = 0;  i < centnum;  i++)
				{
				curents[i].flagbit = CHARTOBIT ($3[0]);
				curents[i].flagflags = FF_CROSSPRODUCT;
				}
			    free ((char *) $3);
			    }
		|	error
			    { $$ = 0; }
		;

rules		:	affix_rule
			    {
			    if (centsize == 0)
				{
				curents = (struct flagent *)
				  malloc (TBLINC * (sizeof (struct flagent)));
				if (curents == NULL)
				    {
				    yyerror (PARSE_Y_NO_SPACE);
				    exit (1);
				    }
				centsize = TBLINC;
				}
			    curents[0] = *$1;
			    centnum = 1;
			    free ((char *) $1);
			    $$ = 0;
			    }
		|	rules affix_rule
			    {
			    if (centnum >= centsize)
				{
				centsize += TBLINC;
				curents = (struct flagent *)
				  realloc ((char *) curents,
				    centsize * (sizeof (struct flagent)));
				if (curents == NULL)
				    {
				    yyerror (PARSE_Y_NO_SPACE);
				    exit (1);
				    }
				}
			    curents[centnum] = *$2;
			    centnum++;
			    free ((char *) $2);
			    }
		;

affix_rule	:	cond_or_null '>' ichar_string
			    {
			    int		i;

			    $1->stripl = 0;
			    $1->strip = NULL;
			    $1->affl = icharlen ($3);
			    $1->affix = $3;
			    upcase ($3);
			    /*
			     * As a special optimization (and a
			     * concession to those who prefer the syntax
			     * that way), convert any single condition
			     * that accepts all characters into no
			     * condition at all.
			     */
			    if ($1->numconds == 1)
				{
				for (i = SET_SIZE + hashheader.nstrchars;
				  --i >= 0;
				  )
				    {
				    if (($1->conds[i] & 1) == 0)
					break;
				    }
				if (i < 0)
				    $1->numconds = 0;
				}
			    $$ = $1;
			    }
		|	cond_or_null '>' '-' ichar_string ',' ichar_string
			    {
			    int		    i;

			    $1->stripl = icharlen ($4);
			    $1->strip = $4;
			    upcase ($4);
			    $1->affl = icharlen ($6);
			    $1->affix = $6;
			    upcase ($6);
			    /*
			     * Convert the syntax ". > -xxx,yyy" into
			     * " > -xxx,yyy", as in the code just above.
			     */
			    if ($1->numconds == 1)
				{
				for (i = SET_SIZE + hashheader.nstrchars;
				  --i >= 0;
				  )
				    {
				    if (($1->conds[i] & 1) == 0)
					break;
				    }
				if (i < 0)
				    $1->numconds = 0;
				}
			    $$ = $1;
			    }
		|	cond_or_null '>' '-' ichar_string ',' '-'
			    {
			    int		    i;

			    $1->stripl = icharlen ($4);
			    $1->strip = $4;
			    upcase ($4);
			    $1->affl = 0;
			    $1->affix = NULL;
			    /*
			     * Convert the syntax ". > -xxx," into
			     * " > -xxx,", as in the code just above.
			     */
			    if ($1->numconds == 1)
				{
				for (i = SET_SIZE + hashheader.nstrchars;
				  --i >= 0;
				  )
				    {
				    if (($1->conds[i] & 1) == 0)
					break;
				    }
				if (i < 0)
				    $1->numconds = 0;
				}
			    $$ = $1;
			    }
		;

cond_or_null	:	/* Empty */
			    {
			    struct flagent *	ent;

			    ent = (struct flagent *)
			      malloc (sizeof (struct flagent));
			    if (ent == NULL)
				{
				yyerror (PARSE_Y_NO_SPACE);
				exit (1);
				}
			    ent->numconds = 0;
			    (void) bzero (ent->conds,
			       SET_SIZE + MAXSTRINGCHARS);
			    $$ = ent;
			    }
		|	conditions
		;

conditions	:	char_set
			    {
			    struct flagent *	ent;
			    int			i;

			    ent = (struct flagent *)
			      malloc (sizeof (struct flagent));
			    if (ent == NULL)
				{
				yyerror (PARSE_Y_NO_SPACE);
				exit (1);
				}
			    ent->numconds = 1;
			    (void) bzero (ent->conds,
			       SET_SIZE + MAXSTRINGCHARS);
			    /*
			     * Copy conditions to the new entry, making
			     * sure that uppercase versions are generated
			     * for lowercase input.
			     */
			    for (i = SET_SIZE + MAXSTRINGCHARS;  --i >= 0;  )
				{
				if ($1.set[i])
				    {
				    ent->conds[i] = 1;
				    if (!$1.complement)
					ent->conds[mytoupper ((ichar_t) i)] = 1;
				    }
				}
			    if ($1.complement)
				{
				for (i = SET_SIZE + MAXSTRINGCHARS;
				--i >= 0;
				)
				    {
				    if ($1.set[i] == 0)
					ent->conds[mytoupper ((ichar_t) i)] = 0;
				    }
				}
			    free ($1.set);
			    $$ = ent;
			    }
		|	conditions char_set
			    {
			    int			i;
			    int			mask;

			    if ($1->numconds >= 8)
				{
				yyerror (PARSE_Y_MANY_CONDS);
				$1->numconds = 7;
				}
			    mask = 1 << $1->numconds;
			    $1->numconds++;
			    for (i = SET_SIZE + MAXSTRINGCHARS;
			      --i >= 0;
			      )
				{
				if ($2.set[i])
				    {
				    $1->conds[i] |= mask;
				    if (!$2.complement)
					$1->conds[mytoupper ((ichar_t) i)]
					  |= mask;
				    }
				}
			    if ($2.complement)
				{
				mask = ~mask;
				for (i = SET_SIZE + MAXSTRINGCHARS;
				  --i >= 0;
				  )
				    {
				    if ($2.set[i] == 0)
					$1->conds[mytoupper ((ichar_t) i)]
					  &= mask;
				    }
				}
			    free ($2.set);
			    }
		;

ichar_string	:	STRING
			    {
			    ichar_t *tichar;

			    tichar = strtosichar ((char *) $1, 1);
			    $$ = (ichar_t *) malloc (sizeof (ichar_t)
			      * (icharlen (tichar) + 1));
			    if ($$ == NULL)
				{
				yyerror (PARSE_Y_NO_SPACE);
				exit (1);
				}
			    icharcpy ($$, tichar);
			    free ((char *) $1);
			    }
		;
%%
/*
 * Trivial lexical analyzer.
 */
static int yylex ()
    {
    int			backslashed; /* NZ if backslash appeared */
    register int	ch;	/* Next character seen */
    register unsigned char *
			lexp;	/* Pointer into lexstring */
    unsigned char	lexstring[256];	/* Space for collecting strings */

    while ((ch = grabchar ()) != EOF  &&  (isspace (ch)  ||  ch == '#'))
	{			/* Skip whitespace and comments */
	if (ch == '#')
	    {
	    while ((ch = grabchar ()) != EOF  &&  ch != '\n')
		;
	    }
	}
    switch (ch)
	{
	case EOF:
	    return EOF;
	case '"':
	    getqstring ();
	    return STRING;
	case '-':
	case '>':
	case ',':
	case ':':
	case '.':
	case '*':
	    yylval.simple = ch;
	    return ch;
	case '[':		/* Beginning of a range set */
	    getrange ();	/* Get the range */
	    return RANGE;
	}
    /*
     * We get here if the character is an ordinary one;  note that
     * this includes backslashes.
     */
    backslashed = 0;
    lexp = lexstring;
    while (1)
	{
	switch (ch)
	    {
	    case EOF:
		*lexp = '\0';
		return kwanalyze (backslashed, lexstring);
	    case '\\':
		backslashed = 1;
		ch = backch ();
		*lexp++ = ch;
		break;
	    case ' ':
	    case '\t':
	    case '\n':
	    case '\f':
	    case '\r':
		*lexp = '\0';
		return kwanalyze (backslashed, lexstring);
	    case '#':
	    case '>':
	    case ':':
	    case '-':
	    case ',':
	    case '[':
		ungrabchar (ch);
		*lexp = '\0';
		return kwanalyze (backslashed, lexstring);
	    default:
		*lexp++ = ch;
		break;
	    }
	ch = grabchar ();
	}
    }

static int kwanalyze (backslashed, str)
    int			backslashed;	/* NZ if string had a backslash */
    register unsigned char *
			str;		/* String to analyze */
    {
    register struct kwtab *
			kwptr;		/* Pointer into keyword table */

    yylval.simple = 0;
    if (!backslashed)			/* Backslash means not keyword */
	{
	for (kwptr = keywords;  kwptr->kw != NULL;  kwptr++)
	    {
	    if (strcmp (kwptr->kw, (char *) str) == 0)
		return (yylval.simple = kwptr->val);
	    }
	}
    yylval.string =
      (unsigned char *) malloc ((unsigned) strlen ((char *) str) + 1);
    if (yylval.string == NULL)
	{
	yyerror (PARSE_Y_NO_SPACE);
	exit (1);
	}
    (void) strcpy ((char *) yylval.string, (char *) str);
    return STRING;
    }

/*
 * Analyze a string in double quotes.  The leading quote has already
 * been processed.
 */
static void getqstring ()
    {
    register int	ch;		/* Next character read */
    char		lexstring[256];	/* Room to collect the string */
    register char *	lexp;		/* Pointer into lexstring */

    for (lexp = lexstring;
      (ch = grabchar ()) != EOF  &&  ch != '"'
	&&  lexp < &lexstring[sizeof lexstring - 1];
      )
	{
	if (ch == '\\')
	    ch = backch ();
	*lexp++ = ch;
	}
    *lexp++ = '\0';
    if (ch == EOF)
	yyerror (PARSE_Y_EOF);
    else if (ch != '"')
	{
	yyerror (PARSE_Y_LONG_QUOTE);
	while ((ch = grabchar ()) != EOF  &&  ch != '"')
	    {
	    if (ch == '\\')
		ch = backch ();
	    }
	}
    yylval.string = (unsigned char *) malloc ((unsigned) (lexp - lexstring));
    if (yylval.string == NULL)
	{
	yyerror (PARSE_Y_NO_SPACE);
	exit (1);
	}
    (void) strcpy ((char *) yylval.string, lexstring);
    }

/*
 * Analyze a range (e.g., [A-Za-z]).  The left square bracket
 * has already been processed.
 */
static void getrange ()			/* Parse a range set */
    {
    register int	ch;		/* Next character read */
    register int	lastch;		/* Previous char, for ranges */
    char		stringch[MAXSTRINGCHARLEN];
    int			stringchlen;

    yylval.charset.set = malloc (SET_SIZE + MAXSTRINGCHARS);
    if (yylval.charset.set == NULL)
	{
	yyerror (PARSE_Y_NO_SPACE);
	exit (1);
	}

    /* Start with a null set */
    (void) bzero (yylval.charset.set, SET_SIZE + MAXSTRINGCHARS);
    yylval.charset.complement = 0;

    lastch = -1;
    ch = grabchar ();
    if (ch == '^')
	{
	yylval.charset.complement = 1;
	ch = grabchar ();
	}
    if (ch == ']')
	{
	lastch = ']';
	yylval.charset.set[']'] = 1;
	}
    else
	ungrabchar (ch);
    while ((ch = grabchar ()) != EOF  &&  ch != ']')
	{
	if (isstringstart (ch))		/* Handle a possible string character */
	    {
	    stringch[0] = ch;
	    for (stringchlen = 1;
	      stringchlen < MAXSTRINGCHARLEN;
	      stringchlen++)
		{
		if (isstringch (stringch, 1))
		    {
		    yylval.charset.set[SET_SIZE + laststringch] = 1;
		    stringchlen = 0;
		    break;
		    }
		ch = grabchar ();
		if (ch == EOF)
		    break;
		else
		    stringch[stringchlen] = ch;
		}
	    if (stringchlen == 0)
		{
		lastch = -1;		/* String characters can't be ranges */
		continue;		/* We found a string character */
		}
	    /*
	     * Not a string character - put it back
	     */
	    while (--stringchlen > 0)
		ungrabchar (stringch[stringchlen] & 0xFF);
	    ch = stringch[0] & 0xFF;
	    }
	if (ch == '\\')
	    {
	    lastch = ch = backch ();
	    yylval.charset.set[ch] = 1;
	    continue;
	    }
	if (ch == '-')			/* Handle a range */
	    {
	    if (lastch == -1)
		{
		lastch = ch = '-';	/* Not really a range */
		yylval.charset.set['-'] = 1;
		}
	    else
		{
		ch = grabchar ();
		if (ch == EOF  ||  ch == ']')
		    {
		    lastch = ch = '-';	/* Not really range */
		    yylval.charset.set['-'] = 1;
		    if (ch != EOF)
			ungrabchar (ch);
		    }
		else
		    {
		    if (ch == '\\')
			ch = backch ();
		    while (lastch <= ch)
			yylval.charset.set[lastch++] = 1;
		    lastch = -1;
		    }
		}
	    }
	else
	    {
	    lastch = ch;
	    yylval.charset.set[ch] = 1;
	    }
	}
    if (yylval.charset.complement)
	{
	for (ch = 0;  ch < SET_SIZE + MAXSTRINGCHARS;  ch++)
	    yylval.charset.set[ch] = !yylval.charset.set[ch];
	}
    }

static int backch ()			/* Process post-backslash characters */
    {
    register int	ch;		/* Next character read */
    register int	octval;		/* Budding octal value */

    ch = grabchar ();
    if (ch == EOF)
	return '\\';
    else if (ch >= '0'  &&  ch <= '7')
	{
	octval = ch - '0';
	ch = grabchar ();
	if (ch >= '0'  &&  ch <= '7')
	    {
	    octval = octval << 3 + ch - '0';
	    ch = grabchar ();
	    if (ch >= '0'  &&  ch <= '7')
		octval = octval << 3 + ch - '0';
	    else
		ungrabchar (ch);
	    }
	else if (ch != EOF)
	    ungrabchar (ch);
	ch = octval;
	}
    else if (ch == 'x')
	{
	ch = grabchar ();
	octval = 0;
	if ((ch >= '0'  &&  ch <= '9')
	  ||  (ch >= 'a'  &&  ch <= 'f')
	  ||  (ch >= 'A'  &&  ch <= 'F'))
	    {
	    if (ch >= '0'  &&  ch <= '9')
		octval = ch - '0';
	    else if (ch >= 'a'  &&  ch <= 'f')
		octval = ch - 'a' + 0xA;
	    else if (ch >= 'A'  &&  ch <= 'F')
		octval = ch - 'A' + 0xA;
	    ch = grabchar ();
	    octval <<= 4;
	    if (ch >= '0'  &&  ch <= '9')
		octval |= ch -'0';
	    else if (ch >= 'a'  &&  ch <= 'f')
		octval |= ch - 'a' + 0xA;
	    else if (ch >= 'A'  &&  ch <= 'F')
		octval |= ch - 'A' + 0xA;
	    else if (ch != EOF)
		{
		octval >>= 4;
		ungrabchar (ch);
		}
	    }
	else if (ch != EOF)
	    ungrabchar (ch);
	ch = octval;
	}
    else
	{
	switch (ch)
	    {
	    case 'n':
		ch = '\n';
		break;
	    case 'f':
		ch = '\f';
		break;
	    case 'r':
		ch = '\r';
		break;
	    case 'b':
		ch = '\b';
		break;
	    case 't':
		ch = '\t';
		break;
	    case 'v':
		ch = '\v';
		break;
	    }
	}
    return ch;
    }

static void yyerror (str)
    char *		str;	/* Error string */
    {
    (void) fflush (stdout);
    (void) fprintf (stderr, PARSE_Y_ERROR_FORMAT(fname, lineno, str));
    (void) fflush (stderr);
    }

int yyopen (file)
    register char *	file;	/* File name to be opened */
    {
    fname = malloc ((unsigned) strlen (file) + 1);
    if (fname == NULL)
	{
	(void) fprintf (stderr, PARSE_Y_MALLOC_TROUBLE);
	exit (1);
	}
    (void) strcpy (fname, file);
    aff_file = fopen (file, "r");
    if (aff_file == NULL)
	{
	(void) fprintf (stderr, CANT_OPEN, file);
	perror ("");
	return 1;
	}
    lineno = 1;
    return 0;
    }

void yyinit ()
    {
    register int	i;	/* Loop counter */

    for (i = 0;  i < SET_SIZE + MAXSTRINGCHARS;  i++)
	{
	hashheader.lowerconv[i] = i;
	hashheader.upperconv[i] = i;
	hashheader.wordchars[i] = 0;
	hashheader.lowerchars[i] = 0;
	hashheader.upperchars[i] = 0;
	hashheader.boundarychars[i] = 0;
	/*
	 * The default sort order is a big value so that there is room
	 * to insert "underneath" it.  In this way, special characters
	 * will sort last, but in ASCII order.
	 */
	hashheader.sortorder[i] = i + 1 + 2 * SET_SIZE;
	}
    for (i = 0;  i < SET_SIZE;  i++)
	hashheader.stringstarts[i] = 0;
    for (i = 0;  i < MAXSTRINGCHARS;  i++)
	{
	hashheader.stringdups[i] = i;
	hashheader.dupnos[i] = 0;
	}
    
    hashheader.sortval = 1;	/* This is so 0 can mean uninitialized */
    (void) bcopy (NRSPECIAL, hashheader.nrchars, sizeof hashheader.nrchars);
    (void) bcopy (TEXSPECIAL, hashheader.texchars, sizeof hashheader.texchars);
    hashheader.defspaceflag = 1; /* Default is to report missing blanks */
    hashheader.defhardflag = 0; /* Default is to try hard only if failures */
    hashheader.nstrchars = 0;	/* No string characters to start with */
    hashheader.flagmarker = '/'; /* Default flag marker is slash */
    /* Set up magic number and compile options */
    hashheader.magic = MAGIC;
    hashheader.compileoptions = COMPILEOPTIONS;
    hashheader.maxstringchars = MAXSTRINGCHARS;
    hashheader.maxstringcharlen = MAXSTRINGCHARLEN;
    }

static int grabchar ()		/* Get a character and count lines */
    {
    int			ch;	/* Next input character */

    if (ungrablen > 0)
	ch = lexungrab[--ungrablen] & 0xFF;
    else
	ch = getc (aff_file);
    if (ch == '\n')
	lineno++;
    return ch;
    }

static void ungrabchar (ch)	/* Unget a character, tracking line numbers */
    int			ch;	/* Character to put back */
    {

    if (ch == '\n')
	lineno--;
    if (ch != EOF)
	{
	if (ungrablen == sizeof (lexungrab))
	    yyerror (PARSE_Y_UNGRAB_PROBLEM);
	else
	    lexungrab[ungrablen++] = ch;
	}
    }

static int sufcmp (flag1, flag2)	/* Compare suffix flags for qsort */
    register struct flagent *	flag1;	/* Flags to be compared */
    register struct flagent *	flag2;	/* ... */
    {
    register ichar_t *		cp1;	/* Pointer into flag1's suffix */
    register ichar_t *		cp2;	/* Pointer into flag2's suffix */

    if (flag1->affl == 0  ||  flag2->affl == 0)
	return flag1->affl - flag2->affl;
    cp1 = flag1->affix + flag1->affl;
    cp2 = flag2->affix + flag2->affl;
    while (*--cp1 == *--cp2  &&  cp1 > flag1->affix  &&  cp2 > flag2->affix)
	;
    if (*cp1 == *cp2)
	{
	if (cp1 == flag1->affix)
	    {
	    if (cp2 == flag2->affix)
		return 0;
	    else
		return -1;
	    }
	else
	    return 1;
	}
    return *cp1 - *cp2;
    }

static int precmp (flag1, flag2)	/* Compare prefix flags for qsort */
    register struct flagent *	flag1;	/* Flags to be compared */
    register struct flagent *	flag2;	/* ... */
    {

    if (flag1->affl == 0  ||  flag2->affl == 0)
	return flag1->affl - flag2->affl;
    else
	return icharcmp (flag1->affix, flag2->affix);
    }

static int addstringchar (str, lower, upper) /* Add a string character */
    register unsigned char *	str;	/* String character to be added */
    int				lower;	/* NZ if a lower string */
    int				upper;	/* NZ if an upper string */
    {
    int				len;	/* Length of the string */
    register int		mslot;	/* Slot being moved or modified */
    register int		slot;	/* Where to put it */

    len = strlen ((char *) str);
    if (len > MAXSTRINGCHARLEN)
	{
	yyerror (PARSE_Y_LONG_STRING);
	}
    else if (len == 0)
	{
	yyerror (PARSE_Y_NULL_STRING);
	return -1;
	}
    else if (hashheader.nstrchars >= MAXSTRINGCHARS)
	{
	yyerror (PARSE_Y_MANY_STRINGS);
	return -1;
	}

    /*
     * Find where to put the new character
     */
    for (slot = 0;  slot < hashheader.nstrchars;  slot++)
	{
	if (stringcharcmp (&hashheader.stringchars[slot][0], (char *) str) > 0)
	    break;
	}
    /*
     * Fix all duplicate numbers to reflect the new slot.
     */
    for (mslot = hashheader.nstrchars;  --mslot >= 0;  )
	{
	if (hashheader.stringdups[mslot] >= slot)
	    hashheader.stringdups[mslot]++;
	}
    /*
     * Fix all characters before it so that their case conversion reflects
     * the new locations of the characters that will follow the new one.
     */
    slot += SET_SIZE;
    for (mslot = SET_SIZE;  mslot < slot;  mslot++)
	{
	if (hashheader.lowerconv[mslot] >= slot)
	    hashheader.lowerconv[mslot]++;
	if (hashheader.upperconv[mslot] >= slot)
	    hashheader.upperconv[mslot]++;
	}
    /*
     * Slide up all the other characters to make room for the new one, also
     * making the appropriate changes in the case-conversion tables.
     */
    for (mslot = hashheader.nstrchars + SET_SIZE;  --mslot >= slot;  )
	{
	(void) strcpy (&hashheader.stringchars[mslot + 1 - SET_SIZE][0],
	  &hashheader.stringchars[mslot - SET_SIZE][0]);
	hashheader.lowerchars[mslot + 1] = hashheader.lowerchars[mslot];
	hashheader.upperchars[mslot + 1] = hashheader.upperchars[mslot];
	hashheader.wordchars[mslot + 1] = hashheader.wordchars[mslot];
	hashheader.boundarychars[mslot + 1] = hashheader.boundarychars[mslot];
	if (hashheader.lowerconv[mslot] >= slot)
	    hashheader.lowerconv[mslot]++;
	if (hashheader.upperconv[mslot] >= slot)
	    hashheader.upperconv[mslot]++;
	hashheader.lowerconv[mslot + 1] = hashheader.lowerconv[mslot];
	hashheader.upperconv[mslot + 1] = hashheader.upperconv[mslot];
	hashheader.sortorder[mslot + 1] = hashheader.sortorder[mslot];
	hashheader.stringdups[mslot + 1 - SET_SIZE] =
	  hashheader.stringdups[mslot - SET_SIZE];
	hashheader.dupnos[mslot + 1 - SET_SIZE] =
	  hashheader.dupnos[mslot - SET_SIZE];
	}
    /*
     * Insert the new string character into the slot we made.  The
     * caller may choose to change the case-conversion field.
     */
    (void) strcpy (&hashheader.stringchars[slot - SET_SIZE][0], (char *) str);
    hashheader.lowerchars[slot] = lower;
    hashheader.upperchars[slot] = upper;
    hashheader.wordchars[slot] = 1;
    hashheader.boundarychars[slot] = 0;
    hashheader.sortorder[slot] = hashheader.sortval++;
    hashheader.lowerconv[slot] = slot;
    hashheader.upperconv[slot] = slot;
    hashheader.stringdups[slot - SET_SIZE] = slot - SET_SIZE;
    hashheader.dupnos[slot - SET_SIZE] = 0;
    /*
     * Add the first character of the string to the string-starts table, and
     * bump the count.
     */
    hashheader.stringstarts[str[0]] = 1;
    hashheader.nstrchars++;
    return slot;
    }

/*
 * This routine is a reimplemention of strcmp(), needed because the
 * idiots at Sun managed to screw up the implementation of strcmp on
 * Sun 4's (they used unsigned comparisons, even though characters
 * default to signed).  I hate hate HATE putting in this routine just
 * to support the stupidity of one programmer who ought to find a new
 * career digging ditches, but there are a lot of Sun 4's out there,
 * so I don't really have a lot of choice.
 */
#ifdef SUN4_IS_STUPID
static int stringcharcmp (a,  b)
    register char *		a;
    register char *		b;
    {

    while (*a != '\0')
	{
	if (*a++ != *b++)
	    return *--a - *--b;
	}
    return *a - *b;
    }
#endif /* SUN_4_IS_STUPID */

#ifdef TBLDEBUG
static void tbldump (flagp, numflags)	/* Dump a flag table */
    register struct flagent *	flagp;	/* First flag entry to dump */
    register int		numflags; /* Number of flags to dump */
    {
    while (--numflags >= 0)
	entdump (flagp++);
    }

static void entdump (flagp)		/* Dump one flag entry */
    register struct flagent *	flagp;	/* Flag entry to dump */
    {
    register int		cond;	/* Condition number */

    (void) fprintf (stderr, "flag %s%c:\t",
      (flagp->flagflags & FF_CROSSPRODUCT) ? "*" : "",
      BITTOCHAR (flagp->flagbit));
    for (cond = 0;  cond < flagp->numconds;  cond++)
	{
	setdump (flagp->conds, 1 << cond);
	if (cond < flagp->numconds - 1)
	    (void) putc (' ', stderr);
	}
    if (cond == 0)			/* No conditions at all? */
	(void) putc ('.', stderr);
    (void) fprintf (stderr, "\t> ");
    (void) putc ('\t', stderr);
    if (flagp->stripl)
	(void) fprintf (stderr, "-%s,", ichartosstr (flagp->strip, 1));
    (void) fprintf (stderr, "%s\n",
      flagp->affl ? ichartosstr (flagp->affix, 1) : "-");
    }

static void setdump (setp, mask)	/* Dump a set specification */
    register char *		setp;	/* Set to be dumped */
    register int		mask;	/* Mask for bit to be dumped */
    {
    register int		cnum;	/* Next character's number */
    register int		firstnz; /* Number of first NZ character */
    register int		numnz;	/* Number of NZ characters */

    numnz = 0;
    for (cnum = SET_SIZE + hashheader.nstrchars;  --cnum >= 0;  )
	{
	if (setp[cnum] & mask)
	    {
	    numnz++;
	    firstnz = cnum;
	    }
	}
    if (numnz == 1)
	{
	if (cnum < SET_SIZE)
	    (void) putc (firstnz, stderr);
	else
	    (void) fputs (hashheader.stringchars[cnum - SET_SIZE], stderr);
	}
    else if (numnz == SET_SIZE)
	(void) putc ('.', stderr);
    else if (numnz > SET_SIZE / 2)
	{
	(void) fprintf (stderr, "[^");
	subsetdump (setp, mask, 0);
	(void) putc (']', stderr);
	}
    else
	{
	(void) putc ('[', stderr);
	subsetdump (setp, mask, mask);
	(void) putc (']', stderr);
	}
    }

static void subsetdump (setp, mask, dumpval) /* Dump part of a set spec */
    register char *		setp;	/* Set to be dumped */
    register int		mask;	/* Mask for bit to be dumped */
    register int		dumpval; /* Value to be printed */
    {
    register int		cnum;	/* Next character's number */
    register int		rangestart; /* Value starting a range */

    for (cnum = 0;  cnum < SET_SIZE;  setp++, cnum++)
	{
	if (((*setp ^ dumpval) & mask) == 0)
	    {
	    for (rangestart = cnum;  cnum < SET_SIZE;  setp++, cnum++)
		{
		if ((*setp ^ dumpval) & mask)
		    break;
		}
	    if (cnum == rangestart + 1)
		(void) putc (rangestart, stderr);
	    else if (cnum <= rangestart + 3)
		{
		while (rangestart < cnum)
		    {
		    (void) putc (rangestart, stderr);
		    rangestart++;
		    }
		}
	    else
		(void) fprintf (stderr, "%c-%c", rangestart, cnum - 1);
	    }
	}
    for (  ;  cnum < SET_SIZE + hashheader.nstrchars;  setp++, cnum++)
	{
	if (((*setp ^ dumpval) & mask) == 0)
	    (void) fputs (hashheader.stringchars[cnum - SET_SIZE], stderr);
	}
    }
#endif
