LCOV - code coverage report
Current view: top level - src/backend/tsearch - spell.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13devel Lines: 1017 1103 92.2 %
Date: 2019-11-13 23:06:49 Functions: 46 46 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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
      87          94 : NIStartBuild(IspellDict *Conf)
      88             : {
      89             :     /*
      90             :      * The temp context is a child of CurTransactionContext, so that it will
      91             :      * go away automatically on error.
      92             :      */
      93          94 :     Conf->buildCxt = AllocSetContextCreate(CurTransactionContext,
      94             :                                            "Ispell dictionary init context",
      95             :                                            ALLOCSET_DEFAULT_SIZES);
      96          94 : }
      97             : 
      98             : /*
      99             :  * Clean up when dictionary construction is complete.
     100             :  */
     101             : void
     102          78 : NIFinishBuild(IspellDict *Conf)
     103             : {
     104             :     /* Release no-longer-needed temp memory */
     105          78 :     MemoryContextDelete(Conf->buildCxt);
     106             :     /* Just for cleanliness, zero the now-dangling pointers */
     107          78 :     Conf->buildCxt = NULL;
     108          78 :     Conf->Spell = NULL;
     109          78 :     Conf->firstfree = NULL;
     110          78 :     Conf->CompoundAffixFlags = NULL;
     111          78 : }
     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        8782 : 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        8782 :     if (size > COMPACT_MAX_REQ)
     138           0 :         return palloc0(size);
     139             : 
     140             :     /* Keep everything maxaligned */
     141        8782 :     size = MAXALIGN(size);
     142             : 
     143             :     /* Need more space? */
     144        8782 :     if (size > Conf->avail)
     145             :     {
     146          90 :         Conf->firstfree = palloc0(COMPACT_ALLOC_CHUNK);
     147          90 :         Conf->avail = COMPACT_ALLOC_CHUNK;
     148             :     }
     149             : 
     150        8782 :     result = (void *) Conf->firstfree;
     151        8782 :     Conf->firstfree += size;
     152        8782 :     Conf->avail -= size;
     153             : 
     154        8782 :     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        4680 : cpstrdup(IspellDict *Conf, const char *str)
     162             : {
     163        4680 :     char       *res = cpalloc(strlen(str) + 1);
     164             : 
     165        4680 :     strcpy(res, str);
     166        4680 :     return res;
     167             : }
     168             : 
     169             : 
     170             : /*
     171             :  * Apply lowerstr(), producing a temporary result (in the buildCxt).
     172             :  */
     173             : static char *
     174        4010 : lowerstr_ctx(IspellDict *Conf, const char *src)
     175             : {
     176             :     MemoryContext saveCtx;
     177             :     char       *dst;
     178             : 
     179        4010 :     saveCtx = MemoryContextSwitchTo(Conf->buildCxt);
     180        4010 :     dst = lowerstr(src);
     181        4010 :     MemoryContextSwitchTo(saveCtx);
     182             : 
     183        4010 :     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        2052 : cmpspell(const void *s1, const void *s2)
     197             : {
     198        2052 :     return strcmp((*(SPELL *const *) s1)->word, (*(SPELL *const *) s2)->word);
     199             : }
     200             : 
     201             : static int
     202        1600 : cmpspellaffix(const void *s1, const void *s2)
     203             : {
     204        1600 :     return strcmp((*(SPELL *const *) s1)->p.flag,
     205        1600 :                   (*(SPELL *const *) s2)->p.flag);
     206             : }
     207             : 
     208             : static int
     209        2782 : cmpcmdflag(const void *f1, const void *f2)
     210             : {
     211        2782 :     CompoundAffixFlag *fv1 = (CompoundAffixFlag *) f1,
     212        2782 :                *fv2 = (CompoundAffixFlag *) f2;
     213             : 
     214             :     Assert(fv1->flagMode == fv2->flagMode);
     215             : 
     216        2782 :     if (fv1->flagMode == FM_NUM)
     217             :     {
     218         542 :         if (fv1->flag.i == fv2->flag.i)
     219          80 :             return 0;
     220             : 
     221         462 :         return (fv1->flag.i > fv2->flag.i) ? 1 : -1;
     222             :     }
     223             : 
     224        2240 :     return strcmp(fv1->flag.s, fv2->flag.s);
     225             : }
     226             : 
     227             : static char *
     228         822 : findchar(char *str, int c)
     229             : {
     230        6874 :     while (*str)
     231             :     {
     232        5962 :         if (t_iseq(str, c))
     233         732 :             return str;
     234        5230 :         str += pg_mblen(str);
     235             :     }
     236             : 
     237          90 :     return NULL;
     238             : }
     239             : 
     240             : static char *
     241          30 : findchar2(char *str, int c1, int c2)
     242             : {
     243         660 :     while (*str)
     244             :     {
     245         630 :         if (t_iseq(str, c1) || t_iseq(str, c2))
     246          30 :             return str;
     247         600 :         str += pg_mblen(str);
     248             :     }
     249             : 
     250           0 :     return NULL;
     251             : }
     252             : 
     253             : 
     254             : /* backward string compare for suffix tree operations */
     255             : static int
     256         818 : strbcmp(const unsigned char *s1, const unsigned char *s2)
     257             : {
     258         818 :     int         l1 = strlen((const char *) s1) - 1,
     259         818 :                 l2 = strlen((const char *) s2) - 1;
     260             : 
     261        1912 :     while (l1 >= 0 && l2 >= 0)
     262             :     {
     263         856 :         if (s1[l1] < s2[l2])
     264         186 :             return -1;
     265         670 :         if (s1[l1] > s2[l2])
     266         394 :             return 1;
     267         276 :         l1--;
     268         276 :         l2--;
     269             :     }
     270         238 :     if (l1 < l2)
     271          64 :         return -1;
     272         174 :     if (l1 > l2)
     273         146 :         return 1;
     274             : 
     275          28 :     return 0;
     276             : }
     277             : 
     278             : static int
     279          28 : strbncmp(const unsigned char *s1, const unsigned char *s2, size_t count)
     280             : {
     281          28 :     int         l1 = strlen((const char *) s1) - 1,
     282          28 :                 l2 = strlen((const char *) s2) - 1,
     283          28 :                 l = count;
     284             : 
     285          70 :     while (l1 >= 0 && l2 >= 0 && l > 0)
     286             :     {
     287          28 :         if (s1[l1] < s2[l2])
     288          14 :             return -1;
     289          14 :         if (s1[l1] > s2[l2])
     290           0 :             return 1;
     291          14 :         l1--;
     292          14 :         l2--;
     293          14 :         l--;
     294             :     }
     295          14 :     if (l == 0)
     296          14 :         return 0;
     297           0 :     if (l1 < l2)
     298           0 :         return -1;
     299           0 :     if (l1 > l2)
     300           0 :         return 1;
     301           0 :     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        1384 : cmpaffix(const void *s1, const void *s2)
     311             : {
     312        1384 :     const AFFIX *a1 = (const AFFIX *) s1;
     313        1384 :     const AFFIX *a2 = (const AFFIX *) s2;
     314             : 
     315        1384 :     if (a1->type < a2->type)
     316         316 :         return -1;
     317        1068 :     if (a1->type > a2->type)
     318          94 :         return 1;
     319         974 :     if (a1->type == FF_PREFIX)
     320         156 :         return strcmp(a1->repl, a2->repl);
     321             :     else
     322         818 :         return strbcmp((const unsigned char *) a1->repl,
     323         818 :                        (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        4248 : getNextFlagFromString(IspellDict *Conf, char **sflagset, char *sflag)
     349             : {
     350             :     int32       s;
     351             :     char       *next,
     352        4248 :                *sbuf = *sflagset;
     353             :     int         maxstep;
     354        4248 :     bool        stop = false;
     355        4248 :     bool        met_comma = false;
     356             : 
     357        4248 :     maxstep = (Conf->flagMode == FM_LONG) ? 2 : 1;
     358             : 
     359        9790 :     while (**sflagset)
     360             :     {
     361        5542 :         switch (Conf->flagMode)
     362             :         {
     363             :             case FM_LONG:
     364             :             case FM_CHAR:
     365        4746 :                 COPYCHAR(sflag, *sflagset);
     366        4746 :                 sflag += pg_mblen(*sflagset);
     367             : 
     368             :                 /* Go to start of the next flag */
     369        4746 :                 *sflagset += pg_mblen(*sflagset);
     370             : 
     371             :                 /* Check if we get all characters of flag */
     372        4746 :                 maxstep--;
     373        4746 :                 stop = (maxstep == 0);
     374        4746 :                 break;
     375             :             case FM_NUM:
     376         796 :                 s = strtol(*sflagset, &next, 10);
     377         796 :                 if (*sflagset == next || errno == ERANGE)
     378           4 :                     ereport(ERROR,
     379             :                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
     380             :                              errmsg("invalid affix flag \"%s\"", *sflagset)));
     381         792 :                 if (s < 0 || s > FLAGNUM_MAXSIZE)
     382           0 :                     ereport(ERROR,
     383             :                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
     384             :                              errmsg("affix flag \"%s\" is out of range",
     385             :                                     *sflagset)));
     386         792 :                 sflag += sprintf(sflag, "%0d", s);
     387             : 
     388             :                 /* Go to start of the next flag */
     389         792 :                 *sflagset = next;
     390        2000 :                 while (**sflagset)
     391             :                 {
     392         832 :                     if (t_isdigit(*sflagset))
     393             :                     {
     394         416 :                         if (!met_comma)
     395           0 :                             ereport(ERROR,
     396             :                                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
     397             :                                      errmsg("invalid affix flag \"%s\"",
     398             :                                             *sflagset)));
     399         416 :                         break;
     400             :                     }
     401         416 :                     else if (t_iseq(*sflagset, ','))
     402             :                     {
     403         416 :                         if (met_comma)
     404           0 :                             ereport(ERROR,
     405             :                                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
     406             :                                      errmsg("invalid affix flag \"%s\"",
     407             :                                             *sflagset)));
     408         416 :                         met_comma = true;
     409             :                     }
     410           0 :                     else if (!t_isspace(*sflagset))
     411             :                     {
     412           0 :                         ereport(ERROR,
     413             :                                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
     414             :                                  errmsg("invalid character in affix flag \"%s\"",
     415             :                                         *sflagset)));
     416             :                     }
     417             : 
     418         416 :                     *sflagset += pg_mblen(*sflagset);
     419             :                 }
     420         792 :                 stop = true;
     421         792 :                 break;
     422             :             default:
     423           0 :                 elog(ERROR, "unrecognized type of Conf->flagMode: %d",
     424             :                      Conf->flagMode);
     425             :         }
     426             : 
     427        5538 :         if (stop)
     428        4244 :             break;
     429             :     }
     430             : 
     431        4244 :     if (Conf->flagMode == FM_LONG && maxstep > 0)
     432           0 :         ereport(ERROR,
     433             :                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
     434             :                  errmsg("invalid affix flag \"%s\" with \"long\" flag value",
     435             :                         sbuf)));
     436             : 
     437        4244 :     *sflag = '\0';
     438        4244 : }
     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        1530 : IsAffixFlagInUse(IspellDict *Conf, int affix, const char *affixflag)
     454             : {
     455             :     char       *flagcur;
     456             :     char        flag[BUFSIZ];
     457             : 
     458        1530 :     if (*affixflag == 0)
     459         424 :         return true;
     460             : 
     461             :     Assert(affix < Conf->nAffixData);
     462             : 
     463        1106 :     flagcur = Conf->AffixData[affix];
     464             : 
     465        4324 :     while (*flagcur)
     466             :     {
     467        2448 :         getNextFlagFromString(Conf, &flagcur, flag);
     468             :         /* Compare first affix flag in flagcur with affixflag */
     469        2448 :         if (strcmp(flag, affixflag) == 0)
     470         336 :             return true;
     471             :     }
     472             : 
     473             :     /* Could not find affixflag */
     474         770 :     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         822 : NIAddSpell(IspellDict *Conf, const char *word, const char *flag)
     486             : {
     487         822 :     if (Conf->nspell >= Conf->mspell)
     488             :     {
     489          90 :         if (Conf->mspell)
     490             :         {
     491           0 :             Conf->mspell *= 2;
     492           0 :             Conf->Spell = (SPELL **) repalloc(Conf->Spell, Conf->mspell * sizeof(SPELL *));
     493             :         }
     494             :         else
     495             :         {
     496          90 :             Conf->mspell = 1024 * 20;
     497          90 :             Conf->Spell = (SPELL **) tmpalloc(Conf->mspell * sizeof(SPELL *));
     498             :         }
     499             :     }
     500         822 :     Conf->Spell[Conf->nspell] = (SPELL *) tmpalloc(SPELLHDRSZ + strlen(word) + 1);
     501         822 :     strcpy(Conf->Spell[Conf->nspell]->word, word);
     502        1644 :     Conf->Spell[Conf->nspell]->p.flag = (*flag != '\0')
     503         822 :         ? cpstrdup(Conf, flag) : VoidString;
     504         822 :     Conf->nspell++;
     505         822 : }
     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
     516          90 : NIImportDictionary(IspellDict *Conf, const char *filename)
     517             : {
     518             :     tsearch_readline_state trst;
     519             :     char       *line;
     520             : 
     521          90 :     if (!tsearch_readline_begin(&trst, filename))
     522           0 :         ereport(ERROR,
     523             :                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
     524             :                  errmsg("could not open dictionary file \"%s\": %m",
     525             :                         filename)));
     526             : 
     527        1002 :     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         822 :         flag = NULL;
     537         822 :         if ((s = findchar(line, '/')))
     538             :         {
     539         732 :             *s++ = '\0';
     540         732 :             flag = s;
     541        3654 :             while (*s)
     542             :             {
     543             :                 /* we allow only single encoded flags for faster works */
     544        2922 :                 if (pg_mblen(s) == 1 && t_isprint(s) && !t_isspace(s))
     545        2190 :                     s++;
     546             :                 else
     547             :                 {
     548         732 :                     *s = '\0';
     549         732 :                     break;
     550             :                 }
     551             :             }
     552             :         }
     553             :         else
     554          90 :             flag = "";
     555             : 
     556             :         /* Remove trailing spaces */
     557         822 :         s = line;
     558        6784 :         while (*s)
     559             :         {
     560        5230 :             if (t_isspace(s))
     561             :             {
     562          90 :                 *s = '\0';
     563          90 :                 break;
     564             :             }
     565        5140 :             s += pg_mblen(s);
     566             :         }
     567         822 :         pstr = lowerstr_ctx(Conf, line);
     568             : 
     569         822 :         NIAddSpell(Conf, pstr, flag);
     570         822 :         pfree(pstr);
     571             : 
     572         822 :         pfree(line);
     573             :     }
     574          90 :     tsearch_readline_end(&trst);
     575          90 : }
     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        1996 : FindWord(IspellDict *Conf, const char *word, const char *affixflag, int flag)
     602             : {
     603        1996 :     SPNode     *node = Conf->Dictionary;
     604             :     SPNodeData *StopLow,
     605             :                *StopHigh,
     606             :                *StopMiddle;
     607        1996 :     const uint8 *ptr = (const uint8 *) word;
     608             : 
     609        1996 :     flag &= FF_COMPOUNDFLAGMASK;
     610             : 
     611       11292 :     while (node && *ptr)
     612             :     {
     613        8816 :         StopLow = node->data;
     614        8816 :         StopHigh = node->data + node->length;
     615       21428 :         while (StopLow < StopHigh)
     616             :         {
     617       11768 :             StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
     618       11768 :             if (StopMiddle->val == *ptr)
     619             :             {
     620        7972 :                 if (*(ptr + 1) == '\0' && StopMiddle->isword)
     621             :                 {
     622         764 :                     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         484 :                         if (StopMiddle->compoundflag & FF_COMPOUNDONLY)
     630           0 :                             return 0;
     631             :                     }
     632         280 :                     else if ((flag & StopMiddle->compoundflag) == 0)
     633           0 :                         return 0;
     634             : 
     635             :                     /*
     636             :                      * Check if this affix rule is presented in the affix set
     637             :                      * with index StopMiddle->affix.
     638             :                      */
     639         764 :                     if (IsAffixFlagInUse(Conf, StopMiddle->affix, affixflag))
     640         672 :                         return 1;
     641             :                 }
     642        7300 :                 node = StopMiddle->node;
     643        7300 :                 ptr++;
     644        7300 :                 break;
     645             :             }
     646        3796 :             else if (StopMiddle->val < *ptr)
     647        1288 :                 StopLow = StopMiddle + 1;
     648             :             else
     649        2508 :                 StopHigh = StopMiddle;
     650             :         }
     651        8144 :         if (StopLow >= StopHigh)
     652         844 :             break;
     653             :     }
     654        1324 :     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         744 : 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         744 :     if (Conf->naffixes >= Conf->maffixes)
     682             :     {
     683          90 :         if (Conf->maffixes)
     684             :         {
     685           0 :             Conf->maffixes *= 2;
     686           0 :             Conf->Affix = (AFFIX *) repalloc((void *) Conf->Affix, Conf->maffixes * sizeof(AFFIX));
     687             :         }
     688             :         else
     689             :         {
     690          90 :             Conf->maffixes = 16;
     691          90 :             Conf->Affix = (AFFIX *) palloc(Conf->maffixes * sizeof(AFFIX));
     692             :         }
     693             :     }
     694             : 
     695         744 :     Affix = Conf->Affix + Conf->naffixes;
     696             : 
     697             :     /* This affix rule can be applied for words with any ending */
     698         744 :     if (strcmp(mask, ".") == 0 || *mask == '\0')
     699             :     {
     700         180 :         Affix->issimple = 1;
     701         180 :         Affix->isregis = 0;
     702             :     }
     703             :     /* This affix rule will use regis to search word ending */
     704         564 :     else if (RS_isRegis(mask))
     705             :     {
     706         472 :         Affix->issimple = 0;
     707         472 :         Affix->isregis = 1;
     708         472 :         RS_compile(&(Affix->reg.regis), (type == FF_SUFFIX),
     709         472 :                    *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          92 :         Affix->issimple = 0;
     721          92 :         Affix->isregis = 0;
     722          92 :         tmask = (char *) tmpalloc(strlen(mask) + 3);
     723          92 :         if (type == FF_SUFFIX)
     724          92 :             sprintf(tmask, "%s$", mask);
     725             :         else
     726           0 :             sprintf(tmask, "^%s", mask);
     727             : 
     728          92 :         masklen = strlen(tmask);
     729          92 :         wmask = (pg_wchar *) tmpalloc((masklen + 1) * sizeof(pg_wchar));
     730          92 :         wmasklen = pg_mb2wchar_with_len(tmask, wmask, masklen);
     731             : 
     732          92 :         err = pg_regcomp(&(Affix->reg.regex), wmask, wmasklen,
     733             :                          REG_ADVANCED | REG_NOSUB,
     734             :                          DEFAULT_COLLATION_OID);
     735          92 :         if (err)
     736             :         {
     737             :             char        errstr[100];
     738             : 
     739           0 :             pg_regerror(err, &(Affix->reg.regex), errstr, sizeof(errstr));
     740           0 :             ereport(ERROR,
     741             :                     (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
     742             :                      errmsg("invalid regular expression: %s", errstr)));
     743             :         }
     744             :     }
     745             : 
     746         744 :     Affix->flagflags = flagflags;
     747         744 :     if ((Affix->flagflags & FF_COMPOUNDONLY) || (Affix->flagflags & FF_COMPOUNDPERMITFLAG))
     748             :     {
     749         134 :         if ((Affix->flagflags & FF_COMPOUNDFLAG) == 0)
     750         134 :             Affix->flagflags |= FF_COMPOUNDFLAG;
     751             :     }
     752         744 :     Affix->flag = cpstrdup(Conf, flag);
     753         744 :     Affix->type = type;
     754             : 
     755         744 :     Affix->find = (find && *find) ? cpstrdup(Conf, find) : VoidString;
     756         744 :     if ((Affix->replen = strlen(repl)) > 0)
     757         720 :         Affix->repl = cpstrdup(Conf, repl);
     758             :     else
     759          24 :         Affix->repl = VoidString;
     760         744 :     Conf->naffixes++;
     761         744 : }
     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        6894 : get_nextfield(char **str, char *next)
     785             : {
     786        6894 :     int         state = PAE_WAIT_MASK;
     787        6894 :     int         avail = BUFSIZ;
     788             : 
     789       36378 :     while (**str)
     790             :     {
     791       28674 :         if (state == PAE_WAIT_MASK)
     792             :         {
     793       12718 :             if (t_iseq(*str, '#'))
     794         242 :                 return false;
     795       12476 :             else if (!t_isspace(*str))
     796             :             {
     797        5842 :                 int         clen = pg_mblen(*str);
     798             : 
     799        5842 :                 if (clen < avail)
     800             :                 {
     801        5842 :                     COPYCHAR(next, *str);
     802        5842 :                     next += clen;
     803        5842 :                     avail -= clen;
     804             :                 }
     805        5842 :                 state = PAE_INMASK;
     806             :             }
     807             :         }
     808             :         else                    /* state == PAE_INMASK */
     809             :         {
     810       15956 :             if (t_isspace(*str))
     811             :             {
     812        5842 :                 *next = '\0';
     813        5842 :                 return true;
     814             :             }
     815             :             else
     816             :             {
     817       10114 :                 int         clen = pg_mblen(*str);
     818             : 
     819       10114 :                 if (clen < avail)
     820             :                 {
     821       10114 :                     COPYCHAR(next, *str);
     822       10114 :                     next += clen;
     823       10114 :                     avail -= clen;
     824             :                 }
     825             :             }
     826             :         }
     827       22590 :         *str += pg_mblen(*str);
     828             :     }
     829             : 
     830         810 :     *next = '\0';
     831             : 
     832         810 :     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        1586 : parse_ooaffentry(char *str, char *type, char *flag, char *find,
     851             :                  char *repl, char *mask)
     852             : {
     853        1586 :     int         state = PAE_WAIT_TYPE;
     854        1586 :     int         fields_read = 0;
     855        1586 :     bool        valid = false;
     856             : 
     857        1586 :     *type = *flag = *find = *repl = *mask = '\0';
     858             : 
     859        8480 :     while (*str)
     860             :     {
     861        6894 :         switch (state)
     862             :         {
     863             :             case PAE_WAIT_TYPE:
     864        1586 :                 valid = get_nextfield(&str, type);
     865        1586 :                 state = PAE_WAIT_FLAG;
     866        1586 :                 break;
     867             :             case PAE_WAIT_FLAG:
     868        1586 :                 valid = get_nextfield(&str, flag);
     869        1586 :                 state = PAE_WAIT_FIND;
     870        1586 :                 break;
     871             :             case PAE_WAIT_FIND:
     872        1586 :                 valid = get_nextfield(&str, find);
     873        1586 :                 state = PAE_WAIT_REPL;
     874        1586 :                 break;
     875             :             case PAE_WAIT_REPL:
     876        1068 :                 valid = get_nextfield(&str, repl);
     877        1068 :                 state = PAE_WAIT_MASK;
     878        1068 :                 break;
     879             :             case PAE_WAIT_MASK:
     880        1068 :                 valid = get_nextfield(&str, mask);
     881        1068 :                 state = -1;     /* force loop exit */
     882        1068 :                 break;
     883             :             default:
     884           0 :                 elog(ERROR, "unrecognized state in parse_ooaffentry: %d",
     885             :                      state);
     886             :                 break;
     887             :         }
     888        6894 :         if (valid)
     889        5842 :             fields_read++;
     890             :         else
     891        1052 :             break;              /* early EOL */
     892        5842 :         if (state < 0)
     893         534 :             break;              /* got all fields */
     894             :     }
     895             : 
     896        1586 :     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         210 : parse_affentry(char *str, char *mask, char *find, char *repl)
     907             : {
     908         210 :     int         state = PAE_WAIT_MASK;
     909         210 :     char       *pmask = mask,
     910         210 :                *pfind = find,
     911         210 :                *prepl = repl;
     912             : 
     913         210 :     *mask = *find = *repl = '\0';
     914             : 
     915        5730 :     while (*str)
     916             :     {
     917        5520 :         if (state == PAE_WAIT_MASK)
     918             :         {
     919         510 :             if (t_iseq(str, '#'))
     920           0 :                 return false;
     921         510 :             else if (!t_isspace(str))
     922             :             {
     923         210 :                 COPYCHAR(pmask, str);
     924         210 :                 pmask += pg_mblen(str);
     925         210 :                 state = PAE_INMASK;
     926             :             }
     927             :         }
     928        5010 :         else if (state == PAE_INMASK)
     929             :         {
     930        2040 :             if (t_iseq(str, '>'))
     931             :             {
     932         210 :                 *pmask = '\0';
     933         210 :                 state = PAE_WAIT_FIND;
     934             :             }
     935        1830 :             else if (!t_isspace(str))
     936             :             {
     937         720 :                 COPYCHAR(pmask, str);
     938         720 :                 pmask += pg_mblen(str);
     939             :             }
     940             :         }
     941        2970 :         else if (state == PAE_WAIT_FIND)
     942             :         {
     943         840 :             if (t_iseq(str, '-'))
     944             :             {
     945          30 :                 state = PAE_INFIND;
     946             :             }
     947         810 :             else if (t_isalpha(str) || t_iseq(str, '\'') /* english 's */ )
     948             :             {
     949         180 :                 COPYCHAR(prepl, str);
     950         180 :                 prepl += pg_mblen(str);
     951         180 :                 state = PAE_INREPL;
     952             :             }
     953         630 :             else if (!t_isspace(str))
     954           0 :                 ereport(ERROR,
     955             :                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
     956             :                          errmsg("syntax error")));
     957             :         }
     958        2130 :         else if (state == PAE_INFIND)
     959             :         {
     960          60 :             if (t_iseq(str, ','))
     961             :             {
     962          30 :                 *pfind = '\0';
     963          30 :                 state = PAE_WAIT_REPL;
     964             :             }
     965          30 :             else if (t_isalpha(str))
     966             :             {
     967          30 :                 COPYCHAR(pfind, str);
     968          30 :                 pfind += pg_mblen(str);
     969             :             }
     970           0 :             else if (!t_isspace(str))
     971           0 :                 ereport(ERROR,
     972             :                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
     973             :                          errmsg("syntax error")));
     974             :         }
     975        2070 :         else if (state == PAE_WAIT_REPL)
     976             :         {
     977          30 :             if (t_iseq(str, '-'))
     978             :             {
     979           0 :                 break;          /* void repl */
     980             :             }
     981          30 :             else if (t_isalpha(str))
     982             :             {
     983          30 :                 COPYCHAR(prepl, str);
     984          30 :                 prepl += pg_mblen(str);
     985          30 :                 state = PAE_INREPL;
     986             :             }
     987           0 :             else if (!t_isspace(str))
     988           0 :                 ereport(ERROR,
     989             :                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
     990             :                          errmsg("syntax error")));
     991             :         }
     992        2040 :         else if (state == PAE_INREPL)
     993             :         {
     994        2040 :             if (t_iseq(str, '#'))
     995             :             {
     996         210 :                 *prepl = '\0';
     997         210 :                 break;
     998             :             }
     999        1830 :             else if (t_isalpha(str))
    1000             :             {
    1001         270 :                 COPYCHAR(prepl, str);
    1002         270 :                 prepl += pg_mblen(str);
    1003             :             }
    1004        1560 :             else if (!t_isspace(str))
    1005           0 :                 ereport(ERROR,
    1006             :                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
    1007             :                          errmsg("syntax error")));
    1008             :         }
    1009             :         else
    1010           0 :             elog(ERROR, "unrecognized state in parse_affentry: %d", state);
    1011             : 
    1012        5310 :         str += pg_mblen(str);
    1013             :     }
    1014             : 
    1015         210 :     *pmask = *pfind = *prepl = '\0';
    1016             : 
    1017         210 :     return (*mask && (*find || *repl));
    1018             : }
    1019             : 
    1020             : /*
    1021             :  * Sets a Hunspell options depending on flag type.
    1022             :  */
    1023             : static void
    1024        2034 : setCompoundAffixFlagValue(IspellDict *Conf, CompoundAffixFlag *entry,
    1025             :                           char *s, uint32 val)
    1026             : {
    1027        2034 :     if (Conf->flagMode == FM_NUM)
    1028             :     {
    1029             :         char       *next;
    1030             :         int         i;
    1031             : 
    1032         438 :         i = strtol(s, &next, 10);
    1033         438 :         if (s == next || errno == ERANGE)
    1034           0 :             ereport(ERROR,
    1035             :                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
    1036             :                      errmsg("invalid affix flag \"%s\"", s)));
    1037         438 :         if (i < 0 || i > FLAGNUM_MAXSIZE)
    1038           0 :             ereport(ERROR,
    1039             :                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
    1040             :                      errmsg("affix flag \"%s\" is out of range", s)));
    1041             : 
    1042         438 :         entry->flag.i = i;
    1043             :     }
    1044             :     else
    1045        1596 :         entry->flag.s = cpstrdup(Conf, s);
    1046             : 
    1047        2034 :     entry->flagMode = Conf->flagMode;
    1048        2034 :     entry->value = val;
    1049        2034 : }
    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
    1059         238 : addCompoundAffixFlagValue(IspellDict *Conf, char *s, uint32 val)
    1060             : {
    1061             :     CompoundAffixFlag *newValue;
    1062             :     char        sbuf[BUFSIZ];
    1063             :     char       *sflag;
    1064             :     int         clen;
    1065             : 
    1066         684 :     while (*s && t_isspace(s))
    1067         208 :         s += pg_mblen(s);
    1068             : 
    1069         238 :     if (!*s)
    1070           0 :         ereport(ERROR,
    1071             :                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
    1072             :                  errmsg("syntax error")));
    1073             : 
    1074             :     /* Get flag without \n */
    1075         238 :     sflag = sbuf;
    1076         942 :     while (*s && !t_isspace(s) && *s != '\n')
    1077             :     {
    1078         466 :         clen = pg_mblen(s);
    1079         466 :         COPYCHAR(sflag, s);
    1080         466 :         sflag += clen;
    1081         466 :         s += clen;
    1082             :     }
    1083         238 :     *sflag = '\0';
    1084             : 
    1085             :     /* Resize array or allocate memory for array CompoundAffixFlag */
    1086         238 :     if (Conf->nCompoundAffixFlag >= Conf->mCompoundAffixFlag)
    1087             :     {
    1088          90 :         if (Conf->mCompoundAffixFlag)
    1089             :         {
    1090           0 :             Conf->mCompoundAffixFlag *= 2;
    1091           0 :             Conf->CompoundAffixFlags = (CompoundAffixFlag *)
    1092           0 :                 repalloc((void *) Conf->CompoundAffixFlags,
    1093           0 :                          Conf->mCompoundAffixFlag * sizeof(CompoundAffixFlag));
    1094             :         }
    1095             :         else
    1096             :         {
    1097          90 :             Conf->mCompoundAffixFlag = 10;
    1098          90 :             Conf->CompoundAffixFlags = (CompoundAffixFlag *)
    1099          90 :                 tmpalloc(Conf->mCompoundAffixFlag * sizeof(CompoundAffixFlag));
    1100             :         }
    1101             :     }
    1102             : 
    1103         238 :     newValue = Conf->CompoundAffixFlags + Conf->nCompoundAffixFlag;
    1104             : 
    1105         238 :     setCompoundAffixFlagValue(Conf, newValue, sbuf, val);
    1106             : 
    1107         238 :     Conf->usecompound = true;
    1108         238 :     Conf->nCompoundAffixFlag++;
    1109         238 : }
    1110             : 
    1111             : /*
    1112             :  * Returns a set of affix parameters which correspondence to the set of affix
    1113             :  * flags s.
    1114             :  */
    1115             : static int
    1116         876 : getCompoundAffixFlagValue(IspellDict *Conf, char *s)
    1117             : {
    1118         876 :     uint32      flag = 0;
    1119             :     CompoundAffixFlag *found,
    1120             :                 key;
    1121             :     char        sflag[BUFSIZ];
    1122             :     char       *flagcur;
    1123             : 
    1124         876 :     if (Conf->nCompoundAffixFlag == 0)
    1125           0 :         return 0;
    1126             : 
    1127         876 :     flagcur = s;
    1128        3548 :     while (*flagcur)
    1129             :     {
    1130        1800 :         getNextFlagFromString(Conf, &flagcur, sflag);
    1131        1796 :         setCompoundAffixFlagValue(Conf, &key, sflag, 0);
    1132             : 
    1133        1796 :         found = (CompoundAffixFlag *)
    1134        1796 :             bsearch(&key, (void *) Conf->CompoundAffixFlags,
    1135        1796 :                     Conf->nCompoundAffixFlag, sizeof(CompoundAffixFlag),
    1136             :                     cmpcmdflag);
    1137        1796 :         if (found != NULL)
    1138         400 :             flag |= found->value;
    1139             :     }
    1140             : 
    1141         872 :     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 *
    1152         104 : getAffixFlagSet(IspellDict *Conf, char *s)
    1153             : {
    1154         104 :     if (Conf->useFlagAliases && *s != '\0')
    1155             :     {
    1156             :         int         curaffix;
    1157             :         char       *end;
    1158             : 
    1159          66 :         curaffix = strtol(s, &end, 10);
    1160          66 :         if (s == end || errno == ERANGE)
    1161           0 :             ereport(ERROR,
    1162             :                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
    1163             :                      errmsg("invalid affix alias \"%s\"", s)));
    1164             : 
    1165          66 :         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          66 :             return Conf->AffixData[curaffix];
    1172           0 :         else if (curaffix > Conf->nAffixData)
    1173           0 :             ereport(ERROR,
    1174             :                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
    1175             :                      errmsg("invalid affix alias \"%s\"", s)));
    1176           0 :         return VoidString;
    1177             :     }
    1178             :     else
    1179          38 :         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
    1189          60 : NIImportOOAffixes(IspellDict *Conf, const char *filename)
    1190             : {
    1191             :     char        type[BUFSIZ],
    1192          60 :                *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          60 :     bool        isSuffix = false;
    1201          60 :     int         naffix = 0,
    1202          60 :                 curaffix = 0;
    1203          60 :     int         sflaglen = 0;
    1204          60 :     char        flagflags = 0;
    1205             :     tsearch_readline_state trst;
    1206             :     char       *recoded;
    1207             : 
    1208             :     /* read file to find any flag */
    1209          60 :     Conf->usecompound = false;
    1210          60 :     Conf->useFlagAliases = false;
    1211          60 :     Conf->flagMode = FM_CHAR;
    1212             : 
    1213          60 :     if (!tsearch_readline_begin(&trst, filename))
    1214           0 :         ereport(ERROR,
    1215             :                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
    1216             :                  errmsg("could not open affix file \"%s\": %m",
    1217             :                         filename)));
    1218             : 
    1219        2400 :     while ((recoded = tsearch_readline(&trst)) != NULL)
    1220             :     {
    1221        2280 :         if (*recoded == '\0' || t_isspace(recoded) || t_iseq(recoded, '#'))
    1222             :         {
    1223         694 :             pfree(recoded);
    1224         694 :             continue;
    1225             :         }
    1226             : 
    1227        1586 :         if (STRNCMP(recoded, "COMPOUNDFLAG") == 0)
    1228          60 :             addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDFLAG"),
    1229             :                                       FF_COMPOUNDFLAG);
    1230        1526 :         else if (STRNCMP(recoded, "COMPOUNDBEGIN") == 0)
    1231          22 :             addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDBEGIN"),
    1232             :                                       FF_COMPOUNDBEGIN);
    1233        1504 :         else if (STRNCMP(recoded, "COMPOUNDLAST") == 0)
    1234           0 :             addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDLAST"),
    1235             :                                       FF_COMPOUNDLAST);
    1236             :         /* COMPOUNDLAST and COMPOUNDEND are synonyms */
    1237        1504 :         else if (STRNCMP(recoded, "COMPOUNDEND") == 0)
    1238          22 :             addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDEND"),
    1239             :                                       FF_COMPOUNDLAST);
    1240        1482 :         else if (STRNCMP(recoded, "COMPOUNDMIDDLE") == 0)
    1241          22 :             addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDMIDDLE"),
    1242             :                                       FF_COMPOUNDMIDDLE);
    1243        1460 :         else if (STRNCMP(recoded, "ONLYINCOMPOUND") == 0)
    1244          60 :             addCompoundAffixFlagValue(Conf, recoded + strlen("ONLYINCOMPOUND"),
    1245             :                                       FF_COMPOUNDONLY);
    1246        1400 :         else if (STRNCMP(recoded, "COMPOUNDPERMITFLAG") == 0)
    1247          22 :             addCompoundAffixFlagValue(Conf,
    1248             :                                       recoded + strlen("COMPOUNDPERMITFLAG"),
    1249             :                                       FF_COMPOUNDPERMITFLAG);
    1250        1378 :         else if (STRNCMP(recoded, "COMPOUNDFORBIDFLAG") == 0)
    1251           0 :             addCompoundAffixFlagValue(Conf,
    1252             :                                       recoded + strlen("COMPOUNDFORBIDFLAG"),
    1253             :                                       FF_COMPOUNDFORBIDFLAG);
    1254        1378 :         else if (STRNCMP(recoded, "FLAG") == 0)
    1255             :         {
    1256          46 :             char       *s = recoded + strlen("FLAG");
    1257             : 
    1258         138 :             while (*s && t_isspace(s))
    1259          46 :                 s += pg_mblen(s);
    1260             : 
    1261          46 :             if (*s)
    1262             :             {
    1263          46 :                 if (STRNCMP(s, "long") == 0)
    1264          22 :                     Conf->flagMode = FM_LONG;
    1265          24 :                 else if (STRNCMP(s, "num") == 0)
    1266          24 :                     Conf->flagMode = FM_NUM;
    1267           0 :                 else if (STRNCMP(s, "default") != 0)
    1268           0 :                     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        1586 :         pfree(recoded);
    1277             :     }
    1278          60 :     tsearch_readline_end(&trst);
    1279             : 
    1280          60 :     if (Conf->nCompoundAffixFlag > 1)
    1281          60 :         qsort((void *) Conf->CompoundAffixFlags, Conf->nCompoundAffixFlag,
    1282             :               sizeof(CompoundAffixFlag), cmpcmdflag);
    1283             : 
    1284          60 :     if (!tsearch_readline_begin(&trst, filename))
    1285           0 :         ereport(ERROR,
    1286             :                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
    1287             :                  errmsg("could not open affix file \"%s\": %m",
    1288             :                         filename)));
    1289             : 
    1290        2400 :     while ((recoded = tsearch_readline(&trst)) != NULL)
    1291             :     {
    1292             :         int         fields_read;
    1293             : 
    1294        2280 :         if (*recoded == '\0' || t_isspace(recoded) || t_iseq(recoded, '#'))
    1295             :             goto nextline;
    1296             : 
    1297        1586 :         fields_read = parse_ooaffentry(recoded, type, sflag, find, repl, mask);
    1298             : 
    1299        1586 :         if (ptype)
    1300        1526 :             pfree(ptype);
    1301        1586 :         ptype = lowerstr_ctx(Conf, type);
    1302             : 
    1303             :         /* First try to parse AF parameter (alias compression) */
    1304        1586 :         if (STRNCMP(ptype, "af") == 0)
    1305             :         {
    1306             :             /* First line is the number of aliases */
    1307         264 :             if (!Conf->useFlagAliases)
    1308             :             {
    1309          22 :                 Conf->useFlagAliases = true;
    1310          22 :                 naffix = atoi(sflag);
    1311          22 :                 if (naffix <= 0)
    1312           0 :                     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          22 :                 naffix++;
    1318             : 
    1319          22 :                 Conf->AffixData = (char **) palloc0(naffix * sizeof(char *));
    1320          22 :                 Conf->lenAffixData = Conf->nAffixData = naffix;
    1321             : 
    1322             :                 /* Add empty flag set into AffixData */
    1323          22 :                 Conf->AffixData[curaffix] = VoidString;
    1324          22 :                 curaffix++;
    1325             :             }
    1326             :             /* Other lines are aliases */
    1327             :             else
    1328             :             {
    1329         242 :                 if (curaffix < naffix)
    1330             :                 {
    1331         242 :                     Conf->AffixData[curaffix] = cpstrdup(Conf, sflag);
    1332         242 :                     curaffix++;
    1333             :                 }
    1334             :                 else
    1335           0 :                     ereport(ERROR,
    1336             :                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
    1337             :                              errmsg("number of aliases exceeds specified number %d",
    1338             :                                     naffix - 1)));
    1339             :             }
    1340         264 :             goto nextline;
    1341             :         }
    1342             :         /* Else try to parse prefixes and suffixes */
    1343        2390 :         if (fields_read < 4 ||
    1344        1308 :             (STRNCMP(ptype, "sfx") != 0 && STRNCMP(ptype, "pfx") != 0))
    1345             :             goto nextline;
    1346             : 
    1347        1068 :         sflaglen = strlen(sflag);
    1348        1068 :         if (sflaglen == 0
    1349        1068 :             || (sflaglen > 1 && Conf->flagMode == FM_CHAR)
    1350        1068 :             || (sflaglen > 2 && Conf->flagMode == FM_LONG))
    1351             :             goto nextline;
    1352             : 
    1353             :         /*--------
    1354             :          * Affix header. For example:
    1355             :          * SFX \ N 1
    1356             :          *--------
    1357             :          */
    1358        1068 :         if (fields_read == 4)
    1359             :         {
    1360         534 :             isSuffix = (STRNCMP(ptype, "sfx") == 0);
    1361         534 :             if (t_iseq(find, 'y') || t_iseq(find, 'Y'))
    1362         370 :                 flagflags = FF_CROSSPRODUCT;
    1363             :             else
    1364         164 :                 flagflags = 0;
    1365             :         }
    1366             :         /*--------
    1367             :          * Affix fields. For example:
    1368             :          * SFX \   0    Y/L [^Y]
    1369             :          *--------
    1370             :          */
    1371             :         else
    1372             :         {
    1373             :             char       *ptr;
    1374         534 :             int         aflg = 0;
    1375             : 
    1376             :             /* Get flags after '/' (flags are case sensitive) */
    1377         534 :             if ((ptr = strchr(repl, '/')) != NULL)
    1378         104 :                 aflg |= getCompoundAffixFlagValue(Conf,
    1379             :                                                   getAffixFlagSet(Conf,
    1380             :                                                                   ptr + 1));
    1381             :             /* Get lowercased version of string before '/' */
    1382         534 :             prepl = lowerstr_ctx(Conf, repl);
    1383         534 :             if ((ptr = strchr(prepl, '/')) != NULL)
    1384         104 :                 *ptr = '\0';
    1385         534 :             pfind = lowerstr_ctx(Conf, find);
    1386         534 :             pmask = lowerstr_ctx(Conf, mask);
    1387         534 :             if (t_iseq(find, '0'))
    1388         450 :                 *pfind = '\0';
    1389         534 :             if (t_iseq(repl, '0'))
    1390          24 :                 *prepl = '\0';
    1391             : 
    1392         534 :             NIAddAffix(Conf, sflag, flagflags | aflg, pmask, pfind, prepl,
    1393             :                        isSuffix ? FF_SUFFIX : FF_PREFIX);
    1394         534 :             pfree(prepl);
    1395         534 :             pfree(pfind);
    1396         534 :             pfree(pmask);
    1397             :         }
    1398             : 
    1399             : nextline:
    1400        2280 :         pfree(recoded);
    1401             :     }
    1402             : 
    1403          60 :     tsearch_readline_end(&trst);
    1404          60 :     if (ptype)
    1405          60 :         pfree(ptype);
    1406          60 : }
    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
    1418          90 : NIImportAffixes(IspellDict *Conf, const char *filename)
    1419             : {
    1420          90 :     char       *pstr = NULL;
    1421             :     char        flag[BUFSIZ];
    1422             :     char        mask[BUFSIZ];
    1423             :     char        find[BUFSIZ];
    1424             :     char        repl[BUFSIZ];
    1425             :     char       *s;
    1426          90 :     bool        suffixes = false;
    1427          90 :     bool        prefixes = false;
    1428          90 :     char        flagflags = 0;
    1429             :     tsearch_readline_state trst;
    1430          90 :     bool        oldformat = false;
    1431          90 :     char       *recoded = NULL;
    1432             : 
    1433          90 :     if (!tsearch_readline_begin(&trst, filename))
    1434           0 :         ereport(ERROR,
    1435             :                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
    1436             :                  errmsg("could not open affix file \"%s\": %m",
    1437             :                         filename)));
    1438             : 
    1439          90 :     Conf->usecompound = false;
    1440          90 :     Conf->useFlagAliases = false;
    1441          90 :     Conf->flagMode = FM_CHAR;
    1442             : 
    1443         960 :     while ((recoded = tsearch_readline(&trst)) != NULL)
    1444             :     {
    1445         840 :         pstr = lowerstr(recoded);
    1446             : 
    1447             :         /* Skip comments and empty lines */
    1448         840 :         if (*pstr == '#' || *pstr == '\n')
    1449             :             goto nextline;
    1450             : 
    1451         570 :         if (STRNCMP(pstr, "compoundwords") == 0)
    1452             :         {
    1453             :             /* Find case-insensitive L flag in non-lowercased string */
    1454          30 :             s = findchar2(recoded, 'l', 'L');
    1455          30 :             if (s)
    1456             :             {
    1457         180 :                 while (*s && !t_isspace(s))
    1458         120 :                     s += pg_mblen(s);
    1459          90 :                 while (*s && t_isspace(s))
    1460          30 :                     s += pg_mblen(s);
    1461             : 
    1462          30 :                 if (*s && pg_mblen(s) == 1)
    1463             :                 {
    1464          30 :                     addCompoundAffixFlagValue(Conf, s, FF_COMPOUNDFLAG);
    1465          30 :                     Conf->usecompound = true;
    1466             :                 }
    1467          30 :                 oldformat = true;
    1468          30 :                 goto nextline;
    1469             :             }
    1470             :         }
    1471         540 :         if (STRNCMP(pstr, "suffixes") == 0)
    1472             :         {
    1473          30 :             suffixes = true;
    1474          30 :             prefixes = false;
    1475          30 :             oldformat = true;
    1476          30 :             goto nextline;
    1477             :         }
    1478         510 :         if (STRNCMP(pstr, "prefixes") == 0)
    1479             :         {
    1480          30 :             suffixes = false;
    1481          30 :             prefixes = true;
    1482          30 :             oldformat = true;
    1483          30 :             goto nextline;
    1484             :         }
    1485         480 :         if (STRNCMP(pstr, "flag") == 0)
    1486             :         {
    1487         256 :             s = recoded + 4;    /* we need non-lowercased string */
    1488         256 :             flagflags = 0;
    1489             : 
    1490         768 :             while (*s && t_isspace(s))
    1491         256 :                 s += pg_mblen(s);
    1492             : 
    1493         256 :             if (*s == '*')
    1494             :             {
    1495         150 :                 flagflags |= FF_CROSSPRODUCT;
    1496         150 :                 s++;
    1497             :             }
    1498         106 :             else if (*s == '~')
    1499             :             {
    1500          30 :                 flagflags |= FF_COMPOUNDONLY;
    1501          30 :                 s++;
    1502             :             }
    1503             : 
    1504         256 :             if (*s == '\\')
    1505          30 :                 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         256 :             if (*s && pg_mblen(s) == 1)
    1513             :             {
    1514         256 :                 COPYCHAR(flag, s);
    1515         256 :                 flag[1] = '\0';
    1516             : 
    1517         256 :                 s++;
    1518         302 :                 if (*s == '\0' || *s == '#' || *s == '\n' || *s == ':' ||
    1519          46 :                     t_isspace(s))
    1520             :                 {
    1521         210 :                     oldformat = true;
    1522         210 :                     goto nextline;
    1523             :                 }
    1524             :             }
    1525          46 :             goto isnewformat;
    1526             :         }
    1527         434 :         if (STRNCMP(recoded, "COMPOUNDFLAG") == 0 ||
    1528         420 :             STRNCMP(recoded, "COMPOUNDMIN") == 0 ||
    1529         420 :             STRNCMP(recoded, "PFX") == 0 ||
    1530         210 :             STRNCMP(recoded, "SFX") == 0)
    1531             :             goto isnewformat;
    1532             : 
    1533         210 :         if ((!suffixes) && (!prefixes))
    1534           0 :             goto nextline;
    1535             : 
    1536         210 :         if (!parse_affentry(pstr, mask, find, repl))
    1537           0 :             goto nextline;
    1538             : 
    1539         210 :         NIAddAffix(Conf, flag, flagflags, mask, find, repl, suffixes ? FF_SUFFIX : FF_PREFIX);
    1540             : 
    1541             : nextline:
    1542         780 :         pfree(recoded);
    1543         780 :         pfree(pstr);
    1544             :     }
    1545          30 :     tsearch_readline_end(&trst);
    1546          30 :     return;
    1547             : 
    1548             : isnewformat:
    1549          60 :     if (oldformat)
    1550           0 :         ereport(ERROR,
    1551             :                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
    1552             :                  errmsg("affix file contains both old-style and new-style commands")));
    1553          60 :     tsearch_readline_end(&trst);
    1554             : 
    1555          60 :     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          46 : 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          46 :     if (*Conf->AffixData[a1] == '\0')
    1573           0 :         return a2;
    1574          46 :     else if (*Conf->AffixData[a2] == '\0')
    1575           0 :         return a1;
    1576             : 
    1577         138 :     while (Conf->nAffixData + 1 >= Conf->lenAffixData)
    1578             :     {
    1579          46 :         Conf->lenAffixData *= 2;
    1580          46 :         Conf->AffixData = (char **) repalloc(Conf->AffixData,
    1581          46 :                                              sizeof(char *) * Conf->lenAffixData);
    1582             :     }
    1583             : 
    1584          46 :     ptr = Conf->AffixData + Conf->nAffixData;
    1585          46 :     if (Conf->flagMode == FM_NUM)
    1586             :     {
    1587          20 :         *ptr = cpalloc(strlen(Conf->AffixData[a1]) +
    1588             :                        strlen(Conf->AffixData[a2]) +
    1589             :                        1 /* comma */ + 1 /* \0 */ );
    1590          20 :         sprintf(*ptr, "%s,%s", Conf->AffixData[a1], Conf->AffixData[a2]);
    1591             :     }
    1592             :     else
    1593             :     {
    1594          26 :         *ptr = cpalloc(strlen(Conf->AffixData[a1]) +
    1595             :                        strlen(Conf->AffixData[a2]) +
    1596             :                        1 /* \0 */ );
    1597          26 :         sprintf(*ptr, "%s%s", Conf->AffixData[a1], Conf->AffixData[a2]);
    1598             :     }
    1599          46 :     ptr++;
    1600          46 :     *ptr = NULL;
    1601          46 :     Conf->nAffixData++;
    1602             : 
    1603          46 :     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
    1611         772 : makeCompoundFlags(IspellDict *Conf, int affix)
    1612             : {
    1613             :     Assert(affix < Conf->nAffixData);
    1614             : 
    1615         772 :     return (getCompoundAffixFlagValue(Conf, Conf->AffixData[affix]) &
    1616             :             FF_COMPOUNDFLAGMASK);
    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        3084 : mkSPNode(IspellDict *Conf, int low, int high, int level)
    1629             : {
    1630             :     int         i;
    1631        3084 :     int         nchar = 0;
    1632        3084 :     char        lastchar = '\0';
    1633             :     SPNode     *rs;
    1634             :     SPNodeData *data;
    1635        3084 :     int         lownew = low;
    1636             : 
    1637       10134 :     for (i = low; i < high; i++)
    1638        7050 :         if (Conf->Spell[i]->p.d.len > level && lastchar != Conf->Spell[i]->word[level])
    1639             :         {
    1640        3022 :             nchar++;
    1641        3022 :             lastchar = Conf->Spell[i]->word[level];
    1642             :         }
    1643             : 
    1644        3084 :     if (!nchar)
    1645         442 :         return NULL;
    1646             : 
    1647        2642 :     rs = (SPNode *) cpalloc0(SPNHDRSZ + nchar * sizeof(SPNodeData));
    1648        2642 :     rs->length = nchar;
    1649        2642 :     data = rs->data;
    1650             : 
    1651        2642 :     lastchar = '\0';
    1652        8938 :     for (i = low; i < high; i++)
    1653        6308 :         if (Conf->Spell[i]->p.d.len > level)
    1654             :         {
    1655        4532 :             if (lastchar != Conf->Spell[i]->word[level])
    1656             :             {
    1657        3014 :                 if (lastchar)
    1658             :                 {
    1659             :                     /* Next level of the prefix tree */
    1660         372 :                     data->node = mkSPNode(Conf, lownew, i, level + 1);
    1661         364 :                     lownew = i;
    1662         364 :                     data++;
    1663             :                 }
    1664        3006 :                 lastchar = Conf->Spell[i]->word[level];
    1665             :             }
    1666        4524 :             data->val = ((uint8 *) (Conf->Spell[i]->word))[level];
    1667        4524 :             if (Conf->Spell[i]->p.d.len == level + 1)
    1668             :             {
    1669         726 :                 bool        clearCompoundOnly = false;
    1670             : 
    1671         726 :                 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          92 :                     clearCompoundOnly = (FF_COMPOUNDONLY & data->compoundflag
    1680          46 :                                          & makeCompoundFlags(Conf, Conf->Spell[i]->p.d.affix))
    1681             :                         ? false : true;
    1682          46 :                     data->affix = MergeAffix(Conf, data->affix, Conf->Spell[i]->p.d.affix);
    1683             :                 }
    1684             :                 else
    1685         680 :                     data->affix = Conf->Spell[i]->p.d.affix;
    1686         726 :                 data->isword = 1;
    1687             : 
    1688         726 :                 data->compoundflag = makeCompoundFlags(Conf, data->affix);
    1689             : 
    1690         722 :                 if ((data->compoundflag & FF_COMPOUNDONLY) &&
    1691           0 :                     (data->compoundflag & FF_COMPOUNDFLAG) == 0)
    1692           0 :                     data->compoundflag |= FF_COMPOUNDFLAG;
    1693             : 
    1694         722 :                 if (clearCompoundOnly)
    1695          46 :                     data->compoundflag &= ~FF_COMPOUNDONLY;
    1696             :             }
    1697             :         }
    1698             : 
    1699             :     /* Next level of the prefix tree */
    1700        2630 :     data->node = mkSPNode(Conf, lownew, high, level + 1);
    1701             : 
    1702        2626 :     return rs;
    1703             : }
    1704             : 
    1705             : /*
    1706             :  * Builds the Conf->Dictionary tree and AffixData from the imported dictionary
    1707             :  * and affixes.
    1708             :  */
    1709             : void
    1710          90 : NISortDictionary(IspellDict *Conf)
    1711             : {
    1712             :     int         i;
    1713          90 :     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          90 :     if (Conf->useFlagAliases)
    1723             :     {
    1724         176 :         for (i = 0; i < Conf->nspell; i++)
    1725             :         {
    1726             :             char       *end;
    1727             : 
    1728         162 :             if (*Conf->Spell[i]->p.flag != '\0')
    1729             :             {
    1730         148 :                 curaffix = strtol(Conf->Spell[i]->p.flag, &end, 10);
    1731         148 :                 if (Conf->Spell[i]->p.flag == end || errno == ERANGE)
    1732           4 :                     ereport(ERROR,
    1733             :                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
    1734             :                              errmsg("invalid affix alias \"%s\"",
    1735             :                                     Conf->Spell[i]->p.flag)));
    1736         144 :                 if (curaffix < 0 || curaffix >= Conf->nAffixData)
    1737           4 :                     ereport(ERROR,
    1738             :                             (errcode(ERRCODE_CONFIG_FILE_ERROR),
    1739             :                              errmsg("invalid affix alias \"%s\"",
    1740             :                                     Conf->Spell[i]->p.flag)));
    1741         140 :                 if (*end != '\0' && !t_isdigit(end) && !t_isspace(end))
    1742           0 :                     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          14 :                 curaffix = 0;
    1754             :             }
    1755             : 
    1756         154 :             Conf->Spell[i]->p.d.affix = curaffix;
    1757         154 :             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          68 :         qsort((void *) Conf->Spell, Conf->nspell, sizeof(SPELL *),
    1765             :               cmpspellaffix);
    1766             : 
    1767          68 :         naffix = 0;
    1768         668 :         for (i = 0; i < Conf->nspell; i++)
    1769             :         {
    1770        1132 :             if (i == 0 ||
    1771         532 :                 strcmp(Conf->Spell[i]->p.flag, Conf->Spell[i - 1]->p.flag) != 0)
    1772         532 :                 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          68 :         Conf->AffixData = (char **) palloc0(naffix * sizeof(char *));
    1781             : 
    1782          68 :         curaffix = -1;
    1783         668 :         for (i = 0; i < Conf->nspell; i++)
    1784             :         {
    1785        1132 :             if (i == 0 ||
    1786         532 :                 strcmp(Conf->Spell[i]->p.flag, Conf->AffixData[curaffix]) != 0)
    1787             :             {
    1788         532 :                 curaffix++;
    1789             :                 Assert(curaffix < naffix);
    1790        1064 :                 Conf->AffixData[curaffix] = cpstrdup(Conf,
    1791         532 :                                                      Conf->Spell[i]->p.flag);
    1792             :             }
    1793             : 
    1794         600 :             Conf->Spell[i]->p.d.affix = curaffix;
    1795         600 :             Conf->Spell[i]->p.d.len = strlen(Conf->Spell[i]->word);
    1796             :         }
    1797             : 
    1798          68 :         Conf->lenAffixData = Conf->nAffixData = naffix;
    1799             :     }
    1800             : 
    1801             :     /* Start build a prefix tree */
    1802          82 :     qsort((void *) Conf->Spell, Conf->nspell, sizeof(SPELL *), cmpspell);
    1803          82 :     Conf->Dictionary = mkSPNode(Conf, 0, Conf->nspell, 0);
    1804          78 : }
    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        1316 : mkANode(IspellDict *Conf, int low, int high, int level, int type)
    1819             : {
    1820             :     int         i;
    1821        1316 :     int         nchar = 0;
    1822        1316 :     uint8       lastchar = '\0';
    1823             :     AffixNode  *rs;
    1824             :     AffixNodeData *data;
    1825        1316 :     int         lownew = low;
    1826             :     int         naff;
    1827             :     AFFIX     **aff;
    1828             : 
    1829        3540 :     for (i = low; i < high; i++)
    1830        2224 :         if (Conf->Affix[i].replen > level && lastchar != GETCHAR(Conf->Affix + i, level, type))
    1831             :         {
    1832        1160 :             nchar++;
    1833        1160 :             lastchar = GETCHAR(Conf->Affix + i, level, type);
    1834             :         }
    1835             : 
    1836        1316 :     if (!nchar)
    1837         502 :         return NULL;
    1838             : 
    1839         814 :     aff = (AFFIX **) tmpalloc(sizeof(AFFIX *) * (high - low + 1));
    1840         814 :     naff = 0;
    1841             : 
    1842         814 :     rs = (AffixNode *) cpalloc0(ANHRDSZ + nchar * sizeof(AffixNodeData));
    1843         814 :     rs->length = nchar;
    1844         814 :     data = rs->data;
    1845             : 
    1846         814 :     lastchar = '\0';
    1847        2410 :     for (i = low; i < high; i++)
    1848        1596 :         if (Conf->Affix[i].replen > level)
    1849             :         {
    1850        1344 :             if (lastchar != GETCHAR(Conf->Affix + i, level, type))
    1851             :             {
    1852        1160 :                 if (lastchar)
    1853             :                 {
    1854             :                     /* Next level of the prefix tree */
    1855         346 :                     data->node = mkANode(Conf, lownew, i, level + 1, type);
    1856         346 :                     if (naff)
    1857             :                     {
    1858          78 :                         data->naff = naff;
    1859          78 :                         data->aff = (AFFIX **) cpalloc(sizeof(AFFIX *) * naff);
    1860          78 :                         memcpy(data->aff, aff, sizeof(AFFIX *) * naff);
    1861          78 :                         naff = 0;
    1862             :                     }
    1863         346 :                     data++;
    1864         346 :                     lownew = i;
    1865             :                 }
    1866        1160 :                 lastchar = GETCHAR(Conf->Affix + i, level, type);
    1867             :             }
    1868        1344 :             data->val = GETCHAR(Conf->Affix + i, level, type);
    1869        1344 :             if (Conf->Affix[i].replen == level + 1)
    1870             :             {                   /* affix stopped */
    1871         608 :                 aff[naff++] = Conf->Affix + i;
    1872             :             }
    1873             :         }
    1874             : 
    1875             :     /* Next level of the prefix tree */
    1876         814 :     data->node = mkANode(Conf, lownew, high, level + 1, type);
    1877         814 :     if (naff)
    1878             :     {
    1879         502 :         data->naff = naff;
    1880         502 :         data->aff = (AFFIX **) cpalloc(sizeof(AFFIX *) * naff);
    1881         502 :         memcpy(data->aff, aff, sizeof(AFFIX *) * naff);
    1882         502 :         naff = 0;
    1883             :     }
    1884             : 
    1885         814 :     pfree(aff);
    1886             : 
    1887         814 :     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         156 : mkVoidAffix(IspellDict *Conf, bool issuffix, int startsuffix)
    1896             : {
    1897             :     int         i,
    1898         156 :                 cnt = 0;
    1899         156 :     int         start = (issuffix) ? startsuffix : 0;
    1900         156 :     int         end = (issuffix) ? Conf->naffixes : startsuffix;
    1901         156 :     AffixNode  *Affix = (AffixNode *) palloc0(ANHRDSZ + sizeof(AffixNodeData));
    1902             : 
    1903         156 :     Affix->length = 1;
    1904         156 :     Affix->isvoid = 1;
    1905             : 
    1906         156 :     if (issuffix)
    1907             :     {
    1908          78 :         Affix->data->node = Conf->Suffix;
    1909          78 :         Conf->Suffix = Affix;
    1910             :     }
    1911             :     else
    1912             :     {
    1913          78 :         Affix->data->node = Conf->Prefix;
    1914          78 :         Conf->Prefix = Affix;
    1915             :     }
    1916             : 
    1917             :     /* Count affixes with empty replace string */
    1918         784 :     for (i = start; i < end; i++)
    1919         628 :         if (Conf->Affix[i].replen == 0)
    1920          20 :             cnt++;
    1921             : 
    1922             :     /* There is not affixes with empty replace string */
    1923         156 :     if (cnt == 0)
    1924         136 :         return;
    1925             : 
    1926          20 :     Affix->data->aff = (AFFIX **) cpalloc(sizeof(AFFIX *) * cnt);
    1927          20 :     Affix->data->naff = (uint32) cnt;
    1928             : 
    1929          20 :     cnt = 0;
    1930         160 :     for (i = start; i < end; i++)
    1931         140 :         if (Conf->Affix[i].replen == 0)
    1932             :         {
    1933          20 :             Affix->data->aff[cnt] = Conf->Affix + i;
    1934          20 :             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         106 : isAffixInUse(IspellDict *Conf, char *affixflag)
    1950             : {
    1951             :     int         i;
    1952             : 
    1953         784 :     for (i = 0; i < Conf->nAffixData; i++)
    1954         766 :         if (IsAffixFlagInUse(Conf, i, affixflag))
    1955          88 :             return true;
    1956             : 
    1957          18 :     return false;
    1958             : }
    1959             : 
    1960             : /*
    1961             :  * Builds Conf->Prefix and Conf->Suffix trees from the imported affixes.
    1962             :  */
    1963             : void
    1964          78 : NISortAffixes(IspellDict *Conf)
    1965             : {
    1966             :     AFFIX      *Affix;
    1967             :     size_t      i;
    1968             :     CMPDAffix  *ptr;
    1969          78 :     int         firstsuffix = Conf->naffixes;
    1970             : 
    1971          78 :     if (Conf->naffixes == 0)
    1972           0 :         return;
    1973             : 
    1974             :     /* Store compound affixes in the Conf->CompoundAffix array */
    1975          78 :     if (Conf->naffixes > 1)
    1976          78 :         qsort((void *) Conf->Affix, Conf->naffixes, sizeof(AFFIX), cmpaffix);
    1977          78 :     Conf->CompoundAffix = ptr = (CMPDAffix *) palloc(sizeof(CMPDAffix) * Conf->naffixes);
    1978          78 :     ptr->affix = NULL;
    1979             : 
    1980         706 :     for (i = 0; i < Conf->naffixes; i++)
    1981             :     {
    1982         628 :         Affix = &(((AFFIX *) Conf->Affix)[i]);
    1983         628 :         if (Affix->type == FF_SUFFIX && i < firstsuffix)
    1984          78 :             firstsuffix = i;
    1985             : 
    1986         734 :         if ((Affix->flagflags & FF_COMPOUNDFLAG) && Affix->replen > 0 &&
    1987         106 :             isAffixInUse(Conf, Affix->flag))
    1988             :         {
    1989          88 :             bool        issuffix = (Affix->type == FF_SUFFIX);
    1990             : 
    1991         116 :             if (ptr == Conf->CompoundAffix ||
    1992          56 :                 issuffix != (ptr - 1)->issuffix ||
    1993          56 :                 strbncmp((const unsigned char *) (ptr - 1)->affix,
    1994          28 :                          (const unsigned char *) Affix->repl,
    1995          28 :                          (ptr - 1)->len))
    1996             :             {
    1997             :                 /* leave only unique and minimals suffixes */
    1998          74 :                 ptr->affix = Affix->repl;
    1999          74 :                 ptr->len = Affix->replen;
    2000          74 :                 ptr->issuffix = issuffix;
    2001          74 :                 ptr++;
    2002             :             }
    2003             :         }
    2004             :     }
    2005          78 :     ptr->affix = NULL;
    2006          78 :     Conf->CompoundAffix = (CMPDAffix *) repalloc(Conf->CompoundAffix, sizeof(CMPDAffix) * (ptr - Conf->CompoundAffix + 1));
    2007             : 
    2008             :     /* Start build a prefix tree */
    2009          78 :     Conf->Prefix = mkANode(Conf, 0, firstsuffix, 0, FF_PREFIX);
    2010          78 :     Conf->Suffix = mkANode(Conf, firstsuffix, Conf->naffixes, 0, FF_SUFFIX);
    2011          78 :     mkVoidAffix(Conf, true, firstsuffix);
    2012          78 :     mkVoidAffix(Conf, false, firstsuffix);
    2013             : }
    2014             : 
    2015             : static AffixNodeData *
    2016        3080 : 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        3080 :     if (node->isvoid)
    2024             :     {                           /* search void affixes */
    2025        2680 :         if (node->data->naff)
    2026         228 :             return node->data;
    2027        2452 :         node = node->data->node;
    2028             :     }
    2029             : 
    2030        6440 :     while (node && *level < wrdlen)
    2031             :     {
    2032        3572 :         StopLow = node->data;
    2033        3572 :         StopHigh = node->data + node->length;
    2034       11456 :         while (StopLow < StopHigh)
    2035             :         {
    2036        5916 :             StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
    2037        5916 :             symbol = GETWCHAR(word, wrdlen, *level, type);
    2038             : 
    2039        5916 :             if (StopMiddle->val == symbol)
    2040             :             {
    2041        1604 :                 (*level)++;
    2042        1604 :                 if (StopMiddle->naff)
    2043         868 :                     return StopMiddle;
    2044         736 :                 node = StopMiddle->node;
    2045         736 :                 break;
    2046             :             }
    2047        4312 :             else if (StopMiddle->val < symbol)
    2048        1072 :                 StopLow = StopMiddle + 1;
    2049             :             else
    2050        3240 :                 StopHigh = StopMiddle;
    2051             :         }
    2052        2704 :         if (StopLow >= StopHigh)
    2053        1968 :             break;
    2054             :     }
    2055        1984 :     return NULL;
    2056             : }
    2057             : 
    2058             : static char *
    2059        1224 : 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        1224 :     if (flagflags == 0)
    2066             :     {
    2067         844 :         if (Affix->flagflags & FF_COMPOUNDONLY)
    2068          88 :             return NULL;
    2069             :     }
    2070         380 :     else if (flagflags & FF_COMPOUNDBEGIN)
    2071             :     {
    2072           0 :         if (Affix->flagflags & FF_COMPOUNDFORBIDFLAG)
    2073           0 :             return NULL;
    2074           0 :         if ((Affix->flagflags & FF_COMPOUNDBEGIN) == 0)
    2075           0 :             if (Affix->type == FF_SUFFIX)
    2076           0 :                 return NULL;
    2077             :     }
    2078         380 :     else if (flagflags & FF_COMPOUNDMIDDLE)
    2079             :     {
    2080         424 :         if ((Affix->flagflags & FF_COMPOUNDMIDDLE) == 0 ||
    2081         152 :             (Affix->flagflags & FF_COMPOUNDFORBIDFLAG))
    2082         120 :             return NULL;
    2083             :     }
    2084         108 :     else if (flagflags & FF_COMPOUNDLAST)
    2085             :     {
    2086         108 :         if (Affix->flagflags & FF_COMPOUNDFORBIDFLAG)
    2087           0 :             return NULL;
    2088         108 :         if ((Affix->flagflags & FF_COMPOUNDLAST) == 0)
    2089         100 :             if (Affix->type == FF_PREFIX)
    2090           0 :                 return NULL;
    2091             :     }
    2092             : 
    2093             :     /*
    2094             :      * make replace pattern of affix
    2095             :      */
    2096        1016 :     if (Affix->type == FF_SUFFIX)
    2097             :     {
    2098         696 :         strcpy(newword, word);
    2099         696 :         strcpy(newword + len - Affix->replen, Affix->find);
    2100         696 :         if (baselen)            /* store length of non-changed part of word */
    2101         696 :             *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         320 :         if (baselen && *baselen + strlen(Affix->find) <= Affix->replen)
    2110           0 :             return NULL;
    2111         320 :         strcpy(newword, Affix->find);
    2112         320 :         strcat(newword, word + Affix->replen);
    2113             :     }
    2114             : 
    2115             :     /*
    2116             :      * check resulting word
    2117             :      */
    2118        1016 :     if (Affix->issimple)
    2119         320 :         return newword;
    2120         696 :     else if (Affix->isregis)
    2121             :     {
    2122         472 :         if (RS_execute(&(Affix->reg.regis), newword))
    2123         448 :             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         224 :         newword_len = strlen(newword);
    2134         224 :         data = (pg_wchar *) palloc((newword_len + 1) * sizeof(pg_wchar));
    2135         224 :         data_len = pg_mb2wchar_with_len(newword, data, newword_len);
    2136             : 
    2137         224 :         if (!(err = pg_regexec(&(Affix->reg.regex), data, data_len, 0, NULL, 0, NULL, 0)))
    2138             :         {
    2139         224 :             pfree(data);
    2140         224 :             return newword;
    2141             :         }
    2142           0 :         pfree(data);
    2143             :     }
    2144             : 
    2145          24 :     return NULL;
    2146             : }
    2147             : 
    2148             : static int
    2149         360 : addToResult(char **forms, char **cur, char *word)
    2150             : {
    2151         360 :     if (cur - forms >= MAX_NORM - 1)
    2152           0 :         return 0;
    2153         360 :     if (forms == cur || strcmp(word, *(cur - 1)) != 0)
    2154             :     {
    2155         360 :         *cur = pstrdup(word);
    2156         360 :         *(cur + 1) = NULL;
    2157         360 :         return 1;
    2158             :     }
    2159             : 
    2160           0 :     return 0;
    2161             : }
    2162             : 
    2163             : static char **
    2164        1004 : NormalizeSubWord(IspellDict *Conf, char *word, int flag)
    2165             : {
    2166        1004 :     AffixNodeData *suffix = NULL,
    2167        1004 :                *prefix = NULL;
    2168        1004 :     int         slevel = 0,
    2169        1004 :                 plevel = 0;
    2170        1004 :     int         wrdlen = strlen(word),
    2171             :                 swrdlen;
    2172             :     char      **forms;
    2173             :     char      **cur;
    2174        1004 :     char        newword[2 * MAXNORMLEN] = "";
    2175        1004 :     char        pnewword[2 * MAXNORMLEN] = "";
    2176        1004 :     AffixNode  *snode = Conf->Suffix,
    2177             :                *pnode;
    2178             :     int         i,
    2179             :                 j;
    2180             : 
    2181        1004 :     if (wrdlen > MAXNORMLEN)
    2182           0 :         return NULL;
    2183        1004 :     cur = forms = (char **) palloc(MAX_NORM * sizeof(char *));
    2184        1004 :     *cur = NULL;
    2185             : 
    2186             : 
    2187             :     /* Check that the word itself is normal form */
    2188        1004 :     if (FindWord(Conf, word, VoidString, flag))
    2189             :     {
    2190         312 :         *cur = pstrdup(word);
    2191         312 :         cur++;
    2192         312 :         *cur = NULL;
    2193             :     }
    2194             : 
    2195             :     /* Find all other NORMAL forms of the 'word' (check only prefix) */
    2196        1004 :     pnode = Conf->Prefix;
    2197        1004 :     plevel = 0;
    2198        2152 :     while (pnode)
    2199             :     {
    2200        1004 :         prefix = FindAffixes(pnode, word, wrdlen, &plevel, FF_PREFIX);
    2201        1004 :         if (!prefix)
    2202         860 :             break;
    2203         288 :         for (j = 0; j < prefix->naff; j++)
    2204             :         {
    2205         144 :             if (CheckAffix(word, wrdlen, prefix->aff[j], flag, newword, NULL))
    2206             :             {
    2207             :                 /* prefix success */
    2208         128 :                 if (FindWord(Conf, newword, prefix->aff[j]->flag, flag))
    2209          32 :                     cur += addToResult(forms, cur, newword);
    2210             :             }
    2211             :         }
    2212         144 :         pnode = prefix->node;
    2213             :     }
    2214             : 
    2215             :     /*
    2216             :      * Find all other NORMAL forms of the 'word' (check suffix and then
    2217             :      * prefix)
    2218             :      */
    2219        2736 :     while (snode)
    2220             :     {
    2221        1404 :         int         baselen = 0;
    2222             : 
    2223             :         /* find possible suffix */
    2224        1404 :         suffix = FindAffixes(snode, word, wrdlen, &slevel, FF_SUFFIX);
    2225        1404 :         if (!suffix)
    2226         676 :             break;
    2227             :         /* foreach suffix check affix */
    2228        1584 :         for (i = 0; i < suffix->naff; i++)
    2229             :         {
    2230         856 :             if (CheckAffix(word, wrdlen, suffix->aff[i], flag, newword, &baselen))
    2231             :             {
    2232             :                 /* suffix success */
    2233         672 :                 if (FindWord(Conf, newword, suffix->aff[i]->flag, flag))
    2234         184 :                     cur += addToResult(forms, cur, newword);
    2235             : 
    2236             :                 /* now we will look changed word with prefixes */
    2237         672 :                 pnode = Conf->Prefix;
    2238         672 :                 plevel = 0;
    2239         672 :                 swrdlen = strlen(newword);
    2240        1568 :                 while (pnode)
    2241             :                 {
    2242         672 :                     prefix = FindAffixes(pnode, newword, swrdlen, &plevel, FF_PREFIX);
    2243         672 :                     if (!prefix)
    2244         448 :                         break;
    2245         448 :                     for (j = 0; j < prefix->naff; j++)
    2246             :                     {
    2247         224 :                         if (CheckAffix(newword, swrdlen, prefix->aff[j], flag, pnewword, &baselen))
    2248             :                         {
    2249             :                             /* prefix success */
    2250         384 :                             char       *ff = (prefix->aff[j]->flagflags & suffix->aff[i]->flagflags & FF_CROSSPRODUCT) ?
    2251         192 :                             VoidString : prefix->aff[j]->flag;
    2252             : 
    2253         192 :                             if (FindWord(Conf, pnewword, ff, flag))
    2254         144 :                                 cur += addToResult(forms, cur, pnewword);
    2255             :                         }
    2256             :                     }
    2257         224 :                     pnode = prefix->node;
    2258             :                 }
    2259             :             }
    2260             :         }
    2261             : 
    2262         728 :         snode = suffix->node;
    2263             :     }
    2264             : 
    2265        1004 :     if (cur == forms)
    2266             :     {
    2267         444 :         pfree(forms);
    2268         444 :         return NULL;
    2269             :     }
    2270         560 :     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        4040 : CheckCompoundAffixes(CMPDAffix **ptr, char *word, int len, bool CheckInPlace)
    2283             : {
    2284             :     bool        issuffix;
    2285             : 
    2286             :     /* in case CompoundAffix is null: */
    2287        4040 :     if (*ptr == NULL)
    2288           0 :         return -1;
    2289             : 
    2290        4040 :     if (CheckInPlace)
    2291             :     {
    2292       11168 :         while ((*ptr)->affix)
    2293             :         {
    2294        4296 :             if (len > (*ptr)->len && strncmp((*ptr)->affix, word, (*ptr)->len) == 0)
    2295             :             {
    2296          40 :                 len = (*ptr)->len;
    2297          40 :                 issuffix = (*ptr)->issuffix;
    2298          40 :                 (*ptr)++;
    2299          40 :                 return (issuffix) ? len : 0;
    2300             :             }
    2301        4256 :             (*ptr)++;
    2302             :         }
    2303             :     }
    2304             :     else
    2305             :     {
    2306             :         char       *affbegin;
    2307             : 
    2308        1712 :         while ((*ptr)->affix)
    2309             :         {
    2310         628 :             if (len > (*ptr)->len && (affbegin = strstr(word, (*ptr)->affix)) != NULL)
    2311             :             {
    2312          84 :                 len = (*ptr)->len + (affbegin - word);
    2313          84 :                 issuffix = (*ptr)->issuffix;
    2314          84 :                 (*ptr)++;
    2315          84 :                 return (issuffix) ? len : 0;
    2316             :             }
    2317         544 :             (*ptr)++;
    2318             :         }
    2319             :     }
    2320        3916 :     return -1;
    2321             : }
    2322             : 
    2323             : static SplitVar *
    2324         940 : CopyVar(SplitVar *s, int makedup)
    2325             : {
    2326         940 :     SplitVar   *v = (SplitVar *) palloc(sizeof(SplitVar));
    2327             : 
    2328         940 :     v->next = NULL;
    2329         940 :     if (s)
    2330             :     {
    2331             :         int         i;
    2332             : 
    2333         440 :         v->lenstem = s->lenstem;
    2334         440 :         v->stem = (char **) palloc(sizeof(char *) * v->lenstem);
    2335         440 :         v->nstem = s->nstem;
    2336         668 :         for (i = 0; i < s->nstem; i++)
    2337         228 :             v->stem[i] = (makedup) ? pstrdup(s->stem[i]) : s->stem[i];
    2338             :     }
    2339             :     else
    2340             :     {
    2341         500 :         v->lenstem = 16;
    2342         500 :         v->stem = (char **) palloc(sizeof(char *) * v->lenstem);
    2343         500 :         v->nstem = 0;
    2344             :     }
    2345         940 :     return v;
    2346             : }
    2347             : 
    2348             : static void
    2349        1260 : AddStem(SplitVar *v, char *word)
    2350             : {
    2351        1260 :     if (v->nstem >= v->lenstem)
    2352             :     {
    2353           0 :         v->lenstem *= 2;
    2354           0 :         v->stem = (char **) repalloc(v->stem, sizeof(char *) * v->lenstem);
    2355             :     }
    2356             : 
    2357        1260 :     v->stem[v->nstem] = word;
    2358        1260 :     v->nstem++;
    2359        1260 : }
    2360             : 
    2361             : static SplitVar *
    2362         880 : SplitToVariants(IspellDict *Conf, SPNode *snode, SplitVar *orig, char *word, int wordlen, int startpos, int minpos)
    2363             : {
    2364         880 :     SplitVar   *var = NULL;
    2365             :     SPNodeData *StopLow,
    2366             :                *StopHigh,
    2367         880 :                *StopMiddle = NULL;
    2368         880 :     SPNode     *node = (snode) ? snode : Conf->Dictionary;
    2369         880 :     int         level = (snode) ? minpos : startpos;    /* recursive
    2370             :                                                          * minpos==level */
    2371             :     int         lenaff;
    2372             :     CMPDAffix  *caff;
    2373             :     char       *notprobed;
    2374         880 :     int         compoundflag = 0;
    2375             : 
    2376         880 :     notprobed = (char *) palloc(wordlen);
    2377         880 :     memset(notprobed, 1, wordlen);
    2378         880 :     var = CopyVar(orig, 1);
    2379             : 
    2380        5848 :     while (level < wordlen)
    2381             :     {
    2382             :         /* find word with epenthetic or/and compound affix */
    2383        4796 :         caff = Conf->CompoundAffix;
    2384        9716 :         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         124 :             lenaff = level - startpos + lenaff;
    2393             : 
    2394         124 :             if (!notprobed[startpos + lenaff - 1])
    2395           0 :                 continue;
    2396             : 
    2397         124 :             if (level + lenaff - 1 <= minpos)
    2398           0 :                 continue;
    2399             : 
    2400         124 :             if (lenaff >= MAXNORMLEN)
    2401           0 :                 continue;       /* skip too big value */
    2402         124 :             if (lenaff > 0)
    2403         124 :                 memcpy(buf, word + startpos, lenaff);
    2404         124 :             buf[lenaff] = '\0';
    2405             : 
    2406         124 :             if (level == 0)
    2407           0 :                 compoundflag = FF_COMPOUNDBEGIN;
    2408         124 :             else if (level == wordlen - 1)
    2409           0 :                 compoundflag = FF_COMPOUNDLAST;
    2410             :             else
    2411         124 :                 compoundflag = FF_COMPOUNDMIDDLE;
    2412         124 :             subres = NormalizeSubWord(Conf, buf, compoundflag);
    2413         124 :             if (subres)
    2414             :             {
    2415             :                 /* Yes, it was a word from dictionary */
    2416          60 :                 SplitVar   *new = CopyVar(var, 0);
    2417          60 :                 SplitVar   *ptr = var;
    2418          60 :                 char      **sptr = subres;
    2419             : 
    2420          60 :                 notprobed[startpos + lenaff - 1] = 0;
    2421             : 
    2422         180 :                 while (*sptr)
    2423             :                 {
    2424          60 :                     AddStem(new, *sptr);
    2425          60 :                     sptr++;
    2426             :                 }
    2427          60 :                 pfree(subres);
    2428             : 
    2429         120 :                 while (ptr->next)
    2430           0 :                     ptr = ptr->next;
    2431          60 :                 ptr->next = SplitToVariants(Conf, NULL, new, word, wordlen, startpos + lenaff, startpos + lenaff);
    2432             : 
    2433          60 :                 pfree(new->stem);
    2434          60 :                 pfree(new);
    2435             :             }
    2436             :         }
    2437             : 
    2438        4796 :         if (!node)
    2439         500 :             break;
    2440             : 
    2441        4296 :         StopLow = node->data;
    2442        4296 :         StopHigh = node->data + node->length;
    2443       10092 :         while (StopLow < StopHigh)
    2444             :         {
    2445        5376 :             StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
    2446        5376 :             if (StopMiddle->val == ((uint8 *) (word))[level])
    2447        3876 :                 break;
    2448        1500 :             else if (StopMiddle->val < ((uint8 *) (word))[level])
    2449         652 :                 StopLow = StopMiddle + 1;
    2450             :             else
    2451         848 :                 StopHigh = StopMiddle;
    2452             :         }
    2453             : 
    2454        4296 :         if (StopLow < StopHigh)
    2455             :         {
    2456        3876 :             if (startpos == 0)
    2457        2180 :                 compoundflag = FF_COMPOUNDBEGIN;
    2458        1696 :             else if (level == wordlen - 1)
    2459         192 :                 compoundflag = FF_COMPOUNDLAST;
    2460             :             else
    2461        1504 :                 compoundflag = FF_COMPOUNDMIDDLE;
    2462             : 
    2463             :             /* find infinitive */
    2464        4900 :             if (StopMiddle->isword &&
    2465        1872 :                 (StopMiddle->compoundflag & compoundflag) &&
    2466         848 :                 notprobed[level])
    2467             :             {
    2468             :                 /* ok, we found full compoundallowed word */
    2469         848 :                 if (level > minpos)
    2470             :                 {
    2471             :                     /* and its length more than minimal */
    2472         528 :                     if (wordlen == level + 1)
    2473             :                     {
    2474             :                         /* well, it was last word */
    2475         208 :                         AddStem(var, pnstrdup(word + startpos, wordlen - startpos));
    2476         208 :                         pfree(notprobed);
    2477         208 :                         return var;
    2478             :                     }
    2479             :                     else
    2480             :                     {
    2481             :                         /* then we will search more big word at the same point */
    2482         320 :                         SplitVar   *ptr = var;
    2483             : 
    2484         816 :                         while (ptr->next)
    2485         176 :                             ptr = ptr->next;
    2486         320 :                         ptr->next = SplitToVariants(Conf, node, var, word, wordlen, startpos, level);
    2487             :                         /* we can find next word */
    2488         320 :                         level++;
    2489         320 :                         AddStem(var, pnstrdup(word + startpos, level - startpos));
    2490         320 :                         node = Conf->Dictionary;
    2491         320 :                         startpos = level;
    2492         320 :                         continue;
    2493             :                     }
    2494             :                 }
    2495             :             }
    2496        3348 :             node = StopMiddle->node;
    2497             :         }
    2498             :         else
    2499         420 :             node = NULL;
    2500        3768 :         level++;
    2501             :     }
    2502             : 
    2503         672 :     AddStem(var, pnstrdup(word + startpos, wordlen - startpos));
    2504         672 :     pfree(notprobed);
    2505         672 :     return var;
    2506             : }
    2507             : 
    2508             : static void
    2509         876 : addNorm(TSLexeme **lres, TSLexeme **lcur, char *word, int flags, uint16 NVariant)
    2510             : {
    2511         876 :     if (*lres == NULL)
    2512         404 :         *lcur = *lres = (TSLexeme *) palloc(MAX_NORM * sizeof(TSLexeme));
    2513             : 
    2514         876 :     if (*lcur - *lres < MAX_NORM - 1)
    2515             :     {
    2516         876 :         (*lcur)->lexeme = word;
    2517         876 :         (*lcur)->flags = flags;
    2518         876 :         (*lcur)->nvariant = NVariant;
    2519         876 :         (*lcur)++;
    2520         876 :         (*lcur)->lexeme = NULL;
    2521             :     }
    2522         876 : }
    2523             : 
    2524             : TSLexeme *
    2525         500 : NINormalizeWord(IspellDict *Conf, char *word)
    2526             : {
    2527             :     char      **res;
    2528         500 :     TSLexeme   *lcur = NULL,
    2529         500 :                *lres = NULL;
    2530         500 :     uint16      NVariant = 1;
    2531             : 
    2532         500 :     res = NormalizeSubWord(Conf, word, 0);
    2533             : 
    2534         500 :     if (res)
    2535             :     {
    2536         324 :         char      **ptr = res;
    2537             : 
    2538        1084 :         while (*ptr && (lcur - lres) < MAX_NORM)
    2539             :         {
    2540         436 :             addNorm(&lres, &lcur, *ptr, 0, NVariant++);
    2541         436 :             ptr++;
    2542             :         }
    2543         324 :         pfree(res);
    2544             :     }
    2545             : 
    2546         500 :     if (Conf->usecompound)
    2547             :     {
    2548         500 :         int         wordlen = strlen(word);
    2549             :         SplitVar   *ptr,
    2550         500 :                    *var = SplitToVariants(Conf, NULL, NULL, word, wordlen, 0, -1);
    2551             :         int         i;
    2552             : 
    2553        1880 :         while (var)
    2554             :         {
    2555         880 :             if (var->nstem > 1)
    2556             :             {
    2557         380 :                 char      **subres = NormalizeSubWord(Conf, var->stem[var->nstem - 1], FF_COMPOUNDLAST);
    2558             : 
    2559         380 :                 if (subres)
    2560             :                 {
    2561         176 :                     char      **subptr = subres;
    2562             : 
    2563         528 :                     while (*subptr)
    2564             :                     {
    2565         440 :                         for (i = 0; i < var->nstem - 1; i++)
    2566             :                         {
    2567         264 :                             addNorm(&lres, &lcur, (subptr == subres) ? var->stem[i] : pstrdup(var->stem[i]), 0, NVariant);
    2568             :                         }
    2569             : 
    2570         176 :                         addNorm(&lres, &lcur, *subptr, 0, NVariant);
    2571         176 :                         subptr++;
    2572         176 :                         NVariant++;
    2573             :                     }
    2574             : 
    2575         176 :                     pfree(subres);
    2576         176 :                     var->stem[0] = NULL;
    2577         176 :                     pfree(var->stem[var->nstem - 1]);
    2578             :                 }
    2579             :             }
    2580             : 
    2581        1828 :             for (i = 0; i < var->nstem && var->stem[i]; i++)
    2582         948 :                 pfree(var->stem[i]);
    2583         880 :             ptr = var->next;
    2584         880 :             pfree(var->stem);
    2585         880 :             pfree(var);
    2586         880 :             var = ptr;
    2587             :         }
    2588             :     }
    2589             : 
    2590         500 :     return lres;
    2591             : }

Generated by: LCOV version 1.13