/************************************************************************/
/*									*/
/* RCPP - Resource Compiler Pre-Processor for NT system			*/
/*									*/
/* P0MACROS.C - Preprocessor Macros definitions				*/
/*									*/
/* 27-Nov-90 w-BrianM  Update for NT from PM SDK RCPP			*/
/*									*/
/************************************************************************/

#include <windows.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <malloc.h>
#include "rcpptype.h"
#include "rcppdecl.h"
#include "rcppext.h"
#include "p0defs.h"
#include "charmap.h"


/************************************************************************
**
**	WARNING:	gather_chars() depends ELIMIT being the boundary of
**		Macro_buffer.
************************************************************************/
#define	ACT_BUFFER		&Macro_buffer[0]
#define	EXP_BUFFER		&Macro_buffer[BIG_BUFFER * 2]
#define	EXP_PAD			5
#define ALIMIT			&Macro_buffer[BIG_BUFFER * 2]
#define ELIMIT			(&Macro_buffer[BIG_BUFFER * 4] - EXP_PAD)


/************************************************************************
**  actual argument lists are length preceeded strings which are copied
**  into ACT_BUFFER. the first argument is pt'd to by exp_actuals in the
**  expansion_t construct. the next actual is obtained by adding the length
**  of the current actual to the start of the current actual.
************************************************************************/
#define ACTUAL_SIZE(P)	(*(short *)(P))
#define	ACTUAL_TEXT(P)	((P) + sizeof(short))
#define	ACTUAL_NEXT(P)	((P) + ACTUAL_SIZE(P))

/************************************************************************
**	the formals are copied into the buffer similar to the actuals, except
**	the size is denoted by an unsigned char, instead of short
************************************************************************/
#define	FORMAL_SIZE(P)	(*(ptext_t)(P))
#define	FORMAL_TEXT(P)	((P) + sizeof(UCHAR))
#define	FORMAL_NEXT(P)	((P) + FORMAL_SIZE(P))

expansion_t	Macro_expansion[LIMIT_MACRO_DEPTH];

ptext_t	P_defn_start;
int		N_formals;
pdefn_t	Defn_level_0[LEVEL_0 + 1];


/************************************************************************
**	These are needed by p0scanner (Exp_ptr,Tiny_lexer_nesting)
************************************************************************/
ptext_t	Exp_ptr = EXP_BUFFER;		/* ptr to free exp space */
int		Tiny_lexer_nesting;	/* stay in tiny lexer or back to main */

static	ptext_t	Act_ptr = ACT_BUFFER;		/* ptr to free actuals space */
static	ptext_t	Save_Exp_ptr = EXP_BUFFER;	/* for buffering unbal parens */

static	ptext_t	P_actuals;		/* actuals for this (level) macro */
static	int		N_actuals;	/* number of actuals in invocation */
static	int		Macro_line;	/*  where we started the macro  */


/************************************************************************/
/* Local Function Prototypes						*/
/************************************************************************/
void	chkbuf(ptext_t);
ptext_t	do_strformal(void);
ptext_t	do_macformal(int *);
void	expand_actual(UCHAR);
void	expand_definition(void);
void	expand_macro(void);
void	fatal_in_macro(int);
ptext_t	gather_chars(ptext_t, UCHAR);
void	get_actuals(pdefn_t, int);
int	get_definition(void);
void	get_formals(void);
int	is_macro_arg(ptext_t);
void	move_to_actual(ptext_t, ptext_t);
void	move_to_exp(ptext_t);
void	move_to_exp_esc(int, ptext_t);
int	post_paste(void);
void	push_macro(pdefn_t);
int	redefn (ptext_t, ptext_t, int);
int	rescan_expansion(void);


/************************************************************************
** UNDEFINE - remove a symbol from the symbol table
**	No noise is made if the programmer attempts to undefine a predefined
**		macro, but it is not done.
************************************************************************/
void   undefine(void)
{
    pdefn_t	pdef;
    pdefn_t	prev;

    prev = NULL;
    pdef = Defn_level_0[Reuse_1_hash & LEVEL_0];
    while(pdef) {
	if(memcmp (Reuse_1, DEFN_IDENT(pdef), Reuse_1_length) == 0) {
	    if(PRE_DEFINED(pdef)) {
		Msg_Temp = GET_MSG (4112);
		SET_MSG (Msg_Text, Msg_Temp, Reuse_1, "#undef");
		warning(4112);
		break;
	    }
	    if(prev == NULL)	/*  match at head of list  */
		Defn_level_0[Reuse_1_hash & LEVEL_0] = DEFN_NEXT(pdef);
	    else
		DEFN_NEXT(prev) = DEFN_NEXT(pdef);
	    break;
	}
	prev = pdef;
	pdef = DEFN_NEXT(pdef);
    }
}


/************************************************************************
**  BEGIN DEFINE A MACRO {
************************************************************************/
void   define(void)
{
    UCHAR	c;

    if (! (LX_IS_IDENT(c = skip_cwhite())) ) {
	Msg_Temp = GET_MSG (2007);
	SET_MSG (Msg_Text, Msg_Temp);
        error (2007); /* #define syntax */
	skip_cnew();
	return;
    }
    getid(c);
    N_formals = 0;
    P_defn_start = Macro_buffer;
/*
**  the next character must be white space or an open paren
*/
first_switch:
    switch(CHARMAP(c = GETCH())) {
    case LX_OPAREN:			/*  we have formal parameters  */
	get_formals();		/*  changes N_formals and fills Macro_buffer */
	if(N_formals == 0) {/*  empty formal list  */
		/*
		**  we must special case this since the expand() reads in the
		**  actual arguments iff there are formal parameters. thus if we
		**	#define	foo()	bar()
		**		. . .
		**		foo()
		**  will expand as
		**		bar()()
		**  we put the right paren in to fool the expander into looking
		**  for actuals.
		*/
	    N_formals = -1;
	}
	break;
    case LX_WHITE:
	break;
    case LX_CR:
	goto first_switch;
    case LX_SLASH:
	if( ! skip_comment()) {
	    Msg_Temp = GET_MSG (2008);
            SET_MSG (Msg_Text, Msg_Temp, '/');
            error (2008);
	}
	break;
    case LX_NL:			/* no definition */
	UNGETCH();
	definstall((ptext_t)0, 0, 0);
	return;
	break;
    case LX_EOS:
	if(handle_eos() != BACKSLASH_EOS) {
	    goto first_switch;
	}
	/* got BACKSLASH_EOS */
	/*
	**  FALLTHROUGH
	*/
    default:
	Msg_Temp = GET_MSG (2008);
        SET_MSG (Msg_Text, Msg_Temp, c);
        error (2008); /* unexpected character in macro definition */
    }
    definstall(P_defn_start, get_definition(), N_formals);
}


