/*
 * What does it mean to do .nx inside a macro?
 *
 * input.c - input operations
 *
 * Comments (\"...) are deleted whenever input is read or reread.
 * Embedded newlines (\(newline)) are also deleted whenever input
 * is read, except within comments.
 *
 * FChIn(), MChIn(), AChIn() return endOfInput (zero) when input
 * source is exhausted. FChIn() discards nulls and characters with high
 * bit set.
 *
 * ChIn0() always returns the next character from the input stream, or
 * endOfInput when there is no more input from any source.
 *
 * ChIn() collapses escChar+next char into one XChar value as appropriate
 * (depending on whether or not copy mode is on).  Special character
 * references (in \(xy format) are also returned as a single XChar value.
 *
 * Ugly hack: In copy mode, \\, \t, \a and \. are returned as \, tab,
 * SOH and ., respectively.  If the character is pushed back, there's
 * no way to determine from looking at the character itself that a \
 * character should be pushed back, too.  (E.g., if you read a \\, you
 * get a \; if you push it back, the first \ is lost, which loses a
 * level of escaping - not good.)  The flag escStripped is used to keep
 * track of whether or not the last character returned by ChIn() had an
 * escape stripped off, and UnChIn() looks at that flag to be able to
 * tell whether or not to put it back.  This would be a problem if more
 * than one character were read and pushed back in copy mode.  In practice
 * that doesn't happen, but it's still unattractive.
 */

#include	<stdio.h>
#ifndef	STRING_H
#define	STRING_H <string.h>
#endif
#include	STRING_H

#include	"etm.h"
#include	"memmgr.h"

#include	"troffcvt.h"


#define	maxPushChar	32
#define	maxMacRecursion	50

#define	macroSource		1
#define	anonStringSource	2
#define	fileSource		3


/*
 * Macro argument information.  This is not part of the MacSrc
 * structure because one macro can cause another macro to inherit
 * its own arguments by interpolating it as a string.
 *
 * The structure holds a copy of the name under which the macro was
 * invoked so that \$0 can be implemented.  (Must keep a copy rather
 * that a pointer to a Name structure, since it's possible the macro
 * will remove itself, which deletes the name from the name list.)
 * Also maintain an array of the arguments for references to $1..$99.
 * These are exported out when necessary through the MacroArgument()
 * function.
 *
 * The reference count is set to one when the structure is created,
 * for a macro invoked as a macro (.xx).  If the macro to which the
 * structure is attached invokes another macro as a string (\*y,
 * \*(yy, \*[yyy]), the other macro inherits the structure, and the
 * reference count is incremented.  As each macro using the structure
 * terminates, the count is decremented, and deallocated when the
 * count reaches zero.
 */

typedef	struct MacArg	MacArg;

struct MacArg
{
	XChar	*mName;			/* macro name (\$0) */
	XChar	mArgc;			/* number of arguments */
	XChar	*mArgv[maxMacroArgs];	/* macro arguments (\$1, \$2, ...) */
	short	mRefCount;		/* no. of macros using this struct */
};


typedef	struct MacSrc		MacSrc;
typedef	struct AnonStrSrc	AnonStrSrc;
typedef	struct FileSrc		FileSrc;
typedef struct Source		Source;


/*
 * Information for macro input sources.  The structure holds a pointer
 * to the argument information (including the macro name).  It also
 * maintains the current position within the macro body and a pointer
 * to the end position.
 */

struct MacSrc
{
	Object	*objPtr;
	MacArg	*mArg;
	long	curPos;
	long	endPos;
	Source	*prevMacSrc;
};

struct AnonStrSrc
{
	XChar	*anonStr;
	XChar	*curAPtr;
};

struct FileSrc
{
	char	*fileName;	/* name of input file */
	FILE	*filePtr;	/* file descriptor */
	int	prevChar;	/* previous char read from file */
	Param	inputLine;	/* current input line (zero-based) */
};


/*
 * Input source information structure.
 *
 * Each input source specifies the function needed to return the next
 * character from the source.  Input functions must return endOfInput
 * when they encounter the end of their input source.  For example,
 * MChIn() returns endOfInput when it reaches the end of the macro
 * definition.
 *
 * The sFakeEof member is non-zero if ChIn() should return endOfInput
 * when the input source is exhausted.  This way a source can be pushed
 * on the stack and the caller can regain control when that source is
 * finished.  To resume reading from any pending input sources, the
 * caller executes ResumeInput().
 */

struct Source
{
	short	sType;			/* source type */
	short	sFakeEof;		/* return eof when source exhausted? */
	XChar	(*sChIn) ();		/* character input function */
	union
	{
		MacSrc		sMacSrc;
		AnonStrSrc	sAnonStrSrc;
		FileSrc		sFileSrc;
	} sInfo;
# define	sMacro		sInfo.sMacSrc
# define	sAnonString	sInfo.sAnonStrSrc
# define	sFile		sInfo.sFileSrc
	Source	*sPrev;		/* previous input source */
};


static XChar	MChIn (void);	/* next character from macro */
static XChar	AChIn (void);	/* next character from anonymous string */
static XChar	FChIn (void);	/* next character from file */
static XChar	ChIn0 (void);

