/* * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands. * See the copyright notice in the ACK home directory, in the file "Copyright". */ /* $Id$ */ /* M A C R O R E P L A C E M E N T */ #include #include #include "nopp.h" #ifndef NOPP #include "debug.h" #include "pathlength.h" #include "strsize.h" #include "nparams.h" #include "idfsize.h" #include "numsize.h" #include "arith.h" #include #include "idf.h" #include "idf_loc.h" #include "input.h" #include "macro.h" #include "LLlex.h" #include "class.h" #include "assert.h" #include "static.h" #include "macbuf.h" #include "replace.h" #include "replace_loc.h" #include "error.h" #include "skip.h" #include "domacro.h" #include "replace_loc.h" extern int InputLevel; struct repl *ReplaceList; /* list of currently active macros */ int replace(struct idf *idf) { /* replace is called by the lexical analyzer to perform macro replacement. The routine actualy functions as a higher interface to the real thing: expand_macro(). */ struct repl *repl; if (!(idf->id_macro)) return 0; if (idf->id_macro->mc_flag & NOREPLACE) return 0; repl = new_repl(); repl->r_ptr = repl->r_text = Malloc(repl->r_size = LAPBUF); repl->r_args = new_args(); repl->r_idf = idf; if (!expand_macro(repl, idf)) return 0; InputLevel++; InsertText(repl->r_text, (int)(repl->r_ptr - repl->r_text)); idf->id_macro->mc_flag |= NOREPLACE; repl->r_level = InputLevel; repl->next = ReplaceList; ReplaceList = repl; return 1; } void unstackrepl() { Unstacked++; } void freeargs(struct args *args) { int i; /* We must don't know how many parameters were specified, so be * prepared to free all NPARAMS parameters. * When an expvec is !0, the rawvec will also be !0. * When an expvec is 0, all remaining vectors will also be 0. */ for (i = 0; i < NPARAMS; i++) { if (args->a_expvec[i]) { free(args->a_expvec[i]); free(args->a_rawvec[i]); } else break; } free_args(args); } void EnableMacros() { struct repl *r = ReplaceList, *prev = 0; ASSERT(Unstacked > 0); while(r) { struct repl *nxt = r->next; if (r->r_level > InputLevel) { r->r_idf->id_macro->mc_flag &= ~NOREPLACE; if (!prev) ReplaceList = nxt; else prev->next = nxt; free(r->r_text); freeargs(r->r_args); free_repl(r); } else prev = r; r = nxt; } Unstacked = 0; } int expand_macro(struct repl *repl, struct idf *idf) { /* expand_macro() does the actual macro replacement. "idf" is a description of the identifier which caused the replacement. If the identifier represents a function-like macro call, the number of actual parameters is checked against the number of formal parameters. Note that in ANSI C the parameters are expanded first; this is done by calling getactuals(). When the possible parameters are expanded, the replace- ment list associated with "idf" is expanded. expand_macro() returns 1 if the replacement succeeded and 0 if some error occurred. A special case is "defined". This acts as a unary operator on a single, unexpanded identifier, which may be surrounded by parenthesis. The function expand_defined() handles this. */ struct macro *mac = idf->id_macro; struct args *args = repl->r_args; int ch; if (mac->mc_nps != -1) { /* with parameter list */ if (mac->mc_flag & FUNC) { /* the following assertion won't compile: ASSERT(!strcmp("defined", idf->id_text)); expand the assert macro by hand (??? dirty, temporary) */ #ifdef DEBUG if (strcmp("defined", idf->id_text)) crash("in %s, %u: assertion %s failed", __FILE__, __LINE__ - 2, "strcmp(\"defined\", idf->id_text)"); #endif if (!AccDefined) return 0; expand_defined(repl); return 1; } ch = GetChar(); ch = skipspaces(ch,1); if (ch != '(') { /* no replacement if no () */ ChPushBack(ch); return 0; } else getactuals(repl, idf); } if (mac->mc_flag & FUNC) /* this macro leads to special action */ macro_func(idf); macro2buffer(repl, idf, args); /* According to the ANSI definition: #define a + a+b; --> + + b ; 'a' must be substituded, but the result should be three tokens: + + ID. Therefore a token separator is inserted after the replacement. */ if (repl->r_text == repl->r_ptr || *(repl->r_ptr - 1) != TOKSEP) { add2repl(repl, TOKSEP); } return 1; } void expand_defined(struct repl *repl) { int ch = GetChar(); struct idf *id; int parens = 0; ch = skipspaces(ch, 0); if (ch == '(') { parens++; ch = GetChar(); ch = skipspaces(ch, 0); } if ((class(ch) != STIDF) && (class(ch) != STELL)) { error("identifier missing"); if (parens && ch != ')') error(") missing"); if (!parens || ch != ')') ChPushBack(ch); add2repl(repl, '0'); return; } ChPushBack(ch); id = GetIdentifier(0); ASSERT(id || class(ch) == STELL); ch = GetChar(); ch = skipspaces(ch, 0); if (parens && ch != ')') error(") missing"); if (!parens || ch != ')') ChPushBack(ch); add2repl(repl, (id && id->id_macro) ? '1' : '0'); add2repl(repl, ' '); } void newarg(struct args *args) { args->a_expptr = args->a_expbuf = Malloc(args->a_expsize = ARGBUF); args->a_rawptr = args->a_rawbuf = Malloc(args->a_rawsize = ARGBUF); } void getactuals(struct repl *repl, struct idf *idf) { /* Get the actual parameters from the input stream. The hard part is done by actual(), only comma's and other syntactic trivialities are checked here. */ struct args *args = repl->r_args; int nps = idf->id_macro->mc_nps; int argcnt; int ch; argcnt = 0; newarg(args); if ((ch = GetChar()) != ')') { UnGetChar(); while ((ch = actual(repl)) != ')' ) { if (ch != ',') { lexerror("illegal macro call"); return; } stash(repl, '\0', 1); args->a_expvec[argcnt] = args->a_expbuf; args->a_rawvec[argcnt] = args->a_rawbuf; ++argcnt; if (argcnt == STDC_NPARAMS) lexstrict("number of parameters exceeds ANSI standard"); if (argcnt >= NPARAMS) fatal("argument vector overflow"); newarg(args); } stash(repl, '\0', 1); args->a_expvec[argcnt] = args->a_expbuf; args->a_rawvec[argcnt] = args->a_rawbuf; ++argcnt; } if (argcnt < nps) lexerror("too few macro arguments"); else if (argcnt > nps) lexerror("too many macro arguments"); } void saveraw(struct repl *repl) { struct repl *nrepl = ReplaceList; struct args *ap = nrepl->r_args; char *p; /* stash identifier name */ for (p = nrepl->r_idf->id_text; *p != '\0'; p++) stash(repl, *p, -1); /* The following code deals with expanded function like macro calls. It makes the following code work: #define def(a,b) x(a,b) #define glue(a,b) a ## b glue(abc,def(a,b)) Results in: abcdef(a,b); */ if (ap->a_rawvec[0]) { /* stash arguments */ int i; for (i = 0; ap->a_rawvec[i] != (char *)0; i++) { if (i == 0) stash(repl, '(', -1); else stash(repl, ',', -1); for (p = ap->a_rawvec[i]; *p != '\0'; p++) stash(repl, *p, -1); } stash(repl, ')', -1); } } int actual(struct repl *repl) { /* This routine deals with the scanning of an actual parameter. It keeps in account the opening and closing brackets, preprocessor numbers, strings and character constants. */ int ch = 0; int level = 0, nostashraw = 0; int lastch; static int Unstacked_missed; while (1) { lastch = ch; ch = GetChar(); if (nostashraw && nostashraw >= Unstacked_missed) { nostashraw -= Unstacked_missed; Unstacked_missed = 0; } if (Unstacked) { nostashraw -= Unstacked; if (nostashraw < 0) { Unstacked_missed = -nostashraw; nostashraw = 0; } EnableMacros(); } if (class(ch) == STIDF || class(ch) == STELL) { /* Scan a preprocessor identifier token. If the token is a macro, it is expanded first. */ char buf[(IDFSIZE > NUMSIZE ? IDFSIZE : NUMSIZE) + 1]; char *p = buf; struct idf *idef; int pos = -1; extern int idfsize; int NoExpandMacro; if (ch == NOEXPM) { NoExpandMacro= 1; ch = GetChar(); } else NoExpandMacro = 0; do { if (++pos < idfsize) { *p++ = ch; } ch = GetChar(); } while (in_idf(ch)); *p++ = '\0'; ch = '\0'; /* It could be an unstashed TOKSEP */ UnGetChar(); /* When the identifier has an associated macro replacement list, it's expanded. */ idef = findidf(buf); if (!idef || NoExpandMacro || !replace(idef)) { if (NoExpandMacro || (idef && idef->id_macro && (idef->id_macro->mc_flag & NOREPLACE))) stash(repl, NOEXPM, !nostashraw); for (p = buf; *p != '\0'; p++) stash(repl, *p, !nostashraw); } else { if (!nostashraw) saveraw(repl); nostashraw++; } } else if (class(ch) == STNUM) { /* a preprocessing number has the following regular expression: [0-9|"."[0-9]]{[0-9"."a-zA-Z_]|{[Ee][+-]}}* */ stash(repl, ch, !nostashraw); if (ch == '.') { ch = GetChar(); if (class(ch) != STNUM) { ch = '\0'; /* It could be an unstashed TOKSEP */ UnGetChar(); continue; } else stash(repl, ch, !nostashraw); } ch = GetChar(); while (in_idf(ch) || ch == '.') { stash(repl, ch, !nostashraw); if ((ch = GetChar()) == 'e' || ch == 'E') { stash(repl, ch, !nostashraw); ch = GetChar(); if (ch == '+' || ch == '-') { stash(repl, ch, !nostashraw); ch = GetChar(); } } } ch = '\0'; /* It could be an unstashed TOKSEP */ UnGetChar(); } else if (ch == '(') { /* a comma may occur between parentheses */ level++; stash(repl, ch, !nostashraw); } else if (ch == ')') { level--; /* closing parenthesis of macro call */ if (level < 0) return ')'; stash(repl, ch, !nostashraw); } else if (ch == ',') { if (level <= 0) { /* comma separator for next argument */ if (level) lexerror("unbalanced parenthesis"); if (!nostashraw) return ','; /* ??? */ } stash(repl, ch, !nostashraw); } else if (ch == '\n') { /* newlines are accepted as white spaces */ LineNumber++; /* This piece of code needs some explanation: consider the call of a macro defined as: #define sum(a,b) (a+b) in the following form: sum( /_* comment *_/ #include phone_number ,2); in which case the include must be handled interpreted as such. */ a_new_line: ch = GetChar(); while (class(ch) == STSKIP || ch == '/') { if (ch == '/') { if ((ch = GetChar()) == '*' && !InputLevel) { skipcomment(); stash(repl, ' ', !nostashraw); ch = GetChar(); continue; } else { UnGetChar(); ch = '/'; } stash(repl, '/', !nostashraw); break; } else ch = GetChar(); } if (ch == '#') { domacro(); /* Clear File_Inserted since domacro could * be called again, which calls GetToken(). */ File_Inserted = 0; goto a_new_line; } else if (ch == EOI) { lexerror("unterminated macro call"); return ')'; } if (ch != '/') { UnGetChar(); ch = ' '; stash(repl, ' ', !nostashraw); } } else if (ch == '/') { /* comments are treated as one white space token */ if ((ch = GetChar()) == '*' && !InputLevel) { skipcomment(); stash(repl, ' ', !nostashraw); } else { UnGetChar(); ch = '/'; stash(repl, '/', !nostashraw); } } else if (ch == '\'' || ch == '"') { /* Strings are considered as ONE token, thus no replacement within strings. */ int match = ch; stash(repl, ch, !nostashraw); while ((ch = GetChar()) != EOI) { if (ch == match) break; if (ch == '\\') { stash(repl, ch, !nostashraw); ch = GetChar(); } else if (ch == '\n') { lexerror("newline in string"); LineNumber++; stash(repl, match, !nostashraw); break; } stash(repl, ch, !nostashraw); } if (ch != match) { lexerror("unterminated macro call"); return ')'; } stash(repl, ch, !nostashraw); } else { if (lastch == TOKSEP && ch == TOKSEP) continue; stash(repl, ch, !nostashraw); } } } void macro_func(struct idf *idef) { /* macro_func() performs the special actions needed with some macros. These macros are __FILE__ and __LINE__ which replacement texts must be evaluated at the time they are used. */ struct macro *mac = idef->id_macro; static char FilNamBuf[PATHLENGTH]; char *long2str(); switch (idef->id_text[2]) { case 'F': /* __FILE__ */ FilNamBuf[0] = '"'; strcpy(&FilNamBuf[1], FileName); strcat(FilNamBuf, "\""); mac->mc_text = FilNamBuf; mac->mc_length = strlen(FilNamBuf); break; case 'L': /* __LINE__ */ mac->mc_text = long2str((long)LineNumber, 10); mac->mc_length = strlen(mac->mc_text); break; default: crash("(macro_func)"); /*NOTREACHED*/ } } void macro2buffer(struct repl *repl, struct idf *idf, struct args *args) { /* macro2buffer expands the replacement list and places the result onto the replacement buffer. It deals with the # and ## operators, and inserts the actual parameters. The argument buffer contains the raw argument (needed for the ## operator), and the expanded argument (for all other parameter substitutions). The grammar of the replacement list is: repl_list: TOKEN repl_list | PARAMETER repl_list | '#' PARAMETER | TOKEN '##' TOKEN | PARAMETER '##' TOKEN | TOKEN '##' PARAMETER | PARAMETER '##' PARAMETER ; As the grammar indicates, we could make a DFA and use this finite state machine for the replacement list parsing (inserting the arguments, etc.). Currently we go through the replacement list in a linear fashion. This is VERY expensive, something smarter should be done (but even a DFA is O(|s|)). */ char *ptr = idf->id_macro->mc_text; int err = 0; int func = idf->id_macro->mc_nps != -1; ASSERT(ptr[idf->id_macro->mc_length] == '\0'); while (*ptr) { if (*ptr == '\'' || *ptr == '"') { int delim = *ptr; do { add2repl(repl, *ptr); if (*ptr == '\\') add2repl(repl, *++ptr); if (*ptr == '\0') { lexerror("unterminated string"); return; } ptr++; } while (*ptr != delim || *ptr == '\0'); add2repl(repl, *ptr++); } else if (*ptr == '#' && (func || *(ptr+1) == '#')) { if (*++ptr == '#') { int tmpindex; /* ## - paste operator */ ptr++; /* trim the actual replacement list */ --repl->r_ptr; while (repl->r_ptr >= repl->r_text && is_wsp(*(unsigned char *)repl->r_ptr)) --repl->r_ptr; /* ## occurred at the beginning of the replacement list. */ if (repl->r_ptr < repl->r_text) { err = 1; break; } if (repl->r_ptr >= repl->r_text && *repl->r_ptr == TOKSEP) --repl->r_ptr; ++repl->r_ptr; tmpindex = repl->r_ptr - repl->r_text; /* tmpindex can be 0 */ /* skip space in macro replacement list */ while ((*ptr & FORMALP) == 0 && is_wsp(*(unsigned char *)ptr)) ptr++; /* ## occurred at the end of the replacement list. */ if (*ptr & FORMALP) { int n = *ptr++ & 0177; char *p; ASSERT(n > 0); p = args->a_rawvec[n-1]; if (p) { /* else macro argument missing */ while (is_wsp(*(unsigned char *)p)) p++; if (*p == NOEXPM) p++; while (*p) add2repl(repl, *p++); } while (tmpindex > 0 && in_idf((unsigned char)repl->r_text[tmpindex])) tmpindex--; if (tmpindex >= 0 && repl->r_text[tmpindex] == NOEXPM) repl->r_text[tmpindex] = TOKSEP; } else if (*ptr == '\0') { err = 1; break; } else { if (in_idf(*(unsigned char *)ptr)) { tmpindex--; while (tmpindex > 0 && in_idf((unsigned char)repl->r_text[tmpindex])) tmpindex--; if (tmpindex >= 0 && repl->r_text[tmpindex] == NOEXPM) repl->r_text[tmpindex] = TOKSEP; } } } else { /* # operator */ ptr = stringify(repl, ptr, args); } } else if (*ptr & FORMALP) { /* insert actual parameter */ int n = *ptr++ & 0177; char *p, *q; ASSERT(n > 0); /* This is VERY dirty, we look ahead for the ## operator. If it's found we use the raw argument buffer instead of the expanded one. */ for (p = ptr; (*p & FORMALP) == 0 && is_wsp(*(unsigned char *)p); p++) /* EMPTY */; if (*p == '#' && p[1] == '#') q = args->a_rawvec[n-1]; else q = args->a_expvec[n-1]; if (q) /* else macro argument missing */ while (*q) add2repl(repl, *q++); if (repl->r_text == repl->r_ptr || *(repl->r_ptr - 1) != TOKSEP) add2repl(repl, TOKSEP); } else { add2repl(repl, *ptr++); } } if (err) lexerror("illegal use of the ## operator"); } char *stringify(struct repl *repl, char *ptr, struct args *args) { /* If a parameter is immediately preceded by a # token both are replaced by a single string literal that contains the spelling of the token sequence for the corresponding argument. Each occurrence of white space between the argument's tokens become a single space character in the string literal. White spaces before the first token and after the last token comprising the argument are deleted. To retain the original spelling we insert backslashes as appropriate. We only escape backslashes if they occure within string tokens. */ int space = 1; /* skip leading spaces */ int delim = 0; /* string or character constant delim */ int backslash = 0; /* last character was a \ */ /* skip spaces macro replacement list */ while ((*ptr & FORMALP) == 0 && is_wsp(*(unsigned char *)ptr)) ptr++; if (*ptr & FORMALP) { int n = *ptr++ & 0177; char *p; ASSERT(n != 0); p = args->a_rawvec[n-1]; add2repl(repl, '"'); while (*p) { if (is_wsp(*(unsigned char *)p)) { if (!space) { space = 1; add2repl(repl, ' '); } p++; continue; } space = 0; if (!delim && (*p == '"' || *p == '\'')) delim = *p; else if (*p == delim && !backslash) delim = 0; backslash = *p == '\\'; if (*p == '"' || (delim && *p == '\\')) add2repl(repl, '\\'); if (*p == TOKSEP || *p == NOEXPM) p++; else add2repl(repl, *p++); } /* trim spaces in the replacement list */ for (--repl->r_ptr; is_wsp(*(unsigned char *)repl->r_ptr); repl->r_ptr--) /* EMPTY */; ++repl->r_ptr; /* oops, one to far */ add2repl(repl, '"'); } else error("illegal use of # operator"); return ptr; } /* The following routine is also called from domacro.c. */ void add2repl(struct repl *repl, int ch) { int index = repl->r_ptr - repl->r_text; ASSERT(index < repl->r_size); if (index + 2 >= repl->r_size) { repl->r_text = Realloc(repl->r_text, (unsigned) (repl->r_size <<= 1)); repl->r_ptr = repl->r_text + index; } *repl->r_ptr++ = ch; *repl->r_ptr = '\0'; } /* If the variable stashraw is negative, we must only stash into the raw * buffer. If the variable is zero, we must only stash into the expanded * buffer. Otherwise, we must use both buffers. */ void stash(struct repl *repl ,int ch, int stashraw) { /* Stash characters into the macro expansion buffer. */ struct args *args = repl->r_args; int index = args->a_expptr - args->a_expbuf; if (stashraw >= 0) { ASSERT(index < args->a_expsize); if (index + 1 >= args->a_expsize) { args->a_expbuf = Realloc(args->a_expbuf, (unsigned) (args->a_expsize <<= 1)); args->a_expptr = args->a_expbuf + index; } *args->a_expptr++ = ch; } if (stashraw) { index = args->a_rawptr - args->a_rawbuf; ASSERT(index < args->a_rawsize); if (index + 1 >= args->a_rawsize) { args->a_rawbuf = Realloc(args->a_rawbuf, (unsigned)(args->a_rawsize <<= 1)); args->a_rawptr = args->a_rawbuf + index; } *args->a_rawptr++ = ch; } } #endif /* NOPP */