/***************************************************************************
 *									   *
 *		   texpp TeX preprocessor, Version 2.16.		   *
 *		      Laci Csirmaz, DIMACS - Rutgers			   *
 *                             Apr. 15, 1991                               *
 *									   *
 ***************************************************************************/
/*** June 11,1993 -- done
1) %undef .EQ	 
   does not work after %define .EQ #1 %...% \\ %dispmode .EQ .EN
2) some problem with \hbox {foo} and \hbox to 10pt {foo}
   I guess that the right definition should be
   %define \hbox #1 {#2} %\hbox #1{#2}%
   %plainpar \hbox
   indicating that the second parameter should be enclosed in brackets. Stuff
   before that parameter goes to the place of #1. Note: that stuff can be empty.
ver 2.15 03/07/93
   "a \hbox{b}" becomes "a\hbox{b}", the space vanishes
ver 2.1x 03/10/93
   if an argument is of the form a$....$b then it is cut at the $ sign. It
   would be better that the $ sign does not denote the end of the argument.
   Also, arguments of the a\hbox{b}c are not allowed. This seems to be too
   difficult to handle, and in some sense contradists the global "idea" of
   parameter handling. So leave it as it is...
ver 2.16 10/11/93
   I want to use %plainpar with macros which have left parameter. Handle
   in define_parameter_keyword() procedure, simply left out the
   "left_pars_k(k)!=0" checking. Will it work????
ver 2.16a 03/07/01
   ported to linux, problems with CR in two places; also changed included
   files and variable number arguments
   if a file not found, try the STANDARD library (/usr/lib/)
   argument change: 
      no argument: output to stdout
      -w-        : no output at all
      -w filename: output to the given filename
ver 2.17 12/05/01
   close_range() had a bug, stack_bottom was set incorrectly
ver 2.18 08/08/07
   introducing -m switch to supress output from macro files
******/
#define VERSION "2.18"

/*-------------------------------------------------------------------------*
 |                            include files		                   |
 *-------------------------------------------------------------------------*/
#include <ctype.h>
#ifdef __TURBOC__
#include <alloc.h>
#include <mem.h>
#else	/* mainly linux */
#include <malloc.h>
#include <memory.h>
#define cdecl
#define setmem(s,n,c)	memset(s,c,n)
#define movmem(s,d,n)	memcpy(d,s,n)
#define stricmp(s1,s2)  strcasecmp(s1,s2)
#define strnicmp(s1,s2,n) strncasecmp(s1,s2,n)
#define STANDARD_DIR	"/usr/lib/texpp/"
#endif
#include <string.h>
#include <stdio.h>
#include <stdarg.h>

#define byte unsigned char
/*-------------------------------------------------------------------------*
 |                           mode and style		                   |
 *-------------------------------------------------------------------------*/
#define MATH_MODE       0x01
#define DISP_MODE       0x02
#define DEFINE_MODE     0x04
#define COMMENT_MODE    0x08
#define PRESERVE_MODE   0x10
#define QUOTE_MODE	0x20

#define SIMPLE_STYLE    0       /* style for `$' or `$$' */
#define DEFINE_STYLE    (-1)    /* style for %mdefine */
#define PAR_STYLE       (-2)    /* style for %mathpar */

int global_mode;                /* mode flags - in math, display mode; 
				   skipping a comment, or reading a TeXpp
				   definition. */
int mode_style;			/* MATH and DISP style number to distinguish
				   between different pairs of math and display
				   mode switches. */

#define in_comment_mode()	(global_mode&COMMENT_MODE)
#define in_def_mode()		(global_mode&DEFINE_MODE)
#define in_disp_mode()		((global_mode&DISP_MODE)!=0)
#define in_math_mode()		((global_mode&MATH_MODE)!=0)
#define in_plain_mode()		((global_mode&(DISP_MODE|MATH_MODE))==0)
#define in_preserve_mode()      (global_mode&PRESERVE_MODE)
#define in_quote_mode() 	(global_mode&QUOTE_MODE)
#define quote_delim_mode()	(global_mode&(QUOTE_MODE|DISP_MODE|MATH_MODE))
#define clear_preserve_mode()   {global_mode &= ~PRESERVE_MODE;}
#define set_preserve_mode()     {global_mode |= PRESERVE_MODE;}
#define set_plain_mode()	{global_mode &= ~(DISP_MODE|MATH_MODE);}
#define set_disp_mode()		{global_mode |= DISP_MODE;}
#define set_math_mode()		{global_mode |= MATH_MODE;}
#define set_quote_mode()	{global_mode |= QUOTE_MODE;}
#define clear_mode()		{global_mode &= ~(DISP_MODE|MATH_MODE|QUOTE_MODE);}

/*--------------------------------------------------------------------------*/
/*                        input/output variables			    */
/*--------------------------------------------------------------------------*/
#define APPEND_OUT	0	/* output file types */
#define WRITE_OUT	1
#define STD_OUT 	2
#define NUL_OUT 	3

FILE *input_file;               /* stream to read from */
FILE *output_file;		/* stream to write to */
FILE *error_file;		/* stream for error messages */
int supress_output;		/* 1 if supress output */
char *output_file_name; 	/* the actual output file name */
int output_type;		/* type of output */
char *input_file_name;		/* the actual input file name */
int input_line_number;		/* which line we are in */

int exit_value=0;		/* 1 if an error occured */

unsigned output_position=0;	/* where the next token goes */
unsigned last_output_position=0;/* saved output_position */
unsigned error_position=0;	/* end of last error position */

#define LAST_OUT		(last_output_position)
#define CURRENT_OUT		(output_position)
#define ERROR_OUT		(error_position)

/*-------------------------------------------------------------------------*
 |                  characters used as special tokens			   |
 | Characters in the interval _SC <= . <= E_EOF code special combinations. |
 | Chars originally in this interval are converted into pairs (E_ESCAPE,   |
 | convert_char(x)), where the new values are just above E_EOF. There must |
 | be enough room for the translated chars, this is checked by the #error  |
 | directive.								   |
 *-------------------------------------------------------------------------*/
#define _SC             180             /* interval of special chars */

#define E_SPACE 	(_SC+9) 	/* replaces \  */
#define E_LBRACE	(_SC+10)	/* replaces \{ */
#define E_RBRACE	(_SC+11)	/* replaces \} */
#define E_PCT_MARK	(_SC+12)	/* replaces \% */
#define E_BACKSLASH	(_SC+13)	/* replaces \\ */
#define E_DOLLAR	(_SC+14)	/* replaces \$ */
#define E_QUOTE 	(_SC+15)	/* replaces \" */
#define E_ESCAPE	(_SC+16)	/* special character */
#define E_KEYWORD	(_SC+17)	/* keyword ahead */
#define E_EOF		(_SC+18)	/* replaces EOF */

#define make_par(x)     ((x)-'1'+_SC)   /* replaces #1 ... #9 */
#define extract_par(x)  ((x)-_SC)
#define is_par(x)       (_SC<=(x)&&(x)<(_SC+9))
#define special_char(x) (_SC<=(x)&&(x)<=E_EOF)
#define convert_char(x) ((x)-_SC+E_EOF+1)   /* illegal -> legal */
#define decodable(x)	(E_EOF < (x) && (x)<=E_EOF+(E_EOF-_SC)+1)
#define decode_char(x)	((x)+_SC-E_EOF-1)
#if decodable(250)
    #error Too many special tokens
#endif

/*-------------------------------------------------------------------------*
 |                  Storing and retrieving macro text                      |
 *-------------------------------------------------------------------------*/
typedef struct _MACRO {
		byte flag1,flag2;	/* flags */
		byte par_type,par_no;	/* number of left and right pars */
                short int style;        /* style if mode switch word */
		byte *name;		/* macro name */
		byte *body;		/* macro body, can be NULL */
                struct _MACRO *link;    /* pointer to the next macro */
                struct _MACRO *keyword; /* pointer to the next \-keyword */
} MACRO;

/*--------------------------- MACRO flags ---------------------------------*/
#define K_MATH		0x01	/* 1 for MATH and DISP mode only */
#define K_PRESERVE	0x02	/* 1 for \preserve keyword */
#define K_BACKSLASH	0x04	/* 1 if starts with backslash */
#define K_CHECKLETTER	0x08	/* 1 if cannot be followed by a letter */
#define K_INOUT		0x10	/* bit for IN (0) or OUT (1) mode switch */
#define K_MATHDISP	0x20	/* bit for MATH (0) or DISP (1) mode switch */
#define K_STANDALONE	0x40	/* 1 for identical IN and OUT mode switch */
#define K_INDENT_ON	0x01	/* 1 for indentspaces on */
#define K_INDENT_OFF	0x02	/* 2 for indentspaces off */
#define K_CAPSULE	0x04	/* bit indicating #1{#2} parameters */

#define P_NONE          0       /* no speacial parameter handling */
#define P_PRESERVE      1       /* no substitution in parameter */
#define P_PLAIN         2       /* plain mode for parameters */
#define P_MATH          3       /* math mode for parameters */

#define is_math_macro(x)	((x)->flag1 & K_MATH)
#define word_length_k(x)	strlen((char *)((x)->name))
#define style_k(x)		((x)->style)
#define body_k(x)		((x)->body)
#define left_pars_k(x)		(((x)->par_no)&0xF)
#define right_pars_k(x) 	(((x)->par_no>>4)&0xF)
#define par_type_k(x)		((x)->par_type)
#define is_preserve_k(x)	((x)->flag1 & K_PRESERVE)
#define is_backslash_k(x)	((x)->flag1 & K_BACKSLASH)
#define is_modeswitch_k(x)	((x)->style > 0)
#define is_math_k(x)		(((x)->flag1 & K_MATHDISP)==0)
#define is_in_k(x)		(((x)->flag1 & K_INOUT)==0)
#define is_standalone_k(x)	((x)->flag1 & K_STANDALONE)
#define check_letter_k(x)	((x)->flag1 & K_CHECKLETTER)
#define is_indent_on_k(x)	((x)->flag2 & K_INDENT_ON)
#define is_indent_off_k(x)	((x)->flag2 & K_INDENT_OFF)
#define is_capsule_k(x) 	((x)->flag2 & K_CAPSULE)

/*-------------------------------------------------------------------------*
 |                            symbols                                      |
 *-------------------------------------------------------------------------*/
#define SOFT_DELIMITER	1	/* space, tab, newline */
#define HARD_DELIMITER	2	/* newline in DEF_MODE */
#define DELIMITER	3	/* control character, not soft delimiter */
#define MACRO_DELIM	4	/* macro text delimiter in DEF_MODE */
#define MATH_IN		5	/* entering math mode */
#define MATH_OUT	6	/* leaving math mode */
#define DISP_IN		7	/* entering displayed mode */
#define DISP_OUT	8	/* leaving displayed mode */
#define QUOTE_IN	25	/* starting quote in math or disp mode */
#define QUOTE_OUT	26	/* closing quote in math or disp mode */
#define PRESERVE	9	/* \preserve keyword */
#define DEF_KEYWORD	10	/* %define keyword */
#define MDEF_KEYWORD	11	/* %mdefine keyword */
#define UNDEF_KEYWORD	12	/* %undefine keyword */
#define MATH_KEYWORD	13	/* %mathmode keyword */
#define DISP_KEYWORD    14      /* %dispmode keyword */
#define PRESPAR_KEYWORD 22      /* %preservepar keyword */
#define PLAINPAR_KEYWORD 23     /* %plainpar keyword */
#define MATHPAR_KEYWORD 24      /* %mathpar keyword */
#define INDENT_KEYWORD	27	/* %indentspaces keyword */
#define COMMENT		15	/* comment in a line */
#define EMPTY_LINE	16	/* empty line, cannot be in a macro */
#define WORD		17	/* a word of visible characters */
#define OPEN		18	/* { */
#define CLOSE		19	/* } */
#define ENDFILE		20	/* at end of file */
#define PARAMETER	21	/* #1 .. #9 */

