/*
 * action.c - action table stuff
 *
 * When requests are defined, any previous definition is
 * deleted.  This can occur when multiple action files
 * are read.
 *
 * Syntax:
 *
 * imm immediate-action-list
 * req req-name parsing-action-list eol non-parsing-action-list
 */

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

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

#include	"tcgen.h"

#include	"troffcvt.h"


/*
 * flag values are:
 *
 * 1	allow in argument parsing part of request action list
 * 2	allow in non-parsing part of request action list
 * 4	allow in immediate action list
 */


typedef	struct ActInfo	ActInfo;

struct ActInfo
{
	int	aiFlags;
	char	*aiName;
	int	(*aiFunc) ();
	int	aiArgs;
};



static int	ParsingActions (Action **apList);
static int	OtherActions (Action **apList, int flags);
static int	ParseActionArgs (Action *ap, int args);
static Action	*AddAction (Action *apList, Action *ap);
static char	*NextToken (void);
static int	GetLine (char *buf, int size, FILE *f);


static FILE	*f;		/* action file pointer and input buffer */
static char	buf[bufSiz];


static ActInfo	aiTab[] =
{
	{ 1,	"parse-absrel-num",	AParseARNumber,		2 },
	{ 1,	"parse-char",		AParseChar,		0 },
	{ 1,	"parse-condition",	AParseCondition,	1 },
	{ 1,	"parse-filename",	AParseFileName,		0 },
	{ 1,	"parse-macro-args",	AParseMacroArgs,	0 },
	{ 1,	"parse-name",		AParseName,		0 },
	{ 1,	"parse-names",		AParseNames,		0 },
	{ 1,	"parse-num",		AParseNumber,		1 },
	{ 1,	"parse-embolden",	AParseEmbolden,		0 },
	{ 1,	"parse-string-value",	AParseStringValue,	1 },
	{ 1,	"parse-tab-stops",	AParseTabStops,		0 },
	{ 1,	"parse-title",		AParseTitle,		0 },
	{ 1,	"parse-transliteration", AParseTransList,	0 },
	{ 1,	"process-condition",	AProcessCondition,	0 },
	{ 1,	"process-do",		AProcessDo,		0 },

	{ 6,	"point-size",		APointSize,		1 },
	{ 6,	"space-size",		ASpaceSize,		1 },
	{ 6,	"constant-width",	AConstantWidth,		3 },
	{ 6,	"embolden",		AEmbolden,		3 },
	{ 6,	"font",			AFont,			1 },
	{ 6,	"font-position",	AFontPosition,		2 },

	{ 6,	"page-length",		APageLength,		1 },
	{ 6,	"begin-page",		ABeginPage,		1 },
	{ 6,	"page-number",		APageNumber,		1 },
	{ 6,	"offset",		AOffset,		1 },
	{ 6,	"need",			ANeed,			1 },
	{ 6,	"no-space",		ANoSpace,		1 },

	{ 6,	"flush",		AFlush,			0 },
	{ 6,	"break",		ABreak,			0 },
	{ 6,	"fill",			AFill,			0 },
	{ 6,	"nofill",		ANofill,		0 },
	{ 6,	"adjust",		AAdjust,		1 },
	{ 6,	"noadjust",		ANoAdjust,		0 },
	{ 6,	"center",		ACenter,		1 },

	{ 6,	"spacing",		ASpacing,		1 },
	{ 6,	"line-spacing",		ALineSpacing,		1 },
	{ 6,	"space",		ASpace,			1 },

	{ 6,	"line-length",		ALineLength,		1 },
	{ 6,	"indent",		AIndent,		1 },
	{ 6,	"temp-indent",		ATempIndent,		1 },

	{ 2,	"define-macro",		ADefineMacro,		2 },
	{ 2,	"append-macro",		AAppendMacro,		2 },
	{ 6,	"define-string",	ADefineString,		2 },
	{ 6,	"append-string",	AAppendString,		2 },
	{ 6,	"rename",		ARenameName,		2 },
	{ 6,	"remove-name",		ARemoveNames,		2 },
	{ 6,	"remove-names",		ARemoveNames,		10 },
	{ 2,	"diversion-begin",	ADiversion,		1 },
	{ 2,	"diversion-append",	AAppendDiversion,	1 },
	{ 6,	"end-macro",		AEndMacro,		1 },
	{ 6,	"input-trap",		AInputTrap,		2 },

	{ 6,	"define-register",	ADefineRegister,	3 },
	{ 6,	"register-format",	ARegisterFormat,	2 },

	{ 6,	"alias-macro",		AAliasMacro,		2 },
	{ 6,	"alias-register",	AAliasRegister,		2 },

	{ 6,	"set-tab",		ASetTabChar,		1 },
	{ 6,	"set-leader",		ASetLeaderChar,		1 },
	{ 6,	"set-field",		ASetFieldChars,		2 },

	{ 6,	"set-escape",		ASetEscape,		1 },
	{ 6,	"noescape",		ANoEscape,		0 },

	{ 6,	"underline",		AUnderline,		1 },
	{ 6,	"continuous-underline",	ACUnderline,		1 },
	{ 6,	"underline-font",	AUnderlineFont,		1 },

	{ 6,	"set-control",		ASetControl,		1 },
	{ 6,	"set-control2",		ASetControl2,		1 },

	{ 6,	"transliterate",	ATransliterate,		1 },


	{ 6,	"hyphenate",		AHyphenation,		1 },
	{ 6,	"hyphen-char",		ASetHyphenChar,		1 },

	{ 6,	"title",		ATitle,			3 },
	{ 6,	"page-num-char",	APageNumChar,		1 },
	{ 6,	"title-length",		ATitleLength,		1 },

	{ 6,	"environment",		AEnvironment,		1 },

	{ 6,	"end-input",		AExit,			0 },
	{ 6,	"abort",		AAbort,			1 },

	{ 6,	"push-file",		APushFile,		1 },
	{ 6,	"push-macro-file",	APushMacroFile,		1 },
	{ 2,	"switch-file",		ASwitchFile,		1 },

	{ 6,	"set-compatibility",	ASetCompatibility,	1 },
	{ 2,	"shift-args",		AShiftArguments,	1 },

	/* echo is special - for .tm, but also can be useful elsewhere */
	{ 7,	"echo",			AEcho,			1 },
	{ 6,	"ignore",		AIgnore,		1 },

	{ 6,	"special-char",		ASpecialChar,		2 },
	{ 6,	"push-string",		APushStr,		1 },
	{ 2,	"eol",			AEol,			0 },
	{ 6,	"output-control",	AWriteControl,		1 },
	{ 6,	"output-text",		AWriteText,		1 },	 
	{ 6,	"output-special",	AWriteSpecial,		1 },

	{ 7,	"debug-flag",		ADebugFlag,		1 },
	{ 7,	"dump-macro",		ADumpMacro,		1 },
	{ 6,	"dump-bad-requests",	ADumpBadRequests,	1 },
	{ 6,	"dump-input-stack",	ADumpInputStack,	0 },

	{ 0,	(char *) NULL,		0,			0 }
};