/************************************************************************
**  get_formals : collect comma separated idents until the first closing paren
**  (the openning paren has already been read)
**  since we can't be in a macro when we're asked for this, we can be assured
**  that we can use a single buffer to collect all the formal names.
************************************************************************/
void   get_formals(void)
{
    UCHAR		c;
    ptext_t	p_stop;
    ptext_t	p_id;

    p_id = p_stop = FORMAL_TEXT(P_defn_start);
    for(;;) {
	switch(CHARMAP(c = skip_cwhite())) {
	case LX_ID:
	    if( p_id != p_stop ) {
		Msg_Temp = GET_MSG (2010);
        	SET_MSG (Msg_Text, Msg_Temp, c);
        	error (2010);
	    }
	    *p_stop++ = c;
	    for(;;) {
		while(LXC_IS_IDENT(c = GETCH())) {	/* while an id char */
		    *p_stop++ = c;			/* collect it */
		} if(c == EOS_CHAR) {
		 	/*
			**  found end of buffer marker, make sure it is,
			**  then handle it.
			*/
		    if(io_eob()) {	/* end of buffer in here is bad */
			Msg_Temp = GET_MSG (1004);
			SET_MSG (Msg_Text, Msg_Temp);
			fatal (1004);
		    }
		    continue;
		}
		if((c == '\\') && (checknl())) {
		    continue;
		}
		UNGETCH();
		break;
	    }
	    *p_stop++ = '\0';
	    break;
	case LX_COMMA:
	case LX_CPAREN:
	    if( p_stop > p_id ) {
		/* make sure an identifier was read */
		if((p_stop - p_id) >= TINY_BUFFER) {
		    p_id[TINY_BUFFER - 1] = '\0';
		    Msg_Temp = GET_MSG (4111);
		    SET_MSG (Msg_Text, Msg_Temp, p_id);
		    warning(4011);		/* id truncated */
		    p_stop = p_id + TINY_BUFFER;
		}
		if(is_macro_arg(p_id) >= 1) {
		    Msg_Temp = GET_MSG (2009);
        	    SET_MSG (Msg_Text, Msg_Temp, p_id);
		    error(2009);		/* reuse of formal */
		}
		else {
		    FORMAL_SIZE(P_defn_start) = (UCHAR)(p_stop - P_defn_start);
		    P_defn_start = p_stop;
		    N_formals++;
		}
	    }
	    else {
		if( (CHARMAP(c) == LX_COMMA) || (N_formals > 0) ) {
		    Msg_Temp = GET_MSG (2010);
        	    SET_MSG (Msg_Text, Msg_Temp, c);
		    error(2010);
		}
	    }
	    if( CHARMAP(c) == LX_CPAREN ) {
		return;
	    }
	    p_id = p_stop = FORMAL_TEXT(P_defn_start);
	    break;
	default:
	    Msg_Temp = GET_MSG (2010);
       	    SET_MSG (Msg_Text, Msg_Temp, c);
	    error(2010); /*  unexpected char in formal list */
	    break;
	}
    }
}


/************************************************************************
** definstall - Install a new definition. id is in Reuse_1.
**	p_text : ptr to the definition
**	n : number of bytes in the definition (may contain embedded nulls)
**	number : number of formals
************************************************************************/
void   definstall(ptext_t p_text, int n, int number)
{
    pdefn_t	p;

    if(n == 0) {
	p_text = NULL;
    }
    if( strcmp (Reuse_1, "defined") == 0) {
	Msg_Temp = GET_MSG (4112);
        SET_MSG (Msg_Text, Msg_Temp, Reuse_1, "#define");
	warning(4112);/* name reserved */
	return;
    }
    if((p = get_defined()) != 0) {
	if(PRE_DEFINED(p)) {
	    Msg_Temp = GET_MSG (4112);
            SET_MSG (Msg_Text, Msg_Temp, Reuse_1, "#define");
	    warning(4112);/* name reserved */
	    return;
	}
	else {
	    if(redefn(p_text, DEFN_TEXT(p), n)) {
		Msg_Temp = GET_MSG (4005);
                SET_MSG (Msg_Text, Msg_Temp, Reuse_1);
		warning(4005);/* redefinition */
	    }
	    else {
		return;
	    }
	}
    }
    else {
	hln_t	ident;

	HLN_NAME(ident) = Reuse_1;
	HLN_HASH(ident) = Reuse_1_hash;
	HLN_LENGTH(ident) = (UCHAR)Reuse_1_length;
	p = malloc(sizeof(defn_t));
	if (p == NULL) {
	    Msg_Temp = GET_MSG (1002);
	    SET_MSG (Msg_Text, Msg_Temp);
	    error(1002);
	    return;
	}
	DEFN_IDENT(p) = HLN_TO_NAME(&ident);
	DEFN_NEXT(p) = Defn_level_0[Reuse_1_hash & LEVEL_0];
	DEFN_TEXT(p) = (char*)NULL;
	DEFN_EXPANDING(p) = 0;
	Defn_level_0[Reuse_1_hash & LEVEL_0] = p;
    }
    if(n != 0) {
	DEFN_TEXT(p) = pstrndup(p_text, n);
	if(number == FROM_COMMAND) {	/* special case from cmd line */
	    *(DEFN_TEXT(p) + n - 1) = EOS_DEFINITION;	/* for handle_eos */
	}
    }
    DEFN_NFORMALS(p) = (char)((number != FROM_COMMAND) ? number : 0);
}