int SYMBOL;			/* the last symbol */
int S_aux1;			/* if SYMBOL==SOFT_DELIMITER then S_aux1=0
				   says that the delimiter vanishes at
				   substitution; if SYMBOL==PARAMETER then the
				   parameter's value (0..8) */
MACRO *S_aux2;                  /* if SYMBOL==WORD then the corresponding
				   MACRO entry, or NULL if none */

/*-------------------------------------------------------------------------*
 |			   Preprocessor units				   |
 *-------------------------------------------------------------------------*/
#define X_PARAMETER	0	/* a parameter */
#define X_CAPSULE	7	/* parameter in brackets */
#define X_OPEN		8	/* parameter ended at { */
#define X_DMODE_OUT	1	/* $ or $$ leaving mode */
#define X_XMODE_OUT	2	/* other mode closing symbol */
#define X_QUOTE_OUT	6	/* closing quote symbol */
#define X_CLOSE		3	/* closing brace */
#define X_ERROR		4	/* error encountered */
#define X_OTHER		5	/* other special symbol */

/*-------------------------------------------------------------------------*
 |                         TeX and TeXpp texts       			   |
 *-------------------------------------------------------------------------*/
#define	T_DEFINE	"define"	/* TeXpp keywords after % */
#define T_DEFINE_LEN	6
#define T_MDEFINE	"mdefine"
#define T_MDEFINE_LEN	7
#define T_UNDEFINE	"undefine"
#define T_UNDEFINE_LEN	8
#define T_INDENT	"indentspaces"
#define T_INDENT_LEN	12
#define T_MATHMODE	"mathmode"
#define T_MATHMODE_LEN	8
#define T_DISPMODE	"dispmode"
#define T_DISPMODE_LEN	8
#define T_PRESPAR       "preservepar"
#define T_PRESPAR_LEN   11
#define T_PLAINPAR      "plainpar"
#define T_PLAINPAR_LEN  8
#define T_MATHPAR       "mathpar"
#define T_MATHPAR_LEN   7

#define T_PRESERVE	"\\preserve"	/* should start with backslash!!! */
#define T_PRESERVE_LEN	9

#define TeXpp_MACRO_DEFINITION	"%%% TeXpp macro definition %"
	/* replacement text for TeXpp macro definition */
#define OPEN_QUOTE_STRING   "\\hbox{"
#define CLOSE_QUOTE_STRING  "}"
	/* replacement text for quoting in math and display mode */


/*-------------------------------------------------------------------------*
 |                        error message texts				   |
 *-------------------------------------------------------------------------*/
#define TEX_ERROR_FORMAT	    "%%%%%%TeXpp error in %s line %d: "
	/* used as a format to insert error message into TeX text */
#define ERR_ERROR_FORMAT	    "Error in %s line %d: "
	/* used as a format to write error message into stderr */

#define NO_INPUT_FILE               "no input file"
#define CANNOT_OPEN_TRANSLATE       "cannot open translate file"
#define CANNOT_OPEN_FILE            "cannot open the file"
#define WRONG_CHARDEF               "wrong decimal number after \\"
#define WRONG_TRANSLATE_DELIM       "replacement string must be enclosed by ' or \""
#define NO_CLOSING_QUOTE            "missing closing quote"
#define LONG_TRANSLATE_STRING       "replacement string is too long"
#define WRONG_FORMAL_PARAMETER      "no digit after #-mark"
#define PARAMETER_TWICE		    "parameter #%d declared twice"
#define WRONG_MACRO_NAME	    "macro name expected"
#define WRONG_MODE_SWITCH_DEF	    "wrong macro name after %%%s"
#define MISSING_DELIMITER	    "missing macro text delimiter %% "
#define TOO_LESS_LEFT_PARAMS	    "less than %d left parameters for %s "
#define TOO_LESS_PARAMS		    "less than %d parameters for %s "
#define TOO_LONG_MACRO_DEF	    "too long definition for macro %s "
#define TOO_LONG_PARAMETER	    "too long parameter for macro %s "
#define UNDEFINED_PARAMETER	    "parameter #%d wasn't declared"
#define WRONG_DOLLAR_SWITCH	    "erroneous $ mode switch"
#define WRONG_CLOSING_DOLLAR	    "erroneous closing $ mode switch"
#define UNBALANCED_PARAMETERS       "unbalanced parameter mode"
#define EMPTY_LINE_IN_MODE	    "empty line in %s mode"
#define MISSING_CLOSING_QUOTE	    "missing closing quote in math or display mode"
#define ENDFILE_IN_MODE		    "end of file in %s mode"
#define WRONG_MODE_SWITCH	    "mode switch %s in an unexpected place"
#define WRONG_PARAMETER_MODIFIER    "wrong macro name after %%%s"
#define OUT_OF_MEMORY		    "no more memory"

/*=========================================================================*/
/*                             prototypes                                  */
/*=========================================================================*/

int skip_to_eol(int);
void clear_translate(void);
int next_translate_char(void);
int translate_line(int,int,unsigned char*,int);
void read_translate_file(void);
int next_char(void);
byte spy_token_ahead(void);
int spy_string_ahead(char *,int);
int get_next_token(void);
void skip_tokens(int);
int alloc_outbuffers(void);
void write_output(byte*,int);
void store_token(int);
void store_string(char*);
void flush_output(void);
int set_out_position(unsigned);
int retrieve_out(unsigned,unsigned,byte*);
void cdecl error(char*,...);
int alloc_macro(void);
MACRO*new_macro(MACRO*,byte*,unsigned);
void insert_macro(void);
void unlink_macro(MACRO*,unsigned);
void insert_backslash_keyword(MACRO*);
int set_macro_structure(MACRO*,int,int,int,unsigned);
void set_modeswitch(MACRO*,int,int,int);
void set_partype(MACRO*,int);
MACRO*search_word(byte*,unsigned);
MACRO*check_backslash_keyword(int);
int alloc_word(void);
void store_word_token(int);
MACRO*retrieve_macro_entry(void);
int check_token(int);
void next_symbol(void);
void read_word(void);
void skip_line_as_comment(void);
void skip_soft_delimiters(void);
int alloc_params(void);
void push_par(unsigned,unsigned,unsigned);
int pop_par(unsigned*,unsigned*,unsigned*);
void open_range(void);
void close_range(void);
int macro_substitution(unsigned*,MACRO*);
void translate_parameters(byte*);
void init_macro_definition(void);
void close_macro_definition(void);
void read_macro_definition(int);
void undefine_macro(void);
void define_mode_keyword(int);
int deal_range(void);
void define_parameter_keyword(int);
int set_mode(MACRO*);
int read_params(int,int,int);
int store_mode_block(unsigned,unsigned,int);
int deal_mode_switch(MACRO*,unsigned,unsigned);
int deal_word(unsigned,int);
void skip_balanced_expression(void);
void skip_word(void);
int store_parameter(int,int,int);
void read_file(void);
int is_param(char*,char);
void find_output(int,char*[]);
int arguments(int,int*,int,char*[]);
int on_line_help(int,char*[]);

/*=========================================================================*/
/*                        translate table input                            */
/*=========================================================================*/

/*-------------------------------------------------------------------------*
 | The lines of translate file describe "translations" of certain chars    |
 | to strings. They have the form: if the first character of the line is   |
 | NOT a space or tab, then it will be redefined; if it start with a back- |
 | slash, a decimal value follows which defines the character value. The   |
 | replacement string is enclosed within quotation marks, everything until |
 | the closing quotation mark is in the replacement (including EOF chars). |
 | Double quotation marks are replaced by a single one.                    |
 *-------------------------------------------------------------------------*/

unsigned char *translate[256];          /* translate table, NULL means keep */

#define TM_MAX      500
typedef unsigned char TM [TM_MAX+2];

TM *translate_mem=NULL;

#define clear_translate()              /* empty translate table */ \
        setmem(translate,sizeof(translate[0])*256,0)

int skip_to_eol(int c)
{
    while(c>=0 && c!='\n') c=getc(input_file);
    return(c);
}

int next_translate_char(void)
{int c; int i;
    while(1){
        c=getc(input_file);
        if(c==0 || c==' '||c=='\t'||c=='\r'){
skip_to_end:
            c=skip_to_eol(0);
        }
        if(c!='\n') break;
        else input_line_number++;
    }
    if(c!='\\') return(c);
    c=getc(input_file); if(c=='\\') return(c);
    i=0; while('0'<=c && c<='9'){ i=i*10+c-'0'; c=getc(input_file);}
    ungetc(c,input_file);
    if(i<=0 || i>255){
        error(WRONG_CHARDEF);       /* wrong code */
        goto skip_to_end;   
    }
    return (i);
}

int translate_line(cnt,chr,p,len) int cnt; int chr; unsigned char *p; int len;
{int c; int quote; int i;
    if(cnt==0){         /* first time in */
        do{quote=getc(input_file);}
            while(quote==' ' || quote=='\t');
        if(quote!='\'' && quote!='\"'){ /* wrong delimiter */
            error(WRONG_TRANSLATE_DELIM);
            skip_to_eol(quote);
            return(0);
        }
        c=getc(input_file);
        if(c==quote){                   /* "" or '' */
            translate[chr]=(unsigned char*)"";
            skip_to_eol(quote);
            return(0);
        }
        translate[chr]=p;
        ungetc(c,input_file);
    } else quote = cnt==-1 ? '\'' : '\"';
    i=0;
    while(1){
        c=getc(input_file);
        if(c<0){*p=0; return(i+1);}
        if(c=='\n'||c=='\r'){
            ungetc(c,input_file); error(NO_CLOSING_QUOTE);
            *p=0; return(i+1);
        }
        if(c==quote){
            c=getc(input_file); if(c!=quote){
                *p=0; skip_to_eol(c); return(i+1);
            }
        }
        *p++=(unsigned char)c; i++; len--;
        if(len<0) return(quote=='\'' ? -1 : -2);
    }
    return(0);
}

void read_translate_file()
{int c; int t_index; TM *t,*tn; int i,n;
    clear_translate();
    t_index=0; t=translate_mem;
    while((c=next_translate_char())>0){
        if(t==NULL){
            t=malloc(sizeof(TM));
            if(t==NULL){ error(OUT_OF_MEMORY); return;}
        }
        i=translate_line(0,c,(*t)+t_index,TM_MAX-t_index);
        if(i>=0) t_index+=i;
        else {                          /* not enough space */
            tn=malloc(sizeof(TM));
            if(tn==NULL){
                translate[c]=NULL; error(OUT_OF_MEMORY); return;
            }
            n=TM_MAX+1-t_index; if(n<=0) n=1;
            translate[c]=&((*tn)[0]);
            movmem((*t)+t_index,*tn,n);
            t=tn; t_index=n;
            i=translate_line(i,c,(*t)+t_index,TM_MAX-t_index);
            if(i>=0)t_index+=i;
            else {
                error(LONG_TRANSLATE_STRING);
                translate[c]=NULL;
                skip_to_eol(0);
            }
        }
        input_line_number++;
    }
}


/*=========================================================================*/
/*                          token input                                    */
/*=========================================================================*/

/*-------------------------------------------------------------------------*
 | The lowest level of the three-level reading is the immediate character  |
 | input from `input_file'. Procedure `next_char()' filters out incoming   |
 | characters ==0 and >126, returns E_EOF on end of file, and makes some   |
 | local translations depending on the mode stored in `global_mode'. If in |
 | o  COMMENT_MODE, all characters are returned. In			   |
 | o  other modes the pairs \{ \} \$ \% and \\ are coded as single chars.  |
 | o  DEFINE_MODE pairs #1 .. #9 are recognized as parameters; and ##      |
 |     is replaced by a single # char.					   |
 *-------------------------------------------------------------------------*/

#define TAB_LENGTH  8			/* tab length */

int do_indent=0, space_no=0;		/* used to translate leading spaces */
#define initialize_char_reading()	\
    {do_indent=space_no=0;}

