/*	$OpenBSD: deroff.c,v 1.8 2009/10/27 23:59:37 deraadt Exp $	*/

/*-
 * Copyright (c) 1988, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * 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. 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.
 */
/*
 * Copyright (C) Caldera International Inc.  2001-2002.
 * All rights reserved.
 *
 * 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 and documentation 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 or owned by Caldera
 *	International, Inc.
 * 4. Neither the name of Caldera International, Inc. nor the names of other
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
 * INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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.
 */

#include <err.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/*
 *	Deroff command -- strip troff, eqn, and Tbl sequences from
 *	a file.  Has two flags argument, -w, to cause output one word per line
 *	rather than in the original format.
 *	-mm (or -ms) causes the corresponding macro's to be interpreted
 *	so that just sentences are output
 *	-ml  also gets rid of lists.
 *	Deroff follows .so and .nx commands, removes contents of macro
 *	definitions, equations (both .EQ ... .EN and $...$),
 *	Tbl command sequences, and Troff backslash constructions.
 *
 *	All input is through the Cget macro;
 *	the most recently read character is in c.
 *
 *	Modified by Robert Henry to process -me and -man macros.
 */

#define Cget ( (c=getc(infile)) == EOF ? eof() : ((c==ldelim)&&(filesp==files) ? skeqn() : c) )
#define C1get ( (c=getc(infile)) == EOF ? eof() :  c)

#ifdef DEBUG
#  define C	_C()
#  define C1	_C1()
#else /* not DEBUG */
#  define C	Cget
#  define C1	C1get
#endif /* not DEBUG */

#define SKIP while (C != '\n')
#define SKIP_TO_COM SKIP; SKIP; pc=c; while (C != '.' || pc != '\n' || C > 'Z')pc=c

#define	YES 1
#define	NO 0
#define	MS 0	/* -ms */
#define	MM 1	/* -mm */
#define	ME 2	/* -me */
#define	MA 3	/* -man */

#ifdef DEBUG
char *mactab[] = { "-ms", "-mm", "-me", "-ma" };
#endif /* DEBUG */

#define	ONE 1
#define	TWO 2

#define NOCHAR -2
#define SPECIAL 0
#define APOS 1
#define PUNCT 2
#define DIGIT 3
#define LETTER 4

#define MAXFILES 20

int	iflag;
int	wordflag;
int	msflag;		/* processing a source written using a mac package */
int	mac;		/* which package */
int	disp;
int	parag;
int	inmacro;
int	intable;
int	keepblock;	/* keep blocks of text; normally false when msflag */

char chars[128];  /* SPECIAL, PUNCT, APOS, DIGIT, or LETTER */

char line[LINE_MAX];
char *lp;

int c;
int pc;
int ldelim;
int rdelim;

char fname[PATH_MAX];
FILE *files[MAXFILES];
FILE **filesp;
FILE *infile;

int argc;
char **argv;

/*
 *	Macro processing
 *
 *	Macro table definitions
 */
typedef	int pacmac;		/* compressed macro name */
int	argconcat = 0;		/* concat arguments together (-me only) */

#define	tomac(c1, c2)		((((c1) & 0xFF) << 8) | ((c2) & 0xFF))
#define	frommac(src, c1, c2)	(((c1)=((src)>>8)&0xFF),((c2) =(src)&0xFF))

struct mactab{
	int	condition;
	pacmac	macname;
	int	(*func)();	/* XXX - args */
};

struct	mactab	troffmactab[];
struct	mactab	ppmactab[];
struct	mactab	msmactab[];
struct	mactab	mmmactab[];
struct	mactab	memactab[];
struct	mactab	manmactab[];

/*
 *	Macro table initialization
 */
#define	M(cond, c1, c2, func) {cond, tomac(c1, c2), func}

/*
 *	Flags for matching conditions other than
 *	the macro name
 */
#define	NONE		0
#define	FNEST		1		/* no nested files */
#define	NOMAC		2		/* no macro */
#define	MAC		3		/* macro */
#define	PARAG		4		/* in a paragraph */
#define	MSF		5		/* msflag is on */
#define	NBLK		6		/* set if no blocks to be kept */

/*
 *	Return codes from macro minions, determine where to jump,
 *	how to repeat/reprocess text
 */
#define	COMX		1		/* goto comx */
#define	COM		2		/* goto com */

