/*
 *  ChkTeX v1.2, header file for main program.
 *  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>
 *
 *
 */

/********************************************************************/
/**************** START OF USER SETTABLE PREFERENCES ****************/

/*
 * Note: This file contains most defines you'll wish to change if you
 * wish to adopt ChkTeX to a new system. It is, as you might notice,
 * heavily documented. If you wish to get into the internals of ChkTeX,
 * the interesting stuff is at the bottom of this file, and in the .c
 * files.
 *
 * This program relies heavily on that the system which
 * automagically free()'s all malloc()'ed memory, works. The program
 * itself does not call free() very much. This is because we're doing
 * lots of tiny allocations, and a properly designed pooling system will
 * hopefully do a quicker job than we'll be able to do. So there.
 *
 * To keep things simple, we trust that the fclose()'ing of fopen()'ed
 * also happens automagically.
 *
 * Please use the getopt included, as we will modify optarg during
 * command processing.
 *
 * I've #ifdef'ed out quite a few functions, so you can pick only those
 * you need. If you've got a similar routine in your local library, please
 * use that one if you can, as this will probably give you a better
 * routine (it may even have been coded in asm).
 *
 * Please note that isalpha(), isdigit() and similar routines may _not_
 * be implemented as macros; we'll use these as callback hooks. For your
 * convenience, we will #undef'ine these for you; however, you must make
 * sure that they exist in a library.
 *
 * You may wish to modify the SetupVars() to better suit your
 * preferences. In any case, it should put the filename (and full path)
 * of the `.chktexrc' file into the ConfigFile array. The array is sized
 * BUFLEN bytes.
 *
 * The program does also assume that AMIGA is defined if the source
 * compiled on an Amiga machine, and that __unix__ is defined if the
 * source is compiled on a UNIX machine.
 *
 */


/*
 * The settings below work well with GCC. If you've got another compiler,
 * you should read through the whole file and define the settings yourself.
 * If you're using SAS/C Amiga, no adjustments have to be done at all. You
 * should, however, still have the SCOPTIONS file in the directory, to
 * optimize things a bit more.
 *
 * The #defines are necessary to use some non-ANSI functions, which exist
 * only on some compilers. Btw., realloc() appears to be broken on GCC (at
 * least on the version I found at our university).
 */

#ifdef __GNUC__
#	define stricmp strcasecmp
#	define	STRUPR_NEED
#	define STRMID_NEED
#endif

#ifdef __unix__
#	define	NO_TICTOC
#endif

/*
 * You'll have to define stricmp(a, b) to a function which does
 * case-insensitive comparison between a and b.
 */

/*
#define stricmp		strcasecmp
*/

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

/*
 * If you have a compiler which doesn't like prototypes, uncomment
 * the following line.
 */

/*
#define __NOPROTO
*/

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

/*
 * Uncomment the following line if your compiler does not have the
 * strdup() function, which returns a duplicate of the string passed.
 */

/*
#define STRDUP_NEED
*/

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

/*
 * Uncomment the following line if your compiler does not have the
 * strupr() function, which makes the string passed into uppercase.
 */

/*
#define STRUPR_NEED
*/


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

/*
 * Uncomment the following line if you need the
 * strmid(source, dest, pos, len) function, which copies a string
 * with a maximum length of `len' starting at `pos' in `source' into
 * `dest'. Should return -1 if the pos value is beyond the length of the
 * source value, else NULL.
 */

/*
#define	STRMID_NEED
*/


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

/*
 * Here you should define what codes which should be returned to the
 * shell upon success/failure.
 *
 */

#ifndef EXIT_FAILURE
#	ifdef  AMIGA
#		define	EXIT_FAILURE	20
#	else
#		define	EXIT_FAILURE	1
#	endif
#endif

#ifndef EXIT_SUCCESS
#	define	EXIT_SUCCESS	0
#endif

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

/*
 * SLASH should be defined to the character your computer uses to
 * separate files/directories. Most systems use '/', messydos uses
 * '\'.
 *
 * DIRCHARS should be defined to the characters a directory entry
 * may end on. On Amigas, this is ":/" (either "FOO:BAR/" or "FOO:"),
 * Unix uses only "/", while messydos uses ":\\".
 *
 * This data will be used to automatically concatenate a directory
 * path and a filename.
 *
 * Adjust both to suit your needs.
 */


#if defined(__unix__) || defined(AMIGA)
#	define	SLASH		'/'
#endif

#if defined(__unix__)
#	define DIRCHARS	"/"
#elif defined(AMIGA)
#	define DIRCHARS	":/"
#endif

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

