/*
 *				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];	/* Work area for define		*/

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		*/
	char		*argument;

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

	if (debug)
	    fprintf(stderr, "define(\"%s\")\n", model);
	argument = 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);
		fprintf(stderr, " in \"%s\"\n at \"%s\"\n", argument, model);
		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, "definition 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);
}