int	 skeqn(void);
int	 eof(void);
int	 _C1(void);
int	 _C(void);
int	 EQ(void);
int	 domacro(void);
int	 PS(void);
int	 skip(void);
int	 intbl(void);
int	 outtbl(void);
int	 so(void);
int	 nx(void);
int	 skiptocom(void);
int	 PP(pacmac);
int	 AU(void);
int	 SH(pacmac);
int	 UX(void);
int	 MMHU(pacmac);
int	 mesnblock(pacmac);
int	 mssnblock(pacmac);
int	 nf(void);
int	 ce(void);
int	 meip(pacmac);
int	 mepp(pacmac);
int	 mesh(pacmac);
int	 mefont(pacmac);
int	 manfont(pacmac);
int	 manpp(pacmac);
int	 macsort(const void *, const void *);
int	 sizetab(struct mactab *);
void	 getfname(void);
void	 textline(char *, int);
void	 work(void);
void	 regline(void (*)(char *, int), int);
void	 macro(void);
void	 tbl(void);
void	 stbl(void);
void	 eqn(void);
void	 backsl(void);
void	 sce(void);
void	 refer(int);
void	 inpic(void);
void	 msputmac(char *, int);
void	 msputwords(int);
void	 meputmac(char *, int);
void	 meputwords(int);
void	 noblock(char, char);
void	 defcomline(pacmac);
void	 comline(void);
void	 buildtab(struct mactab **, int *);
FILE	*opn(char *);
struct mactab *macfill(struct mactab *, struct mactab *);
__dead void usage(void);

int
main(int ac, char **av)
{
	int	i, ch;
	int	errflg = 0;
	int	kflag = NO;

	iflag = NO;
	wordflag = NO;
	msflag = NO;
	mac = ME;
	disp = NO;
	parag = NO;
	inmacro = NO;
	intable = NO;
	ldelim	= NOCHAR;
	rdelim	= NOCHAR;
	keepblock = YES;

	while ((ch = getopt(ac, av, "ikpwm:")) != -1) {
		switch (ch) {
		case 'i':
			iflag = YES;
			break;
		case 'k':
			kflag = YES;
			break;
		case 'm':
			msflag = YES;
			keepblock = NO;
			switch (optarg[0]) {
			case 'm':
				mac = MM;
				break;
			case 's':
				mac = MS;
				break;
			case 'e':
				mac = ME;
				break;
			case 'a':
				mac = MA;
				break;
			case 'l':
				disp = YES;
				break;
			default:
				errflg++;
				break;
			}
			if (errflg == 0 && optarg[1] != '\0')
				errflg++;
			break;
		case 'p':
			parag = YES;
			break;
		case 'w':
			wordflag = YES;
			kflag = YES;
			break;
		default:
			errflg++;
		}
	}
	argc = ac - optind;
	argv = av + optind;

	if (kflag)
		keepblock = YES;
	if (errflg)
		usage();

#ifdef DEBUG
	printf("msflag = %d, mac = %s, keepblock = %d, disp = %d\n",
		msflag, mactab[mac], keepblock, disp);
#endif /* DEBUG */
	if (argc == 0) {
		infile = stdin;
	} else {
		infile = opn(argv[0]);
		--argc;
		++argv;
	}
	files[0] = infile;
	filesp = &files[0];

	for (i = 'a'; i <= 'z' ; ++i)
		chars[i] = LETTER;
	for (i = 'A'; i <= 'Z'; ++i)
		chars[i] = LETTER;
	for (i = '0'; i <= '9'; ++i)
		chars[i] = DIGIT;
	chars['\''] = APOS;
	chars['&'] = APOS;
	chars['.'] = PUNCT;
	chars[','] = PUNCT;
	chars[';'] = PUNCT;
	chars['?'] = PUNCT;
	chars[':'] = PUNCT;
	work();
	exit(0);
}

int
skeqn(void)
{

	while ((c = getc(infile)) != rdelim) {
		if (c == EOF)
			c = eof();
		else if (c == '"') {
			while ((c = getc(infile)) != '"') {
				if (c == EOF ||
				    (c == '\\' && (c = getc(infile)) == EOF))
					c = eof();
			}
		}
	}
	if (msflag)
		return((c = 'x'));
	return((c = ' '));
}

FILE *
opn(char *p)
{
	FILE *fd;

	if ((fd = fopen(p, "r")) == NULL)
		err(1, "fopen %s", p);

	return(fd);
}

int
eof(void)
{

	if (infile != stdin)
		fclose(infile);
	if (filesp > files)
		infile = *--filesp;
	else if (argc > 0) {
		infile = opn(argv[0]);
		--argc;
		++argv;
	} else
		exit(0);
	return(C);
}