#define set_indent()	    (do_indent=1)
#define clear_indent()	    (do_indent=0)
#define is_indent()	    (do_indent!=0)

int next_char() /* lowest level reading from `input_file' */
{int c,c1;
    if(space_no>0){			/* spaces at the beginning */
	space_no--;
	return(do_indent ? E_SPACE : ' ');
    }
    while((c=getc(input_file))==0);     /* skip 0 and CR characters */
    if(c<0) return(E_EOF);              /* end of the file */
    if(special_char(c)){		/* special character */
	ungetc(convert_char(c),input_file);
	return(E_ESCAPE);
    }
    if(in_comment_mode()) return(c);    /* skipping a comment */
    switch(c){
case '\\':                              /* backslach char */
        switch(c1=getc(input_file)){    /* next char */
/*  case ' ':   c=E_SPACE; break;   */  /* do not handle this case! */
    case '%':   c=E_PCT_MARK; break;
    case '{':   c=E_LBRACE; break;
    case '}':   c=E_RBRACE; break;
    case '\\':  c=E_BACKSLASH; break;
    case '$':   c=E_DOLLAR; break;
    case '"':   c=E_QUOTE; break;
    default:	ungetc(c1,input_file); break;/* simply put back the ahead char */
	}
	break;
case '\r':				/* CR */
       c1=getc(input_file);
       if(c1=='\n'){			/* it is the same as LF */
            c='\n';			/* and fall through... */
       } else {
            ungetc(c1,input_file);
       	    break;
       }
case '\n':                              /* newline */
	space_no=0;
	while(1){
	    c1=getc(input_file);
	    if(c1==' ') space_no++;
	    else if(c1=='\t'){
		space_no-=space_no%TAB_LENGTH; space_no+=TAB_LENGTH;
	    } else break;
	}
	ungetc(c1,input_file);
	break;
case '#':
	if(in_def_mode()){		/* check formal parameters */
	    c1=getc(input_file);
	    if('1'<=c1 && c1<='9') c=make_par(c1);
	    else if(c1!='#'){
		error(WRONG_FORMAL_PARAMETER);
		ungetc(c1,input_file);
	    }
	}
	break;
default:
	break;
    }
    return(c);
}

/*-------------------------------------------------------------------------*
 | On the medium level, values given by `next_char()' are passed over as   |
 | tokens. But tokens can be read AHEAD, so special procedures are used to |
 | deal with them. The circular buffer `token_ahead[]' stores the tokens   |
 | read ahead; its size must be a power of 2. Procedures handling tokes:   |
 | -- initialize_token_reading() should be called first.                   |
 | -- spy_token() returns the next token but does not advances ahead.      |
 | -- spy_string_ahead() returns TRUE(!=0) if the next item agrees with    |
 |      the parameter, and checks whether the item following the string is |
 |      a white space or is NOT a letter (to agree with TeX's backslash    |
 |      convention).                                                       |
 | -- get_next_token() simply returns the next character.                  |
 | -- skip_tokens(n) skips `n' tokens ahead.                               |
 | To comply with the mode dependent character reading, the spied chars    |
 | should not change the mode -- so be careful when spying ahead ...       |
 *-------------------------------------------------------------------------*/

#define MAX_TOKEN_AHEAD 128                     /* must be a power of 2 */
byte token_ahead[MAX_TOKEN_AHEAD];		/* the circular buffer */
int token_ahead_in=0, token_ahead_out=0;	/* pointers */

#define initialize_token_reading()      {token_ahead_in=token_ahead_out=0;}

byte spy_token_ahead()	/* Returns the next token but does not advances */
{
    if(token_ahead_in==token_ahead_out){	/* ahead buffer is empty */
	token_ahead[token_ahead_in]=next_char();
	token_ahead_in=(token_ahead_in+1)&(MAX_TOKEN_AHEAD-1);
    }
    return(token_ahead[token_ahead_out]);
}

#define FOLLOW_NOTHING		0
#define FOLLOW_NO_LETTER	1
#define FOLLOW_SPACE		2

int spy_string_ahead(str,follow_up) char *str; int follow_up;
/*-------------------------------------------------------------------------*
 | Returns TRUE (=1) if `str' agrees with the next tokens. The character   |
 | following 'str' should be as described by the 'follow_up' parameter.    |
 *-------------------------------------------------------------------------*/
{int t,i,same; byte tt;
    t=token_ahead_out; same=1;
    while(same && (*str || follow_up)){
	if(t==token_ahead_in){	/* should read ahead */
	    i=(token_ahead_in+1)&(MAX_TOKEN_AHEAD-1);
	    if(i!=token_ahead_out){
		token_ahead[t]=next_char(); token_ahead_in=i;
	    } else return(0);	/* ahead buffer is full, not found */
	}
	tt=token_ahead[t];
	if(*str){
	    same=((unsigned char)(*str))==tt;
	    str++; t=(t+1)&(MAX_TOKEN_AHEAD-1);
	} else {
	    same=follow_up==FOLLOW_NO_LETTER ? ((tt > 127) || !isalpha(tt)) :
	    		   (tt==' ' || tt=='\t');
	    follow_up=0;
	}
    }
    return(same);
}

int get_next_token()  /* gives the next token */
{ byte res;
    if(token_ahead_in==token_ahead_out)
	return(next_char());
    res=token_ahead[token_ahead_out];
    token_ahead_out=(token_ahead_out+1)&(MAX_TOKEN_AHEAD-1);
    return(res);
}

void skip_tokens(n) int n; /* skips the next `n' subsequent tokens */
{int stored;
    stored=(token_ahead_in+MAX_TOKEN_AHEAD-token_ahead_out)
		&(MAX_TOKEN_AHEAD-1);
    if(n<stored){
	token_ahead_out+=n; token_ahead_out&=(MAX_TOKEN_AHEAD-1);
    } else {
	n-=stored;
	token_ahead_out=token_ahead_in;
	while(n-- > 0) next_char();    
    }
}

/*=========================================================================*/
/*                          token output                                   */
/*=========================================================================*/

/*-------------------------------------------------------------------------*
 | Output is done through double buffering: OUT_BUFFER1 and OUT_BUFFER2    |
 | hold the output until the other is full. This means that the every      |
 | time the last OUT_BUFFER_LEN output tokens are recoverable. This is     |
 | used to store macro parameters which are erased after substitution.     |
 | -- output_position is used as an absolute position in output.           |
 | -- store_token(t) puts `t' into the output buffer.			   |
 | -- store_string(str) puts `str' into the output buffer.		   |
 | -- flush_output() flushes output buffers.				   |
 | -- set_output_position(pos) erases all output written since the absolute|
 |     position `pos', if it is possible.				   |
 | -- retrieve_out(from,till,to) reads back the output between positions   |
 |     `from' and `till' and stores it at `to'.				   |
 *-------------------------------------------------------------------------*/

#define OUT_BUFFER_LEN		16384	/* should be a power of 2 */

byte *OUT_BUFFER, *OTHER_OUT_BUFFER;

int other_buffer_is_full=0;	/* indicates if OTHER_OUT_BUFFER is full */
int output_index=0;		/* next free place in OUT_BUFFER */

int alloc_outbuffers()
{
    OUT_BUFFER=malloc(OUT_BUFFER_LEN);
    OTHER_OUT_BUFFER=malloc(OUT_BUFFER_LEN);
    return(OUT_BUFFER==NULL || OTHER_OUT_BUFFER==NULL);
}

void write_output(from,len) byte *from; int len;
/* writes `len' tokens to `output_file' with appropriate translation */
{byte token; byte *tr;
 static int escaped_char=0;
    if(output_file==NULL) return;
    if(supress_output) return;
    while(len-- > 0){
	token=*from++;
	if(escaped_char!=0 && decodable(token)){
	    token=decode_char(token); escaped_char=0;
	} else {
	    escaped_char=0;
	    switch(token){
case E_SPACE:	    putc('\\',output_file); token=' '; break;
case E_LBRACE:	    putc('\\',output_file); token='{'; break;
case E_RBRACE:	    putc('\\',output_file); token='}'; break;
case E_PCT_MARK:    putc('\\',output_file); token='%'; break;
case E_BACKSLASH:   putc('\\',output_file); token='\\'; break;
case E_DOLLAR:	    putc('\\',output_file); token='$'; break;
case E_QUOTE:	    putc('\\',output_file); token='"'; break;
case E_ESCAPE:	    escaped_char=1; break;
default:            break;
	    }
	    if(special_char(token)) continue;
	}
	if((tr=translate[token])!=NULL){
	    while((token = *tr++)!=0) putc((char)token,output_file);
	}
	else putc((char)token,output_file);
    }
}

void store_token(t) int t;      /* puts token `t' into OUT_BUFFER */
{byte *bf;
    OUT_BUFFER[output_index]=t;
    output_index++; output_index &= OUT_BUFFER_LEN-1;
    output_position++;
    if(output_index==0){		/* overturn */
	if(other_buffer_is_full!=0){	/* write OTHER_OUT_BUFFER */
	    write_output(OTHER_OUT_BUFFER,OUT_BUFFER_LEN);
	}
	other_buffer_is_full=1; 
	bf=OUT_BUFFER; OUT_BUFFER=OTHER_OUT_BUFFER; OTHER_OUT_BUFFER=bf;
    }
}

void store_string(str) char *str; /* stores the elements of the string */
{
    while(*str) store_token(*str++);
}

void flush_output()	/* writes everything out */
{
    if(other_buffer_is_full)
	write_output(OTHER_OUT_BUFFER,OUT_BUFFER_LEN);
    other_buffer_is_full=0;
    write_output(OUT_BUFFER,output_index);
    output_index=0;
}

int set_out_position(pos) unsigned pos;
/* erases everything which was written before position `pos' -- if possible */
{unsigned back; byte *bf;
    if(pos<error_position) pos=error_position;
    back=output_position - pos;
    if(back<=(unsigned)output_index){
	output_index-=back; output_position=pos;
	return(0);
    }
    if(other_buffer_is_full!=0 && back-output_index <= OUT_BUFFER_LEN ){
	other_buffer_is_full=0;
	output_position=pos;
	bf=OUT_BUFFER; OUT_BUFFER=OTHER_OUT_BUFFER; OTHER_OUT_BUFFER=bf;
	output_index=OUT_BUFFER_LEN - (back - output_index);
	return(0);
    }
    return(1);
}

int retrieve_out(from,till,to) unsigned from,till; byte *to;
/* copies the output written between positions `from' and `till' into `to' */
{unsigned back,first_part,len;
    back=output_position-from; len=till-from;
    if(len<=0){
	to[0]=0;
        return(0);
    }
    if(back<=(unsigned)output_index){
        movmem(OUT_BUFFER+(output_index-back),to,len);
	to[len]=0;
	return(0);
    } 
    first_part=back-output_index;
    if(other_buffer_is_full!=0 && first_part <= OUT_BUFFER_LEN){
        if(len<=first_part)
           movmem(OTHER_OUT_BUFFER+(OUT_BUFFER_LEN-first_part),to,len);
	else {
           movmem(OTHER_OUT_BUFFER+(OUT_BUFFER_LEN-first_part),to,first_part);
           movmem(OUT_BUFFER,to+first_part,len-first_part);
	}
	to[len]=0;
	return(0);
    }
    return(1);		/* too long parameter, cannot handle */
}

/*=========================================================================*/
/*                           error handling                                */
/*=========================================================================*/

/*-------------------------------------------------------------------------*
 | Whenever an error is discovered, `error()' is called with arguments     |
 | similar to `printf(...)' giving larger freedom to include extra infor-  |
 | mation in the error text. Error messages are inserted into the output   |
 | text, ensuring that error messages will not be erased later. They are   |
 | also repeated in `error_file' (presumably stderr) using a different     |
 | starting format.							   |
 *-------------------------------------------------------------------------*/

