LCOV - code coverage report
Current view: top level - src/backend/tsearch - spell.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 92.7 % 1353 1254
Test Date: 2026-01-26 10:56:24 Functions: 100.0 % 46 46
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 70.2 % 944 663

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

Generated by: LCOV version 2.3.2-1