/*
 *  ChkTeX, error searching & report routines.
 *  Copyright (C) 1995-96 Jens T. Berger Thielemann
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *  Contact the author at:
 *              Jens Berger
 *              Spektrumvn. 4
 *              N-0666 Oslo
 *              Norway
 *              E-mail: <jensthi@ifi.uio.no>
 *
 *
 */


#include "ChkTeX.h"
#include "FindErrs.h"
#include "OpSys.h"
#include "Utility.h"
#include "Resource.h"

#if HAVE_PCRE || HAVE_POSIX_ERE

#if HAVE_PCRE
#include <pcreposix.h>
#else
#include <regex.h>
#endif

#define REGEX_FLAGS REG_EXTENDED
#define NUM_MATCHES 10
#define ERROR_STRING_SIZE 100

regex_t* RegexArray = NULL;
regex_t* SilentRegex = NULL;
int NumRegexes = 0;

#endif

int FoundErr = EXIT_SUCCESS;
int LastWasComment = FALSE;
int SeenSpace = FALSE;
int FrenchSpacing = FALSE;

/***************************** ERROR MESSAGES ***************************/

#undef MSG
#define MSG(num, type, inuse, ctxt, text) {num, type, inuse, ctxt, text},

struct ErrMsg LaTeXMsgs[emMaxFault + 1] = {
    ERRMSGS {emMaxFault, etErr, iuOK, 0, INTERNFAULT}
};

#define istex(c)        (isalpha((unsigned char)c) || (AtLetter && (c == '@')))
#define CTYPE(func) \
static int my_##func(int c) \
{ \
   return(func(c)); \
}

#define SUPPRESSED_ON_LINE(c)  (LineSuppressions & ((uint64_t)1<<c))

#define INUSE(c) \
    ((LaTeXMsgs[(enum ErrNum) c].InUse == iuOK) && !SUPPRESSED_ON_LINE(c))

#define PSERR2(pos,len,err,a,b) \
    PrintError(err, CurStkName(&InputStack), RealBuf, pos, len, Line, a, b)

#define PSERRA(pos,len,err,a) \
    PrintError(err, CurStkName(&InputStack), RealBuf, pos, len, Line, a)

#define HEREA(len, err, a)     PSERRA(BufPtr - Buf - 1, len, err, a)
#define PSERR(pos,len,err)     PSERRA(pos,len,err,"")

#define HERE(len, err)         HEREA(len, err, "")

#define SKIP_BACK(ptr, c, check) \
    while((c = *ptr--)) \
    { \
        if (!(check))  break; \
    } \
    ptr++;

#define SKIP_AHEAD(ptr, c, check) \
    while((c = *ptr++)) \
    { \
        if (!(check)) \
            break; \
    } \
    ptr--;


/*  -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=- -=><=-  */

/*
 * A list of characters LaTeX considers as an end-of-sentence characters, which
 * should be detected when whether sentence spacing is correct.
 *
 */
static const char LTX_EosPunc[] = { '.', ':', '?', '!', 0 };

/*
 * General punctuation characters used on your system.
 */
static const char LTX_GenPunc[] = { ',', ';', 0 };

/*
 * A list of characters LaTeX considers as an small punctuation characters,
 * which should not be preceded by a \/.
 */
static const char LTX_SmallPunc[] = { '.', ',', 0 };

/*
 * A list of characters that could be considered to start a new
 * sentence, or not.
 *
 * This allows "Mr. ``X'' " to warn about the same as "Mr. X".
 *
 */
static const char LTX_BosPunc[] = {'`', '(', '[', 0};

/*
 * String used to delimit a line suppression.  This string must be
 * followed immediately by the number of the warning to be suppressed.
 * If more than one warning is to be suppressed, then multiple copies
 * of LineSuppDelim+number must be used.
 */
const char LineSuppDelim[] = "chktex ";

/*
 * String used to delimit a file suppression.  This string must be
 * followed immediately by the number of the warning to be suppressed.
 * If more than one warning is to be suppressed, then multiple copies
 * of FileSuppDelim+number must be used.
 */
const char FileSuppDelim[] = "chktex-file ";

/*
 * A bit field used to hold the suppressions for the current line.
 */
static uint64_t LineSuppressions;
/*
 * A bit field used to hold the suppressions of numbered user warnings
 * for the current line.
 */
static uint64_t UserLineSuppressions;

static unsigned long Line;

static const char *RealBuf;
static char *BufPtr;

static int ItFlag = efNone;
static int MathFlag = efNone;

NEWBUF(Buf, BUFFER_SIZE);
NEWBUF(CmdBuffer, BUFFER_SIZE);
NEWBUF(ArgBuffer, BUFFER_SIZE);

static enum ErrNum PerformCommand(const char *Cmd, char *Arg);

#ifdef isdigit
CTYPE(isdigit)
#else
#  define my_isdigit isdigit
#endif

#ifdef isalpha
CTYPE(isalpha)
#else
#  define my_isalpha isalpha
#endif

/*
 * Reads in a TeX token from Src and puts it in Dest.
 *
 */


static char *GetLTXToken(char *Src, char *Dest)
{
    int Char;

    if (Src && *Src)
    {
        if (*Src == '\\')
        {
            *Dest++ = *Src++;
            Char = *Dest++ = *Src++;

            if (istex(Char))
            {
                while (istex(Char))
                    Char = *Dest++ = *Src++;

                Src--;
                Dest--;
            }

        }
        else
            *Dest++ = *Src++;
        *Dest = 0;
    }
    else
        Src = NULL;

    return (Src);
}


/*
 * Scans the `SrcBuf' for a LaTeX arg, and puts that arg into `Dest'.
 * `Until' specifies what we'll copy. Assume the text is
 * "{foo}bar! qux} baz".
 *  GET_TOKEN       => "{foo}"
 *  GET_STRIP_TOKEN => "foo"
 *  '!'             => "{foo}bar!" (i.e. till the first "!")
 * Returns NULL if we can't find the argument, ptr to the first character
 * after the argument in other cases.
 *
 * If one of the tokens found is in the wl wordlist, and we're in the
 * outer most paren, and Until isn't a single character, we'll stop.
 * You may pass NULL as wl.
 *
 * We assume that you've previously skipped over leading spaces.
 *
 */

#define GET_TOKEN       256
#define GET_STRIP_TOKEN 257

static char *GetLTXArg(char *SrcBuf, char *OrigDest, const int Until,
	struct WordList *wl)
{
    char *Retval;
    char *TmpPtr;
    char *Dest = OrigDest;
    unsigned long DeliCnt = 0;

    *Dest = 0;
    TmpPtr = SrcBuf;

    switch (Until)
    {
    case GET_STRIP_TOKEN:
    case GET_TOKEN:
        while ((Retval = GetLTXToken(TmpPtr, Dest)))
        {
            switch (*Dest)
            {
            case '{':
                DeliCnt++;
                break;
            case '}':
                DeliCnt--;
            }
            Dest += Retval - TmpPtr;
            TmpPtr = Retval;

            if (!DeliCnt || ((DeliCnt == 1) && wl && HasWord(Dest, wl)))
                break;
        }

        if (Retval && (*OrigDest == '{') && (Until == GET_STRIP_TOKEN))
        {
            int len = strlen(OrigDest+1);
            memmove(OrigDest, OrigDest + 1, len  + 1);
            /* Strip the last '}' off */
            OrigDest[len-1] = 0;
        }
        break;
    default:
        DeliCnt = TRUE;
        while ((Retval = GetLTXArg(TmpPtr, Dest, GET_TOKEN, NULL)))
        {
            if (*Dest == Until)
                DeliCnt = FALSE;

            Dest += Retval - TmpPtr;
            TmpPtr = Retval;

            if (!DeliCnt)
                break;
        }
        break;
    }
    *Dest = 0;

    return (Retval);
}


