/*
 * Request action processors.
 *
 * The Axxx() routines all take the liberty of assuming that they're
 * called with the correct number of arguments (as defined in action.c),
 * although they check when need be to see whether arguments are empty
 * or numeric.
 */

#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	maxDiversions	10


static void	ScanMacroBody (Macro *mp, XChar *name, XChar *endMarker);
static void	AddCharToMacro (Macro *mp, XChar c);
static void	AddStrToMacro (Macro *mp, XChar *s);
static void	PushDiversion (XChar *name, int append);
static void	PopDiversion (void);
static void	DumpChar (char *name, XChar c);
static void	DumpTitlePart (int position, XChar *str);


static XChar	*diStack[maxDiversions];
static int	nDiversions = 0;


/* ------------------------------------------------------------- */

/*
 * Font and character size control
 */

int
APointSize (int argc, XChar **argv)
{
	SetSize (InterpretParam (argv[0], prevSize));
	return (1);
}



/*
 * Set space size to N/36 em.  Note that what is written out
 * is simply N; since the current space size is expressed in
 * terms of ems (and hence depends on current point size), any
 * postprocessor needs to tie actual space size to instantaneous
 * value of 1m.
 */

int
ASpaceSize (int argc, XChar **argv)
{
Param	val;

	if (EmptyArg (argv[0]))		/* ignore if no argument */
		return (0);
	val = InterpretParam (argv[0], (Param) 0);
	if (val < 0)
		val = 0;
	/*val = Units (val, 'm') / 36;*/
	SetSpaceSize (val);
	return (1);
}



/*
 * This writes out only the name of the font that should be constant-spaced.
 * It doesn't write the font width value.
 */

int
AConstantWidth (int argc, XChar **argv)
{
XChar	buf[bufSiz];
XChar	*font;
char	cbuf[bufSiz];

	if ((font = GetFontName (argv[0])) == (XChar *) NULL)
		return (0);
	/* missing arguments are non-numeric, so this test catches those */
	if (NumericArg (argv[1]))
		sprintf (cbuf, "constant-width %s", font);
	else
		sprintf (cbuf, "noconstant-width %s", font);
	ControlOut (cbuf);
	return (1);
}


/*
 * The argument vector can have 2 or 3 arguments, depending on whether
 * the original request format was * ".bd F N" or ".bd S F N".
 *
 * If N is missing/empty, turn emboldening off.
 */

int
AEmbolden (int argc, XChar **argv)
{
char	buf[bufSiz];
XChar	*font;
char	*special = "";
Param	smear;

	if (strcmp ("S", XStrToStr (argv[0])) == 0)	/* .bd S F N */
	{
		special= "-special";
		--argc;
		++argv;
	}
	if ((font = GetFontName (argv[0])) == (XChar *) NULL)
		return (0);
	smear = InterpretParam (argv[1], (Param) 0);
	sprintf (buf, "embolden%s %s %ld", special, XStrToStr (font), smear);
	ControlOut (buf);
	StrSetRegisterValue (".b", smear);
	return (1);
}


int
AFont (int argc, XChar **argv)
{
	if (!AsciiStr (argv[0]))
	{
		ETMMsg ("%snon-ASCII font name <%s>",
					FileInfo (),
					XStrToStr (argv[0]));
		return (0);
	}
	if (EmptyArg (argv[0]))
		SetFont (prevFont);
	else
		SetFont (argv[0]);
	return (1);
}


int
AFontPosition (int argc, XChar **argv)
{
	if (!AsciiStr (argv[0]))
	{
		ETMMsg ("%snon-ASCII font name <%s>",
					FileInfo (),
					XStrToStr (argv[0]));
		return (0);
	}
	if (!NumericArg (argv[0]) || EmptyArg (argv[1]))
		return (0);
	SetFontNameBySlot ((Param) StrToLong (XStrToStr (argv[0])), argv[1]);
	return (1);
}


/* ------------------------------------------------------------- */

/*
 * Page control
 */


int
APageLength (int argc, XChar **argv)
{
	SetPageLength (InterpretParam (argv[0], Units (11, 'i')));
	return (1);
}


/*
 * begin-page and page-number are broken, because relative changes
 * aren't properly done - there isn't any way to know the current
 * page number!
 *
 * (A lame excuse; this should write out information the postprocessor
 * could use to determine that the change is relative.)
 */

int
ABeginPage (int argc, XChar **argv)
{
char	buf[bufSiz];

	if (!NumericArg (argv[0]))
		ControlOut ("begin-page");
	else
	{
		SetPageNumber ((Param) StrToLong (XStrToStr (argv[0])));
		sprintf (buf, "begin-page %s", XStrToStr (argv[0]));
		ControlOut (buf);
	}
	return (1);
}


int
APageNumber (int argc, XChar **argv)
{
	if (NumericArg (argv[0]))
		SetPageNumber ((Param) StrToLong (XStrToStr (argv[0])));
	return (1);
}


