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