Age Owner TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * spell.c
4 : * Normalizing word with ISpell
5 : *
6 : * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
7 : *
8 : * Ispell dictionary
9 : * -----------------
10 : *
11 : * Rules of dictionaries are defined in two files with .affix and .dict
12 : * extensions. They are used by spell checker programs Ispell and Hunspell.
13 : *
14 : * An .affix file declares morphological rules to get a basic form of words.
15 : * The format of an .affix file has different structure for Ispell and Hunspell
16 : * dictionaries. The Hunspell format is more complicated. But when an .affix
17 : * file is imported and compiled, it is stored in the same structure AffixNode.
18 : *
19 : * A .dict file stores a list of basic forms of words with references to
20 : * affix rules. The format of a .dict file has the same structure for Ispell
21 : * and Hunspell dictionaries.
22 : *
23 : * Compilation of a dictionary
24 : * ---------------------------
25 : *
26 : * A compiled dictionary is stored in the IspellDict structure. Compilation of
27 : * a dictionary is divided into the several steps:
28 : * - NIImportDictionary() - stores each word of a .dict file in the
29 : * temporary Spell field.
30 : * - NIImportAffixes() - stores affix rules of an .affix file in the
31 : * Affix field (not temporary) if an .affix file has the Ispell format.
32 : * -> NIImportOOAffixes() - stores affix rules if an .affix file has the
33 : * Hunspell format. The AffixData field is initialized if AF parameter
34 : * is defined.
35 : * - NISortDictionary() - builds a prefix tree (Trie) from the words list
36 : * and stores it in the Dictionary field. The words list is got from the
37 : * Spell field. The AffixData field is initialized if AF parameter is not
38 : * defined.
39 : * - NISortAffixes():
40 : * - builds a list of compound affixes from the affix list and stores it
41 : * in the CompoundAffix.
42 : * - builds prefix trees (Trie) from the affix list for prefixes and suffixes
43 : * and stores them in Suffix and Prefix fields.
44 : * The affix list is got from the Affix field.
45 : *
46 : * Memory management
47 : * -----------------
48 : *
49 : * The IspellDict structure has the Spell field which is used only in compile
50 : * time. The Spell field stores a words list. It can take a lot of memory.
51 : * Therefore when a dictionary is compiled this field is cleared by
52 : * NIFinishBuild().
53 : *
54 : * All resources which should cleared by NIFinishBuild() is initialized using
55 : * tmpalloc() and tmpalloc0().
56 : *
57 : * IDENTIFICATION
58 : * src/backend/tsearch/spell.c
59 : *
60 : *-------------------------------------------------------------------------
61 : */
62 :
63 : #include "postgres.h"
64 :
65 : #include "catalog/pg_collation.h"
66 : #include "miscadmin.h"
67 : #include "tsearch/dicts/spell.h"
68 : #include "tsearch/ts_locale.h"
69 : #include "utils/memutils.h"
70 :
71 :
72 : /*
73 : * Initialization requires a lot of memory that's not needed
74 : * after the initialization is done. During initialization,
75 : * CurrentMemoryContext is the long-lived memory context associated
76 : * with the dictionary cache entry. We keep the short-lived stuff
77 : * in the Conf->buildCxt context.
78 : */
79 : #define tmpalloc(sz) MemoryContextAlloc(Conf->buildCxt, (sz))
80 : #define tmpalloc0(sz) MemoryContextAllocZero(Conf->buildCxt, (sz))
81 :
82 : /*
83 : * Prepare for constructing an ISpell dictionary.
84 : *
85 : * The IspellDict struct is assumed to be zeroed when allocated.
86 : */
87 : void
4568 tgl 88 CBC 67 : NIStartBuild(IspellDict *Conf)
89 : {
90 : /*
91 : * The temp context is a child of CurTransactionContext, so that it will
92 : * go away automatically on error.
93 : */
94 67 : Conf->buildCxt = AllocSetContextCreate(CurTransactionContext,
95 : "Ispell dictionary init context",
96 : ALLOCSET_DEFAULT_SIZES);
5710 97 67 : }
98 :
99 : /*
100 : * Clean up when dictionary construction is complete.
101 : */
102 : void
4568 103 55 : NIFinishBuild(IspellDict *Conf)
104 : {
105 : /* Release no-longer-needed temp memory */
106 55 : MemoryContextDelete(Conf->buildCxt);
107 : /* Just for cleanliness, zero the now-dangling pointers */
108 55 : Conf->buildCxt = NULL;
109 55 : Conf->Spell = NULL;
110 55 : Conf->firstfree = NULL;
2579 teodor 111 55 : Conf->CompoundAffixFlags = NULL;
4568 tgl 112 55 : }
113 :
114 :
115 : /*
116 : * "Compact" palloc: allocate without extra palloc overhead.
117 : *
118 : * Since we have no need to free the ispell data items individually, there's
119 : * not much value in the per-chunk overhead normally consumed by palloc.
120 : * Getting rid of it is helpful since ispell can allocate a lot of small nodes.
121 : *
122 : * We currently pre-zero all data allocated this way, even though some of it
123 : * doesn't need that. The cpalloc and cpalloc0 macros are just documentation
124 : * to indicate which allocations actually require zeroing.
125 : */
126 : #define COMPACT_ALLOC_CHUNK 8192 /* amount to get from palloc at once */
127 : #define COMPACT_MAX_REQ 1024 /* must be < COMPACT_ALLOC_CHUNK */
128 :
129 : static void *
130 6202 : compact_palloc0(IspellDict *Conf, size_t size)
131 : {
132 : void *result;
133 :
134 : /* Should only be called during init */
135 6202 : Assert(Conf->buildCxt != NULL);
136 :
137 : /* No point in this for large chunks */
138 6202 : if (size > COMPACT_MAX_REQ)
4568 tgl 139 UBC 0 : return palloc0(size);
140 :
141 : /* Keep everything maxaligned */
4568 tgl 142 CBC 6202 : size = MAXALIGN(size);
143 :
144 : /* Need more space? */
145 6202 : if (size > Conf->avail)
146 : {
147 64 : Conf->firstfree = palloc0(COMPACT_ALLOC_CHUNK);
148 64 : Conf->avail = COMPACT_ALLOC_CHUNK;
149 : }
150 :
151 6202 : result = (void *) Conf->firstfree;
152 6202 : Conf->firstfree += size;
153 6202 : Conf->avail -= size;
154 :
155 6202 : return result;
156 : }
157 :
158 : #define cpalloc(size) compact_palloc0(Conf, size)
159 : #define cpalloc0(size) compact_palloc0(Conf, size)
160 :
161 : static char *
162 3312 : cpstrdup(IspellDict *Conf, const char *str)
163 : {
164 3312 : char *res = cpalloc(strlen(str) + 1);
165 :
166 3312 : strcpy(res, str);
167 3312 : return res;
168 : }
169 :
170 :
171 : /*
172 : * Apply lowerstr(), producing a temporary result (in the buildCxt).
173 : */
174 : static char *
175 2873 : lowerstr_ctx(IspellDict *Conf, const char *src)
176 : {
177 : MemoryContext saveCtx;
178 : char *dst;
179 :
180 2873 : saveCtx = MemoryContextSwitchTo(Conf->buildCxt);
5710 181 2873 : dst = lowerstr(src);
182 2873 : MemoryContextSwitchTo(saveCtx);
183 :
184 2873 : return dst;
185 : }
186 :
187 : #define MAX_NORM 1024
188 : #define MAXNORMLEN 256
189 :
190 : #define STRNCMP(s,p) strncmp( (s), (p), strlen(p) )
191 : #define GETWCHAR(W,L,N,T) ( ((const uint8*)(W))[ ((T)==FF_PREFIX) ? (N) : ( (L) - 1 - (N) ) ] )
192 : #define GETCHAR(A,N,T) GETWCHAR( (A)->repl, (A)->replen, N, T )
193 :
194 : static char *VoidString = "";
195 :
196 : static int
197 1446 : cmpspell(const void *s1, const void *s2)
198 : {
2061 peter_e 199 1446 : return strcmp((*(SPELL *const *) s1)->word, (*(SPELL *const *) s2)->word);
200 : }
201 :
202 : static int
5710 tgl 203 1128 : cmpspellaffix(const void *s1, const void *s2)
204 : {
2061 peter_e 205 2256 : return strcmp((*(SPELL *const *) s1)->p.flag,
1957 rhaas 206 1128 : (*(SPELL *const *) s2)->p.flag);
207 : }
208 :
209 : static int
2579 teodor 210 1962 : cmpcmdflag(const void *f1, const void *f2)
211 : {
2495 rhaas 212 1962 : CompoundAffixFlag *fv1 = (CompoundAffixFlag *) f1,
213 1962 : *fv2 = (CompoundAffixFlag *) f2;
214 :
2579 teodor 215 1962 : Assert(fv1->flagMode == fv2->flagMode);
216 :
217 1962 : if (fv1->flagMode == FM_NUM)
218 : {
219 380 : if (fv1->flag.i == fv2->flag.i)
220 57 : return 0;
221 :
222 323 : return (fv1->flag.i > fv2->flag.i) ? 1 : -1;
223 : }
224 :
225 1582 : return strcmp(fv1->flag.s, fv2->flag.s);
226 : }
227 :
228 : static char *
5710 tgl 229 583 : findchar(char *str, int c)
230 : {
231 4295 : while (*str)
232 : {
233 4231 : if (t_iseq(str, c))
234 519 : return str;
235 3712 : str += pg_mblen(str);
236 : }
237 :
238 64 : return NULL;
239 : }
240 :
241 : static char *
2590 242 21 : findchar2(char *str, int c1, int c2)
243 : {
244 441 : while (*str)
245 : {
246 441 : if (t_iseq(str, c1) || t_iseq(str, c2))
247 21 : return str;
248 420 : str += pg_mblen(str);
249 : }
250 :
2590 tgl 251 UBC 0 : return NULL;
252 : }
253 :
254 :
255 : /* backward string compare for suffix tree operations */
256 : static int
5710 tgl 257 CBC 577 : strbcmp(const unsigned char *s1, const unsigned char *s2)
258 : {
259 577 : int l1 = strlen((const char *) s1) - 1,
260 577 : l2 = strlen((const char *) s2) - 1;
261 :
262 772 : while (l1 >= 0 && l2 >= 0)
263 : {
264 604 : if (s1[l1] < s2[l2])
265 131 : return -1;
266 473 : if (s1[l1] > s2[l2])
267 278 : return 1;
268 195 : l1--;
269 195 : l2--;
270 : }
271 168 : if (l1 < l2)
272 45 : return -1;
273 123 : if (l1 > l2)
274 103 : return 1;
275 :
276 20 : return 0;
277 : }
278 :
279 : static int
280 20 : strbncmp(const unsigned char *s1, const unsigned char *s2, size_t count)
281 : {
282 20 : int l1 = strlen((const char *) s1) - 1,
283 20 : l2 = strlen((const char *) s2) - 1,
284 20 : l = count;
285 :
286 30 : while (l1 >= 0 && l2 >= 0 && l > 0)
287 : {
288 20 : if (s1[l1] < s2[l2])
289 10 : return -1;
290 10 : if (s1[l1] > s2[l2])
5710 tgl 291 UBC 0 : return 1;
5710 tgl 292 CBC 10 : l1--;
293 10 : l2--;
294 10 : l--;
295 : }
296 10 : if (l == 0)
297 10 : return 0;
5710 tgl 298 UBC 0 : if (l1 < l2)
299 0 : return -1;
300 0 : if (l1 > l2)
301 0 : return 1;
302 0 : return 0;
303 : }
304 :
305 : /*
306 : * Compares affixes.
307 : * First compares the type of an affix. Prefixes should go before affixes.
308 : * If types are equal then compares replaceable string.
309 : */
310 : static int
5710 tgl 311 CBC 976 : cmpaffix(const void *s1, const void *s2)
312 : {
313 976 : const AFFIX *a1 = (const AFFIX *) s1;
314 976 : const AFFIX *a2 = (const AFFIX *) s2;
315 :
316 976 : if (a1->type < a2->type)
317 223 : return -1;
318 753 : if (a1->type > a2->type)
319 66 : return 1;
320 687 : if (a1->type == FF_PREFIX)
321 110 : return strcmp(a1->repl, a2->repl);
322 : else
323 577 : return strbcmp((const unsigned char *) a1->repl,
324 577 : (const unsigned char *) a2->repl);
325 : }
326 :
327 : /*
328 : * Gets an affix flag from the set of affix flags (sflagset).
329 : *
330 : * Several flags can be stored in a single string. Flags can be represented by:
331 : * - 1 character (FM_CHAR). A character may be Unicode.
332 : * - 2 characters (FM_LONG). A character may be Unicode.
333 : * - numbers from 1 to 65000 (FM_NUM).
334 : *
335 : * Depending on the flagMode an affix string can have the following format:
336 : * - FM_CHAR: ABCD
337 : * Here we have 4 flags: A, B, C and D
338 : * - FM_LONG: ABCDE*
339 : * Here we have 3 flags: AB, CD and E*
340 : * - FM_NUM: 200,205,50
341 : * Here we have 3 flags: 200, 205 and 50
342 : *
343 : * Conf: current dictionary.
344 : * sflagset: the set of affix flags. Returns a reference to the start of a next
345 : * affix flag.
346 : * sflag: returns an affix flag from sflagset.
347 : */
348 : static void
2579 teodor 349 3010 : getNextFlagFromString(IspellDict *Conf, char **sflagset, char *sflag)
350 : {
351 : int32 s;
352 : char *next,
353 3010 : *sbuf = *sflagset;
354 : int maxstep;
355 3010 : bool stop = false;
356 3010 : bool met_comma = false;
357 :
358 3010 : maxstep = (Conf->flagMode == FM_LONG) ? 2 : 1;
359 :
2495 rhaas 360 3943 : while (**sflagset)
361 : {
2579 teodor 362 3943 : switch (Conf->flagMode)
363 : {
364 3374 : case FM_LONG:
365 : case FM_CHAR:
366 3374 : COPYCHAR(sflag, *sflagset);
367 3374 : sflag += pg_mblen(*sflagset);
368 :
369 : /* Go to start of the next flag */
370 3374 : *sflagset += pg_mblen(*sflagset);
371 :
372 : /* Check if we get all characters of flag */
373 3374 : maxstep--;
374 3374 : stop = (maxstep == 0);
375 3374 : break;
376 569 : case FM_NUM:
377 569 : s = strtol(*sflagset, &next, 10);
378 569 : if (*sflagset == next || errno == ERANGE)
379 3 : ereport(ERROR,
380 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
381 : errmsg("invalid affix flag \"%s\"", *sflagset)));
382 566 : if (s < 0 || s > FLAGNUM_MAXSIZE)
2579 teodor 383 UBC 0 : ereport(ERROR,
384 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
385 : errmsg("affix flag \"%s\" is out of range",
386 : *sflagset)));
2579 teodor 387 CBC 566 : sflag += sprintf(sflag, "%0d", s);
388 :
389 : /* Go to start of the next flag */
390 566 : *sflagset = next;
391 868 : while (**sflagset)
392 : {
393 604 : if (t_isdigit(*sflagset))
394 : {
395 302 : if (!met_comma)
2592 teodor 396 UBC 0 : ereport(ERROR,
397 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
398 : errmsg("invalid affix flag \"%s\"",
399 : *sflagset)));
2579 teodor 400 CBC 302 : break;
401 : }
402 302 : else if (t_iseq(*sflagset, ','))
403 : {
404 302 : if (met_comma)
2592 teodor 405 UBC 0 : ereport(ERROR,
406 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
407 : errmsg("invalid affix flag \"%s\"",
408 : *sflagset)));
2579 teodor 409 CBC 302 : met_comma = true;
410 : }
2579 teodor 411 UBC 0 : else if (!t_isspace(*sflagset))
412 : {
413 0 : ereport(ERROR,
414 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
415 : errmsg("invalid character in affix flag \"%s\"",
416 : *sflagset)));
417 : }
418 :
2579 teodor 419 CBC 302 : *sflagset += pg_mblen(*sflagset);
420 : }
421 566 : stop = true;
422 566 : break;
2579 teodor 423 UBC 0 : default:
424 0 : elog(ERROR, "unrecognized type of Conf->flagMode: %d",
425 : Conf->flagMode);
426 : }
427 :
2579 teodor 428 CBC 3940 : if (stop)
2592 429 3007 : break;
430 : }
431 :
2579 432 3007 : if (Conf->flagMode == FM_LONG && maxstep > 0)
2579 teodor 433 UBC 0 : ereport(ERROR,
434 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
435 : errmsg("invalid affix flag \"%s\" with \"long\" flag value",
436 : sbuf)));
437 :
2579 teodor 438 CBC 3007 : *sflag = '\0';
2592 439 3007 : }
440 :
441 : /*
442 : * Checks if the affix set Conf->AffixData[affix] contains affixflag.
443 : * Conf->AffixData[affix] does not contain affixflag if this flag is not used
444 : * actually by the .dict file.
445 : *
446 : * Conf: current dictionary.
447 : * affix: index of the Conf->AffixData array.
448 : * affixflag: the affix flag.
449 : *
450 : * Returns true if the string Conf->AffixData[affix] contains affixflag,
451 : * otherwise returns false.
452 : */
453 : static bool
1986 peter_e 454 1112 : IsAffixFlagInUse(IspellDict *Conf, int affix, const char *affixflag)
455 : {
456 : char *flagcur;
457 : char flag[BUFSIZ];
458 :
2579 teodor 459 1112 : if (*affixflag == 0)
2592 460 318 : return true;
461 :
1254 tgl 462 794 : Assert(affix < Conf->nAffixData);
463 :
2592 teodor 464 794 : flagcur = Conf->AffixData[affix];
465 :
466 2295 : while (*flagcur)
467 : {
2579 468 1750 : getNextFlagFromString(Conf, &flagcur, flag);
469 : /* Compare first affix flag in flagcur with affixflag */
470 1750 : if (strcmp(flag, affixflag) == 0)
2592 471 249 : return true;
472 : }
473 :
474 : /* Could not find affixflag */
475 545 : return false;
476 : }
477 :
478 : /*
479 : * Adds the new word into the temporary array Spell.
480 : *
481 : * Conf: current dictionary.
482 : * word: new word.
483 : * flag: set of affix flags. Single flag can be get by getNextFlagFromString().
484 : */
485 : static void
5624 bruce 486 583 : NIAddSpell(IspellDict *Conf, const char *word, const char *flag)
487 : {
5710 tgl 488 583 : if (Conf->nspell >= Conf->mspell)
489 : {
490 64 : if (Conf->mspell)
491 : {
4568 tgl 492 UBC 0 : Conf->mspell *= 2;
5710 493 0 : Conf->Spell = (SPELL **) repalloc(Conf->Spell, Conf->mspell * sizeof(SPELL *));
494 : }
495 : else
496 : {
5710 tgl 497 CBC 64 : Conf->mspell = 1024 * 20;
498 64 : Conf->Spell = (SPELL **) tmpalloc(Conf->mspell * sizeof(SPELL *));
499 : }
500 : }
501 583 : Conf->Spell[Conf->nspell] = (SPELL *) tmpalloc(SPELLHDRSZ + strlen(word) + 1);
502 583 : strcpy(Conf->Spell[Conf->nspell]->word, word);
2592 teodor 503 1166 : Conf->Spell[Conf->nspell]->p.flag = (*flag != '\0')
504 583 : ? cpstrdup(Conf, flag) : VoidString;
5710 tgl 505 583 : Conf->nspell++;
506 583 : }
507 :
508 : /*
509 : * Imports dictionary into the temporary array Spell.
510 : *
511 : * Note caller must already have applied get_tsearch_config_filename.
512 : *
513 : * Conf: current dictionary.
514 : * filename: path to the .dict file.
515 : */
516 : void
5624 bruce 517 64 : NIImportDictionary(IspellDict *Conf, const char *filename)
518 : {
519 : tsearch_readline_state trst;
520 : char *line;
521 :
5408 tgl 522 64 : if (!tsearch_readline_begin(&trst, filename))
5710 tgl 523 UBC 0 : ereport(ERROR,
524 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
525 : errmsg("could not open dictionary file \"%s\": %m",
526 : filename)));
527 :
5408 tgl 528 CBC 647 : while ((line = tsearch_readline(&trst)) != NULL)
529 : {
530 : char *s,
531 : *pstr;
532 :
533 : /* Set of affix flags */
534 : const char *flag;
535 :
536 : /* Extract flag from the line */
5710 537 583 : flag = NULL;
5706 538 583 : if ((s = findchar(line, '/')))
539 : {
5710 540 519 : *s++ = '\0';
541 519 : flag = s;
542 2075 : while (*s)
543 : {
544 : /* we allow only single encoded flags for faster works */
545 2075 : if (pg_mblen(s) == 1 && t_isprint(s) && !t_isspace(s))
546 1556 : s++;
547 : else
548 : {
549 519 : *s = '\0';
550 519 : break;
551 : }
552 : }
553 : }
554 : else
555 64 : flag = "";
556 :
557 : /* Remove trailing spaces */
5706 558 583 : s = line;
5710 559 4231 : while (*s)
560 : {
561 3712 : if (t_isspace(s))
562 : {
563 64 : *s = '\0';
564 64 : break;
565 : }
566 3648 : s += pg_mblen(s);
567 : }
4568 568 583 : pstr = lowerstr_ctx(Conf, line);
569 :
5710 570 583 : NIAddSpell(Conf, pstr, flag);
571 583 : pfree(pstr);
572 :
5706 573 583 : pfree(line);
574 : }
5408 575 64 : tsearch_readline_end(&trst);
5710 576 64 : }
577 :
578 : /*
579 : * Searches a basic form of word in the prefix tree. This word was generated
580 : * using an affix rule. This rule may not be presented in an affix set of
581 : * a basic form of word.
582 : *
583 : * For example, we have the entry in the .dict file:
584 : * meter/GMD
585 : *
586 : * The affix rule with the flag S:
587 : * SFX S y ies [^aeiou]y
588 : * is not presented here.
589 : *
590 : * The affix rule with the flag M:
591 : * SFX M 0 's .
592 : * is presented here.
593 : *
594 : * Conf: current dictionary.
595 : * word: basic form of word.
596 : * affixflag: affix flag, by which a basic form of word was generated.
597 : * flag: compound flag used to compare with StopMiddle->compoundflag.
598 : *
599 : * Returns 1 if the word was found in the prefix tree, else returns 0.
600 : */
601 : static int
1986 peter_e 602 1497 : FindWord(IspellDict *Conf, const char *word, const char *affixflag, int flag)
603 : {
5710 tgl 604 1497 : SPNode *node = Conf->Dictionary;
605 : SPNodeData *StopLow,
606 : *StopHigh,
607 : *StopMiddle;
4228 peter_e 608 1497 : const uint8 *ptr = (const uint8 *) word;
609 :
2579 teodor 610 1497 : flag &= FF_COMPOUNDFLAGMASK;
611 :
5710 tgl 612 6972 : while (node && *ptr)
613 : {
614 6612 : StopLow = node->data;
615 6612 : StopHigh = node->data + node->length;
616 9459 : while (StopLow < StopHigh)
617 : {
618 8826 : StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
619 8826 : if (StopMiddle->val == *ptr)
620 : {
621 5979 : if (*(ptr + 1) == '\0' && StopMiddle->isword)
622 : {
623 573 : if (flag == 0)
624 : {
625 : /*
626 : * The word can be formed only with another word. And
627 : * in the flag parameter there is not a sign that we
628 : * search compound words.
629 : */
630 363 : if (StopMiddle->compoundflag & FF_COMPOUNDONLY)
5710 tgl 631 UBC 0 : return 0;
632 : }
5710 tgl 633 CBC 210 : else if ((flag & StopMiddle->compoundflag) == 0)
5710 tgl 634 UBC 0 : return 0;
635 :
636 : /*
637 : * Check if this affix rule is presented in the affix set
638 : * with index StopMiddle->affix.
639 : */
2592 teodor 640 CBC 573 : if (IsAffixFlagInUse(Conf, StopMiddle->affix, affixflag))
5710 tgl 641 504 : return 1;
642 : }
643 5475 : node = StopMiddle->node;
644 5475 : ptr++;
645 5475 : break;
646 : }
647 2847 : else if (StopMiddle->val < *ptr)
648 966 : StopLow = StopMiddle + 1;
649 : else
650 1881 : StopHigh = StopMiddle;
651 : }
652 6108 : if (StopLow >= StopHigh)
653 633 : break;
654 : }
655 993 : return 0;
656 : }
657 :
658 : /*
659 : * Adds a new affix rule to the Affix field.
660 : *
661 : * Conf: current dictionary.
662 : * flag: affix flag ('\' in the below example).
663 : * flagflags: set of flags from the flagval field for this affix rule. This set
664 : * is listed after '/' character in the added string (repl).
665 : *
2495 rhaas 666 ECB : * For example L flag in the hunspell_sample.affix:
667 : * SFX \ 0 Y/L [^Y]
668 : *
669 : * mask: condition for search ('[^Y]' in the above example).
670 : * find: stripping characters from beginning (at prefix) or end (at suffix)
671 : * of the word ('0' in the above example, 0 means that there is not
672 : * stripping character).
2592 teodor 673 : * repl: adding string after stripping ('Y' in the above example).
674 : * type: FF_SUFFIX or FF_PREFIX.
2592 teodor 675 EUB : */
5710 tgl 676 : static void
2495 rhaas 677 GIC 530 : NIAddAffix(IspellDict *Conf, const char *flag, char flagflags, const char *mask,
678 : const char *find, const char *repl, int type)
679 : {
5710 tgl 680 ECB : AFFIX *Affix;
681 :
5710 tgl 682 GIC 530 : if (Conf->naffixes >= Conf->maffixes)
683 : {
684 64 : if (Conf->maffixes)
5710 tgl 685 ECB : {
4568 tgl 686 UIC 0 : Conf->maffixes *= 2;
61 peter 687 UNC 0 : Conf->Affix = (AFFIX *) repalloc(Conf->Affix, Conf->maffixes * sizeof(AFFIX));
5710 tgl 688 ECB : }
689 : else
690 : {
5710 tgl 691 CBC 64 : Conf->maffixes = 16;
5710 tgl 692 GIC 64 : Conf->Affix = (AFFIX *) palloc(Conf->maffixes * sizeof(AFFIX));
693 : }
5710 tgl 694 ECB : }
695 :
5710 tgl 696 CBC 530 : Affix = Conf->Affix + Conf->naffixes;
5710 tgl 697 ECB :
2592 teodor 698 : /* This affix rule can be applied for words with any ending */
2592 teodor 699 CBC 530 : if (strcmp(mask, ".") == 0 || *mask == '\0')
700 : {
5710 tgl 701 GIC 128 : Affix->issimple = 1;
702 128 : Affix->isregis = 0;
703 : }
704 : /* This affix rule will use regis to search word ending */
705 402 : else if (RS_isRegis(mask))
706 : {
707 336 : Affix->issimple = 0;
708 336 : Affix->isregis = 1;
2592 teodor 709 336 : RS_compile(&(Affix->reg.regis), (type == FF_SUFFIX),
3324 sfrost 710 CBC 336 : *mask ? mask : VoidString);
5710 tgl 711 ECB : }
2592 teodor 712 : /* This affix rule will use regex_t to search word ending */
5710 tgl 713 : else
714 : {
715 : int masklen;
5710 tgl 716 EUB : int wmasklen;
717 : int err;
5710 tgl 718 ECB : pg_wchar *wmask;
719 : char *tmask;
720 :
5710 tgl 721 GIC 66 : Affix->issimple = 0;
722 66 : Affix->isregis = 0;
723 66 : tmask = (char *) tmpalloc(strlen(mask) + 3);
724 66 : if (type == FF_SUFFIX)
725 66 : sprintf(tmask, "%s$", mask);
5710 tgl 726 ECB : else
5710 tgl 727 LBC 0 : sprintf(tmask, "^%s", mask);
728 :
5710 tgl 729 GIC 66 : masklen = strlen(tmask);
5710 tgl 730 CBC 66 : wmask = (pg_wchar *) tmpalloc((masklen + 1) * sizeof(pg_wchar));
5710 tgl 731 GIC 66 : wmasklen = pg_mb2wchar_with_len(tmask, wmask, masklen);
732 :
733 : /*
734 : * The regex and all internal state created by pg_regcomp are
735 : * allocated in the dictionary's memory context, and will be freed
736 : * automatically when it is destroyed.
737 : */
1 tmunro 738 GNC 66 : Affix->reg.pregex = palloc(sizeof(regex_t));
739 66 : err = pg_regcomp(Affix->reg.pregex, wmask, wmasklen,
740 : REG_ADVANCED | REG_NOSUB,
4382 tgl 741 ECB : DEFAULT_COLLATION_OID);
5710 tgl 742 CBC 66 : if (err)
743 : {
5710 tgl 744 ECB : char errstr[100];
745 :
1 tmunro 746 UNC 0 : pg_regerror(err, Affix->reg.pregex, errstr, sizeof(errstr));
5710 tgl 747 LBC 0 : ereport(ERROR,
5710 tgl 748 ECB : (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
749 : errmsg("invalid regular expression: %s", errstr)));
750 : }
751 : }
752 :
5710 tgl 753 GIC 530 : Affix->flagflags = flagflags;
754 530 : if ((Affix->flagflags & FF_COMPOUNDONLY) || (Affix->flagflags & FF_COMPOUNDPERMITFLAG))
755 : {
756 96 : if ((Affix->flagflags & FF_COMPOUNDFLAG) == 0)
757 96 : Affix->flagflags |= FF_COMPOUNDFLAG;
758 : }
2579 teodor 759 530 : Affix->flag = cpstrdup(Conf, flag);
5710 tgl 760 530 : Affix->type = type;
761 :
4568 762 530 : Affix->find = (find && *find) ? cpstrdup(Conf, find) : VoidString;
5710 763 530 : if ((Affix->replen = strlen(repl)) > 0)
4568 764 513 : Affix->repl = cpstrdup(Conf, repl);
765 : else
5710 766 17 : Affix->repl = VoidString;
767 530 : Conf->naffixes++;
768 530 : }
769 :
770 : /* Parsing states for parse_affentry() and friends */
5710 tgl 771 ECB : #define PAE_WAIT_MASK 0
772 : #define PAE_INMASK 1
773 : #define PAE_WAIT_FIND 2
2615 774 : #define PAE_INFIND 3
775 : #define PAE_WAIT_REPL 4
776 : #define PAE_INREPL 5
777 : #define PAE_WAIT_TYPE 6
778 : #define PAE_WAIT_FLAG 7
779 :
780 : /*
781 : * Parse next space-separated field of an .affix file line.
782 : *
783 : * *str is the input pointer (will be advanced past field)
784 : * next is where to copy the field value to, with null termination
785 : *
786 : * The buffer at "next" must be of size BUFSIZ; we truncate the input to fit.
787 : *
2062 peter_e 788 : * Returns true if we found a field, false if not.
2615 tgl 789 : */
790 : static bool
2615 tgl 791 GIC 4955 : get_nextfield(char **str, char *next)
2615 tgl 792 ECB : {
2615 tgl 793 GIC 4955 : int state = PAE_WAIT_MASK;
794 4955 : int avail = BUFSIZ;
795 :
796 21192 : while (**str)
2615 tgl 797 ECB : {
2615 tgl 798 GIC 20610 : if (state == PAE_WAIT_MASK)
2615 tgl 799 ECB : {
2615 tgl 800 CBC 9140 : if (t_iseq(*str, '#'))
2615 tgl 801 GIC 176 : return false;
802 8964 : else if (!t_isspace(*str))
803 : {
2615 tgl 804 CBC 4197 : int clen = pg_mblen(*str);
805 :
806 4197 : if (clen < avail)
807 : {
808 4197 : COPYCHAR(next, *str);
809 4197 : next += clen;
810 4197 : avail -= clen;
811 : }
2615 tgl 812 GIC 4197 : state = PAE_INMASK;
813 : }
2615 tgl 814 ECB : }
815 : else /* state == PAE_INMASK */
816 : {
2615 tgl 817 CBC 11470 : if (t_isspace(*str))
818 : {
819 4197 : *next = '\0';
2615 tgl 820 GIC 4197 : return true;
821 : }
822 : else
823 : {
824 7273 : int clen = pg_mblen(*str);
825 :
826 7273 : if (clen < avail)
827 : {
828 7273 : COPYCHAR(next, *str);
829 7273 : next += clen;
830 7273 : avail -= clen;
831 : }
832 : }
833 : }
834 16237 : *str += pg_mblen(*str);
835 : }
836 :
2615 tgl 837 CBC 582 : *next = '\0';
838 :
2118 tgl 839 GIC 582 : return (state == PAE_INMASK); /* OK if we got a nonempty field */
2615 tgl 840 ECB : }
841 :
842 : /*
843 : * Parses entry of an .affix file of MySpell or Hunspell format.
844 : *
845 : * An .affix file entry has the following format:
846 : * - header
847 : * <type> <flag> <cross_flag> <flag_count>
848 : * - fields after header:
849 : * <type> <flag> <find> <replace> <mask>
850 : *
851 : * str is the input line
852 : * field values are returned to type etc, which must be buffers of size BUFSIZ.
853 : *
854 : * Returns number of fields found; any omitted fields are set to empty strings.
855 : */
856 : static int
2615 tgl 857 CBC 1141 : parse_ooaffentry(char *str, char *type, char *flag, char *find,
2615 tgl 858 ECB : char *repl, char *mask)
859 : {
2615 tgl 860 CBC 1141 : int state = PAE_WAIT_TYPE;
861 1141 : int fields_read = 0;
862 1141 : bool valid = false;
2615 tgl 863 ECB :
2615 tgl 864 CBC 1141 : *type = *flag = *find = *repl = *mask = '\0';
2615 tgl 865 ECB :
2615 tgl 866 CBC 4955 : while (*str)
2615 tgl 867 ECB : {
2615 tgl 868 CBC 4955 : switch (state)
2615 tgl 869 ECB : {
2615 tgl 870 GBC 1141 : case PAE_WAIT_TYPE:
871 1141 : valid = get_nextfield(&str, type);
2615 tgl 872 GIC 1141 : state = PAE_WAIT_FLAG;
873 1141 : break;
874 1141 : case PAE_WAIT_FLAG:
2615 tgl 875 CBC 1141 : valid = get_nextfield(&str, flag);
876 1141 : state = PAE_WAIT_FIND;
2615 tgl 877 GIC 1141 : break;
2615 tgl 878 CBC 1141 : case PAE_WAIT_FIND:
879 1141 : valid = get_nextfield(&str, find);
880 1141 : state = PAE_WAIT_REPL;
2615 tgl 881 GIC 1141 : break;
882 766 : case PAE_WAIT_REPL:
2615 tgl 883 CBC 766 : valid = get_nextfield(&str, repl);
2615 tgl 884 GIC 766 : state = PAE_WAIT_MASK;
885 766 : break;
886 766 : case PAE_WAIT_MASK:
887 766 : valid = get_nextfield(&str, mask);
888 766 : state = -1; /* force loop exit */
889 766 : break;
2615 tgl 890 UIC 0 : default:
891 0 : elog(ERROR, "unrecognized state in parse_ooaffentry: %d",
892 : state);
2615 tgl 893 ECB : break;
894 : }
2615 tgl 895 CBC 4955 : if (valid)
896 4197 : fields_read++;
2615 tgl 897 ECB : else
2615 tgl 898 CBC 758 : break; /* early EOL */
2615 tgl 899 GIC 4197 : if (state < 0)
2615 tgl 900 CBC 383 : break; /* got all fields */
901 : }
2615 tgl 902 ECB :
2615 tgl 903 GIC 1141 : return fields_read;
2615 tgl 904 ECB : }
905 :
906 : /*
2615 tgl 907 EUB : * Parses entry of an .affix file of Ispell format
2615 tgl 908 ECB : *
909 : * An .affix file entry has the following format:
910 : * <mask> > [-<find>,]<replace>
911 : */
5710 912 : static bool
5408 tgl 913 GIC 147 : parse_affentry(char *str, char *mask, char *find, char *repl)
914 : {
5710 tgl 915 CBC 147 : int state = PAE_WAIT_MASK;
5710 tgl 916 GIC 147 : char *pmask = mask,
5710 tgl 917 CBC 147 : *pfind = find,
5710 tgl 918 GIC 147 : *prepl = repl;
5710 tgl 919 ECB :
5710 tgl 920 CBC 147 : *mask = *find = *repl = '\0';
921 :
922 3864 : while (*str)
923 : {
924 3864 : if (state == PAE_WAIT_MASK)
5710 tgl 925 ECB : {
5710 tgl 926 GIC 357 : if (t_iseq(str, '#'))
5710 tgl 927 UIC 0 : return false;
5710 tgl 928 CBC 357 : else if (!t_isspace(str))
929 : {
930 147 : COPYCHAR(pmask, str);
5710 tgl 931 GIC 147 : pmask += pg_mblen(str);
5710 tgl 932 CBC 147 : state = PAE_INMASK;
933 : }
5710 tgl 934 ECB : }
5710 tgl 935 GIC 3507 : else if (state == PAE_INMASK)
5710 tgl 936 ECB : {
5710 tgl 937 CBC 1428 : if (t_iseq(str, '>'))
5710 tgl 938 ECB : {
5710 tgl 939 GIC 147 : *pmask = '\0';
5710 tgl 940 CBC 147 : state = PAE_WAIT_FIND;
5710 tgl 941 EUB : }
5710 tgl 942 GIC 1281 : else if (!t_isspace(str))
943 : {
944 504 : COPYCHAR(pmask, str);
5710 tgl 945 CBC 504 : pmask += pg_mblen(str);
946 : }
5710 tgl 947 ECB : }
5710 tgl 948 GIC 2079 : else if (state == PAE_WAIT_FIND)
5710 tgl 949 ECB : {
5710 tgl 950 CBC 588 : if (t_iseq(str, '-'))
951 : {
952 21 : state = PAE_INFIND;
953 : }
954 567 : else if (t_isalpha(str) || t_iseq(str, '\'') /* english 's */ )
5710 tgl 955 ECB : {
5710 tgl 956 GIC 126 : COPYCHAR(prepl, str);
5710 tgl 957 GBC 126 : prepl += pg_mblen(str);
958 126 : state = PAE_INREPL;
959 : }
5710 tgl 960 GIC 441 : else if (!t_isspace(str))
5710 tgl 961 UIC 0 : ereport(ERROR,
5710 tgl 962 ECB : (errcode(ERRCODE_CONFIG_FILE_ERROR),
963 : errmsg("syntax error")));
964 : }
5710 tgl 965 GIC 1491 : else if (state == PAE_INFIND)
5710 tgl 966 EUB : {
5710 tgl 967 GIC 42 : if (t_iseq(str, ','))
5710 tgl 968 ECB : {
5710 tgl 969 GIC 21 : *pfind = '\0';
5710 tgl 970 CBC 21 : state = PAE_WAIT_REPL;
5710 tgl 971 ECB : }
5710 tgl 972 CBC 21 : else if (t_isalpha(str))
973 : {
5710 tgl 974 GBC 21 : COPYCHAR(pfind, str);
975 21 : pfind += pg_mblen(str);
976 : }
5710 tgl 977 UIC 0 : else if (!t_isspace(str))
978 0 : ereport(ERROR,
5710 tgl 979 ECB : (errcode(ERRCODE_CONFIG_FILE_ERROR),
980 : errmsg("syntax error")));
981 : }
5710 tgl 982 GIC 1449 : else if (state == PAE_WAIT_REPL)
5710 tgl 983 ECB : {
5710 tgl 984 CBC 21 : if (t_iseq(str, '-'))
985 : {
5710 tgl 986 LBC 0 : break; /* void repl */
987 : }
5710 tgl 988 CBC 21 : else if (t_isalpha(str))
5710 tgl 989 ECB : {
5710 tgl 990 GIC 21 : COPYCHAR(prepl, str);
5710 tgl 991 CBC 21 : prepl += pg_mblen(str);
5710 tgl 992 GBC 21 : state = PAE_INREPL;
993 : }
5710 tgl 994 UIC 0 : else if (!t_isspace(str))
995 0 : ereport(ERROR,
996 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
5408 tgl 997 EUB : errmsg("syntax error")));
998 : }
5710 tgl 999 CBC 1428 : else if (state == PAE_INREPL)
1000 : {
5710 tgl 1001 GIC 1428 : if (t_iseq(str, '#'))
5710 tgl 1002 ECB : {
5710 tgl 1003 GIC 147 : *prepl = '\0';
5710 tgl 1004 CBC 147 : break;
1005 : }
5710 tgl 1006 GIC 1281 : else if (t_isalpha(str))
1007 : {
1008 189 : COPYCHAR(prepl, str);
1009 189 : prepl += pg_mblen(str);
1010 : }
5710 tgl 1011 CBC 1092 : else if (!t_isspace(str))
5710 tgl 1012 UIC 0 : ereport(ERROR,
1013 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
5408 tgl 1014 ECB : errmsg("syntax error")));
1015 : }
1016 : else
5611 tgl 1017 UIC 0 : elog(ERROR, "unrecognized state in parse_affentry: %d", state);
1018 :
5710 tgl 1019 CBC 3717 : str += pg_mblen(str);
5710 tgl 1020 ECB : }
5710 tgl 1021 EUB :
5710 tgl 1022 GIC 147 : *pmask = *pfind = *prepl = '\0';
1023 :
2592 teodor 1024 CBC 147 : return (*mask && (*find || *repl));
5710 tgl 1025 EUB : }
1026 :
1027 : /*
1028 : * Sets a Hunspell options depending on flag type.
2579 teodor 1029 ECB : */
1030 : static void
2579 teodor 1031 GIC 1428 : setCompoundAffixFlagValue(IspellDict *Conf, CompoundAffixFlag *entry,
2579 teodor 1032 ECB : char *s, uint32 val)
1033 : {
2579 teodor 1034 CBC 1428 : if (Conf->flagMode == FM_NUM)
2579 teodor 1035 ECB : {
2495 rhaas 1036 : char *next;
1037 : int i;
1038 :
2579 teodor 1039 GIC 309 : i = strtol(s, &next, 10);
1040 309 : if (s == next || errno == ERANGE)
2579 teodor 1041 UIC 0 : ereport(ERROR,
1042 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1043 : errmsg("invalid affix flag \"%s\"", s)));
2579 teodor 1044 GIC 309 : if (i < 0 || i > FLAGNUM_MAXSIZE)
2579 teodor 1045 UIC 0 : ereport(ERROR,
2579 teodor 1046 ECB : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1047 : errmsg("affix flag \"%s\" is out of range", s)));
1048 :
2579 teodor 1049 GIC 309 : entry->flag.i = i;
1050 : }
1051 : else
1052 1119 : entry->flag.s = cpstrdup(Conf, s);
2579 teodor 1053 ECB :
2579 teodor 1054 CBC 1428 : entry->flagMode = Conf->flagMode;
2579 teodor 1055 GIC 1428 : entry->value = val;
2579 teodor 1056 CBC 1428 : }
2579 teodor 1057 EUB :
1058 : /*
1059 : * Sets up a correspondence for the affix parameter with the affix flag.
1060 : *
1061 : * Conf: current dictionary.
2592 teodor 1062 ECB : * s: affix flag in string.
1063 : * val: affix parameter.
1064 : */
5710 tgl 1065 : static void
2579 teodor 1066 CBC 171 : addCompoundAffixFlagValue(IspellDict *Conf, char *s, uint32 val)
5710 tgl 1067 ECB : {
2495 rhaas 1068 : CompoundAffixFlag *newValue;
1069 : char sbuf[BUFSIZ];
1070 : char *sflag;
1071 : int clen;
1072 :
5710 tgl 1073 CBC 321 : while (*s && t_isspace(s))
5407 tgl 1074 GIC 150 : s += pg_mblen(s);
5710 tgl 1075 ECB :
5710 tgl 1076 GIC 171 : if (!*s)
5710 tgl 1077 UBC 0 : ereport(ERROR,
5710 tgl 1078 EUB : (errcode(ERRCODE_CONFIG_FILE_ERROR),
5408 1079 : errmsg("syntax error")));
5710 1080 :
1081 : /* Get flag without \n */
2579 teodor 1082 GIC 171 : sflag = sbuf;
1083 506 : while (*s && !t_isspace(s) && *s != '\n')
2579 teodor 1084 ECB : {
2579 teodor 1085 CBC 335 : clen = pg_mblen(s);
1086 335 : COPYCHAR(sflag, s);
2579 teodor 1087 GIC 335 : sflag += clen;
1088 335 : s += clen;
1089 : }
2579 teodor 1090 CBC 171 : *sflag = '\0';
1091 :
2579 teodor 1092 ECB : /* Resize array or allocate memory for array CompoundAffixFlag */
2579 teodor 1093 GIC 171 : if (Conf->nCompoundAffixFlag >= Conf->mCompoundAffixFlag)
2579 teodor 1094 ECB : {
2579 teodor 1095 CBC 64 : if (Conf->mCompoundAffixFlag)
2579 teodor 1096 ECB : {
2579 teodor 1097 UIC 0 : Conf->mCompoundAffixFlag *= 2;
1098 0 : Conf->CompoundAffixFlags = (CompoundAffixFlag *)
61 peter 1099 UNC 0 : repalloc(Conf->CompoundAffixFlags,
2118 tgl 1100 UIC 0 : Conf->mCompoundAffixFlag * sizeof(CompoundAffixFlag));
1101 : }
1102 : else
2579 teodor 1103 ECB : {
2579 teodor 1104 GIC 64 : Conf->mCompoundAffixFlag = 10;
2579 teodor 1105 CBC 64 : Conf->CompoundAffixFlags = (CompoundAffixFlag *)
2579 teodor 1106 GIC 64 : tmpalloc(Conf->mCompoundAffixFlag * sizeof(CompoundAffixFlag));
1107 : }
1108 : }
1109 :
1110 171 : newValue = Conf->CompoundAffixFlags + Conf->nCompoundAffixFlag;
2579 teodor 1111 ECB :
2579 teodor 1112 GBC 171 : setCompoundAffixFlagValue(Conf, newValue, sbuf, val);
1113 :
5710 tgl 1114 CBC 171 : Conf->usecompound = true;
2579 teodor 1115 171 : Conf->nCompoundAffixFlag++;
5710 tgl 1116 GIC 171 : }
5710 tgl 1117 ECB :
3090 1118 : /*
1119 : * Returns a set of affix parameters which correspondence to the set of affix
1120 : * flags s.
2592 teodor 1121 : */
1122 : static int
2579 teodor 1123 GIC 618 : getCompoundAffixFlagValue(IspellDict *Conf, char *s)
2592 teodor 1124 ECB : {
2495 rhaas 1125 CBC 618 : uint32 flag = 0;
1126 : CompoundAffixFlag *found,
1127 : key;
2495 rhaas 1128 ECB : char sflag[BUFSIZ];
1129 : char *flagcur;
1130 :
2579 teodor 1131 GIC 618 : if (Conf->nCompoundAffixFlag == 0)
2579 teodor 1132 UIC 0 : return 0;
1133 :
2592 teodor 1134 GIC 618 : flagcur = s;
1135 1875 : while (*flagcur)
1136 : {
2579 1137 1260 : getNextFlagFromString(Conf, &flagcur, sflag);
1138 1257 : setCompoundAffixFlagValue(Conf, &key, sflag, 0);
2579 teodor 1139 ECB :
1140 : found = (CompoundAffixFlag *)
61 peter 1141 GNC 1257 : bsearch(&key, Conf->CompoundAffixFlags,
2579 teodor 1142 GIC 1257 : Conf->nCompoundAffixFlag, sizeof(CompoundAffixFlag),
1143 : cmpcmdflag);
1144 1257 : if (found != NULL)
1145 287 : flag |= found->value;
2592 teodor 1146 ECB : }
1147 :
2592 teodor 1148 GBC 615 : return flag;
1149 : }
1150 :
1151 : /*
2592 teodor 1152 ECB : * Returns a flag set using the s parameter.
1153 : *
1154 : * If Conf->useFlagAliases is true then the s parameter is index of the
1155 : * Conf->AffixData array and function returns its entry.
1156 : * Else function returns the s parameter.
1157 : */
1158 : static char *
2579 teodor 1159 GBC 75 : getAffixFlagSet(IspellDict *Conf, char *s)
2592 teodor 1160 EUB : {
2579 teodor 1161 GIC 75 : if (Conf->useFlagAliases && *s != '\0')
1162 : {
2495 rhaas 1163 EUB : int curaffix;
1164 : char *end;
1165 :
2579 teodor 1166 CBC 48 : curaffix = strtol(s, &end, 10);
2579 teodor 1167 GIC 48 : if (s == end || errno == ERANGE)
2579 teodor 1168 UIC 0 : ereport(ERROR,
1169 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1170 : errmsg("invalid affix alias \"%s\"", s)));
1171 :
1254 tgl 1172 GIC 48 : if (curaffix > 0 && curaffix < Conf->nAffixData)
1173 :
1174 : /*
1175 : * Do not subtract 1 from curaffix because empty string was added
2495 rhaas 1176 ECB : * in NIImportOOAffixes
1177 : */
2592 teodor 1178 GIC 48 : return Conf->AffixData[curaffix];
1254 tgl 1179 LBC 0 : else if (curaffix > Conf->nAffixData)
1254 tgl 1180 UIC 0 : ereport(ERROR,
1181 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1182 : errmsg("invalid affix alias \"%s\"", s)));
1253 1183 0 : return VoidString;
1184 : }
1185 : else
2592 teodor 1186 GIC 27 : return s;
2592 teodor 1187 ECB : }
1188 :
1189 : /*
1190 : * Import an affix file that follows MySpell or Hunspell format.
1191 : *
1192 : * Conf: current dictionary.
1193 : * filename: path to the .affix file.
1194 : */
1195 : static void
5624 bruce 1196 CBC 43 : NIImportOOAffixes(IspellDict *Conf, const char *filename)
5710 tgl 1197 ECB : {
1198 : char type[BUFSIZ],
5710 tgl 1199 GIC 43 : *ptype = NULL;
5710 tgl 1200 ECB : char sflag[BUFSIZ];
5710 tgl 1201 EUB : char mask[BUFSIZ],
1202 : *pmask;
1203 : char find[BUFSIZ],
1204 : *pfind;
1205 : char repl[BUFSIZ],
5710 tgl 1206 ECB : *prepl;
5710 tgl 1207 GIC 43 : bool isSuffix = false;
2592 teodor 1208 CBC 43 : int naffix = 0,
2592 teodor 1209 GIC 43 : curaffix = 0;
2579 teodor 1210 CBC 43 : int sflaglen = 0;
5710 tgl 1211 43 : char flagflags = 0;
1212 : tsearch_readline_state trst;
1213 : char *recoded;
5710 tgl 1214 ECB :
1215 : /* read file to find any flag */
5710 tgl 1216 GIC 43 : Conf->usecompound = false;
2592 teodor 1217 CBC 43 : Conf->useFlagAliases = false;
1218 43 : Conf->flagMode = FM_CHAR;
1219 :
5408 tgl 1220 43 : if (!tsearch_readline_begin(&trst, filename))
5710 tgl 1221 UBC 0 : ereport(ERROR,
1222 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1223 : errmsg("could not open affix file \"%s\": %m",
5710 tgl 1224 ECB : filename)));
1225 :
5408 tgl 1226 GIC 1682 : while ((recoded = tsearch_readline(&trst)) != NULL)
5710 tgl 1227 ECB : {
5710 tgl 1228 CBC 1639 : if (*recoded == '\0' || t_isspace(recoded) || t_iseq(recoded, '#'))
1229 : {
5706 1230 498 : pfree(recoded);
5710 1231 498 : continue;
1232 : }
5710 tgl 1233 ECB :
5710 tgl 1234 CBC 1141 : if (STRNCMP(recoded, "COMPOUNDFLAG") == 0)
2579 teodor 1235 GIC 43 : addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDFLAG"),
1236 : FF_COMPOUNDFLAG);
5710 tgl 1237 CBC 1098 : else if (STRNCMP(recoded, "COMPOUNDBEGIN") == 0)
2579 teodor 1238 GBC 16 : addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDBEGIN"),
1239 : FF_COMPOUNDBEGIN);
5710 tgl 1240 GIC 1082 : else if (STRNCMP(recoded, "COMPOUNDLAST") == 0)
2579 teodor 1241 LBC 0 : addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDLAST"),
1242 : FF_COMPOUNDLAST);
5710 tgl 1243 ECB : /* COMPOUNDLAST and COMPOUNDEND are synonyms */
5710 tgl 1244 GIC 1082 : else if (STRNCMP(recoded, "COMPOUNDEND") == 0)
2579 teodor 1245 CBC 16 : addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDEND"),
2579 teodor 1246 ECB : FF_COMPOUNDLAST);
5710 tgl 1247 GIC 1066 : else if (STRNCMP(recoded, "COMPOUNDMIDDLE") == 0)
2579 teodor 1248 CBC 16 : addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDMIDDLE"),
1249 : FF_COMPOUNDMIDDLE);
5710 tgl 1250 1050 : else if (STRNCMP(recoded, "ONLYINCOMPOUND") == 0)
2579 teodor 1251 43 : addCompoundAffixFlagValue(Conf, recoded + strlen("ONLYINCOMPOUND"),
2579 teodor 1252 ECB : FF_COMPOUNDONLY);
5710 tgl 1253 CBC 1007 : else if (STRNCMP(recoded, "COMPOUNDPERMITFLAG") == 0)
2579 teodor 1254 GBC 16 : addCompoundAffixFlagValue(Conf,
2579 teodor 1255 EUB : recoded + strlen("COMPOUNDPERMITFLAG"),
1256 : FF_COMPOUNDPERMITFLAG);
5710 tgl 1257 GIC 991 : else if (STRNCMP(recoded, "COMPOUNDFORBIDFLAG") == 0)
2579 teodor 1258 UIC 0 : addCompoundAffixFlagValue(Conf,
1259 : recoded + strlen("COMPOUNDFORBIDFLAG"),
1260 : FF_COMPOUNDFORBIDFLAG);
5710 tgl 1261 GIC 991 : else if (STRNCMP(recoded, "FLAG") == 0)
1262 : {
5710 tgl 1263 CBC 33 : char *s = recoded + strlen("FLAG");
1264 :
1265 66 : while (*s && t_isspace(s))
5407 tgl 1266 GIC 33 : s += pg_mblen(s);
5710 tgl 1267 ECB :
2592 teodor 1268 CBC 33 : if (*s)
1269 : {
2592 teodor 1270 GIC 33 : if (STRNCMP(s, "long") == 0)
2592 teodor 1271 CBC 16 : Conf->flagMode = FM_LONG;
2592 teodor 1272 GBC 17 : else if (STRNCMP(s, "num") == 0)
2592 teodor 1273 GIC 17 : Conf->flagMode = FM_NUM;
2592 teodor 1274 UIC 0 : else if (STRNCMP(s, "default") != 0)
1275 0 : ereport(ERROR,
1276 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2428 tgl 1277 ECB : errmsg("Ispell dictionary supports only "
1278 : "\"default\", \"long\", "
1279 : "and \"num\" flag values")));
1280 : }
5710 1281 : }
1282 :
5706 tgl 1283 GIC 1141 : pfree(recoded);
5710 tgl 1284 ECB : }
5408 tgl 1285 GIC 43 : tsearch_readline_end(&trst);
5710 tgl 1286 ECB :
2579 teodor 1287 CBC 43 : if (Conf->nCompoundAffixFlag > 1)
61 peter 1288 GNC 43 : qsort(Conf->CompoundAffixFlags, Conf->nCompoundAffixFlag,
1289 : sizeof(CompoundAffixFlag), cmpcmdflag);
1290 :
5408 tgl 1291 CBC 43 : if (!tsearch_readline_begin(&trst, filename))
5710 tgl 1292 UIC 0 : ereport(ERROR,
1293 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
5710 tgl 1294 ECB : errmsg("could not open affix file \"%s\": %m",
1295 : filename)));
1296 :
5408 tgl 1297 CBC 1682 : while ((recoded = tsearch_readline(&trst)) != NULL)
5710 tgl 1298 ECB : {
2615 tgl 1299 EUB : int fields_read;
1300 :
5710 tgl 1301 GIC 1639 : if (*recoded == '\0' || t_isspace(recoded) || t_iseq(recoded, '#'))
5706 1302 498 : goto nextline;
1303 :
2615 tgl 1304 CBC 1141 : fields_read = parse_ooaffentry(recoded, type, sflag, find, repl, mask);
1305 :
5710 1306 1141 : if (ptype)
1307 1098 : pfree(ptype);
4568 tgl 1308 GIC 1141 : ptype = lowerstr_ctx(Conf, type);
1309 :
2592 teodor 1310 ECB : /* First try to parse AF parameter (alias compression) */
2592 teodor 1311 CBC 1141 : if (STRNCMP(ptype, "af") == 0)
1312 : {
1313 : /* First line is the number of aliases */
2592 teodor 1314 GIC 192 : if (!Conf->useFlagAliases)
1315 : {
2592 teodor 1316 CBC 16 : Conf->useFlagAliases = true;
2592 teodor 1317 GIC 16 : naffix = atoi(sflag);
1822 tgl 1318 CBC 16 : if (naffix <= 0)
2592 teodor 1319 LBC 0 : ereport(ERROR,
1320 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1321 : errmsg("invalid number of flag vector aliases")));
2592 teodor 1322 EUB :
1323 : /* Also reserve place for empty flag set */
2592 teodor 1324 GIC 16 : naffix++;
1325 :
1326 16 : Conf->AffixData = (char **) palloc0(naffix * sizeof(char *));
2592 teodor 1327 CBC 16 : Conf->lenAffixData = Conf->nAffixData = naffix;
1328 :
1329 : /* Add empty flag set into AffixData */
1330 16 : Conf->AffixData[curaffix] = VoidString;
1331 16 : curaffix++;
2592 teodor 1332 ECB : }
1333 : /* Other lines are aliases */
1334 : else
1335 : {
2592 teodor 1336 CBC 176 : if (curaffix < naffix)
2592 teodor 1337 ECB : {
2592 teodor 1338 GBC 176 : Conf->AffixData[curaffix] = cpstrdup(Conf, sflag);
2592 teodor 1339 GIC 176 : curaffix++;
1340 : }
1341 : else
1822 tgl 1342 UIC 0 : ereport(ERROR,
1343 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1344 : errmsg("number of aliases exceeds specified number %d",
1822 tgl 1345 ECB : naffix - 1)));
1346 : }
2592 teodor 1347 CBC 192 : goto nextline;
2592 teodor 1348 ECB : }
1349 : /* Else try to parse prefixes and suffixes */
2615 tgl 1350 GIC 949 : if (fields_read < 4 ||
2615 tgl 1351 CBC 766 : (STRNCMP(ptype, "sfx") != 0 && STRNCMP(ptype, "pfx") != 0))
5706 tgl 1352 GIC 183 : goto nextline;
1353 :
2592 teodor 1354 766 : sflaglen = strlen(sflag);
1355 766 : if (sflaglen == 0
1356 766 : || (sflaglen > 1 && Conf->flagMode == FM_CHAR)
1357 766 : || (sflaglen > 2 && Conf->flagMode == FM_LONG))
2592 teodor 1358 UIC 0 : goto nextline;
1359 :
1360 : /*--------
2592 teodor 1361 ECB : * Affix header. For example:
1362 : * SFX \ N 1
1363 : *--------
1364 : */
2615 tgl 1365 CBC 766 : if (fields_read == 4)
1366 : {
2592 teodor 1367 GIC 383 : isSuffix = (STRNCMP(ptype, "sfx") == 0);
5690 1368 383 : if (t_iseq(find, 'y') || t_iseq(find, 'Y'))
5710 tgl 1369 CBC 265 : flagflags = FF_CROSSPRODUCT;
5710 tgl 1370 ECB : else
5710 tgl 1371 CBC 118 : flagflags = 0;
5710 tgl 1372 ECB : }
2532 rhaas 1373 : /*--------
2592 teodor 1374 : * Affix fields. For example:
2532 rhaas 1375 : * SFX \ 0 Y/L [^Y]
1376 : *--------
2592 teodor 1377 : */
1378 : else
5710 tgl 1379 : {
1380 : char *ptr;
5710 tgl 1381 CBC 383 : int aflg = 0;
5710 tgl 1382 ECB :
2590 1383 : /* Get flags after '/' (flags are case sensitive) */
2590 tgl 1384 GIC 383 : if ((ptr = strchr(repl, '/')) != NULL)
2579 teodor 1385 75 : aflg |= getCompoundAffixFlagValue(Conf,
2579 teodor 1386 ECB : getAffixFlagSet(Conf,
1387 : ptr + 1));
1388 : /* Get lowercased version of string before '/' */
4568 tgl 1389 GIC 383 : prepl = lowerstr_ctx(Conf, repl);
5710 tgl 1390 CBC 383 : if ((ptr = strchr(prepl, '/')) != NULL)
1391 75 : *ptr = '\0';
4568 1392 383 : pfind = lowerstr_ctx(Conf, find);
1393 383 : pmask = lowerstr_ctx(Conf, mask);
5710 tgl 1394 GIC 383 : if (t_iseq(find, '0'))
1395 323 : *pfind = '\0';
1396 383 : if (t_iseq(repl, '0'))
1397 17 : *prepl = '\0';
1398 :
2579 teodor 1399 383 : NIAddAffix(Conf, sflag, flagflags | aflg, pmask, pfind, prepl,
1400 : isSuffix ? FF_SUFFIX : FF_PREFIX);
5710 tgl 1401 383 : pfree(prepl);
1402 383 : pfree(pfind);
1403 383 : pfree(pmask);
1404 : }
5710 tgl 1405 ECB :
5624 bruce 1406 GIC 1639 : nextline:
5706 tgl 1407 CBC 1639 : pfree(recoded);
1408 : }
1409 :
5408 tgl 1410 GIC 43 : tsearch_readline_end(&trst);
5710 1411 43 : if (ptype)
1412 43 : pfree(ptype);
5710 tgl 1413 CBC 43 : }
5710 tgl 1414 ECB :
1415 : /*
1416 : * import affixes
1417 : *
1418 : * Note caller must already have applied get_tsearch_config_filename
1419 : *
3090 1420 : * This function is responsible for parsing ispell ("old format") affix files.
3090 tgl 1421 EUB : * If we realize that the file contains new-format commands, we pass off the
1422 : * work to NIImportOOAffixes(), which will re-read the whole file.
1423 : */
1424 : void
5624 bruce 1425 GIC 64 : NIImportAffixes(IspellDict *Conf, const char *filename)
5710 tgl 1426 ECB : {
5690 teodor 1427 CBC 64 : char *pstr = NULL;
2579 teodor 1428 ECB : char flag[BUFSIZ];
1429 : char mask[BUFSIZ];
5710 tgl 1430 : char find[BUFSIZ];
1431 : char repl[BUFSIZ];
1432 : char *s;
5706 tgl 1433 GIC 64 : bool suffixes = false;
1434 64 : bool prefixes = false;
5710 tgl 1435 CBC 64 : char flagflags = 0;
5408 tgl 1436 ECB : tsearch_readline_state trst;
5706 tgl 1437 GIC 64 : bool oldformat = false;
5706 tgl 1438 CBC 64 : char *recoded = NULL;
1439 :
5408 tgl 1440 GIC 64 : if (!tsearch_readline_begin(&trst, filename))
5710 tgl 1441 LBC 0 : ereport(ERROR,
5710 tgl 1442 ECB : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1443 : errmsg("could not open affix file \"%s\": %m",
1444 : filename)));
1445 :
5710 tgl 1446 CBC 64 : Conf->usecompound = false;
2592 teodor 1447 64 : Conf->useFlagAliases = false;
2592 teodor 1448 GIC 64 : Conf->flagMode = FM_CHAR;
5710 tgl 1449 ECB :
5408 tgl 1450 GIC 610 : while ((recoded = tsearch_readline(&trst)) != NULL)
5710 tgl 1451 ECB : {
5706 tgl 1452 CBC 589 : pstr = lowerstr(recoded);
1453 :
5706 tgl 1454 ECB : /* Skip comments and empty lines */
5710 tgl 1455 CBC 589 : if (*pstr == '#' || *pstr == '\n')
5706 tgl 1456 GIC 189 : goto nextline;
1457 :
5710 tgl 1458 CBC 400 : if (STRNCMP(pstr, "compoundwords") == 0)
1459 : {
2590 tgl 1460 ECB : /* Find case-insensitive L flag in non-lowercased string */
2590 tgl 1461 CBC 21 : s = findchar2(recoded, 'l', 'L');
5710 1462 21 : if (s)
5710 tgl 1463 ECB : {
5710 tgl 1464 GIC 105 : while (*s && !t_isspace(s))
5407 tgl 1465 CBC 84 : s += pg_mblen(s);
5710 tgl 1466 GIC 42 : while (*s && t_isspace(s))
5407 tgl 1467 CBC 21 : s += pg_mblen(s);
5690 teodor 1468 ECB :
5710 tgl 1469 CBC 21 : if (*s && pg_mblen(s) == 1)
5710 tgl 1470 ECB : {
2579 teodor 1471 GIC 21 : addCompoundAffixFlagValue(Conf, s, FF_COMPOUNDFLAG);
5710 tgl 1472 CBC 21 : Conf->usecompound = true;
1473 : }
5706 1474 21 : oldformat = true;
1475 21 : goto nextline;
1476 : }
5710 tgl 1477 ECB : }
5710 tgl 1478 CBC 379 : if (STRNCMP(pstr, "suffixes") == 0)
1479 : {
5706 1480 21 : suffixes = true;
5706 tgl 1481 GIC 21 : prefixes = false;
5706 tgl 1482 CBC 21 : oldformat = true;
1483 21 : goto nextline;
1484 : }
5710 1485 358 : if (STRNCMP(pstr, "prefixes") == 0)
1486 : {
5706 1487 21 : suffixes = false;
1488 21 : prefixes = true;
5706 tgl 1489 GIC 21 : oldformat = true;
1490 21 : goto nextline;
5710 tgl 1491 ECB : }
5710 tgl 1492 CBC 337 : if (STRNCMP(pstr, "flag") == 0)
1493 : {
5624 bruce 1494 GIC 180 : s = recoded + 4; /* we need non-lowercased string */
5710 tgl 1495 180 : flagflags = 0;
1496 :
1497 360 : while (*s && t_isspace(s))
5407 1498 180 : s += pg_mblen(s);
5710 tgl 1499 ECB :
5710 tgl 1500 GIC 180 : if (*s == '*')
5710 tgl 1501 ECB : {
5710 tgl 1502 CBC 105 : flagflags |= FF_CROSSPRODUCT;
5710 tgl 1503 GIC 105 : s++;
5710 tgl 1504 ECB : }
5710 tgl 1505 CBC 75 : else if (*s == '~')
5710 tgl 1506 ECB : {
5710 tgl 1507 GIC 21 : flagflags |= FF_COMPOUNDONLY;
5710 tgl 1508 CBC 21 : s++;
5710 tgl 1509 ECB : }
1510 :
5710 tgl 1511 GIC 180 : if (*s == '\\')
5710 tgl 1512 CBC 21 : s++;
1513 :
3090 tgl 1514 ECB : /*
1515 : * An old-format flag is a single ASCII character; we expect it to
1516 : * be followed by EOL, whitespace, or ':'. Otherwise this is a
1517 : * new-format flag command.
1518 : */
3090 tgl 1519 GIC 180 : if (*s && pg_mblen(s) == 1)
3090 tgl 1520 ECB : {
2579 teodor 1521 GBC 180 : COPYCHAR(flag, s);
2579 teodor 1522 GIC 180 : flag[1] = '\0';
2579 teodor 1523 ECB :
3090 tgl 1524 GBC 180 : s++;
3090 tgl 1525 GIC 213 : if (*s == '\0' || *s == '#' || *s == '\n' || *s == ':' ||
3090 tgl 1526 CBC 33 : t_isspace(s))
1527 : {
1528 147 : oldformat = true;
1529 147 : goto nextline;
3090 tgl 1530 ECB : }
1531 : }
3090 tgl 1532 CBC 33 : goto isnewformat;
5710 tgl 1533 ECB : }
3090 tgl 1534 GIC 157 : if (STRNCMP(recoded, "COMPOUNDFLAG") == 0 ||
3090 tgl 1535 CBC 147 : STRNCMP(recoded, "COMPOUNDMIN") == 0 ||
1536 147 : STRNCMP(recoded, "PFX") == 0 ||
3090 tgl 1537 GBC 147 : STRNCMP(recoded, "SFX") == 0)
3090 tgl 1538 GIC 10 : goto isnewformat;
1539 :
5710 tgl 1540 CBC 147 : if ((!suffixes) && (!prefixes))
5706 tgl 1541 UIC 0 : goto nextline;
5710 tgl 1542 ECB :
5408 tgl 1543 GIC 147 : if (!parse_affentry(pstr, mask, find, repl))
5706 tgl 1544 UIC 0 : goto nextline;
1545 :
5710 tgl 1546 GIC 147 : NIAddAffix(Conf, flag, flagflags, mask, find, repl, suffixes ? FF_SUFFIX : FF_PREFIX);
1547 :
5624 bruce 1548 546 : nextline:
5690 teodor 1549 546 : pfree(recoded);
5710 tgl 1550 546 : pfree(pstr);
1551 : }
5408 tgl 1552 CBC 21 : tsearch_readline_end(&trst);
3090 tgl 1553 GIC 21 : return;
1554 :
1555 43 : isnewformat:
3090 tgl 1556 CBC 43 : if (oldformat)
3090 tgl 1557 UIC 0 : ereport(ERROR,
1558 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
2118 tgl 1559 ECB : errmsg("affix file contains both old-style and new-style commands")));
3090 tgl 1560 GBC 43 : tsearch_readline_end(&trst);
3090 tgl 1561 ECB :
3090 tgl 1562 GBC 43 : NIImportOOAffixes(Conf, filename);
1563 : }
1564 :
2592 teodor 1565 ECB : /*
1566 : * Merges two affix flag sets and stores a new affix flag set into
1567 : * Conf->AffixData.
1568 : *
1569 : * Returns index of a new affix flag set.
1570 : */
1571 : static int
5624 bruce 1572 CBC 32 : MergeAffix(IspellDict *Conf, int a1, int a2)
5710 tgl 1573 ECB : {
1574 : char **ptr;
1575 :
1254 tgl 1576 GIC 32 : Assert(a1 < Conf->nAffixData && a2 < Conf->nAffixData);
1577 :
2585 teodor 1578 ECB : /* Do not merge affix flags if one of affix flags is empty */
2585 teodor 1579 GIC 32 : if (*Conf->AffixData[a1] == '\0')
2585 teodor 1580 UIC 0 : return a2;
2585 teodor 1581 GIC 32 : else if (*Conf->AffixData[a2] == '\0')
2585 teodor 1582 LBC 0 : return a1;
1583 :
1584 : /* Double the size of AffixData if there's not enough space */
647 drowley 1585 CBC 32 : if (Conf->nAffixData + 1 >= Conf->lenAffixData)
1586 : {
5710 tgl 1587 32 : Conf->lenAffixData *= 2;
1588 32 : Conf->AffixData = (char **) repalloc(Conf->AffixData,
2118 1589 32 : sizeof(char *) * Conf->lenAffixData);
1590 : }
5710 tgl 1591 ECB :
5710 tgl 1592 GIC 32 : ptr = Conf->AffixData + Conf->nAffixData;
2585 teodor 1593 32 : if (Conf->flagMode == FM_NUM)
1594 : {
1595 14 : *ptr = cpalloc(strlen(Conf->AffixData[a1]) +
1596 : strlen(Conf->AffixData[a2]) +
1597 : 1 /* comma */ + 1 /* \0 */ );
1598 14 : sprintf(*ptr, "%s,%s", Conf->AffixData[a1], Conf->AffixData[a2]);
2585 teodor 1599 ECB : }
1600 : else
1601 : {
2585 teodor 1602 GIC 18 : *ptr = cpalloc(strlen(Conf->AffixData[a1]) +
2585 teodor 1603 ECB : strlen(Conf->AffixData[a2]) +
1604 : 1 /* \0 */ );
2585 teodor 1605 GIC 18 : sprintf(*ptr, "%s%s", Conf->AffixData[a1], Conf->AffixData[a2]);
1606 : }
5710 tgl 1607 32 : ptr++;
1608 32 : *ptr = NULL;
1609 32 : Conf->nAffixData++;
1610 :
1611 32 : return Conf->nAffixData - 1;
1612 : }
1613 :
1614 : /*
1615 : * Returns a set of affix parameters which correspondence to the set of affix
2592 teodor 1616 ECB : * flags with the given index.
1617 : */
1618 : static uint32
5624 bruce 1619 CBC 543 : makeCompoundFlags(IspellDict *Conf, int affix)
5710 tgl 1620 ECB : {
1254 tgl 1621 GIC 543 : Assert(affix < Conf->nAffixData);
1622 :
1254 tgl 1623 CBC 543 : return (getCompoundAffixFlagValue(Conf, Conf->AffixData[affix]) &
1624 : FF_COMPOUNDFLAGMASK);
5710 tgl 1625 ECB : }
1626 :
1627 : /*
2592 teodor 1628 : * Makes a prefix tree for the given level.
1629 : *
1630 : * Conf: current dictionary.
1631 : * low: lower index of the Conf->Spell array.
1632 : * high: upper index of the Conf->Spell array.
1633 : * level: current prefix tree level.
1634 : */
5710 tgl 1635 : static SPNode *
5624 bruce 1636 CBC 2172 : mkSPNode(IspellDict *Conf, int low, int high, int level)
5710 tgl 1637 ECB : {
1638 : int i;
5710 tgl 1639 CBC 2172 : int nchar = 0;
1640 2172 : char lastchar = '\0';
5710 tgl 1641 ECB : SPNode *rs;
1642 : SPNodeData *data;
5710 tgl 1643 CBC 2172 : int lownew = low;
1644 :
1645 7138 : for (i = low; i < high; i++)
5710 tgl 1646 GIC 4966 : if (Conf->Spell[i]->p.d.len > level && lastchar != Conf->Spell[i]->word[level])
1647 : {
5710 tgl 1648 CBC 2129 : nchar++;
1649 2129 : lastchar = Conf->Spell[i]->word[level];
5710 tgl 1650 ECB : }
1651 :
5710 tgl 1652 CBC 2172 : if (!nchar)
5710 tgl 1653 GIC 311 : return NULL;
5710 tgl 1654 ECB :
4568 tgl 1655 CBC 1861 : rs = (SPNode *) cpalloc0(SPNHDRSZ + nchar * sizeof(SPNodeData));
5710 tgl 1656 GIC 1861 : rs->length = nchar;
5710 tgl 1657 CBC 1861 : data = rs->data;
1658 :
1659 1861 : lastchar = '\0';
5710 tgl 1660 GIC 6295 : for (i = low; i < high; i++)
1661 4443 : if (Conf->Spell[i]->p.d.len > level)
1662 : {
1663 3192 : if (lastchar != Conf->Spell[i]->word[level])
1664 : {
1665 2123 : if (lastchar)
1666 : {
2592 teodor 1667 ECB : /* Next level of the prefix tree */
5710 tgl 1668 CBC 262 : data->node = mkSPNode(Conf, lownew, i, level + 1);
5710 tgl 1669 GIC 256 : lownew = i;
5710 tgl 1670 CBC 256 : data++;
1671 : }
5710 tgl 1672 GIC 2117 : lastchar = Conf->Spell[i]->word[level];
5710 tgl 1673 ECB : }
5710 tgl 1674 CBC 3186 : data->val = ((uint8 *) (Conf->Spell[i]->word))[level];
5710 tgl 1675 GIC 3186 : if (Conf->Spell[i]->p.d.len == level + 1)
5710 tgl 1676 ECB : {
5710 tgl 1677 GIC 511 : bool clearCompoundOnly = false;
5710 tgl 1678 ECB :
5710 tgl 1679 GBC 511 : if (data->isword && data->affix != Conf->Spell[i]->p.d.affix)
5710 tgl 1680 EUB : {
1681 : /*
5710 tgl 1682 ECB : * MergeAffix called a few times. If one of word is
1683 : * allowed to be in compound word and another isn't, then
1684 : * clear FF_COMPOUNDONLY flag.
1685 : */
1686 :
5710 tgl 1687 GIC 64 : clearCompoundOnly = (FF_COMPOUNDONLY & data->compoundflag
2118 tgl 1688 CBC 32 : & makeCompoundFlags(Conf, Conf->Spell[i]->p.d.affix))
1689 : ? false : true;
5710 1690 32 : data->affix = MergeAffix(Conf, data->affix, Conf->Spell[i]->p.d.affix);
1691 : }
1692 : else
5710 tgl 1693 GIC 479 : data->affix = Conf->Spell[i]->p.d.affix;
1694 511 : data->isword = 1;
1695 :
1696 511 : data->compoundflag = makeCompoundFlags(Conf, data->affix);
1697 :
5710 tgl 1698 CBC 508 : if ((data->compoundflag & FF_COMPOUNDONLY) &&
5710 tgl 1699 UIC 0 : (data->compoundflag & FF_COMPOUNDFLAG) == 0)
1700 0 : data->compoundflag |= FF_COMPOUNDFLAG;
1701 :
5710 tgl 1702 GIC 508 : if (clearCompoundOnly)
1703 32 : data->compoundflag &= ~FF_COMPOUNDONLY;
1704 : }
1705 : }
1706 :
1707 : /* Next level of the prefix tree */
1708 1852 : data->node = mkSPNode(Conf, lownew, high, level + 1);
1709 :
5710 tgl 1710 CBC 1849 : return rs;
1711 : }
5710 tgl 1712 ECB :
1713 : /*
1714 : * Builds the Conf->Dictionary tree and AffixData from the imported dictionary
1715 : * and affixes.
5706 1716 : */
1717 : void
5624 bruce 1718 CBC 64 : NISortDictionary(IspellDict *Conf)
5710 tgl 1719 ECB : {
5624 bruce 1720 : int i;
1721 : int naffix;
1722 : int curaffix;
1723 :
5710 tgl 1724 : /* compress affixes */
5706 1725 :
1726 : /*
1727 : * If we use flag aliases then we need to use Conf->AffixData filled in
1728 : * the NIImportOOAffixes().
2592 teodor 1729 : */
2592 teodor 1730 GBC 64 : if (Conf->useFlagAliases)
1731 : {
2592 teodor 1732 GIC 126 : for (i = 0; i < Conf->nspell; i++)
1733 : {
1734 : char *end;
1735 :
2579 1736 116 : if (*Conf->Spell[i]->p.flag != '\0')
1737 : {
1738 106 : curaffix = strtol(Conf->Spell[i]->p.flag, &end, 10);
1739 106 : if (Conf->Spell[i]->p.flag == end || errno == ERANGE)
1740 3 : ereport(ERROR,
2579 teodor 1741 ECB : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1742 : errmsg("invalid affix alias \"%s\"",
1743 : Conf->Spell[i]->p.flag)));
1254 tgl 1744 CBC 103 : if (curaffix < 0 || curaffix >= Conf->nAffixData)
1745 3 : ereport(ERROR,
1746 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1747 : errmsg("invalid affix alias \"%s\"",
1748 : Conf->Spell[i]->p.flag)));
1254 tgl 1749 GIC 100 : if (*end != '\0' && !t_isdigit(end) && !t_isspace(end))
1254 tgl 1750 UIC 0 : ereport(ERROR,
1751 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1254 tgl 1752 ECB : errmsg("invalid affix alias \"%s\"",
1753 : Conf->Spell[i]->p.flag)));
1754 : }
2592 teodor 1755 : else
2579 1756 : {
1757 : /*
2592 1758 : * If Conf->Spell[i]->p.flag is empty, then get empty value of
1759 : * Conf->AffixData (0 index).
1760 : */
2579 teodor 1761 GIC 10 : curaffix = 0;
1762 : }
1763 :
1764 110 : Conf->Spell[i]->p.d.affix = curaffix;
2592 1765 110 : Conf->Spell[i]->p.d.len = strlen(Conf->Spell[i]->word);
1766 : }
1767 : }
2592 teodor 1768 ECB : /* Otherwise fill Conf->AffixData here */
1769 : else
1770 : {
1771 : /* Count the number of different flags used in the dictionary */
61 peter 1772 GNC 48 : qsort(Conf->Spell, Conf->nspell, sizeof(SPELL *),
2579 teodor 1773 ECB : cmpspellaffix);
5710 tgl 1774 :
2592 teodor 1775 GIC 48 : naffix = 0;
2592 teodor 1776 CBC 470 : for (i = 0; i < Conf->nspell; i++)
2592 teodor 1777 ECB : {
1458 michael 1778 CBC 422 : if (i == 0 ||
1779 374 : strcmp(Conf->Spell[i]->p.flag, Conf->Spell[i - 1]->p.flag) != 0)
2592 teodor 1780 GIC 374 : naffix++;
1781 : }
5706 tgl 1782 ECB :
2592 teodor 1783 : /*
1784 : * Fill in Conf->AffixData with the affixes that were used in the
1785 : * dictionary. Replace textual flag-field of Conf->Spell entries with
1786 : * indexes into Conf->AffixData array.
1787 : */
2592 teodor 1788 GIC 48 : Conf->AffixData = (char **) palloc0(naffix * sizeof(char *));
1789 :
2592 teodor 1790 CBC 48 : curaffix = -1;
1791 470 : for (i = 0; i < Conf->nspell; i++)
5710 tgl 1792 ECB : {
1458 michael 1793 GIC 422 : if (i == 0 ||
1794 374 : strcmp(Conf->Spell[i]->p.flag, Conf->AffixData[curaffix]) != 0)
1795 : {
2592 teodor 1796 374 : curaffix++;
1797 374 : Assert(curaffix < naffix);
1798 374 : Conf->AffixData[curaffix] = cpstrdup(Conf,
2495 rhaas 1799 374 : Conf->Spell[i]->p.flag);
1800 : }
1801 :
2592 teodor 1802 422 : Conf->Spell[i]->p.d.affix = curaffix;
1803 422 : Conf->Spell[i]->p.d.len = strlen(Conf->Spell[i]->word);
1804 : }
1805 :
2592 teodor 1806 CBC 48 : Conf->lenAffixData = Conf->nAffixData = naffix;
1807 : }
1808 :
2592 teodor 1809 ECB : /* Start build a prefix tree */
61 peter 1810 GNC 58 : qsort(Conf->Spell, Conf->nspell, sizeof(SPELL *), cmpspell);
5710 tgl 1811 GIC 58 : Conf->Dictionary = mkSPNode(Conf, 0, Conf->nspell, 0);
1812 55 : }
5710 tgl 1813 ECB :
1814 : /*
1815 : * Makes a prefix tree for the given level using the repl string of an affix
1816 : * rule. Affixes with empty replace string do not include in the prefix tree.
2592 teodor 1817 : * This affixes are included by mkVoidAffix().
1818 : *
1819 : * Conf: current dictionary.
1820 : * low: lower index of the Conf->Affix array.
1821 : * high: upper index of the Conf->Affix array.
1822 : * level: current prefix tree level.
1823 : * type: FF_SUFFIX or FF_PREFIX.
1824 : */
5710 tgl 1825 : static AffixNode *
5624 bruce 1826 GIC 928 : mkANode(IspellDict *Conf, int low, int high, int level, int type)
5710 tgl 1827 ECB : {
1828 : int i;
5710 tgl 1829 GIC 928 : int nchar = 0;
5710 tgl 1830 CBC 928 : uint8 lastchar = '\0';
5710 tgl 1831 ECB : AffixNode *rs;
1832 : AffixNodeData *data;
5710 tgl 1833 GIC 928 : int lownew = low;
5710 tgl 1834 ECB : int naff;
1835 : AFFIX **aff;
1836 :
5710 tgl 1837 GIC 2497 : for (i = low; i < high; i++)
5710 tgl 1838 CBC 1569 : if (Conf->Affix[i].replen > level && lastchar != GETCHAR(Conf->Affix + i, level, type))
1839 : {
1840 818 : nchar++;
5710 tgl 1841 GIC 818 : lastchar = GETCHAR(Conf->Affix + i, level, type);
1842 : }
5710 tgl 1843 ECB :
5710 tgl 1844 CBC 928 : if (!nchar)
5710 tgl 1845 GIC 354 : return NULL;
5710 tgl 1846 ECB :
5710 tgl 1847 CBC 574 : aff = (AFFIX **) tmpalloc(sizeof(AFFIX *) * (high - low + 1));
1848 574 : naff = 0;
5710 tgl 1849 ECB :
4568 tgl 1850 GIC 574 : rs = (AffixNode *) cpalloc0(ANHRDSZ + nchar * sizeof(AffixNodeData));
5710 tgl 1851 CBC 574 : rs->length = nchar;
1852 574 : data = rs->data;
1853 :
1854 574 : lastchar = '\0';
5710 tgl 1855 GIC 1700 : for (i = low; i < high; i++)
5710 tgl 1856 CBC 1126 : if (Conf->Affix[i].replen > level)
5710 tgl 1857 ECB : {
5710 tgl 1858 GIC 948 : if (lastchar != GETCHAR(Conf->Affix + i, level, type))
5710 tgl 1859 ECB : {
5710 tgl 1860 GIC 818 : if (lastchar)
1861 : {
1862 : /* Next level of the prefix tree */
1863 244 : data->node = mkANode(Conf, lownew, i, level + 1, type);
5710 tgl 1864 CBC 244 : if (naff)
5710 tgl 1865 ECB : {
5710 tgl 1866 GIC 55 : data->naff = naff;
4568 tgl 1867 CBC 55 : data->aff = (AFFIX **) cpalloc(sizeof(AFFIX *) * naff);
5710 1868 55 : memcpy(data->aff, aff, sizeof(AFFIX *) * naff);
1869 55 : naff = 0;
5710 tgl 1870 ECB : }
5710 tgl 1871 GIC 244 : data++;
1872 244 : lownew = i;
5710 tgl 1873 ECB : }
5710 tgl 1874 GIC 818 : lastchar = GETCHAR(Conf->Affix + i, level, type);
5710 tgl 1875 ECB : }
5710 tgl 1876 GIC 948 : data->val = GETCHAR(Conf->Affix + i, level, type);
1877 948 : if (Conf->Affix[i].replen == level + 1)
1878 : { /* affix stopped */
1879 429 : aff[naff++] = Conf->Affix + i;
1880 : }
1881 : }
1882 :
2592 teodor 1883 ECB : /* Next level of the prefix tree */
5710 tgl 1884 GIC 574 : data->node = mkANode(Conf, lownew, high, level + 1, type);
1885 574 : if (naff)
5710 tgl 1886 ECB : {
5710 tgl 1887 CBC 354 : data->naff = naff;
4568 1888 354 : data->aff = (AFFIX **) cpalloc(sizeof(AFFIX *) * naff);
5710 1889 354 : memcpy(data->aff, aff, sizeof(AFFIX *) * naff);
5710 tgl 1890 GIC 354 : naff = 0;
5710 tgl 1891 ECB : }
1892 :
5710 tgl 1893 GIC 574 : pfree(aff);
5710 tgl 1894 ECB :
5710 tgl 1895 GIC 574 : return rs;
5710 tgl 1896 ECB : }
1897 :
1898 : /*
1899 : * Makes the root void node in the prefix tree. The root void node is created
1900 : * for affixes which have empty replace string ("repl" field).
2592 teodor 1901 : */
5710 tgl 1902 : static void
5624 bruce 1903 GIC 110 : mkVoidAffix(IspellDict *Conf, bool issuffix, int startsuffix)
1904 : {
1905 : int i,
5710 tgl 1906 CBC 110 : cnt = 0;
1907 110 : int start = (issuffix) ? startsuffix : 0;
1908 110 : int end = (issuffix) ? Conf->naffixes : startsuffix;
5710 tgl 1909 GIC 110 : AffixNode *Affix = (AffixNode *) palloc0(ANHRDSZ + sizeof(AffixNodeData));
1910 :
5710 tgl 1911 CBC 110 : Affix->length = 1;
1912 110 : Affix->isvoid = 1;
1913 :
1914 110 : if (issuffix)
5710 tgl 1915 ECB : {
5710 tgl 1916 GIC 55 : Affix->data->node = Conf->Suffix;
5710 tgl 1917 CBC 55 : Conf->Suffix = Affix;
5710 tgl 1918 ECB : }
1919 : else
1920 : {
5710 tgl 1921 CBC 55 : Affix->data->node = Conf->Prefix;
1922 55 : Conf->Prefix = Affix;
1923 : }
1924 :
1925 : /* Count affixes with empty replace string */
5710 tgl 1926 GIC 553 : for (i = start; i < end; i++)
1927 443 : if (Conf->Affix[i].replen == 0)
1928 14 : cnt++;
1929 :
1930 : /* There is not affixes with empty replace string */
1931 110 : if (cnt == 0)
1932 96 : return;
1933 :
4568 1934 14 : Affix->data->aff = (AFFIX **) cpalloc(sizeof(AFFIX *) * cnt);
5710 1935 14 : Affix->data->naff = (uint32) cnt;
1936 :
5710 tgl 1937 CBC 14 : cnt = 0;
5710 tgl 1938 GIC 112 : for (i = start; i < end; i++)
1939 98 : if (Conf->Affix[i].replen == 0)
1940 : {
5710 tgl 1941 CBC 14 : Affix->data->aff[cnt] = Conf->Affix + i;
1942 14 : cnt++;
5710 tgl 1943 ECB : }
1944 : }
1945 :
1946 : /*
1947 : * Checks if the affixflag is used by dictionary. Conf->AffixData does not
1948 : * contain affixflag if this flag is not used actually by the .dict file.
1949 : *
1950 : * Conf: current dictionary.
1951 : * affixflag: affix flag.
2592 teodor 1952 : *
1953 : * Returns true if the Conf->AffixData array contains affixflag, otherwise
1954 : * returns false.
1955 : */
1956 : static bool
2579 teodor 1957 CBC 75 : isAffixInUse(IspellDict *Conf, char *affixflag)
1958 : {
5710 tgl 1959 ECB : int i;
5710 tgl 1960 EUB :
5710 tgl 1961 GIC 551 : for (i = 0; i < Conf->nAffixData; i++)
2592 teodor 1962 539 : if (IsAffixFlagInUse(Conf, i, affixflag))
5710 tgl 1963 CBC 63 : return true;
5710 tgl 1964 ECB :
5710 tgl 1965 CBC 12 : return false;
5710 tgl 1966 ECB : }
1967 :
2592 teodor 1968 : /*
1969 : * Builds Conf->Prefix and Conf->Suffix trees from the imported affixes.
1970 : */
5710 tgl 1971 : void
5624 bruce 1972 CBC 55 : NISortAffixes(IspellDict *Conf)
1973 : {
5710 tgl 1974 ECB : AFFIX *Affix;
1975 : size_t i;
1976 : CMPDAffix *ptr;
5706 tgl 1977 CBC 55 : int firstsuffix = Conf->naffixes;
1978 :
5710 1979 55 : if (Conf->naffixes == 0)
5710 tgl 1980 LBC 0 : return;
5710 tgl 1981 ECB :
2592 teodor 1982 : /* Store compound affixes in the Conf->CompoundAffix array */
5710 tgl 1983 CBC 55 : if (Conf->naffixes > 1)
61 peter 1984 GNC 55 : qsort(Conf->Affix, Conf->naffixes, sizeof(AFFIX), cmpaffix);
5710 tgl 1985 GIC 55 : Conf->CompoundAffix = ptr = (CMPDAffix *) palloc(sizeof(CMPDAffix) * Conf->naffixes);
5710 tgl 1986 CBC 55 : ptr->affix = NULL;
5710 tgl 1987 ECB :
5710 tgl 1988 CBC 498 : for (i = 0; i < Conf->naffixes; i++)
5710 tgl 1989 ECB : {
5710 tgl 1990 GIC 443 : Affix = &(((AFFIX *) Conf->Affix)[i]);
5706 1991 443 : if (Affix->type == FF_SUFFIX && i < firstsuffix)
5710 1992 55 : firstsuffix = i;
5710 tgl 1993 ECB :
5710 tgl 1994 CBC 518 : if ((Affix->flagflags & FF_COMPOUNDFLAG) && Affix->replen > 0 &&
2592 teodor 1995 GIC 75 : isAffixInUse(Conf, Affix->flag))
1996 : {
1823 tgl 1997 CBC 63 : bool issuffix = (Affix->type == FF_SUFFIX);
1823 tgl 1998 ECB :
5710 tgl 1999 CBC 63 : if (ptr == Conf->CompoundAffix ||
1823 2000 40 : issuffix != (ptr - 1)->issuffix ||
5710 tgl 2001 GIC 20 : strbncmp((const unsigned char *) (ptr - 1)->affix,
2002 20 : (const unsigned char *) Affix->repl,
2003 20 : (ptr - 1)->len))
5710 tgl 2004 ECB : {
2005 : /* leave only unique and minimal suffixes */
5710 tgl 2006 GIC 53 : ptr->affix = Affix->repl;
2007 53 : ptr->len = Affix->replen;
1823 2008 53 : ptr->issuffix = issuffix;
5710 2009 53 : ptr++;
2010 : }
5710 tgl 2011 ECB : }
2012 : }
5710 tgl 2013 CBC 55 : ptr->affix = NULL;
2014 55 : Conf->CompoundAffix = (CMPDAffix *) repalloc(Conf->CompoundAffix, sizeof(CMPDAffix) * (ptr - Conf->CompoundAffix + 1));
5710 tgl 2015 ECB :
2016 : /* Start build a prefix tree */
5710 tgl 2017 GIC 55 : Conf->Prefix = mkANode(Conf, 0, firstsuffix, 0, FF_PREFIX);
5710 tgl 2018 CBC 55 : Conf->Suffix = mkANode(Conf, firstsuffix, Conf->naffixes, 0, FF_SUFFIX);
5706 tgl 2019 GIC 55 : mkVoidAffix(Conf, true, firstsuffix);
5706 tgl 2020 CBC 55 : mkVoidAffix(Conf, false, firstsuffix);
5710 tgl 2021 ECB : }
2022 :
2023 : static AffixNodeData *
5624 bruce 2024 CBC 2310 : FindAffixes(AffixNode *node, const char *word, int wrdlen, int *level, int type)
5710 tgl 2025 ECB : {
2026 : AffixNodeData *StopLow,
2027 : *StopHigh,
2028 : *StopMiddle;
5624 bruce 2029 : uint8 symbol;
5710 tgl 2030 :
5710 tgl 2031 CBC 2310 : if (node->isvoid)
5710 tgl 2032 ECB : { /* search void affixes */
5710 tgl 2033 CBC 2010 : if (node->data->naff)
5710 tgl 2034 GIC 171 : return node->data;
5710 tgl 2035 CBC 1839 : node = node->data->node;
5710 tgl 2036 ECB : }
2037 :
5710 tgl 2038 CBC 2691 : while (node && *level < wrdlen)
2039 : {
2040 2679 : StopLow = node->data;
2041 2679 : StopHigh = node->data + node->length;
5710 tgl 2042 GIC 5913 : while (StopLow < StopHigh)
5710 tgl 2043 ECB : {
5710 tgl 2044 GIC 4437 : StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
2045 4437 : symbol = GETWCHAR(word, wrdlen, *level, type);
2046 :
5710 tgl 2047 CBC 4437 : if (StopMiddle->val == symbol)
2048 : {
5710 tgl 2049 GIC 1203 : (*level)++;
2050 1203 : if (StopMiddle->naff)
2051 651 : return StopMiddle;
2052 552 : node = StopMiddle->node;
5710 tgl 2053 CBC 552 : break;
2054 : }
2055 3234 : else if (StopMiddle->val < symbol)
2056 804 : StopLow = StopMiddle + 1;
2057 : else
2058 2430 : StopHigh = StopMiddle;
2059 : }
5710 tgl 2060 GBC 2028 : if (StopLow >= StopHigh)
2061 1476 : break;
5710 tgl 2062 EUB : }
5710 tgl 2063 GBC 1488 : return NULL;
5710 tgl 2064 EUB : }
2065 :
5710 tgl 2066 ECB : static char *
5624 bruce 2067 GIC 918 : CheckAffix(const char *word, size_t len, AFFIX *Affix, int flagflags, char *newword, int *baselen)
5710 tgl 2068 ECB : {
2069 : /*
2070 : * Check compound allow flags
2071 : */
2072 :
5710 tgl 2073 GIC 918 : if (flagflags == 0)
5710 tgl 2074 ECB : {
5710 tgl 2075 GBC 633 : if (Affix->flagflags & FF_COMPOUNDONLY)
5710 tgl 2076 CBC 66 : return NULL;
5710 tgl 2077 ECB : }
5710 tgl 2078 GBC 285 : else if (flagflags & FF_COMPOUNDBEGIN)
2079 : {
5710 tgl 2080 UIC 0 : if (Affix->flagflags & FF_COMPOUNDFORBIDFLAG)
2081 0 : return NULL;
2082 0 : if ((Affix->flagflags & FF_COMPOUNDBEGIN) == 0)
2083 0 : if (Affix->type == FF_SUFFIX)
5710 tgl 2084 LBC 0 : return NULL;
2085 : }
5710 tgl 2086 CBC 285 : else if (flagflags & FF_COMPOUNDMIDDLE)
5710 tgl 2087 ECB : {
5710 tgl 2088 CBC 204 : if ((Affix->flagflags & FF_COMPOUNDMIDDLE) == 0 ||
2089 114 : (Affix->flagflags & FF_COMPOUNDFORBIDFLAG))
5710 tgl 2090 GIC 90 : return NULL;
2091 : }
2092 81 : else if (flagflags & FF_COMPOUNDLAST)
2093 : {
2094 81 : if (Affix->flagflags & FF_COMPOUNDFORBIDFLAG)
5710 tgl 2095 UIC 0 : return NULL;
5710 tgl 2096 GIC 81 : if ((Affix->flagflags & FF_COMPOUNDLAST) == 0)
5710 tgl 2097 CBC 75 : if (Affix->type == FF_PREFIX)
5710 tgl 2098 UBC 0 : return NULL;
5710 tgl 2099 ECB : }
2100 :
2101 : /*
2102 : * make replace pattern of affix
2103 : */
5710 tgl 2104 GIC 762 : if (Affix->type == FF_SUFFIX)
2105 : {
5710 tgl 2106 CBC 522 : strcpy(newword, word);
2107 522 : strcpy(newword + len - Affix->replen, Affix->find);
2108 522 : if (baselen) /* store length of non-changed part of word */
5710 tgl 2109 GIC 522 : *baselen = len - Affix->replen;
5710 tgl 2110 ECB : }
2111 : else
2112 : {
2113 : /*
2114 : * if prefix is an all non-changed part's length then all word
2115 : * contains only prefix and suffix, so out
2116 : */
5710 tgl 2117 GIC 240 : if (baselen && *baselen + strlen(Affix->find) <= Affix->replen)
5710 tgl 2118 UIC 0 : return NULL;
5710 tgl 2119 GIC 240 : strcpy(newword, Affix->find);
5710 tgl 2120 CBC 240 : strcat(newword, word + Affix->replen);
5710 tgl 2121 ECB : }
2122 :
2123 : /*
2124 : * check resulting word
2125 : */
5710 tgl 2126 GIC 762 : if (Affix->issimple)
5710 tgl 2127 CBC 240 : return newword;
2128 522 : else if (Affix->isregis)
2129 : {
5710 tgl 2130 GBC 354 : if (RS_execute(&(Affix->reg.regis), newword))
5710 tgl 2131 GIC 336 : return newword;
2132 : }
5710 tgl 2133 ECB : else
2134 : {
2135 : pg_wchar *data;
2136 : size_t data_len;
2137 : int newword_len;
2138 :
2139 : /* Convert data string to wide characters */
5710 tgl 2140 GBC 168 : newword_len = strlen(newword);
5710 tgl 2141 CBC 168 : data = (pg_wchar *) palloc((newword_len + 1) * sizeof(pg_wchar));
5710 tgl 2142 GIC 168 : data_len = pg_mb2wchar_with_len(newword, data, newword_len);
5710 tgl 2143 ECB :
1 tmunro 2144 GNC 168 : if (pg_regexec(Affix->reg.pregex, data, data_len,
947 tgl 2145 ECB : 0, NULL, 0, NULL, 0) == REG_OKAY)
2146 : {
5710 tgl 2147 GIC 168 : pfree(data);
5710 tgl 2148 GBC 168 : return newword;
2149 : }
5710 tgl 2150 UIC 0 : pfree(data);
2151 : }
5710 tgl 2152 ECB :
5710 tgl 2153 GIC 18 : return NULL;
5710 tgl 2154 ECB : }
2155 :
2156 : static int
5710 tgl 2157 CBC 270 : addToResult(char **forms, char **cur, char *word)
5710 tgl 2158 ECB : {
5710 tgl 2159 GIC 270 : if (cur - forms >= MAX_NORM - 1)
5710 tgl 2160 UIC 0 : return 0;
5710 tgl 2161 GIC 270 : if (forms == cur || strcmp(word, *(cur - 1)) != 0)
5710 tgl 2162 ECB : {
5710 tgl 2163 CBC 270 : *cur = pstrdup(word);
5050 bruce 2164 270 : *(cur + 1) = NULL;
5710 tgl 2165 GIC 270 : return 1;
2166 : }
2167 :
5710 tgl 2168 UIC 0 : return 0;
5710 tgl 2169 ECB : }
5710 tgl 2170 EUB :
5710 tgl 2171 ECB : static char **
5624 bruce 2172 CBC 753 : NormalizeSubWord(IspellDict *Conf, char *word, int flag)
2173 : {
5710 tgl 2174 GIC 753 : AffixNodeData *suffix = NULL,
2175 753 : *prefix = NULL;
5710 tgl 2176 CBC 753 : int slevel = 0,
5710 tgl 2177 GIC 753 : plevel = 0;
5710 tgl 2178 CBC 753 : int wrdlen = strlen(word),
5710 tgl 2179 ECB : swrdlen;
2180 : char **forms;
2181 : char **cur;
5710 tgl 2182 GIC 753 : char newword[2 * MAXNORMLEN] = "";
2183 753 : char pnewword[2 * MAXNORMLEN] = "";
5710 tgl 2184 CBC 753 : AffixNode *snode = Conf->Suffix,
5710 tgl 2185 ECB : *pnode;
2186 : int i,
2187 : j;
2188 :
5710 tgl 2189 CBC 753 : if (wrdlen > MAXNORMLEN)
5710 tgl 2190 LBC 0 : return NULL;
5710 tgl 2191 CBC 753 : cur = forms = (char **) palloc(MAX_NORM * sizeof(char *));
5710 tgl 2192 GIC 753 : *cur = NULL;
5710 tgl 2193 ECB :
2194 :
2195 : /* Check that the word itself is normal form */
2579 teodor 2196 CBC 753 : if (FindWord(Conf, word, VoidString, flag))
5710 tgl 2197 ECB : {
5710 tgl 2198 GIC 234 : *cur = pstrdup(word);
2199 234 : cur++;
5710 tgl 2200 CBC 234 : *cur = NULL;
2201 : }
2202 :
2203 : /* Find all other NORMAL forms of the 'word' (check only prefix) */
5710 tgl 2204 GIC 753 : pnode = Conf->Prefix;
2205 753 : plevel = 0;
2206 861 : while (pnode)
5710 tgl 2207 ECB : {
5706 tgl 2208 GIC 753 : prefix = FindAffixes(pnode, word, wrdlen, &plevel, FF_PREFIX);
5710 tgl 2209 CBC 753 : if (!prefix)
5710 tgl 2210 GIC 645 : break;
2211 216 : for (j = 0; j < prefix->naff; j++)
5710 tgl 2212 ECB : {
5710 tgl 2213 CBC 108 : if (CheckAffix(word, wrdlen, prefix->aff[j], flag, newword, NULL))
5710 tgl 2214 ECB : {
2215 : /* prefix success */
5710 tgl 2216 CBC 96 : if (FindWord(Conf, newword, prefix->aff[j]->flag, flag))
5710 tgl 2217 GIC 24 : cur += addToResult(forms, cur, newword);
5710 tgl 2218 ECB : }
2219 : }
5710 tgl 2220 GIC 108 : pnode = prefix->node;
5710 tgl 2221 ECB : }
2222 :
2223 : /*
2224 : * Find all other NORMAL forms of the 'word' (check suffix and then
2225 : * prefix)
2226 : */
5710 tgl 2227 CBC 1299 : while (snode)
5710 tgl 2228 ECB : {
5710 tgl 2229 GIC 1053 : int baselen = 0;
5710 tgl 2230 ECB :
2231 : /* find possible suffix */
5706 tgl 2232 CBC 1053 : suffix = FindAffixes(snode, word, wrdlen, &slevel, FF_SUFFIX);
5710 2233 1053 : if (!suffix)
5710 tgl 2234 GIC 507 : break;
5710 tgl 2235 ECB : /* foreach suffix check affix */
5710 tgl 2236 GIC 1188 : for (i = 0; i < suffix->naff; i++)
2237 : {
5710 tgl 2238 CBC 642 : if (CheckAffix(word, wrdlen, suffix->aff[i], flag, newword, &baselen))
5710 tgl 2239 ECB : {
2240 : /* suffix success */
5710 tgl 2241 CBC 504 : if (FindWord(Conf, newword, suffix->aff[i]->flag, flag))
2242 138 : cur += addToResult(forms, cur, newword);
2243 :
2244 : /* now we will look changed word with prefixes */
2245 504 : pnode = Conf->Prefix;
5710 tgl 2246 GIC 504 : plevel = 0;
2247 504 : swrdlen = strlen(newword);
2248 672 : while (pnode)
2249 : {
5706 tgl 2250 CBC 504 : prefix = FindAffixes(pnode, newword, swrdlen, &plevel, FF_PREFIX);
5710 tgl 2251 GIC 504 : if (!prefix)
2252 336 : break;
5710 tgl 2253 CBC 336 : for (j = 0; j < prefix->naff; j++)
2254 : {
2255 168 : if (CheckAffix(newword, swrdlen, prefix->aff[j], flag, pnewword, &baselen))
5710 tgl 2256 ECB : {
2257 : /* prefix success */
2495 rhaas 2258 CBC 288 : char *ff = (prefix->aff[j]->flagflags & suffix->aff[i]->flagflags & FF_CROSSPRODUCT) ?
2495 rhaas 2259 GIC 144 : VoidString : prefix->aff[j]->flag;
2260 :
5710 tgl 2261 144 : if (FindWord(Conf, pnewword, ff, flag))
2262 108 : cur += addToResult(forms, cur, pnewword);
2263 : }
2264 : }
2265 168 : pnode = prefix->node;
2266 : }
2267 : }
2268 : }
2269 :
5710 tgl 2270 CBC 546 : snode = suffix->node;
2271 : }
2272 :
5710 tgl 2273 GIC 753 : if (cur == forms)
2274 : {
5710 tgl 2275 CBC 333 : pfree(forms);
2061 peter_e 2276 GBC 333 : return NULL;
2277 : }
2061 peter_e 2278 CBC 420 : return forms;
2279 : }
5710 tgl 2280 ECB :
2281 : typedef struct SplitVar
2282 : {
2283 : int nstem;
5562 teodor 2284 : int lenstem;
5710 tgl 2285 : char **stem;
2286 : struct SplitVar *next;
5624 bruce 2287 : } SplitVar;
2288 :
5710 tgl 2289 : static int
5624 bruce 2290 GIC 3030 : CheckCompoundAffixes(CMPDAffix **ptr, char *word, int len, bool CheckInPlace)
2291 : {
2292 : bool issuffix;
2293 :
2294 : /* in case CompoundAffix is null: */
3090 tgl 2295 3030 : if (*ptr == NULL)
3090 tgl 2296 LBC 0 : return -1;
2297 :
5710 tgl 2298 CBC 3030 : if (CheckInPlace)
2299 : {
2300 5784 : while ((*ptr)->affix)
5710 tgl 2301 ECB : {
5710 tgl 2302 CBC 3222 : if (len > (*ptr)->len && strncmp((*ptr)->affix, word, (*ptr)->len) == 0)
5710 tgl 2303 ECB : {
5710 tgl 2304 GIC 30 : len = (*ptr)->len;
5710 tgl 2305 CBC 30 : issuffix = (*ptr)->issuffix;
5710 tgl 2306 GIC 30 : (*ptr)++;
2307 30 : return (issuffix) ? len : 0;
5710 tgl 2308 ECB : }
5710 tgl 2309 GIC 3192 : (*ptr)++;
2310 : }
2311 : }
5710 tgl 2312 ECB : else
2313 : {
2314 : char *affbegin;
2315 :
5710 tgl 2316 CBC 846 : while ((*ptr)->affix)
5710 tgl 2317 ECB : {
5710 tgl 2318 GIC 471 : if (len > (*ptr)->len && (affbegin = strstr(word, (*ptr)->affix)) != NULL)
2319 : {
2320 63 : len = (*ptr)->len + (affbegin - word);
5710 tgl 2321 CBC 63 : issuffix = (*ptr)->issuffix;
2322 63 : (*ptr)++;
2323 63 : return (issuffix) ? len : 0;
5710 tgl 2324 ECB : }
5710 tgl 2325 CBC 408 : (*ptr)++;
2326 : }
2327 : }
5710 tgl 2328 GIC 2937 : return -1;
5710 tgl 2329 ECB : }
2330 :
2331 : static SplitVar *
5624 bruce 2332 GIC 705 : CopyVar(SplitVar *s, int makedup)
5710 tgl 2333 ECB : {
5710 tgl 2334 GIC 705 : SplitVar *v = (SplitVar *) palloc(sizeof(SplitVar));
2335 :
2336 705 : v->next = NULL;
5710 tgl 2337 CBC 705 : if (s)
2338 : {
5710 tgl 2339 ECB : int i;
2340 :
5562 teodor 2341 GBC 330 : v->lenstem = s->lenstem;
2342 330 : v->stem = (char **) palloc(sizeof(char *) * v->lenstem);
5710 tgl 2343 GIC 330 : v->nstem = s->nstem;
2344 501 : for (i = 0; i < s->nstem; i++)
5710 tgl 2345 CBC 171 : v->stem[i] = (makedup) ? pstrdup(s->stem[i]) : s->stem[i];
5710 tgl 2346 ECB : }
2347 : else
2348 : {
5562 teodor 2349 GIC 375 : v->lenstem = 16;
5562 teodor 2350 CBC 375 : v->stem = (char **) palloc(sizeof(char *) * v->lenstem);
5710 tgl 2351 GIC 375 : v->nstem = 0;
5562 teodor 2352 ECB : }
5710 tgl 2353 GIC 705 : return v;
2354 : }
5710 tgl 2355 ECB :
5562 teodor 2356 : static void
5562 teodor 2357 CBC 945 : AddStem(SplitVar *v, char *word)
2358 : {
5050 bruce 2359 GIC 945 : if (v->nstem >= v->lenstem)
2360 : {
5562 teodor 2361 UIC 0 : v->lenstem *= 2;
5562 teodor 2362 LBC 0 : v->stem = (char **) repalloc(v->stem, sizeof(char *) * v->lenstem);
2363 : }
2364 :
5562 teodor 2365 CBC 945 : v->stem[v->nstem] = word;
5562 teodor 2366 GIC 945 : v->nstem++;
5562 teodor 2367 CBC 945 : }
5710 tgl 2368 ECB :
2369 : static SplitVar *
5624 bruce 2370 GIC 660 : SplitToVariants(IspellDict *Conf, SPNode *snode, SplitVar *orig, char *word, int wordlen, int startpos, int minpos)
5710 tgl 2371 ECB : {
5710 tgl 2372 GIC 660 : SplitVar *var = NULL;
2373 : SPNodeData *StopLow,
5710 tgl 2374 ECB : *StopHigh,
5710 tgl 2375 CBC 660 : *StopMiddle = NULL;
5710 tgl 2376 GIC 660 : SPNode *node = (snode) ? snode : Conf->Dictionary;
2377 660 : int level = (snode) ? minpos : startpos; /* recursive
2378 : * minpos==level */
2379 : int lenaff;
2380 : CMPDAffix *caff;
2381 : char *notprobed;
2382 660 : int compoundflag = 0;
5710 tgl 2383 ECB :
2384 : /* since this function recurses, it could be driven to stack overflow */
228 tgl 2385 CBC 660 : check_stack_depth();
228 tgl 2386 EUB :
5710 tgl 2387 GIC 660 : notprobed = (char *) palloc(wordlen);
5710 tgl 2388 CBC 660 : memset(notprobed, 1, wordlen);
5710 tgl 2389 GBC 660 : var = CopyVar(orig, 1);
2390 :
5710 tgl 2391 CBC 3726 : while (level < wordlen)
5710 tgl 2392 EUB : {
5710 tgl 2393 ECB : /* find word with epenthetic or/and compound affix */
5710 tgl 2394 CBC 3597 : caff = Conf->CompoundAffix;
2395 3690 : while (level > startpos && (lenaff = CheckCompoundAffixes(&caff, word + level, wordlen - level, (node) ? true : false)) >= 0)
2396 : {
5710 tgl 2397 ECB : /*
5710 tgl 2398 EUB : * there is one of compound affixes, so check word for existings
5710 tgl 2399 ECB : */
5710 tgl 2400 EUB : char buf[MAXNORMLEN];
2401 : char **subres;
5710 tgl 2402 ECB :
5710 tgl 2403 CBC 93 : lenaff = level - startpos + lenaff;
5710 tgl 2404 ECB :
5710 tgl 2405 GIC 93 : if (!notprobed[startpos + lenaff - 1])
5710 tgl 2406 UIC 0 : continue;
5710 tgl 2407 ECB :
5710 tgl 2408 CBC 93 : if (level + lenaff - 1 <= minpos)
5710 tgl 2409 LBC 0 : continue;
2410 :
5050 bruce 2411 CBC 93 : if (lenaff >= MAXNORMLEN)
5050 bruce 2412 UIC 0 : continue; /* skip too big value */
5710 tgl 2413 CBC 93 : if (lenaff > 0)
5710 tgl 2414 GIC 93 : memcpy(buf, word + startpos, lenaff);
5710 tgl 2415 CBC 93 : buf[lenaff] = '\0';
5710 tgl 2416 ECB :
5562 teodor 2417 GIC 93 : if (level == 0)
5710 tgl 2418 LBC 0 : compoundflag = FF_COMPOUNDBEGIN;
5710 tgl 2419 GIC 93 : else if (level == wordlen - 1)
5710 tgl 2420 LBC 0 : compoundflag = FF_COMPOUNDLAST;
5710 tgl 2421 EUB : else
5710 tgl 2422 CBC 93 : compoundflag = FF_COMPOUNDMIDDLE;
5710 tgl 2423 GIC 93 : subres = NormalizeSubWord(Conf, buf, compoundflag);
5710 tgl 2424 CBC 93 : if (subres)
5710 tgl 2425 ECB : {
2426 : /* Yes, it was a word from dictionary */
5710 tgl 2427 GIC 45 : SplitVar *new = CopyVar(var, 0);
2428 45 : SplitVar *ptr = var;
5710 tgl 2429 CBC 45 : char **sptr = subres;
5710 tgl 2430 ECB :
5710 tgl 2431 GIC 45 : notprobed[startpos + lenaff - 1] = 0;
5710 tgl 2432 ECB :
5710 tgl 2433 CBC 90 : while (*sptr)
5710 tgl 2434 ECB : {
5050 bruce 2435 GIC 45 : AddStem(new, *sptr);
5710 tgl 2436 CBC 45 : sptr++;
5710 tgl 2437 ECB : }
5710 tgl 2438 CBC 45 : pfree(subres);
5710 tgl 2439 ECB :
5710 tgl 2440 CBC 45 : while (ptr->next)
5710 tgl 2441 UIC 0 : ptr = ptr->next;
5710 tgl 2442 CBC 45 : ptr->next = SplitToVariants(Conf, NULL, new, word, wordlen, startpos + lenaff, startpos + lenaff);
2443 :
5710 tgl 2444 GIC 45 : pfree(new->stem);
5710 tgl 2445 CBC 45 : pfree(new);
2446 : }
5710 tgl 2447 ECB : }
2448 :
5710 tgl 2449 CBC 3597 : if (!node)
2450 375 : break;
2451 :
2452 3222 : StopLow = node->data;
5710 tgl 2453 GIC 3222 : StopHigh = node->data + node->length;
2454 4347 : while (StopLow < StopHigh)
5710 tgl 2455 ECB : {
5710 tgl 2456 CBC 4032 : StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
2457 4032 : if (StopMiddle->val == ((uint8 *) (word))[level])
5710 tgl 2458 GIC 2907 : break;
2459 1125 : else if (StopMiddle->val < ((uint8 *) (word))[level])
5710 tgl 2460 CBC 489 : StopLow = StopMiddle + 1;
2461 : else
5710 tgl 2462 GIC 636 : StopHigh = StopMiddle;
5710 tgl 2463 ECB : }
2464 :
5710 tgl 2465 GIC 3222 : if (StopLow < StopHigh)
5710 tgl 2466 ECB : {
2592 teodor 2467 CBC 2907 : if (startpos == 0)
5710 tgl 2468 1635 : compoundflag = FF_COMPOUNDBEGIN;
5710 tgl 2469 GIC 1272 : else if (level == wordlen - 1)
2470 144 : compoundflag = FF_COMPOUNDLAST;
5710 tgl 2471 ECB : else
5710 tgl 2472 GIC 1128 : compoundflag = FF_COMPOUNDMIDDLE;
5710 tgl 2473 ECB :
2474 : /* find infinitive */
5710 tgl 2475 CBC 2907 : if (StopMiddle->isword &&
2476 768 : (StopMiddle->compoundflag & compoundflag) &&
2477 636 : notprobed[level])
2478 : {
5710 tgl 2479 ECB : /* ok, we found full compoundallowed word */
5710 tgl 2480 CBC 636 : if (level > minpos)
5710 tgl 2481 ECB : {
2482 : /* and its length more than minimal */
5710 tgl 2483 CBC 396 : if (wordlen == level + 1)
2484 : {
2485 : /* well, it was last word */
5050 bruce 2486 GIC 156 : AddStem(var, pnstrdup(word + startpos, wordlen - startpos));
5710 tgl 2487 CBC 156 : pfree(notprobed);
5710 tgl 2488 GIC 156 : return var;
2489 : }
5710 tgl 2490 ECB : else
5710 tgl 2491 CBC 240 : {
2492 : /* then we will search more big word at the same point */
5710 tgl 2493 GIC 240 : SplitVar *ptr = var;
5710 tgl 2494 ECB :
5710 tgl 2495 CBC 372 : while (ptr->next)
2496 132 : ptr = ptr->next;
5710 tgl 2497 GIC 240 : ptr->next = SplitToVariants(Conf, node, var, word, wordlen, startpos, level);
2498 : /* we can find next word */
2499 240 : level++;
5050 bruce 2500 CBC 240 : AddStem(var, pnstrdup(word + startpos, level - startpos));
5710 tgl 2501 GIC 240 : node = Conf->Dictionary;
5710 tgl 2502 CBC 240 : startpos = level;
2503 240 : continue;
2504 : }
5710 tgl 2505 ECB : }
2506 : }
5710 tgl 2507 CBC 2511 : node = StopMiddle->node;
5710 tgl 2508 ECB : }
2509 : else
5710 tgl 2510 CBC 315 : node = NULL;
2511 2826 : level++;
2512 : }
5710 tgl 2513 ECB :
5050 bruce 2514 GIC 504 : AddStem(var, pnstrdup(word + startpos, wordlen - startpos));
5710 tgl 2515 504 : pfree(notprobed);
5710 tgl 2516 CBC 504 : return var;
2517 : }
2518 :
5562 teodor 2519 ECB : static void
5050 bruce 2520 CBC 657 : addNorm(TSLexeme **lres, TSLexeme **lcur, char *word, int flags, uint16 NVariant)
5562 teodor 2521 ECB : {
5050 bruce 2522 GIC 657 : if (*lres == NULL)
5562 teodor 2523 CBC 303 : *lcur = *lres = (TSLexeme *) palloc(MAX_NORM * sizeof(TSLexeme));
2524 :
5050 bruce 2525 657 : if (*lcur - *lres < MAX_NORM - 1)
2526 : {
5562 teodor 2527 657 : (*lcur)->lexeme = word;
5562 teodor 2528 GIC 657 : (*lcur)->flags = flags;
5562 teodor 2529 CBC 657 : (*lcur)->nvariant = NVariant;
5562 teodor 2530 GIC 657 : (*lcur)++;
5562 teodor 2531 CBC 657 : (*lcur)->lexeme = NULL;
5562 teodor 2532 ECB : }
5562 teodor 2533 GIC 657 : }
5562 teodor 2534 ECB :
2535 : TSLexeme *
5624 bruce 2536 GIC 375 : NINormalizeWord(IspellDict *Conf, char *word)
5710 tgl 2537 ECB : {
2538 : char **res;
5710 tgl 2539 CBC 375 : TSLexeme *lcur = NULL,
5710 tgl 2540 GIC 375 : *lres = NULL;
5710 tgl 2541 CBC 375 : uint16 NVariant = 1;
2542 :
5710 tgl 2543 GIC 375 : res = NormalizeSubWord(Conf, word, 0);
5710 tgl 2544 ECB :
5710 tgl 2545 GIC 375 : if (res)
5710 tgl 2546 ECB : {
5710 tgl 2547 GIC 243 : char **ptr = res;
5710 tgl 2548 ECB :
5050 bruce 2549 GIC 570 : while (*ptr && (lcur - lres) < MAX_NORM)
5710 tgl 2550 ECB : {
5050 bruce 2551 GIC 327 : addNorm(&lres, &lcur, *ptr, 0, NVariant++);
5710 tgl 2552 CBC 327 : ptr++;
2553 : }
2554 243 : pfree(res);
2555 : }
5710 tgl 2556 ECB :
5710 tgl 2557 GIC 375 : if (Conf->usecompound)
5710 tgl 2558 ECB : {
5710 tgl 2559 GIC 375 : int wordlen = strlen(word);
2560 : SplitVar *ptr,
5710 tgl 2561 CBC 375 : *var = SplitToVariants(Conf, NULL, NULL, word, wordlen, 0, -1);
5710 tgl 2562 ECB : int i;
2563 :
5710 tgl 2564 GIC 1035 : while (var)
2565 : {
5710 tgl 2566 CBC 660 : if (var->nstem > 1)
5710 tgl 2567 ECB : {
5710 tgl 2568 CBC 285 : char **subres = NormalizeSubWord(Conf, var->stem[var->nstem - 1], FF_COMPOUNDLAST);
2569 :
5710 tgl 2570 GIC 285 : if (subres)
2571 : {
5710 tgl 2572 CBC 132 : char **subptr = subres;
5710 tgl 2573 ECB :
5710 tgl 2574 CBC 264 : while (*subptr)
5710 tgl 2575 ECB : {
5710 tgl 2576 CBC 330 : for (i = 0; i < var->nstem - 1; i++)
5710 tgl 2577 ECB : {
5050 bruce 2578 GIC 198 : addNorm(&lres, &lcur, (subptr == subres) ? var->stem[i] : pstrdup(var->stem[i]), 0, NVariant);
2579 : }
2580 :
5050 bruce 2581 CBC 132 : addNorm(&lres, &lcur, *subptr, 0, NVariant);
5710 tgl 2582 GIC 132 : subptr++;
2583 132 : NVariant++;
2584 : }
2585 :
2586 132 : pfree(subres);
2587 132 : var->stem[0] = NULL;
2588 132 : pfree(var->stem[var->nstem - 1]);
2589 : }
2590 : }
2591 :
2592 1371 : for (i = 0; i < var->nstem && var->stem[i]; i++)
2593 711 : pfree(var->stem[i]);
2594 660 : ptr = var->next;
2595 660 : pfree(var->stem);
2596 660 : pfree(var);
2597 660 : var = ptr;
2598 : }
2599 : }
2600 :
2601 375 : return lres;
2602 : }
|