/*
 *			    C P P 4 . C
 *		M a c r o  D e f i n i t i o n s
 *
 * Edit History
 * 31-Aug-84	MM	USENET net.sources release
 * 04-Oct-84	MM	__LINE__ and __FILE__ must call ungetstring()
 *			so they work correctly with token concatenation.
 *			Added string formal recognition.
 * 25-Oct-84	MM	"Short-circuit" evaluate #if's so that we
 *			don't print unnecessary error messages for
 *			#if !defined(FOO) && FOO != 0 && 10 / FOO ...
 * 31-Oct-84	ado/MM	Added token concatenation
 *  6-Nov-84	MM	Split off eval stuff
 */

#include	<stdio.h>
#include	<ctype.h>
#include	"cppdef.h"
#include	"cpp.h"
/*
 * parm[], parmp, and parlist[] are used to store #define() argument
 * lists.  nargs contains the actual number of parameters stored.
 */
static char	parm[NPARMWORK + 1];	/* define param work buffer 	*/
static char	*parmp;			/* Free space in parm		*/
static char	*parlist[LASTPARM];	/* -> start of each parameter	*/
static int	nargs;			/* Parameters for this macro	*/

dodefine()
/*
 * Called from control when a #define is scanned.  This module
 * parses formal parameters and the replacement string.  When
 * the formal parameter name is encountered in the replacement
 * string, it is replaced by a character in the range 128 to
 * 128+NPARAM (this allows up to 32 parameters within the
 * Dec Multinational range).  If cpp is ported to an EBCDIC
 * machine, you will have to make other arrangements.
 *
 * There is some special case code to distinguish
 *	#define foo	bar
 * from	#define foo()	bar
 *
 * Also, we make sure that
 *	#define	foo	foo
 * expands to "foo" but doesn't put cpp into an infinite loop.
 *
 * A warning message is printed if you redefine a symbol to a
 * different text.  I.e,
 *	#define	foo	123
 *	#define foo	123
 * is ok, but
 *	#define foo	123
 *	#define	foo	+123
 * is not.
 *
 * The following subroutines are called from define():
 * checkparm	called when a token is scanned.  It checks through the
 *		array of formal parameters.  If a match is found, the
 *		token is replaced by a control byte which will be used
 *		to locate the parameter when the macro is expanded.
 * textput	puts a string in the macro work area (parm[]), updating
 *		parmp to point to the first free byte in parm[].
 *		textput() tests for work buffer overflow.
 * charput	puts a single character in the macro work area (parm[])
 *		in a manner analogous to textput().
 */
{
	register int		c;
	register DEFBUF		*dp;		/* -> new definition	*/
	int			isredefine;	/* TRUE if redefined	*/
	char			*old;		/* Remember redefined	*/
	extern int		save();		/* Save char in work[]	*/

	if (type[(c = skipws())] != LET)
	    goto bad_define;
	isredefine = FALSE;			/* Set if redefining	*/
	if ((dp = lookid(c)) == NULL)		/* If not known now	*/
	    dp = defendel(token, FALSE);	/* Save the name	*/
	else {					/* It's known:		*/
	    isredefine = TRUE;			/* Remember this fact	*/
	    old = dp->repl;			/* Remember replacement	*/
	    dp->repl = NULL;			/* No replacement now	*/
	}
	parlist[0] = parmp = parm;		/* Setup parm buffer	*/
	if ((c = get()) == '(') {		/* With arguments?	*/
	    nargs = 0;				/* Init formals counter	*/
	    do {				/* Collect formal parms	*/
		if (nargs >= LASTPARM)
		    cfatal("Too many arguments for macro", NULLST);
		else if ((c = skipws()) == ')')
		    break;			/* Got them all		*/
		else if (type[c] != LET)	/* Bad formal syntax	*/
		    goto bad_define;
		scanid(c);			/* Get the formal param	*/
		parlist[nargs++] = parmp;	/* Save its start	*/
		textput(token);			/* Save text in parm[]	*/
	    } while ((c = skipws()) == ',');	/* Get another argument	*/
	    if (c != ')')			/* Must end at )	*/
		goto bad_define;
	    c = ' ';				/* Will skip to body	*/
	}
	else {
	    /*
	     * DEF_NOARGS is needed to distinguish between
	     * "#define foo" and "#define foo()".
	     */
	    nargs = DEF_NOARGS;			/* No () parameters	*/
	}
	if (type[c] == SPA)			/* At whitespace?	*/
	    c = skipws();			/* Not any more.	*/
	workp = work;				/* Replacement put here	*/
	inmacro = TRUE;				/* Keep \<newline> now	*/
	while (c != EOF_CHAR && c != '\n') {	/* Compile macro body	*/
#if OK_CONCAT
#if COMMENT_INVISIBLE
	    if (c == COM_SEP) {			/* Token concatenation? */
		save(TOK_SEP);			/* Stuff a delimiter	*/
		c = get();
#else
	    if (c == '#') {			/* Token concatenation?	*/
		while (workp > work && type[workp[-1]] == SPA)
		    --workp;			/* Erase leading spaces	*/
		save(TOK_SEP);			/* Stuff a delimiter	*/
		c = skipws();			/* Eat whitespace	*/
#endif
		if (type[c] == LET)		/* Another token here?	*/
		    ;				/* Stuff it normally	*/
		else if (type[c] == DIG) {	/* Digit string after?	*/
		    while (type[c] == DIG) {	/* Stuff the digits	*/
			save(c);
			c = get();
		    }
		    save(TOK_SEP);		/* Delimit 2nd token	*/
		}
		else {
#if ! COMMENT_INVISIBLE
		    ciwarn("Strange character after # (%d.)", c);
#endif
		}
		continue;
	    }
#endif
	    switch (type[c]) {
	    case LET:
		checkparm(c, dp);		/* Might be a formal	*/
		break;

	    case DIG:				/* Number in mac. body	*/
	    case DOT:				/* Maybe a float number	*/
		scannumber(c, save);		/* Scan it off		*/
		break;

	    case QUO:				/* String in mac. body	*/
#if STRING_FORMAL
		stparmscan(c, dp);		/* Do string magic	*/
#else
		stparmscan(c);
#endif
		break;

	    case BSH:				/* Backslash		*/
		save('\\');
		if ((c = get()) == '\n')
		    wrongline = TRUE;
		save(c);
		break;

	    case SPA:				/* Absorb whitespace	*/
		/*
		 * Note: the "end of comment" marker is passed on
		 * to allow comments to separate tokens.
		 */
		if (workp[-1] == ' ')		/* Absorb multiple	*/
		    break;			/* spaces		*/
		else if (c == '\t')
		    c = ' ';			/* Normalize tabs	*/
		/* Fall through to store character			*/
	    default:				/* Other character	*/
		save(c);
		break;
	    }
	    c = get();
	}
	inmacro = FALSE;			/* Stop newline hack	*/
	unget();				/* For control check	*/
	if (workp > work && workp[-1] == ' ')	/* Drop trailing blank	*/
	    workp--;
	*workp = EOS;				/* Terminate work	*/
	dp->repl = savestring(work);		/* Save the string	*/
	dp->nargs = nargs;			/* Save arg count	*/
#if DEBUG
	if (debug)
	    dumpadef("macro definition", dp);
#endif
	if (isredefine) {			/* Error if redefined	*/
	    if ((old != NULL && dp->repl != NULL && !streq(old, dp->repl))
	     || (old == NULL && dp->repl != NULL)
	     || (old != NULL && dp->repl == NULL)) {
#ifdef STRICT_UNDEF
		cerror("Redefining defined variable \"%s\"", dp->name);
#else
		cwarn("Redefining defined variable \"%s\"", dp->name);
#endif
	    }
	    if (old != NULL)			/* We don't need the	*/
		free(old);			/* old definition now.	*/
	}	 
	return;

bad_define:
	cerror("#define syntax error", NULLST);
	inmacro = FALSE;			/* Stop <newline> hack	*/
}

checkparm(c, dp)
register int	c;
DEFBUF		*dp;
/*
 * Replace this param if it's defined.  Note that the macro name is a
 * possible replacement token.  We stuff DEF_MAGIC in front of the token
 * which is treated as a LETTER by the token scanner and eaten by
 * the output routine.  This prevents the macro expander from
 * looping if someone writes "#define foo foo".
 */
{
	register int		i;
	register char		*cp;

	scanid(c);				/* Get parm to token[]	*/
	for (i = 0; i < nargs; i++) {		/* For each argument	*/
	    if (streq(parlist[i], token)) {	/* If it's known	*/
		save(i + MAC_PARM);		/* Save a magic cookie	*/
		return;				/* And exit the search	*/
	    }
	}
	if (streq(dp->name, token))		/* Macro name in body?	*/
	    save(DEF_MAGIC);			/* Save magic marker	*/
	for (cp = token; *cp != EOS;)		/* And save		*/
	    save(*cp++);			/* The token itself	*/
}

#if STRING_FORMAL
stparmscan(delim, dp)
int		delim;
register DEFBUF	*dp;
/*
 * Scan the string (starting with the given delimiter).
 * The token is replaced if it is the only text in this string or
 * character constant.  The algorithm follows checkparm() above.
 * Note that scanstring() has approved of the string.
 */
{
	register int		c;

	/*
	 * Warning -- this code hasn't been tested for a while.
	 * It exists only to preserve compatibility with earlier
	 * implementations of cpp.  It is not part of the Draft
	 * ANSI Standard C language.
	 */
	save(delim);
	instring = TRUE;
	while ((c = get()) != delim
	     && c != '\n'
	     && c != EOF_CHAR) {
	    if (type[c] == LET)			/* Maybe formal parm	*/
		checkparm(c, dp);
	    else {
		save(c);
		if (c == '\\')
		    save(get());
	    }
	}
	instring = FALSE;
	if (c != delim)
	    cerror("Unterminated string in macro body", NULLST);
	save(c);
}
#else
stparmscan(delim)
int		delim;
/*
 * Normal string parameter scan.
 */
{
	register char		*wp;
	register int		i;
	extern int		save();

	wp = workp;			/* Here's where it starts	*/
	if (!scanstring(delim, save))
	    return;			/* Exit on scanstring error	*/
	workp[-1] = EOS;		/* Erase trailing quote		*/
	wp++;				/* -> first string content byte	*/ 
	for (i = 0; i < nargs; i++) {
	    if (streq(parlist[i], wp)) {
		*wp++ = MAC_PARM + PAR_MAC;	/* Stuff a magic marker	*/
		*wp++ = (i + MAC_PARM);		/* Make a formal marker	*/
		*wp = wp[-3];			/* Add on closing quote	*/
		workp = wp + 1;			/* Reset string end	*/
		return;
	    }
	}
	workp[-1] = wp[-1];		/* Nope, reset end quote.	*/
}
#endif

doundef()
/*
 * Remove the symbol from the defined list.
 * Called from the #control processor.
 */
{
	register int		c;

	if (type[(c = skipws())] != LET)
	    cerror("Illegal #undef argument", NULLST);
	else {
	    scanid(c);				/* Get name to token[]	*/
	    if (defendel(token, TRUE) == NULL) {
#ifdef STRICT_UNDEF
		cwarn("Symbol \"%s\" not defined in #undef", token);
#endif
	    }
	}
}

textput(text)
char		*text;
/*
 * Put the string in the parm[] buffer.
 */
{
	register int	size;

	size = strlen(text) + 1;
	if ((parmp + size) >= &parm[NPARMWORK])
	    cfatal("Macro work area overflow", NULLST);
	else {
	    strcpy(parmp, text);
	    parmp += size;
	}
}

charput(c)
register int	c;
/*
 * Put the byte in the parm[] buffer.
 */
{
	if (parmp >= &parm[NPARMWORK])
	    cfatal("Macro work area overflow", NULLST);
	else {
	    *parmp++ = c;
	}
}

/*
 *		M a c r o   E x p a n s i o n
 */

static DEFBUF	*macro;		/* Catches start of infinite macro	*/

expand(tokenp)
register DEFBUF	*tokenp;
/*
 * Expand a macro.  Called from the cpp mainline routine (via subroutine
 * macroid()) when a token is found in the symbol table.  It calls
 * expcollect() to parse actual parameters, checking for the correct number.
 * It then creates a "file" containing a single line containing the
 * macro with actual parameters inserted appropriately.  This is
 * "pushed back" onto the input stream.  (When the get() routine runs
 * off the end of the macro line, it will dismiss the macro itself.)
 */
{
	register int		c;
	register FILEINFO	*file;
	extern FILEINFO		*getfile();

#if DEBUG
	if (debug)
	    dumpadef("expand entry", tokenp);
#endif
	/*
	 * If no macro is pending, save the name of this macro
	 * for an eventual error message.
	 */
	if (recursion++ == 0)
	    macro = tokenp;
	else if (recursion == RECURSION_LIMIT) {
	    cerror("Recursive macro definition of \"%s\"", tokenp->name);
	    fprintf(stderr, "(Defined by \"%s\")\n", macro->name);
	    if (rec_recover) {
		do {
		    c = get();
		} while (infile != NULL && infile->fp == NULL);
		unget();
		recursion = 0;
		return;
	    }
	}
	/*
	 * Here's a macro to expand.
	 */
	nargs = 0;				/* Formals counter	*/
	parmp = parm;				/* Setup parm buffer	*/
	switch (tokenp->nargs) {
	case (-2):				/* __LINE__		*/
	    sprintf(work, "%d", line);
	    ungetstring(work);
	    break;

	case (-3):				/* __FILE__		*/
	    for (file = infile; file != NULL; file = file->parent) {
		if (file->fp != NULL) {
		    sprintf(work, "\"%s\"", (file->progname != NULL)
			? file->progname : file->filename);
		    ungetstring(work);
		    break;
		}
	    }
	    break;

	default:
	    /*
	     * Nothing funny about this macro.
	     */
	    if (tokenp->nargs < 0)
		cfatal("Bug: Illegal __ macro \"%s\"", tokenp->name);
	    while ((c = skipws()) == '\n')	/* Look for (, skipping	*/
		wrongline = TRUE;		/* spaces and newlines	*/
	    if (c != '(') {
		/*
		 * If the programmer writes
		 *	#define foo() ...
		 *	...
		 *	foo [no ()]
		 * just write foo to the output stream.
		 */
		unget();
		cwarn("Macro \"%s\" needs arguments", tokenp->name);
		fputs(tokenp->name, stdout);
		return;
	    }
	    else if (expcollect()) {		/* Collect arguments	*/
		if (tokenp->nargs != nargs) {	/* Should be an error?	*/
		    cwarn("Wrong number of macro arguments for \"%s\"",
			tokenp->name);
		}
#if DEBUG
		if (debug)
		    dumpparm("expand");
#endif
	    }				/* Collect arguments		*/
	case DEF_NOARGS:		/* No parameters just stuffs	*/
	    expstuff(tokenp);		/* Do actual parameters		*/
	}				/* nargs switch			*/
}

FILE_LOCAL int
expcollect()
/*
 * Collect the actual parameters for this macro.  TRUE if ok.
 */
{
	register int	c;
	register int	paren;			/* For embedded ()'s	*/
	extern int	charput();

	for (;;) {
	    paren = 0;				/* Collect next arg.	*/
	    while ((c = skipws()) == '\n')	/* Skip over whitespace	*/
		wrongline = TRUE;		/* and newlines.	*/
	    if (c == ')') {			/* At end of all args?	*/
		/*
		 * Note that there is a guard byte in parm[]
		 * so we don't have to check for overflow here.
		 */
		*parmp = EOS;			/* Make sure terminated	*/
		break;				/* Exit collection loop	*/
	    }
	    else if (nargs >= LASTPARM)
		cfatal("Too many arguments in macro expansion", NULLST);
	    parlist[nargs++] = parmp;		/* At start of new arg	*/
	    for (;; c = cget()) {		/* Collect arg's bytes	*/
		if (c == EOF_CHAR) {
		    cerror("end of file within macro argument", NULLST);
		    return (FALSE);		/* Sorry.		*/
		}
		else if (c == '\\') {		/* Quote next character	*/
		    charput(c);			/* Save the \ for later	*/
		    charput(cget());		/* Save the next char.	*/
		    continue;			/* And go get another	*/
		}
		else if (type[c] == QUO) {	/* Start of string?	*/
		    scanstring(c, charput);	/* Scan it off		*/
		    continue;			/* Go get next char	*/
		}
		else if (c == '(')		/* Worry about balance	*/
		    paren++;			/* To know about commas	*/
		else if (c == ')') {		/* Other side too	*/
		    if (paren == 0) {		/* At the end?		*/
			unget();		/* Look at it later	*/
			break;			/* Exit arg getter.	*/
		    }
		    paren--;			/* More to come.	*/
		}
		else if (c == ',' && paren == 0) /* Comma delimits args	*/
		    break;
		else if (c == '\n')		/* Newline inside arg?	*/
		    wrongline = TRUE;		/* We'll need a #line	*/
		charput(c);			/* Store this one	*/
	    }					/* Collect an argument	*/
	    charput(EOS);			/* Terminate argument	*/
#if DEBUG
	    if (debug)
	        printf("parm[%d] = \"%s\"\n", nargs, parlist[nargs - 1]);
#endif
	}					/* Collect all args.	*/
	return (TRUE);				/* Normal return	*/
}

FILE_LOCAL
expstuff(tokenp)
DEFBUF		*tokenp;		/* Current macro being expanded	*/
/*
 * Stuff the macro body, replacing formal parameters by actual parameters.
 */
{
	register int	c;			/* Current character	*/
	register char	*inp;			/* -> repl string	*/
	register char	*defp;			/* -> macro output buff	*/
	int		size;			/* Actual parm. size	*/
	char		*defend;		/* -> output buff end	*/
	int		string_magic;		/* String formal hack	*/
	FILEINFO	*file;			/* Funny #include	*/
	extern FILEINFO	*getfile();

	file = getfile(NBUFF, tokenp->name);
	inp = tokenp->repl;			/* -> macro replacement	*/
	defp = file->buffer;			/* -> output buffer	*/
	defend = defp + (NBUFF - 1);		/* Note its end		*/
	if (inp != NULL) {
	    while ((c = (*inp++ & 0xFF)) != EOS) {
		if (c >= MAC_PARM && c <= (MAC_PARM + PAR_MAC)) {
		    string_magic = (c == (MAC_PARM + PAR_MAC));
		    if (string_magic)
		 	c = (*inp++ & 0xFF);
		    /*
		     * Replace formal parameter by actual parameter string.
		     */
		    if ((c -= MAC_PARM) < nargs) {
			size = strlen(parlist[c]);
			if ((defp + size) >= defend)
			    goto nospace;
			/*
			 * Erase the extra set of quotes.
			 */
			if (string_magic && defp[-1] == parlist[c][0]) {
			    strcpy(defp-1, parlist[c]);
			    defp += (size - 2);
			}
			else {
			    strcpy(defp, parlist[c]);
			    defp += size;
			}
		    }
		}
		else if (defp >= defend) {
nospace:	    cfatal("Out of space in macro \"%s\" arg expansion",
			tokenp->name);
		}
		else {
		    *defp++ = c;
		}
	    }
	}
	*defp = EOS;
#if DEBUG
	if (debug > 1)
	    printf("macroline: \"%s\"\n", file->buffer);
#endif
}

#if DEBUG
dumpparm(why)
char		*why;
/*
 * Dump parameter list.
 */
{
	register int	i;

	printf("dump of %d parameters (%d bytes total) %s\n",
	    nargs, parmp - parm, why);
	for (i = 0; i < nargs; i++) {
	    printf("parm[%d] (%d) = \"%s\"\n",
		i + 1, strlen(parlist[i]), parlist[i]);
	}
}
#endif