/*
 * Pointers to current input source and most recent macro input source.
 * Most recent macro is used when references to macro arguments are found
 * in input sources that occur within macros, e.g., in a file that's .so'd
 * from within a macro.  This way it's not necessary to traverse back
 * through the input stack to find the macro.
 */

static Source	*curSrc = (Source *) NULL;
static Source	*curMacSrc = (Source *) NULL;

static short	returnEof = 0;

/*
 * Pushback buffer.  It's a char buffer because pushed-back XChar values are
 * converted back into char form.  (Can't push back XChars because a char
 * might be parsed initially in non-copy mode, pushed back, and re-read in
 * copy mode.  The character might be interpreted differently in the two
 * modes.)  The input level at which characters are pushed back is remembered
 * to avoid returning them into the wrong input level.
 */

static int	pushCount = 0;
static char	pushBuf[maxPushChar];
static char	pushLevel[maxPushChar];

/*
 * inputLevel is the current input level; it's incremented whenever a new
 * input source begins, and decremented when the source terminates.  The
 * value is the the level of the last char read, not the next char.  Thus,
 * an input call returns the last character of a source, the value is that
 * source's level.  It's not until the next call that EOF on the source is
 * detected and the level is decremented.
 */

static int	inputLevel = 0;
static int	copyMode = 0;
static int	escStripped = 0;

/*
 * Last character read by ChIn().  This is a hack needed by the conditional
 * processors, which have to push back the last character they read.  (These
 * lines act like "lines with another line after the condition", so putting
 * the end of line character back on the input fakes input processing out
 * by making it act as though there was an end of line found for both lines.)
 * However, if the end of line is endOfInput, it's important not to push back
 * a newline or it will seem as though there is an extra blank line in the
 * input.  (This can happen if you do 'push-string "some stuff"' with no
 * newline on the end of "some stuff".)  lastCharRead allows endOfInput to
 * be pushed back if that was the end-of-line character.
 */

XChar	lastCharRead = endOfInput;


static void
SetInputLineRegisters (Param lineNum)
{
	StrSetRegisterValue (".c", lineNum);
	StrSetRegisterValue ("c.", lineNum);
}


/*
 * Turn copy mode on or off.  Turning it on triggers some
 * horrendously complex shenanighans inside of ChIn().
 */

void
CopyMode (int onoff)
{
	copyMode = onoff;	/* non-zero to turn on */
}


/*
 * Resume any pending input sources.  This is called after an input source
 * has been pushed on the stack such that the "return fake eof" flag is
 * set.  After the input source is exhausted, returnEof will be set and
 * ChIn() returns endOfInput until ResumeInput() is called.
 */

void
ResumeInput (void)
{
	returnEof = 0;
}


/*
 * Return input level, or zero if compatibility mode is enabled.
 * (When compat mode is on, parsing routines pay no attention to
 * input level, which is like standard troff.  groff uses the input
 * level to avoid interpreting delimiters in strings, arguments, etc.
 * as terminating \w'xxx', .if 'x'y', etc.
 */

short
ILevel (void)
{
	return (compatMode ? 0 : inputLevel);
}


/*
 * Format a string containing the current input filename and line number.
 * Return value is the empty string if no input file is known, which is
 * the case, e.g., while the action files are being read.
 *
 * Since the current input source might not be a file, have to look back
 * through the input stack to find the most recent source that is a file.
 *
 * Returns a pointer to a static buffer; caller should make a copy if it
 * needs to hang on to the return value for a while.
 */

char *
FileInfo (void)
{
static char	buf[bufSiz];
Source	*sp;

	buf[0] = '\0';
	sp = curSrc;
	while (sp != (Source *) NULL)
	{
		if (sp->sType == fileSource)
		{
			sprintf (buf, "%s (line %ld): ",
					sp->sFile.fileName,
					sp->sFile.inputLine);
			break;
		}
		sp = sp->sPrev;
	}
	return (buf);
}


/*
 * Dump out information about the input stack.
 */

void
DumpInputStack (void)
{
Source	*sp = curSrc;
MacArg	*ma;
int	i;

	ETMMsg ("Input stack:");
	if (sp == (Source *) NULL)
	{
		ETMMsg ("input stack is empty");
		return;
	}
	while (sp != (Source *) NULL)
	{
		ETMMsg ("----");
		switch (sp->sType)
		{
		default:
			ETMMsg ("unknown input type");
			break;
		case fileSource:
			ETMMsg ("file: %s", sp->sFile.fileName);
			ETMMsg ("current line: %ld", sp->sFile.inputLine);
			break;
		case macroSource:
			ma = sp->sMacro.mArg;
			ETMMsg ("macro/string: %s",
					ma == (MacArg *) NULL ?
					"(no name)" : XStrToStr (ma->mName));
			ETMMsg ("number of arguments: %d",
					ma == (MacArg *) NULL ? 0 : ma->mArgc);
			if (ma != (MacArg *) NULL)
			{
				for (i = 0; i < ma->mArgc; i++)
				{
					ETMMsg ("argument %d: <%s>",
						i, XStrToStr (ma->mArgv[i]));
				}
			}
			break;
		case anonStringSource:
			ETMMsg ("anonymous string");
			break;
		}
		sp = sp->sPrev;
	}
}