int
AOffset (int argc, XChar **argv)
{
	SetOffset (InterpretParam (argv[0], prevOffset));
	return (1);
}


int
ANeed (int argc, XChar **argv)
{
Param	val;
char	buf[bufSiz];

	val = InterpretParam (argv[0], Units (1, 'v'));
	sprintf (buf, "need %ld", val);
	ControlOut (buf);
	return (1);
}


/* ------------------------------------------------------------- */


/*
 * Text filling, adjusting and centering
 */

/*
 * Issue a break -- maybe.  This checks whether a break should be
 * issued or not by checking whether the request was given with the
 * normal or no-break control character.
 *
 * "break" should be specified in the action list for any request
 * that normally causes a break.
 */

int
ABreak (int argc, XChar **argv)
{
	if (curCtrlChar == ctrlChar)
		Break ();
	return (1);
}

/*
 * Flush current output line if there is anything on it.  This is like
 * break, but doesn't write \break, doesn't affect \c continuation,
 * and doesn't remove need for space character between lines.
 * (Basically, it's used to allow you to write \pass xxx output with
 * a guarantee that xxx will appear at the beginning of a new line.)
 */

int
AFlush (int argc, XChar **argv)
{
	FlushOText ();
	return (1);
}


/*
 * Turn filling on or off
 */

int
AFill (int argc, XChar **argv)
{
	SetCFA (centerCount, (Param) fill, adjust, adjMode);
	return (1);
}


int ANofill (int argc, XChar **argv)
{
	SetCFA (centerCount, (Param) nofill, adjust, adjMode);
	return (1);
}



/*
 * Set adjustment type.  If no type is given, use whatever the
 * value was before.  Thus, ".ad" after ".na" will restore adjustment
 * and set it to what it was before being suppressed with ".na".
 *
 * Use of the .j register value as an argument is supported, but
 * its correct operation depends on adj{Center,Left,Right,Full}
 * values being single-digit and non-negative, since the .ad request
 * argument is probably parsed with the "parse-char" action.
 */

int
AAdjust (int argc, XChar **argv)
{
Param	mode = adjMode, adj = 1;

	if (Digit (argv[0][0]))	/* probably had ".ad \n(.j" */
	{
		adj = 0;
		if ((mode = (Param) (argv[0][0] - '0')) > 4)
		{
			adj = 1;
			mode -= 4;
		}
	}
	else switch (argv[0][0])
	{
	case 'c': mode = adjCenter; break;
	case 'l': mode = adjLeft; break;
	case 'r': mode = adjRight; break;
	case 'b':
	case 'n': mode = adjFull; break;
	}
	SetCFA (centerCount, fillMode, adj, mode);
	return (1);
}


int
ANoAdjust (int argc, XChar **argv)
{
	SetCFA (centerCount, fillMode, (Param) 0, adjMode);
	return (1);
}


/*
 * Somewhat similar to .ad c but causes a break, takes a line count,
 * suppresses filling and adjusting, and turns off after a certain
 * number of input lines.
 *
 * If the count is missing, use 1.  If the count is 0 (or negative!)
 * turn off centering, if it isn't already off.  If count is positive,
 * turn centering on, if it isn't already on.
 */

int
ACenter (int argc, XChar **argv)
{
Param	count;

	if (!NumericArg (argv[0]))
		count = 1;
	else
		count = StrToLong (XStrToStr (argv[0]));
	if (count < 0)
		count = 0;
	SetCFA (count, fillMode, adjust, adjMode);
	return (1);
}


/* ------------------------------------------------------------- */


/*
 * Vertical spacing
 */


int
ASpacing (int argc, XChar **argv)
{
	SetSpacing (InterpretParam (argv[0], prevVSize));
	return (1);
}


/*
 * Fractional values (e.g., .ls 1.5) are processed as just that,
 * even though troff truncates to integer values.
 */

int
ALineSpacing (int argc, XChar **argv)
{
	SetLineSpacing (InterpretParam (argv[0], prevLineSpacing));
	return (1);
}


int
ASpace (int argc, XChar **argv)
{
Param	val;
char	buf[bufSiz];

	val = InterpretParam (argv[0], Units (1, 'v'));
	sprintf (buf, "space %ld", val);
	ControlOut (buf);
	return (1);
}


/*
 * Argument is 'y if no-space mode should be turned on, 'n' if it
 * should be turned off.
 *
 * Actually: not implemented.
 */

int
ANoSpace (int argc, XChar **argv)
{
	return (0);
}


/* ------------------------------------------------------------- */


/*
 * Line length and indenting
 */


int
ALineLength (int argc, XChar **argv)
{
	SetLineLength (InterpretParam (argv[0], prevLineLen));
	return (1);
}


int
AIndent (int argc, XChar **argv)
{
	SetIndent (InterpretParam (argv[0], prevIndent));
	return (1);
}


