/*	CCPP.C - New KCC Preprocessor
**
**	(c) Copyright Ken Harrenstien 1989
**
*/

#ifndef DEBUG_PP
#define DEBUG_PP 0	/* Include debugging printout capability */
#endif

#include "cc.h"
#include "ccchar.h"
#include "cclex.h"
#include <time.h>	/* Needed for __DATE__ and __TIME__ */
#include <string.h>
#include <stdlib.h>	/* calloc, free, atoi */

/* Imported functions used here */
extern
SYMBOL *symfind(char *, int); /*CCSYM to look up macro or identifier */
extern
SYMBOL *symgcreat(char *);	/* CCSYM to create a macro name */
extern
SYMBOL *symfnext(SYMBOL *),	/* CCSYM */
		*shmacsym(SYMBOL *);
extern
void freesym(SYMBOL *);		/* CCSYM */
extern
long pconst(void);		/* CCSTMT to parse constant expr */
extern
char *estrcpy(char *, char *),
	*fstrcpy(char *, char *, char *);	/* CCASMB */
extern
int symval(char *, char *, int);		/* CCASMB */
extern
int nextoken(void);			/* CCLEX  */
extern
void flushcode (void);
extern
void outstr (char *);			/* CCOUT  */

/* Exported functions */
void ppinit(void);		/* Initialize the input preprocessor. (CC) */
void ppdefine(int, char **, int, char **); /* Inits predefined macros (CC) */
void passthru(FILE *);	/*Invoked by -E for simple pass-thru processing (CC)*/
int nextpp(void);		/* Read next token from input (CCLEX) */
void pushpp(void);		/* Push back current token (CCLEX) */
int nsert_file (char *f, int insert_flag);

/* Internal functions */
static
void dotad(void);
static
int nextch(void), tchesc(void), pushstr(char *);
static
void pushch(int), bstrpush(char *), bstrpop(void);
static
int nextrawpp(void), scanhwsp(void), ppnconst(int), ppunknwn(void);
static
void ppsconst(int), scancomm(void);
static
void		pptresize(void);			// FW KCC-NT
static
int nextmacpp(void);
static
void pushmp(void), mtlpop(void);
static
int findident(void);
static
void directive(void);
static
int d_define(void), d_undef(void), d_asm(void), d_endasm(void),
	d_ifdef(int), d_if(void), d_else(void), d_elif(void), d_endif(void),
	d_include(void), d_line(void), d_error(void), d_pragma(void);
static
int iftest(void);
static
void iffwarn(char *), ifpush(int), ifpopchk(int);
static
void flushcond(void), filepush(FILE *, char *, int, int, int);
static
SYMBOL *findmacsym(char *);
static
void freemacsym(SYMBOL *);
static
int tskipwsp(void), tskiplwsp(void), cskiplwsp(void), flushtoeol(void);
static
void checkeol(void);
static
int getfile(char *, int);
static
void sinbeg(char *), sinend(void);
static
int  mpeekpar(void);
static
int get_title (char *pragma_str, char *ext);

static
void dmp_errmsg(void);
void dmpmlbuf(void);

 /* Flag for top-of-file, needed for mixed listing generation-KAR 8/3/90 */
 /* KAR-6/91, changed type to short */
static
short fstart = 1;

 /* KAR-11/91, flag for usage bef. init. code for signaling parsing the
  * the right side of and assignment expression.
  */
char ra_expr;

/*
 * KAR-2/93, adding the following enumerated type and instance for
 * needed support to allow C++ style comments (PPS 4574).
 */
typedef enum {		/* Used to signal style of comment, C or C++ */
    C,
    CPP
} comment_style;

static
comment_style comment_type = C;


/* The main global variables of interest to callers (i.e. CCLEX) are those
** set by nextpp(), as follows:
**
**	curpp - Current token (a tokdef enum)
**	curval - Token value (either unused or a string pointer)
**	curptr - Pointer to corresponding pptok in tokenlist, if any.
**	cursym - Pointer to symbol, if curpp == T_IDENT.  This will have
**			class SC_UNDEF if sym didn't exist previously.
*/

/* Note that most routines operate, or begin to operate, on the current
 * character in "ch", rather than immediately reading the next char.  When
 * a character is completely processed and is not needed any more, nextch()
 * must be called in order to get rid of it and set up a new char for
 * whatever will be next looking at the input.  Occasionally "ch" is
 * set directly for proper "priming".
 * This is to avoid having to call pushch() whenever a token ends.
 */

extern
char _ch_cpy;	/* set in nextpp() used in ccstmt.c */
/* Mixed listing output buffer variables */
char		*mlbuf = NULL;
char		*mlbptr = NULL;
static
short	ch_ungot = 0;	/* KAR-5/91, changed type to short */

/* Include file nesting stack - holds saved input file context.
 *	Indexed by inlevel.
 */
static
int inlevel;			/* 0 - top level */
static
int scanning_comment = 0;	/* flags true while in scancomm */

static
struct
    {
    FILE    *cptr;	/* file pointer  */
    filename cname;	/* filename      */
    int	    cpage;	/* page number   */
    int	    cline;	/* line number in page */
    int	    cfline;	/* line number in file */
    }
inc[MAXINCLNEST];

/* #if handling variables.  Arrays are indexed by iflevel. */
static
int iflevel,	/* #if-type command nesting level (0 = none) */
    flushing;		/* Set = iflevel when inside failing conditional */
static
int ifline[MAXIFLEVEL];	/* Line # that last if/elif/else was on */
static
int iffile[MAXIFLEVEL];	/* File # for ditto. 0 for current file, */
				/* -1 for expired file, N for inc[N-1] */
static
int iftype[MAXIFLEVEL];	/* iftype[lvl] set to one of the following: */
#define IN_ELSE	0		/* inside an #else, or nothing.  Must be 0 */
#define IN_IF	1		/* inside an #if */
#define IN_ELIF	2		/* inside an #elif */
static
char *ifname[] = { "else", "if", "elif" };	/* Indexed by iftype */


static
int indirp;	/* Set when handling a directive */
static
int inasm;	/* Set when inside assembly passthrough */

/* Pre-Processing TokenList definitions */
typedef struct		/* TokenList descriptor type */
    {
    PPTOK *tl_head;
    PPTOK *tl_tail;
    }
tlist_t;

static
PPTOK *pptptr = NULL;		/* Ptr to last allocated token */

static
PPTOK *pptoks = NULL;		/* Tokens allocated from pptoks */
static
PPTOK *pptmax = NULL;		/* Highest valid ptr */
 #define tokpcre() (++pptptr <= pptmax ? pptptr : (pptresize (), pptptr))
 #define pptreset() (pptptr = pptmax = pptoks = NULL) /* Flush all tokens, start over*/

 #ifndef MAXPOOLSIZE
 #define MAXPOOLSIZE 4000
 #endif



#define tokn2p(n) (n)		/* Return ptr, given next-ptr */
#define tokp2n(p) (p)		/* Return next-ptr, given ptr */
#define tlpinit(tl,p) ((tl).tl_head=(tl).tl_tail=(p))	/*Init w/ptr to tok */
#define tlzinit(tl) tlpinit(tl,0)		/* Init empty list */
#define tlpcur(tl) ((tl).tl_head)		/* Get ptr to 1st token */
#define tltcur(tl) (*tlpcur(tl))		/* Get 1st token */
#define tlppeek(tl) ((tl).tl_head->pt_nxt)	/* Ptr to next token */

#if 0	/* 8/91 shrink KCC */
#define tltpeek(tl) (*tlppeek(tl))		/* Peek at next token */
#endif

#define tlpskip(tl) ((tl).tl_head=tlppeek(tl))	/* Skip this token */

#define tlpadd(tl,p) (!(tl).tl_head \
	? tlpinit(tl,p) \
	: ((tl).tl_tail = (tl).tl_tail->pt_nxt = (p))) /* Add ptr at tail */
#define tltadd(tl,t) (*tlpadd(tl,tokpcre()) = (t))	/* Add tok at tail */
#define tlpins(tl,p) (((p)->pt_nxt = (tl).tl_head) == 0 \
	? tlpinit(tl,p) \
	: ((tl).tl_head = (p)))		/* Insert ptr at head of list */

#define tllapp(tl,il) ((tl).tl_tail->pt_nxt = tokp2n((il).tl_head), \
			(tl).tl_tail = (il).tl_tail)	/* Append list */

#if 0	/* 8/91 shrink KCC */
#define tllins(tl,il) ((il).tl_tail->pt_nxt = tokp2n((tl).tl_head), \
			(tl).tl_head = (il).tl_head)	/* Insert list */
#endif

/* Various TokenList declarations */
 /*
  * mltpush() moved down here where tlist_t is known... 
  */
static
tlist_t tlfrstr(char *),
       tlimake(INT, int), tlcopy(tlist_t), tlstrize(tlist_t);
static
char *tltostr(tlist_t, char *, int), *tltomac(tlist_t, char *);
static
PPTOK tokize(void);
static
void tlrawadd(tlist_t *), mtlpush(tlist_t);
static
void ppcqstr(char *), ppctstr(PPTOK *);
static
int pptfput(PPTOK *, FILE *);
static
void tkerr(char *, int);
static
tlist_t getlinetl(void), tlwspdel(tlist_t, int);
static
tlist_t asmrefill(void);
static
char *sltostr(PPTOK *, char *, int);


/* PP-Token char pool.  This is reset and the contents flushed whenever
** nextpp() is asked to read a new token at top level, by setting ppcptr NULL.
** Several handy macros are defined to use this pool.
**	ppcreset() - Resets pool, flushes all chars
**	ppcbeg() - Start depositing into pool, returns ptr to start
**	ppcput(c) - Put char into pool, returns c
**	ppclast() - Returns last char put into pool
**	ppclen() - Returns # chars deposited thus far
**	ppcend() - Ends deposit into pool, returns # chars
**			(invokes fatal error if overflowed)
**	ppcbackup() - Backs up over last char, can be used to undo ppcend().
**	ppcsave(st) - Saves state in "st" (type "ppcstate_t")
**	ppcrest(st) - Restores state from "st"
*/
static
char *ppcptr = NULL;	/* Ptr into ppcpool */
static
int ppcleft;		/* # chars left in ppcpool */
static
int ppcocnt;		/* Saved ppcleft for deriving string lens */
typedef struct		/* State struct for saving above vars */
    {
#if 0	/* 5/91 Dynamic tables */
    ptrdiff_t	    cptr;
    unsigned char   ppcsize;
#else
    char *cptr;
#endif
    int cleft;
    int cocnt;
    }
ppcstate_t;

#define ppcreset() (ppcptr = NULL)

#if 0	/* 5/91 Dynamic tables */
static
unsigned char ppcsize = 4;
static
char *ppcpool = NULL;
 #define ppcbeg() (!ppcptr? ((!ppcpool? ppcresize(): 0), ppcocnt=ppcleft=  \
     (ppcsize*DYN_SIZE - 1),(ppcptr=ppcpool)+1) : (ppcocnt=ppcleft, ppcptr+1))
 #define ppcput(c) ((--ppcleft > 0 ? 0 : ppcresize()), *++ppcptr = (c))
 #define ppcend() ((--ppcleft > 0 ? 0:ppcresize()), *++ppcptr = 0, ppclen()-1)
 #define ppcsave(st) \
	 (void)(st.cptr = ppcptr-ppcpool, st.cleft = ppcleft, \
	 st.cocnt = ppcocnt, st.ppcsize = ppcsize)
 #define ppcrest(st) \
	 (void)(ppcptr = (char *) ((ptrdiff_t) ppcpool + st.cptr), ppcleft = \
	 st.cleft + DYN_SIZE*(ppcsize-st.ppcsize), ppcocnt = st.cocnt)
#else
static
char ppcpool[MAXPOOLSIZE];
 #define ppcbeg() (!ppcptr?(ppcocnt=ppcleft=MAXPOOLSIZE-1,(ppcptr=ppcpool)+1)\
			: (ppcocnt=ppcleft, ppcptr+1))
 #define ppcput(c) (--ppcleft > 0 ? *++ppcptr = (c) : (c))
 #define ppcend() (--ppcleft > 0 ? (*++ppcptr = 0, ppclen()-1) : ppcresize())
 #define ppcsave(st)	\
	 (void)(st.cptr = ppcptr, st.cleft = ppcleft, st.cocnt = ppcocnt)
 #define ppcrest(st)	\
	 (void)(ppcptr = st.cptr, ppcleft = st.cleft, ppcocnt = st.cocnt)
#endif

#define ppclast() (*ppcptr)
#define ppclen() (ppcocnt - ppcleft)
#define ppcbackup() (++ppcleft, *--ppcptr)

static
tlist_t tlmake(/* int typ, union pptokval val */);
static
int ppcresize(void);
#if 0
static
int ppcresize();		/* Just invokes efatal(CPOOL) */
#endif

/* Raw Tokenizer variables - nextrawpp() */
static
int rawpp;		/* Current raw token type from nextrawpp() */
static
union pptokval rawval;	/* Current raw token value */
static
int rawpplen;		/* Length of token string, if any */
static
PPTOK *rawptr;		/* Ptr to current raw pptoken, if any */

/* Macro Tokenizer - nextmacpp() */
static
int mactlev = 0;		/* # levels nesting for nextmacpp() input */
static
tlist_t mactl;		/* Current tokenlist nextmacpp() is using */
static
tlist_t mactls[MAXMACNEST];	/* Stack of input tokenlists */

/* Cooked Tokenizer - nextpp()  (also declared by CCLEX.H)*/
extern
int curpp;		/* Current cooked token type from nextpp() */
extern
union pptokval curval;	/* Current cooked token value */
static
tlist_t curtl;		/* Current cooked token list, if any */
extern
PPTOK *curptr;		/* Ptr to current cooked pptoken, if any */
extern
SYMBOL *cursym;		/* Ptr to symbol, if token is T_IDENT */

/* Raw character input variables - nextch() */
static
int ch;			/* Current char awaiting tokenizer */
#define MAXBKSTRS (MAXINCLNEST+2)	/* Rough guess */
static
char *backstr = NULL;	/* If non-NULL, ptr to pushback string */
static
int bkstrlev = 0;	/* # of ptrs on saved pushback string stack */
static
char *bkstrs[MAXBKSTRS];

#define TCH_ESC '\305'		/* Input string escape char */
#define TCH_EOF 'E'		/* Input string EOF */


/* MACRO DETAILS:
**	A defined macro is stored in the symbol table as a symbol with
** class SC_MACRO.  It has no type and uses these components:
**	Smacptr	- char pointer to the macro body (allocated by calloc)
**	Smacnpar - # of params/arguments (or special value)
**	Smacparlen - # chars at start of body that are actually param names.
**	Smaclen - total # chars in macro body string, not counting final NUL.
** The value of Smacnpar has the following meanings:
**	>= 0	# of arguments in argument list.  0 = mac(), 1 = mac(a), etc.
**	< 0	Special value, one of:
*/
#define MACF_ATOM (-1)	/* Normal atomic, no argument list (i.e. "mac") */
#define MACF_DEFD (-2)	/* "defined" operator.  No body. */
#define MACF_LINE (-3)	/* "__LINE__" macro.  No body. */
#define MACF_FILE (-4)	/* "__FILE__" macro.  No body. */
#define MACF_DATE (-5)	/* "__DATE__" macro.  No body. */
#define MACF_TIME (-6)	/* "__TIME__" macro.  No body. */
#define MACF_STDC (-7)	/* "__STDC__" macro.  No body. */
#define MACF_KCC  (-8)	/* "__COMPILER_KCC__" macro.  No body. */
#define MACF_SYMF (-9)	/* "_KCCsymfnd("file","sym")" macro. No body. */
#define MACF_SYMV (-10)	/* "_KCCsymval("file","sym")" macro. No body. */

#if __MSDOS__ /* KAR-8/92, Add predefined macro __KCCDOS__ if KCCDOS */
#define MACF_DOS  (-11) /* "__KCCDOS__ macro. No body. evals to 1;only if KCCDOS */
#endif
/* All special macros other than MACF_ATOM cannot be redefined or
** undefined except by the command-line -U or -D switches.
*/

/* MACRO BODY FORMAT:
**
** Smacptr (if not null) points to the macro body, which consists of
** the tokenized text of the macro, perhaps prefixed by parameter names.
**	Smacptr ->  <"Smacparlen" chars of param-names>
**		    <tokenized text, see below>
**
** The parameter names are only checked during redefinition of a macro
** to see whether the new macro's params have the same spelling (CHOKE BLETCH).
**
** The text is entirely tokenized.  The first char of each token is the
** token type value, such as T_WSP or T_IDENT.  Following chars depend
** entirely on the token type.  There are currently three general types:
**	Simple token: just one char of token type and nothing else.
**	String token: token type is followed by the chars of the string, ending
**		with a zero byte.
**	Param token: the char after the token type is an encoded macro
**		parameter # -- see MAC_ARGOFF.
**
** In all cases, the next char after any of the above begins another token.
** A zero token marks the end of the macro body; this and not Smaclen is
** normally used to stop a scan.  This zero token is not included in the
** Smaclen count.
*/
#define MAC_ARGOFF '0'	/* Number to encode param # with */


/* Macro Frame definitions */
struct macframe
    {
    SYMBOL *mf_sym;		/* Pointer to macro symbol def */
    int mf_nargs;		/* # args we have */
    int mf_parlen;		/* # chars in param name prefix (bletch) */
    int mf_len;		/* # chars total in macro body string */
    char *mf_body;		/* Ptr to body of macro def */
    union
	{
	tlist_t mfp_tls[MAXMARG];	/* Argument token lists (for expand) */
	char *  mfp_cps[MAXMARG];	/* Parameter name strs (for define) */
	}
    mf_p;

#if 0	/* 8/91 shrink KCC */
    int mf_used[MAXMARG];	/* # times each arg is invoked */
#endif
    };
#define mf_argtl mf_p.mfp_tls
#define mf_parcp mf_p.mfp_cps

#if 0
static
nhidemacs = 0;		/* # macros in hideset */
#endif
static
SYMBOL *hidemacs[MAXMACNEST];	/* Used by hspush() and mishid() */

static
int maclevel;	/* Macro expansion level (used???) */

static
char defcdmy;			/* Needed if not using "define" */
static
char *defcsname = &defcdmy;	/* Ptr to "defined" macro sym name */

static
int tadset;		/* True if date/time strings are set */
static
char datestr[14] = "\"Jun 07 1989\"";	/* __DATE__ string */
static
char timestr[11] = "\"01:23:45\"";	/* __TIME__ string */


static
SYMBOL *mdefstr(char *, int, char *),
	     *mdefsym(char *, struct macframe
*);
static
int     mexptop(SYMBOL *, int), margs(tlist_t *, int, int);
static
tlist_t mexpand(tlist_t, int), mexplim(tlist_t, int),
	      mexpsym(SYMBOL *, int), msubst(struct macframe
*, int);
static
void mpaste(tlist_t, int, int);
static
int     hspush(SYMBOL *, int), tkhide(PPTOK *, int),
	      mishid(SYMBOL *, int);
static
int mdefinp(struct macframe
*mf);

#if DEBUG_PP
static
int debpp = 1;
static
FILE *fpp = stderr;
static
int debppt = 1;
static
void pmacframe(char *, struct macframe
*),
	  pmactl(char *, tlist_t, int),
	  tlfput(tlist_t, FILE *, int),
	  tkfput(PPTOK *, FILE *, int);
static
char *plevindent(void);
static
char *tkids(PPTOK *);
 #if 0
static
void pmacframe(), pmactl(), tlfput(), tkfput();
static
char *plevindent();
static
char *tkids();
 #endif
#endif