static void
PrintDebugChar (char type, XChar c)
{
	if (c == endOfInput)
		ETMMsg ("%c %d <EOF>", type, inputLevel);
	else if (c == lf)
		ETMMsg ("%c %d <lf>", type, inputLevel);
	else
		ETMMsg ("%c %d <%s>", type, inputLevel, XCharToStr (c));
}


/*
 * Find the given file, looking in the directories named by the given
 * path list (colon-separated list of directories in which to look).
 * Returns a pointer to a full pathname where the file was found and
 * can be read, or NULL if the file is not found in any of the directories.
 *
 * If the file is an absolute pathname, look only for the file named by
 * that path.
 *
 * Return value is to static buffer.  Caller should copy if it needs to
 * hang onto the value for a while.
 */

static char *
FindFileByPathList (char *file, char *pathList)
{
static char	path[bufSiz];
FILE	*f = (FILE *) NULL;
char	*p;
char	dir[bufSiz];
int	i;

	if (*file == '/')		/* absolute pathname */
	{
		if ((f = fopen (file, "r")) == (FILE *) NULL)
			return ((char *) NULL);
		(void) fclose (f);
		(void) strcpy (path, file);
		return (path);
	}

	if ((p = pathList) == (char *) NULL)
		return ((char *) NULL);

	while (*p != '\0')
	{
		i = 0;
		while (*p == ':')		/* skip leading separators */
			++p;
		while (*p != ':' && *p != '\0')	/* get next pathname */
			dir[i++] = *p++;
		dir[i] = '\0';			/* terminate pathname */
		if (i == 0)			/* empty pathname */
			continue;
		sprintf (path, "%s/%s", dir, file);
		f = fopen (path, "r");
		if (f != (FILE *) NULL)		/* opened successfully */
		{
			(void) fclose (f);
			return (path);
		}
	}
	return ((char *) NULL);
}


/*
 * Locate a macro file by looking for it in the following places:
 * - Directories named in TROFFCVT_TMAC_PATH environment variable, if it's set.
 * This always takes precedence, to allow the user to specify where to find
 * the files.
 * - Directory named in TROFFCVT_LIB_DIR environment variable, if it's set.
 * - Current directory.
 * - The troffcvt library directory.  (This allows you to override system
 * macro packages by placing more troffcvt-friendly versions in this directory.
 * - The directory named by the MACROLIBDIR configuration parameter.
 *
 * Return a pointer to the filename if it was found, NULL otherwise.
 * Return value is to static buffer.  Caller should copy if it needs to
 * hang onto the value for a while.
 */

char *
FindMacroFile (char *file)
{
extern char *getenv ();
char	*p;

	p = FindFileByPathList (file, getenv ("TROFFCVT_TMAC_PATH"));
	if (p == (char *) NULL)
		p = FindFileByPathList (file, getenv ("TROFFCVT_LIB_DIR"));
	if (p == (char *) NULL)
		p = FindFileByPathList (file, ".");
	if (p == (char *) NULL)
		p = FindFileByPathList (file, PROJLIBDIR);
	if (p == (char *) NULL)
		p = FindFileByPathList (file, MACROLIBDIR);
	return (p);
}


/*
 * Allocate a new input source structure, fill in the type, set
 * up the input function pointer, and switch the current source
 * pointer to it.  The caller must fill in the union information.
 */

static void
PushSource (short type, short fakeEof)
{
Source	*sp = New (Source);

	switch (sp->sType = type)
	{
	case fileSource:	sp->sChIn = FChIn; break;
	case macroSource:	sp->sChIn = MChIn; break;
	case anonStringSource:	sp->sChIn = AChIn; break;
	default:		ETMPanic ("NewSource: unknown input source type");
	}
	sp->sFakeEof = fakeEof;
	sp->sPrev = curSrc;	/* link to head of source list */
	curSrc = sp;
	++inputLevel;
}

/*
 * Switch to another file without saving current file.  If this
 * fails, the caller should cause an exit; no effort should be
 * made to continue processing.
 *
 * THIS REALLY NEEDS TO UNWIND ALL ACTIVE INPUT SOURCES.
 */

int
SwitchFile (char *name)
{
	if (Debug (bugInputStack))
	{
		ETMMsg ("switch file <%s>",
			name == (char *) NULL ? "(stdin)" : name);
	}
	if (name == (char *) NULL)
		return (0);
	if (curSrc == (Source *) NULL)
		ETMPanic ("%sSwitchFile: no current source", FileInfo ());
	if (curSrc->sType != fileSource)
	{
		ETMMsg ("%scannot switch file: current input source not file",
							FileInfo ());
		return (0);
	}
	pushCount = 0;		/* throw away any pushback */
	if (freopen (name, "r", curSrc->sFile.filePtr) == (FILE *) NULL)
		return (0);
	/*
	 * Replace filename in file information structure
	 */
	Free (curSrc->sFile.fileName);
	curSrc->sFile.fileName = StrAlloc (name);
	/*
	 * Say previous char is end of line so we think we're at the
	 * beginning of a line initially.
	 */
	curSrc->sFile.prevChar = lf;
	curSrc->sFile.inputLine = 0;
	SetInputLineRegisters ((Param) 0);
	return (1);
}