void
getfname(void)
{
	char *p;
	struct chain {
		struct chain *nextp;
		char *datap;
	} *q;
	static struct chain *namechain= NULL;

	while (C == ' ')
		;	/* nothing */

	for (p = fname ; p - fname < sizeof(fname) && (*p = c) != '\n' &&
	    c != ' ' && c != '\t' && c != '\\'; ++p)
		C;
	*p = '\0';
	while (c != '\n')
		C;

	/* see if this name has already been used */
	for (q = namechain ; q; q = q->nextp)
		if (strcmp(fname, q->datap) == 0) {
			fname[0] = '\0';
			return;
		}

	q = (struct chain *) malloc(sizeof(struct chain));
	if (q == NULL)
		err(1, NULL);
	q->nextp = namechain;
	q->datap = strdup(fname);
	if (q->datap == NULL)
		err(1, NULL);
	namechain = q;
}

/*ARGSUSED*/
void
textline(char *str, int constant)
{

	if (wordflag) {
		msputwords(0);
		return;
	}
	puts(str);
}

void
work(void)
{

	for (;;) {
		C;
#ifdef FULLDEBUG
		printf("Starting work with `%c'\n", c);
#endif /* FULLDEBUG */
		if (c == '.' || c == '\'')
			comline();
		else
			regline(textline, TWO);
	}
}

void
regline(void (*pfunc)(char *, int), int constant)
{

	line[0] = c;
	lp = line;
	while (lp - line < sizeof(line)) {
		if (c == '\\') {
			*lp = ' ';
			backsl();
		}
		if (c == '\n')
			break;
		if (intable && c == 'T') {
			*++lp = C;
			if (c == '{' || c == '}') {
				lp[-1] = ' ';
				*lp = C;
			}
		} else {
			*++lp = C;
		}
	}
	*lp = '\0';

	if (line[0] != '\0')
		(*pfunc)(line, constant);
}

void
macro(void)
{

	if (msflag) {
		do {
			SKIP;
		} while (C!='.' || C!='.' || C=='.');	/* look for  .. */
		if (c != '\n')
			SKIP;
		return;
	}
	SKIP;
	inmacro = YES;
}

void
tbl(void)
{

	while (C != '.')
		;	/* nothing */
	SKIP;
	intable = YES;
}

void
stbl(void)
{

	while (C != '.')
		;	/* nothing */
	SKIP_TO_COM;
	if (c != 'T' || C != 'E') {
		SKIP;
		pc = c;
		while (C != '.' || pc != '\n' || C != 'T' || C != 'E')
			pc = c;
	}
}

void
eqn(void)
{
	int c1, c2;
	int dflg;
	char last;

	last=0;
	dflg = 1;
	SKIP;

	for (;;) {
		if (C1 == '.'  || c == '\'') {
			while (C1 == ' ' || c == '\t')
				;
			if (c == 'E' && C1 == 'N') {
				SKIP;
				if (msflag && dflg) {
					putchar('x');
					putchar(' ');
					if (last) {
						putchar(last);
						putchar('\n');
					}
				}
				return;
			}
		} else if (c == 'd') {
			/* look for delim */
			if (C1 == 'e' && C1 == 'l')
				if (C1 == 'i' && C1 == 'm') {
					while (C1 == ' ')
						;	/* nothing */

					if ((c1 = c) == '\n' ||
					    (c2 = C1) == '\n' ||
					    (c1 == 'o' && c2 == 'f' && C1=='f')) {
						ldelim = NOCHAR;
						rdelim = NOCHAR;
					} else {
						ldelim = c1;
						rdelim = c2;
					}
				}
			dflg = 0;
		}

		if (c != '\n')
			while (C1 != '\n') {
				if (chars[c] == PUNCT)
					last = c;
				else if (c != ' ')
					last = 0;
			}
	}
}

/* skip over a complete backslash construction */
void
backsl(void)
{
	int bdelim;

sw:
	switch (C) {
	case '"':
		SKIP;
		return;

	case 's':
		if (C == '\\')
			backsl();
		else {
			while (C >= '0' && c <= '9')
				;	/* nothing */
			ungetc(c, infile);
			c = '0';
		}
		--lp;
		return;

	case 'f':
	case 'n':
	case '*':
		if (C != '(')
			return;

	case '(':
		if (msflag) {
			if (C == 'e') {
				if (C == 'm') {
					*lp = '-';
					return;
				}
			}
			else if (c != '\n')
				C;
			return;
		}
		if (C != '\n')
			C;
		return;

	case '$':
		C;	/* discard argument number */
		return;

	case 'b':
	case 'x':
	case 'v':
	case 'h':
	case 'w':
	case 'o':
	case 'l':
	case 'L':
		if ((bdelim = C) == '\n')
			return;
		while (C != '\n' && c != bdelim)
			if (c == '\\')
				backsl();
		return;

	case '\\':
		if (inmacro)
			goto sw;

	default:
		return;
	}
}

