Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  spell.c

  Sprache: C
 

/*-------------------------------------------------------------------------
 *
 * spell.c
 *  Normalizing word with ISpell
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 *
 * Ispell dictionary
 * -----------------
 *
 * Rules of dictionaries are defined in two files with .affix and .dict
 * extensions. They are used by spell checker programs Ispell and Hunspell.
 *
 * An .affix file declares morphological rules to get a basic form of words.
 * The format of an .affix file has different structure for Ispell and Hunspell
 * dictionaries. The Hunspell format is more complicated. But when an .affix
 * file is imported and compiled, it is stored in the same structure AffixNode.
 *
 * A .dict file stores a list of basic forms of words with references to
 * affix rules. The format of a .dict file has the same structure for Ispell
 * and Hunspell dictionaries.
 *
 * Compilation of a dictionary
 * ---------------------------
 *
 * A compiled dictionary is stored in the IspellDict structure. Compilation of
 * a dictionary is divided into the several steps:
 * - NIImportDictionary() - stores each word of a .dict file in the
 *   temporary Spell field.
 * - NIImportAffixes() - stores affix rules of an .affix file in the
 *   Affix field (not temporary) if an .affix file has the Ispell format.
 *   -> NIImportOOAffixes() - stores affix rules if an .affix file has the
 *   Hunspell format. The AffixData field is initialized if AF parameter
 *   is defined.
 * - NISortDictionary() - builds a prefix tree (Trie) from the words list
 *   and stores it in the Dictionary field. The words list is got from the
 *   Spell field. The AffixData field is initialized if AF parameter is not
 *   defined.
 * - NISortAffixes():
 *   - builds a list of compound affixes from the affix list and stores it
 *  in the CompoundAffix.
 *   - builds prefix trees (Trie) from the affix list for prefixes and suffixes
 *  and stores them in Suffix and Prefix fields.
 *   The affix list is got from the Affix field.
 *
 * Memory management
 * -----------------
 *
 * The IspellDict structure has the Spell field which is used only in compile
 * time. The Spell field stores a words list. It can take a lot of memory.
 * Therefore when a dictionary is compiled this field is cleared by
 * NIFinishBuild().
 *
 * All resources which should cleared by NIFinishBuild() is initialized using
 * tmpalloc() and tmpalloc0().
 *
 * IDENTIFICATION
 *   src/backend/tsearch/spell.c
 *
 *-------------------------------------------------------------------------
 */


#include "postgres.h"

#include "catalog/pg_collation.h"
#include "miscadmin.h"
#include "tsearch/dicts/spell.h"
#include "tsearch/ts_locale.h"
#include "utils/formatting.h"
#include "utils/memutils.h"


/*
 * Initialization requires a lot of memory that's not needed
 * after the initialization is done.  During initialization,
 * CurrentMemoryContext is the long-lived memory context associated
 * with the dictionary cache entry.  We keep the short-lived stuff
 * in the Conf->buildCxt context.
 */

#define tmpalloc(sz)  MemoryContextAlloc(Conf->buildCxt, (sz))
#define tmpalloc0(sz)  MemoryContextAllocZero(Conf->buildCxt, (sz))

/*
 * Prepare for constructing an ISpell dictionary.
 *
 * The IspellDict struct is assumed to be zeroed when allocated.
 */

void
NIStartBuild(IspellDict *Conf)
{
 /*
  * The temp context is a child of CurTransactionContext, so that it will
  * go away automatically on error.
 */

 Conf->buildCxt = AllocSetContextCreate(CurTransactionContext,
             "Ispell dictionary init context",
             ALLOCSET_DEFAULT_SIZES);
}

/*
 * Clean up when dictionary construction is complete.
 */

void
NIFinishBuild(IspellDict *Conf)
{
 /* Release no-longer-needed temp memory */
 MemoryContextDelete(Conf->buildCxt);
 /* Just for cleanliness, zero the now-dangling pointers */
 Conf->buildCxt = NULL;
 Conf->Spell = NULL;
 Conf->firstfree = NULL;
 Conf->CompoundAffixFlags = NULL;
}


/*
 * "Compact" palloc: allocate without extra palloc overhead.
 *
 * Since we have no need to free the ispell data items individually, there's
 * not much value in the per-chunk overhead normally consumed by palloc.
 * Getting rid of it is helpful since ispell can allocate a lot of small nodes.
 *
 * We currently pre-zero all data allocated this way, even though some of it
 * doesn't need that.  The cpalloc and cpalloc0 macros are just documentation
 * to indicate which allocations actually require zeroing.
 */

#define COMPACT_ALLOC_CHUNK 8192 /* amount to get from palloc at once */
#define COMPACT_MAX_REQ  1024 /* must be < COMPACT_ALLOC_CHUNK */

static void *
compact_palloc0(IspellDict *Conf, size_t size)
{
 void    *result;

 /* Should only be called during init */
 Assert(Conf->buildCxt != NULL);

 /* No point in this for large chunks */
 if (size > COMPACT_MAX_REQ)
  return palloc0(size);

 /* Keep everything maxaligned */
 size = MAXALIGN(size);

 /* Need more space? */
 if (size > Conf->avail)
 {
  Conf->firstfree = palloc0(COMPACT_ALLOC_CHUNK);
  Conf->avail = COMPACT_ALLOC_CHUNK;
 }

 result = Conf->firstfree;
 Conf->firstfree += size;
 Conf->avail -= size;

 return result;
}

#define cpalloc(size) compact_palloc0(Conf, size)
#define cpalloc0(size) compact_palloc0(Conf, size)

static char *
cpstrdup(IspellDict *Conf, const char *str)
{
 char    *res = cpalloc(strlen(str) + 1);

 strcpy(res, str);
 return res;
}


/*
 * Apply str_tolower(), producing a temporary result (in the buildCxt).
 */

static char *
lowerstr_ctx(IspellDict *Conf, const char *src)
{
 MemoryContext saveCtx;
 char    *dst;

 saveCtx = MemoryContextSwitchTo(Conf->buildCxt);
 dst = str_tolower(src, strlen(src), DEFAULT_COLLATION_OID);
 MemoryContextSwitchTo(saveCtx);

 return dst;
}

#define MAX_NORM 1024
#define MAXNORMLEN 256

#define STRNCMP(s,p) strncmp( (s), (p), strlen(p) )
#define GETWCHAR(W,L,N,T) ( ((const uint8*)(W))[ ((T)==FF_PREFIX) ? (N) : ( (L) - 1 - (N) ) ] )
#define GETCHAR(A,N,T)   GETWCHAR( (A)->repl, (A)->replen, N, T )

static const char *VoidString = "";

static int
cmpspell(const void *s1, const void *s2)
{
 return strcmp((*(SPELL *const *) s1)->word, (*(SPELL *const *) s2)->word);
}

static int
cmpspellaffix(const void *s1, const void *s2)
{
 return strcmp((*(SPELL *const *) s1)->p.flag,
      (*(SPELL *const *) s2)->p.flag);
}

static int
cmpcmdflag(const void *f1, const void *f2)
{
 CompoundAffixFlag *fv1 = (CompoundAffixFlag *) f1,
      *fv2 = (CompoundAffixFlag *) f2;

 Assert(fv1->flagMode == fv2->flagMode);

 if (fv1->flagMode == FM_NUM)
 {
  if (fv1->flag.i == fv2->flag.i)
   return 0;

  return (fv1->flag.i > fv2->flag.i) ? 1 : -1;
 }

 return strcmp(fv1->flag.s, fv2->flag.s);
}

static char *
findchar(char *str, int c)
{
 while (*str)
 {
  if (t_iseq(str, c))
   return str;
  str += pg_mblen_cstr(str);
 }

 return NULL;
}

static char *
findchar2(char *str, int c1, int c2)
{
 while (*str)
 {
  if (t_iseq(str, c1) || t_iseq(str, c2))
   return str;
  str += pg_mblen_cstr(str);
 }

 return NULL;
}


/* backward string compare for suffix tree operations */
static int
strbcmp(const unsigned char *s1, const unsigned char *s2)
{
 int   l1 = strlen((const char *) s1) - 1,
    l2 = strlen((const char *) s2) - 1;

 while (l1 >= 0 && l2 >= 0)
 {
  if (s1[l1] < s2[l2])
   return -1;
  if (s1[l1] > s2[l2])
   return 1;
  l1--;
  l2--;
 }
 if (l1 < l2)
  return -1;
 if (l1 > l2)
  return 1;

 return 0;
}

static int
strbncmp(const unsigned char *s1, const unsigned char *s2, size_t count)
{
 int   l1 = strlen((const char *) s1) - 1,
    l2 = strlen((const char *) s2) - 1,
    l = count;

 while (l1 >= 0 && l2 >= 0 && l > 0)
 {
  if (s1[l1] < s2[l2])
   return -1;
  if (s1[l1] > s2[l2])
   return 1;
  l1--;
  l2--;
  l--;
 }
 if (l == 0)
  return 0;
 if (l1 < l2)
  return -1;
 if (l1 > l2)
  return 1;
 return 0;
}

/*
 * Compares affixes.
 * First compares the type of an affix. Prefixes should go before affixes.
 * If types are equal then compares replaceable string.
 */

static int
cmpaffix(const void *s1, const void *s2)
{
 const AFFIX *a1 = (const AFFIX *) s1;
 const AFFIX *a2 = (const AFFIX *) s2;

 if (a1->type < a2->type)
  return -1;
 if (a1->type > a2->type)
  return 1;
 if (a1->type == FF_PREFIX)
  return strcmp(a1->repl, a2->repl);
 else
  return strbcmp((const unsigned char *) a1->repl,
        (const unsigned char *) a2->repl);
}

