Tizen 2.1 base
[external/enchant.git] / src / ispell / tgood.cpp
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright 1987, 1988, 1989, 1992, 1993, Geoff Kuenning, Granada Hills, CA
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All modifications to the source code must be clearly marked as
16  *    such.  Binary redistributions based on modified source code
17  *    must be clearly marked as modified versions in the documentation
18  *    and/or other materials provided with the distribution.
19  * 4. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgment:
21  *      This product includes software developed by Geoff Kuenning and
22  *      other unpaid contributors.
23  * 5. The name of Geoff Kuenning may not be used to endorse or promote
24  *    products derived from this software without specific prior
25  *    written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY GEOFF KUENNING AND CONTRIBUTORS ``AS IS'' AND
28  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30  * ARE DISCLAIMED.  IN NO EVENT SHALL GEOFF KUENNING OR CONTRIBUTORS BE LIABLE
31  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37  * SUCH DAMAGE.
38  */
39
40 /*
41  * Table-driven version of good.c.
42  *
43  * Geoff Kuenning, July 1987
44  */
45
46 /*
47  * $Log$
48  * Revision 1.4  2003/08/14 17:51:29  dom
49  * update license - exception clause should be Lesser GPL
50  *
51  * Revision 1.3  2003/07/28 20:40:28  dom
52  * fix up the license clause, further win32-registry proof some directory getting functions
53  *
54  * Revision 1.2  2003/07/16 22:52:56  dom
55  * LGPL + exception license
56  *
57  * Revision 1.1  2003/07/15 01:15:09  dom
58  * ispell enchant backend
59  *
60  * Revision 1.2  2003/01/29 05:50:12  hippietrail
61  *
62  * Fixed my mess in EncodingManager.
63  * Changed many C casts to C++ casts.
64  *
65  * Revision 1.1  2003/01/24 05:52:36  hippietrail
66  *
67  * Refactored ispell code. Old ispell global variables had been put into
68  * an allocated structure, a pointer to which was passed to many functions.
69  * I have now made all such functions and variables private members of the
70  * ISpellChecker class. It was C OO, now it's C++ OO.
71  *
72  * I've fixed the makefiles and tested compilation but am unable to test
73  * operation. Please back out my changes if they cause problems which
74  * are not obvious or easy to fix.
75  *
76  * Revision 1.6  2003/01/06 18:48:42  dom
77  * ispell cleanup, start of using new 'add' save features
78  *
79  * Revision 1.5  2002/09/19 05:31:20  hippietrail
80  *
81  * More Ispell cleanup.  Conditional globals and DEREF macros are removed.
82  * K&R function declarations removed, converted to Doxygen style comments
83  * where possible.  No code has been changed (I hope).  Compiles for me but
84  * unable to test.
85  *
86  * Revision 1.4  2002/09/17 03:03:31  hippietrail
87  *
88  * After seeking permission on the developer list I've reformatted all the
89  * spelling source which seemed to have parts which used 2, 3, 4, and 8
90  * spaces for tabs.  It should all look good with our standard 4-space
91  * tabs now.
92  * I've concentrated just on indentation in the actual code.  More prettying
93  * could be done.
94  * * NO code changes were made *
95  *
96  * Revision 1.3  2002/09/13 17:20:14  mpritchett
97  * Fix more warnings for Linux build
98  *
99  * Revision 1.2  2001/05/12 16:05:42  thomasf
100  * Big pseudo changes to ispell to make it pass around a structure rather
101  * than rely on all sorts of gloabals willy nilly here and there.  Also
102  * fixed our spelling class to work with accepting suggestions once more.
103  * This code is dirty, gross and ugly (not to mention still not supporting
104  * multiple hash sized just yet) but it works on my machine and will no
105  * doubt break other machines.
106  *
107  * Revision 1.1  2001/04/15 16:01:24  tomas_f
108  * moving to spell/xp
109  *
110  * Revision 1.7  1999/10/20 06:03:56  sterwill
111  * Changed C++-style comments to C-style comments in C code.
112  *
113  * Revision 1.6  1999/10/20 03:19:35  paul
114  * Hacked ispell code to ignore any characters that don't fit in the lookup tables loaded from the dictionary.  It ain't pretty, but at least we don't crash there any more.
115  *
116  * Revision 1.5  1999/04/13 17:12:51  jeff
117  * Applied "Darren O. Benham" <gecko@benham.net> spell check changes.
118  * Fixed crash on Win32 with the new code.
119  *
120  * Revision 1.4  1998/12/29 14:55:33  eric
121  *
122  * I've doctored the ispell code pretty extensively here.  It is now
123  * warning-free on Win32.  It also *works* on Win32 now, since I
124  * replaced all the I/O calls with ANSI standard ones.
125  *
126  * Revision 1.4  1998/12/29 14:55:33  eric
127  *
128  * I've doctored the ispell code pretty extensively here.  It is now
129  * warning-free on Win32.  It also *works* on Win32 now, since I
130  * replaced all the I/O calls with ANSI standard ones.
131  *
132  * Revision 1.3  1998/12/28 23:11:30  eric
133  *
134  * modified spell code and integration to build on Windows.
135  * This is still a hack.
136  *
137  * Actually, it doesn't yet WORK on Windows.  It just builds.
138  * SpellCheckInit is failing for some reason.
139  *
140  * Revision 1.2  1998/12/28 22:16:22  eric
141  *
142  * These changes begin to incorporate the spell checker into AbiWord.  Most
143  * of this is a hack.
144  *
145  * 1.  added other/spell to the -I list in config/abi_defs
146  * 2.  replaced other/spell/Makefile with one which is more like
147  *      our build system.
148  * 3.  added other/spell to other/Makefile so that the build will now
149  *      dive down and build the spell check library.
150  * 4.  added the AbiSpell library to the Makefiles in wp/main
151  * 5.  added a call to SpellCheckInit in wp/main/unix/UnixMain.cpp.
152  *      This call is a HACK and should be replaced with something
153  *      proper later.
154  * 6.  added code to fv_View.cpp as follows:
155  *      whenever you double-click on a word, the spell checker
156  *      verifies that word and prints its status to stdout.
157  *
158  * Caveats:
159  * 1.  This will break the Windows build.  I'm going to work on fixing it
160  *      now.
161  * 2.  This only works if your dictionary is in /usr/lib/ispell/american.hash.
162  *      The dictionary location is currently hard-coded.  This will be
163  *      fixed as well.
164  *
165  * Anyway, such as it is, it works.
166  *
167  * Revision 1.1  1998/12/28 18:04:43  davet
168  * Spell checker code stripped from ispell.  At this point, there are
169  * two external routines...  the Init routine, and a check-a-word routine
170  * which returns a boolean value, and takes a 16 bit char string.
171  * The code resembles the ispell code as much as possible still.
172  *
173  * Revision 1.32  1994/11/02  06:56:16  geoff
174  * Remove the anyword feature, which I've decided is a bad idea.
175  *
176  * Revision 1.31  1994/10/25  05:46:25  geoff
177  * Add support for the FF_ANYWORD (affix applies to all words, even if
178  * flag bit isn't set) flag option.
179  *
180  * Revision 1.30  1994/05/24  06:23:08  geoff
181  * Don't create a hit if "allhits" is clear and capitalization
182  * mismatches.  This cures a bug where a word could be in the dictionary
183  * and yet not found.
184  *
185  * Revision 1.29  1994/05/17  06:44:21  geoff
186  * Add support for controlled compound formation and the COMPOUNDONLY
187  * option to affix flags.
188  *
189  * Revision 1.28  1994/01/25  07:12:13  geoff
190  * Get rid of all old RCS log lines in preparation for the 3.1 release.
191  *
192  */
193
194 #include <ctype.h>
195 #include <stdlib.h>
196 #include <string.h>
197
198 #include "ispell_checker.h"
199
200 /*!
201  * Check possible affixes
202  *
203  * \param word Word to be checked
204  * \param ucword Upper-case-only copy of word
205  * \param len The length of word/ucword
206  * \param ignoreflagbits Ignore whether affix is legal
207  * \param allhits Keep going after first hit
208  * \param pfxopts Options to apply to prefixes
209  * \param sfxopts Options to apply to suffixes
210  */
211 void ISpellChecker::chk_aff (ichar_t *word, ichar_t *ucword, 
212                           int len, int ignoreflagbits, int allhits, int pfxopts, int sfxopts)
213 {
214     register ichar_t *  cp;             /* Pointer to char to index on */
215     struct flagptr *    ind;            /* Flag index table to test */
216
217     pfx_list_chk (word, ucword, len, pfxopts, sfxopts, &m_pflagindex[0],
218       ignoreflagbits, allhits);
219     cp = ucword;
220         /* HACK: bail on unrecognized chars */
221         if (*cp >= (SET_SIZE + MAXSTRINGCHARS))
222                 return;
223     ind = &m_pflagindex[*cp++];
224     while (ind->numents == 0  &&  ind->pu.fp != NULL)
225         {
226                 if (*cp == 0)
227                         return;
228                 if (ind->pu.fp[0].numents)
229                 {
230                         pfx_list_chk (word, ucword, len, pfxopts, sfxopts, &ind->pu.fp[0],
231                           ignoreflagbits, allhits);
232                         if (m_numhits  &&  !allhits  &&  /* !cflag  && */  !ignoreflagbits)
233                                 return;
234                 }
235                 /* HACK: bail on unrecognized chars */
236                 if (*cp >= (SET_SIZE + MAXSTRINGCHARS))
237                         return;
238                 ind = &ind->pu.fp[*cp++];
239         }
240     pfx_list_chk (word, ucword, len, pfxopts, sfxopts, ind, ignoreflagbits,
241       allhits);
242     if (m_numhits  &&  !allhits  &&  /* !cflag  &&*/  !ignoreflagbits)
243                 return;
244     chk_suf (word, ucword, len, sfxopts, static_cast<struct flagent *>(NULL),
245       ignoreflagbits, allhits);
246 }
247
248 /*!
249  * Check some prefix flags
250  *
251  * \param word Word to be checked
252  * \param ucword Upper-case-only word
253  * \param len The length of ucword
254  * \param optflags Options to apply
255  * \param sfxopts Options to apply to suffixes
256  * \param ind Flag index table
257  * \param ignoreflagbits Ignore whether affix is legal
258  * \param allhits Keep going after first hit
259  * */
260 void ISpellChecker::pfx_list_chk (ichar_t *word, ichar_t *ucword, int len, int optflags, 
261                                         int sfxopts, struct flagptr * ind, int ignoreflagbits, int allhits)
262 {
263     int                 cond;           /* Condition number */
264     register ichar_t *  cp;             /* Pointer into end of ucword */
265     struct dent *       dent;           /* Dictionary entry we found */
266     int                 entcount;       /* Number of entries to process */
267     register struct flagent *
268                         flent;          /* Current table entry */
269     int                 preadd;         /* Length added to tword2 as prefix */
270     register int        tlen;           /* Length of tword */
271     ichar_t             tword[INPUTWORDLEN + 4 * MAXAFFIXLEN + 4]; /* Tmp cpy */
272     ichar_t             tword2[sizeof tword]; /* 2nd copy for ins_root_cap */
273
274     for (flent = ind->pu.ent, entcount = ind->numents;
275       entcount > 0;
276       flent++, entcount--)
277         {
278                 /*
279                  * If this is a compound-only affix, ignore it unless we're
280                  * looking for that specific thing.
281                  */
282                 if ((flent->flagflags & FF_COMPOUNDONLY) != 0
283                   &&  (optflags & FF_COMPOUNDONLY) == 0)
284                         continue;
285
286                 /*
287                  * See if the prefix matches.
288                  */
289                 tlen = len - flent->affl;
290                 if (tlen > 0
291                   &&  (flent->affl == 0
292                         ||  icharncmp (flent->affix, ucword, flent->affl) == 0)
293                   &&  tlen + flent->stripl >= flent->numconds)
294                 {
295                         /*
296                          * The prefix matches.  Remove it, replace it by the "strip"
297                          * string (if any), and check the original conditions.
298                          */
299                         if (flent->stripl)
300                                 icharcpy (tword, flent->strip);
301                         icharcpy (tword + flent->stripl, ucword + flent->affl);
302                         cp = tword;
303                         for (cond = 0;  cond < flent->numconds;  cond++)
304                         {
305                                 if ((flent->conds[*cp++] & (1 << cond)) == 0)
306                                         break;
307                         }
308                         if (cond >= flent->numconds)
309                         {
310                                 /*
311                                  * The conditions match.  See if the word is in the
312                                  * dictionary.
313                                  */
314                                 tlen += flent->stripl;
315
316                                 if (ignoreflagbits)
317                                 {
318                                         if ((dent = ispell_lookup (tword, 1)) != NULL)
319                                         {
320                                                 cp = tword2;
321                                                 if (flent->affl)
322                                                 {
323                                                         icharcpy (cp, flent->affix);
324                                                         cp += flent->affl;
325                                                         *cp++ = '+';
326                                                 }
327                                                 preadd = cp - tword2;
328                                                 icharcpy (cp, tword);
329                                                 cp += tlen;
330                                                 if (flent->stripl)
331                                                 {
332                                                         *cp++ = '-';
333                                                         icharcpy (cp, flent->strip);
334                                                 }
335                                         }
336                                 }
337                                 else if ((dent = ispell_lookup (tword, 1)) != NULL
338                                   &&  TSTMASKBIT (dent->mask, flent->flagbit))
339                                 {
340                                         if (m_numhits < MAX_HITS)
341                                         {
342                                                 m_hits[m_numhits].dictent = dent;
343                                                 m_hits[m_numhits].prefix = flent;
344                                                 m_hits[m_numhits].suffix = NULL;
345                                                 m_numhits++;
346                                         }
347                                         if (!allhits)
348                                         {
349 #ifndef NO_CAPITALIZATION_SUPPORT
350                                                 if (cap_ok (word, &m_hits[0], len))
351                                                         return;
352                                                 m_numhits = 0;
353 #else /* NO_CAPITALIZATION_SUPPORT */
354                                                 return;
355 #endif /* NO_CAPITALIZATION_SUPPORT */
356                                         }
357                                 }
358                                 /*
359                                  * Handle cross-products.
360                                  */
361                                 if (flent->flagflags & FF_CROSSPRODUCT)
362                                                 chk_suf (word, tword, tlen, sfxopts | FF_CROSSPRODUCT,
363                                         flent, ignoreflagbits, allhits);
364                         }
365             }
366         }
367 }
368
369 /*!
370  * Check possible suffixes
371  *
372  * \param word Word to be checked
373  * \param ucword Upper-case-only word
374  * \param len The length of ucword
375  * \param optflags Affix option flags
376  * \param pfxent Prefix flag entry if cross-prod
377  * \param ignoreflagbits Ignore whether affix is legal
378  * \param allhits Keep going after first hit
379  */
380 void
381 ISpellChecker::chk_suf (ichar_t *word, ichar_t *ucword, 
382                                         int len, int optflags, struct flagent *pfxent, 
383                                         int ignoreflagbits, int allhits)
384 {
385     register ichar_t *  cp;             /* Pointer to char to index on */
386     struct flagptr *    ind;            /* Flag index table to test */
387
388     suf_list_chk (word, ucword, len, &m_sflagindex[0], optflags, pfxent,
389       ignoreflagbits, allhits);
390     cp = ucword + len - 1;
391         /* HACK: bail on unrecognized chars */
392         if (*cp >= (SET_SIZE + MAXSTRINGCHARS))
393                 return;
394     ind = &m_sflagindex[*cp];
395     while (ind->numents == 0  &&  ind->pu.fp != NULL)
396         {
397                 if (cp == ucword)
398                         return;
399                 if (ind->pu.fp[0].numents)
400                 {
401                         suf_list_chk (word, ucword, len, &ind->pu.fp[0],
402                           optflags, pfxent, ignoreflagbits, allhits);
403                         if (m_numhits != 0  &&  !allhits  &&  /* !cflag  && */  !ignoreflagbits)
404                                 return;
405                 }
406                 /* HACK: bail on unrecognized chars */
407                 if (*(cp-1) >= (SET_SIZE + MAXSTRINGCHARS))
408                         return;
409                 ind = &ind->pu.fp[*--cp];
410         }
411     suf_list_chk (word, ucword, len, ind, optflags, pfxent,
412       ignoreflagbits, allhits);
413 }
414     
415 /*!
416  * \param word Word to be checked
417  * \param ucword Upper-case-only word
418  * \param len The length of ucword
419  * \param ind Flag index table
420  * \param optflags Affix option flags
421  * \param pfxent Prefix flag entry if crossonly
422  * \param ignoreflagbits Ignore whether affix is legal
423  * \pram allhits Keep going after first hit
424  */
425 void ISpellChecker::suf_list_chk (ichar_t *word, ichar_t *ucword, 
426                                                   int len, struct flagptr *ind, int optflags, 
427                                                   struct flagent *pfxent, int ignoreflagbits, int allhits)
428 {
429     register ichar_t *  cp;             /* Pointer into end of ucword */
430     int                 cond;           /* Condition number */
431     struct dent *       dent;           /* Dictionary entry we found */
432     int                 entcount;       /* Number of entries to process */
433     register struct flagent *
434                         flent;          /* Current table entry */
435     int                 preadd;         /* Length added to tword2 as prefix */
436     register int        tlen;           /* Length of tword */
437     ichar_t             tword[INPUTWORDLEN + 4 * MAXAFFIXLEN + 4]; /* Tmp cpy */
438     ichar_t             tword2[sizeof tword]; /* 2nd copy for ins_root_cap */
439
440     icharcpy (tword, ucword);
441     for (flent = ind->pu.ent, entcount = ind->numents;
442       entcount > 0;
443       flent++, entcount--)
444         {
445                 if ((optflags & FF_CROSSPRODUCT) != 0
446                   &&  (flent->flagflags & FF_CROSSPRODUCT) == 0)
447                         continue;
448                 /*
449                  * If this is a compound-only affix, ignore it unless we're
450                  * looking for that specific thing.
451                  */
452                 if ((flent->flagflags & FF_COMPOUNDONLY) != 0
453                   &&  (optflags & FF_COMPOUNDONLY) == 0)
454                         continue;
455
456                 /*
457                  * See if the suffix matches.
458                  */
459                 tlen = len - flent->affl;
460                 if (tlen > 0
461                   &&  (flent->affl == 0
462                         ||  icharcmp (flent->affix, ucword + tlen) == 0)
463                   &&  tlen + flent->stripl >= flent->numconds)
464                 {
465                         /*
466                          * The suffix matches.  Remove it, replace it by the "strip"
467                          * string (if any), and check the original conditions.
468                          */
469                         icharcpy (tword, ucword);
470                         cp = tword + tlen;
471                         if (flent->stripl)
472                         {
473                                 icharcpy (cp, flent->strip);
474                                 tlen += flent->stripl;
475                                 cp = tword + tlen;
476                         }
477                         else
478                                 *cp = '\0';
479                         for (cond = flent->numconds;  --cond >= 0;  )
480                         {
481                                 if ((flent->conds[*--cp] & (1 << cond)) == 0)
482                                         break;
483                         }
484                         if (cond < 0)
485                         {
486                                 /*
487                                  * The conditions match.  See if the word is in the
488                                  * dictionary.
489                                  */
490                                 if (ignoreflagbits)
491                                 {
492                                         if ((dent = ispell_lookup (tword, 1)) != NULL)
493                                         {
494                                                 cp = tword2;
495                                                 if ((optflags & FF_CROSSPRODUCT)
496                                                   &&  pfxent->affl != 0)
497                                                 {
498                                                         icharcpy (cp, pfxent->affix);
499                                                         cp += pfxent->affl;
500                                                         *cp++ = '+';
501                                                 }
502                                                 preadd = cp - tword2;
503                                                 icharcpy (cp, tword);
504                                                 cp += tlen;
505                                                 if ((optflags & FF_CROSSPRODUCT)
506                                                   &&  pfxent->stripl != 0)
507                                                 {
508                                                         *cp++ = '-';
509                                                         icharcpy (cp, pfxent->strip);
510                                                         cp += pfxent->stripl;
511                                                 }
512                                                 if (flent->stripl)
513                                                 {
514                                                         *cp++ = '-';
515                                                         icharcpy (cp, flent->strip);
516                                                         cp += flent->stripl;
517                                                 }
518                                                 if (flent->affl)
519                                                 {
520                                                         *cp++ = '+';
521                                                         icharcpy (cp, flent->affix);
522                                                         cp += flent->affl;
523                                                 }
524                                         }
525                                 }
526                                 else if ((dent = ispell_lookup (tword, 1)) != NULL
527                                   &&  TSTMASKBIT (dent->mask, flent->flagbit)
528                                   &&  ((optflags & FF_CROSSPRODUCT) == 0
529                                         || TSTMASKBIT (dent->mask, pfxent->flagbit)))
530                                 {
531                                         if (m_numhits < MAX_HITS)
532                                         {
533                                                 m_hits[m_numhits].dictent = dent;
534                                                 m_hits[m_numhits].prefix = pfxent;
535                                                 m_hits[m_numhits].suffix = flent;
536                                                 m_numhits++;
537                                         }
538                                         if (!allhits)
539                                         {
540 #ifndef NO_CAPITALIZATION_SUPPORT
541                                                 if (cap_ok (word, &m_hits[0], len))
542                                                         return;
543                                                 m_numhits = 0;
544 #else /* NO_CAPITALIZATION_SUPPORT */
545                                                 return;
546 #endif /* NO_CAPITALIZATION_SUPPORT */
547                                         }
548                                 }
549                         }
550                 }
551         }
552 }
553
554 /*!
555  * Expand a dictionary prefix entry
556  *
557  * \param croot Char version of rootword
558  * \param rootword Root word to expand
559  * \param mask Mask bits to expand on
560  * \param option Option, see expandmode
561  * \param extra Extra info to add to line
562  *
563  * \return
564  */
565 int ISpellChecker::expand_pre (char *croot, ichar_t *rootword, MASKTYPE mask[], 
566                                 int option, char *extra)
567 {
568     int                         entcount;       /* No. of entries to process */
569     int                         explength;      /* Length of expansions */
570     register struct flagent *
571                                 flent;          /* Current table entry */
572
573     for (flent = m_pflaglist, entcount = m_numpflags, explength = 0;
574       entcount > 0;
575       flent++, entcount--)
576         {
577                 if (TSTMASKBIT (mask, flent->flagbit))
578                         explength +=
579                           pr_pre_expansion (croot, rootword, flent, mask, option, extra);
580         }
581     return explength;
582 }
583
584 /*!
585  * Print a prefix expansion
586  *
587  * \param croot Char version of rootword
588  * \param rootword Root word to expand
589  * \param flent Current table entry
590  * \param mask Mask bits to expand on
591  * \param option Option, see    expandmode
592  * \param extra Extra info to add to line
593  *
594  * \return
595  */
596 int ISpellChecker::pr_pre_expansion ( char *croot, ichar_t *rootword, 
597                                                         struct flagent *flent, MASKTYPE mask[], int option, 
598                                                         char *extra)
599 {
600     int                         cond;           /* Current condition number */
601     register ichar_t *          nextc;          /* Next case choice */
602     int                         tlen;           /* Length of tword */
603     ichar_t                     tword[INPUTWORDLEN + MAXAFFIXLEN]; /* Temp */
604
605     tlen = icharlen (rootword);
606     if (flent->numconds > tlen)
607                 return 0;
608     tlen -= flent->stripl;
609     if (tlen <= 0)
610                 return 0;
611     tlen += flent->affl;
612     for (cond = 0, nextc = rootword;  cond < flent->numconds;  cond++)
613         {
614                 if ((flent->conds[mytoupper (*nextc++)] & (1 << cond)) == 0)
615                         return 0;
616         }
617     /*
618      * The conditions are satisfied.  Copy the word, add the prefix,
619      * and make it the proper case.   This code is carefully written
620      * to match that ins_cap and cap_ok.  Note that the affix, as
621      * inserted, is uppercase.
622      *
623      * There is a tricky bit here:  if the root is capitalized, we
624      * want a capitalized result.  If the root is followcase, however,
625      * we want to duplicate the case of the first remaining letter
626      * of the root.  In other words, "Loved/U" should generate "Unloved",
627      * but "LOved/U" should generate "UNLOved" and "lOved/U" should
628      * produce "unlOved".
629      */
630     if (flent->affl)
631         {
632                 icharcpy (tword, flent->affix);
633                 nextc = tword + flent->affl;
634         }
635     icharcpy (nextc, rootword + flent->stripl);
636     if (myupper (rootword[0]))
637         {
638                 /* We must distinguish followcase from capitalized and all-upper */
639                 for (nextc = rootword + 1;  *nextc;  nextc++)
640                 {
641                         if (!myupper (*nextc))
642                                 break;
643                 }
644                 if (*nextc)
645                 {
646                         /* It's a followcase or capitalized word.  Figure out which. */
647                         for (  ;  *nextc;  nextc++)
648                         {
649                                 if (myupper (*nextc))
650                                         break;
651                         }
652                         if (*nextc)
653                         {
654                                 /* It's followcase. */
655                                 if (!myupper (tword[flent->affl]))
656                                         forcelc (tword, flent->affl);
657                         }
658                         else
659                         {
660                                 /* It's capitalized */
661                                 forcelc (tword + 1, tlen - 1);
662                         }
663                 }
664         }
665     else
666         {
667                 /* Followcase or all-lower, we don't care which */
668                 if (!myupper (*nextc))
669                         forcelc (tword, flent->affl);
670         }
671     if (option == 3)
672                 printf ("\n%s", croot);
673     if (option != 4)
674                 printf (" %s%s", ichartosstr (tword, 1), extra);
675     if (flent->flagflags & FF_CROSSPRODUCT)
676                 return tlen
677                   + expand_suf (croot, tword, mask, FF_CROSSPRODUCT, option, extra);
678     else
679                 return tlen;
680 }
681
682 /*!
683  * Expand a dictionary suffix entry
684  *
685  * \param croot Char version of rootword
686  * \param rootword Root word to expand 
687  * \param mask Mask bits to expand on
688  * \param optflags Affix option flags
689  * \param option Option, see expandmode
690  * \param extra Extra info to add to line
691  *
692  * \return
693  */
694 int ISpellChecker::expand_suf (char *croot, ichar_t *rootword, MASKTYPE mask[], 
695                                 int optflags, int option, char *extra)
696 {
697     int                         entcount;       /* No. of entries to process */
698     int                         explength;      /* Length of expansions */
699     register struct flagent *
700                                 flent;          /* Current table entry */
701
702     for (flent = m_sflaglist, entcount = m_numsflags, explength = 0;
703       entcount > 0;
704       flent++, entcount--)
705         {
706                 if (TSTMASKBIT (mask, flent->flagbit))
707                 {
708                         if ((optflags & FF_CROSSPRODUCT) == 0
709                           ||  (flent->flagflags & FF_CROSSPRODUCT))
710                         explength +=
711                           pr_suf_expansion (croot, rootword, flent, option, extra);
712                 }
713         }
714     return explength;
715 }
716
717 /*!
718  * Print a suffix expansion
719  *
720  * \param croot Char version of rootword
721  * \param rootword Root word to expand
722  * \param flent Current table entry
723  * \param option Option, see expandmode
724  * \param extra Extra info to add to line
725  *
726  * \return
727  */
728 int ISpellChecker::pr_suf_expansion (char *croot, ichar_t *rootword, 
729                                                         struct flagent *flent, int option, char *extra)
730 {
731     int                         cond;           /* Current condition number */
732     register ichar_t *          nextc;          /* Next case choice */
733     int                         tlen;           /* Length of tword */
734     ichar_t                     tword[INPUTWORDLEN + MAXAFFIXLEN]; /* Temp */
735
736     tlen = icharlen (rootword);
737     cond = flent->numconds;
738     if (cond > tlen)
739                 return 0;
740     if (tlen - flent->stripl <= 0)
741                 return 0;
742     for (nextc = rootword + tlen;  --cond >= 0;  )
743         {
744                 if ((flent->conds[mytoupper (*--nextc)] & (1 << cond)) == 0)
745                         return 0;
746         }
747     /*
748      * The conditions are satisfied.  Copy the word, add the suffix,
749      * and make it match the case of the last remaining character of the
750      * root.  Again, this code carefully matches ins_cap and cap_ok.
751      */
752     icharcpy (tword, rootword);
753     nextc = tword + tlen - flent->stripl;
754     if (flent->affl)
755         {
756                 icharcpy (nextc, flent->affix);
757                 if (!myupper (nextc[-1]))
758                         forcelc (nextc, flent->affl);
759         }
760     else
761                 *nextc = 0;
762     if (option == 3)
763                 printf ("\n%s", croot);
764     if (option != 4)
765                 printf (" %s%s", ichartosstr (tword, 1), extra);
766     return tlen + flent->affl - flent->stripl;
767 }
768
769 /*!
770  * \param dst Destination to modify
771  * \param len Length to copy
772  */
773 void ISpellChecker::forcelc (ichar_t *dst, int len)                     /* Force to lowercase */
774 {
775
776     for (  ;  --len >= 0;  dst++)
777                 *dst = mytolower (*dst);
778 }