int
ATempIndent (int argc, XChar **argv)
{
	if (EmptyArg (argv[0]))		/* ignore if no argument */	
		return (0);
	SetTempIndent (InterpretParam (argv[0], (Param) 0));
	return (1);
}


/* ------------------------------------------------------------- */


/*
 * Macro and string definitions
 */


/*
 * Define macro.  Begin with a buffer of macroAllocSize XChars for holding the
 * macro body (may need to allocate more later).
 */

int
ADefineMacro (int argc, XChar **argv)
{
Macro	*mp;

	if (EmptyArg (argv[0]))		/* no macro name */
		return (0);

	if (Debug (bugMacroDef))
		ETMMsg ("New macro %s", XStrToStr (argv[0]));
	mp = NewMacro (argv[0]);
	ScanMacroBody (mp, argv[0], argv[1]);

	return (1);
}


/*
 * Append to a macro (create it if it doesn't exist).
 */

int
AAppendMacro (int argc, XChar **argv)
{
Macro	*mp;

	if (EmptyArg (argv[0]))		/* no macro name */
		return (0);
	if ((mp = LookupMacro (argv[0])) == (Macro *) NULL)
		return (ADefineMacro (argc, argv));
	ScanMacroBody (mp, argv[0], argv[1]);
	return (1);
}


/*
 * Scan current macro body.  This is also used by .ig, in which
 * case mp is NULL.  endMarker is non-empty if an explicit endMarker
 * was given, and is taken to be the name of a macro to invoke
 * after collecting the macro body.
 *
 * Note the ambiguity of ".de XX ." as opposed to ".de XX"; in both
 * cases the ending line is "..", but in the first case the macro "."
 * should be invoked after collecting the macro.  (Wonder if that
 * was Ossanna's intent?)
 */

static void
ScanMacroBody (Macro *mp, XChar *name, XChar *endMarker)
{
XChar	defEndMarker[2] = { '.', 0 };	/* default is "." */
XChar	*endMacro = (XChar *) NULL, *ep, *p;
XChar	c, cPrev;
char	*macroName;

	/* get name for debugging */
	if (name != (XChar *) NULL)
		macroName = StrAlloc (XStrToStr (name));
	else
		macroName = StrAlloc ("[ignore]");

	if (EmptyArg (endMarker))	/* no explicit end marker */
		endMarker = defEndMarker;
	else
		endMacro = endMarker;

	if (Debug (bugMacroDef))
	{
		ETMMsg ("ScanMacroBody(%s): until <%s>",
				macroName, XStrToStr (endMarker));
	}
	cPrev = lf;
	if (mp != (Macro *) NULL)
		p = mp->macBuf;
	CopyMode (1);
	for (;;)
	{
		if ((c = ChIn ()) == endOfInput)
		{
			ETMPanic ("%sEOF while scanning macro <%s> body",
					FileInfo (), macroName);
		}
		AddCharToMacro (mp, c);
		if (c == ctrlChar && Eol (cPrev))
		{
			if (mp != (Macro *) NULL)
				p = &mp->macBuf[mp->macSiz-1];
			for (ep = endMarker; *ep != '\0'; ep++)
			{
				if (Debug (bugMacroDef))
					ETMMsg ("SMB: look for %s",
							XCharToStr (*ep));
				if ((c = ChIn ()) == endOfInput)
				{
					ETMPanic ("%sEOF while scanning macro <%s> body",
						FileInfo (), macroName);
				}
				AddCharToMacro (mp, c);
				if (*ep != c)
					break;
			}
			if (*ep == 0 && Eol (ChPeek ()))	/* found match */
			{
				if (mp != (Macro *) NULL)
					mp->macSiz = (p - mp->macBuf);
				break;
			}
		}
		cPrev = c;
	}
	CopyMode (0);
	SkipToEol ();
	if (Debug (bugMacroDef))
	{
		ETMMsg ("ScanMacroBody(%s) : found <%s>",
					macroName, XStrToStr (endMarker));
	}

	if (endMacro != (XChar *) NULL)
		(void) PushMacro (endMacro, 0, NULL);

	Free (macroName);
}


/*
 * Add a character to a macro body.  If it won't fit, reallocate
 * to get macroAllocSize XChars more space.
 *
 * mp is NULL if this is called during .ig.  In this case the character
 * is simply discarded.
 */

static void
AddCharToMacro (Macro *mp, XChar c)
{
XChar	*p;
int	i;

	if (mp == (Macro *) NULL)
		return;
	if (mp->macSiz >= mp->macMaxSiz)
	{
		/*
		 * Allocate bigger buffer, copy existing buffer into
		 * it, then free the old buffer and install the new
		 * one.  Reset the max size indicator.
		 */
		p = (XChar *) VAlloc (mp->macMaxSiz + macroAllocSize,
					sizeof (XChar));
		for (i = 0; i < mp->macSiz; i++)
			p[i] = mp->macBuf[i];
		Free ((char *) mp->macBuf);
		mp->macBuf = p;
		mp->macMaxSiz += macroAllocSize;
	}
	mp->macBuf[mp->macSiz++] = c;
}