/*
 * Switch to a file and save the current input source on a stack.
 *
 * If name is NULL, switch to stdin (this trick is used by main() when
 * there are no named input files, but should not be used otherwise).
 */

int
PushFile (char *name)
{
FILE	*f;

	if (Debug (bugInputStack))
		ETMMsg ("push file <%s>",
			name == (char *) NULL ? "(stdin)" : name);
	if (name == (char *) NULL)
	{
		f = stdin;
		name = "(stdin)";
	}
	else if ((f = fopen (name, "r")) == (FILE *) NULL)
		return (0);
	PushSource (fileSource, 0);
	curSrc->sFile.fileName = StrAlloc (name);
	curSrc->sFile.filePtr = f;
	/*
	 * Say previous char is end of line so we think we're at the
	 * beginning of a line initially.
	 */
	curSrc->sFile.prevChar = lf;
	curSrc->sFile.inputLine = 0;
	SetInputLineRegisters ((Param) 0);
	return (1);
}


/*
 * Set up a MacArg structure containing copies of a macro's name and
 * arguments.  Also initialize the structure's use count to one.
 */

static MacArg *
SetUpArguments (XChar *name, int argc, XChar *argv[])
{
MacArg	*ma;
int	i;

	ma = New (MacArg);
	ma->mName = XStrAlloc (name);
	ma->mArgc = argc;
	for (i = 0; i < ma->mArgc; i++)
		ma->mArgv[i] = XStrAlloc (argv[i]);
	ma->mRefCount = 1;
	return (ma);

}


/*
 * Release a macro's argument structure.  This decrements the use count
 * and returns if the count isn't zero (since that means some other
 * currently executing macro is using the structure).  If the count is
 * zero, deallocate the structure.
 */

static void
ReleaseArguments (MacArg *ma)
{
int	i;

	if (ma == (MacArg *) NULL)
		return;
	if (--ma->mRefCount > 0)
		return;
	XStrFree (ma->mName);
	for (i = 0; i < ma->mArgc; i++)
		XStrFree (ma->mArgv[i]);
	Free ((char *) ma);
}

/*
 * Switch to a macro and save the current input source on a stack.
 * Sets register ".$" to the number of arguments provided to the macro.
 * Also save a pointer to the previous macro input source and update
 * curMacSrc to point to the new macro source.
 *
 * If a macro invokes another macro by interpolating it as a string
 * (\*x, \*(xx, \*[xxx]), then, as in groff, the arguments of the outer
 * macro are not hidden to the inner macro.  This is implemented by
 * having the inner macro inherit the outer macro's argument information
 * structure (which includes \$0, the macro name).  It's not sufficient
 * to simply copy the arguments because .shift in the inner macro affects
 * the outer macro's arguments.
 *
 * We know that a macro was invoked as a string if argc is -1.  (See
 * EncodeEscape()).
 */

int
PushMacro (XChar *name, int argc, XChar *argv[])
{
Name	*np;
MacArg	*ma;
Object	*op;
int	i;

	if ((np = LookupName (name, macDef)) == (Name *) NULL)
		return (0);
	if (Debug (bugInputStack))
	{
		ETMMsg ("push macro <%s>", XStrToStr (name));
		for (i = 0; i < argc; i++)
			ETMMsg ("argument %d: <%s>", i, XStrToStr (argv[i]));
	}
	if (argc > maxMacroArgs)
	{
		ETMMsg ("%smacro passed too many args (>%d), extra ignored",
					FileInfo (),
					maxMacroArgs);
		argc = maxMacroArgs;
	}
	if (argc >= 0)
		ma = SetUpArguments (name, argc, argv);
	else
	{
		/*
		 * Pushing a string, or macro invoked as string.  Get pointer
		 * to argument structure from current macro so as to inherit
		 * its arguments.  (Make sure to set the pointer to NULL and
		 * make the arg count zero if there isn't any current macro
		 * or it doesn't have any argument information.)
		 */
		ma = (MacArg *) NULL;
		argc = 0;
		if (curMacSrc != (Source *) NULL)
		{
			ma = curMacSrc->sMacro.mArg;
			if (ma != (MacArg *) NULL)
			{
				argc = ma->mArgc;
				++ma->mRefCount;
			}
		}
	}

	op = np->nObject;

	/*
	 * The reference count of the macro's underlying object is incremented
	 * for each invocation of the macro, but it's only an approximation of
	 * recursion depth, since any aliases to the name increment the
	 * reference count as well.  But with a large value of maxMacRecursion,
	 * this shouldn't be a problem.
	 */
	if (op->oRefCount >= maxMacRecursion)
	{
		ETMMsg ("%smacro or string <%s> possible infinite recursion refused",
					FileInfo (), XStrToStr (name));
		return (0);
	}
	PushSource (macroSource, 0);
	GrabObject (op);
	curSrc->sMacro.objPtr = op;
	curSrc->sMacro.curPos = 0L;
	curSrc->sMacro.endPos = op->oMacro->macSiz;
	curSrc->sMacro.mArg = ma;
	curSrc->sMacro.prevMacSrc = curMacSrc;
	curMacSrc = curSrc;
	if (Debug (bugInputStack))
		ETMMsg (".$ set to %d", argc);
	StrSetRegisterValue (".$", (Param) argc);
	return (1);
}