static char *PreProcess(void)
{
    char *TmpPtr;

    /* Reset any line suppressions  */
    LineSuppressions = *(uint64_t *)StkTop(&FileSuppStack);
    UserLineSuppressions = *(uint64_t *)StkTop(&UserFileSuppStack);

    /* Kill comments. */
    strcpy(Buf, RealBuf);

    TmpPtr = Buf;

    LastWasComment = FALSE;
    while ((TmpPtr = strchr(TmpPtr, '%')))
    {
        char *EscapePtr = TmpPtr;
        int NumBackSlashes = 0;
        while (EscapePtr != Buf && EscapePtr[-1] == '\\')
        {
            ++NumBackSlashes;
            --EscapePtr;
        }

        /* If there is an even number of backslashes, then it's a comment. */
        if ((NumBackSlashes % 2) == 0)
        {
            LastWasComment = TRUE;
            PSERR(TmpPtr - Buf, 1, emComment);
            *TmpPtr = 0;
            /* Check for line suppressions */
            if (!NoLineSupp)
            {
                int error;
                const int MaxSuppressionBits = 63;

                /* Convert to lowercase to compare with LineSuppDelim */
                EscapePtr = ++TmpPtr; /* move past NUL terminator */
                while ( *EscapePtr )
                {
                    *EscapePtr = tolower((unsigned char)*EscapePtr);
                    ++EscapePtr;
                }

                EscapePtr = TmpPtr; /* Save it for later */
                while ((TmpPtr = strstr(TmpPtr, FileSuppDelim))) {
                    TmpPtr += STRLEN(FileSuppDelim);
                    error = atoi(TmpPtr);

                    if (abs(error) > MaxSuppressionBits)
                    {
                        PrintPrgErr(pmSuppTooHigh, error, MaxSuppressionBits);
                    }
                    uint64_t errbit = ((uint64_t)1 << abs(error));
                    if (error > 0)
                    {
                        *(uint64_t *)StkTop(&FileSuppStack) |= errbit;
                        LineSuppressions |= errbit;
                    }
                    else
                    {
                        *(uint64_t *)StkTop(&UserFileSuppStack) |= errbit;
                        UserLineSuppressions |= errbit;
                    }
                }
                TmpPtr = EscapePtr;

                while ((TmpPtr = strstr(TmpPtr, LineSuppDelim))) {

                    TmpPtr += STRLEN(LineSuppDelim);
                    error = atoi(TmpPtr);

                    if (abs(error) > MaxSuppressionBits)
                    {
                        PrintPrgErr(pmSuppTooHigh, error, MaxSuppressionBits);
                    }

                    if (error > 0)
                    {
                        LineSuppressions |= ((uint64_t)1 << error);
                    }
                    else
                    {
                        UserLineSuppressions |= ((uint64_t)1 << (-error));
                    }
                }
            }
            break;
        }
        TmpPtr++;
    }
    return (Buf);
}

/*
 * Interpret environments
 */

static void PerformEnv(char *Env, int Begin)
{
    static char VBStr[BUFFER_SIZE] = "";

    if (HasWord(Env, &MathEnvir))
    {
        if (Begin)
            PushMode(TRUE, &MathModeStack);
        else
        {
            if (!CurStkMode(&MathModeStack))
                PSERRA(BufPtr - Buf - 4, 1, emMathModeConfusion, "on");
            StkPop(&MathModeStack);
        }
    }

    if (HasWord(Env, &TextEnvir))
    {
        if (Begin)
            PushMode(FALSE, &MathModeStack);
        else
        {
            if (CurStkMode(&MathModeStack))
                PSERRA(BufPtr - Buf - 4, 1, emMathModeConfusion, "off");
            StkPop(&MathModeStack);
        }
    }

    if (Begin && HasWord(Env, &VerbEnvir))
    {
        VerbMode = TRUE;
        strcpy(VBStr, "\\end{");
        strcat(VBStr, Env);
        strcat(VBStr, "}");
        VerbStr = VBStr;
    }
}

static char *SkipVerb(void)
{
    char *TmpPtr = BufPtr;
    int TmpC;

    if (VerbMode && BufPtr)
    {
        if (!(TmpPtr = strstr(BufPtr, VerbStr)))
            BufPtr = &BufPtr[strlen(BufPtr)];
        else
        {
            VerbMode = FALSE;
            BufPtr = &TmpPtr[strlen(VerbStr)];
            SKIP_AHEAD(BufPtr, TmpC, LATEX_SPACE(TmpC));
            if (*BufPtr)
                PSERR(BufPtr - Buf, strlen(BufPtr) - 2, emIgnoreText);
        }
    }
    return (TmpPtr);
}

#define CHECKDOTS(wordlist, dtval) \
for(i = 0; (i < wordlist.Stack.Used) && !(Back && Front);  i++) \
 { if(!strafter(PstPtr, wordlist.Stack.Data[i])) \
         Back = dtval; \
   if(!strinfront(PrePtr, wordlist.Stack.Data[i])) \
         Front = dtval; }



/*
 * Checks that the dots are correct
 */

static enum DotLevel CheckDots(char *PrePtr, char *PstPtr)
{
    unsigned long i;
    int TmpC;
    enum DotLevel Front = dtUnknown, Back = dtUnknown;

    if (CurStkMode(&MathModeStack))
    {
        PrePtr--;
#define SKIP_EMPTIES(macro, ptr) macro(ptr, TmpC, \
(LATEX_SPACE(TmpC) || (TmpC == '{') || (TmpC == '}')))

        SKIP_EMPTIES(SKIP_BACK, PrePtr);
        SKIP_EMPTIES(SKIP_AHEAD, PstPtr);

        CHECKDOTS(CenterDots, dtCDots);

        if (!(Front && Back))
        {
            CHECKDOTS(LowDots, dtLDots);
        }
        return (Front & Back);
    }
    else
        return (dtLDots);

}

static const char *Dot2Str(enum DotLevel dl)
{
    const char *Retval = INTERNFAULT;
    switch (dl)
    {
    case dtUnknown:
        Retval = "\\cdots or \\ldots";
        break;
    case dtDots:
        Retval = "\\dots";
        break;
    case dtCDots:
        Retval = "\\cdots";
        break;
    case dtLDots:
        Retval = "\\ldots";
        break;
    }
    return Retval;
}

/*
 * Wipes a command, according to the definition in WIPEARG
 */

static void WipeArgument(const char *Cmd, char *CmdPtr)
{
    unsigned long CmdLen = strlen(Cmd);
    const char *Format;
    char *TmpPtr;
    int c, TmpC;

    if (Cmd && *Cmd)
    {
        TmpPtr = &CmdPtr[CmdLen];
        Format = &Cmd[CmdLen + 1];

        while (TmpPtr && *TmpPtr && *Format)
        {
            switch (c = *Format++)
            {
            case '*':
                SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
                if (*TmpPtr == '*')
                    TmpPtr++;
                break;
            case '[':
                SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
                if (*TmpPtr == '[')
                    TmpPtr = GetLTXArg(TmpPtr, ArgBuffer, ']', NULL);
                break;
            case '(':
                SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
                if (*TmpPtr == '(')
                    TmpPtr = GetLTXArg(TmpPtr, ArgBuffer, ')', NULL);
                break;
            case '{':
                SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
                TmpPtr = GetLTXArg(TmpPtr, ArgBuffer, GET_TOKEN, NULL);
            case '}':
            case ']':
            case ')':
                break;
            default:
                PrintPrgErr(pmWrongWipeTemp, &Cmd[strlen(Cmd) + 1]);
                break;
            }
        }

        if (TmpPtr)
            strwrite(CmdPtr+CmdLen, VerbClear, TmpPtr - CmdPtr - CmdLen);
        else
            strxrep(CmdPtr+CmdLen, "()[]{}", *VerbClear);
    }
}

/*
 * Checks italic.
 *
 */

static void CheckItal(const char *Cmd)
{
    int TmpC;
    char *TmpPtr;
    if (HasWord(Cmd, &NonItalic))
        ItState = itOff;
    else if (HasWord(Cmd, &Italic))
        ItState = itOn;
    else if (HasWord(Cmd, &ItalCmd))
    {
        TmpPtr = BufPtr;
        SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
        if (*TmpPtr == '{')
        {
            ItFlag = ItState ? efItal : efNoItal;
            ItState = itOn;
        }
    }
}

/*
 * Interpret isolated commands.
 *
 */

