store_aff_word(
    spellinfo_T	*spin,		/* spell info */
    char_u	*word,		/* basic word start */
    char_u	*afflist,	/* list of names of supported affixes */
    afffile_T	*affile,
    hashtab_T	*ht,
    hashtab_T	*xht,
    int		condit,		/* CONDIT_SUF et al. */
    int		flags,		/* flags for the word */
    char_u	*pfxlist,	/* list of prefix IDs */
    int		pfxlen)		/* nr of flags in "pfxlist" for prefixes, rest
				 * is compound flags */
{
    int		todo;
    hashitem_T	*hi;
    affheader_T	*ah;
    affentry_T	*ae;
    char_u	newword[MAXWLEN];
    int		retval = OK;
    int		i, j;
    char_u	*p;
    int		use_flags;
    char_u	*use_pfxlist;
    int		use_pfxlen;
    int		need_affix;
    char_u	store_afflist[MAXWLEN];
    char_u	pfx_pfxlist[MAXWLEN];
    size_t	wordlen = STRLEN(word);
    int		use_condit;

    todo = (int)ht->ht_used;
    for (hi = ht->ht_array; todo > 0 && retval == OK; ++hi)
    {
	if (!HASHITEM_EMPTY(hi))
	{
	    --todo;
	    ah = HI2AH(hi);

	    /* Check that the affix combines, if required, and that the word
	     * supports this affix. */
	    if (((condit & CONDIT_COMB) == 0 || ah->ah_combine)
		    && flag_in_afflist(affile->af_flagtype, afflist,
								 ah->ah_flag))
	    {
		/* Loop over all affix entries with this name. */
		for (ae = ah->ah_first; ae != NULL; ae = ae->ae_next)
		{
		    /* Check the condition.  It's not logical to match case
		     * here, but it is required for compatibility with
		     * Myspell.
		     * Another requirement from Myspell is that the chop
		     * string is shorter than the word itself.
		     * For prefixes, when "PFXPOSTPONE" was used, only do
		     * prefixes with a chop string and/or flags.
		     * When a previously added affix had CIRCUMFIX this one
		     * must have it too, if it had not then this one must not
		     * have one either. */
		    if ((xht != NULL || !affile->af_pfxpostpone
				|| ae->ae_chop != NULL
				|| ae->ae_flags != NULL)
			    && (ae->ae_chop == NULL
				|| STRLEN(ae->ae_chop) < wordlen)
			    && (ae->ae_prog == NULL
				|| vim_regexec_prog(&ae->ae_prog, FALSE,
							    word, (colnr_T)0))
			    && (((condit & CONDIT_CFIX) == 0)
				== ((condit & CONDIT_AFF) == 0
				    || ae->ae_flags == NULL
				    || !flag_in_afflist(affile->af_flagtype,
					ae->ae_flags, affile->af_circumfix))))
		    {
			/* Match.  Remove the chop and add the affix. */
			if (xht == NULL)
			{
			    /* prefix: chop/add at the start of the word */
			    if (ae->ae_add == NULL)
				*newword = NUL;
			    else
				vim_strncpy(newword, ae->ae_add, MAXWLEN - 1);
			    p = word;
			    if (ae->ae_chop != NULL)
			    {
				/* Skip chop string. */
#ifdef FEAT_MBYTE
				if (has_mbyte)
				{
				    i = mb_charlen(ae->ae_chop);
				    for ( ; i > 0; --i)
					mb_ptr_adv(p);
				}
				else
#endif
				    p += STRLEN(ae->ae_chop);
			    }
			    STRCAT(newword, p);
			}
			else
			{
			    /* suffix: chop/add at the end of the word */
			    vim_strncpy(newword, word, MAXWLEN - 1);
			    if (ae->ae_chop != NULL)
			    {
				/* Remove chop string. */
				p = newword + STRLEN(newword);
				i = (int)MB_CHARLEN(ae->ae_chop);
				for ( ; i > 0; --i)
				    mb_ptr_back(newword, p);
				*p = NUL;
			    }
			    if (ae->ae_add != NULL)
				STRCAT(newword, ae->ae_add);
			}

			use_flags = flags;
			use_pfxlist = pfxlist;
			use_pfxlen = pfxlen;
			need_affix = FALSE;
			use_condit = condit | CONDIT_COMB | CONDIT_AFF;
			if (ae->ae_flags != NULL)
			{
			    /* Extract flags from the affix list. */
			    use_flags |= get_affix_flags(affile, ae->ae_flags);

			    if (affile->af_needaffix != 0 && flag_in_afflist(
					affile->af_flagtype, ae->ae_flags,
							affile->af_needaffix))
				need_affix = TRUE;

			    /* When there is a CIRCUMFIX flag the other affix
			     * must also have it and we don't add the word
			     * with one affix. */
			    if (affile->af_circumfix != 0 && flag_in_afflist(
					affile->af_flagtype, ae->ae_flags,
							affile->af_circumfix))
			    {
				use_condit |= CONDIT_CFIX;
				if ((condit & CONDIT_CFIX) == 0)
				    need_affix = TRUE;
			    }

			    if (affile->af_pfxpostpone
						|| spin->si_compflags != NULL)
			    {
				if (affile->af_pfxpostpone)
				    /* Get prefix IDS from the affix list. */
				    use_pfxlen = get_pfxlist(affile,
						 ae->ae_flags, store_afflist);
				else
				    use_pfxlen = 0;
				use_pfxlist = store_afflist;

				/* Combine the prefix IDs. Avoid adding the
				 * same ID twice. */
				for (i = 0; i < pfxlen; ++i)
				{
				    for (j = 0; j < use_pfxlen; ++j)
					if (pfxlist[i] == use_pfxlist[j])
					    break;
				    if (j == use_pfxlen)
					use_pfxlist[use_pfxlen++] = pfxlist[i];
				}

				if (spin->si_compflags != NULL)
				    /* Get compound IDS from the affix list. */
				    get_compflags(affile, ae->ae_flags,
						  use_pfxlist + use_pfxlen);

				/* Combine the list of compound flags.
				 * Concatenate them to the prefix IDs list.
				 * Avoid adding the same ID twice. */
				for (i = pfxlen; pfxlist[i] != NUL; ++i)
				{
				    for (j = use_pfxlen;
						   use_pfxlist[j] != NUL; ++j)
					if (pfxlist[i] == use_pfxlist[j])
					    break;
				    if (use_pfxlist[j] == NUL)
				    {
					use_pfxlist[j++] = pfxlist[i];
					use_pfxlist[j] = NUL;
				    }
				}
			    }
			}

			/* Obey a "COMPOUNDFORBIDFLAG" of the affix: don't
			 * use the compound flags. */
			if (use_pfxlist != NULL && ae->ae_compforbid)
			{
			    vim_strncpy(pfx_pfxlist, use_pfxlist, use_pfxlen);
			    use_pfxlist = pfx_pfxlist;
			}

			/* When there are postponed prefixes... */
			if (spin->si_prefroot != NULL
				&& spin->si_prefroot->wn_sibling != NULL)
			{
			    /* ... add a flag to indicate an affix was used. */
			    use_flags |= WF_HAS_AFF;

			    /* ... don't use a prefix list if combining
			     * affixes is not allowed.  But do use the
			     * compound flags after them. */
			    if (!ah->ah_combine && use_pfxlist != NULL)
				use_pfxlist += use_pfxlen;
			}

			/* When compounding is supported and there is no
			 * "COMPOUNDPERMITFLAG" then forbid compounding on the
			 * side where the affix is applied. */
			if (spin->si_compflags != NULL && !ae->ae_comppermit)
			{
			    if (xht != NULL)
				use_flags |= WF_NOCOMPAFT;
			    else
				use_flags |= WF_NOCOMPBEF;
			}

			/* Store the modified word. */
			if (store_word(spin, newword, use_flags,
						 spin->si_region, use_pfxlist,
							  need_affix) == FAIL)
			    retval = FAIL;

			/* When added a prefix or a first suffix and the affix
			 * has flags may add a(nother) suffix.  RECURSIVE! */
			if ((condit & CONDIT_SUF) && ae->ae_flags != NULL)
			    if (store_aff_word(spin, newword, ae->ae_flags,
					affile, &affile->af_suff, xht,
					   use_condit & (xht == NULL
							? ~0 :  ~CONDIT_SUF),
				      use_flags, use_pfxlist, pfxlen) == FAIL)
				retval = FAIL;

			/* When added a suffix and combining is allowed also
			 * try adding a prefix additionally.  Both for the
			 * word flags and for the affix flags.  RECURSIVE! */
			if (xht != NULL && ah->ah_combine)
			{
			    if (store_aff_word(spin, newword,
					afflist, affile,
					xht, NULL, use_condit,
					use_flags, use_pfxlist,
					pfxlen) == FAIL
				    || (ae->ae_flags != NULL
					&& store_aff_word(spin, newword,
					    ae->ae_flags, affile,
					    xht, NULL, use_condit,
					    use_flags, use_pfxlist,
					    pfxlen) == FAIL))
				retval = FAIL;
			}
		    }
		}
	    }
	}
    }

    return retval;
}

#define BY_NOFLAGS	0	/* end of word without flags or region; for
				 * postponed prefix: no <pflags> */

#define BY_FLAGS	2	/* end of word, <flags> byte follows; for
				 * postponed prefix: <pflags> follows */

#define BY_FLAGS2	3	/* end of word, <flags> and <flags2> bytes
				 * follow; never used in prefix tree */

#define VIMSPELLMAGIC "VIMspell"  /* string at start of Vim spell file */

#define SN_COMPOUND	8	/* compound words section */

#define SN_SUGFILE	11	/* timestamp for .sug file */

#define SN_NOSPLITSUGS	14	/* don't split word for suggestions */

#define SN_INFO		15	/* info section */

#define SN_NOCOMPOUNDSUGS 16	/* don't compound for suggestions */

#define SN_END		255	/* end of sections */

#define SNF_REQUIRED	1	/* <sectionflags>: required section */

static int set_spell_finish(spelltab_T	*new_st);

static int write_spell_prefcond(FILE *fd, garray_T *gap);

static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *lenp);

static int read_region_section(FILE *fd, slang_T *slang, int len);

static int read_charflags_section(FILE *fd);

static int read_prefcond_section(FILE *fd, slang_T *lp);

static int read_rep_section(FILE *fd, garray_T *gap, short *first);

static int read_sal_section(FILE *fd, slang_T *slang);

static int read_words_section(FILE *fd, slang_T *lp, int len);

static int read_sofo_section(FILE *fd, slang_T *slang);

static int read_compound(FILE *fd, slang_T *slang, int len);

static int set_sofo(slang_T *lp, char_u *from, char_u *to);

static int *mb_str2wide(char_u *s);

static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt);

static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr);

static void spell_reload_one(char_u *fname, int added_word);

static void set_spell_charflags(char_u *flags, int cnt, char_u *upp);

static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp);

static void set_map_str(slang_T *lp, char_u *map);

static char *e_spell_trunc = N_("E758: Truncated spell file");

static char *e_affname = N_("Affix name too long in %s line %d: %s");

static char *e_affform = N_("E761: Format error in affix file FOL, LOW or UPP");

static char *msg_compressing = N_("Compressing word tree...");

#define MAXLINELEN  500		/* Maximum length in bytes of a line in a .aff
				   and .dic file. */


clear_node(wordnode_T *node)
{
    wordnode_T	*np;

    if (node != NULL)
	for (np = node; np != NULL; np = np->wn_sibling)
	{
	    np->wn_u1.index = 0;
	    np->wn_u2.wnode = NULL;

	    if (np->wn_byte != NUL)
		clear_node(np->wn_child);
	}
}

patch 8.0.0322: possible overflow with corrupted spell file

Problem:    Possible overflow with spell file where the tree length is
            corrupted.
Solution:   Check for an invalid length (suggested by shqking)

#define BY_NOFLAGS	0	/* end of word without flags or region; for
				 * postponed prefix: no <pflags> */

#define BY_INDEX	1	/* child is shared, index follows */

#define BY_FLAGS	2	/* end of word, <flags> byte follows; for
				 * postponed prefix: <pflags> follows */

#define SN_SUGFILE	11	/* timestamp for .sug file */

#define SN_NOSPLITSUGS	14	/* don't split word for suggestions */

#define SN_NOCOMPOUNDSUGS 16	/* don't compound for suggestions */

typedef struct afffile_S
{
    char_u	*af_enc;	/* "SET", normalized, alloc'ed string or NULL */
    int		af_flagtype;	/* AFT_CHAR, AFT_LONG, AFT_NUM or AFT_CAPLONG */
    unsigned	af_rare;	/* RARE ID for rare word */
    unsigned	af_keepcase;	/* KEEPCASE ID for keep-case word */
    unsigned	af_bad;		/* BAD ID for banned word */
    unsigned	af_needaffix;	/* NEEDAFFIX ID */
    unsigned	af_circumfix;	/* CIRCUMFIX ID */
    unsigned	af_needcomp;	/* NEEDCOMPOUND ID */
    unsigned	af_comproot;	/* COMPOUNDROOT ID */
    unsigned	af_compforbid;	/* COMPOUNDFORBIDFLAG ID */
    unsigned	af_comppermit;	/* COMPOUNDPERMITFLAG ID */
    unsigned	af_nosuggest;	/* NOSUGGEST ID */
    int		af_pfxpostpone;	/* postpone prefixes without chop string and
				   without flags */
    int		af_ignoreextra;	/* IGNOREEXTRA present */
    hashtab_T	af_pref;	/* hashtable for prefixes, affheader_T */
    hashtab_T	af_suff;	/* hashtable for suffixes, affheader_T */
    hashtab_T	af_comp;	/* hashtable for compound flags, compitem_T */
} afffile_T;

struct affentry_S
{
    affentry_T	*ae_next;	/* next affix with same name/number */
    char_u	*ae_chop;	/* text to chop off basic word (can be NULL) */
    char_u	*ae_add;	/* text to add to basic word (can be NULL) */
    char_u	*ae_flags;	/* flags on the affix (can be NULL) */
    char_u	*ae_cond;	/* condition (NULL for ".") */
    regprog_T	*ae_prog;	/* regexp program for ae_cond or NULL */
    char	ae_compforbid;	/* COMPOUNDFORBIDFLAG found */
    char	ae_comppermit;	/* COMPOUNDPERMITFLAG found */
}

# define AH_KEY_LEN 17		/* 2 x 8 bytes + NUL */

