/*
 *  ChkTeX v1.4, 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"

#define INTFAULTMSG \
"INTERNAL FAULT OCCURED! PLEASE SUBMIT A BUG REPORT!\n"

#ifndef __LOCALIZED
char InternFault [] =   INTFAULTMSG;
#       define INTERNFAULT      &InternFault[0]
#else
#       define INTERNFAULT      INTFAULTMSG
#endif

STRPTR VerbClear = "|";
BOOL    AtLetter = FALSE,  /* Whether `@' is a letter or not. */
        InHeader = TRUE,   /* Whether we're in the header */
        VerbMode = FALSE;  /* Whether we're in complete ignore-mode */

LONG    MathMode;  /* Whether we're in math mode or not	*/
STRPTR  VerbStr;   /* String we'll terminate verbmode upon */


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

struct ErrMsg LaTeXMsgs [emMaxFault + 1] =
{
{emMinFault,    etErr,  TRUE,   ctNone,
     INTERNFAULT},

{emSpaceTerm,   etWarn, TRUE,   ctOutMath,
     "Command terminated with space."},

{emNBSpace,     etWarn, TRUE,   ctNone,
     "Non-breaking space (`~') should have been used."},

{emEnclosePar,  etWarn, TRUE,   ctInMath,
     "You should enclose the previous parenthesis with `{}\'."},

{emItInNoIt,    etWarn, TRUE,   ctOutHead,
     "Italic correction (`\\/') found in non-italic buffer."},

{emItDup,       etWarn, TRUE,   ctNone,
     "Italic correction (`\\/') found more than once."},

{emNoItFound,   etWarn, TRUE,   ctOutHead,
     "No italic correction (`\\/') found."},

{emAccent,      etWarn, TRUE,   ctNone,
     "Accent command `%s' needs use of `\\%c%s'."},

{emWrongDash,   etWarn, TRUE,   ctOutMath,
     "Wrong length of dash may have been used."},

{emExpectC,     etWarn, TRUE,   ctNone,
     "`%s' expected, found `%s'."},

{emSoloC,       etWarn, TRUE,   ctNone,
     "Solo `%s' found."},

{emEllipsis,    etWarn, TRUE,   ctNone,
     "You should use %s to achieve an ellipsis."},

{emInterWord,   etWarn, TRUE,   ctOutMath,
     "Interword spacing (`\\ ') should perhaps be used."},

{emInterSent,   etWarn, TRUE,   ctOutMath,
     "Intersentence spacing (`\\@') should perhaps be used."},

{emNoArgFound,  etErr,  TRUE,   ctNone,
     "Could not find argument for command."},

{emNoMatchC,    etWarn, TRUE,   ctOutMath,
     "No match found for `%s'."},

{emMathStillOn, etWarn, TRUE,   ctNone,
     "Mathmode still on at end of LaTeX file."},

{emNoMatchCC,   etWarn, TRUE,   ctNone,
     "Number of `%c' doesn't match the number of `%c'!"},

{emUseQuoteLiga, etWarn, TRUE,  ctNone,
     "Use either `` or '' as an alternative to `\"'."},

{emUseOtherQuote, etWarn, TRUE, ctNone,
     "Use \"'\" (ASCII 39) instead  of \"´\" (ASCII 180)."},

{emUserWarn,    etWarn, TRUE,   ctNone,
     "User-specified pattern found."},

{emNotIntended, etWarn, FALSE,  ctNone,
     "This command might not be intended."},

{emComment,     etMsg,  FALSE,  ctNone,
     "Comment displayed."},

{emThreeQuotes, etWarn, TRUE,   ctNone,
     "Either %c\\,%c%c or %c%c\\,%c will look better."},

{emFalsePage,   etWarn, TRUE,   ctNone,
     "Delete this space to maintain correct pagereferences."},

{emEmbrace,     etWarn, TRUE,   ctInMath,
     "You might wish to put this between a pair of `{}'"},

{emSpacePunct,  etWarn, TRUE,   ctOutMath,
     "You ought to remove spaces in front of punctuation."},

{emNoCmdExec,   etWarn, TRUE,   ctNone,
     "Could not execute LaTeX command."},

{emItPunct,     etWarn, TRUE,   ctNone,
     "Don't use \\/ in front of small punctuation."},

{emUseTimes,    etWarn, TRUE,   ctNone,
     "$\\times$ may look prettier here."},

{emMultiSpace,  etWarn, FALSE,  ctOutMath,
     "Multiple spaces detected in input."},

{emIgnoreText,  etWarn, TRUE,   ctNone,
    "This text may be ignored."},

{emBeginQ,      etWarn, TRUE,   ctOutMath,
   "Use ` to begin quotation, not '."},

{emEndQ,        etWarn, TRUE,   ctOutMath,
   "Use ' to end quotation, not `."},

{emQuoteMix,    etWarn, TRUE,   ctNone,
     "Don't mix quotes."},

{emWordCommand, etWarn, TRUE,   ctInMath,
     "You should perhaps use `\\%s' instead."},

{emMaxFault,    etErr,  TRUE,   0,
     INTERNFAULT}

};