void
sce(void)
{
	char *ap;
	int n, i;
	char a[10];

	for (ap = a; C != '\n'; ap++) {
		*ap = c;
		if (ap == &a[9]) {
			SKIP;
			ap = a;
			break;
		}
	}
	if (ap != a)
		n = atoi(a);
	else
		n = 1;
	for (i = 0; i < n;) {
		if (C == '.') {
			if (C == 'c') {
				if (C == 'e') {
					while (C == ' ')
						;	/* nothing */
					if (c == '0') {
						SKIP;
						break;
					} else
						SKIP;
				}
				else
					SKIP;
			} else if (c == 'P' || C == 'P') {
				if (c != '\n')
					SKIP;
				break;
			} else if (c != '\n')
				SKIP;
		} else {
			SKIP;
			i++;
		}
	}
}

void
refer(int c1)
{
	int c2;

	if (c1 != '\n')
		SKIP;

	for (c2 = -1;;) {
		if (C != '.')
			SKIP;
		else {
			if (C != ']')
				SKIP;
			else {
				while (C != '\n')
					c2 = c;
				if (c2 != -1 && chars[c2] == PUNCT)
					putchar(c2);
				return;
			}
		}
	}
}

void
inpic(void)
{
	int c1;
	char *p1;

	SKIP;
	p1 = line;
	c = '\n';
	for (;;) {
		c1 = c;
		if (C == '.' && c1 == '\n') {
			if (C != 'P') {
				if (c == '\n')
					continue;
				else {
					SKIP;
					c = '\n';
					continue;
				}
			}
			if (C != 'E') {
				if (c == '\n')
					continue;
				else {
					SKIP;
					c = '\n';
					continue;
				}
			}
			SKIP;
			return;
		}
		else if (c == '\"') {
			while (C != '\"') {
				if (c == '\\') {
					if (C == '\"')
						continue;
					ungetc(c, infile);
					backsl();
				} else
					*p1++ = c;
			}
			*p1++ = ' ';
		}
		else if (c == '\n' && p1 != line) {
			*p1 = '\0';
			if (wordflag)
				msputwords(NO);
			else {
				puts(line);
				putchar('\n');
			}
			p1 = line;
		}
	}
}

#ifdef DEBUG
int
_C1(void)
{

	return(C1get);
}

int
_C(void)
{

	return(Cget);
}
#endif /* DEBUG */

/*
 *	Put out a macro line, using ms and mm conventions.
 */
void
msputmac(char *s, int constant)
{
	char *t;
	int found;
	int last;

	last = 0;
	found = 0;
	if (wordflag) {
		msputwords(YES);
		return;
	}
	while (*s) {
		while (*s == ' ' || *s == '\t')
			putchar(*s++);
		for (t = s ; *t != ' ' && *t != '\t' && *t != '\0' ; ++t)
			;	/* nothing */
		if (*s == '\"')
			s++;
		if (t > s + constant && chars[(unsigned char)s[0]] == LETTER &&
		    chars[(unsigned char)s[1]] == LETTER) {
			while (s < t)
				if (*s == '\"')
					s++;
				else
					putchar(*s++);
			last = *(t-1);
			found++;
		} else if (found && chars[(unsigned char)s[0]] == PUNCT &&
		    s[1] == '\0') {
			putchar(*s++);
		} else {
			last = *(t - 1);
			s = t;
		}
	}
	putchar('\n');
	if (msflag && chars[last] == PUNCT) {
		putchar(last);
		putchar('\n');
	}
}

/*
 *	put out words (for the -w option) with ms and mm conventions
 */
void
msputwords(int macline)
{
	char *p, *p1;
	int i, nlet;

	for (p1 = line;;) {
		/*
		 *	skip initial specials ampersands and apostrophes
		 */
		while (chars[(unsigned char)*p1] < DIGIT)
			if (*p1++ == '\0')
				return;
		nlet = 0;
		for (p = p1 ; (i = chars[(unsigned char)*p]) != SPECIAL ; ++p)
			if (i == LETTER)
				++nlet;

		if (nlet > 1 && chars[(unsigned char)p1[0]] == LETTER) {
			/*
			 *	delete trailing ampersands and apostrophes
			 */
			while ((i = chars[(unsigned char)p[-1]]) == PUNCT ||
			    i == APOS )
				--p;
			while (p1 < p)
				putchar(*p1++);
			putchar('\n');
		} else {
			p1 = p;
		}
	}
}