# define AH_KEY_LEN 7		/* 6 digits + NUL */

typedef struct affheader_S
{
    char_u	ah_key[AH_KEY_LEN]; /* key for hashtab == name of affix */
    unsigned	ah_flag;	/* affix name as number, uses "af_flagtype" */
    int		ah_newID;	/* prefix ID after renumbering; 0 if not used */
    int		ah_combine;	/* suffix may combine with prefix */
    int		ah_follows;	/* another affix block should be following */
    affentry_T	*ah_first;	/* first affix entry */
} affheader_T;


spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile)
{
    hashtab_T	ht;
    char_u	line[MAXLINELEN];
    char_u	*p;
    char_u	*afflist;
    char_u	store_afflist[MAXWLEN];
    int		pfxlen;
    int		need_affix;
    char_u	*dw;
    char_u	*pc;
    char_u	*w;
    int		l;
    hash_T	hash;
    hashitem_T	*hi;
    FILE	*fd;
    int		lnum = 1;
    int		non_ascii = 0;
    int		retval = OK;
    char_u	message[MAXLINELEN + MAXWLEN];
    int		flags;
    int		duplicate = 0;

    /*
     * Open the file.
     */
    fd = mch_fopen((char *)fname, "r");
    if (fd == NULL)
    {
	EMSG2(_(e_notopen), fname);
	return FAIL;
    }

    /* The hashtable is only used to detect duplicated words. */
    hash_init(&ht);

    vim_snprintf((char *)IObuff, IOSIZE,
				  _("Reading dictionary file %s ..."), fname);
    spell_message(spin, IObuff);

    /* start with a message for the first line */
    spin->si_msg_count = 999999;

    /* Read and ignore the first line: word count. */
    (void)vim_fgets(line, MAXLINELEN, fd);
    if (!vim_isdigit(*skipwhite(line)))
	EMSG2(_("E760: No word count in %s"), fname);

    /*
     * Read all the lines in the file one by one.
     * The words are converted to 'encoding' here, before being added to
     * the hashtable.
     */
    while (!vim_fgets(line, MAXLINELEN, fd) && !got_int)
    {
	line_breakcheck();
	++lnum;
	if (line[0] == '#' || line[0] == '/')
	    continue;	/* comment line */

	/* Remove CR, LF and white space from the end.  White space halfway
	 * the word is kept to allow e.g., "et al.". */
	l = (int)STRLEN(line);
	while (l > 0 && line[l - 1] <= ' ')
	    --l;
	if (l == 0)
	    continue;	/* empty line */
	line[l] = NUL;

#ifdef FEAT_MBYTE
	/* Convert from "SET" to 'encoding' when needed. */
	if (spin->si_conv.vc_type != CONV_NONE)
	{
	    pc = string_convert(&spin->si_conv, line, NULL);
	    if (pc == NULL)
	    {
		smsg((char_u *)_("Conversion failure for word in %s line %d: %s"),
						       fname, lnum, line);
		continue;
	    }
	    w = pc;
	}
	else
#endif
	{
	    pc = NULL;
	    w = line;
	}

	/* Truncate the word at the "/", set "afflist" to what follows.
	 * Replace "\/" by "/" and "\\" by "\". */
	afflist = NULL;
	for (p = w; *p != NUL; mb_ptr_adv(p))
	{
	    if (*p == '\\' && (p[1] == '\\' || p[1] == '/'))
		STRMOVE(p, p + 1);
	    else if (*p == '/')
	    {
		*p = NUL;
		afflist = p + 1;
		break;
	    }
	}

	/* Skip non-ASCII words when "spin->si_ascii" is TRUE. */
	if (spin->si_ascii && has_non_ascii(w))
	{
	    ++non_ascii;
	    vim_free(pc);
	    continue;
	}

	/* This takes time, print a message every 10000 words. */
	if (spin->si_verbose && spin->si_msg_count > 10000)
	{
	    spin->si_msg_count = 0;
	    vim_snprintf((char *)message, sizeof(message),
		    _("line %6d, word %6d - %s"),
		       lnum, spin->si_foldwcount + spin->si_keepwcount, w);
	    msg_start();
	    msg_puts_long_attr(message, 0);
	    msg_clr_eos();
	    msg_didout = FALSE;
	    msg_col = 0;
	    out_flush();
	}

	/* Store the word in the hashtable to be able to find duplicates. */
	dw = (char_u *)getroom_save(spin, w);
	if (dw == NULL)
	{
	    retval = FAIL;
	    vim_free(pc);
	    break;
	}

	hash = hash_hash(dw);
	hi = hash_lookup(&ht, dw, hash);
	if (!HASHITEM_EMPTY(hi))
	{
	    if (p_verbose > 0)
		smsg((char_u *)_("Duplicate word in %s line %d: %s"),
							     fname, lnum, dw);
	    else if (duplicate == 0)
		smsg((char_u *)_("First duplicate word in %s line %d: %s"),
							     fname, lnum, dw);
	    ++duplicate;
	}
	else
	    hash_add_item(&ht, hi, dw, hash);

	flags = 0;
	store_afflist[0] = NUL;
	pfxlen = 0;
	need_affix = FALSE;
	if (afflist != NULL)
	{
	    /* Extract flags from the affix list. */
	    flags |= get_affix_flags(affile, afflist);

	    if (affile->af_needaffix != 0 && flag_in_afflist(
			  affile->af_flagtype, afflist, affile->af_needaffix))
		need_affix = TRUE;

	    if (affile->af_pfxpostpone)
		/* Need to store the list of prefix IDs with the word. */
		pfxlen = get_pfxlist(affile, afflist, store_afflist);

	    if (spin->si_compflags != NULL)
		/* Need to store the list of compound flags with the word.
		 * Concatenate them to the list of prefix IDs. */
		get_compflags(affile, afflist, store_afflist + pfxlen);
	}

	/* Add the word to the word tree(s). */
	if (store_word(spin, dw, flags, spin->si_region,
					   store_afflist, need_affix) == FAIL)
	    retval = FAIL;

	if (afflist != NULL)
	{
	    /* Find all matching suffixes and add the resulting words.
	     * Additionally do matching prefixes that combine. */
	    if (store_aff_word(spin, dw, afflist, affile,
			   &affile->af_suff, &affile->af_pref,
			    CONDIT_SUF, flags, store_afflist, pfxlen) == FAIL)
		retval = FAIL;

	    /* Find all matching prefixes and add the resulting words. */
	    if (store_aff_word(spin, dw, afflist, affile,
			  &affile->af_pref, NULL,
			    CONDIT_SUF, flags, store_afflist, pfxlen) == FAIL)
		retval = FAIL;
	}

	vim_free(pc);
    }

    if (duplicate > 0)
	smsg((char_u *)_("%d duplicate word(s) in %s"), duplicate, fname);
    if (spin->si_ascii && non_ascii > 0)
	smsg((char_u *)_("Ignored %d word(s) with non-ASCII characters in %s"),
							    non_ascii, fname);
    hash_clear(&ht);

    fclose(fd);
    return retval;
}

#define BY_NOFLAGS	0	/* end of word without flags or region; for
				 * postponed prefix: no <pflags> */

#define BY_INDEX	1	/* child is shared, index follows */

#define BY_FLAGS	2	/* end of word, <flags> byte follows; for
				 * postponed prefix: <pflags> follows */

#define BY_FLAGS2	3	/* end of word, <flags> and <flags2> bytes
				 * follow; never used in prefix tree */

#define VIMSPELLMAGIC "VIMspell"  /* string at start of Vim spell file */

#define SN_COMPOUND	8	/* compound words section */

#define SN_SUGFILE	11	/* timestamp for .sug file */

#define SN_WORDS	13	/* common words */

#define SN_NOSPLITSUGS	14	/* don't split word for suggestions */

#define SN_NOCOMPOUNDSUGS 16	/* don't compound for suggestions */

#define SN_END		255	/* end of sections */

static int set_spell_finish(spelltab_T	*new_st);

static int write_spell_prefcond(FILE *fd, garray_T *gap);

static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *lenp);

static int read_region_section(FILE *fd, slang_T *slang, int len);

static int read_charflags_section(FILE *fd);

static int read_prefcond_section(FILE *fd, slang_T *lp);

static int read_rep_section(FILE *fd, garray_T *gap, short *first);

static int read_sal_section(FILE *fd, slang_T *slang);

static int read_words_section(FILE *fd, slang_T *lp, int len);

static int read_sofo_section(FILE *fd, slang_T *slang);

static int read_compound(FILE *fd, slang_T *slang, int len);

static int set_sofo(slang_T *lp, char_u *from, char_u *to);

static void set_sal_first(slang_T *lp);

static int *mb_str2wide(char_u *s);

static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt);

static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr);

static void spell_reload_one(char_u *fname, int added_word);

static void set_spell_charflags(char_u *flags, int cnt, char_u *upp);

static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp);

static void set_map_str(slang_T *lp, char_u *map);

static char *e_spell_trunc = N_("E758: Truncated spell file");

static char *e_afftrailing = N_("Trailing text in %s line %d: %s");

static char *e_affname = N_("Affix name too long in %s line %d: %s");

static char *e_affform = N_("E761: Format error in affix file FOL, LOW or UPP");

static char *e_affrange = N_("E762: Character in FOL, LOW or UPP is out of range");

static char *msg_compressing = N_("Compressing word tree...");

#define MAXLINELEN  500		/* Maximum length in bytes of a line in a .aff
				   and .dic file. */


read_sofo_section(FILE *fd, slang_T *slang)
{
    int		cnt;
    char_u	*from, *to;
    int		res;

    slang->sl_sofo = TRUE;

    /* <sofofromlen> <sofofrom> */
    from = read_cnt_string(fd, 2, &cnt);
    if (cnt < 0)
	return cnt;

    /* <sofotolen> <sofoto> */
    to = read_cnt_string(fd, 2, &cnt);
    if (cnt < 0)
    {
	vim_free(from);
	return cnt;
    }

    /* Store the info in slang->sl_sal and/or slang->sl_sal_first. */
    if (from != NULL && to != NULL)
	res = set_sofo(slang, from, to);
    else if (from != NULL || to != NULL)
	res = SP_FORMERROR;    /* only one of two strings is an error */
    else
	res = 0;

    vim_free(from);
    vim_free(to);
    return res;
}

#define BY_FLAGS2	3	/* end of word, <flags> and <flags2> bytes
				 * follow; never used in prefix tree */

#define SN_INFO		15	/* info section */

static int set_spell_finish(spelltab_T	*new_st);

static int write_spell_prefcond(FILE *fd, garray_T *gap);

static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *lenp);

static int read_region_section(FILE *fd, slang_T *slang, int len);

static int read_charflags_section(FILE *fd);

static int read_prefcond_section(FILE *fd, slang_T *lp);

static int read_rep_section(FILE *fd, garray_T *gap, short *first);

static int read_sal_section(FILE *fd, slang_T *slang);

static int read_words_section(FILE *fd, slang_T *lp, int len);

static int read_sofo_section(FILE *fd, slang_T *slang);

static int read_compound(FILE *fd, slang_T *slang, int len);

static int set_sofo(slang_T *lp, char_u *from, char_u *to);

static void set_sal_first(slang_T *lp);

static int *mb_str2wide(char_u *s);

static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt);

static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr);

static void spell_reload_one(char_u *fname, int added_word);

static void set_spell_charflags(char_u *flags, int cnt, char_u *upp);

static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp);

static void set_map_str(slang_T *lp, char_u *map);

static char *e_affform = N_("E761: Format error in affix file FOL, LOW or UPP");

#define MAXLINELEN  500		/* Maximum length in bytes of a line in a .aff
				   and .dic file. */

typedef struct afffile_S
{
    char_u	*af_enc;	/* "SET", normalized, alloc'ed string or NULL */
    int		af_flagtype;	/* AFT_CHAR, AFT_LONG, AFT_NUM or AFT_CAPLONG */
    unsigned	af_rare;	/* RARE ID for rare word */
    unsigned	af_keepcase;	/* KEEPCASE ID for keep-case word */
    unsigned	af_bad;		/* BAD ID for banned word */
    unsigned	af_needaffix;	/* NEEDAFFIX ID */
    unsigned	af_circumfix;	/* CIRCUMFIX ID */
    unsigned	af_needcomp;	/* NEEDCOMPOUND ID */
    unsigned	af_comproot;	/* COMPOUNDROOT ID */
    unsigned	af_compforbid;	/* COMPOUNDFORBIDFLAG ID */
    unsigned	af_comppermit;	/* COMPOUNDPERMITFLAG ID */
    unsigned	af_nosuggest;	/* NOSUGGEST ID */
    int		af_pfxpostpone;	/* postpone prefixes without chop string and
				   without flags */
    int		af_ignoreextra;	/* IGNOREEXTRA present */
    hashtab_T	af_pref;	/* hashtable for prefixes, affheader_T */
    hashtab_T	af_suff;	/* hashtable for suffixes, affheader_T */
    hashtab_T	af_comp;	/* hashtable for compound flags, compitem_T */
} afffile_T;