/*
 * Gets an affix flag from the set of affix flags (sflagset).
 *
 * Several flags can be stored in a single string. Flags can be represented by:
 * - 1 character (FM_CHAR). A character may be Unicode.
 * - 2 characters (FM_LONG). A character may be Unicode.
 * - numbers from 1 to 65000 (FM_NUM).
 *
 * Depending on the flagMode an affix string can have the following format:
 * - FM_CHAR: ABCD
 *  Here we have 4 flags: A, B, C and D
 * - FM_LONG: ABCDE*
 *  Here we have 3 flags: AB, CD and E*
 * - FM_NUM: 200,205,50
 *  Here we have 3 flags: 200, 205 and 50
 *
 * Conf: current dictionary.
 * sflagset: the set of affix flags. Returns a reference to the start of a next
 *    affix flag.
 * sflag: returns an affix flag from sflagset.
 */

static void
getNextFlagFromString(IspellDict *Conf, const char **sflagset, char *sflag)
{
 int32  s;
 char    *next;
 const char *sbuf = *sflagset;
 int   maxstep;
 int   clen;
 bool  stop = false;
 bool  met_comma = false;

 maxstep = (Conf->flagMode == FM_LONG) ? 2 : 1;

 while (**sflagset)
 {
  switch (Conf->flagMode)
  {
   case FM_LONG:
   case FM_CHAR:
    clen = ts_copychar_cstr(sflag, *sflagset);
    sflag += clen;

    /* Go to start of the next flag */
    *sflagset += clen;

    /* Check if we get all characters of flag */
    maxstep--;
    stop = (maxstep == 0);
    break;
   case FM_NUM:
    errno = 0;
    s = strtol(*sflagset, &next, 10);
    if (*sflagset == next || errno == ERANGE)
     ereport(ERROR,
       (errcode(ERRCODE_CONFIG_FILE_ERROR),
        errmsg("invalid affix flag \"%s\"", *sflagset)));
    if (s < 0 || s > FLAGNUM_MAXSIZE)
     ereport(ERROR,
       (errcode(ERRCODE_CONFIG_FILE_ERROR),
        errmsg("affix flag \"%s\" is out of range",
         *sflagset)));
    sflag += sprintf(sflag, "%0d", s);

    /* Go to start of the next flag */
    *sflagset = next;
    while (**sflagset)
    {
     if (isdigit((unsigned char) **sflagset))
     {
      if (!met_comma)
       ereport(ERROR,
         (errcode(ERRCODE_CONFIG_FILE_ERROR),
          errmsg("invalid affix flag \"%s\"",
           *sflagset)));
      break;
     }
     else if (t_iseq(*sflagset, ','))
     {
      if (met_comma)
       ereport(ERROR,
         (errcode(ERRCODE_CONFIG_FILE_ERROR),
          errmsg("invalid affix flag \"%s\"",
           *sflagset)));
      met_comma = true;
     }
     else if (!isspace((unsigned char) **sflagset))
     {
      ereport(ERROR,
        (errcode(ERRCODE_CONFIG_FILE_ERROR),
         errmsg("invalid character in affix flag \"%s\"",
          *sflagset)));
     }

     *sflagset += pg_mblen_cstr(*sflagset);
    }
    stop = true;
    break;
   default:
    elog(ERROR, "unrecognized type of Conf->flagMode: %d",
      Conf->flagMode);
  }

  if (stop)
   break;
 }

 if (Conf->flagMode == FM_LONG && maxstep > 0)
  ereport(ERROR,
    (errcode(ERRCODE_CONFIG_FILE_ERROR),
     errmsg("invalid affix flag \"%s\" with \"long\" flag value",
      sbuf)));

 *sflag = '\0';
}

/*
 * Checks if the affix set Conf->AffixData[affix] contains affixflag.
 * Conf->AffixData[affix] does not contain affixflag if this flag is not used
 * actually by the .dict file.
 *
 * Conf: current dictionary.
 * affix: index of the Conf->AffixData array.
 * affixflag: the affix flag.
 *
 * Returns true if the string Conf->AffixData[affix] contains affixflag,
 * otherwise returns false.
 */

static bool
IsAffixFlagInUse(IspellDict *Conf, int affix, const char *affixflag)
{
 const char *flagcur;
 char  flag[BUFSIZ];

 if (*affixflag == 0)
  return true;

 Assert(affix < Conf->nAffixData);

 flagcur = Conf->AffixData[affix];

 while (*flagcur)
 {
  getNextFlagFromString(Conf, &flagcur, flag);
  /* Compare first affix flag in flagcur with affixflag */
  if (strcmp(flag, affixflag) == 0)
   return true;
 }

 /* Could not find affixflag */
 return false;
}

/*
 * Adds the new word into the temporary array Spell.
 *
 * Conf: current dictionary.
 * word: new word.
 * flag: set of affix flags. Single flag can be get by getNextFlagFromString().
 */

static void
NIAddSpell(IspellDict *Conf, const char *word, const char *flag)
{
 if (Conf->nspell >= Conf->mspell)
 {
  if (Conf->mspell)
  {
   Conf->mspell *= 2;
   Conf->Spell = (SPELL **) repalloc(Conf->Spell, Conf->mspell * sizeof(SPELL *));
  }
  else
  {
   Conf->mspell = 1024 * 20;
   Conf->Spell = (SPELL **) tmpalloc(Conf->mspell * sizeof(SPELL *));
  }
 }
 Conf->Spell[Conf->nspell] = (SPELL *) tmpalloc(SPELLHDRSZ + strlen(word) + 1);
 strcpy(Conf->Spell[Conf->nspell]->word, word);
 Conf->Spell[Conf->nspell]->p.flag = (*flag != '\0')
  ? cpstrdup(Conf, flag) : VoidString;
 Conf->nspell++;
}

/*
 * Imports dictionary into the temporary array Spell.
 *
 * Note caller must already have applied get_tsearch_config_filename.
 *
 * Conf: current dictionary.
 * filename: path to the .dict file.
 */

void
NIImportDictionary(IspellDict *Conf, const char *filename)
{
 tsearch_readline_state trst;
 char    *line;

 if (!tsearch_readline_begin(&trst, filename))
  ereport(ERROR,
    (errcode(ERRCODE_CONFIG_FILE_ERROR),
     errmsg("could not open dictionary file \"%s\": %m",
      filename)));

 while ((line = tsearch_readline(&trst)) != NULL)
 {
  char    *s,
       *pstr;

  /* Set of affix flags */
  const char *flag;

  /* Extract flag from the line */
  flag = NULL;
  if ((s = findchar(line, '/')))
  {
   *s++ = '\0';
   flag = s;
   while (*s)
   {
    /* we allow only single encoded flags for faster works */
    if (pg_mblen_cstr(s) == 1 && isprint((unsigned char) *s) && !isspace((unsigned char) *s))
     s++;
    else
    {
     *s = '\0';
     break;
    }
   }
  }
  else
   flag = "";

  /* Remove trailing spaces */
  s = line;
  while (*s)
  {
   if (isspace((unsigned char) *s))
   {
    *s = '\0';
    break;
   }
   s += pg_mblen_cstr(s);
  }
  pstr = lowerstr_ctx(Conf, line);

  NIAddSpell(Conf, pstr, flag);
  pfree(pstr);

  pfree(line);
 }
 tsearch_readline_end(&trst);
}

/*
 * Searches a basic form of word in the prefix tree. This word was generated
 * using an affix rule. This rule may not be presented in an affix set of
 * a basic form of word.
 *
 * For example, we have the entry in the .dict file:
 * meter/GMD
 *
 * The affix rule with the flag S:
 * SFX S   y  ies  [^aeiou]y
 * is not presented here.
 *
 * The affix rule with the flag M:
 * SFX M   0  's         .
 * is presented here.
 *
 * Conf: current dictionary.
 * word: basic form of word.
 * affixflag: affix flag, by which a basic form of word was generated.
 * flag: compound flag used to compare with StopMiddle->compoundflag.
 *
 * Returns 1 if the word was found in the prefix tree, else returns 0.
 */

static int
FindWord(IspellDict *Conf, const char *word, const char *affixflag, int flag)
{
 SPNode    *node = Conf->Dictionary;
 SPNodeData *StopLow,
      *StopHigh,
      *StopMiddle;
 const uint8 *ptr = (const uint8 *) word;

 flag &= FF_COMPOUNDFLAGMASK;

 while (node && *ptr)
 {
  StopLow = node->data;
  StopHigh = node->data + node->length;
  while (StopLow < StopHigh)
  {
   StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
   if (StopMiddle->val == *ptr)
   {
    if (*(ptr + 1) == '\0' && StopMiddle->isword)
    {
     if (flag == 0)
     {
      /*
       * The word can be formed only with another word. And
       * in the flag parameter there is not a sign that we
       * search compound words.
 */

      if (StopMiddle->compoundflag & FF_COMPOUNDONLY)
       return 0;
     }
     else if ((flag & StopMiddle->compoundflag) == 0)
      return 0;

     /*
      * Check if this affix rule is presented in the affix set
      * with index StopMiddle->affix.
 */

     if (IsAffixFlagInUse(Conf, StopMiddle->affix, affixflag))
      return 1;
    }
    node = StopMiddle->node;
    ptr++;
    break;
   }
   else if (StopMiddle->val < *ptr)
    StopLow = StopMiddle + 1;
   else
    StopHigh = StopMiddle;
  }
  if (StopLow >= StopHigh)
   break;
 }
 return 0;
}

/*
 * Adds a new affix rule to the Affix field.
 *
 * Conf: current dictionary.
 * flag: affix flag ('\' in the below example).
 * flagflags: set of flags from the flagval field for this affix rule. This set
 *     is listed after '/' character in the added string (repl).
 *
 *     For example L flag in the hunspell_sample.affix:
 *     SFX \   0 Y/L [^Y]
 *
 * mask: condition for search ('[^Y]' in the above example).
 * find: stripping characters from beginning (at prefix) or end (at suffix)
 *   of the word ('0' in the above example, 0 means that there is not
 *   stripping character).
 * repl: adding string after stripping ('Y' in the above example).
 * type: FF_SUFFIX or FF_PREFIX.
 */

