/*===========================================================================
  Copyright (C) 1988-2009 European Southern Observatory (ESO)
 
  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 Massachusetss Ave, Cambridge, 
  MA 02139, USA.
 
  Corresponding concerning ESO-MIDAS should be addressed as follows:
	Internet e-mail: midas@eso.org
	Postal address: European Southern Observatory
			Data Management Division 
			Karl-Schwarzschild-Strasse 2
			D 85748 Garching bei Muenchen 
			GERMANY
===========================================================================*/

/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.TYPE		Module
.IDENTIFICATION tex.c
.AUTHOR    	Francois Ochsenbein [ESO-IPG]
.LANGUAGE  	C
.KEYWORDS	Text scanning
.ENVIRONMENT	Any
.COMMENTS	
\begin{TeX}
This module includes the necessary functions to scan a piece of TeX
text, and to perform the macro definitions / substitutions.
The actions are executed \via the functions (included in the TeX structure):
\begin{itemize}
\item output function (text, length) which performs the
	actual output. A typical example of such a function could be 
	{\tt write(1,{\rm text, length})}.
\item action routine, triggered by the macro {\tt \b action\{{\em text}\}}.
	Parameters are the {\em text} action and the length of this action.
	Return is 0 / -1 (Stop) / len
\end{itemize}

All these user-defined functions return the number of bytes processed; a 0 value
indicates ``stop processing''

Standard actions (calling the action routine, with a one-byte argument) 
are:
$$\begin{tabular}{|ll|} \hline
\{	&triggered by \{\\
\}	&triggered by \}\\
\ 	&stretchable blank (one or several blanks / newlines / tabs)\\
\b r	&triggered by \b\b (go to next line)\\
\b n	&new paragraph (triggered by blank lines)\\
\^{ } &	triggered by the carret\\
\_	&triggered by the underscore\\
\&	&triggered by the ampersand\\
\$	&triggered by the \$ sign\\
1\dots9  &returns in TeX structure the address of parameter \#$n$\\
\hline
\end{tabular}$$

Primitives macros are:
\begin{itemize}
\item {\tt\b def} macro
	\eg {\tt\b def\b{\em name}\{{\em equivalence}\}}
\item {\tt\b action\{{\em action string}\}}
\item {\tt\b begin\{verbatim\}} and {\tt\b end\{verbatim\}}
\item {\tt\b len\{{\em text}\}} computes length of a text
\item {\tt\b input\{{\em filename}\}}
\item {\tt\b inputverbatim\{{\em filename}\}}
\item {\tt\b defenv} macro, as {\tt\b defenv\b{\em name[\#$n$]}\{{\em 
	start options}\}\{{\em end options}\}   }.
	It is suggested to use {\tt\b0} as start option, triggering
	action $0$ and providing in the TeX structure the environment
	name.
\item {\tt\b b} to issue a backslash.
\item {\tt\b EOF} end-of-file marker
\item {\tt \~{ }} non-breakable space
\item {\tt \b\b} go to next line
\item {\tt \b\{} \quad {\tt\b\}} \quad {\tt\b\$} \quad {\tt\b\_} 
	\quad {\tt\b\^{ }} \quad {\tt\b\~{ }} \quad {\tt\b\#} \quad {\tt\b\&} 
	to issue the single (escape) character
\item {\tt\b iftrue} {\tt\b iffalse} {\tt\b else} {\tt\b fi} {\tt\b ifdef}
	for tests
\item {\tt\b obeylines} {\tt\b obeyspaces} {\tt\b noobey} to keep
	the line breaks / spaces \\
	{\br Remark}: the {\tt \{verbatim\}} mode stops the {\tt obey} mode.
\item {\tt\b today} {\tt\b now} {\tt\b time} 
	for current date / time 
\item {\tt\b0} to {\tt\b9} call action routine, setting parameter \#$n$
	in the TeX structure
\end{itemize}

{\bf Description of Method}

Pieces of text to scan are defined via {\em SDV} descriptors, which are
used here instead of standard character strings.
The text after substitution is stacked into the {\em substitute}
buffer, which can be automatically expanded.

An {\em SDV} descriptor contains:
\begin{itemize}
\item the position of the text to scan, as a {\em text} char pointer
	and an {\em offset} number. The {\em text} pointer is null
	if the text is located in the {\em substitute} buffer,
	because the address may change when the {\em substitute} buffer
	is expanded.
\item	the {\em length} of the text
\item	a {\em pos} position of the text in the {\em substitute} buffer,
	taking a value of $-1$ if the text is not in the {\em substitute}
	buffer or is not to be deallocated from this buffer.
\end{itemize}

Macro definitions are stored as a hash table, which address is a part
of the TeX structure. The equivalence string of a macro in the
hash table is a character string, starting with a byte indicating
the number of parameters.

Two functions, included in the TeX structure, are required:
\begin{enumerate}
\item 	the {\em output} function, called to output the substituted text
\item	the {\em action} function, called when one of the actions
	listed above is found, or when the {\tt\b action} macro
	is encountered.
\end{enumerate}

\end{TeX}

.VERSION   1.0	25-Apr-1988: Creation
.VERSION   1.1	26-May-1988: Added \if \else \fi \today \now \time \len
.VERSION   1.2	13-Oct-1988: Cosmetic Modifications
.VERSION   1.3	14-Mar-1989: Removed bug in tex_subs (\iffalse executions)
.VERSION   1.4	29-Mar-1989: Include file default extension = ``.tex''
			Also corrected \if's stacks
.VERSION   1.5	21-Jul-1989: Removed bug in getparm
.VERSION   1.6	22-Mar-1990: Changed tex_rescan to have another parameter
			asking not to record errors.
.VERSION   1.7	27-Oct-1990: Added call to atex_ to force inclusion of
			the atex module (problem with Guu-cc on Vax/Vms)
.VERSION   1.8	21-Nov-1990: Added \obeylines \obeyspaces facility.

 090831		last modif
----------------------------------------------------------------------------*/

#define	DEBUG		0	/* For debugging only		*/

#define	PM_LEVEL 	LEVEL_STR

#include <midas_def.h>

#define	 PASCAL_DEF	0
#include <stesodef.h>
#include <tex.h>	/* Definition of TeX structure	*/
#include <atex.h>	/* Definition of TeX attributes */
#include <hash.h>	/* Uses a Hash table for Macros	*/
#include <buffer.h>	/* Need to know buffers		*/
#include <trtime.h>	/* Time operations		*/
#include <string.h>

extern int eh_ed_str2(), eh_ed_i(), eh_put1(), ed_t(), h_log();
extern int pm_enter(), pm_ed_trace(), pm_iexit(), pm_ed_tr2();
extern int fi_load();

extern unsigned char *atex_();




int tex_unit();


#define HSIZE		200		/* Default Size of Hash table	*/
#define DEFAULT_EXTENSION	".tex"	/* Default extension for include files */
#define SUBSTITUTE_MAX	32		/* Maximal substitution		*/
#define IFS_MAX		16		/* Maximal embedded if's	*/

typedef struct s_SDV{	/* Descriptor of pieces of memory	*/
	int	pos;	/* Position in substitute buffer	*/
	int 	offset;	/* Present position	*/
	int	len;	/* Length of string	*/
	char	*text;	/* Text to scan: NULL if in substitute buffer	*/
  } SDV;
typedef struct { 
	short len; 
	char buf[TeX_BUF];
  } OUT_BUF;
static OUT_BUF *out_buf;

static unsigned char	ifs[IFS_MAX];
static unsigned char	*ifc;
#define ifs_min (&ifs[0])
#define ifs_max	(&ifs[IFS_MAX])

#define StackIFS()	if (++ifc >= ifs_max) {			   \
				ERROR("Too many embedded \\if's"); \
			    	ifc--; status = NOK; FINISH; }	   \
			else	*ifc = *(ifc-1)