static void PerformBigCmd(char *CmdPtr)
{
    char *TmpPtr;
    const char *ArgEndPtr;
    unsigned long CmdLen = strlen(CmdBuffer);
    int TmpC;
    enum ErrNum ErrNum;
    struct ErrInfo *ei;

    enum DotLevel dotlev, realdl = dtUnknown;

    TmpPtr = BufPtr;
    SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));

    ArgEndPtr = GetLTXArg(TmpPtr, ArgBuffer, GET_STRIP_TOKEN, NULL);

    /* Kill `\verb' commands */

    if (WipeVerb)
    {
        if (!strcmp(CmdBuffer, "\\verb"))
        {
            if (*BufPtr && (*BufPtr != '*' || BufPtr[1]))
            {
                if (*BufPtr == '*')
                    TmpPtr = strchr(&BufPtr[2], BufPtr[1]);
                else
                    TmpPtr = strchr(&BufPtr[1], *BufPtr);
                if (TmpPtr)
                    strwrite(CmdPtr, VerbClear, (TmpPtr - CmdPtr) + 1);
                else
                    PSERR(CmdPtr - Buf, 5, emNoArgFound);
            }
        }
    }

    if (HasWord(CmdBuffer, &IJAccent))
    {
        if (ArgEndPtr)
        {
            TmpPtr = ArgBuffer;
            SKIP_AHEAD(TmpPtr, TmpC, TmpC == '{');      /* } */

            if ((*TmpPtr == 'i') || (*TmpPtr == 'j'))
                PrintError(emAccent, CurStkName(&InputStack), RealBuf,
                           CmdPtr - Buf, (long)strlen(CmdBuffer), Line,
                           CmdBuffer, *TmpPtr,
                           CurStkMode(&MathModeStack) ? "math" : "");
        }
        else
            PSERR(CmdPtr - Buf, CmdLen, emNoArgFound);
    }

    if (HasWord(CmdBuffer, &NotPreSpaced) && isspace((unsigned char)CmdPtr[-1]))
        PSERRA(CmdPtr - Buf - 1, 1, emRemPSSpace, CmdBuffer);

    if ((TmpPtr = HasWord(CmdBuffer, &NoCharNext)))
    {
        char *BPtr = BufPtr;

        TmpPtr += strlen(TmpPtr) + 1;
        SKIP_AHEAD(BPtr, TmpC, LATEX_SPACE(TmpC));

        if (strchr(TmpPtr, *BPtr))
        {
            PSERR2(CmdPtr - Buf, CmdLen, emNoCharMean, CmdBuffer, *BPtr);
        }
    }

    /* LaTeX environment tracking */
    if (!strcmp(CmdBuffer, "\\begin") || !strcmp(CmdBuffer, "\\end"))
    {
        if (ArgEndPtr)
        {
            if (!strcmp(ArgBuffer, "document"))
                InHeader = FALSE;

            if (CmdBuffer[1] == 'b')
            {
                if (!(PushErr(ArgBuffer, Line, CmdPtr - Buf,
                              CmdLen, RealBuf, &EnvStack)))
                    PrintPrgErr(pmNoStackMem);
            }
            else
            {
                if ((ei = PopErr(&EnvStack)))
                {
                    if (strcmp(ei->Data, ArgBuffer))
                        PrintError(emExpectC, CurStkName(&InputStack), RealBuf,
                                   CmdPtr - Buf,
                                   (long) strlen(CmdBuffer),
                                   Line, ei->Data, ArgBuffer);

                    FreeErrInfo(ei);
                }
                else
                    PrintError(emSoloC, CurStkName(&InputStack), RealBuf,
                               CmdPtr - Buf,
                               (long) strlen(CmdBuffer),
                               Line, ArgBuffer);
            }

            PerformEnv(ArgBuffer, (int) CmdBuffer[1] == 'b');
        }
        else
            PSERR(CmdPtr - Buf, CmdLen, emNoArgFound);
    }

    /* ConTeXt \start \stop tracking */
    if (!strncmp(CmdBuffer, "\\start", 6) || !strncmp(CmdBuffer, "\\stop", 5))
    {
        if (CmdBuffer[3] == 'a') /* start */
        {
            TmpPtr = CmdBuffer + 6;
            if (!(PushErr(TmpPtr, Line, CmdPtr - Buf + 6,
                          CmdLen - 6, RealBuf, &ConTeXtStack)))
                PrintPrgErr(pmNoStackMem);
        }
        else
        {
            TmpPtr = CmdBuffer + 5;
            if ((ei = PopErr(&ConTeXtStack)))
            {
                if (strcmp(ei->Data, TmpPtr))
                    PrintError(emExpectConTeXt, CurStkName(&InputStack), RealBuf,
                               CmdPtr - Buf + 5,
                               (long) strlen(TmpPtr),
                               Line, ei->Data, TmpPtr);

                FreeErrInfo(ei);
            }
            else
            {
                PrintError(emSoloC, CurStkName(&InputStack), RealBuf,
                           CmdPtr - Buf,
                           (long) strlen(CmdBuffer),
                           Line, TmpPtr);
            }
        }
        /* TODO: Do I need to call PerformEnv? */
        /* It handles math and verbatim environments */
    }

    CheckItal(CmdBuffer);

    if ((ErrNum = PerformCommand(CmdBuffer, BufPtr)))
        PSERR(CmdPtr - Buf, CmdLen, ErrNum);

    if (!strcmp(CmdBuffer, "\\cdots"))
        realdl = dtCDots;

    if (!strcmp(CmdBuffer, "\\ldots"))
        realdl = dtLDots;

    if (!strcmp(CmdBuffer, "\\dots"))
        realdl = dtLDots;

    if (realdl != dtUnknown)
    {
        dotlev = CheckDots(CmdPtr, BufPtr);
        if (dotlev && (dotlev != realdl))
        {
            const char *cTmpPtr = Dot2Str(dotlev);
            PSERRA(CmdPtr - Buf, CmdLen, emEllipsis, cTmpPtr);
        }
    }

    if ((TmpPtr = HasWord(CmdBuffer, &WipeArg)))
        WipeArgument(TmpPtr, CmdPtr);
}

/*
 * Check user abbreviations. Pass a pointer to the `.';
 * also ensure that it's followed by spaces, etc.
 *
 * Note: We assume that all abbrevs have been transferred from
 * AbbrevCase into Abbrev.
 */

static void CheckAbbrevs(const char *Buffer)
{
    long i;
    char *TmpPtr;
    const char *AbbPtr;

    if (INUSE(emInterWord))
    {
        TmpPtr = TmpBuffer + Abbrev.MaxLen + 2;
        *TmpPtr = 0;
        AbbPtr = Buffer;

        for (i = Abbrev.MaxLen; i >= 0; i--)
        {
            *--TmpPtr = *AbbPtr--;
            if (!isalpha((unsigned char)*AbbPtr) &&
                /* Ignore spacing problems after commands if desired */
                (*AbbPtr != '\\' || (CmdSpace & csInterWord)) &&
                HasWord(TmpPtr, &Abbrev))
            {
                PSERR(Buffer - Buf + 1, 1, emInterWord);
            }
            if (!*AbbPtr)
                break;
        }
    }
}


/*
 * Check misc. things which can't be included in the main loop.
 *
 */