void cdecl error(char *format,...)
/* writes an error message using a variable number of arguments */
{char buffer [1024]; byte *bf;
#ifndef __TURBOC__
 va_list ap;
 va_start(ap,format);
#endif
    exit_value=1;			/* inform that we had an error */
    /**** error message in TeX ****/
	sprintf(buffer,TEX_ERROR_FORMAT,input_file_name,input_line_number);
	store_string(buffer);
#ifdef __TURBOC__
	vsprintf(buffer,format,...);
#else
	vsprintf(buffer,format,ap);
#endif	
	store_string(buffer); store_string("\n");
	ERROR_OUT=CURRENT_OUT;				/* freeze output */
    /**** error message in error_file ****/
    if(error_file!=NULL){
	fprintf(error_file,ERR_ERROR_FORMAT,input_file_name,input_line_number);
	bf=(byte *)&buffer[0];				/* error message */
	while(*bf){
	    switch(*bf){
case E_SPACE:	    fprintf(error_file,"\\ "); break;
case E_LBRACE:	    fprintf(error_file,"\\{"); break;
case E_RBRACE:	    fprintf(error_file,"\\}"); break;
case E_PCT_MARK:    fprintf(error_file,"\\%%"); break;
case E_BACKSLASH:   fprintf(error_file,"\\\\"); break;
case E_DOLLAR:	    fprintf(error_file,"\\$"); break;
case E_QUOTE:	    fprintf(error_file,"\\\""); break;
default:	    if(*bf < ' ') fprintf(error_file,"^%c",'A'-1+*bf);
		    else if(*bf < 128) putc((char)*bf,error_file);
	    }
	    bf++;
	}
	putc('\n',error_file);
    }
#ifndef __TURBOC__
    va_end(ap);
#endif    
}

/*===========================================================================*/
/*                   Storing and retrieving macro text                       */
/*===========================================================================*/

/*---------------------------------------------------------------------------*
 | The MACRO structure is used to store all words which occur as macro names |
 | or as mode switch identifiers. The latter ones starting with backlash are |
 | linked separately so that we can search them sequentially whenever a	     |
 | backslash character appears in the input. Otherwise the words are	     |
 | searched by hashing: words sharing the same hash code are linked together.|
 | Initially macro texts and structures are stored in a reserved space. If   |
 | that space is full, extra space is reserved for each subsequent           |
 | definition.								     |
 | -- new_macro(old,word,hashcode) reserves space for a new macro definition |
 |      given the old definition (if applicable), the macro name and the     |
 |      hashcode.							     |
 | -- set_macro_structure(macro,new_type,left_par,right_par,body_len) fills  |
 |      the reserved `macro' with the given values; allocates space for the  |
 |      macro body.							     |
 | -- set_modeswitch(macro,display,standalone,out) resets the type of macro  |
 |      with the given values, and inserts into the list of `mode_keywords'. |
 | -- insert_macro() inserts the reserved macro into the hash table.	     |
 | -- unlink_macro(old,hashcode) deletes the `old' macro.		     |
 | -- search_word(word,hashcode) searches the macro definition for `word'.   |
 | -- check_backslash_keyword(from) looks whether one of the mode_keywords   |
 |      can be found spying ahead.					     |
 *---------------------------------------------------------------------------*/

#define PRIME		1999		/* must be a prime, used as the length
					   of the hash table. Other possible
					   values are: 2503, 2999 */
#define TEXT_LENGTH	20000		/* initial length of a text table
					   to store macro names and bodies */
byte *macro_text;                       /* the text table */
unsigned macro_text_length=0;           /* how much is used up of it */
#define MACRO_NO        300             /* initial number of macros */
MACRO *macro;                           /* reserved MACRO_NO macro */
int macro_no=1;                         /* how many macros are defined */

MACRO *mode_keywords;                   /* list of keywords starting with \ */
int next_style_number=SIMPLE_STYLE;	/* next available style number */

MACRO **hash_table;			/* the HASH table of size PRIME */

int alloc_macro()
{static MACRO preserve_keyword={        /* \preserve keyword */
	K_PRESERVE | K_CHECKLETTER,0,	/* flag1,flag2 */
	0,0,				/* par_type, par_no */
	0,				/* style */
	(byte*)T_PRESERVE,		/* name */
	NULL,NULL,NULL			/* body, link, next keyword */
};
    macro_text=malloc(TEXT_LENGTH);
    macro=calloc(MACRO_NO,sizeof(MACRO));
    hash_table=calloc(PRIME,sizeof(MACRO*));
    if(macro_text==NULL || macro==NULL || hash_table==NULL) return(1);
    macro[0]=preserve_keyword; macro_no=1;
    mode_keywords=&macro[0];
    return(0);
}

/*---------------------------------------------------------------------------*/
unsigned new_hashcode=0;
MACRO *new_macro_entry=NULL;

MACRO *new_macro(old,word,hashcode)
                MACRO *old; byte *word; unsigned hashcode;
/* makes a new entry to hash_table */
{
    if(macro_no<MACRO_NO){
	new_macro_entry=macro+macro_no; macro_no++;
    } else {
        new_macro_entry=(MACRO *)malloc(sizeof(MACRO));
	if(new_macro_entry==NULL){ error(OUT_OF_MEMORY); return(NULL); }
        setmem(new_macro_entry,sizeof(MACRO),0);
    }
    new_hashcode=hashcode%PRIME;
    if(old!=NULL) *new_macro_entry = *old;
    else {
        setmem(new_macro_entry,sizeof(MACRO),0);
	if((new_macro_entry->name=(byte *)strdup((char*)word))==NULL){
	    error(OUT_OF_MEMORY); return(NULL);
	}
    }
    new_macro_entry->link=NULL;
    return(new_macro_entry);
}

void insert_macro()	/* inserts `new_macro_entry' into its place */
{
    if(new_macro_entry==NULL) return;
    new_macro_entry->link=hash_table[new_hashcode];
    hash_table[new_hashcode]=new_macro_entry;
}

void unlink_macro(old,hashcode) MACRO *old; unsigned hashcode;
/* unlinks "old" from the hash table */
{MACRO *k,*k1;
    hashcode%=PRIME; k=hash_table[hashcode];	/* unlink from hash table */
    if(k==old) hash_table[hashcode]=old->link;
    else {
	while(k1=k->link, k1!=NULL && k1!=old) k=k1;
	k->link=old->link;
    }
    if(is_backslash_k(old)){			/* unlink from keyword */
        if(mode_keywords==old) mode_keywords=old->keyword;
	else {
	    k=mode_keywords;
	    while(k1=k->keyword, k1!=NULL && k1!=old) k=k1;
	    k->keyword=old->keyword;
	}
    }
}

int set_macro_structure(k,type,left_par,right_par,len)
        MACRO *k; int type,left_par,right_par; unsigned len;
/* fills k with the given values */
{
    k->flag1 &= ~K_MATH;	    /* clear K_MATH bit */
    k->flag2 &= ~K_CAPSULE;	    /* clear capsuled bit */
    if(type&1) k->flag1 |= K_MATH;  /* set K_MATH bit if necessary */
    if(type&2) k->flag2 |= K_CAPSULE;
    k->par_type=0;
    k->par_no=(right_par<<4) | left_par;
    if(macro_text_length+len < TEXT_LENGTH){	/* preserved memory */
	k->body=macro_text+macro_text_length;
	macro_text_length+=len;
	return(0);
    } 
    if((k->body=(byte *)malloc(len))==NULL){
	error(OUT_OF_MEMORY); return(1);
    }
    return(0);
}

void insert_backslash_keyword(s) MACRO *s;	/* backslash macro */
{MACRO *k; byte*name; int next_char;
    name=s->name;
    if(*name!='\\' || is_backslash_k(s)) return;
    while((next_char=*++name)!=0){
	if(next_char>127 || !isalpha(next_char))
	    return;			/* contains not only letters */
    }
    s->flag1 |= K_CHECKLETTER|K_BACKSLASH;
    k=mode_keywords;
    while(k!=NULL && k!=s) k=k->keyword;
    if(k==NULL){
        s->keyword=mode_keywords;
        mode_keywords=s;
    }
}

void set_modeswitch(s,type,standalone,out) MACRO *s; int type,standalone,out;
/* sets the appropriate mode for "s". Also puts it on the mode_keyword list */
{
    if(s==NULL) return;
    if(type==INDENT_KEYWORD){
	s->flag2 &= ~(K_INDENT_ON|K_INDENT_OFF);
	s->flag2 |= standalone ? (K_INDENT_ON|K_INDENT_OFF) :
		    out ? K_INDENT_OFF :
		    K_INDENT_ON;
    } else {
	if(out==0) next_style_number++;
	s->style=next_style_number;
	s->flag1 &= ~(K_INOUT | K_MATHDISP | K_STANDALONE);
	if(standalone) s->flag1 |= K_STANDALONE;
	if(type==DISP_KEYWORD) s->flag1 |= K_MATHDISP;
	if(out) s->flag1 |= K_INOUT;
    }
    insert_backslash_keyword(s);
}

void set_partype(s,type) MACRO *s; int type;    /* sets the parameter type */
{
    if(s==NULL) return;
    s->par_type = type;
    insert_backslash_keyword(s);
}

/*---------------------------------------------------------------------------*/
MACRO *search_word(word,hashcode) byte *word; unsigned hashcode;
/* returns the structure whose name agrees with `word', given its hash code. */
{MACRO *k;
    k=hash_table[hashcode%PRIME];
    while(k!=NULL){
	if(strcmp((char*)word,(char*)k->name)==0) return(k);
	k=k->link;
    }
    return(NULL);
}

MACRO *check_backslash_keyword(i) int i;
/* returns the structure whose `name' starting at the `i'-th character agrees
   with the spy_string_ahead. */
{MACRO *k;
    k=mode_keywords; while(k!=NULL){
	if(spy_string_ahead((char *)((k->name)+i),
		check_letter_k(k) ? FOLLOW_NO_LETTER : FOLLOW_NOTHING))
	    return(k); /* found */
	k=k->keyword;
    }
    return(NULL);
}

/*=========================================================================*/
/*                          Word handling                                  */
/*=========================================================================*/

/*-------------------------------------------------------------------------*
 | Words, i.e. character sequences between white spaces are stored sepa-   |
 | rately (not only in the output buffers); also their hash code is	   |
 | computed "on the fly". Macro handling routines got their approproate    |
 | parameters here.							   |
 | -- clear_word_store() should be called before a new word is dealt with. |
 | -- store_word_token(t) store `t' as the next word constituent.	   |
 | -- close_word_store() closes the word.				   |
 | -- prepare_new_macro_entry() the last word is becoming a new macro.     |
 | -- remove_macro() the last word is a macro to be "undefined".	   |
 | -- look_up_word() searches the stored word as a macro.		   |
 *-------------------------------------------------------------------------*/
#define MAX_WORD_LENGTH		512	/* no longer words are dealt with */
byte *WORD_STORE;			/* tokens of the last word */
int word_store_index;			/* index to WORD_STORE */
unsigned word_hash_code;		/* hash code computed on the fly */

int alloc_word()
{
    WORD_STORE=malloc(MAX_WORD_LENGTH);
    return(WORD_STORE==NULL);
}

#define clear_word_store()      {word_store_index=0;word_hash_code=952;}

void store_word_token(t) int t;
/* stores the word consitutent `t' in `WORD_STORE[]', and computes the
   hash code of the word "in fly". */
{
    WORD_STORE[word_store_index++]=t;
    word_hash_code = ((t+word_hash_code)<<4)+t;
    if(word_store_index==MAX_WORD_LENGTH) word_store_index--;
}

#define close_word_store()	{WORD_STORE[word_store_index]=0;}

#define prepare_new_macro_entry()  \
		new_macro(S_aux2,WORD_STORE,word_hash_code)

MACRO *retrieve_macro_entry()
{MACRO *k;
    if(S_aux2!=NULL)
	return S_aux2;
    k=new_macro(NULL,WORD_STORE,word_hash_code);
    insert_macro();
    return k;
}

#define remove_macro()		unlink_macro(S_aux2,word_hash_code)