/*
 * Define this constant if you want a slower program which isn't
 * that wasteful concerning memory.
 */

/*
#define NO_DIRTY_TRICKS
*/


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

/*
 * This will be the length of our buffers. If you're operating with
 * extremely long lines, you may wish to increase this one to avoid
 * errors. They should be non-fatal, but may cause extra spaces to be
 * inserted in the input.
 *
 * In most cases, however, your C provider has already set an
 * appropiate value for BUFSIZ.
 */
#define BUFLEN	BUFSIZ

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

/*
 * Uncomment the following line if you don't wish to have any
 * `twirling baton' (`\|/-'). Is a waste of cycles if you've got
 * a speedy CPU. Some displays do also not like this huge amount
 * of carriage returns without a subsequent line feed.
 *
 * You may also just use the -t0 or -v0 option at runtime.
 */

/*
#define NO_TICTOC
*/

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

/*
 * We're killing \verb@foo@ commands by overwriting them with a single
 * character. This should not be an alphabetic character (in case the user
 * writes (\foo\verb@bar@), neither should it be one of LaTeX' reserved
 * characters (`#$%&~_^\{}'), or any parenthesis character ('()[]{}').
 * If possible, don't use a punctuation character, either, or any spacing
 * character. The asterix is also unsuitable, as some commands behave in
 *  another way if they are postpended with an asterix.  Which more or less
 * leaves us with the pipe.  Still set to changeable; no one knows (not even 
 * you? :-) ) what your LaTeX configuration is.
 */


#define VERBCLEAR	'|'

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

/*
 * For fancy printing of commands, we'll use these strings to turn
 * on/off the error indication. The codes listed here are ANSI
 * compatible; if you don't have that type of terminal, you may wish
 * to adjust this. Use "chktex -v2 Test.tex" to check the effects of
 * these macros. Note: These strings will be printf()'ed, so watch your
 * %'s.
 *
 * On VT10[02] terminals, these values will be ignored; we'll use
 * the sequence "_\b%c" for each character instead.
 *
 * PRE_ERROR_STR is of course printed in front of each location we
 * wish to show as an error, and POST_ERROR_STR after each location.
 *
 * The codes #defined here, will switch back- and foreground colours.
 * We're using '\033[' as escape character, some terminals may like
 * '\233' better.
 *
 */

#define	PRE_ERROR_STR	"\033[7m"
#define POST_ERROR_STR	"\033[0m"

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

/*
 * The next macro should return TRUE if the character should be
 * stripped from a string, e.g. if it is a space, tab, control
 * character or any other `invisible' character. I've listed a few
 * alternatives which should work OK on ASCII X3.64 machines, select the
 * one that fits (or create your own :) ). Please embrace it with
 * parentheses.
 */

/*
 * This one is the most correct one - should always work
#define	SKIP_STRIP(c)	(isspace(c) || iscntrl(c))
 *
 * This one works better on ANSI X3.64-1979 terminals
#define SKIP_STRIP(c)	((c > 0 && <= ' ') || (c >= 0x7f && c <= 0xa0))
 *
 * This one works on 7-bits terminals, which is enough for most people.
 */

#define SKIP_STRIP(c)	(c > 0 && c <= ' ')

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

/*
 * The next macro should return TRUE if LaTeX (and you?) considers
 * the character `c' as a space, which should be detected when
 * we're checking whether commands are terminated by spaces.
 *
 * The alternatives listed above should be suitable here, too.
 */

#define LATEX_SPACE(c)	(c > 0 && c <= ' ')

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

/*
 * The next macro should return TRUE if LaTeX (and you?) considers
 * the character `c' as an end-of-sentence character, which should be
 * detected when whether sentence spacing is correct.
 *
 */

/*
 * LaTeX defines the following as punctuation characters; however, all of
 * them are not considered end-of-sentence characters.
 *
#define LATEX_PUNCT(c)	(strchr(".:;,?!`'()[]{}-/*@", c))
 *
 * You may also wish to use this one instead:
 *
#define LATEX_PUNCT(c)	ispunct(c)
 */

#define LATEX_PUNCT(c)	(strchr(".:?!", c))



/***************** END OF USER SETTABLE PREFERENCES *****************/
/********************************************************************/


#include <ctype.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>

#define _STRICT_ANSI
#include <string.h>

#ifdef AMIGA
#	include <exec/execbase.h>
#endif

#ifdef __unix__
#	include <unistd.h>
#endif

#include "exec/types.h"
#include "getopt.h"

/* We don't _really_ need it, but it's nice to keep consistent */