#define UnstackIFS()	if (ifc > ifs_min)	ifc--

static SDV 	sdv_pieces[SUBSTITUTE_MAX];
#define sdv_min (&sdv_pieces[0])
#define sdv_max	(&sdv_pieces[SUBSTITUTE_MAX])
static SDV *sdvc;			/* Current piece to substitute	*/
static SDV sdvb0 = {-1, 0, 1, (char *)0};	/* Blank descriptor	*/

static SDV ssdvp[10];			/* Where are the parameters	*/
static SDV *sdvp;			/* Parameters addresses		*/

#define GetString(sdv)	((sdv)->text ? (sdv)->text : substitute->buf)
#define GetText(sdv)	GetString(sdv) + (sdv)->offset
#define Text(string,sdv) string + (sdv)->offset
#define Length(sdv)	((sdv)->len - (sdv)->offset)
#define GobbleSpaces(s,sdv)  \
			(sdv)->offset += skip_spaces(Text(s,sdv),Length(sdv),\
				(char *)0)
#define GobbleNewlines(s,sdv,ab)  \
			(sdv)->offset += skip_spaces(Text(s,sdv),Length(sdv),\
				ab)
#define StackSDV()	if (++sdvc >= sdv_max) {	\
			    ERR_SDV(); status = NOK; FINISH; }
#define UnstackSDV()	if (sdvc->pos >= 0)	substitute->used = sdvc->pos;\
			    sdvc--

static TeX  	*stex;
static char 	*mode;
static H_TABLE 	*macros;		/* Table of Macros	*/
static BUFFER  	*substitute;		/* Stack of Substitutions */
static BUFFER  	*param;			/* Stack of Parameters	*/
static int    	(*fout)();		/* Output function	*/
static int    	(*fact)();		/* Action function	*/
static char 	len_text[4];		/* Length of a text	*/
static int	*alen;
static int	error_opt = 1;		/* Error report option	*/

static char EOF_marker[2] = { '0', -1};
MID_RSTATIC char char0 = 0;

#define time_pic "dd MMMMMMMMMM YYYY, hh:mm:ss"
#define o_time	20
static char time_buf[sizeof(time_pic)];
static SDV  sdv_time = {-1, 0, sizeof(time_buf)-1, time_buf};

#define FINISH		goto FIN

	/* Static Functions used before they are defined */

static int tex_rescan(); 	/* Mandatory, recursivity... */

/* 	MONITOR(TeX);  		K.B. status is here unnecessary */

/*==========================================================================*/
static int ERR_SDV()
/*+++
.PURPOSE Error (too many sdv's)
.RETURNS NOK
.REMARKS 
---*/
{ 	
	register SDV  *sdv;

  for (sdv = sdv_max-5; sdv < sdv_max; sdv++)  
  	ERR_ED_STR2("... ", GetText(sdv), Length(sdv));
  sdvc--;
  ERR_ED_I("Max. substitutions reached: ", SUBSTITUTE_MAX);

  return(NOK);
}

/*==========================================================================*/
static int skip_comment (str, len)
/*+++
.PURPOSE Skip the comment, gobbling the newline character.
.RETURNS Index of first character following comment
.REMARKS Not traced.
---*/
	char *str;	/* IN: text; first byte is %	*/
	int len;	/* IN: Length of text	*/
{ 	
	register int	i;

  i = oscloc(str, len, '\n');
  if (i < len)	i++;
  
  return(i);
}

/*==========================================================================*/
static int skip_spaces (str, len, blank)
/*+++
.PURPOSE Span spaces, including comments
.RETURNS Index of first non-space character
.REMARKS Blank contains in return a blank or a newline (if empty lines)
---*/
	char *str;	/* IN: text	*/
	int len;	/* IN: Length of text	*/
	char *blank;	/* OUT: blank or newline; if null pointer, stop at \
				the first newline */
{ 	
	char 	*p, *pe;
	int	nl;

  for (nl = 0, p = str, pe = str + len; p < pe; )  {
  	if (*p == '\n')	{
		if_not(blank)	FINISH;
		nl++, p++;
		continue;
	}

  	if (isTeX(*p, _TeX_SPACE_)) {
  		p++;
		continue;
	}

	if (*p == '\0')	{
		p++;
		continue;
	}

	if (*p == '%') 	{	
		p += skip_comment(p, pe-p);
		continue;
  	}
	break;
  }
  if_not(blank)	FINISH;
  *blank = (nl >= 2 ? '\n' : ' ');
	
  FIN:
  return(p-str);
}