struct ErrMsg PrgMsgs [pmMaxFault + 1] =
{
{pmMinFault,    etErr,  TRUE, 0,
     INTERNFAULT},

{pmUnknownTerm, etErr,  TRUE, 0,
     "Unknown terminal type - using normal verbosity."},

{pmNoFileMatch, etWarn, TRUE, 0,
     "No files matched the pattern `%s'."},
{pmNoTeXOpen,   etErr,  TRUE, 0,
     "Unable to open the TeX file `%s'."},

{pmRename,      etMsg,  TRUE, 0,
     "Renaming `%s' as `%s'."},
{pmRenameErr,   etErr,  TRUE, 0,
     "Could not rename `%s' to `%s'."},

{pmOutOpen,     etErr,  TRUE, 0,
     "Unable to open output file."},
{pmOutTwice,    etErr,  TRUE, 0,
     "You can specify output file only once."},

{pmStrDupErr,   etErr,  TRUE, 0,
     "Unable to duplicate strings - no memory?"},
{pmWordListErr, etErr,  TRUE, 0,
     "Unable to create wordlist - no memory?"},
{pmNoStackMem,  etErr,  TRUE, 0,
     "Unable to create stack - no memory?\n"},

{pmWarnNumErr,  etErr,  TRUE, 0,
     "Illegal warning number used."},
{pmVerbLevErr,  etErr,  TRUE, 0,
     "Illegal verbosity level."},

{pmNotPSDigit,  etWarn, TRUE, 0,
     "`%c' is not a %s digit - ignored!"},
{pmEscCode,     etWarn, TRUE, 0,
     "Unknown escape code `%c%c' - ignored!"},

{pmKeyWord,     etErr,  TRUE, 0,
     "Unsupported control word (`%s') encountered in file `%s'."},
{pmFaultFmt,    etErr,  TRUE, 0,
     "\"%s\", line %d: Faulty format - unexpected %s found."},
{pmRsrcOpen,    etWarn, TRUE, 0,
     "Could not open `%s', may cause unwanted behaviour."},

{pmSlowAbbr,    etMsg,  FALSE, 0,
     "The abbreviation `%s' requires slow abbreviation searching."},

{pmEmptyToken,  etErr,  TRUE,  0,
     "Empty token isolated in `%s' - probably faulty format"},

{pmAssert,      etErr,  TRUE,  0,
     "Assertion failed. Please report bug."},

{pmNoRsrc,      etWarn, TRUE, 0,
     "Could not find global resource file."},

{pmMaxFault,    etErr,  TRUE, 0,
     INTERNFAULT},
};

/*
 *  %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)
 */


#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)

#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;

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