static void
NIAddAffix(IspellDict *Conf, const char *flag, char flagflags, const char *mask,
     const char *find, const char *repl, int type)
{
 AFFIX    *Affix;

 if (Conf->naffixes >= Conf->maffixes)
 {
  if (Conf->maffixes)
  {
   Conf->maffixes *= 2;
   Conf->Affix = (AFFIX *) repalloc(Conf->Affix, Conf->maffixes * sizeof(AFFIX));
  }
  else
  {
   Conf->maffixes = 16;
   Conf->Affix = (AFFIX *) palloc(Conf->maffixes * sizeof(AFFIX));
  }
 }

 Affix = Conf->Affix + Conf->naffixes;

 /* This affix rule can be applied for words with any ending */
 if (strcmp(mask, ".") == 0 || *mask == '\0')
 {
  Affix->issimple = 1;
  Affix->isregis = 0;
 }
 /* This affix rule will use regis to search word ending */
 else if (RS_isRegis(mask))
 {
  Affix->issimple = 0;
  Affix->isregis = 1;
  RS_compile(&(Affix->reg.regis), (type == FF_SUFFIX),
       *mask ? mask : VoidString);
 }
 /* This affix rule will use regex_t to search word ending */
 else
 {
  int   masklen;
  int   wmasklen;
  int   err;
  pg_wchar   *wmask;
  char    *tmask;

  Affix->issimple = 0;
  Affix->isregis = 0;
  tmask = (char *) tmpalloc(strlen(mask) + 3);
  if (type == FF_SUFFIX)
   sprintf(tmask, "%s$", mask);
  else
   sprintf(tmask, "^%s", mask);

  masklen = strlen(tmask);
  wmask = (pg_wchar *) tmpalloc((masklen + 1) * sizeof(pg_wchar));
  wmasklen = pg_mb2wchar_with_len(tmask, wmask, masklen);

  /*
   * The regex and all internal state created by pg_regcomp are
   * allocated in the dictionary's memory context, and will be freed
   * automatically when it is destroyed.
 */

  Affix->reg.pregex = palloc(sizeof(regex_t));
  err = pg_regcomp(Affix->reg.pregex, wmask, wmasklen,
       REG_ADVANCED | REG_NOSUB,
       DEFAULT_COLLATION_OID);
  if (err)
  {
   char  errstr[100];

   pg_regerror(err, Affix->reg.pregex, errstr, sizeof(errstr));
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
      errmsg("invalid regular expression: %s", errstr)));
  }
 }

 Affix->flagflags = flagflags;
 if ((Affix->flagflags & FF_COMPOUNDONLY) || (Affix->flagflags & FF_COMPOUNDPERMITFLAG))
 {
  if ((Affix->flagflags & FF_COMPOUNDFLAG) == 0)
   Affix->flagflags |= FF_COMPOUNDFLAG;
 }
 Affix->flag = cpstrdup(Conf, flag);
 Affix->type = type;

 Affix->find = (find && *find) ? cpstrdup(Conf, find) : VoidString;
 if ((Affix->replen = strlen(repl)) > 0)
  Affix->repl = cpstrdup(Conf, repl);
 else
  Affix->repl = VoidString;
 Conf->naffixes++;
}

/* Parsing states for parse_affentry() and friends */
#define PAE_WAIT_MASK 0
#define PAE_INMASK  1
#define PAE_WAIT_FIND 2
#define PAE_INFIND  3
#define PAE_WAIT_REPL 4
#define PAE_INREPL  5
#define PAE_WAIT_TYPE 6
#define PAE_WAIT_FLAG 7

/*
 * Parse next space-separated field of an .affix file line.
 *
 * *str is the input pointer (will be advanced past field)
 * next is where to copy the field value to, with null termination
 *
 * The buffer at "next" must be of size BUFSIZ; we truncate the input to fit.
 *
 * Returns true if we found a field, false if not.
 */

static bool
get_nextfield(char **str, char *next)
{
 int   state = PAE_WAIT_MASK;
 int   avail = BUFSIZ;

 while (**str)
 {
  int   clen = pg_mblen_cstr(*str);

  if (state == PAE_WAIT_MASK)
  {
   if (t_iseq(*str, '#'))
    return false;
   else if (!isspace((unsigned char) **str))
   {
    if (clen < avail)
    {
     ts_copychar_with_len(next, *str, clen);
     next += clen;
     avail -= clen;
    }
    state = PAE_INMASK;
   }
  }
  else     /* state == PAE_INMASK */
  {
   if (isspace((unsigned char) **str))
   {
    *next = '\0';
    return true;
   }
   else
   {
    if (clen < avail)
    {
     ts_copychar_with_len(next, *str, clen);
     next += clen;
     avail -= clen;
    }
   }
  }
  *str += clen;
 }

 *next = '\0';

 return (state == PAE_INMASK); /* OK if we got a nonempty field */
}

/*
 * Parses entry of an .affix file of MySpell or Hunspell format.
 *
 * An .affix file entry has the following format:
 * - header
 *  <type>  <flag>  <cross_flag>  <flag_count>
 * - fields after header:
 *  <type>  <flag>  <find>  <replace> <mask>
 *
 * str is the input line
 * field values are returned to type etc, which must be buffers of size BUFSIZ.
 *
 * Returns number of fields found; any omitted fields are set to empty strings.
 */

static int
parse_ooaffentry(char *str, char *type, char *flag, char *find,
     char *repl, char *mask)
{
 int   state = PAE_WAIT_TYPE;
 int   fields_read = 0;
 bool  valid = false;

 *type = *flag = *find = *repl = *mask = '\0';

 while (*str)
 {
  switch (state)
  {
   case PAE_WAIT_TYPE:
    valid = get_nextfield(&str, type);
    state = PAE_WAIT_FLAG;
    break;
   case PAE_WAIT_FLAG:
    valid = get_nextfield(&str, flag);
    state = PAE_WAIT_FIND;
    break;
   case PAE_WAIT_FIND:
    valid = get_nextfield(&str, find);
    state = PAE_WAIT_REPL;
    break;
   case PAE_WAIT_REPL:
    valid = get_nextfield(&str, repl);
    state = PAE_WAIT_MASK;
    break;
   case PAE_WAIT_MASK:
    valid = get_nextfield(&str, mask);
    state = -1;  /* force loop exit */
    break;
   default:
    elog(ERROR, "unrecognized state in parse_ooaffentry: %d",
      state);
    break;
  }
  if (valid)
   fields_read++;
  else
   break;    /* early EOL */
  if (state < 0)
   break;    /* got all fields */
 }

 return fields_read;
}

/*
 * Parses entry of an .affix file of Ispell format
 *
 * An .affix file entry has the following format:
 * <mask>  >  [-<find>,]<replace>
 *
 * Output buffers mask, find, repl must be of length BUFSIZ;
 * we truncate the input to fit.
 */

static bool
parse_affentry(const char *str, char *mask, char *find, char *repl)
{
 int   state = PAE_WAIT_MASK;
 char    *pmask = mask,
      *pfind = find,
      *prepl = repl;
 char    *emask = mask + BUFSIZ;
 char    *efind = find + BUFSIZ;
 char    *erepl = repl + BUFSIZ;

 *mask = *find = *repl = '\0';

 while (*str)
 {
  int   clen = pg_mblen_cstr(str);

  if (state == PAE_WAIT_MASK)
  {
   if (t_iseq(str, '#'))
    return false;
   else if (!isspace((unsigned char) *str))
   {
    if (pmask < emask - clen)
     pmask += ts_copychar_with_len(pmask, str, clen);
    state = PAE_INMASK;
   }
  }
  else if (state == PAE_INMASK)
  {
   if (t_iseq(str, '>'))
   {
    *pmask = '\0';
    state = PAE_WAIT_FIND;
   }
   else if (!isspace((unsigned char) *str))
   {
    if (pmask < emask - clen)
     pmask += ts_copychar_with_len(pmask, str, clen);
   }
  }
  else if (state == PAE_WAIT_FIND)
  {
   if (t_iseq(str, '-'))
   {
    state = PAE_INFIND;
   }
   else if (t_isalpha_cstr(str) || t_iseq(str, '\'') /* english 's */ )
   {
    if (prepl < erepl - clen)
     prepl += ts_copychar_with_len(prepl, str, clen);
    state = PAE_INREPL;
   }
   else if (!isspace((unsigned char) *str))
    ereport(ERROR,
      (errcode(ERRCODE_CONFIG_FILE_ERROR),
       errmsg("syntax error")));
  }
  else if (state == PAE_INFIND)
  {
   if (t_iseq(str, ','))
   {
    *pfind = '\0';
    state = PAE_WAIT_REPL;
   }
   else if (t_isalpha_cstr(str))
   {
    if (pfind < efind - clen)
     pfind += ts_copychar_with_len(pfind, str, clen);
   }
   else if (!isspace((unsigned char) *str))
    ereport(ERROR,
      (errcode(ERRCODE_CONFIG_FILE_ERROR),
       errmsg("syntax error")));
  }
  else if (state == PAE_WAIT_REPL)
  {
   if (t_iseq(str, '-'))
   {
    break;   /* void repl */
   }
   else if (t_isalpha_cstr(str))
   {
    if (prepl < erepl - clen)
     prepl += ts_copychar_with_len(prepl, str, clen);
    state = PAE_INREPL;
   }
   else if (!isspace((unsigned char) *str))
    ereport(ERROR,
      (errcode(ERRCODE_CONFIG_FILE_ERROR),
       errmsg("syntax error")));
  }
  else if (state == PAE_INREPL)
  {
   if (t_iseq(str, '#'))
   {
    *prepl = '\0';
    break;
   }
   else if (t_isalpha_cstr(str))
   {
    if (prepl < erepl - clen)
     prepl += ts_copychar_with_len(prepl, str, clen);
   }
   else if (!isspace((unsigned char) *str))
    ereport(ERROR,
      (errcode(ERRCODE_CONFIG_FILE_ERROR),
       errmsg("syntax error")));
  }
  else
   elog(ERROR, "unrecognized state in parse_affentry: %d", state);

  str += clen;
 }

 *pmask = *pfind = *prepl = '\0';

 return (*mask && (*find || *repl));
}