/*
 * Switch to an unnamed string (not a string register), obtained, e.g.,
 * by macro argument or number register reference.
 *
 * Save the current input source on a stack.
 */

int
PushAnonString (XChar *s, short pauseAfter)
{
	if (Debug (bugInputStack))
		ETMMsg ("push anon-string <%s>", XStrToStr (s));
	PushSource (anonStringSource, pauseAfter);
	curSrc->sAnonString.anonStr = XStrAlloc (s);
	curSrc->sAnonString.curAPtr = curSrc->sAnonString.anonStr;
	return (1);
}


/*
 * Terminate current input source and resume the previous one if there is one.
 *
 * If the just-terminated was a macro, reset the .$ register to the value it
 * had before the macro was executed.
 *
 * If the just-terminated source was a macro, check whether or not it needs
 * to be deallocated.  This will be the case if the macro was redefined or
 * removed while executing, since removal has to be deferred until it
 * terminates then.
 */

static void
PopSource (void)
{
Source	*sp;
MacSrc	*ms;
Param	nargs;

	if (Debug (bugInputStack))
		ETMMsg ("pop input source");
	if (curSrc == (Source *) NULL)
		ETMPanic ("%sPopSource: logic error", FileInfo ());
	switch (curSrc->sType)
	{
	case fileSource:
		(void) fclose (curSrc->sFile.filePtr);
		Free (curSrc->sFile.fileName);
		break;
	case macroSource:
		ms = &curSrc->sMacro;
		/*
		 * Release this use of the macro to decrement its reference
		 * count (this also deallocates the macro object if the
		 * reference count goes to zero, e.g., if the macro removed
		 * itself).
		 */
		ReleaseObject (ms->objPtr);
		/*
		 * Release the name and the arguments
		 */
		ReleaseArguments (ms->mArg);
		/*
		 * Reset curMacSrc to the value it had before this macro
		 * was shoved on the input stack, and update .$ to the
		 * correct value for the previous macro.
		 */
		curMacSrc = ms->prevMacSrc;
		nargs = 0;
		if (curMacSrc != (Source *) NULL)
		{
			if (curMacSrc->sMacro.mArg != (MacArg *) NULL)
				nargs = curMacSrc->sMacro.mArg->mArgc;
		}
		StrSetRegisterValue (".$", nargs);
		break;
	case anonStringSource:
		XStrFree (curSrc->sAnonString.anonStr);
		break;
	default:
		ETMPanic ("PopSource: unknown input type");
	}
	returnEof = curSrc->sFakeEof;
	sp = curSrc;
	curSrc = sp->sPrev;	/* revert pointer to previous input source */
	Free ((char *) sp);
	--inputLevel;

	if (curSrc != (Source *) NULL && curSrc->sType == fileSource)
		SetInputLineRegisters (curSrc->sFile.inputLine);
}


/*
 * Return a pointer to one of the current macro's arguments, or NULL if
 * the argument is out of range or there is no currently executing macro
 * from which arguments can be taken.
 * n is the argument number, but indexing is skewed:
 * n is in the range 1..99, but arg 1..99 is in margv[0..98].
 * $0 is returned as the macro name, a la groff.
 *
 * Returns a pointer to a static area.
 */

static XChar *
MacroArgument (int n)
{
MacArg	*ma;
XChar	*p;

	if (curMacSrc == (Source *) NULL
		|| (ma = curMacSrc->sMacro.mArg) == (MacArg *) NULL
		|| n < 0
		|| n > ma->mArgc)
	{
		p = (XChar *) NULL;
	}
	else if (n == 0)			/* macro name */
		p = ma->mName;
	else					/* regular macro argument */
		p = ma->mArgv[n-1];
	if (Debug (bugInputStack))
	{
		ETMMsg ("macro arg %d is <%s>",
			n,
		 	p == (XChar *) NULL ? "(null)" : XStrToStr (p));
	}
	return (p);
}


void
ShiftMacroArguments (short n)
{
MacArg	*ma;
short	i;

	if (curMacSrc == (Source *) NULL)	/* not in macro */
		return;
	ma = curMacSrc->sMacro.mArg;
	if (ma == (MacArg *) NULL)		/* no argument information */
		return;
	if (n <= 0)				/* nothing to do */
		return;
	if (n > ma->mArgc)			/* don't shift too many */
		n = ma->mArgc;
	/*
	 * Deallocate the arguments that will be shifted off the arg array
	 */
	for (i = 0; i < n; i++)
		XStrFree (ma->mArgv[i]);
	/*
	 * Shift the other arguments down
	 */
	for (i = n; i < ma->mArgc; i++)
		ma->mArgv[i-n] = ma->mArgv[i];
	/*
	 * Decrement argument count and reset .$ register
	 */
	ma->mArgc -= n;
	StrSetRegisterValue (".$", (Param) ma->mArgc);
}