#define look_up_word()		search_word(WORD_STORE,word_hash_code)

/*========================================================================*/
/*                            symbols                                     */
/*========================================================================*/

/*------------------------------------------------------------------------*
 | Highest level reading. The input text is broken into "symbol"s which   |
 | are passed to the main loop. A "symbol" is a sequence of tokens; and   |
 | `store_token()' is called with all tokens in it.			  |
 | o  SYMBOL  is the type of the symbol read;				  |
 | o  LAST_OUT holds the output position where the tokens forming the	  |
 |      last symbol start;						  |
 | o  S_aux1 contains some extra information about the SYMBOL;		  |
 | o  S_aux2 is the associated macro definition for the symbol if it is a |
 |      WORD.								  |
 | o  LAST_TOKEN and last_keyword are auxiliary variables to prevent	  |
 |      double parsing of certain sequences.				  |
 | The procedures which are called outside:				  |
 | -- initialize_symbol_reading() should be called first.		  |
 | -- next_symbol() produces the next symbol.				  |
 | -- skip_line_as_comment() skips everything till the end of line.	  |
 | -- skip_soft_delimiters() reads until the SYMBOL differs from	  |
 |       SOFT_DELIMITER.						  |
 *------------------------------------------------------------------------*/

int LAST_TOKEN='\n';                    /* last token dealt with */
MACRO *last_keyword;                    /* parsed keyword */

#define initialize_symbol_reading()	{LAST_TOKEN='\n';}

int check_token(t) int t;		/* checks the type of the next token */
{
    t &= 0xFF;
    if(t<=' ' || is_par(t)) return(0);	/* word boundary */
    switch(t){
case '{': case '}': case '%': case E_EOF: case E_SPACE:
case '$':  return(0);	 		/* word boundary */
case '"':  return(quote_delim_mode() ? 0 : 1);
					/* " in quote mode */
case '\\': if(in_def_mode() && spy_string_ahead("\\\n",FOLLOW_NOTHING))
		return(0);		/* word boundary */
		return(2);		/* check for keywords */
default:   return(1);			/* word constituent */
    }
}

void read_word()			/* reads the next word */
{unsigned t; MACRO *k;
        SYMBOL=WORD;
        while(1){switch(check_token(spy_token_ahead())){
    case 0: /* word boundary, do not advance */
            close_word_store(); S_aux2=look_up_word(); 
            return;
    case 2: /* backslash */
            k=check_backslash_keyword(0);
            if(k!=NULL){ /* a keyword parsed successfully after a WORD */
                last_keyword=k; LAST_TOKEN=E_KEYWORD;
                close_word_store(); S_aux2=look_up_word(); 
                return;
            }
    case 1: /* word constituent */
            t=get_next_token();
            store_token(t); store_word_token(t); break;
        }}
}

void next_symbol()              /* produces the next SYMBOL */
{int t,lt,len,i; MACRO *k;
    LAST_OUT=CURRENT_OUT;		/* where SYMBOL output starts */
    if(in_comment_mode()){		/* read until the end of line */
	while((t=get_next_token())!='\n' && t!=E_EOF) store_token(t);
	input_line_number++;
	if(t==E_EOF) t='\n';
	LAST_TOKEN=t; store_token(t);
	SYMBOL=COMMENT; return;
    }
try_again:				/* after \newline in def mode */
    t=get_next_token(); lt=LAST_TOKEN; LAST_TOKEN=t;
    store_token(t);
    clear_word_store(); store_word_token(t);
    switch(t){
case E_EOF: LAST_TOKEN='\n'; SYMBOL=ENDFILE; return;
case '{': SYMBOL=OPEN; return;
case '}': SYMBOL=CLOSE; return;
case '%': if(in_def_mode()) {SYMBOL=MACRO_DELIM; return;}
	SYMBOL=COMMENT;
        if(lt=='\n' && in_preserve_mode()==0){   /* check for %keywords */
	    len=0;
	    if(spy_string_ahead(T_DEFINE,FOLLOW_SPACE)){
		len=T_DEFINE_LEN; SYMBOL=DEF_KEYWORD;
	    } else if(spy_string_ahead(T_MDEFINE,FOLLOW_SPACE)){
		len=T_MDEFINE_LEN; SYMBOL=MDEF_KEYWORD;
	    } else if(spy_string_ahead(T_UNDEFINE,FOLLOW_SPACE)){
		len=T_UNDEFINE_LEN; SYMBOL=UNDEF_KEYWORD;
	    } else if(spy_string_ahead(T_MATHMODE,FOLLOW_SPACE)){
		len=T_MATHMODE_LEN; SYMBOL=MATH_KEYWORD;
            } else if(spy_string_ahead(T_DISPMODE,FOLLOW_SPACE)){
		len=T_DISPMODE_LEN; SYMBOL=DISP_KEYWORD;
	    } else if(spy_string_ahead(T_INDENT,FOLLOW_SPACE)){
		len=T_INDENT_LEN; SYMBOL=INDENT_KEYWORD;
            } else if(spy_string_ahead(T_PRESPAR,FOLLOW_SPACE)){
                len=T_PRESPAR_LEN; SYMBOL=PRESPAR_KEYWORD;
            } else if(spy_string_ahead(T_PLAINPAR,FOLLOW_SPACE)){
                len=T_PLAINPAR_LEN; SYMBOL=PLAINPAR_KEYWORD;
            } else if(spy_string_ahead(T_MATHPAR,FOLLOW_SPACE)){
                len=T_MATHPAR_LEN; SYMBOL=MATHPAR_KEYWORD;
            }
	    if(len>0) skip_tokens(len);
	}
	return;
case ' ': case '\t': S_aux1=0; SYMBOL=SOFT_DELIMITER; return;
	/* S_aux1==0 says that the delimiter vanishes at substitution */
case '\n': input_line_number++;
	S_aux1=1; SYMBOL= lt=='\n' ? EMPTY_LINE : 
			 in_def_mode() ? HARD_DELIMITER : SOFT_DELIMITER; 
	return;
case E_SPACE: S_aux1=1; SYMBOL=SOFT_DELIMITER; return;
case '"':
	if(in_preserve_mode()){ read_word(); return;}
	if(in_quote_mode()){ SYMBOL=QUOTE_OUT; return;}
	if(in_plain_mode()){ read_word(); return;}
	SYMBOL=QUOTE_IN; return;
case '$':
	if(in_math_mode() && mode_style==SIMPLE_STYLE) /* single $ */
	    SYMBOL=MATH_OUT; 
	else if(spy_token_ahead()=='$'){ /* double $$ */
	    skip_tokens(1); store_token('$');
	    SYMBOL=in_disp_mode() && mode_style==SIMPLE_STYLE ? 
			DISP_OUT : DISP_IN;
	} else SYMBOL=MATH_IN;
	return;
case '\\': /* E_KEYWORD means a \keyword was succesfully parsed */
	k=lt==E_KEYWORD ? last_keyword : check_backslash_keyword(1);
	if(k!=NULL){			/* LAST_TOKEN=='\\' */
	    len=word_length_k(k)-1;	/* number of tokens in k */
	    for(i=0;i<len;i++){
		t=get_next_token(); store_token(t); store_word_token(t);
	    }
	    if(is_preserve_k(k)){
		SYMBOL=PRESERVE;
		return;
	    }
	    close_word_store(); S_aux2=k; SYMBOL=WORD; return;
	}
	if(in_def_mode() && spy_token_ahead()=='\n'){
	    set_out_position(LAST_OUT);	/* do not store backslash */
	    skip_tokens(1);		/* skip over newline */
	    input_line_number++;
	    goto try_again;
	}
	read_word(); return;
default:
	if(is_par(t)){ S_aux1=extract_par(t); SYMBOL=PARAMETER; return;}
	if(t<' '){ SYMBOL=DELIMITER; return;}
	/* now t is an inner character of a word (maybe `\') */
        read_word(); return;
    }
}

void skip_line_as_comment() 
/* If not at the end of the line, skip the rest of the line. */
{
    if(LAST_TOKEN=='\n'){		/* we've hit the end of line */
	store_token('\n');
	return;
    }
    global_mode |= COMMENT_MODE; next_symbol(); global_mode &= ~COMMENT_MODE;
}

void skip_soft_delimiters()
/* go ahead until a not SOFT_DELIMITER is found */
{ while(SYMBOL==SOFT_DELIMITER) next_symbol(); }

/*=========================================================================*/
/*			      Parameter stack				   */
/*=========================================================================*/

/*-------------------------------------------------------------------------*
 | The potential actual parameters of macro calls are stored in a stack.   |
 | The depth of the stack, however, is bounded, and information at the	   |
 | bottom of the stack loses as the stack grows. Each OPEN symbol opens a  |
 | new range, thus the stack shrinks to that point only. Each entry in the |
 | stack has three fields:						   |
 | o  replace: the position from where the text should be erased (white	   |
 |         space before the parameter)					   |
 | o  start: output position where the parameter text starts		   |
 | o  end: output position until the parameter text starts.		   |
 | Procedures handling the parameter stack:				   |
 | -- push_par(replace,start,end) puts an entry into the stack.		   |
 | -- pop_par(replace,start,end) pops the uppermost entry in the stack.	   |
 | -- open_range() opens a new range to prevent the stack shrink below.    |
 | -- close_range() closes the range, shrinks the stack until the 	   |
 |        corresponding open_range.					   |
 | -- shrink_par_stack() shrinks the stack until the last range boundary.  |
 *-------------------------------------------------------------------------*/

#define STACK_DEPTH	256
typedef struct { unsigned replace,start,end;} STACK;

STACK *par_stack;
unsigned stack_pointer=0, stack_bottom=0, border_pointer=0;

int alloc_params()
{
    par_stack=calloc(STACK_DEPTH,sizeof(STACK));
    return(par_stack==NULL);
}

#define	stack_depth()	((stack_pointer-border_pointer)%STACK_DEPTH)

void push_par(replace,start,end) unsigned replace,start,end;
/* pushes the next entry into the stack */
{
    par_stack[stack_pointer].replace=replace;
    par_stack[stack_pointer].start=start;
    par_stack[stack_pointer].end=end;
    stack_pointer++; stack_pointer%=STACK_DEPTH;
    if(stack_pointer==stack_bottom) {
	stack_bottom++; stack_bottom%=STACK_DEPTH;
	if(stack_pointer==border_pointer) border_pointer=stack_bottom;
    }
}

int pop_par(replace,start,end) unsigned *replace,*start, *end;
/* pops the next element from the stack */
{
    if(stack_pointer==border_pointer) return(1);
    stack_pointer--; stack_pointer%=STACK_DEPTH;
    *replace=par_stack[stack_pointer].replace;
    *start=par_stack[stack_pointer].start;
    *end=par_stack[stack_pointer].end;
    return(0);
}

void open_range()	/* opens a new range */
{
    push_par(border_pointer,0,0); border_pointer=stack_pointer;
}

void close_range()	/* closes a range if possible */
{unsigned dummy;
    stack_pointer=border_pointer;
    if(border_pointer==stack_bottom) return;
    border_pointer++;	/* fool `pop_par' */
    pop_par(&border_pointer,&dummy,&dummy);
/*** ver 2.17 ***/
    if((stack_bottom <= stack_pointer) ? 
	! (stack_bottom <= border_pointer && border_pointer <= stack_pointer)
	: (stack_pointer < border_pointer && border_pointer < stack_bottom))
	border_pointer=stack_bottom;
}

#define shrink_par_stack()	{stack_pointer=border_pointer;}

/*=========================================================================*/
/*                       Parameter substitution                            */
/*=========================================================================*/

/*-------------------------------------------------------------------------*
 | Parameter substitution is performed here. Descriptions of parameters    |
 | are popped out of the stack, retrieved from the output buffer into	   |
 | allocated memory; the output buffer is rewind until the `from' position |
 | and then the macro body is written into output while replacing the      |
 | formal parameters. At the end the allocated memory is freed.		   |
 *-------------------------------------------------------------------------*/