/*
 * Read an action file.  Compatibility mode is always forced off when
 * reading such files.
 */

int
ReadActionFile (char *filename)
{
TSScanner	scanner;
char		*scanEscape;
char	*type, *name;
Request	*rp;
Action	*pap, *oap;
XChar	xbuf[bufSiz];
char	buf[bufSiz];
Param	compat;
short	out;

	compat = SetCompatMode (0);
	out = AllowOutput (1);
	sprintf (buf, "processing action file %s", filename);
	CommentOut (buf);
	(void) AllowOutput (out);
	if ((f = TCROpenLibFile (filename, "r")) == (FILE *) NULL)
	{
		(void) SetCompatMode (compat);
		return (0);
	}
	/*
	 * Turn off backslash escape mechanism while parsing
	 * action file.  Restore it later.
	 */
	TSGetScanner (&scanner);
	scanEscape = scanner.scanEscape;
	scanner.scanEscape = "";
	TSSetScanner (&scanner);
	while (GetLine (buf, (int) sizeof (buf), f))
	{
		if (buf[0] == '#')			/* comment */
			continue;
		TSScanInit (buf);
		if ((type = NextToken ()) == (char *) NULL)	/* empty line */
			continue;
		if (strcmp (type, "req") == 0)
		{
			/* request definition */
			if ((name = NextToken ()) == (char *) NULL)
				continue;	/* malformed */
			(void) XStrCpy (xbuf, StrToXStr (name));
			rp = NewRequest (xbuf);
			if (Debug (bugActionParse))
				ETMMsg ("parsing action for request \"%s\"", name);
			if (ParsingActions (&pap) && OtherActions (&oap, 6))
			{
				rp->reqPActions = pap;
				rp->reqPPActions = oap;
			}
			else
			{
				ETMMsg ("action %s definition malformed", name);
				(void) XStrCpy (xbuf, StrToXStr (name));
				RemoveNamedObject (xbuf, 0);
			}
		}
		else if (strcmp (type, "imm") == 0)
		{	/* immediate execution action list */
			if (OtherActions (&pap, 4))
			{
				ProcessActionList (pap);
				FreeActions (pap);
			}
			else
				ETMMsg ("immediate action list malformed");
		}
		else
		{
			ETMMsg ("action line malformed, first token: %s",
								type);
		}
	}
	(void) fclose (f);
	scanner.scanEscape = scanEscape;
	TSSetScanner (&scanner);

	(void) SetCompatMode (compat);

	return (1);
}


/*
 * Read current line for parsing actions.  List must be terminated
 * by "eol" action, which is not added to list (it's implicit).
 *
 * Returns non-zero for success and stuffs head of list into
 * argument.  Returns zero if list is malformed.
 */

