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