/*
 *	put out a macro using the me conventions
 */
#define SKIPBLANK(cp)	while (*cp == ' ' || *cp == '\t') { cp++; }
#define SKIPNONBLANK(cp) while (*cp !=' ' && *cp !='\cp' && *cp !='\0') { cp++; }

void
meputmac(char *cp, int constant)
{
	char	*np;
	int	found;
	int	argno;
	int	last;
	int	inquote;

	last = 0;
	found = 0;
	if (wordflag) {
		meputwords(YES);
		return;
	}
	for (argno = 0; *cp; argno++) {
		SKIPBLANK(cp);
		inquote = (*cp == '"');
		if (inquote)
			cp++;
		for (np = cp; *np; np++) {
			switch (*np) {
			case '\n':
			case '\0':
				break;

			case '\t':
			case ' ':
				if (inquote)
					continue;
				else
					goto endarg;

			case '"':
				if (inquote && np[1] == '"') {
					memmove(np, np + 1, strlen(np));
					np++;
					continue;
				} else {
					*np = ' '; 	/* bye bye " */
					goto endarg;
				}

			default:
				continue;
			}
		}
		endarg: ;
		/*
		 *	cp points at the first char in the arg
		 *	np points one beyond the last char in the arg
		 */
		if ((argconcat == 0) || (argconcat != argno))
			putchar(' ');
#ifdef FULLDEBUG
		{
			char	*p;
			printf("[%d,%d: ", argno, np - cp);
			for (p = cp; p < np; p++) {
				putchar(*p);
			}
			printf("]");
		}
#endif /* FULLDEBUG */
		/*
		 *	Determine if the argument merits being printed
		 *
		 *	constant is the cut off point below which something
		 *	is not a word.
		 */
		if (((np - cp) > constant) &&
		    (inquote || (chars[(unsigned char)cp[0]] == LETTER))) {
			for (cp = cp; cp < np; cp++)
				putchar(*cp);
			last = np[-1];
			found++;
		} else if (found && (np - cp == 1) &&
		    chars[(unsigned char)*cp] == PUNCT) {
			putchar(*cp);
		} else {
			last = np[-1];
		}
		cp = np;
	}
	if (msflag && chars[last] == PUNCT)
		putchar(last);
	putchar('\n');
}

/*
 *	put out words (for the -w option) with ms and mm conventions
 */
void
meputwords(int macline)
{

	msputwords(macline);
}

/*
 *
 *	Skip over a nested set of macros
 *
 *	Possible arguments to noblock are:
 *
 *	fi	end of unfilled text
 *	PE	pic ending
 *	DE	display ending
 *
 *	for ms and mm only:
 *		KE	keep ending
 *
 *		NE	undocumented match to NS (for mm?)
 *		LE	mm only: matches RL or *L (for lists)
 *
 *	for me:
 *		([lqbzcdf]
 */
void
noblock(char a1, char a2)
{
	int c1,c2;
	int eqnf;
	int lct;

	lct = 0;
	eqnf = 1;
	SKIP;
	for (;;) {
		while (C != '.')
			if (c == '\n')
				continue;
			else
				SKIP;
		if ((c1 = C) == '\n')
			continue;
		if ((c2 = C) == '\n')
			continue;
		if (c1 == a1 && c2 == a2) {
			SKIP;
			if (lct != 0) {
				lct--;
				continue;
			}
			if (eqnf)
				putchar('.');
			putchar('\n');
			return;
		} else if (a1 == 'L' && c2 == 'L') {
			lct++;
			SKIP;
		}
		/*
		 *	equations (EQ) nested within a display
		 */
		else if (c1 == 'E' && c2 == 'Q') {
			if ((mac == ME && a1 == ')')
			    || (mac != ME && a1 == 'D')) {
				eqn();
				eqnf=0;
			}
		}
		/*
		 *	turning on filling is done by the paragraphing
		 *	macros
		 */
		else if (a1 == 'f') {	/* .fi */
			if  ((mac == ME && (c2 == 'h' || c2 == 'p'))
			    || (mac != ME && (c1 == 'P' || c2 == 'P'))) {
				SKIP;
				return;
			}
		} else {
			SKIP;
		}
	}
}

int
EQ(void)
{

	eqn();
	return(0);
}