/*
 * Sets a Hunspell options depending on flag type.
 */

static void
setCompoundAffixFlagValue(IspellDict *Conf, CompoundAffixFlag *entry,
        char *s, uint32 val)
{
 if (Conf->flagMode == FM_NUM)
 {
  char    *next;
  int   i;

  errno = 0;
  i = strtol(s, &next, 10);
  if (s == next || errno == ERANGE)
   ereport(ERROR,
     (errcode(ERRCODE_CONFIG_FILE_ERROR),
      errmsg("invalid affix flag \"%s\"", s)));
  if (i < 0 || i > FLAGNUM_MAXSIZE)
   ereport(ERROR,
     (errcode(ERRCODE_CONFIG_FILE_ERROR),
      errmsg("affix flag \"%s\" is out of range", s)));

  entry->flag.i = i;
 }
 else
  entry->flag.s = cpstrdup(Conf, s);

 entry->flagMode = Conf->flagMode;
 entry->value = val;
}

/*
 * Sets up a correspondence for the affix parameter with the affix flag.
 *
 * Conf: current dictionary.
 * s: affix flag in string.
 * val: affix parameter.
 */

static void
addCompoundAffixFlagValue(IspellDict *Conf, const char *s, uint32 val)
{
 CompoundAffixFlag *newValue;
 char  sbuf[BUFSIZ];
 char    *sflag;

 while (*s && isspace((unsigned char) *s))
  s += pg_mblen_cstr(s);

 if (!*s)
  ereport(ERROR,
    (errcode(ERRCODE_CONFIG_FILE_ERROR),
     errmsg("syntax error")));

 /* Get flag without \n */
 sflag = sbuf;
 while (*s && !isspace((unsigned char) *s) && *s != '\n')
 {
  int   clen = pg_mblen_cstr(s);

  /* Truncate the input to fit in BUFSIZ */
  if (sflag < sbuf + BUFSIZ - clen)
   sflag += ts_copychar_with_len(sflag, s, clen);
  s += clen;
 }
 *sflag = '\0';

 /* Resize array or allocate memory for array CompoundAffixFlag */
 if (Conf->nCompoundAffixFlag >= Conf->mCompoundAffixFlag)
 {
  if (Conf->mCompoundAffixFlag)
  {
   Conf->mCompoundAffixFlag *= 2;
   Conf->CompoundAffixFlags = (CompoundAffixFlag *)
    repalloc(Conf->CompoundAffixFlags,
       Conf->mCompoundAffixFlag * sizeof(CompoundAffixFlag));
  }
  else
  {
   Conf->mCompoundAffixFlag = 10;
   Conf->CompoundAffixFlags = (CompoundAffixFlag *)
    tmpalloc(Conf->mCompoundAffixFlag * sizeof(CompoundAffixFlag));
  }
 }

 newValue = Conf->CompoundAffixFlags + Conf->nCompoundAffixFlag;

 setCompoundAffixFlagValue(Conf, newValue, sbuf, val);

 Conf->usecompound = true;
 Conf->nCompoundAffixFlag++;
}

/*
 * Returns a set of affix parameters which correspondence to the set of affix
 * flags s.
 */

static int
getCompoundAffixFlagValue(IspellDict *Conf, const char *s)
{
 uint32  flag = 0;
 CompoundAffixFlag *found,
    key;
 char  sflag[BUFSIZ];
 const char *flagcur;

 if (Conf->nCompoundAffixFlag == 0)
  return 0;

 flagcur = s;
 while (*flagcur)
 {
  getNextFlagFromString(Conf, &flagcur, sflag);
  setCompoundAffixFlagValue(Conf, &key, sflag, 0);

  found = (CompoundAffixFlag *)
   bsearch(&key, Conf->CompoundAffixFlags,
     Conf->nCompoundAffixFlag, sizeof(CompoundAffixFlag),
     cmpcmdflag);
  if (found != NULL)
   flag |= found->value;
 }

 return flag;
}

/*
 * Returns a flag set using the s parameter.
 *
 * If Conf->useFlagAliases is true then the s parameter is index of the
 * Conf->AffixData array and function returns its entry.
 * Else function returns the s parameter.
 */

static const char *
getAffixFlagSet(IspellDict *Conf, char *s)
{
 if (Conf->useFlagAliases && *s != '\0')
 {
  int   curaffix;
  char    *end;

  errno = 0;
  curaffix = strtol(s, &end, 10);
  if (s == end || errno == ERANGE)
   ereport(ERROR,
     (errcode(ERRCODE_CONFIG_FILE_ERROR),
      errmsg("invalid affix alias \"%s\"", s)));

  if (curaffix > 0 && curaffix < Conf->nAffixData)

   /*
    * Do not subtract 1 from curaffix because empty string was added
    * in NIImportOOAffixes
 */

   return Conf->AffixData[curaffix];
  else if (curaffix > Conf->nAffixData)
   ereport(ERROR,
     (errcode(ERRCODE_CONFIG_FILE_ERROR),
      errmsg("invalid affix alias \"%s\"", s)));
  return VoidString;
 }
 else
  return s;
}

/*
 * Import an affix file that follows MySpell or Hunspell format.
 *
 * Conf: current dictionary.
 * filename: path to the .affix file.
 */

static void
NIImportOOAffixes(IspellDict *Conf, const char *filename)
{
 char  type[BUFSIZ],
      *ptype = NULL;
 char  sflag[BUFSIZ];
 char  mask[BUFSIZ],
      *pmask;
 char  find[BUFSIZ],
      *pfind;
 char  repl[BUFSIZ],
      *prepl;
 bool  isSuffix = false;
 int   naffix = 0,
    curaffix = 0;
 int   sflaglen = 0;
 char  flagflags = 0;
 tsearch_readline_state trst;
 char    *recoded;

 /* read file to find any flag */
 Conf->usecompound = false;
 Conf->useFlagAliases = false;
 Conf->flagMode = FM_CHAR;

 if (!tsearch_readline_begin(&trst, filename))
  ereport(ERROR,
    (errcode(ERRCODE_CONFIG_FILE_ERROR),
     errmsg("could not open affix file \"%s\": %m",
      filename)));

 while ((recoded = tsearch_readline(&trst)) != NULL)
 {
  if (*recoded == '\0' || isspace((unsigned char) *recoded) || t_iseq(recoded, '#'))
  {
   pfree(recoded);
   continue;
  }

  if (STRNCMP(recoded, "COMPOUNDFLAG") == 0)
   addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDFLAG"),
           FF_COMPOUNDFLAG);
  else if (STRNCMP(recoded, "COMPOUNDBEGIN") == 0)
   addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDBEGIN"),
           FF_COMPOUNDBEGIN);
  else if (STRNCMP(recoded, "COMPOUNDLAST") == 0)
   addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDLAST"),
           FF_COMPOUNDLAST);
  /* COMPOUNDLAST and COMPOUNDEND are synonyms */
  else if (STRNCMP(recoded, "COMPOUNDEND") == 0)
   addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDEND"),
           FF_COMPOUNDLAST);
  else if (STRNCMP(recoded, "COMPOUNDMIDDLE") == 0)
   addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDMIDDLE"),
           FF_COMPOUNDMIDDLE);
  else if (STRNCMP(recoded, "ONLYINCOMPOUND") == 0)
   addCompoundAffixFlagValue(Conf, recoded + strlen("ONLYINCOMPOUND"),
           FF_COMPOUNDONLY);
  else if (STRNCMP(recoded, "COMPOUNDPERMITFLAG") == 0)
   addCompoundAffixFlagValue(Conf,
           recoded + strlen("COMPOUNDPERMITFLAG"),
           FF_COMPOUNDPERMITFLAG);
  else if (STRNCMP(recoded, "COMPOUNDFORBIDFLAG") == 0)
   addCompoundAffixFlagValue(Conf,
           recoded + strlen("COMPOUNDFORBIDFLAG"),
           FF_COMPOUNDFORBIDFLAG);
  else if (STRNCMP(recoded, "FLAG") == 0)
  {
   char    *s = recoded + strlen("FLAG");

   while (*s && isspace((unsigned char) *s))
    s += pg_mblen_cstr(s);

   if (*s)
   {
    if (STRNCMP(s, "long") == 0)
     Conf->flagMode = FM_LONG;
    else if (STRNCMP(s, "num") == 0)
     Conf->flagMode = FM_NUM;
    else if (STRNCMP(s, "default") != 0)
     ereport(ERROR,
       (errcode(ERRCODE_CONFIG_FILE_ERROR),
        errmsg("Ispell dictionary supports only "
         "\"default\", \"long\", "
         "and \"num\" flag values")));
   }
  }

  pfree(recoded);
 }
 tsearch_readline_end(&trst);

 if (Conf->nCompoundAffixFlag > 1)
  qsort(Conf->CompoundAffixFlags, Conf->nCompoundAffixFlag,
     sizeof(CompoundAffixFlag), cmpcmdflag);

 if (!tsearch_readline_begin(&trst, filename))
  ereport(ERROR,
    (errcode(ERRCODE_CONFIG_FILE_ERROR),
     errmsg("could not open affix file \"%s\": %m",
      filename)));

 while ((recoded = tsearch_readline(&trst)) != NULL)
 {
  int   fields_read;

  if (*recoded == '\0' || isspace((unsigned char) *recoded) || t_iseq(recoded, '#'))
   goto nextline;

  fields_read = parse_ooaffentry(recoded, type, sflag, find, repl, mask);

  if (ptype)
   pfree(ptype);
  ptype = lowerstr_ctx(Conf, type);

  /* First try to parse AF parameter (alias compression) */
  if (STRNCMP(ptype, "af") == 0)
  {
   /* First line is the number of aliases */
   if (!Conf->useFlagAliases)
   {
    Conf->useFlagAliases = true;
    naffix = atoi(sflag);
    if (naffix <= 0)
     ereport(ERROR,
       (errcode(ERRCODE_CONFIG_FILE_ERROR),
        errmsg("invalid number of flag vector aliases")));

    /* Also reserve place for empty flag set */
    naffix++;

    Conf->AffixData = (const char **) palloc0(naffix * sizeof(char *));
    Conf->lenAffixData = Conf->nAffixData = naffix;

    /* Add empty flag set into AffixData */
    Conf->AffixData[curaffix] = VoidString;
    curaffix++;
   }
   /* Other lines are aliases */
   else
   {
    if (curaffix < naffix)
    {
     Conf->AffixData[curaffix] = cpstrdup(Conf, sflag);
     curaffix++;
    }
    else
     ereport(ERROR,
       (errcode(ERRCODE_CONFIG_FILE_ERROR),
        errmsg("number of aliases exceeds specified number %d",
         naffix - 1)));
   }
   goto nextline;
  }
  /* Else try to parse prefixes and suffixes */
  if (fields_read < 4 ||
   (STRNCMP(ptype, "sfx") != 0 && STRNCMP(ptype, "pfx") != 0))
   goto nextline;

  sflaglen = strlen(sflag);
  if (sflaglen == 0
   || (sflaglen > 1 && Conf->flagMode == FM_CHAR)
   || (sflaglen > 2 && Conf->flagMode == FM_LONG))
   goto nextline;

  /*--------
   * Affix header. For example:
   * SFX \ N 1
   *--------
 */

  if (fields_read == 4)
  {
   isSuffix = (STRNCMP(ptype, "sfx") == 0);
   if (t_iseq(find, 'y') || t_iseq(find, 'Y'))
    flagflags = FF_CROSSPRODUCT;
   else
    flagflags = 0;
  }
  /*--------
   * Affix fields. For example:
   * SFX \   0 Y/L [^Y]
   *--------
 */

  else
  {
   char    *ptr;
   int   aflg = 0;

   /* Get flags after '/' (flags are case sensitive) */
   if ((ptr = strchr(repl, '/')) != NULL)
    aflg |= getCompoundAffixFlagValue(Conf,
              getAffixFlagSet(Conf,
                  ptr + 1));
   /* Get lowercased version of string before '/' */
   prepl = lowerstr_ctx(Conf, repl);
   if ((ptr = strchr(prepl, '/')) != NULL)
    *ptr = '\0';
   pfind = lowerstr_ctx(Conf, find);
   pmask = lowerstr_ctx(Conf, mask);
   if (t_iseq(find, '0'))
    *pfind = '\0';
   if (t_iseq(repl, '0'))
    *prepl = '\0';

   NIAddAffix(Conf, sflag, flagflags | aflg, pmask, pfind, prepl,
        isSuffix ? FF_SUFFIX : FF_PREFIX);
   pfree(prepl);
   pfree(pfind);
   pfree(pmask);
  }

nextline:
  pfree(recoded);
 }

 tsearch_readline_end(&trst);
 if (ptype)
  pfree(ptype);
}

