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

Generated by: LCOV version 1.14