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