/*
 * Should arg parsers take argument indicating whether not to fail
 * if no argument is present?  Or should they ALWAYS return 1, even if
 * the thing they're looking for isn't present or is malformed?
 *
 * parse.c
 *
 * Routines to parse request name from request line, several
 * different styles of request-argument parsing, and escape
 * sequence parsing.
 *
 * Argument parsers should skip whitespace before trying to parse
 * the argument(s).
 *
 * The action list for a request should specify the "eol" action
 * after all arguments have been parsed, in order to skip to the
 * end of the input line.
 *
 * All argument parsing routines leave their result in rargc/rargv.
 * However, the argument initialization stuff is done in
 * ParseRequestName(), since that must always be called before
 * any argument parsing is done anyway.  Better to call it once
 * than have to remember it in each parsing routine.
 *
 * Escape sequence parsers are called after the escape and the
 * character following have been read.
 *
 * All parsers must be careful not to run past the end of the input
 * line, and have to push back the linefeed if they read it.
 */

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

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

#include	"troffcvt.h"

#define	maxReqArgs	(maxMacroArgs+1)	/* req. name + 99 arguments */

#define	opEq		1
#define	opNe		2
#define	opLess		3
#define	opLessEq	4
#define	opGreater	5
#define	opGreaterEq	6
#define	opPlus		7
#define	opMinus		8
#define	opMultiply	9
#define	opDivide	10
#define	opMod		11
#define	opAnd		12
#define	opOr		13


typedef	struct ParamMap	ParamMap;

struct ParamMap
{
	char	*pName;
	Param	*pAddr;
};


static void	InitArgs (void);
static void	AddArgChar (XChar c);
static void	EndArg (void);
static void	ProcessRequest2 (short jiggerCompat);
static int	ParseRequestName (void);
static void	ParseNumberArg (int defUnit, int relAllowed, Param curVal);
static Param	ParseExpr (int defUnit);
static Param	ParseExprTerm (int defUnit);
static int	LooksLikeExpr (void);
static int	ParseCondition (void);
static int	ParseStrCondition (void);
static void	ProcessCondition (void);
static void	SkipCondition (void);



/*
 * Argument count and pointer vector (the request name is rargv[0]).
 * The arguments are pointers into a single buffer and should NOT be
 * written into by anything but InitArg()/AddArgChar()/EndArg().
 * InitArgs() is called only by ProcessRequest2(), and the other two
 * functions should be called only by argument parsing routines.
 */

static int	rargc = 0;		/* argument count */
static XChar	*rargv[maxReqArgs];	/* argument pointers */
static XChar	rargBuf[bufSiz];	/* argument buffer */
static XChar	*rpCur;			/* current argument being collected */
static int	rpIdx;			/* index past rpCur */
static int	rleft = 0;		/* chars left in buffer */


/*
 * These are the parameters which can be set relatively, and for
 * which there must be a way to refer to them symbolically.  This
 * map links the parameter symbolic name with the address of the
 * variable holding the parameter value.
 */

static ParamMap	paramMap [] =
{
	"point-size",		&curSize,
	"spacing",		&curVSize,
	"line-spacing",		&curLineSpacing,
	"indent",		&curIndent,
	"line-length",		&curLineLen,
	"page-length",		&curPageLen,
	"title-length",		&curTitleLen,
	"offset",		&curOffset,
	"page-number",		&curPageNum,
	(char *) NULL,		(Param *) NULL
};


/*
 * Read request line, parse request name and arguments, and execute any
 * actions associated the request.  Actually, all the work is done by
 * ProcessRequest2().  All that ProcessRequest() does is save any existing
 * request arguments before calling ProcessRequest2() and then restore
 * them afterward.  This prevents inner levels of recursive request
 * processing from destroying the arguments of outer levels.
 *
 * jiggerCompat is a hack for the groff .do request; it indicates whether
 * compatibility mode needs to be disabled before parsing the command
 * and reenabled after parsing it (but before executing it).
 */

void
ProcessRequest (short jiggerCompat)
{
int	_rargc;
XChar	*_rargv[maxReqArgs];
XChar	_rargBuf[bufSiz];

	_rargc = rargc;
	BCopy (rargv, _rargv, sizeof (rargv));
	BCopy (rargBuf, _rargBuf, sizeof (rargBuf));

	ProcessRequest2 (jiggerCompat);

	rargc = _rargc;
	BCopy (_rargv, rargv, sizeof (rargv));
	BCopy (_rargBuf, rargBuf, sizeof (rargBuf));
}


