Trailing-Edge
-
PDP-10 Archives
-
SRI_NIC_PERM_FS_1_19910112
-
kcc-4/kcc/ccinp.c
There are 6 other files named ccinp.c in the archive. Click here to see a list.
#ifndef DEBUG
#define DEBUG 0
#endif
/* CCINP.C - KCC Preprocessor and character input
**
** All changes after version 64 (8/8/85), unless otherwise specified, are
** Copyright 1985, 1986 by Ken Harrenstien, SRI International.
*/
/* [SRI-NIC]SS:<C.KCC.CC>CCINP.C.75, 17-Dec-85 08:06:02, Edit by KLH */
/* Rationalized names of constants and structures */
/* [SRI-NIC]SS:<C.KCC.CC>CCINP.C.68, 13-Dec-85 06:39:23, Edit by KLH */
/* Bulletproof deposit into errlin (input line saved for err context) */
/* Also check macro nesting depth at _nextc */
/* <KCC.CC>CCINP.C.60, 27-Jun-85 17:20:49, Edit by KRONJ */
/* Don't pass \n after #x through to input */
/* <KCC.CC>CCINP.C.58, 27-Jun-85 10:09:50, Edit by KRONJ */
/* Set firstime variously to be careful of %$ in ident */
/* <KCC.CC>CCINP.C.57, 27-Jun-85 10:06:14, Edit by KRONJ */
/* Always close input file (even if main source) on eof */
/* <KCC.CC>CCINP.C.55, 25-Jun-85 10:39:33, Edit by KRONJ */
/* No complaints when redefining already-defined macro */
/* Rework macro defs not to use string pool (runs out for monsym.h) */
/* <KCC.CC>CCINP.C.50, 18-Jun-85 10:46:46, Edit by KRONJ */
/* Fancy comment handling for #asm */
/* <KCC.CC>CCINP.C.48, 13-Jun-85 14:01:40, Edit by KRONJ */
/* #asm and #endasm */
/* <KCC.CC>CCINP.C.39, 4-Jun-85 11:18:49, Edit by KRONJ */
/* Support -I */
/* <KCC.CC>CCINP.C.37, 3-Jun-85 15:51:07, Edit by KRONJ */
/* Support -D */
/* <KCC.CC>CCINP.C.36, 21-May-85 15:40:13, Edit by KRONJ */
/* #undef when symbol not known is not an error */
/* <KCC.CC>CCINP.C.32, 9-Mar-85 15:37:32, Edit by KRONJ */
/* Don't loop forever on unclosed comment */
/* <KCC.CC>CCINP.C.31, 4-Mar-85 02:44:43, Edit by SATZ */
/* Make some constants bigger. 32 => _STRSIZ */
/* <KCC.CC>CCINP.C.30, 27-Feb-85 19:44:07, Edit by SATZ */
/* Make sure user sees a warning for doubly defined macros */
/* <KCC.CC>CCINP.C.29, 27-Feb-85 11:17:16, Edit by SATZ */
/* Make sure #define, #undef, and #if[n]def use macro arguments */
/* <KCC.CC>CCINP.C.26, 20-Feb-85 23:55:01, Edit by SATZ */
/* Make sure all include files get closed upon EOF */
/* <KCC.CC>CCINP.C.25, 18-Feb-85 12:35:33, Edit by SATZ */
/* check for eof while flushing lines between #ifdef */
/* <KCC.CC>CCINP.C.25, 18-Feb-85 11:45:36, Edit by SATZ */
/* Check for unmatching #endif */
/* <KCC.CC>CCINP.C.23, 17-Feb-85 11:45:31, Edit by SATZ */
/* Make #include "foo.h" not print C:foo.h on error (or C:C:foo.h) */
/* and make missing include files warnings not errors */
/* <KCC.CC>CCINP.C.22, 29-Jan-85 20:50:09, Edit by SATZ */
/* #if expr was eating \n which caused the next line to be skiped */
/* <KCC.CC>CCINP.C.21, 29-Jan-85 19:31:06, Edit by SATZ */
/* Don't complain if argument to #undef is already undefined */
/* <KCC.CC>CCINP.C.20, 8-Jan-85 01:04:38, Edit by SATZ */
/* fix undefine(); it always returned the error EIDENT */
/* <KCC.CC>CCINP.C.18, 3-Jan-85 12:36:39, Edit by SATZ */
/* make sure #define handles comments by calling nextcc() */
/* <KCC.CC>CCINP.C.16, 3-Jan-85 00:13:39, Edit by SATZ */
/* make skipblanks() call nextcc() */
/* <KCC.CC>CCINP.C.15, 2-Jan-85 23:35:06, Edit by SATZ */
/* fix up calling conventions for nextcc() */
/* <KCC.CC>CCINP.C.14, 2-Jan-85 23:20:16, Edit by SATZ */
/* break out comment processing in nextc() to nextcc() so it */
/* can be used by other routines */
/* SCORE:<KCC.CC>CCINP.C.12, 17-Aug-84 15:19:09, Edit by KRONJ */
/* try looking in C: for included files if not in DSK: */
/* SCORE:<KCC.CC>CCINP.C.2, 3-Aug-84 12:53:42, Edit by KRONJ */
/* comment processing moves here */
/* SCORE:<KCC.CC>CC3.C.12, 29-Jun-84 15:38:59, Edit by KRONJ */
/* fix #else */
/* cc3.c -- Preprocessor (C) 1981 K. Chen */
#include "cc.h"
#include "ccchar.h"
#include "cclex.h"
#include <time.h> /* Needed for __DATE__ and __TIME__ */
/* Imported functions used here */
extern SYMBOL *findmsym(); /* CCSYM to look up macro name */
extern SYMBOL *creatgsym(); /* CCSYM to create a macro name */
extern int pconst(); /* CCSTMT to parse constant expr */
extern char *malloc();
/* Exported functions defined in CCINP: */
void initinp(); /* Initialize the input preprocessor. (CC) */
int nextc(); /* Read next character from input (CCLEX) */
int _nextc(); /* Same, but no comment or #-cmd processing (CCLEX)*/
void pushc(); /* Push 1 char back on input (CCLEX) */
int expmacsym(); /* Called to expand a macro (CCLEX) */
void pdefine(); /* Called to initialize predefined macros (CC) */
void passthru(); /* Invoked by -E for simple pass-thru processing (CC)*/
/* The main global variable of interest is
* ch - set to the current input character
* 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, _nextc()
* 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".
*/
/* Internal functions */
static int macesc();
static void macpush(), macpop();
static int getargs();
static void preprocess(), cdefine(), cundef(), casm(), cendasm(), cifdef(),
cif(), celse(), celif(), cendif(), flushcond(), cinclude(), cline();
static int iftest();
static void filepush();
static SYMBOL *findmacsym(), *defmacsym();
static void freemacsym();
static int skipcomm(), skipblanks(), wsptest();
static void trimblanks(), skiptoeol();
static int getidstr(), getidrest(), getfile();
static void pass_ident();
static int pass_str();
/* Include file nesting stack - holds saved input file context.
* Indexed by global "level".
*/
static struct {
FILE *cptr; /* file pointer */
filename cname; /* filename */
int cline; /* line number */
int cpage; /* page number */
} inc[MAXINCLNEST];
static int iflevel, /* #if-type command nesting level. */
flushing; /* Set = iflevel when inside failing conditional */
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 int inasm; /* Set when inside assembly passthrough */
static FILE *prepfp; /* Set to output stream when doing -E */
static char *eolstop; /* Set non-NULL when EOL should stop parsing of an
* expression or constant by higher level. Value
* should be that of the string to expand instead of
* EOL (the EOL is pushed back for re-reading).
* Used by #if and #line expression parsing.
*/
/* MACRO DETAILS:
** A defined macro is stored in the symbol table as a symbol with
** class SC_MACRO. It has no type and uses only two components:
** Svalue - # of arguments (or special value)
** Smacptr - char pointer to the macro body (allocated by malloc)
** The value of Svalue 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) /* 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. */
/* All special macros other than MACF_ATOM cannot be redefined or
** undefined except by the command-line -U or -D switches.
*/
/*
** Smacptr (if not null) points to the macro body, which consists
** simply of the text of the macro. Places where a macro arg should
** be expanded are indicated by a MAC_ESC (macro escape) character
** value, followed by another special MAC_ char value which typically
** indicates that the next character has the number of the macro arg to
** expand at this point. Doubling MAC_ESC will quote it.
** Note that MAC_ESC should not be an identifier character value!
*/
#define MAC_ESC ('\177') /* Escape character within macro body */
#define MAC_ARG ('A') /* Next character is macro arg number (1, 2, ...) */
/* Other special escape sequences may be defined in the future. */
/* Expansion context stack used to hold macros currently being expanded.
** maclevel is used to index this stack. When maclevel == 0, no
** macro expansion is in progress. (This means that the contents of the
** first stack entry (index 0) are ignored, although it is referenced).
** Otherwise a macro expansion is in progress. The string being expanded
** is not necessarily a macro body; it could be a macro argument string,
** or anything else given to macpush().
** macptr points to string currently being expanded.
** mac[maclevel].mptr is unused. In theory this is initially == macptr.
** mac[maclevel].msym Holds symbol ptr (if any) for this expansion.
** mac[maclevel].marg[] Holds pointers to the argument strings
** (if any) for this expansion. They are used
** if the expansion string asks for an arg.
** Only expmacsym() can set up .msym and .marg.
**
** mac[maclevel-1].mptr Previous value of macptr.
** mac[maclevel-1].marg[] Previous expansion args if any.
** ..etc..
*/
struct MAC { /* Structure of each expansion context entry */
char *mptr; /* Saved value of macptr */
SYMBOL *msym; /* Macro symbol being expanded */
char *marg[MAXMARG]; /* Arguments for current expansion */
};
static struct MAC mac[MAXMACNEST]; /* For macro nesting */
static int maclevel; /* Macro expansion level */
static char *macptr; /* Pointer into macro body being expanded */
/* This buffer is only used by expmacsym() */
static char _macpool[MACPOOLSIZE]; /* Macro arg char pool */
static char *macpool; /* Current pointer into pool */
static int macpleft; /* Countdown of # free chars left */
static char *defcsname; /* Pointer to the "defined" macro symbol name */
static char defcdummy; /* Use this in case not using macro */
static int tadset; /* True if date/time strings are set */
static char datestr[14] = "\"Mmm dd yyyy\""; /* __DATE__ string */
static char timestr[11] = "\"hh:mm:ss\""; /* __TIME__ string */
/* ------------------------------------------------------------ */
/* initinp() - Initialize input preprocessor (this module) */
/* Called once for each file compiled. */
/* ------------------------------------------------------------ */
void
initinp()
{
SYMBOL *s;
/* Set globals */
eof = 0;
tline = 0;
page = line = 1;
/* Set variables local to preprocessor */
level = iflevel = flushing = inasm = maclevel = 0;
iftype[0] = 0; /* Just in case */
tadset = 0; /* Date/time strings not set */
eolstop = NULL;
erptr = errlin; /* Init pointer to current saved input line */
*erptr = '\0';
erpleft = ERRLSIZE; /* Set countdown of chars left in errlin */
/* Enter special macro pre-definitions into symbol table. */
defmacsym("__LINE__", MACF_LINE, NULL, 0);
defmacsym("__FILE__", MACF_FILE, NULL, 0);
if (clevel >= CLEV_ANSI) {
defmacsym("__DATE__", MACF_DATE, NULL, 0);
defmacsym("__TIME__", MACF_TIME, NULL, 0);
#if 0 /* When we're fully ready */
defmacsym("__STDC__", MACF_STDC, "1", 1);
#endif
}
/* "defined" is somewhat more special */
if (clevel >= CLEV_CARM) {
s = defmacsym("defined", MACF_DEFD, NULL, 0);
defcsname = &s->Sname[0]; /* Remember ptr to 1st char of sym */
*defcsname = SPC_MACDEF; /* Hide sym by smashing 1st char */
} else defcsname = &defcdummy; /* Don't define, use dummy */
ch = '\n';
(void) nextc(); /* Prime with 1st character! */
}
/* 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(EGEN,"Cannot get date and time");
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! */
}
/* -------------------------------------------------------- */
/* nextc() - get next character from input source */
/* Does full preprocessing. */
/* -------------------------------------------------------- */
/* This function needs to be invoked carefully, because
** preprocessor commands are recognized here. The only places
** within CCINP itself that call nextc() are:
** flushcond() - while flushing input lines for failing conditionals.
** casm() - while gobbling input for conversion to asm().
** passthru() - used for -E.
** All other input uses _nextc().
*/
int
nextc()
{
static cmdlev = 0; /* Command recursion level */
/* About cmdlev: when preprocess() returns from handling a
** preproc command, the current char will be the EOL that terminated
** the line it appeared on. Normally this can simply be ignored,
** since at top level preprocessor lines should be completely removed.
** But if we are still within a preprocessor command that is flushing
** lines, the EOL needs to be passed on to the next higher level
** so that the overall (top-level) command can return it. Otherwise
** the skiptoeol() done for preprocessor commands can accidentally
** flush valid input lines.
*/
if (isceol(ch)) { /* If current char is a line break, */
if (_nextc() == '#') { /* Check next for preproc cmd */
cmdlev++; /* Aha, found one! Note in preproc */
preprocess(); /* Process it. */
if(--cmdlev > 0) /* If this was within a higher-level cmd */
return ch; /* then just return terminating (EOL) char */
return nextc(); /* Else top level, can ignore EOL */
}
} else _nextc(); /* Current char not linebreak, just get next */
return skipcomm(); /* Check new char for comment processing */
}
/* _NEXTC() - get next character without any preprocessing or comment removal.
** The only modification made to the input is removal of the
** sequence '\\' '\n' (i.e. a quoted newline).
** Because the value returned by _nextc() must be pushable by pushc(),
** and because we cannot call pushc() 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 pushc().
** This is why the calls to macpush() exist.
*/
int
_nextc()
{
int i;
/* first, try for macro handling */
if (maclevel) { /* are we expanding macros? */
if (i = *macptr++) { /* yes, are there more chars? */
if (i != MAC_ESC) /* Yes, is this a macro arg escape? */
return ch = i; /* No, not special char, just return */
return ch = macesc(); /* Aha! Handle macro escape stuff */
}
macpop(); /* End of a macro expansion, pop it */
return _nextc(); /* and try for a new char */
}
/* not in a macro, just normal char */
switch (i = getc(in)) {
case EOF: /* End Of File on input */
/* Do a couple of checks to verify */
if (ferror(in))
error(EGEN, "I/O error detected while reading file %s", inpfname);
else if (!feof(in))
error(EINT, "spurious EOF from getc without error flag");
if (level-- > 0) { /* drop level. do we have more? */
fclose(in); /* Yes, make sure we close this one */
in = inc[level].cptr; /* then set vars from popped level */
line = inc[level].cline;
page = inc[level].cpage;
strcpy(inpfname, inc[level].cname);
ch = '\n';
*erptr = 0; /* Terminate old line */
erptr = errlin; /* Start save of new line */
erpleft = ERRLSIZE; /* " */
return _nextc(); /* 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 (ch != '\n') { /* EOF at top level. OK to stop now? */
i = '\n'; /* No, attempt graceful termination */
break; /* by providing EOL as last char. */
}
if (eof) break; /* If already seen EOF, needn't redo stuff. */
eof = 1; /* Flag end of file at top level. */
if (iflevel) /* Error if preprocessor unbalanced */
error(EGEN,"EOF within unterminated #if");
break; /* Return EOF char */
case '\\':
if(--erpleft > 0) /* Unless 1 char or less left, */
*erptr++ = i; /* add to saved input line. */
{
char *sav = eolstop; /* Push eolstop value */
eolstop = NULL; /* and clear in case next char EOL */
_nextc(); /* get another char */
eolstop = sav; /* Pop eolstop back */
}
if (isceol(ch)) return _nextc(); /* Quoted EOL is nothing. */
pushc(ch); /* otherwise put char back */
macpush("\\"); /* and set up a pushc-able input */
return _nextc(); /* and return backslash */
case '\f': /* Form-feed */
page++; /* Starts new page */
line = 0; /* at first line */
/* Fall through to line-break */
case '\r':
case '\v':
case '\n':
line++; /* new line, same page */
tline++;
*erptr = 0; /* terminate old line */
erptr = errlin; /* start at beginning of line */
erpleft = ERRLSIZE; /* " */
if (eolstop) { /* If treating EOL specially */
pushc(i); /* put it back for skiptoeol etc. */
macpush(eolstop); /* and expand substitute string */
eolstop = NULL; /* then clear so done once only. */
return _nextc(); /* Return 1st char of subst string */
}
break;
default:
if (--erpleft > 0) /* Unless 1 char or less left, */
*erptr++ = i; /* add to saved input line. */
}
return ch = i;
}
/* --------------------------------------------------- */
/* macesc - Handle macro escape char within macro body */
/* --------------------------------------------------- */
/* macptr points to next char after the MAC_ESC.
** Returns next char
*/
static int
macesc()
{
int i;
char *newptr;
switch(i = *macptr++) { /* Get next char, handle it */
case MAC_ESC: /* Quoting the escape char */
return (i);
case MAC_ARG: /* Macro arg expansion */
i = *macptr++; /* Get next char = argument # */
if(i <= 0 || i > MAXMARG) /* Just paranoid... */
efatal(EINT, "macro arg index %d", i);
macpush(mac[maclevel].marg[i-1]); /* Expand the argument */
return _nextc(); /* and try for a new char */
default:
efatal(EINT, "macro escape char '%c'", i);
}
}
static void
macpush(newptr)
char *newptr;
{
if (maclevel >= MAXMACNEST-1) { /* Check for nest overflow */
error(EGEN, "Macro nesting depth exceeded");
return;
}
mac[maclevel].mptr = macptr; /* Push old pointer if any */
maclevel++; /* Mark down another expansion level */
mac[maclevel].msym = NULL; /* Say not expanding a macro sym */
macptr = newptr; /* Set to read from new body pointer */
#if DEBUG
fprintf(stderr, "Macpushed new frame:\n");
pmacframe(maclevel);
#endif
}
static void
macpop()
{
if (maclevel <= 0) /* end of macro, pop */
efatal(EINT,"macro overpop");
if (mac[maclevel].msym) /* If we were expanding a macro sym, */
mac[maclevel].msym->Sflags &= ~SF_MACEXP; /* turn off exp flag */
macptr = mac[--maclevel].mptr; /* Bump level down and restore ptr */
#if DEBUG
fprintf(stderr, "Macpopped, restored frame:\n");
pmacframe(maclevel);
#endif
}
/* expmacsym(sym) - Expand a macro invocation; read args and start expansion.
** Returns:
** 1 Macro now being expanded
** 0 Could not expand macro, caller must process symbol token.
** -1 Error, ignore token. (error msg already printed)
**
** Called primarily by getident() in CCLEX if macro symbol seen; also
** called within CCINP by getfile() and pass_ident().
** Parses macro args and sets up character stream for macro expansion.
** Only stores as many args as the macro was defined to handle; extra
** "arguments" are gobbled and thrown away.
** Note that macro invocation parsing can theoretically be different
** from the parsing for preprocessor commands, because macro invocations
** are supposed to look like C function calls. Following this principle to
** its logical conclusion would require that "nextc()" be used to read
** the input during argument parsing. This would permit preprocessor
** commands to be recognized. The main difficulty this might cause is
** that #undef would have to be careful not to free() up a macro body
** that was currently being used (this one or one on macro stack).
** Another possible difficulty is that macro invocation during preprocessor
** commands which permit it (#if,#elif,#include,#line) could be screwed up
** by the inclusion of preproc cmds in the args!
** For the time being, the C standard apparently does not permit this,
** so we are conservative and use _nextc().
**
** Does _nextc() to re-initialize character input with the first char of
** the macro body. This is OK even when being called by CCLEX (the lexer),
** which normally uses nextc(), because comments have already been flushed
** from macro bodies. And it is necessary in order to avoid recognizing
** preprocessor commands during macro expansion!
*/
/* Macro to handle deposit of chars into macro arg char pool (_macpool) */
#define putmacpool(c) (--macpleft <= 0? (int)fatal(EMACPOOL): *macpool++ = (c))
int
expmacsym(sym)
SYMBOL *sym;
{
char *bodystr; /* Macro body */
int nargs; /* # of arguments it takes */
int i, nlev, parens;
static char xbuff[_FILSIZ+2]; /* Must be able to handle filename */
char s[_IDSIZE];
struct MAC tmpmac; /* Temporary struct for gathering args */
if (sym->Sflags & SF_MACEXP) { /* This sym already being expanded? */
if (clevel < CLEV_ANSI) /* Give error unless ANSI. */
error(EGEN,"Recursive macro expansion: %s", sym->Sname);
#if DEBUG
else warn(EGEN,"Recursive macro expansion: %s", sym->Sname);
#endif
return 0; /* Then just ignore it. */
}
#if DEBUG
fprintf(stderr,"Expanding \"%s\", old frame:", sym->Sname);
pmacframe(maclevel);
#endif
/* Check for exceeding macro nesting depth. If exceeded, we complain
** and ignore this expansion attempt, flushing the macro args if any.
*/
nlev = maclevel+1; /* This will be new level */
if (nlev > MAXMACNEST-1) { /* Does entry for this level exist? */
error(EGEN,"Macro nesting depth exceeded: %s", sym->Sname);
(void) getargs((char **)NULL, -1, sym->Svalue); /* Flush args */
return -1;
}
if ((nargs = sym->Svalue) < 0) { /* Special macro type? */
pushc(ch); /* Yes, push back name delimiter */
switch(nargs) {
case MACF_ATOM: /* Simple case, normal macro without arglist */
bodystr = sym->Smacptr;
break;
case MACF_LINE: /* __LINE__ */
sprintf(xbuff, "%d", line);
bodystr = xbuff;
break;
case MACF_FILE: /* __FILE__ */
sprintf(xbuff, "\"%s\"", inpfname);
bodystr = xbuff;
break;
case MACF_DATE: /* __DATE__ */
if (!tadset) dotad();
bodystr = datestr;
break;
case MACF_TIME: /* __TIME__ */
if (!tadset) dotad();
bodystr = timestr;
break;
case MACF_DEFD: /* "defined" operator */
/* We allow either "defined NAME" or "defined(NAME)" as per
** H&S 3.5.5. Note that input is done here without
** any preprocessing (other than comment removal).
*/
_nextc(); /* Get next char after operator */
parens = 0;
if(skipblanks() == '(') {
parens++;
_nextc();
skipblanks();
}
if(!getidstr(s)) {
error(EGEN,"Bad arg to \"defined\" operator");
return -1;
}
if (findmacsym(s))
bodystr = "(1)"; /* Expand into "true" */
else bodystr = "(0)"; /* Expand into "false" */
if (parens) {
if(skipblanks() == ')') _nextc();
else error(EGEN, "Missing ')' for \"defined\" operator");
}
pushc(ch); /* Push so will re-read after this expansion */
break;
default:
error(EINT, "bad macro value");
return -1;
}
} else { /* Macro expects an arg list */
bodystr = sym->Smacptr; /* This is body of macro */
i = getargs(&tmpmac.marg[0], nargs, nargs); /* Parse args */
/* Complain if not exactly the right number of args. Sigh. */
if (nargs != i) {
error(EGEN, "Wrong number of macro args - %d expected, %d seen",
nargs, i);
if (i < 0) i = 0; /* Handle case of MACF_ATOM */
while (i < nargs) /* Ensure arg table filled out */
tmpmac.marg[i++] = ""; /* so no refs to wild ptrs */
}
}
/* Now set up macro frame. */
macpush(bodystr); /* Bump level, set pointer to body */
sym->Sflags |= SF_MACEXP; /* Say symbol now being macro-expanded */
mac[maclevel].msym = sym; /* Save sym ptr in macro exp frame */
if (nargs > 0) /* Copy arg array into frame. */
for (i = 0; i < nargs; ++i)
mac[maclevel].marg[i] = tmpmac.marg[i];
#if DEBUG
fprintf(stderr,"Macro \"%s\" now on frame.\n", sym->Sname);
#endif
_nextc(); /* re-init character input */
return 1; /* Say success */
}
#if DEBUG
pmacframe(lev)
{
SYMBOL *s;
int i;
char *cp;
fprintf(stderr,"\tlevel = %d%s\n", lev, lev==maclevel?" (maclevel)":"");
cp = (lev == maclevel ? macptr : mac[lev].mptr);
fprintf(stderr,"\tcurrent body ptr %s= %o\n",
lev==maclevel?"(macptr)":"(.mptr)", cp);
if (cp)
fprintf(stderr,"\tremaining body: \"%s\"\n", cp);
if ((s = mac[lev].msym) == NULL)
fprintf(stderr,"\tNo macro symbol.\n");
else {
fprintf(stderr,"\tMacro sym: \"%s\", args %d\n",
s->Sname, s->Svalue);
for (i = 0; i < s->Svalue; ++i)
fprintf(stderr,"\tArg %d: \"%s\"\n", i+i, mac[lev].marg[i]);
}
}
#endif
static int
getargs(tabptr, maxsto, nexpected)
char **tabptr; /* Pointer to array of char ptrs */
int maxsto; /* Max # of args to store in array */
int nexpected; /* # of args we expect to parse */
{
int nargs; /* # args parsed */
int i, plev;
skipblanks(); /* Allow whitespace between name and arglist */
if (ch != '(') { /* any args there? */
pushc(ch); /* No, put char back for another try */
return(MACF_ATOM); /* and say no arglist at all. */
}
/* Have arg list, check for having no args */
_nextc(); /* Get next char */
if (skipblanks() == ')') /* Check out 1st non-wsp char */
return (0); /* Null arg list, return 0 as # of args */
else pushc(ch); /* Oops, push back so arg loop will see it */
/* Have args, set up to record the args in the macro arg char pool */
if(maclevel == 0 /* If at top level, or */
|| macpool == NULL) { /* not initialized yet, we can */
macpool = _macpool; /* re-initialize, to re-use space! */
macpleft = MACPOOLSIZE; /* Reset countdown of chars left */
}
if ((nargs = 1) <= maxsto)
*tabptr = macpool; /* Set pointer for 1st arg */
/* Now loop, parsing args */
plev = 1; /* Start with 1 left-paren already seen */
while (plev) {
_nextc(); /* Get next char */
trimblanks(); /* Reduce any whitespace to single space */
if(isceol(ch)) {
/* Handle newline in arg list scan.
** Standard def of C seems to prohibit these, but we allow
** them by changing into a space. 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(EGEN,"Missing ')' in macro arg list");
plev = 0; /* pretend we saw top-level paren. */
putmacpool('\0');
}
else putmacpool(' '); /* Substitute space for EOL */
continue;
}
switch (ch) {
case EOF: break;
case '\'': /* Pass character constant */
putmacpool('\'');
if(_nextc() == '\\') { /* Char escape always gets next char */
putmacpool('\\');
if (_nextc() != EOF) {
putmacpool(ch);
_nextc();
}
}
/* Read at most 3 chars for constant */
for (i = 4; !eof && --i > 0; _nextc()) {
putmacpool(ch); /* Store a char of constant */
if (ch == '\'') break; /* Stop when stored end. */
}
/* If didn't end in quotemark, just pass on to lexer
** and let it do the complaining.
*/
break;
case '"': /* Pass string constant */
do {
if (eof) break;
if (ch == '\n') {
error (EGEN, "Unexpected newline in string constant");
break; /* Break out of loop, store fake dbl-quote */
}
if (putmacpool(ch) == '\\')
putmacpool(_nextc());
} while (_nextc() != '"');
if (!eof) putmacpool('"'); /* add close quote */
break;
case ',': /* comma splits arg unless protected */
if (plev > 1) putmacpool(ch); /* by paren */
else {
putmacpool('\0');
if(++nargs <= maxsto) /* Only set ptr if we know */
*++tabptr = macpool; /* we can handle more args */
}
break;
case ')': /* close paren counts for balancing, */
if (--plev) putmacpool(ch);
else putmacpool('\0'); /* and may close arg */
break;
case '(': /* open paren counts for balancing */
plev++; /* and is always included in arg */
default: /* other chars are simply included */
putmacpool(ch);
} /* End of switch */
if (eof) { /* don't run off end of world */
error (EEOF,"during macro arg parsing");
break; /* Get out of loop */
}
} /* End of loop */
return(nargs); /* Return # of args parsed into array */
}
/* FINDMACSYM - Find macro name symbol if one exists
*/
static SYMBOL *
findmacsym(id)
char *id;
{ SYMBOL *macsym;
if (macsym = findmsym(id)) { /* Note NOT using findsym()! */
if (macsym->Sclass == SC_MACRO) /* Found symbol, see if macro */
return macsym; /* Yup, return the pointer */
else --(macsym->Srefs); /* Compensate for spurious findmsym ref */
}
return NULL;
}
/* -------------------------------------------------------------------- */
/* skipcomm() - Skips over comments, starting with current char. */
/* If no comment, skips nothing & returns the current char. */
/* If comment, skips it and returns space as current char. */
/* -------------------------------------------------------------------- */
static int
skipcomm()
{
if (ch != '/') return ch; /* not slash, normal char */
if (_nextc() != '*') { /* is it comment? */
pushc(ch); /* no, put it back */
macpush("/"); /* Allow a pushc of the '/' */
return _nextc(); /* get slash again */
}
_nextc(); /* skip over star */
if (!keepcmts || (!prepf && !inasm)) {
/* Normal case - simple fast comment skip */
int och = ch;
while (1) {
if (_nextc() == '/') {
if (och == '*') break; /* Found end of comment, stop */
if (_nextc() == '*' && !flushing)
warn(EGEN,"Nested comment");
}
if((och = ch) == EOF) {
error(EEOF,"in comment");
return(ch);
}
}
return (ch = ' '); /* Return space as substitute for comment */
}
if (prepf) {
/* Comment skip for -E when retaining comments (-C) */
fputs ("/*", stdout); /* pass comment start */
while (1) { /* until we break */
putc (ch, stdout); /* send char off */
if (ch != '*') _nextc(); /* find a star */
else if (_nextc() == '/') break; /* and a slash to terminate */
if (eof) {
error(EEOF,"in comment"); /* break out of inf loop */
return(ch);
}
}
putc ('/', stdout); /* finish -E comments */
return ch = ' ';
}
/* Else within #asm passthrough.
** This stuff isn't very well thought out and should probably
** be entirely flushed someday.
*/
#if 0
if (ch == '\n') { /* multi-line #asm comment */
fputs ("\n\tCOMMENT \\", out); /* start it */
while (1) { /* until we break */
if (ch != '*') {
if (ch != '\\') putc (ch, out);
_nextc();
} else {
if (_nextc() == '/') break;
putc ('*', out);
}
if (eof) {
error(EEOF,"in comment"); /* break out of inf loop */
return(ch);
}
}
fputs ("\t\\\n", out); /* finish it */
return ' ';
}
#endif /* flushed stuff */
/* comment skip for #asm */
putc (';', out); /* start it */
while (1) { /* until we break */
if (ch != '*') {
putc (ch, out); /* send comment char */
if (ch == '\n') putc (';', out); /* continue comment */
_nextc();
} else {
if (_nextc() == '/') break; /* star slash exits */
putc ('*', out); /* otherwise pass star */
}
if (eof) {
error(EEOF,"in comment"); /* break out of inf loop */
return(ch);
}
}
if (_nextc() != '\n') { /* if not end of line */
putc ('\n', out); /* then have to terminate here */
pushc(ch);
ch = ' ';
}
return ch;
}
/* ----------------------------------------------------- */
/* push a character back into the input source */
/* ----------------------------------------------------- */
void
pushc(c)
{
if (maclevel) {
macptr--; /* macro input, back up over it */
if(c != *macptr)
error(EINT,"bad backup char");
return;
}
/* File input, back up over that. */
/* Note that errlin and t/line are only fixed up for file input, since
** macro input does not modify them. Perhaps someday macro input should
** be stored in errlin as well.
*/
if (c != ungetc(c, in))
error(EINT, "ungetc of %o failed", c);
if (isceol(c)) {
line--; /* dont lose track of line count */
tline--;
} else /* back over char in error line */
if (erpleft > 0 /* Make sure something was deposited */
&& erpleft < ERRLSIZE) /* and not at start of line */
erpleft++, erptr--;
}
/* ------------------------------------------ */
/* service a # preprocessor command */
/* ------------------------------------------ */
static void
preprocess()
{
int unknown;
char s[_IDSIZE];
_nextc(); /* Skip the #, get next char */
skipblanks(); /* Flush whitespace */
if (isceol(ch)) return; /* ignore if nothing else on line */
unknown = 0; /* Assume ident will be ok */
switch (getidstr(s)) { /* read in the ident, check length */
/* 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".
*/
case 0:
if (!flushing) error(EIDENT); /* complain if not identifier */
break; /* Don't set unknown, already complained */
case 2:
if (!strcmp(s,"if")) cif();
else unknown++;
break;
case 3: /* #asm only supported if KCC extensions OK */
if (!strcmp(s,"asm") && clevkcc) { if(!flushing) casm(); }
else unknown++;
break;
case 4:
if (!strcmp(s,"else")) celse();
else if (!strcmp(s,"elif") && clevel >= CLEV_CARM) celif();
else if (!strcmp(s,"line")) { if(!flushing) cline(); }
else unknown++;
break;
case 5:
if (!strcmp(s,"endif")) cendif();
else if (!strcmp(s,"ifdef")) cifdef(1);
else if (!strcmp(s,"undef")) { if (!flushing) cundef(); }
else unknown++;
break;
case 6: /* #endasm only supported if KCC extensions OK */
if (!strcmp(s,"ifndef")) cifdef(0);
else if (!strcmp(s,"endasm") && clevkcc) {if(!flushing)cendasm();}
else if (!strcmp(s,"define")) { if (!flushing) cdefine(); }
else unknown++;
break;
case 7:
if (!strcmp(s,"include")) { if (!flushing) cinclude(); }
else unknown++;
break;
default:
unknown++;
}
skiptoeol();
if (unknown && !flushing) /* If unknown, complain after the skiptoeol */
error(EGEN, "Unsupported preprocessor command: \"%s\"", s);
/* so user will see entire line of context. */
}
/*
** Process preliminary #undefs and #defines requested by -U and -D
*/
void
pdefine(unum, utab, dnum, dtab)
int unum, dnum; /* # of strings in table */
char **utab, **dtab; /* Address of char-pointer array */
{
int i, reeq;
char *cp;
SYMBOL *sym;
/* First handle all undefinitions... */
for ( ; --unum >= 0; utab++) {
cp = *utab; /* Get pointer to -U arg (ident string) */
if (!iscsymf(*cp)) /* Must be an ident */
fatal(EGEN,"Bad syntax for -U macro name: \"%s\"",cp);
do cp++; while (iscsym(*cp)); /* Skip over ident name */
if (*cp != '\0') /* Name should be all there is */
fatal(EGEN,"Bad syntax for -U macro name: \"%s\"",cp);
if (sym = findmacsym(*utab)) /* Look up the macro symbol */
freemacsym(sym); /* Found it, flush it! */
else
warn(EGEN,"Macro in -U%s doesn't exist.", *utab);
}
/* Now handle all -D definitions. */
for ( ; --dnum >= 0; dtab++) {
cp = *dtab; /* get this -D string */
reeq = 0; /* assume no definition string */
if (!iscsymf(*cp)) /* Must be an ident */
fatal(EGEN,"Bad syntax for -D macro name: \"%s\"",cp);
do cp++; while (iscsym(*cp)); /* Skip over ident name */
if (*cp == '\0') cp = "1"; /* -D without =, use 1 */
else if (*cp != '=')
fatal(EGEN,"Bad syntax for -D macro def: \"%s\"", *dtab);
else {
*cp++ = '\0'; /* terminate and move on */
reeq = 1; /* have to put = back later */
}
/* Now the identifier is null-terminated, and we
** have the definition for it pointed to by cp.
** Define the symbol as a macro without an arg list.
*/
defmacsym(*dtab, MACF_ATOM, cp, strlen(cp)); /* Define the macro! */
if (reeq) *--cp = '='; /* Put back equal sign if zapped it */
}
}
/* --------------------------------- */
/* #define preprocessor command */
/* --------------------------------- */
static void
cdefine()
{
SYMBOL *sym;
int i;
char *defptr, *args[MAXMARG];
int nargs; /* # args in new macro */
char name[_IDSIZE]; /* Name of new macro */
char *bodyptr; /* Pointer to new macro body */
char defstr[MAXMAC]; /* Temp string storage for body and args */
/* Must be last in case of overflow (ugh) */
skipblanks(); /* Move to first non-whitespace char */
if (!getidstr(name)) { /* Read macro name */
error(EIDENT); /* complain if nothing. */
return;
}
/* look for arguments */
defptr = defstr; /* point to start of string */
nargs = MACF_ATOM; /* Initially assume no arg list */
if (ch == '(') {
nargs = 0; /* Have arg list */
_nextc(); /* Skip over (, get next char */
while (1) {
skipblanks(); /* Skip over whitespace */
if (ch == ')')
break;
if (!getidstr(defptr)) { /* Try to read an arg ident */
error(EGEN, "Macro formal parameter must be identifier");
nargs = 0; /* Ugh! Don't try to hack args on */
/* expansion, but remember that */
break; /* an arglist exists. */
}
if (nargs >= MAXMARG - 1)
error(EGEN,"More than %d args in macro definition of \"%s\"",
MAXMARG, name);
else {
/* Have arg, store it. Really should also check for
** duplicate arg names (yes it happens)
*/
args[nargs] = defptr; /* Remember the arg name */
for (i = 0; i < nargs; i++) /* Scan to find dups */
if (!strcmp(args[i], defptr)) {
error(EGEN, "Duplicate formal params in macro def");
break;
}
nargs++;
}
while (*defptr++ != '\0') ; /* skip past null at end */
skipblanks(); /* Ensure current char not whitesp */
if (ch == ',') _nextc(); /* If comma, continue loop */
else break;
}
if (ch != ')')
error(EGEN, "Close paren needed to end formal parameter list");
else _nextc();
}
/* Arguments read, now read body of macro.
** This code needs some work in order to handle char and string constants
** properly. However, cannot readily do this without giving up the
** ability to recognize macro parameters within them.
*/
bodyptr = defptr; /* Remember start of macro body */
skipblanks(); /* Ensure current char not whitesp */
while (!eof && !isceol(ch)) {
trimblanks(); /* Reduce any whitespace/comments to 1 space */
if (nargs && iscsymf(ch)) { /* ident that can be an arg? */
getidrest (defptr); /* yes, read in rest of identifier */
for (i = 0; i < nargs; i++) /* Scan to see if it's an arg */
if (!strcmp(args[i], defptr))
break;
if (i < nargs) { /* If found arg, insert macro arg escape */
*defptr++ = MAC_ESC; /* Start with escape char */
*defptr++ = MAC_ARG; /* then indicate arg spec follows */
*defptr++ = i + 1; /* Furnish argument # (1-based) */
} else
while (*defptr != '\0') /* No match, skip to keep ident */
defptr++; /* as it is. */
} else {
/* Not an ident char, just stick in body */
if (ch == MAC_ESC) /* If escape char seen in body, */
*defptr++ = ch; /* double it to quote it. */
*defptr++ = ch;
_nextc();
}
}
*defptr = '\0';
/*
** Now defptr points to the null at the end of the string, and bodyptr
** points to the beginning of the string, but they both point to defstr
** which is on the stack. We need to rearrange these to point to allocated
** memory instead.
**
** We take this opportunity to make sure we haven't overflowed defstr.
** If we have, we lose because the next function call (eg to malloc or
** error) will smash the string. So we truncate it and bravely carry on.
*/
if (defptr >= defstr + MAXMAC) {
error(EGEN,"More than %d chars in macro definition of \"%s\"",
MAXMAC, name);
defstr[MAXMAC-1] = '\0'; /* Pitiful band-aid */
}
/* 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 (nargs == sym->Svalue /* Args and body must match */
&& (bodyptr == sym->Smacptr /* OK if both bodies null */
|| (bodyptr && sym->Smacptr /* else must both exist & match */
&& (strcmp(bodyptr, sym->Smacptr) == 0))))
return; /* Identical, no need to do anything! */
if (sym->Svalue < MACF_ATOM) { /* Check for specialness */
error(EGEN, "Illegal to redefine \"%s\"", name);
return;
}
warn(EGEN, "Macro redefined: \"%s\"", name);
freemacsym(sym); /* Flush it completely */
}
/* Now define the macro! */
defmacsym(name, nargs, bodyptr, defptr-bodyptr);
}
/* DEFMACSYM - auxiliary that actually creates the macro symbol and
** sets its definition. In particular, it copies the body string
** (if one was given) into a malloc'd string.
*/
static SYMBOL *
defmacsym(name, nargs, body, blen)
char *name; /* Macro symbol name */
int nargs; /* # args (or special MACF_ type) */
char *body; /* If not NULL, body of macro */
int blen; /* # chars in macro body */
{
register SYMBOL *s;
s = creatgsym(name); /* Create global symbol */
s->Sclass = SC_MACRO; /* Say it's a macro */
s->Sflags |= SF_MACRO; /* Say ditto, with flag. */
s->Svalue = nargs; /* Set # args (or MACF_ type) */
if (body == NULL)
s->Smacptr = NULL;
else {
if ((s->Smacptr = malloc(blen+1)) == NULL)
efatal(EOUTMEM); /* Out of memory trying to store macro body */
strcpy(s->Smacptr, body); /* Copy macro body there! */
}
return s; /* Return pointer to defined macro */
}
/* ---------------------------------------- */
/* #undefine preprocessor command */
/* ---------------------------------------- */
static void
cundef()
{
char s[_IDSIZE];
SYMBOL *sym;
skipblanks(); /* Ensure current char not whitespace */
if (!getidstr(s)) {
error(EIDENT); /* Bad identifier */
return;
}
*defcsname = 'd'; /* Allow "defined" to be found */
sym = findmacsym(s);
*defcsname = SPC_MACDEF; /* Hide "defined" again */
if (sym != NULL) { /* If already defined, */
if (sym->Svalue >= MACF_ATOM) /* Check for specialness */
freemacsym(sym); /* OK to flush it, do so. */
else error(EGEN, "Illegal to undefine \"%s\"", s);
}
}
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. */
}
/*
** #asm and #endasm
**
** #asm passes text through to the FAIL file until ended by a #endasm.
** Maybe someday this will be useful inside functions.
*/
#define TASM_STR "(in #asm)" /* Str appended to current fname for fakery */
static void
casm()
{
FILE *fp;
extern char *mktemp();
static char *tmpfname = NULL;
char fakename[_FILSIZ+sizeof(TASM_STR)];
skiptoeol(); /* ignore rest of line */
if (flushing) return; /* inside failing #ifdef, stop now */
if (inasm) { /* bump level. if nested, complain */
error(EGEN, "Already in #asm, can't nest");
return;
}
inasm = 1; /* Say processing assembler input */
if (prepf) fp = prepfp;
else {
if (!tmpfname && !(tmpfname = "asmtmp.tmp;t")) {
error(EINT,"cannot invent temporary file for #asm");
return;
}
if (!(fp = fopen(tmpfname, "w"))) {
error(EINT, "cannot open temporary #asm file \"%s\"", tmpfname);
return;
}
}
/* Now scan input text, converting from assembler format into
** suitable string literal format for asm() argument.
** Current char is an EOL of some kind by virtue of skiptoeol().
*/
fputs("asm(\"", fp); /* Deposit initial string */
nextc();
while (inasm) {
if(iscsymf(ch)) { /* If identifier, check for macro */
pass_ident(fp);
continue;
}
if (ch == EOF) {
error(EGEN,"EOF within unterminated #asm");
break;
}
switch(ch) {
case '"': fputs("\\\"", fp); break;
case '\\': fputs("\\\\", fp); break;
case '\n': fputs("\\n\\\n", fp); break;
#if 0
/* Removed because it causes problems for code with embedded
** semicolons, such as
** MOVE 1,[ASCIZ "FOO.TXT;P770000"]
*/
case ';': /* Flush asm comments */
if (!keepcmts) { /* if not keeping them. */
while (_nextc() != '\n' && !eof); /* Scan to EOL */
continue; /* Then handle term char */
} /* Else drop thru as for normal char */
#endif
default: putc(ch, fp);
}
nextc();
}
fputs("\");\n", fp); /* Finally wrap up */
if (!prepf) {
fclose(fp);
if (!(fp = fopen(tmpfname,"r"))) {
error(EINT, "cannot re-open temporary #asm file \"%s\"", tmpfname);
return;
}
filepush(fp, strcat(strcpy(fakename,inpfname),TASM_STR), line, page);
}
}
static void
cendasm()
{
skiptoeol(); /* ignore rest of line */
if (flushing) return; /* inside failing #ifdef, stop now */
if (inasm == 0) /* If not inside #asm, complain */
error(EGEN,"Not in #asm, ignoring #endasm");
else inasm = 0; /* Tell casm() to stop */
}
/*
** Pass through text to file
** Used only by #asm and by -E (i.e. either inasm or prepf is set)
**
*/
void
passthru (fp)
FILE *fp;
{
SYMBOL *sym;
char ident[_IDSIZE];
int i;
prepfp = fp; /* Set for benefit of casm() */
/* skip newlines (from definition files with -E) */
while (isceol(ch)) nextc();
/* now pass rest through to output */
while (1) { /* Until hit EOF */
if(iscsymf(ch)) { /* If start of identifier, check for macro */
pass_ident(fp);
continue;
}
if (isdigit(ch)) { /* If start of a constant, */
do putc(ch, fp); /* pass it all. */
while (iscsym(_nextc()));
continue;
}
/* Not an identifier char, check for other cases */
switch (ch) {
case EOF:
return;
/*
** 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 escape character.
*/
case '\'':
/* Gobble and pass string, with max length of 4 */
if (!pass_str(fp, ch, 4)) {
error(EEOF,"within char constant");
return;
}
break; /* fall through to pass on last char */
case '`': /* Quoted identifier (maybe) */
if (clevkcc /* If KCC extensions allowed, */
&& !pass_str(fp, ch, 0)) { /* parse like string! */
error(EEOF,"within quoted identifier");
return;
}
break; /* else treat like normal char */
case '"': /* Quoted string */
if (!pass_str(fp, ch, 0)) {
error(EEOF,"within string literal");
return;
}
break; /* fall through to pass on last char */
}
putc(ch, fp); /* send normal char or closing delim */
nextc(); /* move on to next char from file */
} /* End of loop */
}
/* PASS_IDENT - auxiliary for passthru() and casm() 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(fp)
FILE *fp;
{
char ident[_IDSIZE];
SYMBOL *sym;
getidrest(ident); /* Gobble all of ident */
if ((sym = findmacsym(ident)) == NULL /* If not a macro, */
|| expmacsym(sym) == 0) /* or cannot expand it, */
fputs(ident, fp); /* just output the identifier. */
}
/* 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 int
pass_str(fp, delim, cnt)
FILE *fp; /* Output file pointer */
int delim; /* Delimiter char */
int cnt; /* If non-zero, max # chars of string to read */
{
do {
if (cnt && --cnt <= 0) break; /* Halt loop if too long */
if (eof) /* don't run off end of file */
return 0;
putc(ch, fp); /* send start delim or text */
if (ch == '\\') putc(_nextc(), fp); /* handle backslash */
} while (_nextc() != delim); /* until string done */
return 1;
}
#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
/* -------------------------------- */
/* #ifdef and #ifndef commands */
/* -------------------------------- */
static void
cifdef(cond)
int cond; /* 1 == ifdef, 0 == ifndef */
{
char id[_IDSIZE]; /* buffer for argument */
if (++iflevel >= MAXIFLEVEL-1) { /* this is a new if level */
error(EGEN,"#if nesting depth exceeded");
--iflevel;
}
iftype[iflevel] = IN_IF; /* Say IF seen for this level */
if (flushing) return; /* if in false condition, that's all */
skipblanks(); /* skip over blanks */
if (getidstr(id)) {
if (cond == ((findmacsym(id) != NULL)))
return; /* good condition, return */
} else error (EIDENT);
flushcond(); /* Failed, flush body */
}
/* --------------------------------------------- */
/* #if preprocessor command */
/* --------------------------------------------- */
static void
cif()
{
int yes;
if (++iflevel >= MAXIFLEVEL-1) { /* this is a new if level */
error(EGEN,"#if nesting depth exceeded");
--iflevel;
}
iftype[iflevel] = IN_IF; /* Say IF seen for this level. */
if (flushing) return; /* If already flushing, that's all. */
if (!iftest()) /* Do the test. If fail, */
flushcond(); /* then flush body. */
}
/* --------------------------------- */
/* #elif preprocessor command */
/* --------------------------------- */
static void
celif()
{
/* Make sure an #if or #elif preceeded this #elif */
if (iftype[iflevel] == IN_ELSE) { /* If not IN_IF or IN_ELIF */
error(EGEN, "#elif without preceding #if");
cif(); /* Handle as if plain #if */
return;
}
/* 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. */
} /* Else, stay in-if and keep flushing. */
return;
}
/* 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 */
if (flushing) return; /* If already flushing, just keep flushing. */
flushcond(); /* No, so start the flush now. */
}
/* --------------------------------- */
/* #else preprocessor command */
/* --------------------------------- */
static void
celse()
{
int prev = iftype[iflevel]; /* Remember current level type */
if (prev == IN_ELSE) { /* Not IN_IF or IN_ELIF */
error(EGEN, "#else without preceding #if");
if (iflevel == 0) { /* If not already inside a conditional */
prev = iftype[++iflevel] = IN_IF; /* Pretend we were in an #if */
flushing = 0; /* that was winning. */
}
}
iftype[iflevel] = IN_ELSE; /* Whatever, say #if no longer last thing */
/* 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) flushcond(); /* Start flushing if not */
else if (flushing == iflevel && prev != IN_ELIF)
flushing = 0; /* Stop flushing! */
/* Else just keep flushing. */
}
/* --------------------------------- */
/* #endif preprocessor command */
/* --------------------------------- */
static void
cendif()
{
iftype[iflevel] = 0; /* Just for neatness */
if (iflevel) { /* Are we in a conditional? */
if (iflevel == flushing) flushing = 0; /* stop flushing */
iflevel--; /* drop a level */
} else
error(EGEN, "Unmatched #endif");
}
/* FLUSHCOND - Auxiliary for if, elif, else.
** Flush the body of a failing conditional command.
** 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 */
do {
skiptoeol(); /* Flush all of current line */
nextc(); /* Get next char, handling any preproc cmds */
} while (flushing && !eof); /* If still flushing, keep going. */
}
/* IFTEST - auxiliary for #if and #elif.
** Parses the conditional expression and returns its value.
*/
static int
iftest()
{
int val;
if (isceol(ch)) { /* Make sure we have something on line */
error(ECONST); /* Nothing, barf about it */
return 0; /* and pretend we failed */
}
/* Set up to parse a one-line constant expression */
eolstop = ";"; /* Read only one line, stop parser with ';' */
*defcsname = 'd'; /* Restore proper 1st char to "defined" sym */
nextoken(); /* start up token parser again */
val = pconst(); /* parse constant expression */
*defcsname = SPC_MACDEF; /* Re-Zap 1st char of "defined" macro sym */
if (eolstop) { /* If flag still set, EOL wasn't found */
eolstop = NULL;
error(ECONST); /* complain constant needed */
return 0; /* and return false. */
}
return val; /* Return expression value */
}
/* ------------------------ */
/* #include macro */
/* ------------------------ */
/* This needs to be fixed to take the current file's directory
* into account; technically the search should begin there for '"' files,
* rather than in the connected directory.
*/
static void
cinclude()
{
char f[_FILSIZ], f2[_FILSIZ], *winf;
FILE *fp, *fopen();
int ftype;
skipblanks(); /* Ensure current char not whitesp */
if ((ftype = getfile(f)) == 0)
return; /* If failed to get name, ignore */
/* Now distinguish between "" and <> filespecs */
winf = f2; /* Assuming winning filename will be here */
if(ftype != '>') { /* Wants standard-place directory? */
/* No, file specified with "file" instead of <file>. Must look in
** several places.
*/
if (fp = fopen(f, "r")) /* First try to open as is */
winf = f; /* Won! This is winning filename */
else { /* Unsuccessful open, try others */
int i;
for(i = 0; i < nincdirs; ++i) {
strcpy(f2, incdirs[i]); /* Get prefix */
strcat(f2, f); /* Add file name */
strcat(f2, postincdirs[i]); /* Add postfix */
if ((fp = fopen(f2, "r")) != NULL)
break; /* Won, get out of loop */
}
}
} else fp = NULL; /* <> forces invoke of standard-place stuff */
if(fp == NULL) { /* Wants standard-place directory? */
strcpy(f2, sprefix);
strcat(f2, f);
strcat(f2, spstfix);
if ((fp = fopen(f2, "r")) == NULL) {
error(EGEN, "Can't open include file %s",
ftype=='>' ? f2 : f); /* Not there, complain */
return; /* and give up after one shot. */
}
}
filepush(fp, winf, 1, 1); /* Push to new input file */
}
/* FILEPUSH - auxiliary for #include and #asm
** Similar to macpush(), but for file input rather than string input.
*/
static void
filepush(fp, fname, lin, pag)
FILE *fp;
char *fname;
int lin, pag;
{
if (level >= MAXINCLNEST-1) { /* Make sure we have room */
error(EGEN,"Include file nesting depth exceeded -- ignoring %s",fname);
return; /* Just ignore it if not */
}
strcpy(inc[level].cname, inpfname); /* save old context */
inc[level].cptr = in;
inc[level].cline = line;
inc[level].cpage = page;
level++; /* create new context */
strcpy(inpfname, fname); /* remember winning file name */
in = fp; /* this is current input stream */
line = lin;
page = pag;
}
/* ------------------------------- */
/* #line preprocessor command */
/* ------------------------------- */
/* Note macro substitution is performed on the input. */
static void
cline()
{
char *s;
char fname[_FILSIZ];
if (isceol(ch)) /* If that's all there is, */
return; /* just return without any fuss. */
eolstop = " "; /* Read only 1 line, stop tokenizer with wsp */
nextoken();
eolstop = NULL; /* Ensure flag is reset */
if (token != T_LCONST /* Must be literal constant */
|| !tisinteg(constant.ctype)) { /* of integral type */
error(ECONST);
skiptoeol(); /* Flush rest of line */
} else {
line = constant.cvalue - 1; /* OK, set line number */
skipblanks(); /* Ensure current char not whitesp */
if (!isceol(ch) /* If filename specified, */
&& getfile(fname))
strcpy(inpfname, fname); /* zap our input filename with it */
}
}
/* ------------------------------------------------------------ */
/* skipblanks() - skip whitespace, starting with current char */
/* Skip to non-whitespace. Returns new */
/* current char, which will not be whitespace. */
/* ------------------------------------------------------------ */
/* This implements the notion of preprocessor whitespace,
* which is not the same as C whitespace. Comments, blanks, and tabs
* are flushed, and the new current char will be a non-whitespace char.
*/
static int
skipblanks()
{
while (skipcomm() == ' ' || ch == '\t')
_nextc();
return(ch);
}
/* trimblanks() - reduce any stretch of whitespace, including
** comments, to a single space left as the current char.
*/
static void
trimblanks()
{
if (wsptest()) /* If at start of whitespace */
if (skipblanks() != '/') { /* flush it, check 1st non-wsp */
pushc(ch); /* then push back 1st non-wsp */
ch = ' '; /* and substitute single space */
/* for entire stretch of wsp. */
} else { /* But if '/' then must do special "push" */
macpush(" /"); /* since pushc() was already called while */
_nextc(); /* examining next char for commentness! */
}
}
/* wsptest() - TRUE if current char is start of some whitespace */
static int
wsptest()
{
if (ch == ' ' || ch == '\t') return(1);
if (ch == '/') {
int nch;
nch = _nextc(); /* Peek at next char */
pushc(nch); /* push it back */
ch = '/'; /* to effect the "peek" */
if (nch == '*')
return 1;
}
return 0;
}
/* ------------------------------------------------------------ */
/* skiptoeol() - ensure that current char is a line-break or EOF. */
/* Skips to EOL, flushing any comments. */
/* Does not return current char. */
/* ------------------------------------------------------------ */
static void
skiptoeol()
{
while (skipcomm() != EOF && !isceol(ch)) _nextc();
}
/* GETIDSTR - get an identifier string from input source.
** GETIDREST - ditto when current char known to be OK.
** Both return # of chars read in the identifier.
** Note that this code is identical to that in CCLEX's getident(), except
** for the use of _nextc() instead of nextc().
*/
static int
getidstr(s)
char *s;
{
return(iscsymf(ch) ? getidrest(s) : 0);
}
static int
getidrest(str)
char *str;
{
char *s = str;
int i = _IDSIZE-1; /* Leave room for null terminator */
*s = ch; /* First char always goes in */
while (iscsym(_nextc())) /* If succeeding char is alphanum */
if (--i > 0) *++s = ch; /* is legal, add to ident */
*++s = '\0'; /* null terminate */
if (i <= 0) {
warn(EGEN, "Identifier truncated: \"%s\"", str);
i = 1; /* Set i so return value is correct */
}
return _IDSIZE - i; /* Return number of chars in string */
}
/* -------------------------------------------------- */
/* getfile(str) - Read file name as for #include */
/* Returns 0 if no name was read, */
/* otherwise '"' for "file" and '>' for <file>. */
/* -------------------------------------------------- */
static int
getfile(f)
char *f;
{
int i, type, gotdel;
char *s, *t, *last;
char name[_IDSIZE];
SYMBOL *sym;
s = f; /* copy pointer */
switch (skipblanks()) {
case '"':
type = '"';
break;
case '<':
type = '>';
break;
default: /* Neither one, it better be a macro name */
if (isceol(ch)) {
error(EGEN, "Filename expected");
return(0);
}
if (getidstr(name) /* Must be identifier */
&& (sym = findmacsym(name)) /* which is a macro */
&& expmacsym(sym) == 1) /* that can be expanded */
return getfile(f); /* in order to win! */
error(EGEN,"Filename not enclosed by \"\" or <>");
return(0);
}
/* Got first delimiter char, try to find terminating one.
** Algorithm is to look for delimiter followed by whitespace
** or comment, which is then flushed. If not then at EOL (ie
** some more non-whitespace exists on the line) then we give up
** and complain.
*/
_nextc(); /* Skip over 1st delimiter */
i = _FILSIZ;
while(--i > 0) {
int nch, och;
if(isceol(ch) || eof) {
error(EGEN,"Filename not enclosed by \"\" or <>");
type = 0; /* Failed */
break;
}
if (ch != type) {
*s++ = ch; /* Nothing special, store char */
_nextc();
continue;
}
/* Char is delimiter, see if want to break */
if (isceol(_nextc()))
break; /* Followed by EOL, won! */
if (wsptest()) { /* If followed by whitespace */
if (!isceol(skipblanks())) {
type = 0; /* Error, wsp not followed by EOL */
error(EGEN,"Junk seen following filename");
}
break; /* Stop loop either way */
}
*s++ = type; /* Not followed by wsp, so just store it */
}
if (i <= 0) {
error(EGEN, "Filename too long");
type = 0;
}
*s = 0;
skiptoeol();
return(type);
}