/************************************************************************
**  get_defined : is the given id in the macro symbol table?
**  return a ptr to it if so, NULL if not.
************************************************************************/
pdefn_t	  get_defined(void)
{
    pdefn_t	pdef;

    for( pdef = Defn_level_0[Reuse_1_hash & LEVEL_0]; pdef;
        pdef = DEFN_NEXT(pdef)) 	{
	if(memcmp (Reuse_1, DEFN_IDENT(pdef), Reuse_1_length) == 0) {
	    return(pdef);
	}
    }
    return(NULL);
}


/************************************************************************
**  redefn : are the two definitions the same?
************************************************************************/
int redefn(REG	ptext_t p_new, ptext_t p_old, int n)
{
    if(p_old && p_new) {
	if(strncmp(p_new, p_old, n) == 0) {	/* strings are exact */
	    return(FALSE);
	}
	return(TRUE);
    }
    return((p_old != NULL) || (p_new != NULL));
}


/************************************************************************
**  get_definition : accumulate the macro definition, stops when it finds
**  a newline (it uses it). returns a ptr to the end of the string it builds.
**  builds the string in Macro_buffer. (given the start in P_defn_start)
************************************************************************/
int   get_definition(void)
{
    REG	ptext_t	p;
    UCHAR	c;
    int		stringize = FALSE;
    int		charize = FALSE;

    p = P_defn_start;
    c = skip_cwhite();
    for(;;) {
	chkbuf(p);
	switch(CHARMAP(c)) {
	case LX_EOS:
	    if(handle_eos() == BACKSLASH_EOS) {
		/* got backslash EOS */
		/* \<anything else> goes out as is.  The <anything else>
			* character must be emitted now, so that
			*		#define FOO(name)	\name
			*		. . .
			*		FOO(bar)
			*
			* does NOT see occurence of name in the definition as an
			* occurence of the formal param and emit \bar when it is
			* expanded later,but if the definition is \nname it will
			* find name as a formal paramater and emit \nbar
			*/
		*p++ = c;	/* put in backslash, break'll add new char */
		c = get_non_eof();
	    }
	    else {
		c = GETCH();
		continue;
	    }
	    break;
	case LX_NL:		/*  only way out  */
	    UNGETCH();
	    if(p == P_defn_start) {
		return(0);
	    }
	    chkbuf(p);
	    *p++ = EOS_CHAR;
	    *p++ = EOS_DEFINITION;	/* tells handle_eos defn finished */
	    return((int)(p - P_defn_start));/* p's last incr counts the 0*/
	    break;
	case LX_DQUOTE:
	case LX_SQUOTE:
	    p = gather_chars(p, c);
	    c = GETCH();
	    continue;
	    break;
	case LX_POUND:
split_op:
	    switch(CHARMAP(GETCH())) {
	    case LX_POUND:
		/*
		**  handle ## processing. cant be the first or the last.
		*/
		if(p == P_defn_start) {
	    	    Msg_Temp = GET_MSG (2160);
       	    	    SET_MSG (Msg_Text, Msg_Temp);
		    error(2160);	/* ## not allowed as first entry */
		    continue;
		}
		if(*(p - 1) == ' ') {	/* hose the last blank */
		    p--;
		}
		if(CHARMAP(c = skip_cwhite()) == LX_NL) {
		    UNGETCH();
	    	    Msg_Temp = GET_MSG (2161);
       	    	    SET_MSG (Msg_Text, Msg_Temp);
		    error(2161);
		    continue;
		}
		/* this case does *not* fall through to LX_ID */
		continue;
		break;
	    case LX_EACH:
		charize = TRUE;
		break;
	    case LX_EOS:
		if( handle_eos() != BACKSLASH_EOS ) {
		    goto split_op;
		}
		/*
		**	FALLTHROUGH
		*/
	    default:
		UNGETCH();
		stringize = TRUE;
		break;
	    }
	    if(CHARMAP(c = skip_cwhite()) != LX_ID) {
	    	Msg_Temp = GET_MSG (2162);
       	    	SET_MSG (Msg_Text, Msg_Temp);
		error(2162);	/* must have id following */
		continue;
	    }
	    /*
	    **  FALLTHROUGH
	    */
	case LX_ID:
	    {
		/* we have the start of an identifier - check it to see if
		 * its an occurence of a formal parameter name.
		 * we gather the id ourselves (instead of getid()) since this
		 * wil save us from having to copy it to our string if it's
		 * not a formal parameter.
		 */
		int			n;
		ptext_t	p_macformal;

		p_macformal = p;
		do {
		    chkbuf(p);
		    *p++ = c;
get_more_id:
		    c = GETCH();
		} while(LXC_IS_IDENT(c));
		if(CHARMAP(c) == LX_EOS) {
		    if(handle_eos() != BACKSLASH_EOS) {
			goto get_more_id;
		    }
		}
		*p = '\0'; /* term. string, but do not advance ptr */
		if((n = is_macro_arg(p_macformal)) >= 1) {
		    /*
		    **  this is an occurance of formal 'n', replace the id with
		    **  the special MAC character.
		    */
		    p = p_macformal;
		    if(stringize) {
			*p++ = LX_FORMALSTR;
		    }
		    else {
			if(charize) {
			    *p++ = LX_FORMALCHAR;
			}
			else {
			    *p++ = LX_FORMALMARK;
			}
		    }
		    *p++ = (UCHAR) n;
		}
		else if(charize || stringize) {
	  	    Msg_Temp = GET_MSG (2162);
       	    	    SET_MSG (Msg_Text, Msg_Temp);
		    error(2162);
		}
		stringize = FALSE;
		charize = FALSE;
		continue;	/* we broke out of the loop with a new char */
	    }
	case LX_SLASH:
	    if( ! skip_comment() ) {	/* really is a slash */
		break;
	    }
	    /*
			**  FALLTHROUGH
			*/
	case LX_CR:
	case LX_WHITE:
	    /*
	    **  this is white space, all contiguous whitespace is transformed
	    **  to 1 blank. (hence the skip_cwhite() and the continue).
	    */
	    if(CHARMAP(c = skip_cwhite()) != LX_NL) {
		*p++ = ' ';
	    }
	    continue;				/* restart loop */
	case LX_ILL:
	    Msg_Temp = GET_MSG (2018);
       	    SET_MSG (Msg_Text, Msg_Temp, c);
	    error(2018);
	    c = GETCH();
	    continue;
	}
	*p++ = c;
	c = GETCH();
    }
}


