#include <stdio.h>
#include <signal.h>

#define ERROR NULL
#define	READ	"r"
#define	WRITE	"w"

#define	EOS	0
int	lpar	= '(';
#define	LPAR	lpar
#define	RPAR	')'
#define	COMMA	','
#define	GRAVE	'`'
#define	ACUTE	'\''
#define LBRAK	'['
#define RBRAK	']'
#ifdef  M4
char	lquote	LBRAK;
char	rquote	RBRAK;
#endif
#ifndef M4
char	lquote	= GRAVE;
char	rquote	= ACUTE;
#endif
#define	COMMENT	'#'
#define	ALPH	1
#define	DIG	2

#define	HSHSIZ	199	/* prime */
#define	STACKS	50
#define	SAVS	4096
#define	TOKS	128

#define	putbak(c)	*ip++ = c;
#define	getchr()	(ip>cur_ip?*--ip: getc(infile[infptr]))
#define	putchr(c)	if (cp==NULL) {if (curfile)putc(c,curfile);} else *op++ = c
char	type[] = {
	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,
	DIG,	DIG,	DIG,	DIG,	DIG,	DIG,	DIG,	DIG,
	DIG,	DIG,	0,	0,	0,	0,	0,	0,
	0,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,
	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,
	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,
	ALPH,	ALPH,	ALPH,	0,	0,	0,	0,	ALPH,
	0,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,
	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,
	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,	ALPH,
	ALPH,	ALPH,	ALPH,	0,	0,	0,	0,	0,
};

char	token[TOKS];
char	eoa[]	= "\0";
struct	nlist {
	char	*name;
	char	*def;
	struct	nlist *next;
};

struct	nlist	*hshtab[HSHSIZ];
char	ibuf[SAVS+TOKS];
char	obuf[SAVS+TOKS];
char	*op	= obuf;
char	*ip	= ibuf;
char *ip_stk[10] = {ibuf};
char *cur_ip = ibuf;
struct call {
	char	**argp;
	int	plev;
};
struct	call	*cp = NULL;

char	*makeloc;
char	*ifdefloc;
char	*lenloc;
char	*undefloc;
char	*shiftloc;
char	*cqloc;
char	*defloc;
char	*evaloc;
char	*incrloc;
char	*substrloc;
char	*indexloc;
char	*transloc;
char	*ifloc;
char	*divloc;
char	*divnumloc;
char	*undivloc;
char	*dnlloc;
char	*inclloc;
char	*sinclloc;
char	*syscmdloc;
char	*dumploc;
char	*errploc;

char	*tempname;
struct nlist	*lookup();
char	*install();
char	*malloc();
char	*mktemp();
char	*copy();
long	ctol();
int	hshval;
FILE	*olist[11] = { stdout };
int	okret;
int	curout	= 0;
FILE	*curfile = { stdout };
FILE	*infile[10] = { stdin };
int	infptr	= 0;