/*==========================================================================*/
static int tex_pout(str, len)
/*+++
.PURPOSE Output substituted text in param buffer
.RETURNS Length of scanned text
.REMARKS *** For internal use ***. At input str-> macro to define.
---*/
	char	*str;		/* IN: String to scan		*/
	int	len;		/* IN: Length of str	*/
{ 	

  BUF_AppendItems(param, char, str, len);

  return(len);
}

/*==========================================================================*/
static int tex_lact(str, len)
/*+++
.PURPOSE Action routine four count.
.RETURNS len
.REMARKS Count only blanks.
---*/
	char	*str;		/* IN: String to scan		*/
	int	len;		/* IN: Length of str	*/
{ 	
	char	*p, *pe;

  for (p = str, pe = p + len; p < pe; p ++)
  {	if (*p == ' ')	*alen += 1;
  } 
  
  return(len);
}

/*==========================================================================*/
static int tex_lout(str, len)
/*+++
.PURPOSE Count total length
.RETURNS len
.REMARKS Zero bytes are not counted.
---*/
	char	*str;		/* IN: String to scan		*/
	int	len;		/* IN: Length of str	*/
{ 	
	char	*p, *pe;
	int	i;

  for (p = str, pe = p + len; p < pe; p += i)
  {	i = oscloc(p, pe-p, '\0');
  	*alen += i;
  	if (i == 0)	i = 1;
  } 
  
  return(len);
}

/*==========================================================================*/
static int act0()
/*+++
.PURPOSE Internal action routine, to output the collected buffer
.RETURNS OK (processed) / NOK (not fully processed)
.REMARKS 
---*/
{ 	
	int  i;

  i = (*fout)((&out_buf->buf[0]), out_buf->len);
  if (i < out_buf->len)		/* Not all bytes processed */
  	out_buf->len = oscopy(&out_buf->buf[0], 
  			&out_buf->buf[i], out_buf->len - i),
  	i = NOK;
  else	out_buf->len = 0,
  	i = OK;

  return(i);
}

/*==========================================================================*/
static int tex_macro(str, len)
/*+++
.PURPOSE Compute length of a macro
.RETURNS Length
.REMARKS Not traced.
---*/
	char *str;	/* IN: text; first byte is \	*/
	int len;	/* IN: Length of text	*/
{ 	
	register int	i;

  if (len < 2)	return(len);
  
  if (isTeX(*(str+1), _TeX_ALPHA_))	/* Macro name with alpha char */
	i = 1 + TeX_span(str+1, len-1, _TeX_ALPHA_);	
  else	i = 2;
  
  return(i);
}

/*==========================================================================*/
static int getlen(str, len)
/*+++
.PURPOSE Get length of text
.RETURNS Length of text, also edited in len_text
.REMARKS Parameter is rescanned.
---*/
	char	*str;	/* IN: Text string	*/
	int	len;	/* IN: Length of text	*/
{ 	
	int count, i, n;
	SDV sdv;

  sdv.pos = -1, sdv.offset = 0, sdv.len = len, sdv.text = str;
  count = 0;
  alen = &count;

  tex_rescan(&sdv, tex_lout, tex_lact, error_opt);
  
	/* Edit the resulting count */
	
  for (i = sizeof(len_text), n = count; --i >= 0; n /= 10)
	len_text[i] = '0' + n%10;

  return(count);
}

/*==========================================================================*/
static int tex_init(htex)
/*+++
.PURPOSE Initialize TeX parsing
.RETURNS OK / NOK
.REMARKS Copies locally parts of TeX structure for easier communication with
	subroutines. TeX structure also installed if not yet done.
---*/
	TeX	*htex;	/* IN: The TeX header	*/
{
	int status, id, ld, ieq, leq;
		/* Note: sequence \05\03mask for Set Mode Mask,
				  \05\04mask for Clear Mode Mask
	        */
	MID_STATIC char definitions[] = {"\
\\begin:1\01#1\0\
\\end:1\02#1\0\
\\iffalse:0\021\0\
\\iftrue:0\021\01\0\
\\else:0\021\02\0\
\\fi:0\021\03\0\
\\action:1\03\0\
\\ifdef:0\06\0\
\\def:1\06\0\
\\defenv:2\06\0\
\01verbatim:0\05\01\0\
\02verbatim:0\05\02\0\
\\obeylines:0\05\03\101\0\
\\obeyspaces:0\05\03\103\0\
\\noobey:0\05\04\103\0\
\\b:0\05\\\0\
\\\\:0\03\r\04\0\
\\len:1\022#1\022\0\
\\today:0\023A\0\
\\now:0\023.\0\
\\time:0\023B\0\
\\inputverbatim:1\07\05\0\
\\input:1\07\01"};

  ENTER("tex_init");

  status = NOK;

  sdvp = ssdvp;
  stex = htex;
  macros = htex->macros;
  fout = htex->output;
  fact = htex->action;
  mode = &(htex->mode);
  out_buf = (OUT_BUF *)(&(htex->nbuf));

  if_not(fout)
  {	ERROR("No output function");
  	FINISH;
  }
  if_not(fact)
  {	ERROR("No Action function");
  	FINISH;
  }

	/* Look if contents of TeX header is OK	*/
	
  if_not(substitute)
	if_not(substitute = BUF_Open(char,1024,1024))
		FINISH;

  if_not(param)
	if_not(param = BUF_Open(char,128,128))
		FINISH;

  if_not(macros)	macros = H_Create(HSIZE);
  if_not(macros)	FINISH;			/* Failed to allocate	*/
  htex->macros = macros;	
  
  if(macros->symbols == 0) 			/* Insert Basic Macros	*/
  {	atex_();		/* Ensures the inclusion of atex module	*/
  	for (id=0; id<sizeof(definitions)-1; )
	{	ld = oscloc(&definitions[id], sizeof(definitions), ':');
		ieq = id + ld + 1;
		leq = strlen(&definitions[ieq]);
		h_add(macros, &definitions[id], ld, &definitions[ieq], leq);
		id = ieq + leq + 1;
	}
	h_add(macros,"\\EOF",4,EOF_marker,2);
  }
  
  status = OK;

  FIN:
  EXIT(status);
}

