/*


 Copyright (C) 1990 Texas Instruments Incorporated.

 Permission is granted to any individual or institution to use, copy, modify,
 and distribute this software, provided that this complete copyright and
 permission notice is maintained, intact, in all copies and supporting
 documentation.

 Texas Instruments Incorporated provides this software "as is" without
 express or implied warranty.


 *			    C P P 4 . C
 *		M a c r o  D e f i n i t i o n s
 *
 * Edit History
 * 30-Oct-90    MJF	Fix __LINE__ when in a macro
 * 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
 * 21-Oct-85	RMS	Rename `token' to `tokenbuf'.
 *			In doundef, don't complain if arg already not defined.
 * 20-Apr-90    MJF     Changed redefining of defined variable to a warning
 * 18-May-90    MBN     Conditional compilation for COOL to get "clean" cpp
 * 25-Jun-91	GPD	Fixes to make ## operator expansion ANSI conformant.
 *  5-Sep-91	DLS	Remove previous fixes which cause COOL problems.
 */

#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(tokenbuf, 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(tokenbuf);		/* 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 (c == '#') {			/* Token concatenation?	*/
	      if((c = get()) != '#') {
		save('#');			/* Lone #, just save it. */
		if(type[c] == LET) {
		  if (checkparm(c, dp)) 	/* Single # next to parm */
		    workp[-2] = MAC_PARM + PAR_MAC;/* Mark next parameter */
		  c = get();}  
	        continue;}
	      while (workp > work && type[(unsigned char)workp[-1]] == SPA)
		--workp;			/* Erase leading spaces	*/
/****	      save(TOK_SEP); */			/* Stuff a delimiter	*/
	      c = skipws();			/* Eat whitespace	*/
	      continue;
	    }
#endif	/*  OK_CONCAT  */
	    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
		scanstring(c, save);
#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)) {
		cwarn("Redefining defined variable \"%s\"", dp->name);
	    }
	    if (old != NULL)			/* We don't need the	*/
		free(old);			/* old definition now.	*/
	}	 
	return(0);

bad_define:
	cerror("#define syntax error", NULLST);
	inmacro = FALSE;			/* Stop <newline> hack	*/
}

int
checkparm(c, dp)
register int	c;
DEFBUF		*dp;
/*
 * Replace this param if it's defined, returning TRUE.
 * 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 tokenbuf */
	for (i = 0; i < nargs; i++) {		/* For each argument	*/
	    if (streq(parlist[i], tokenbuf)) {	/* If it's known	*/
		save(i + MAC_PARM);	        /* Save a magic cookie	*/
		return(TRUE);			/* And exit the search	*/
	    }
	}
	if (streq(dp->name, tokenbuf))		/* Macro name in body?	*/
	    save(DEF_MAGIC);			/* Save magic marker	*/
	for (cp = tokenbuf; *cp != EOS;)	/* And save		*/
	    save(*cp++);			/* The token itself	*/
	return(FALSE);
}

#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);
}
#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 tokenbuf */
      defendel(tokenbuf, TRUE);
    }
}

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
 */

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;

#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(0);
	    }
	}
	/*
	 * Here's a macro to expand.
	 */
	nargs = 0;				/* Formals counter	*/
	parmp = parm;				/* Setup parm buffer	*/
	switch (tokenp->nargs) {
	case DEF_BUILTIN:
	  (tokenp->expander)(tokenp->repl);	/* Builtin  macros      */
	  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(0);
	    }
	    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			*/
}

void
expand_line (dp)
  char* dp;
{
  register FILEINFO	*file;
			  /* Find bottom level file (the file being compiled) */
  for (file = infile; file != NULL; file = file->parent) {
    if (file->parent == NULL) {
      sprintf(work, "%d", file->line);
      ungetstring(work);
      break;
    }
  }
}

void
expand_file (dp)
     char *dp;
{
  register FILEINFO	*file;
			  /* Find bottom level file (the file being compiled) */
  for (file = infile; file != NULL; file = file->parent) {
    if (file->parent == NULL) {
      sprintf(work, "\"%s\"", (file->progname != NULL)
	      ? file->progname : file->filename);
      ungetstring(work);
      break;
    }
  }
}

#ifndef COOL
/* We need this routine here only when we are building a clean, ie. non-COOL
 * preprocessor. For COOL, this function is defined in cpp7.c
 */
FILEINFO*
get_temp_file (bufsize, name)
     int bufsize;
     char* name;
{
  extern FILEINFO	*getfile();
  FILEINFO* file = getfile(bufsize, name);
  infile = file->parent;
  file->parent = NULL;
  line = infile->line;
  return(file);
}
#endif

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) {
		    cfatal("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();
	extern FILEINFO	*get_temp_file();

	file = getfile(NBUFF, tokenp->name);
	inp = tokenp->repl;			/* -> macro replacement	*/
	defp = file->buffer;			/* -> output buffer	*/
	defend = defp + (NBUFF - 24);		/* Note its end		*/
	if (inp != NULL) {
	    while ((c = (*inp++ & 0xFF)) != EOS) {
	      if (defp >= defend) {		/* If out of space      */
		FILEINFO* new = get_temp_file(NBUFF, file->filename);
		new->parent = file->parent; /* When new ends read from infile */
		file->parent = new;	    /* When file ends read from new   */
		file = new;
		*defp = EOS;
		defp = file->buffer;
		defend = defp + (NBUFF - 24);
	      }

	      if (c >= MAC_PARM && c <= (MAC_PARM + PAR_MAC)) {
		string_magic = (c == (MAC_PARM + PAR_MAC));
		if (string_magic) {		/* When quoted parameter */
		  c = (*inp++ & 0xFF);
		  *defp++ = '\"';
		}
		/*
		 * Replace formal parameter by actual parameter string.
		 */
		if ((c -= MAC_PARM) < nargs) {
		  size = strlen(parlist[c]);
		  if ((defp + size + 24) >= defend) {
		    *defp = EOS;		/* If out of space */
		    defend = defp;
		    if (string_magic) inp--; 
		    inp--;			/* backup and try again */
		    continue;
		  }
		  if (string_magic) {		/* When quoted parameter */
		    char* p = parlist[c];
		    for(; (c = *p++) != EOS;) {
		      switch (c) {
		      case '\"':
		      case '\\':
			*defp++ = '\\';
		      default:
			*defp++ = c;
		      } /* switch */
		    } /* for */
		    *defp++ = '\"';
		  } else {
		    strncpy(defp, parlist[c], size);
		    defp += size;
		  } /* if string_magic */
		} /* if valid parm */
	      } /* if parm */
	      else
		/* The following was added by GPD to fix ## token concatenation
		   but it causes cpp to go into a loop in processing the COOL
		   source (Symbol.C) so it was removed.

		if (c != DEF_MAGIC)
		*/
		*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