/************************************************************************/
/* is_macro_arg ()							*/
/************************************************************************/
int   is_macro_arg(ptext_t name)
{
    REG	int			i;
    REG	ptext_t	p;

    p = Macro_buffer;
    for(i = 1; i <= N_formals; i++) {
	if( strcmp(name, FORMAL_TEXT(p)) == 0) {
	    return(i);
	}
	p = FORMAL_NEXT(p);
    }
    return(-1);
}



/************************************************************************/
/* chkbuf ()								*/
/************************************************************************/
void   chkbuf(ptext_t p)
{
    if( p >= ELIMIT ) {
	Msg_Temp = GET_MSG (1065);
	SET_MSG (Msg_Text, Msg_Temp, Reuse_1);
	fatal (1065);
    }
}


/************************************************************************
**  gather_chars : collect chars until a matching one is found.
**  skip backslashed chars. moves the chars into the buffer,
**  returns a ptr past the last char copied.
************************************************************************/
ptext_t   gather_chars(REG ptext_t p, UCHAR match_c)
{
    UCHAR	c;

    *p++ = match_c;
    for(;;) {
	if(p > ELIMIT) {
	    return(ELIMIT);
	}
	switch(CHARMAP(c = GETCH())) {
	case LX_NL:
	    Msg_Temp = GET_MSG (2001);
       	    SET_MSG (Msg_Text, Msg_Temp);
	    error(2001);
	    UNGETCH();
	    c = match_c;
	    /*
			**  FALLTHROUGH
			*/
	case LX_DQUOTE:
	case LX_SQUOTE:
	    if(c == match_c) {
		*p++ = c;
		return(p);		/* only way out */
	    }
	    break;
	case LX_EOS:
	    if(handle_eos() != BACKSLASH_EOS) {
		continue;
	    }
	    else {
		/* got backslash */
		*p++ = '\\';
		c = get_non_eof();
		if((c == '\\') && (checknl())) {
		    continue;
		}
	    }
	    break;
	case LX_LEADBYTE:
	    *p++ = c;
	    c = get_non_eof();
	    break;
	}
	*p++ = c;
    }
}
/************************************************************************
**  END DEFINING MACROS }
************************************************************************/

/************************************************************************
**  BEGIN EXPANDING MACROS {
************************************************************************/
/************************************************************************
**	can_expand:		tries to expand the macro passed to it - returns
**		true if it succeeded in expanding it.  It will only return FALSE
**		if a macro name was found, a paren was expected, and a paren was
**		not the next non white character.
************************************************************************/
int can_expand(pdefn_t pdef)
{
    UCHAR		c;
    int			n_formals;
    int			return_value = FALSE;

    Tiny_lexer_nesting = 0;
    Save_Exp_ptr = Exp_ptr;		/* not necessarily EXP_BUFFER */
    Macro_line = Linenumber;
expand_name:

    P_actuals = Act_ptr;
    N_actuals = 0;

    n_formals = DEFN_NFORMALS(pdef);
    if( PRE_DEFINED(pdef) ) {
	push_macro(pdef);
	DEFN_EXPANDING(CURRENT_MACRO)++;
	if(rescan_expansion()) {
	    return(TRUE);			/* could expand macro */
	}
    }
    else if( n_formals == 0 ) {
	return_value = TRUE;
	if(DEFN_TEXT(pdef)) {
	    push_macro(pdef);
	    expand_definition();
	}
	else {
	    /*
		**	Macro expands to nothing (no definition).  Since it
		**	didn't have any actuals, Act_ptr is already correct.
		**	Exp_ptr must be changed however to delete the
		**	identifier from the expanded text.
		*/
	    Exp_ptr = Save_Exp_ptr;
	}
    }
    else {
	if( n_formals == -1 ) {
	    n_formals = 0;
	}
name_comment_paren:
	if( can_get_non_white()) {
	    if(CHARMAP(CHECKCH()) == LX_SLASH) {
		SKIPCH();
		if(skip_comment()) {
		    goto name_comment_paren;
		}
		else {
		    UNGETCH();
		}
	    }
	    if(CHARMAP(CHECKCH())==LX_OPAREN) {
		SKIPCH();
		return_value = TRUE;
		get_actuals(pdef, n_formals);
	    }
	    else {
		/*
				**	#define xx(a) a
				**  xx bar();
				**  don't lose white space between "xx" and "bar"
				*/
		ptext_t	p = Exp_ptr;

		push_macro(pdef);
		DEFN_EXPANDING(CURRENT_MACRO)++;
		Exp_ptr = p;
		if( rescan_expansion() ) {
		    return(FALSE);
		}
	    }
	}
	else {
	}
    }
    /*
	**	makes sure a macro is being worked on. At this point, there will
	**	be a macro to expand, unless the macro expand_the_named_macro was
	**	passed had no definition text.  If it had no defintion text,
	**	Tiny_lexer_nesting was not incremented.
	*/
    while(Tiny_lexer_nesting != 0) {
	if(Exp_ptr >= ELIMIT) {
	    fatal_in_macro(10056);
	}
	switch(CHARMAP(c = GETCH())) {
	case LX_ID:
	case LX_MACFORMAL:
	    Save_Exp_ptr = Exp_ptr;
	    if(tl_getid(c) && ((pdef = get_defined())!= 0)) {
		if(DEFN_EXPANDING(pdef)) {
		    /*
						**	the macro is already being expanded, so just
						**	write the do not expand marker and the
						**	identifier to the expand area.  The do not
						**	expand marker is necessary so this macro
						**	doesn't get expanded on the rescan
						*/
		    int		len = Reuse_1_length - 1;

		    *Exp_ptr++ = LX_NOEXPANDMARK;
		    *Exp_ptr++ = ((UCHAR)len);
		}
		else {
		    /*
						** a legal identifier was read, it is defined, and
						** it is not currently being expanded.  This means
						** there is reason to believe it can be expanded.
						*/
		    goto expand_name;
		}
	    }
	    if(InIf &&(memcmp(Reuse_1, "defined", 8) ==0)) {
		do_defined(Reuse_1);
	    }
	    continue;
	    break;
	case LX_NUMBER:
	    /* getnum with Prep on to keep leading 0x on number */
	    {
		int	Save_prep = Prep;
		Prep = TRUE;
		getnum(c);
		Prep = Save_prep;
	    }
	    continue;
	    break;
	case LX_DOT:
	    *Exp_ptr++ = '.';
dot_switch:
	    switch(CHARMAP(c = GETCH())) {
	    case LX_EOS:
		if(handle_eos() != BACKSLASH_EOS) {
		    if(Tiny_lexer_nesting > 0) {
			goto dot_switch;
		    }
		    continue;
		}
		break;
	    case LX_DOT:
		*Exp_ptr++ = '.';
		if( ! checkop('.')) {
		    break;	/* error will be caught on rescan */
		}
		*Exp_ptr++ = '.';
		continue;
		break;
	    case LX_NUMBER:
		*Exp_ptr++ = c;
		get_real(Exp_ptr);
		continue;
	    }
	    UNGETCH();
	    continue;
	case LX_CHARFORMAL:
	    move_to_exp_esc('\'', do_strformal());
	    continue;
	    break;
	case LX_STRFORMAL:
	    move_to_exp_esc('"', do_strformal());
	    continue;
	    break;
	case LX_DQUOTE:
	case LX_SQUOTE:
	    /*
	    ** 	gather_chars is called even though the error reported
	    **	on overflow may need to be changed.
	    */
	    Exp_ptr = gather_chars(Exp_ptr, c);
	    continue;
	    break;
	case LX_WHITE:
	    while(LXC_IS_WHITE(GETCH())) {
		;
	    }
	    UNGETCH();
	    c = ' ';
	    break;
	case LX_EOS:
	    if(handle_eos() == BACKSLASH_EOS) {
		*Exp_ptr++ = c;
		c = GETCH();
		break;
	    }
	    continue;
	    break;
	}
	*Exp_ptr++ = c;
    }
    return(return_value);
}