static void
ProcessRequest2 (short jiggerCompat)
{
Name	*np;
Object	*op;
Request	*rp;
XChar	*p;
Param	prevCompat;
int	i;

	if (jiggerCompat)	/* disable compat mode, save previous value */
		prevCompat = SetCompatMode (0);
	InitArgs ();

	/*
	 * Read and save the first character in a global variable; some
	 * requests need to know whether it was the break or no-break
	 * control character (e.g., . or `).
	 */
	curCtrlChar = ChIn ();
	if (!ParseRequestName ())	/* put request name in rargv[0] */
	{
		SkipToEol ();
		if (jiggerCompat)	/* restore previous compat mode */
			(void) SetCompatMode (prevCompat);
		return;
	}

	/*
	 * First see if request is a macro invocation.  If so, push macro
	 * onto input stack so that it will be executed.
	 */
	if (LookupMacro (rargv[0]) != (Macro *) NULL)
	{
		AParseMacroArgs (0, (XChar **) NULL);
		SkipToEol ();
		if (jiggerCompat)	/* restore previous compat mode */
			(void) SetCompatMode (prevCompat);
		(void) PushMacro (rargv[0], rargc-1, &rargv[1]);
		return;
	}

	if (Debug (bugReqProcess))
		ETMMsg ("Request: <%s>", XStrToStr (rargv[0]));
	if ((np = LookupName (rargv[0], reqDef)) == (Name *) NULL)
	/*if ((rp = LookupRequest (rargv[0])) == (Request *) NULL)*/
	{
		/* request not found; gobble rest of line and ignore */
		if (Debug (bugReqProcess))
			ETMMsg ("request <%s> not found", XStrToStr (rargv[0]));
		if (dumpBadReq)
		{
			FlushOText ();
			StrOut ("\\comment bad-req: ");
			StrOut (XCharToStr (curCtrlChar));
			for (p = rargv[0]; *p != '\0'; p++)
				StrOut (XCharToStr (*p));
			while (!Eol (ChPeek ()))
				StrOut (XCharToStr (ChIn ()));
			ChOut (lf);
		}
		SkipToEol ();
		if (jiggerCompat)	/* restore previous compat mode */
			(void) SetCompatMode (prevCompat);
		return;
	}
	if (Debug (bugReqProcess))
		ETMMsg ("processing <%s>", XStrToStr (rargv[0]));

	/*
	 * Perform actions necessary to parse the rest of the line.
	 * Skip to the end of the line to make sure it's gobbled from the
	 * input (in case parsing actions fail and quit reading the line
	 * early).  If parsing was successful, perform any actions that
	 * should be executed to carry out the request.
	 *
	 * There is a pitfall lurking here in that the parsing action list
	 * or the post-parsing action list might remove the request.  To
	 * prevent a request's action list from removing the request's data
	 * structure right out from under itself while it's executing, the
	 * reference count is incremented while the actions are being
	 * processed.  Have to get the object pointer here and work with
	 * that.  Can't work with the name pointer since the name might
	 * get blasted by the actions!
	 */
	op = np->nObject;
	GrabObject (op);
	rp = op->oRequest;
	i = ProcessActionList (rp->reqPActions);
	SkipToEol ();
	if (jiggerCompat)	/* restore previous compat mode */
		(void) SetCompatMode (prevCompat);
	if (i)
		(void) ProcessActionList (rp->reqPPActions);
	ReleaseObject (op);
}



/*
 * Initialize rargc to zero and set up pointers for stuffing
 * arguments in the argument buffer.
 */

static void
InitArgs (void)
{
	rargc = 0;
	rpCur = rargBuf;
	rpIdx = 0;
	rleft = sizeof (rargBuf) / sizeof (XChar);
}


/*
 * Add a character to the end of the current argument.  This
 * call does nothing if the maximum number of arguments has already
 * been parsed, or if there isn't enough room to add the character
 * and a trailing null byte.
 */

static void
AddArgChar (XChar c)
{
	if (rargc < maxReqArgs && rleft > 0)
	{
		rpCur[rpIdx++] = c;
		--rleft;
	}
}


/*
 * Call this function when the end of an argument has been seen.  It
 * bumps the arg count rargc and initializes the pointer to it in the
 * rargv array.  Print a message if argument buffer overflow occurred.
 */

static void
EndArg (void)
{
	/* make sure there's still room for trailing null */
	if (rargc < maxReqArgs && rleft > 0)
	{
		rpCur[rpIdx++] = '\0';	/* terminate current argument */
		rargv[rargc++] = rpCur;	/* save pointer to beginning of it */
		rpCur +=  rpIdx;	/* advance current argument pointer */
		rpIdx = 0;
	}
	else if (rargc == 0)	/* couldn't even parse first argument - yow! */
		ETMPanic ("arg buffer overflow while parsing request name!");
	else
	{
		ETMMsg ("request <%s>, arg buffer overflow, excess ignored",
						XStrToStr (rargv[0]));
	}
}


/*
 * Skip whitespace up to the request (or macro) name, and read one or two
 * characters to get the name.  Also initialize the argument buffer.
 *
 * The linefeed at the end of the line is NOT eaten by this function,
 * no matter what the return value.
 *
 * Request name can be up to four characters, if both request characters
 * are special chars (each is unpacked into two bytes).  The name can be
 * arbitrary length if long names are allowed (i.e., if compatibility mode
 * is not turned on).
 */

static int
ParseRequestName (void)
{
XChar	c;
int	i = 0;

	SkipWhite ();			/* ctrl char might have white after */
	if (Eol (ChPeek ()))
		return (0);		/* no request named */
	AddArgChar (ChIn ());
	if (!WhiteOrEol (ChPeek ()))	/* 2- or multi-character request */
	{
		AddArgChar (ChIn ());
		if (compatMode)
		{
			if (!WhiteOrEol (ChPeek()))
			{
				ETMMsg ("%swarning: ambiguous request name",
							FileInfo ());
			}
		}
		else			/* multi-character */
		{
			while (!WhiteOrEol (ChPeek ()))
				AddArgChar (ChIn ());
		}
	}
	EndArg ();
	return (1);
}


/*
 * Parse macro arguments, each consisting of strings of non-white
 * characters.  Terminated by end of line.  An argument that begins
 * with a double quote is parsed in quote mode until a closing quote.
 * Quoted arguments may contain whitespace.
 *
 * Quotes in arguments are handled as follows:
 * - If not in quote mode, just treat the quote as part of the argument.
 * - If in quote mode and the quote is doubled, it becomes one quote
 * character.  If the next character is not a quote, the quote is the
 * closing quote of a quoted argument.
 * - If not in compatibility mode, only a quote at the same input level
 * can terminate a quoted argument (thus, something like "\*[xx]", where
 * xx contains a quote, will be parsed correctly).
 *
 * If an escape occurs in an argument, grab the next character, too
 * (even if it's whitespace).
 */