unsigned start[10], pend[10];	/* parameter places */
byte *parameters[10];		/* the parameters themselves */

int macro_substitution(from,k) unsigned *from; MACRO *k;
/* performs the given substitution */
{unsigned replace; unsigned len; int i,par_no; char *memory;
 byte *p,*body,t;
    par_no=left_pars_k(k)+right_pars_k(k);
    len=0; memory=NULL; replace = *from;
    for(i=par_no-1;i>=0;i--){
	if(pop_par(&replace,&start[i],&pend[i])) return(0);
	len += pend[i]-start[i]+1;
    }
    if(*from < replace) replace = *from;	/* place to replace from */
    *from=replace;
    if(len>0){
	memory=malloc(len);
	p=(byte *)memory;
	if(p==NULL){
	    error(OUT_OF_MEMORY);
	    return(0);
	}
	for(i=0;i<par_no;i++){
	    parameters[i]=p;
	    if(retrieve_out(start[i],pend[i],p)){ /* parameter lost */
		error(TOO_LONG_PARAMETER,k->name);
		free(memory); return(0);
	    }
	    p+=pend[i]-start[i]+1;
	}
    } else
	memory=NULL;
    if(set_out_position(replace)){
	error(TOO_LONG_PARAMETER,k->name);
	if(memory!=NULL) free(memory); return(0);
    }
    body=k->body;
    while((t = *body++)!=0){
	if(is_par(t)){
	    p=parameters[extract_par(t)];
	    while((t = *p++)!=0) store_token((int)t);
	} else store_token((int)t);
    }
    free(memory);
    return(1);
}

/*=========================================================================*/
/*                           Macro definition	                           */
/*=========================================================================*/

/*-------------------------------------------------------------------------*
 | This part deals with macro and keyword definitions. All of them vanish  |
 | from the output text, and are replaced by TeXpp_MACRO_DEFINITION. The   |
 | macro text is expanded in the output buffer, and copied into the memory |
 | later. Calling `translate_parameters()' changes all references to the   |
 | formal parameters from their face value into their position.            |
 | -- init_macro_definition() saves the old mode, the actual output posi-  |
 |        tion, and changes into DEFINE_MODE.				   |
 | -- close_macro_definition() restores the original mode rewinds the	   |
 |        output, and inserts the appropriate text.			   |
 | -- read_macro_definition(type) handles the macro definition. The `type' |
 |        tells whether the definition was %mdefine (=1) or not (=0).	   |
 | -- undefine_macro() handles the case %undefine. The macro is unliked    |
 |        both from the hash table and the keyword list.		   |
 | -- define_keyword(type) deals with the %mathmode and %dispmode keywords |
 *-------------------------------------------------------------------------*/

int old_mode, old_style;
unsigned save_out_position, start_macro_text;
int params[9];

void translate_parameters(body) byte *body;
/* replaces parameter #i by its absolute position */
{byte p;
    while((p = *body)!=0){
	if(is_par(p)) *body=make_par(params[extract_par(p)]+'0');
	body++;
    }
}

void init_macro_definition()
{int i;
    old_mode=global_mode; old_style=mode_style;
    global_mode=DEFINE_MODE;		/* save old mode, switch to define */
    save_out_position=CURRENT_OUT;	/* only a single % has been stored */
    shrink_par_stack();			/* no previous parameter */
    flush_output();			/* no backtrack beyond this point */
    for(i=0;i<9;i++)params[i]=0;	/* no parameters defined */
}

void close_macro_definition()
{
    set_out_position(save_out_position);/* cancel garbage */
/***	store_string(TeXpp_MACRO_DEFINITION); ***/
					/* do not appear in the output */
    skip_line_as_comment();		/* do not deal with the rest */
    global_mode=old_mode; mode_style=old_style;
}

void read_macro_definition(type) int type;
/* reads a macro definition -- issues appropriate error messages */
{int result; MACRO *k; int left_params,all_params;
    init_macro_definition();
    left_params=0;
next_left_param:			/* read leftist parameters */
    next_symbol(); skip_soft_delimiters();
    if(SYMBOL==PARAMETER){
	if(params[S_aux1]!=0){		/* declared twice */
	    error(PARAMETER_TWICE,S_aux1+1);
	    close_macro_definition(); return;
	}
	params[S_aux1]= ++left_params; 
	goto next_left_param;
    }
    if(SYMBOL!=WORD){
	error(WRONG_MACRO_NAME); close_macro_definition(); return;
    }
    k=prepare_new_macro_entry();	/* if NULL, then no memory */
    if(k==NULL){ close_macro_definition(); return; }
    all_params=left_params;
next_right_param:			/* read rightist parameters */
    next_symbol(); skip_soft_delimiters();
    switch(SYMBOL){
case PARAMETER:
	if(params[S_aux1]!=0){		 /* declared twice */
	    error(PARAMETER_TWICE,S_aux1+1);
	    close_macro_definition(); return;
	}
	params[S_aux1]= ++all_params;
	goto next_right_param;
case OPEN:
	if(left_params!=0 || all_params!=1){
	    error(MISSING_DELIMITER); close_macro_definition(); return;
        }
	type|=6;
	goto next_right_param;
case CLOSE:
	if((type&6)!=6 || all_params!=2){
	    error(MISSING_DELIMITER); close_macro_definition(); return;
        }
	type &= ~4;
	goto next_right_param;
case MACRO_DELIM:
	if((type&4)!=0){
	    error(MISSING_DELIMITER); close_macro_definition(); return;
	}
	break;
default:
	error(MISSING_DELIMITER); close_macro_definition(); return;
    }
    start_macro_text=CURRENT_OUT;
    if((type&1)!=0){			/* %mdefine */
	global_mode |= MATH_MODE; mode_style=DEFINE_STYLE;
    }
    do{ next_symbol();} while((result=deal_range())==X_CLOSE);
    if(result==X_ERROR){
	close_macro_definition(); return;
    }
    if(SYMBOL!=MACRO_DELIM) error(MISSING_DELIMITER);
    if(set_macro_structure( k,type,left_params,all_params-left_params,
	    LAST_OUT-start_macro_text+1)){		/* no more memory */
	close_macro_definition(); return;
    }
    if(retrieve_out(start_macro_text,LAST_OUT,k->body)){
	error(TOO_LONG_MACRO_DEF,k->name);
	close_macro_definition(); return;
    }
    translate_parameters(k->body);
    insert_macro();
    insert_backslash_keyword(k); /* if starts with backslash */
    close_macro_definition();
}

void undefine_macro()	/* %undefine <macro_name> */
{
    init_macro_definition();
    next_symbol(); skip_soft_delimiters();
    if(SYMBOL==WORD && S_aux2!=NULL){		/* delete it */
	remove_macro();
    } else error(WRONG_MACRO_NAME);
    close_macro_definition();
}

void define_mode_keyword(type) int type;	/* %mathmode or %dispmode */
{MACRO *k1,*k2;
    init_macro_definition();
    next_symbol(); skip_soft_delimiters();	/* to mode keyword */
    if(SYMBOL!=WORD){
	error(WRONG_MODE_SWITCH_DEF,
	    type==INDENT_KEYWORD ? T_INDENT :
	    type==MATH_KEYWORD ? T_MATHMODE : T_DISPMODE);
	close_macro_definition();
	return;
    }
    k1=retrieve_macro_entry();
    next_symbol(); skip_soft_delimiters();
    switch(SYMBOL){				/* from mode keyword */
case MACRO_DELIM: case HARD_DELIMITER:
	set_modeswitch(k1,type,1,0);		/* single keyword */
	break;
case WORD:
	 k2= k1==S_aux2 ? NULL : retrieve_macro_entry();
	next_symbol(); skip_soft_delimiters();
	if(SYMBOL==MACRO_DELIM || SYMBOL==HARD_DELIMITER){
	    set_modeswitch(k1,type,k2==NULL,0);	/* single keyword ? */
	    set_modeswitch(k2,type,0,1);
	    break;
	}
default:
	error(WRONG_MODE_SWITCH_DEF,
	    type==INDENT_KEYWORD ? T_INDENT :
	    type==MATH_KEYWORD ? T_MATHMODE : T_DISPMODE); break;
    }
    close_macro_definition();
}

void define_parameter_keyword(type) int type; /* %???par keyword */
{MACRO *k;
    init_macro_definition();
    next_symbol(); skip_soft_delimiters();      /*get macro name*/
    k=S_aux2;
/****** ver. 2.16
    if(SYMBOL!=WORD || k==NULL || body_k(k)==NULL || left_pars_k(k)!=0 ||
**********/
    if(SYMBOL!=WORD || k==NULL || body_k(k)==NULL ||
            right_pars_k(k)==0 || (is_math_macro(k) && type==P_MATH)){
        error(WRONG_PARAMETER_MODIFIER,type==P_MATH ? T_MATHPAR :
                         type==P_PRESERVE ? T_PRESPAR : T_PLAINPAR);
        close_macro_definition();
        return;
    }
    set_partype(k,type);
    close_macro_definition();
}

/*=========================================================================*/
/*		      Macro and mode switch handling			   */
/*=========================================================================*/

/*-------------------------------------------------------------------------*
 | -- deal_range() reads things between {...} and returns X_CLOSE, X_ERROR |
 |	or X_OTHER, skipping unbalanced mode switches. At the end does     |
 |	not advances.							   |
 | -- set_mode(k) switches into mode as given by struct MACRO parameter k. |
 |	Returns !=0 if the switch is unbalanced.			   |
 | -- store_mode_block() stores a block enclosed by mode switches. This    |
 |	behaves as a single (unbreakable) parameter.			   |
 | -- deal_mode_switch(k) decides whether `k' is a also a mode switch. If  |
 |	not, then pushes it as a paramter. If yes, skips until the closing |
 |	switch.								   |
 | -- deal_word() checks whether the last word is a macro name, or is a    |
 |	mode switch. Performs the appropriate actions in each case.	   |
 *-------------------------------------------------------------------------*/
 
int deal_range()
/* Reads things between {...} and returns X_CLOSE, X_ERROR or X_OTHER, 
   skipping unbalanced mode switches. At the end does not advance. */
{int result;
    while(1){
	while((result=store_parameter(0,1,P_NONE))==X_PARAMETER ||
		result==X_CAPSULE);
	if(result==X_ERROR) return(X_ERROR);
	if(result==X_OTHER){ switch(SYMBOL){
case CLOSE:	return(X_CLOSE);
case DELIMITER:	break;			/* allowed withing braces */
default:	return(X_OTHER);
	}}
	shrink_par_stack(); next_symbol();
    }
    return(X_ERROR);
}

int set_mode(k) MACRO *k;
/* Switches into math or disp mode. Returns !=0 if the switch is wrong. */
{
    if(is_standalone_k(k) || is_in_k(k)){
	clear_mode();
	global_mode |= is_math_k(k) ? MATH_MODE : DISP_MODE;
	mode_style=style_k(k);
	return(0);
    }
    return(1);
}

int store_mode_block(replace,from,mode_out) 
	unsigned replace,from; int mode_out;
/* advances and stores a mode block closed by `mode_out' */
{int result;
    open_range(); next_symbol();
    while((result=store_parameter(0,1,P_NONE))==X_PARAMETER ||
	    result==X_CAPSULE);
    close_range();
    if(result!=mode_out) return(result);
    push_par(replace,from,CURRENT_OUT);
    return(X_PARAMETER);
}

int deal_mode_switch(k,replace,from) 
          MACRO *k; unsigned replace,from;