/*==========================================================================*/
static int tex_subs()
/*+++
.PURPOSE Substitute a macro
.RETURNS OK / NOK
.REMARKS verbatim mode not checked.
---*/
{ 	
	SDV  	*sdvin, *sdvx;
	SDV  	sdv0, sdva;
	H_ITEM	*pi;
	char 	*p, *eq, *string, x, y, npdef;
	int  	i, lm, np, status;
	char 	blank;
	static char defaction[2] = {_TeX_ACTION_, _TeX_ENDACTION_};
	static char ifdef[] = "\021\01";		/* If true */
	
#define LENGTH	12000
	
  npdef = ' ';
  ENTER("tex_subs");

  sdvin = sdvc;
  string = GetString(sdvin), p = Text(string, sdvin), lm = Length(sdvin);

  status = OK;

	/* 1. Fetch Macro Name, and save position as param. 0 */

  sdv0 	= *sdvin, sdv0.pos = -1;
  lm 	= tex_macro(p, lm);
  sdvin->offset += lm;

  TRACE_ED_STR2("Substitute: ", p, lm);

  if_not(pi = h_look(macros, p, lm))	/* Macro Not Found	*/
  {	if ((*ifc) && (error_opt))
 	{	if (*p == '\\') ERR_ED_STR2("Undefined Macro: ", p, lm);
		else		ERR_ED_STR2("Unknown Environment: ", p+1,lm-1);
	}
	status = NOK;
	FINISH;
  }
  sdv0.offset= 0;
  sdv0.len   = lm;
  sdv0.text  = &pi->strings[0];

  eq = &(pi->strings[1+lm]);		/* Equivalence text of macro	*/

  GobbleSpaces(string,sdvin);

	/* 2. Allocate new SDV	*/

  StackSDV();
  sdvc->pos = -1, sdvc->offset = 0, sdvc->len = 0, sdvc->text = NULL_PTR(char);


	/* 3. If the macro has no parameter, exit here	*/

  np = *(eq++) - '0';
  if ((np == 0) && (*eq != _TeX_DEF_))	/* No parameters. Simply give the string */
  {	sdvc->text = eq;
  	sdvc->len = strlen(eq);
	*sdvp = sdv0;	/* Save macro name */
	FINISH;
  }
	/* 3'. If Text to be ignored, exit here	*/
  if (*ifc == 0)	{ status = NOK; FINISH; }

	/* 4. In case of \def: get name of macro to define */

  string = GetString(sdvin);

  if (*eq == _TeX_DEF_)
  {	p = Text(string, sdvin);
	sdv0 = *sdvin, sdv0.pos = -1;
  	lm = tex_macro(p, Length(sdvin));
	sdvin->offset += lm;
	sdv0.len = sdv0.offset + lm;
  	
  		/* 4': Ifdef macro	*/
  	if (np == 0)
  	{	ifdef[1] = (h_look(macros, p, lm) ? '\01' : '\0');
  		sdvc->text = ifdef, sdvc->len = 2;
  		FINISH;
  	}

  		/* 4": Retrieve number of parameters in \def,
  		 * form #1#2...
  		 */
  	GobbleSpaces(string,sdvin);
  	for (npdef='0'; Length(sdvin)>0; )
	{	p = Text(string, sdvin);
		if (*p != '#')	break;
		p++, sdvin->offset += 1;
		if (isTeX(*p, _TeX_DIGIT_))
			npdef = *p;
		else	ERR_ED_STR2("Bad # in \\def\\", GetText(&sdv0), 
				Length(&sdv0)),
			status = NOK;
		sdvin->offset += 1;
	}
  }
	/* 5: Get addresses of parameters */
	
  for (i = 1; i <= np; i++)	
  {	sdvx = (*eq == _TeX_ACTION_ ? &sdva : sdvp+i);
	GobbleNewlines(string, sdvin, &blank);
	sdvx->pos = -1, sdvx->offset = 0, sdvx->len = 0;
	
	while ((Length(sdvin) <= 0) && (sdvin > sdv_min))
						/* Try to get previous */
		sdvin--,
		string = GetString(sdvin),
		GobbleNewlines(string, sdvin, &blank);

	if ( (Length(sdvin) <= 0) || (string[sdvin->offset] != '{'))
	{	ERR_ED_STR2("Missing parameter(s) in ", 
			&pi->strings[0], (int)pi->ls);
		status = NOK;
		continue;
	}

	sdvin->offset += 1;		/* Skip the { */
	*sdvx = *sdvin, sdvx->pos = -1;
  	lm = tex_unit(Text(string, sdvin), Length(sdvin));
  	sdvin->offset += lm;
	sdvx->len = sdvx->offset + lm;
	if ( string[sdvin->offset] != '}')
		ERR_ED_STR2("Missing } in ", &pi->strings[0], (int)pi->ls),
		status = NOK;
	else	sdvin->offset += 1;	/* Skip the } */
  }

	/* 6. If \def, insert now definition in table 	*/

  if (*eq == _TeX_DEF_)
  {	string = GetText(&sdv0), lm = Length(&sdv0);
	x = *string;
	for (i=1; i <= np; i++, npdef = '0')
	{	sdvx = sdvp + i;
		sdvx->offset -= 1;	/* Get 1 byte for parameters */
		if (np > 1)	*string = i;
		p = GetText(sdvx);
		y = *p, *p = npdef;
		h_add(macros, string, lm, p, Length(sdvx));
		*p = y;
	}
	*string = x;
	UnstackSDV();
	FINISH;
  }
  
  sdvc->pos = substitute->used;	/* To compute length of substituted string */
  sdvc->offset = sdvc->pos;


	/* 7. If \action, rescan text to get correct action.
	 */
	
  if (*eq == _TeX_ACTION_)
  {	i = param->used;
	tex_rescan(&sdva, tex_pout, tex_pout, error_opt);
	BUF_AppendItems(substitute, char, &defaction[0], 1);
	BUF_AppendItems(substitute, char, param->buf + i, param->used - i);
	BUF_AppendItems(substitute, char, &defaction[1], 1);
	param->used = i;
	sdvc->len = substitute->used;
	FINISH;
  }
  

	/* 8. Copy equivalence text, with substitution of #'s by parameters 
	 * Note that parameters are preceded / followed with nuls
	 * to avoid concatenation in names
	 */
	
  *sdvp = sdv0;	/* Save macro name */

  for ( ; *eq; eq += i)  
  {	i = 0;
  	switch(*eq)
  	{ case '\\':	/* Just in case of \ followed by EOS	*/
		i = (*(eq+1) ? 2 : 1);
		break;

	  case '#':	/* Substitute parameter */
		eq++, i = 1;
		if_not(isTeX(*eq,_TeX_DIGIT_))
		{	ERR_ED_STR2("Bad #-parameter(s) in ", &pi->strings[0], 
				(int)pi->ls);
			status = NOK;
		}
		else
		{	sdvx = sdvp + (*eq - '0');
			p = substitute->buf + substitute->used - 1;
			if (isTeX(*p,_TeX_GRAPH_))
				BUF_AppendItem(substitute, char, &char0);
			BUF_AppendItems(substitute, char, 
				GetText(sdvx), Length(sdvx));
			p = substitute->buf + substitute->used - 1;
			if (isTeX(*p,_TeX_GRAPH_))
				BUF_AppendItem(substitute, char, &char0);
		}
		continue;
  	}
	if (i == 0) i = TeX_span(eq, LENGTH, _TeX_NORMAL_);
	if (i == 0) i = 1;
	BUF_AppendItems(substitute, char, eq, i);
  }
  sdvc->len = substitute->used;

  FIN: 
  EXIT(status);
}