main(argc, argv)
char **argv;
{
	char *argstk[STACKS+10];
	struct call callst[STACKS];
	register char *tp, **ap;
	int delexit(), catchsig();
	register t;
	int i;

#ifdef gcos
#ifdef M4
	install("GCOS", eoa);
#endif
#ifndef M4
	install("gcos", eoa);
#endif
#endif
#ifdef unix
#ifdef M4
	install("UNIX", eoa);
#endif
#ifndef M4
	install("unix", eoa);
#endif
#endif

#ifdef M4
	makeloc = install("MAKETEMP", eoa);
	ifdefloc = install("IFDEF", eoa);
	lenloc = install("LEN", eoa);
	undefloc = install("UNDEFINE", eoa);
	shiftloc = install("SHIFT", eoa);
	cqloc = install("CHANGEQUOTE", eoa);
	defloc = install("DEFINE", eoa);
	evaloc = install("EVAL", eoa);
	inclloc = install("INCLUDE", eoa);
	sinclloc = install("SINCLUDE", eoa);
	syscmdloc = install("SYSCMD", eoa);
	dumploc = install("DUMPDEF", eoa);
	errploc = install("ERRPRINT", eoa);
	incrloc = install("INCR", eoa);
	substrloc = install("SUBSTR", eoa);
	indexloc = install("INDEX", eoa);
	transloc = install("TRANSLIT", eoa);
	ifloc = install("IFELSE", eoa);
	divloc = install("DIVERT", eoa);
	divnumloc = install("DIVNUM", eoa);
	undivloc = install("UNDIVERT", eoa);
	dnlloc = install("DNL", eoa);
#endif

#ifndef M4
	makeloc = install("maketemp", eoa);
	ifdefloc = install("ifdef", eoa);
	lenloc = install("len", eoa);
	undefloc = install("undefine", eoa);
	shiftloc = install("shift", eoa);
	cqloc = install("changequote", eoa);
	defloc = install("define", eoa);
	evaloc = install("eval", eoa);
	inclloc = install("include", eoa);
	sinclloc = install("sinclude", eoa);
	syscmdloc = install("syscmd", eoa);
	dumploc = install("dumpdef", eoa);
	errploc = install("errprint", eoa);
	incrloc = install("incr", eoa);
	substrloc = install("substr", eoa);
	indexloc = install("index", eoa);
	transloc = install("translit", eoa);
	ifloc = install("ifelse", eoa);
	divloc = install("divert", eoa);
	divnumloc = install("divnum", eoa);
	undivloc = install("undivert", eoa);
	dnlloc = install("dnl", eoa);
#endif
	ap = argstk;
#ifndef gcos
	if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
		signal(SIGHUP, catchsig);
	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
		signal(SIGINT, catchsig);
	tempname = mktemp("/tmp/m4aXXXXX");
	close(creat(tempname, 0));
#endif
#ifdef gcos
	tempname = "m4.tempa";
#endif
	if (argc>1)
		putbak(0);
	for (;;) {
		tp = token;
		*tp++ = t = getchr();
		*tp = EOS;
		if (t<=0) {
			if (infptr > 0) {
				fclose(infile[infptr]);
				infptr--;
				cur_ip = ip_stk[infptr];
				continue;
			}
			if (argc<=1)
				break;
			argc--;
			argv++;
			if (infile[infptr]!=stdin)
				fclose(infile[infptr]);
			if (**argv=='-')
				infile[infptr] = stdin;
			else if ((infile[infptr]=fopen(argv[0], READ))==ERROR) {
				fprintf(stderr, "m4: file not found: %s\n", argv[0]);
				delexit();
			}
			continue;
		}
		if (type[t]==ALPH) {
			while ((t=type[*tp++=getchr()])==ALPH||t==DIG);
			putbak(*--tp);
			*tp = EOS;
			if (*ap = lookup(token)->def) {
				if (++ap >= &argstk[STACKS]) {
					fprintf(stderr, "m4: arg stack overflow\n");
					delexit();
				}
				if (cp==NULL)
					cp = callst;
				else if (++cp > &callst[STACKS]) {
					fprintf(stderr, "m4: call stack overflow\n");
					delexit();
				}
				cp->argp = ap;
				*ap++ = op;
				puttok();
				*op++ = '\0';
				t = getchr();
				putbak(t);
				if (t!=LPAR) {
					/* if (t!=' ' && t!='\t') */
						putbak(')');
					putbak('(');
				}
				else	/* try to fix arg count */
					*ap++ = op;
				cp->plev = 0;
			} else
				puttok();
		} else if (t==lquote) {
			i = 1;
			for (;;) {
				t = getchr();
				if (t==rquote) {
					i--;
					if (i==0)
						break;
				} else if (t==lquote)
					i++;
				else if (t<0) {
					fprintf(stderr, "m4: EOF in string\n");
					delexit();
				}
				putchr(t);
			}
		} else if (t==COMMENT) {
			putbak(t);
			while ((t = getchr())!='\n'&& t>=0)
				if (cp==NULL)
					putchr(t);
			putbak(t);
		} else if (cp==NULL) {
			puttok();
		} else if (t==LPAR) {
			if (cp->plev)
				*op++ = t;
			cp->plev++;
			while ( (t=getchr())==' ' || t=='\t' || t=='\n')
				;	/* skip leading white space during arg collection */
			putbak(t);
/*
		} else if (t==' ' || t=='\t' || t=='\n') {
			continue;
*/
		} else if (t==RPAR) {
			cp->plev--;
			if (cp->plev==0) {
				*op++ = '\0';
				expand(cp->argp, ap-cp->argp-1);
				op = *cp->argp;
				ap = cp->argp-1;
				cp--;
				if (cp < callst)
					cp = NULL;
			} else
				*op++ = t;
		} else if (t==COMMA && cp->plev<=1) {
			*op++ = '\0';
			*ap++ = op;
			while ((t=getchr())==' ' || t=='\t' || t=='\n')
				;	/* skip leading white space during arg collection */
			putbak(t);
		} else
			*op++ = t;
	}
	if (cp!=NULL) {
		fprintf(stderr, "m4: unexpected EOF\n");
		delexit();
	}
	okret = 1;
	delexit();
}