/* checks whether the last word is also a mode switch */
{ int old_mode,old_style; int res;
    if(k==NULL){
	push_par(replace,from,CURRENT_OUT);
        return(X_PARAMETER);
    }
    if(is_indent_on_k(k)){
	if(is_indent() && !is_indent_off_k(k))
	    error(WRONG_MODE_SWITCH,k->name);
	set_indent();
    } else if(is_indent_off_k(k)){
	if(!is_indent())
	    error(WRONG_MODE_SWITCH,k->name);
	clear_indent();
    }
    if(!is_modeswitch_k(k)){			/* not a mode switch */
	push_par(replace,from,CURRENT_OUT);
	return(X_PARAMETER);
    }
    if(in_plain_mode()){			/* switch to mode */
	old_mode=global_mode; old_style=mode_style;
	if(set_mode(k)){			/* wrong switch */
	    error(WRONG_MODE_SWITCH,k->name);
	    return(X_XMODE_OUT);
	}
	res=store_mode_block(replace,from,X_XMODE_OUT);
	if(res==X_PARAMETER || res==X_CAPSULE){
	    global_mode=old_mode; mode_style=old_style;
	}
	return(res);
    }
    if(mode_style!=style_k(k) || (!is_standalone_k(k) && is_in_k(k))){
	error(WRONG_MODE_SWITCH,k->name);
	clear_mode(); set_mode(k);
    } else clear_mode();
    return(X_XMODE_OUT);
}

int read_params(caps,right_pars, par_type) int caps,right_pars,par_type;
{int i,result; unsigned replace,start,end,dummy;
    if(right_pars>0) next_symbol();
    if(caps!=0){			/* read until a { */
	result=store_parameter(1,1,par_type);
        if(result==X_PARAMETER){
again:      result=store_parameter(1,1,par_type);
            pop_par(&dummy,&dummy,&end);
            if(result==X_PARAMETER){
                pop_par(&replace,&start,&dummy);
		push_par(replace,start,end);
		goto again;
	    }
        }
	if(result!=X_OPEN){
	    return result;			/* some error */
	}
        result=store_parameter(0,0,par_type);   /* 2nd parameter */
        return result==X_CAPSULE ? X_PARAMETER : result;
    }
    result=X_PARAMETER;
    for(i=1; (result==X_PARAMETER || result==X_CAPSULE) && i<=right_pars;i++)
		result=store_parameter(0,i<right_pars,par_type);
    return result==X_CAPSULE ? X_PARAMETER : result;
}

int deal_word(replace_from,advance) unsigned replace_from; int advance;
/* Checks whether the word is a macro name. Also checks for mode switch. */
{MACRO *k; int replaced,result,par_type;
 int old_mode,old_style;
    k=S_aux2;
    if(k==NULL || body_k(k)==NULL || (is_math_macro(k) && in_plain_mode())){
	replaced=0;
	result=deal_mode_switch(k,replace_from,LAST_OUT);
    } else {					/* macro name */
	if(stack_depth() < left_pars_k(k)) {
	    error(TOO_LESS_LEFT_PARAMS,left_pars_k(k),k->name);
	    return(X_ERROR);
	}
	par_type=par_type_k(k);
        if(par_type!=P_NONE){
            old_mode=global_mode; old_style=mode_style;
	    clear_mode();
            if(par_type==P_MATH){ set_math_mode(); mode_style=PAR_STYLE;}
        }
	result=read_params(is_capsule_k(k),right_pars_k(k),par_type);
	if(result!=X_PARAMETER){
	    error(TOO_LESS_PARAMS,left_pars_k(k)+right_pars_k(k),k->name);
	    return(X_ERROR);
	}
        if(par_type!=P_NONE){
            if(in_math_mode()!=(par_type==P_MATH && mode_style==PAR_STYLE) ||
                in_disp_mode()) error(UNBALANCED_PARAMETERS);
            global_mode=old_mode; mode_style=old_style;
        }
	replaced=macro_substitution(&replace_from,k);
	result=deal_mode_switch(k,replace_from,replace_from);
    }
    if((result==X_PARAMETER || result==X_CAPSULE) && advance){
	replaced &= SYMBOL!=CLOSE;	/***** ?????? ******/
	next_symbol();			/* skip whitespace after a WORD */
	if(replaced && SYMBOL==SOFT_DELIMITER && S_aux1==0){
	    set_out_position(LAST_OUT); next_symbol();
	}
    }
    return(result);
}

/*=========================================================================*/
/*			   Reading parameters				   */
/*=========================================================================*/

/*-------------------------------------------------------------------------*
 | -- skip_balanced_expression() used skipping a {...} parameter for the   |
 |	keyword \preserve.						   |
 | -- skip_word() skips until the next SOFT_DELIMITER after \preserve.	   |
 | -- store_parameter(open,advance,type) stores and handles the next	   |
 |	SYMBOL. If `advance' is TRUE (!=0) then reads ahead one more       |
 |	SYMBOL. 							   |
 *-------------------------------------------------------------------------*/
 
void skip_balanced_expression()	/* skip until an unbalanced CLOSE */
{int level=0;
    set_preserve_mode();
    while(1){
	next_symbol(); switch(SYMBOL){
case HARD_DELIMITER: case EMPTY_LINE: case ENDFILE:
            clear_preserve_mode(); return;
case OPEN:  level++; break;
case CLOSE: level--; if(level<0) {clear_preserve_mode(); return;}
default:    break;
	}
    }
}

void skip_word()		/* skips a word after \preserve */
{
    while(1){ switch(SYMBOL){
case SOFT_DELIMITER: case HARD_DELIMITER:
case EMPTY_LINE: case ENDFILE: case OPEN:	/* maybe OPEN not needed ??? */
	return;
default:
	next_symbol(); break;
    }}
}

int store_parameter(noopen,advance,type) int noopen,advance,type;
/* Stores a single parameter. If returns !=0 or advance==0 then does not 
   advances */
{unsigned replace_from,start; int result;
 int old_mode,old_style;
    /* skip whitespaces */
    replace_from=LAST_OUT;
    while(SYMBOL==SOFT_DELIMITER){
	if(S_aux1==0){ next_symbol(); } /* delimiter vanishes substitution */
	else {next_symbol(); replace_from=LAST_OUT;}
    }
    switch(SYMBOL){
case WORD:
	if(S_aux2!=NULL && is_backslash_k(S_aux2)){ /* starts with backslash */
	    replace_from=LAST_OUT;
	}
        if(type==P_PRESERVE){
            push_par(replace_from,LAST_OUT,CURRENT_OUT);
            return(X_PARAMETER);
	}
	return(deal_word(replace_from,advance));
case PARAMETER:	/* formal parameter in macro text */
	if(params[S_aux1]==0){
	    error(UNDEFINED_PARAMETER,1+S_aux1);
	    return(X_ERROR);
	}
	push_par(replace_from,LAST_OUT,CURRENT_OUT);
	if(advance) next_symbol();
	return(X_PARAMETER);
case PRESERVE:	/* \preserve keyword */
	start=LAST_OUT;
	do{ set_out_position(LAST_OUT); next_symbol();}
	    while(SYMBOL==SOFT_DELIMITER);	/* skip soft delimiters */
	if(SYMBOL==OPEN){	/* skip until the corresponding CLOSE */
	    set_out_position(LAST_OUT);		/* do not copy OPEN */
            skip_balanced_expression();
        } else skip_word();
	set_out_position(LAST_OUT);		/* do not copy CLOSE */
	if(advance) next_symbol();
	push_par(replace_from,start,LAST_OUT);
	return(X_PARAMETER);
case QUOTE_IN:
	start=LAST_OUT;
	set_out_position(LAST_OUT);		/* do not copy open quote */
	store_string(OPEN_QUOTE_STRING);
	old_mode=global_mode; old_style=mode_style; /* save mode type */
	clear_mode(); set_quote_mode();
	result=store_mode_block(replace_from,start,X_QUOTE_OUT);
	if(result==X_PARAMETER || result==X_CAPSULE){
	    global_mode=old_mode; mode_style=old_style;
	    if(advance) next_symbol();
        }
        return(result);
case QUOTE_OUT:
	set_out_position(LAST_OUT);		/* do not copy closing quote */
	store_string(CLOSE_QUOTE_STRING);
	return(X_QUOTE_OUT);
case MATH_IN: case DISP_IN:
	if(!in_plain_mode()){
	    error(WRONG_DOLLAR_SWITCH);
	    clear_mode();
	}
	old_mode=global_mode; old_style=mode_style; /* save mode type */
	clear_mode();
	global_mode|= SYMBOL==MATH_IN ? MATH_MODE : DISP_MODE;
	mode_style=SIMPLE_STYLE;
        result=store_mode_block(replace_from,LAST_OUT,X_DMODE_OUT);
	if(result==X_PARAMETER || result==X_CAPSULE){
	    global_mode=old_mode; mode_style=old_style;
	    if(advance) next_symbol();
        }
	return(result);
case MATH_OUT:					/* do not advance! */
	if(!in_math_mode() || mode_style!=SIMPLE_STYLE){
	    error(WRONG_CLOSING_DOLLAR);
	}
	clear_mode();
	return(X_DMODE_OUT);
case DISP_OUT:					/* do not advance! */
	if(!in_disp_mode() || mode_style!=SIMPLE_STYLE){
	    error(WRONG_CLOSING_DOLLAR);
	}
	clear_mode();
	return(X_DMODE_OUT);
case OPEN:
	if(noopen!=0){
	    push_par(LAST_OUT,CURRENT_OUT,CURRENT_OUT);
	    return(X_OPEN);
	}
	replace_from=LAST_OUT; start=CURRENT_OUT;
        if(type==P_PRESERVE){
            skip_balanced_expression();
        } else {
            open_range();
            next_symbol();                          /* advance */
            result=deal_range();
            close_range();
            if(result!=X_CLOSE) return(result);
        }
	push_par(replace_from,start,LAST_OUT);
	if(advance) next_symbol();		/* what comes after CLOSE */
	return(X_CAPSULE);
default:					/* do not advance! */
	return(X_OTHER);
    }
}

/*=========================================================================*/
/*				Main cycle				   */
/*=========================================================================*/

/*-------------------------------------------------------------------------*
 | read_file() is the main cycle of the program. It is called with all	   |
 | input files. The procedure reads in a cycle until the end of the file,  |
 | flushing all the output and shrinking the parameter stack in each	   |
 | iteration. Macro parameters cannot go over these constructs, e.g. empty |
 | line, comment, TeX commands, etc.					   |
 *-------------------------------------------------------------------------*/
void read_file()			/* goes through a file */
{int result;
    initialize_char_reading();
    initialize_token_reading();
    initialize_symbol_reading();
again:
    shrink_par_stack();			/* no previous parameter */
    flush_output();			/* no backtrack beyond this point */
    next_symbol();			/* first symbol to read */
    while((result=store_parameter(0,1,P_NONE))==X_PARAMETER ||
	    result==X_CAPSULE);
    					/* read until can */
    if(result!=X_OTHER) goto again;
    shrink_par_stack();			/* no parameters to deal with */
    switch(SYMBOL){			/* what caused the trouble */
case EMPTY_LINE:
	if(!in_plain_mode()){		/* check math and disp mode */
	    error(EMPTY_LINE_IN_MODE,in_math_mode() ? "math" : "display");
	    clear_mode();
	} else if(in_quote_mode()){
	    error(MISSING_CLOSING_QUOTE);
	    clear_mode();
	}
	goto again;
case ENDFILE:				/* end of everything */
	if(!in_plain_mode()){
	    error(ENDFILE_IN_MODE,in_math_mode() ? "math":"display");
	    clear_mode();
	} else if(in_quote_mode()){
	    error(MISSING_CLOSING_QUOTE);
	    clear_mode();
	}
	break;
case COMMENT:				/* a % sign somewhere */
	skip_line_as_comment();
	goto again;
case DELIMITER:				/* control character */
case CLOSE:				/* unmatched closing bracket */
	goto again;
case DEF_KEYWORD:			/* %define */
	read_macro_definition(0);
	goto again;
case MDEF_KEYWORD:			/* %mdefine */
	read_macro_definition(1);
	goto again;
case UNDEF_KEYWORD:			/* %undefine <name> */
	undefine_macro();
	goto again;
case MATH_KEYWORD:			/* %matmode <in> <out> */
case DISP_KEYWORD:			/* %dispmode <in> <out> */
case INDENT_KEYWORD:			/* %indentspaces <in> <out> */
	define_mode_keyword(SYMBOL);
	goto again;
case PLAINPAR_KEYWORD:                  /* %plainpar <name> */
        define_parameter_keyword(P_PLAIN);
        goto again;
case PRESPAR_KEYWORD:                   /* %preservepar <name> */
        define_parameter_keyword(P_PRESERVE);
        goto again;
case MATHPAR_KEYWORD:                   /* %mathpar <name> */
        define_parameter_keyword(P_MATH);
        goto again;
/*** case MATH_IN: case MATH_OUT: case DISP_IN: case DISP_OUT: case PRESERVE:
     case HARD_DELIMITER: case OPEN: case WORD: case MACRO_DELIM:
     case PARAMETER: case SOFT_DELIMITER: ***/
default:	/* something which should not occur */
        fprintf(stderr,"Unreachable symbol: %d\n",SYMBOL); break;
    }
}