int
AParseMacroArgs (argc, argv)
int	argc;
XChar	**argv;
{
XChar	c;
int	quote;
short	iLevel;

	CopyMode (1);			/* turn on copy mode */
	for (;;)
	{
		quote = 0;
		SkipWhite ();
		if (Eol (ChPeek ()))
			break;
		if (ChPeek () == '"')		/* quoted argument */
		{
			quote = 1;
			(void) ChIn ();
		}
		iLevel = ILevel ();
		while (!Eol (ChPeek ()))
		{
			c = ChIn ();
			if (c == '"' && iLevel == ILevel ())
			{
				/*
				 * If quote is true, we're in a quoted
				 * argument.  If the next char is another
				 * quote, it's a doubled quote, which
				 * turns into one quote.  If the next
				 * char isn't a quote, the terminating
				 * quote for the argument has been found.
				 * If quote isn't true, this is a quote in
				 * the middle of an unquoted argument and
				 * becomes part of the argument unchanged.
				 */
				if (quote)
				{
					if (ChPeek () == '"'
							&& iLevel == ILevel ())
						c = ChIn ();	/* "" -> " */
					else
						break;	/* end of quoted arg */
				}
			}
			else if (c == escChar)
			{
				AddArgChar(c);		/* add escape */
				c = ChIn ();		/* grab next char */
			}
			else if (!quote && White (c))	/* found word break */
				break;
			AddArgChar (c);
		}
		EndArg ();
	}
	CopyMode (0);			/* turn off copy mode */
	return (1);
}


/*
 * Look for a filename argument: skip any whitespace, then find
 * a sequence of non-white characters.  Reads through the end of
 * line character.
 */

int
AParseFileName (int argc, XChar **argv)
{
	SkipWhite ();
	if (!Eol (ChPeek ()))
	{
		while (!WhiteOrEol (ChPeek ()))
			AddArgChar (ChIn ());
	}
	EndArg ();
	return (1);
}


/*
 * Look for a (register, string, macro) name.  The name can be only one or
 * two characters in compatibility mode, longer otherwise.
 */

int
AParseName (int argc, XChar **argv)
{
XChar	c;

	SkipWhite ();
	if (!Eol (ChPeek ()))
	{
		AddArgChar (ChIn ());		/* first char */
		if (!WhiteOrEol (ChPeek ()))
		{
			AddArgChar (ChIn ());	/* second char */
			if (!compatMode)	/* allow third, etc. */
			{
				while (!WhiteOrEol (c = ChIn ()))
					AddArgChar (c);
				UnChIn (c);
			}
		}
	}
	EndArg ();
	return (1);
}

int
AParseNames (int argc, XChar **argv)
{
	for (;;)
	{
		SkipWhite ();
		if (Eol (ChPeek ()))
			break;
		AParseName (0, (XChar **) NULL);
	}
	return (1);
}


/*
 * Multiple-name equivalent of AParseName
 */



/*
 * Skip whitespace, then parse a string to the end of the input
 * line in copy mode.  The argument is 'y' if a leading double
 * quote should be recognized and stripped (as for .ds) or not
 * (as for .tm and .ab).
 */

int
AParseStringValue (int argc, XChar **argv)
{
	SkipWhite ();
	if (argv[0][0] == 'y' && ChPeek () == '"')
		(void) ChIn ();
	CopyMode (1);			/* turn on copy mode */
	while (!Eol (ChPeek ()))
		AddArgChar (ChIn ());
	EndArg ();
	CopyMode (0);			/* turn off copy mode */
	return (1);
}


/*
 * Parse one of those three-part title things: three title parts,
 * delimited by the first non-white character following the request
 * name.  Any part may be empty/missing.  Stop on end of line.  The
 * three parts go into rargv as usual.  rargc will indicate the number
 * of parts actually found.
 */

int
AParseTitle (int argc, XChar **argv)
{
int	i;
XChar	delim, c;
short	iLevel;

	SkipWhite ();
	if (!Eol (ChPeek ()))
	{
		delim = ChIn ();
		iLevel = ILevel ();
		for (i = 0; i < 3; i++)
		{
			while (1)
			{
				c = ChIn ();
				if (Eol (c))
				{
					UnChIn (c);
					break;
				}
				if (c == delim && iLevel == ILevel ())
					break;
				AddArgChar (c);
			}
			EndArg ();
			if (Eol (ChPeek ()))
				break;
		}
	}
	return (1);
}


/*
 * Parse a single-character argument.
 */

int
AParseChar (int argc, XChar **argv)
{
	SkipWhite ();
	if (!Eol (ChPeek ()))
	{
		AddArgChar (ChIn ());
		EndArg ();
	}
	return (1);
}


/*
 * Parse number as an absolute quantity and convert back to string
 * form for placement in the argument vector.
 *
 * argv[0] is default units indicator.
 */

int
AParseNumber (int argc, XChar **argv)
{
	SkipWhite ();
	ParseNumberArg ((int) argv[0][0], 0, (Param) 0);
	return (1);
}


/*
 * Parse number as an absolute or relative quantity and convert back
 * to string form for placement in the argument vector.
 *
 * argv[0] is default units indicator, argv[1] is the value against
 * which relative values should be taken (zero if they must be absolute).
 * argv[1] may be a symbolic name to represent the current setting
 * for some parameter, a literal number, or, if it begins with \,
 * the name of a number register (use the register value).  The latter
 * is for use with .nr, where the initial value argument can be relative
 * to the register's current value.
 */

