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