/* -*- Mode:Text -*- */
#ifndef lint
static char Rcs_Id[] =
    "$Id: tgood.c,v 1.18 91/09/11 23:23:00 geoff Exp $";
#endif

/*
 * Copyright 1987, 1988, 1989, by Geoff Kuenning, Manhattan Beach, CA
 * Permission for non-profit use is hereby granted.
 * All other rights reserved.
 * See "version.h" for a more complete copyright notice.
 */

/*
 * $Log:	tgood.c,v $
 * Revision 1.18  91/09/11  23:23:00  geoff
 * Make sure that pure-deletion suffixes operate correctly, by adding the
 * necessary null byte after the strip portion.
 * 
 * Revision 1.17  91/07/11  19:52:27  geoff
 * Remove the include of stdio.h, since ispell.h now does this.
 * 
 * Revision 1.16  91/05/27  21:48:12  geoff
 * When checking affixes, remember to check for the case of affixes that
 * strip nothing and only add characters.
 * 
 * Revision 1.15  90/12/31  01:04:16  geoff
 * Add a text-mode line so emacs will edit consistently.
 * 
 * Revision 1.14  90/04/26  22:44:28  geoff
 * Add the canonicalize parameter to calls to ichartosstr.
 * 
 * Revision 1.13  89/06/09  15:56:32  geoff
 * Add support for the internal "character" type, ichar_t.
 * 
 * Revision 1.12  89/04/27  23:34:27  geoff
 * In suf_list_chk, call setfindlast just before the condition check, because
 * the word changes on each pass through the loop.  Also call findlastchar
 * with the correct parameters.
 * 
 * Revision 1.11  89/04/03  01:58:53  geoff
 * Fix a bunch of lint complaints.  Add (partial) support for string
 * characters.
 * 
 * Revision 1.10  88/12/26  02:33:04  geoff
 * Update the copyright notice.
 * 
 * Revision 1.9  88/11/25  19:54:28  geoff
 * Fix some inconsistencies in the formatting of curly braces.
 * 
 * Revision 1.8  88/04/30  22:16:04  geoff
 * Fix some lint complaints.
 * 
 * Revision 1.7  88/02/20  23:14:44  geoff
 * Support the new capitalization handling.  Add the "allhits" parameter to
 * chk_aff and its descendents.
 * 
 * Revision 1.6  87/10/02  22:38:11  geoff
 * Modify expansion routines to exactly duplicate the capitalization
 * rules implemented by ins_cap and cap_ok.
 * 
 * Revision 1.5  87/09/30  23:31:48  geoff
 * Move some globals to ispell.h.  Add support for ignoreflagbits.
 * Rationalize some array sizes.  Get rid of the checkbit calls and
 * replace them with a macro call.
 * 
 * Revision 1.4  87/09/26  15:41:25  geoff
 * Make the sizes of the word buffers consistent.
 * 
 * Revision 1.3  87/09/14  22:39:19  geoff
 * Add copyright comments
 * 
 * Revision 1.2  87/09/09  00:19:43  geoff
 * Replace a lot of #ifdef's on NO8BIT with use of SET_SIZE and NOPARITY.
 * 
 * Revision 1.1  87/07/20  22:56:14  geoff
 * Initial revision
 * 
 */

/*
 * Table-driven version of good.c.
 *
 * Geoff Kuenning, July 1987
 */

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

extern struct dent *	lookup ();	/* Look up a dictionary entry */

extern char *		strcpy ();

