/*
 *				b u i l d 4 . c
 *
 * Process macro definitions and expansions
 *
 * define(model, flag)		Define a macro
 * expand(arg, outfun, outarg)	Expand macros
 * expout(arg)			Expand macros, writing output to stdout
 */

#include	<stdio.h>
#include	"build.h"

static char	def_name[NAMESIZE];

static char *
defname(string)
register char	*string;
/*
 * String points to the first byte of a macro name (the byte after the '$').
 * Compile the name into static def_name, and return an updated argument
 * pointer (which now points to the first byte after the name -- possibly EOS).
 * Calling sequence:
 *		arg = defname(arg);
 *		sylookup(def_name);
 */
{
	register char	*np;		/* Name buffer pointer		*/
	register int	c;		/* Working character		*/
	register int	closer;		/* Terminator if (...)		*/

	np = def_name;
	if ((c = *string) != EOS) {
		if (c == '(' || c == '{') {
			/*
			 * $(FOO) or ${FOO}
			 */
			closer = (c == '(') ? ')' : '}';
			++string;		/* Skip over '('	*/
			while (iswhite(*string))
				string++;	/* Leading whitespace	*/
			while (!iswhite(*string)
			  && *string != closer && *string != EOS
			  && (np - def_name) < NAMESIZE - 1)
				*np++ = *string++;
			while (*string != closer && *string != EOS)
				string++;	/* Trailing whitespace	*/
			if (*string == closer)
				string++;	/* and closing ')'	*/
		}
		else {
			*np++ = c;		/* $X			*/
			string++;		/* update string ptr	*/
		}
	}
	*np = EOS;
	return (string);
}				

define(model, flag)
register char	*model;		/* Macro definition(s)			*/
int		flag;		/* Set in sy_flag			*/
/*
 * process model strings, building macro definitions.
 * continues until an EOS is passed from nextch.
 *
 * Macro definitions have the following format:
 *	$X = Y
 * where X may be
 *	a single character
 *	(TEXT) or {TEXT}
 * and Y may be
 *	a string up to the next blank.
 *	{ text }
 *
 */
{
	register int	c;
	register char	*tp;		/* Random text pointer		*/

#define	skip	*(model = skipwhite(model))

	if (debug)
		fprintf(stderr, "define(\"%s\")\n", model);
	while ((c = skip) != EOS) {
		model++;
		if (c == '#') {
			/*
			 * Comment, skip to end of line
			 */
			while (c != EOS && c != '\n')
				c = *model++;
			continue;
		}
		if (c != '$') {
			fprintf(stderr, "expecting '$', got '%c'\n", c);
			continue;
		}
		model = defname(model);
		if ((c = skip) != '=') {
			fprintf(stderr,
			  "definition of %s followed by '%c', '=' needed\n",
					def_name, c);
			continue;
		}
		model++;
		/*
		 * Copy the definition into work[]
		 */
		tp = work;
		if ((c = skip) != '{') {
			/*
			 * Just a word
			 */
			model++;
			while (c != EOS && !iswhite(c)) {
				if (tp - work >= WORKSIZE-1)
					fatal("work overflow in define",
						model);
				*tp++ = c;
				c = *model++;
			}
		}
		else {
			/*
			 * { text }
			 */
			model++;
			c = skip;
			if (c != EOS) {
				model++;
				while (c != EOS && c != '}') {
					if (tp - work >= WORKSIZE-1)
						fatal("overflow in define {}",
							model);
					*tp++ = c;
					c = *model++;
				}
			}
			if (c != '}') {
				fprintf(stderr,
					"def of %s not followed by '}'\n",
						def_name);
				continue;
			}
		}
		*tp = EOS;
		sysave(def_name, work, flag);
	}
}		

expout(arg)
char		*arg;
/*
 * Call expand, writing the output to stdout
 */
{
	expand(arg, out, stdout);
}

/*
 * Macro expansion
 */

static int	ex_depth = 0;	/* Catch recursion		*/

char *
expand(arg, sfunc, sarg)
register char	*arg;		/* What to expand			*/
char		*(*sfunc)();	/* Output function			*/
char		*sarg;		/* Output argument			*/
/*
 * Output arg, expanding all macros.  Sfunc is called with each character
 * as follows:
 *	sarg = (*sfunc)(byte, sarg);
 * Thus, to output text to a file, you could execute expand as:
 *	FILE *myfile;
 *	myfile = stdout;
 *	expand(arg, myfun, myfile);
 * Where myfun is defined as:
 *	FILE *
 *	myfun(byte, arg)
 *	char		byte;
 *	FILE		*arg;
 *	{
 *		if (byte != EOS)
 *			putc(byte, arg);
 *		return(arg);
 *
 * To output text to a buffer, do the following:
 *	char *myarg;
 *	myarg = buffer;
 *	myarg = expand(arg, myfun, myarg);
 * On return, myarg will point to the EOS at the end of the string.
 * Myfun() is defined as:
 *	char *
 *	myfun(byte, arg)
 *	char		byte;
 *	char		*arg;
 *	{
 *		if (arg - buffer > sizeof buffer)
 *			fatal("Overstuffed buffer", NULL);
 *		*arg = byte;
 *		if (byte != EOS)
 *			arg++;
 *		return (arg);
 *	}
 */
{
	register char	*np;		/* Name pointer			*/
	register SYMBOL	*sy;		/* Symbol table pointer		*/

	if (++ex_depth > 50)
		fatal("Very recursive macro", NULL);
	if (arg != NULL) {
		if (debug)
			printf("`%d'[", ex_depth);
		while (*arg) {
			if (*arg != '$') {
				sarg = (*sfunc)(*arg++, sarg);
			}
			else if (*++arg == EOS || *arg == ' ') {
				/*
				 * "$ " or "$<EOS>"
				 */
				sarg = (*sfunc)('$', sarg);
			}
			else if (*arg == '$') {
				/*
				 * "$$" => "$"
				 */
				sarg = (*sfunc)('$', sarg);
				arg++;
			}
			else {
				arg = defname(arg);
				sy = sylookup(def_name, FALSE);
				if (sy != NULL && sy->sy_value != NULL) {
					if (debug)
						printf("](%s)<", def_name);
					sarg = expand(sy->sy_value,
							sfunc, sarg);
					if (debug)
						printf(">[");
					sy->sy_flag |= USED;
				}
			}		/* if macro found		*/
		}			/* arg while loop		*/
	}				/* if non-null arg		*/
	sarg = (*sfunc)(EOS, sarg);
	--ex_depth;
	if (debug)
		printf("]");
	return (sarg);
}