/* PPINIT() - Invoke to initialize preprocessor at start of file scan.
**	Called once for each file compiled.
*/
void
ppinit(void)
{
    /* Set globals */
    eof = 0;
    tline = 0;
    fline = page = line = 1;

    /* Set variables local to preprocessor */
    ppcreset();
    pptreset();
    inlevel = iflevel = flushing = inasm = indirp = maclevel = 0;
    iftype[0] = 0;		/* Just in case */
    tadset = 0;			/* Date/time strings not set */
    erptr = errlin;		/* Init pointer to error-context buffer */
    erpleft = ERRLSIZE;		/* Set countdown of chars left in errlin */
    memset((char *) erptr, 0, erpleft);	/* Clear the circular buffer */
    pushstr("\n");		/* Prime input with EOL, set up 1st char! */

    /* Enter special macro pre-definitions into symbol table. */
    mdefstr("__COMPILER_KCC__", MACF_KCC, NULL);
    mdefstr("__LINE__",		MACF_LINE, NULL);
    mdefstr("__FILE__",		MACF_FILE, NULL);

#if __MSDOS__ /* KAR-8/92, predefine __KCCDOS__ if KCCDOS */
    mdefstr("__KCCDOS__", MACF_DOS, "1");
#endif

    if (clevel >= CLEV_CARM)
	{
	mdefstr("__DATE__", MACF_DATE, NULL);
	mdefstr("__TIME__", MACF_TIME, NULL);
	/* "defined" is a bit more special.  Need to remember ptr to
	** 1st char of the symbol name, so it can be turned on and off.
	*/
	defcsname = &(mdefstr("defined", MACF_DEFD, NULL)->Sname[0]);
	*defcsname = SPC_MACDEF;	/* Hide sym by smashing 1st char */
	}
    if (clevel >= CLEV_STDC)		/* When we're fully ready */
	{
	mdefstr("__STDC__", MACF_STDC, "1");
	}
    if (clevkcc)
	{
	mdefstr("_KCCsymfnd", MACF_SYMF, NULL);
	mdefstr("_KCCsymval", MACF_SYMV, NULL);
	}
}

/* DOTAD - Initialize datestr and timestr for __DATE__ and __TIME__.
**	This is only invoked if one of those macros is seen; otherwise,
**	the overhead is avoided.
*/
static
void
dotad()
{
    register char *cp;
    time_t utad;

    if (time(&utad) == (time_t)-1)
	{
	warn("Cannot get date/time, using %s %s", timestr, datestr);
	return;			/* Leave both strings alone */
	}
    cp = ctime(&utad);		/* Get pointer to ascii date and time */

    /* Now have "Dow Mon dd hh:mm:ss yyyy\n\0" */
    /*		 012345678901234567890123 4 5  */
    strncpy(datestr+1, cp+4, 6);	/* Copy "Mon dd" */
    strncpy(datestr+8, cp+20, 4);	/* Copy "yyyy" */
    strncpy(timestr+1, cp+11, 8);	/* Copy "hh:mm:ss" */
    tadset = 1;				/* Strings now set! */
}

/* PPDEFINE(cruft) - Process startup #undefs and #defines requested by the
**	-U and -D switches in the main CC loop.
**	We assume the syntax has already been checked by chkmacname() in CC.
*/
void
ppdefine(unum, utab, dnum, dtab)
int unum, dnum;		/* # of strings in table */
char **utab, **dtab;	/* Address of char-pointer array */
{
    int savch;
    char *cp;
    SYMBOL *sym;

    /* First handle all -U undefinitions... */
    for ( ; --unum >= 0; utab++)
	{
	if ((sym = findmacsym(*utab)) != NULL)	/* Look up the macro symbol */
	    freemacsym(sym);		/* Found it, flush it! */
	else
	    note("Macro in -U%s doesn't exist", *utab);
	}

    /* Now handle all -D definitions. */
    for ( ; --dnum >= 0; dtab++)
	{
	cp = *dtab;			/* Get this -D string */
	while (*++cp && *cp != '=')
	    ;	/* Scan for start of body if any */
	savch = *cp;			/* Remember terminator char */
	*cp = '\0';			/* Tie name off in case of '=' */
	if ((sym = findmacsym(*dtab)) != NULL)	/* See if already defined */
	    {
	    advise("Redefining macro: -D%s", *dtab);	/* Ugh! */
	    freemacsym(sym);
	    }
	mdefstr(*dtab, MACF_ATOM,	/* Define object-like macro */
		savch ? cp+1 : "1");	/* with "1" if no body. */
	*cp = savch;			/* Restore zapped char if any */
	}
}

/*
 * PASSTHRU(fp) - Pass through text to file
 *	Used only by -E (i.e. prepf is set)
 *
 * General strategy:
 *	Attempts to do a fast scan through the input whenever possible, just
 * reading and outputting on the character level.
 * It necessarily has to simulate some of what nextrawpp() and nextpp()
 * do.
 *	(nextrawpp: handle comments, string-type literals)
 *	(nextpp: handle directives, macro expansion)
 *
 * When it encounters a directive or macro, it punts and asks nextpp() to
 * handle it, whereupon it just translates tokens until it determines
 * that things are back at top level again.  This return of control may
 * be tricky.
 */

static
void	    pass_ident (FILE *),
	    pass_num (void),
	    pass_hwsp (void),
	    pass_line (int),
	    pass_comm (void),
	    pass_str (int);

static
FILE*	    passfp;



void
passthru (FILE* fp)
    {
#if 0	/* 8/91 shrink KCC */
    prepfp = fp;		/* Set for benefit of ppasm() */
#endif

    passfp = fp;

#if DEBUG_PP
    if (debpp && debppt)		/* Do trivial passthru */
	{
	while (nextpp() != T_EOF)
	    {
	    pptreset();
	    ppcreset();
	    pptfput(tlpcur(tlmake(curpp, curval)), fp);
	    }
	return;
	}
#endif

    /* Note at start of file, ch should always contain \n as "first" char */

    for (;;)
	{
	switch (ch)	/* Scan starting with current char */
	    {
	    case EOF:			/* Done when hit EOF */
		return;

	    /* Check for whitespace other than new-line */

	    case '\r':			/* Include CR here */
	    case '\v':			/* Vert tab */
	    case '\f':			/* Ditto FF */
	    case '\t':			/* Horiz tab */
	    case ' ':			/* Space */

		if (keepcmts)		/* If keeping comments, */
		    {
		    putc (ch, fp);	/* just pass it all */
		    nextch ();
		    }
		else
		    pass_hwsp ();	/* Flushing, so try to be clever */

		continue;


	    case '\n':
		/* Newline needs special handling (maybe directives, etc) */

		pass_line (0);
		continue;

	    case '/':
		/* Check for possible start of comment */

		if (nextch () == '*')
		    pass_comm ();

		/*
		 * KAR-2/93, added for PPS 4574, C++ comments
		 * FW 2A(45) July 93, added check of clevnocpp
		 *
		 * NB. The test of 'ch' is right here because the
		 * previous call to nextch () set it up.
		 */

		else if (ch == '/' && !clevnocpp)
		    {
		    /* C++ style comment */

		    comment_type = CPP;
		    pass_comm ();
		    comment_type = C;
		    }
		else
		    putc ('/', fp);

		continue;

	    default:
		if (iscsymf (ch))
		    {
		    /* start of ident, check for macro */

		    pass_ident (fp);
		    continue;
		    }

		putc (ch, fp);		/* Normal char, pass it on */
		nextch ();		/* Get next */

		continue;

	    case '.':			/* '.' may start a pp-number */
		putc ('.', fp);

		if (!isdigit (nextch ()))
		    continue;		/* Nope, handle whatever it is */

	    /* Is pp-number, drop thru to handle it */

	    case '0':
	    case '1':
	    case '2':
	    case '3':
	    case '4':
	    case '5':
	    case '6':
	    case '7':
	    case '8':
	    case '9':
		pass_num ();
		continue;

	    /*
	     * Pass through string constants.  Note that character constants
	     * (e.g. 'a') are handled a little differently because they
	     * shouldn't be longer than the largest possible char constant.
	     */

	    case '\'':
		pass_str (tgcpw+2);
		continue;	/* Char constant */

	    case '"':
		pass_str (0);
		continue;	/* String literal */

	    case '`':			/* Quoted identifier (maybe) */

		if (clevkcc)		/* If KCC extensions allowed, */
		    {
		    pass_str (0);	/* parse like string! */
		    continue;
		    }

		putc (ch, fp);		/* Normal char, pass it on */
		nextch ();		/* Get next */
		continue;
	    }				/* End of loop */
	}
    }

/*
 * PASS_IDENT - auxiliary for passthru () and d_asm () to parse an
 *	identifier and either pass it along or expand it if it's a macro.
 *	Current char is the first char of the identifier.
 *	Returns with current char the 1st thing after ident (or expansion).
 */

static
void
pass_ident (FILE* fp)
    {
    SYMBOL*	sym;
    register
    PPTOK*	p;
    register
    char*	cp;


    cp = ppcbeg ();

    do
	ppcput (ch);		/* Gobble up ident starting with current ch */
    while (iscsym (nextch ()));

    (void) ppcend ();		/* Tie off so can look it up */

    if (((sym = findmacsym (cp)) == NULL)	/* If not a macro, */
	    || !mexptop (sym, 0))	/* or can't expand it, */
	{
	fputs (cp, fp);		/* just output the identifier. */
	ppcreset ();		/* Clean up token char pool */
	return;
	}

    /*
     * Expanded macro!
     * Since mexptop () leaves a tokenlist in curtl, we grab that and
     * translate it into a string that is then output, with special
     * quoting if in #asm.
     */

    if (!inasm)
	{
	for ( ; (p = tlpcur (curtl)) != NULL; tlpskip (curtl))
	    pptfput (p, fp);
	}
    else			/* in #asm, must do special quoting */
	{
	curtl = tlstrize (curtl);	/* Turn into giant string literal */
	cp = curtl.tl_head->pt_val.cp;	/* Get ptr to start */
	cp[strlen (cp) - 1] = '\0';	/* Chop '"' off end */
	fputs (cp + 1, fp);		/* And skip '"' at beg */
	}

    tlzinit (curtl);	/* Ensure curtl flushed out */
    ppcreset ();		/* Also be safe and flush stg used */
    pptreset ();
    }

/*
 * PASS_NUM - auxiliary for PASSTHRU () to pass a PP-number.
 *	Returns with delimiting char still in CH, not yet output.
 */

static
void
pass_num (void)
    {
    register
    int		i;


    for ( ; ; )
	{
	i = ch;
	putc (i, passfp);	/* Pass char, remember it */

	switch (nextch ())
	    {
	    case '+' :
	    case '-' :		/* Sign chars are part of number, */

		if (toupper ((char) i) != 'E')	/* if prev char was E */ // FW KCC-NT
		    return;		/* Oops, gotta stop */

	    case '.' :

		break;

	    default :

		if (!iscsym (ch))
		    return;

		break;
	    }
	}
    }

/*
 * PASS_STR - auxiliary for PASSTHRU () to parse a string-type literal
 *	Returns with delimiting char still in CH, not yet output.
 *	Return value is 0 if EOF encountered, else non-zero.
 */

static
void
pass_str (int cnt)
    {
    int		delim = ch;		/* Delimiter char */


    putc (ch, passfp);		/* Send initial delimiter */

    while ((nextch () != EOF) && (ch != '\n') && (--cnt != 0))
	{
	putc (ch, passfp);

	if (ch == delim)	/* If that was end of string, */
	    {
	    nextch ();		/* set up next char and return. */
	    return;
	    }

	if (ch == '\\')		/* Handle backslash */
	    putc (nextch (), passfp);	/* by quoting next char */
	}

    /*
     * Either EOF, or char length exceeded, or illegal char (newline).
     * For now, we don't complain about anything but EOF.
     */

    if (ch == EOF)
	error ("Unexpected EOF within %s",
	       (delim == '\"' ? "string literal"
			       : (delim == '\'' ? "char constant"
						: "quoted identifier")));
    }

/*
 * PASS_HWSP - auxiliary for PASSTHRU () to scan horiz whitespace NOT at
 *	start of a line, and not being kept.  Just reduce to one space,
 *	unless hit EOL in which case do nothing.
 *	Returns with delimiting char still in CH, not yet output.
 */

static
void
pass_hwsp (void)
    {
    for ( ; ; )
	{
	while (ischwsp (nextch ()))
	    ;				/* Flush all horiz wsp */

	if (ch == '/')
	    {
	    if (nextch () == '*')	/* Start of comment? */
		{
		scancomm ();		/* Flush it!  Leaves last '/' in ch */
		continue;
		}
	    else if (ch == '/' && !clevnocpp)
		{
		comment_type = CPP;
		scancomm ();
		pushch (ch);		/* FW 2A(50) September 1993 */
		comment_type = C;
		continue;
		}
	    else
		{
		fputs (" /", passfp);
		return;
		}
	    }

	break;
	}

    if (ch != '\n')
	putc (' ', passfp);
    }

/*
 * PASS_LINE - auxiliary for PASSTHRU () to handle start of a line.
 *	Must check for directives, etc.
 *	If keeping comments, pass on all whitespace & comments, up to
 *		first non-whitespace.
 *	If not keeping comments, store all whitespace in temp buffer
 *		(using 1 space for each comment) up to 1st non-wsp.
 *	If 1st non-wsp is "#" then invoke directive processing.
 *	If 1st non-wsp is EOL just re-invoke without outputting an EOL.
 *	Returns with delimiting char still in CH, not yet output.
 */

static
void
pass_line (int suppress)
    {
    char	wspbuf[120];	/* # chars of leading whitespace to save */
    register
    char*	cp = wspbuf;
    register
    int		cnt = sizeof (wspbuf) - 2;


    if (!suppress)
	putc ('\n', passfp);		/* First must end any current line */

    if (!keepcmts || inasm)
	{
	for ( ; ; )			/* Flush whitespace and comments */
	    {
	    switch (nextch ())
		{
		case '/':

		    if ((nextch () != '*') && (ch != '/')) /* KAR PPS4574 */
			{
			pushch (ch);
			ch = '/';
			break;
			}
		    else if (ch == '/' && !clevnocpp)
			comment_type = CPP;

		    pass_comm ();
		    comment_type = C;		/* KAR-2/93, PPS 4574 */

		/* Drop thru to output single space */

		case ' ':
		case '\t':
		case '\r':
		case '\v':
		case '\f':
		    if (--cnt > 0)
			*cp++ = ch;

		    continue;

		case '\n':
		    pass_line (++suppress); /* Tail recurse, don't emit EOL */
		    return;

		default:
			break;		    
		}

	    *cp = 0;			/* Tie off */
	    break;
	    }
	}
    else			/* Keeping comments */
	{
	for (;;)			/* Flush whitespace and comments */
	    {
	    if (ischwsp (nextch ()))
		putc (ch, passfp);
	    else if (ch != '/')
		break;
	    else if (nextch () == '*')
		pass_comm ();
	    else if (ch == '/' && !clevnocpp) {
		comment_type = CPP;
		pass_comm ();
		comment_type = C;
		}
	    else
		{
		pushch (ch);
		ch = '/';
		break;
		}
	    }
	}

    if (ch == '#')
	{
	nextch ();		/* Must move past it to set up... */
	directive ();		/* Process directive! */

	while (inasm && nextpp() != T_EOF)	/* If in #asm, do hack */
	    {
	    pptreset ();
	    ppcreset ();
	    pptfput (tlpcur (tlmake (curpp, curval)), passfp);
	    }
	if (ch != EOF)		/* Sigh, must put back */
	    pushch (ch);		/* the 1st char of beg of line */
	pass_line (++suppress);		/* Then can tail recurse! */
	return;
	}
    else if (!keepcmts)
	fputs (wspbuf, passfp);	/* Output saved whitespace up to here */
}

/* PASS_COMM - auxiliary for PASSTHRU () to scan over a comment.
**	Current char is the '*' starting comment.
**	Returns with a space in CH; next input char will be 1st char
**	after the '/' that ended comment.
*/

static
void
pass_comm ()
    {
    if (!keepcmts)
	scancomm ();			/* Flush the comment! */
    else if (comment_type != CPP)	/* KAR-2/93, PPS 4574; C++ comments */
	{
	/* Comment skip for -E when retaining comments (-C) */

	fputs ("/*", passfp);		/* Pass comment start */
	nextch ();

	for (;;)			/* until we break */
	    {
	    putc (ch, passfp);		/* send char off */

	    if (ch != '*')
		nextch ();	/* find a star */
	    else if (nextch () == '/')	/* and a slash to terminate */
		{
		putc ('/', passfp);
		break;			/* Stop loop */
		}

	    if (eof)			/* Also stop if input gone */
		{
		error ("Unexpected EOF in comment");
		break;
		}
	    }
        }
    else
        {				/* KAR-2/93, PPS 4574; C++ comments */
	fputs ("//", passfp);
	nextch ();

	for (;;)
	    {
	    putc (ch, passfp);

	    if (ch != '\n')		/* C++ comment delimeter is EOL */
		nextch ();
	    else
		break;

	    if (eof)
	        {
		error ("Unexpected EOF in comment");
		break;
	        }
	    }
        }

    ch = ' ';		/* Pretend current char now a space */
    }

#if 0

 nextch()	Phase 1, 2 - produce source chars & logical lines
			(handles trigraphs, \-newlines)
 nextrawpp()	Phase 3 - produce raw PP tokens
			(handles comments, flushes WSP)
 nextmacpp()	Phase 3.5 - used to redirect source of raw tokens
			during macro expansion
 nextpp()	Phase 4 - produce clean expanded PP tokens
			(handles directives, macro expansion, flushes EOLs)
 nextoken()	Phase 5, 6, 7 - Produce C tokens from clean PP tokens
			(handles char escapes, string concatenation, token
				conversion & constant parsing)