CTYPE(isdigit)
CTYPE(isalpha)


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, int Begin)
{
static
    UBYTE   VBStr [BUFSIZ] = "";

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

    if(HasWord(Env, &VerbEnvir))
    {
        VerbMode = Begin;
        sprintf(VBStr, "\\end{%s}", Env);
        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--;
	SKIP_BACK(PrePtr, TmpC, (LATEX_SPACE(TmpC) || strchr("{}", TmpC)));
	SKIP_AHEAD(PstPtr, TmpC, (LATEX_SPACE(TmpC)|| strchr("{}", TmpC)));

	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;
}

/*
 * Interpret isolated commands.
 *
 */

void PerformBigCmd(STRPTR CmdPtr)
{
    STRPTR  TmpPtr,
            ArgEndPtr = GetLTXArg(BufPtr, ArgBuffer, '{'); /*}*/
    ULONG   CmdLen = strlen(CmdBuffer);
    UBYTE   TmpC;
    enum ErrNum
            ErrNum;
    struct ErrInfo *ei;

    enum DotLevel dotlev, realdl = dtUnknown;

    /* 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(strchr("ij", *TmpPtr))
            {
                if(TmpPtr[-1] != '\\')
                    PrintError(CurStkName(&InputStack),  RealBuf,
                               CmdPtr - Buf,
                               (LONG) strlen(CmdBuffer), Line,
                               emAccent, CmdBuffer,
			       *TmpPtr, MathMode? "math" : "");
            }
        }
        else
            PSERR(CmdPtr - Buf, CmdLen, emNoArgFound);
    }

    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, CmdBuffer[1] == 'b');
        } else
            PSERR(CmdPtr - Buf, CmdLen, emNoArgFound);
    }

    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(HasWord(CmdBuffer, &WipeArg))
    {
	if(*BufPtr == '[') /* ] */
	    ArgEndPtr = GetLTXArg(BufPtr, ArgBuffer, '['); /* [ */
	else
	    ArgEndPtr = BufPtr;

	if(ArgEndPtr)
            ArgEndPtr = GetLTXArg(ArgEndPtr, ArgBuffer, '{'); /* } */

	if(ArgEndPtr)
	    strwrite(BufPtr, VerbClear, ArgEndPtr - BufPtr);
	else
            PSERR(CmdPtr - Buf, CmdLen, emNoArgFound);
    }
}

/*
 * Isolate & check abbreviations.
 */

static void CheckAbbrevs(void)
{
    /* Search for abbrevs... */
    ULONG Count, CmdLen;
    int Char;
    STRPTR TmpPtr, AbbPtr;

    if(INUSE(emInterWord))
    {
        BOOL HasAlpha = FALSE;

        strcpy(TmpBuffer, Buf);
        strupr(TmpBuffer);

        for(Count = 0;
            Count < SlowAbbrev.Stack.Used;
            Count++)
        {
            for(AbbPtr = TmpBuffer;
                (AbbPtr = strstr(AbbPtr, SlowAbbrev.Stack.Data[Count]));
                AbbPtr++)
            {
                CmdLen = strlen(SlowAbbrev.Stack.Data[Count]);
                Char = AbbPtr[CmdLen];
                if(LATEX_SPACE(Char))
                        PrintError(CurStkName(&InputStack), RealBuf,
                                   (AbbPtr - TmpBuffer) + CmdLen, 1, Line,
                                   emInterWord);
            }
        }

        /*
         * We define an abbrev to be any word (e.g. alphas separated with
         * non-alpha characters) containing punctums. For instance:
         * e.g. foo.bar.
         *
         * This will _not_ catch abbrevs like etc., so we'll
         * keep the .chktexrc ABBREV keyword.
         */

        Count = 0;         /* Amount of . in a word - 1 */
        for(AbbPtr = TmpBuffer;
            (Char = *AbbPtr++);
            )
        {
            if(Char == '.')
            {
                 if(HasAlpha && LATEX_SPACE(*AbbPtr) && Count)
		     PSERR(AbbPtr - TmpBuffer, 1, emInterWord);
                 Count++;
            }
            else if(!isalpha(Char))
            {
                Count = 0;
                HasAlpha = FALSE;
            }
            else
                HasAlpha = TRUE;
        }


        /* First isolate abbrev, then hold it against table. */
        /* NOTE - this modifies TmpBuffer! */

        TmpPtr = TmpBuffer;
        for(AbbPtr = Buf;
            (Char = *AbbPtr);
            AbbPtr++)
        {
            if(isalpha(Char) || (Char == '.'))
                *TmpPtr++ = Char;
            else
            {
                *TmpPtr-- = 0;
                if(LATEX_SPACE(Char) && (*TmpPtr == '.'))
                {
                    strupr(TmpBuffer);
                    if(HasWord(TmpBuffer, &Abbrev))
			PSERR(AbbPtr- Buf, 1, emInterWord);
                }

                TmpPtr = TmpBuffer;
            }
        }
    }
}