catchsig()
{
	okret = 0;
	delexit();
}

delexit()
{
	register FILE *fp;
	register i, c;

	if (!okret) {
		signal(SIGHUP, SIG_IGN);
		signal(SIGINT, SIG_IGN);
	}
	for (i=1; i<10; i++) {
		if (olist[i]==NULL)
			continue;
		fclose(olist[i]);
		tempname[7] = 'a'+i;
		if (okret) {
			fp = fopen(tempname, READ);
			while ((c = getc(fp)) > 0)
				putchar(c);
			fclose(fp);
		}
		unlink(tempname);
	}
	tempname[7] = 'a';
	unlink(tempname);
	exit(1-okret);
}

puttok()
{
	register char *tp;

	tp = token;
	if (cp) {
		if (op >= &obuf[SAVS]) {
			fprintf(stderr, "m4: argument overflow\n");
			delexit();
		}
		while (*tp)
			*op++ = *tp++;
	} else if (curfile)
		while (*tp)
			putc(*tp++, curfile);
}

pbstr(str)
register char *str;
{
	register char *p;

	p = str;
	while (*p++);
	--p;
	if (ip >= &ibuf[SAVS]) {
		fprintf(stderr, "m4: pushback overflow\n");
		delexit();
	}
	while (p > str)
		putbak(*--p);
}

expand(a1, c)
register char **a1;
{
	register char *dp;
	register n;

	dp = a1[-1];
	if (dp==defloc)
		dodef(a1, c);
	else if (dp==evaloc)
		doeval(a1, c);
	else if (dp==inclloc)
		doincl(a1, c, 1);
	else if (dp==sinclloc)
		doincl(a1, c, 0);
	else if (dp==makeloc)
		domake(a1, c);
	else if (dp==syscmdloc)
		dosyscmd(a1, c);
	else if (dp==incrloc)
		doincr(a1, c);
	else if (dp==substrloc)
		dosubstr(a1, c);
	else if (dp==indexloc)
		doindex(a1, c);
	else if (dp==transloc)
		dotransl(a1, c);
	else if (dp==ifloc)
		doif(a1, c);
	else if (dp==divloc)
		dodiv(a1, c);
	else if (dp==divnumloc)
		dodivnum(a1, c);
	else if (dp==undivloc)
		doundiv(a1, c);
	else if (dp==dnlloc)
		dodnl(a1, c);
	else if (dp==dumploc)
		dodump(a1, c);
	else if (dp==errploc)
		doerrp(a1, c);
	else if (dp==lenloc)
		dolen(a1, c);
	else if (dp==ifdefloc)
		doifdef(a1, c);
	else if (dp==undefloc)
		doundef(a1, c);
	else if (dp==shiftloc)
		doshift(a1, c);
	else if (dp==cqloc)
		docq(a1, c);
	else {
		while (*dp++);
		for (dp--; dp>a1[-1]; ) {
			if (--dp>a1[-1] && dp[-1]=='$') {
				n = *dp-'0';
				if (n>=0 && n<=9) {
					if (n <= c)
						pbstr(a1[n]);
					dp--;
				} else
					putbak(*dp);
			} else
				putbak(*dp);
		}
	}
}

struct nlist *lookup(str)
char *str;
{
	register char *s1, *s2;
	register struct nlist *np;
	static struct nlist nodef;

	s1 = str;
	for (hshval = 0; *s1; )
		hshval += *s1++;
	hshval %= HSHSIZ;
	for (np = hshtab[hshval]; np!=NULL; np = np->next) {
		s1 = str;
		s2 = np->name;
		while (*s1++ == *s2)
			if (*s2++ == EOS)
				return(np);
	}
	return(&nodef);
}