int
domacro(void)
{

	macro();
	return(0);
}

int
PS(void)
{

	for (C; c == ' ' || c == '\t'; C)
		;	/* nothing */

	if (c == '<') {		/* ".PS < file" -- don't expect a .PE */
		SKIP;
		return(0);
	}
	if (!msflag)
		inpic();
	else
		noblock('P', 'E');
	return(0);
}

int
skip(void)
{

	SKIP;
	return(0);
}

int
intbl(void)
{

	if (msflag)
		stbl();
	else
		tbl();
	return(0);
}

int
outtbl(void)
{

	intable = NO;
	return(0);
}

int
so(void)
{

	if (!iflag) {
		getfname();
		if (fname[0]) {
			if (++filesp - &files[0] > MAXFILES)
				err(1, "too many nested files (max %d)",
				    MAXFILES);
			infile = *filesp = opn(fname);
		}
	}
	return(0);
}

int
nx(void)
{

	if (!iflag) {
		getfname();
		if (fname[0] == '\0')
			exit(0);
		if (infile != stdin)
			fclose(infile);
		infile = *filesp = opn(fname);
	}
	return(0);
}

int
skiptocom(void)
{

	SKIP_TO_COM;
	return(COMX);
}

int
PP(pacmac c12)
{
	int c1, c2;

	frommac(c12, c1, c2);
	printf(".%c%c", c1, c2);
	while (C != '\n')
		putchar(c);
	putchar('\n');
	return(0);
}

int
AU(void)
{

	if (mac == MM)
		return(0);
	SKIP_TO_COM;
	return(COMX);
}

int
SH(pacmac c12)
{
	int c1, c2;

	frommac(c12, c1, c2);

	if (parag) {
		printf(".%c%c", c1, c2);
		while (C != '\n')
			putchar(c);
		putchar(c);
		putchar('!');
		for (;;) {
			while (C != '\n')
				putchar(c);
			putchar('\n');
			if (C == '.')
				return(COM);
			putchar('!');
			putchar(c);
		}
		/*NOTREACHED*/
	} else {
		SKIP_TO_COM;
		return(COMX);
	}
}

int
UX(void)
{

	if (wordflag)
		printf("UNIX\n");
	else
		printf("UNIX ");
	return(0);
}

int
MMHU(pacmac c12)
{
	int c1, c2;

	frommac(c12, c1, c2);
	if (parag) {
		printf(".%c%c", c1, c2);
		while (C != '\n')
			putchar(c);
		putchar('\n');
	} else {
		SKIP;
	}
	return(0);
}

int
mesnblock(pacmac c12)
{
	int c1, c2;

	frommac(c12, c1, c2);
	noblock(')', c2);
	return(0);
}

int
mssnblock(pacmac c12)
{
	int c1, c2;

	frommac(c12, c1, c2);
	noblock(c1, 'E');
	return(0);
}

int
nf(void)
{

	noblock('f', 'i');
	return(0);
}

int
ce(void)
{

	sce();
	return(0);
}

int
meip(pacmac c12)
{

	if (parag)
		mepp(c12);
	else if (wordflag)	/* save the tag */
		regline(meputmac, ONE);
	else
		SKIP;
	return(0);
}

/*
 *	only called for -me .pp or .sh, when parag is on
 */
int
mepp(pacmac c12)
{

	PP(c12);		/* eats the line */
	return(0);
}

/*
 *	Start of a section heading; output the section name if doing words
 */
int
mesh(pacmac c12)
{

	if (parag)
		mepp(c12);
	else if (wordflag)
		defcomline(c12);
	else
		SKIP;
	return(0);
}

/*
 *	process a font setting
 */
int
mefont(pacmac c12)
{

	argconcat = 1;
	defcomline(c12);
	argconcat = 0;
	return(0);
}

int
manfont(pacmac c12)
{

	return(mefont(c12));
}

int
manpp(pacmac c12)
{

	return(mepp(c12));
}

void
defcomline(pacmac c12)
{
	int c1, c2;

	frommac(c12, c1, c2);
	if (msflag && mac == MM && c2 == 'L') {
		if (disp || c1 == 'R') {
			noblock('L', 'E');
		} else {
			SKIP;
			putchar('.');
		}
	}
	else if (c1 == '.' && c2 == '.') {
		if (msflag) {
			SKIP;
			return;
		}
		while (C == '.')
			/*VOID*/;
	}
	++inmacro;
	/*
	 *	Process the arguments to the macro
	 */
	switch (mac) {
	default:
	case MM:
	case MS:
		if (c1 <= 'Z' && msflag)
			regline(msputmac, ONE);
		else
			regline(msputmac, TWO);
		break;
	case ME:
		regline(meputmac, ONE);
		break;
	}
	--inmacro;
}