/*
 * 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);

        for(Count = 0L;
            Count < UserWarn.Stack.Used;
            Count++)
        {
            for(UsrPtr = TmpBuffer;
                (UsrPtr = strstr(UsrPtr, UserWarn.Stack.Data[Count]));
                UsrPtr++)
            {
                CmdLen = strlen(UserWarn.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 = FALSE;
    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;
	    for(i = 0; i < wl->Stack.Used; i++)
	    {
		Len = strtol(wl->Stack.Data[i], NULL, 0);
		if(TmpCount == Len)
		{
		    Errored = FALSE;
		    break;
		}
	    }
	    if(Errored)
		HERE(TmpCount, emWrongDash);
	}
    }
}



/*
 * 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 */

  UBYTE
    TmpC,     /* Just a temp var used throughout the proc.*/
    MatchC = 0,
    Char;     /* Char. currently processed */
  ULONG
    BrOffset, /* Offset into BrOrder array */
    CmdLen;   /* Length of misc. things */
  BOOL MixingQuotes;

  int (*pstcb)(int c); 

  struct ErrInfo
    *ei;

/*
  static const     Characters we'll do some magic with
    UBYTE MgcChars [] = "$\\{}[]()-^_\"´`\'.xX";
*/

  UBYTE
    ABuf [2] = {0},
    BBuf [2] = {0};

  enum DotLevel dotlev;

    LineCpy = NULL;

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

        BufPtr = PreProcess();

        BufPtr = SkipVerb();

        while(BufPtr && *BufPtr)
        {
            PrePtr = BufPtr - 1;
            switch(Char = *BufPtr++)
            {
            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);
                }
                break;
            case '.':
                if((Char == *BufPtr) && (Char == BufPtr[1]))
		{
		    dotlev = CheckDots(&PrePtr[1], &BufPtr[2]);
		    TmpPtr = Dot2Str(dotlev);
		    HEREA(3, emEllipsis, TmpPtr);
		}
		if(!MathMode && islower(*PrePtr))
		{
		    TmpPtr = BufPtr;
		    SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));
		    if((TmpPtr != BufPtr) && islower(*TmpPtr))
			PSERR(BufPtr - Buf, TmpPtr - BufPtr, emInterWord);
		}
		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(LATEX_PUNCT, *PrePtr) || isspace(*PrePtr)))
			HERE(TmpPtr - BufPtr + 1, emBeginQ);
		    break;
		case '`':
		    if(isalpha(*PrePtr) &&
		       (strchr(LATEX_PUNCT, *TmpPtr) || isspace(*TmpPtr)))
			HERE(TmpPtr - BufPtr + 1, emEndQ);
		    break;
		}
		BufPtr = TmpPtr;
                break;
            case '"':
                HERE(1, emUseQuoteLiga);
                break;

            case 180: /* ´ */
                HERE(1, emUseOtherQuote);
                break;

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

                    CmdLen = 1;

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

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

                    if((TmpPtr = strip(BufPtr, STRP_LFT)))
                    {
                        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  */
                CmdPtr = CmdBuffer;
                *CmdPtr++ = Char;
                *CmdPtr++ = Char = *BufPtr++;

                if(istex(Char))
                {
                    while(istex(Char))
                        Char = *CmdPtr++ = *BufPtr++;

                    BufPtr--;
                    CmdPtr--;
                }

                *CmdPtr = 0;

                /* We've now isolated the command */

                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(Char) && !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;
                /*{{*/
            case '}':
                /* This should be implemented via the stacking below FIXME */

                TmpPtr = BufPtr;
                while(*TmpPtr++ == '}')
                    ;
                TmpPtr--;

                if((*PrePtr != '\\') && !strchr(SMALL_PUNCT, *TmpPtr))
                {
                    if(ItState == itOn)
			PSERR(PrePtr - Buf + 1, 1,emNoItFound);
                }
                ItState = itOff;

                /* FALLTHRU */
            case '{': /* } */
            case '(':
            case '[':

            case ')':
            case ']':
                AddBracket(Char);

                if((BrOffset = BrackIndex(Char)) != ~0UL)
                {
                    if(BrOffset & 1)      /* Closing bracket of some sort */
                    {
                        if((ei = PopErr(&CharStack)))
                        {
                            TmpC = MatchBracket(*(ei->Data));
                            FreeErrInfo(ei);
                        }
                        else
                            TmpC = 0;

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

                    }
                    else         /* Opening bracket of some sort  */
                    {
                        if(!LineCpy)
                            LineCpy = strdup(RealBuf);

                        if(LineCpy)
                        {
                            ifn(PushChar(Char, Line, PrePtr - Buf + 1,
                                         &CharStack, LineCpy))
                                PrintPrgErr(pmNoStackMem);
                        }
                        else
                            PrintPrgErr(pmStrDupErr);
                    }
                }
                break;
            case '$':
                if(*PrePtr != '\\')
                {
                    if(*BufPtr == '$')
                        BufPtr++;
                    MathMode ^= TRUE;
                }

                break;
	    default:
		if(isalpha(Char) && !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);
		}

		/*
		 * Search for multiple spaces in input; space in front
		 * of punctuation. 
		 */
		
		if(LATEX_SPACE(Char))
		{
		    TmpPtr = BufPtr;
		    SKIP_AHEAD(TmpPtr, TmpC, LATEX_SPACE(TmpC));

		    if(*TmpPtr)
		    {
			if((TmpPtr - BufPtr) > 0)
			{
			    HERE(TmpPtr - BufPtr + 1, emMultiSpace);
			    strwrite(BufPtr, VerbClear, TmpPtr - BufPtr - 1);
			}
		    }
		    if(*BufPtr && strchr(LATEX_PUNCT, *BufPtr))
			HERE(2, emSpacePunct);
		}

		if(isupper(*PrePtr) && strchr(LATEX_PUNCT, Char) && 
		   LATEX_SPACE(*BufPtr) && !LATEX_SPACE(PrePtr[-1]))
		    HERE(1, emInterSent);
            }
        }

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

    return(TRUE);
}