/************************************************************************
**  get_actuals :  Paren must already be found.  If all the actuals can
**		be read, the macro is pushed and expansion begins. Otherwise,
**		this function is quickly exited and lets the tiny lexer take
**		care of rescanning.
************************************************************************/
void   get_actuals(pdefn_t pdef, int n_formals)
{
    /*
    **	The only concern with this is that a rescan could finish while
    **	this is trying to collect actuals.  When a rescan finishes, it
    **	may reset Act_ptr and Exp_ptr.  Unless these are saved before the
    **	end of rescan is handled, the part of the actual collected so far
    **	would be lost.
    */
    REG	ptext_t	start;
    UCHAR		c;
    ptext_t	actuals_start;
    int			paste;
    int			level;

    *Exp_ptr++ = PREVCH();			/* must be oparen */
    level = 0;
    actuals_start = Act_ptr;

    while( level >= 0) {
	if(Exp_ptr >= ELIMIT) {
	    fatal_in_macro(10056);
	}
more_white:
	if( ! can_get_non_white()) {
	    return;
	}
	if(CHARMAP(CHECKCH()) == LX_SLASH) {
	    SKIPCH();
	    if(skip_comment()) {
		goto more_white;
	    }
	    else {
		start = Exp_ptr;
		*Exp_ptr++ = '/';
	    }
	}
	else {
	    start = Exp_ptr;
	}
	paste = FALSE;

	for(;;) {
	    switch(CHARMAP(c = GETCH())) {
	    case LX_CPAREN:
		if(--level < 0) {
		    goto leave_loop;
		}
		break;
	    case LX_COMMA:
		/*
		**	if the comma is not at level == 0, it is part of
		**	a parenthesized list and not a delimiter
		*/
		if(level == 0) {
		    goto leave_loop;
		}
		break;
	    case LX_SLASH:
		if( ! skip_comment()) {
		    break;
		}
		if(*(Exp_ptr - 1) == ' ') {
		    continue;
		}
		c = ' ';
		break;
	    case LX_CR:
	    case LX_NL:
	    case LX_WHITE:
		UNGETCH();		/* This char is valid white space */
		if( ! can_get_non_white()) {
		    return;
		}
		continue;
		break;
	    case LX_OPAREN:
		++level;
		break;
	    case LX_DQUOTE:
	    case LX_SQUOTE:
		Exp_ptr = gather_chars(Exp_ptr, c);
		continue;
		break;
	    case LX_ID:
		*Exp_ptr++ = c;
		while(LXC_IS_IDENT(c = GETCH())) {
		    if(Exp_ptr >= ELIMIT) {
			fatal_in_macro(10056);
		    }
		    *Exp_ptr++ = c;
		}
		if(CHARMAP(c) != LX_MACFORMAL) {
		    UNGETCH();
		    continue;
		}
		paste = TRUE;
		/*
		**	FALLTHROUGH
		*/
	    case LX_MACFORMAL:
		move_to_exp(do_macformal(&paste));
		continue;
		break;
	    case LX_STRFORMAL:
		move_to_exp_esc('"', do_strformal());
		continue;
		break;
	    case LX_CHARFORMAL:
		move_to_exp_esc('\'', do_strformal());
		continue;
		break;
	    case LX_EOS:
		/*
		**	Will saving this pointers create dead space in the
		**	buffers?  Yes, but only temporarily.
		**
		**	handle_eos() may reset Act_ptr and Exp_ptr to the
		**	beginning of the buffers if a rescan is finishing
		**	and Macro_depth is going to be 0.  ANSI allows
		**	actuals to start within a macro defintion and be
		**	completed (further actuals and closing paren) later
		**	in the text.
		**
		**	These buffer pointers will eventually be reset to
		**	the beginnings of their respective buffers when the
		**	macro for the actuals being collected right now
		**	finish rescan
		**
		**	This is special handling for folks who use
		**	unbalanced parens in macro definitions
		*/
		{
		    ptext_t	Exp_save;
		    ptext_t	Act_save;
		    int	eos_res;

		    Exp_save = Exp_ptr;
		    Act_save = Act_ptr;
		    if((eos_res = handle_eos()) & (ACTUAL_EOS | RESCAN_EOS)) {
			return;
		    }
		    Act_ptr = Act_save;
		    Exp_ptr = Exp_save;
		    if(eos_res == BACKSLASH_EOS) {	/* ??? DFP QUESTION  */
			*Exp_ptr++ = c;		/*  save the \  */
			c = get_non_eof();	/*  get char following \  */
			break;
		    }
		}
		continue;
		break;
	    }
	    *Exp_ptr++ = c;
	}
leave_loop:
	/*
		**	if the last character was whitespace, hose it
		*/
	if(CHARMAP(*(Exp_ptr - 1)) == LX_WHITE) {
	    Exp_ptr--;
	}
	/*
	**	if Exp_ptr <= start, foo() was read, don't incr N_actuals
	*/
	if(Exp_ptr > start) {
	    N_actuals++;
	    move_to_actual(start, Exp_ptr);
	}
	*Exp_ptr++ = c;
    }

    P_actuals = actuals_start;
    if(n_formals < N_actuals) {
	Msg_Temp = GET_MSG (4002);
        SET_MSG (Msg_Text, Msg_Temp, Reuse_1);
	warning(4002);
    }
    else if(n_formals > N_actuals) {
	Msg_Temp = GET_MSG (4003);
        SET_MSG (Msg_Text, Msg_Temp, Reuse_1);
	warning(4003);
    }

    if(DEFN_TEXT(pdef)) {
	push_macro(pdef);
	expand_macro();
    }
    else {
	/*
		**	the macro expands to nothing (no definition)
		**	This essentially means delete the macro and its actuals
		**	from the expanded text
		*/
	Act_ptr = P_actuals;	/* reset pointer to get rid of actuals */
	Exp_ptr = Save_Exp_ptr;	/* delete macro & actuals from exp text */
    }
}