/*=========================================================================*/
/*                        Command line arguments                           */
/*=========================================================================*/

/*-------------------------------------------------------------------------*
 | Arguments in the command line are the files which are to be processed.  |
 | Argument STDIN_ARG means the standard input; a file name preceeded by   |
 | WRITE_ARG or APPEND_ARG is considered the output file. If no output	   |
 | file is present then the output goes to the standard output. In this    |
 | latter case error messages are not repeated at stderr.		   |
 *-------------------------------------------------------------------------*/
#define NUL_ARG 	"-"
#define STDOUT_ARG	"*"
#define WRITE_PAR       'w'
#define APPEND_PAR      'a'
#define DEFAULT_OUT     "texpp.tex"
#define STDIN_ARG	"-"
#define HELP_PAR        'h'
#define MACRO_PAR	'm'
#define SILENT_PAR      's'
#define TRANSLATE_PAR   't'
#define TEXPP_ENV	"texpp="
#define TEXPP_ENV_LEN	6


int find_environment_args(char *envp[], char ***envv_address)
{int envc,envlen; char ce, *env,*e; char **envv;
    *envv_address=NULL;
    while(*envp && strnicmp(*envp,TEXPP_ENV,TEXPP_ENV_LEN)!=0) envp++;
    if(*envp == NULL){			    /* no environment found */
	*envv_address=NULL; return(0);
    }
    env = (*envp)+TEXPP_ENV_LEN; envlen=TEXPP_ENV_LEN+1;
    while((ce=*env)==' ' || ce=='\t') env++;
    for(envc=1; ce!=0; envc++) {
	while(ce!=0 && ce!=' ' && ce!='\t'){envlen++; env++; ce=*env; }
	while(ce==' ' || ce=='\t') {env++; ce=*env;}
	envlen++;
    }
    e=malloc(envlen);
    envv=malloc(envc*sizeof(char*));
    if(envv==NULL || e==NULL) return(-1);
    *envv_address=envv;
    envv[0]=e; env=*envp; movmem(env,e,TEXPP_ENV_LEN);
    e+=TEXPP_ENV_LEN; env += TEXPP_ENV_LEN;
    while((ce=*env)==' ' || *env=='\t') env++;
    *e++='\0';
    for(envc=1; ce!=0; envc++) {
	envv[envc]=e;
	while(ce!=0 && ce!=' ' && ce!='\t'){*e++=ce; env++; ce=*env;}
	while(ce==' ' || ce=='\t'){env++; ce=*env;}
	*e++='\0';
    }
    return(envc);
}

int is_param(c,q) char *c; char q;    /* check if c is /c or -c */
{
    if(*c!='/' && *c!='-') return(0);
    c++; 
    return( *c==q ? 1 : ('a'<=q && q <='z') ? *c==q-'a'+'A' : 0);
}

void find_output(argc,argv) int argc; char *argv[];
/* searches the argument list for output file */
{int i,found; char *name;
    found=0;
    for(i=1;i<argc;i++){
        found = is_param(argv[i],WRITE_PAR) ? 1 :
                is_param(argv[i],APPEND_PAR) ? 2 : 0;
        if(found!=0) break;
    }
    if(found==0) return;
    name=argv[i]+2;
    if(*name==0){
	if(i+1==argc){
	    fprintf(stderr,"texpp: output filename is missing, using %s\n",
	                     DEFAULT_OUT);
	    output_file_name=DEFAULT_OUT;
        } else output_file_name=argv[i+1];
    } else output_file_name=name;
    output_type = stricmp(name,NUL_ARG)==0 ? NUL_OUT :
		 stricmp(name,STDOUT_ARG)==0 ? STD_OUT :
		 found == 1 ? WRITE_OUT : APPEND_OUT;
}

int arguments(s,i,argc,argv) int s; int *i,argc; char *argv[];
/*-------------------------------------------------------------------------*
 | gives back the i-th argument file, or NULL if error, or no more. Skips  |
 | `-nopar' arguments, and increases i. Should be called with *i=0 fist.   |
 | Also initializes `input_file_name' and `input_line_number'.             |
 *-------------------------------------------------------------------------*/
{int firstcall; char *p;
#ifndef __TURBOC__
 char fname[1024];
#endif 
    firstcall= (*i)++ == 0;	/* this indicates the first call */
    input_line_number=1;	/* start new file */
    supress_output=0;		/* show output */
    while(*i < argc){
        p=argv[*i];
        if(is_param(p,WRITE_PAR) || is_param(p,APPEND_PAR)){
            if(p[2]==0)(*i)++;
            (*i)++;
            continue;
	} 
        if(is_param(p,MACRO_PAR)){ /* no output for this file */
            flush_output();
            supress_output=1;
            if(p[2]==0){ (*i)++; p=argv[*i]; } else p+=2;
        }
	if(stricmp(p,STDIN_ARG)==0){
            input_file_name="stdin";
            input_file=stdin;
	    if(s==0)printf("Reading file %s\n",input_file_name);
            return(1);
	} else {
            input_file_name=p;
            input_file=fopen(input_file_name,"r");
	    if(input_file!=NULL){
		if(s==0)printf("Reading file %s\n",input_file_name);
                return(1);
	    }
#ifndef __TURBOC__
	    strcpy(fname,STANDARD_DIR); strncat(fname,input_file_name,1000);
	    input_file=fopen(fname,"r");
	    if(input_file!=NULL){
	        if(s==0)printf("Reading standard file %s\n",fname);
	        return(1);
	    }
#endif
            error(CANNOT_OPEN_FILE); (*i)++;
	}
    }
    if(firstcall) *i=0;
    return(0); /* no more parameters */
}

int on_line_help(argc,argv) int argc; char *argv[];
/*-------------------------------------------------------------------------*
 | Some useless on line help                                               |
 *-------------------------------------------------------------------------*/
{int help_arg;
    help_arg = argc>1 && is_param(argv[1],'h');
    if(argc==1 || help_arg){
        fprintf(stderr,"Usage:\n");
	fprintf(stderr,
"   texpp [-s] [-t xxxx] [-m] input1 ... [-m ] inputn [-w outfile]\n"
"Options:\n"
"   -h         more help\n"
"   -s         silent mode (do not write header)\n"
"   -t xxxx    read character translation from file \"xxxx\"\n"
"   -m input   read macro definitions from \"input\"\n"
"   -w-        do not produce output, diagnose only\n\n");
        if(help_arg){
	fprintf(stderr,
"Texpp is a macro preprocessor designed specially for TeX. The files\n"
"\"input1\"...\"inputn\" are read, macro definitions are extracted, and\n"
"substituted. The result is written into \"outfile\", or stdout if not given.\n"
"Input files marked by \"-m\" are processed only and not printed.\n"
"After macro substitution characters can be replaced by other strings (e.g. á\n"
"by \\'a ). Character translations are defined in the optional file after\n"
"parameter \"-t\". Using \"-a\" in place of \"-w\" causes the result appended.\n"
"If no \"-axxxx\" or \"-wxxxx\" parameter is given, the result goes to stdout.\n"
"\"-\" as one of the input files means reading from stdin (usually keyboard).\n\n"
"   The environment variable TEXPP is checked first for arguments, but switches\n"
"given after \"texpp\" have priorities over switches in the environment variable.\n\n"
"For a detailed description see the manual \"texpp.man\".\n");
        }
        return(1);
    }
    return(0);
}

/*=========================================================================*/
/*                          Main routine                                   */
/*=========================================================================*/

int cdecl main(argc,argv,envp) int argc; char *argv[]; char *envp[];
{int input_file_no; int envc; char **envv; int silent;
    envc=find_environment_args(envp,&envv);
    if(envc<0){
	fprintf(stderr,"Not enough memory to run texpp\n");
        return(1);
    }
    silent=0;
    if(argc>1 && is_param(argv[1],SILENT_PAR)){
	argc--; argv++; silent=1;
    }
    if(envc>1 && is_param(envv[1],SILENT_PAR)){
	envc--; envv++; silent=1;
    }
    if(silent==0)
	printf("TEXPP V"
	VERSION
	". -- TeX macro preprocessor, L.Csirmaz(1991)\n\n");
    if(on_line_help(argc,argv)) return(0);
    if(alloc_outbuffers() || alloc_macro() || alloc_word() || alloc_params()){
	fprintf(stderr,"Not enough memory to run texpp\n");
	return(1);
    }
    output_file_name=DEFAULT_OUT; output_type=NUL_OUT;
    find_output(envc,envv);
    find_output(argc,argv);
    error_file=stderr;
    switch(output_type){
	case STD_OUT:	    output_file=stdout; error_file=NULL; break;
	case NUL_OUT:	    output_file=NULL; break;
	case APPEND_OUT:    output_file=fopen(output_file_name,"a"); break;
	case WRITE_OUT:     output_file=fopen(output_file_name,"w"); break;
    }
    if(output_file==NULL && output_type!=NUL_OUT){
	fprintf(stderr,"cannot open file %s\n",output_file_name);
	return(1);
    }
    clear_translate();
    input_file_name=NULL; input_line_number=1;
    if(envc>1 && is_param(envv[1],TRANSLATE_PAR)){  /* -txxxx parameter */
	input_file_name=envv[1]+2;
	envc--; envv++;
        if(*input_file_name==0){
	    if(envc<=1){
		fprintf(stderr, "missing file name after parameter -t\n");
		input_file_name=NULL;
	    } else {
		input_file_name = envv[1];
		envc--; envv++;
	    }
        }
    }
    if(argc>1 && is_param(argv[1],TRANSLATE_PAR)){  /* -txxxx parameter */
	input_file_name=argv[1]+2;
        argc--; argv++;
        if(*input_file_name==0){
	    if(argc<=1){
		fprintf(stderr, "missing file name after parameter -t\n");
                return(1);
            }
            input_file_name = argv[1];
            argc--; argv++;
        }
    }
    if(input_file_name!=NULL){
        input_file=fopen(input_file_name,"r");
        if(input_file==NULL) {
#ifndef __TURBOC__
	    char fname[1024]; 
	    strcpy(fname,STANDARD_DIR);
	    strncat(fname,input_file_name,1000);
	    input_file=fopen(fname,"r");
	    if(input_file!=NULL) {
	        read_translate_file(); fclose(input_file); 
	    } else
#endif
            error(CANNOT_OPEN_TRANSLATE);
        } else { read_translate_file(); fclose(input_file); }
    }
    exit_value=0; set_plain_mode();
    input_file_no=0;
    while(arguments(silent,&input_file_no,envc,envv)){
	/* for each file on the argument list */
        read_file();
	if(input_file!=stdin) fclose(input_file);
    }
    input_file_no=0;
    while(arguments(silent,&input_file_no,argc,argv)){
	/* for each file on the argument list */
        read_file();
	if(input_file!=stdin) fclose(input_file);
    }
    if(input_file_no==0){                   /* no input file */
        input_file_name="";
        error(NO_INPUT_FILE); return(1);
    }
    flush_output();
    return(exit_value);
}