/*
 * 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)
      fprintf(stderr,
              "%ld error(s) printed; %ld warning(s) printed; %ld user suppressed warnings.\n",
              ErrPrint, WarnPrint, UserSupp);
}


/*
 * Scans the `Buf' for a LaTeX arg, and puts that arg into `Dest'. `Delim'
 * is the leading character for the arg (either `{' or `[', that is).
 * Returns NULL if we can't find the argument, ptr to the first character
 * after the argument in other cases.
 *
 * Dest == NULL => we won't copy anything out.
 *
 */

STRPTR GetLTXArg(STRPTR Buf, STRPTR Dest, const UBYTE Delim)
{
    UBYTE       mileD, TmpC;
    STRPTR      Retval = NULL;
    ULONG       DeliCnt = 1,
    BufCnt = 0L;

    if(Dest)
        *Dest = 0;

    if((mileD = MatchBracket(Delim)))
    {
        Buf = strip(Buf, STRP_LFT);

        if((TmpC = *Buf) == Delim)
        {
            while(DeliCnt > 0L)
            {
                TmpC = *++Buf;

                if(Dest)
                {
                    if(BufCnt++ < (BUFSIZ - 1))
                        *Dest++ = TmpC;
                    else
                        break;
                }

                if(TmpC == Delim)
                    DeliCnt++;
                elif(TmpC == mileD)
                    DeliCnt--;
                elif(!TmpC)
                    break;
            }
            if(Dest)
                *--Dest = 0L;
            Retval = ++Buf;
        }
        else if(TmpC)
        {
            if(Dest)
            {
                Dest[0] = TmpC;
                Dest[1] = 0L;
            }
            Retval = ++Buf;
        }
    }
    return(Retval);
}

