/*
 *  ChkTeX v1.5, 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., 675 Mass Ave, Cambridge, MA 02139, 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"

/***************************** 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(c) || (AtLetter && (c == '@')))
#define CTYPE(func) \
static int my_##func(int c) \
{ \
   return(func(c)); \
}

#define INUSE(c)        (LaTeXMsgs[(enum ErrNum) c].InUse == iuOK)

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

#define PSERRA(pos,len,err,a) \
    PrintError(CurStkName(&InputStack), RealBuf, pos, len, Line, err, 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--)) \
    { \
        ifn(check)  break; \
    } \
    ptr++;

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


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

static ULONG Line;

static STRPTR RealBuf, LineCpy, BufPtr;

static int ItFlag = efNone;

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

static enum ErrNum PerformCommand(const STRPTR Cmd, STRPTR 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 STRPTR GetLTXToken(STRPTR Src, STRPTR 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 STRPTR GetLTXArg(STRPTR SrcBuf,
                        const STRPTR OrigDest,
                        const int Until,
                        struct WordList *wl)

{
    STRPTR      Retval, TmpPtr, Dest = OrigDest;
    ULONG       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))
        {
            strcpy(OrigDest, OrigDest + 1);
            OrigDest[strlen(OrigDest) - 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 STRPTR MakeCpy(void)
{
    if(!LineCpy)
        LineCpy = strdup(RealBuf);

    if(!LineCpy)
        PrintPrgErr(pmStrDupErr);

    return(LineCpy);
}

static STRPTR PreProcess(void)
{
    /* First, kill comments. */

    STRPTR TmpPtr;

    strcpy(Buf, RealBuf);

    TmpPtr = Buf;

    while((TmpPtr = strchr(TmpPtr, '%')))
    {
        if(TmpPtr[-1] != '\\')
        {
            PSERR(TmpPtr - Buf, 1, emComment);
            *TmpPtr = 0;
            break;
        }
        TmpPtr++;
    }
    return(Buf);
}

/*
 * Interpret environments
 */