static void CheckRest(void)
{
    unsigned long Count;
    long CmdLen;
    char *UsrPtr;

    /* Search for user-specified warnings */

#if ! (HAVE_PCRE || HAVE_POSIX_ERE)

    if (INUSE(emUserWarnRegex) && UserWarnRegex.Stack.Used > 0)
    {
        PrintPrgErr(pmNoRegExp);
        ClearWord( &UserWarnRegex );
    }
    else if (INUSE(emUserWarn))
    {
        strcpy(TmpBuffer, Buf);
    }

#else

    if (INUSE(emUserWarnRegex) && UserWarnRegex.Stack.Used > 0)
    {
        static char error[ERROR_STRING_SIZE];
        static regmatch_t MatchVector[NUM_MATCHES];
        int rc;
        int len = strlen(TmpBuffer);
        strcpy(TmpBuffer, Buf);

        /* Compile all regular expressions if not already compiled. */
        if ( !RegexArray && UserWarnRegex.Stack.Used > 0 )
        {
            RegexArray = (regex_t*)malloc( sizeof(regex_t) * UserWarnRegex.Stack.Used );
            if (!RegexArray)
            {
                /* Allocation failed. */
                PrintPrgErr(pmNoRegexMem);
                ClearWord(&UserWarnRegex);
                NumRegexes = 0;
            }
            else
            {
                NumRegexes = 0;
                FORWL(Count, UserWarnRegex)
                {
                    char *pattern = UserWarnRegex.Stack.Data[Count];
                    char *CommentEnd = NULL;

                    /* See if it's got a special name that it goes by.
                       Only use the comment if it's at the very beginning. */
                    if ( strncmp(pattern,"(?#",3) == 0 )
                    {
                        CommentEnd = strchr(pattern, ')');
                        /* TODO: check for PCRE/POSIX only regexes */
                        if ( CommentEnd != NULL )
                        {
                            *CommentEnd = '\0';
                            /* We're leaking a little here, but this was never freed until exit anyway... */
                            UserWarnRegex.Stack.Data[NumRegexes] = pattern+3;

                            /* Compile past the end of the comment so that it works with POSIX too. */
                            pattern = CommentEnd + 1;
                        }
                    }

                    /* Ignore PCRE and POSIX specific regexes.
                     * This is mostly to make testing easier. */
                    if ( strncmp(pattern,"PCRE:",5) == 0 )
                    {
                        #if HAVE_PCRE
                        pattern += 5;
                        #else
                        continue;
                        #endif
                    }
                    if ( strncmp(pattern,"POSIX:",6) == 0 )
                    {
                        #if HAVE_POSIX_ERE
                        pattern += 6;
                        #else
                        continue;
                        #endif
                    }

                    rc = regcomp((regex_t*)(&RegexArray[NumRegexes]),
                                 pattern, REGEX_FLAGS);

                    /* Compilation failed: print the error message */
                    if (rc != 0)
                    {
                        /* TODO: decide whether a non-compiling regex should completely stop, or just be ignored */
                        regerror(rc,(regex_t*)(&RegexArray[NumRegexes]),
                                 error, ERROR_STRING_SIZE);
                        PrintPrgErr(pmRegexCompileFailed, pattern, error);
                    }
                    else
                    {
                        if ( !CommentEnd )
                        {
                            ((char*)UserWarnRegex.Stack.Data[NumRegexes])[0] = '\0';
                        }
                        ++NumRegexes;
                    }
                }
            }
        }

        for (Count = 0; Count < NumRegexes; ++Count)
        {
            int offset = 0;
            char *ErrMessage = UserWarnRegex.Stack.Data[Count];
            const int NamedWarning = strlen(ErrMessage) > 0;

            while (offset < len)
            {
                /* Check if this warning should be suppressed. */
                if (UserLineSuppressions && NamedWarning)
                {
                    /* The warning can be named with positive or negative numbers. */
                    int UserWarningNumber = abs(atoi(ErrMessage));
                    if (UserLineSuppressions & ((uint64_t)1 << UserWarningNumber))
                    {
                        break;
                    }
                }

                rc = regexec( (regex_t*)(&RegexArray[Count]), TmpBuffer+offset,
                              NUM_MATCHES, MatchVector, 0);
                /* Matching failed: handle error cases */
                if (rc != 0)
                {
                    switch(rc)
                    {
                        case REG_NOMATCH:
                            /* no match, no problem */
                            break;
                        default:
                            regerror(rc, (regex_t*)(&RegexArray[Count]),
                                     error, ERROR_STRING_SIZE);
                            PrintPrgErr(pmRegexMatchingError, error);
                            break;
                    }

                    offset = len; /* break out of loop */
                }
                else
                {
#define MATCH (MatchVector[0])
                    if ( NamedWarning )
                    {
                        /* User specified error message */
                        PSERR2(offset + MATCH.rm_so, MATCH.rm_eo - MATCH.rm_so,
                               emUserWarnRegex,
                               strlen(ErrMessage), ErrMessage);
                    }
                    else
                    {
                        /* Default -- show the match */
                        PSERR2(offset + MATCH.rm_so, MATCH.rm_eo - MATCH.rm_so,
                               emUserWarnRegex,
                               /* The format specifier expects an int */
                               (int)(MATCH.rm_eo - MATCH.rm_so),
                               TmpBuffer + offset + MATCH.rm_so);
                    }
                    if ( MATCH.rm_eo == 0 )
                    {
                        /* Break out of loop if the match was empty.
                         * This avoids an infinite loop when the match
                         * is empty, e.g $ */
                        offset = len;
                    }
                    else
                    {
                        offset += MATCH.rm_eo;
                    }
#undef MATCH
                }
            }
        }
    }
    else if (INUSE(emUserWarn))
    {
        strcpy(TmpBuffer, Buf);
    }

#endif


    if (INUSE(emUserWarn))
    {
        FORWL(Count, UserWarn)
        {
            for (UsrPtr = TmpBuffer;
                 (UsrPtr = strstr(UsrPtr, UserWarn.Stack.Data[Count]));
                 UsrPtr++)
            {
                CmdLen = strlen(UserWarn.Stack.Data[Count]);
                PSERRA(UsrPtr - TmpBuffer, CmdLen, emUserWarn, UserWarn.Stack.Data[Count]);
            }
        }

        strlwr(TmpBuffer);

        FORWL(Count, UserWarnCase)
        {
            for (UsrPtr = TmpBuffer;
                 (UsrPtr = strstr(UsrPtr, UserWarnCase.Stack.Data[Count]));
                 UsrPtr++)
            {
                CmdLen = strlen(UserWarnCase.Stack.Data[Count]);
                PSERRA(UsrPtr - TmpBuffer, CmdLen, emUserWarn, UserWarnCase.Stack.Data[Count]);
            }
        }
    }
}


/*
 * Checks that the dash-len is correct.
 */

static void CheckDash(void)
{
    char *TmpPtr;
    int TmpC;
    long TmpCount, Len;
    struct WordList *wl = NULL;
    unsigned long i;
    int Errored;
    char *PrePtr = &BufPtr[-2];

    TmpPtr = BufPtr;
    SKIP_AHEAD(TmpPtr, TmpC, TmpC == '-');
    TmpCount = TmpPtr - BufPtr + 1;

    if (CurStkMode(&MathModeStack))
    {
        if (TmpCount > 1)
            HERE(TmpCount, emWrongDash);
    }
    else
    {
        if (LATEX_SPACE(*PrePtr) && LATEX_SPACE(*TmpPtr))
            wl = &WordDash;
        if (isdigit((unsigned char)*PrePtr) && isdigit((unsigned char)*TmpPtr))
            wl = &NumDash;
        if (isalpha((unsigned char)*PrePtr) && isalpha((unsigned char)*TmpPtr))
            wl = &HyphDash;

        if (wl)
        {
            Errored = TRUE;
            FORWL(i, *wl)
            {
                Len = strtol(wl->Stack.Data[i], NULL, 0);
                if (TmpCount == Len)
                {
                    Errored = FALSE;
                    break;
                }
            }

            if (Errored)
            {
                struct WordList *el = &DashExcpt;

                FORWL(i, *el)
                {
                    char *exception = el->Stack.Data[i];

                    char *e = exception;
                    while ( *e )
                    {
                        if ( *e == '-' && 0 == strncmp( BufPtr, e, strlen(e) ) )
                        {
                            char *f = e;
                            TmpPtr = BufPtr;
                            while ( f > exception && *(--f) == *(--TmpPtr) )
                            {
                                /* Nothing */
                            }

                            if ( f <= exception && *f == *TmpPtr )
                            {
                                Errored = FALSE;
                                break;
                            }
                        }

                        ++e;
                    }

                    if ( !Errored )
                        break;
                }
            }

            /* Check DashExcpt looking for phrase with hyphenation that doesn't
             * match what's in DashExcpt.  This really only makes sense for
             * HyphDash, but it should be cheap in the other cases. */
            if (!Errored)
            {
                TmpPtr = BufPtr-1;

                SKIP_BACK(TmpPtr, TmpC, (TmpC == '-'));
                SKIP_BACK(TmpPtr, TmpC, isalpha(TmpC));
                /* If we found a dash going backwards, the we already checked
                 * this on the first dash */
                if (*TmpPtr != '-')
                {
                    /* PrePtr now points to the beginning of the hyphenated phrase */
                    PrePtr = ++TmpPtr;

                    struct WordList *el = &DashExcpt;

                    FORWL(i, *el)
                    {
                        char *e = el->Stack.Data[i];
                        TmpPtr = PrePtr;

                        /* Walk through the strings until we find a
                         * mismatch.  */
                        int FoundHyphenDiff = FALSE;
                        while (*e && *TmpPtr && *e == *TmpPtr)
                        {
                            /* Skip past characters that are the same */
                            while (*e && *TmpPtr && *e == *TmpPtr)
                            {
                                ++e;
                                ++TmpPtr;
                            }
                            /* Skip past differences in hyphens */
                            while (*e == '-' && *TmpPtr != '-')
                            {
                                ++e;
                                FoundHyphenDiff = TRUE;
                            }
                            while (*TmpPtr == '-' && *e != '-')
                            {
                                ++TmpPtr;
                                FoundHyphenDiff = TRUE;
                            }
                        }

                        /* If there was no mismatch all the way to the end of e,
                         * and TmpPtr is not in the middle of a word, then they
                         * matched ignoring hyphens, so we have found the one
                         * DashExcpt element we care about and don't have to
                         * check any others.  Moreover, if we found a difference
                         * in hyphenation, then we must warn because it matches
                         * something in DashExcpt but with improper hyphenation.
                         * It's possible they could put the same phrase in twice
                         * with different hyphenations, but that seems pretty
                         * pathological. */
                        if (*e == '\0' && !isalpha((unsigned char)*TmpPtr))
                        {
                            if (FoundHyphenDiff)
                                Errored = TRUE;
                            break;
                        }
                    }
                }
            }

            if (Errored)
                HERE(TmpCount, emWrongDash);
        }
    }
}