/* Check possible affixes */
chk_aff (word, ucword, len, ignoreflagbits, allhits)
    ichar_t *		word;		/* Word to be checked */
    ichar_t *		ucword;		/* Upper-case-only copy of word */
    int			len;		/* The length of word/ucword */
    int			ignoreflagbits;	/* Ignore whether affix is legal */
    int			allhits;	/* Keep going after first hit */
    {
    register ichar_t *	cp;		/* Pointer to char to index on */
    struct flagptr *	ind;		/* Flag index table to test */

    pfx_list_chk (word, ucword, len, &pflagindex[0], ignoreflagbits, allhits);
    cp = ucword;
    ind = &pflagindex[*cp++];
    while (ind->numents == 0  &&  ind->pu.fp != NULL)
	{
	if (*cp == 0)
	    return;
	if (ind->pu.fp[0].numents)
	    {
	    pfx_list_chk (word, ucword, len, &ind->pu.fp[0],
	      ignoreflagbits, allhits);
	    if (numhits  &&  !allhits  &&  !cflag  &&  !ignoreflagbits)
		return;
	    }
	ind = &ind->pu.fp[*cp++];
	}
    pfx_list_chk (word, ucword, len, ind, ignoreflagbits, allhits);
    if (numhits  &&  !allhits  &&  !cflag  &&  !ignoreflagbits)
	return;
    chk_suf (word, ucword, len, 0, (struct flagent *) NULL,
      ignoreflagbits, allhits);
    }

/* Check some prefix flags */
pfx_list_chk (word, ucword, len, ind, ignoreflagbits, allhits)
    ichar_t *		word;		/* Word to be checked */
    ichar_t *		ucword;		/* Upper-case-only word */
    int			len;		/* The length of ucword */
    struct flagptr *	ind;		/* Flag index table */
    int			ignoreflagbits;	/* Ignore whether affix is legal */
    int			allhits;	/* Keep going after first hit */
    {
    int			cond;		/* Condition number */
    register ichar_t *	cp;		/* Pointer into end of ucword */
    struct dent *	dent;		/* Dictionary entry we found */
    int			entcount;	/* Number of entries to process */
    register struct flagent *
			flent;		/* Current table entry */
    int			preadd;		/* Length added to tword2 as prefix */
    register int	tlen;		/* Length of tword */
    ichar_t		tword[INPUTWORDLEN + 4 * MAXAFFIXLEN + 4]; /* Tmp cpy */
    ichar_t		tword2[sizeof tword]; /* 2nd copy for ins_root_cap */

    for (flent = ind->pu.ent, entcount = ind->numents;
      entcount > 0;
      flent++, entcount--)
	{
	/*
	 * See if the prefix matches.
	 */
	tlen = len - flent->affl + flent->stripl;
	if (tlen > 0  &&  icharncmp (flent->affix, ucword, flent->affl) == 0
	  &&  tlen >= flent->numconds)
	    {
	    /*
	     * The prefix matches.  Remove it, replace it by the "strip"
	     * string (if any), and check the original conditions.
	     */
	    if (flent->stripl)
		(void) icharcpy (tword, flent->strip);
	    (void) icharcpy (tword + flent->stripl, ucword + flent->affl);
	    cp = tword;
	    for (cond = 0;  cond < flent->numconds;  cond++)
		{
		if ((flent->conds[*cp++] & (1 << cond)) == 0)
		    break;
		}
	    if (cond >= flent->numconds)
		{
		/*
		 * The conditions match.  See if the word is in the
		 * dictionary.
		 */
		if (cflag)
		    flagpr (tword, BITTOCHAR (flent->flagbit), flent->stripl,
		      flent->affl, -1, 0);
		else if (ignoreflagbits)
		    {
		    if ((dent = lookup (tword, 1)) != NULL)
			{
			cp = tword2;
			if (flent->affl)
			    {
			    (void) icharcpy (cp, flent->affix);
			    cp += flent->affl;
			    *cp++ = '+';
			    }
			preadd = cp - tword2;
			(void) icharcpy (cp, tword);
			cp += tlen;
			if (flent->stripl)
			    {
			    *cp++ = '-';
			    (void) icharcpy (cp, flent->strip);
			    }
			(void) ins_root_cap (tword2, word,
			  flent->stripl, preadd,
			  0, (cp - tword2) - tlen - preadd,
			  dent, flent, (struct flagent *) NULL);
			}
		    }
		else if ((dent = lookup (tword, 1)) != NULL
		  &&  TSTMASKBIT (dent->mask, flent->flagbit))
		    {
		    if (numhits < MAX_HITS)
			{
			hits[numhits].dictent = dent;
			hits[numhits].prefix = flent;
			hits[numhits].suffix = NULL;
			numhits++;
			}
		    if (!allhits)
			return;
		    }
		/*
		 * Handle cross-products.
		 */
		if (flent->flagflags & FF_CROSSPRODUCT)
		    chk_suf (word, tword, tlen, 1, flent,
		      ignoreflagbits, allhits);
		}
	    }
	}
    }