static void PerformEnv(STRPTR Env, BOOL Begin)
{
static
    TEXT   VBStr [BUFSIZ] = "";

    if(HasWord(Env, &MathEnvir))
    {
        MathMode += Begin ? 1 : -1;
        MathMode = max(MathMode, 0);
    }

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

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

    if(VerbMode && BufPtr)
    {
        ifn(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(STRPTR PrePtr, STRPTR PstPtr)
{
    ULONG i;
    int TmpC;
    enum DotLevel Front = dtUnknown, Back = dtUnknown;

    if(MathMode)
    {
        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);

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

}

static STRPTR Dot2Str(enum DotLevel dl)
{
    STRPTR 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(STRPTR Cmd, STRPTR CmdPtr)
{
    ULONG CmdLen = strlen(Cmd);
    STRPTR Format, 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));
                TmpPtr = GetLTXArg(TmpPtr, ArgBuffer, GET_TOKEN, NULL);
            case '}':
            case ']':
                break;
            default:
                PrintPrgErr(pmWrongWipeTemp, &Cmd[strlen(Cmd) + 1]);
                break;
            }
        }

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

/*
 * Checks italic.
 *
 */

static void CheckItal(const STRPTR Cmd)
{
    int TmpC;
    STRPTR TmpPtr;
    if(HasWord(Cmd, &NonItalic))
        ItState = itOff;
    elif(HasWord(Cmd, &Italic))
        ItState = itOn;
    elif(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(STRPTR CmdPtr)
{
    STRPTR  TmpPtr,
            ArgEndPtr;
    ULONG   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)
            {
                if((TmpPtr = strchr(&BufPtr[1], *BufPtr)))
                    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(CurStkName(&InputStack),  RealBuf,
                           CmdPtr - Buf,
                           (LONG) strlen(CmdBuffer), Line,
                           emAccent, CmdBuffer,
                           *TmpPtr, MathMode? "math" : "");
        }
        else
            PSERR(CmdPtr - Buf, CmdLen, emNoArgFound);
    }

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

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

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

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

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

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

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

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

    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))
        {
            TmpPtr = Dot2Str(dotlev);
            PSERRA(CmdPtr - Buf, CmdLen, emEllipsis, TmpPtr);
        }
    }

    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 STRPTR Buffer)
{
    LONG i;
    STRPTR TmpPtr, AbbPtr;

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

        for(i = Abbrev.MaxLen;
            i >= 0;
            i--)
        {
            *--TmpPtr = *AbbPtr--;
            if(!isalpha(*AbbPtr) && 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)
{
    ULONG Count;
    LONG CmdLen;
    STRPTR  UsrPtr;

    /* Search for user-specified warnings */

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


        strlwr(TmpBuffer);

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


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

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

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

    if(MathMode)
    {
        if(TmpCount > 1)
            HERE(TmpCount, emWrongDash);
    }
    else
    {
        if(LATEX_SPACE(*PrePtr) && LATEX_SPACE(*TmpPtr))
            wl = &WordDash;
        if(isdigit(*PrePtr) && isdigit(*TmpPtr))
            wl = &NumDash;
        if(isalpha(*PrePtr) && isalpha(*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)
                HERE(TmpCount, emWrongDash);
        }
    }
}

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

static void HandleBracket(int Char)
{
    ULONG BrOffset;  /* Offset into BrOrder array */
    struct ErrInfo *ei;
    int    TmpC, Match;
    TEXT   ABuf[2], BBuf[2];
    STRPTR TmpPtr;

    AddBracket(Char);

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

                        if(!strchr(LTX_SmallPunc, *TmpPtr))
                            HERE(1, emNoItFound);
                    }

                    ItState = FALSE;
                }
                elif(ei->Flags & efItal)
                    ItState = TRUE;
                FreeErrInfo(ei);
            }
            else
                Match = 0;

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

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

            else
                PrintPrgErr(pmNoStackMem);
        }
    }
}



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

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

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

  int (*pstcb)(int c);

  enum DotLevel dotlev;

    LineCpy = NULL;

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

        BufPtr = PreProcess();

        BufPtr = SkipVerb();

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

            switch(Char)
            {
            case '~':
                TmpPtr = NULL;
                if(isspace(*PrePtr))
                    TmpPtr = PrePtr;
                elif(isspace(*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(*TmpPtr))
                {
                    TmpPtr = BufPtr;

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

                    if(isdigit(*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(*PrePtr) && (*PrePtr != '\\') && MathMode)
                {
                    TmpPtr = BufPtr;
                    CmdPtr = CmdBuffer;
                    do
                    {
                        *CmdPtr++ = Char;
                        Char = *TmpPtr++;
                    } while(isalpha(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]))
                {
                    dotlev = CheckDots(&PrePtr[1], &BufPtr[2]);
                    TmpPtr = Dot2Str(dotlev);
                    HEREA(3, emEllipsis, TmpPtr);
                }

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

                TmpPtr = BufPtr;
                SKIP_AHEAD(TmpPtr, TmpC, strchr(LTX_GenPunc, TmpC));
                if(LATEX_SPACE(*TmpPtr))
                {
                    if(!isupper(*PrePtr) && (*PrePtr != '@') &&
                       (*PrePtr != '.'))
                    {
                        SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
                        if(islower(*TmpPtr))
                            PSERR(BufPtr - Buf, 1, emInterWord);
                        else
                            CheckAbbrevs(&BufPtr[-1]);
                    }
                }

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

                if(isspace(*BufPtr) && isupper(*PrePtr) &&
                   (isupper(PrePtr[-1]) || (Char != '.')))
                    HERE(1, emInterSent);

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

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

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

                break;
            case '\'':
            case '`':
                if((Char == *BufPtr) && (Char == BufPtr[1]))
                {
                    PrintError(CurStkName(&InputStack), RealBuf,
                               BufPtr - Buf - 1, 3, Line,
                               emThreeQuotes,
                               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 == '´'))
                    MixingQuotes = TRUE;

                SKIP_AHEAD(TmpPtr, TmpC, strchr("`\'\"´", TmpC));

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

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

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

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

		    {
			STRPTR WordPtr = PrePtr;
			SKIP_BACK(WordPtr, TmpC, (isalnum(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(*PrePtr) &&
                       (strchr(LTX_GenPunc, *TmpPtr) || isspace(*TmpPtr)))
                        HERE(TmpPtr - BufPtr + 1, emEndQ);
                    break;
                }
                BufPtr = TmpPtr;
                break;
            case '"':
                HERE(1, emUseQuoteLiga);
                break;

		/* One of these are unnecessary, but what the heck... */
            case 180: /* ´. NOTE: '\xb4' gets converted to - something*/
	    case ~(0xff &(~180)):  /* This yields 0xff...fb4 in */
				   /* arbitrary precision. */

                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(*TmpPtr))
                        pstcb = &my_isalpha;
                    elif(isdigit(*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(LATEX_SPACE(*PrePtr))
                {
                    if(HasWord(CmdBuffer, &Linker))
                        PSERR(PrePtr - Buf, 1, emNBSpace);
                    if(HasWord(CmdBuffer, &PostLink))
                        PSERR(PrePtr - Buf, 1, emFalsePage);
                }

                if(LATEX_SPACE(*BufPtr) && !MathMode &&
                   (!HasWord(CmdBuffer, &Silent)) &&
                   (strlen(CmdBuffer) != 2))
                {
                    PSERR(BufPtr - Buf, 1, emSpaceTerm);
                }
                elif((*BufPtr == '\\') && (!isalpha(BufPtr[1])) &&
                     (!LATEX_SPACE(BufPtr[1])))
                    PSERR(BufPtr - Buf, 2, emNotIntended);

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

                break;

            LOOP(bracket,
            case '(':
                ifn(!*PrePtr || LATEX_SPACE(*PrePtr) || isdigit(*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(*BufPtr))
                    PSERRA(BufPtr - Buf, 1, emNoSpaceParen, "after");

                LAST(bracket);
            case ')':
                if(isspace(*PrePtr))
                    PSERRA(BufPtr - Buf - 1, 1, emNoSpaceParen,
                           "in front of");
                if(isalpha(*BufPtr))
                    PSERRA(BufPtr - Buf, 1, emSpaceParen, "after");

                LAST(bracket);
            )

            case '}':
            case '{':
            case '[':
            case ']':
                HandleBracket(Char);
                break;
            case '$':
                if(*PrePtr != '\\')
                {
                    if(*BufPtr == '$')
                        BufPtr++;
                    MathMode ^= TRUE;
               }

                break;
            }
        }

        if(!VerbMode)
        {
            CheckRest();
        }
    }

    return(TRUE);
}

/*
 * 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, ULONG Cnt, STRPTR 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(ULONG Lines)
{
  ULONG Cnt;
  struct ErrInfo *ei;


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

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

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

  for(Cnt = 0L; Cnt < (NUMBRACKETS>>1); Cnt++)
  {
      if(Brackets[Cnt << 1] != Brackets[(Cnt << 1) + 1])
      {
          PrintError(CurStkName(&InputStack), "", 0L, 0L, Lines,
                     emNoMatchCC,
                     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 printed.\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  STRPTR  File,   const  STRPTR String,
                 const LONG Position,   const LONG Len,
                 const LONG LineNo, const enum ErrNum Error, ...)
{
    static      /* Just to reduce stack usage... */
        TEXT   PrintBuffer[BUFSIZ];
    va_list     MsgArgs;

    STRPTR      LastNorm = OutputFormat, of;
    int         c;

    enum Context Context;

    if(betw(emMinFault, Error, emMaxFault))
    {
        switch(LaTeXMsgs[Error].InUse)
        {
        case iuOK:
            do
            {
                Context  = LaTeXMsgs[Error].Context;

                if(!HeadErrOut)
                    Context |= ctOutHead;

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

                RGTCTXT(ctInMath, MathMode);
                RGTCTXT(ctOutMath, !MathMode);
                RGTCTXT(ctInHead, InHeader);
                RGTCTXT(ctOutHead, !InHeader);
                
                switch(LaTeXMsgs[Error].Type)
                {
                case etWarn:
                    WarnPrint++;
                    break;
                case etErr:
                    ErrPrint++;
                    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':
                        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, Error);
                        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);
            } while(FALSE);
            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 STRPTR Cmd, STRPTR Arg)
{
    STRPTR Argument = "";
    enum ErrNum
        en = emMinFault;
    int TmpC;

    if(!strcmp(Cmd, "\\makeatletter"))
        AtLetter = TRUE;
    elif(!strcmp(Cmd, "\\makeatother"))
        AtLetter = FALSE;
    elif(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);

        ifn(Argument && PushFileName(Argument, &InputStack))
            en = emNoCmdExec;
    }
    elif(HasWord(Cmd, &Primitives))
        en = emTeXPrim;
    elif(*Cmd == '\\')
    {
        /* Quicker check of single lettered commands. */
        switch(Cmd[1])
        {
        case '(':
        case '[':
            MathMode = TRUE;
            break;
        case ']':
        case ')':
            MathMode = FALSE;
            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);
}