/*==========================================================================*/
static int getparm(np)
/*+++
.PURPOSE Get parameter #np in TeX structure
.RETURNS OK / NOK (np outside range 0..9)
.REMARKS Parameter is rescanned.
---*/
	int  np;	/* IN: Parameter number, range [0...9] */
{ 	
	int status, old_offset;

  status = OK;
  old_offset = param->offset;
  param->offset = param->used;

  if ((np>0) && (np <= 9))
  {	tex_rescan(sdvp+np, tex_pout, tex_pout, error_opt);
	BUF_AppendItem(param, char, &char0);	/* Terminate with EOS */
	stex->ap = param->buf + old_offset;
  }
  else 	if (np == 0)		/* Don't rescan parameter 0 = macro name */
	stex->ap = GetText(sdvp);
  else	stex->ap = &char0, status = NOK;
  
  param->used   = param->offset;
  param->offset = old_offset;

  return(status);
}

/*==========================================================================*/
static int act9(sdv, len)
/*+++
.PURPOSE Internal action routine, to substitute parameters
.RETURNS OK / NOK / EOF
.REMARKS 
---*/
	SDV  *sdv;	/* IN: Action string */
	int  len;	/* IN: Length of string */
{ 	
	int  i;
	char *str;

  str = GetText(sdv);
  if ((out_buf) && (out_buf->len)) /* First, output what remains in buffer */
  	if_not(act0())	return(0);

  if_not(*ifc)		return(len);	/* Nothing iffalse */

  i = TeX_scan(str, len, _TeX_DIGIT_);	/* Digit action => get parameters */
  if (i < len)
  {	i = *(str + i) - '0';
  	getparm(i);
	str = GetText(sdv);
  }

  return((*fact)(str,len));
}

/*==========================================================================*/
static int out9(str, len)
/*+++
.PURPOSE Internal output routine.
.RETURNS Length processed.
.REMARKS Copied to tex buffer.
---*/
	char *str;	/* IN: Action string */
	int  len;	/* IN: Length of string */
{ 	
	int  i;

  i = 0;

  if_not(*ifc)		return(len);	/* Nothing iffalse */

  if_not(out_buf)	goto Immediate_Output;

  if (*mode == _TeX_VERBATIM_)	/* Output immediately */
  {	if (out_buf->len)	/* First, output what remains in buffer */
	  	if_not(act0())	FINISH;
  }

  if ((out_buf->len + len) > TeX_BUF)
	if (out_buf->len)	
	  	if_not(act0())	FINISH;

  if (len > TeX_BUF)
  	goto Immediate_Output;
  else 	i = oscopy(&out_buf->buf[out_buf->len], str, len),
		out_buf->len += i;
  FINISH;
  
  Immediate_Output:
  i = (*fout)(str,len);

  FIN:
  return(i);
}