static void
WriteLineInfo (void)
{
char	buf[bufSiz];

	if (!writeLineInfo || curSrc->sType != fileSource)
		return;
	sprintf (buf, "line %s %ld",
		curSrc->sFile.fileName, curSrc->sFile.inputLine);
	ControlOut (buf);
}


/*
 * Return next character from file input.
 *
 * This routine also turns CR or CRLF into LF, so that text files
 * created on a Macintosh or MS-DOS machine can be read properly
 * without problem.  The pushback here is done using stdio mechanism
 * instead of UnChIn(), since the latter's at too high a level of
 * abstraction.
 */

static XChar
FChIn (void)
{
int	c, c2;
XChar	xc;

	for (;;)
	{
		/* map CR and CRLF to LF */
		if ((c = getc (curSrc->sFile.filePtr)) == cr)
		{
			/* have CR, look at next char, push back if not LF */
			if ((c2 = getc (curSrc->sFile.filePtr)) != lf)
				ungetc (c2, curSrc->sFile.filePtr);
			c = lf;
		}
		if (c == EOF)
			break;
		/* filter nulls */
		if (c != '\0')
			break;
	}
	if (curSrc->sFile.prevChar == lf)
	{
		++curSrc->sFile.inputLine;
		SetInputLineRegisters (curSrc->sFile.inputLine);
		WriteLineInfo ();
	}
	curSrc->sFile.prevChar = c;	/* save for next call */
	/*
	 * Convert stdio return value to XChar, taking care to map stdio
	 * end-of-file value to internal end-of-file value.
	 */
	xc = (c == EOF ? endOfInput : c);
	if (Debug (bugFChIn))
		PrintDebugChar ('F', xc);
	return (xc);
}


static XChar
MChIn (void)
{
MacSrc	*mp = &curSrc->sMacro;
XChar	c;

	if (mp->curPos < mp->endPos)
		c = mp->objPtr->oMacro->macBuf[mp->curPos++];
	else
		c = endOfInput;
	if (Debug (bugMChIn))
		PrintDebugChar ('M', c);
	return (c);
}


static XChar
AChIn (void)
{
XChar	c;

	if ((c = curSrc->sAnonString.curAPtr[0]) != (XChar) 0)
		++(curSrc->sAnonString.curAPtr);
	else
		c = endOfInput;
	if (Debug (bugAChIn))
		PrintDebugChar ('A', c);
	return (c);
}


/*
 * Return the next character from the current input source:
 * - If we're simulating EOF, return endOfInput
 * - If there is anything in the pushback queue for the current input
 * level, return next character from the queue.  (Checking the input
 * level avoids problems if you've pushed back a character looking for
 * the end of a reference, and then push a string that the reference
 * results in onto the stack; without the level, you'd return the pushed
 * back character before the string you just pushed!)
 * - Call the input function for the current source.  If current
 * source returns endOfInput, unwind to previous source and retry.
 */

static XChar
ChIn0 (void)
{
XChar	c = endOfInput;

	while (!returnEof && curSrc != (Source *) NULL)
	{
		if (pushCount > 0 && pushLevel[pushCount-1] == inputLevel)
		{
			c = (XChar) pushBuf[--pushCount];
			break;
		}
		if ((c = (*(curSrc->sChIn)) ()) != endOfInput)
			break;
		PopSource ();	/* resume reading previous source */
	}

	if (Debug (bugChIn0))
		PrintDebugChar ('0', c);
	return (c);
}


/*
 * Interpret a macro argument reference.  $* and $@ are special and
 * produce the concatenation of all the arguments separated by spaces
 * ($@ in addition double-quotes earch argument).  Otherwise the
 * reference must be $n, $(nn, or $[nnn], where n is a number.
 *
 * If there is no macro in the input stack from which arguments can be
 * taken, or the reference is malformed, the reference is simply ignored.
 */