int
AParseARNumber (int argc, XChar **argv)
{
ParamMap	*pm;
Param	current;
char	buf[bufSiz];

	(void) strcpy (buf, XStrToStr (argv[1]));
	if (argv[1][0] == '\\')
		current = GetRegisterValue (&argv[1][1]);
	else if (!AsciiStr (argv[1]))
	{
		ETMMsg ("%sAParseARNumber: non-ASCII argument <%s>",
						FileInfo (),
						buf);
	}
	else
	{
		for (pm = paramMap; pm->pName != (char *) NULL; pm++)
		{
			if (strcmp (pm->pName, buf) == 0)
				break;
		}
		/* probably should test that it's really all digits... */
		if (pm->pName == (char *) NULL)
			current = (Param) StrToLong (buf);
		else
			current = *(pm->pAddr);
	}
	SkipWhite ();
	ParseNumberArg ((int) argv[0][0], 1, current);
	return (1);
}


/*
 * Parse number and put it into the current argument, in basic units
 * if need be.  First argument is the default units, the second is
 * non-zero if the number can be a relative quantity, zero if it must
 * be absolute.  If it can be relative, the third argument is the
 * current value, which is adjusted by the value of the number parsed.
 *
 * If the input doesn't look like it could be a number, the argument
 * is left empty.
 */

static void
ParseNumberArg (int defUnit, int relAllowed, Param curVal)
{
char	buf[bufSiz], *p;
Param	val;

	if (ParseNumber (defUnit, relAllowed, curVal, &val))
	{
		sprintf (buf, "%ld", val);
		for (p = buf; *p != '\0'; p++)
			AddArgChar (*p);
	}
	EndArg ();
}


/*
 * Parse number and return it, in basic units if need be.  Return value
 * is zero if no number is found, else non-zero and the number value is
 * placed in val.
 *
 * First argument is the default units, the second is non-zero if the
 * number can be a relative quantity, zero if it must be absolute.
 * If it can be relative, the third argument is the current value,
 * which is adjusted by the value of the number parsed.
 *
 * Expressions that specify distance to an absolute position ("|N")
 * evaluate to zero, since the current position is unknown.
 */

int
ParseNumber (int defUnit, int relAllowed, Param curVal, Param *val)
{
XChar	inc = 0;
int	dist = 0;

	if (!LooksLikeExpr ())
		return (0);

	if (relAllowed && Sign (ChPeek ()))
		inc = ChIn ();
	else if (ChPeek () == '|')
	{
		(void) ChIn ();
		dist = 1;
	}

	*val = ParseExpr (defUnit);

	/* inc is 0 if !relAllowed... */
	if (inc == '+')
		*val = curVal + *val;
	else if (inc == '-')
		*val = curVal - *val;

	if (dist)
		*val = 0;

	return (1);
}


/*
 * Parse a numeric expression: term { op term { op term ... } }.
 * Evaluation is left to right, except as overridden by parens.
 */

static Param
ParseExpr (int defUnit)
{
Param	val, val2;
int	op;
XChar	c;

	val = ParseExprTerm (defUnit);

	/*
	 * Look for op expr now
	 */

	for (;;)
	{
		switch (c = ChIn ())
		{
		default:		/* no operator; end of expr */
			UnChIn (c);
			return (val);
		case '+':
			op = opPlus;
			break;
		case '-':
			op = opMinus;
			break;
		case '/':
			op = opDivide;
			break;
		case '*':
			op = opMultiply;
			break;
		case '%':
			op = opMod;
			break;
		case '&':
			op = opAnd;
			break;
		case ':':
			op = opOr;
			break;
		case '<':			/* < or <= */
			if (ChPeek () != '=')
				op = opLess;
			else
			{
				(void) ChIn ();
				op = opLessEq;
			}
			break;
		case '>':			/* > or >= */
			if (ChPeek () != '=')
				op = opGreater;
			else
			{
				(void) ChIn ();
				op = opGreaterEq;
			}
			break;
		case '=':			/* = or == */
			if (ChPeek () == '=')
				(void) ChIn ();
			op = opEq;
			break;
		}
		val2 = ParseExprTerm (defUnit);
		switch (op)
		{
		default:
			ETMPanic ("%sParseExpr: logic error, op = %d",
							FileInfo (),
							op);
		case opPlus:
			val += val2;
			break;
		case opMinus:
			val -= val2;
			break;
		case opDivide:
			if (val2 == 0)
			{
				ETMMsg ("%sdivide by zero detected",
							FileInfo ());
				val = 0;
			}
			else
				val /= val2;
			break;
		case opMultiply:
			val *= val2;
			break;
		case opMod:
			val %= val2;
			break;
		case opAnd:
			val = (val && val2);
			break;
		case opOr:
			val = (val || val2);
			break;
		case opLess:
			val = (val < val2);
			break;
		case opLessEq:
			val = (val <= val2);
			break;
		case opGreater:
			val = (val > val2);
			break;
		case opGreaterEq:
			val = (val >= val2);
			break;
		case opEq:
			val = (val == val2);
			break;
		}
	}
}


/*
 * Parse a single term of an expression, either a number or "(expr)"
 */