/*
 * Add a string to a macro body.
 */

static void
AddStrToMacro (Macro *mp, XChar *s)
{
	while (*s != '\0')
		AddCharToMacro (mp, *s++);
}


/*
 * Define a string.  This really defines a macro, because a strings are
 * treated as macros without arguments.
 *
 * The first and second arguments are the name and value of the string.  
 */

int
ADefineString (int argc, XChar **argv)
{
Macro	*mp;

	if (EmptyArg (argv[0]))
		return (0);
	mp = NewMacro (argv[0]);
	AddStrToMacro (mp, argv[1]);
	/*(void) NewString (argv[0], argv[1]);*/
	return (1);
}


/*
 * Append to an existing string (create it if it doesn't exist).
 */

int
AAppendString (int argc, XChar **argv)
{
Macro	*mp;

	if ((mp = LookupMacro (argv[0])) == (Macro *) NULL)
		return (ADefineString (argc, argv));
	AddStrToMacro (mp, argv[1]);
	return (1);
}


/*
 * Rename request, macro or string name
 */

int
ARenameName (int argc, XChar **argv)
{
Name	*np;

	if (EmptyArg (argv[0]) || EmptyArg (argv[1]))
		return (0);
	if ((np = LookupName (argv[0], reqDef|macDef)) == (Name *) NULL)
		return (0);
	RenameName (np, argv[1]);
	return (1);
}


/*
 * Remove a list of names.  If argv[0][0] is 'y', remove number registers,
 * otherwise remove string, request, macro names.
 *
 * Used for .rm and .rr.  troff manual doesn't say it, but these
 * requests can be given multiple arguments.  Since the troffcvt mechanism
 * for passing arguments to post-parsing action functions requires a fixed
 * number of arguments, the post-parsing actions for those requests call
 * this function a number of times with different ranges of arguments from
 * the parsing section, to make sure all the names get passed.  Most of
 * them will be empty most of the time, so this ignores empty arguments.
 */

int
ARemoveNames (int argc, XChar **argv)
{
Register	*rp;
XChar	*p;
int	i;
int	isNumReg;

	if (argc < 1)
		ETMPanic ("ARemoveNames: insufficient arguments");

	isNumReg = (argv[0][0] == 'y');		/* 1 if number, 0 if not */

	for (i = 1; i < argc; i++)
	{
		p = argv[i];
		if (EmptyArg (p))
			continue;
		if (isNumReg && (rp = LookupRegister (p)) != (Register *) NULL)
		{
			if (rp->regReadOnly)
			{
				ETMMsg ("%scannot remove readonly register <%s>",
							FileInfo (),
							XStrToStr (p));
				continue;
			}
		}
		RemoveNamedObject (p, isNumReg ? regDef : reqDef|macDef);
	}
	return (1);
}


int
AEndMacro (int argc, XChar **argv)
{
	XStrFree (endMacro);
	if (LookupMacro (argv[0]) == (Macro *) NULL)
	{
		ETMMsg ("%sunknown end macro: %s",
					FileInfo (),
					XStrToStr (argv[0]));
	}
	endMacro = XStrAlloc (argv[0]);
	return (1);
}


int
AInputTrap (int argc, XChar **argv)
{
	XStrFree (itMacro);
	itCount = 0;
	if (NumericArg (argv[0]) && !EmptyArg (argv[1]))
	{
		if (LookupMacro (argv[1]) == (Macro *) NULL)
		{
			ETMMsg ("%sunknown input trap macro: %s",
						FileInfo (),
						XStrToStr (argv[1]));
		}
		itMacro = XStrAlloc (argv[1]);
		itCount = InterpretParam (argv[0], (Param) 0);
	}
	return (1);
}


int
ADiversion (int argc, XChar **argv)
{
	if (EmptyArg (argv[0]))		/* terminate current diversion */
		PopDiversion ();
	else				/* begin new diversion */
		PushDiversion (argv[0], 0);
	return (1);
}


int
AAppendDiversion (int argc, XChar **argv)
{
	if (EmptyArg (argv[0]))		/* terminate current diversion */
		PopDiversion ();
	else				/* append to existing diversion */
		PushDiversion (argv[0], 1);
	return (1);
}


XChar *
CurrentDiversion (void)
{
static XChar nullDiversionName[1] = { 0 };

	if (nDiversions < 1)
		return (nullDiversionName);
	return (diStack[nDiversions-1]);
}


/*
 * Copy diversion name into stack and indicate start (or continuation)
 * of diversion.
 *
 * Diversion names can be written directly to output, so they must be
 * plain ASCII names.  That constraint is enforced here.
 */

