LCOV - differential code coverage report
Current view: top level - src/backend/tsearch - spell.c (source / functions) Coverage Total Hit UNC LBC UIC UBC GBC GIC GNC CBC EUB ECB DUB DCB
Current: Differential Code Coverage HEAD vs 15 Lines: 92.1 % 1127 1038 3 16 46 24 33 435 8 562 30 450 2 10
Current Date: 2023-04-08 15:15:32 Functions: 100.0 % 46 46 28 18 28
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

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

Generated by: LCOV version v1.16-55-g56c0a2a