spell_read_aff(spellinfo_T *spin, char_u *fname)
{
    FILE	*fd;
    afffile_T	*aff;
    char_u	rline[MAXLINELEN];
    char_u	*line;
    char_u	*pc = NULL;
#define MAXITEMCNT  30
    char_u	*(items[MAXITEMCNT]);
    int		itemcnt;
    char_u	*p;
    int		lnum = 0;
    affheader_T	*cur_aff = NULL;
    int		did_postpone_prefix = FALSE;
    int		aff_todo = 0;
    hashtab_T	*tp;
    char_u	*low = NULL;
    char_u	*fol = NULL;
    char_u	*upp = NULL;
    int		do_rep;
    int		do_repsal;
    int		do_sal;
    int		do_mapline;
    int		found_map = FALSE;
    hashitem_T	*hi;
    int		l;
    int		compminlen = 0;		/* COMPOUNDMIN value */
    int		compsylmax = 0;		/* COMPOUNDSYLMAX value */
    int		compoptions = 0;	/* COMP_ flags */
    int		compmax = 0;		/* COMPOUNDWORDMAX value */
    char_u	*compflags = NULL;	/* COMPOUNDFLAG and COMPOUNDRULE
					   concatenated */
    char_u	*midword = NULL;	/* MIDWORD value */
    char_u	*syllable = NULL;	/* SYLLABLE value */
    char_u	*sofofrom = NULL;	/* SOFOFROM value */
    char_u	*sofoto = NULL;		/* SOFOTO value */

    /*
     * Open the file.
     */
    fd = mch_fopen((char *)fname, "r");
    if (fd == NULL)
    {
	EMSG2(_(e_notopen), fname);
	return NULL;
    }

    vim_snprintf((char *)IObuff, IOSIZE, _("Reading affix file %s ..."), fname);
    spell_message(spin, IObuff);

    /* Only do REP lines when not done in another .aff file already. */
    do_rep = spin->si_rep.ga_len == 0;

    /* Only do REPSAL lines when not done in another .aff file already. */
    do_repsal = spin->si_repsal.ga_len == 0;

    /* Only do SAL lines when not done in another .aff file already. */
    do_sal = spin->si_sal.ga_len == 0;

    /* Only do MAP lines when not done in another .aff file already. */
    do_mapline = spin->si_map.ga_len == 0;

    /*
     * Allocate and init the afffile_T structure.
     */
    aff = (afffile_T *)getroom(spin, sizeof(afffile_T), TRUE);
    if (aff == NULL)
    {
	fclose(fd);
	return NULL;
    }
    hash_init(&aff->af_pref);
    hash_init(&aff->af_suff);
    hash_init(&aff->af_comp);

    /*
     * Read all the lines in the file one by one.
     */
    while (!vim_fgets(rline, MAXLINELEN, fd) && !got_int)
    {
	line_breakcheck();
	++lnum;

	/* Skip comment lines. */
	if (*rline == '#')
	    continue;

	/* Convert from "SET" to 'encoding' when needed. */
	vim_free(pc);
#ifdef FEAT_MBYTE
	if (spin->si_conv.vc_type != CONV_NONE)
	{
	    pc = string_convert(&spin->si_conv, rline, NULL);
	    if (pc == NULL)
	    {
		smsg((char_u *)_("Conversion failure for word in %s line %d: %s"),
							   fname, lnum, rline);
		continue;
	    }
	    line = pc;
	}
	else
#endif
	{
	    pc = NULL;
	    line = rline;
	}

	/* Split the line up in white separated items.  Put a NUL after each
	 * item. */
	itemcnt = 0;
	for (p = line; ; )
	{
	    while (*p != NUL && *p <= ' ')  /* skip white space and CR/NL */
		++p;
	    if (*p == NUL)
		break;
	    if (itemcnt == MAXITEMCNT)	    /* too many items */
		break;
	    items[itemcnt++] = p;
	    /* A few items have arbitrary text argument, don't split them. */
	    if (itemcnt == 2 && spell_info_item(items[0]))
		while (*p >= ' ' || *p == TAB)    /* skip until CR/NL */
		    ++p;
	    else
		while (*p > ' ')    /* skip until white space or CR/NL */
		    ++p;
	    if (*p == NUL)
		break;
	    *p++ = NUL;
	}

	/* Handle non-empty lines. */
	if (itemcnt > 0)
	{
	    if (is_aff_rule(items, itemcnt, "SET", 2) && aff->af_enc == NULL)
	    {
#ifdef FEAT_MBYTE
		/* Setup for conversion from "ENC" to 'encoding'. */
		aff->af_enc = enc_canonize(items[1]);
		if (aff->af_enc != NULL && !spin->si_ascii
			&& convert_setup(&spin->si_conv, aff->af_enc,
							       p_enc) == FAIL)
		    smsg((char_u *)_("Conversion in %s not supported: from %s to %s"),
					       fname, aff->af_enc, p_enc);
		spin->si_conv.vc_fail = TRUE;
#else
		    smsg((char_u *)_("Conversion in %s not supported"), fname);
#endif
	    }
	    else if (is_aff_rule(items, itemcnt, "FLAG", 2)
					      && aff->af_flagtype == AFT_CHAR)
	    {
		if (STRCMP(items[1], "long") == 0)
		    aff->af_flagtype = AFT_LONG;
		else if (STRCMP(items[1], "num") == 0)
		    aff->af_flagtype = AFT_NUM;
		else if (STRCMP(items[1], "caplong") == 0)
		    aff->af_flagtype = AFT_CAPLONG;
		else
		    smsg((char_u *)_("Invalid value for FLAG in %s line %d: %s"),
			    fname, lnum, items[1]);
		if (aff->af_rare != 0
			|| aff->af_keepcase != 0
			|| aff->af_bad != 0
			|| aff->af_needaffix != 0
			|| aff->af_circumfix != 0
			|| aff->af_needcomp != 0
			|| aff->af_comproot != 0
			|| aff->af_nosuggest != 0
			|| compflags != NULL
			|| aff->af_suff.ht_used > 0
			|| aff->af_pref.ht_used > 0)
		    smsg((char_u *)_("FLAG after using flags in %s line %d: %s"),
			    fname, lnum, items[1]);
	    }
	    else if (spell_info_item(items[0]))
	    {
		    p = (char_u *)getroom(spin,
			    (spin->si_info == NULL ? 0 : STRLEN(spin->si_info))
			    + STRLEN(items[0])
			    + STRLEN(items[1]) + 3, FALSE);
		    if (p != NULL)
		    {
			if (spin->si_info != NULL)
			{
			    STRCPY(p, spin->si_info);
			    STRCAT(p, "\n");
			}
			STRCAT(p, items[0]);
			STRCAT(p, " ");
			STRCAT(p, items[1]);
			spin->si_info = p;
		    }
	    }
	    else if (is_aff_rule(items, itemcnt, "MIDWORD", 2)
							   && midword == NULL)
	    {
		midword = getroom_save(spin, items[1]);
	    }
	    else if (is_aff_rule(items, itemcnt, "TRY", 2))
	    {
		/* ignored, we look in the tree for what chars may appear */
	    }
	    /* TODO: remove "RAR" later */
	    else if ((is_aff_rule(items, itemcnt, "RAR", 2)
			|| is_aff_rule(items, itemcnt, "RARE", 2))
							 && aff->af_rare == 0)
	    {
		aff->af_rare = affitem2flag(aff->af_flagtype, items[1],
								 fname, lnum);
	    }
	    /* TODO: remove "KEP" later */
	    else if ((is_aff_rule(items, itemcnt, "KEP", 2)
			|| is_aff_rule(items, itemcnt, "KEEPCASE", 2))
						     && aff->af_keepcase == 0)
	    {
		aff->af_keepcase = affitem2flag(aff->af_flagtype, items[1],
								 fname, lnum);
	    }
	    else if ((is_aff_rule(items, itemcnt, "BAD", 2)
			|| is_aff_rule(items, itemcnt, "FORBIDDENWORD", 2))
							  && aff->af_bad == 0)
	    {
		aff->af_bad = affitem2flag(aff->af_flagtype, items[1],
								 fname, lnum);
	    }
	    else if (is_aff_rule(items, itemcnt, "NEEDAFFIX", 2)
						    && aff->af_needaffix == 0)
	    {
		aff->af_needaffix = affitem2flag(aff->af_flagtype, items[1],
								 fname, lnum);
	    }
	    else if (is_aff_rule(items, itemcnt, "CIRCUMFIX", 2)
						    && aff->af_circumfix == 0)
	    {
		aff->af_circumfix = affitem2flag(aff->af_flagtype, items[1],
								 fname, lnum);
	    }
	    else if (is_aff_rule(items, itemcnt, "NOSUGGEST", 2)
						    && aff->af_nosuggest == 0)
	    {
		aff->af_nosuggest = affitem2flag(aff->af_flagtype, items[1],
								 fname, lnum);
	    }
	    else if ((is_aff_rule(items, itemcnt, "NEEDCOMPOUND", 2)
			|| is_aff_rule(items, itemcnt, "ONLYINCOMPOUND", 2))
						     && aff->af_needcomp == 0)
	    {
		aff->af_needcomp = affitem2flag(aff->af_flagtype, items[1],
								 fname, lnum);
	    }
	    else if (is_aff_rule(items, itemcnt, "COMPOUNDROOT", 2)
						     && aff->af_comproot == 0)
	    {
		aff->af_comproot = affitem2flag(aff->af_flagtype, items[1],
								 fname, lnum);
	    }
	    else if (is_aff_rule(items, itemcnt, "COMPOUNDFORBIDFLAG", 2)
						   && aff->af_compforbid == 0)
	    {
		aff->af_compforbid = affitem2flag(aff->af_flagtype, items[1],
								 fname, lnum);
		if (aff->af_pref.ht_used > 0)
		    smsg((char_u *)_("Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in %s line %d"),
			    fname, lnum);
	    }
	    else if (is_aff_rule(items, itemcnt, "COMPOUNDPERMITFLAG", 2)
						   && aff->af_comppermit == 0)
	    {
		aff->af_comppermit = affitem2flag(aff->af_flagtype, items[1],
								 fname, lnum);
		if (aff->af_pref.ht_used > 0)
		    smsg((char_u *)_("Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in %s line %d"),
			    fname, lnum);
	    }
	    else if (is_aff_rule(items, itemcnt, "COMPOUNDFLAG", 2)
							 && compflags == NULL)
	    {
		/* Turn flag "c" into COMPOUNDRULE compatible string "c+",
		 * "Na" into "Na+", "1234" into "1234+". */
		p = getroom(spin, STRLEN(items[1]) + 2, FALSE);
		if (p != NULL)
		{
		    STRCPY(p, items[1]);
		    STRCAT(p, "+");
		    compflags = p;
		}
	    }
	    else if (is_aff_rule(items, itemcnt, "COMPOUNDRULES", 2))
	    {
		/* We don't use the count, but do check that it's a number and
		 * not COMPOUNDRULE mistyped. */
		if (atoi((char *)items[1]) == 0)
		    smsg((char_u *)_("Wrong COMPOUNDRULES value in %s line %d: %s"),
						       fname, lnum, items[1]);
	    }
	    else if (is_aff_rule(items, itemcnt, "COMPOUNDRULE", 2))
	    {
		/* Don't use the first rule if it is a number. */
		if (compflags != NULL || *skipdigits(items[1]) != NUL)
		{
		    /* Concatenate this string to previously defined ones,
		     * using a slash to separate them. */
		    l = (int)STRLEN(items[1]) + 1;
		    if (compflags != NULL)
			l += (int)STRLEN(compflags) + 1;
		    p = getroom(spin, l, FALSE);
		    if (p != NULL)
		    {
			if (compflags != NULL)
			{
			    STRCPY(p, compflags);
			    STRCAT(p, "/");
			}
			STRCAT(p, items[1]);
			compflags = p;
		    }
		}
	    }
	    else if (is_aff_rule(items, itemcnt, "COMPOUNDWORDMAX", 2)
							      && compmax == 0)
	    {
		compmax = atoi((char *)items[1]);
		if (compmax == 0)
		    smsg((char_u *)_("Wrong COMPOUNDWORDMAX value in %s line %d: %s"),
						       fname, lnum, items[1]);
	    }
	    else if (is_aff_rule(items, itemcnt, "COMPOUNDMIN", 2)
							   && compminlen == 0)
	    {
		compminlen = atoi((char *)items[1]);
		if (compminlen == 0)
		    smsg((char_u *)_("Wrong COMPOUNDMIN value in %s line %d: %s"),
						       fname, lnum, items[1]);
	    }
	    else if (is_aff_rule(items, itemcnt, "COMPOUNDSYLMAX", 2)
							   && compsylmax == 0)
	    {
		compsylmax = atoi((char *)items[1]);
		if (compsylmax == 0)
		    smsg((char_u *)_("Wrong COMPOUNDSYLMAX value in %s line %d: %s"),
						       fname, lnum, items[1]);
	    }
	    else if (is_aff_rule(items, itemcnt, "CHECKCOMPOUNDDUP", 1))
	    {
		compoptions |= COMP_CHECKDUP;
	    }
	    else if (is_aff_rule(items, itemcnt, "CHECKCOMPOUNDREP", 1))
	    {
		compoptions |= COMP_CHECKREP;
	    }
	    else if (is_aff_rule(items, itemcnt, "CHECKCOMPOUNDCASE", 1))
	    {
		compoptions |= COMP_CHECKCASE;
	    }
	    else if (is_aff_rule(items, itemcnt, "CHECKCOMPOUNDTRIPLE", 1))
	    {
		compoptions |= COMP_CHECKTRIPLE;
	    }
	    else if (is_aff_rule(items, itemcnt, "CHECKCOMPOUNDPATTERN", 2))
	    {
		if (atoi((char *)items[1]) == 0)
		    smsg((char_u *)_("Wrong CHECKCOMPOUNDPATTERN value in %s line %d: %s"),
						       fname, lnum, items[1]);
	    }
	    else if (is_aff_rule(items, itemcnt, "CHECKCOMPOUNDPATTERN", 3))
	    {
		garray_T    *gap = &spin->si_comppat;
		int	    i;

		/* Only add the couple if it isn't already there. */
		for (i = 0; i < gap->ga_len - 1; i += 2)
		    if (STRCMP(((char_u **)(gap->ga_data))[i], items[1]) == 0
			    && STRCMP(((char_u **)(gap->ga_data))[i + 1],
							       items[2]) == 0)
			break;
		if (i >= gap->ga_len && ga_grow(gap, 2) == OK)
		{
		    ((char_u **)(gap->ga_data))[gap->ga_len++]
					       = getroom_save(spin, items[1]);
		    ((char_u **)(gap->ga_data))[gap->ga_len++]
					       = getroom_save(spin, items[2]);
		}
	    }
	    else if (is_aff_rule(items, itemcnt, "SYLLABLE", 2)
							  && syllable == NULL)
	    {
		syllable = getroom_save(spin, items[1]);
	    }
	    else if (is_aff_rule(items, itemcnt, "NOBREAK", 1))
	    {
		spin->si_nobreak = TRUE;
	    }
	    else if (is_aff_rule(items, itemcnt, "NOSPLITSUGS", 1))
	    {
		spin->si_nosplitsugs = TRUE;
	    }
	    else if (is_aff_rule(items, itemcnt, "NOCOMPOUNDSUGS", 1))
	    {
		spin->si_nocompoundsugs = TRUE;
	    }
	    else if (is_aff_rule(items, itemcnt, "NOSUGFILE", 1))
	    {
		spin->si_nosugfile = TRUE;
	    }
	    else if (is_aff_rule(items, itemcnt, "PFXPOSTPONE", 1))
	    {
		aff->af_pfxpostpone = TRUE;
	    }
	    else if (is_aff_rule(items, itemcnt, "IGNOREEXTRA", 1))
	    {
		aff->af_ignoreextra = TRUE;
	    }
	    else if ((STRCMP(items[0], "PFX") == 0
					      || STRCMP(items[0], "SFX") == 0)
		    && aff_todo == 0
		    && itemcnt >= 4)
	    {
		int	lasti = 4;
		char_u	key[AH_KEY_LEN];

		if (*items[0] == 'P')
		    tp = &aff->af_pref;
		else
		    tp = &aff->af_suff;

		/* Myspell allows the same affix name to be used multiple
		 * times.  The affix files that do this have an undocumented
		 * "S" flag on all but the last block, thus we check for that
		 * and store it in ah_follows. */
		vim_strncpy(key, items[1], AH_KEY_LEN - 1);
		hi = hash_find(tp, key);
		if (!HASHITEM_EMPTY(hi))
		{
		    cur_aff = HI2AH(hi);
		    if (cur_aff->ah_combine != (*items[2] == 'Y'))
			smsg((char_u *)_("Different combining flag in continued affix block in %s line %d: %s"),
						   fname, lnum, items[1]);
		    if (!cur_aff->ah_follows)
			smsg((char_u *)_("Duplicate affix in %s line %d: %s"),
						       fname, lnum, items[1]);
		}
		else
		{
		    /* New affix letter. */
		    cur_aff = (affheader_T *)getroom(spin,
						   sizeof(affheader_T), TRUE);
		    if (cur_aff == NULL)
			break;
		    cur_aff->ah_flag = affitem2flag(aff->af_flagtype, items[1],
								 fname, lnum);
		    if (cur_aff->ah_flag == 0 || STRLEN(items[1]) >= AH_KEY_LEN)
			break;
		    if (cur_aff->ah_flag == aff->af_bad
			    || cur_aff->ah_flag == aff->af_rare
			    || cur_aff->ah_flag == aff->af_keepcase
			    || cur_aff->ah_flag == aff->af_needaffix
			    || cur_aff->ah_flag == aff->af_circumfix
			    || cur_aff->ah_flag == aff->af_nosuggest
			    || cur_aff->ah_flag == aff->af_needcomp
			    || cur_aff->ah_flag == aff->af_comproot)
			smsg((char_u *)_("Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in %s line %d: %s"),
						       fname, lnum, items[1]);
		    STRCPY(cur_aff->ah_key, items[1]);
		    hash_add(tp, cur_aff->ah_key);

		    cur_aff->ah_combine = (*items[2] == 'Y');
		}

		/* Check for the "S" flag, which apparently means that another
		 * block with the same affix name is following. */
		if (itemcnt > lasti && STRCMP(items[lasti], "S") == 0)
		{
		    ++lasti;
		    cur_aff->ah_follows = TRUE;
		}
		else
		    cur_aff->ah_follows = FALSE;

		/* Myspell allows extra text after the item, but that might
		 * mean mistakes go unnoticed.  Require a comment-starter. */
		if (itemcnt > lasti && *items[lasti] != '#')
		    smsg((char_u *)_(e_afftrailing), fname, lnum, items[lasti]);

		if (STRCMP(items[2], "Y") != 0 && STRCMP(items[2], "N") != 0)
		    smsg((char_u *)_("Expected Y or N in %s line %d: %s"),
						       fname, lnum, items[2]);

		if (*items[0] == 'P' && aff->af_pfxpostpone)
		{
		    if (cur_aff->ah_newID == 0)
		    {
			/* Use a new number in the .spl file later, to be able
			 * to handle multiple .aff files. */
			check_renumber(spin);
			cur_aff->ah_newID = ++spin->si_newprefID;

			/* We only really use ah_newID if the prefix is
			 * postponed.  We know that only after handling all
			 * the items. */
			did_postpone_prefix = FALSE;
		    }
		    else
			/* Did use the ID in a previous block. */
			did_postpone_prefix = TRUE;
		}

		aff_todo = atoi((char *)items[3]);
	    }
	    else if ((STRCMP(items[0], "PFX") == 0
					      || STRCMP(items[0], "SFX") == 0)
		    && aff_todo > 0
		    && STRCMP(cur_aff->ah_key, items[1]) == 0
		    && itemcnt >= 5)
	    {
		affentry_T	*aff_entry;
		int		upper = FALSE;
		int		lasti = 5;

		/* Myspell allows extra text after the item, but that might
		 * mean mistakes go unnoticed.  Require a comment-starter,
		 * unless IGNOREEXTRA is used.  Hunspell uses a "-" item. */
		if (itemcnt > lasti
			&& !aff->af_ignoreextra
			&& *items[lasti] != '#'
			&& (STRCMP(items[lasti], "-") != 0
						     || itemcnt != lasti + 1))
		    smsg((char_u *)_(e_afftrailing), fname, lnum, items[lasti]);

		/* New item for an affix letter. */
		--aff_todo;
		aff_entry = (affentry_T *)getroom(spin,
						    sizeof(affentry_T), TRUE);
		if (aff_entry == NULL)
		    break;

		if (STRCMP(items[2], "0") != 0)
		    aff_entry->ae_chop = getroom_save(spin, items[2]);
		if (STRCMP(items[3], "0") != 0)
		{
		    aff_entry->ae_add = getroom_save(spin, items[3]);

		    /* Recognize flags on the affix: abcd/XYZ */
		    aff_entry->ae_flags = vim_strchr(aff_entry->ae_add, '/');
		    if (aff_entry->ae_flags != NULL)
		    {
			*aff_entry->ae_flags++ = NUL;
			aff_process_flags(aff, aff_entry);
		    }
		}

		/* Don't use an affix entry with non-ASCII characters when
		 * "spin->si_ascii" is TRUE. */
		if (!spin->si_ascii || !(has_non_ascii(aff_entry->ae_chop)
					  || has_non_ascii(aff_entry->ae_add)))
		{
		    aff_entry->ae_next = cur_aff->ah_first;
		    cur_aff->ah_first = aff_entry;

		    if (STRCMP(items[4], ".") != 0)
		    {
			char_u	buf[MAXLINELEN];

			aff_entry->ae_cond = getroom_save(spin, items[4]);
			if (*items[0] == 'P')
			    sprintf((char *)buf, "^%s", items[4]);
			else
			    sprintf((char *)buf, "%s$", items[4]);
			aff_entry->ae_prog = vim_regcomp(buf,
					    RE_MAGIC + RE_STRING + RE_STRICT);
			if (aff_entry->ae_prog == NULL)
			    smsg((char_u *)_("Broken condition in %s line %d: %s"),
						       fname, lnum, items[4]);
		    }

		    /* For postponed prefixes we need an entry in si_prefcond
		     * for the condition.  Use an existing one if possible.
		     * Can't be done for an affix with flags, ignoring
		     * COMPOUNDFORBIDFLAG and COMPOUNDPERMITFLAG. */
		    if (*items[0] == 'P' && aff->af_pfxpostpone
					       && aff_entry->ae_flags == NULL)
		    {
			/* When the chop string is one lower-case letter and
			 * the add string ends in the upper-case letter we set
			 * the "upper" flag, clear "ae_chop" and remove the
			 * letters from "ae_add".  The condition must either
			 * be empty or start with the same letter. */
			if (aff_entry->ae_chop != NULL
				&& aff_entry->ae_add != NULL
#ifdef FEAT_MBYTE
				&& aff_entry->ae_chop[(*mb_ptr2len)(
						   aff_entry->ae_chop)] == NUL
#else
				&& aff_entry->ae_chop[1] == NUL
#endif
				)
			{
			    int		c, c_up;

			    c = PTR2CHAR(aff_entry->ae_chop);
			    c_up = SPELL_TOUPPER(c);
			    if (c_up != c
				    && (aff_entry->ae_cond == NULL
					|| PTR2CHAR(aff_entry->ae_cond) == c))
			    {
				p = aff_entry->ae_add
						  + STRLEN(aff_entry->ae_add);
				mb_ptr_back(aff_entry->ae_add, p);
				if (PTR2CHAR(p) == c_up)
				{
				    upper = TRUE;
				    aff_entry->ae_chop = NULL;
				    *p = NUL;

				    /* The condition is matched with the
				     * actual word, thus must check for the
				     * upper-case letter. */
				    if (aff_entry->ae_cond != NULL)
				    {
					char_u	buf[MAXLINELEN];
#ifdef FEAT_MBYTE
					if (has_mbyte)
					{
					    onecap_copy(items[4], buf, TRUE);
					    aff_entry->ae_cond = getroom_save(
								   spin, buf);
					}
					else
#endif
					    *aff_entry->ae_cond = c_up;
					if (aff_entry->ae_cond != NULL)
					{
					    sprintf((char *)buf, "^%s",
							  aff_entry->ae_cond);
					    vim_regfree(aff_entry->ae_prog);
					    aff_entry->ae_prog = vim_regcomp(
						    buf, RE_MAGIC + RE_STRING);
					}
				    }
				}
			    }
			}

			if (aff_entry->ae_chop == NULL
					       && aff_entry->ae_flags == NULL)
			{
			    int		idx;
			    char_u	**pp;
			    int		n;

			    /* Find a previously used condition. */
			    for (idx = spin->si_prefcond.ga_len - 1; idx >= 0;
									--idx)
			    {
				p = ((char_u **)spin->si_prefcond.ga_data)[idx];
				if (str_equal(p, aff_entry->ae_cond))
				    break;
			    }
			    if (idx < 0 && ga_grow(&spin->si_prefcond, 1) == OK)
			    {
				/* Not found, add a new condition. */
				idx = spin->si_prefcond.ga_len++;
				pp = ((char_u **)spin->si_prefcond.ga_data)
									+ idx;
				if (aff_entry->ae_cond == NULL)
				    *pp = NULL;
				else
				    *pp = getroom_save(spin,
							  aff_entry->ae_cond);
			    }

			    /* Add the prefix to the prefix tree. */
			    if (aff_entry->ae_add == NULL)
				p = (char_u *)"";
			    else
				p = aff_entry->ae_add;

			    /* PFX_FLAGS is a negative number, so that
			     * tree_add_word() knows this is the prefix tree. */
			    n = PFX_FLAGS;
			    if (!cur_aff->ah_combine)
				n |= WFP_NC;
			    if (upper)
				n |= WFP_UP;
			    if (aff_entry->ae_comppermit)
				n |= WFP_COMPPERMIT;
			    if (aff_entry->ae_compforbid)
				n |= WFP_COMPFORBID;
			    tree_add_word(spin, p, spin->si_prefroot, n,
						      idx, cur_aff->ah_newID);
			    did_postpone_prefix = TRUE;
			}

			/* Didn't actually use ah_newID, backup si_newprefID. */
			if (aff_todo == 0 && !did_postpone_prefix)
			{
			    --spin->si_newprefID;
			    cur_aff->ah_newID = 0;
			}
		    }
		}
	    }
	    else if (is_aff_rule(items, itemcnt, "FOL", 2) && fol == NULL)
	    {
		fol = vim_strsave(items[1]);
	    }
	    else if (is_aff_rule(items, itemcnt, "LOW", 2) && low == NULL)
	    {
		low = vim_strsave(items[1]);
	    }
	    else if (is_aff_rule(items, itemcnt, "UPP", 2) && upp == NULL)
	    {
		upp = vim_strsave(items[1]);
	    }
	    else if (is_aff_rule(items, itemcnt, "REP", 2)
		     || is_aff_rule(items, itemcnt, "REPSAL", 2))
	    {
		/* Ignore REP/REPSAL count */;
		if (!isdigit(*items[1]))
		    smsg((char_u *)_("Expected REP(SAL) count in %s line %d"),
								 fname, lnum);
	    }
	    else if ((STRCMP(items[0], "REP") == 0
			|| STRCMP(items[0], "REPSAL") == 0)
		    && itemcnt >= 3)
	    {
		/* REP/REPSAL item */
		/* Myspell ignores extra arguments, we require it starts with
		 * # to detect mistakes. */
		if (itemcnt > 3 && items[3][0] != '#')
		    smsg((char_u *)_(e_afftrailing), fname, lnum, items[3]);
		if (items[0][3] == 'S' ? do_repsal : do_rep)
		{
		    /* Replace underscore with space (can't include a space
		     * directly). */
		    for (p = items[1]; *p != NUL; mb_ptr_adv(p))
			if (*p == '_')
			    *p = ' ';
		    for (p = items[2]; *p != NUL; mb_ptr_adv(p))
			if (*p == '_')
			    *p = ' ';
		    add_fromto(spin, items[0][3] == 'S'
					 ? &spin->si_repsal
					 : &spin->si_rep, items[1], items[2]);
		}
	    }
	    else if (is_aff_rule(items, itemcnt, "MAP", 2))
	    {
		/* MAP item or count */
		if (!found_map)
		{
		    /* First line contains the count. */
		    found_map = TRUE;
		    if (!isdigit(*items[1]))
			smsg((char_u *)_("Expected MAP count in %s line %d"),
								 fname, lnum);
		}
		else if (do_mapline)
		{
		    int		c;

		    /* Check that every character appears only once. */
		    for (p = items[1]; *p != NUL; )
		    {
#ifdef FEAT_MBYTE
			c = mb_ptr2char_adv(&p);
#else
			c = *p++;
#endif
			if ((spin->si_map.ga_len > 0
				    && vim_strchr(spin->si_map.ga_data, c)
								      != NULL)
				|| vim_strchr(p, c) != NULL)
			    smsg((char_u *)_("Duplicate character in MAP in %s line %d"),
								 fname, lnum);
		    }

		    /* We simply concatenate all the MAP strings, separated by
		     * slashes. */
		    ga_concat(&spin->si_map, items[1]);
		    ga_append(&spin->si_map, '/');
		}
	    }
	    /* Accept "SAL from to" and "SAL from to  #comment". */
	    else if (is_aff_rule(items, itemcnt, "SAL", 3))
	    {
		if (do_sal)
		{
		    /* SAL item (sounds-a-like)
		     * Either one of the known keys or a from-to pair. */
		    if (STRCMP(items[1], "followup") == 0)
			spin->si_followup = sal_to_bool(items[2]);
		    else if (STRCMP(items[1], "collapse_result") == 0)
			spin->si_collapse = sal_to_bool(items[2]);
		    else if (STRCMP(items[1], "remove_accents") == 0)
			spin->si_rem_accents = sal_to_bool(items[2]);
		    else
			/* when "to" is "_" it means empty */
			add_fromto(spin, &spin->si_sal, items[1],
				     STRCMP(items[2], "_") == 0 ? (char_u *)""
								: items[2]);
		}
	    }
	    else if (is_aff_rule(items, itemcnt, "SOFOFROM", 2)
							  && sofofrom == NULL)
	    {
		sofofrom = getroom_save(spin, items[1]);
	    }
	    else if (is_aff_rule(items, itemcnt, "SOFOTO", 2)
							    && sofoto == NULL)
	    {
		sofoto = getroom_save(spin, items[1]);
	    }
	    else if (STRCMP(items[0], "COMMON") == 0)
	    {
		int	i;

		for (i = 1; i < itemcnt; ++i)
		{
		    if (HASHITEM_EMPTY(hash_find(&spin->si_commonwords,
								   items[i])))
		    {
			p = vim_strsave(items[i]);
			if (p == NULL)
			    break;
			hash_add(&spin->si_commonwords, p);
		    }
		}
	    }
	    else
		smsg((char_u *)_("Unrecognized or duplicate item in %s line %d: %s"),
						       fname, lnum, items[0]);
	}
    }

    if (fol != NULL || low != NULL || upp != NULL)
    {
	if (spin->si_clear_chartab)
	{
	    /* Clear the char type tables, don't want to use any of the
	     * currently used spell properties. */
	    init_spell_chartab();
	    spin->si_clear_chartab = FALSE;
	}

	/*
	 * Don't write a word table for an ASCII file, so that we don't check
	 * for conflicts with a word table that matches 'encoding'.
	 * Don't write one for utf-8 either, we use utf_*() and
	 * mb_get_class(), the list of chars in the file will be incomplete.
	 */
	if (!spin->si_ascii
#ifdef FEAT_MBYTE
		&& !enc_utf8
#endif
		)
	{
	    if (fol == NULL || low == NULL || upp == NULL)
		smsg((char_u *)_("Missing FOL/LOW/UPP line in %s"), fname);
	    else
		(void)set_spell_chartab(fol, low, upp);
	}

	vim_free(fol);
	vim_free(low);
	vim_free(upp);
    }

    /* Use compound specifications of the .aff file for the spell info. */
    if (compmax != 0)
    {
	aff_check_number(spin->si_compmax, compmax, "COMPOUNDWORDMAX");
	spin->si_compmax = compmax;
    }

    if (compminlen != 0)
    {
	aff_check_number(spin->si_compminlen, compminlen, "COMPOUNDMIN");
	spin->si_compminlen = compminlen;
    }

    if (compsylmax != 0)
    {
	if (syllable == NULL)
	    smsg((char_u *)_("COMPOUNDSYLMAX used without SYLLABLE"));
	aff_check_number(spin->si_compsylmax, compsylmax, "COMPOUNDSYLMAX");
	spin->si_compsylmax = compsylmax;
    }

    if (compoptions != 0)
    {
	aff_check_number(spin->si_compoptions, compoptions, "COMPOUND options");
	spin->si_compoptions |= compoptions;
    }

    if (compflags != NULL)
	process_compflags(spin, aff, compflags);

    /* Check that we didn't use too many renumbered flags. */
    if (spin->si_newcompID < spin->si_newprefID)
    {
	if (spin->si_newcompID == 127 || spin->si_newcompID == 255)
	    MSG(_("Too many postponed prefixes"));
	else if (spin->si_newprefID == 0 || spin->si_newprefID == 127)
	    MSG(_("Too many compound flags"));
	else
	    MSG(_("Too many postponed prefixes and/or compound flags"));
    }

    if (syllable != NULL)
    {
	aff_check_string(spin->si_syllable, syllable, "SYLLABLE");
	spin->si_syllable = syllable;
    }

    if (sofofrom != NULL || sofoto != NULL)
    {
	if (sofofrom == NULL || sofoto == NULL)
	    smsg((char_u *)_("Missing SOFO%s line in %s"),
				     sofofrom == NULL ? "FROM" : "TO", fname);
	else if (spin->si_sal.ga_len > 0)
	    smsg((char_u *)_("Both SAL and SOFO lines in %s"), fname);
	else
	{
	    aff_check_string(spin->si_sofofr, sofofrom, "SOFOFROM");
	    aff_check_string(spin->si_sofoto, sofoto, "SOFOTO");
	    spin->si_sofofr = sofofrom;
	    spin->si_sofoto = sofoto;
	}
    }

    if (midword != NULL)
    {
	aff_check_string(spin->si_midword, midword, "MIDWORD");
	spin->si_midword = midword;
    }

    vim_free(pc);
    fclose(fd);
    return aff;
}