/*==========================================================================*/
static int load1(fn, pos, len, opt)
/*+++
.PURPOSE Loads len bytes from file fid.
.RETURNS Number of bytes loaded.
.REMARKS The file is closed...
---*/
	char *fn;	/* IN: File Name	*/
	long pos;	/* IN: Position to start loading	*/
	int  len;	/* IN: Number of bytes to load */
	int  opt;	/* IN: Option _TeX_VERBATIM_ for inputverbatim */
{
	char *b;
	int  status, l;

  status = 0;
  l = len;
  if (opt == _TeX_VERBATIM_)	l += 4;
  StackSDV();
  sdvc->pos = substitute->used;
  sdvc->offset = sdvc->pos;
  sdvc->len    = sdvc->pos;
  sdvc->text = NULL_PTR(char);
  if_not (BUF_AllocateItems(substitute, char, l))
  	FINISH;

  b = substitute->buf + sdvc->pos;

  if (opt == _TeX_VERBATIM_)	/* Add begin{verbatim}	*/
  	*b = '\05', *(b+1) = '\01', b += 2;

  b += fi_load(NameFile(fn,DEFAULT_EXTENSION), pos, b, len);

  if (opt == _TeX_VERBATIM_)	/* Add end{verbatim}	*/
  	*b = '\05', *(b+1) = '\02', b += 2;

  sdvc->len = (b - substitute->buf);	/* Final Length */

  status = Length(sdvc);
  TRACE_ED_STR2("Input: ", substitute->buf + sdvc->offset, status);
  
  FIN:
  return(status);
}

/*==========================================================================*/
static int input1(opt)
/*+++
.PURPOSE Includes a file in the stack (macro \input{})
.RETURNS OK / NOK
.REMARKS Parameter is sdvp[1].
---*/
	int	opt;	/* IN: Option _TeX_VERBATIM_ for inputverbatim */
{
	char *endfile, *b, x;
	SDV  *sdvx;
	int  status;
	long int fs;
	
  sdvx = sdvp + 1;
  b = GetText(sdvx);
  endfile = b + Length(sdvx);
  x = *endfile;	/* Save ending character */
  *endfile = EOS;
  status = NOK;

  fs = fi_size(NameFile(b,DEFAULT_EXTENSION));	/* Mod. 28-Mar-89 Size of File */
		
  load1(b, 0L, (int)fs, opt);

  *endfile = x;		/* Restore last byte	*/
  return(status);
}

/*==========================================================================*/
static int tex_1exec(sdv_stop)
/*+++
.PURPOSE Transform the string
.RETURNS OK / NOK (not finished) 
.REMARKS 
---*/
	SDV *sdv_stop;	/* IN: Ending position	*/
{ 	
	char 	*p;
	int  	i, l, n, status, returned_status;
	char 	blank;
	SDV 	sdvb;	/* Blank descriptor	*/

  returned_status = NOK;
  status = NOK;
  sdvb = sdvb0;
  sdvb.text = &blank;
  
  if ((out_buf) && (out_buf->len))	/* Output what's in the buffer */
  	if_not(act0())	FINISH;

  for( ; sdvc >= sdv_stop; sdvc->offset += i)
  {	i = 0;
  	if (sdvc->offset >= sdvc->len)	/* This piece is exhausted	*/
  	{	UnstackSDV();
  		continue;
  	}

  	p = GetText(sdvc), l = Length(sdvc);
	if (status == EOF)	/* \EOF was encountered */
	{	i = l, status = NOK;	/* To force unstack */
		continue;
	}

	switch(*p)
	{ case _TeX_VERBATIM_:	/* Simply switch state */
		i = 2;
		switch(*++p)
		{ case _TeX_BEGIN_: *mode = _TeX_VERBATIM_;	break;
		  case _TeX_END_:   *mode = 0;			break;
		  case _TeX_ACTION_:   	i = 3; *mode |= p[1];	break;
		  case _TeX_ENDACTION_:	i = 3; *mode &= ~p[1];	break;
		  default:	
		  	if_not(out9(p,1))	FINISH;
		}
		continue;

	  case _TeX_IF_:		/* Test */
	  	switch(*++p)
	  	{ case 0: case 1:	/* New if	*/
	  		StackIFS();
			*ifc &= * (unsigned char *)p;
			break;
		  case 2:		/* Else		*/
			if (ifc == ifs_min)	
				ERROR("\\else outside \\if ... \\fi");
		  	else 	*ifc = *(ifc-1) & (*ifc ^ 1);
			break;
		  default:		/* Endif	*/
		  	UnstackIFS();
		  	break;
	  	}
	  	i = 2;
		continue;
		
	  case -1:	/* EOF */
		if ((out_buf) && (out_buf->len))
		  	if_not(act0())	FINISH;
		status = EOF;
		i = l;
	  	continue;

	  case 0: case _TeX_ENDACTION_:
	  	i = 1;
		continue;

	  case '~':	/* Non-breakable space */
		blank = (*mode == _TeX_VERBATIM_ ? '~' : ' ');
	  	if_not(i = out9(&blank, 1))	FINISH;
	  	continue;

	  case '\\':	
		if (*mode == _TeX_VERBATIM_)
		{	if (l < 14) 	break;
			if (oscomp(p,"\\end{verbatim}", 14) != 0)
	  			break;		/* Not the \end{verbatim} */
		}
		blank = *(p+1);		/* Char following escape */
		if (isTeX(blank, _TeX_DIGIT_))
		{	status = act9(&sdvb, 1);
			if(status == 0)		FINISH;
			i = 2;
			continue;
		}
		if (isTeX(blank, _TeX_SPACE_|_TeX_ESCAPE_))
		{	if(blank != '\\')
			{	if (isTeX(blank, _TeX_SPACE_))	blank = ' ';
				if_not(out9(&blank,1))	FINISH;
				i = 2;
				continue;
			}
		}

	  case _TeX_BEGIN_: case _TeX_END_: 	/* Also macro and \\	*/
		tex_subs();
		continue;

	  case '%':		/* Fetch the next newline	*/
		i = skip_comment(p, l);
		if (*mode == _TeX_VERBATIM_)	break;
	  	continue;
		
	  case _TeX_INCLUDE_:		/* Special Action   */
		sdvc->offset += 2;	/* ... will change SDV */
		if (*ifc)	input1(*++p);
		continue;
		
	  case _TeX_LEN_:		/* Special Action   */
		i = oscloc(++p, l-1, _TeX_LEN_);
		n = getlen(p, i);
		i += 2;		/* Skip beginning / end of string */
		if_not(out9(len_text, sizeof(len_text)))	FINISH;
		continue;
		
	  case _TeX_TIME_:		/* Special Action   */
		sdvc->offset += 2;	/* ... will change SDV */
		ed_t(time_buf, time_pic, oshtime());
		StackSDV();
		*sdvc = sdv_time;

		switch(*++p)
		{ case 'A':	/* Date only */	
			sdvc->len = o_time - 2;	break;
		  case 'B':	/* Time only */ 
			sdvc->offset = o_time;	break;
		  default :	/* Both	     */ break;
		}
		continue;
		
	  case _TeX_ACTION_:		/* Special Action   */
		n = oscskip(p, l, _TeX_ACTION_);
		p += n, l -= n;
		i = oscloc(p, l, _TeX_ENDACTION_);
		if (i)		/* There is an action ... */
		{	sdvc->offset += n;
			status = act9(sdvc, i);
			sdvc->offset -= n;
			if (status == 0)	FINISH;
		}
		i += n+1;	/* Skip Action */
		continue;
	}

	if (*mode == _TeX_VERBATIM_)
	{	if (i == 0)	i = TeX_span(p, l, _TeX_NORMAL_);
		if (i == 0)	i = 1;
		if_not(i = out9(p,i))	FINISH;
		continue;
	}

	if (isTeX(*p,_TeX_GRAPH_))   	/* Scan Normal Text */
	{	i = TeX_span(p, l, _TeX_GRAPH_);
		if_not(i = out9(p, i))	FINISH;
		continue;
	}

	if (isTeX(*p,_TeX_SPACE_)) {	/* Scan Blank Text */
		if (*mode & 0100)  {	/* Obey spaces...  */
			i = skip_spaces(p, l, (char *)0);
			if (i && (*mode & 02)) {	/* ALL SPACES */
				if_not(i = out9(p, i))	FINISH;
				continue;
			}
			if (i == 0)	blank = '\r', i = 1;	/* Next Line */
			else 		blank = ' ';
		}
		else	i = skip_spaces(p, l, &blank);
		status = act9(&sdvb, 1);
		if(status == 0)		FINISH;
		continue;
	}

	if_not(isTeX(*p, _TeX_ESCAPE_))	/* Bad Character...	*/
	{	ERR_ED_STR2("Bad character: ", p, l);
		i = 1;
		continue;
	}

	i = 1;			/* Escape */
	status = act9(sdvc,1);
	if(status == 0)		FINISH;
  }

  if ((out_buf) && (out_buf->len))
  	if_not(act0())	FINISH;

  returned_status = OK;

  FIN:
  return(returned_status);
}  