/*
 * Pushes and pops nesting characters.
 *
 */

static void HandleBracket(char Char)
{
    unsigned long BrOffset;     /* Offset into BrOrder array */
    struct ErrInfo *ei;
    char TmpC, Match;
    char ABuf[2], BBuf[2];
    char *TmpPtr;

    AddBracket(Char);

    if ((BrOffset = BrackIndex(Char)) != ~0UL)
    {
        if (BrOffset & 1)       /* Closing bracket of some sort */
        {
            if ((ei = PopErr(&CharStack)))
            {
                Match = MatchBracket(*(ei->Data));
                /* Return italics to proper state */
                if (ei->Flags & efNoItal)
                {
                    if (ItState == itOn)
                    {
                        TmpPtr = BufPtr;
                        SKIP_AHEAD(TmpPtr, TmpC, TmpC == '}');

                        /* If the next character is a period or comma,
                         * or the last character is, then it's not an
                         * error. */
                        /* Checking 2 characters back seems dangerous,
                         * but it's already done in CheckDash. */
                        if ( !strchr(LTX_SmallPunc, *TmpPtr) &&
                             !strchr(LTX_SmallPunc, *(TmpPtr-2)) )
                            HERE(1, emNoItFound);
                    }

                    ItState = FALSE;
                }
                else if (ei->Flags & efItal)
                    ItState = TRUE;

                /* Same for math mode */
                if (ei->Flags & efMath || ei->Flags & efNoMath)
                    StkPop(&MathModeStack);

                FreeErrInfo(ei);
            }
            else
                Match = 0;

            if (Match != Char)
            {
                ABuf[0] = Match;
                BBuf[0] = Char;
                ABuf[1] = BBuf[1] = 0;
                if (Match)
                    PrintError(emExpectC, CurStkName(&InputStack), RealBuf,
                               BufPtr - Buf - 1, 1, Line,
                               ABuf, BBuf);
                else
                    HEREA(1, emSoloC, BBuf);
            }

        }
        else                    /* Opening bracket of some sort  */
        {
            if ((ei = PushChar(Char, Line, BufPtr - Buf - 1,
                               &CharStack, RealBuf)))
            {
                if (Char == '{')
                {
                    switch (ItFlag)
                    {
                    default:
                        ei->Flags |= ItFlag;
                        ItFlag = efNone;
                        break;
                    case efNone:
                        ei->Flags |= ItState ? efItal : efNoItal;
                    }

                    switch (MathFlag)
                    {
                    case efNone:
                        break;
                    case efMath:
                    case efNoMath:
                        PushMode((MathFlag == efMath), &MathModeStack);
                        /* Save for when we exit this delimiter */
                        ei->Flags |= MathFlag;
                        /* Reset flag just in case... */
                        MathFlag = efNone;
                        break;
                    }
                }
            }

            else
                PrintPrgErr(pmNoStackMem);
        }
    }
}


/*
 * Checks to see if CmdBuffer matches any of the words in Silent, or
 * any of the regular expressions in SilentCase.
 *
 */

int CheckSilentRegex(void)
{

#if ! (HAVE_PCRE || HAVE_POSIX_ERE)

    return HasWord(CmdBuffer, &Silent) != NULL;

#else

    static char error[ERROR_STRING_SIZE];
    char *pattern;
    char *tmp;
    int i;
    int rc;
    int len = 4;                /* Enough for the (?:) */

    /* Initialize regular expression */
    if (INUSE(emSpaceTerm) && SilentCase.Stack.Used > 0)
    {
        /* Find the total length we need */
        /* There is 1 for | and the final for null terminator */
        FORWL(i, SilentCase)
        {
            len += strlen( SilentCase.Stack.Data[i] ) + 1;
        }

        /* (A|B|...) */
        tmp = (pattern = (char*)malloc( sizeof(char) * len ));

        #if HAVE_PCRE
        tmp = stpcpy(tmp,"(?:");
        #else
        tmp = stpcpy(tmp,"(");
        #endif

        FORWL(i, SilentCase)
        {
            tmp = stpcpy(tmp, SilentCase.Stack.Data[i]);
            *tmp++ = '|';
        }
        tmp = stpcpy(tmp - 1, ")");

        SilentRegex = malloc( sizeof(regex_t) );
        rc = regcomp(SilentRegex, pattern, REGEX_FLAGS);

        /* Compilation failed: print the error message */
        if (rc != 0)
        {
            regerror(rc, SilentRegex, error, ERROR_STRING_SIZE);
            PrintPrgErr(pmRegexCompileFailed, pattern, error);
            SilentRegex = NULL;
        }
        /* Ensure we won't initialize it again */
        SilentCase.Stack.Used = 0;
        free(pattern);
    }

    /* Check against the normal */
    if ( HasWord(CmdBuffer, &Silent) )
        return 1;
    if (!SilentRegex)
        return 0;

    /* Check against the regexes */
    rc = regexec(SilentRegex, CmdBuffer, 0, NULL, 0);
    if (rc == 0)
        return 1;

    /* Matching failed: handle error cases */
    switch(rc)
    {
        case REG_NOMATCH:
            return 0;
            break;
        default:
            regerror(rc, SilentRegex, error, ERROR_STRING_SIZE);
            PrintPrgErr(pmRegexMatchingError, error);
            break;
    }
    return 0;

#endif
}

/*
 * Searches the `Buf' for possible errors, and prints the errors. `Line'
 * is supplied for error printing.
 */