There are 4 types of scanning, differentiated by the different end results
of the scan.  Each of them invokes at some point the types below it.
	A. Toplevel (language) tokenizing	(Phase 1-7)
	B. Macro/directive (preproc) tokenizing	(Phase 1-4)
	C. Passthru scanning (for -E and #asm)	(Phase 1-4)
	D. Passover scanning (for #if skipping)	(Phase 1-3)
#endif

/* NEXTCH() - Get next character.
**	Implements Translation Phase 1 and 2.
**	Handles trigraphs and quoted newlines ('\\' '\n').
**
**	Because the value returned by nextch() must be pushable by pushch(),
**	and because we cannot call pushch() twice in a row on file input
**	(otherwise STDIO's ungetc() complains), we have to divert input
**	to come from a string whenever this routine does need to call pushch().
**	This is why the calls to pushstr() exist.
**
**	Note: implement IESC escape char similar to MACESC so can
**	easily insert special tokens (like temporary EOF) into input.
*/
static
int
nextch (void)
{
    int prevch = ch;		/* Remember previous char */
    char	 tmpbuf[200];
    char	 *tmp = tmpbuf;
    int		 skp_nl = 0;


    /* First check for pushed-back string */
    if (backstr)			/* Pushback string exists? */
	{
	if ((ch = *backstr++) != '\0')	/* Yes, are there more chars? */
	    return (ch != TCH_ESC)	/* Yes, is this a special escape? */
		? ch			/* Nope, just return char. */
		: (ch = tchesc());	/* Aha!  Handle escape stuff! */
	bstrpop();			/* End of a pushback string, pop it */
	ch = prevch;			/* Ensure "prev chr" isn't NUL */
	return nextch();		/* and try for a new char */
	}

    /* Just get normal input char */

    if (module_pragma)
	ch = EOF;
    else
	ch = getc(in);

    _ch_cpy = ch;		/* 12/90 ccstmt.c needs _ch_cpy */

    if (mlist && fstart && (inlevel <= 0) && (fline == 1))
	{
	sprintf (tmp, "\n; %d\t", fline);	/* KAR 8/3/90 */
	oline++;
	strcat (mlbptr, tmp);
	fstart = 0;
	} /* if */

    if (mlist && (!ch_ungot) && (inlevel <= 0) && (ch != '\n') &&
	    (ch != '\r') && (ch != '\v') && (ch != '\f')) /* KAR 5/91 '\f' */
	{
	sprintf (tmp, "%c", ch);
	strcat (mlbptr, tmp);			/* KAR 8/3/90 */
	} /* if */
    else if (ch_ungot)
	{
	if (ch == '\n')
	    skp_nl = 1;
	ch_ungot = 0;
	} /* else if ch_ungot */

    /* Add char to small circular buffer for possible error messages */

    *erptr++ = ch;		/* Add to saved input line. */

    if (--erpleft <= 0)	/* If reached end of buffer, */
	{
	erptr = errlin;		/* wrap back around. */
	erpleft = ERRLSIZE;
	}

    switch (ch)
	{
	case EOF:			/* End Of File on input */
				/* Do a couple of checks to verify */
	    if (ferror(in))
	        error("I/O error detected while reading file %s", inpfname);
	    else if (!feof(in) && !module_pragma)
		int_error("nextch: spurious EOF");

	    if (prevch != '\n' && prevch != EOF) /* Was last ch in file EOL? */
		{
		if (!module_pragma)
		    warn("File does not end with EOL (\\n)");	/* Nope */
		return pushstr("\n");	/* Attempt graceful termination */
		}			/* by providing EOL as last char. */

	    if (scanning_comment)
		error ("EOF while scanning comment");

	    if (inlevel > 0)		/* Drop level.  If more, pop input. */
		{
		ifpopchk(inlevel);	/* Do balanced conditional check */
		fclose(in);		/* Close this file */
		--inlevel;
		in = inc[inlevel].cptr;	/* then set vars from popped level */
		page = inc[inlevel].cpage;
		line = inc[inlevel].cline;
		fline = inc[inlevel].cfline;
		ch_ungot = 1;
		strcpy(inpfname, inc[inlevel].cname);

#if DEBUG_PP
		if (debpp)
		    {
		    fprintf (fpp, "#include %d: restored \"%s\", new ch %#o\n",
			    inlevel+1, inpfname, nextch ());
		    return ch;
		    }
#endif

		return nextch();	/* try for another char */
		}

	    /*
	     * EOF at top level (main input file).  We DON'T close the
	     * stream at this point, so that it's OK to call this routine
	     * again.  Some preproc/parsing routines want to just punt
	     * on EOF and let a higher level take care of it.  The CC
	     * mainline fcloses the stream.
	     */

	    if (eof)
		{
		if (mlist)		 /* KAR 8/90 */
		    mlbuf[0] = '\0'; /* Don't dump garbage if seen EOF */

		break;		/* If already seen EOF, needn't redo stuff. */
		} /* eof */

	    eof = 1;		/* Flag end of file at top level. */

	    if (iflevel)		/* Error if preprocessor unbalanced */
		ifpopchk(0);	/* Spit out helpful error message */

	    break;			/* Return EOF char */


	case '?':			/* (&%#^#$(*@!!! trigraph hackery */
	    {
	    static
	    int		skipflg;	/* TRUE if skipped a '?' */
	    static
	    int		intrig = 0;	/* TRUE if in this code already */

	    
	    if (clevel < CLEV_STDC)	/* Only if STDC */
		break;

	    if (intrig)
		break;	/* If already scanning, just return char. */

	    ++intrig;		/* Say scanning trigraph! */

	    if (!skipflg		/* If we didn't skip a '?' already, */
		  && nextch() != '?')	/* OK to read next ch. Two in a row? */
		{
		pushch (ch);		/* Nope, push back peeked-at char */
		intrig = 0;		/* No longer scanning */
		return pushstr ("?");	/* Return original q-mark */
		}

	    nextch ();		/* Got two '?'s in row!  Scan for third... */
	    intrig = 0;		/* No longer scanning */
	    skipflg = 0;		/* Skip hackery done */

	    switch (ch)		/* Dispatch on 3rd char! */
		{
		case '=':
		    ch = '#'; break;

		case '(':
		    ch = '['; break;

		case '/':
		    ch = '\\'; break;

		case ')':
		    ch = ']'; break;

		case '\'':
		    ch = '^'; break;

		case '<':
		    ch = '{'; break;

		case '!':
		    ch = '|'; break;

		case '>':
		    ch = '}'; break;

		case '-':
		    ch = '~'; break;

		default:		/* Not trigraph, but simple pushback */
		    pushch (ch);	/* Make use of 1-char file backup */
		    return pushstr ("??"); /* OK to use string for other 2 */

		case '?':		/* Not a trigraph, big screwup. */
		    /*
		     * On the next call we have to make sure that
		     * re-examination starts again -- just pushing a
		     * string back won't work right.  We also can't
		     * count on having more than 1 char of file backup!
		     * The solution is to realize that we'll come here
		     * again when the pushch'd '?' is read from getc (),
		     * so we don't try to push back the middle '?' at
		     * all, and instead set a flag that says it was
		     * "skipped"; when we do come back, we'll check
		     * that flag and do the right thing.
		     */

		    pushch (ch);		/* Push back 3rd '?' */
		    skipflg = 1;		/* Say 2nd is skipped */
		    return pushstr ("?");	/* Return 1st */
		}
	    }

	break;

    case '\\':
	if (isceol(nextch()))	/* Check next char */
	    {
	    ch = 0;		/* Quoted EOL is ignored totally, to extent */
	    return nextch();	/* of forgetting last char was EOL! */
	    }
	pushch(ch);		/* otherwise put char back */
	ch_ungot = 1;		/* don't put in buffer twice -- KAR 8/90 */
	return pushstr("\\");	/* and set up a pushch-able input */
				/* and return backslash */

    case '\f':			/* Form-feed */
	page++;			/* Starts new page */
	line = 1;		/* at first line */
	break;
    case '\r':
    case '\v':
    case '\n':
	line++;			/* new line, same page */
	fline++;
	tline++;

	if (mlist && (inlevel <= 0))
	    {
	    oline++;
	    if (oline > MAX_OLINE)
		{
		opage++;
		sprintf(tmp,
		   "\n\f; %-26s\t\tCompuServe Incorporated\t\t%s\t  Page %d\n",
			dspfname, creatime, opage);
		strcat (mlbptr, tmp);
		sprintf(tmp, "; KCC: %-20s\t\t\t\t\t\t\t%s\n",
			ver_str, comptime);
		strcat (mlbptr, tmp);
		oline = HDR_LINES;
		} /* if MAX_OLINE */
	    if (!skp_nl)
		strcat(mlbptr, "\n");
	    if (!skp_nl)
		{
		dmpmlbuf();
		dmp_errmsg();
		sprintf (tmp, "; %d\t", fline); /* KAR 8/3/90 */
		strcat (mlbptr, tmp);
		} /* if  */
    else
	skp_nl = 0;
	} /* if mlist */

    break;
    default:
	;	/* do nothing */
	}
    return ch;
}

/* DMPMLBUF() - dump contents of the mixed listing buffer
**	Only function calls this, nextoken(); in cclex.c
*/
void
dmpmlbuf (void)
{
	#define EOS '\0'

	/* KAR-10/91, took out the if condition; no longer needed */
    fputs(mlbptr, out);
    mlbuf [0] = EOS;
	#undef EOS
}

/* DMP_ERRMSG() - dump error messages accumulated.
**
*/
static
void
dmp_errmsg (void)
{
	/* KAR-10/91, took out sup_dmp in if condition; no longer needed */
    if (err_waiting)
	{
	fprintf (out, "%s", errbuf);
	free (errbuf);
	errbuf = NULL;
	oline += ((err_waiting * 2) + 1);
	err_waiting = 0;
	} /* if */
}

/* TCHESC() - Handle token char escape.
**	backstr is set, and char it points to says what to do.
**	Returns char to be given to caller of nextch().
*/
static
int
tchesc()
{
    int i;
    switch (i = *backstr++)	/* Handle it */
	{
	default:
	    int_error("tchesc: token escape char %d='%c'", i, i);

	case TCH_ESC:		/* Quoting escape char */
	    return i;

	case TCH_EOF:
	    backstr -= 2;	/* Bump back to point at TCH_ESC */
	    return EOF;		/* and return EOF as char */
	}
}


/* SINBEG(cp) - Make nextch() return input from given string.
** SINEND() - Stop using string input, restore previous source.
**	Note that nextch() will return EOF when the string input is done,
**	instead of popping input and continuing.
**	SINEND() must be called to proceed further.
** These routines are recursive, but could be simplified by forbidding
** recursion and just saving/restoring CH rather than calling pushch/nextch.
*/
static
char eofstr[] =
{
    TCH_ESC, TCH_EOF
}
;

static
void
sinbeg(cp)
char *cp;
{
    pushch(ch);		/* Save current char (since not tokenized yet) */
    bstrpush(eofstr);	/* Put EOF on end of input string */
    pushstr(cp);	/* and set up string for input! */
}
static
void
sinend()
{
    while (backstr != eofstr)		/* We should have hit string EOF... */
	{
	int_error("sinend: leftover input");
	bstrpop();			/* Will get error if overpop */
	}
    bstrpop();		/* Take off EOF string, restore previous source */
    nextch();		/* Set up next char for tokenizer */
}

/* PUSHCH - Push back char for nextch()
** PUSHSTR - Push back string, sets up 1st char as current char
** BSTRPUSH - Push back string for ditto (save any current string)
** BSTRPOP - Pop pushback string, restore previous string
*/
void
pushch(c)
int c;
{
    if (backstr)
	{
	backstr--;		/* pushed string input, back up over it */
	if(c != *backstr)
	    int_error("pushch: bad backup char");
	return;
	}

    /* File input, back up over that.
    ** Note that errlin and t/f/line only need to be fixed up for file input.
    */
    if (c != ungetc(c, in))
	int_error("pushch: ungetc failed: %o", c);
    if (isceol(c))
	{
	line--;				/* dont lose track of line count */
	fline--;
	tline--;
	}
    /* Back up over char of saved context */
    if (erptr == errlin)		/* If at start of buffer */
	{
	erptr = errlin + ERRLSIZE;	/* Wrap back to end */
	erpleft = 0;
	}
    *--erptr = '\0';		/* Back up and zap what we previously stored */
    ++erpleft;
}

static
int
pushstr(cp)
char *cp;
{
    bstrpush(cp);
    return nextch();
}

static
void
bstrpush(cp)
char *cp;
{
    if (bkstrlev >= MAXBKSTRS-1)
	{
	int_error("bstrpush: bkstrs overflow");
	bkstrlev = MAXBKSTRS-1;
	}
else
    {
    bkstrs[bkstrlev++] = backstr;
    backstr = cp;
    }
}

static
void
bstrpop (void)
{
    if (--bkstrlev < 0)
	{
	int_error("bstrpop: bkstrs underflow");
	backstr = NULL, bkstrlev = 0;
	}
else
    backstr = bkstrs[bkstrlev];
}

/* NEXTRAWPP()	Phase 3 - produce raw PP tokens
**			(handles comments, flushes WSP)
**
*/

#define retchk(chr,no,yes)	if (nextch () != chr)	\
				    rawpp = no;		\
				else			\
				    {			\
				    nextch ();		\
				    rawpp = yes;	\
				    }			 

#define retchk2(ch1,ch2,no,yes1,yes2)	switch (nextch ())		\
					    {				\
					    case ch1:			\
						nextch ();		\
						rawpp = yes1;		\
						break;			\
					    case ch2:			\
						nextch ();		\
						rawpp = yes2;		\
						break;			\
					    default:			\
						rawpp = no;		\
						break;			\
					    }

static
int
nextrawpp (void)
{
    int	    retflag = 0;


    rawval.i = 0;
    
    switch (ch)
	{
	case EOF:
	    rawpp = T_EOF;
	    retflag = 1;
	    break;


	/* Check for "bad" horiz whitespace (may give error msg) */

	case '\r':			/* Include CR here for now */
	case '\v':			/* Vert tab */
	case '\f':			/* Ditto FF */
	    pushch (ch);	/* Ensure scanhwsp () will see this char */

	/* Drop thru to call scanner */

	/* Check for "good" horiz whitespace */

	case '\t':			/* Horiz tab */
	case ' ':			/* Space */
	    scanhwsp ();
	    retflag = 1;
	    break;


	case '\n':
	    rawpp = T_EOL;
	    break;


	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
	    ppnconst (0);
	    retflag = 1;
	    break;


	case '\'':
	    ppsconst (0);		/* Char constant */
	    rawpp = T_CCONST;
	    retflag = 1;
	    break;


	case '"':
	    ppsconst (0);		/* String constant */
	    rawpp = T_SCONST;
	    retflag = 1;
	    break;


	case '.':

	    if (isdigit (nextch ()))	/* If followed by digit, */
		{
		ppnconst ('.');
		retflag = 1;
		break;
		}

	    if (ch != '.')
		{
		rawpp = Q_DOT;
		retflag = 1;
		break;
		}

	    if (nextch() != '.')	/* Ellipsis "..."? */
		{
		pushch(ch);		/* Nope, push it back */
		ch = '.';		/* And restore prev char */
		rawpp = Q_DOT;
		retflag = 1;
		break;
		}

	    rawpp = T_ELPSIS;		/* ... */
	    break;


	case '#':
	    retchk('#', T_SHARP, T_SHARP2) /* # and ## */
	    retflag = 1;
	    break;


	case '=':	/* KAR-11/91, usage before initialization code */

	    if (nextch() != '=')
		{
		ra_expr = 1;
		rawpp = Q_ASGN;
		retflag = 1;
		break;
		}

	    nextch();
	    rawpp = Q_EQUAL;
	    retflag = 1;
	    break;


	case '!':
	    retchk('=', Q_NOT, Q_NEQ)	/* ! and != */
	    retflag = 1;
	    break;


	case '*':
	    retchk('=', Q_MPLY, Q_ASMPLY) /* * and *= */
	    retflag = 1;
	    break;


	case '%':
	    retchk('=', Q_MOD, Q_ASMOD)	/* % and %= */
	    retflag = 1;
	    break;


	case '^':
	    retchk('=', Q_XORT, Q_ASXOR) /* ^ and ^= */
	    retflag = 1;
	    break;


	case '/':	/* Special, to check for comments! */

	    switch (nextch ())
		{
		default:
		    rawpp = Q_DIV;
		    break;


		case '=':
		    nextch();
		    rawpp = Q_ASDIV;
		    break;


		case '*':
		    scancomm ();
		    scanhwsp ();
		    break;

		case '/':		/* KAR-2/93, PPS 4574; C++ comments */

		    if (!clevnocpp)	/* FW 2A(46) August 1993 */
			{
			comment_type = CPP;
			scancomm ();
			pushch (ch);	/* FW 2A(50) September 1993 */
			comment_type = C;
			}

		    scanhwsp ();
		    break;
		    
		}

	    retflag = 1;
	    break;


	case '|':
	    retchk2('=','|', Q_OR, Q_ASOR, Q_LOR) /* |  and |= and || */
	    retflag = 1;
	    break;


	case '&':
	    retchk2('=','&', Q_ANDT, Q_ASAND, Q_LAND) /* &  and &= and && */
	    retflag = 1;
	    break;


	case '+':
	    retchk2('=','+', Q_PLUS, Q_ASPLUS, T_INC)	/* +  and += and ++ */
	    retflag = 1;
	    break;


	case '-':

	    switch (nextch())
		{
		case '-':
		    rawpp = T_DEC;
		    break;	/* -- */


		case '=':
		    rawpp = Q_ASMINUS;
		    break;/* -= */


		case '>':
		    rawpp = Q_MEMBER;
		    break;/* -> */


		default:
		    rawpp = Q_MINUS;
		    retflag = 1;
		    break;
		}

	    break;


	case '>':

	    switch (nextch())
		{
		default:
		    rawpp = Q_GREAT;
		    retflag = 1;
		    break;


		case '=':
		    rawpp = Q_GEQ;
		    break;		/* >= */


		case '>':
		    retchk ('=', Q_RSHFT, Q_ASRSH)	/* >> and >>= */
		    retflag = 1;
		    break;
		}

	    break;


	case '<':

	    switch (nextch())
		{
		default:
		    rawpp = Q_LESS;
		    retflag = 1;
		    break;


		case '=':
		    rawpp = Q_LEQ;		/* <= */
		    break;


		case '<':
		    retchk('=', Q_LSHFT, Q_ASLSH)	/* << and <<= */
		    retflag = 1;
		    break;
		}
	    
	    break;


	case '(':
	    rawpp = T_LPAREN;
	    break;


	case ')':
	    rawpp = T_RPAREN;
	    break;


	case ',':
	    rawpp = T_COMMA;
	    ra_expr = 0;
	    break;


	case ':':
	    rawpp = T_COLON;
	    break;


	case ';':
	    rawpp = T_SCOLON;
	    ra_expr = 0;
	    break;


	case '?':
	    rawpp = Q_QUERY;
	    break;


	case '[':
	    rawpp = T_LBRACK;
	    break;


	case ']':
	    rawpp = T_RBRACK;
	    break;


	case '{':
	    rawpp = T_LBRACE;
	    ra_expr = 0;
	    break;


	case '}':
	    rawpp = T_RBRACE;
	    break;


	case '~':
	    rawpp = Q_COMPL;
	    break;


	case '`':			/* KCC extension: identifier quoter */

	    if (clevkcc)		/* If extensions in effect, */
		{
		ppsconst(0);		/* parse like string literal! */
		rawpp = T_IDENT;
		}
	    else
		ppunknwn();

	    retflag = 1;
	    break;


	default:

	    if (!iscsymf(ch))	/* Can char be start of identifier? */
		{
		ppunknwn();
		retflag = 1;
		break;
		}

	    /* Handle identifier! */

	    rawval.cp = ppcbeg();	/* Assume ident, harmless if not */

	    if (ch == 'L')	/* Possible "wide char" indicator? (BARF!!) */
		{
		int ch2 = nextch();


		if (ch2 =='\'')
		    {
		    ppsconst('L');
		    rawpp = T_CCONST;
		    retflag = 1;
		    break;
		    }
		else if (ch2 =='\"')
		    {
		    ppsconst('L');
		    rawpp = T_SCONST;
		    retflag = 1;
		    break;
		    }

		ppcput('L');	/* Not a wide char, recover from peekahead */

		if (!iscsym(ch))	/* Was it just "L"?  Barf, barf */
		    {
		    rawpplen = ppcend();
		    rawpp = T_IDENT;
		    retflag = 1;
		    break;
		    }
		}

	    do
		{
		ppcput(ch);	/* Gobble up identifier */
		}
	    while (iscsym(nextch()));

	    rawpplen = ppcend();
	    rawpp = T_IDENT;
	    retflag = 1;
	    break;
	}				/* End of switch(ch) */

    if (!retflag)
	nextch();		/* Done, set up next char after this token */

    /*
     * A tokenizing pre-compilation scheme will break the input stream
     * at this point.  Tokens, and their associated string literals
     * if any, will be diverted to a file.
     */
    
    return rawpp;
}

/* SCANHWSP - gobbles all horiz whitespace starting with current char, and
**	returns a T_WSP token.
*/
static
int
scanhwsp (void)
{
    for (;;)
	{
	while (iscppwsp(nextch())) ;	/* Skip all valid PP whitespace */
	switch (ch)
	    {
	    case '\r':
		if (nextch() == '\n')	/* Peek ahead */
		    {
		    note("Stray '\\r' seen, ignoring");
		    break;
		    }
		else
		    {
		    pushch(ch);
		    ch = '\r';	/* put back and drop through */
		    }

	    case '\v':
	    case '\f':
		if (indirp)	/* Processing directive between # and EOL? */
		    {
		    error("invalid whitespace in directive: '\\%#o'", ch);
		    nextch();
		    return rawpp = T_EOL;
		    }
		continue;

	    case '/':		/* Possible comment! */
		if (nextch() == '*')
		    {
		    scancomm ();	/* Yep, flush comment */
		    continue;	/* and go get next char after comment */
		    }
		/* KAR-2/93, PPS 4574; C++ comments */
		else if (ch == '/' && !clevnocpp)
		    {
		    comment_type = CPP;
		    scancomm ();
		    pushch (ch);	/* FW 2A(50) September 1993*/
		    comment_type = C;
		    continue;
		    }
		
		/* Ugh, not a comment, must back up and leave '/' there */
		pushch (ch);		/* Push '*' back */
		ch = '/';

		break;

	    default:
		break;	/* do nothing */
	    }
	break;
	}
    return rawpp = T_WSP;
}

/* SCANCOMM - Skips over a comment; current char must be the '*' opening
**	a comment.  On return, current char is the '/' closing the
**	comment (or EOF).
*/
static
void
scancomm (void)
{
    scanning_comment = 1;

    for (;;)
	{
	switch (nextch())
	    {
	    case EOF:
		error("Unexpected EOF in comment");
		break;			/* Get out of loop */

	    /* KAR-2/93, PPS 4574; C++ comments */
	    case '\n':
		if (comment_type == CPP)
		    break;
		else
		    continue;
		
	    case '/':			/* Special helpful check  */
		do
		    {
			
		    if (nextch() == '*')
			{
			advise("Nested comment");
			break;
			}
		    }
		while (ch == '/');
		if (ch != '*') continue;
		/* Drop thru to handle the * we just got */

	    case '*':
		while (nextch() == '*') ;	/* Skip all '*'s */
		if (ch == '/')
		    break;			/* End of comment, stop! */
	    default:
		continue;			/* Not ended yet */
	    }
	break;			/* Break from switch is break from loop */
	}
    scanning_comment = 0;
}

/* Various preprocessor token parsing routines. */

/* PPNCONST - Parse a pp-number, starting with char in "ch", unless
**	arg is non-zero in which case arg is the 1st char, "ch" the 2nd.
**	This only happens if the first char was '.', ie a floating constant.
*/
static
int
ppnconst(ch1)
{
    static int hexconst;


    hexconst = 0;		/* Set kludge flag */
    rawval.cp = ppcbeg();	/* Get ptr into char pool */

    if (ch1)
	{
	ppcput(ch1);
	rawpp = T_FCONST;	/* Aha, float const */
	}
    else
	rawpp = T_ICONST;	/* Assume integer until detect otherwise */

    ppcput(ch);

    for (;; ppcput(ch))
	{
	switch (nextch())
	    {
	    case 'x':
	    case 'X':		/* Remember if hex constant */
		if (ppclen() == 1 && ppclast() == '0')
		    ++hexconst;
		continue;
	    case 'E':
	    case 'e':			/* 'E' means float const, */
		if (!hexconst)		/* unless already hex const! */
	    case '.':
		rawpp = T_FCONST;	/* Remember float const */
	    continue;
	    case '-':
	    case '+':		/* If prev char not 'E' or 'e', stop now */
		if (toupper(ppclast()) != 'E')
		    break;
		continue;
	    default:
		if (!iscsym(ch))	/* Any identifier char (BARF BARF) */
		    break;
		continue;
	    }
	break;			/* Break from switch is break from loop */
	}
rawpplen = ppcend();	/* Done with string */
return rawpp;		/* Return either T_ICONST or T_FCONST */
}

/* PPSCONST - Parse a string literal or char constant, starting with char
**	in "ch", unless
**	arg is non-zero in which case arg is the 1st char, "ch" the 2nd.
**	"ch" will always contain '\"' or '\'' to indicate type of constant.
**	Note no token value is set or returned; the caller must do this.
*/
static
void
ppsconst(ch1)
{
    int delim = ch;		/* Remember delimiter we're using */

    rawval.cp = ppcbeg();	/* Get ptr into char pool */
    if (ch1) ppcput(ch1);
    ppcput(ch);
    for (;; ppcput(ch))
	{
	switch (nextch())
	    {
	    case EOF:
	    case '\n':
		break;
	    case '\\':			/* Escape (just treat as quoter) */
		ppcput(ch);
		switch (nextch())
		    {
		    case EOF:
		    case '\n':
			break;
		    default:
			continue;
		    }
		break;
	    default:
		if (ch != delim)
		    continue;
		ppcput(delim);		/* Succeeded! */
		rawpplen = ppcend();	/* Done with string */
		nextch();			/* Advance past delimiter */
		return;			/* Done, return */
	    }
	break;			/* Break from switch is break from loop */
	}

    /* Stopped because hit erroneous char.  Fix up and barf about it. */
    ppcput(delim);		/* Ensure token is well-formed */
    rawpplen = ppcend();	/* Finish off string */

    error("%s in %s",
	(ch=='\n' ? "EOL" : (ch==EOF ? "EOF" : "Illegal char")),
	(delim=='\'' ? "char constant"
		: (delim=='\"' ? "string literal"
		: "quoted identifier")));
}


/* PPUNKNWN - "Parse" the unknown char in "ch" into a T_UNKNWN token.
*/
static
int
ppunknwn()
{
    rawval.cp = ppcbeg();	/* Get ptr into char pool */
    ppcput(ch);
    nextch();			/* Consume it, get next */
    rawpplen = ppcend();	/* Done with string */
    return rawpp = T_UNKNWN;
}

static
void									// FW KCC-NT
pptresize()
{
    static struct pptlist_s
	{
	PPTOK		 *pptblock;
	struct pptlist_s
	*pptnext;
	}
    pptfirst, *pptcurr = NULL;

    if (pptoks == NULL)
	{
	if (pptcurr == NULL)	/* if NOT pptreset() */
	    /* First call to pptresize() */
	    {
	    pptfirst.pptblock =(PPTOK *) calloc (1, DYN_SIZE * sizeof(PPTOK));
	    pptfirst.pptnext = NULL;
	    }
	pptcurr = &pptfirst;	/* init pptcurr for first pptblock */
	}
else if (pptcurr->pptnext == NULL)	/* init another pptblock */
    {
    pptcurr = pptcurr->pptnext =
	    (struct pptlist_s
    *) calloc (1, sizeof(struct pptlist_s
    ));
pptcurr->pptblock =  (PPTOK *) calloc (1, DYN_SIZE * sizeof(PPTOK));
pptcurr->pptnext = NULL;
    }
else	/* reuse a pptblock */
    pptcurr = pptcurr->pptnext;
if (pptcurr == NULL || pptcurr->pptblock == NULL ||
	pptfirst.pptblock == NULL)
    efatal("Out of memory, preprocessor token table overflow");
pptptr = pptoks = pptcurr->pptblock;
pptmax = &pptoks[DYN_SIZE - 1];
}

static
int
ppcresize()
{
#if 0	/* 5/91 Dynamic tables,  ppcresize() was ppcerror() */
    ptrdiff_t offset = ppcptr - ppcpool;

    ppcpool = (char *) realloc (ppcpool, ppcsize * DYN_SIZE * sizeof(char));
    ppcleft += DYN_SIZE * sizeof(char);
    ppcptr = (char *) ((ptrdiff_t) ppcpool + offset);
    ++ppcsize;
    if (ppcpool == NULL)
#endif
	{
	efatal("Preprocessor token char pool overflow");
	return -1;	/* simulate error (number of remaining chars) */
	}
}

/* Translation Phase 4 - directive & macro expansion handling
**	If a current token list exists (curtl), uses that; all tokens on
**	that list are "cooked" and no longer subject to macro expansion.
**	Returns curpp after setting all current stuff.
*/
int
nextpp (void)
{
    if ((curptr = curtl.tl_head) != NULL)	/* If using token list, */
	{
	curpp = curptr->pt_typ;		/* consume first token on it */
	curval = curptr->pt_val;
	if (tlpskip(curtl) == NULL)
	    {
	    /* Token list ran out.  Clean up for next call. */
	    tlzinit(curtl);		/* No active cooked token list */
	    pptreset();			/* Free all tokens (curptr still OK) */
	    if (inasm)			/* If in #asm, refill token list */
		return curtl = asmrefill(), nextpp();
	    }
	return (curpp != T_IDENT) ? curpp : findident();
	}

    /* Get a new token from input */
    ppcreset();				/* Free all token string storage */
    if (nextrawpp() == T_EOL)		/* At EOL must start directive scan */
	{
	while (tskiplwsp()==T_SHARP)	/* Get 1st non-wsp token */
	    {
	    directive();		/* Handle directive! */
	    if (curtl.tl_head)		/* If it set up a cooked list, */
		return nextpp();	/* get input from that. */
	    }
	}
    curval = rawval;
    return ((curpp = rawpp) != T_IDENT) ? curpp : findident();
}

/* PUSHPP() - push back current token.
**	So far, only needed by one call in CCLEX.
*/
void
pushpp()
{
    if (curptr)			/* If current token is from list, */
	tlpins(curtl, curptr);	/* backup is trivial! */
else    /* If current token not from a list, must fake one up -- barf. */
    curtl = tlmake(curpp, curval);	/* Make a token list with this */
}

/* FINDIDENT - auxiliary for NEXTPP that checks for macro expansion at
**	top level.
*/
static
int
findident()
{
    register char *cp = curval.cp;

    if (*cp == SPC_IDQUOT)		/* Special quoted ident? */
	{
	cursym = NULL;			/* Just punt to CCLEX */
	return curpp;
	}

    /* See if this symbol is already defined as a macro or C sym. */
    /* Note that if curptr is set, token came from cooked tokenlist and so
    ** is not to be expanded.
    */
    cursym = symfind(cp, 1);	/* Find sym or create global symbol! */
    if (cursym->Sclass == SC_MACRO)
	{
	if (!curptr && mexptop(cursym, 0))	/* Is macro! OK to expand? */
	    return nextpp();		/* Yes, return cooked tokens!*/
	    
	/* Sym is macro, but not being expanded, so search again to find
	** the C symbol that the macro was shadowing.
	** This is tricky since the search has to be continued from AFTER
	** the point where the macro symbol exists.
	** Even trickier is the requirement that, if no
	** existing symbol is found, we must create
	** a new global symbol which comes AFTER the macro symbol.
	*/
	if ((cursym = symfnext(cursym)) == NULL) /* Find next sym.  If none, */
	    {
	    cursym = shmacsym(symgcreat(cp));	/* create shadow sym */
	    }
	}
    return curpp;
}

/* FINDMACSYM - Find macro name symbol if one exists
*/
static
SYMBOL *
findmacsym(id)
char *id;
{
    SYMBOL *macsym;

	if ((macsym = symfind(id, 0)) != NULL) /* Scan for macro or identifier */
	{
	if (macsym->Sclass == SC_MACRO)	/* Found symbol, see if macro */
	    return macsym;		/* Yup, return the pointer */
else
    --(macsym->Srefs);	/* Compensate for spurious symfind ref */
    }
return NULL;
}

/* NEXTMACPP() - Token input for macro expansion, which is called in place
**	of nextrawpp() so input can easily be redirected to token lists.
**	"rawptr" is set if the token is a pptok.
**	NOTE that the returned token is no longer needed since the
**	list pointer has been bumped prior to return, so it is OK to
**	mung the token (assuming nothing else needs the overall list).
**	The exception is T_EOF, which leaves the list pointer alone so that
**	all further calls will return T_EOF until a mtlpop() is done.
*/
static
int
nextmacpp()
{
    if ((rawptr = mactl.tl_head) != NULL)	/* If using token list, */
	{
	rawval = rawptr->pt_val;	/* get from list */
	if (((rawpp = rawptr->pt_typ) != T_EOF)	/* If not EOF, */
	  && (tlpskip(mactl) == NULL))	/* set up for next call */
	    {
	    mtlpop();			/* List ran out, pop it off */
	    }
	return rawpp;
	}
    /* No token list, now emulate nextpp() without directive processing */
    if ((curptr = curtl.tl_head) != NULL)	/* If using token list, */
	{
	rawptr = curptr;
	rawpp = curpp = curptr->pt_typ;	/* Consume first token on it */
	rawval = curval = curptr->pt_val;
	if (tlpskip(curtl) == NULL)
	    {
	    /* Token list ran out.  Clean up for next call. */
	    tlzinit(curtl);		/* No active cooked token list */
	    }
	return curpp;
	}
    /* Get new token from file input */
    return nextrawpp();
}

/* PUSHMP() - Push current token back for nextmacpp().
*/
static
void
pushmp()
{
    if (rawptr)	tlpins(mactl, rawptr);
else
    pushpp();
}

/* MTLPUSH(tl) - Push a token list on stack for nextmacpp() to read from.
*/
static
void
mtlpush(tl)
tlist_t tl;
{
    if (mactlev >= MAXMACNEST-1)
	{
	int_error("mtlpush: mactls overflow");
	}
else
    {
    mactls[mactlev++] = mactl;
    mactl = tl;
    }
}

/* MTLPOP() - Pop off an exhausted token list
*/
static
void
mtlpop()
{
    if (--mactlev < 0)
	{
	int_error("mtlpop: mactls underflow");
	mactlev = 0, mactl.tl_head = NULL;
	}
else
    mactl = mactls[mactlev];
}

/* Auxiliary token handling routines */

/* TLTOMAC(tl,cp) - Convert token list to macro body string
**	Returns updated ptr to end of string
*/
static
char *
tltomac(tl,cp)
tlist_t tl;
char *cp;
{
    register int i;
    register PPTOK *p;

    for (p = tlpcur(tl); p; p = p->pt_nxt)
	{
	if ((i = p->pt_typ) != 0 && i < NTOKDEFS)
	    {
	    *cp++ = i;			/* Always stick token type in */
	    if (!tokstr[i]) switch (i)	/* Token not op or punct? */
		{
		case T_MACARG:
		case T_MACSTR:
		case T_MACINS:		/* Value is param # */
		    *cp++ = p->pt_val.i + MAC_ARGOFF;
		case T_MACCAT:
		    break;
		default:		/* Assume rest of string is token */
		    cp = estrcpy(cp, p->pt_val.cp);
		    ++cp;		/* Move over NUL char */
		    break;
		}
	    }
else
    tkerr("tltomac", i);
    }
*cp++ = '\0';		/* Add final terminator to whole thing */
return cp;
}

#if 0
/* TLFRMAC(cp) - Convert macro body string to token list
**	Returns token list.  List will be empty if string is NULL or "".
*/
static
tlist_t
tlfrmac(cp)
char *cp;
{
    tlist_t tl;
    register int i;
    static PPTOK zpptok;
    PPTOK pptok;

    tlzinit(tl);			/* Init to zero */
    if (!cp || !*cp)
	return tl;
    while ((i = *cp++) != '\0')		/* Stop when see zero token */
	{
	if (i < NTOKDEFS)
	    {
	    pptok = zpptok;
	    pptok.pt_typ = i;		/* Stick in token # */
	    if (!tokstr[i]) switch(i)	/* Token not an op or punct? */
		{
		case T_MACARG:
		case T_MACSTR:
		case T_MACINS:
		    pptok.pt_val.i = *cp++ - MAC_ARGOFF; /* Value is param # */
		case T_MACCAT:
		    break;
		default:	/* Assume rest of string is token */
		    pptok.pt_val.cp = cp;	/* Remember ptr to start */
		    if (*cp) while (*++cp);	/* Skip over */
		    break;
		}
	    tltadd(tl, pptok);	/* Add to end of list */
	    }
else
    tkerr("tltomac", i);
    }
return tl;
}
#endif

/* TLTOSTR(tl,cp,n) - Convert token list to char string
**	Only writes up to a max of N chars, plus 1 additional NUL.
**	Returns updated ptr to the NUL at end of string.
*/
static
char *
tltostr(tlist_t tl, char *cp, int n)
{
    register int i;
    register char *str;
    for (; tlpcur(tl); tlpskip(tl))
	{
	if ((i = tlpcur(tl)->pt_typ) < NTOKDEFS)
	    {
	    if ((str = tokstr[i]) == 0)
		str = tlpcur(tl)->pt_val.cp;
	    if (!str)
		{
		tkerr("tltostr", i);
		break;
		}
	    if ((i = strlen(str)) <= n)
		{
		cp = estrcpy(cp, str);
		n -= i;
		}
else
    {
    *cp = 0;
    strncat(cp, str, n);
    cp += n;
    break;
    }
    }
else
    tkerr("tltostr", i);
    }
return cp;
}

/* TLFRSTR(cp) - Convert string to token list
**	Returns token list.
*/
static
tlist_t
tlfrstr(cp)
char *cp;
{
    tlist_t tl;

    tlzinit(tl);
    sinbeg(cp);		/* Set up input from this string */
    while (nextrawpp() != T_EOF)
	tltadd(tl, tokize());
    sinend();		/* Done, restore previous input */
    return tl;
}

/* TLIMAKE(typ, val) - Make a tokenlist consisting of just one T_ICONST
**	token, with the given value.  String is expressed in terms of
**	the given base (current choices are 0 for octal, 1 decimal)
*/
static
tlist_t
tlimake(val, baseflg)
INT val;
{
    tlist_t tl;
    static PPTOK itok =
	{
	T_ICONST
	};
	char *cp;

	if (ppcleft < (TGSIZ_WORD/3)+2)	/* If might overflow buffer, */
	{
#if 0	/* 5/91 Dynamic tables */
	ppcresize();
#else
	ppcleft = -1, (void)ppcend();	/* trigger fatal error msg now */
#endif
	}
    *(tl.tl_head = tl.tl_tail = tokpcre()) = itok;
    cp = ppcbeg();
    if (baseflg && val >= 0)		/* Deposit num string in char pool */
	sprintf(cp, "%ld",  val);		/* Decimal */
else
    sprintf(cp, "%#lo",  val);	/* Octal */
tl.tl_head->pt_val.cp = cp;
ppcleft -= (val = strlen(cp)+1);	/* Update char pool vars */
ppcptr += val;
return tl;
}


/* TOKIZE() - Make current raw token into a pptok.
*/
static
PPTOK
tokize()
{
    static PPTOK ztok;	/* Zero pptok, for initialization */
    PPTOK tok;
    tok = ztok;
    tok.pt_typ = rawpp;		/* Token type */
    tok.pt_val = rawval;	/* Token value */
    return tok;
}

/* TLRAWADD(&tl) - Make current raw token into a pptok at tail of specified list.
*/
static
void
tlrawadd(atl)
tlist_t *atl;
{
    static PPTOK ztok;	/* Zero pptok, for initialization */
    PPTOK tok;
    tok = ztok;
    tok.pt_typ = rawpp;		/* Token type */
    tok.pt_val = rawval;	/* Token value */
    tltadd(*atl,tok);
}

/* TLCOPY(tl) - Make copy of a tokenlist.
*/
static
tlist_t
tlcopy(il)
tlist_t il;
{
    tlist_t ol;
    PPTOK *p;

    tlzinit(ol);		/* Set OL to empty list */
    for (; tlpcur(il); tlpskip(il))
	{
	*(p = tokpcre()) = tltcur(il);	/* Get copy of pptok */
	p->pt_nxt = 0;		/* Ensure it doesn't point to anything */
	tlpadd(ol, p);		/* Add onto list */
	}
    return ol;
}

/* TLSTRIZE(tl) - Make a single string-literal token from a tokenlist.
**	If the list was empty, a null-string literal is returned.
*/
static
tlist_t
tlstrize(tl)
tlist_t tl;
{
    char *cp = ppcbeg();	/* Start deposit into token char pool */

    ppcput('"');
    for (; tlpcur(tl); tlpskip(tl))
	switch (tlpcur(tl)->pt_typ)
	{
	case T_SCONST:	/* These have to be handled specially */
	case T_CCONST:
	    ppcqstr(tlpcur(tl)->pt_val.cp);
	    break;
	default:
	    ppctstr(tlpcur(tl));
	}
    ppcput('"');
    (void) ppcend();
    return tlmake(T_SCONST, cp);
}

/* PPTFPUT(p, fp) - Output token's string.
*/
static
int
pptfput(p, fp)
register PPTOK *p;
FILE *fp;
{
    register char *cp;
    if (p->pt_typ < NTOKDEFS
      && ((cp = tokstr[p->pt_typ]) != 0
	|| (cp = p->pt_val.cp) != 0))
	    return fputs(cp, fp);
    tkerr("pptfput", p->pt_typ);
    return EOF;
}

/* SLTOSTR(p, cp, i) - Copy string-literal token to string literal.
**	Observes specified limit on length of string.
**	Returns pointer to end of string (null char)
*/
static
char *
sltostr(p, cp, max)
PPTOK *p;
char *cp;
{
    register char *frm = p->pt_val.cp;
    register int c;

    while (--max > 0)
	{
	switch (c = *++frm)
	    {
	    case '\\':
		*cp++ = *++frm;	/* For now, don't hack escapes, just quote */
		continue;
	    case '\"':
		if (*++frm)
	    case 0:
		int_error("slttostr: bad lit");
	    break;
	    default:
		*cp++ = c;
		continue;
	    }
	break;
	}
    *cp = 0;
    return cp;
}

/* Routines for PPC stuff (PP char pool) */

/* PPCQSTR(cp) - Deposit string in token char pool, quoting anything
**	that would cause trouble for a string literal (" and \)
*/
static
void
ppcqstr(cp)
register char *cp;
{
    --cp;
    for (;;)
	{
	switch (*++cp)
	    {
	    case 0:
		return;
	    case '"':
	    case '\\':
		ppcput('\\'); break;
	    default:
		;	/* do nothing */
	    }
	(void)ppcput(*cp);
	}
}

/* PPCTSTR(p) - Deposit token's string in token char pool, except for
**	terminating null.
*/
static
void
ppctstr(p)
register PPTOK *p;
{
    register char *cp;
    if (p->pt_typ < NTOKDEFS)
	{
	if ((cp = tokstr[p->pt_typ]) == NULL)
	    cp = p->pt_val.cp;
	if (!cp) tkerr("ppctstr", p->pt_typ);
	--cp;
	while (*++cp)
	    (void)ppcput(*cp);
	}
else
    tkerr("ppctstr", p->pt_typ);
}

static
void
tkerr(rtn, i)
char *rtn;
{
    int_error("%s: bad token %Q", rtn, i);
}

/* MEXPTOP() - Expand a macro invocation at top level.
**	Takes input from nextmacpp(), which can be either a tokenlist or
**	file input, and inserts resulting token list into curtl.
*/
static
int
mexptop(sym, hs)
SYMBOL *sym;
{
    tlist_t tl;

    /* Special check.  If fn-like macro, there must be a '('
    ** as next token or we mustn't expand it.  Gross hack needed here.
    */
    if (sym->Smacnpar >= 0)	/* Function-like macro? */
	{
	if (cskiplwsp() != '(')	/* Do special check for left-paren */
	    return 0;		/* Say macro not expanded */
	}
    /* OK to expand, do internal check that input source is correct */
    if (curtl.tl_head || mactl.tl_head)
	int_error("mexptop: not toplev");

    tl = mexpsym(sym, hs);	/* Expand macro! */
    if (tl.tl_head)		/* May expand to empty list... */
	{
	if (curtl.tl_head)
	    tllapp(tl, curtl);	/* Append old input list to new one! */
	curtl = tl;		/* Start reading from new input list */
	}
    return 1;
}

/* MPEEKPAR() - Returns TRUE if next token will be a T_LPAREN, but without
**	actually reading it.  This is needed because toplevel stuff
**	really wants to avoid gobbling a token, if possible.
*/
static
int
mpeekpar()
{
    PPTOK *p;
    if ((p = mactl.tl_head) != NULL)
	{
	if (p->pt_typ == T_LPAREN) return 1;
	if (p->pt_typ != T_WSP) return 0;
	if ((p = p->pt_nxt) != NULL)
	    return (p->pt_typ == T_LPAREN);
	}
    /* No tokenlist input, or is going to run out.  Check raw input chars. */
    return (cskiplwsp() == '(');
}

/* MEXPSYM(sym, hs) - Expand macro identified by symbol ptr.
**	Input is taken from nextmacpp(), and next token will be the
**	first token after the macro identifier that invoked us.
**	Reads arguments and completely expands the macro, returning
**	a tokenlist of the results.
*/
static
tlist_t
mexpsym(sym, hs)
SYMBOL *sym;
{
    tlist_t tl, tl2;
    struct macframe mf;		/* Current macro frame */
    int i, parens, symv;
    char *cp1, *cp2;

#if DEBUG_PP
    if (debpp)
	fprintf(fpp,"mexpsym(%#lo->\"%s\", %d)\n", (INT) sym, sym->Sname, hs);
#endif

    tlzinit(tl);
    if (sym->Sflags & SF_MACEXP)	/* This sym already being expanded? */
	{
	if (clevel < CLEV_ANSI)		/* Give error unless ANSI. */
	    error("Recursive macro expansion: %s", sym->Sname);
	return tl;			/* Then just ignore it. */
	}

    /* Check for exceeding macro nesting depth.  If exceeded, we complain
    ** and ignore this expansion attempt, flushing the macro args if any.
    */
    if (maclevel >= MAXMACNEST-1)
	{
	error("Macro nesting depth exceeded: %s", sym->Sname);
	(void) margs((tlist_t *)NULL, -1, sym->Smacnpar); /* Flush args */
	return tl;			/* Empty list */
	}

    if ((mf.mf_nargs = sym->Smacnpar) < 0)	/* Special macro type? */
	/* Special macro!  Handle it... */
	{
	switch (mf.mf_nargs)
	    {

#if __MSDOS__ /* KAR-8/92, predefine __KCCDOS__ for KCCDOS only */
	    case MACF_DOS:
#endif
	    case MACF_ATOM:	/* Simple case, normal macro without arglist */
	    case MACF_STDC:
		mf.mf_body = sym->Smacptr;
		break;		/* Get out and return substituted stuff */

	    case MACF_KCC:	/* __COMPILER_KCC__ */
		    {
		    return tlmake(T_SCONST, ver_str);
		    }
	    case MACF_LINE:	/* __LINE__ */
		return tlimake(fline, 10);	/* Return a T_ICONST list */

	    case MACF_FILE:	/* __FILE__ */
		    {
		    char *cp = ppcbeg();
		    ppcput('"'); ppcqstr(inpfname); ppcput('"');
		    (void) ppcend();
		    return tlmake(T_SCONST, cp);
		    }
	    case MACF_DATE:	/* __DATE__ */
		if (!tadset) dotad();
		return tlmake(T_SCONST, datestr);

	    case MACF_TIME:	/* __TIME__ */
		if (!tadset) dotad();
		return tlmake(T_SCONST, timestr);

	    case MACF_DEFD:	/* "defined" operator */
		/* We allow either "defined NAME" or "defined(NAME)" as per
		** H&S 3.5.5.  Note that input had better not be expanded.
		*/
		while (nextmacpp() == T_WSP) ;	/* Flush any whitespace */
		parens = 0;
		if (rawpp == T_LPAREN)
		    {
		    parens++;
		    while (nextmacpp() == T_WSP);
		    }
		if (rawpp != T_IDENT)
		    {
		    error("Bad arg to \"defined\" operator");
		    pushmp();			/* Re-read bad token */
		    return tl;			/* Empty list */
		    }
		tl = tlmake(T_ICONST,		/* Expand into true or false */
			(findmacsym(rawval.cp) ? "1" : "0"));
		if (parens)
		    {
		    while (nextmacpp() == T_WSP) ;	/* Skip more blanks */
		    if (rawpp != T_RPAREN)
			{
			error("Missing ')' for \"defined\" operator");
			pushmp();		/* Re-read bad token */
			}
		    }
		return tl;

	    case MACF_SYMF:
		if (1) symv = 0;	/* Set flag 0 for existence, */
else
    case MACF_SYMV:
	symv = 1;		/* 1 for value */
		/* Do common code for _KCCsymfnd and _KCCsymval */
	    i = margs(&mf.mf_argtl[0], 2, 2);	/* Parse args */
	    if (i == 2)
	    {
	    tl = tlwspdel(mexplim(mf.mf_argtl[0], hs), 1);
	    tl2 = tlwspdel(mexplim(mf.mf_argtl[1], hs), 1);
	    }
	if (i != 2
	  || tlpcur(tl)->pt_typ != T_SCONST
	  || tlpcur(tl)->pt_nxt
	  || tlpcur(tl2)->pt_typ != T_SCONST
	  || tlpcur(tl2)->pt_nxt)
	    {
	    error("Args to \"_KCCsym%s\" must be two string literals",
		symv? "val" : "fnd");
	    return tlmake(T_ICONST, "0");	/* Return 0 if err */
	    }
    else
	{
	char sbuf[200];		/* Arbitrary big size */
	cp1 = (char *) sbuf;	/* Must copy string lits */
	cp2 = sltostr(tlpcur(tl), sbuf, (int)sizeof(sbuf))+1;
	(void) sltostr(tlpcur(tl2), cp2, (int)sizeof(sbuf)-(cp2 -sbuf));
	return tlimake(symval(cp1, cp2, symv), 0);
	}

    default:
	int_error("mexpsym: bad mac val: %d", mf.mf_nargs);
	return tl;
	}
	}
    else			/* Macro expects an arg list */
	/* Get ptr to macro body, skipping over idiotic params at front */
	{
	mf.mf_body = sym->Smacptr + sym->Smacparlen;
	i = margs(&mf.mf_argtl[0], mf.mf_nargs, mf.mf_nargs); /* Parse args */

	/* Complain if not exactly the right number of args.  Sigh. */
	if (mf.mf_nargs != i)
	    {
	    error("Wrong number of macro args - %d expected, %d seen",
			mf.mf_nargs, i);
	    if (i < 0) i = 0;		/* Handle case of MACF_ATOM */
	    while (i < mf.mf_nargs)	/* Ensure arg table filled out */
		mf.mf_argtl[i++] = tl;	/* so no refs to wild ptrs */
	    }
	}

    /* Have body and args, now turn it into a tokenlist */
    maclevel++;			/* Is this really necessary? */
    mf.mf_sym = sym;
    tl = msubst(&mf, hs);	/* Do it and return! */
    tl = mexpand(tl, hspush(sym, hs));
#if 0
    tl = tlhide(tl, hspush(sym, hs));	/* Apply hideset to all tokens */
#endif
    --maclevel;
    return tl;
}

/* MARGS() - Gobble arguments for macro, from nextmacpp().
**	On entry, opening left-paren hasn't yet been read.
**	Returns # of args seen: -1 if no open paren, 0 if no args,
**	else # of args (some may be empty).
**	On return, current token will be either T_RPAREN, T_EOF, or
**	(for an atomic macro) whatever was seen instead of an open-paren,
**	pushed on the mactl stack.
*/
static
int
margs(tabptr, maxsto, nexpected)
tlist_t *tabptr;		/* Pointer to array of tlists */
int maxsto;			/* Max # of args to store in array */
int nexpected;			/* # of args we expect to parse */
{
    int nargs;			/* # args parsed */
    int plev;
    tlist_t tl;			/* Token list for current arg */
    PPTOK *prevlast;		/* Remember ptr to previous last */

    /* Allow whitespace (plus EOL) between name and arglist. */
    while (nextmacpp() == T_WSP || rawpp == T_EOL);
    if (rawpp != T_LPAREN)		/* Any args there? */
	{
	pushmp();			/* No, must put token back */
	return -1;			/* and say no arglist at all. */
	}

    /* Now loop, parsing args */
    nargs = 0;
    plev = 1;			/* Start with 1 left-paren already seen */
    tlzinit(tl);		/* Init current tokenlist to nothing */
    prevlast = NULL;
    while (plev > 0)
	{
	switch (nextmacpp())
	    {
	    case T_EOL:
		/* Handle newline in arg list scan.
		** Standard def of C seems to prohibit these, but we allow
		** them by changing into a space.  The new ANSI draft
		** specifically permits this.
		** However, in order to
		** prevent a missing ')' from allowing the arg scan to
		** gobble all the rest of the file, we check to see whether
		** we have already found all of our args.
		*/
		if (nargs >= nexpected) /* If see \n when too many args */
		    {
		    error("Missing ')' in macro arg list");
		    return nargs;	/* Stop now with what we got */
		    }
		/* Drop thru to handle like normal whitespace */

	    case T_WSP:
		if (tl.tl_head == NULL		/* Ignore wsp if at start */
		  || (tl.tl_tail->pt_typ == T_WSP))	/* or last was wsp */
		    continue;			/* Ignore, get next token */
		break;				/* OK to deposit it */

	    case T_EOF:		/* Uh-oh... stopped unexpectedly */
		if (eof)	/* Check for running off end of world */
		    error("Unexpected EOF during macro arg scan");
else
    error("Macro arg scan truncated");
    plev = 0;	/* Must stop loop! */
		/* Drop thru to store arg before quitting */

    case T_RPAREN:	/* Close paren counted for balancing */
	if (--plev > 0)
	    break;	/* Just add to arg */
	if (nargs == 0 && tl.tl_head == NULL)	/* No args at all? */
	    return 0;	/* None read... */
		/* Arg (and all args) done, drop thru to store current arg */

    case T_COMMA:	/* Comma stops arg unless paren-protected */
	if (plev > 1)	/* If still within an arg, */
	    break;	/* just gobble the comma too */

		/* Store argument thus far! */
		/* Only set arg if we know there's room in table. */
	if (++nargs <= maxsto)
	    {
	    if (prevlast && tl.tl_tail->pt_typ == T_WSP)
		{
			/* Truncate whitespace off arglist */
		(tl.tl_tail = prevlast)->pt_nxt = 0;
		}
	    *tabptr++ = tl;	/* Store the arg list! */
	    }
	if (plev <= 0) continue;	/* Check for done */
	tlzinit(tl);		/* Start a new arg */
	continue;		/* Get next token */

    case T_LPAREN:	/* Open paren counted for balancing */
	plev++;		/* and always included in arg */
    default:
	break;		/* All other tokens included in arg */
	}	/* End of switch */

	/* Add token to current arg, get next, and continue loop */
    prevlast = tl.tl_tail;	/* Remember previous last-ptr */
    tltadd(tl, tokize());	/* Always copy token for now */
	}		/* End of loop */

    return nargs;		/* Return # of args parsed into array */
}

/* MEXPLIM(tl, hs) - Expand a tokenlist, using no additional tokens.
**	This is used to expand macro arguments and directive lines.
**	Note that the furnished list is munged; thus, a copy must be
**	given by the caller if the original must be preserved.
*/
static
tlist_t
mexplim(il, hs)
tlist_t il;		/* Input list */
{
    static PPTOK eoftok =
	{
	T_EOF
	};
	tlist_t ml;

	tltadd(il, eoftok);		/* Add EOF to end of input list */
	ml = mexpand(il, hs);	/* Read from it, expand macros, get new list */
	if (mactl.tl_tail != il.tl_tail)		/* Input must still be there */
	    int_error("mexplim: lost input list");
	if (mactl.tl_head != il.tl_tail)
	    int_error("mexplim: input not all read");
	mtlpop();			/* Done, pop EOF-terminated list off input */
	return ml;
}

/* MEXPAND(tl,hs) - Expand a tokenlist, using additional input from
**	nextmacpp() if any is needed to read the arguments of a function-like
**	macro.
**	This is used to rescan macro bodies.
**	On return, all identifiers will have been marked to indicate whether
**	they are macros, hidden macros, or known not to be macros.
**	Note that the furnished list is munged; thus, a copy must be
**	given by the caller if the original must be preserved.
*/
static
tlist_t
mexpand(il, hs)
tlist_t il;
{
    SYMBOL *sym;
    int ourlev;
    tlist_t ml, ol;

#if DEBUG_PP
    if (debpp) pmactl("enter mexpand", il, hs);
#endif
    if (!il.tl_head)		/* If list is empty, */
	return il;		/* just return empty list */
    mtlpush(il);		/* Push into input for nextmacpp */
    tlzinit(ol);		/* Start with empty output list */
    ourlev = mactlev;		/* Remember level we're reading at */
    while (ourlev <= mactlev)
	{
	switch (nextmacpp())
	    {
	    case T_EOF:		/* Premature EOF can happen from mexplim */
		if (ol.tl_tail)		/* Ensure list is terminated */
		    ol.tl_tail->pt_nxt = 0;
#if DEBUG_PP
		if (debpp) pmactl("leave mexpand", ol, hs);
#endif
		return ol;

	    case T_MACRO:		/* Macro, always expand */
		sym = rawptr->pt_val.sym;
		break;
	    case T_IDENT:		/* Ident, must check it. */
		switch (rawptr->pt_is)
		    {
		    case IS_MHID:			/* Hidden macro */
		    case IS_MNOT:			/* Not a macro */
			tlpadd(ol, rawptr);
			continue;
		    case IS_MEXP:			/* Expandable macro */
			if ((sym = findmacsym(rawval.cp)) == NULL)
			    {
			    int_error("mexpand: Cannot lookup IS_MEXP \"%s\"",
					    rawval.cp);
			    rawptr->pt_is = IS_MNOT;
			    tlpadd(ol, rawptr);
			    continue;
			    }
			break;			/* Break out to expand! */

		    case IS_UNK:			/* Unknown identifier */
			if ((sym = findmacsym(rawval.cp)) == NULL)
			    {
			    rawptr->pt_is = IS_MNOT;	/* Found not a macro */
			    tlpadd(ol, rawptr);
			    continue;
			    }
			if (mishid(sym, hs))
			    {
			    rawptr->pt_is = IS_MHID;	/* Macro, now hidden */
			    tlpadd(ol, rawptr);
			    continue;
			    }
			break;			/* Break out to expand! */

		    default:
			int_error("mexpand: Bad IS_ type (%d)", rawptr->pt_is);
		    }
	    /* Come here when ident is expandable macro */
		if (sym->Smacnpar < 0	/* If object-like macro, */
		  || mpeekpar())		/* or fn-like and followed by ( */
		    break;			/* then OK to expand macro! */
	    /* Fall through if cannot expand macro */
#if 0	/* This is wrong.  Perhaps save sym by making T_MACRO?? */
		rawptr->pt_is = IS_MHID;	/* Hide it forever */
#endif
	    default:
		tlpadd(ol, rawptr);
		continue;		/* Just leave on token list */
	    }

	/* Handle expansion of the macro pointed to by "sym".
	** This is a simple-minded method that has the virtue of working.
	*/
	ml = mexpsym(sym, hs);		/* Expand into ml */
	if (ml.tl_head)			/* If anything was returned, */
	    mtlpush(ml);		/* put it on input for rescan! */
	continue;
#if 0
	/* Handle expansion of the macro pointed to by "sym".
	** Most of the following hair is because we're trying to be
	** efficient and mung the input tokenlist in place.
	*/
	if (p == tl.tl_tail)	/* Cons up input tokenlist (IL) to rest */
	    tlzinit(il);
else
    {
    il.tl_head = tokn2p(p->pt_nxt);
    il.tl_tail = tl.tl_tail;
    }
tltadd(il, eoftok);	/* Add EOF to end of it */
mtlpush(il);		/* Push list on input stack */
ml = mexpsym(sym, hs);	/* Read from it, expand macro, get new list */
if (mactl.tl_tail != il.tl_tail		/* Input must still be there */
  || !(il.tl_head = mactl.tl_head))	/* Update IL for amt read */
    int_error("mexpand: lost input list");
mtlpop();				/* Done, pop list off input */

	/* Clean up IL to contain remaining input */
if (il.tl_head->pt_typ == T_EOF)	/* If all gone, */
    tlzinit(il);			/* just clear it */
else
    (il.tl_tail = tl.tl_tail)->pt_nxt = 0;	/* remove the EOF */

	/* Set pointer such that loop will get correct next token from it */
    p = lastp;		/* This will be NULL if no prev token */

	/* Now add expanded list (ML) to scan list (TL) */
    if (tl.tl_tail = p)	/* Truncate current scan list */
	{
	if (ml.tl_head)	/* Add expanded list to current list */
	    tllapp(tl, ml);
	}
else
    tl = ml;		/* TL truncated to empty, just use ML now */

	/* Now add remaining input (IL) back onto scan (TL) */
    if (tl.tl_tail)	/* If there's something on TL */
	{
	if (il.tl_head)	/* Add whatever's left on IL */
	    tllapp(tl, il);
	}
else
    tl = il;		/* Nothing on TL, just use IL */

	/* If we replaced the first token, there wasn't any prev token, so
	** loop can't continue by getting next token from P; have to start
	** over by calling ourselves on the new TL.
	*/
    if (!p) return mexptl(tl, hs);
#endif
    }
if (ol.tl_tail)		/* Ensure list is terminated */
    ol.tl_tail->pt_nxt = 0;
#if DEBUG_PP
if (debpp) pmactl("leave mexpand", ol, hs);
#endif
return ol;
}

/* MSUBST() - Substitute and expand args to a macro.
**	Given a macro frame with body string and array of arglists,
**	returns a tokenlist of the completely substituted result.
*/
static
tlist_t
msubst(struct macframe
*mf, int hs)
{
    tlist_t tl, argl;
    register int typ, i;
    register char *cp;
    static PPTOK zpptok;
    PPTOK pptok;
    int ncats = 0;		/* # of concat ops seen */

#if DEBUG_PP
    if (debpp) pmacframe("msubst", mf);
#endif
    tlzinit(tl);		/* Init list to zero */
    if ((cp = mf->mf_body) != '\0') while ((typ = *cp++) != 0)
	{
	if (typ < 0 || typ >= NTOKDEFS)
	    int_error("msubst: illegal token %d in body of macro %S",
				typ, mf->mf_sym);
	switch (typ)
	    {
	    case T_MACSTR:		/* Stringize argument */
	    case T_MACINS:		/* Insert argument */
	    case T_MACARG:		/* Expand argument */
		if ((i = *cp++ - MAC_ARGOFF) < 0 || i >= mf->mf_nargs)
		    int_error("msubst: illegal param %d in body of macro %S",
				    i, mf->mf_sym);
		argl = mf->mf_argtl[i];
		switch (typ)	/* Handle arg as directed */
		    {
		    case T_MACSTR:
			argl = tlstrize(argl);   break;	/* Stringize */
		    case T_MACINS:
			argl = tlcopy(argl);     break;	/* Insert */
		    case T_MACARG:
			argl = mexplim(tlcopy(argl), hs); break;	/* Expand */
		    default:
			int_error ("msubst: invalid macro %d type", typ);
		    }
		if (argl.tl_head)	/* Add resulting arglist to accumulation */
		    {
		    if (tl.tl_head) tllapp(tl, argl);
else
    tl = argl;
    }
continue;

    case T_MACCAT:		/* Op: concatenate tokens */
	++ncats;		/* Remember how many seen */
	pptok = zpptok;
	pptok.pt_typ = typ;
	tltadd(tl, pptok);	/* Add to end of list */
	continue;
    default:
	;	/* do nothing */
	}

	/* Default for plain old body tokens - transform into pptok */
    pptok = zpptok;		/* Init pptok to zero */
    pptok.pt_typ = typ;	/* Stick in token # */
    if (!tokstr[typ])	/* Token not an op or punct? */
	    /* Assume rest of string is token */
	{
	pptok.pt_val.cp = cp;	/* Remember ptr to start */
	if (*cp) while (*++cp);	/* Skip over */
	++cp;
	}
    tltadd(tl, pptok);	/* Add to end of list */
	}				/* Stop loop when hit end of macro body */

    /* All tokens present in list, now do concatenation if needed */
    if (ncats)
	mpaste(tl, hs, ncats);
    return tl;
}

static
void
mpaste(tl, hs, ncats)
tlist_t tl;
{
    register PPTOK *p, *lastp = NULL;
    tlist_t nl;
    char *cp;

#if DEBUG_PP
    if (debpp) fprintf(fpp, "%smpaste: %d cats in:", plevindent(), ncats),
		tlfput(tl,fpp,0), putc('\n',fpp);
#endif
    for (p = tlpcur(tl); p; lastp = p, p = tokn2p(p->pt_nxt))
	{
	if (p->pt_typ != T_MACCAT)
	    continue;
	/* Start concatenation!  Concatenate previous token with next one */
	cp = ppcbeg();
	ppctstr(lastp);			/* Dump prev token */
	do
	    {
	    p = tokn2p(p->pt_nxt);
	    if (!lastp || !p)
		int_error("mpaste: ## arg missing");
	    ppctstr(p);			/* Dump next token */
	    p = tokn2p(p->pt_nxt);
	    }
	while(--ncats > 0 && p && p->pt_typ == T_MACCAT);
	(void) ppcend();		/* Token done... */

	/* Everything concatenated for this stretch, now retokenize it.
	** If it cannot be parsed, it is left as T_UNKWN for possible later
	** salvage by higher levels, or at worst an error message.
	*/
	lastp->pt_val.cp = cp;		/* Default is to make it "unknown" */
	lastp->pt_typ = T_UNKNWN;
	nl = tlfrstr(cp);		/* Retokenize! */
#if DEBUG_PP
	if (debpp) fprintf(fpp, "%sRetokenized ", plevindent()),
		tkfput(lastp, fpp, 1),
		fputs(" into", fpp),
		tlfput(nl,fpp,1),
		putc('\n',fpp);
#endif
	if (nl.tl_head == nl.tl_tail)	/* Got exactly one token? */
	    {
	    *lastp = *(nl.tl_head);	/* Yes, hurray!  Shove it onto list */
	    lastp->pt_nxt = tokp2n(p);	/* Skip over junked tokens */
	    /* Now decide how to (or whether to) "hide" this, if T_IDENT */
	    tkhide(lastp, hs);
	    }

	if (ncats <= 0)
	    break;			/* If no more, leave loop! */
	}
}

/* Macro Hideset hackery.
*/

/* HSPUSH(sym,hs) - Add symbol to given hideset, return new hideset
*/
static
int
hspush(sym, hs)
SYMBOL *sym;
{
    if (hs >= MAXMACNEST)
	{
	int_error("Macros nested too deep (%d)", hs);
	}
else
    hidemacs[hs] = sym;		/* Remember ptr to macro sym */
return hs+1;
}

/* TKHIDE(p, hs) - hide given token, if applicable
**	Returns resulting IS_ code.
*/
static
int
tkhide(p, hs)
PPTOK *p;
{
    SYMBOL *sym;

    if (p->pt_typ == T_IDENT)
	{
	if ((sym = findmacsym(p->pt_val.cp)) == 0)
	    return p->pt_is = IS_MNOT;	/* Known not to be macro */
else if (mishid(sym, hs))
    return p->pt_is = IS_MHID;	/* Is hidden macro */
return p->pt_is = IS_MEXP;	/* Is expandable macro */
    }
return IS_UNK;
}

/* MISHID(sym, hs) - Return TRUE if macro symbol is being hidden
**	by given hideset.
*/
static
int
mishid(sym, hs)
SYMBOL *sym;
{
    if (hs < 0 || hs > MAXMACNEST)
	return int_error("mishid: bad arg"), 1;
    while (--hs >= 0)
	if (hidemacs[hs] == sym)
	{
#if DEBUG_PP
	if (debpp) fprintf(fpp, "%smishid() suppressed %s!\n",
		    plevindent(), sym->Sname);
#endif
	return 1;
	}
    return 0;
}

/* Debugging Support */

#if DEBUG_PP

static
char *
plevindent()
{
    static char sps[] = "                                                    ";
    return (maclevel <= 0 ? "" :
		(maclevel*4 > sizeof(sps) ? sps
			: &sps[sizeof(sps) - maclevel*4]));
}

/* PMACFRAME - print macro frame
*/
static
void
pmacframe(str, mf)
char *str;
struct macframe
*mf;
{
    int i;
    char *cp;
    SYMBOL *s = mf->mf_sym;
    char *ind = plevindent();	/* Get indentation to use */

    fprintf(fpp,
"%s%s: Macframe for %#lo->\"%s\" (npar: %d, parlen: %d, len: %d)\n",
		ind, str, (INT) s, s->Sname, s->Smacnpar, s->Smacparlen,
		i = s->Smaclen);
    fprintf(fpp,"%sBody: %#lo-> \"", ind, (INT) cp = s->Smacptr);
    if (cp) while (--i >= 0)
	if (iscntrl(*cp)) fprintf(fpp, "^%c", (*cp++)^0100);
else
    putc(*cp++, fpp);
fprintf(fpp, "\"\n");

for (i = 0; i < mf->mf_nargs; ++i)
	{
	fprintf(fpp, "%sArg %d:", ind, i);
	tlfput(mf->mf_argtl[i], fpp, 0);
	putc('\n', fpp);
	}
}

static
void
pmactl(s, tl, hs)
char *s;
tlist_t tl;
{
    char *ind = plevindent();

    fprintf(fpp, "%sTokenlist %s (hs %d): ", ind, s, hs);
    tlfput(tl, fpp, 1);
    putc('\n', fpp);
}

static
void
tlfput(tl, fp, vrbflg)	/* vrbflg TRUE for more verbose output */
tlist_t tl;
FILE *fp;
{
    register PPTOK *p;
    for (p = tlpcur(tl); p; p = p->pt_nxt)
	tkfput(p, fp, vrbflg);
}

static
char *
tkids(p)
PPTOK *p;
{
    static char buf[30];
    switch (p->pt_is)
	{
	case IS_UNK:
	    return "IS_UNK";
	case IS_MNOT:
	    return "IS_MNOT";
	case IS_MHID:
	    return "IS_MHID";
	case IS_MEXP:
	    return "IS_MEXP";
	}
    sprintf(buf, "T_IDENT+%#o", p->pt_is);
    return buf;
}

static
void
tkfput(p, fp, vrbflg)	/* vrbflg TRUE for more verbose output */
PPTOK *p;
FILE *fp;
{
    register int i;
    char *cp;

    if ((i = p->pt_typ) < 0 || i >= NTOKDEFS
      || ( !(cp = tokstr[i])
	&& !(cp = p->pt_val.cp)))
	fprintf(fp, " <%d=%s %#o>", i,
		((i < NTOKDEFS) ? nopname[i] : ""),
		p->pt_val.i);
else
    {
    if (vrbflg)
	{
	fprintf(fp," <%s %s>",(i == T_IDENT ? tkids(p) : nopname[i]), cp);
	}
else
    fprintf(fp, " <%s>", cp);
    }
}
#endif

/* DIRECTIVE() - Process a PP directive.
**	Current raw token is T_SHARP.
**	On return, raw token is either T_EOL or T_EOF, and the
**	current char is either T_EOF or the first char of the next line.
**	Callers must check again for a possible directive on the next line!
**	Note that all token storage is flushed before and after processing.
*/
/* Return values for PP handling rtns */
#define PPR_UNKNOWN	-1	/* Unknown directive */
#define PPR_FLUSH	0	/* Flush everything up to EOL */
#define PPR_ATEOL	1	/* We're at EOL, just return */
#define PPR_CHECKEOL	2	/* Verify that no non-whitespace exists
				** between here and EOL.
				*/
static
void
directive (void)
{
    int res = PPR_UNKNOWN;
    int len;

    /* Use ident length as quick hash into switch table.
    ** Result value is a PPR_ type.
    */
    indirp++;			/* Set this for scanhwsp checking */
    ppcreset();			/* Reset tokenizer storage */
    pptreset();
    len = (tskipwsp() == T_IDENT ? strlen(rawval.cp) : 0);
#if DEBUG_PP
    if (debpp && len) fprintf(fpp, "#-DIRECTIVE seen: \"%s\"\n", rawval.cp);
#endif
    switch (len)
	{

	/* Note that if we are in the middle of a failing conditional
	** (flushing stuff) then only #else, #endif, and #if-type commands
	** are acted upon.  All others are ignored, including
	** unknown #-type "commands", altho the latter generate a warning.
	*/
	case 0:
	    if (rawpp != T_EOL && !flushing)	/* If not EOL or identifier, */
		error("Preprocessor directive expected");	/* barf. */
	    res = PPR_FLUSH;			/* Nothing else to do */
	    break;

	case 2:
	    if (!strcmp(rawval.cp,"if")) res = d_if();
	    break;
	case 3:
	    if (!strcmp(rawval.cp,"asm") && clevkcc)
		res = flushing ? PPR_FLUSH : d_asm();	/* KCC-only */
	    break;
	case 4:
	    if (!strcmp(rawval.cp,"else")) res = d_else();
else if (!strcmp(rawval.cp,"elif") && clevel >= CLEV_CARM) res = d_elif();
else if (!strcmp(rawval.cp,"line"))
    res = flushing ? PPR_FLUSH : d_line();
break;
    case 5:
	if (!strcmp(rawval.cp,"endif")) res = d_endif();
    else if (!strcmp(rawval.cp,"ifdef")) res = d_ifdef(1);
    else if (!strcmp(rawval.cp,"undef"))
	res = flushing ? PPR_FLUSH : d_undef();
    else if (!strcmp(rawval.cp,"error"))
	res = flushing ? PPR_FLUSH : d_error();
    break;
    case 6:
	if (!strcmp(rawval.cp,"ifndef")) res = d_ifdef(0);
    else if (!strcmp(rawval.cp,"define"))
	res = flushing ? PPR_FLUSH : d_define();
    else if (!strcmp(rawval.cp,"pragma"))
	res = flushing ? PPR_FLUSH : d_pragma();
    else if (!strcmp(rawval.cp,"endasm") && clevkcc)
	res = flushing ? PPR_FLUSH : d_endasm();	/* KCC-only */
    break;
    case 7:
	if (!strcmp(rawval.cp,"include"))
	    res = flushing ? PPR_FLUSH : d_include();
	break;
    default:
	int_error ("directive: invalid preprocessor length %d", len);
	}

    /* Now do common cleanup code by examining result */
    switch (res)
	{
	case PPR_CHECKEOL:
	    checkeol();
	case PPR_ATEOL:
	    break;
	case PPR_UNKNOWN:
	    if (flushing)
		warn("Unsupported preprocessor command: \"%s\"", rawval.cp);
    else
	error("Unsupported preprocessor command: \"%s\"", rawval.cp);
    case PPR_FLUSH:
	indirp = 0;		/* Avoid whitespace err msgs */
	flushtoeol();
	break;
    default:
	if (res != 3)
	    int_error ("directive: invalid res %d", res);
	}
    indirp = 0;			/* No longer scanning directive */
    ppcreset();			/* Reset tokenizer storage */
    pptreset();
}

/* D_DEFINE() - Process #define directive
*/
static
int
d_define (void)
    {
    char *name;		/* Name of new macro (in ppcpool) */
    SYMBOL *sym;
    struct macframe m;
    int i;


    if (tskipwsp() != T_IDENT)	/* Get first non-whitespace token */
	{
	error("Macro name expected");	/* complain if nothing. */
	return PPR_FLUSH;
	}

    name = rawval.cp;		/* Remember ptr to ident */

    if (*name == '`')
	{
	error("Macro name cannot be quoted identifier");
	return PPR_FLUSH;
	}

    /* Look for formal-parameter names */

    m.mf_nargs = -1;		/* Initially assume no formals */
    m.mf_parlen = 0;		/* Keep track of # chars that formals need */

    /*
     * Here's a tricky situation.  If the source line is
     *
     *  "#define TOKEN [comment]"
     *
     * then the comment is going to be detected and abolished by this
     * call to nextrawpp ().  nextrawpp () does not know who called it;
     * left to its own devices, it will return the first token of the
     * next non-empty line.
     */

    if (nextrawpp () == T_LPAREN)
	{
	/*
	 * We have formal parameters.
	 */

	m.mf_nargs = 0;

	while (1)
	    {
	    if (tskipwsp () == T_RPAREN)/* Skip over whitespace */
		break;			/* Macro params done! */

	    if (rawpp != T_IDENT)	/* Better be a param ident */
		{
		error("Macro formal parameter must be identifier");
		m.mf_parlen = m.mf_nargs = 0;
					/* Ugh!  Don't try to hack args on */
					/* expansion, but remember that */
		break;			/* this is a fncall macro. */
		}

	    if (m.mf_nargs >= MAXMARG - 1)
		error("More than %d args in macro definition of \"%s\"",
			MAXMARG, name);
	    else
		{
		/* Have arg, store it.  Must also check for
		** duplicate arg names (yes it happens)
		*/

		m.mf_parcp[m.mf_nargs] = rawval.cp;	/* Remember arg name */

		for (i = 0; i < m.mf_nargs; i++)	/* Scan to find dups */
		    {
		    if (!strcmp(m.mf_parcp[i], rawval.cp))
			{
			error ("Duplicate formal parameter in macro def; \"%s\"",
			       rawval.cp);
			break;
			}
		    }

		m.mf_nargs++;
		m.mf_parlen += strlen(rawval.cp)+1;	/* Sigh */
		}

	    if (tskipwsp() != T_COMMA)	/* If comma next, continue loop */
		break;			/* else stop now */
	    }

	if (rawpp != T_RPAREN)
	    error("Close paren needed to end formal parameter list");
	else
	    nextrawpp();		/* Set up next token */
	}

    /*
     * At this point, we may have parsed a list of formals, or there
     * may simply have been no formals at all.
     */
    
    /* Arguments read, now read rest of line into a tokenlist. */

    if (!mdefinp(&m))			/* If failed somehow, */
	return PPR_FLUSH;		/* just flush rest of line */

    /* OK, now can check for macro already being defined */
*defcsname = 'd';			/* Allow "defined" to be found */
sym = findmacsym(name);
*defcsname = SPC_MACDEF;		/* Hide "defined" again */
if (sym != NULL)			/* If already defined, compare it */
    {
    if (m.mf_nargs == sym->Smacnpar	/* Args and body must match */
      && m.mf_len == (int) (sym->Smaclen) // FW KCC-NT
      && (m.mf_len == 0		/* OK if both bodies null */
	|| memcmp(m.mf_body, sym->Smacptr, m.mf_len) == 0))
	{
	if (m.mf_body) free(m.mf_body);	/* Identical, we win! */
	return PPR_CHECKEOL;
	}

    if (sym->Smacnpar < MACF_ATOM)	/* Check for specialness */
	{
	error("Illegal to redefine \"%s\"", name);
	if (m.mf_body) free(m.mf_body);
	return PPR_FLUSH;
	}
	/* Later just output "note" if macro is functionally identical,
	** i.e. differs only in parameter names (gark bletch)
	*/
    error("Macro redefined: \"%s\"", name);
    freemacsym(sym);		/* Flush old sym completely */
    }

    /* Now define the new macro! */
mdefsym(name, &m);
return PPR_CHECKEOL;
}

/* MDEFINP - auxiliary that defines a macro body given input from nextrawpp().
**	Returns true if everything went OK, else wants rest of input flushed.
*/
static
int
mdefinp(struct macframe
*mf)
{
    register int i;
    register char *cp;
    static PPTOK sptok =
	{
	T_WSP
	};
	tlist_t tl;
	int wspf;

    /* Current token is the first token of the macro body. */
	tlzinit(tl);		/* Init tokenlist */
	wspf = 0;			/* No whitespace seen yet */
	mf->mf_len = 0;		/* Keep track of # chars that body needs */
	while (rawpp == T_WSP)	/* Skip initial whitespace first... */
	    nextrawpp();
	while (wspf >= 0)
	{
	switch (rawpp)
	    {
	    case T_EOF:
	    case T_EOL:
	    /* Done, ignore any trailing whitespace */
		if (tl.tl_tail && tl.tl_tail->pt_typ == T_MACCAT)
		    {
		    error("## operator cannot end macro body");
		    tl.tl_tail->pt_typ = T_SHARP2;	/* Too hard to flush, so sub */
		    }
		wspf = -1;		/* Leave loop now */
		continue;

	    case T_WSP:		/* Whitespace just sets flag */
		wspf = 1;
		if (tl.tl_tail && tl.tl_tail->pt_typ == T_MACCAT)
		    wspf = 0;	/* Ignore wsp if ## was previous token */
		nextrawpp();
		continue;

	    case T_IDENT:
		for (i = 0; i < mf->mf_nargs; i++)	/* Scan to see if a param */
		    if (!strcmp(mf->mf_parcp[i], rawval.cp))
			break;
		if (i < mf->mf_nargs)	/* If found param, fix token */
		    {
		    if (tl.tl_tail && tl.tl_tail->pt_typ == T_MACCAT)
			rawpp = T_MACINS;	/* Say token is inserted param */
else
    {
    if (wspf) tltadd(tl, sptok), mf->mf_len++;
    rawpp = T_MACARG;	/* Say token is expanded param */
    }
rawval.i = i;		/* with this param # */
mf->mf_len += 2;
break;
    }
	    /* Drop thru to add normal ident token to list */

    default:
	if (wspf) tltadd(tl, sptok), mf->mf_len++;
	mf->mf_len++;	/* Bump for token type */
	if (rawval.cp)	/* Len is 1 extra for NUL terminator */
	    mf->mf_len += strlen(rawval.cp) + 1;
	break;		/* Add token */

    case T_SHARP:
	if (tskipwsp() != T_IDENT)
	    {
	    error("Formal parameter must follow the # operator");
	    continue;	/* Sigh, skip the # */
	    }
	for (i = 0; i < mf->mf_nargs; i++)	/* Scan to see if param */
	    if (!strcmp(mf->mf_parcp[i], rawval.cp))
		break;
	if (i >= mf->mf_nargs)	 /* Check again */
	    {
	    error("Formal parameter must follow the # operator");
	    continue;		/* Sigh, skip the # */
	    }
	if (wspf) tltadd(tl, sptok), mf->mf_len++;
	rawpp = T_MACSTR;		/* Stringize this macro arg! */
	rawval.i = i;
	mf->mf_len += 2;
	break;			/* Add to list and continue */

    case T_SHARP2:
	    /* Ignore any preceding whitespace.
	    ** Param preceding this op becomes inserted, not expanded!
	    */
	if (!tl.tl_tail)
	    {
	    error("## operator cannot begin macro body");
	    nextrawpp();		/* Flush it and continue */
	    continue;
	    }
	if (tl.tl_tail->pt_typ == T_MACARG)		/* Preceding param? */
	    tl.tl_tail->pt_typ = T_MACINS;		/* Yes, say insert! */
	rawpp = T_MACCAT;
	mf->mf_len++;
	break;
	}
    wspf = 0;
    tlrawadd(&tl);		/* Add token to list */
    nextrawpp();		/* and get next and continue */
	}

    /* If no body, we win with nothing else to do. */
    if ((mf->mf_len += mf->mf_parlen) == 0)
	{
	mf->mf_body = NULL;
	return 1;
	}

    /* We now have the macro body tokenlist in TL.
    ** Using the computed length, we allocate dynamic storage for it and
    ** transform it into a body string.  Unfortunately this has to be done
    ** before we can check for a definition clash.
    */
    if ((cp = mf->mf_body = calloc(1, mf->mf_len+1)) == NULL)
	{
	error("Out of memory for macro body");
	mf->mf_len = mf->mf_parlen = 0;	/* Recover by saying no body */
	return 1;
	}

    /* First copy TRULY ASSININE parameter names, thanks to some
    ** anonymous moronic anal-retentive cretins on X3J11.  Really.
    */
    if (mf->mf_nargs > 0)
	{
	for (i = 0; i < mf->mf_nargs; i++)
	    {
	    cp = estrcpy(cp, mf->mf_parcp[i]);
	    *cp++ = ',';
	    }
	cp[-1] = ')';		/* Minor touch for easier debugging */
	}

    /* Copy tokenlist into a body in memory, with safety checks */
    if (((cp - mf->mf_body) != mf->mf_parlen)
      || ((tltomac(tl, cp) - mf->mf_body) != (mf->mf_len+1)))
	int_error("mdefinp: bad mac len");
    return 1;
}

/* MDEFSTR - auxiliary for use by startup code which defines macros given
**	a name, macro type, and string.
**	This cannot be used to define macros with arguments.
*/
static
SYMBOL *
mdefstr(name, mactyp, body)
char *name;			/* Macro symbol name */
int mactyp;			/* Special MACF_ type */
char *body;			/* Text of macro definition */
{
    struct macframe m;

    m.mf_nargs = mactyp;
    m.mf_parlen = 0;
    if (body)
	{
	PPTOK *savppt = pptptr;	/* Save these values so can flush stg used */
	ppcstate_t savppc;

	ppcsave(savppc);
	sinbeg(body);		/* Redirect input to come from string */
	nextmacpp();		/* Set up first token */
	mdefinp(&m);		/* Gobble body from input, fill macframe */
	sinend();		/* Stop input, check for gobbling all! */
	ppcrest(savppc);	/* Flush all strings & pptoks used */
	if (savppt == NULL)
	    pptptr = pptoks;
else
    pptptr = savppt;
    }
else
    m.mf_body = NULL, m.mf_len = 0;
    return mdefsym(name, &m);	/* Always define macro, and return! */
}

/* MDEFSYM(name, &mf) - auxiliary that actually creates the macro symbol and
**	sets its definition, given a macro frame argument already set up
**	by other calls.
*/
static
SYMBOL *
mdefsym(name, mf)
char *name;			/* Macro symbol name */
register struct macframe
*mf;
{
    register SYMBOL *s;

    s = symfind(name, 1);	/* Find or create symbol so barfs on trunc */
    if (s->Sclass != SC_UNDEF)
	{
	s->Srefs--;
	s = symgcreat(name);	/* Create global symbol */
	}
    s->Sclass = SC_MACRO;	/* Say it's a macro */
    s->Sflags |= SF_MACRO;	/* Say ditto, with flag. */
    s->Smacnpar = mf->mf_nargs;	/* Set # args or MACF_ type */
    s->Smacparlen = mf->mf_parlen;	/* # chars in param-name prefix */
    s->Smaclen = mf->mf_len;	/* # chars total in macro body */
    s->Smacptr = mf->mf_body;	/* Body string already set up! */
    return s;			/* Return pointer to defined macro */
}

/* D_UNDEF() - Process #undef directive
*/
static
int
d_undef()
{
    SYMBOL *sym;

    if (tskipwsp() != T_IDENT)
	{
	error("Macro name expected");	/* Bad identifier */
	return PPR_FLUSH;
	}

    *defcsname = 'd';			/* Allow "defined" to be found */
    sym = findmacsym(rawval.cp);
    *defcsname = SPC_MACDEF;		/* Hide "defined" again */
    if (sym != NULL)			/* If already defined, */
	{
	if (sym->Smacnpar >= MACF_ATOM)	/* Check for specialness */
	    freemacsym(sym);		/* OK to flush it, do so. */
else
    error("Illegal to undefine \"%s\"", rawval.cp);
    }
checkeol();
}

static
void
freemacsym(sym)
SYMBOL *sym;
{
    if (sym->Smacptr)		/* If it has a macro body, */
	free(sym->Smacptr);	/* free it up. */
    freesym(sym);		/* Then flush the symbol definition. */
}

/* D_ASM() - Process #asm directive
**
** All text between #asm and #endasm is turned into a series of
** asm() expressions, one per line.  Each line is macro-expanded before
** being put into the asm() string literal argument.
** Thus, the sequence
**	#asm
**		text1
**		text2
**	#endasm
** becomes:
**	asm("text1");
**	asm("text2");
** This is consequently only meaningful inside functions.
*/
static
unsigned int asmfline;

static
int
d_asm()
{
    flushtoeol();			/* ignore rest of line */
    if (inasm)			/* bump level.  if nested, complain */
	{
	error("Already in #asm, can't nest");
	return PPR_ATEOL;
	}
    inasm = 1;				/* Say processing assembler input */
    asmfline = fline;
    if (curtl.tl_head)
	int_error("d_asm: cooked top-level input");
    curtl = tlmake(T_EOL, (INT) 0);		/* Set up curtl with dummy */
    return PPR_CHECKEOL;
}

/* ASMREFILL() - refill nextpp's token list with an #asm input line.
**	Current char is 1st of line to crunch.
*/
static
tlist_t
asmrefill()
{
    tlist_t tl;
    static PPTOK lptok =
	{
	T_LPAREN
	}
    ,
		stok =
	{
	T_SCONST
	}
    ,
    rptok =
	{
	T_RPAREN
	}
    ,
    sctok =
	{
	T_SCOLON
	}
    ,
    eoltok =
	{
	T_EOL
	};
tlzinit(tl);
ppcreset();		/* Clear token char pool and token stg */
pptreset();

for (;;)
	{
	while (cskiplwsp() == '\n')	/* Flush whitespace and lines */
	    nextch();
	if (ch == '#')	/* If hit directive */
	    {
	    nextch();		/* Set up next char */
	    directive();	/* and process directive! */
	    if (!inasm)	/* If it included an #endasm, */
		{
		if (ch != '\n')
		    {
		    pushch(ch);
		    pushstr("\n");
		    }
		return tl;	/* then just return empty list. */
		}
	    continue;
	    }
	break;
	}
    tl = tlmake(T_IDENT, "asm");	/* Make start of returned tokenlist */
    tltadd(tl, lptok);		/* Add left-paren */
    stok.pt_val.cp = ppcbeg();	/* Got something real, start a literal! */
    ppcput('"');

    for (;;)
	{
	switch (ch)	/* Scan starting with current char */
	    {
	    case EOF:
		error("EOF within unterminated #asm beginning at line %d", asmfline);
		inasm = 0;		/* Stop now */
		break;			/* Still try to finish up cleanly */

	    case '\n':			/* Newline means done, return tokenlist! */
		break;

	    case '/':			/* Check for possible start of comment */
		if (nextch() == '*')
		    {
		    scancomm ();
		    nextch ();
		    ppcput (' ');
		    }
		else if (ch == '/' && !clevnocpp) /* FW 2A(46) August 1993 */
		    {			/* " */
		    comment_type = CPP;	/* " */
		    scancomm ();	/* " */
		    comment_type = C;	/* " */
		    }			/* " */
		else
		    ppcput('/');

		continue;

	    case '\\':			/* Must quote these chars */
	    case '"':
		ppcput('\\'); ppcput(ch); nextch();
		continue;

	    default:
		if (!iscsymf(ch))	/* Start of identifier? */
		    {
		    ppcput(ch);		/* Nope, just pass char on */
		    nextch();
		    continue;
		    }
		else
		    {
		    SYMBOL *sym;
		    register char *cp;
		    tlist_t strtl;

		    ppcput(ch);			/* Store first char of ident */
		    cp = ppcptr;		/* Remember pointer to it */
		    while (iscsym(nextch()))	/* Gobble all of identifier */
			ppcput(ch);
		    (void)ppcend();

		    if ((sym = findmacsym(cp)) == NULL	/* If not a macro, */
			|| !mexptop(sym, 0))		/* or can't expand it, */
			{
		        (void)ppcbackup();		/* just undo the ppcend() */
			continue;			/* and carry on! */
			}

		    /* Expanded macro!
		     ** First do tricky stuff to recover the string literal
		     ** token we were collecting, by clobbering the collected
		     ** macro identifier with '"' and NUL.  There will always be
		     ** enough room for this because the identifier had at least one
		     ** char and was null-terminated.
		     */
		    *cp = '"';			/* Set end quote of string literal */
		    *++cp = 0;			/* Tie off string */
		    tltadd(tl, stok);		/* And add literal to token list */

		    /* Now get the expanded macro tokens, which mexptop() left
		     ** in a tokenlist in curtl.  We swipe that and translate it into
		     ** a single string literal which is appended to our tokenlist.
		     */
		    strtl = tlstrize(curtl);	/* Make string lit from it */
		    tlzinit(curtl);		/* Done with that list */
		    tllapp(tl, strtl);		/* Append lit to our list */

		    stok.pt_val.cp = ppcbeg();	/* Now start a new string literal */
		    ppcput('"');
		    continue;
	            }
	    }				/* End of switch */

	break;
	}				/* End of loop */

    ppcput('\\');
    ppcput('n');
    ppcput('"');		/* Done, stop literal */
    (void)ppcend();
    tltadd(tl, stok);		/* And add to list */
    tltadd(tl, rptok);		/* Add right paren */
    tltadd(tl, sctok);		/* And semicolon */
    tltadd(tl, eoltok);		/* And EOL for niceness to passthru() */
    tltadd(tl, eoltok);		/* Another one to sacrifice to nextpp() */
    return tl;			/* Return the tokenlist! */
}

/* D_ENDASM() - Process #endasm directive
*/
static
int
d_endasm()
{
    if (inasm == 0)			/* If not inside #asm, complain */
	error("Not in #asm, ignoring #endasm");
else
    inasm = 0;			/* Tell d_asm() to stop */
    return PPR_FLUSH;
}

#if 0
	This page contains all of the "conditional commands", i.e.
if, ifdef, ifndef, elif,
else
    , and endif.  ifdef and ifndef are merely
    variants of #if.
	    flush  means if flushing.
	    flush= means if flushing at current level.
	    flush* means if flushing at any other level.

    Action
table:
    Encounter	 Prev	 Flush?	 Action to take:
	 if	 *	 flush	 push lev, set in-if, keep flushing.
		 *	 ok	 push lev, set in-if, test.
Fail:
    set flush lev
elif	if	flush*	Set in-elif, keep flushing.
	if	flush=	Test.
Win:
    set in-elif;
fail:
    set in-if,flush.
    if	    ok	    Set in-elif, set flush lev.
    elif    flush   Stay in elif, keep flushing.
    elif    ok	    Set in-elif, set flush lev.
    other = Error.
else if	     flush=  Set in-
else
    , stop flush.
	    if	    flush*  Set in-
else
    , keep flushing.
    elif    flush   Set in-
else
    , keep flushing.
    if	    ok	    Set in-
else
    , set flush lev.
    elif    ok	    Set in-
else
    , set flush lev.
    other = Error.
endif	*	flush=	Pop level.
	*	flush*	Pop level, keep flushing.
	*	ok	Pop level.
	not if/elif/
else
    = Error.

The tricky part is that elif only sets in-elif if the initial
if (or one of the previous elifs) was true.  As long as no test has succeeded
the setting is always kept at in-if.  This allows both elif and
else
    to
    know whether they are part of a elif chain that succeeded or not.
#endif

/* D_IFDEF() - Process #if and #ifndef directives
*/
    static int
    d_ifdef(cond)
    int cond;		/* 1 == ifdef, 0 == ifndef */
{

    if (++iflevel >= MAXIFLEVEL-1)	/* this is a new if level */
	{
	error("#if nesting depth exceeded");
	--iflevel;
	}
    iftype[iflevel] = IN_IF;	/* Say IF seen for this level */
    iffile[iflevel] = 0;	/* In current input file */
    ifline[iflevel] = fline;	/* At this line */
    if (flushing) return PPR_FLUSH;	/* if in false condition, that's all */

    if (nextrawpp() != T_WSP
      || nextrawpp() != T_IDENT)
	{
	error("Macro name expected");		/* Bad identifier */
	return PPR_FLUSH;
	}
    if (cond == ((findmacsym(rawval.cp) != NULL)))
	return PPR_CHECKEOL;	/* good condition, return */
    checkeol();
    flushcond();		/* Failed, flush body */
    return PPR_ATEOL;
}

/* D_IF() - Process #if directive
*/
static
int
d_if (void)
{
    if (++iflevel >= MAXIFLEVEL-1)	/* this is a new if level */
	{
	error("#if nesting depth exceeded");
	--iflevel;
	}
    iftype[iflevel] = IN_IF;		/* Say IF seen for this level. */
    iffile[iflevel] = 0;		/* In current input file */
    ifline[iflevel] = fline;		/* At this line */
    if (flushing) return PPR_FLUSH;	/* If already flushing, that's all. */

    if (!iftest())		/* Do the test.  If fail, */
	flushcond();		/* then flush body. */
    return PPR_ATEOL;
}

/* D_ELIF() - Process #elif directive
*/
static
int
d_elif()
{
    /* Make sure an #if or #elif preceeded this #elif */
    if (iftype[iflevel] == IN_ELSE)	/* If not IN_IF or IN_ELIF */
	{
	error("#elif without preceding #if, treating as #if");
	return d_if();				/* Handle as if plain #if */
	}

    /* For helpfulness, check to see if active conditional is in same file */
    if (iffile[iflevel] > 0)
	iffwarn("elif");	/* Matches condit from different file */

    /* See if OK for elif to test its expression.  This is only allowed
    ** if we are in an #if that just failed, or part of an elif chain
    ** that has never succeeded yet (which looks just the same).
    */
    if (flushing == iflevel && iftype[iflevel] == IN_IF)
	{
	if (iftest())		/* OK, do the test!! */
	    {
	    flushing = 0;		/* Won!  Stop flushing */
	    iftype[iflevel] = IN_ELIF;	/* and say inside an elif. */
	    iffile[iflevel] = 0;	/* In current input file */
	    ifline[iflevel] = fline-1;	/* At this line (note EOL gobbled) */
	    }			/* Else, stay in-if and keep flushing. */
	return PPR_ATEOL;
	}

    /* No need to perform test; this elif body must always fail at this point.
    **  Just ensure we're marked in-elif and see whether to start flushing
    ** or not.
    */
    iftype[iflevel] = IN_ELIF;	/* Set or stay in-elif */
    iffile[iflevel] = 0;	/* In current input file */
    ifline[iflevel] = fline;	/* At this line */

    if (flushing) return PPR_FLUSH;	/* If already flushing, just keep flushing. */
    flushtoeol();		/* No, start flushing to start of next line */
    flushcond();		/* And start flush checking now. */
    return PPR_ATEOL;
}


/* D_ELSE() - Process #else directive
*/
static
int
d_else()
{
    int prev = iftype[iflevel];	/* Remember current level type */

    if (prev == IN_ELSE)	/* Not IN_IF or IN_ELIF */
	{
	error("#else without preceding #if");
	if (iflevel == 0)	/* If not already inside a conditional */
	    {
	    prev = iftype[++iflevel] = IN_IF;	/* Pretend we were in an #if */
	    iffile[iflevel] = 0;
	    ifline[iflevel] = fline;
	    flushing = 0;			/* that was winning. */
	    }
	}

    /* For helpfulness, check to see if active conditional is in same file */
    if (iffile[iflevel] > 0)
	iffwarn("else");		/* Matches condit from diff file */

    iftype[iflevel] = IN_ELSE;	/* Whatever, say #if no longer last thing */
    iffile[iflevel] = 0;
    ifline[iflevel] = fline;

    /* Now invert sense of flushing:
    **	If not flushing, start doing so.
    **	If already flushing at this level, stop.  EXCEPT if prev was elif,
    **		in which case keep flushing!
    **	If flushing at a different level, keep going.
    */
    if (flushing == 0)
	{
	checkeol();		/* Move to start of next line */
	flushcond();		/* Start flushing if not */
	}
else if (flushing == iflevel && prev != IN_ELIF)
    {
    flushing = 0;		/* Stop flushing! */
    }
else if (flushing != iflevel)	/* Else just keep flushing. */
    flushtoeol();
return PPR_CHECKEOL;
}

/* D_ENDIF() - Process #endif directive
*/
static
int
d_endif()
{
    if (iflevel)		/* Are we in a conditional? */
	{
	if (iflevel == flushing) flushing = 0; /* stop flushing */
	if (iffile[iflevel] > 0)	/* Verify balanced within file! */
	    iffwarn("endif");		/* Barf, condit is from diff file */
	iflevel--;		/* drop a level */
	}
else
    error("Unmatched #endif");

if (flushing) flushtoeol();
return PPR_CHECKEOL;
}

/* FLUSHCOND - Auxiliary for if, elif, else.
**	Flush the body of a failing conditional command.
**	Assumes next token is 1st of next line (ie current char is 1st char).
**	When it returns, the current char will either be EOF
**	or will be the EOL which terminated the command that
**	caused flushing to stop.  See nextc()'s cmdlev variable.
*/
static
void
flushcond()
{
    flushing = iflevel;		/* not ok, set flushing */
    indirp = 0;			/* No longer hacking directive */
    do
	{
	if (tskipwsp() == T_SHARP)	/* Check start of line */
	    directive();		/* Handle directive! */
else
    flushtoeol();
    }
while (flushing && !eof);		/* If still flushing, keep going. */
}

/* IFTEST - auxiliary for #if and #elif.
**	Parses the conditional expression and returns its value.
** This works by gobbling the rest of the line as a token list,
** expanding it, and then substituting this token list as if a macro
** to the C expression parser.
**	On return, current token is garbage (usually T_EOF after the
** mexplim()) but current char is 1st of next line after the #if.
** Be careful not to try to examine the "current token" after iftest().
** Sigh.
*/
static
int
iftest()
{
    PPTOK *p;
    static PPTOK scoltok =
	{
	T_SCOLON
	};	/* Token for ";" */
	static PPTOK eoltok =
	{
	T_EOL
	};
	int won = 0;		/* Set non-zero if expr parse wins. */
	long val;

    /* Set up to parse a one-line constant expression */
	if (tlpcur(curtl))		/* Make sure we can hack curtl */
	    int_error("iftest: parsing #if with active curtl");

	*defcsname = 'd';		/* Restore proper 1st char to "defined" sym */
	curtl = mexplim(getlinetl(), 0);	/* Read line, expand it */
	*defcsname = SPC_MACDEF;	/* Re-Zap 1st char of "defined" macro sym */

    /* Check to make sure no identifiers left */
	for (p = tlpcur(curtl); p; p = tokn2p(p->pt_nxt))
	    if (p->pt_typ == T_IDENT)
	{
	note("Undefined identifier \"%s\" - substituting \"0L\"",
		    p->pt_val.cp);
	p->pt_typ = T_ICONST;
	p->pt_val.cp = "0L";
	}
    if (tlpcur(curtl))	/* Add ";" on end */
	{
	tltadd(curtl, scoltok);
	tltadd(curtl, eoltok);	/* Plus EOL in case of inasm */
	p = curtl.tl_tail;	/* Remember ptr to last token */
#if DEBUG_PP
	if (debpp) pmactl("iftest", curtl, 0);
#endif
	nextoken();		/* Initialize top-level token parser */
	val = pconst();		/* Parse input into value */
	curptr = NULL;		/* Clean up from recursive nextpp() */
	if (!tlpcur(curtl)	/* Make sure everything gobbled */
	  || tlpcur(curtl) == p)	/* If anything left, better be EOL */
	    {
	    ++won;		/* Yep to either, parse won! */
	    }
	tlzinit(curtl);		/* Ensure input tokenlist is flushed */
	}
    if (!won)
	{
	error("Bad syntax for #if expression, using 0");
	val = 0;
	}
    return val != 0;		/* Return logical result */
}

/* Various auxiliaries that exist only to provide helpful warnings or
** error messages if conditionals appear to be unbalanced across file
** boundaries.
*/

/* IFFWARN - Called when changing or ending an active conditional which
**	started in another file.
*/
static
void
iffwarn(typ)
char *typ;
{
    warn("#%s matches #%s from different file (\"%s\", line %d)",
	typ,
	ifname[iftype[iflevel]],
	inc[iffile[iflevel]-1].cname,
	ifline[iflevel]);
}

/* IFPUSH - Called when file input pushed by #include, to fix up
**	the info saved about active conditionals.
*/
static
void
ifpush(inlev)
{
    register int i;
    ++inlev;				/* Bump up so never zero */
    for (i = iflevel; i > 0; --i)	/* For all active conditionals */
	{
	if (iffile[i] == 0)		/* If in condit for this file */
	    iffile[i] = inlev;		/* remember its place in inc[] */
	}
}

/* IFPOPCHK - Current input file just ended, popping input or stopping
**	altogether.  Check for unterminated or unbalanced conditionals,
**	and output helpful messages if necessary.
*/
static
void
ifpopchk(int inlev)
{
    register int i;

#if 0			/* 8/91 #pragma eof */
    if (eof_pragma)	/* match (pop off) each #if, #ifdef, #ifndef*/
	{
	while (iflevel && iffile[iflevel] == 0)
	    (void) d_endif ();
    eof_pragma = 0;
	}
#endif
    if (inlev)		/* Just popping an include file? */
	{
	for (i = iflevel; i > 0; --i)
	    {
	    if (iffile[i] == 0)	/* If still in condit for this file */
		{
		warn("Unterminated #%s (starting at line %d)",
			    ifname[iftype[i]], ifline[i]);
	    iffile[i] = -1;		/* Say name no longer available */
		}
else if (iffile[i] == inlev)
    iffile[i] = 0;		/* This will become current file! */
    }
    }
else
    {

	/* EOF of whole input, check for unterminated stuff and give errors */
    for (i = iflevel; i > 0 ; --i)		/* For each active condit */
	    error("Unterminated #%s (starting at line %d%s)",
		    ifname[iftype[i]], ifline[i],
		    iffile[i] ? " of an included file"	/* Gone file */
			    : "");				/* Main file */
    }
}

/* D_INCLUDE() - Process #include directive
**
**	 Note that this emulates the Un*x compiler behavior by
** starting the search for "" files in the current source directory,
** rather than in the process' connected directory.
*/
static
int cinctry(int, char **, char *, char *, FILE **);
#if 0
static
int cinctry();	/* Auxiliary just for cinclude() */
#endif

static
int
d_include (void)
{
    char f[FNAMESIZE], f2[FNAMESIZE];
    int ftype, done = 0;
    FILE *fp = NULL;

    /* 11/91 #pragma include_once uses find_file() to avoid 2nd include */
    #define find_file(f)    (nsert_file((f),0))
    if ((ftype = getfile(f, FNAMESIZE)) == 0 || find_file(f))
	return PPR_FLUSH;	/* If failed to get name, ignore */

    /* A filename starting with '/' (ie an absolute pathname)
    ** tries just that name; nothing else would make sense.
    */
    if (*f == '/')
	{
	fp = fopen(strcpy(f2, f), "r");	/* Try to open just this one */
	++done;				/* Always done now */
	}
else if (ftype != '>')
    {
	/* File specified with "file" instead of <file>.  Must look in
	** several places, starting with the directory the input source is in.
	*/

    if (nincpaths)
	done = cinctry(nincpaths, incpaths, f2, f, &fp);
    if (!done)
	{
	estrcpy(estrcpy(estrcpy(f2,	/* Use source filename pref+suff */
		inpfdir), f), inpfsuf);
	if ((fp = fopen(f2, "r")) != NULL)
	    ++done;
else			/* V 2A(37): try the user's filespec
					    exactly as given (SPR 9577) */ if ((fp = fopen (strcpy (f2, f), "r")) != NULL)
    ++done;
    }
    }
    /* Now drop thru, so if nothing worked for a ""-type include, we
    ** try looking in the standard places for <>.
    */
if (!done)		/* Look in <> places */

	/* If redirecting <sys/ > files, try alternate paths from -h switches.
	** Note that default entries (if they exist) are tried last.
	*/
    {
    if ((nhfsypaths || nihfsypaths) && (strncmp(f, "sys/", 4) == 0)
      && (nhfsypaths <= 0
	    || !cinctry(nhfsypaths, hfsypaths, f2, f+4, &fp)))
	cinctry(nihfsypaths, ihfsypaths, f2, f+4, &fp);

	/* If not, or didn't succeed, just handle basic <> file with -H sws.
	** Note that default entries (if they exist) are tried last.
	*/
    if (!fp
      && (nhfpaths <= 0 || !cinctry(nhfpaths, hfpaths, f2, f, &fp)))
	cinctry(nihfpaths, ihfpaths, f2, f, &fp);
    }

if (!fp)				/* If nothing worked, complain */
    error("Can't open include file, last tried \"%s\"", f2);
else
    filepush(fp, f2, 1, 1, 1);	/* Won, push to new input file! */
    return PPR_ATEOL;
}

/* CINCTRY - Attempt to open an include file using the specified array
**	of directory pathnames.
**	Succeeds if file is opened OR if a NULL entry is encountered.
*/
static
int
cinctry(n, ptab, f2, f, fp)
register int n;			/* # paths in table */
char **ptab;			/* Addr of table */
char *f2, *f;			/* Places to deposit built and orig names */
FILE **fp;
{
    for(; --n >= 0; ++ptab)
	{
	if (!*ptab || !**ptab)		/* If we hit enforced stop, */
	    return 1;			/* always done now. */
	fstrcpy(f2, *ptab, f);		/* Build filename to try */
	if ((*fp = fopen(f2, "r")) != NULL)
	    return 1;			/* Won! */
	}
    return 0;				/* No stop and no opens... */
}

/* FILEPUSH - auxiliary for #include and #asm
**	Similar to bstrpush(), but for file input rather than string input.
**	Note that the current char needs to be saved; for both #include and
**	#asm this will be the 1st char on the next line after the directive,
**	due to the tokenizer's peek-ahead strategy.
*/
static
void
filepush(fp, fname, lin, pag, flin)
FILE *fp;
char *fname;
int lin, pag, flin;
{
    if (inlevel >= MAXINCLNEST-1)	/* Make sure we have room */
	{
	error("Include file nesting depth exceeded -- ignoring %s",fname);
	return;				/* Just ignore it if not */
	}

    pushch(ch);				/* Remember current file input char */
    if (eof)				/* If read-ahead has left us at */
	eof = 0;			/* top-level EOF, undo it. */
    strcpy(inc[inlevel].cname, inpfname);	/* Save old context */
    inc[inlevel].cptr = in;
    inc[inlevel].cpage = page;
    inc[inlevel].cline = line;
    inc[inlevel].cfline = fline;
    ifpush(inlevel);			/* Fix up conditional info */

    inlevel++;				/* Create new context */
    strcpy(inpfname, fname);		/* Set new current file name */
    in = fp;				/* Set new current input stream */
    fline = flin;
    line = lin;
    page = pag;
#if DEBUG_PP
    if (debpp)
	{
	int savch = ch;
	nextch();
	fprintf(fpp, "#include %d: saved char %#o, new file \"%s\", new char %#o\n",
			inlevel, savch, fname, ch);
	return;
	}
#endif
    nextch();				/* Set up new current char */
}

/* D_LINE() - Process #line directive
**
**	Note macro substitution is performed on the input.
*/
static
int
d_line(void)
{
    tlist_t tl;
    PPTOK *pint, *pstr;

    /* Gobble rest of line into tokenlist, expand it, and strip wsp */
    tl = tlwspdel(mexplim(getlinetl(),0), 1);	/* Flush all wsp */

    if ((pint = tlpcur(tl)) == NULL)
	return error("Empty #line"), PPR_CHECKEOL;
    if (pint->pt_typ != T_ICONST)
	return error("Bad #line number"), PPR_CHECKEOL;
    if ((pstr = tokn2p(pint->pt_nxt)) != NULL)
	{
	if (pstr->pt_typ != T_SCONST)
	    return error("Bad #line filename"), PPR_CHECKEOL;
	if (pstr->pt_nxt)
	    return error("Bad #line syntax"), PPR_CHECKEOL;
	}
    /* Complete winnage, process stuff. */
    fline = atoi(pint->pt_val.cp);	/* Set up number */

    if (*pstr->pt_val.cp == 'L')
	{
	error("#line directive string literal cannot be wide char");
	    pstr->pt_val.cp++;
	}

    if (pstr)				/* Copy filename w/o quotemarks */
	(void) sltostr(pstr, inpfname, FNAMESIZE-1);

    return PPR_CHECKEOL;
}

/* D_ERROR() - Process #error directive
*/
static
int
d_error(void)
{
    char errstr[120];		/* Max size of err msg */

    /* Gobble in pp-tokens and turn carefully into a string */
    tltostr(getlinetl(), errstr, (int)sizeof(errstr)-1);
    error("#error: %s", errstr);	/* Provoke error! */
    return PPR_CHECKEOL;
}

/* D_PRAGMA() - Process #pragma directive (barf choke vomit puke bletch)
 *
 * MVS 9/8/90:  added recognition of two pragmas: "debug" and "nodebug"
 *		to turn high-level debugging and profiling on and off.
 *		This is primative and not meant to be general, but must
 *		not be broken by a future more general implementation.
 *		Michael Snyder, Compuserve
 */

static
int
d_pragma (void)
{
    #define MAX_PRAGMA_SIZE	128
    char pragma_str[MAX_PRAGMA_SIZE];		/* Max size of pragma msg */
    char ext;

    /* Gobble in pp-tokens and turn carefully into a string */
    tltostr(getlinetl(), pragma_str, (int)sizeof(pragma_str)-1);

    if (strncmp(pragma_str, " debug", 6) == 0)
	debcsi = (debcsi >= 0) ? debcsi : -debcsi;	/* make positive */
else if (strncmp(pragma_str, " nodebug", 8) == 0)
    debcsi = (debcsi <= 0) ? debcsi : -debcsi;	/* make negative */
else if (strncmp(pragma_str, " message", 8) == 0)
	/* 11/91 prints a string without the halting of a #error */
    fprintf (stdout, "%s\n", &pragma_str[8]);
else if (strncmp(pragma_str, " request_library", 16) == 0 &&
    get_title (pragma_str, &ext))
    {
	/* 11/91 adds a .REQUEST  LIB*.REL to the .MAC file */
    void outnl (void);
    outstr ("\t.REQUEST ");
    outstr (title);
#define fnxisREL(s) ((s)[0]=='.' && toupper((s)[1])=='R' &&toupper((s)[2])=='E' && toupper((s)[3])=='L')
    if (!fnxisREL(&title[ext]))
	outstr (".REL");
    outnl();
    }
else if (strncmp(pragma_str, " include_once", 13) == 0)
	/* 11/91 causes d_include() to avoid fopen() for later inclusions */
    nsert_file(inpfname, 1);

    /* 1/92 #pragma module(title) PPS 4329 */
else if (strncmp(pragma_str, " module", 7) == 0 &&
	get_title(pragma_str, &ext))
    {
    ungetc(ch, in);		/* getlinetl() above ate 1 char too many */
    ch = ' ';		/* getlinetl() above got 1 ch too many */
    module_pragma = 1;	/* only place that turns on module_pragma */
    }
#if 0 /* KAR-8/92, removed note, but kept source around for later use in, */
      /* possibly, a new class of messages to the user, or such */
else
    note("Unknown #pragma %s", pragma_str);
#endif
return PPR_CHECKEOL;
}

static
int
get_title (char *pragma_str, char *ext)
{
    char i, j;

    /* 8 is 1 + min length of "request_library" and "module" */
    for (i = 8; i < MAX_PRAGMA_SIZE; i++)
	if (pragma_str[i] == '(')
	{
	for (j = 0; pragma_str[i+j+1] != ')' && j < TITLE_SIZE; j++)
	    title[j] = pragma_str[i+1+j];
	title[j] = '\0';
	*ext = j - 4;
	return 1;
	}
    return 0;
}

/* GETFILE(cp, n) - Read an include file name into specified loc,
**	using only up to N chars (plus NUL).
**	Returns 0 if name was bad, else '"' for "file" and '>' for <file>.
*/

static
int
getfile(char *f, int n)
{
    int i, type;
    char *s = f;

    pushch(ch);		/* Set up current char for re-read by scanhwsp */
    scanhwsp();		/* Flush horiz whitespace */
    switch (ch)
	{
	case '"':
	    type = '"';	  break;
	case '<':
	    type = '>';	  break;
	case '\n':
	    error("No filename for #include");
	    return 0;
	default:	/* Neither one, it better be some macros */
	    /* Get line of tokens, expand, and trim wsp from ends */
	    tltostr(tlwspdel(mexplim(getlinetl(), 0), 0), f, n);
	    type = 0;
	    if (*f == '"') type = '"';
else if (*f == '<') type = '>';
if (!type || (f[(i = strlen(f))-1] != type))
    {
    error("Bad syntax for #include file: %s", f);
    return 0;
    }
memmove(f, f+1, i -= 2);	/* Shift string up */
f[i] = '\0';		/* And tie off properly */
return type;
    }

    /* Got first delimiter char, try to find terminating one.
    ** The only thing that stops our scan is EOL, EOF, or terminator.
    ** If more stuff exists in line after terminator, we complain.
    */
nextch();			/* Skip over 1st delimiter */
i = n;
while (--i > 0)
    {
    if (isceol(ch) || ch == EOF)
	{
	error("missing %c in include file spec", type);
	type = 0;		/* Failed, term not found */
	break;
	}
    if (ch != type)
	{
	*s++ = ch;		/* Nothing special, store char */
	nextch();
	continue;
	}

	/* Char is delimiter */
    *s = '\0';		/* Tie string off! */
    nextch();		/* Move over delimiter */
    if (tskipwsp() != T_EOL)
	{
	type = 0;		/* Error, more junk on line */
	error("Bad #include syntax, junk follows filename");
	}
    break;
    }

if (i <= 0)
    {
    error("Filename too long");
    type = 0;
    }
*s = 0;
flushtoeol();
return(type);
}

/* Various misc auxiliaries for directive parsing */

/* TSKIPWSP() - (Token-based) Skip PP whitespace starting with current char.
**	Returns first non-WSP token.
**	For use in directives - complains and stops if non-PP whitespace
**	is seen, specifically \r, \v, \f (CR, VT, FF)
*/
static
int
tskipwsp(void)
{
    while (nextrawpp() == T_WSP) ;
    return rawpp;
}

/* TSKIPLWSP() - (Token-based) Skip Lines & Whitespace starting with
**	current char.
**	Returns first non-WSP, non-EOL token.
**	This skips anything that qualifies as C whitespace.
*/
static
int
tskiplwsp(void)
{
    for (;;) switch (nextrawpp())
	{
	case T_WSP:
	case T_EOL:
	    continue;
	default:
	    return rawpp;
	}
}

/* FLUSHTOEOL() - Flushes all input up to next EOL, including comments,
**	starting with current token (current char is 1st char of next token)
**	Stops with T_EOL as current token, and current char the 1st
**	after the EOL.
**	Except for EOF, of course.
**	Does it fast.  Ignores string literals since they shouldn't include
**	newlines anyway.
*/
static
int
flushtoeol(void)
{
    if (rawpp == T_EOL) return rawpp;	/* Make sure not already there */
    for (;;) switch (ch)
	{
	case EOF:
	    return rawpp = T_EOF;

	case '\n':
	    return nextch(), rawpp = T_EOL;

	case '/':
	    if (nextch() == '*')
		scancomm ();
	    else if (ch == '/' && !clevnocpp) /* FW 2A(46) August 1993 */
		{			/* " */
		comment_type = CPP;	/* " */
		scancomm ();		/* " */
		pushch (ch);		/* FW 2A(50) September 1993 */
		comment_type = C;	/* " */
		return nextch (), rawpp = T_EOL; /* FW 2A(47) August 1993 */
		}

	default:
	    nextch();
	}
}

/* CSKIPLWSP() - (Char-based) Special routine for top-level macro expansion.
**	Starting with current char,
**	scans character stream to find next non-whitespace char,
**	without going through or affecting tokenizer.
**	Whitespace includes line breaks and comments.
**	Returns new current char (first non-whitespace).
**	If first non-LWSP char is '#' and an EOL has been skipped, then
**	a \n is pushed back and returned so that directives will be
**	recognized.  Hack, hack.
*/

static
int
cskiplwsp (void)
    {
    int		eolseen = 0;


    for (;;)
	{
	switch (ch)
	    {
	    case '/':

		if (nextch () != '*')
		    {
		    /*
		     * This is not a C comment.  It may be a C++
		     * comment, or it may not be a comment at all.
		     */

		    if (ch == '/' && !clevnocpp) /* FW 2A(46) August 1993 */
			comment_type = CPP;	/* FW 2A(46) August 1993 */
		    else			/* FW 2A(46) August 1993 */
			{
			pushch (ch);
			return pushstr ("/");
			}
		    }

		scancomm ();

		if (comment_type == CPP)/* FW 2A(50) September 1993 */
		    pushch (ch);	/* FW 2A(50) September 1993 */

		comment_type = C;	/* FW 2A(46) August 1993 */
		nextch ();
		break;

	    case '\n':
		++eolseen;

	    default:

		if (!iscwsp (ch))	/* See if any kind of C whitespace */
		    {
		    if (eolseen && ch == '#')	/* If possible directive, */
			{
			pushch (ch);		/* push back char */
			pushstr ("\n");		/* and make EOL be current */
			}

		    return ch;
		    }

		nextch ();
	    }
	}
    }

/* CHECKEOL - finish up after directive has parsed everything it wants.
**	Makes sure that there is nothing but whitespace between the
**	current token and EOL; checks for already being at EOL to avoid
**	gobbling a line by accident.
*/

static
void
checkeol(void)
{
    if (rawpp == T_EOL || rawpp == T_EOF)	/* Check for already at EOL */
	return;
    for (;;) switch (nextrawpp())	/* Not there, start skipping wsp */
	{
	case T_WSP:
	    continue;
	default:
	    if (clevel >= CLEV_STRICT)
		error("Non-whitespace following directive");
else
    warn("Non-whitespace following directive");
flushtoeol();
    case T_EOL:
    case T_EOF:
	return;
	}
}

#if 0
/* GETIDREST - Get an identifier string when current char known to be OK.
**	Returns # of chars read in the identifier.
*/
static
int
getidrest(char *str)
{
    char *s = str;
    int i = IDENTSIZE-1;		/* Leave room for null terminator */

    *s = ch;				/* First char always goes in */
    while (iscsym(nextch()))		/* If succeeding char is alphanum */
	if (--i > 0) *++s = ch;		/* is legal, add to ident */
    *++s = '\0';			/* null terminate */
    if (i <= 0)
	{
	note("Identifier truncated: \"%s\"", str);
	i = 1;				/* Set i so return value is correct */
	}
    return IDENTSIZE - i;		/* Return number of chars in string */
}
#endif

/* GETLINETL() - Gobble rest of raw input line into a tokenlist
**	and return that.
**	On return, current token will be T_EOL (or T_EOF) and
**	current char the first one past the EOL.
*/
static
tlist_t
getlinetl(void)
{
    tlist_t tl;

    tlzinit(tl);
    while (nextrawpp() != T_EOL && rawpp != T_EOF)
	tlrawadd(&tl);
    return tl;
}

/* TLWSPDEL(tl) - Removes T_WSP tokens from a tokenlist,
**	to simplify life for other routines.
**	If allf 0, only flushes from start and end.
**	Otherwise, flushes all T_WSP from entire list.
*/
static
tlist_t
tlwspdel(tlist_t tl, int allf)		/* TRUE to flush all whitespace */
{
    register PPTOK *p, *lastp = NULL;

    for (p = tlpcur(tl); p;)
	if (p->pt_typ == T_WSP
	  && (allf || p == tl.tl_head || p == tl.tl_tail))
	{
	if (lastp) lastp->pt_nxt = p->pt_nxt;	/* Fix up prev */
else
    tl.tl_head = tokn2p(p->pt_nxt);
    if ((p = tokn2p(p->pt_nxt)) == NULL)		/* Fix up tail */
	tl.tl_tail = (lastp ? lastp : tl.tl_head);
    }
else
    lastp = p, p = tokn2p(p->pt_nxt);	/* Just pass non-wsp */
return tl;
}

/* TLMAKE(typ, val) - Make a tokenlist consisting of just one token,
**	with the given token-type and value.
**	This routine is at the end of CCPP for now to avoid lots of warning
**	messages about mismatched argument types.
*/
static
tlist_t
tlmake(int typ, union pptokval val)
{
    tlist_t tl;
    static PPTOK ztok;
    *(tl.tl_head = tl.tl_tail = tokpcre()) = ztok;
    tl.tl_head->pt_typ = typ;
    tl.tl_head->pt_val = val;
    return tl;
}

#define FILE_LEN	 12
#define FILE_MAX	128

int
nsert_file (char *f, int insert_flag)
{
    typedef char (* file_list_t)[FILE_LEN];
    static file_list_t file_list = NULL;
    int i = 0;

    if (!f)		/* 2/92 free previous list if #pragma module (title)*/
	{
	if (file_list)
	    free(file_list);
	file_list = NULL;
	return 0;
	}
else if (file_list)
    {
    for (; file_list[i][0] != '\0'; i++)
      if (strncmp(f, file_list[i], FILE_LEN) == 0)
	  return 1;	/* file with #pragma include_once already included */
    }
else
    {
    file_list = (file_list_t) malloc (FILE_MAX * FILE_LEN);
    if (file_list == NULL)
	jerr("Out of memory for file list\n");
    file_list[0][0] = '\0';   /* ensure that initial file_list[][0] is zero */
    }

/* #pragma include_once sets insert_flag  and  -i flag sets insert_all_files 
 * Insert first 127 *.H files (remaining files may be inserted twice or more)
 */
if ((insert_flag || insert_all_files) && i < FILE_MAX - 1)
    {
    strncpy (file_list[i], f, FILE_LEN);
    file_list[i+1][0] = '\0';   /* ensure that next file_list[][0] is zero */
    }
return 0;			/* not found, possibly inserted */
}