/* Check possible suffixes */
chk_suf (word, ucword, len, crossonly, pfxent, ignoreflagbits, allhits)
    ichar_t *		word;		/* Word to be checked */
    ichar_t *		ucword;		/* Upper-case-only word */
    int			len;		/* The length of ucword */
    int			crossonly;	/* NZ to do only cross-products */
    struct flagent *	pfxent;		/* Prefix flag entry if crossonly */
    int			ignoreflagbits;	/* Ignore whether affix is legal */
    int			allhits;	/* Keep going after first hit */
    {
    register ichar_t *	cp;		/* Pointer to char to index on */
    struct flagptr *	ind;		/* Flag index table to test */

    suf_list_chk (word, ucword, len, &sflagindex[0], crossonly, pfxent,
      ignoreflagbits, allhits);
    cp = ucword + len - 1;
    ind = &sflagindex[*cp];
    while (ind->numents == 0  &&  ind->pu.fp != NULL)
	{
	if (cp == ucword)
	    return;
	if (ind->pu.fp[0].numents)
	    {
	    suf_list_chk (word, ucword, len, &ind->pu.fp[0],
	      crossonly, pfxent, ignoreflagbits, allhits);
	    if (numhits != 0  &&  !allhits  &&  !cflag  &&  !ignoreflagbits)
		return;
	    }
	ind = &ind->pu.fp[*--cp];
	}
    suf_list_chk (word, ucword, len, ind, crossonly, pfxent,
      ignoreflagbits, allhits);
    }
    