/*
 * import affixes
 *
 * Note caller must already have applied get_tsearch_config_filename
 *
 * This function is responsible for parsing ispell ("old format") affix files.
 * If we realize that the file contains new-format commands, we pass off the
 * work to NIImportOOAffixes(), which will re-read the whole file.
 */

void
NIImportAffixes(IspellDict *Conf, const char *filename)
{
 char    *pstr = NULL;
 char  flag[BUFSIZ];
 char  mask[BUFSIZ];
 char  find[BUFSIZ];
 char  repl[BUFSIZ];
 char    *s;
 bool  suffixes = false;
 bool  prefixes = false;
 char  flagflags = 0;
 tsearch_readline_state trst;
 bool  oldformat = false;
 char    *recoded = NULL;

 if (!tsearch_readline_begin(&trst, filename))
  ereport(ERROR,
    (errcode(ERRCODE_CONFIG_FILE_ERROR),
     errmsg("could not open affix file \"%s\": %m",
      filename)));

 Conf->usecompound = false;
 Conf->useFlagAliases = false;
 Conf->flagMode = FM_CHAR;

 while ((recoded = tsearch_readline(&trst)) != NULL)
 {
  pstr = str_tolower(recoded, strlen(recoded), DEFAULT_COLLATION_OID);

  /* Skip comments and empty lines */
  if (*pstr == '#' || *pstr == '\n')
   goto nextline;

  if (STRNCMP(pstr, "compoundwords") == 0)
  {
   /* Find case-insensitive L flag in non-lowercased string */
   s = findchar2(recoded, 'l''L');
   if (s)
   {
    while (*s && !isspace((unsigned char) *s))
     s += pg_mblen_cstr(s);
    while (*s && isspace((unsigned char) *s))
     s += pg_mblen_cstr(s);

    if (*s && pg_mblen_cstr(s) == 1)
    {
     addCompoundAffixFlagValue(Conf, s, FF_COMPOUNDFLAG);
     Conf->usecompound = true;
    }
    oldformat = true;
    goto nextline;
   }
  }
  if (STRNCMP(pstr, "suffixes") == 0)
  {
   suffixes = true;
   prefixes = false;
   oldformat = true;
   goto nextline;
  }
  if (STRNCMP(pstr, "prefixes") == 0)
  {
   suffixes = false;
   prefixes = true;
   oldformat = true;
   goto nextline;
  }
  if (STRNCMP(pstr, "flag") == 0)
  {
   s = recoded + 4/* we need non-lowercased string */
   flagflags = 0;

   while (*s && isspace((unsigned char) *s))
    s += pg_mblen_cstr(s);

   if (*s == '*')
   {
    flagflags |= FF_CROSSPRODUCT;
    s++;
   }
   else if (*s == '~')
   {
    flagflags |= FF_COMPOUNDONLY;
    s++;
   }

   if (*s == '\\')
    s++;

   /*
    * An old-format flag is a single ASCII character; we expect it to
    * be followed by EOL, whitespace, or ':'.  Otherwise this is a
    * new-format flag command.
 */

   if (*s && pg_mblen_cstr(s) == 1)
   {
    flag[0] = *s++;
    flag[1] = '\0';

    if (*s == '\0' || *s == '#' || *s == '\n' || *s == ':' ||
     isspace((unsigned char) *s))
    {
     oldformat = true;
     goto nextline;
    }
   }
   goto isnewformat;
  }
  if (STRNCMP(recoded, "COMPOUNDFLAG") == 0 ||
   STRNCMP(recoded, "COMPOUNDMIN") == 0 ||
   STRNCMP(recoded, "PFX") == 0 ||
   STRNCMP(recoded, "SFX") == 0)
   goto isnewformat;

  if ((!suffixes) && (!prefixes))
   goto nextline;

  if (!parse_affentry(pstr, mask, find, repl))
   goto nextline;

  NIAddAffix(Conf, flag, flagflags, mask, find, repl, suffixes ? FF_SUFFIX : FF_PREFIX);

nextline:
  pfree(recoded);
  pfree(pstr);
 }
 tsearch_readline_end(&trst);
 return;

isnewformat:
 if (oldformat)
  ereport(ERROR,
    (errcode(ERRCODE_CONFIG_FILE_ERROR),
     errmsg("affix file contains both old-style and new-style commands")));
 tsearch_readline_end(&trst);

 NIImportOOAffixes(Conf, filename);
}

/*
 * Merges two affix flag sets and stores a new affix flag set into
 * Conf->AffixData.
 *
 * Returns index of a new affix flag set.
 */

static int
MergeAffix(IspellDict *Conf, int a1, int a2)
{
 const char **ptr;

 Assert(a1 < Conf->nAffixData && a2 < Conf->nAffixData);

 /* Do not merge affix flags if one of affix flags is empty */
 if (*Conf->AffixData[a1] == '\0')
  return a2;
 else if (*Conf->AffixData[a2] == '\0')
  return a1;

 /* Double the size of AffixData if there's not enough space */
 if (Conf->nAffixData + 1 >= Conf->lenAffixData)
 {
  Conf->lenAffixData *= 2;
  Conf->AffixData = (const char **) repalloc(Conf->AffixData,
               sizeof(char *) * Conf->lenAffixData);
 }

 ptr = Conf->AffixData + Conf->nAffixData;
 if (Conf->flagMode == FM_NUM)
 {
  char    *p = cpalloc(strlen(Conf->AffixData[a1]) +
        strlen(Conf->AffixData[a2]) +
        1 /* comma */ + 1 /* \0 */ );

  sprintf(p, "%s,%s", Conf->AffixData[a1], Conf->AffixData[a2]);
  *ptr = p;
 }
 else
 {
  char    *p = cpalloc(strlen(Conf->AffixData[a1]) +
        strlen(Conf->AffixData[a2]) +
        1 /* \0 */ );

  sprintf(p, "%s%s", Conf->AffixData[a1], Conf->AffixData[a2]);
  *ptr = p;
 }
 ptr++;
 *ptr = NULL;
 Conf->nAffixData++;

 return Conf->nAffixData - 1;
}

/*
 * Returns a set of affix parameters which correspondence to the set of affix
 * flags with the given index.
 */

static uint32
makeCompoundFlags(IspellDict *Conf, int affix)
{
 Assert(affix < Conf->nAffixData);

 return (getCompoundAffixFlagValue(Conf, Conf->AffixData[affix]) &
   FF_COMPOUNDFLAGMASK);
}

/*
 * Makes a prefix tree for the given level.
 *
 * Conf: current dictionary.
 * low: lower index of the Conf->Spell array.
 * high: upper index of the Conf->Spell array.
 * level: current prefix tree level.
 */