/************************************************************************
**	rescan_expansion:	pops a level off of tiny lexer.  If this is the
**		original macro called, the rescan is set up, otherwise the MACRO
**		(not only the tiny lexer level) is popped.
************************************************************************/
int rescan_expansion(void)
{
    if(--Tiny_lexer_nesting == 0) {
	if(Exp_ptr >= ELIMIT) {
	    fatal_in_macro(10056);
	}
	*Exp_ptr++ = EOS_CHAR;
	*Exp_ptr++ = EOS_RESCAN;
	Current_char = CURRENT_TEXT;
	return(TRUE);			/* rescan the expanded text */
    }
    else {
	/* reset Current_char, pop the macro */

	Current_char = CURRENT_STRING;
	Act_ptr = CURRENT_ACTUALS;	/* don't need its actuals */
	DEFN_EXPANDING(CURRENT_MACRO)--;
	--Macro_depth;
	return(FALSE);			/* do not rescan expanded text */
    }
}


/************************************************************************
** move_to_actual:	moves the string located between start and finish
**		inclusive to the current location in ACT_BUFFER as a new actual.
************************************************************************/
void   move_to_actual(ptext_t start, ptext_t finish)
{
    REG	ptext_t	p;
    REG	int			len;

    len = (int)(finish - start);
    if(Act_ptr + len >= ALIMIT - 2) {
	fatal_in_macro(10056);
    }
    strncpy(ACTUAL_TEXT(Act_ptr), start, len);
    p = ACTUAL_TEXT(Act_ptr) + len;
    if ((((ULONG_PTR)p) & 1) == 0) {
        *p++ = EOS_CHAR;
        *p++ = EOS_ACTUAL;
    }
    else {
        *p++ = EOS_CHAR;
        *p++ = EOS_PAD;
        *p++ = EOS_ACTUAL;
    }
    ACTUAL_SIZE(Act_ptr) = (short)(p - Act_ptr);
    Act_ptr = p;
}


/************************************************************************
** move_to_exp_esc:	moves zero terminated string starting at source to
**	the current position in EXP_BUFFER, with quotes placed around the
**	string and interior backslashes and dquotes are escaped with a
**	backslash.  The terminating null should not be copied.  The null
**	does not come from the property of a string, but rather is the
**	marker used to indicate there is no more actual.
************************************************************************/
void   move_to_exp_esc(int quote_char, REG ptext_t source)
{
    int		mapped_c;
    int		mapped_quote;
    int		in_quoted = FALSE;

    if( ! source ) {
	return;
    }

    *Exp_ptr++ = (char)quote_char;
    for(;;) {
	if(Exp_ptr >= ELIMIT) {
	    fatal_in_macro(10056);
	}
	switch(mapped_c = CHARMAP(*source)) {
	case LX_EOS:
	    if(*source == EOS_CHAR) {
		goto leave_move_stringize;
	    }
	    /* got BACKSLASH */
	    /* but it can't be backslash-newline combination because
				** we are reprocessing text already read in
				*/
	    if(in_quoted) {
		*Exp_ptr++ = '\\';
	    }
	    break;
	    break;
	case LX_DQUOTE:
	    if(CHARMAP(quote_char) == LX_DQUOTE) {
		*Exp_ptr++ = '\\';
	    }
	    /*
			**	FALLTHROUGH
			*/
	case LX_SQUOTE:
	    if(CHARMAP(quote_char) == LX_SQUOTE) {
		break;
	    }
	    if(in_quoted ) {
		if(mapped_c == mapped_quote) {
		    in_quoted = FALSE;
		}
	    }
	    else {
		in_quoted = TRUE;
		mapped_quote = mapped_c;
	    }
	    break;
	case LX_LEADBYTE:
	    *Exp_ptr++ = *source++;
	    break;
	}
	*Exp_ptr++ = *source++;
    }

leave_move_stringize:
    *Exp_ptr++ = (char)quote_char;
}


/************************************************************************
**	move_to_exp:	moves zero terminated string starting at source to
**		the current position in EXP_BUFFER.  The terminating null should
**		not be copied.
************************************************************************/
void   move_to_exp(REG ptext_t source)
{
    if( ! source ) {
	return;
    }

    while( *source ) {
	if(Exp_ptr >= ELIMIT) {
	    fatal_in_macro(10056);
	}
	*Exp_ptr++ = *source++;
    }
}


