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  */
chk_aff(ichar_t * word,ichar_t * ucword,int len,int ignoreflagbits,int allhits,int pfxopts,int sfxopts)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  * */
pfx_list_chk(ichar_t * word,ichar_t * ucword,int len,int optflags,int sfxopts,struct flagptr * ind,int ignoreflagbits,int allhits)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
chk_suf(ichar_t * word,ichar_t * ucword,int len,int optflags,struct flagent * pfxent,int ignoreflagbits,int allhits)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  */
suf_list_chk(ichar_t * word,ichar_t * ucword,int len,struct flagptr * ind,int optflags,struct flagent * pfxent,int ignoreflagbits,int allhits)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  */
expand_pre(char * croot,ichar_t * rootword,MASKTYPE mask[],int option,char * extra)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  */
pr_pre_expansion(char * croot,ichar_t * rootword,struct flagent * flent,MASKTYPE mask[],int option,char * extra)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  */
expand_suf(char * croot,ichar_t * rootword,MASKTYPE mask[],int optflags,int option,char * extra)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  */
pr_suf_expansion(char * croot,ichar_t * rootword,struct flagent * flent,int option,char * extra)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  */
forcelc(ichar_t * dst,int len)773 void ISpellChecker::forcelc (ichar_t *dst, int len)			/* Force to lowercase */
774 {
775 
776     for (  ;  --len >= 0;  dst++)
777 		*dst = mytolower (*dst);
778 }
779