static void
PushDiversion (XChar *name, int append)
{
char	buf[bufSiz];

	if (!AsciiStr (name))
	{
		ETMMsg ("%snon-ASCII diversion name <%s>",
					FileInfo (),
					XStrToStr (name));
		return;
	}
	if (nDiversions >= maxDiversions)
		ETMPanic ("%sPushDiversion: diversion stack overflow",
							FileInfo ());
	diStack[nDiversions++] = XStrAlloc (name);
	sprintf (buf, "diversion-%s %s", append ? "append" : "begin",
							XStrToStr (name));
	ControlOut (buf);
}


/*
 * Indicate end of diversion, free storage for name.
 */

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

	if (nDiversions > 0)
	{
		sprintf (buf, "diversion-end %s",
			XStrToStr (diStack[--nDiversions]));
		ControlOut (buf);
		XStrFree (diStack[nDiversions]);
	}
}


/* ------------------------------------------------------------- */


/*
 * Define a number register.  If the register existed before,
 * use previous increment and format if corresponding arguments
 * are empty.  (Actually, old format is ignored, since formats
 * are pretty well broken anyway.)
 */

int
ADefineRegister (int argc, XChar **argv)
{
Register	*rp;
Param	value = 0, oldValue = 0, oldIncr = 0;
Param	increment = 0;
char	*p;

	if (EmptyArg (argv[0]))
		return (0);
	if ((rp = LookupRegister (argv[0])) != (Register *) NULL)
	{
		oldValue = rp->regValue;
		oldIncr = rp->regIncrement;
		if (rp->regReadOnly)
		{
			ETMMsg ("attempt to set readonly register <%s> ignored",
							XStrToStr (argv[0]));
			return (0);
		}
	}
	value = InterpretParam (argv[1], oldValue);
	increment = InterpretParam (argv[2], oldIncr);
	p = StrAlloc (XStrToStr (argv[0]));
	rp = StrNewRegister (p, value, increment, "1", 0);
	Free (p);
	return (1);
}


int
ARegisterFormat (int argc, XChar **argv)
{
Register	*rp;

	if (EmptyArg (argv[1]))
		return (0);
	if ((rp = LookupRegister (argv[0])) == (Register *) NULL)
		return (0);
	SetRegisterFormat (rp, argv[1]);
	return (1);
}


/* ------------------------------------------------------------- */


int
ASetTabChar (int argc, XChar **argv)
{
	tabChar = InterpretCharParam (argv[0], '\0');
	DumpChar ("tab-char", tabChar);
	return (1);
}


int
ASetLeaderChar (int argc, XChar **argv)
{
	leaderChar = InterpretCharParam (argv[0], '\0');
	DumpChar ("leader-char", leaderChar);
	return (1);
}


/*
 * Dump a character name control word and optionally the character's
 * value.
 */

static void
DumpChar (char *name, XChar c)
{
char	buf[bufSiz];

	if (c == 0)
		ControlOut (name);
	else if (c == '"' || c == '\\')
	{
		sprintf (buf, "%s \"\\%c\"", name, c);
		ControlOut (buf);
	}
	else
	{
		sprintf (buf, "%s %s", name, CharToOutputStr (c));
		ControlOut (buf);
	}
}


int
ASetFieldChars (int argc, XChar **argv)
{
	if (EmptyArg (argv[0]) && EmptyArg (argv[1]))
	{
		fieldDelimChar = '\0';	/* turn field mechanism off */
		fieldPadChar = '\0';
	}
	else
	{
		fieldDelimChar = InterpretCharParam (argv[0], '\0');
		fieldPadChar = InterpretCharParam (argv[1], ' ');
	}
	return (1);
}


/* ------------------------------------------------------------- */


/*
 * Escape character cannot be an escaped character \x or special
 * character \(xx since then it would be recursive.
 */

int
ASetEscape (int argc, XChar **argv)
{
	if (EmptyArg (argv[0]))
		escChar = defEscChar;
	else if (AsciiStr (argv[0]))
		escChar = argv[0][0];
	else
		ETMMsg ("illegal escape character: <%s>", XStrToStr (argv[0]));
	doEscapes = 1;
	return (1);
}


int
ANoEscape (int argc, XChar **argv)
{
	doEscapes = 0;
	return (1);
}


/* ------------------------------------------------------------- */


int
AUnderline (int argc, XChar **argv)
{
Param	count;

	count = InterpretParam (argv[0], (Param) 1);
	if (count < 0)
		count = 0;

	SetUnderline (count, 0);

	return (1);
}


int
ACUnderline (int argc, XChar **argv)
{
Param	count;

	count = InterpretParam (argv[0], (Param) 1);
	if (count < 0)
		count = 0;

	SetUnderline (0, count);

	return (1);
}


int
AUnderlineFont (int argc, XChar **argv)
{
char	buf[bufSiz];
XChar	*font;

	if (!AsciiStr (argv[0]))
	{
		ETMMsg ("%snon-ASCII font name <%s>",
					FileInfo (),
					XStrToStr (argv[0]));
		return (0);
	}
	if ((font = GetFontName (argv[0])) == (XChar *) NULL)
		return (0);
	sprintf (buf, "underline-font %s", XStrToStr (font));
	ControlOut (buf);
	return (1);
}