/************************************************************************
** push_macro:			pushes macro information onto the macro stack.
**	Information such as the current location in the Exp and Act buffers
**	will be used by whatever macros this one may call.
************************************************************************/
void   push_macro(pdefn_t pdef)
{
	/*
	**	note that increment leaves element 0 of the macro stack unused.
	**	this element can be reserved for links to dynamically allocated
	**	macro expansion stacks, if they become desirable
	*/
    if(++Macro_depth > LIMIT_MACRO_DEPTH) {
	Msg_Temp = GET_MSG (1009);
	SET_MSG (Msg_Text, Msg_Temp, Reuse_1);
	fatal (1009);
    }
    Tiny_lexer_nesting++;
    CURRENT_MACRO = pdef;
    CURRENT_ACTUALS = P_actuals;
    CURRENT_NACTUALS = (UCHAR)N_actuals;
    CURRENT_NACTSEXPANDED = 0;
    CURRENT_STRING = Current_char;
    CURRENT_TEXT = Exp_ptr = Save_Exp_ptr;
}


/************************************************************************
**expand_definition:		sets the input stream to start reading from
**		the macro definition.  Also marks the macro as in the process of
**		expanding so if it eventually invokes itself, it will not expand
**		the new occurence.
************************************************************************/
void   expand_definition(void)
{
    Current_char = DEFN_TEXT(CURRENT_MACRO);
    DEFN_EXPANDING(CURRENT_MACRO)++;
}


/************************************************************************
**expand_actual:	sets the input stream to start reading from
**		the actual specified in actual.
************************************************************************/
void   expand_actual(UCHAR actual)
{
    ptext_t	p;
    p = CURRENT_ACTUALS;
    while(--actual) {
	p = ACTUAL_NEXT(p);
    }
    Current_char = ACTUAL_TEXT(p);
}

/************************************************************************
**	expand_macro:		if there are still actuals for this macro to be
**		expanded, the next one is set up, otherwise this sets up to
**		expand the macro definition
************************************************************************/
void   expand_macro(void)
{
    if(CURRENT_NACTUALS > CURRENT_NACTSEXPANDED) {
	expand_actual(++CURRENT_NACTSEXPANDED);
    }
    else {
	expand_definition();
    }
}


/************************************************************************
**post_paste:		looks ahead one character to find out if a paste has
**	been requested immediately after this identifier.  If the next
**	character can continue an identifier, or is the macformal marker,
**	a paste should be done.  This is called after a macformal is found
**	to find out if the expanded or unexpanded actual should be used.
************************************************************************/
int  post_paste(void)
{
    UCHAR	c;

    if((CHARMAP(c = GETCH()) == LX_MACFORMAL) || (LXC_IS_IDENT(c))) {
	UNGETCH();
	return(TRUE);
    }
    UNGETCH();
    return(FALSE);
}

/************************************************************************
**do_macformal:		This function is called after a macformal marker is
**	found.  It reads the next character to find out which macformal is
**	wanted.  Then it checks to see if a paste is wanted, to find out
**	if the expanded or unexpanded actual should be used.  The return
**	value is a pointer to the text of the actual wanted, or NULL if the
**	actual asked for was not provided.
************************************************************************/
ptext_t   do_macformal(int *pre_paste)
{
    UCHAR		n;
    ptext_t	p;
    int	temp_paste;

    p = CURRENT_ACTUALS;
    n = GETCH();
    if(n > CURRENT_NACTUALS) {
	return(NULL);		/* already output warning */
    }
    temp_paste = post_paste();
    if(( ! (*pre_paste)) && ( ! temp_paste) ) {
	/*
	**	if the programmer provided x actuals, actuals x+1 to 2x are
	**	those actuals expanded
	*/
	n += CURRENT_NACTUALS;
    }
    *pre_paste = temp_paste;
    if (n != 0)
	while(--n) {
	    p = ACTUAL_NEXT(p);
	}

    return(ACTUAL_TEXT(p));
}


/************************************************************************
**tl_getid:		This function reads an identifier for the tiny lexer
**		into EXP_BUFFER.  if macformal is found, the text of that actual
**		(expanded or not) is appended to the identifier.  It is possible
**		that this text will contain characters that are not legal
**		identifiers so return value is whether checking to see if the
**		"identifier" is defined is worth the bother.
************************************************************************/
int tl_getid(UCHAR c)
{
    UCHAR	*p;
    int		paste;
    int		legal_identifier;
    int		length = 0;

    p = Exp_ptr;
    paste = FALSE;
    legal_identifier = TRUE;

do_handle_macformal:
    if(CHARMAP(c) == LX_MACFORMAL) {
	ptext_t	p_buf;

	if((p_buf = do_macformal(&paste)) != 0) {
	    while( *p_buf ) {
		if( ! LXC_IS_IDENT(*p_buf)) {
		    legal_identifier = FALSE;
		}
		if(Exp_ptr >= ELIMIT) {
		    fatal_in_macro(10056);
		}
		*Exp_ptr++ = *p_buf++;
	    }
	}
    }
    else {
	*Exp_ptr++ = c;
    }

do_handle_eos:
    while(LXC_IS_IDENT(c = GETCH())) {
	if(Exp_ptr >= ELIMIT) {
	    fatal_in_macro(10056);
	}
	*Exp_ptr++ = c;
    }

    if(CHARMAP(c) == LX_NOEXPAND) {
	length = (int)GETCH();			/* just skip length */
	goto do_handle_eos;
    }

    if(CHARMAP(c) == LX_MACFORMAL) {
	paste = TRUE;
	goto do_handle_macformal;
    }

    UNGETCH();
    if(legal_identifier && (length == (Exp_ptr - p))) {
	legal_identifier = FALSE;
    }

    if(legal_identifier) {
	if(((Exp_ptr - p) > LIMIT_ID_LENGTH) && ( ! Prep)) {
	    Exp_ptr = &p[LIMIT_ID_LENGTH];
	    *Exp_ptr = '\0';	/* terminates identifier for warning */
	    Msg_Temp = GET_MSG (4011);
            SET_MSG (Msg_Text, Msg_Temp, p);
	    warning(4011);		/* id truncated */
	}
	else {
	    *Exp_ptr = '\0';	/* terminates identifier for expandable check */
	}
	/*
	**	Whether or not we are doing Prep output, we still have to make
	**	sure the identifier will fit in Reuse_1
	*/
	if((Exp_ptr - p) > sizeof(Reuse_1)) {
	    Exp_ptr = &p[LIMIT_ID_LENGTH];
	    *Exp_ptr = '\0';
	    Msg_Temp = GET_MSG (4011);
            SET_MSG (Msg_Text, Msg_Temp, p);
	    warning(4011);
	}
	/*
	**	copy into Reuse_1 for warnings about mismatched number of
	**	formals/actuals, and in case it's not expandable
	*/
	memcpy(Reuse_1, p, (int)((Exp_ptr - p) + 1));
	Reuse_1_hash = local_c_hash(Reuse_1);
	/*
	**	the characters from Exp_ptr to p inclusive do not include the
	**	the hash character, the length character, and the terminating
	**	null.
	*/
	Reuse_1_length = (UCHAR)((Exp_ptr - p) + 1);
    }
    return(legal_identifier);
}