static Param
ParseExprTerm (int defUnit)
{
double	val = 0.0, mult;
int	sign = 1;
int	unit;

	if (ChPeek () == '(')		/* ( expr ) - recurse */
	{
		(void) ChIn ();
		val = ParseExpr (defUnit);
		if (ChPeek () == ')')	/* ')' should be there, but */
			(void) ChIn ();	/* don't be too fussy :-) */
		return (Units (val, 'u'));
	}

	while (Sign (ChPeek ()))
		sign *= (ChIn () == '+' ? 1 : -1);

	while (Digit (ChPeek ()))
		val = val * 10 + ChIn () - '0';

	if (ChPeek () == '.')
	{
		(void) ChIn ();
		mult = .1;
		while (Digit (ChPeek ()))
		{
			val += (ChIn () - '0') * mult;
			mult /= 10.0;
		}
	}

	unit = (UnitChar (ChPeek ()) ? ChIn () : defUnit);
	if (defUnit == 'x')
		unit = 'x';

	return (Units (val, unit) * sign);
}


/*
 * Look at the next character to see whether it looks like it
 * could begin a numeric expression.
 */

static int
LooksLikeExpr (void)
{
XChar	c = ChPeek ();

	return (Digit (c) || Sign (c) || c == '.' || c == '(' || c == '|');
}


/*
 * Parse tab stops.  This writes out the results as it parses;
 * the stops are not maintained internally at all.
 */

int
AParseTabStops (int argc, XChar **argv)
{
Param	pos[maxTabStops];
char	type[maxTabStops];
int	i;
Param	prev = 0;

	for (i = 0; i < maxTabStops; i++)
	{
		SkipWhite ();
		if (!ParseNumber ('m', 1, prev, &pos[i]))
			break;
		type[i] = 'l';
		switch (ChPeek ())
		{
		case 'C':
			type[i] = 'c';
			(void) ChIn ();
			break;
		case 'R':
			type[i] = 'r';
			(void) ChIn ();
			break;
		}
		prev = pos[i];
	}
	SetTabStops ((Param) i, pos, type);

	return (1);
}


/*
 * Parse "if" condition.  argv[0][0] is 'y' if an .el should
 * be expected afterward.
 */

int
AParseCondition (int argc, XChar **argv)
{
int	result = 0;

	if ((result = ParseCondition ()) <= 0)	/* malformed or false */
		SkipCondition ();
	else if (result > 0)			/* condition true */
		ProcessCondition ();

	/*
	 * It's necessary to put the last (end-of-line) char back into the
	 * input since the request processor is going to look for eol
	 * after calling the argument processing actions for the current
	 * .if/.ie request.  Without this, the next input line would be
	 * lost.  A subterfuge.  (Push back lastCharRead since the last
	 * char can be either a newline or endOfInput; pushing back newline
	 * if the char was really endOfInput would result in the appearance
	 * of an extra blank line in the input.)
	 */

	UnChIn (lastCharRead);

	/*
	 * Save result for following .el if this was .ie request.
	 * result < 0 and result > 0 are both considered successes.
	 * This causes the .el to be skipped both if the test was
	 * malformed, or true.  If this was an .if (not .ie), then
	 * set the result true so that any (illegal) following .el
	 * gets skipped.
	 */

	if (argv[0][0] == 'n')		/* was .if, not .ie */
		ifResult = 1;
	else if (result != 0)		/* .ie succeeded */
		ifResult = 1;
	else				/* .ie failed */
		ifResult = 0;
	return (1);
}


/*
 * Parse the condition in preparation for processing the
 * stuff contingent on the condition.  Some tests are impossible
 * to do, e.g., whether the current page is even or odd.  In
 * such cases, assume true.
 *
 * Return value:
 * 1	condition true
 * 0	condition false
 * -1	error (malformed conditional)
 */

static int
ParseCondition (void)
{
XChar	buf[bufSiz], *p;
XChar	c;
SpChar	*sp;
int	reverse = 0;
int	result = 0;
int	type;

	SkipWhite ();
	if (ChPeek () == '!')
	{
		(void) ChIn ();
		reverse = 1;
	}
	if (WhiteOrEol (ChPeek ()))
		return (-1);
	c = ChIn ();
	if (c == 't')
		result = troff;
	else if (c == 'n')
		result = !troff;
	else if (c == 'e' || c == 'o')	/* can't tell - yucko */
		result = 1;
	else if (c == 'c')
	{
		inCharTest = 1;		/* suppress normal warning message */
		SkipWhite ();
		if (Eol (ChPeek ()))
			result = -1;
		else
		{
			/*
			 * The character will exist unless it's a special
			 * character reference for which the value is empty.
			 * That signifies a special character that was
			 * referenced but was never defined in an action file.
			 */
			result = 1;
			c = ChIn ();
			if (Special (c))
			{
				sp = LookupSpCharByCode (c);
				if (sp->spValue[0] == '\0')
					result = 0;
			}
		}
		inCharTest = 0;
	}
	else if (c == 'd' || c == 'r')
	{
		type = (c == 'd' ? reqDef|macDef : regDef);
		SkipWhite ();
		p = buf;
		while (!WhiteOrEol (ChPeek ()))
			*p++ = ChIn ();
		*p = '\0';
		result = (LookupName (buf, type) != (Name *) NULL);
	}
	else
	{
		UnChIn (c);
		if (LooksLikeExpr ())
			result = (ParseExpr ('u') > 0);
		else
			result = ParseStrCondition ();
	}
	if (result < 0)
		return (-1);
	return (reverse ? !result : result);
}


/*
 * Parse a string-valued conditional
 *
 * Return value:
 * 1	condition true
 * 0	condition false
 * -1	error (malformed conditional)
 */