static void
MacroArgumentReference (void)
{
MacArg	*ma;
char	buf[10], *bp;
XChar	c;
XChar	*p, *p2, *p3;
int	needQuote;
int	i, n, len;

	/*
	 * Get pointer to macro argument structure, but then parse the
	 * reference before checking whether the pointer is NULL.
	 * Otherwise the stuff after the \$ will appear literally in
	 * the output.
	 */
	ma = (MacArg *) NULL;
	if (curMacSrc != (Source *) NULL)
		ma = curMacSrc->sMacro.mArg;

	if ((c = ChPeek ()) == '*' || c == '@')
	{
		c = ChIn ();
		needQuote = (c == '@');
		if (ma == (MacArg *) NULL)
			return;
		n = ma->mArgc;
		if (n == 0)		/* no arguments */
			return;
		/*
		 * Construct a string that looks like:
		 * \$1 \$2 ... \$n		(for \$*)
		 * or
		 * "\$1" "\$2" ... "\$n"	(for \$@)
		 * and shove it on the input stack.  This way, the string
		 * itself is processed at a new input level, and when the
		 * embedded argument references are encountered, they'll
		 * be processed at a level above that of the string.
		 * Assume 10 chars is enough to encode any arg reference.
		 */
		len = n * 10;
		p = (XChar *) VAlloc (len, sizeof (XChar));
		p2 = p;
		for (i = 1; i <= n; i++)
		{
			if (i > 1)
				*p2++ = ' ';
			if (needQuote)
				*p2++ = '"';
			*p2++ = escChar;
			*p2++ = '$';
			if (i < 10)
				*p2++ = i + '0';
			else
			{
				sprintf (buf, "%d", i);
				bp = buf;
				*p2++ = '[';
				while (*bp != '\0')
					*p2++ = *bp++;
				*p2++ = ']';
			}
			if (needQuote)
				*p2++ = '"';
		}
		*p2 = '\0';
		if (p + len <= p2)
			ETMPanic ("MacroArgumentReference: logic error");
		PushAnonString (p, 0);
		return;
	}
	n = -1;
	if ((p = ParseNameRef ()) != (XChar *) NULL)
	{
		p2 = p;		/* save pointer so can free it later */
		n = 0;
		while (*p2 != '\0')
		{
			if (Digit (*p2))
				n = n * 10 + (*p2++ - '0');
			else
			{
				ETMMsg ("%snon-numeric macro argument reference: <%s>",
						FileInfo (),
						XStrToStr (p));
				n = -1;
				break;
			}
		}
		XStrFree (p);
	}
	if (n >= 0)
	{
		p = MacroArgument (n);
		if (p != (XChar *) NULL)
			PushAnonString (p, 0);
	}
}


/*
 * Process character following an escape.  If the sequence is one
 * such that it causes a new input source to be pushed, return '\0'.
 * Otherwise map the character to an escape or special char code if
 * necessary and return the result.  A '\0' return can also mean the
 * input sequence was malformed and ignored, so that the caller should
 * keep looking for input.
 *
 * Following are interpreted even in copy mode (see sec. 7.2):
 *
 * \n		pushes input to string representing number register value
 * \*		pushes input to string
 * \$		pushes input to macro argument (if a macro is executing)
 * \\		convert to single '\'
 * \t,\a	convert to tab, leader character
 * \.		convert to plain '.'
 *
 * Following are interpreted only when not in copy mode:
 *
 * \n		same as in copy mode
 * \*		same as in copy mode
 * \$		same as in copy mode
 * \(xx		convert to special character code
 * \[xxx]	convert to special character code
 * \w'str'	calculate width of str and interpolate into input stream.
 * 	everything else converted to escape code for the character
 *
 * These actions perhaps are not especially obvious from the code below.
 */

static XChar
EncodeEscape (XChar c)
{
Name	*np;
SpChar	*scp;
XChar	*p;
XChar	buf[bufSiz];
int	needQuote;
int	i, n, len;

	/*
	 * The following characters are interpolated whether
	 * or not copy mode is on.  Process them first.
	 */

	switch (c)
	{
	case 'n':
		/*
		 * This is pretty ugly because the value of the .z
		 * register is the NAME of the current diversion, not
		 * a number.  Ditto for .ev, which is the name of the
		 * current environment.
		 */
		if ((np = ParseRegisterRef ()) != (Name *) NULL)
		{
			if (strcmp (XStrToStr (np->nName), ".z") == 0)
				p = CurrentDiversion ();
			else if (strcmp (XStrToStr (np->nName), ".ev") == 0)
				p = CurrentEnvironmentName ();
			else
				p = FormatRegister (np->nRegister);
		}
		else
		{
			(void) XStrCpy (buf, StrToXStr ("0"));
			p = buf;
		}
		(void) PushAnonString (p, 0);
		return ('\0');
	case '*':
		/*
		 * String reference.
		 * Strings and macros share the same data structure.
		 * See the PushMacro() code for explanation of passing
		 * an argc value of -1.
		 */
		if ((p = ParseNameRef ()) != (XChar *) NULL)
		{
			if (LookupMacro (p) != (Macro *) NULL)
				(void) PushMacro (p, -1, (XChar **) NULL);
			XStrFree (p);
		}
		return ('\0');
	case '$':
		/*
		 * Macro argument reference.
		 */
		MacroArgumentReference ();
		return ('\0');
	}

	/*
	 * There are a few more characters to be interpreted if copy mode
	 * is on.  If in copy mode and the character is NOT interpreted,
	 * push it back so it'll be returned on the next input call, and
	 * return the escape character that preceded it.
	 */

	if (copyMode)
	{
		escStripped = 1;
		/*
		 * escChar must be tested separately; it's not a constant,
		 * so it can't be one of the cases in the switch below.
		 */
		if (c == escChar)
			return (c);
		switch (c)
		{
		case 't':
			return ('\t');
		case 'a':
			return (1);	/* ASCII SOH */
		case '.':
			return ('.');
		}
		/*
		 * The character following the escape isn't part of a
		 * recognized escape sequence.  Put the character back and
		 * return a literal escape character.
		 */
		escStripped = 0;
		UnChIn (c);
		return (escChar);
	}

	/*
	 * Not copy mode - return escape code for character except for
	 * special characters, \w, \A
	 */

	if (c == '(' || (!compatMode && c == '['))
	{
		UnChIn (c);
		if ((p = ParseNameRef ()) == (XChar *) NULL)
			return ('\0');
		if (!AsciiStr (p))
		{
			ETMMsg ("%swarning: illegal special character <%s>",
							FileInfo (),
							XStrToStr (p));
			XStrFree (p);
			return ('\0');
		}
		if ((scp = LookupSpCharByName (p)) == (SpChar *) NULL)
		{
			/*
			 * If special char doesn't exist, print a warning
			 * (unless processing an .if c request).  Then
			 * define the character with an empty value so no
			 * more warnings will be generated.
			 */
			if (!inCharTest)
			{
				ETMMsg ("%swarning: special character <%s> not defined",
							FileInfo (),
							XStrToStr (p));
			}
			buf[0] = '\0';	/* empty XChar string */
			(void) NewSpChar (p, buf);
			scp = LookupSpCharByName (p);
		}
		XStrFree (p);
		return (scp->spCode);
	}
	else if (c == 'w')
	{
		ParseWidth ();
		return ('\0');
	}
	else if (c == 'A')
	{
		ParseNameTest ();
		return ('\0');
	}
	return (ToEsc (c));
}