#define PRINT_MESSAGE   va_start(MsgArgs, Error);   \
vfprintf(OutputFile, LaTeXMsgs[Error].Message, MsgArgs); \
va_end(MsgArgs)


/*
 * 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 Line, const enum ErrNum Error, ...)
{
    static      /* Just to reduce stack usage... */
        UBYTE   PrintBuffer[BUFSIZ];
    va_list     MsgArgs;

    STRPTR      LastNorm = OutputFormat,
                of = OutputFormat;
    int         c;

    enum Context Context;

    if(betw(emMinFault, Error, emMaxFault) && LaTeXMsgs[Error].InUse)
    {
        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(PRE_ERROR_STR, OutputFile);
                    break;
                case 'I':
                    fputs(POST_ERROR_STR, 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", Line);
                    break;
                case 'm':
                    PRINT_MESSAGE;
                    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':
                    strmid(String, PrintBuffer, 0L, Position);
                    fputs(PrintBuffer, OutputFile);
                    break;
                case 's':
                    strmid(String, PrintBuffer, Position, Len);
                    fputs(PrintBuffer, OutputFile);
                    break;
                case 't':
                    strmid(String, PrintBuffer, Position + Len, LONG_MAX);
                    fputs(PrintBuffer, OutputFile);
                    break;
                default:
                    fputc(c, OutputFile);
                    break;
                }
                LastNorm = of;
            }
            fputs(LastNorm, OutputFile);

        } while(FALSE);
    }
    else
        UserSupp++;

}
#undef PRINT_MESSAGE

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

enum ErrNum PerformCommand(const STRPTR Cmd, STRPTR Arg)
{
    STRPTR Argument = "";
    enum ErrNum
        en = emMinFault;

    if(HasWord(Cmd, &Italic))
        ItState = itOn;
    elif(!strcmp(Cmd, "\\makeatletter"))
        AtLetter = TRUE;
    elif(!strcmp(Cmd, "\\makeatother"))
        AtLetter = FALSE;
    elif(InputFiles && !(strcmp(Cmd, "\\input") && strcmp(Cmd, "\\include")))
    {
        Arg = strip(Arg, STRP_LFT);
        if(*Arg == '{') /* } */
        {
            if(GetLTXArg(Arg, TmpBuffer, '{'))  /* } */
                Argument = TmpBuffer;
        }
        else
            Argument = strip(Arg, STRP_BTH);

        ifn(Argument && PushFileName(Argument, &InputStack))
            en = emNoCmdExec;
    }
    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;

                while(*Argument++ == '}')
                    ;
                Argument--;

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

                break;
            case itCorrected:
                en = emItDup;
                break;
            case itOff:
                en = emItInNoIt;
            }
            break;
        }
    }
    return(en);
}