#ifdef __SASC
#	ifdef	_STRICT_ANSI
#		define	STRUPR_NEED
#		define	STRMID_NEED
#		define	STRDUP_NEED
#	endif
#endif

#ifdef isalnum
#	undef isalnum
#endif

#ifdef isalpha
#	undef isalpha
#endif

#ifdef iscntrl
#	undef iscntrl
#endif

#ifdef isdigit
#	undef isdigit
#endif

#ifdef isgraph
#	undef isgraph
#endif

#ifdef islower
#	undef islower
#endif

#ifdef isprint
#	undef isprint
#endif

#ifdef ispunct
#	undef ispunct
#endif

#ifdef isspace
#	undef isspace
#endif

#ifdef isupper
#	undef isupper
#endif

#ifdef isxdigit
#	undef isxdigit
#endif


/*
 * This is the name of the global resource file.
 */

#define RCFILE			".chktexrc"


/*
 * How many indexes we'll allocate first time
 */
#define MINPUDDLE	256


/*
 * How many bytes we want in front/end of each UBYTE buffer.
 */

#define WALLBYTES	2

#define	betw(a,b,c)		((a < b) && (b < c))
#define within(a,b,c)	((a <= b) && (b <= c))
#define elif			else if
#ifndef NOT
#	define NOT(a)			(!(a))
#endif
#define ifn(a)			if(NOT(a))

#ifndef min
#	define min(a,b)	((a)<=(b)?(a):(b))
#endif

#ifndef max
#	define max(a,b)	((a)>(b)?(a):(b))
#endif


#define QUOTE	'\"'	/* Char used to quote text containing blanks	*/
#define ESCAPE	'!'		/* Char used to access special characters		*/
#define CMNT	'#'		/* Char used as comment							*/

enum ErrNum
{
	emMinFault = 0,
	emSpaceTerm,	emNBSpace,		emEnclosePar,	emItInNoIt,
	emItDup,		emNoItFound,	emAccent,		emWrongDash,
	emExpectC,		emSoloC,		emEllipsis,		emInterWord,
	emInterSent,	emNoArgFound,	emNoMatchC,		emMathStillOn,
	emNoMatchCC,	emUseQuoteLiga,	emUseOtherQuote,emUserWarn,
	emNotIntended,	emComment,		emThreeQuotes,	emFalsePage,
	emEmbrace,
	emMaxFault
};

enum PrgErrNum {
	pmMinFault = 0,

	pmUnknownTerm,	pmNoFileMatch,	pmNoTeXOpen,	pmRename,
	pmRenameErr,	pmOutOpen,		pmOutTwice,		pmStrDupErr,
	pmWordListErr,	pmNoStackMem,	pmWarnNumErr,	pmVerbLevErr,
	pmNotPSDigit,	pmEscCode,		pmKeyWord,		pmBraceCnt,
	pmRsrcOpen,

	pmMaxFault,
};


extern 
	struct ErrMsg 
		LaTeXMsgs [emMaxFault + 1],
		PrgMsgs [pmMaxFault + 1];



enum TermType
{
	tmAnsi,
	tmVT100,
	tmUnknown
};

struct	ErrMsg
{
	enum ErrNum	Number;
	enum
	{
		etMsg,
		etWarn,
		etErr
	}		Type;
	BOOL	InUse;
	STRPTR	Message;
};

struct Stack
{
	APTR	*Data;
	ULONG	Size, Used;
};

struct WordList
{
	struct Stack	Stack;
	BOOL			Sorted;
};

struct CharInfo
{
	UBYTE	Char;
	ULONG	Line, Column;
	STRPTR	LineBuf;
};


#define	STRP_LFT			1
#define	STRP_RGT			2
#define	STRP_BTH			(STRP_LFT|STRP_RGT)

#ifndef __NOPROTO
#	ifndef __PROTO
#		define __PROTO(a) a
#	endif
#else
#	ifndef __PROTO
#		define __PROTO(a) ()
#	endif
#endif

#define	NEWBUF(name,len)								\
static													\
	UBYTE	_##name		[len  + (WALLBYTES<<1)] = {0};	\
	STRPTR	name =		&_##name[WALLBYTES]

extern 
	STRPTR	ReadBuffer,
			CmdBuffer,
			TmpBuffer;
extern
	UBYTE	ConfigFile[BUFLEN];

extern struct WordList
	Silent	,
	Abbrev	,
	Linker	,
	IJAccent,
	Italic	,
	UserWarn,
	CmdLine	,
	PostLink,
	*CurRead;

extern 
	struct Stack
		CharStack;


#define NUMBRACKETS		6