void
comline(void)
{
	int	c1;
	int	c2;
	pacmac	c12;
	int	mid;
	int	lb, ub;
	int	hit;
	static	int	tabsize = 0;
	static	struct	mactab	*mactab = (struct mactab *)0;
	struct	mactab	*mp;

	if (mactab == 0)
		 buildtab(&mactab, &tabsize);
com:
	while (C == ' ' || c == '\t')
		;
comx:
	if ((c1 = c) == '\n')
		return;
	c2 = C;
	if (c1 == '.' && c2 != '.')
		inmacro = NO;
	if (msflag && c1 == '[') {
		refer(c2);
		return;
	}
	if (parag && mac==MM && c1 == 'P' && c2 == '\n') {
		printf(".P\n");
		return;
	}
	if (c2 == '\n')
		return;
	/*
	 *	Single letter macro
	 */
	if (mac == ME && (c2 == ' ' || c2 == '\t') )
		c2 = ' ';
	c12 = tomac(c1, c2);
	/*
	 *	binary search through the table of macros
	 */
	lb = 0;
	ub = tabsize - 1;
	while (lb <= ub) {
		mid = (ub + lb) / 2;
		mp = &mactab[mid];
		if (mp->macname < c12)
			lb = mid + 1;
		else if (mp->macname > c12)
			ub = mid - 1;
		else {
			hit = 1;
#ifdef FULLDEBUG
			printf("preliminary hit macro %c%c ", c1, c2);
#endif /* FULLDEBUG */
			switch (mp->condition) {
			case NONE:
				hit = YES;
				break;
			case FNEST:
				hit = (filesp == files);
				break;
			case NOMAC:
				hit = !inmacro;
				break;
			case MAC:
				hit = inmacro;
				break;
			case PARAG:
				hit = parag;
				break;
			case NBLK:
				hit = !keepblock;
				break;
			default:
				hit = 0;
			}

			if (hit) {
#ifdef FULLDEBUG
				printf("MATCH\n");
#endif /* FULLDEBUG */
				switch ((*(mp->func))(c12)) {
				default:
					return;
				case COMX:
					goto comx;
				case COM:
					goto com;
				}
			}
#ifdef FULLDEBUG
			printf("FAIL\n");
#endif /* FULLDEBUG */
			break;
		}
	}
	defcomline(c12);
}

int
macsort(const void *p1, const void *p2)
{
	struct mactab *t1 = (struct mactab *)p1;
	struct mactab *t2 = (struct mactab *)p2;

	return(t1->macname - t2->macname);
}

int
sizetab(struct mactab *mp)
{
	int i;

	i = 0;
	if (mp) {
		for (; mp->macname; mp++, i++)
			/*VOID*/ ;
	}
	return(i);
}

struct mactab *
macfill(struct mactab *dst, struct mactab *src)
{

	if (src) {
		while (src->macname)
			*dst++ = *src++;
	}
	return(dst);
}

__dead void
usage(void)
{
	extern char *__progname;

	fprintf(stderr, "usage: %s [-ikpw] [-m a | e | l | m | s] [file ...]\n", __progname);
	exit(1);
}

void
buildtab(struct mactab **r_back, int *r_size)
{
	int	size;
	struct	mactab	*p, *p1, *p2;
	struct	mactab	*back;

	size = sizetab(troffmactab) + sizetab(ppmactab);
	p1 = p2 = NULL;
	if (msflag) {
		switch (mac) {
		case ME:
			p1 = memactab;
			break;
		case MM:
			p1 = msmactab;
			p2 = mmmactab;
			break;
		case MS:
			p1 = msmactab;
			break;
		case MA:
			p1 = manmactab;
			break;
		default:
			break;
		}
	}
	size += sizetab(p1);
	size += sizetab(p2);
	back = (struct mactab *)calloc(size+2, sizeof(struct mactab));
	if (back == NULL)
		err(1, NULL);

	p = macfill(back, troffmactab);
	p = macfill(p, ppmactab);
	p = macfill(p, p1);
	p = macfill(p, p2);

	qsort(back, size, sizeof(struct mactab), macsort);
	*r_size = size;
	*r_back = back;
}

/*
 *	troff commands
 */