/* ------------------------------------------------------------- */


/*
 * Control character operations
 */


int
ASetControl (int argc, XChar **argv)
{
	ctrlChar = InterpretCharParam (argv[0], defCtrlChar);
	return (1);
}


int
ASetControl2 (int argc, XChar **argv)
{
	ctrlChar = InterpretCharParam (argv[0], defNbCtrlChar);
	return (1);
}


/* ------------------------------------------------------------- */


/*
 * Transliteration
 */

int
ATransliterate (int argc, XChar **argv)
{
XChar	*p = argv[0];
int	c1, c2;

	while ((c1 = *p) != '\0')
	{
		if (*++p != '\0')
			c2 = *p++;
		else
			c2 = ' ';
		Transliterate (c1, c2);
	}

	return (1);
}

/* ------------------------------------------------------------- */

/*
 * Hyphenation
 */


/*
 * Turn hyphenation on or off.  0 = off, empty defaults to 1.
 * Values are as described in troff manual.
 */

int
AHyphenation (int argc, XChar **argv)
{
	SetHyphenation (InterpretParam (argv[0], (Param) 1));
	return (1);
}



/*
 * Set the optional-hyphenation character.
 */

int
ASetHyphenChar (int argc, XChar **argv)
{
	optHyphenChar = InterpretCharParam (argv[0], ToEsc ('%'));
	return (1);
}


/* ------------------------------------------------------------- */

/*
 * Titles
 */


/*
 * Dump all parts of a three-part title.  If a part is empty or missing,
 * dump an empty part.
 */

int
ATitle (int argc, XChar **argv)
{
	DumpTitlePart ('l', argc >= 1 ? argv[0] : (XChar *) NULL);
	DumpTitlePart ('m', argc >= 2 ? argv[1] : (XChar *) NULL);
	DumpTitlePart ('r', argc >= 3 ? argv[2] : (XChar *) NULL);
	return (1);
}


static void
DumpTitlePart (int position, XChar *str)
{
char	buf[bufSiz];

	sprintf (buf, "title-begin %c", position);	/* l, m, or r */
	ControlOut (buf);
	TextXStrOut (str);
	ControlOut ("title-end");
}


/*
 * Set, or remove, page number character
 */

int
APageNumChar (int argc, XChar **argv)
{
	pageNumChar = InterpretCharParam (argv[0], '\0');
	return (1);
}


/*
 * Set title length
 */

int
ATitleLength (int argc, XChar **argv)
{
	SetTitleLength (InterpretParam (argv[0], prevTitleLen));
	return (1);
}


/* ------------------------------------------------------------- */

/*
 * Environments
 */

int
AEnvironment (int argc, XChar **argv)
{
	if (EmptyArg (argv[0]))		/* revert to previous environment*/
		PopEnvironment ();
	else				/* enter new environment */
		PushEnvironment (argv[0]);
	return (1);
}


/* ------------------------------------------------------------- */



int
AExit (int argc, XChar **argv)
{
	allowInput = 0;
	return (1);
}



int
AAbort (int argc, XChar **argv)
{
char	*p = "User abort";

	if (!EmptyArg (argv[0]))
		p = XStrToStr (argv[0]);
	ETMPanic ("%s", p);
	return (1);
}


/*
 * Push a file on the input stack.  search is non-zero if DoPushFile()
 * should look for the file the way it looks for macro files.
 */

static int
DoPushFile (int argc, XChar **argv, short search)
{
char	buf[bufSiz], *p;

	/* on error, say something but don't exit */
	if (EmptyArg (argv[0]))
	{
		ETMMsg ("%sMissing filename for push-%sfile",
						FileInfo (),
						search ? "macro-" : "");
	}
	else if (!AsciiStr (argv[0]))
	{
		ETMMsg ("%snon-ASCII filename for push-%sfile <%s>",
						FileInfo (),
						search ? "macro-" : "",
						XStrToStr (argv[0]));
	}
	else
	{
		/* PushFile() takes a char* string */
		(void) strcpy (buf, XStrToStr (argv[0]));
		p = (char *) NULL;
		if (search)
			p = FindMacroFile (buf);
		/*
		 * The following sets p to buf if: (i) searching isn't on, or
		 * (ii) searching is on but the file wasn't found.  Setting p
		 * to buf in the latter case is safe; PushFile() will fail
		 * and the error message will be printed.
		 */
		if (p == (char *) NULL)
			p = buf;
		if (PushFile (p))
			return (1);
		ETMMsg ("%scannot open \"%s\" for push-%sfile",
						FileInfo (),
						buf,
						search ? "macro-" : "");
	}
	return (0);
}


int
APushFile (int argc, XChar **argv)
{
	return (DoPushFile (argc, argv, 0));
}


