/* * main.c * Facility: m4 macro processor * by: oz */ #include "mdef.h" /* * m4 - macro processor * * PD m4 is based on the macro tool distributed with the software * tools (VOS) package, and described in the "SOFTWARE TOOLS" and * "SOFTWARE TOOLS IN PASCAL" books. It has been expanded to include * most of the command set of SysV m4, the standard UN*X macro processor. * * Since both PD m4 and UN*X m4 are based on SOFTWARE TOOLS macro, * there may be certain implementation similarities between * the two. The PD m4 was produced without ANY references to m4 * sources. * * References: * * Software Tools distribution: macro * * Kernighan, Brian W. and P. J. Plauger, SOFTWARE * TOOLS IN PASCAL, Addison-Wesley, Mass. 1981 * * Kernighan, Brian W. and P. J. Plauger, SOFTWARE * TOOLS, Addison-Wesley, Mass. 1976 * * Kernighan, Brian W. and Dennis M. Ritchie, * THE M4 MACRO PROCESSOR, Unix Programmer's Manual, * Seventh Edition, Vol. 2, Bell Telephone Labs, 1979 * * System V man page for M4 * * Modification History: * * Jan 28 1986 Oz Break the whole thing into little * pieces, for easier (?) maintenance. * * Dec 12 1985 Oz Optimize the code, try to squeeze * few microseconds out.. * * Dec 05 1985 Oz Add getopt interface, define (-D), * undefine (-U) options. * * Oct 21 1985 Oz Clean up various bugs, add comment handling. * * June 7 1985 Oz Add some of SysV m4 stuff (m4wrap, pushdef, * popdef, decr, shift etc.). * * June 5 1985 Oz Initial cut. * * Implementation Notes: * * [1] PD m4 uses a different (and simpler) stack mechanism than the one * described in Software Tools and Software Tools in Pascal books. * The triple stack nonsense is replaced with a single stack containing * the call frames and the arguments. Each frame is back-linked to a * previous stack frame, which enables us to rewind the stack after * each nested call is completed. Each argument is a character pointer * to the beginning of the argument string within the string space. * The only exceptions to this are (*) arg 0 and arg 1, which are * the macro definition and macro name strings, stored dynamically * for the hash table. * * . . * | . | <-- sp | . | * +-------+ +-----+ * | arg 3 ------------------------------->| str | * +-------+ | . | * | arg 2 --------------+ . * +-------+ | * * | | | * +-------+ | +-----+ * | plev | <-- fp +---------------->| str | * +-------+ | . | * | type | . * +-------+ * | prcf -----------+ plev: paren level * +-------+ | type: call type * | . | | prcf: prev. call frame * . | * +-------+ | * | <----------+ * +-------+ * * [2] We have three types of null values: * * nil - nodeblock pointer type 0 * null - null string ("") * NULL - Stdio-defined NULL * */ ndptr hashtab[HASHSIZE]; /* hash table for macros etc. */ char buf[BUFSIZE]; /* push-back buffer */ char *bp = buf; /* first available character */ char *endpbb = buf+BUFSIZE; /* end of push-back buffer */ stae mstack[STACKMAX+1]; /* stack of m4 machine */ char strspace[STRSPMAX+1]; /* string space for evaluation */ char *ep = strspace; /* first free char in strspace */ char *endest= strspace+STRSPMAX;/* end of string space */ int sp; /* current m4 stack pointer */ int fp; /* m4 call frame pointer */ FILE *infile[MAXINP]; /* input file stack (0=stdin) */ FILE *outfile[MAXOUT]; /* diversion array(0=bitbucket)*/ FILE *active; /* active output file pointer */ char *m4temp; /* filename for diversions */ int ilevel = 0; /* input file stack pointer */ int oindex = 0; /* diversion index.. */ char *null = ""; /* as it says.. just a null.. */ char *m4wraps = ""; /* m4wrap string default.. */ char lquote = LQUOTE; /* left quote character (`) */ char rquote = RQUOTE; /* right quote character (') */ char scommt = SCOMMT; /* start character for comment */ char ecommt = ECOMMT; /* end character for comment */ struct keyblk keywrds[] = { /* m4 keywords to be installed */ "include", INCLTYPE, "sinclude", SINCTYPE, "define", DEFITYPE, "defn", DEFNTYPE, "divert", DIVRTYPE, "expr", EXPRTYPE, "eval", EXPRTYPE, "substr", SUBSTYPE, "ifelse", IFELTYPE, "ifdef", IFDFTYPE, "len", LENGTYPE, "incr", INCRTYPE, "decr", DECRTYPE, "dnl", DNLNTYPE, "changequote", CHNQTYPE, "changecom", CHNCTYPE, "index", INDXTYPE, #ifdef EXTENDED "paste", PASTTYPE, "spaste", SPASTYPE, #endif "popdef", POPDTYPE, "pushdef", PUSDTYPE, "dumpdef", DUMPTYPE, "shift", SHIFTYPE, "translit", TRNLTYPE, "undefine", UNDFTYPE, "undivert", UNDVTYPE, "divnum", DIVNTYPE, "maketemp", MKTMTYPE, "errprint", ERRPTYPE, "m4wrap", M4WRTYPE, "m4exit", EXITTYPE, #if unix || vms "syscmd", SYSCTYPE, "sysval", SYSVTYPE, #endif #if unix "unix", MACRTYPE, #else #if vms "vms", MACRTYPE, #endif #endif }; #define MAXKEYS (sizeof(keywrds)/sizeof(struct keyblk)) extern int optind; extern char *optarg; int main(argc,argv) int argc; char *argv[]; { register int c; register int n; char *p; static char divnam[] = DIVNAM; if (signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, onintr); #ifdef NONZEROPAGES initm4(); #endif initkwds(); while ((c = getopt(argc, argv, "tD:U:o:")) != EOF) { switch(c) { case 'D': /* define something..*/ for (p = optarg; *p; p++) if (*p == '=') break; if (*p) *p++ = EOS; dodefine(optarg, p); break; case 'U': /* undefine... */ remhash(optarg, TOP); break; case 'o': /* specific output */ case '?': default: usage(); } } argc -= optind; argv += optind; if(argc > 1) { usage(); } infile[0] = stdin; /* default input (naturally) */ if(argc == 1) { if(!(infile[0] = fopen(argv[0], "r"))) { perror(argv[0]); return 1; } } active = stdout; /* default active output */ m4temp = mktemp(divnam); /* filename for diversions */ sp = -1; /* stack pointer initialized */ fp = 0; /* frame pointer initialized */ macro(); /* get some work done here */ if (*m4wraps) { /* anything for rundown ?? */ ilevel = 0; /* in case m4wrap includes.. */ putback(EOF); /* eof is a must !! */ pbstr(m4wraps); /* user-defined wrapup act */ macro(); /* last will and testament */ } else /* default wrap-up: undivert */ for (n = 1; n < MAXOUT; n++) if (outfile[n] != NULL) getdiv(n); /* remove bitbucket if used */ if (outfile[0] != NULL) { (void) fclose(outfile[0]); m4temp[UNIQUE] = '0'; #if vms (void) remove(m4temp); #else (void) unlink(m4temp); #endif } exit(0); } /* * macro - the work horse.. * */ void macro() { char token[MAXTOK]; register char *s; register int t, l; register ndptr p; register int nlpar; cycle { if ((t = gpbc()) == '_' || isalpha(t)) { putback(t); if ((p = inspect(s = token)) == nil) { if (sp < 0) while (*s) putc(*s++, active); else while (*s) chrsave(*s++); } else { /* * real thing.. First build a call frame: * */ pushf(fp); /* previous call frm */ pushf(p->type); /* type of the call */ pushf(0); /* parenthesis level */ fp = sp; /* new frame pointer */ /* * now push the string arguments: * */ pushs(p->defn); /* defn string */ pushs(p->name); /* macro name */ pushs(ep); /* start next..*/ putback(l = gpbc()); if (l != LPAREN) { /* add bracks */ putback(RPAREN); putback(LPAREN); } } } else if (t == EOF) { if (sp > -1) error("m4: unexpected end of input"); if (--ilevel < 0) break; /* all done thanks.. */ (void) fclose(infile[ilevel+1]); continue; } /* * non-alpha single-char token seen.. * [the order of else if .. stmts is * important.] * */ else if (t == lquote) { /* strip quotes */ nlpar = 1; do { if ((l = gpbc()) == rquote) nlpar--; else if (l == lquote) nlpar++; else if (l == EOF) error("m4: missing right quote"); if (nlpar > 0) { if (sp < 0) putc(l, active); else chrsave(l); } } while (nlpar != 0); } else if (sp < 0) { /* not in a macro at all */ if (t == scommt) { /* comment handling here */ putc(t, active); while ((t = gpbc()) != ecommt) putc(t, active); } putc(t, active); /* output directly.. */ } else switch(t) { case LPAREN: if (PARLEV > 0) chrsave(t); while (isspace(l = gpbc())) ; /* skip blank, tab, nl.. */ putback(l); PARLEV++; break; case RPAREN: if (--PARLEV > 0) chrsave(t); else { /* end of argument list */ chrsave(EOS); if (sp == STACKMAX) error("m4: internal stack overflow"); if (CALTYP == MACRTYPE) expand((char**)mstack+fp+1,(int)sp-fp); else eval((char**)mstack+fp+1,sp-fp,CALTYP); ep = PREVEP; /* flush strspace */ sp = PREVSP; /* previous sp.. */ fp = PREVFP; /* rewind stack...*/ } break; case COMMA: if (PARLEV == 1) { chrsave(EOS); /* new argument */ while (isspace(l = gpbc())) ; putback(l); pushs(ep); } break; default: chrsave(t); /* stack the char */ break; } } } /* * build an input token.. * consider only those starting with _ or A-Za-z. This is a * combo with lookup to speed things up. */ ndptr inspect(tp) register char *tp; { register int h = 0; register char c; register char *name = tp; register char *etp = tp+MAXTOK; register ndptr p; while (tp < etp && (isalnum(c = gpbc()) || c == '_')) h += (*tp++ = c); putback(c); if (tp == etp) error("m4: token too long"); *tp = EOS; for (p = hashtab[h%HASHSIZE]; p != nil; p = p->nxtptr) if (strcmp(name, p->name) == 0) break; return(p); } #ifdef NONZEROPAGES /* * initm4 - initialize various tables. Useful only if your system * does not know anything about demand-zero pages. * */ void initm4() { register int i; for (i = 0; i < HASHSIZE; i++) hashtab[i] = nil; for (i = 0; i < MAXOUT; i++) outfile[i] = NULL; } #endif /* * initkwds - initialise m4 keywords as fast as possible. * This very similar to install, but without certain overheads, * such as calling lookup. Malloc is not used for storing the * keyword strings, since we simply use the static pointers * within keywrds block. We also assume that there is enough memory * to at least install the keywords (i.e. malloc won't fail). * */ void initkwds() { register int i; register int h; register ndptr p; for (i = 0; i < MAXKEYS; i++) { h = hash(keywrds[i].knam); p = (ndptr) malloc(sizeof(struct ndblock)); p->nxtptr = hashtab[h]; hashtab[h] = p; p->name = keywrds[i].knam; p->defn = null; p->type = keywrds[i].ktyp | STATIC; } }