int FindErr(const char *_RealBuf, const unsigned long _Line)
{
    char *CmdPtr;               /* We'll have to copy each command out. */
    char *PrePtr;               /* Ptr to char in front of command, NULL if
                                 * the cmd appears as the first character  */
    char *TmpPtr;               /* Temporary pointer */
    char *ErrPtr;               /* Ptr to where an error started */

    char TmpC,                  /* Just a temp var used throughout the proc. */
      MatchC, Char;             /* Char. currently processed */
    unsigned long CmdLen;       /* Length of misc. things */
    int MixingQuotes;

    int (*pstcb) (int c);

    enum DotLevel dotlev;

    FoundErr = EXIT_SUCCESS;

    if (_RealBuf)
    {
        RealBuf = _RealBuf;
        Line = _Line;

        if (!LastWasComment)
        {
            SeenSpace = TRUE;
        }
        BufPtr = PreProcess();

        BufPtr = SkipVerb();

        /* Skip past leading whitespace which is insignificant in TeX to avoid
         * spurious warnings (Delete this space to maintain correct
         * pagereferences).  If we have seen a space we don't _need_ to skip
         * past, and doing so misses Message 30 (Multiple spaces detected).  We
         * can miss some of Message 30 in the "not SeenSpace" case too, but I
         * think it's less important, since Message 30 is for newbies.
         */
        if (!SeenSpace && BufPtr)
        {
            SKIP_AHEAD(BufPtr, TmpC, LATEX_SPACE(TmpC));
        }

        while (BufPtr && *BufPtr)
        {
            PrePtr = BufPtr - 1;
            Char = *BufPtr++;
            if (isspace((unsigned char)Char))
                Char = ' ';

            switch (Char)
            {
            case '~':
                TmpPtr = NULL;
                if (isspace((unsigned char)*PrePtr))
                    TmpPtr = PrePtr;
                else if (isspace((unsigned char)*BufPtr))
                    TmpPtr = BufPtr;

                if (TmpPtr)
                    PSERR(TmpPtr - Buf, 1, emDblSpace);
                break;

            case 'X':
            case 'x':
                TmpPtr = PrePtr;

                SKIP_BACK(TmpPtr, TmpC,
                          (LATEX_SPACE(TmpC) || strchr("$", TmpC)));

                if (isdigit((unsigned char)*TmpPtr))
                {
                    TmpPtr = BufPtr;

                    SKIP_AHEAD(TmpPtr, TmpC,
                               (LATEX_SPACE(TmpC) || strchr("$", TmpC)));

                    if (isdigit((unsigned char)*TmpPtr))
                        HERE(1, emUseTimes);
                }
                /* FALLTHRU */
                /* CTYPE: isalpha() */
            case 'a':
            case 'b':
            case 'c':
            case 'd':
            case 'e':
            case 'f':
            case 'g':
            case 'h':
            case 'i':
            case 'j':
            case 'k':
            case 'l':
            case 'm':
            case 'n':
            case 'o':
            case 'p':
            case 'q':
            case 'r':
            case 's':
            case 't':
            case 'u':
            case 'v':
            case 'w':          /* case 'x': */
            case 'y':
            case 'z':

            case 'A':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
            case 'G':
            case 'H':
            case 'I':
            case 'J':
            case 'K':
            case 'L':
            case 'M':
            case 'N':
            case 'O':
            case 'P':
            case 'Q':
            case 'R':
            case 'S':
            case 'T':
            case 'U':
            case 'V':
            case 'W':          /* case 'X': */
            case 'Y':
            case 'Z':
                if (!isalpha((unsigned char)*PrePtr) && (*PrePtr != '\\') &&
                    CurStkMode(&MathModeStack))
                {
                    TmpPtr = BufPtr;
                    CmdPtr = CmdBuffer;
                    do
                    {
                        *CmdPtr++ = Char;
                        Char = *TmpPtr++;
                    }
                    while (isalpha((unsigned char)Char));

                    *CmdPtr = 0;

                    if (HasWord(CmdBuffer, &MathRoman))
                        HEREA(strlen(CmdBuffer), emWordCommand, CmdBuffer);
                }

                break;
            case ' ':
                TmpPtr = BufPtr;
                SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));

                if (*TmpPtr && *PrePtr)
                {
                    if ((TmpPtr - BufPtr) > 0)
                    {
                        HERE(TmpPtr - BufPtr + 1, emMultiSpace);
                        strwrite(BufPtr, VerbClear, TmpPtr - BufPtr - 1);
                    }
                }
                break;

            case '.':
                if ((Char == *BufPtr) && (Char == BufPtr[1]))
                {
                    const char *cTmpPtr;
                    dotlev = CheckDots(&PrePtr[1], &BufPtr[2]);
                    cTmpPtr = Dot2Str(dotlev);
                    HEREA(3, emEllipsis, cTmpPtr);
                }

                /* Regexp: "([^A-Z@.])\.[.!?:]*\s[ \`([]*[a-z]" */

                TmpPtr = BufPtr;
                SKIP_AHEAD(TmpPtr, TmpC, strchr(LTX_EosPunc, TmpC));
                if (LATEX_SPACE(*TmpPtr))
                {
                    if (!isupper((unsigned char)*PrePtr) && (*PrePtr != '@') &&
                        (*PrePtr != '.'))
                    {
                        SKIP_AHEAD(TmpPtr, TmpC,
                                   (LATEX_SPACE(TmpC) || TmpC == '\\' ||
                                    strchr(LTX_BosPunc, TmpC)));
                        if (islower((unsigned char)*TmpPtr))
                        {
                            /* Ignore spacing problems after commands if desired */
                            TmpPtr = PrePtr;
                            SKIP_BACK(TmpPtr, TmpC, istex(TmpC));
                            if (*TmpPtr != '\\' || (CmdSpace & csInterWord))
                                PSERR(BufPtr - Buf, 1, emInterWord);
                        }
                        else
                            CheckAbbrevs(&BufPtr[-1]);
                    }
                }

                /* FALLTHRU */
            case ':':
            case '?':
            case '!':
            case ';':
                /* Regexp: "[A-Z][A-Z][.!?:;]\s+" */

                if (isspace((unsigned char)*BufPtr) &&
                    isupper((unsigned char)*PrePtr) &&
                    (isupper((unsigned char)PrePtr[-1]) || (Char != '.')) &&
                    !FrenchSpacing)
                {
                    /* Ignore spacing problems after commands if desired */
                    TmpPtr = PrePtr;
                    SKIP_BACK(TmpPtr, TmpC, istex(TmpC));
                    if (*TmpPtr != '\\' || (CmdSpace & csInterSentence))
                        HERE(1, emInterSent);
                }

                /* FALLTHRU */
            case ',':
                if (isspace((unsigned char)*PrePtr) &&
                    !(isdigit((unsigned char)*BufPtr) &&
                      ((BufPtr[-1] == '.') || (BufPtr[-1] == ','))))
                    PSERR(PrePtr - Buf, 1, emSpacePunct);

                if (CurStkMode(&MathModeStack) &&
                    (((*BufPtr == '$') && (BufPtr[1] != '$')) ||
                     (!strafter(BufPtr, "\\)"))))
                    HEREA(1, emPunctMath, "outside inner");

                if (!CurStkMode(&MathModeStack) &&
                    (((*PrePtr == '$') && (PrePtr[-1] == '$')) ||
                     (!strinfront(PrePtr, "\\]"))))
                    HEREA(1, emPunctMath, "inside display");

                break;
            case '\'':
            case '`':
                if ((Char == *BufPtr) && (Char == BufPtr[1]))
                {
                    PrintError(emThreeQuotes, CurStkName(&InputStack), RealBuf,
                               BufPtr - Buf - 1, 3, Line,
                               Char, Char, Char, Char, Char, Char);
                }

                if (Char == '\'')
                    MatchC = '`';
                else
                    MatchC = '\'';

                TmpPtr = BufPtr;
                SKIP_AHEAD(TmpPtr, TmpC, TmpC == Char);

                MixingQuotes = FALSE;

                if ((*TmpPtr == MatchC) || (*TmpPtr == '\"') ||
                    (*TmpPtr == '\xB4')) /* xB4 = latin1 acute accent */
                    MixingQuotes = TRUE;

                SKIP_AHEAD(TmpPtr, TmpC, strchr("`\'\"\xB4", TmpC)); /* xB4 = latin1 acute accent */

                if (MixingQuotes)
                    HERE(TmpPtr - BufPtr + 1, emQuoteMix);

                switch (Char)
                {
                case '\'':
                    if (isalpha((unsigned char)*TmpPtr) &&
                        (strchr(LTX_GenPunc, *PrePtr) || isspace((unsigned char)*PrePtr)))
                        HERE(TmpPtr - BufPtr + 1, emBeginQ);

                    /* Now check quote style */
#define ISPUNCT(ptr) ((strchr(LTX_EosPunc, *ptr) || strchr(LTX_GenPunc, *ptr)) && (ptr[-1] != '\\'))

                    /* We ignore all single words/abbreviations in quotes */

                    {
                        char *WordPtr = PrePtr;
                        SKIP_BACK(WordPtr, TmpC, (isalnum((unsigned char)TmpC) ||
                                                  strchr(LTX_GenPunc, TmpC)));

                        if (*WordPtr != '`')
                        {
                            if (*PrePtr && (Quote != quTrad)
                                && ISPUNCT(PrePtr))
                                PSERRA(PrePtr - Buf, 1,
                                       emQuoteStyle, "in front of");

                            if (*TmpPtr && (Quote != quLogic)
                                && ISPUNCT(TmpPtr))
                                PSERRA(TmpPtr - Buf, 1,
                                       emQuoteStyle, "after");
                        }
                    }

                    break;
                case '`':
                    if (isalpha((unsigned char)*PrePtr) &&
                        (strchr(LTX_GenPunc, *TmpPtr) || isspace((unsigned char)*TmpPtr)))
                        HERE(TmpPtr - BufPtr + 1, emEndQ);
                    break;
                }
                BufPtr = TmpPtr;
                break;
            case '"':
                HERE(1, emUseQuoteLiga);
                break;

            case '\264':             /* ´ (in Latin-1) */
                HERE(1, emUseOtherQuote);
                break;

            case '_':
            case '^':
                if (*PrePtr != '\\')
                {
                    TmpPtr = PrePtr;
                    SKIP_BACK(TmpPtr, TmpC, LATEX_SPACE(TmpC));

                    CmdLen = 1;

                    switch (*TmpPtr)
                    {
                        /*{ */
                    case '}':
                        if (PrePtr[-1] != '\\')
                            break;

                        CmdLen++;
                        PrePtr--;
                        /* FALLTHRU */
                        /*[( */
                    case ')':
                    case ']':
                        PSERR(PrePtr - Buf, CmdLen, emEnclosePar);
                    }

                    TmpPtr = BufPtr;
                    SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));

                    ErrPtr = TmpPtr;

                    if (isalpha((unsigned char)*TmpPtr))
                        pstcb = &my_isalpha;
                    else if (isdigit((unsigned char)*TmpPtr))
                        pstcb = &my_isdigit;
                    else
                        break;

                    while ((*pstcb) (*TmpPtr++))
                        ;
                    TmpPtr--;

                    if ((TmpPtr - ErrPtr) > 1)
                        PSERR(ErrPtr - Buf, TmpPtr - ErrPtr, emEmbrace);
                }
                break;
            case '-':
                CheckDash();
                break;
            case '\\':         /* Command encountered  */
                BufPtr = GetLTXToken(--BufPtr, CmdBuffer);

                if (SeenSpace)
                {
                    /* We must be careful to not point to the "previous space"
                     * when it was actually on the previous line.  This could
                     * cause us to write into someone else's memory (inside of
                     * PrintError). */
                    if (HasWord(CmdBuffer, &Linker))
                        PSERR( (PrePtr > Buf) ? (PrePtr - Buf) : 0,
                               1, emNBSpace);
                    if (HasWord(CmdBuffer, &PostLink))
                        PSERR( (PrePtr > Buf) ? (PrePtr - Buf) : 0,
                               1, emFalsePage);
                }

                if (LATEX_SPACE(*BufPtr) && !CurStkMode(&MathModeStack) &&
                    !CheckSilentRegex() && (strlen(CmdBuffer) != 2))
                {
                    PSERR(BufPtr - Buf, 1, emSpaceTerm);
                }
                else if ((*BufPtr == '\\') && (!isalpha((unsigned char)BufPtr[1])) &&
                         (!LATEX_SPACE(BufPtr[1])))
                    PSERR(BufPtr - Buf, 2, emNotIntended);

                PerformBigCmd(PrePtr + 1);
                BufPtr = SkipVerb();

                break;

            case '(':
                if (*PrePtr && !LATEX_SPACE(*PrePtr) && !isdigit((unsigned char)*PrePtr)
                    && !strchr("([{`~", *PrePtr))
                {
                    if (PrePtr[-1] != '\\')     /* Short cmds */
                    {
                        TmpPtr = PrePtr;
                        SKIP_BACK(TmpPtr, TmpC, istex(TmpC));
                        if (*TmpPtr != '\\')    /* Long cmds */
                            PSERRA(BufPtr - Buf - 1, 1, emSpaceParen,
                                   "in front of");
                    }
                }
                if (isspace((unsigned char)*BufPtr))
                    PSERRA(BufPtr - Buf, 1, emNoSpaceParen, "after");
                HandleBracket(Char);
                break;

            case ')':
                if (SeenSpace)
                    PSERRA(BufPtr - Buf - 1, 1, emNoSpaceParen,
                           "in front of");
                if (isalpha((unsigned char)*BufPtr))
                    PSERRA(BufPtr - Buf, 1, emSpaceParen, "after");
                HandleBracket(Char);
                break;

            case '}':
            case '{':
            case '[':
            case ']':
                HandleBracket(Char);
                break;
            case '$':
                if (*PrePtr != '\\')
                {
                    if (*BufPtr == '$')
                    {
                        BufPtr++;
                        TmpPtr = BufPtr;
                        SKIP_AHEAD(TmpPtr, TmpC, (TmpC != '$' && TmpC != '\0'));
                        PSERR(BufPtr - Buf - 2, TmpPtr-BufPtr+4, emDisplayMath);
                    }
                    else
                    {
                        TmpPtr = BufPtr;
                        SKIP_AHEAD(TmpPtr, TmpC, (TmpC != '$' && TmpC != '\0'));
                        PSERR(BufPtr - Buf - 1, TmpPtr-BufPtr+2, emInlineMath);
                    }
                    if (CurStkMode(&MathModeStack))
                    {
                        StkPop(&MathModeStack);
                    }
                    else
                    {
                        PushMode(TRUE, &MathModeStack);
                    }
                }

                break;
            }

            SeenSpace = LATEX_SPACE(Char);
        }

        if (!VerbMode)
        {
            CheckRest();
        }

    }

    return FoundErr;
}