suf_list_chk (word, ucword, len, ind, crossonly, pfxent,
  ignoreflagbits, allhits)
    ichar_t *		word;		/* Word to be checked */
    ichar_t *		ucword;		/* Upper-case-only word */
    int			len;		/* The length of ucword */
    struct flagptr *	ind;		/* Flag index table */
    int			crossonly;	/* NZ to do only cross-products */
    struct flagent *	pfxent;		/* Prefix flag entry if crossonly */
    int			ignoreflagbits;	/* Ignore whether affix is legal */
    int			allhits;	/* Keep going after first hit */
    {
    register ichar_t *	cp;		/* Pointer into end of ucword */
    int			cond;		/* Condition number */
    struct dent *	dent;		/* Dictionary entry we found */
    int			entcount;	/* Number of entries to process */
    register struct flagent *
			flent;		/* Current table entry */
    int			preadd;		/* Length added to tword2 as prefix */
    register int	tlen;		/* Length of tword */
    ichar_t		tword[INPUTWORDLEN + 4 * MAXAFFIXLEN + 4]; /* Tmp cpy */
    ichar_t		tword2[sizeof tword]; /* 2nd copy for ins_root_cap */

    (void) icharcpy (tword, ucword);
    for (flent = ind->pu.ent, entcount = ind->numents;
      entcount > 0;
      flent++, entcount--)
	{
	if (crossonly  &&  (flent->flagflags & FF_CROSSPRODUCT) == 0)
	    continue;
	/*
	 * See if the suffix matches.
	 */
	tlen = len - flent->affl;
	if (tlen > 0
	  &&  tlen + flent->stripl > 1
	  &&  (flent->affl == 0
	    ||  icharcmp (flent->affix, ucword + tlen) == 0)
	  &&  tlen + flent->stripl >= flent->numconds)
	    {
	    /*
	     * The suffix matches.  Remove it, replace it by the "strip"
	     * string (if any), and check the original conditions.
	     */
	    (void) icharcpy (tword, ucword);
	    cp = tword + tlen;
	    if (flent->stripl)
		{
		(void) icharcpy (cp, flent->strip);
		tlen += flent->stripl;
		cp = tword + tlen;
		}
	    else
		*cp = '\0';
	    for (cond = flent->numconds;  --cond >= 0;  )
		{
		if ((flent->conds[*--cp] & (1 << cond)) == 0)
		    break;
		}
	    if (cond < 0)
		{
		/*
		 * The conditions match.  See if the word is in the
		 * dictionary.
		 */
		if (cflag)
		    {
		    if (crossonly)
			flagpr (tword, BITTOCHAR (pfxent->flagbit),
			  pfxent->stripl, pfxent->affl,
			  BITTOCHAR (flent->flagbit), flent->affl);
		    else
			flagpr (tword, -1, 0, 0,
			  BITTOCHAR (flent->flagbit), flent->affl);
		    }
		else if (ignoreflagbits)
		    {
		    if ((dent = lookup (tword, 1)) != NULL)
			{
			cp = tword2;
			if (crossonly  &&  pfxent->affl != 0)
			    {
			    (void) icharcpy (cp, pfxent->affix);
			    cp += pfxent->affl;
			    *cp++ = '+';
			    }
			preadd = cp - tword2;
			(void) icharcpy (cp, tword);
			cp += tlen;
			if (crossonly  &&  pfxent->stripl != 0)
			    {
			    *cp++ = '-';
			    (void) icharcpy (cp, pfxent->strip);
			    cp += pfxent->stripl;
			    }
			if (flent->stripl)
			    {
			    *cp++ = '-';
			    (void) icharcpy (cp, flent->strip);
			    cp += flent->stripl;
			    }
			if (flent->affl)
			    {
			    *cp++ = '+';
			    (void) icharcpy (cp, flent->affix);
			    cp += flent->affl;
			    }
			(void) ins_root_cap (tword2, word,
			  crossonly ? pfxent->stripl : 0, preadd,
			  flent->stripl, (cp - tword2) - tlen - preadd,
			  dent, pfxent, flent);
			}
		    }
		else if ((dent = lookup (tword, 1)) != NULL
		  &&  TSTMASKBIT (dent->mask, flent->flagbit)
		  &&  (!crossonly || TSTMASKBIT (dent->mask, pfxent->flagbit)))
		    {
		    if (numhits < MAX_HITS)
			{
			hits[numhits].dictent = dent;
			hits[numhits].prefix = pfxent;
			hits[numhits].suffix = flent;
			numhits++;
			}
		    if (!allhits)
			return;
		    }
		}
	    }
	}
    }

/*
 * Expand a dictionary prefix entry
 */
expand_pre (rootword, mask)
    ichar_t *			rootword;	/* Root word to expand */
    register MASKTYPE		mask[];		/* Mask bits to expand on */
    {
    int				entcount;	/* No. of entries to process */
    register struct flagent *
				flent;		/* Current table entry */

    for (flent = pflaglist, entcount = numpflags;
      entcount > 0;
      flent++, entcount--)
	{
	if (TSTMASKBIT (mask, flent->flagbit))
	    pr_pre_expansion (rootword, flent, mask);
	}
    }