#define BY_NOFLAGS	0	/* end of word without flags or region; for
				 * postponed prefix: no <pflags> */

#define BY_INDEX	1	/* child is shared, index follows */

#define BY_FLAGS	2	/* end of word, <flags> byte follows; for
				 * postponed prefix: <pflags> follows */

#define BY_FLAGS2	3	/* end of word, <flags> and <flags2> bytes
				 * follow; never used in prefix tree */

#define BY_SPECIAL  BY_FLAGS2	/* highest special byte value */

#define SAL_F0LLOWUP		1

#define SAL_COLLAPSE		2

#define SAL_REM_ACCENTS		4

#define VIMSPELLMAGIC "VIMspell"  /* string at start of Vim spell file */

#define VIMSPELLMAGICL 8

#define VIMSPELLVERSION 50

#define SN_REGION	0	/* <regionname> section */

#define SN_CHARFLAGS	1	/* charflags section */

#define SN_MIDWORD	2	/* <midword> section */

#define SN_PREFCOND	3	/* <prefcond> section */

#define SN_REP		4	/* REP items section */

#define SN_SAL		5	/* SAL items section */

#define SN_SOFO		6	/* soundfolding section */

#define SN_MAP		7	/* MAP items section */

#define SN_COMPOUND	8	/* compound words section */