extern const
	UBYTE BrOrder[NUMBRACKETS + 1];

extern
	ULONG Brackets	[NUMBRACKETS];

extern enum TermType TermType;

extern
	BOOL	AtLetter,			/* Whether `@' is a letter or not.		*/
			MathMode;			/* Whether we're in math mode or not	*/

extern
	ULONG	ErrPrint,			/* # errors printed 					*/
			WarnPrint,			/* # warnings printed					*/
			UserSupp;			/* # user suppressed warnings			*/

enum Verbosity
{
	vbSilent = 0,
	vbNormal,
	vbFancy
};

enum ItState {
	itOff,
	itOn,
	itCorrected
};

extern 
	enum ItState
		ItState;

extern
	LONG Verbosity;

extern
	FILE *OutputFile, *InputFile;

extern
	STRPTR 
		OutputName,
		InputName;

extern
	STRPTR
		Delimit;

extern
	BOOL 	GlobalRC, WipeVerb, TicToc,	BackupOut,
			Quiet, LicenseOnly, UsingStdIn;



void		AddBracket		__PROTO((UBYTE const));
ULONG		BrackIndex		__PROTO((UBYTE const));
BOOL		fexists			__PROTO((STRPTR Filename));
BOOL		FindErr			__PROTO((STRPTR const, ULONG const));
STRPTR		GetLTXArg		__PROTO((STRPTR, STRPTR, const UBYTE));
BOOL		HasWord			__PROTO((STRPTR const, struct WordList *));
BOOL		InsertWord		__PROTO((STRPTR const, struct WordList *));
int			main			__PROTO((int argc, char **argv));
UBYTE		MatchBracket	__PROTO((UBYTE const));
STRPTR		MatchFileName	__PROTO((STRPTR String));
BOOL		OpenOut			__PROTO((void));
int			ParseArgs		__PROTO((ULONG argc, char **argv));
int			ParseBoolArg	__PROTO((BOOL *Dest, STRPTR *Argument));
int			ParseNumArg		__PROTO((LONG *, LONG, STRPTR *));
void		PerformCommand	__PROTO((STRPTR const));
struct CharInfo *
			PopChar			__PROTO((struct Stack *));
void		PrintError		__PROTO((STRPTR const, LONG, LONG const,
								const LONG, enum ErrNum const, ...));
void		PrintPrgErr		__PROTO((enum PrgErrNum Error, ...));
void        PrintStatus     __PROTO((ULONG));
BOOL		PushChar		__PROTO((const UBYTE c, const ULONG Line,
								const ULONG Column, struct Stack *Stk,
								const STRPTR LineCpy));
STRPTR		ReadLine		__PROTO((STRPTR, FILE *));
BOOL		ReadRC			__PROTO((STRPTR const));
STRPTR		ReadWord		__PROTO((STRPTR, FILE *));
void		ResetSettings	__PROTO((void));
void		ReverseVideo	__PROTO((STRPTR String, FILE *fh));
void		SetupVars		__PROTO((void));
APTR		sfmemset		__PROTO((APTR to, int c, LONG n));
int			ShiftArg		__PROTO((STRPTR *Argument));
void		ShowIntStatus	__PROTO((void));
void		SilentPrint		__PROTO((ULONG Line, ULONG Pos));
APTR		StkPop			__PROTO((struct Stack *Stack));
BOOL		StkPush			__PROTO((const APTR Data, struct Stack *Stack));
APTR		StkTop			__PROTO((struct Stack *Stack));
STRPTR		strcatm			__PROTO((STRPTR dest, STRPTR String, ...));
STRPTR		strip			__PROTO((STRPTR, WORDBITS const));
void		strrep			__PROTO((STRPTR, UBYTE const, UBYTE const));
int			str_cmp			__PROTO((void const *, void const *));
void		tackon			__PROTO((STRPTR, const STRPTR));
void		tictoc			__PROTO((void));
struct CharInfo *
			TopChar			__PROTO((struct Stack *));



void		FooBar			__PROTO((void)); /* Generic test function */

#ifdef	STRMID_NEED
WORD		strmid			__PROTO((const STRPTR source, STRPTR dest,
									LONG pos, LONG len));
#endif

#ifdef	STRUPR_NEED
STRPTR		strupr			__PROTO((STRPTR));
#endif

#ifdef	STRDUP_NEED
STRPTR		strdup			__PROTO((STRPTR const));
#endif

#define	realloc	realloc_
void *		realloc_		__PROTO((void *,size_t));




/* #define DEBUG */
#ifdef		DEBUG
#	undef NULL
#	define NULL	((void *) 0L)
#endif