static int
ParsingActions (Action **apList)
{
ActInfo	*ai;
Action	*ap = (Action *) NULL, *apCur;
char	*p;
int	result = 0;	/* assume MALFORMED until eol found */

	while ((p = NextToken ()) != (char *) NULL)
	{
		if (Debug (bugActionParse))
			ETMMsg ("parsing action name is \"%s\"", p);
		if (strcmp (p, "eol") == 0)
		{
			result = 1;
			break;
		}
		for (ai = aiTab; ai->aiName != (char *) NULL; ai++)
		{
			if ((ai->aiFlags & 1) && strcmp (ai->aiName, p) == 0)
				break;
		}
		if (ai->aiName == (char *) NULL)
		{
			ETMMsg ("non-parsing action %s in parsing action list",
									p);
			break;
		}
		apCur = New (Action);
		apCur->actName = ai->aiName;
		apCur->actFunc = ai->aiFunc;
		apCur->actNext = (Action *) NULL;
		ap = AddAction (ap, apCur);
		if (!ParseActionArgs (apCur, ai->aiArgs))
			break;
	}
	if (result)
		*apList = ap;
	else
		FreeActions (ap);
	return (result);
}


/*
 * Read current line for "other" actions.  flags  is either 4
 * (immediate actions only allowed) or 6 (immediate or non-parsing
 * actions allow).
 *
 * Returns non-zero for success and stuffs head of list into
 * argument.  Return zero if list is malformed.
 */

static int
OtherActions (Action **apList, int flags)
{
ActInfo	*ai;
Action	*ap = (Action *) NULL, *apCur;
char	*p;
int	result = 1;	/* assume NOT malformed unless error found */

	while ((p = NextToken ()) != (char *) NULL)
	{
		if (Debug (bugActionParse))
			ETMMsg ("action name is \"%s\"", p);
		for (ai = aiTab; ai->aiName != (char *) NULL; ai++)
		{
			if ((ai->aiFlags & flags) && strcmp (ai->aiName, p) == 0)
				break;
		}
		if (ai->aiName == (char *) NULL)
		{
			ETMMsg ("disallowed action %s in action list", p);
			result = 0;
			break;
		}
		apCur = New (Action);
		apCur->actName = ai->aiName;
		apCur->actFunc = ai->aiFunc;
		apCur->actNext = (Action *) NULL;
		ap = AddAction (ap, apCur);
		if (!ParseActionArgs (apCur, ai->aiArgs))
			break;
	}
	if (result)
		*apList = ap;
	else
		FreeActions (ap);
	return (result);
}


/*
 * Arguments are converted from char * strings into XChar * strings so that
 * their type is consistent with what the rest of the program uses to
 * represent characters internally.
 */

static int
ParseActionArgs (Action *ap, int args)
{
char	*p;
int	i;

	/*
	 * If this error occurs, it's a program error, not a
	 * request file error.  Bump up maxActionArgs and recompile.
	 */
	if (args > maxActionArgs - 1)
		ETMPanic ("action %s requires too many args (%d, max is %d)",
					ap->actName, args, maxActionArgs - 1);
	ap->actArgc = 0;
	for (i = 0; i < args; i++)
	{
		if ((p = NextToken ()) == (char *) NULL)
		{
			ETMMsg ("action %s not given enough args", ap->actName);
			return (0);
		}
		ap->actArgv[i] = XStrAlloc (StrToXStr (p));
	}
	/* put a null pointer after all the non-null arguments */
	ap->actArgc = i;
	ap->actArgv[i] = (XChar *) NULL;

	return (1);
}


/*
 * Add an action to the end of a list.  Pass head of list (NULL
 * if empty) and the action to be added.
 */

static Action *
AddAction (Action *apList, Action *ap)
{
Action	*ap2;

	if (apList == (Action *) NULL)
		return (ap);
	ap2 = apList;
	while (ap2->actNext != (Action *) NULL)
		ap2 = ap2->actNext;
	ap2->actNext = ap;
	return (apList);
}


void
FreeActions (Action *ap)
{
Action	*apNext;
int	i;

	while (ap != (Action *) NULL)
	{
		apNext = ap->actNext;
		for (i = 0; i < ap->actArgc; i++)
			XStrFree (ap->actArgv[i]);
		Free ((char *) ap);
		ap = apNext;
	}
}


/*
 * Read next token from current line and return pointer to it.
 * If token is "\" at end of line, read next line as continuation.
 * Return NULL when no more tokens.
 *
 * The use to which TSGetScanPos() is put implies that GetLine()
 * removes trailing newline fluff from lines it reads.
 */

static char *
NextToken (void)
{
char	*p;

	while ((p = TSScan ()) != (char *) NULL)
	{
		if (strcmp (p, "\\") != 0)
			return (p);
		if (*TSGetScanPos () != '\0')
			return (p);
		/* \ at end of line found */
		if (!GetLine (buf, (int) sizeof (buf), f))
			break;
		TSScanInit (buf);
	}
	return ((char *) NULL);
}


/*
 * Read a line from a file, strip any cr, lf or crlf from the
 * end.  Return zero for EOF, non-zero otherwise.
 */

static int
GetLine (char *buf, int size, FILE *f)
{
int	len;

	if (fgets (buf, size, f) == (char *) NULL)
		return (0);
	if ((len = strlen (buf)) > 0 && buf[len-1] == lf)
		buf[len-1] = '\0';
	if ((len = strlen (buf)) > 0 && buf[len-1] == cr)
		buf[len-1] = '\0';
	return (1);
}