static int
ParseStrCondition (void)
{
XChar	delim, c;
XChar	buf[bufSiz], *p;
short	iLevel;

	delim = ChIn ();
	iLevel = ILevel ();
	p = buf;
	while (!Eol (c = ChIn ()))	/* find first string */
	{
		if (c == delim && iLevel == ILevel ())
			break;
		*p++ = c;
	}
	if (Eol (c))		/* no terminating delim - malformed */
	{
		UnChIn (c);
		return (-1);
	}
	*p = '\0';
	p = buf;
	while (!Eol (c = ChIn ()))	/* find second string */
	{
		if (c == delim && iLevel == ILevel ())	/* end of second */
			return (*p == '\0');		/* end of first? */
		if (c != *p++)				/* mismatch */
			break;
	}
	/*
	 * At this point, have either seen end of line without
	 * terminating delimiter (malformed condition) or a
	 * mismatch in the strings and need to look for closing
	 * delimiter of second string.
	 */
	if (Eol (c))		/* no terminating delim - malformed */
	{
		UnChIn (c);
		return (-1);
	}
	while (!Eol (c = ChIn ()))
	{
		if (c == delim && iLevel == ILevel ())
			return (0);
	}
	UnChIn (c);
	return (-1);
}


/*
 * This is used for .el clauses.  Look at the result of the last
 * .ie to see whether to process or skip the clause.  If it's
 * processed, set ifResult so that any further .el's without an
 * intervening .ie (shouldn't happen, but you never know) are
 * skipped.
 */

int
AProcessCondition (int argc, XChar **argv)
{
	if (Debug (bugConditional))
		ETMMsg ("el: %s condition", ifResult ? "skip" : "process");
	if (ifResult)			/* .ie succeeded, skip .el */
		SkipCondition ();
	else				/* .ie failed, accept .el */
	{
		ProcessCondition ();
		ifResult = 1;
	}
	UnChIn (lastCharRead);	/* <--see AParseCondition() for explanation */
	return (1);
}


static void
ProcessCondition ()
{
int	level;

	if (Debug (bugConditional))
		ETMMsg ("process condition");
	SkipWhite ();
	if (ChPeek () != ToEsc ('{'))	/* single line */
	{
		if (Debug (bugConditional))
			ETMMsg ("process single-line condition");
		(void) ProcessLine ();
	}
	else				/* \{ ... \} */
	{
		if (Debug (bugConditional))
			ETMMsg ("process multiple-line condition");
		(void) ChIn ();
		level = ifLevel++;
		while (level < ifLevel)
		{
			if (Debug (bugConditional))
				ETMMsg ("current level %d, seeking %d",
							ifLevel, level);
			if (!ProcessLine ())
			{
				ETMPanic ("%sEOF looking for conditional end",
								FileInfo ());
			}
		}
		if (Debug (bugConditional))
			ETMMsg ("found level %d", level);
	}
	if (Debug (bugConditional))
		ETMMsg ("done processing");
}


static void
SkipCondition (void)
{
int	ifBeg = ToEsc ('{');
int	level;
XChar	c;

	if (Debug (bugConditional))
		ETMMsg ("skip condition");
	while (!Eol (c = ChIn ()) && c != ifBeg)
	{
		/* spin wheels */
	}
	if (Eol (c))
	{
		if (Debug (bugConditional))
			ETMMsg ("skip single-line condition");
		return;
	}
	if (Debug (bugConditional))
		ETMMsg ("skip multiple-line condition");
	level = ifLevel++;
	while (level < ifLevel)
	{
		if (Debug (bugConditional) && Eol (c))
			ETMMsg ("current level %d, seeking %d",
							ifLevel, level);
		/*
		 * Must check level *again* here because ChIn() might
		 * change it!  If last line of input has closing \}, it
		 * ends the loop, even if it's missing a newline.
		 */
		if ((c = ChIn ()) == endOfInput && level < ifLevel)
		{
			ETMPanic ("%sEOF looking for conditional end",
							FileInfo ());
		}
		if (c == ifBeg)
			++ifLevel;
	}
	if (Debug (bugConditional))
		ETMMsg ("found level %d", level);
	while (!Eol (c))
		c = ChIn ();
	if (Debug (bugConditional))
		ETMMsg ("done skipping");
}


/*
 * This is a hack for .bd request, which can be in two formats,
 * ".bd F N" or ".bd S F N".
 */

int
AParseEmbolden (int argc, XChar **argv)
{
	SkipWhite ();
	while (!WhiteOrEol (ChPeek ()))	/* parse first argument */
		AddArgChar (ChIn ());
	EndArg ();
	/*
	 * If first argument is "S", the request is ".bd S F N", otherwise
	 * it's ".bd F N".
	 */
	if (strcmp ("S", XStrToStr (rargv[rargc-1])) == 0)
	{
		SkipWhite ();
		while (!WhiteOrEol (ChPeek ()))
			AddArgChar (ChIn ());
		EndArg ();
	}
	SkipWhite ();
	ParseNumberArg ('x', 0, 0);	/* parse smear value */
	return (1);
}


/*
 * Parse transliteration list.
 */

int
AParseTransList (int argc, XChar **argv)
{
	SkipWhite ();
	while (!Eol (ChPeek ()))
		AddArgChar (ChIn ());
	EndArg ();
	return (1);
}


/*
 * Handle the .do request:
 * - Skip whitespace
 * - "Unread" a control character; ".do xxx" doesn't expect one before the
 * "xxx" part, but ProcessRequest() needs one.  Putting a fake control
 * character into the input fools ProcessRequest().  Use the character
 * that was read before the "do", so that "'do" can be used to pass the
 * no-break control character to the request.
 * - Call ProcessRequest(), telling it to disable compatibility mode while
 * parsing the request.
 * - Shove the last input character read back on the input (see
 * AParseCondition() for explanation, it's the same reason here).
 */