struct	mactab	troffmactab[] = {
	M(NONE,		'\\','"',	skip),	/* comment */
	M(NOMAC,	'd','e',	domacro),	/* define */
	M(NOMAC,	'i','g',	domacro),	/* ignore till .. */
	M(NOMAC,	'a','m',	domacro),	/* append macro */
	M(NBLK,		'n','f',	nf),	/* filled */
	M(NBLK,		'c','e',	ce),	/* centered */

	M(NONE,		's','o',	so),	/* source a file */
	M(NONE,		'n','x',	nx),	/* go to next file */

	M(NONE,		't','m',	skip),	/* print string on tty */
	M(NONE,		'h','w',	skip),	/* exception hyphen words */
	M(NONE,		0,0,		0)
};

/*
 *	Preprocessor output
 */
struct	mactab	ppmactab[] = {
	M(FNEST,	'E','Q',	EQ),	/* equation starting */
	M(FNEST,	'T','S',	intbl),	/* table starting */
	M(FNEST,	'T','C',	intbl),	/* alternative table? */
	M(FNEST,	'T','&',	intbl),	/* table reformatting */
	M(NONE,		'T','E',	outtbl),/* table ending */
	M(NONE,		'P','S',	PS),	/* picture starting */
	M(NONE,		0,0,		0)
};

/*
 *	Particular to ms and mm
 */
struct	mactab	msmactab[] = {
	M(NONE,		'T','L',	skiptocom),	/* title follows */
	M(NONE,		'F','S',	skiptocom),	/* start footnote */
	M(NONE,		'O','K',	skiptocom),	/* Other kws */

	M(NONE,		'N','R',	skip),	/* undocumented */
	M(NONE,		'N','D',	skip),	/* use supplied date */

	M(PARAG,	'P','P',	PP),	/* begin parag */
	M(PARAG,	'I','P',	PP),	/* begin indent parag, tag x */
	M(PARAG,	'L','P',	PP),	/* left blocked parag */

	M(NONE,		'A','U',	AU),	/* author */
	M(NONE,		'A','I',	AU),	/* authors institution */

	M(NONE,		'S','H',	SH),	/* section heading */
	M(NONE,		'S','N',	SH),	/* undocumented */
	M(NONE,		'U','X',	UX),	/* unix */

	M(NBLK,		'D','S',	mssnblock),	/* start display text */
	M(NBLK,		'K','S',	mssnblock),	/* start keep */
	M(NBLK,		'K','F',	mssnblock),	/* start float keep */
	M(NONE,		0,0,		0)
};

struct	mactab	mmmactab[] = {
	M(NONE,		'H',' ',	MMHU),	/* -mm ? */
	M(NONE,		'H','U',	MMHU),	/* -mm ? */
	M(PARAG,	'P',' ',	PP),	/* paragraph for -mm */
	M(NBLK,		'N','S',	mssnblock),	/* undocumented */
	M(NONE,		0,0,		0)
};

struct	mactab	memactab[] = {
	M(PARAG,	'p','p',	mepp),
	M(PARAG,	'l','p',	mepp),
	M(PARAG,	'n','p',	mepp),
	M(NONE,		'i','p',	meip),

	M(NONE,		's','h',	mesh),
	M(NONE,		'u','h',	mesh),

	M(NBLK,		'(','l',	mesnblock),
	M(NBLK,		'(','q',	mesnblock),
	M(NBLK,		'(','b',	mesnblock),
	M(NBLK,		'(','z',	mesnblock),
	M(NBLK,		'(','c',	mesnblock),

	M(NBLK,		'(','d',	mesnblock),
	M(NBLK,		'(','f',	mesnblock),
	M(NBLK,		'(','x',	mesnblock),

	M(NONE,		'r',' ',	mefont),
	M(NONE,		'i',' ',	mefont),
	M(NONE,		'b',' ',	mefont),
	M(NONE,		'u',' ',	mefont),
	M(NONE,		'q',' ',	mefont),
	M(NONE,		'r','b',	mefont),
	M(NONE,		'b','i',	mefont),
	M(NONE,		'b','x',	mefont),
	M(NONE,		0,0,		0)
};

struct	mactab	manmactab[] = {
	M(PARAG,	'B','I',	manfont),
	M(PARAG,	'B','R',	manfont),
	M(PARAG,	'I','B',	manfont),
	M(PARAG,	'I','R',	manfont),
	M(PARAG,	'R','B',	manfont),
	M(PARAG,	'R','I',	manfont),

	M(PARAG,	'P','P',	manpp),
	M(PARAG,	'L','P',	manpp),
	M(PARAG,	'H','P',	manpp),
	M(NONE,		0,0,		0)
};