pr_pre_expansion (rootword, flent, mask) /* Print a prefix expansion */
    register ichar_t *		rootword; /* Root word to expand */
    register struct flagent *	flent;	/* Current table entry */
    MASKTYPE			mask[];		/* Mask bits to expand on */
    {
    int				cond;	/* Current condition number */
    register ichar_t *		nextc;	/* Next case choice */
    int				tlen;	/* Length of tword */
    ichar_t			tword[INPUTWORDLEN + MAXAFFIXLEN]; /* Tmp cpy */

    tlen = icharlen (rootword);
    if (flent->numconds > tlen)
	return;
    if (tlen - flent->stripl + flent->affl <= 0)
	return;
    for (cond = 0, nextc = rootword;  cond < flent->numconds;  cond++)
	{
	if ((flent->conds[mytoupper (*nextc++)] & (1 << cond)) == 0)
	    return;
	}
    /*
     * The conditions are satisfied.  Copy the word, add the prefix,
     * and make it the proper case.   This code is carefully written
     * to match that ins_cap and cap_ok.  Note that the affix, as
     * inserted, is uppercase.
     *
     * There is a tricky bit here:  if the root is capitalized, we
     * want a capitalized result.  If the root is followcase, however,
     * we want to duplicate the case of the first remaining letter
     * of the root.  In other words, "Loved/U" should generate "Unloved",
     * but "LOved/U" should generate "UNLOved" and "lOved/U" should
     * produce "unlOved".
     */
    if (flent->affl)
	{
	(void) icharcpy (tword, flent->affix);
	nextc = tword + flent->affl;
	}
    (void) icharcpy (nextc, rootword + flent->stripl);
    if (myupper (rootword[0]))
	{
	/* We must distinguish followcase from capitalized */
	for (nextc = rootword + 1;  *nextc;  )
	    {
	    if (!myupper (*nextc++))
		break;
	    }
	if (*nextc)
	    {
	    /* It's a followcase or all-upper word.  We don't care which. */
	    if (!myupper (tword[flent->affl]));
		forcelc (tword, flent->affl);
	    }
	else
	    {
	    /* It's capitalized */
	    forcelc (tword + 1, tlen + flent->affl - flent->stripl);
	    }
	}
    else
	{
	/* Followcase or all-lower, we don't care which */
	if (!myupper (*nextc));
	    forcelc (tword, flent->affl);
	}
    (void) printf (" %s", ichartosstr (tword, 1));
    if (flent->flagflags & FF_CROSSPRODUCT)
	expand_suf (tword, mask, 1);
    }

/*
 * Expand a dictionary suffix entry
 */
expand_suf (rootword, mask, crossonly)
    ichar_t *			rootword;	/* Root word to expand */
    register MASKTYPE		mask[];		/* Mask bits to expand on */
    int				crossonly;	/* NZ for cross-products only */
    {
    int				entcount;	/* No. of entries to process */
    register struct flagent *
				flent;		/* Current table entry */

    for (flent = sflaglist, entcount = numsflags;
      entcount > 0;
      flent++, entcount--)
	{
	if (TSTMASKBIT (mask, flent->flagbit))
	    {
	    if (!crossonly  ||  (flent->flagflags & FF_CROSSPRODUCT))
		pr_suf_expansion (rootword, flent);
	    }
	}
    }

pr_suf_expansion (rootword, flent)	/* Print a suffix expansion */
    register ichar_t *		rootword; /* Root word to expand */
    register struct flagent *	flent;	/* Current table entry */
    {
    int				cond;	/* Current condition number */
    register ichar_t *		nextc;	/* Next case choice */
    int				tlen;	/* Length of tword */
    ichar_t			tword[INPUTWORDLEN + MAXAFFIXLEN]; /* Tmp cpy */

    tlen = icharlen (rootword);
    cond = flent->numconds;
    if (cond > tlen)
	return;
    if (tlen - flent->stripl + flent->affl <= 0)
	return;
    for (nextc = rootword + tlen;  --cond >= 0;  )
	{
	if ((flent->conds[mytoupper (*--nextc)] & (1 << cond)) == 0)
		return;
	}
    /*
     * The conditions are satisfied.  Copy the word, add the suffix,
     * and make it match the case of the last remaining character of the
     * root.  Again, this code carefully matches ins_cap and cap_ok.
     */
    (void) icharcpy (tword, rootword);
    nextc = tword + tlen - flent->stripl;
    if (flent->affl)
	{
	(void) icharcpy (nextc, flent->affix);
	if (!myupper (nextc[-1]))
	    forcelc (nextc, flent->affl);
	}
    else
	*nextc = 0;
    (void) printf (" %s", ichartosstr (tword, 1));
    }

forcelc (dst, len)				/* Force to lowercase */
    register ichar_t *		dst;		/* Destination to modify */
    register int		len;		/* Length to copy */
    {

    for (  ;  --len >= 0;  dst++)
	*dst = mytolower (*dst);
    }
