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