/*==========================================================================*/
static int tex_rescan(sdv, f_out, f_act, er_opt)
/*+++
.PURPOSE Rescan text, storing result via supplied output / action routines
.RETURNS OK / NOK
.REMARKS 
---*/
	SDV *sdv;	/* IN: String to rescan	*/
	int (*f_out)();	/* IN: The output routine to use	*/
	int (*f_act)();	/* IN: The action routine to use	*/
	int er_opt;	/* IN: Error report option: 0=no report	*/
{ 	
	int  status, old_eropt;
	int (*old_act)(), (*old_out)();
	OUT_BUF *old_buf;
	SDV  sdvp1[10], *old_parm;

  old_eropt = 0;
  status = NOK;
  StackSDV();
  *sdvc = *sdv;
  sdvc->pos = -1;	/* Don't free */
  param->offset = param->used;
  old_eropt = error_opt;
  error_opt = er_opt;

  old_parm = sdvp;
  sdvp = &sdvp1[0];
  old_act = fact, old_out = fout, old_buf = out_buf;
  fact = f_act, fout = f_out, out_buf = (OUT_BUF *)0;
  
  status = tex_1exec(sdvc);
  
  fout = old_out, fact = old_act, out_buf = old_buf;
  sdvp = old_parm;

  FIN:
  error_opt = old_eropt;
  return(status);
}

/*==========================================================================*/
int tex_unit(str, len)
/*+++
.PURPOSE Scan for a closing brace }
.RETURNS Offset of matching } 
.REMARKS Not traced.
---*/
	char *str;	/* IN: String to scan; points to char following { */
	int   len;	/* IN: Length of String	*/
{
	char 	*p, *pe;
	int 	lb;

  for (lb = 1, p = str, pe = p+len; p < pe; p++)
  { 	switch(*p)
	{ case '\\':	p++;	continue;
	  case '{':	lb++;	continue;
	  case '}':	lb--;	break;
	  case '%':	p += skip_comment(p, pe-p) - 1;
	  		continue;
	  default:	continue;
	}
	if (lb == 0)	break;
  }

  if (lb != 0)
	ERR_ED_STR2("Missing Right Brace in {",str, p-str);
  
  return(p-str);
}

/*==========================================================================*/
int tex_input(htex, filename, opt)
/*+++
.PURPOSE Loads a file,
.RETURNS Number of bytes loaded.
.REMARKS 
---*/
	TeX *htex;	/* IN: TeX structure */
	char *filename;	/* IN: Name of file to load. */
	int	opt;	/* IN: Option _TeX_VERBATIM_ for inputverbatim */
{ 	
	long int fs;
	int  status;

  ENTER("tex_input");

  status = NOK;
  
  if_not(tex_init(htex))	FINISH;
  
  fs = fi_size(NameFile(filename,DEFAULT_EXTENSION));	/* Size of File */
		
  status = load1(filename, 0L, (int)fs, opt);

  FIN:
  EXIT(status);
}

/*==========================================================================*/
int tex_load(htex, fid, len, opt)
/*+++
.PURPOSE Load n bytes from a file,
.RETURNS Number of bytes loaded.
.REMARKS File is closed...
---*/
	TeX *htex;	/* IN: TeX structure */
	int  fid;	/* IN: File identifier */
	int len;	/* IN: Number of bytes to load */
	int	opt;	/* IN: Option _TeX_VERBATIM_ for inputverbatim */
{ 	
	int  status;
	char *fn;
	long pos;

  ENTER("tex_load");

  status = NOK;
  
  if_not(tex_init(htex))	FINISH;
  
  fn = fi_name(fid), pos = fi_tell(fid);
  close(fid);
  status = load1(fn, pos, len, opt);

  FIN:
  EXIT(status);
}

