root/usr.bin/deroff/deroff.c
/*      $OpenBSD: deroff.c,v 1.19 2026/04/01 17:49:40 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[256];  /* SPECIAL, PUNCT, APOS, DIGIT, or LETTER */

size_t linesz;
char *line;
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;

        if (pledge("stdio rpath", NULL) == -1)
                err(1, "pledge");

        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 = 1;
                                break;
                        }
                        if (optarg[1] != '\0')
                                errflg = 1;
                        break;
                case 'p':
                        parag = YES;
                        break;
                case 'w':
                        wordflag = YES;
                        kflag = YES;
                        break;
                default:
                        errflg = 1;
                }
        }
        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];

        linesz = LINE_MAX;
        if ((line = malloc(linesz)) == NULL)
                err(1, NULL);

        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 = 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;
}

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;
        for (;;) {
                if (lp - line == linesz - 1) {
                        char *newline = reallocarray(line, linesz, 2);
                        if (newline == NULL)
                                err(1, NULL);
                        lp = newline + (lp - line);
                        line = newline;
                        linesz *= 2;
                }
                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, *ep;

        SKIP;
        p1 = line;
        ep = line + sizeof(line) - 1;
        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 if (p1 + 1 >= ep) {
                                        errx(1, ".PS length exceeds limit");
                                } 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 < 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 = NULL;
        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 = 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)
};