char *install(nam, val)
char *nam, *val;
{
	register struct nlist *np;

	if ((np = lookup(nam))->name == NULL) {
		np = (struct nlist *)malloc(sizeof(*np));
		if (np == NULL) {
			fprintf(stderr, "m4: no space for alloc\n");
			exit(1);
		}
		np->name = copy(nam);
		np->def = copy(val);
		np->next = hshtab[hshval];
		hshtab[hshval] = np;
		return(np->def);
	}
	free(np->def);
	np->def = copy(val);
	return(np->def);
}

doundef(ap, c)
char **ap;
{
	register struct nlist *np, *tnp;

	if (c < 1 || (np = lookup(ap[1]))->name == NULL)
		return;
	tnp = hshtab[hshval];	/* lookup sets hshval */
	if (tnp == np)	/* it's in first place */
		hshtab[hshval] = np->next;
	else {
		for ( ; tnp->next != np; tnp = tnp->next)
			;
		tnp->next = np->next;
	}
	free(np->name);
	free(np->def);
	free((char *)np);
}

char *copy(s)
register char *s;
{
	register char *p, *s1;

	p = s1 = malloc((unsigned)strlen(s)+1);
	if (p == NULL) {
		fprintf(stderr, "m4: no space for alloc\n");
		exit(1);
	}
	while (*s1++ = *s++);
	return(p);
}

dodef(ap, c)
char **ap;
{
	if (c >= 2) {
		if (strcmp(ap[1], ap[2]) == 0) {
			fprintf(stderr, "m4: %s defined as itself\n", ap[1]);
			delexit();
		}
		install(ap[1], ap[2]);
	}
	else if (c == 1)
		install(ap[1], "");
}

doifdef(ap, c)
char **ap;
{
	register struct nlist *np;

	if (c < 2)
		return;
	if (lookup(ap[1])->name != NULL)
		pbstr(ap[2]);
	else if (c >= 3)
		pbstr(ap[3]);
}

dolen(ap, c)
char **ap;
{
	putnum((long) strlen(ap[1]));
}

docq(ap, c)
char **ap;
{
	if (c > 1) {
		lquote = *ap[1];
		rquote = *ap[2];
	} else if (c == 1) {
		lquote = rquote = *ap[1];
	} else {
#ifndef M4
		lquote = GRAVE;
		rquote = ACUTE;
#endif
#ifdef M4
		lquote = LBRAK;
		rquote = RBRAK;
#endif
	}
}

doshift(ap, c)
char **ap;
{
	fprintf(stderr, "m4: shift not yet implemented\n");
}

dodump(ap, c)
char **ap;
{
	int i;
	register struct nlist *np;

	if (c > 0)
		while (c--) {
			if ((np = lookup(*++ap))->name != NULL)
				fprintf(stderr, "`%s'	`%s'\n", np->name, np->def);
		}
	else
		for (i=0; i<HSHSIZ; i++)
			for (np=hshtab[i]; np!=NULL; np=np->next)
				fprintf(stderr, "`%s'	`%s'\n", np->name, np->def);
}

doerrp(ap, c)
char **ap;
{
	if (c > 0) {
		fprintf(stderr, ap[1], ap[2], ap[3], ap[4], ap[5], ap[6]);
		fprintf(stderr, "\n");
	}
}


long	evalval;	/* return value from yacc stuff */
char	*pe;	/* used by grammar */

doeval(ap, c)
char **ap;
{

	if (c > 0) {
		pe = ap[1];
		if (yyparse() == 0)
			putnum(evalval);
		else
			fprintf(stderr, "m4: invalid expression in eval: %s\n", ap[1]);
	}
}

doincl(ap, c, noisy)
char **ap;
{
	if (c > 0 && strlen(ap[1]) > 0) {
		infptr++;
		ip_stk[infptr] = cur_ip = ip;
		if ((infile[infptr] = fopen(ap[1], READ))==ERROR) {
			if (noisy) {
				fprintf(stderr, "m4: file not found: %s\n", ap[1]);
				delexit();
			}
			else
				infptr--;
		}
	}
}

dosyscmd(ap, c)
char **ap;
{
	if (c > 0)
		system(ap[1]);
}

domake(ap, c)
char **ap;
{
	if (c > 0)
		pbstr(mktemp(ap[1]));
}

doincr(ap, c)
char **ap;
{
	if (c >= 1)
		putnum(ctol(ap[1])+1);
}