int
APushMacroFile (int argc, XChar **argv)
{
	return (DoPushFile (argc, argv, 1));
}


/*
 * Shift macro arguments by n positions if numeric argument is given,
 * by 1 if no argument is given.  Ignore if non-numeric argument given,
 * or n is negative.
 */

int
AShiftArguments (int argc, XChar **argv)
{
short	shift;

	if (EmptyArg (argv[0]))
		shift = 1;
	else if (NumericArg (argv[0]))
	{
		shift = StrToShort (argv[0]);
		if (shift < 0)
		{
			ETMMsg ("%snegative argument shift amount: %hd",
							FileInfo (),
							shift);
			return (0);
		}
	}
	ShiftMacroArguments (shift);
	return (1);
}

int
ASwitchFile (int argc, XChar **argv)
{
char	buf[bufSiz];

	/* SwitchFile() takes a char* string */
	(void) strcpy (buf, XStrToStr (argv[0]));

	/* on error, say something and exit */
	if (EmptyArg (argv[0]))
	{
		ETMPanic ("%sMissing filename for switch-file",
							FileInfo ());
	}
	if (!AsciiStr (argv[0]))
	{
		ETMMsg ("%snon-ASCII filename for switch-file <%s>",
							FileInfo (),
							buf);
	}
	else if (!SwitchFile (buf))
	{
		ETMPanic ("%scannot open \"%s\" for switch-file",
							FileInfo (),
							buf);
	}
	return (1);
}


/* ------------------------------------------------------------- */


/*
 * Read rest of request line and echo to stderr.  Used for .tm,
 * but can be useful for debugging argument parsing, too.
 */

int
AEcho (int argc, XChar **argv)
{
	ETMMsg ("%s", XStrToStr (argv[0]));
	return (1);
}


/*
 * Ignore input for a while
 */

int
AIgnore (int argc, XChar **argv)
{
	ScanMacroBody ((Macro *) NULL, (XChar *) NULL, argv[0]);
	return (1);
}


/* ------------------------------------------------------------- */

/*
 * Set compatibility mode.  (Sets compatMode variable, .C register)
 *
 * If the argument is missing/non-numeric or non-zero, enable compatibility
 * mode.  If the argument is zero, disable compatibility mode.  Long names
 * are not recognized when the mode is enabled.
 */

int
ASetCompatibility (int argc, XChar **argv)
{
Param	newCompat = 1;	/* default if argument missing or non-numeric */

	if (NumericArg (argv[0]))	/* present and numeric */
		newCompat = (StrToLong (XStrToStr (argv[0])) != 0);
	(void) SetCompatMode (newCompat);
	return (1);
}


/* ------------------------------------------------------------- */

/*
 * Alias stuff
 */


/*
 * Alias a macro (or string or request).
 */

int
AAliasMacro (int argc, XChar **argv)
{
Name	*np;

	if (argc < 2)
		return (0);
	if (XStrCmp (argv[0], argv[1]) == 0)
	{
		ETMMsg ("%syou cannot alias <%s> to itself",
						FileInfo (),
						XStrToStr (argv[0]));
		return (0);
	}
	np = LookupName (argv[1], reqDef|macDef);
	if (np == (Name *) NULL)
	{
		ETMMsg ("%syou cannot alias to non-existing name <%s>",
						FileInfo (),
						XStrToStr (argv[1]));
		return (0);
	}
	switch (np->nObject->oType)
	{
	default:
		ETMPanic ("AAliasMacro: logic error");
	case reqDef:
	case macDef:
		NewName (argv[0], np->nObject, np->nObject->oType);
		break;
	}
	return (1);
}


/*
 * Alias a register.
 */

int
AAliasRegister (int argc, XChar **argv)
{
Name	*np;

	if (argc < 2)
		return (0);
	if (XStrCmp (argv[0], argv[1]) == 0)
	{
		ETMMsg ("%syou cannot alias <%s> to itself",
						FileInfo (),
						XStrToStr (argv[0]));
		return (0);
	}
	np = LookupName (argv[1], regDef);
	if (np == (Name *) NULL)
	{
		ETMMsg ("%syou cannot alias to non-existing register <%s>",
						FileInfo (),
						XStrToStr (argv[1]));
		return (0);
	}
	NewName (argv[0], np->nObject, np->nObject->oType);
	return (1);
}


/* ------------------------------------------------------------- */


/*
 * Miscellaneous actions that don't really correspond to
 * any particular request.
 */

/*
 * Define a special character.  This isn't a troff request, but
 * it allows the list of known special characters to be specified in
 * an action file at run time.
 *
 * First arg is the special char name, second is the string to
 * output when character is referenced.  The output string cannot
 * be empty (this is to allow the .if c test to tell whether a
 * special character was ever defined by checking whether or not
 * the value is empty.)
 *
 * If the character is already defined, just replace the value.
 * Otherwise add it to the list.
 */