/*
 * Return next character from input stream, perhaps processing
 * escape sequence or pushing input first.  Returns endOfInput when
 * there is no more input or if AExit() has been called.
 */

XChar
ChIn (void)
{
static int eofCount = 0;
XChar	c;

	if (!allowInput)
		return (lastCharRead = endOfInput);

	escStripped = 0;
	for (;;)
	{
		if ((c = ChIn0 ()) == endOfInput)
			break;
		/*
		 * If next char is not an escape, it's just a normal
		 * character.
		 */
		if (c != escChar || !doEscapes)
			break;
		if ((c = ChIn0 ()) == endOfInput)	/* malformed */
			break;
		if (c == lf)		/* embedded newline, discard */
			continue;
		if (c == '"')		/* comment, discard until newline */
		{
			while (!Eol (ChIn0 ()))
			{
				/* gobble rest of line */
			}
			c = lf;
			break;
		}
		/*
		 * Encode the character following the escape if necessary.
		 * The return value cases are:
		 * '\0' - a new input source (\*, \$, \n) was countered
		 * and pushed on the input stack.  Loop to continue reading.
		 * \} - an input conditional level end was found.  Decrement
		 * the conditional level and loop to continue reading.
		 * Otherwise return the character
		 */
		if ((c = EncodeEscape (c)) != '\0')
		{
			if (c != ToEsc ('}'))	/* check for \} */
				break;
			--ifLevel;
		}
	}
	/*
	 * This bit of ugliness helps track down errors in input logic
	 * that results from some hunk of code running wild in a
	 * ChIn()/UnChIn() loop.
	 */
	if (c == endOfInput)
	{
		if (eofCount++ > 100)
			ETMPanic ("Program terminated, input logic error");
	}
	else
		eofCount = 0;

	if (Debug (bugChIn))
		PrintDebugChar (' ', c);
	return (lastCharRead = c);
}


/*
 * Peek at next input character and return it, without removing it
 * from the input stream.
 */

XChar
ChPeek (void)
{
XChar	c;

	UnChIn (c = ChIn ());
	return (c);
}


/*
 * Push a character back onto the current input source.
 *
 * endOfInput is allowed but is discarded.  This is so that callers don't
 * have to check whether or not they're pushing a "real" character.
 *
 * Escape codes and special characters are converted back into their
 * original multiple-character input sequences.  When these sequeuences are
 * reread, they'll be converted back into as appropriate.  This unpacking is
 * NECESSARY, because an escaped character might first be seen in non-copy
 * mode, then pushed back and reread in copy mode.  If the escape code
 * itself were pushed back, the character wouldn't be interpreted in
 * copy mode properly.
 */

void
UnChIn (XChar c)
{
char	*p;
int	len;

	if (c == endOfInput)
	{
		if (Debug (bugUnChIn))
			ETMMsg ("push %d <EOF> (discarded)", inputLevel);
		return;
	}
	p = XCharToStr (c);
	len = strlen (p);
	if (pushCount + len + (escStripped ? 1 : 0) > maxPushChar)
		ETMPanic ("UnChIn: character pushback limit exceeded");
	while (len > 0)
	{
		pushLevel[pushCount] = inputLevel;
		pushBuf[pushCount++] = *(p + --len);
	}
	if (escStripped)
	{
		pushLevel[pushCount] = inputLevel;
		pushBuf[pushCount++] = '\\';
	}
	escStripped = 0;
	if (Debug (bugUnChIn))
	{
	char	*s;

		s = (escStripped ? "\\" : "");
		if (c == lf)
			ETMMsg ("push %d <%slf>", inputLevel, s);
		else
			ETMMsg ("push %d <%s%s>", inputLevel, s, p);
	}
}