putnum(num)
long num;
{
	register sign;

	sign = (num < 0) ? '-' : '\0';
	if (num < 0)
		num = -num;
	do {
		putbak(num%10+'0');
		num = num/10;
	} while (num!=0);
	if (sign == '-')
		putbak('-');
}

dosubstr(ap, c)
char **ap;
{
	int nc;
	register char *sp, *fc;

	if (c<2)
		return;
	if (c<3)
		nc = TOKS;
	else
		nc = ctoi(ap[3]);
	fc = ap[1] + max(0, min(ctoi(ap[2]), strlen(ap[1])));
	sp = fc + min(nc, strlen(fc));
	while (sp > fc)
		putbak(*--sp);
}

doindex(ap, c)
char **ap;
{
	if (c >= 2)
		putnum((long) strindex(ap[1], ap[2]));
}

strindex(p1, p2)
char *p1, *p2;
{
	register m;
	register char *s, *t, *p;

	for (p=p1; *p; p++) {
		s = p;
		m = 1;
		for (t=p2; *t; )
			if (*t++ != *s++)
				m = 0;
		if (m == 1)
			return(p-p1);
	}
	return(-1);
}

dotransl(ap, c)
char **ap;
{
	register char *s, *fr, *to;

	if (c <= 1) return;

	if (c == 2) {
		register int i;
		to = ap[1];
		for (s = ap[1]; *s; s++) {
			i = 0;
			for (fr = ap[2]; *fr; fr++)
				if (*s == *fr) {
					i++;
					break;
				}
			if (i == 0)
				*to++ = *s;
		}
		*to = '\0';
	}

	if (c >= 3) {
		for (s = ap[1]; *s; s++)
			for (fr = ap[2], to = ap[3]; *fr && *to; fr++, to++)
				if (*s == *fr)
					*s = *to;
	}

	pbstr(ap[1]);
}

doif(ap, c)
register char **ap;
{
	if (c < 3)
		return;
	while (c >= 3) {
		if (strcmp(ap[1], ap[2]) == 0) {
			pbstr(ap[3]);
			return;
		}
		c -= 3;
		ap += 3;
	}
	if (c > 0)
		pbstr(ap[1]);
}

dodiv(ap, c)
register char **ap;
{
	register int f;

	if (c<1)
		f = 0;
	else
		f = ctoi(ap[1]);
	if (f>=10 || f<0) {
		curfile = NULL;
		return;
	}
	tempname[7] = 'a' + f;
	if (olist[f] || (olist[f]=fopen(tempname, WRITE))) {
		curout = f;
		curfile = olist[f];
	}
}

doundiv(ap, c)
char **ap;
{
	register FILE *fp;
	register int i, ch;
	int j;

	if (c == 0) {
		for (i=1; i<10; i++) {
			if (i==curout || olist[i]==NULL)
				continue;
			fclose(olist[i]);
			tempname[7] = 'a'+i;
			fp = fopen(tempname, READ);
			if (curfile != NULL)
				while ((ch = getc(fp)) > 0)
					putc(ch, curfile);
			fclose(fp);
			unlink(tempname);
			olist[i] = NULL;
		}

	}
	else {
		for (j = 1; j <= c; j++) {
			i = ctoi(*++ap);
			if (i<1 || i>9 || i==curout || olist[i]==NULL)
				continue;
			fclose(olist[i]);
			tempname[7] = 'a'+i;
			fp = fopen(tempname, READ);
			if (curfile != NULL)
				while ((ch = getc(fp)) > 0)
					putc(ch, curfile);
			fclose(fp);
			unlink(tempname);
			olist[i] = NULL;
		}
	}
}

dodivnum(ap, c)
char **ap;
{
	putnum((long) curout);
}

dodnl(ap, c)
char **ap;
{
	register t;

	while ((t=getchr())!='\n' && t>=0)
		;
}

long ctol(str)
register char *str;
{
	register sign;
	long num;

	while (*str==' ' || *str=='\t' || *str=='\n')
		str++;
	num = 0;
	if (*str == '-') {
		sign = -1;
		str++;
	}
	else
		sign = 1;
	while (*str>='0' && *str<='9')
		num = num*10 + *str++ - '0';
	return(sign * num);
}

ctoi(s)
char *s;
{
	return(ctol(s));
}

min(a, b)
{
	if (a>b)
		return(b);
	return(a);
}

max(a, b)
{
	if (a>b)
		return(a);
	return(b);
}