int
ASpecialChar (int argc, XChar **argv)
{
	if (EmptyArg (argv[0]))
	{
		ETMMsg ("%sspecial char name is empty", FileInfo ());
		return (0);
	}
	if (EmptyArg (argv[1]))
	{
		ETMMsg ("%svalue of special char <%s> is empty",
						FileInfo (),
						XStrToStr (argv[0]));
		return (0);
	}
	(void) NewSpChar (argv[0], argv[1]);
	return (1);
}



/*
 * Push a string onto the input stack to interpolate it into
 * the input stream.  Force it to be processed immediately
 * until the string is exhausted.  Then restart whatever was
 * already on the input stack and return.
 */

int
APushStr (int argc, XChar **argv)
{
	PushAnonString (argv[0], 1);
	ReadInput ();
	ResumeInput ();
	return (1);
}


/*
 * Skip to the end of the current line
 */

int
AEol (int argc, XChar **argv)
{
	SkipToEol ();
	return (1);
}


/*
 * AWrite{Control,Text,Special} should not contain escaped or
 * special characters...
 */

int
AWriteControl (int argc, XChar **argv)
{
char	*p;

	p = StrAlloc (XStrToStr (argv[0]));
	if (!AsciiStr (argv[0]))
		ETMMsg ("AWriteControl: warning: non-ASCII string <%s>", p);
	ControlOut (p);
	Free (p);
	return (1);
}


int
AWriteText (int argc, XChar **argv)
{
XChar	*p;

	p = argv[0];
	if (!AsciiStr (p))
		ETMMsg ("AWriteText: warning: non-ASCII string <%s>",
								XStrToStr (p));
	while (*p != '\0')
		TextChOut (*p++);
	return (1);
}


int
AWriteSpecial (int argc, XChar **argv)
{
char	*p;

	p = StrAlloc (XStrToStr (argv[0]));
	if (!AsciiStr (argv[0]))
		ETMMsg ("AWriteSpecial: warning: non-ASCII string <%s>", p);
	SpecialTextOut (p);
	Free (p);
	return (1);
}


int
ADebugFlag (int argc, XChar **argv)
{
Param	val;

	/* missing arguments are non-numeric, so this test catches those */
	if (!NumericArg (argv[0]))
		debug = 0xffffffff;
	else if ((val = InterpretParam (argv[0], (Param) 0)) < 0)
		debug = 0;
	else
		debug |= 1L<<val;
	ETMMsg ("debugging flags now %#lx", debug);
	return (1);
}


int
ADumpMacro (int argc, XChar **argv)
{
Macro	*mp;
XChar	*p;
char	*cp;
long	i;

	if (EmptyArg (argv[0]))
		return (0);
	if ((mp = LookupMacro (argv[0])) == (Macro *) NULL)
		ETMMsg ("Macro <%s>: not found", XStrToStr (argv[0]));
	else
	{
		ETMMsg ("Macro <%s> body:", XStrToStr (argv[0]));
		if ((p = mp->macBuf) != (XChar *) NULL)
		{
			for (i = 0; i < mp->macSiz; i++)
			{
				cp = XCharToStr (*p++);
				fwrite (cp, 1, strlen (cp), stderr);
			}
		}
		ETMMsg ("-------");
	}
	return (1);
}


/*
 * Turn dumping of bad requests on \other lines on or off.  The
 * argument should be 1 or 0.  A missing argument is interpreted
 * as 0.
 */

int
ADumpBadRequests (int argc, XChar **argv)
{
	dumpBadReq = (InterpretParam (argv[0], (Param) 0) != 0);
	return (1);
}


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

int
ADumpInputStack (int argc, XChar **argv)
{
	DumpInputStack ();
}


/* ------------------------------------------------------------- */


/*
 * Look at a string, return its value.  String should be empty or
 * represent a number.  If empty, return default value.  If represents
 * a number, look at last char to see if it's a units indicator.  If
 * it is, use it to convert number to basic units.
 */

Param
InterpretParam (XChar *s, Param defValue)
{
char	buf[bufSiz];
int	unit;
int	len;

	/* missing arguments are non-numeric, so this test catches those */
	if (!NumericArg (s))
		return (defValue);
	/*
	 * If it's a numeric argument, it's also an ASCII string, so convert
	 * to a char* string and work with that.
	 */
	(void) strcpy (buf, XStrToStr (s));
	if ((len = strlen (buf)) > 0 && UnitChar (unit = buf[len-1]))
		buf[len-1] = '\0';	/* chop off the units character */
	else
		unit = 'u';
	return (Units (StrToDouble (buf), unit));
}


/*
 * Interpret s to find the value of some sort of single-character parameter
 * (e.g., the tab or field character, or the control char).  If s is
 * an empty argument, use the default value.
 */

int
InterpretCharParam (XChar *s, int defValue)
{
	if (EmptyArg (s))
		return (defValue);
	return (s[0]);
}