#define SN_SYLLABLE	9	/* syllable section */

#define SN_NOBREAK	10	/* NOBREAK section */

#define SN_SUGFILE	11	/* timestamp for .sug file */

#define SN_REPSAL	12	/* REPSAL items section */

#define SN_WORDS	13	/* common words */

#define SN_NOSPLITSUGS	14	/* don't split word for suggestions */

#define SN_INFO		15	/* info section */

#define SN_NOCOMPOUNDSUGS 16	/* don't compound for suggestions */

#define SN_END		255	/* end of sections */

#define SNF_REQUIRED	1	/* <sectionflags>: required section */

#define CF_WORD		0x01

#define CF_UPPER	0x02

static int set_spell_finish(spelltab_T	*new_st);

static int write_spell_prefcond(FILE *fd, garray_T *gap);

static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *lenp);

static int read_region_section(FILE *fd, slang_T *slang, int len);

static int read_charflags_section(FILE *fd);

static int read_prefcond_section(FILE *fd, slang_T *lp);

static int read_rep_section(FILE *fd, garray_T *gap, short *first);

static int read_sal_section(FILE *fd, slang_T *slang);

static int read_words_section(FILE *fd, slang_T *lp, int len);

static int read_sofo_section(FILE *fd, slang_T *slang);

static int read_compound(FILE *fd, slang_T *slang, int len);

static int set_sofo(slang_T *lp, char_u *from, char_u *to);

static void set_sal_first(slang_T *lp);

static int *mb_str2wide(char_u *s);

static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt);

static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr);

static void spell_reload_one(char_u *fname, int added_word);

static void set_spell_charflags(char_u *flags, int cnt, char_u *upp);


aff_check_number(int spinval, int affval, char *name)
{
    if (spinval != 0 && spinval != affval)
	smsg((char_u *)_("%s value differs from what is used in another .aff file"), name);
}

#define BY_FLAGS2	3	/* end of word, <flags> and <flags2> bytes
				 * follow; never used in prefix tree */

#define BY_SPECIAL  BY_FLAGS2	/* highest special byte value */

#define VIMSPELLMAGIC "VIMspell"  /* string at start of Vim spell file */

#define SN_SUGFILE	11	/* timestamp for .sug file */

static int set_spell_finish(spelltab_T	*new_st);

static int write_spell_prefcond(FILE *fd, garray_T *gap);

static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *lenp);

static int read_region_section(FILE *fd, slang_T *slang, int len);

static int read_charflags_section(FILE *fd);

static int read_prefcond_section(FILE *fd, slang_T *lp);

static int read_rep_section(FILE *fd, garray_T *gap, short *first);

static int read_sal_section(FILE *fd, slang_T *slang);

static int read_words_section(FILE *fd, slang_T *lp, int len);

static int read_sofo_section(FILE *fd, slang_T *slang);

static int read_compound(FILE *fd, slang_T *slang, int len);

static int set_sofo(slang_T *lp, char_u *from, char_u *to);

static int *mb_str2wide(char_u *s);

static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt);

static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr);

static void spell_reload_one(char_u *fname, int added_word);

static void set_spell_charflags(char_u *flags, int cnt, char_u *upp);

static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp);

static void set_map_str(slang_T *lp, char_u *map);

static char *e_spell_trunc = N_("E758: Truncated spell file");

static char *e_afftrailing = N_("Trailing text in %s line %d: %s");

static char *e_affname = N_("Affix name too long in %s line %d: %s");

static char *e_affform = N_("E761: Format error in affix file FOL, LOW or UPP");

static char *e_affrange = N_("E762: Character in FOL, LOW or UPP is out of range");

static char *msg_compressing = N_("Compressing word tree...");

#define MAXLINELEN  500		/* Maximum length in bytes of a line in a .aff
				   and .dic file. */


spell_free_aff(afffile_T *aff)
{
    hashtab_T	*ht;
    hashitem_T	*hi;
    int		todo;
    affheader_T	*ah;
    affentry_T	*ae;

    vim_free(aff->af_enc);

    /* All this trouble to free the "ae_prog" items... */
    for (ht = &aff->af_pref; ; ht = &aff->af_suff)
    {
	todo = (int)ht->ht_used;
	for (hi = ht->ht_array; todo > 0; ++hi)
	{
	    if (!HASHITEM_EMPTY(hi))
	    {
		--todo;
		ah = HI2AH(hi);
		for (ae = ah->ah_first; ae != NULL; ae = ae->ae_next)
		    vim_regfree(ae->ae_prog);
	    }
	}
	if (ht == &aff->af_suff)
	    break;
    }

    hash_clear(&aff->af_pref);
    hash_clear(&aff->af_suff);
    hash_clear(&aff->af_comp);
}

#define BY_NOFLAGS	0	/* end of word without flags or region; for
				 * postponed prefix: no <pflags> */

#define BY_FLAGS	2	/* end of word, <flags> byte follows; for
				 * postponed prefix: <pflags> follows */

#define SN_REP		4	/* REP items section */

#define SN_SAL		5	/* SAL items section */

#define SN_MAP		7	/* MAP items section */

#define SN_SUGFILE	11	/* timestamp for .sug file */

#define SN_REPSAL	12	/* REPSAL items section */

#define SN_NOSPLITSUGS	14	/* don't split word for suggestions */

#define SN_NOCOMPOUNDSUGS 16	/* don't compound for suggestions */

static int set_spell_finish(spelltab_T	*new_st);

static int write_spell_prefcond(FILE *fd, garray_T *gap);

static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *lenp);

static int read_region_section(FILE *fd, slang_T *slang, int len);

static int read_charflags_section(FILE *fd);

static int read_prefcond_section(FILE *fd, slang_T *lp);

static int read_rep_section(FILE *fd, garray_T *gap, short *first);

static int read_sal_section(FILE *fd, slang_T *slang);

static int read_words_section(FILE *fd, slang_T *lp, int len);

static int read_sofo_section(FILE *fd, slang_T *slang);

static int read_compound(FILE *fd, slang_T *slang, int len);

static int set_sofo(slang_T *lp, char_u *from, char_u *to);

static int *mb_str2wide(char_u *s);

static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt);

static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr);

static void spell_reload_one(char_u *fname, int added_word);

static void set_spell_charflags(char_u *flags, int cnt, char_u *upp);

static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp);

#define MAXLINELEN  500		/* Maximum length in bytes of a line in a .aff
				   and .dic file. */


spell_print_tree(wordnode_T *root)
{
    if (root != NULL)
    {
	/* Clear the "wn_u1.index" fields, used to remember what has been
	 * done. */
	spell_clear_flags(root);

	/* Recursively print the tree. */
	spell_print_node(root, 0);
    }
}

#define BY_INDEX	1	/* child is shared, index follows */

#define BY_FLAGS2	3	/* end of word, <flags> and <flags2> bytes
				 * follow; never used in prefix tree */

static char *msg_compressing = N_("Compressing word tree...");

typedef struct afffile_S
{
    char_u	*af_enc;	/* "SET", normalized, alloc'ed string or NULL */
    int		af_flagtype;	/* AFT_CHAR, AFT_LONG, AFT_NUM or AFT_CAPLONG */
    unsigned	af_rare;	/* RARE ID for rare word */
    unsigned	af_keepcase;	/* KEEPCASE ID for keep-case word */
    unsigned	af_bad;		/* BAD ID for banned word */
    unsigned	af_needaffix;	/* NEEDAFFIX ID */
    unsigned	af_circumfix;	/* CIRCUMFIX ID */
    unsigned	af_needcomp;	/* NEEDCOMPOUND ID */
    unsigned	af_comproot;	/* COMPOUNDROOT ID */
    unsigned	af_compforbid;	/* COMPOUNDFORBIDFLAG ID */
    unsigned	af_comppermit;	/* COMPOUNDPERMITFLAG ID */
    unsigned	af_nosuggest;	/* NOSUGGEST ID */
    int		af_pfxpostpone;	/* postpone prefixes without chop string and
				   without flags */
    int		af_ignoreextra;	/* IGNOREEXTRA present */
    hashtab_T	af_pref;	/* hashtable for prefixes, affheader_T */
    hashtab_T	af_suff;	/* hashtable for suffixes, affheader_T */
    hashtab_T	af_comp;	/* hashtable for compound flags, compitem_T */
} afffile_T;

struct affentry_S
{
    affentry_T	*ae_next;	/* next affix with same name/number */
    char_u	*ae_chop;	/* text to chop off basic word (can be NULL) */
    char_u	*ae_add;	/* text to add to basic word (can be NULL) */
    char_u	*ae_flags;	/* flags on the affix (can be NULL) */
    char_u	*ae_cond;	/* condition (NULL for ".") */
    regprog_T	*ae_prog;	/* regexp program for ae_cond or NULL */
    char	ae_compforbid;	/* COMPOUNDFORBIDFLAG found */
    char	ae_comppermit;	/* COMPOUNDPERMITFLAG found */
}

