LCOV - code coverage report
Current view: top level - src/backend/tsearch - spell.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 92.0 % 1153 1061
Test Date: 2026-09-05 06:15:58 Functions: 100.0 % 48 48
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 73.9 % 909 672

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

Generated by: LCOV version 2.0-1