static SPNode *
mkSPNode(IspellDict *Conf, int low, int high, int level)
{
 int   i;
 int   nchar = 0;
 char  lastchar = '\0';
 SPNode    *rs;
 SPNodeData *data;
 int   lownew = low;

 for (i = low; i < high; i++)
  if (Conf->Spell[i]->p.d.len > level && lastchar != Conf->Spell[i]->word[level])
  {
   nchar++;
   lastchar = Conf->Spell[i]->word[level];
  }

 if (!nchar)
  return NULL;

 rs = (SPNode *) cpalloc0(SPNHDRSZ + nchar * sizeof(SPNodeData));
 rs->length = nchar;
 data = rs->data;

 lastchar = '\0';
 for (i = low; i < high; i++)
  if (Conf->Spell[i]->p.d.len > level)
  {
   if (lastchar != Conf->Spell[i]->word[level])
   {
    if (lastchar)
    {
     /* Next level of the prefix tree */
     data->node = mkSPNode(Conf, lownew, i, level + 1);
     lownew = i;
     data++;
    }
    lastchar = Conf->Spell[i]->word[level];
   }
   data->val = ((uint8 *) (Conf->Spell[i]->word))[level];
   if (Conf->Spell[i]->p.d.len == level + 1)
   {
    bool  clearCompoundOnly = false;

    if (data->isword && data->affix != Conf->Spell[i]->p.d.affix)
    {
     /*
      * MergeAffix called a few times. If one of word is
      * allowed to be in compound word and another isn't, then
      * clear FF_COMPOUNDONLY flag.
 */


     clearCompoundOnly = (FF_COMPOUNDONLY & data->compoundflag
           & makeCompoundFlags(Conf, Conf->Spell[i]->p.d.affix))
      ? false : true;
     data->affix = MergeAffix(Conf, data->affix, Conf->Spell[i]->p.d.affix);
    }
    else
     data->affix = Conf->Spell[i]->p.d.affix;
    data->isword = 1;

    data->compoundflag = makeCompoundFlags(Conf, data->affix);

    if ((data->compoundflag & FF_COMPOUNDONLY) &&
     (data->compoundflag & FF_COMPOUNDFLAG) == 0)
     data->compoundflag |= FF_COMPOUNDFLAG;

    if (clearCompoundOnly)
     data->compoundflag &= ~FF_COMPOUNDONLY;
   }
  }

 /* Next level of the prefix tree */
 data->node = mkSPNode(Conf, lownew, high, level + 1);

 return rs;
}

/*
 * Builds the Conf->Dictionary tree and AffixData from the imported dictionary
 * and affixes.
 */

void
NISortDictionary(IspellDict *Conf)
{
 int   i;
 int   naffix;
 int   curaffix;

 /* compress affixes */

 /*
  * If we use flag aliases then we need to use Conf->AffixData filled in
  * the NIImportOOAffixes().
 */

 if (Conf->useFlagAliases)
 {
  for (i = 0; i < Conf->nspell; i++)
  {
   char    *end;

   if (*Conf->Spell[i]->p.flag != '\0')
   {
    errno = 0;
    curaffix = strtol(Conf->Spell[i]->p.flag, &end, 10);
    if (Conf->Spell[i]->p.flag == end || errno == ERANGE)
     ereport(ERROR,
       (errcode(ERRCODE_CONFIG_FILE_ERROR),
        errmsg("invalid affix alias \"%s\"",
         Conf->Spell[i]->p.flag)));
    if (curaffix < 0 || curaffix >= Conf->nAffixData)
     ereport(ERROR,
       (errcode(ERRCODE_CONFIG_FILE_ERROR),
        errmsg("invalid affix alias \"%s\"",
         Conf->Spell[i]->p.flag)));
    if (*end != '\0' && !isdigit((unsigned char) *end) && !isspace((unsigned char) *end))
     ereport(ERROR,
       (errcode(ERRCODE_CONFIG_FILE_ERROR),
        errmsg("invalid affix alias \"%s\"",
         Conf->Spell[i]->p.flag)));
   }
   else
   {
    /*
     * If Conf->Spell[i]->p.flag is empty, then get empty value of
     * Conf->AffixData (0 index).
 */

    curaffix = 0;
   }

   Conf->Spell[i]->p.d.affix = curaffix;
   Conf->Spell[i]->p.d.len = strlen(Conf->Spell[i]->word);
  }
 }
 /* Otherwise fill Conf->AffixData here */
 else
 {
  /* Count the number of different flags used in the dictionary */
  qsort(Conf->Spell, Conf->nspell, sizeof(SPELL *),
     cmpspellaffix);

  naffix = 0;
  for (i = 0; i < Conf->nspell; i++)
  {
   if (i == 0 ||
    strcmp(Conf->Spell[i]->p.flag, Conf->Spell[i - 1]->p.flag) != 0)
    naffix++;
  }

  /*
   * Fill in Conf->AffixData with the affixes that were used in the
   * dictionary. Replace textual flag-field of Conf->Spell entries with
   * indexes into Conf->AffixData array.
 */

  Conf->AffixData = (const char **) palloc0(naffix * sizeof(const char *));

  curaffix = -1;
  for (i = 0; i < Conf->nspell; i++)
  {
   if (i == 0 ||
    strcmp(Conf->Spell[i]->p.flag, Conf->AffixData[curaffix]) != 0)
   {
    curaffix++;
    Assert(curaffix < naffix);
    Conf->AffixData[curaffix] = cpstrdup(Conf,
              Conf->Spell[i]->p.flag);
   }

   Conf->Spell[i]->p.d.affix = curaffix;
   Conf->Spell[i]->p.d.len = strlen(Conf->Spell[i]->word);
  }

  Conf->lenAffixData = Conf->nAffixData = naffix;
 }

 /* Start build a prefix tree */
 qsort(Conf->Spell, Conf->nspell, sizeof(SPELL *), cmpspell);
 Conf->Dictionary = mkSPNode(Conf, 0, Conf->nspell, 0);
}

/*
 * Makes a prefix tree for the given level using the repl string of an affix
 * rule. Affixes with empty replace string do not include in the prefix tree.
 * This affixes are included by mkVoidAffix().
 *
 * Conf: current dictionary.
 * low: lower index of the Conf->Affix array.
 * high: upper index of the Conf->Affix array.
 * level: current prefix tree level.
 * type: FF_SUFFIX or FF_PREFIX.
 */

static AffixNode *
mkANode(IspellDict *Conf, int low, int high, int level, int type)
{
 int   i;
 int   nchar = 0;
 uint8  lastchar = '\0';
 AffixNode  *rs;
 AffixNodeData *data;
 int   lownew = low;
 int   naff;
 AFFIX   **aff;

 for (i = low; i < high; i++)
  if (Conf->Affix[i].replen > level && lastchar != GETCHAR(Conf->Affix + i, level, type))
  {
   nchar++;
   lastchar = GETCHAR(Conf->Affix + i, level, type);
  }

 if (!nchar)
  return NULL;

 aff = (AFFIX **) tmpalloc(sizeof(AFFIX *) * (high - low + 1));
 naff = 0;

 rs = (AffixNode *) cpalloc0(ANHRDSZ + nchar * sizeof(AffixNodeData));
 rs->length = nchar;
 data = rs->data;

 lastchar = '\0';
 for (i = low; i < high; i++)
  if (Conf->Affix[i].replen > level)
  {
   if (lastchar != GETCHAR(Conf->Affix + i, level, type))
   {
    if (lastchar)
    {
     /* Next level of the prefix tree */
     data->node = mkANode(Conf, lownew, i, level + 1, type);
     if (naff)
     {
      data->naff = naff;
      data->aff = (AFFIX **) cpalloc(sizeof(AFFIX *) * naff);
      memcpy(data->aff, aff, sizeof(AFFIX *) * naff);
      naff = 0;
     }
     data++;
     lownew = i;
    }
    lastchar = GETCHAR(Conf->Affix + i, level, type);
   }
   data->val = GETCHAR(Conf->Affix + i, level, type);
   if (Conf->Affix[i].replen == level + 1)
   {     /* affix stopped */
    aff[naff++] = Conf->Affix + i;
   }
  }

 /* Next level of the prefix tree */
 data->node = mkANode(Conf, lownew, high, level + 1, type);
 if (naff)
 {
  data->naff = naff;
  data->aff = (AFFIX **) cpalloc(sizeof(AFFIX *) * naff);
  memcpy(data->aff, aff, sizeof(AFFIX *) * naff);
  naff = 0;
 }

 pfree(aff);

 return rs;
}

/*
 * Makes the root void node in the prefix tree. The root void node is created
 * for affixes which have empty replace string ("repl" field).
 */

static void
mkVoidAffix(IspellDict *Conf, bool issuffix, int startsuffix)
{
 int   i,
    cnt = 0;
 int   start = (issuffix) ? startsuffix : 0;
 int   end = (issuffix) ? Conf->naffixes : startsuffix;
 AffixNode  *Affix = (AffixNode *) palloc0(ANHRDSZ + sizeof(AffixNodeData));

 Affix->length = 1;
 Affix->isvoid = 1;

 if (issuffix)
 {
  Affix->data->node = Conf->Suffix;
  Conf->Suffix = Affix;
 }
 else
 {
  Affix->data->node = Conf->Prefix;
  Conf->Prefix = Affix;
 }

 /* Count affixes with empty replace string */
 for (i = start; i < end; i++)
  if (Conf->Affix[i].replen == 0)
   cnt++;

 /* There is not affixes with empty replace string */
 if (cnt == 0)
  return;

 Affix->data->aff = (AFFIX **) cpalloc(sizeof(AFFIX *) * cnt);
 Affix->data->naff = (uint32) cnt;

 cnt = 0;
 for (i = start; i < end; i++)
  if (Conf->Affix[i].replen == 0)
  {
   Affix->data->aff[cnt] = Conf->Affix + i;
   cnt++;
  }
}