/*==========================================================================*/
int tex_list(htex)
/*+++
.PURPOSE Display stored macros in Log file.
.RETURNS Free space for symbol definitions.
.REMARKS 
---*/
	TeX *htex;	/* IN: TeX structure */
{ 	
	int n, i;
	H_ITEM *pi;

  
  n = 0;
  ENTER("+tex_list");
  if_not(tex_init(htex))	FINISH;
  n = h_log(macros);	/* Log the size / collisions / free */
  
  for (i=0; i < macros->size; i++)
  {	if_not(macros->start[i])	continue;
	for (pi = macros->start[i]; pi; pi = pi->next)
		LOG_ED_STRING((pi->next ? "+" : " "), &pi->strings[0]);
  }

  FIN:
  EXIT(n);
}

/*==========================================================================*/
int tex_getvparm(np)
/*+++
.PURPOSE Get ``verbatim'' parameter #np in TeX structure
.RETURNS Length of the parameter. The address is in the TeX structure.
	-1 is returned in case of error.
.REMARKS Parameter is NOT rescanned.
---*/
	int  np;	/* IN: Parameter number, range [0...9] */
{ 	
	int  	status;
	SDV	*p;

  ENTER("+tex_getvparm");

  if ((np>0) && (np <= 9))
  {	p = sdvp + np;
  	stex->ap = GetText(p);
  	status = Length(p);
  }
  else	status = -1;

  EXIT(status);
}

/*==========================================================================*/
int tex_getparm(np)
/*+++
.PURPOSE Get parameter #np in TeX structure
.RETURNS OK / NOK (np outside range 0..9)
.REMARKS Parameter is rescanned.
---*/
	int  np;	/* IN: Parameter number, range [0...9] */
{ 	
	int  status;

  ENTER("tex_getparm");

  BUF_Clear(param);
  
  status = getparm(np);

  EXIT(status);
}

/*==========================================================================*/
int tex_exec(htex, str, len)
/*+++
.PURPOSE Transform the string
.RETURNS OK / NOK (not finished) 
.REMARKS 
---*/
	TeX	*htex;	/* IN: The TeX header	*/
	char 	*str;	/* IN: String to scan; NULL to continue	*/
	int	len;	/* IN: Length of string	*/
{ 	
	int 	status;

  ENTER("tex_exec");

  status = NOK;
  if_not(tex_init(htex))	FINISH;

  if(str)	/* Start a new set of substitutions */
  {	BUF_Clear(substitute), BUF_Clear(param);
	htex->nbuf = 0;
	ifc  = ifs_min;
	*ifc = 1;		/* TRUE */
	sdvc = sdv_min;
	sdvc->pos 	= -1;
	sdvc->offset 	= 0;
	sdvc->len 	= len;
	sdvc->text 	= str;
	error_opt       = 1;	/* Error report is ON	*/
  }

  status = tex_1exec(sdv_min);

  FIN:
  EXIT(status);
}

/*==========================================================================*/
int tex_mexec(htex, str, nstrings)
/*+++
.PURPOSE Multiple transformations
.RETURNS OK / NOK (not finished) 
.REMARKS 
---*/
	TeX	*htex;	/* IN: The TeX header	*/
	char 	**str;	/* IN: Array of strings to scan	*/
	int	nstrings; /* IN: Number of strings	*/
{ 	
	int 	status;
	char	**s;

  ENTER("tex_mexec");

  status = NOK;
  if_not(tex_init(htex))	FINISH;

  if (nstrings >= SUBSTITUTE_MAX - 2)
  {	ERR_ED_I("To many strings to substitute: ", nstrings);
  	FINISH;
  }

  if(nstrings > 0)	/* Start a new set of substitutions */
  {	BUF_Clear(substitute), BUF_Clear(param);
	htex->nbuf = 0;
	ifc  = ifs_min;
	*ifc = 1;		/* TRUE */
	error_opt       = 1;	/* Error report is ON	*/
	for (s = str + 2*nstrings, sdvc = sdv_min; s > str; sdvc++)
	{	s -= 2;
		sdvc->pos 	= -1;
		sdvc->offset 	= 0;
		sdvc->text 	= *s;
		sdvc->len 	= *(s+1) - sdvc->text;
	}
	sdvc--;
  }

  status = tex_1exec(sdv_min);

  FIN:
  EXIT(status);
}

/*==========================================================================*/
int tex_tell()
/*+++
.PURPOSE Tell where we are in the TeX processing 
.RETURNS Offset from last string passed to tex_exec / -1 if terminated
.REMARKS 
---*/
{ 	
	int 	status;

  ENTER("+tex_tell");

  status = (sdvc >= sdv_min ? (sdv_min)->offset : -1);

  EXIT(status);
}

/*==========================================================================*/
char *tex_symbol(str)
/*+++
.PURPOSE Retrieve a symbol
.RETURNS Address of processed symbol / NULL
.REMARKS Uses the last TeX structure
---*/
	char 	*str;	/* IN: String to substitute	*/
{ 	
	char	*p;
	int 	old_offset;
	SDV	sdvl;

  ENTER("*tex_symbol");

  sdvl.pos 	= -1;
  sdvl.offset 	= 0;
  sdvl.len 	= strlen(str);
  sdvl.text 	= str;
  old_offset 	= param->offset;	/* Save	*/
  param->offset = param->used;

  tex_rescan(&sdvl, tex_pout, tex_pout, 0);
  BUF_AppendItem(param, char, &char0);	/* Terminate with EOS */
  
  param->used   = param->offset;	/* Restore	*/
  param->offset = old_offset;

  p = param->buf + param->offset;
  EXITp(p);
}