int
AProcessDo (int argc, XChar **argv)
{
	SkipWhite ();
	if (Eol (ChPeek ()))	/* there's no request to process */
		return (1);
	UnChIn (ctrlChar);
	ProcessRequest (1);
	UnChIn (lastCharRead);	/* <--see AParseCondition() for explanation */
	return (1);
}


/*
 * Interpret an action argument.  Returns a pointer into a static
 * area, which should be copied out if the caller needs to hang onto
 * it for a while.
 *
 * Instances of $n (n=1..9) are replaced by the appropriate request
 * argument.  $$ is replaced by the number of request arguments.  $*
 * is replaced by all the arguments, concatenated and space-separated.
 * $@ is replaced by all the arguments, double-quotes, concatenated,
 * and space-separated.  \n and \t are replaced by linefeed and tab,
 * respectively.  Other backslash sequences just have the initial
 * backslash stripped.  (This means that to include a backslash,
 * you should double it.)
 *
 * The request name is in rargv[0], but doesn't count as an argument,
 * so $$ is really rargc-1, and $n is rargv[n].
 */

XChar *
InterpretActionArg (XChar *p)
{
static XChar	xbuf[bufSiz];
XChar	*xp = xbuf;
char	cbuf[bufSiz];
char	*cp;
XChar	c;
int	needQuote;
int	i;

	if (p == NULL)
		ETMPanic ("%sInterpretActionArg: logic error", FileInfo ());
	while ((c = *p++) != 0)
	{
		if (c == '$')		/* substitute request argument */
		{
			if ((c = *p++) == '\0')
				break;		/* malformed */
			if (c == '$')
			{
				sprintf (cbuf, "%d", rargc-1);
				cp = cbuf;
				while (*cp != '\0')
					*xp++ = *cp++;
			}
			else if (Digit (c))
			{
				if ((i = c - '0') > 0 && i < rargc)
				{
					(void) XStrCpy (xp, rargv[i]);
					xp += XStrLen (xp);
				}
			}
			else if (c == '*' || c == '@')
			{
				needQuote = (c == '@');
				for (i = 1; i < rargc; i++)
				{
					if (i > 0)
						*xp++ = ' ';
					if (needQuote)
						*xp++ = '"';
					(void) XStrCpy (xp, rargv[i]);
					xp += XStrLen (xp);
					if (needQuote)
						*xp++ = '"';
				}
			}
			continue;
		}
		if (c == '\\')		/* strip escape, process next char */
		{
			if ((c = *p++) == '\0')
				break;		/* malformed */
			switch (c)
			{
			case 'n': c = lf; break;
			case 't': c = '\t'; break;
			/* otherwise leave alone */
			}
		}
		*xp++ = c;
	}
	*xp = '\0';
	return (xbuf);
}


/*	--------------------------------------------------	*/
/*	Escape sequence interpreters				*/
/*	--------------------------------------------------	*/



/*
 * Look for sequence following \s.  Anything illegal is treated
 * like \s0 (e.g., \sx)
 *
 * \s0			revert to previous
 * \s-n			decrease current by 1-9 (n = single digit)
 * \s+n			increase current by 1-9 (n = single digit)
 * \sn			set to 1-9 (n = single digit)
 * \snn			set to 10..36, where nn is two digits
 *
 * That last line require comment:
 * \s can be followed by a two-digit number <= 36; the number is
 * interpreted as an absolute change.  (Original C/A/T phototypesetter
 * had sizes up to 36.  Original troff would take \s36 as .ps 36 and
 * \s37 as .ps 3 followed by "7".  What a pain.)
 *
 * The \s'nnn' and \s[nnn] constructions allowed by groff are not accepted,
 * because those refer to scaled points, and troffcvt has no notion of
 * scaled points.
 */

Param
ParseSizeRef (void)
{
XChar	c;
double	inc = 0.0;	/* 0 = abs, -1 = rel. dec., +1 = rel. inc. */
Param	val;

	c = ChPeek ();
	if (c == '0')			/* restore previous */
	{
		(void) ChIn ();
		return (prevSize);
	}
	if (Sign (c))			/* relative change */
	{
		(void) ChIn ();
		inc = (c == '+' ? 1.0 : -1.0);
		if (!Digit (ChPeek ()))	/* malformed */
			return (prevSize);
		/* fails on FreeBSD unless the (double) is there... weird */
		return (curSize + Units (((double) (ChIn () - '0')) * inc, 'x'));
	}

	/* one- or two-digit absolute setting */

	if (!Digit (c))			/* malformed */
		return (prevSize);
	val = ChIn () - '0';
	if (!Digit (c = ChPeek ()))		/* one-digit */
		return (Units (val, 'x'));
	/* two-digit, but don't eat second unless <= 36 */
	c -= '0';
	if (val < 3 || (val == 3 && c <= 6))
	{
		val = val * 10 + c;
		(void) ChIn ();
	}
	return (Units (val, 'x'));
}


/*
 * Parse a register reference following \n.  Return pointer to
 * name definition structure if it was found, NULL on malformed
 * reference.  (Returns a pointer to name definition rather than
 * to register structure, because the caller needs to get at the
 * name.)
 *
 * \nx
 * \n(xx
 * \n+x,\n+(xx, \n+[xxx]
 * \n-x,\n-(xx, \n-[xxx]
 */