/************************************************************************
**	do_strformal:	returns pointer to the actual requested without
**		checking for paste (a legal token is not possible, so if a paste
**		is being done on a strformal, the behavior is undefined
************************************************************************/
ptext_t   do_strformal(void)
{
    UCHAR		n;
    ptext_t	p;

    /* use unexpanded actual */
    p = CURRENT_ACTUALS;
    n = GETCH();
    if(n > CURRENT_NACTUALS) {
	return(NULL);		/* already output warning */
    }
    if (n != 0)
	while(--n) {
	    p = ACTUAL_NEXT(p);
	}
    return(ACTUAL_TEXT(p));
}


/************************************************************************
**	can_get_non_white:	tries to get the next non white character
**		using P1 rules for white space (NL included).  If the end of
**		an actual, or a rescan is found, this returns FALSE, so control
**		can drop into one of the lexers.
************************************************************************/
int	  can_get_non_white(void)
{
    int		return_value = FALSE;
    int		white_found = FALSE;

    for(;;) {
	switch(CHARMAP(GETCH())) {
	case LX_NL:
	    if(On_pound_line) {
		UNGETCH();
		goto leave_cgnw;
	    }
	    Linenumber++;
	    /*
				**	FALLTHROUGH
				*/
	case LX_WHITE:
	case LX_CR:
	    white_found = TRUE;
	    break;
	case LX_EOS:
	    {
		int	eos_res;
		if((eos_res = handle_eos()) & (ACTUAL_EOS | RESCAN_EOS)) {
		    goto leave_cgnw;
		}
		if(eos_res != BACKSLASH_EOS) {
		    break;
		}
	    }
	    /*
				**	FALLTHROUGH
				*/
	default:
	    UNGETCH();
	    return_value = TRUE;
	    goto leave_cgnw;
	    break;
	}
    }
leave_cgnw:
    if(white_found) {
	if(Exp_ptr >= ELIMIT) {
	    fatal_in_macro(10056);
	}
	if(*(Exp_ptr - 1) != ' ') {
	    *Exp_ptr++ = ' ';
	}
    }
    return(return_value);		/* could you get next non white? */
}


/************************************************************************/
/* fatal_in_macro ()							*/
/************************************************************************/
void fatal_in_macro(int e)
{
    Linenumber = Macro_line;
    Msg_Temp = GET_MSG(e);
    SET_MSG (Msg_Text, Msg_Temp);
    fatal (e);
}


/************************************************************************
**  handle_eos : handle the end of a string.
************************************************************************/
int   handle_eos(void)
{
    if(PREVCH() == '\\') {
	if(checknl()) {
	    return(FILE_EOS);
	}
	else {
	    return(BACKSLASH_EOS);
	}
    }
    if(Macro_depth == 0) {	/* found end of file buffer or backslash */
	if(io_eob()) {		/* end of buffer in here is bad */
	    Msg_Temp = GET_MSG(1004);
	    SET_MSG (Msg_Text, Msg_Temp);
	    fatal (1004);
	}
	return(FILE_EOS);
    }

    again:
    switch(GETCH()) {
    case EOS_PAD:
        goto again;
    case EOS_ACTUAL:
	/*
	** Just finished expanding actual.  Check to see if there are
	** any more actuals to be expanded.  If there are, set up to
	** expand them and return.  Otherwise, set up to expand defn
	*/

	/* move expanded text of this actual to act_buffer */
	move_to_actual(CURRENT_TEXT, Exp_ptr);

	/* reset Exp_ptr for more expansions at this macro depth */
	Exp_ptr = CURRENT_TEXT;

	/* expand next actual if there, otherwise expand definition */
	expand_macro();

	return(ACTUAL_EOS);
	break;
    case EOS_DEFINITION:
	if(rescan_expansion()) {
	    return(RESCAN_EOS);
	}
	else {
	    return(DEFINITION_EOS);
	}
	break;
    case EOS_RESCAN:
	/*
	** Reset Current_char, Exp_ptr and Act_ptr, pop the macro
	*/

	/*	get input from the previous stream */
	Current_char = CURRENT_STRING;

	/* mark this macro as not expanding */
	DEFN_EXPANDING(CURRENT_MACRO)--;


	/*
	**	if looking for the actuals of a macro, these pointers
	**	should really not be reset, however, it is cleaner to
	**	save them before calling handle_eos, and restore them
	**	upon returning, than check a static variable here.
	*/
	if(Macro_depth == 1) {
	    Act_ptr = ACT_BUFFER;
	    Exp_ptr = EXP_BUFFER;
	}
	--Macro_depth;
	return(DEFINITION_EOS);
	break;
	/* the following conditional compile is so brackets match */

    DEFAULT_UNREACHABLE;
    }
}
/************************************************************************
**	END EXPANDING MACRO }
************************************************************************/
