+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ozan Yigit.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)main.c 5.6 (Berkeley) 3/6/91";
+#endif /* not lint */
+
+/*
+ * main.c
+ * Facility: m4 macro processor
+ * by: oz
+ */
+
+#include <signal.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "mdef.h"
+#include "pathnames.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,
+ "syscmd", SYSCTYPE,
+ "sysval", SYSVTYPE,
+ "unix", MACRTYPE,
+};
+
+#define MAXKEYS (sizeof(keywrds)/sizeof(struct keyblk))
+
+extern ndptr lookup();
+extern ndptr addent();
+extern void onintr();
+
+extern int optind;
+extern char *optarg;
+
+main(argc,argv)
+ int argc;
+ char **argv;
+{
+ register int c;
+ register int n;
+ char *p;
+
+ 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();
+ }
+
+ infile[0] = stdin; /* default input (naturally) */
+ active = stdout; /* default active output */
+ m4temp = mktemp(strdup(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 */
+ }
+
+ if (active != stdout)
+ active = stdout; /* reset output just in case */
+ for (n = 1; n < MAXOUT; n++) /* default wrap-up: undivert */
+ if (outfile[n] != NULL)
+ getdiv(n);
+ /* remove bitbucket if used */
+ if (outfile[0] != NULL) {
+ (void) fclose(outfile[0]);
+ m4temp[UNIQUE] = '0';
+ (void) unlink(m4temp);
+ }
+
+ exit(0);
+}
+
+ndptr inspect(); /* forward ... */
+
+/*
+ * macro - the work horse..
+ *
+ */
+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(mstack+fp+1, sp-fp);
+ else
+ eval(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.
+ *
+ */
+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).
+ *
+ */
+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;
+ }
+}