/*
 * Tries to create plural forms for words. Put a '%s' where a
 * suffix should be put, e.g. "warning%s". Watch your %'s!
 */

static void Transit(FILE * fh, unsigned long Cnt, const char *Str)
{
    switch (Cnt)
    {
    case 0:
        fputs("No ", fh);
        fprintf(fh, Str, "s");
        break;
    case 1:
        fputs("One ", fh);
        fprintf(fh, Str, "");
        break;
    default:
        fprintf(fh, "%ld ", Cnt);
        fprintf(fh, Str, "s");
        break;
    }
}

/*
 * Prints the status/conclusion after doing all the testing, including
 * bracket stack status, math mode, etc.
 */

void PrintStatus(unsigned long Lines)
{
    unsigned long Cnt;
    struct ErrInfo *ei;


    while ((ei = PopErr(&CharStack)))
    {
        PrintError(emNoMatchC, ei->File, ei->LineBuf, ei->Column,
                   ei->ErrLen, ei->Line, (char *) ei->Data);
        FreeErrInfo(ei);
    }

    while ((ei = PopErr(&EnvStack)))
    {
        PrintError(emNoMatchC, ei->File, ei->LineBuf, ei->Column,
                   ei->ErrLen, ei->Line, (char *) ei->Data);
        FreeErrInfo(ei);
    }

    while ((ei = PopErr(&ConTeXtStack)))
    {
        PrintError(emNoMatchConTeXt, ei->File, ei->LineBuf, ei->Column,
                   ei->ErrLen, ei->Line, (char *) ei->Data);
        FreeErrInfo(ei);
    }

    if (CurStkMode(&MathModeStack))
    {
        PrintError(emMathStillOn, CurStkName(&InputStack), "", 0L, 0L, Lines);
    }

    for (Cnt = 0L; Cnt < (NUMBRACKETS >> 1); Cnt++)
    {
        if (Brackets[Cnt << 1] != Brackets[(Cnt << 1) + 1])
        {
            PrintError(emNoMatchCC, CurStkName(&InputStack), "", 0L, 0L, Lines,
                       BrOrder[Cnt << 1], BrOrder[(Cnt << 1) + 1]);
        }
    }

    if (!Quiet)
    {
        Transit(stderr, ErrPrint, "error%s printed; ");
        Transit(stderr, WarnPrint, "warning%s printed; ");
        Transit(stderr, UserSupp, "user suppressed warning%s; ");
        Transit(stderr, LineSupp, "line suppressed warning%s.\n");

        /* Print how to suppress warnings. */
        if ( ErrPrint + WarnPrint > 0 ) {
            fprintf(
                stderr,
                "See the manual for how to suppress some or all of these warnings/errors.\n"
                "The manual is available "
#ifdef TEX_LIVE
                "by running `texdoc chktex` or "
#endif
                "at https://www.nongnu.org/chktex/ChkTeX.pdf\n");
        }
    }
}