typedef struct affheader_S
{
    char_u	ah_key[AH_KEY_LEN]; /* key for hashtab == name of affix */
    unsigned	ah_flag;	/* affix name as number, uses "af_flagtype" */
    int		ah_newID;	/* prefix ID after renumbering; 0 if not used */
    int		ah_combine;	/* suffix may combine with prefix */
    int		ah_follows;	/* another affix block should be following */
    affentry_T	*ah_first;	/* first affix entry */
} affheader_T;

typedef struct wordnode_S wordnode_T;


spell_read_wordfile(spellinfo_T *spin, char_u *fname)
{
    FILE	*fd;
    long	lnum = 0;
    char_u	rline[MAXLINELEN];
    char_u	*line;
    char_u	*pc = NULL;
    char_u	*p;
    int		l;
    int		retval = OK;
    int		did_word = FALSE;
    int		non_ascii = 0;
    int		flags;
    int		regionmask;

    /*
     * Open the file.
     */
    fd = mch_fopen((char *)fname, "r");
    if (fd == NULL)
    {
	EMSG2(_(e_notopen), fname);
	return FAIL;
    }

    vim_snprintf((char *)IObuff, IOSIZE, _("Reading word file %s ..."), fname);
    spell_message(spin, IObuff);

    /*
     * Read all the lines in the file one by one.
     */
    while (!vim_fgets(rline, MAXLINELEN, fd) && !got_int)
    {
	line_breakcheck();
	++lnum;

	/* Skip comment lines. */
	if (*rline == '#')
	    continue;

	/* Remove CR, LF and white space from the end. */
	l = (int)STRLEN(rline);
	while (l > 0 && rline[l - 1] <= ' ')
	    --l;
	if (l == 0)
	    continue;	/* empty or blank line */
	rline[l] = NUL;

	/* Convert from "/encoding={encoding}" to 'encoding' when needed. */
	vim_free(pc);
#ifdef FEAT_MBYTE
	if (spin->si_conv.vc_type != CONV_NONE)
	{
	    pc = string_convert(&spin->si_conv, rline, NULL);
	    if (pc == NULL)
	    {
		smsg((char_u *)_("Conversion failure for word in %s line %d: %s"),
							   fname, lnum, rline);
		continue;
	    }
	    line = pc;
	}
	else
#endif
	{
	    pc = NULL;
	    line = rline;
	}

	if (*line == '/')
	{
	    ++line;
	    if (STRNCMP(line, "encoding=", 9) == 0)
	    {
		if (spin->si_conv.vc_type != CONV_NONE)
		    smsg((char_u *)_("Duplicate /encoding= line ignored in %s line %d: %s"),
						       fname, lnum, line - 1);
		else if (did_word)
		    smsg((char_u *)_("/encoding= line after word ignored in %s line %d: %s"),
						       fname, lnum, line - 1);
		else
		{
#ifdef FEAT_MBYTE
		    char_u	*enc;

		    /* Setup for conversion to 'encoding'. */
		    line += 9;
		    enc = enc_canonize(line);
		    if (enc != NULL && !spin->si_ascii
			    && convert_setup(&spin->si_conv, enc,
							       p_enc) == FAIL)
			smsg((char_u *)_("Conversion in %s not supported: from %s to %s"),
							  fname, line, p_enc);
		    vim_free(enc);
		    spin->si_conv.vc_fail = TRUE;
#else
		    smsg((char_u *)_("Conversion in %s not supported"), fname);
#endif
		}
		continue;
	    }

	    if (STRNCMP(line, "regions=", 8) == 0)
	    {
		if (spin->si_region_count > 1)
		    smsg((char_u *)_("Duplicate /regions= line ignored in %s line %d: %s"),
						       fname, lnum, line);
		else
		{
		    line += 8;
		    if (STRLEN(line) > 16)
			smsg((char_u *)_("Too many regions in %s line %d: %s"),
						       fname, lnum, line);
		    else
		    {
			spin->si_region_count = (int)STRLEN(line) / 2;
			STRCPY(spin->si_region_name, line);

			/* Adjust the mask for a word valid in all regions. */
			spin->si_region = (1 << spin->si_region_count) - 1;
		    }
		}
		continue;
	    }

	    smsg((char_u *)_("/ line ignored in %s line %d: %s"),
						       fname, lnum, line - 1);
	    continue;
	}

	flags = 0;
	regionmask = spin->si_region;

	/* Check for flags and region after a slash. */
	p = vim_strchr(line, '/');
	if (p != NULL)
	{
	    *p++ = NUL;
	    while (*p != NUL)
	    {
		if (*p == '=')		/* keep-case word */
		    flags |= WF_KEEPCAP | WF_FIXCAP;
		else if (*p == '!')	/* Bad, bad, wicked word. */
		    flags |= WF_BANNED;
		else if (*p == '?')	/* Rare word. */
		    flags |= WF_RARE;
		else if (VIM_ISDIGIT(*p)) /* region number(s) */
		{
		    if ((flags & WF_REGION) == 0)   /* first one */
			regionmask = 0;
		    flags |= WF_REGION;

		    l = *p - '0';
		    if (l > spin->si_region_count)
		    {
			smsg((char_u *)_("Invalid region nr in %s line %d: %s"),
							  fname, lnum, p);
			break;
		    }
		    regionmask |= 1 << (l - 1);
		}
		else
		{
		    smsg((char_u *)_("Unrecognized flags in %s line %d: %s"),
							      fname, lnum, p);
		    break;
		}
		++p;
	    }
	}

	/* Skip non-ASCII words when "spin->si_ascii" is TRUE. */
	if (spin->si_ascii && has_non_ascii(line))
	{
	    ++non_ascii;
	    continue;
	}

	/* Normal word: store it. */
	if (store_word(spin, line, flags, regionmask, NULL, FALSE) == FAIL)
	{
	    retval = FAIL;
	    break;
	}
	did_word = TRUE;
    }

    vim_free(pc);
    fclose(fd);

    if (spin->si_ascii && non_ascii > 0)
    {
	vim_snprintf((char *)IObuff, IOSIZE,
		  _("Ignored %d words with non-ASCII characters"), non_ascii);
	spell_message(spin, IObuff);
    }

    return retval;
}

#define BY_NOFLAGS	0	/* end of word without flags or region; for
				 * postponed prefix: no <pflags> */

#define BY_FLAGS	2	/* end of word, <flags> byte follows; for
				 * postponed prefix: <pflags> follows */

#define BY_FLAGS2	3	/* end of word, <flags> and <flags2> bytes
				 * follow; never used in prefix tree */

#define VIMSPELLMAGIC "VIMspell"  /* string at start of Vim spell file */

#define SN_COMPOUND	8	/* compound words section */

#define SN_SUGFILE	11	/* timestamp for .sug file */

#define SN_WORDS	13	/* common words */

#define SN_NOSPLITSUGS	14	/* don't split word for suggestions */

#define SN_NOCOMPOUNDSUGS 16	/* don't compound for suggestions */

#define SN_END		255	/* end of sections */

static int set_spell_finish(spelltab_T	*new_st);

static int write_spell_prefcond(FILE *fd, garray_T *gap);

static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *lenp);

static int read_region_section(FILE *fd, slang_T *slang, int len);

static int read_charflags_section(FILE *fd);

static int read_prefcond_section(FILE *fd, slang_T *lp);

static int read_rep_section(FILE *fd, garray_T *gap, short *first);

static int read_sal_section(FILE *fd, slang_T *slang);

static int read_words_section(FILE *fd, slang_T *lp, int len);

static int read_sofo_section(FILE *fd, slang_T *slang);

static int read_compound(FILE *fd, slang_T *slang, int len);

static int set_sofo(slang_T *lp, char_u *from, char_u *to);

static int *mb_str2wide(char_u *s);

static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt);

static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr);

static void spell_reload_one(char_u *fname, int added_word);

static void set_spell_charflags(char_u *flags, int cnt, char_u *upp);

static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp);

static void set_map_str(slang_T *lp, char_u *map);

static char *e_spell_trunc = N_("E758: Truncated spell file");

static char *e_afftrailing = N_("Trailing text in %s line %d: %s");

static char *e_affname = N_("Affix name too long in %s line %d: %s");

static char *e_affform = N_("E761: Format error in affix file FOL, LOW or UPP");

static char *e_affrange = N_("E762: Character in FOL, LOW or UPP is out of range");

static char *msg_compressing = N_("Compressing word tree...");

#define MAXLINELEN  500		/* Maximum length in bytes of a line in a .aff
				   and .dic file. */


read_tree_node(
    FILE	*fd,
    char_u	*byts,
    idx_T	*idxs,
    int		maxidx,		    /* size of arrays */
    idx_T	startidx,	    /* current index in "byts" and "idxs" */
    int		prefixtree,	    /* TRUE for reading PREFIXTREE */
    int		maxprefcondnr)	    /* maximum for <prefcondnr> */
{
    int		len;
    int		i;
    int		n;
    idx_T	idx = startidx;
    int		c;
    int		c2;
#define SHARED_MASK	0x8000000

    len = getc(fd);					/* <siblingcount> */
    if (len <= 0)
	return SP_TRUNCERROR;

    if (startidx + len >= maxidx)
	return SP_FORMERROR;
    byts[idx++] = len;

    /* Read the byte values, flag/region bytes and shared indexes. */
    for (i = 1; i <= len; ++i)
    {
	c = getc(fd);					/* <byte> */
	if (c < 0)
	    return SP_TRUNCERROR;
	if (c <= BY_SPECIAL)
	{
	    if (c == BY_NOFLAGS && !prefixtree)
	    {
		/* No flags, all regions. */
		idxs[idx] = 0;
		c = 0;
	    }
	    else if (c != BY_INDEX)
	    {
		if (prefixtree)
		{
		    /* Read the optional pflags byte, the prefix ID and the
		     * condition nr.  In idxs[] store the prefix ID in the low
		     * byte, the condition index shifted up 8 bits, the flags
		     * shifted up 24 bits. */
		    if (c == BY_FLAGS)
			c = getc(fd) << 24;		/* <pflags> */
		    else
			c = 0;

		    c |= getc(fd);			/* <affixID> */

		    n = get2c(fd);			/* <prefcondnr> */
		    if (n >= maxprefcondnr)
			return SP_FORMERROR;
		    c |= (n << 8);
		}
		else /* c must be BY_FLAGS or BY_FLAGS2 */
		{
		    /* Read flags and optional region and prefix ID.  In
		     * idxs[] the flags go in the low two bytes, region above
		     * that and prefix ID above the region. */
		    c2 = c;
		    c = getc(fd);			/* <flags> */
		    if (c2 == BY_FLAGS2)
			c = (getc(fd) << 8) + c;	/* <flags2> */
		    if (c & WF_REGION)
			c = (getc(fd) << 16) + c;	/* <region> */
		    if (c & WF_AFX)
			c = (getc(fd) << 24) + c;	/* <affixID> */
		}

		idxs[idx] = c;
		c = 0;
	    }
	    else /* c == BY_INDEX */
	    {
							/* <nodeidx> */
		n = get3c(fd);
		if (n < 0 || n >= maxidx)
		    return SP_FORMERROR;
		idxs[idx] = n + SHARED_MASK;
		c = getc(fd);				/* <xbyte> */
	    }
	}
	byts[idx++] = c;
    }

    /* Recursively read the children for non-shared siblings.
     * Skip the end-of-word ones (zero byte value) and the shared ones (and
     * remove SHARED_MASK) */
    for (i = 1; i <= len; ++i)
	if (byts[startidx + i] != 0)
	{
	    if (idxs[startidx + i] & SHARED_MASK)
		idxs[startidx + i] &= ~SHARED_MASK;
	    else
	    {
		idxs[startidx + i] = idx;
		idx = read_tree_node(fd, byts, idxs, maxidx, idx,
						     prefixtree, maxprefcondnr);
		if (idx < 0)
		    break;
	    }
	}

    return idx;
}

#define BY_NOFLAGS	0	/* end of word without flags or region; for
				 * postponed prefix: no <pflags> */

#define BY_INDEX	1	/* child is shared, index follows */

#define BY_FLAGS	2	/* end of word, <flags> byte follows; for
				 * postponed prefix: <pflags> follows */

#define BY_FLAGS2	3	/* end of word, <flags> and <flags2> bytes
				 * follow; never used in prefix tree */

#define BY_SPECIAL  BY_FLAGS2	/* highest special byte value */

#define SAL_F0LLOWUP		1

#define SAL_COLLAPSE		2

#define SAL_REM_ACCENTS		4

#define VIMSPELLMAGIC "VIMspell"  /* string at start of Vim spell file */

#define VIMSPELLMAGICL 8

#define VIMSPELLVERSION 50

#define SN_REGION	0	/* <regionname> section */

#define SN_CHARFLAGS	1	/* charflags section */

#define SN_MIDWORD	2	/* <midword> section */

#define SN_PREFCOND	3	/* <prefcond> section */

#define SN_REP		4	/* REP items section */

#define SN_SAL		5	/* SAL items section */

#define SN_SOFO		6	/* soundfolding section */

#define SN_MAP		7	/* MAP items section */

#define SN_COMPOUND	8	/* compound words section */

#define SN_SYLLABLE	9	/* syllable section */

#define SN_NOBREAK	10	/* NOBREAK section */

#define SN_SUGFILE	11	/* timestamp for .sug file */

#define SN_REPSAL	12	/* REPSAL items section */

#define SN_WORDS	13	/* common words */

#define SN_NOSPLITSUGS	14	/* don't split word for suggestions */

#define SN_INFO		15	/* info section */

#define SN_NOCOMPOUNDSUGS 16	/* don't compound for suggestions */

#define SN_END		255	/* end of sections */

#define SNF_REQUIRED	1	/* <sectionflags>: required section */

#define CF_WORD		0x01

#define CF_UPPER	0x02

static int set_spell_finish(spelltab_T	*new_st);

static int write_spell_prefcond(FILE *fd, garray_T *gap);

static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *lenp);

static int read_region_section(FILE *fd, slang_T *slang, int len);

static int read_charflags_section(FILE *fd);

static int read_prefcond_section(FILE *fd, slang_T *lp);

static int read_rep_section(FILE *fd, garray_T *gap, short *first);