/*
 * Checks if the affixflag is used by dictionary. Conf->AffixData does not
 * contain affixflag if this flag is not used actually by the .dict file.
 *
 * Conf: current dictionary.
 * affixflag: affix flag.
 *
 * Returns true if the Conf->AffixData array contains affixflag, otherwise
 * returns false.
 */

static bool
isAffixInUse(IspellDict *Conf, const char *affixflag)
{
 int   i;

 for (i = 0; i < Conf->nAffixData; i++)
  if (IsAffixFlagInUse(Conf, i, affixflag))
   return true;

 return false;
}

/*
 * Builds Conf->Prefix and Conf->Suffix trees from the imported affixes.
 */

void
NISortAffixes(IspellDict *Conf)
{
 AFFIX    *Affix;
 size_t  i;
 CMPDAffix  *ptr;
 int   firstsuffix = Conf->naffixes;

 if (Conf->naffixes == 0)
  return;

 /* Store compound affixes in the Conf->CompoundAffix array */
 if (Conf->naffixes > 1)
  qsort(Conf->Affix, Conf->naffixes, sizeof(AFFIX), cmpaffix);
 Conf->CompoundAffix = ptr = (CMPDAffix *) palloc(sizeof(CMPDAffix) * Conf->naffixes);
 ptr->affix = NULL;

 for (i = 0; i < Conf->naffixes; i++)
 {
  Affix = &(((AFFIX *) Conf->Affix)[i]);
  if (Affix->type == FF_SUFFIX && i < firstsuffix)
   firstsuffix = i;

  if ((Affix->flagflags & FF_COMPOUNDFLAG) && Affix->replen > 0 &&
   isAffixInUse(Conf, Affix->flag))
  {
   bool  issuffix = (Affix->type == FF_SUFFIX);

   if (ptr == Conf->CompoundAffix ||
    issuffix != (ptr - 1)->issuffix ||
    strbncmp((const unsigned char *) (ptr - 1)->affix,
       (const unsigned char *) Affix->repl,
       (ptr - 1)->len))
   {
    /* leave only unique and minimal suffixes */
    ptr->affix = Affix->repl;
    ptr->len = Affix->replen;
    ptr->issuffix = issuffix;
    ptr++;
   }
  }
 }
 ptr->affix = NULL;
 Conf->CompoundAffix = (CMPDAffix *) repalloc(Conf->CompoundAffix, sizeof(CMPDAffix) * (ptr - Conf->CompoundAffix + 1));

 /* Start build a prefix tree */
 Conf->Prefix = mkANode(Conf, 0, firstsuffix, 0, FF_PREFIX);
 Conf->Suffix = mkANode(Conf, firstsuffix, Conf->naffixes, 0, FF_SUFFIX);
 mkVoidAffix(Conf, true, firstsuffix);
 mkVoidAffix(Conf, false, firstsuffix);
}

static AffixNodeData *
FindAffixes(AffixNode *node, const char *word, int wrdlen, int *level, int type)
{
 AffixNodeData *StopLow,
      *StopHigh,
      *StopMiddle;
 uint8 symbol;

 if (node->isvoid)
 {       /* search void affixes */
  if (node->data->naff)
   return node->data;
  node = node->data->node;
 }

 while (node && *level < wrdlen)
 {
  StopLow = node->data;
  StopHigh = node->data + node->length;
  while (StopLow < StopHigh)
  {
   StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
   symbol = GETWCHAR(word, wrdlen, *level, type);

   if (StopMiddle->val == symbol)
   {
    (*level)++;
    if (StopMiddle->naff)
     return StopMiddle;
    node = StopMiddle->node;
    break;
   }
   else if (StopMiddle->val < symbol)
    StopLow = StopMiddle + 1;
   else
    StopHigh = StopMiddle;
  }
  if (StopLow >= StopHigh)
   break;
 }
 return NULL;
}

/*
 * Checks to see if affix applies to word, transforms word if so.
 * The transformation consists of replacing Affix->replen leading or
 * trailing bytes with the Affix->find string.
 *
 * word: input word
 * len: length of input word
 * Affix: affix to consider
 * flagflags: context flags showing whether we are handling a compound word
 * newword: output buffer (MUST be of length 2 * MAXNORMLEN)
 * baselen: input/output argument
 *
 * If baselen isn't NULL, then *baselen is used to return the length of
 * the non-changed part of the word when applying a suffix, and is used
 * to detect whether the input contained only a prefix and suffix when
 * later applying a prefix.
 *
 * Returns newword on success, or NULL if the affix can't be applied.
 * On success, the modified word is stored into newword.
 */

static char *
CheckAffix(const char *word, size_t len, AFFIX *Affix, int flagflags, char *newword, int *baselen)
{
 size_t  keeplen,
    findlen;

 /*
  * Check compound allow flags
 */


 if (flagflags == 0)
 {
  if (Affix->flagflags & FF_COMPOUNDONLY)
   return NULL;
 }
 else if (flagflags & FF_COMPOUNDBEGIN)
 {
  if (Affix->flagflags & FF_COMPOUNDFORBIDFLAG)
   return NULL;
  if ((Affix->flagflags & FF_COMPOUNDBEGIN) == 0)
   if (Affix->type == FF_SUFFIX)
    return NULL;
 }
 else if (flagflags & FF_COMPOUNDMIDDLE)
 {
  if ((Affix->flagflags & FF_COMPOUNDMIDDLE) == 0 ||
   (Affix->flagflags & FF_COMPOUNDFORBIDFLAG))
   return NULL;
 }
 else if (flagflags & FF_COMPOUNDLAST)
 {
  if (Affix->flagflags & FF_COMPOUNDFORBIDFLAG)
   return NULL;
  if ((Affix->flagflags & FF_COMPOUNDLAST) == 0)
   if (Affix->type == FF_PREFIX)
    return NULL;
 }

 /*
  * Protect against output buffer overrun (len < Affix->replen would be
  * caller error, but check anyway)
 */

 Assert(len == strlen(word));
 if (len < Affix->replen)
  return NULL;
 keeplen = len - Affix->replen; /* how much of word we will keep */
 findlen = strlen(Affix->find);
 if (keeplen + findlen >= 2 * MAXNORMLEN)
  return NULL;

 /*
  * make replace pattern of affix
 */

 if (Affix->type == FF_SUFFIX)
 {
  memcpy(newword, word, keeplen);
  strcpy(newword + keeplen, Affix->find);
  if (baselen)   /* store length of non-changed part of word */
   *baselen = keeplen;
 }
 else
 {
  /*
   * if prefix is an all non-changed part's length then all word
   * contains only prefix and suffix, so out
 */

  if (baselen && *baselen + findlen <= Affix->replen)
   return NULL;
  memcpy(newword, Affix->find, findlen);
  strcpy(newword + findlen, word + Affix->replen);
 }

 /*
  * check resulting word
 */

 if (Affix->issimple)
  return newword;
 else if (Affix->isregis)
 {
  if (RS_execute(&(Affix->reg.regis), newword))
   return newword;
 }
 else
 {
  pg_wchar   *data;
  size_t  data_len;
  int   newword_len;

  /* Convert data string to wide characters */
  newword_len = strlen(newword);
  data = (pg_wchar *) palloc((newword_len + 1) * sizeof(pg_wchar));
  data_len = pg_mb2wchar_with_len(newword, data, newword_len);

  if (pg_regexec(Affix->reg.pregex, data, data_len,
        0, NULL, 0, NULL, 0) == REG_OKAY)
  {
   pfree(data);
   return newword;
  }
  pfree(data);
 }

 return NULL;
}

static int
addToResult(char **forms, char **cur, char *word)
{
 if (cur - forms >= MAX_NORM - 1)
  return 0;
 if (forms == cur || strcmp(word, *(cur - 1)) != 0)
 {
  *cur = pstrdup(word);
  *(cur + 1) = NULL;
  return 1;
 }

 return 0;
}

static char **
NormalizeSubWord(IspellDict *Conf, const char *word, int flag)
{
 AffixNodeData *suffix = NULL,
      *prefix = NULL;
 int   slevel = 0,
    plevel = 0;
 int   wrdlen = strlen(word),
    swrdlen;
 char   **forms;
 char   **cur;
 char  newword[2 * MAXNORMLEN] = "";
 char  pnewword[2 * MAXNORMLEN] = "";
 AffixNode  *snode = Conf->Suffix,
      *pnode;
 int   i,
    j;

 if (wrdlen > MAXNORMLEN)
  return NULL;
 cur = forms = (char **) palloc(MAX_NORM * sizeof(char *));
 *cur = NULL;


 /* Check that the word itself is normal form */
 if (FindWord(Conf, word, VoidString, flag))
 {
  *cur = pstrdup(word);
  cur++;
  *cur = NULL;
 }

 /* Find all other NORMAL forms of the 'word' (check only prefix) */
 pnode = Conf->Prefix;
 plevel = 0;
 while (pnode)
 {
  prefix = FindAffixes(pnode, word, wrdlen, &plevel, FF_PREFIX);
  if (!prefix)
   break;
  for (j = 0; j < prefix->naff; j++)
  {
   if (CheckAffix(word, wrdlen, prefix->aff[j], flag, newword, NULL))
   {
    /* prefix success */
    if (FindWord(Conf, newword, prefix->aff[j]->flag, flag))
     cur += addToResult(forms, cur, newword);
   }
  }
  pnode = prefix->node;
 }

 /*
  * Find all other NORMAL forms of the 'word' (check suffix and then
  * prefix)
 */

 while (snode)
 {
  int   baselen = 0;

  /* find possible suffix */
  suffix = FindAffixes(snode, word, wrdlen, &slevel, FF_SUFFIX);
  if (!suffix)
   break;
  /* foreach suffix check affix */
  for (i = 0; i < suffix->naff; i++)
  {
   if (CheckAffix(word, wrdlen, suffix->aff[i], flag, newword, &baselen))
   {
    /* suffix success */
    if (FindWord(Conf, newword, suffix->aff[i]->flag, flag))
     cur += addToResult(forms, cur, newword);

    /* now we will look changed word with prefixes */
    pnode = Conf->Prefix;
    plevel = 0;
    swrdlen = strlen(newword);
    while (pnode)
    {
     prefix = FindAffixes(pnode, newword, swrdlen, &plevel, FF_PREFIX);
     if (!prefix)
      break;
     for (j = 0; j < prefix->naff; j++)
     {
      if (CheckAffix(newword, swrdlen, prefix->aff[j], flag, pnewword, &baselen))
      {
       /* prefix success */
       const char *ff = (prefix->aff[j]->flagflags & suffix->aff[i]->flagflags & FF_CROSSPRODUCT) ?
        VoidString : prefix->aff[j]->flag;

       if (FindWord(Conf, pnewword, ff, flag))
        cur += addToResult(forms, cur, pnewword);
      }
     }
     pnode = prefix->node;
    }
   }
  }

  snode = suffix->node;
 }

 if (cur == forms)
 {
  pfree(forms);
  return NULL;
 }
 return forms;
}