Name *
ParseRegisterRef (void)
{
Name		*np;
Register	*rp;
XChar	*p;
int	inc = 0;
XChar	c;

	c = ChPeek ();
	if (Eol (c))
		return ((Name *) NULL);
	if (Sign (c))			/* auto inc/dec */
	{
		(void) ChIn ();		/* skip inc/dec char */
		inc = (c == '+' ? 1 : -1);
	}

	/* now positioned at x or (xx or [xxx] */

	if ((p = ParseNameRef ()) == (XChar *) NULL)
		return ((Name *) NULL);

	if ((np = LookupName (p, regDef)) != (Name *) NULL)
	{
		rp = np->nRegister;
		/* auto inc/dec if necessary */
		if (inc && rp->regReadOnly)
			ETMMsg ("%sattempt to set readonly register <%s> ignored",
							FileInfo (),
							XStrToStr (p));
		else
			rp->regValue += (inc * rp->regIncrement);
	}
	XStrFree (p);
	return (np);
}



/*
 * Look for a single, double, or multiple character reference.  Used
 * for references to fonts ("\f"), strings ("\*"), number registers ("\n"),
 * macro references ("\$"), and special characters ("\(").
 *
 * The name can be a single character, two characters preceded by "(",
 * or, if compatibility mode is not on, multiple characters surrounded
 * by "[" and "]".  Long ([...] names are used by groff and possibly
 * other extended versions of troff.
 *
 * This HAS to allocate memory and can't use a static buffer because
 * it can be called recursively via ChIn() (e.g., .nr \(ts\(ts ....).
 * Caller MUST free return value when done with it.
 */

XChar *
ParseNameRef (void)
{
XChar	regName[bufSiz];
XChar	c;
short	iLevel = ILevel ();
int	i = 0;

	if (WhiteOrEol (ChPeek ()))		/* malformed */
		return ((XChar *) NULL);
	c = ChIn ();
	if (!compatMode && c == '[')		/* multiple-character name */
	{
		for (;;)
		{
			c = ChIn();
			/*
			 * Look for trailing ] that terminates the name, but
			 * silently accept whitespace or eol as the end of
			 * the name, even if the closing ] hasn't been seen.
			 */
			if ((c == ']' && iLevel == ILevel ())
						|| WhiteOrEol (c))
			{
				if (i == 0)	/* empty name; malformed */
					return ((XChar *) NULL);
				regName[i] = '\0';
				return (XStrAlloc (regName));
			}
			regName[i++] = c;
		}
		/*NOTREACHED*/
	}
	else if (c == '(')		/* double-character name */
	{
		/* read next two characters */

		if (WhiteOrEol (ChPeek ()))		/* malformed */
			return ((XChar *) NULL);
		regName[i++] = ChIn ();
		/* allow one-character name even though looking for two... */
		if (!WhiteOrEol (ChPeek ()))
			regName[i++] = ChIn ();
		regName[i] = '\0';
		return (XStrAlloc (regName));
	}
	else				/* single-character name */
	{
		regName[i++] = c;
		regName[i] = '\0';
		return (XStrAlloc (regName));
	}
}


/*
 * Parse a sequence of the form \w'string', calculate the width
 * of the string, convert that number to string form and switch
 * the input to it.  The \w will already have been seen.
 *
 * The width is crudely approximated as the number of characters
 * in the string times the width of an en.
 *
 * Size and font changes are recognized but skipped.
 *
 * The width string is put into the input pushback to avoid problems
 * with the order in which any trailing peeked-at character and the
 * width string will be returned later.
 */

void
ParseWidth (void)
{
char	buf[bufSiz], *p;
XChar	*xp;
XChar	c, delim;
Param	wid = 0;
short	iLevel;

	delim = ChIn ();
	iLevel = ILevel ();
	if (Eol (delim))		/* malformed */
		UnChIn (delim);
	else while (1)
	{
		c = ChIn ();
		if (Eol (c))			/* malformed */
		{
			UnChIn (c);
			break;
		}
		if (c == delim && iLevel == ILevel ())
			break;
		if (c == ToEsc ('s'))		/* size change */
			(void) ParseSizeRef ();
		else if (c == ToEsc ('f'))	/* font change */
			XStrFree (ParseNameRef ());	/* gobble/ignore ref */
		else
			++wid;
	}

	/* figure width in ens, convert to string, push back onto input */
	sprintf (buf, "%ld", Units (wid, 'n'));
	xp = XStrAlloc (StrToXStr (buf));
	PushAnonString (xp, 0);
	XStrFree (xp);
}


/*
 * Parse a sequence of the form \A'string' and shove 1 or 0 on the
 * input depending on whether or not string is a value register, macro,
 * font, etc. name.
 *
 * This is a groff construct.
 */

void
ParseNameTest (void)
{
char	buf[bufSiz], *p = buf;
XChar	c, delim, result[2];
short	len = 0, valid = 1;
short	iLevel;

	/*
	 * Start out by assuming the name will be valid, but mark it invalid
	 * if an illegal character is found in the name.  Also mark the name
	 * invalid if it turns out to be zero-length.
	 */

	delim = ChIn ();
	iLevel = ILevel ();
	if (Eol (delim))		/* malformed */
		UnChIn (delim);
	else while (1)
	{
		c = ChIn ();
		if (Eol (c))			/* malformed */
		{
			UnChIn (c);
			break;
		}
		if (c == delim && iLevel == ILevel ())
			break;
		++len;
		/* I dunno why groff allows \, but it does... */
		if (c != ToEsc ('\\') && (White (c) || Esc(c) || Special (c)))
			valid = 0;
	}
	if (len == 0)
		valid = 0;

	UnChIn (valid ? '1' : '0');
}


/*
 * Skip-character functions.
 */

void
SkipWhite (void)
{
	while (White (ChPeek ()))
		(void) ChIn ();
}


void
SkipToEol (void)
{
	while (!Eol (ChIn ()))
	{
		/* loop */
	}
}