static int read_sal_section(FILE *fd, slang_T *slang);

static int read_words_section(FILE *fd, slang_T *lp, int len);

static int read_sofo_section(FILE *fd, slang_T *slang);

static int read_compound(FILE *fd, slang_T *slang, int len);

static int set_sofo(slang_T *lp, char_u *from, char_u *to);

static int *mb_str2wide(char_u *s);

static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt);

static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr);

static void spell_reload_one(char_u *fname, int added_word);

static void set_spell_charflags(char_u *flags, int cnt, char_u *upp);


spell_check_msm(void)
{
    char_u	*p = p_msm;
    long	start = 0;
    long	incr = 0;
    long	added = 0;

    if (!VIM_ISDIGIT(*p))
	return FAIL;
    /* block count = (value * 1024) / SBLOCKSIZE (but avoid overflow)*/
    start = (getdigits(&p) * 10) / (SBLOCKSIZE / 102);
    if (*p != ',')
	return FAIL;
    ++p;
    if (!VIM_ISDIGIT(*p))
	return FAIL;
    incr = (getdigits(&p) * 102) / (SBLOCKSIZE / 10);
    if (*p != ',')
	return FAIL;
    ++p;
    if (!VIM_ISDIGIT(*p))
	return FAIL;
    added = getdigits(&p) * 1024;
    if (*p != NUL)
	return FAIL;

    if (start == 0 || incr == 0 || added == 0 || incr > start)
	return FAIL;

    compress_start = start;
    compress_inc = incr;
    compress_added = added;
    return OK;
}

#define BY_SPECIAL  BY_FLAGS2	/* highest special byte value */

#define VIMSPELLMAGIC "VIMspell"  /* string at start of Vim spell file */

static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *lenp);

static int set_sofo(slang_T *lp, char_u *from, char_u *to);

static void set_sal_first(slang_T *lp);

static int *mb_str2wide(char_u *s);

static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt);

static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr);

static void spell_reload_one(char_u *fname, int added_word);

static void set_spell_charflags(char_u *flags, int cnt, char_u *upp);

static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp);

static void set_map_str(slang_T *lp, char_u *map);

static char *e_affname = N_("Affix name too long in %s line %d: %s");

typedef struct afffile_S
{
    char_u	*af_enc;	/* "SET", normalized, alloc'ed string or NULL */
    int		af_flagtype;	/* AFT_CHAR, AFT_LONG, AFT_NUM or AFT_CAPLONG */
    unsigned	af_rare;	/* RARE ID for rare word */
    unsigned	af_keepcase;	/* KEEPCASE ID for keep-case word */
    unsigned	af_bad;		/* BAD ID for banned word */
    unsigned	af_needaffix;	/* NEEDAFFIX ID */
    unsigned	af_circumfix;	/* CIRCUMFIX ID */
    unsigned	af_needcomp;	/* NEEDCOMPOUND ID */
    unsigned	af_comproot;	/* COMPOUNDROOT ID */
    unsigned	af_compforbid;	/* COMPOUNDFORBIDFLAG ID */
    unsigned	af_comppermit;	/* COMPOUNDPERMITFLAG ID */
    unsigned	af_nosuggest;	/* NOSUGGEST ID */
    int		af_pfxpostpone;	/* postpone prefixes without chop string and
				   without flags */
    int		af_ignoreextra;	/* IGNOREEXTRA present */
    hashtab_T	af_pref;	/* hashtable for prefixes, affheader_T */
    hashtab_T	af_suff;	/* hashtable for suffixes, affheader_T */
    hashtab_T	af_comp;	/* hashtable for compound flags, compitem_T */
} afffile_T;

struct affentry_S
{
    affentry_T	*ae_next;	/* next affix with same name/number */
    char_u	*ae_chop;	/* text to chop off basic word (can be NULL) */
    char_u	*ae_add;	/* text to add to basic word (can be NULL) */
    char_u	*ae_flags;	/* flags on the affix (can be NULL) */
    char_u	*ae_cond;	/* condition (NULL for ".") */
    regprog_T	*ae_prog;	/* regexp program for ae_cond or NULL */
    char	ae_compforbid;	/* COMPOUNDFORBIDFLAG found */
    char	ae_comppermit;	/* COMPOUNDPERMITFLAG found */
}

# define AH_KEY_LEN 17		/* 2 x 8 bytes + NUL */

# define AH_KEY_LEN 7		/* 6 digits + NUL */


set_sofo(slang_T *lp, char_u *from, char_u *to)
{
    int		i;

#ifdef FEAT_MBYTE
    garray_T	*gap;
    char_u	*s;
    char_u	*p;
    int		c;
    int		*inp;

    if (has_mbyte)
    {
	/* Use "sl_sal" as an array with 256 pointers to a list of wide
	 * characters.  The index is the low byte of the character.
	 * The list contains from-to pairs with a terminating NUL.
	 * sl_sal_first[] is used for latin1 "from" characters. */
	gap = &lp->sl_sal;
	ga_init2(gap, sizeof(int *), 1);
	if (ga_grow(gap, 256) == FAIL)
	    return SP_OTHERERROR;
	vim_memset(gap->ga_data, 0, sizeof(int *) * 256);
	gap->ga_len = 256;

	/* First count the number of items for each list.  Temporarily use
	 * sl_sal_first[] for this. */
	for (p = from, s = to; *p != NUL && *s != NUL; )
	{
	    c = mb_cptr2char_adv(&p);
	    mb_cptr_adv(s);
	    if (c >= 256)
		++lp->sl_sal_first[c & 0xff];
	}
	if (*p != NUL || *s != NUL)	    /* lengths differ */
	    return SP_FORMERROR;

	/* Allocate the lists. */
	for (i = 0; i < 256; ++i)
	    if (lp->sl_sal_first[i] > 0)
	    {
		p = alloc(sizeof(int) * (lp->sl_sal_first[i] * 2 + 1));
		if (p == NULL)
		    return SP_OTHERERROR;
		((int **)gap->ga_data)[i] = (int *)p;
		*(int *)p = 0;
	    }

	/* Put the characters up to 255 in sl_sal_first[] the rest in a sl_sal
	 * list. */
	vim_memset(lp->sl_sal_first, 0, sizeof(salfirst_T) * 256);
	for (p = from, s = to; *p != NUL && *s != NUL; )
	{
	    c = mb_cptr2char_adv(&p);
	    i = mb_cptr2char_adv(&s);
	    if (c >= 256)
	    {
		/* Append the from-to chars at the end of the list with
		 * the low byte. */
		inp = ((int **)gap->ga_data)[c & 0xff];
		while (*inp != 0)
		    ++inp;
		*inp++ = c;		/* from char */
		*inp++ = i;		/* to char */
		*inp++ = NUL;		/* NUL at the end */
	    }
	    else
		/* mapping byte to char is done in sl_sal_first[] */
		lp->sl_sal_first[c] = i;
	}
    }
    else
#endif
    {
	/* mapping bytes to bytes is done in sl_sal_first[] */
	if (STRLEN(from) != STRLEN(to))
	    return SP_FORMERROR;

	for (i = 0; to[i] != NUL; ++i)
	    lp->sl_sal_first[from[i]] = to[i];
	lp->sl_sal.ga_len = 1;		/* indicates we have soundfolding */
    }

    return 0;
}

#define BY_NOFLAGS	0	/* end of word without flags or region; for
				 * postponed prefix: no <pflags> */

#define BY_INDEX	1	/* child is shared, index follows */

#define BY_FLAGS	2	/* end of word, <flags> byte follows; for
				 * postponed prefix: <pflags> follows */

#define BY_FLAGS2	3	/* end of word, <flags> and <flags2> bytes
				 * follow; never used in prefix tree */

#define BY_SPECIAL  BY_FLAGS2	/* highest special byte value */

#define SN_REP		4	/* REP items section */

#define SN_SAL		5	/* SAL items section */

#define SN_SOFO		6	/* soundfolding section */

#define SN_MAP		7	/* MAP items section */

#define SN_SUGFILE	11	/* timestamp for .sug file */

#define SN_REPSAL	12	/* REPSAL items section */

#define SN_NOSPLITSUGS	14	/* don't split word for suggestions */

#define SN_NOCOMPOUNDSUGS 16	/* don't compound for suggestions */

#define SN_END		255	/* end of sections */

static int set_spell_finish(spelltab_T	*new_st);

static int write_spell_prefcond(FILE *fd, garray_T *gap);

static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *lenp);

static int read_region_section(FILE *fd, slang_T *slang, int len);

static int read_charflags_section(FILE *fd);

static int read_prefcond_section(FILE *fd, slang_T *lp);

static int read_rep_section(FILE *fd, garray_T *gap, short *first);

static int read_sal_section(FILE *fd, slang_T *slang);

static int read_words_section(FILE *fd, slang_T *lp, int len);

static int read_sofo_section(FILE *fd, slang_T *slang);

static int read_compound(FILE *fd, slang_T *slang, int len);

static int set_sofo(slang_T *lp, char_u *from, char_u *to);

static void set_sal_first(slang_T *lp);

static int *mb_str2wide(char_u *s);

static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt);

static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr);

static void spell_reload_one(char_u *fname, int added_word);

static void set_spell_charflags(char_u *flags, int cnt, char_u *upp);

static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp);

static void set_map_str(slang_T *lp, char_u *map);

static char *e_spell_trunc = N_("E758: Truncated spell file");

static char *e_afftrailing = N_("Trailing text in %s line %d: %s");

static char *e_affname = N_("Affix name too long in %s line %d: %s");

static char *e_affform = N_("E761: Format error in affix file FOL, LOW or UPP");

static char *e_affrange = N_("E762: Character in FOL, LOW or UPP is out of range");

static char *msg_compressing = N_("Compressing word tree...");

#define MAXLINELEN  500		/* Maximum length in bytes of a line in a .aff
				   and .dic file. */


set_spell_charflags(
    char_u	*flags,
    int		cnt,	    /* length of "flags" */
    char_u	*fol)
{
    /* We build the new tables here first, so that we can compare with the
     * previous one. */
    spelltab_T	new_st;
    int		i;
    char_u	*p = fol;
    int		c;

    clear_spell_chartab(&new_st);

    for (i = 0; i < 128; ++i)
    {
	if (i < cnt)
	{
	    new_st.st_isw[i + 128] = (flags[i] & CF_WORD) != 0;
	    new_st.st_isu[i + 128] = (flags[i] & CF_UPPER) != 0;
	}

	if (*p != NUL)
	{
#ifdef FEAT_MBYTE
	    c = mb_ptr2char_adv(&p);
#else
	    c = *p++;
#endif
	    new_st.st_fold[i + 128] = c;
	    if (i + 128 != c && new_st.st_isu[i + 128] && c < 256)
		new_st.st_upper[c] = i + 128;
	}
    }

    (void)set_spell_finish(&new_st);
}

#define BY_NOFLAGS	0	/* end of word without flags or region; for
				 * postponed prefix: no <pflags> */

#define BY_FLAGS	2	/* end of word, <flags> byte follows; for
				 * postponed prefix: <pflags> follows */

#define BY_FLAGS2	3	/* end of word, <flags> and <flags2> bytes
				 * follow; never used in prefix tree */

#define SN_SUGFILE	11	/* timestamp for .sug file */

#define SN_NOSPLITSUGS	14	/* don't split word for suggestions */

#define SN_NOCOMPOUNDSUGS 16	/* don't compound for suggestions */

#define CF_WORD		0x01

#define CF_UPPER	0x02

static int set_spell_finish(spelltab_T	*new_st);

static int write_spell_prefcond(FILE *fd, garray_T *gap);

static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *lenp);

static int read_region_section(FILE *fd, slang_T *slang, int len);

static int read_charflags_section(FILE *fd);

static int read_prefcond_section(FILE *fd, slang_T *lp);

static int read_rep_section(FILE *fd, garray_T *gap, short *first);

static int read_sal_section(FILE *fd, slang_T *slang);

static int read_words_section(FILE *fd, slang_T *lp, int len);

static int read_sofo_section(FILE *fd, slang_T *slang);

static int read_compound(FILE *fd, slang_T *slang, int len);

static int set_sofo(slang_T *lp, char_u *from, char_u *to);

static void set_sal_first(slang_T *lp);

static int *mb_str2wide(char_u *s);

static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt);

static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr);

static void spell_reload_one(char_u *fname, int added_word);

static void set_spell_charflags(char_u *flags, int cnt, char_u *upp);

static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp);

static void set_map_str(slang_T *lp, char_u *map);

#define MAXLINELEN  500		/* Maximum length in bytes of a line in a .aff
				   and .dic file. */


getroom(
    spellinfo_T *spin,
    size_t	len,		/* length needed */
    int		align)		/* align for pointer */
{
    char_u	*p;
    sblock_T	*bl = spin->si_blocks;

    if (align && bl != NULL)
	/* Round size up for alignment.  On some systems structures need to be
	 * aligned to the size of a pointer (e.g., SPARC). */
	bl->sb_used = (bl->sb_used + sizeof(char *) - 1)
						      & ~(sizeof(char *) - 1);

    if (bl == NULL || bl->sb_used + len > SBLOCKSIZE)
    {
	if (len >= SBLOCKSIZE)
	    bl = NULL;
	else
	    /* Allocate a block of memory. It is not freed until much later. */
	    bl = (sblock_T *)alloc_clear(
				   (unsigned)(sizeof(sblock_T) + SBLOCKSIZE));
	if (bl == NULL)
	{
	    if (!spin->si_did_emsg)
	    {
		EMSG(_("E845: Insufficient memory, word list will be incomplete"));
		spin->si_did_emsg = TRUE;
	    }
	    return NULL;
	}
	bl->sb_next = spin->si_blocks;
	spin->si_blocks = bl;
	bl->sb_used = 0;
	++spin->si_blocks_cnt;
    }

    p = bl->sb_data + bl->sb_used;
    bl->sb_used += (int)len;

    return p;
}

#define BY_NOFLAGS	0	/* end of word without flags or region; for
				 * postponed prefix: no <pflags> */

#define BY_FLAGS	2	/* end of word, <flags> byte follows; for
				 * postponed prefix: <pflags> follows */

#define BY_FLAGS2	3	/* end of word, <flags> and <flags2> bytes
				 * follow; never used in prefix tree */

#define SN_SUGFILE	11	/* timestamp for .sug file */

#define SN_NOSPLITSUGS	14	/* don't split word for suggestions */