typedef struct SplitVar
{
 int   nstem;
 int   lenstem;
 char   **stem;
 struct SplitVar *next;
} SplitVar;

static int
CheckCompoundAffixes(CMPDAffix **ptr, const char *word, int len, bool CheckInPlace)
{
 bool  issuffix;

 /* in case CompoundAffix is null: */
 if (*ptr == NULL)
  return -1;

 if (CheckInPlace)
 {
  while ((*ptr)->affix)
  {
   if (len > (*ptr)->len && strncmp((*ptr)->affix, word, (*ptr)->len) == 0)
   {
    len = (*ptr)->len;
    issuffix = (*ptr)->issuffix;
    (*ptr)++;
    return (issuffix) ? len : 0;
   }
   (*ptr)++;
  }
 }
 else
 {
  const char *affbegin;

  while ((*ptr)->affix)
  {
   if (len > (*ptr)->len && (affbegin = strstr(word, (*ptr)->affix)) != NULL)
   {
    len = (*ptr)->len + (affbegin - word);
    issuffix = (*ptr)->issuffix;
    (*ptr)++;
    return (issuffix) ? len : 0;
   }
   (*ptr)++;
  }
 }
 return -1;
}

static SplitVar *
CopyVar(SplitVar *s, int makedup)
{
 SplitVar   *v = (SplitVar *) palloc(sizeof(SplitVar));

 v->next = NULL;
 if (s)
 {
  int   i;

  v->lenstem = s->lenstem;
  v->stem = (char **) palloc(sizeof(char *) * v->lenstem);
  v->nstem = s->nstem;
  for (i = 0; i < s->nstem; i++)
   v->stem[i] = (makedup) ? pstrdup(s->stem[i]) : s->stem[i];
 }
 else
 {
  v->lenstem = 16;
  v->stem = (char **) palloc(sizeof(char *) * v->lenstem);
  v->nstem = 0;
 }
 return v;
}

static void
AddStem(SplitVar *v, char *word)
{
 if (v->nstem >= v->lenstem)
 {
  v->lenstem *= 2;
  v->stem = (char **) repalloc(v->stem, sizeof(char *) * v->lenstem);
 }

 v->stem[v->nstem] = word;
 v->nstem++;
}

static SplitVar *
SplitToVariants(IspellDict *Conf, SPNode *snode, SplitVar *orig, const char *word, int wordlen, int startpos, int minpos)
{
 SplitVar   *var = NULL;
 SPNodeData *StopLow,
      *StopHigh,
      *StopMiddle = NULL;
 SPNode    *node = (snode) ? snode : Conf->Dictionary;
 int   level = (snode) ? minpos : startpos; /* recursive
 * minpos==level */

 int   lenaff;
 CMPDAffix  *caff;
 char    *notprobed;
 int   compoundflag = 0;

 /* since this function recurses, it could be driven to stack overflow */
 check_stack_depth();

 notprobed = (char *) palloc(wordlen);
 memset(notprobed, 1, wordlen);
 var = CopyVar(orig, 1);

 while (level < wordlen)
 {
  /* find word with epenthetic or/and compound affix */
  caff = Conf->CompoundAffix;
  while (level > startpos && (lenaff = CheckCompoundAffixes(&caff, word + level, wordlen - level, (node) ? true : false)) >= 0)
  {
   /*
    * there is one of compound affixes, so check word for existings
 */

   char  buf[MAXNORMLEN];
   char   **subres;

   lenaff = level - startpos + lenaff;

   if (!notprobed[startpos + lenaff - 1])
    continue;

   if (level + lenaff - 1 <= minpos)
    continue;

   if (lenaff >= MAXNORMLEN)
    continue;  /* skip too big value */
   if (lenaff > 0)
    memcpy(buf, word + startpos, lenaff);
   buf[lenaff] = '\0';

   if (level == 0)
    compoundflag = FF_COMPOUNDBEGIN;
   else if (level == wordlen - 1)
    compoundflag = FF_COMPOUNDLAST;
   else
    compoundflag = FF_COMPOUNDMIDDLE;
   subres = NormalizeSubWord(Conf, buf, compoundflag);
   if (subres)
   {
    /* Yes, it was a word from dictionary */
    SplitVar   *new = CopyVar(var, 0);
    SplitVar   *ptr = var;
    char   **sptr = subres;

    notprobed[startpos + lenaff - 1] = 0;

    while (*sptr)
    {
     AddStem(new, *sptr);
     sptr++;
    }
    pfree(subres);

    while (ptr->next)
     ptr = ptr->next;
    ptr->next = SplitToVariants(Conf, NULL, new, word, wordlen, startpos + lenaff, startpos + lenaff);

    pfree(new->stem);
    pfree(new);
   }
  }

  if (!node)
   break;

  StopLow = node->data;
  StopHigh = node->data + node->length;
  while (StopLow < StopHigh)
  {
   StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
   if (StopMiddle->val == ((uint8 *) (word))[level])
    break;
   else if (StopMiddle->val < ((uint8 *) (word))[level])
    StopLow = StopMiddle + 1;
   else
    StopHigh = StopMiddle;
  }

  if (StopLow < StopHigh)
  {
   if (startpos == 0)
    compoundflag = FF_COMPOUNDBEGIN;
   else if (level == wordlen - 1)
    compoundflag = FF_COMPOUNDLAST;
   else
    compoundflag = FF_COMPOUNDMIDDLE;

   /* find infinitive */
   if (StopMiddle->isword &&
    (StopMiddle->compoundflag & compoundflag) &&
    notprobed[level])
   {
    /* ok, we found full compoundallowed word */
    if (level > minpos)
    {
     /* and its length more than minimal */
     if (wordlen == level + 1)
     {
      /* well, it was last word */
      AddStem(var, pnstrdup(word + startpos, wordlen - startpos));
      pfree(notprobed);
      return var;
     }
     else
     {
      /* then we will search more big word at the same point */
      SplitVar   *ptr = var;

      while (ptr->next)
       ptr = ptr->next;
      ptr->next = SplitToVariants(Conf, node, var, word, wordlen, startpos, level);
      /* we can find next word */
      level++;
      AddStem(var, pnstrdup(word + startpos, level - startpos));
      node = Conf->Dictionary;
      startpos = level;
      continue;
     }
    }
   }
   node = StopMiddle->node;
  }
  else
   node = NULL;
  level++;
 }

 AddStem(var, pnstrdup(word + startpos, wordlen - startpos));
 pfree(notprobed);
 return var;
}

static void
addNorm(TSLexeme **lres, TSLexeme **lcur, char *word, int flags, uint16 NVariant)
{
 if (*lres == NULL)
  *lcur = *lres = (TSLexeme *) palloc(MAX_NORM * sizeof(TSLexeme));

 if (*lcur - *lres < MAX_NORM - 1)
 {
  (*lcur)->lexeme = word;
  (*lcur)->flags = flags;
  (*lcur)->nvariant = NVariant;
  (*lcur)++;
  (*lcur)->lexeme = NULL;
 }
}

TSLexeme *
NINormalizeWord(IspellDict *Conf, const char *word)
{
 char   **res;
 TSLexeme   *lcur = NULL,
      *lres = NULL;
 uint16  NVariant = 1;

 res = NormalizeSubWord(Conf, word, 0);

 if (res)
 {
  char   **ptr = res;

  while (*ptr && (lcur - lres) < MAX_NORM)
  {
   addNorm(&lres, &lcur, *ptr, 0, NVariant++);
   ptr++;
  }
  pfree(res);
 }

 if (Conf->usecompound)
 {
  int   wordlen = strlen(word);
  SplitVar   *ptr,
       *var = SplitToVariants(Conf, NULL, NULL, word, wordlen, 0, -1);
  int   i;

  while (var)
  {
   if (var->nstem > 1)
   {
    char   **subres = NormalizeSubWord(Conf, var->stem[var->nstem - 1], FF_COMPOUNDLAST);

    if (subres)
    {
     char   **subptr = subres;

     while (*subptr)
     {
      for (i = 0; i < var->nstem - 1; i++)
      {
       addNorm(&lres, &lcur, (subptr == subres) ? var->stem[i] : pstrdup(var->stem[i]), 0, NVariant);
      }

      addNorm(&lres, &lcur, *subptr, 0, NVariant);
      subptr++;
      NVariant++;
     }

     pfree(subres);
     var->stem[0] = NULL;
     pfree(var->stem[var->nstem - 1]);
    }
   }

   for (i = 0; i < var->nstem && var->stem[i]; i++)
    pfree(var->stem[i]);
   ptr = var->next;
   pfree(var->stem);
   pfree(var);
   var = ptr;
  }
 }

 return lres;
}

Messung V0.5 in Prozent
C=94 H=94 G=93

¤ Dauer der Verarbeitung: 0.49 Sekunden  (vorverarbeitet am  2026-08-08) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=141584
#Domains=738142