/*
 * Uses OutputFormat. Be sure that `String'
 * does not contain tabs, newlines, etc.
 * Prints a formatted string. Formatting codes understood:
 *  %b  - string to print Between fields (from -s option)
 *  %c  - Column position of error
 *  %d  - lenght of error (Digit)
 *  %f  - current Filename
 *  %i  - Turn on inverse printing mode.
 *  %I  - Turn off inverse printing mode.
 *  %k  - Kind of error (warning, error, message)
 *  %l  - Line number of error
 *  %m  - warning Message
 *  %n  - warning Number
 *  %u  - an Underlining line (like the one which appears when using -v1)
 *  %r  - part of line in front of error ('S' - 1)
 *  %s  - part of line which contains error (String)
 *  %t  - part of line after error ('S' + 1)
 */


void
PrintError(const enum ErrNum Error, const char *File, const char *String,
           const long Position, const long Len, const long LineNo,  ...)
{
    static                      /* Just to reduce stack usage... */
    char PrintBuffer[BUFFER_SIZE];
    va_list MsgArgs;

    char *LastNorm = OutputFormat;
    char *of;
    int c;

    enum Context Context;

    if (betw(emMinFault, Error, emMaxFault))
    {
        switch (LaTeXMsgs[Error].InUse)
        {
        case iuOK:
            if (SUPPRESSED_ON_LINE(Error))
            {
                LineSupp++;
            }
            else
            {
                Context = LaTeXMsgs[Error].Context;

                if (!HeadErrOut)
                    Context |= ctOutHead;

#define RGTCTXT(Ctxt, Var) if((Context & Ctxt) && !(Var)) break;

                RGTCTXT(ctInMath, CurStkMode(&MathModeStack));
                RGTCTXT(ctOutMath, !CurStkMode(&MathModeStack));
                RGTCTXT(ctInHead, InHeader);
                RGTCTXT(ctOutHead, !InHeader);

                /* Count how warnings or errors we've found, and
                 * update the return code with the worst. */
                switch (LaTeXMsgs[Error].Type)
                {
                case etWarn:
                    WarnPrint++;
                    FoundErr = max(FoundErr, EXIT_WARNINGS);
                    break;
                case etErr:
                    ErrPrint++;
                    FoundErr = max(FoundErr, EXIT_ERRORS);
                    break;
                case etMsg:
                    break;
                }

                while ((of = strchr(LastNorm, '%')))
                {
                    c = *of;
                    *of = 0;

                    fputs(LastNorm, OutputFile);

                    *of++ = c;

                    switch (c = *of++)
                    {
                    case 'b':
                        fputs(Delimit, OutputFile);
                        break;
                    case 'c':
                        /* TODO: need to add the offset of the column
                         * here when long lines are broken. */
                        fprintf(OutputFile, "%ld", Position + 1);
                        break;
                    case 'd':
                        fprintf(OutputFile, "%ld", Len);
                        break;
                    case 'f':
                        fputs(File, OutputFile);
                        break;
                    case 'i':
                        fputs(ReverseOn, OutputFile);
                        break;
                    case 'I':
                        fputs(ReverseOff, OutputFile);
                        break;
                    case 'k':
                        switch (LaTeXMsgs[Error].Type)
                        {
                        case etWarn:
                            fprintf(OutputFile, "Warning");
                            break;
                        case etErr:
                            fprintf(OutputFile, "Error");
                            break;
                        case etMsg:
                            fprintf(OutputFile, "Message");
                            break;
                        }
                        break;
                    case 'l':
                        fprintf(OutputFile, "%ld", LineNo);
                        break;
                    case 'm':
                        va_start(MsgArgs, LineNo);
                        vfprintf(OutputFile,
                                 LaTeXMsgs[Error].Message, MsgArgs);
                        va_end(MsgArgs);
                        break;
                    case 'n':
                        fprintf(OutputFile, "%d", Error);
                        break;
                    case 'u':
                        sfmemset(PrintBuffer, ' ', (long) Position);

                        sfmemset(&PrintBuffer[Position], '^', Len);
                        PrintBuffer[Position + Len] = 0;
                        fputs(PrintBuffer, OutputFile);
                        break;
                    case 'r':
                        substring(String, PrintBuffer, 0L, Position);
                        fputs(PrintBuffer, OutputFile);
                        break;
                    case 's':
                        substring(String, PrintBuffer, Position, Len);
                        fputs(PrintBuffer, OutputFile);
                        break;
                    case 't':
                        substring(String, PrintBuffer,
                                  Position + Len, LONG_MAX);
                        fputs(PrintBuffer, OutputFile);
                        break;
                    default:
                        fputc(c, OutputFile);
                        break;
                    }
                    LastNorm = of;
                }
                fputs(LastNorm, OutputFile);
            }
            break;
        case iuNotUser:
            UserSupp++;
            break;
        case iuNotSys:
            break;
        }
    }
}

/*
 * All commands isolated is routed through this command, so we can
 * update global statuses like math mode and whether @ is a letter
 * or not.
 */

static enum ErrNum PerformCommand(const char *Cmd, char *Arg)
{
    const char *Argument = "";
    enum ErrNum en = emMinFault;
    int TmpC;

    if (!strcmp(Cmd, "\\makeatletter"))
        AtLetter = TRUE;
    else if (!strcmp(Cmd, "\\makeatother"))
        AtLetter = FALSE;
    else if (!strcmp(Cmd, "\\frenchspacing"))
        FrenchSpacing = TRUE;
    else if (!strcmp(Cmd, "\\nonfrenchspacing"))
        FrenchSpacing = FALSE;
    else if (InputFiles && !(strcmp(Cmd, "\\input") && strcmp(Cmd, "\\include")))
    {
        SKIP_AHEAD(Arg, TmpC, LATEX_SPACE(TmpC));
        if (*Arg == '{')        /* } */
        {
            if (GetLTXArg(Arg, TmpBuffer, GET_STRIP_TOKEN, NULL))
                Argument = TmpBuffer;
        }
        else
            Argument = strip(Arg, STRP_BTH);

        if (!(Argument && PushFileName(Argument, &InputStack)))
            en = emNoCmdExec;
    }
    else if (HasWord(Cmd, &Primitives))
        en = emTeXPrim;
    else if (HasWord(Cmd, &MathCmd))
    {
        SKIP_AHEAD(Arg, TmpC, LATEX_SPACE(TmpC));
        if (*Arg == '{')
        {
            /* We will actually turn on math mode when we enter the {} */
            MathFlag = efMath;
        }
    }
    else if (HasWord(Cmd, &TextCmd))
    {
        SKIP_AHEAD(Arg, TmpC, LATEX_SPACE(TmpC));
        if (*Arg == '{')
        {
            /* We will actually turn on text mode when we enter the {} */
            MathFlag = efNoMath;
        }
    }
    else if (*Cmd == '\\')
    {
        /* Quicker check of single lettered commands. */
        switch (Cmd[1])
        {
        case '(':
        case '[':
            PushMode(TRUE, &MathModeStack);
            break;
        case ']':
        case ')':
            if (!CurStkMode(&MathModeStack))
                PSERRA(BufPtr - Buf - 2, 1, emMathModeConfusion, "on");

            StkPop(&MathModeStack);
            break;
        case '/':
            switch (ItState)
            {
            case itOn:
                ItState = itCorrected;
                Argument = Arg;

                SKIP_AHEAD(Argument, TmpC, TmpC == '{' || TmpC == '}');

                if (strchr(".,", *Argument))
                    en = emItPunct;

                break;
            case itCorrected:
                en = emItDup;
                break;
            case itOff:
                en = emItInNoIt;
            }
            break;
        }
    }

    return (en);
}