#define SN_NOCOMPOUNDSUGS 16	/* don't compound for suggestions */

static int set_spell_finish(spelltab_T	*new_st);

static int write_spell_prefcond(FILE *fd, garray_T *gap);

static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *lenp);

static int read_region_section(FILE *fd, slang_T *slang, int len);

static int read_charflags_section(FILE *fd);

static int read_prefcond_section(FILE *fd, slang_T *lp);

static int read_rep_section(FILE *fd, garray_T *gap, short *first);

static int read_sal_section(FILE *fd, slang_T *slang);

static int read_words_section(FILE *fd, slang_T *lp, int len);

static int read_sofo_section(FILE *fd, slang_T *slang);

static int read_compound(FILE *fd, slang_T *slang, int len);

static int set_sofo(slang_T *lp, char_u *from, char_u *to);

static int *mb_str2wide(char_u *s);

static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt);

static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr);

static void spell_reload_one(char_u *fname, int added_word);

static void set_spell_charflags(char_u *flags, int cnt, char_u *upp);

static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp);

static void set_map_str(slang_T *lp, char_u *map);

static char *e_spell_trunc = N_("E758: Truncated spell file");

static char *e_afftrailing = N_("Trailing text in %s line %d: %s");

static char *e_affname = N_("Affix name too long in %s line %d: %s");

static char *e_affform = N_("E761: Format error in affix file FOL, LOW or UPP");

static char *e_affrange = N_("E762: Character in FOL, LOW or UPP is out of range");

static char *msg_compressing = N_("Compressing word tree...");

#define MAXLINELEN  500		/* Maximum length in bytes of a line in a .aff
				   and .dic file. */


set_map_str(slang_T *lp, char_u *map)
{
    char_u	*p;
    int		headc = 0;
    int		c;
    int		i;

    if (*map == NUL)
    {
	lp->sl_has_map = FALSE;
	return;
    }
    lp->sl_has_map = TRUE;

    /* Init the array and hash tables empty. */
    for (i = 0; i < 256; ++i)
	lp->sl_map_array[i] = 0;
#ifdef FEAT_MBYTE
    hash_init(&lp->sl_map_hash);
#endif

    /*
     * The similar characters are stored separated with slashes:
     * "aaa/bbb/ccc/".  Fill sl_map_array[c] with the character before c and
     * before the same slash.  For characters above 255 sl_map_hash is used.
     */
    for (p = map; *p != NUL; )
    {
#ifdef FEAT_MBYTE
	c = mb_cptr2char_adv(&p);
#else
	c = *p++;
#endif
	if (c == '/')
	    headc = 0;
	else
	{
	    if (headc == 0)
		 headc = c;

#ifdef FEAT_MBYTE
	    /* Characters above 255 don't fit in sl_map_array[], put them in
	     * the hash table.  Each entry is the char, a NUL the headchar and
	     * a NUL. */
	    if (c >= 256)
	    {
		int	    cl = mb_char2len(c);
		int	    headcl = mb_char2len(headc);
		char_u	    *b;
		hash_T	    hash;
		hashitem_T  *hi;

		b = alloc((unsigned)(cl + headcl + 2));
		if (b == NULL)
		    return;
		mb_char2bytes(c, b);
		b[cl] = NUL;
		mb_char2bytes(headc, b + cl + 1);
		b[cl + 1 + headcl] = NUL;
		hash = hash_hash(b);
		hi = hash_lookup(&lp->sl_map_hash, b, hash);
		if (HASHITEM_EMPTY(hi))
		    hash_add_item(&lp->sl_map_hash, hi, b, hash);
		else
		{
		    /* This should have been checked when generating the .spl
		     * file. */
		    EMSG(_("E783: duplicate char in MAP entry"));
		    vim_free(b);
		}
	    }
	    else
#endif
		lp->sl_map_array[c] = headc;
	}
    }
}

#define BY_NOFLAGS	0	/* end of word without flags or region; for
				 * postponed prefix: no <pflags> */

#define BY_FLAGS	2	/* end of word, <flags> byte follows; for
				 * postponed prefix: <pflags> follows */

#define BY_FLAGS2	3	/* end of word, <flags> and <flags2> bytes
				 * follow; never used in prefix tree */

#define VIMSPELLMAGIC "VIMspell"  /* string at start of Vim spell file */

#define SN_MAP		7	/* MAP items section */

#define SN_SUGFILE	11	/* timestamp for .sug file */

#define SN_NOSPLITSUGS	14	/* don't split word for suggestions */

#define SN_NOCOMPOUNDSUGS 16	/* don't compound for suggestions */

static int set_spell_finish(spelltab_T	*new_st);

static int write_spell_prefcond(FILE *fd, garray_T *gap);

static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *lenp);

static int read_region_section(FILE *fd, slang_T *slang, int len);

static int read_charflags_section(FILE *fd);

static int read_prefcond_section(FILE *fd, slang_T *lp);

static int read_rep_section(FILE *fd, garray_T *gap, short *first);

static int read_sal_section(FILE *fd, slang_T *slang);

static int read_words_section(FILE *fd, slang_T *lp, int len);

static int read_sofo_section(FILE *fd, slang_T *slang);

static int read_compound(FILE *fd, slang_T *slang, int len);

static int set_sofo(slang_T *lp, char_u *from, char_u *to);

static void set_sal_first(slang_T *lp);

static int *mb_str2wide(char_u *s);

static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt);

static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr);

static void spell_reload_one(char_u *fname, int added_word);

static void set_spell_charflags(char_u *flags, int cnt, char_u *upp);

static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp);

static void set_map_str(slang_T *lp, char_u *map);

static char *e_spell_trunc = N_("E758: Truncated spell file");

static char *e_afftrailing = N_("Trailing text in %s line %d: %s");

static char *e_affname = N_("Affix name too long in %s line %d: %s");

static char *e_affform = N_("E761: Format error in affix file FOL, LOW or UPP");

static char *e_affrange = N_("E762: Character in FOL, LOW or UPP is out of range");

static char *msg_compressing = N_("Compressing word tree...");

#define MAXLINELEN  500		/* Maximum length in bytes of a line in a .aff
				   and .dic file. */


affitem2flag(
    int		flagtype,
    char_u	*item,
    char_u	*fname,
    int		lnum)
{
    unsigned	res;
    char_u	*p = item;

    res = get_affitem(flagtype, &p);
    if (res == 0)
    {
	if (flagtype == AFT_NUM)
	    smsg((char_u *)_("Flag is not a number in %s line %d: %s"),
							   fname, lnum, item);
	else
	    smsg((char_u *)_("Illegal flag in %s line %d: %s"),
							   fname, lnum, item);
    }
    if (*p != NUL)
    {
	smsg((char_u *)_(e_affname), fname, lnum, item);
	return 0;
    }

    return res;
}

static int set_spell_finish(spelltab_T	*new_st);

static int write_spell_prefcond(FILE *fd, garray_T *gap);

static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *lenp);

static int read_region_section(FILE *fd, slang_T *slang, int len);

static int read_charflags_section(FILE *fd);

static int read_prefcond_section(FILE *fd, slang_T *lp);

static int read_rep_section(FILE *fd, garray_T *gap, short *first);

static int read_sal_section(FILE *fd, slang_T *slang);

static int read_words_section(FILE *fd, slang_T *lp, int len);

static int read_sofo_section(FILE *fd, slang_T *slang);

static int read_compound(FILE *fd, slang_T *slang, int len);

static int set_sofo(slang_T *lp, char_u *from, char_u *to);

static int *mb_str2wide(char_u *s);

static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt);

static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr);

static void spell_reload_one(char_u *fname, int added_word);

static void set_spell_charflags(char_u *flags, int cnt, char_u *upp);

static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp);

static void set_map_str(slang_T *lp, char_u *map);

static char *e_afftrailing = N_("Trailing text in %s line %d: %s");

static char *e_affname = N_("Affix name too long in %s line %d: %s");

#define MAXLINELEN  500		/* Maximum length in bytes of a line in a .aff
				   and .dic file. */

typedef struct afffile_S
{
    char_u	*af_enc;	/* "SET", normalized, alloc'ed string or NULL */
    int		af_flagtype;	/* AFT_CHAR, AFT_LONG, AFT_NUM or AFT_CAPLONG */
    unsigned	af_rare;	/* RARE ID for rare word */
    unsigned	af_keepcase;	/* KEEPCASE ID for keep-case word */
    unsigned	af_bad;		/* BAD ID for banned word */
    unsigned	af_needaffix;	/* NEEDAFFIX ID */
    unsigned	af_circumfix;	/* CIRCUMFIX ID */
    unsigned	af_needcomp;	/* NEEDCOMPOUND ID */
    unsigned	af_comproot;	/* COMPOUNDROOT ID */
    unsigned	af_compforbid;	/* COMPOUNDFORBIDFLAG ID */
    unsigned	af_comppermit;	/* COMPOUNDPERMITFLAG ID */
    unsigned	af_nosuggest;	/* NOSUGGEST ID */
    int		af_pfxpostpone;	/* postpone prefixes without chop string and
				   without flags */
    int		af_ignoreextra;	/* IGNOREEXTRA present */
    hashtab_T	af_pref;	/* hashtable for prefixes, affheader_T */
    hashtab_T	af_suff;	/* hashtable for suffixes, affheader_T */
    hashtab_T	af_comp;	/* hashtable for compound flags, compitem_T */
} afffile_T;

#define AFT_NUM		3	/* flags are numbers, comma separated */


init_spellfile(void)
{
    char_u	*buf;
    int		l;
    char_u	*fname;
    char_u	*rtp;
    char_u	*lend;
    int		aspath = FALSE;
    char_u	*lstart = curbuf->b_s.b_p_spl;

    if (*curwin->w_s->b_p_spl != NUL && curwin->w_s->b_langp.ga_len > 0)
    {
	buf = alloc(MAXPATHL);
	if (buf == NULL)
	    return;

	/* Find the end of the language name.  Exclude the region.  If there
	 * is a path separator remember the start of the tail. */
	for (lend = curwin->w_s->b_p_spl; *lend != NUL
			&& vim_strchr((char_u *)",._", *lend) == NULL; ++lend)
	    if (vim_ispathsep(*lend))
	    {
		aspath = TRUE;
		lstart = lend + 1;
	    }

	/* Loop over all entries in 'runtimepath'.  Use the first one where we
	 * are allowed to write. */
	rtp = p_rtp;
	while (*rtp != NUL)
	{
	    if (aspath)
		/* Use directory of an entry with path, e.g., for
		 * "/dir/lg.utf-8.spl" use "/dir". */
		vim_strncpy(buf, curbuf->b_s.b_p_spl,
					    lstart - curbuf->b_s.b_p_spl - 1);
	    else
		/* Copy the path from 'runtimepath' to buf[]. */
		copy_option_part(&rtp, buf, MAXPATHL, ",");
	    if (filewritable(buf) == 2)
	    {
		/* Use the first language name from 'spelllang' and the
		 * encoding used in the first loaded .spl file. */
		if (aspath)
		    vim_strncpy(buf, curbuf->b_s.b_p_spl,
						  lend - curbuf->b_s.b_p_spl);
		else
		{
		    /* Create the "spell" directory if it doesn't exist yet. */
		    l = (int)STRLEN(buf);
		    vim_snprintf((char *)buf + l, MAXPATHL - l, "/spell");
		    if (filewritable(buf) != 2)
			vim_mkdir(buf, 0755);

		    l = (int)STRLEN(buf);
		    vim_snprintf((char *)buf + l, MAXPATHL - l,
				 "/%.*s", (int)(lend - lstart), lstart);
		}
		l = (int)STRLEN(buf);
		fname = LANGP_ENTRY(curwin->w_s->b_langp, 0)
							 ->lp_slang->sl_fname;
		vim_snprintf((char *)buf + l, MAXPATHL - l, ".%s.add",
			fname != NULL
			  && strstr((char *)gettail(fname), ".ascii.") != NULL
				       ? (char_u *)"ascii" : spell_enc());
		set_option_value((char_u *)"spellfile", 0L, buf, OPT_LOCAL);
		break;
	    }
	    aspath = FALSE;
	}

	vim_free(buf);
    }
}

#define BY_NOFLAGS	0	/* end of word without flags or region; for
				 * postponed prefix: no <pflags> */

#define BY_FLAGS	2	/* end of word, <flags> byte follows; for
				 * postponed prefix: <pflags> follows */

#define BY_FLAGS2	3	/* end of word, <flags> and <flags2> bytes
				 * follow; never used in prefix tree */

#define VIMSPELLMAGIC "VIMspell"  /* string at start of Vim spell file */

#define SN_SUGFILE	11	/* timestamp for .sug file */

#define SN_NOSPLITSUGS	14	/* don't split word for suggestions */

#define SN_NOCOMPOUNDSUGS 16	/* don't compound for suggestions */

#define SN_END		255	/* end of sections */

static int set_spell_finish(spelltab_T	*new_st);

static int write_spell_prefcond(FILE *fd, garray_T *gap);

static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *lenp);

static int read_region_section(FILE *fd, slang_T *slang, int len);

static int read_charflags_section(FILE *fd);

static int read_prefcond_section(FILE *fd, slang_T *lp);

static int read_rep_section(FILE *fd, garray_T *gap, short *first);

static int read_sal_section(FILE *fd, slang_T *slang);

static int read_words_section(FILE *fd, slang_T *lp, int len);

static int read_sofo_section(FILE *fd, slang_T *slang);

static int read_compound(FILE *fd, slang_T *slang, int len);

static int set_sofo(slang_T *lp, char_u *from, char_u *to);

static void set_sal_first(slang_T *lp);

static int *mb_str2wide(char_u *s);

static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt);

static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr);

static void spell_reload_one(char_u *fname, int added_word);

static void set_spell_charflags(char_u *flags, int cnt, char_u *upp);

static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp);

static void set_map_str(slang_T *lp, char_u *map);

static char *e_spell_trunc = N_("E758: Truncated spell file");

static char *e_afftrailing = N_("Trailing text in %s line %d: %s");

static char *e_affname = N_("Affix name too long in %s line %d: %s");

static char *e_affform = N_("E761: Format error in affix file FOL, LOW or UPP");

static char *e_affrange = N_("E762: Character in FOL, LOW or UPP is out of range");

static char *msg_compressing = N_("Compressing word tree...");

#define MAXLINELEN  500		/* Maximum length in bytes of a line in a .aff
				   and .dic file. */