Logo Search packages:      
Sourcecode: ksh version File versions  Download package

troff2html.c

/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1996-2007 AT&T Intellectual Property          *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                    by AT&T Intellectual Property                     *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * Glenn Fowler
 * AT&T Research
 *
 * troff to html filter
 *
 * NOTE: not handled in state.groff mode
 *
 *    \?anything\?
 */

static const char usage[] =
"[-?\n@(#)$Id: troff2html (AT&T Research) 2004-04-26 $\n]"
USAGE_LICENSE
"[+NAME?troff2html - convert troff/groff input to html]"
"[+DESCRIPTION?\btroff2html\b converts \btroff\b(1) (or \bgroff\b(1),"
"     depending on the processing mode) input documents to an \bhtml\b"
"     document on the standard output. Although a full \atroff\a parse"
"     is done, many features are ignored in the mapping to \bhtml\b.]"
"[+?The \atroff\a \bt\b condition test evaluates \btrue\b, the \bn\b"
"     condition tests evaluates \bfalse\b, and the \agroff\a compatibility"
"     register \b\\\\n[.C]]\b evaluates to 0 (enable \agroff\a parsing)"
"     if it is referenced before the first \agroff\a \b.cp\b (compatibility"
"     mode) request.]"
"[+?The generated \bhtml\b has properly nested begin/end tags, even though most"
"     browsers don't care.]"

"[i:identify?Reads identification options from \afile\a. Unknown options"
"     are silently ignored. See the \b.xx\b request below for a description"
"     of the options.]:[file]"
"[I:include?Appends \adirectory\a to the list of directories searched"
"     for \b--macros\b and \b.so\b request files.]:[directory]"
"[m:macros?Locates and reads the macro package file \apackage\a. In order"
"     to accomodate different \atroff\a installation styles the file search"
"     order is fairly involved:]:[package]{"
"           [+./package?]"
"           [+directory/package?for all \b--include\b directories]"
"           [+directory/tmac.package?for all \b--include\b directories]"
"           [+../lib/tmac/tmac.package?for all directories on \b$PATH\b]"
"           [+../lib/html/package?for all directories on \b$PATH\b]"
"           [+../lib/html/mpackage.tr?for all directories on \b$PATH\b]"
"           [+../share/groff/tmac/tmac.package?for all directories on \b$PATH\b]"
"           [+../groff/share/groff/tmac/tmac.package?for all directories on \b$PATH\b]"
"}"
"[r:register?Initializes the number \aregister\a to \aN\a.]:[registerN]"
"[s:script?Reads \ascript\a as if it came from a file. \b--script='.nr A 123'\b"
"     is equivalent to \b-rA123\b.]:[script]"
"[v:verbose?Enables verbose error and warning messages. Following \atroff\a"
"     tradition, \btroff2html\b by default does not warn about unknown"
"     requests; \b--verbose\b enables such warnings.]"

"[+EXTENSIONS?\b.xx\b \aname\a[=\avalue\a]] is a special \btroff2html\b"
"     request that handles program tracing, \bhtml\b extensions and \atroff\a"
"     macro package magic that went way past the author's willingness"
"     to understand. Supported operations are:]{"
"     [+author=text?Specifies the contact information for the document"
"           HEAD section.]"
"     [+background=URL?Specifies the document background URL.]"
"     [+debug=level?The debug trace \alevel\a; higher levels produce"
"           more output.]"
"     [+get=+-register?Traces each \bget\b for the named number"
"           \aregister\a. \b-\b turns tracing off.]"
"     [+hot='word ...'?Adds (\b+\b) or removes (\b-\b) \aword\a ... from"
"           the hot-word list. Constructs that match (\ahot-word\a ..."
"           \aSWITCH-FONT\a text \aSWITCH-FONT\a ... ) adds \atext\a as"
"           a hot link to another portion of the document. \brefer\b and"
"           \bsee\b are the default hot words. Case is ignored when"
"           matching hot words.]"
"     [+logo=URL?Specifies the logo/banner image URL that is centered"
"           at the top of the document.]"
"     [+mailto=address?Sets the email \aaddress\a to send comments and"
"           suggestions.]"
"     [+meta.name?Emits the \bhtml\b tag \b<META name=\b\"\aname\a\""
"           \bcontent=\b\"\acontent\a\"\b>\b.]"
"     [+package=text?\atext\a is prepended to the \bhtml\b document title.]"
"     [+set=+-register?Traces each \bset\b for the named number"
"           \aregister\a. \b-\b turns tracing off.]"
"     [+title=text?Sets the document title.]"
"}"
"[+?Local URL links are generated for all top level headings. These can be"
"     referenced by embedding the benign (albeit convoluted) \atroff\a"
"     construct \\h'0*\\w\"label\"'text\\g'0', where \alabel\a is the"
"     local link label and \atext\a is the hot link text. If \alabel\a"
"     and \atext\a are the same then use \\h'0*1'text\\h'0'.]"

"\n"
"\n[ file ... ]\n"
"\n"
"[+SEE ALSO?\bgroff\b(1), \bhtml2rtf\b(1), \bman\b(1), \bmm\b(1),"
"     \bmm2html\b(1), \btroff\b(1)]"
;

#include "troff2html.h"

#include <error.h>
#include <hashkey.h>
#include <ls.h>
#include <tm.h>

/*
 * intermediate code
 *
 *    CODE_0
 *    CODE_1 <op>
 *    CODE_2 <data> <op>
 *    CODE_n <length> <op> <length-data>
 */

#define DEFAULT_cc      '.'
#define DEFAULT_c2      '\''
#define DEFAULT_ec      '\\'
#define DEFAULT_ft      1
#define DEFAULT_pc      '%'
#define DEFAULT_ps      10
#define DEFAULT_vs      12

#define CODE_1          1
#define CODE_2          2
#define CODE_n          3
#define CODE_0          4

#define END(x)          ((x)|OP_END)
#define LABEL(x)  ((x)|OP_LABEL)
#define OP(x)           ((x)&077)

#define OP_END          0200
#define OP_LABEL  0100

#define OP_a            1
#define OP_body         2
#define OP_br           3
#define OP_cc           4
#define OP_center 5
#define OP_comment      6
#define OP_dd           7
#define OP_div          8
#define OP_dl           9
#define OP_dt           10
#define OP_fn           11
#define OP_ft1          12
#define OP_ft2          13
#define OP_ft3          14
#define OP_ft4          15
#define OP_ft5          16
#define OP_h2           17
#define OP_h3           18
#define OP_h4           19
#define OP_head         20
#define OP_hr           21
#define OP_html         22
#define OP_link         23
#define OP_p            24
#define OP_pre          25
#define OP_ps           26
#define OP_sub          27
#define OP_sup          28
#define OP_ta           29
#define OP_tab          30
#define OP_tab_data     31
#define OP_tab_head     32
#define OP_tab_row      33
#define OP_title  34
#define OP_RAW          35

#define OP_ft           (OP_ft1-1)

#define ARG_ALIGN(a)    (((a)&3)-1)
#define ARG_ATTR(a)     ((a)&0x0fff)

#define ARG_left  0x0001
#define ARG_center      0x0002
#define ARG_right 0x0003

#define ARG_compact     0x0010
#define ARG_wide  0x0020

#define ATT_INDEX(a)    (((a)&0x0fff)-1)

#define ATT_NUMBER      0x8000

#define ATT_background  1
#define ATT_href  2
#define ATT_lref  3
#define ATT_id          4
#define ATT_name  5
#define ATT_size  (ATT_NUMBER|6)
#define ATT_src         7

#define LINE            0x2000
#define LIST            0x4000
#define STACK           0x8000

#define DIVERTED()      (state.out_top>state.out_stack)
#undef      EOF
#define EOF       0
#define INDEX(a,b)      (((a)<<8)|(b))
#define ISFILE()  ((state.in_top-1)->ip)
#define GETC()          (*state.in++)
#define GROFFINIT()     do{if(!state.groff_init)state.groff|=state.groff_init=1;}while(0)
#define UNGETC(c) (*--state.in=(c))

#define UNGET_MAX (SF_BUFSIZE/8)

State_t                 state;

/*
 * sfstruse(sp) with fatal error check
 */

static char*
use(Sfio_t* sp)
{
      char* s;

      if (!(s = sfstruse(sp)))
            error(ERROR_SYSTEM|3, "out of space");
      return s;
}

/*
 * push input file/string
 */

static void
pushin(char* name, int line, Sfio_t* ip, char* data, Arg_t* arg)
{
      register Pushin_t*      pp;
      int               n;

      if (state.in_top >= &state.in_stack[elementsof(state.in_stack)])
            error(3, "input stack overflow");
      pp = state.in_top++;
      pp->in = state.in;
      if (!(pp->ip = ip))
      {
            if (!(state.in = (unsigned char*)data) || !*state.in)
            {
                  state.in = pp->in;
                  --state.in_top;
                  return;
            }
      }
      else if (!(pp->buf = oldof(0, unsigned char, SF_BUFSIZE, UNGET_MAX + 1)))
            error(ERROR_SYSTEM|3, "out of space [file pushin]");
      else
      {
            pp->buf += UNGET_MAX;
            if ((n = sfread(ip, pp->buf, SF_BUFSIZE)) <= 0)
            {
                  if (ip && ip != sfstdin)
                        sfclose(ip);
                  state.in_top--;
                  return;
            }
            pp->end = pp->buf + n;
            *pp->end++ = EOF;
            state.in = pp->buf;
      }
      pp->file = error_info.file;
      if (name)
      {
            if (!state.original.file)
            {
                  state.original.file = error_info.file;
                  state.original.line = error_info.line;
            }
            error_info.file = name;
      }
      pp->line = error_info.line;
      if (line < 0)
            state.noline++;
      else
      {
            if (line > 0)
                  error_info.line = line;
            if (state.noline > 0)
                  state.noline++;
      }
      if (arg)
      {
            pp->mac = state.mac;
            state.mac = arg;
            if (pp->top.sp)
                  sfstrseek(pp->top.sp, 0, SEEK_SET);
            else if (!(pp->top.sp = sfstropen()))
                  error(ERROR_SYSTEM|3, "out of space [macro call]");
            pp->tag = state.tag;
            state.tag = &pp->top;
      }
      else
            pp->tag = 0;
}

static long expression(char*, char**, int);

/*
 * return the next expression operand
 */

static long
operand(register char* s, char** e, int scale)
{
      register int      c;
      register long     n;
      register long     f;
      int         abs;
      int         neg;
      long        d;
      char*       x;

      while (isspace(*s))
            s++;
      if (abs = *s == '|')
            s++;
      if (neg = *s == '-')
            s++;
      else if (*s == '+')
            s++;
      d = 1;
      if (*s == '(')
      {
            n = (state.groff && isalpha(*(s + 1)) && *(s + 2) == ';') ?
                  expression(s + 3, &x, *(s + 1)) :
                  expression(s + 1, &x, scale);
            s = x;
            if (*s == ')')
                  s++;
            c = *s++;
      }
      else
      {
            n = 0;
            while ((c = *s++) >= '0' && c <= '9')
                  n = n * 10 + c - '0';
            if (c == '.')
            {
                  f = 0;
                  while ((c = *s++) >= '0' && c <= '9')
                  {
                        d *= 10;
                        n *= 10;
                        f = f * 10 + c - '0';
                  }
                  n += f;
            }
      }
      for (;;)
      {
            switch (c)
            {
            case 'P':
                  n *= 72;
                  break;
            case 'c':
                  n *= 170;
                  break;
            case 'i':
                  n *= 432;
                  break;
            case 'm':
                  n *= 8 * state.env->ps.current;
                  break;
            case 'n':
            case 'w':
                  n *= 6 * state.env->ps.current;
                  break;
            case 'p':
                  n *= 6;
                  break;
            case 's':
            case 'u':
            case 'z':
                  break;
            case 'v':
                  n *= 6 * state.env->vs.current;
                  break;
            default:
                  s--;
                  if (c = scale)
                  {
                        scale = 0;
                        continue;
                  }
                  break;
            }
            break;
      }
      n /= d;
      if (e)
      {
            while (isspace(*s))
                  s++;
            *e = s;
      }
      if (abs)
            return 1;
      if (neg)
            n = -n;
      return n;
}

/*
 * convert units to scale
 */

static long
convert(long n, int up, int scale)
{
      long  m;

      m = n;
      switch (scale)
      {
      case 'M':
            if (up)
                  n *= state.env->ps.current / 12;
            else
                  n /= state.env->ps.current / 12;
            break;
      case 'P':
            if (up)
                  n *= 72;
            else
                  n /= 72;
            break;
      case 'c':
            if (up)
                  n *= 170;
            else
                  n /= 170;
            break;
      case 'i':
            if (up)
                  n *= 432;
            else
                  n /= 432;
            break;
      case 'm':
            if (up)
                  n *= 8 * state.env->ps.current;
            else
                  n /= 8 * state.env->ps.current;
            break;
      case 'n':
      case 'w':
            if (up)
                  n *= 6 * state.env->ps.current;
            else
                  n /= 6 * state.env->ps.current;
            break;
      case 'p':
            if (up)
                  n *= 6;
            else
                  n /= 6;
            break;
      case 's':
      case 'u':
      case 'z':
            break;
      case 'v':
            if (up)
                  n *= 6 * state.env->vs.current;
            else
                  n /= 6 * state.env->vs.current;
            break;
      }
      return n ? n : (m > 0) ? 1 : 0;
}

/*
 * evaluate numeric expression in s
 */

static long
expression(char* s, char** e, int scale)
{
      long  n;
      long  m;

      n = operand(s, &s, scale);
      for (;;)
      {
            switch (*s++)
            {
            case CODE_2:
                  s++;
                  /*FALLTHROUGH*/
            case CODE_1:
                  s++;
                  /*FALLTHROUGH*/
            case CODE_0:
                  continue;
            case '+':
                  n += operand(s, &s, scale);
                  continue;
            case '-':
                  n -= operand(s, &s, scale);
                  continue;
            case '/':
                  if (m = operand(s, &s, scale))
                        n /= m;
                  else
                        n = 0;
                  continue;
            case '*':
                  n *= operand(s, &s, scale);
                  continue;
            case '%':
                  if (m = operand(s, &s, scale))
                        n %= m;
                  else
                        n = 0;
                  continue;
            case '<':
                  if (state.groff && *s == '?')
                  {
                        m = operand(s + 1, &s, scale);
                        if (m < n)
                              n = m;
                  }
                  else if (*s == '=')
                        n = n <= operand(s + 1, &s, scale);
                  else
                        n = n < operand(s, &s, scale);
                  continue;
            case '>':
                  if (state.groff && *s == '?')
                  {
                        m = operand(s + 1, &s, scale);
                        if (m > n)
                              n = m;
                  }
                  else if (*s == '=')
                        n = n >= operand(s + 1, &s, scale);
                  else
                        n = n > operand(s, &s, scale);
                  continue;
            case '=':
                  if (*s == '=')
                        s++;
                  n = n == operand(s, &s, scale);
                  continue;
            case '&':
                  if (*s == '&')
                        s++;
                  n = (operand(s, &s, scale) > 0) && (n > 0);
                  continue;
            case ':':
                  if (*s == ':')
                        s++;
                  n = (operand(s, &s, scale) > 0) || (n > 0);
                  continue;
            default:
                  s--;
                  break;
            }
            break;
      }
      if (e)
      {
            while (isspace(*s))
                  s++;
            *e = s;
      }
      if (scale)
            n = convert(n, 0, scale);
      return n;
}

/*
 * evaluate a conditional expression
 */

static long
cond(register char* s, char** e)
{
      register int      c;
      register int      i;
      register int      q;
      register long     v;
      register char*    t;
      char*       u;

      while (isspace(*s))
            s++;
      if (i = *s == '!')
            s++;
      while (isspace(*s))
            s++;
      switch (c = *s)
      {
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
      case '-':
      case '+':
      case '(':
            v = expression(s, &u, 0) > 0;
            s = u;
            break;
      case 'c':
            s++;
            if (*s == 'c' && *(s + 1) == 'h')
                  s += 2;
            v = 0;
            break;
      case 'd':
      case 'r':
            t = s;
            while (*++s && !isspace(*s));
            q = *s;
            *s = 0;
            *t = c == 'd' ? '.' : 'n';
            v = hashget(state.symbols, t) != 0;
            *t = c;
            *s = q;
            break;
      case 'g':
            GROFFINIT();
            v = state.groff != 0;
            s++;
            break;
      case 'o':
      case 't':
            v = 1;
            s++;
            break;
      default:
            if (isalpha(c))
            {
                  v = 0;
                  s++;
            }
            else
            {
                  for (u = ++s; *s && *s != c; s++);
                  if (*s)
                  {
                        *s++ = 0;
                        for (t = s; *s && *s != c; s++);
                        if (*s)
                              *s++ = 0;
                        v = !strcmp(u, t);
                  }
                  else 
                        v = 0;
            }
            break;
      }
      if (i)
            v = !v;
      while (isspace(*s))
            s++;
      if (e)
            *e = s;
      return v;
}

static void expand(Sfio_t*, char*);

/*
 * while test with expansion
 */

static long
test(register char* s, char** e, int* f)
{
      register char*    t;
      char*       q;
      int         n;
      long        v;

      if (state.ec)
      {
            t = s;
            while (t = strchr(t, state.ec))
                  if (*++t != '{' && *t != '}')
                        break;
            if (t)
            {
                  expand(state.arg, s);
                  sfputr(state.tmp, use(state.arg), 0);
                  s = use(state.tmp);
            }
      }
      v = cond(s, &q);
      n = 0;
      for (t = q; *t; t++)
      {
            if (*t == state.ec)
            {
                  if (*(t + 1) == '{' || isspace(*(t + 1)))
                  {
                        t++;
                        n = COND_BLOCK;
                  }
                  else
                        break;
            }
            else if (!isspace(*t))
                  break;
      }
      if (e)
            *e = t;
      if (f)
            *f = n;
      return v;
}

/*
 * pop input stream
 * return non-0 on EOF
 */

static int
popin(void)
{
      register Pushin_t*      pp;
      int               n;

      if (state.in_top <= state.in_stack)
      {
            state.in--;
            return 1;
      }
      pp = state.in_top;
      if (pp->loop)
      {
            state.in = (unsigned char*)sfstrbase(pp->loop);
            return 0;
      }
      if (--pp == state.in_stack)
      {
            if (state.cond.level > 1 && state.verbose)
                  error(1, "%d missing .el request%s", state.cond.level - 1, state.cond.level == 2 ? "" : "s");
            state.cond.level = 0;
      }
      if (pp->ip)
      {
            if (state.in < pp->end)
                  return 0;
            if ((n = sfread(pp->ip, pp->buf, SF_BUFSIZE)) > 0)
            {
                  pp->end = pp->buf + n;
                  *pp->end++ = EOF;
                  state.in = pp->buf;
                  return 0;
            }
            if (pp->ip && pp->ip != sfstdin)
                  sfclose(pp->ip);
            free(pp->buf - UNGET_MAX);
      }
      state.in_top = pp;
      if (state.noline > 0)
            state.noline--;
      if ((error_info.file = pp->file) == state.original.file)
      {
            state.original.file = 0;
            state.original.line = 0;
      }
      error_info.line = pp->line;
      if (pp->tag)
      {
            state.tag = pp->tag;
            state.mac = pp->mac;
      }
      state.in = pp->in;
      return 0;
}

/*
 * copy <type><name> into buf of size n
 */

static char*
nam(int type, register char* name, char* buf, size_t n)
{
      register char*    t = buf;
      register char*    e = t + n - 1;

      *t++ = type;
      if ((*t++ = *name++) && (*t++ = *name))
      {
            if (state.groff)
                  while (t < e && (*t++ = *++name));
            else
                  *t = 0;
      }
      return buf;
}

/*
 * return number register pointer given name
 * pointer created if not found
 */

static Num_t*
num(register char* s)
{
      register Num_t*   np;
      char        buf[MAXNAME];

      if (!(np = (Num_t*)hashget(state.symbols, nam('n', s, buf, sizeof(buf)))))
      {
            if (!(np = newof(0, Num_t, 1, 0)))
                  error(ERROR_SYSTEM|3, "out of space [number]");
            np->name = hashput(state.symbols, NiL, np);
            np->format = '1';
            np->increment = 1;
      }
      return np;
}

/*
 * lookup or create (force!=0) io stream handle
 */

static Stream_t*
iop(char* s, int force)
{
      register Stream_t*      sp;
      char              buf[MAXNAME];

      if (!(sp = (Stream_t*)hashget(state.symbols, nam('s', s, buf, sizeof(buf)))))
      {
if (!force) error(1, "iop %s lookup failed", buf);
            if (!force)
                  return 0;
            if (!(sp = newof(0, Stream_t, 1, 0)))
                  error(ERROR_SYSTEM|3, "out of space [stream]");
            hashput(state.symbols, NiL, sp);
      }
      return sp;
}

/*
 * push output stream np
 */

static void
pushout(Sfio_t* np)
{
      if (state.out_top >= &state.out_stack[elementsof(state.out_stack)])
            error(3, "output stack overflow");
      *state.out_top++ = state.out;
      iop("stdout", 1)->sp = state.out = np;
}

/*
 * pop ouput stream
 * if retain>0 then strdup() buffer
 * append an extra 0 for easy arg '\n' append by if/ie/el
 */

static char*
popout(void)
{
      char* s;

      if (!DIVERTED())
            return 0;
      if (state.out == state.tag->sp)
            sfputc(state.out, 0);
      if (state.out == state.nul)
            s = 0;
      else
            s = use(state.out);
      iop("stdout", 1)->sp = state.out = *--state.out_top;
      return s;
}

/*
 * return next character with possible line join
 */

static int
nextchar(void)
{
      int   c;
      int   n;

      for (;;)
      {
            if ((c = GETC()) == EOF)
            {
                  if (popin())
                        break;
                  continue;
            }
            if (c == state.ec)
            {
            escape:
                  switch (n = GETC())
                  {
                  case EOF:
                        if (popin())
                              break;
                        goto escape;
                  case '\n':
                        error_info.line++;
                        continue;
                  case 't':
                        c = '\t';
                        break;
                  case '"':
                        for (;;)
                        {
                              switch (c = GETC())
                              {
                              case EOF:
                                    if (popin())
                                          break;
                                    continue;
                              case '\n':
                                    break;
                              default:
                                    if (c == state.ec)
                                    {
                                    escape_2:
                                          switch (n = GETC())
                                          {
                                          case EOF:
                                                if (popin())
                                                      break;
                                                goto escape_2;
                                          case '\n':
                                                error_info.line++;
                                                continue;
                                          default:
                                                continue;
                                          }
                                          break;
                                    }
                                    continue;
                              }
                              break;
                        }
                        break;
                  default:
                        UNGETC(n);
                        break;
                  }
                  break;
            }
            break;
      }
      return c;
}

/*
 * call a macro
 */

static void
macro(Tag_t* tp, Arg_t* ap)
{
      if (tp->body)
            pushin(tp->file, tp->line, NiL, tp->body, ap);
}

/*
 * set macro value
 */

static void
set(Tag_t* mp, char* value, int append)
{
      int   n;

      if (append && mp->body)
      {
            sfputr(state.tmp, mp->body, -1);
            sfputr(state.tmp, value, -1);
            value = use(state.tmp);
      }
      n = strlen(value) + 1;
      if (mp->size < n)
      {
            if (mp->size)
                  free(mp->body);
            mp->size = n = roundof(n, 64);
            if (!(mp->body = newof(0, char, n, 0)))
                  error(ERROR_SYSTEM|3, "out of space [set value]");
      }
      strcpy(mp->body, value);
      mp->call = macro;
      mp->flags |= TAG_PASS;
      if (!mp->line)
      {
            mp->line = error_info.line;
            mp->file = error_info.file;
      }
      if (mp->flags & TAG_TRACE_SET)
            error(2, "set macro %s `%s'", mp->name + 1, mp->body);
}

/*
 * return macro pointer given name
 * pointer created if not found
 */

static Tag_t*
mac(char* name)
{
      register Tag_t*   mp;
      char        buf[MAXNAME];

      if (!(mp = (Tag_t*)hashget(state.symbols, nam('.', name, buf, sizeof(buf)))))
      {
            if (!(mp = newof(0, Tag_t, 1, 0)))
                  error(ERROR_SYSTEM|3, "out of space [mac]");
            mp->name = hashput(state.symbols, NiL, mp);
      }
      return mp;
}

static Env_t*
env(char* s)
{
      register Env_t*   v;
      char*       e;
      long        n;
      char        buf[MAXNAME];

      n = expression(s, &e, 0);
      if (*e)
            sfsprintf(buf, sizeof(buf), "E%s", s);
      else
            sfsprintf(buf, sizeof(buf), "E%d", n);
      if (!(v = (Env_t*)hashget(state.symbols, buf)))
      {
            if (!(v = newof(0, Env_t, 1, 0)))
                  error(ERROR_SYSTEM|3, "out of space [environment]");
            v->name = hashput(state.symbols, NiL, v);
      }
      if (v->generation < state.generation)
      {
            v->generation = state.generation;
            v->c2 = DEFAULT_c2;
            v->cc = DEFAULT_cc;
            v->ft.current = v->ft.previous = DEFAULT_ft;
            v->nf = 0;
            v->ps.current = v->ps.previous = DEFAULT_ps;
            v->ss = 0;
            v->vs.current = v->vs.previous = DEFAULT_vs;
      }
      return v;
}

/*
 * get macro value by name
 */

static char*
get(char* name)
{
      register Tag_t*   mp;

      return (mp = (Tag_t*)hashget(state.symbols, name)) && mp->body ? mp->body : "";
}

/*
 * open name for reading
 * looking in state.dirs if needed
 * verbose<0 for -m option
 */

static Sfio_t*
find(char* name, char** found, int verbose)
{
      register char*          s;
      register Sfio_t*  sp;
      char*             path;
      char*             t;
      char*             x;
      int               i;
      Dir_t*                  dp;
      char              buf[PATH_MAX];

      static const char*      pathdirs[] =
                        {
                              "lib/tmac/tmac.%s",
                              "lib/tmac/%s",
                              "lib/html/%s",
                              "lib/html/m%s.tr",
                              "share/groff/tmac/tmac.%s",
                              "share/groff/tmac/%s",
                              "groff/share/groff/tmac/tmac.%s",
                              "groff/share/groff/tmac/%s",
                        };

      if (verbose >= 0 && (sp = sfopen(NiL, name, "r")))
      {
            sfprintf(state.tmp, "/%s", name);
            path = use(state.tmp);
            goto hit;
      }
      if (*name != '/')
      {
            if (*name == '.' && (x = getenv("HOME")))
            {
                  sfprintf(state.tmp, "/%s/%s", x, name);
                  path = use(state.tmp);
                  if (sp = sfopen(NiL, path + 1, "r"))
                        goto hit;
            }
            for (dp = state.dirs; dp; dp = dp->next)
            {
                  if (verbose >= 0)
                  {
                        sfprintf(state.tmp, "/%s/%s", dp->name, name);
                        path = use(state.tmp);
                        if (sp = sfopen(NiL, path + 1, "r"))
                              goto hit;
                  }
                  sfprintf(state.tmp, "/%s/tmac.%s", dp->name, name);
                  path = use(state.tmp);
                  if (sp = sfopen(NiL, path + 1, "r"))
                        goto hit;
            }
            for (i = 0; i < elementsof(pathdirs); i++)
            {
                  sfprintf(state.tmp, pathdirs[i], name);
                  path = use(state.tmp);
                  if (pathpath(buf + 1, path, "", PATH_REGULAR|PATH_READ) && (sp = sfopen(NiL, buf + 1, "r")))
                  {
                        *(path = buf) = '/';
                        goto hit;
                  }
            }
      }
      if (verbose > 0)
      {
            /*
             * some systems provide
             *    /usr/lib/tmac.mXXX
             * that references
             *    /usr/lib/macros/mXXX[tn]
             * that is not provided
             */

            if (*(s = name) == '/')
                  while (s = strchr(s, '/'))
                        if (!strncmp(++s, "macros/", 7) && *(s += 7))
                        {
                              t = s + strlen(s) - 1;
                              x = strchr(s, '.') ? "" : ".tr";
                              while (*s)
                              {
                                    sfprintf(state.tmp, "lib/html/%s%s", s, x);
                                    path = use(state.tmp);
                                    if (pathpath(buf + 1, path, "", PATH_REGULAR|PATH_READ) && (sp = sfopen(NiL, buf + 1, "r")))
                                    {
                                          *(path = buf) = '/';
                                          goto hit;
                                    }
                                    if (*t != 't')
                                          break;
                                    *t-- = 0;
                              }
                        }
            error(ERROR_SYSTEM|2, "%s: cannot read", name);
      }
      else if (verbose < 0)
            error(ERROR_SYSTEM|3, "-m%s: cannot find macro package", name);
      return 0;
 hit:
      message((-1, "FIND %s => %s", name, path + 1));
      if (found)
            *found = hashput(state.symbols, path, path + 1) + 1;
      s = path + strlen(path);
      while (s > path + 2)
            if (*--s == '/')
            {
                  if (!strcmp(s, "/lib"))
                  {
                        *s = 0;
                        set(mac(".P"), path + 1, 0);
                        break;
                  }
                  *s = 0;
            }
      return sp;
}

/*
 * generate intermediate CODE_0
 */

static void
code_0(void)
{
      sfputc(state.out, CODE_0);
}

/*
 * generate intermediate CODE_1
 */

static void
code_1(int code)
{
      sfputc(state.out, CODE_1);
      sfputc(state.out, code);
}

/*
 * generate intermediate CODE_2
 */

static void
code_2(int code, int data)
{
      sfputc(state.out, CODE_2);
      sfputc(state.out, data);
      sfputc(state.out, code);
}

/*
 * generate intermediate CODE_n
 */

static void
code_n(int code, char* s)
{
      int   n;

      sfputc(state.out, CODE_n);
      n = s ? (strlen(s) + 1) : 1;
      if (n > 0177)
            sfputc(state.out, ((n >> 8) & 0177) | 0200);
      sfputc(state.out, n & 0177);
      sfputc(state.out, code);
      if (s)
            sfputr(state.out, s, 0);
      else
            sfputc(state.out, 0);
}

/*
 * join all args from n on into 1 arg
 */

static char*
join(Arg_t* ap, int n)
{
      if (n < 0 || ap->argc < n)
            return 0;
      n++;
      while (ap->argc >= n)
            *(ap->argv[ap->argc--] - 1) = ' ';
      return ap->argv[n - 1];
}

/*
 * output error message line
 */

static void
notify(register char* s)
{
      register int      c;
      Sfoff_t           p;

      p = sftell(sfstderr);
      for (;;)
      {
            switch (c = *s++)
            {
            case 0:
                  break;
            case CODE_0:
                  continue;
            case CODE_1:
                  s++;
                  continue;
            case CODE_2:
                  c = *s++;
                  if (*s++ == OP_cc)
                        sfputc(sfstderr, c);
                  continue;
            default:
                  sfputc(sfstderr, c);
                  continue;
            }
            break;
      }
      if (sftell(sfstderr) != p)
            sfputc(sfstderr, '\n');
}

/*
 * add trap s to xp
 */

static void
trap(Trap_t** xp, char* s)
{
      Trap_t*     ip;
      Trap_t*     pp;

      for (pp = 0, ip = *xp; ip; pp = ip, ip = ip->next)
            if (streq(ip->name + 1, s))
                  return;
      if (!(ip = newof(0, Trap_t, 1, strlen(s) + 1)))
            error(ERROR_SYSTEM|3, "out of space [trap]");
      *ip->name = '.';
      strcpy(ip->name + 1, s);
      if (pp)
            pp->next = ip;
      else
            *xp = ip;
}

/*
 * trigger traps on xp
 */

static void
trigger(Trap_t** xp)
{
      Trap_t*     ip;
      Trap_t*     pp;

      if (!DIVERTED() && (ip = *xp))
      {
            state.t = 1;
            do
            {
                  sfputr(state.req, ip->name, '\n');
                  pp = ip;
                  ip = ip->next;
                  free(pp);
            } while (ip);
            *xp = 0;
            pushin(xp == &state.fini ? "[*EOF*]" : "[*TRAP*]", 1, NiL, use(state.req), NiL);
      }
}

/*
 * return value for register name with type
 * and increment/decrement i={0,'+','-'}
 */

static char*
value(char* name, int type, int i)
{
      register Num_t*   np;
      register char*    b;
      register char*    x;
      register long     n;
      register long     m;
      Tag_t*            tp;
      char        buf[8];

      b = hashget(state.symbols, name);
      switch (type)
      {
      case 'g':
            if (!(np = (Num_t*)b))
                  return "";
            b = np->value;
            if (isalnum(np->format))
                  *b++ = np->format;
            else
            {
                  for (n = 0; n < np->format; n++)
                        *b++ = '0';
                  *b++ = '1';
            }
            *b = 0;
            return np->value;
      case 'j':
            return "";
      case 'n':
            break;
      case '.':
            if (!(tp = (Tag_t*)b))
                  return 0;
            if (tp->flags & TAG_TRACE_GET)
                  error(2, "get macro %s `%s'", tp->name + 1, tp->body);
            return tp->body;
      default:
            return b;
      }
      if (!b)
      {
            name = strcpy(buf, "n");
            if (!(b = (char*)hashget(state.symbols, name)))
                  return "0";
      }
      np = (Num_t*)b;
      if (np->flags & TAG_TRACE_GET)
            error(2, "get register %s %d %d", np->name + 1, np->number, np->increment);
      if (np->internal)
      {
            switch (INDEX(name[1], name[2]))
            {
            case INDEX('%',0):
                  n = np->number;
                  break;
            case INDEX('.','$'):
                  n = state.mac ? state.mac->argc : 0;
                  break;
            case INDEX('.','C'):
                  GROFFINIT();
                  n = state.groff == 0;
                  break;
            case INDEX('.','F'):
                  return state.original.file ? state.original.file : error_info.file;
            case INDEX('.','H'):
                  n = 3000;
                  break;
            case INDEX('.','L'):
                  n = 1;
                  break;
            case INDEX('.','P'):
                  n = 1;
                  break;
            case INDEX('.','R'):
                  n = 256;
                  break;
            case INDEX('.','V'):
                  n = 6000;
                  break;
            case INDEX('.','c'):
                  if (name[3] == 'e')
                  {
                        n = state.it.center;
                        break;
                  }
                  /*FALLTHROUGH*/
            case INDEX('c','.'):
                  n = state.original.line ? state.original.line : error_info.line;
                  break;
            case INDEX('.','e'):
                  if (name[3] == 'v')
                        n = state.it.center; /*GROFF: string valued?*/
                  break;
            case INDEX('.','f'):
                  n = state.env->ft.current;
                  break;
            case INDEX('.','g'):
                  GROFFINIT();
                  n = state.groff != 0;
                  break;
            case INDEX('.','i'):
                  n = state.env->in.current;
                  break;
            case INDEX('.','k'):
                  n = 100;
                  break;
            case INDEX('.','l'):
                  n = state.env->ll.current;
                  break;
            case INDEX('.','n'):
                  n = state.n;
                  break;
            case INDEX('.','p'):
                  if (name[3] == 'n')
                        n = 2;
                  else if (name[3] == 's' && name[4] == 'r')
                        n = state.env->ps.previous;
                  else
                        n = state.env->ps.current;
                  break;
            case INDEX('.','r'):
                  n = 0;
                  break;
            case INDEX('.','s'):
                  if (name[3] == 'r')
                        n = state.env->ps.previous;
                  else
                        n = state.env->ps.current;
                  break;
            case INDEX('.','t'):
                  n = state.t;
                  break;
            case INDEX('.','u'):
                  n = !state.env->nf;
                  break;
            case INDEX('.','v'):
                  if (name[3] == 'p')
                        n = 1;
                  else
                        n = 6 * state.env->vs.current;
                  break;
            case INDEX('.','w'):
                  if (name[3] == 'a')
                        n = 0;
                  else
                        n = 6 * state.env->ps.current;
                  break;
            case INDEX('.','x'):
                  n = 19980401;
                  break;
            case INDEX('.','y'):
                  n = 1;
                  break;
            case INDEX('.','z'):
                  return state.divert ? state.divert->tag->name : "";
            case INDEX('d','l'):
                  if (state.test & 0x1000) n = 0; else
                  n = state.dl;
                  break;
            case INDEX('d','n'):
                  if (state.test & 0x2000) n = 0; else
                  n = state.dn;
                  break;
            case INDEX('l','n'):
                  if (state.test & 0x4000) n = 0; else
                  n = state.ln;
                  break;
            case INDEX('n','l'):
                  if (!(state.test & 0x8000)) n = 0; else
                  n = state.nl;
                  break;
            default:
                  n = 0;
                  break;
            }
            np->number = n;
      }
      if (i)
      {
            switch (i)
            {
            case '+':
                  np->number += np->increment;
                  break;
            case '-':
                  np->number -= np->increment;
                  break;
            }
            if (np->flags & TAG_TRACE_SET)
                  error(2, "set register %s %d %d", np->name + 1, np->number, np->increment);
      }
      n = np->number;
      b = np->value;
      switch (np->format)
      {
      case '1':
            sfsprintf(b, sizeof(np->value), "%ld", np->number);
            break;
      case 'A':
      case 'a':
            x = islower(np->format) ? "abcdefghijklmnopqrstuvwxyz" : "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            if (n < 0)
            {
                  n = -n;
                  *b++ = '-';
            }
            for (m = 1; m > 0 && m < n; m *= 26);
            if (m <= 0)
                  sfsprintf(b, sizeof(buf) - 1, "<%ld>", n);
            else
            {
                  for (; m > 0; m /= 26)
                  {
                        i = n / m;
                        n -= m * i;
                        *b++ = x[i];
                  }
                  *b = 0;
            }
            break;
      case 'I':
      case 'i':
            x = islower(np->format) ? "zwmdclxvi" : "ZWMDCLXVI";
            if (n <= -40000 || n >= 40000)
                  sfsprintf(b, sizeof(buf), "<%ld>", n);
            else if (!n)
                  sfsprintf(b, sizeof(buf), "0");
            else
            {
                  if (n < 0)
                  {
                        n = -n;
                        *b++ = '-';
                  }
                  while (n >= 10000)
                  {
                        n -= 10000;
                        *b++ = x[0];
                  }
                  for (i = 1000; i > 0; i /= 10, x += 2)
                  {
                        m = n / i;
                        n -= m * i;
                        switch (m)
                        {
                        case 9:
                              *b++ = x[2];
                              *b++ = x[0];
                              break;
                        case 8:
                              *b++ = x[1];
                              *b++ = x[2];
                              *b++ = x[2];
                              *b++ = x[2];
                              break;
                        case 7:
                              *b++ = x[1];
                              *b++ = x[2];
                              *b++ = x[2];
                              break;
                        case 6:
                              *b++ = x[1];
                              *b++ = x[2];
                              break;
                        case 5:
                              *b++ = x[1];
                              break;
                        case 4:
                              *b++ = x[2];
                              *b++ = x[1];
                              break;
                        case 3:
                              *b++ = x[2];
                              /*FALLTHROUGH*/
                        case 2:
                              *b++ = x[2];
                              /*FALLTHROUGH*/
                        case 1:
                              *b++ = x[2];
                              break;
                        }
                  }
                  *b = 0;
            }
            break;
      default:
            sfsprintf(b, sizeof(np->value), "%0*ld", np->format, np->number);
            break;
      }
      return np->value;
}

/*
 * return the value for typed register
 */

static char*
interpolate(int type)
{
      register int      c;
      register char*    b;
      register char*    x;
      register char*    t;
      int         i;
      int         k;
      char        buf[MAXNAME];

      i = k = 0;
      b = buf;
      *b++ = (type == 'g' || type == 'j') ? 'f' : type;
      x = buf + 2;
      do
      {
            switch (c = GETC())
            {
            case EOF:
                  if (popin())
                        return 0;
                  continue;
            case '\n':
                  error_info.line++;
                  break;
            case '(':
                  if (x == buf + 2)
                  {
                        x++;
                        continue;
                  }
                  break;
            case '[':
                  if (state.groff && x == buf + 2)
                  {
                        x = buf + sizeof(buf) - 2;
                        k = 1;
                        continue;
                  }
                  break;
            case ']':
                  if (k)
                        goto done;
                  break;
            case '-':
            case '+':
                  if (type == 'n' && b == buf + 1)
                  {
                        i = c;
                        continue;
                  }
                  break;
            default:
                  if (c == state.ec)
                  {
                  escape:
                        switch (c = GETC())
                        {
                        case EOF:
                              if (popin())
                                    return 0;
                              goto escape;
                        case '\n':
                              error_info.line++;
                              continue;
                        case '*':
                              c = '.';
                              /*FALLTHROUGH*/
                        case 'n':
                              if (t = interpolate(c))
                                    while (b < x && (*b = *t++))
                                          b++;
                              continue;
                        }
                  }
                  break;
            }
            *b++ = c;
      } while (b < x);
 done:
      *b = 0;
      return value(buf, type, i);
}

/*
 * switch to font s
 */

static void
ft(char* s)
{
      int   c;

      if (!s || (c = *s) < '0' || c > '5')
            c = 0;
      else
            c -= '0';
      if (!c)
            c = state.env->ft.previous;
      state.env->ft.previous = state.env->ft.current;
      state.env->ft.current = c;
      code_1(OP_ft + c);
}

/*
 * switch to point size n
 */

static void
ps(int n)
{
      state.env->ps.previous = state.env->ps.current;
      state.env->ps.current = n;
      code_2(OP_ps, n);
}

/*
 * sync list state
 */

static void
li(int force)
{
      List_t*     list = state.list;

      state.it.dc = 0;
      if (state.divert || state.env->nf)
      {
            if (state.env->nf)
                  state.it.dl = 0;
            return;
      }
      if (force)
      {
            while (state.env->in.current < state.list->in)
            {
                  message((-2, "DROP %d %d %d:%d %d:%d", state.list - state.list_stack, state.list->dl, state.list->ti, state.list->in, force ? state.env->ti.current : 0, state.env->in.current));
                  if (state.list->dl)
                        code_1(END(OP_dl));
                  state.list--;
            }
      }
      else
            message((-2, "TEST %d %d %d:%d %d:%d ops=%d df=%d dc=%d dl=%d", state.list - state.list_stack, state.list->dl, state.list->ti, state.list->in, force ? state.env->ti.current : 0, state.env->in.current, state.list - list, force, state.it.dc, state.it.dl));
      if (state.env->in.current > state.list->in || force > 0 && state.env->ti.current > state.list->in)
      {
            if (state.list >= &state.list_stack[elementsof(state.list_stack)-1])
                  error(2, "list stack overflow");
            else
                  state.list++;
            state.list->dl = 0;
            state.list->in = state.env->in.current;
            state.list->ti = state.env->in.current > state.list->in ? state.env->in.current : state.env->ti.current;
            state.it.dl++;
      }
      else if (!force && state.env->in.current < state.list->in)
            state.it.dc = 1;
      message((-2, "LIST %d %d %d:%d %d:%d ops=%d df=%d dc=%d dl=%d", state.list - state.list_stack, state.list->dl, state.list->ti, state.list->in, force ? state.env->ti.current : 0, state.env->in.current, state.list - list, force, state.it.dc, state.it.dl));
}

/*
 * pop inline traps
 */

static void
it(void)
{
      register List_t*  p;

      if (state.env->nf)
      {
            if (state.env->in.current > 0)
                  sfputc(state.out, '\t');
      }
      else if (!DIVERTED())
      {
            if (state.it.dc && ISFILE())
                  li(-1);
            p = state.list;
            while (state.it.dl > 0)
            {
                  state.it.dl--;
                  for (; p >= state.list_stack; p--)
                        if (!p->dl)
                        {
                              p->dl = 1;
                              code_1(OP_dl);
                              break;
                        }
            }
            if (state.it.dt)
            {
                  state.it.dt = 0;
                  code_1((state.list - state.list_stack) <= 1 ? LABEL(OP_dt) : OP_dt);
                  state.it.dd = 1;
            }
            else if (state.it.dd)
            {
                  state.it.dd = 0;
                  code_1(OP_dd);
            }
      }
}

/*
 * initialize number register
 */

static void
nr(char* s, int v, int format, int internal)
{
      register Num_t*   np;

      np = num(s);
      np->number = v;
      np->format = format;
      if (internal > 0)
            np->internal = internal;
}

/*
 * set time vars to t
 */

static void
tm(time_t t)
{
      Tm_t* xp;

      state.date = t;
      xp = tmmake(&t);
      nr("dw", xp->tm_wday + 1, 0, 0);
      nr("dy", xp->tm_mday, 0, 0);
      nr("mo", xp->tm_mon + 1, 0, 0);
      nr("yr", xp->tm_year % 100, 2, 0);
      nr("YR", 1900 + xp->tm_year, 4, 0);
}

/*
 * expand if expression s into op
 */

static void
expand(register Sfio_t* op, register char* s)
{
      int   c;
      int   d;
      int   q;
      int   v;
      int   w;
      int   z;
      char* t;
      char* b;
      char  buf[MAXNAME];

      for (;;)
      {
            switch (c = *s++)
            {
            case 0:
                  break;
            case CODE_2:
                  sfputc(op, c);
                  c = *s++;
                  /*FALLTHROUGH*/
            case CODE_1:
                  sfputc(op, c);
                  c = *s++;
                  /*FALLTHROUGH*/
            case CODE_0:
                  sfputc(op, c);
                  continue;
            default:
                  if (c == state.ec)
                        switch (w = c = *s++)
                  {
                  case 0:
                        s--;
                        break;
                  case 'A':
                  case 'C':
                  case 'L':
                  case 'N':
                  case 'R':
                  case 'V':
                  case 'X':
                  case 'Y':
                  case 'Z':
                  case 'b':
                  case 'h':
                  case 'l':
                  case 'o':
                  case 'w':
                  case 'x':
                        v = 0;
                        c = *s++;
                        if (c == '(')
                              z = 2;
                        else
                        {
                              z = -1;
                              if (state.groff && c == '[')
                                    c = ']';
                        }
                        b = s;
                        for (;;)
                        {
                              switch (d = *s++)
                              {
                              case 0:
                                    s--;
                                    break;
                              case CODE_2:
                                    s++;
                                    /*FALLTHROUGH*/
                              case CODE_1:
                                    if (*s++ == OP_cc)
                                          v++;
                                    /*FALLTHROUGH*/
                              case CODE_0:
                                    continue;
                              default:
                                    if (d == state.ec)
                                    {
                                          switch (d = *s++)
                                          {
                                          case 0:
                                                s--;
                                                break;
                                          case 'A':
                                          case 'C':
                                          case 'L':
                                          case 'N':
                                          case 'R':
                                          case 'V':
                                          case 'X':
                                          case 'Y':
                                          case 'Z':
                                          case 'b':
                                          case 'h':
                                          case 'l':
                                          case 'o':
                                          case 'w':
                                          case 'x':
                                                q = *s++;
                                                for (;;)
                                                {
                                                      switch (d = *s++)
                                                      {
                                                      case 0:
                                                            s--;
                                                            break;
                                                      case CODE_2:
                                                            s++;
                                                            /*FALLTHROUGH*/
                                                      case CODE_1:
                                                            s++;
                                                            /*FALLTHROUGH*/
                                                      case CODE_0:
                                                            continue;
                                                      default:
                                                            if (d == q)
                                                                  break;
                                                            continue;
                                                      }
                                                      break;
                                                }
                                                v++;
                                                break;
                                          case 'f':
                                          case 's':
                                                if (*s)
                                                      s++;
                                                break;
                                          case 'g':
                                          case 'j':
                                                t = buf;
                                                *t++ = 'n';
                                                if ((*t++ = *s++) == '(')
                                                {
                                                      *(t - 1) = *s++;
                                                      *t++ = *s++;
                                                }
                                                *t = 0;
                                                if (t = value(buf, d, 0))
                                                      v += strlen(t);
                                                break;
                                          }
                                    }
                                    else if (z > 0)
                                          z--;
                                    else if (d == c || z == 0)
                                          break;
                                    v++;
                                    continue;
                              }
                              break;
                        }
                        switch (w)
                        {
                        case 'A':
                              sfprintf(op, "1");
                              break;
                        case 'R':
                        case 'X':
                        case 'V':
                              c = *(s - 1);
                              *(s - 1) = 0;
                              switch (w)
                              {
                              case 'R':
                                    /*HERE*/
                                    break;
                              case 'V':
                                    if (t = getenv(b))
                                          sfprintf(op, "%s", t);
                                    break;
                              case 'X':
                                    /*HERE*/
                                    break;
                              }
                              *(s - 1) = c;
                              break;
                        case 'w':
                              sfprintf(op, "%d", convert(v, 1, w));
                              break;
                        }
                        break;
                  case 'g':
                  case 'j':
                        t = buf;
                        *t++ = 'n';
                        if ((*t++ = *s++) == '(')
                        {
                              *(t - 1) = *s++;
                              *t++ = *s++;
                        }
                        *t = 0;
                        if (t = value(buf, c, 0))
                              sfputr(op, t, -1);
                        break;
                  default:
                        sfputc(op, state.ec);
                        sfputc(op, c);
                        break;
                  }
                  else
                        sfputc(op, c);
                  continue;
            }
            break;
      }
}

/*
 * remove macro/register s
 */

static void
rm(char* s)
{
      register Tag_t*   mp;
      char        buf[MAXNAME];

      if (mp = (Tag_t*)hashget(state.symbols, nam('.', s, buf, sizeof(buf))))
      {
            if (mp->flags & TAG_TRACE_SET)
                  error(2, "remove macro %s", mp->name + 1);
            if (mp->size)
                  free(mp->body);
            if (!(mp->flags & TAG_STATIC))
                  free(mp);
            hashput(state.symbols, NiL, NiL);
      }
      if (mp = (Tag_t*)hashget(state.symbols, nam('n', s, buf, sizeof(buf))))
      {
            if (mp->flags & TAG_TRACE_SET)
                  error(2, "remove register %s", mp->name + 1);
            if (mp->size)
                  free(mp->body);
            if (!(mp->flags & TAG_STATIC))
                  free(mp);
            hashput(state.symbols, NiL, NiL);
      }
}

/*
 * skip one conditional block
 * if op!=0 then copy block data to s
 */

static void
skip(char* s, int f, register Sfio_t* op)
{
      register int      c;
      register int      n;

      n = (f & COND_BLOCK) != 0;
      if (s)
            pushin(NiL, 0, NiL, s, NiL);
      else if (!n)
            return;
      for (;;)
      {
            switch (c = GETC())
            {
            case EOF:
                  if (s && n <= 0)
                        break;
                  if (popin())
                        break;
                  continue;
            case CODE_2:
                  if (op)
                        sfputc(op, c);
                  c = GETC();
                  /*FALLTHROUGH*/
            case CODE_1:
                  if (op)
                        sfputc(op, c);
                  c = GETC();
                  /*FALLTHROUGH*/
            case CODE_0:
                  if (op)
                        sfputc(op, c);
                  continue;
            case '\n':
                  if (op)
                        sfputc(op, c);
                  error_info.line++;
                  if (n <= 0)
                        break;
                  continue;
            default:
                  if (op)
                        sfputc(op, c);
                  if (c == state.ec)
                  {
                  escape:
                        switch (c = GETC())
                        {
                        case EOF:
                              if (popin())
                                    break;
                              goto escape;
                        case '\n':
                              if (op)
                                    sfputc(op, c);
                              error_info.line++;
                              continue;
                        case '{':
                              if (op)
                                    sfputc(op, c);
                              n++;
                              continue;
                        case '}':
                              if (op)
                                    sfputc(op, c);
                              if (--n <= 0)
                              {
                                    switch (c = nextchar())
                                    {
                                    case '\n':
                                          if (op)
                                                sfputc(op, c);
                                          error_info.line++;
                                          break;
                                    default:
                                          UNGETC(c);
                                          break;
                                    }
                                    break;
                              }
                              continue;
                        default:
                              if (op)
                                    sfputc(op, c);
                              continue;
                        }
                        break;
                  }
                  continue;
            }
            break;
      }
      if (n > 0 && state.verbose)
            error(2, "unbalanced \\{ ... \\} block");
}

/*
 * internal groff requests
 */

static void
groff_aln(Tag_t* tp, Arg_t* ap)
{
      Tag_t*      xp;
      char  buf[MAXNAME];

      if (ap->argc != 2)
            error(1, "%s: two arguments expected", ap->argv[0]);
      else if (!(xp = (Tag_t*)hashget(state.symbols, nam('n', ap->argv[2], buf, sizeof(buf)))))
            error(1, "%s: not defined", buf);
      else
            hashput(state.symbols, nam('n', ap->argv[1], buf, sizeof(buf)), xp);
}

static void
groff_als(Tag_t* tp, Arg_t* ap)
{
      Tag_t*      xp;
      char  buf[MAXNAME];

      if (ap->argc != 2)
            error(1, "%s: two arguments expected", ap->argv[0]);
      else if (!(xp = (Tag_t*)hashget(state.symbols, nam('.', ap->argv[2], buf, sizeof(buf)))))
            error(1, "%s: not defined", buf);
      else
            hashput(state.symbols, nam('.', ap->argv[1], buf, sizeof(buf)), xp);
}

static void
groff_asciify(Tag_t* tp, Arg_t* ap)
{
      static int  warned;

      if (!warned++)
            error(1, "%s: not implemented yet", tp->name);
}

static void
groff_break(Tag_t* tp, Arg_t* ap)
{
      register Pushin_t*      pp;

      for (pp = state.in_top; pp >= state.in_stack; pp--)
            if (pp->loop)
            {
                  sfclose(pp->loop);
                  pp->loop = 0;
                  return;
            }
      error(1, "%s: outside of loop", tp->name);
}

static void
groff_chop(Tag_t* tp, Arg_t* ap)
{
      register char*    s;
      register Tag_t*   mp;

      if (ap->argc >= 1)
      {
            mp = mac(ap->argv[1]);
            if ((s = mp->body) && *s)
                  *(s + strlen(s) - 1) = 0;
      }
}

static void
groff_close(Tag_t* tp, Arg_t* ap)
{
      Stream_t*   sp;

      if (ap->argc < 1)
      {
            error(1, "%s: one argument expected", ap->argv[0]);
            return;
      }
      if (!(sp = iop(ap->argv[1], 0)))
      {
            error(1, "%s: %s: stream not open", ap->argv[0], ap->argv[1]);
            return;
      }
      sfclose(sp->sp);
      sp->sp = 0;
}

static void
groff_continue(Tag_t* tp, Arg_t* ap)
{
      register Pushin_t*      pp;

      for (pp = state.in_top; pp >= state.in_stack; pp--)
            if (pp->loop)
            {
                  pp->in = (unsigned char*)sfstrbase(pp->loop);
                  return;
            }
      error(1, "%s: outside of loop", tp->name);
}

static void
groff_cp(Tag_t* tp, Arg_t* ap)
{
      NoP(tp);
      if (ap->argc < 1 || expression(ap->argv[1], NiL, 0))
            state.groff &= 2;
      else
            state.groff |= 1;
}

static void
groff_open(Tag_t* tp, Arg_t* ap)
{
      char*       path;
      char*       mode;
      Stream_t*   sp;

      if (ap->argc < 2)
      {
            error(1, "%s: two arguments expected", ap->argv[0]);
            return;
      }
      mode = strlen(ap->argv[0]) > 5 ? (ap->argv[0] + 5) : "w";
      path = ap->argv[2];
      sp = iop(ap->argv[1], 1);
      if (*mode == 'w' || !streq(sp->path, path))
      {
            if (sp->sp)
            {
                  sfclose(sp->sp);
                  sp->sp = 0;
            }
            if (sp->path)
                  free(sp->path);
            if (!(sp->path = strdup(path)))
                  error(ERROR_SYSTEM|3, "out of space [stream path]");
      }
      if (!sp->sp && !(sp->sp = sfopen(NiL, path, mode)))
            error(ERROR_SYSTEM|2, "%s: %s: \"%s\" mode open error", ap->argv[0], path, mode);
}

static void
groff_pso(Tag_t* tp, Arg_t* ap)
{
      char* s;
      Sfio_t*     sp;

      if (s = join(ap, 1))
      {
            if (!(sp = sfpopen(NiL, s, "r")))
                  error(ERROR_SYSTEM|2, "%s: %s: command error", ap->argv[0], s);
            else
                  pushin(NiL, 0, sp, NiL, NiL);
      }
}

static void
groff_rnn(Tag_t* tp, Arg_t* ap)
{
      Num_t*      mp;
      char* s;

      NoP(tp);
      if (ap->argc >= 2)
      {
            s = ap->argv[2];
            rm(s);
            *--s = 'n';
            mp = num(ap->argv[1]);
            hashput(state.symbols, NiL, NiL);
            mp->name = hashput(state.symbols, s, mp);
      }
}

static void
groff_shift(Tag_t* tp, Arg_t* ap)
{
      register int      n;
      register int      i;
      register Arg_t*   pp;

      if (pp = state.mac)
      {
            n = (ap->argc >= 1) ? expression(ap->argv[1], NiL, 0) : 1;
            i = 0;
            while (n < pp->argc)
                  pp->argv[++i] = pp->argv[++n];
            pp->argc = i;
      }
}

static void
groff_sy(Tag_t* tp, Arg_t* ap)
{
      char* s;

      nr("systat", (long)((s = join(ap, 1)) ? system(s) : 0), 0, 0);
}

static void
groff_while(Tag_t* tp, Arg_t* ap)
{
      register char*    s;
      register char*    t;
      Sfio_t*           op;

      if (s = join(ap, 1))
      {
            if (!(op = sfstropen()))
                  error(ERROR_SYSTEM|3, "out of space [%s]", tp->name);
            sfputr(op, ".ie", ' ');
            sfputr(op, s, '\n');
            t = s + strlen(s);
            while (t > s && isspace(*--t));
            if (t > s && *t == '{' && *(t - 1) == state.ec)
                  skip(NiL, COND_BLOCK, op);
            sfputr(op, "\n.el .break\n", 0);
            pushin(NiL, 0, NiL, sfstrbase(op), NiL);
            state.in_top->loop = op;
      }
}

static void
groff_write(Tag_t* tp, Arg_t* ap)
{
      char*       s;
      Stream_t*   sp;

      if (ap->argc < 1)
      {
            error(1, "%s: at least one argument expected", ap->argv[0]);
            return;
      }
      if (!(sp = iop(ap->argv[1], 0)))
      {
            error(1, "%s: %s: stream not open", ap->argv[0], ap->argv[1]);
            return;
      }
      if (!(s = join(ap, 2)))
            s = "";
      if (*s == '"')
            s++;
      if (sfputr(sp->sp, s, '\n') < 0)
            error(ERROR_SYSTEM|2, "%s: %s: write error", ap->argv[0], ap->argv[1]);
}

/*
 * internal troff requests
 */

static void
troff_ab(Tag_t* tp, Arg_t* ap)
{
      char* s;

      NoP(tp);
      if (!(s = join(ap, 1)))
            s = "User abort";
      notify(s);
      exit(1);
}

static void
troff_ad(Tag_t* tp, Arg_t* ap)
{
      NoP(tp);
      if (ap->argc >= 1)
            switch (ap->argv[1][0])
            {
            case 'b':
            case 'B':
            case 'n':
            case 'N':
                  break;
            case 'c':
            case 'C':
                  break;
            case 'l':
            case 'L':
                  break;
            case 'r':
            case 'R':
                  break;
            }
}

static void
troff_af(Tag_t* tp, Arg_t* ap)
{
      Num_t*      np;
      char* s;

      NoP(tp);
      if (ap->argc >= 2)
      {
            np = num(ap->argv[1]);
            switch (*(s = ap->argv[2]))
            {
            case '0':
                  for (np->format = 0; *s++ == '0'; np->format++);
                  break;
            case 'a':
            case 'A':
            case 'i':
            case 'I':
                  np->format = *s;
                  break;
            case '1':
            default:
                  np->format = '1';
                  break;
            }
      }
}

static void
troff_br(Tag_t* tp, Arg_t* ap)
{
      NoP(tp);
      NoP(ap);
      if (state.it.dd)
      {
            state.it.dd = 0;
            code_1(OP_dd);
      }
      code_1(OP_br);
}

static void
troff_c2(Tag_t* tp, Arg_t* ap)
{
      NoP(tp);
      state.env->c2 = (ap->argc >= 1) ? ap->argv[1][0] : DEFAULT_c2;
}

static void
troff_cc(Tag_t* tp, Arg_t* ap)
{
      NoP(tp);
      state.env->cc = (ap->argc >= 1) ? ap->argv[1][0] : DEFAULT_cc;
}

static void
troff_ce(Tag_t* tp, Arg_t* ap)
{
      int   n;
      int   r;

      NoP(tp);
      n = (ap->argc >= 1) ? expression(ap->argv[1], NiL, 0) : 1;
      r = *(ap->argv[0] + 1) == 'r';
      if (n >= 1)
      {
            if (state.it.center && state.it.right != r)
            {
                  state.it.center = 0;
                  code_1(state.it.right ? END(OP_p) : END(OP_center));
            }
            if (!state.it.center)
            {
                  if (r)
                        code_2(OP_p, ARG_right);
                  else
                        code_1(OP_center);
            }
      }
      else
      {
            if (state.it.right)
                  code_1(END(OP_p));
            else if (state.it.center)
                  code_1(END(OP_center));
            n = 0;
            r = 0;
      }
      state.it.center = n;
      state.it.right = r;
}

static void
troff_cf(Tag_t* tp, Arg_t* ap)
{
      Sfio_t*     sp;

      if (ap->argc >= 1 && (sp = find(ap->argv[1], NiL, 1)))
      {
            sfmove(sp, state.out, SF_UNBOUND, -1);
            sfclose(sp);
      }
}

static void troff_wh(Tag_t*, Arg_t*);

static void
troff_ch(Tag_t* tp, Arg_t* ap)
{
      char* s;
      Trap_t*     xp;

      NoP(tp);
      NoP(ap);
      if (ap->argc == 1)
      {
            for (xp = state.trap; xp; xp = xp->next)
                  if (streq(xp->name + 1, ap->argv[1]))
                        xp->name[0] = 0;
      }
      else if (ap->argc >= 2)
      {
            s = ap->argv[1];
            ap->argv[1] = (state.test & 1) ? "1" : ap->argv[2];
            ap->argv[2] = s;
            troff_wh(tp, ap);
      }
}

static void
troff_de(Tag_t* tp, Arg_t* ap)
{
      register Tag_t*   mp;

      if (ap->argc >= 1)
      {
            mp = mac(ap->argv[1]);
            if (mp->body && tp->name[1] == 'a')
                  sfputr(state.tmp, mp->body, -1);
            mp->file = error_info.file;
            mp->line = error_info.line;
            mp->flags |= TAG_PASS;
            mp->call = macro;
            state.define = mp;
            state.end = (Tag_t*)hashget(state.symbols, "..");
            state.pass = 1;
            pushout(state.tmp);
      }
}

static void
troff_di(Tag_t* tp, Arg_t* ap)
{
      Divert_t*   dp;
      int         n;

      if (!ap->argc)
      {
            if (dp = state.divert)
            {
                  state.dl = state.env->dl;
                  state.dn = state.env->dn;
                  if (dp->env->ft.current != state.env->ft.current)
                        code_1(OP_ft + dp->env->ft.current);
                  if (dp->env->ps.current != state.env->ps.current)
                        code_2(OP_ps, dp->env->ps.current);
                  state.env = dp->env;
                  state.pass = 0;
                  state.divert = dp->next;
                  if ((n = sfstrtell(state.out)) > 0 && *(sfstrbase(state.out) + n - 1) != '\n')
                        sfputc(state.out, '\n');
                  set(dp->tag, popout(), 0);
                  message((-2, "%s: pop diversion `%s'", dp->tag->name, dp->tag->body));
                  sfstrclose(dp->sp);
                  free(dp);
            }
      }
      else if (!(dp = newof(0, Divert_t, 1, 0)) || !(dp->sp = sfstropen()))
            error(ERROR_SYSTEM|3, "out of space [divert]");
      else
      {
            dp->tag = mac(ap->argv[1]);
            if (dp->tag->body && tp->name[2] == 'a')
                  sfputr(dp->sp, dp->tag->body, -1);
            dp->next = state.divert;
            state.divert = dp;
            state.pass = 1;
            pushout(dp->sp);
            dp->env = state.env;
            dp->environment = *state.env;
            state.env = &dp->environment;
            state.env->dl = 0;
            state.env->dn = 0;
      }
}

static void
troff_ds(Tag_t* tp, Arg_t* ap)
{
      char* v;

      if (v = join(ap, 2))
            set(mac(ap->argv[1]), v, tp->name[1] == 'a');
}

static void
troff_ec(Tag_t* tp, Arg_t* ap)
{
      NoP(tp);
      state.ec = state.eo = (ap->argc >= 1) ? ap->argv[1][0] : DEFAULT_ec;
}

static void
troff_eo(Tag_t* tp, Arg_t* ap)
{
      NoP(tp);
      if (state.ec)
      {
            state.eo = state.ec;
            state.ec = 0;
      }
}

static void
troff_ev(Tag_t* tp, Arg_t* ap)
{
      register Env_t*   oe;
      register Env_t*   ne;

      if (ap->argc < 1)
      {
            if (state.env_sp <= state.env_stack)
                  error(2, "%s: stack underflow", tp->name);
            else
                  state.env_sp--;
      }
      else if (state.env_sp >= &state.env_stack[elementsof(state.env_stack)])
            error(2, "%s: stack overflow", tp->name);
      else
            *++state.env_sp = env(ap->argv[1]);
      oe = state.env;
      ne = state.env = *state.env_sp;
      if (oe != ne)
      {
            if (oe->ft.current != ne->ft.current)
                  code_1(OP_ft + ne->ft.current);
            if (oe->nf != ne->nf)
                  code_1(ne->nf ? OP_pre : END(OP_pre));
            if (oe->ps.current != ne->ps.current)
                  code_2(OP_ps, ne->ps.current);
      }
}

static void
troff_em(Tag_t* tp, Arg_t* ap)
{
      int   i;

      NoP(tp);
      for (i = 1; i <= ap->argc; i++)
            trap(&state.fini, ap->argv[i]);
}

static void
troff_fi(Tag_t* tp, Arg_t* ap)
{
      NoP(tp);
      NoP(ap);
      if (state.env->nf)
      {
            state.env->nf = 0;
            code_1(END(OP_pre));
      }
}

static void
troff_fp(Tag_t* tp, Arg_t* ap)
{
      char* s;
      char* t;

      NoP(tp);
      if (ap->argc >= 2)
      {
            s = ap->argv[1];
            *--s = 'f';
            t = ap->argv[1];
            *--t = 'f';
            hashput(state.symbols, s, hashget(state.symbols, t));
      }
}

static void
troff_ft(Tag_t* tp, Arg_t* ap)
{
      char* s;

      NoP(tp);
      if (ap->argc >= 1)
      {
            s = ap->argv[1];
            *--s = 'f';
            s = (char*)hashget(state.symbols, s);
      }
      else s = 0;
      ft(s);
}

static void
troff_ie(Tag_t* tp, Arg_t* ap)
{
      int   f;
      int   q;
      long  v;
      char* s;
      char* t;
      char* e;

      if (tp->name[1] == 'e')
      {
            if (!state.cond.level || !(state.cond.flags[state.cond.level] & COND_IE))
            {
                  if (state.verbose)
                        error(2, "%s: no matching .ie", tp->name);
                  if (!state.cond.level)
                        state.cond.level++;
                  state.cond.flags[state.cond.level] = COND_IE|COND_KEPT;
            }
            f = COND_EL;
      }
      else
      {
            if (state.cond.level >= elementsof(state.cond.flags) - 1)
                  error(3, "%s: conditional stack too deep", tp->name);
            f = tp->name[2] == 'f' ? COND_IF : COND_IE;
      }
      if (error_info.trace <= -5)
      {
            int   g = state.cond.flags[state.cond.level];

            error(-5, "IE +++ %s level=%d |%s%s%s%s%s", tp->name, state.cond.level, (g & COND_IF) ? "IF|" : "", (g & COND_IE) ? "IE|" : "", (g & COND_BLOCK) ? "BLOCK|" : "", (g & COND_SKIP) ? "SKIP|" : "", (g & COND_KEPT) ? "KEPT|" : "");
      }
      if (s = join(ap, 1))
      {
            if (state.ec)
            {
                  t = s;
                  while (t = strchr(t, state.ec))
                        if (*++t != '{' && *t != '}')
                              break;
                  if (t)
                  {
                        if (state.test & 0x100)
                              error(2, "EXPAND +++ `%s'", s);
                        expand(state.arg, s);
                        sfputr(ap->sp, use(state.arg), 0);
                        s = use(ap->sp);
                        if (state.test & 0x100)
                              error(2, "EXPAND --- `%s'", s);
                  }
            }
            for (;;)
            {
                  if (f & COND_EL)
                        v = !(state.cond.flags[state.cond.level] & COND_KEPT);
                  else
                  {
                        v = cond(s, &e);
                        s = e;
                  }
                  if (v || s[0] != state.env->cc && s[0] != state.env->c2 || s[1] != 'i')
                        break;
                  q = f;
                  if (s[2] == 'e')
                        f = COND_IE|COND_KEPT|COND_SKIP;
                  else if (s[2] == 'f')
                        f = COND_IF|COND_SKIP;
                  else
                        break;
                  if (q & COND_EL)
                        state.cond.level--;
            }
            if (*s == state.ec && state.ec && *(s + 1) == '{')
            {
                  s += 2;
                  f |= COND_BLOCK;
            }
            while (isspace(*s))
                  s++;
            if (!*s)
                  s = 0;
            else
                  *(s + strlen(s)) = '\n';
      }
      if (!v)
      {
            f |= COND_SKIP;
            skip(s, f, NiL);
      }
      else
      {
            if (s)
            {
                  pushin(NiL, 0, NiL, s, ap);
                  error_info.line--;
            }
            if (f & (COND_EL|COND_IE))
                  f |= COND_KEPT;
      }
      if (f & COND_EL)
      {
            if ((f & (COND_SKIP|COND_BLOCK)) == COND_BLOCK)
                  state.cond.flags[state.cond.level] = COND_IE|COND_EL|COND_KEPT|COND_BLOCK;
            else
                  state.cond.level--;
      }
      else if ((f & COND_IE) || (f & (COND_BLOCK|COND_SKIP)) == COND_BLOCK)
            state.cond.flags[++state.cond.level] = f;
      if (error_info.trace <= -5)
      {
            int   g = state.cond.flags[state.cond.level];

            error(-5, "IE --- %s level=%d |%s%s%s%s%s", tp->name, state.cond.level, (g & COND_IF) ? "IF|" : "", (g & COND_IE) ? "IE|" : "", (g & COND_BLOCK) ? "BLOCK|" : "", (g & COND_SKIP) ? "SKIP|" : "", (g & COND_KEPT) ? "KEPT|" : "");
      }
}

static void
troff_ignore(Tag_t* tp, Arg_t* ap)
{
      char* s;

      if (!state.end)
      {
            *tp->name = 'e';
            state.end = (s = (char*)hashget(state.symbols, tp->name)) ? (Tag_t*)hashget(state.symbols, s) : (Tag_t*)0;
            *tp->name = '.';
            if (state.end)
            {
                  sfprintf(state.out, "<BR>%s ... %s OMITTED<BR>\n", tp->name, state.end->name);
                  state.pass = 1;
                  pushout(state.nul);
            }
      }
}

static void
troff_in(Tag_t* tp, Arg_t* ap)
{
      int   n;

      NoP(tp);
      if (ap->argc < 1)
            n = state.env->in.previous;
      else
      {
            n = expression(ap->argv[1], NiL, 'u');
            switch (ap->argv[1][0])
            {
            case '-':
            case '+':
                  n += state.env->in.current;
                  break;
            }
      }
      state.env->in.previous = state.env->in.current;
      state.env->in.current = n;
      li(0);
}

static void
troff_it(Tag_t* tp, Arg_t* ap)
{
      char* s;
      char* t;

      NoP(tp);
      state.it.count = 0;
      if (ap->argc >= 2 && (state.it.count = expression(ap->argv[1], NiL, 0)) > 0 && *(s = ap->argv[2]))
      {
            t = state.it.trap;
            *t++ = '.';
            *t++ = *s++;
            if (*s)
            {
                  *t++ = *s;
                  if (state.groff)
                        while (t < &state.it.trap[sizeof(state.it.trap)-2] && (*t = *++s))
                              t++;
            }
            *t++ = '\n';
            *t = 0;
      }
}

static void
troff_ll(Tag_t* tp, Arg_t* ap)
{
      int   n;

      NoP(tp);
      if (ap->argc < 1)
            n = state.env->ll.previous;
      else
      {
            n = expression(ap->argv[1], NiL, 'u');
            switch (ap->argv[1][0])
            {
            case '-':
            case '+':
                  n += state.env->ll.current;
                  break;
            }
      }
      state.env->ll.previous = state.env->ll.current;
      state.env->ll.current = n;
      li(0);
}

static void
troff_ne(Tag_t* tp, Arg_t* ap)
{
      NoP(tp);
      state.t = (ap->argc < 1) ? 1 : INT_MAX;
}

static void
troff_nf(Tag_t* tp, Arg_t* ap)
{
      NoP(tp);
      NoP(ap);
      if (!state.env->nf)
      {
            if (!state.it.interrupt)
                  it();
            li(-1);
            state.env->nf = 1;
            code_1(OP_pre);
      }
}

static void
troff_nr(Tag_t* tp, Arg_t* ap)
{
      int   i;
      long  n;
      char* s;
      Num_t*      np;

      NoP(tp);
      if (ap->argc >= 2)
      {
            np = num(ap->argv[1]);
            if ((i = *(s = ap->argv[2])) == '+' || i == '-')
                  s++;
            n = expression(s, NiL, 0);
            switch (i)
            {
            case '-':
                  n = np->number - n;
                  break;
            case '+':
                  n = np->number + n;
                  break;
            }
            np->number = n;
            if (np->internal)
                  switch (INDEX(np->name[1], np->name[2]))
                  {
                  case INDEX('c','.'):
                        if (state.original.line)
                              state.original.line = n;
                        else
                              error_info.line = n;
                        break;
                  case INDEX('d','l'):
                        state.dl = n;
                        break;
                  case INDEX('d','n'):
                        state.dn = n;
                        break;
                  case INDEX('l','n'):
                        state.ln = n;
                        break;
                  case INDEX('n','l'):
                        state.nl = n;
                        break;
                  }
            if (ap->argc >= 3)
                  np->increment = expression(ap->argv[3], NiL, 0);
            if (np->flags & TAG_TRACE_SET)
                  error(2, "set register %s %d %d", np->name + 1, np->number, np->increment);
      }
}

static void
troff_pc(Tag_t* tp, Arg_t* ap)
{
      NoP(tp);
      state.pc = (ap->argc >= 1) ? ap->argv[1][0] : 0;
}

static void
troff_ps(Tag_t* tp, Arg_t* ap)
{
      int   n;

      NoP(tp);
      if (ap->argc < 1)
            n = state.env->ps.previous;
      else
      {
            n = expression(ap->argv[1], NiL, 'p');
            switch (ap->argv[1][0])
            {
            case '-':
            case '+':
                  n += state.env->ps.current;
                  break;
            default:
                  if (!n)
                        n = state.env->ps.previous;
                  break;
            }
      }
      if (n > 0)
            ps(n);
}

static void
troff_rm(Tag_t* tp, Arg_t* ap)
{
      int   i;

      NoP(tp);
      for (i = 1; i <= ap->argc; i++)
            rm(ap->argv[i]);
}

static void
troff_rn(Tag_t* tp, Arg_t* ap)
{
      Tag_t*      mp;
      char* s;

      NoP(tp);
      if (ap->argc >= 2)
      {
            s = ap->argv[2];
            rm(s);
            *--s = '.';
            mp = mac(ap->argv[1]);
            hashput(state.symbols, NiL, NiL);
            mp->name = hashput(state.symbols, s, mp);
      }
}

static void
rr(char* name)
{
      register Num_t*   np;
      char        buf[MAXNAME];

      if (np = (Num_t*)hashget(state.symbols, nam('.', name, buf, sizeof(buf))))
      {
            if (np->internal)
                  return;
            if (np->flags & TAG_TRACE_SET)
                  error(2, "remove register %s", np->name + 1);
            free(np);
            hashput(state.symbols, NiL, NiL);
      }
}

static void
troff_rr(Tag_t* tp, Arg_t* ap)
{
      int   i;

      NoP(tp);
      for (i = 1; i <= ap->argc; i++)
            rr(ap->argv[i]);
}

static void
troff_so(Tag_t* tp, Arg_t* ap)
{
      Sfio_t*     sp;
      char* path;

      NoP(tp);
      if (ap->argc >= 1 && (sp = find(ap->argv[1], &path, 1)))
            pushin(path, 1, sp, NiL, NiL);
}

static void
troff_sp(Tag_t* tp, Arg_t* ap)
{
      NoP(tp);
      if (state.it.dd)
      {
            state.it.dd = 0;
            code_1(OP_dd);
      }
      code_1((ap->argc < 1 || expression(ap->argv[1], NiL, 'v') > 0) ? OP_p : OP_br);
}

static void
troff_ta(Tag_t* tp, Arg_t* ap)
{
      int         i;
      unsigned char     ta[elementsof(state.ta)];

      NoP(tp);
      if (ap->argc < 1)
      {
            state.ta[0] = 8;
            i = 1;
      }
      else
      {
            if (ap->argc >= elementsof(ta))
                  ap->argc = elementsof(ta) - 1;
            for (i = 0; i < ap->argc; i++)
                  ta[i] = expression(ap->argv[i+1], NiL, 'u');
      }
      ta[i] = 0;
      code_n(OP_ta, (char*)ta);
}

static void
troff_ti(Tag_t* tp, Arg_t* ap)
{
      int   n;

      NoP(tp);
      if (ap->argc < 1)
            n = state.env->ti.previous;
      else
      {
            n = expression(ap->argv[1], NiL, 'u');
            switch (ap->argv[1][0])
            {
            case '-':
            case '+':
                  n += state.env->ti.current;
                  break;
            }
      }
      state.env->ti.previous = state.env->ti.current;
      state.env->ti.current = n;
      li(1);
      if (state.list > state.list_stack && state.env->ti.current < state.list->in)
            state.it.dt = 1;
}

static void
troff_tl(Tag_t* tp, Arg_t* ap)
{
      register int      c;
      register int      q;
      register char*    s;
      register char*    t;
      int         n;

      NoP(tp);
      if (s = join(ap, 1))
      {
            if (state.head)
                  state.footer = 1;
            else
            {
                  state.head = 1;
                  code_1(END(OP_head));
                  code_1(OP_body);
            }
            code_1(OP_h3);
            code_2(OP_tab, ARG_wide);
            code_1(OP_tab_row);
            if (q = *s++)
                  for (n = 1; n <= 3; n++)
                  {
                        code_2(OP_tab_head, n);
                        if (n == 1)
                              code_2(OP_cc, ' ');
                        while ((c = *s++) != q)
                        {
                              if (!c)
                              {
                                    s--;
                                    break;
                              }
                              if (c != state.pc)
                                    sfputc(state.out, c);
                              else if (t = value("n%", 'n', 0))
                                    sfputr(state.out, t, -1);
                        }
                        if (n == 1)
                              code_2(OP_cc, ' ');
                  }
            code_1(END(OP_tab));
            code_1(END(OP_h3));
            sfputc(state.out, '\n');
      }
}

static void
troff_tm(Tag_t* tp, Arg_t* ap)
{
      char* s;

      NoP(tp);
      if (s = join(ap, 1))
            notify(s);
}

static void
troff_vs(Tag_t* tp, Arg_t* ap)
{
      int   n;

      NoP(tp);
      if (ap->argc < 1)
            n = state.env->vs.previous;
      else
      {
            n = expression(ap->argv[1], NiL, 'p');
            switch (ap->argv[1][0])
            {
            case '-':
            case '+':
                  n += state.env->vs.current;
                  break;
            default:
                  if (!n)
                        n = state.env->vs.previous;
                  break;
            }
      }
      if (n > 0)
      {
            state.env->vs.previous = state.env->vs.current;
            state.env->vs.current = n;
      }
}

/*
 * return 1 if any non-diverted text escaped
 */

static int
text(void)
{
      register char*    s;
      register char*    e;

      if (state.it.text)
            return 1;
      s = sfstrbase(state.out);
      e = sfstrseek(state.out, 0, SEEK_CUR);
      while (s < e)
      {
            switch (*s++)
            {
            case CODE_2:
                  s++;
                  /*FALLTHROUGH*/
            case CODE_1:
                  s++;
                  /*FALLTHROUGH*/
            case ' ':
            case '\t':
            case '\n':
            case CODE_0:
                  continue;
            default:
                  message((-9, "TEXT begin"));
                  return 1;
            }
      }
      return 0;
}

static void
troff_wh(Tag_t* tp, Arg_t* ap)
{
      int         i;
      Trap_t**    xp;

      if (ap->argc > 1)
      {
            if (!(i = expression(ap->argv[1], NiL, 'u')))
            {
                  if (!state.it.text && text())
                        state.it.text = 1;
                  if (state.it.text)
                        i = -1;
            }
            xp = (i < 0) ? &state.fini : &state.trap;
            for (i = 2; i <= ap->argc; i++)
                  trap(xp, ap->argv[i]);
      }
}

static void
hot(register char* s, int add)
{
      register Dir_t*   x;
      register Dir_t*   p;

      for (p = 0, x = state.hot; x; p = x, x = x->next)
            if (streq(s, x->name))
            {
                  if (!add)
                  {
                        if (p)
                              p->next = x->next;
                        else
                              state.hot = state.hot->next;
                        free(x);
                  }
                  return;
            }
      if (add)
      {
            if (!(x = newof(0, Dir_t, 1, strlen(s) + 1)))
                  error(ERROR_SYSTEM|3, "out of space [hot]");
            strcpy(x->name = (char*)x + sizeof(*x), s);
            x->next = state.hot;
            state.hot = x;
      }
}

#define OPT_SWITCH      1
#define OPT_begin 2
#define OPT_debug 3
#define OPT_end         4
#define OPT_footnote    5
#define OPT_get         6
#define OPT_hot         7
#define OPT_label 8
#define OPT_link  9
#define OPT_test  10
#define OPT_toolbar     11
#define OPT_set         12

typedef struct
{
      const char* name;
      char**            value;
      int         index;
} Option_t;

static Option_t   options[] =
{
      "author",   &state.author,          0,
      "background",     &state.background,      0,
      "begin",    0,                OPT_begin,
      "company",  &state.company,         0,
      "corporation",    &state.corporation,     0,
      "debug",    0,                OPT_debug,
      "end",            0,                OPT_end,
      "footnote", 0,                OPT_footnote,
      "get",            0,                OPT_get,
      "hot",            0,                OPT_hot,
      "label",    0,                OPT_label,
      "link",           0,                OPT_link,
      "location", &state.location,  0,
      "logo",           &state.logo,            0,
      "mailto",   &state.mailto,          0,
      "organization",   &state.organization,    0,
      "package",  &state.package,         0,
      "set",            0,                OPT_set,
      "test",           0,                OPT_test,
      "title",    &state.title,           0,
      "toolbar",  &state.toolbar,         OPT_toolbar,
      "verbose",  (char**)&state.verbose, OPT_SWITCH,
      0,          0,                0
};

/*
 * called by stropt() to set name=value
 */

static int
setopt(void* a, const void* x, register int n, const char* v)
{
      register char*          s;
      register char*          t;
      register Option_t*      p = (Option_t*)x;
      register int            f;

      if (p)
            switch (p->index)
            {
            case OPT_begin:
                  sfprintf(state.tmp, "<!-- %s -->", v + n);
                  code_n(OP_RAW, use(state.tmp));
                  break;
            case OPT_debug:
                  error_info.trace = n ? -expression((char*)v, NiL, 'u') : 0;
                  break;
            case OPT_end:
                  sfprintf(state.tmp, "<!-- /%s -->", v + n);
                  code_n(OP_RAW, use(state.tmp));
                  break;
            case OPT_footnote:
                  if (!state.out)
                        error(1, "%s: option valid from document body only", p->name);
                  else
                  {
                        /*
                         * NOTE: mm .FS/.FE is so convoluted that I
                         *       punted to this; trying to decipher
                         *     it prompted .xx [debug|get|set] in
                         *     the first place; I'll get back to a
                         *     real solution on a meetingless day.
                         */

                        if (n)
                        {
                              register char*    b;
                              long        m;

                              m = sfstrtell(state.out);
                              b = sfstrbase(state.out);
                              s = b + m;
                              while (s > b)
                                    if (!isspace(*--s))
                                    {
                                          s++;
                                          break;
                                    }
                              t = s;
                              while (s > b)
                                    if (isspace(*--s))
                                    {
                                          s++;
                                          break;
                                    }
                              sfprintf(state.tmp, "FN%ld\t%-.*s", m, t - s, s);
                              sfstrseek(state.out, s - b, SEEK_SET);
                              code_n(OP_link, use(state.tmp));
                              sfprintf(state.tmp, "FN%ld", m);
                              code_n(OP_fn, use(state.tmp));
                        }
                        else
                              code_1(END(OP_fn));
                  }
                  break;
            case OPT_get:
            case OPT_hot:
            case OPT_set:
                  switch (p->index)
                  {
                  case OPT_get:
                        f = TAG_TRACE_GET;
                        break;
                  case OPT_hot:
                        f = 0;
                        break;
                  case OPT_set:
                        f = TAG_TRACE_SET;
                        break;
                  }
                  s = (char*)v;
                  do
                  {
                        while (isspace(*s))
                              s++;
                        for (t = s;; t++)
                              if (!*t)
                              {
                                    t = 0;
                                    break;
                              }
                              else if (isspace(*t))
                              {
                                    *t++ = 0;
                                    break;
                              }
                        if (!s[0])
                              break;
                        if (s[0] == '+' && !s[1])
                              n = 1;
                        else if (s[0] == '-' && !s[1])
                              n = 0;
                        else if (!f)
                              hot(s, n);
                        else if (n)
                        {
                              num(s)->flags |= f;
                              mac(s)->flags |= f;
                        }
                        else
                        {
                              num(s)->flags &= ~f;
                              mac(s)->flags &= ~f;
                        }
                  } while (s = t);
                  break;
            case OPT_label:
                  if (!state.out)
                        error(1, "%s: option valid from document body only", p->name);
                  else if (n)
                  {
                        sfprintf(state.tmp, "%s\t", v);
                        code_n(LABEL(OP_link), use(state.tmp));
                  }
                  break;
            case OPT_link:
                  if (!state.out)
                        error(1, "%s: option valid from document body only", p->name);
                  else if (n)
                        code_n(OP_link, (char*)v);
                  break;
            case OPT_test:
                  if (n)
                        state.test |= expression((char*)v, NiL, 'u');
                  else
                        state.test &= ~expression((char*)v, NiL, 'u');
                  break;
            case OPT_SWITCH:
                  *((int*)p->value) = n && expression((char*)v, NiL, 'u');
                  break;
            default:
                  if (*p->value)
                        free(*p->value);
                  *p->value = n && v ? strdup(v) : (char*)0;
                  break;
            }
      else if (a)
      {
            if (strneq(v, "meta.", 5))
            {
                  sfprintf(state.tmp, "<META name=\"%s\" content=\"%s\">", v + 5, v + n);
                  code_n(OP_RAW, use(state.tmp));
            }
            else
                  error(1, "%s: unknown option", v);
      }
      return 0;
}

static void
troff_xx(Tag_t* tp, Arg_t* ap)
{
      NoP(tp);
      stropt(join(ap, 1), options, sizeof(*options), setopt, options);
}

#define COMMENT         001
#define COPY            002
#define EAT       004
#define RAW       010
#define STRING          020

#define ONE() \
      do \
      {\
            if (cc <= 0) \
            { \
                  if (cc < 0) \
                        cc++; \
                  if (!argc && !state.it.interrupt) \
                        it(); \
            } \
            cc++; \
      } while (0)

/*
 * convert troff file ip to intermediate file op
 */

static void
process(char* file, Sfio_t* ip, Sfio_t* op)
{
      register int      c;
      register int      cc;
      register int      lastc;
      register char*    s;
      int         argc;
      int         quote;
      int         n;
      int         m;
      unsigned char*    map;
      Tag_t*            tp;
      int         argv[ARGS];
      struct stat st;
      char        buf[MAXNAME];

      pushin(file, 1, ip, NiL, NiL);
      state.generation++;
      *(state.env_sp = state.env_stack) = state.env = env("0");
      state.it.dd = state.it.dl = state.it.dt = 0;
      state.link = 0;
      iop("stdout", 1)->sp = state.out = op;
      state.pass = 0;
      if (ip && !fstat(sffileno(ip), &st))
            tm(st.st_mtime);
      argc = 0;
      argv[0] = 0;
      cc = 0;
      lastc = 0;
      quote = 0;
      map = ccmap(CC_NATIVE, CC_ASCII);
      for (;;)
      {
            switch (c = GETC())
            {
            case EOF:
                  if (popin())
                        goto done;
                  continue;
            case CODE_n:
                  if (ISFILE())
                  {
                        cc++;
                        code_2(OP_cc, c);
                  }
                  else
                  {
                        sfputc(state.out, c);
                        n = GETC();
                        sfputc(state.out, n);
                        if (n & 0200)
                        {
                              n = (n & 0177) << 8;
                              c = GETC();
                              sfputc(state.out, c);
                              n |= c;
                        }
                        do
                        {
                              c = GETC();
                              sfputc(state.out, c);
                        } while (n-- > 0);
                  }
                  continue;
            case CODE_2:
                  if (ISFILE())
                  {
                        cc++;
                        code_2(OP_cc, c);
                  }
                  else
                  {
                        m = GETC();
                        n = GETC();
                        if (cc <= 0 && (n == OP_cc || n >= OP_ft1 && n <= OP_ft5 || n == OP_ps))
                              ONE();
                        sfputc(state.out, c);
                        sfputc(state.out, m);
                        sfputc(state.out, n);
                  }
                  continue;
            case CODE_1:
                  if (ISFILE())
                  {
                        cc++;
                        code_2(OP_cc, c);
                  }
                  else
                  {
                        sfputc(state.out, c);
                        switch (c = GETC())
                        {
                        case OP_br:
                        case OP_p:
                              cc = 0;
                              break;
                        }
                        sfputc(state.out, c);
                  }
                  continue;
            case CODE_0:
                  if (ISFILE())
                  {
                        cc++;
                        code_2(OP_cc, c);
                  }
                  else
                  {
                        ONE();
                        sfputc(state.out, c);
                  }
                  continue;
            case '"':
                  if (argc && !(quote & RAW))
                  {
                        lastc = c;
                        switch ((quote & STRING) ? (c = nextchar()) : EOF)
                        {
                        case '"':
                              break;
                        default:
                              UNGETC(c);
                              /*FALLTHROUGH*/
                        case EOF:
                              quote ^= STRING;
                              continue;
                        }
                  }
                  break;
            case '\n':
                  if (state.noline)
                  {
                        if (isspace(lastc))
                              continue;
                        c = ' ';
                        break;
                  }
                  if (quote & COMMENT)
                  {
                        quote &= ~COMMENT;
                        if (quote & EAT)
                        {
                              quote &= ~EAT;
                              c = ' ';
                        }
                        popout();
                  }
                  if (argc)
                  {
                        cc = 0;
                        lastc = c;
                        quote = 0;
                        state.groff &= 1;
                        state.pass = 0;
                        if (!(s = popout()))
                        {
                              argc = 0;
                              continue;
                        }
                        if (!s[argv[--argc]] && argc > 0)
                              argc--;
                        for (state.tag->argc = argc; argc >= 0; argc--)
                              state.tag->argv[argc] = s + argv[argc];
                        argc = 0;
                        if (error_info.trace <= -4)
                        {
                              if (state.end)
                                    sfprintf(state.tmp, "    ");
                              sfprintf(state.tmp, "%s", state.tag->argv[0]);
                              if (!tp)
                                    sfprintf(state.tmp, " [UNKNOWN]");
                              for (n = 1; n <= state.tag->argc; n++)
                                    sfprintf(state.tmp, " `%s'", state.tag->argv[n]);
                              error(-4, "%s:%d: %s", error_info.file, error_info.line, use(state.tmp));
                        }
                        if (tp)
                        {
                              error_info.line++;
                              if (tp->call)
                                    (*tp->call)(tp, state.tag);
                              if (tp->flags & TAG_TRIGGER)
                              {
                                    tp->flags &= ~TAG_TRIGGER;
                                    trigger(&state.trap);
                              }
                        }
                        else if (state.verbose)
                        {
                              switch (state.tag->argv[0][1])
                              {
                              case 0:
                                    break;
                              case '#':
                                    if (!state.tag->argv[0][2])
                                          break;
                                    /*FALLTHROUGH*/
                              default:
                                    if (error_info.trace >= 0)
                                          error(1, "%s: unknown request", state.tag->argv[0]);
                                    sfprintf(state.tmp, "UNKNOWN REQUEST %s", join(state.tag, 0));
                                    code_n(OP_comment, use(state.tmp));
                                    break;
                              }
                              error_info.line++;
                        }
                        continue;
                  }
                  error_info.line++;
                  n = 6 * state.env->vs.current;
                  state.nl += n;
                  state.env->dn += n;
                  n = 6 * state.env->ps.current;
                  if (n > state.env->dl)
                        state.env->dl = n;
                  if (!DIVERTED())
                  {
                        state.ln++;
                        if (state.it.text || cc > 1)
                              state.it.text++;
                        else
                        {
                              cc = 0;
                              continue;
                        }
                  }
                  state.it.interrupt = 0;
                  if (state.it.center > 0)
                  {
                        if (!--state.it.center)
                              code_1(state.it.right ? END(OP_p) : END(OP_center));
                        else
                              code_1(OP_br);
                  }
                  if (state.it.count > 0 && !--state.it.count)
                  {
                        sfputc(state.out, '\n');
                        pushin(NiL, 0, NiL, state.it.trap, NiL);
                        cc = 0;
                        continue;
                  }
                  cc = -2;
                  break;
            case ' ':
            case '\t':
                  if (argc)
                  {
                        if (!quote || (quote == RAW || quote == (COPY|RAW)) && argc == 1)
                        {
                              if (lastc != ' ' && argc < elementsof(argv))
                              {
                                    sfputc(state.out, 0);
                                    argv[argc++] = sftell(state.out);
                                    lastc = ' ';
                              }
                              continue;
                        }
                  }
                  break;
            default:
                  if (c == state.ec)
                  {
                  escape:
                        switch (c = GETC())
                        {
                        case EOF:
                              if (popin())
                              {
                                    c = '\n';
                                    break;
                              }
                              goto escape;
                        case '\n':
                              if (ISFILE())
                                    error_info.line++;
                              else
                                    UNGETC(c);
                              continue;
                        case 'a':
                              if (state.pass)
                                    goto passthrough;
                              c = 1;
                              break;
                        case 'c':
                              if (state.pass || (quote & RAW))
                                    goto passthrough;
                              if ((c = nextchar()) == '\n')
                              {
                                    message((-9, "INTERRUPT %s:%d: it.center=%d it.count=%d it.dt", error_info.file, error_info.line, state.it.center, state.it.count));
                                    state.it.interrupt = 1;
                                    error_info.line++;
                                    cc = 0;
                                    continue;
                              }
                              UNGETC(c);
                              continue;
                        case 'd':
                              if (state.env->ss <= 0)
                              {
                                    state.env->ss--;
                                    code_1(OP_sub);
                              }
                              else
                              {
                                    state.env->ss++;
                                    code_1(END(OP_sup));
                              }
                              continue;
                        case 'e':
                        case 'E':
                              if (state.pass)
                                    goto passthrough;
                              ONE();
                              code_2(OP_cc, state.eo);
                              lastc = state.eo;
                              continue;
                        case 'f':
                              if (state.pass || (quote & RAW))
                                    goto passthrough;
                              ft(interpolate('f'));
                              continue;
                        case 'g':
                        case 'j':
                              if (state.pass || (quote & RAW))
                                    goto passthrough;
                              pushin(NiL, 0, NiL, interpolate(c), NiL);
                              continue;
                        case 'k':
                              nextchar();
                              continue;
                        case '*':
                              c = '.';
                              goto interp;
                        case '[':
                              if (!state.groff)
                                    goto passthrough;
                        case '(':
                              if (state.pass)
                                    goto passthrough;
                              UNGETC(c);
                              /*FALLTHROUGH*/
                        case 'n':
                        interp:
                              if (quote & COPY)
                                    goto passthrough;
                              if (s = interpolate(c))
                                    pushin(NiL, -1, NiL, s, NiL);
                              continue;
                        case 'p':
                        case 'r':
                        case 'z':
                              continue;
                        case 's':
                              if (state.pass || (quote & RAW))
                                    goto passthrough;
                              switch (c = nextchar())
                              {
                              case '-':
                              case '+':
                                    m = c;
                                    c = nextchar();
                                    break;
                              default:
                                    m = 0;
                                    break;
                              }
                              switch (c)
                              {
                              case '(':
                                    n = 1;
                                    sfputc(state.tmp, c);
                                    break;
                              case '\'':
                                    n = c;
                                    goto size_long;
                              case '[':
                                    n = ']';
                              size_long:
                                    c = nextchar();
                                    if (!m)
                                          switch (c)
                                          {
                                          case '-':
                                          case '+':
                                                m = c;
                                                c = nextchar();
                                                break;
                                          }
                                    while (c != EOF && c != n)
                                    {
                                          sfputc(state.tmp, c);
                                          c = nextchar();
                                    }
                                    goto size_eval;
                              default:
                                    n = 0;
                                    break;
                              }
                              if (c == '0' && m)
                              {
                                    if ((n = nextchar()) == '\'')
                                    {
                                          while ((c = nextchar()) != EOF && c != '\'')
                                                sfputc(state.tmp, c);
                                          code_n(m == '+' ? OP_link : LABEL(OP_link), use(state.tmp));
                                          continue;
                                    }
                                    UNGETC(n);
                                    n = 0;
                              }
                              if (c != EOF)
                              {
                                    if (n)
                                    {
                                          for (;; c = nextchar())
                                          {
                                                switch (c)
                                                {
                                                case EOF:
                                                      break;
                                                case '(':
                                                      sfputc(state.tmp, c);
                                                      n++;
                                                      continue;
                                                case ')':
                                                      sfputc(state.tmp, c);
                                                      if (--n <= 0)
                                                            break;
                                                      continue;
                                                default:
                                                      sfputc(state.tmp, c);
                                                      continue;
                                                }
                                                break;
                                          }
                                    size_eval:
                                          n = expression(use(state.tmp), NiL, 'p');
                                    }
                                    else
                                          n = isdigit(c) ? (c - '0') : 0;
                                    if (!n)
                                          n = state.env->ps.previous;
                                    else switch (m)
                                    {
                                    case '-':
                                          n = state.env->ps.current - n;
                                          break;
                                    case '+':
                                          n = state.env->ps.current + n;
                                          break;
                                    }
                                    if (n > 0)
                                          ps(n);
                              }
                              continue;
                        case 't':
                              if (state.pass)
                                    goto passthrough;
                              c = '\t';
                              break;
                        case 'u':
                              if (state.env->ss >= 0)
                              {
                                    state.env->ss++;
                                    code_1(OP_sup);
                              }
                              else
                              {
                                    state.env->ss--;
                                    code_1(END(OP_sub));
                              }
                              continue;
                        case 'v':
                        case 'w':
                              if (state.pass || (quote & RAW))
                                    goto passthrough;
                              /*FALLTHROUGH*/
                        case 'b':
                        case 'h':
                        case 'l':
                        case 'L':
                        case 'o':
                        case 'x':
                              if ((n = nextchar()) != EOF)
                              {
                                    while ((m = nextchar()) != n)
                                          sfputc(state.arg, m);
                                    s = use(state.arg);
                                    switch (c)
                                    {
                                    case 'h':
                                          if (*s++ == '0')
                                          {
                                                /*UNDENT...*/
      if ((c = *s++) == '*' || c == '/')
      {
            state.link = c == '*' ? OP_link : LABEL(OP_link);
            if (*s++ == '\\' && *s++ == 'w' && *s++ == '"')
            {
                  if ((c = strlen(s)) > 0 && s[--c] == '"')
                        s[c] = 0;
                  sfputr(state.ref, s, '\t');
            }
            pushout(state.ref);
      }
      else if (!c && state.link)
      {
            code_n(state.link, popout());
            state.link = 0;
      }
                                                /*...INDENT*/
                                          }
                                          /* yep, this is a grade A hack, even for this file */
                                          if ((c = nextchar()) == state.ec)
                                          {
                                                if ((n = nextchar()) == 'c')
                                                      break;
                                                UNGETC(n);
                                          }
                                          UNGETC(c);
                                          break;
                                    case 'v':
                                          c = expression(s, NiL, 0) >= 0 ? 'd' : 'u';
                                          sfprintf(state.arg, "%c%c", state.ec, c);
                                          pushin(NiL, 0, NiL, use(state.arg), NiL);
                                          break;
                                    case 'w':
                                          n = convert(strlen(s), 1, 'n');
                                          sfprintf(state.arg, "%d", n);
                                          pushin(NiL, 0, NiL, use(state.arg), NiL);
                                          break;
                                    }
                              }
                              continue;
                        case '$':
                              if (state.mac)
                              {
                                    c = nextchar();
                                    if (c == '(')
                                    {
                                          if ((c = nextchar()) != EOF)
                                          {
                                                sfputc(state.tmp, c);
                                                if ((c = nextchar()) != EOF)
                                                      sfputc(state.tmp, c);
                                          }
                                          goto arg_eval;
                                    }
                                    else if (c == '[')
                                    {
                                          while ((c = nextchar()) != EOF && c != ']')
                                                sfputc(state.tmp, c);
                                    arg_eval:
                                          c = expression(use(state.tmp), NiL, 0);
                                          if (c >= 0 && c <= state.mac->argc)
                                                pushin(NiL, -argc, NiL, state.mac->argv[c], NiL);
                                    }
                                    else if (c == '@')
                                    {
                                          for (c = 1; c < state.mac->argc; c++)
                                                sfprintf(state.tmp, "\"%s\" ", state.mac->argv[c]);
                                          if (c == state.mac->argc)
                                                sfprintf(state.tmp, "\"%s\"", state.mac->argv[c]);
                                          pushin(NiL, -argc, NiL, use(state.tmp), NiL);
                                    }
                                    else if (c < '0' || c > '9')
                                    {
                                          for (c = 1; c < state.mac->argc; c++)
                                                sfputr(state.tmp, state.mac->argv[c], ' ');
                                          if (c == state.mac->argc)
                                                sfputr(state.tmp, state.mac->argv[c], -1);
                                          pushin(NiL, -argc, NiL, use(state.tmp), NiL);
                                    }
                                    else if ((c -= '0') <= state.mac->argc)
                                          pushin(NiL, -argc, NiL, state.mac->argv[c], NiL);
                              }
                              continue;
                        case '{':
                              for (;;)
                              {
                                    if ((n = GETC()) == EOF)
                                    {
                                          if (popin())
                                                break;
                                          continue;
                                    }
                                    if (n == state.ec)
                                    {
                                    escape_splice:
                                          switch (m = GETC())
                                          {
                                          case EOF:
                                                if (popin())
                                                      break;
                                                goto escape_splice;
                                          case '\n':
                                                UNGETC(m);
                                                break;
                                          default:
                                                UNGETC(m);
                                                UNGETC(n);
                                                break;
                                          }
                                          break;
                                    }
                                    else
                                          UNGETC(n);
                                    break;
                              }
                              if (state.pass)
                                    goto passthrough;
                              continue;
                        case '}':
                              if (state.end || !(state.cond.flags[state.cond.level] & COND_BLOCK))
                                    goto passthrough;
                              if (state.cond.flags[state.cond.level] & (COND_EL|COND_IF))
                                    state.cond.level--;
                              else
                                    state.cond.flags[state.cond.level] &= ~COND_BLOCK;
                              continue;
                        case '0':
                        case '|':
                        case '^':
                        case ' ':
                        case '/':
                        case ',':
                        case '~':
                              if (state.pass)
                                    goto passthrough;
                              ONE();
                              code_2(OP_cc, ' ');
                              continue;
                        case '&':
                              if (state.pass || (quote & RAW))
                                    goto passthrough;
                              if (!cc)
                                    cc = -1;
                              code_0();
                              continue;
                        case '-':
                        case '%':
                              if (state.pass)
                                    goto passthrough;
                              ONE();
                              code_2(OP_cc, 45);
                              continue;
                        case '"':
                              if (!quote)
                              {
                                    quote |= COMMENT;
                                    pushout(state.nul);
                              }
                              cc++;
                              continue;
                        case '#':
                              if (!quote)
                              {
                                    quote |= COMMENT|EAT;
                                    pushout(state.nul);
                              }
                              cc++;
                              continue;
                        case '!':
                              if (!cc)
                              {
                                    cc++;
                                    continue;
                              }
                              break;
                        default:
                              if (c == state.ec)
                                    break;
                              if ((c == state.env->cc || c == state.env->c2) && !cc)
                                    goto request;
                              if (state.pass)
                                    goto passthrough;
                              break;
                        }
                        break;
                  passthrough:
                        ONE();
                        sfputc(state.out, state.ec);
                        break;
                  }
                  else if ((c == state.env->cc || c == state.env->c2) && !cc)
                  {
                  request:
                        n = c;
                        s = buf;
                        *s++ = '.';
                        if ((c = nextchar()) != EOF)
                        {
                              while (c == ' ' || c == '\t')
                                    c = nextchar();
                              if (c == state.ec || c == '\n')
                                    UNGETC(c);
                              else if (c != EOF)
                              {
                                    *s++ = c;
                                    while (s < &buf[sizeof(buf)-1] && (c = nextchar()) != EOF)
                                    {
                                          if (c == state.ec || isspace(c))
                                          {
                                                UNGETC(c);
                                                break;
                                          }
                                          *s++ = c;
                                          if (!state.groff)
                                          {
                                                if ((c = nextchar()) != EOF)
                                                {
                                                      UNGETC(c);
                                                      if (!isspace(c))
                                                            pushin(NiL, 0, NiL, " ", NiL);
                                                }
                                                break;
                                          }
                                    }
                              }
                        }
                        *s = 0;
                        tp = (Tag_t*)hashget(state.symbols, buf);
                        if (tp && (tp->flags & TAG_DO))
                        {
                              state.groff |= 2;
                              c = n;
                              goto request;
                        }
                        if (state.end)
                        {
                              if (tp == state.end)
                              {
                                    state.end = 0;
                                    state.pass = 0;
                                    s = popout();
                                    if (tp = state.define)
                                    {
                                          state.define = 0;
                                          set(tp, s, 0);
                                    }
                                    quote |= COMMENT;
                                    pushout(state.nul);
                              }
                              else
                              {
                                    pushin(NiL, 0, NiL, buf + 1, NiL);
                                    c = n;
                              }
                              break;
                        }
                        if (tp)
                        {
                              if ((tp->flags & TAG_BREAK) && n == state.env->cc)
                              {
                                    if (!DIVERTED() && (state.it.text || text()))
                                          state.it.text++;
                                    if (state.it.interrupt)
                                    {
                                          message((-9, "BREAK %s:%d: it.center=%d it.count=%d", error_info.file, error_info.line, state.it.center, state.it.count));
                                          state.it.interrupt = 0;
                                          sfputc(state.out, '\n');
                                          ONE();
                                    }
                                    tp->flags |= TAG_TRIGGER;
                              }
                              if (tp->flags & TAG_COPY)
                                    quote |= COPY|RAW;
                              else if (tp->flags & TAG_RAW)
                                    quote |= RAW;
                              if (tp->flags & TAG_PASS)
                                    state.pass = 1;
                        }
                        else
                              state.pass = 1;
                        argc = 1;
                        pushout(state.tag->sp);
                        sfputr(state.out, buf, -1);
                        cc = s - buf;
                        continue;
                  }
                  else if ((n = ccmapchr(map, c)) > 0177)
                  {
                        ONE();
                        code_2(OP_cc, n & 0377);
                        continue;
                  }
                  break;
            }
            ONE();
            sfputc(state.out, c);
            lastc = c;
      }
 done:
      if (state.end)
      {
            if (state.define)
            {
                  error(2, "%s macro definition end tag %s not found", state.define->name, state.end->name);
                  state.define = 0;
            }
            else
                  error(2, "group end tag %s not found", state.end->name);
            state.end = 0;
      }
      while (DIVERTED())
            popout();
}

static Tag_t tags[] =
{
      ".",        0,          0,                0,0,0,0,
      ".'",       0,          0,                0,0,0,0,
      ".''",            0,          0,                0,0,0,0,
      "..",       0,          0,                0,0,0,0,
      ".EN",            0,          0,                0,0,0,0,
      ".EQ",            troff_ignore,     0,                0,0,0,0,
      ".TE",            0,          0,                0,0,0,0,
      ".TS",            troff_ignore,     0,                0,0,0,0,
      ".\"",            0,          0,                0,0,0,0,
      ".\\\"",0,              0,                0,0,0,0,
      ".ab",            troff_ab,   0,                0,0,0,0,
      ".ad",            troff_ad,   TAG_PASS,         0,0,0,0,
      ".af",            troff_af,   0,                0,0,0,0,
      ".al",            0,          0,                0,0,0,0,
      ".aln",           groff_aln,  0,                0,0,0,0,
      ".als",           groff_als,  0,                0,0,0,0,
      ".am",            troff_de,   TAG_PASS,         0,0,0,0,
      ".as",            troff_ds,   TAG_PASS,         0,0,0,0,
      ".asciify", groff_asciify,    0,                0,0,0,0,
      ".backtrace",     0,          0,                0,0,0,0,
      ".bd",            0,          0,                0,0,0,0,
      ".blm",           0,          0,                0,0,0,0,
      ".bp",            0,          TAG_BREAK,        0,0,0,0,
      ".br",            troff_br,   TAG_BREAK,        0,0,0,0,
      ".break",   groff_break,      0,                0,0,0,0,
      ".c2",            troff_c2,   0,                0,0,0,0,
      ".cc",            troff_cc,   0,                0,0,0,0,
      ".ce",            troff_ce,   TAG_BREAK,        0,0,0,0,
      ".cf",            troff_cf,   0,                0,0,0,0,
      ".cflags",  0,          0,                0,0,0,0,
      ".ch",            troff_ch,   0,                0,0,0,0,
      ".char",    0,          0,                0,0,0,0,
      ".chop",    groff_chop, 0,                0,0,0,0,
      ".close",   groff_close,      0,                0,0,0,0,
      ".continue",      groff_continue,   0,                0,0,0,0,
      ".cp",            groff_cp,   0,                0,0,0,0,
      ".cs",            0,          0,                0,0,0,0,
      ".cu",            0,          0,                0,0,0,0,
      ".da",            troff_di,   TAG_PASS,         0,0,0,0,
      ".de",            troff_de,   TAG_PASS,         0,0,0,0,
      ".di",            troff_di,   TAG_PASS,         0,0,0,0,
      ".do",            0,          TAG_DO,                 0,0,0,0,
      ".ds",            troff_ds,   TAG_PASS,         0,0,0,0,
      ".dt",            troff_wh,   TAG_PASS,         0,0,0,0,
      ".ec",            troff_ec,   0,                0,0,0,0,
      ".eo",            troff_eo,   0,                0,0,0,0,
      ".el",            troff_ie,   TAG_PASS|TAG_RAW, 0,0,0,0,
      ".em",            troff_em,   TAG_PASS,         0,0,0,0,
      ".ev",            troff_ev,   TAG_PASS,         0,0,0,0,
      ".fam",           0,          0,                0,0,0,0,
      ".fc",            0,          0,                0,0,0,0,
      ".fi",            troff_fi,   TAG_BREAK,        0,0,0,0,
      ".fl",            0,          TAG_BREAK,        0,0,0,0,
      ".fp",            troff_fp,   0,                0,0,0,0,
      ".fspecial",      0,          0,                0,0,0,0,
      ".ft",            troff_ft,   0,                0,0,0,0,
      ".ftr",           0,          0,                0,0,0,0,
      ".hcode",   0,          0,                0,0,0,0,
      ".hla",           0,          0,                0,0,0,0,
      ".hlm",           0,          0,                0,0,0,0,
      ".hpf",           0,          0,                0,0,0,0,
      ".hw",            0,          0,                0,0,0,0,
      ".hy",            0,          0,                0,0,0,0,
      ".hym",           0,          0,                0,0,0,0,
      ".hys",           0,          0,                0,0,0,0,
      ".ie",            troff_ie,   TAG_PASS|TAG_RAW, 0,0,0,0,
      ".if",            troff_ie,   TAG_PASS|TAG_RAW, 0,0,0,0,
      ".ig",            troff_ignore,     TAG_PASS,         0,0,0,0,
      ".in",            troff_in,   TAG_BREAK,        0,0,0,0,
      ".it",            troff_it,   0,                0,0,0,0,
      ".kern",    0,          0,                0,0,0,0,
      ".lc",            0,          0,                0,0,0,0,
      ".lf",            0,          0,                0,0,0,0,
      ".ll",            troff_ll,   0,                0,0,0,0,
      ".ls",            0,          0,                0,0,0,0,
      ".lt",            0,          0,                0,0,0,0,
      ".mk",            0,          0,                0,0,0,0,
      ".mso",           troff_so,   0,                0,0,0,0,
      ".na",            0,          0,                0,0,0,0,
      ".ne",            troff_ne,   0,                0,0,0,0,
      ".nf",            troff_nf,   TAG_BREAK,        0,0,0,0,
      ".nh",            0,          0,                0,0,0,0,
      ".nm",            0,          0,                0,0,0,0,
      ".nn",            0,          0,                0,0,0,0,
      ".nr",            troff_nr,   0,                0,0,0,0,
      ".nroff",   0,          0,                0,0,0,0,
      ".ns",            0,          0,                0,0,0,0,
      ".open",    groff_open, 0,                0,0,0,0,
      ".opena",   groff_open, 0,                0,0,0,0,
      ".os",            0,          0,                0,0,0,0,
      ".pc",            troff_pc,   0,                0,0,0,0,
      ".pl",            0,          0,                0,0,0,0,
      ".pm",            0,          0,                0,0,0,0,
      ".pn",            0,          0,                0,0,0,0,
      ".pnr",           0,          0,                0,0,0,0,
      ".po",            0,          0,                0,0,0,0,
      ".ps",            troff_ps,   0,                0,0,0,0,
      ".pso",           groff_pso,  0,                0,0,0,0,
      ".ptr",           0,          0,                0,0,0,0,
      ".rchar",   0,          0,                0,0,0,0,
      ".rj",            troff_ce,   0,                0,0,0,0,
      ".rm",            troff_rm,   0,                0,0,0,0,
      ".rn",            troff_rn,   0,                0,0,0,0,
      ".rnn",           groff_rnn,  0,                0,0,0,0,
      ".rr",            troff_rr,   0,                0,0,0,0,
      ".rs",            0,          0,                0,0,0,0,
      ".rt",            0,          0,                0,0,0,0,
      ".shc",           0,          0,                0,0,0,0,
      ".shift",   groff_shift,      0,                0,0,0,0,
      ".so",            troff_so,   0,                0,0,0,0,
      ".sp",            troff_sp,   TAG_BREAK,        0,0,0,0,
      ".special", 0,          0,                0,0,0,0,
      ".ss",            0,          0,                0,0,0,0,
      ".sty",           0,          0,                0,0,0,0,
      ".sv",            0,          0,                0,0,0,0,
      ".sy",            groff_sy,   0,                0,0,0,0,
      ".ta",            troff_ta,   TAG_BREAK,        0,0,0,0,
      ".tc",            0,          0,                0,0,0,0,
      ".ti",            troff_ti,   TAG_BREAK,        0,0,0,0,
      ".tkf",           0,          0,                0,0,0,0,
      ".tl",            troff_tl,   0,                0,0,0,0,
      ".tm",            troff_tm,   0,                0,0,0,0,
      ".tr",            0,          0,                0,0,0,0,
      ".trf",           0,          0,                0,0,0,0,
      ".trnt",    0,          0,                0,0,0,0,
      ".troff",   0,          0,                0,0,0,0,
      ".uf",            0,          0,                0,0,0,0,
      ".ul",            0,          0,                0,0,0,0,
      ".vpt",           0,          0,                0,0,0,0,
      ".vs",            troff_vs,   0,                0,0,0,0,
      ".warn",    0,          0,                0,0,0,0,
      ".wh",            troff_wh,   TAG_PASS,         0,0,0,0,
      ".while",   groff_while,      TAG_PASS|TAG_COPY,      0,0,0,0,
      ".write",   groff_write,      0,                0,0,0,0,
      ".xx",            troff_xx,   TAG_RAW,          0,0,0,0,
};

static Var_t vars[] =
{
      "(**",      "*",
      "(+-",      "\261",
      "(ap",      "~",
      "(bu",      "\267",
      "(bv",      "|",
      "(co",      "\251",
      "(dg",      "\247",
      "(fm",      "'",
      "(lq",      "``",
      "(rg",      "\256",
      "(rq",      "''",
      "(sq",      "\244",
      "eEQ",      ".EN",
      "eTS",      ".TE",
      "eig",      "..",
      "f",  "",
      "f1", "1",
      "f2", "2",
      "f3", "3",
      "f5", "5",
      "fB", "3",
      "fCW",      "5",
      "fF", "5",
      "fI", "2",
      "fL", "5",
      "fM", "5",
      "fR", "1",
      "fX", "5",
};

static Dir_t dot =
{
      0,    ".",
};

/*
 * initialize the global data
 */

static void
init(void)
{
      register int      i;

      state.groff |= 2;
      state.tag = &state.top;
      if (!(state.top.sp = sfstropen()))
            error(ERROR_SYSTEM|3, "out of space [top buffer]");
      if (!(state.arg = sfstropen()))
            error(ERROR_SYSTEM|3, "out of space [arg buffer]");
      if (!(state.nul = sfopen(NiL, "/dev/null", "w")))
            error(ERROR_SYSTEM|3, "out of space [nul buffer]");
      if (!(state.ref = sfstropen()))
            error(ERROR_SYSTEM|3, "out of space [ref buffer]");
      if (!(state.req = sfstropen()))
            error(ERROR_SYSTEM|3, "out of space [req buffer]");
      if (!(state.tmp = sfstropen()))
            error(ERROR_SYSTEM|3, "out of space [tmp buffer]");
      if (!(state.symbols = hashalloc(NiL, HASH_name, "symbols", 0)))
            error(ERROR_SYSTEM|3, "out of space [symbol hash]");
      for (i = 0; i < elementsof(vars); i++)
            if (!hashput(state.symbols, vars[i].name, vars[i].value))
                  error(ERROR_SYSTEM|3, "out of space [var hash put]");
      for (i = 0; i < elementsof(tags); i++)
      {
            tags[i].flags |= TAG_STATIC;
            if (!hashput(state.symbols, tags[i].name, &tags[i]))
                  error(ERROR_SYSTEM|3, "out of space [tag hash put]");
      }
      hashset(state.symbols, HASH_ALLOCATE);
      nr("%", 1, 0, 1);
      nr(".$", 0, 0, 1);
      nr(".A", 0, 0, 1);
      nr(".C", 0, 0, 1);
      nr(".F", 0, 0, 1);
      nr(".H", 0, 0, 1);
      nr(".L", 0, 0, 1);
      nr(".P", 0, 0, 1);
      nr(".T", 0, 0, 1);
      nr(".R", 0, 0, 1);
      nr(".V", 0, 0, 1);
      nr(".a", 0, 0, 1);
      nr(".b", 0, 0, 1);
      nr(".c", 0, 0, 1);
      nr(".ce", 0, 0, 1);
      nr(".d", 0, 0, 1);
      nr(".ev", 0, 0, 1);
      nr(".f", 0, 0, 1);
      nr(".g", 0, 0, 1);
      nr(".i", 0, 0, 1);
      nr(".in", 0, 0, 1);
      nr(".j", 0, 0, 1);
      nr(".k", 0, 0, 1);
      nr(".l", 0, 0, 1);
      nr(".ll", 0, 0, 1);
      nr(".n", 0, 0, 1);
      nr(".pn", 0, 0, 1);
      nr(".ps", 0, 0, 1);
      nr(".psr", 0, 0, 1);
      nr(".s", 0, 0, 1);
      nr(".sr", 0, 0, 1);
      nr(".t", 0, 0, 1);
      nr(".u", 0, 0, 1);
      nr(".v", 0, 0, 1);
      nr(".vpt", 0, 0, 1);
      nr(".w", 0, 0, 1);
      nr(".warn", 0, 0, 1);
      nr(".x", 0, 0, 1);
      nr(".y", 0, 0, 1);
      nr(".z", 0, 0, 1);
      nr("c.", 0, 0, 1);
      nr("dl", 0, 0, 1);
      nr("dn", 0, 0, 1);
      nr("ln", 0, 0, 1);
      nr("nl", 0, 0, 1);
      nr("systat", 0, 0, 0);
      tm(NiL);
      hot("see", 1);
      hot("refer", 1);
      state.ec = state.eo = DEFAULT_ec;
      state.in = (unsigned char*)"";
      state.in_top = state.in_stack;
      state.out_top = state.out_stack;
      state.tag_top = state.tag_stack;
      state.pc = DEFAULT_pc;
      state.list = state.list_stack;
      state.ta[0] = 8;
      state.ta[1] = 0;
      state.t = 1;
      iop("stderr", 1)->sp = sfstderr;
      state.groff &= 1;
}

/*
 * convert intermediate code in s to html on op
 */

static const char*      opt_align[] =
{
      "LEFT", "CENTER", "RIGHT",
};

static const char*      opt_attribute[] =
{
      "BACKGROUND", "HREF", "HREF", "ID", "NAME", "SIZE", "SRC",
};

static const char*      tag_name[] =
{
      0,          /* unknown  */
      "A",        /* OP_a           */
      "BODY",           /* OP_body  */
      "BR",       /* OP_br    */
      0,          /* OP_cc    */
      "CENTER",   /* OP_center      */
      0,          /* OP_comment     */
      "DD",       /* OP_dd    */
      "DIV",            /* OP_div   */
      "DL",       /* OP_dl    */
      "DT",       /* OP_dt    */
      "FN",       /* OP_fn    */
      0,          /* OP_ft1   */
      "EM",       /* OP_ft2   */
      "STRONG",   /* OP_ft3   */
      0,          /* OP_ft4   */
      "TT",       /* OP_ft5   */
      "H2",       /* OP_h2    */
      "H3",       /* OP_h3    */
      "H4",       /* OP_h4    */
      "HEAD",           /* OP_head  */
      "HR",       /* OP_hr    */
      "HTML",           /* OP_html  */
      0,          /* OP_link  */
      "P",        /* OP_p           */
      "PRE",            /* OP_pre   */
      "FONT",           /* OP_ps    */
      "SUB",            /* OP_sub   */
      "SUP",            /* OP_sup   */
      "TABLE",    /* OP_tab   */
      "TD",       /* OP_tab_data    */
      "TH",       /* OP_tab_head    */
      "TR",       /* OP_tab_row     */
      "TITLE",    /* OP_title */
};

/*
 * emit tag and optionally check stack
 */

static void
tag(Sfio_t* op, int index, register int flags, int att, char* att_str, int att_num)
{
      register int            n;
      register int            m;
      register unsigned char* sp;

      if (index & OP_END)
      {
            index |= OP_LABEL;
            sp = state.tag_top;
            m = 1;
            for (;;)
            {
                  if (sp <= state.tag_stack)
                  {
                        error(2, "tag stack underflow trying to match <%s>", tag_name[OP(index)]);
                        sfprintf(sfstderr, "stack contents:\n");
                        sp = state.tag_top;
                        while (--sp >= state.tag_stack)
                              sfprintf(sfstderr, "\t<%s%s%s>\n", (*sp & OP_END) ? "/" : "", tag_name[OP(*sp)], (*sp & OP_LABEL) ? " label=1" : "");
                        return;
                  }
                  n = *--sp;
                  if (!(n & OP_END))
                  {
                        if (n == OP_pre && OP(index) != OP_pre)
                        {
                              m = 0;
                              break;
                        }
                        n |= OP_END|OP_LABEL;
                        if (tag_name[OP(n)])
                        {
                              flags |= LIST;
                              sfprintf(op, "</%s>", tag_name[OP(n)]);
                        }
                        *sp = n;
                  }
                  if (n == index)
                  {
                        *sp &= ~OP_LABEL;
                        break;
                  }
                  m = 0;
            }
            if (m)
            {
                  if ((flags & (LINE|LIST)) == (LINE|LIST))
                        sfputc(op, '\n');
                  state.tag_top = sp;
            }
      }
      else
      {
            if (flags & STACK)
            {
                  if (state.tag_top >= &state.tag_stack[elementsof(state.tag_stack)])
                        error(3, "tag stack overflow");
                  *state.tag_top++ = tag_name[index] ? index : END(index);
            }
            if (tag_name[index])
            {
                  sfprintf(op, "<%s", tag_name[index]);
                  if (att && ((att & ATT_NUMBER) || att_str))
                  {
                        sfprintf(op, " %s=", opt_attribute[ATT_INDEX(att)]);
                        if (att & ATT_NUMBER)
                        {
                              if (att == ATT_size)
                              {
                                    if (att_num < 0)
                                    {
                                          att_num = -att_num;
                                          sfputc(op, '-');
                                    }
                                    else
                                          sfputc(op, '+');
                                    att_num = (att_num + 5) / 6;
                              }
                              sfprintf(op, "%d", att_num);
                        }
                        else
                              sfprintf(op, "\"%s%s\"", att == ATT_lref ? "#" : "", att_str);
                  }
                  if (ARG_ATTR(flags))
                  {
                        if ((n = ARG_ALIGN(flags)) >= 0)
                              sfprintf(op, " align=%s", opt_align[n]);
                        if (flags & ARG_compact)
                              sfputr(op, " COMPACT", -1);
                        if (flags & ARG_wide)
                              sfputr(op, " width=100%", -1);
                  }
                  if (index == OP_body)
                  {
                        if (state.background)
                              sfprintf(op, " background=\"%s\"", state.background);
                        if (state.logo)
                              sfprintf(op, ">\n<CENTER><IMG src=\"%s\"></CENTER", state.logo);
                  }
                  sfputc(op, '>');
                  if (flags & LINE)
                        sfputc(op, '\n');
            }
      }
}

/*
 * if OP_dl follows OP_ft,OP_ps then do it now
 */

static void
peeklist(Sfio_t* op, register char* s)
{
      for (;;)
      {
            switch (*s++)
            {
            case 0:
                  break;
            case CODE_0:
                  continue;
            case CODE_2:
                  s++;
                  /*FALLTHROUGH*/
            case CODE_1:
                  switch (*s++)
                  {
                  case OP_ft1:
                  case OP_ft2:
                  case OP_ft3:
                  case OP_ft4:
                  case OP_ft5:
                  case OP_ps:
                        continue;
                  case OP_dl:
                        *--s = CODE_0;
                        *--s = CODE_0;
                        tag(op, OP_dl, STACK|ARG_compact, 0, NiL, 0);
                        continue;
                  }
                  break;
            case ' ':
            case '\t':
            case '\n':
                  continue;
            default:
                  break;
            }
            break;
      }
}

#define P() \
      col = br = p = 0

#define DATA() \
      do \
      { \
            if (li) \
            { \
                  if (li == 1) \
                        li = 0; \
                  P(); \
            } \
            else if (p) \
            { \
                  P(); \
                  sfputr(op, "<P>", '\n'); \
            } \
            else if (br) \
            { \
                  P(); \
                  sfputr(op, "<BR>", '\n'); \
            } \
      } while (0)

static void
html(register unsigned char* s, Sfio_t* op)
{
      register int            c;
      register int            br = 0;
      register int            col = 0;
      register int            li = 0;
      register int            p = 0;
      register int            nf = 0;
      register unsigned char* v;
      unsigned char*          t;
      unsigned char*          u;
      int               n;
      int               m;
      int               ta;
      int               ts;
      int               ft = 1;
      int               hot = 0;
      int               label = 0;
      int               ps = DEFAULT_ps;
      int               ps_set = 0;
      Dir_t*                  x;
      Sfio_t*                 subject;
      char              a[2];

      sfputr(op, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">", '\n');
      tag(op, OP_html, STACK|LINE, 0, NiL, 0);
      tag(op, OP_head, STACK|LINE, 0, NiL, 0);
      t = (unsigned char*)strchr(usage, '\n') + 5;
      sfprintf(op, "<META name=\"generator\" content=\"%-.*s", strchr((char*)t, '\n') - (char*)t, t);
      for (x = state.macros; x; x = x->next)
            sfprintf(op, " -m%s", x->name);
      sfputr(op, "\">\n", -1);
      tag(op, OP_title, STACK, 0, NiL, 0);
      if (!(subject = sfstropen()))
            error(ERROR_SYSTEM|3, "out of space [subject]");
      if (state.package)
            sfputr(subject, state.package, ' ');
      if (state.title)
            sfputr(subject, state.title, -1);
      else
      {
            if (state.input)
            {
                  if (t = (unsigned char*)strrchr(state.input, '/'))
                        t++;
                  else
                        t = (unsigned char*)state.input;
                  sfputr(subject, (char*)t, -1);
            }
            if (state.macros)
                  sfprintf(subject, " m%s document", state.macros->name);
      }
      sfputr(op, use(subject), -1);
      tag(op, END(OP_title), STACK|LINE, 0, NiL, 0);
      if (state.author)
            sfprintf(op, "<AUTHOR>%s</AUTHOR>\n", state.author);
      if (!state.head)
      {
            tag(op, END(OP_head), STACK|LINE, 0, NiL, 0);
            tag(op, OP_body, STACK|LINE, 0, NiL, 0);
      }
      for (;;)
      {
            switch (*s++)
            {
            case 0:
                  break;
            case '&':
                  DATA();
                  col++;
                  sfputr(op, "&amp;", -1);
                  continue;
            case '<':
                  DATA();
                  col++;
                  sfputr(op, "&lt;", -1);
                  continue;
            case '>':
                  DATA();
                  col++;
                  sfputr(op, "&gt;", -1);
                  continue;
            case CODE_0:
                  continue;
            case CODE_2:
                  c = *s++;
                  /*FALLTHROUGH*/
            case CODE_1:
                  if (!nf)
                  {
                        n = *s ^ OP_END;
                        for (v = s + 1;;)
                        {
                              switch (*v++)
                              {
                              case '\n':
                              case ' ':
                              case '\t':
                                    continue;
                              case CODE_0:
                                    continue;
                              case CODE_1:
                                    if ((m = *v++) == n)
                                    {
                                          n = 0;
                                          s = v;
                                          break;
                                    }
                                    else if (m != OP_br && m != OP_p)
                                          break;
                                    continue;
                              case CODE_2:
                                    if (*v++ != ' ' || *v++ != OP_cc)
                                          break;
                                    continue;
                              default:
                                    break;
                              }
                              break;
                        }
                        if (!n)
                              continue;
                  }
                  switch (m = *s++)
                  {
                  case END(OP_a):
                        tag(op, m, STACK, 0, NiL, 0);
                        *--s = a[1];
                        *--s = a[0];
                        break;
                  case OP_body:
                        tag(op, m, STACK|LINE, 0, NiL, 0);
                        tag(op, OP_hr, LINE, 0, NiL, 0);
                        col = 0;
                        goto compact;
                  case OP_br:
                        while (*s == ' ')
                              s++;
                        if (!li)
                        {
                              if (nf)
                                    goto compact;
                              else
                                    br++;
                        }
                        break;
                  case OP_cc:
                        DATA();
                        sfputc(op, '&');
                        switch (c)
                        {
                        case '&':
                              sfputr(op, "amp", -1);
                              break;
                        case '<':
                              sfputr(op, "lt", -1);
                              break;
                        case '>':
                              sfputr(op, "gt", -1);
                              break;
                        case ' ':
                              sfputr(op, "nbsp", -1);
                              break;
                        default:
                              sfprintf(op, "#%03d", c);
                              break;
                        }
                        sfputc(op, ';');
                        col++;
                        break;
                  case OP_center:
                        if (col)
                        {
                              col = 0;
                              sfputc(op, '\n');
                        }
                        tag(op, m, STACK|c, 0, NiL, 0);
                        if (!state.head)
                        {
                              tag(op, OP_h2, STACK, 0, NiL, 0);
                              for (;;)
                              {
                                    switch (n = *s++)
                                    {
                                    case 0:
                                    case '\n':
                                    case CODE_1:
                                    case CODE_2:
                                    case CODE_n:
                                          s--;
                                          break;
                                    default:
                                          sfputc(op, n);
                                          continue;
                                    }
                                    break;
                              }
                              tag(op, END(OP_h2), STACK, 0, NiL, 0);
                              tag(op, OP_h4, STACK, 0, NiL, 0);
                        }
                        break;
                  case END(OP_center):
                        col = 0;
                        if (!state.head)
                        {
                              state.head = 1;
                              tag(op, END(OP_h4), STACK, 0, NiL, 0);
                        }
                        tag(op, m, STACK, 0, NiL, 0);
                        break;
                  case LABEL(OP_dd):
                        tag(op, END(OP_a), STACK, 0, NiL, 0);
                        tag(op, END(OP_h3), STACK, 0, NiL, 0);
                        /*FALLTHROUGH*/
                  case OP_dd:
                        li = 1;
                        if (col)
                        {
                              col = 0;
                              sfputc(op, '\n');
                        }
                        tag(op, OP_dd, 0, 0, NiL, 0);
                        break;
                  case OP_div:
                        if (col)
                        {
                              col = 0;
                              sfputc(op, '\n');
                        }
                        tag(op, m, STACK|c, 0, NiL, 0);
                        break;
                  case END(OP_div):
                        col = 0;
                        tag(op, m, STACK, 0, NiL, 0);
                        break;
                  case OP_dl:
                        li = 0;
                        if (col)
                        {
                              col = 0;
                              sfputc(op, '\n');
                        }
                        tag(op, m, STACK|LINE|ARG_compact, 0, NiL, 0);
                        break;
                  case END(OP_dl):
                        v = s;
                        for (;;)
                        {
                              switch (*v++)
                              {
                              case CODE_0:
                                    continue;
                              case CODE_1:
                                    switch (*v++)
                                    {
                                    case OP_dl:
                                          *(v - 2) = *(v - 1) = CODE_0;
                                          v = 0;
                                          break;
                                    default:
                                          continue;
                                    }
                                    break;
                              case CODE_2:
                                    v += 2;
                                    continue;
                              default:
                                    break;
                              }
                              break;
                        }
                        if (v)
                        {
                              li = 0;
                              col = 0;
                              tag(op, m, STACK|LINE, 0, NiL, 0);
                        }
                        break;
                  case LABEL(OP_dt):
                        label = 1;
                        n = 1;
                        /*FALLTHROUGH*/
                  case OP_dt:
                        if (col)
                        {
                              col = 0;
                              sfputc(op, '\n');
                        }
                        if (p)
                        {
                              P();
                              tag(op, OP_p, LINE, 0, NiL, 0);
                        }
                        v = s;
                        for (;;)
                        {
                              switch (*v++)
                              {
                              case CODE_0:
                                    continue;
                              case CODE_2:
                                    v++;
                                    /*FALLTHROUGH*/
                              case CODE_1:
                                    switch (*v++)
                                    {
                                    case OP_dl:
                                          *(v - 2) = *(v - 1) = CODE_0;
                                          tag(op, OP_dl, STACK|LINE|ARG_compact, 0, NiL, 0);
                                          if (!label)
                                                break;
                                          continue;
                                    case OP_dd:
                                          if (label)
                                                *(v - 1) = LABEL(OP_dd);
                                          break;
                                    default:
                                          continue;
                                    }
                                    break;
                              case ' ':
                              case '\t':
                              case '\n':
                                    if (!label)
                                          break;
                                    if (!n)
                                    {
                                          n = 1;
                                          sfputc(state.tmp, ' ');
                                    }
                                    continue;
                              case '"':
                                    if (!label)
                                          break;
                                    continue;
                              default:
                                    if (!label)
                                          break;
                                    n = 0;
                                    sfputc(state.tmp, *(v - 1));
                                    continue;
                              }
                              break;
                        }
                        li = 2;
                        tag(op, OP_dt, LINE, 0, NiL, 0);
                        if (label)
                        {
                              label = 0;
                              n = sfstrtell(state.tmp);
                              v = (unsigned char*)use(state.tmp);
                              while (--n > 0 && (isspace(v[n]) || v[n] == '.'));
                              v[n + 1] = 0;
                              if (isdigit(*v))
                                    tag(op, OP_hr, LINE, 0, NiL, 0);
                              tag(op, OP_h3, STACK, 0, NiL, 0);
                              tag(op, OP_a, STACK, ATT_name, (char*)v, 0);
                        }
                        break;
                  case END(OP_fn):
                        tag(op, m, STACK, 0, NiL, 0);
                        break;
                  case OP_ft1:
                  case OP_ft2:
                  case OP_ft3:
                  case OP_ft4:
                  case OP_ft5:
                        if (hot)
                        {
                              int   ob = 0;
                              int   cb = 0;
                              int   p = 0;
                              int   r = ATT_lref;
                              int   z = 0;

                              v = s;
                              for (;;)
                              {
                                    switch (n = *v++)
                                    {
                                    case 0:
                                          hot = 0;
                                          break;
                                    case CODE_0:
                                          continue;
                                    case CODE_2:
                                          v++;
                                          /*FALLTHROUGH*/
                                    case CODE_1:
                                          n = *v++;
                                          if (!p && n >= OP_ft1 && n <= OP_ft5)
                                                p = -1;
                                          continue;
                                    case '(':
                                    case '[':
                                    case '{':
                                    case '<':
                                          if (n == ob)
                                                p++;
                                          else if (p <= 0)
                                          {
                                                r = ATT_href;
                                                p = 1;
                                                switch (ob = n)
                                                {
                                                case '(':
                                                      cb = ')';
                                                      break;
                                                case '[':
                                                      cb = ']';
                                                      break;
                                                case '{':
                                                      cb = '}';
                                                      break;
                                                case '<':
                                                      cb = '>';
                                                      break;
                                                }
                                          }
                                          z = 0;
                                          sfputc(state.tmp, n);
                                          continue;
                                    case ')':
                                    case ']':
                                    case '}':
                                    case '>':
                                          if (p <= 0)
                                          {
                                                v--;
                                                break;
                                          }
                                          z = 0;
                                          sfputc(state.tmp, n);
                                          if (n == cb && !--p)
                                                break;
                                          continue;
                                    case ' ':
                                    case '\t':
                                    case '\n':
                                          if (!z)
                                          {
                                                z = 1;
                                                sfputc(state.tmp, ' ');
                                          }
                                          continue;
                                    case '"':
                                          continue;
                                    default:
                                          if (p < 0)
                                          {
                                                v--;
                                                break;
                                          }
                                          z = 0;
                                          sfputc(state.tmp, n);
                                          continue;
                                    }
                                    break;
                              }
                              n = sfstrtell(state.tmp);
                              if (!*(t = (unsigned char*)use(state.tmp)))
                                    hot = 0;
                              if (hot)
                              {
                                    hot = 0;
                                    while (--n > 0 && (isspace(t[n]) || t[n] == '.'));
                                    t[n + 1] = 0;
                                    tag(op, OP_a, STACK, r, (char*)t, 0);
                                    a[0] = v[0];
                                    v[0] = CODE_1;
                                    a[1] = v[1];
                                    v[1] = END(OP_a);
                              }
                        }
                        c = m - OP_ft;
                        if (c != ft)
                        {
                              peeklist(op, (char*)s);
                              if (ft != 1)
                                    tag(op, END(OP_ft + ft), STACK, 0, NiL, 0);
                              ft = c;
                              if (ft != 1)
                                    tag(op, OP_ft + ft, STACK, 0, NiL, 0);
                        }
                        break;
                  case OP_h2:
                  case OP_h3:
                  case OP_h4:
                  case END(OP_h2):
                  case END(OP_h3):
                  case END(OP_h4):
                        tag(op, m, STACK, 0, NiL, 0);
                        break;
                  case OP_head:
                  case END(OP_head):
                        col = 0;
                        tag(op, m, STACK|LINE, 0, NiL, 0);
                        goto compact;
                  case OP_hr:
                        col = 0;
                        tag(op, OP_hr, LINE, 0, NiL, 0);
                        goto compact;
                  case OP_p:
                        while (*s == ' ')
                              s++;
                        if (c)
                        {
                              if (col)
                              {
                                    col = 0;
                                    sfputc(op, '\n');
                              }
                              P();
                              tag(op, m, STACK|c, 0, NiL, 0);
                        }
                        else if (!li)
                        {
                              if (nf)
                                    goto compact;
                              else
                                    p++;
                        }
                        break;
                  case END(OP_p):
                        col = 0;
                        tag(op, m, STACK, 0, NiL, 0);
                        break;
                  case OP_pre:
                        if (!nf)
                        {
                              nf = 03;
                              tag(op, m, STACK, 0, NiL, 0);
                        }
                        goto compact;
                  case END(OP_pre):
                        if (nf)
                        {
                              nf = 02;
                              tag(op, m, STACK, 0, NiL, 0);
                        }
                        goto compact;
                  case OP_ps:
                        if (c != ps)
                        {
                              peeklist(op, (char*)s);
                              if (ps_set)
                              {
                                    ps = ps_set;
                                    ps_set = 0;
                                    tag(op, END(OP_ps), STACK, 0, NiL, 0);
                              }
                              if (n = c - ps)
                              {
                                    ps_set = ps;
                                    ps = c;
                                    tag(op, OP_ps, STACK, ATT_size, NiL, n);
                              }
                        }
                        break;
                  case OP_sub:
                  case END(OP_sub):
                  case OP_sup:
                  case END(OP_sup):
                        tag(op, m, STACK, 0, NiL, 0);
                        break;
                  case OP_tab:
                        if (col)
                        {
                              col = 0;
                              sfputc(op, '\n');
                        }
                        tag(op, m, STACK|c, 0, NiL, 0);
                        goto compact;
                  case END(OP_tab):
                        tag(op, m, STACK|LINE, 0, NiL, 0);
                        break;
                  case OP_tab_data:
                  case OP_tab_head:
                  case OP_tab_row:
                        tag(op, m, c, 0, NiL, 0);
                        break;
                  default:
                        if (!(v = (unsigned char*)tag_name[OP(m)]))
                        {
                              sfprintf(state.tmp, "(%d)", OP(m));
                              v = (unsigned char*)use(state.tmp);
                        }
                        error(2, "internal error: <%s%s%s %d> ignored", (m & OP_END) ? "/" : "", v, (m & OP_LABEL) ? " label=" : "", c);
                        break;
                  compact:
                        if (col)
                              sfputc(op, '\n');
                        P();
                        v = s;
                        for (;;)
                        {
                              switch (*s++)
                              {
                              case 0:
                                    break;
                              case '\n':
                                    p++;
                                    col = 0;
                                    v = s;
                                    continue;
                              case ' ':
                              case '\t':
                                    if (!nf)
                                          break;
                                    col = 1;
                                    continue;
                              case CODE_0:
                                    continue;
                              case CODE_1:
                                    switch (*s)
                                    {
                                    case OP_pre:
                                          if (!nf)
                                                break;
                                          /*FALLTHROUGH*/
                                    case OP_br:
                                    case OP_p:
                                          s++;
                                          p++;
                                          col = 0;
                                          v = s;
                                          continue;
                                    case OP_body:
                                          p = 0;
                                          col = 0;
                                          break;
                                    }
                                    break;
                              }
                              if (col)
                              {
                                    col = 0;
                                    s = v;
                              }
                              else
                                    s--;
                              break;
                        }
                        if (nf > 1)
                              nf &= 01;
                        else if (nf && p)
                              sfputc(op, '\n');
                        p = 0;
                        break;
                  }
                  c = 0;
                  continue;
            case CODE_n:
                  n = *s++;
                  if (n & 0200)
                  {
                        n = (n & 0177) << 8;
                        n |= *s++;
                  }
                  c = *s++;
                  v = s;
                  s += n;
                  switch (c)
                  {
                  case OP_comment:
                        if (col)
                        {
                              col = 0;
                              sfputc(op, '\n');
                        }
                        sfprintf(op, "<!--\"%s\"-->\n", v);
                        break;
                  case OP_fn:
                        tag(op, c, STACK, ATT_id, (char*)v, 0);
                        break;
                  case OP_link:
                        DATA();
                        if (u = (unsigned char*)strchr((char*)v, '\t'))
                              *u++ = 0;
                        else
                              u = 0;
                        t = v;
                        while (isdigit(*v))
                              v++;
                        while (isalpha(*v))
                              v++;
                        if (*v == ':' || *v == '/' || *v == '.' || *(v + 1) == '/')
                        {
                              if (!u)
                                    u = v + 1;
                              v = (unsigned char*)"";
                        }
                        else
                        {
                              if (!u)
                                    u = t;
                              v = (unsigned char*)"#";
                        }
                        sfprintf(op, "<A href=\"%s%s\">%s</A>", v, t, u);
                        break;
                  case LABEL(OP_link):
                        DATA();
                        if (u = (unsigned char*)strchr((char*)v, '\t'))
                              *u++ = 0;
                        else
                              u = v;
                        sfprintf(op, "<A name=\"%s\">%s</A>", v, u);
                        break;
                  case OP_ta:
                        strcpy((char*)state.ta, (char*)v);
                        break;
                  case OP_RAW:
                        DATA();
                        if (col)
                        {
                              col = 0;
                              sfputc(op, '\n');
                        }
                        sfputr(op, (char*)v, '\n');
                        break;
                  }
                  continue;
            case ' ':
                  if (nf)
                  {
                        col++;
                        sfputc(op, *(s - 1));
                  }
                  else
                  {
                        while (isspace(*s))
                              s++;
                        if (col >= 70)
                        {
                              col = 0;
                              sfputc(op, '\n');
                        }
                        else if (col > 0)
                        {
                              col++;
                              sfputc(op, ' ');
                        }
                  }
                  continue;
            case '\t':
                  if (nf)
                  {
                        ta = state.ta[ts = 0];
                        while (col >= ta)
                        {
                              if (state.ta[ts+1])
                                    ts++;
                              ta += state.ta[ts];
                        }
                        do
                        {
                              sfputc(op, ' ');
                        } while (++col < ta);
                  }
                  else
                  {
                        col++;
                        sfputc(op, '\t');
                  }
                  continue;
            case '\n':
                  if (nf)
                  {
                        v = s;
                        col = 0;
                        for (;;)
                        {
                              switch (*v++)
                              {
                              case 0:
                                    break;
                              case '\n':
                                    continue;
                              case ' ':
                              case '\t':
                                    continue;
                              case CODE_0:
                                    continue;
                              case CODE_1:
                                    switch (*v++)
                                    {
                                    case OP_br:
                                    case OP_p:
                                    case OP_pre:
                                          continue;
                                    case END(OP_pre):
                                          col = 1;
                                          s = v - 2;
                                          break;
                                    }
                                    break;
                              }
                              break;
                        }
                        if (col)
                        {
                              col = 0;
                              continue;
                        }
                        sfputc(op, '\n');
                  }
                  else
                  {
                        while (isspace(*s))
                              s++;
                        if (col >= 70)
                        {
                              col = 0;
                              sfputc(op, '\n');
                        }
                        else if (col > 0)
                        {
                              col++;
                              sfputc(op, ' ');
                        }
                  }
                  continue;
            case '(':
                  if (hot)
                        hot = 0;
                  else
                        for (x = state.hot; x; x = x->next)
                        {
                              v = s;
                              u = (unsigned char*)x->name;
                              do
                              {
                                    if (!*u)
                                    {
                                          if (isspace(*v))
                                          {
                                                hot = 1;
                                                goto data;
                                          }
                                          break;
                                    }
                              } while (*u++ == *v++);
                        }
                  goto data;
            case ')':
                  hot = 0;
                  goto data;
            case '.':
                  if (!nf && isspace(*s))
                  {
                        while (isspace(*s))
                              s++;
                        col = 0;
                        sfputc(op, '.');
                        sfputc(op, '\n');
                        continue;
                  }
                  /*FALLTHROUGH*/
            default:
            data:
                  DATA();
                  col++;
                  sfputc(op, *(s - 1));
                  continue;
            }
            break;
      }
      if (col)
            sfputc(op, '\n');
      if (state.mailto)
            sfprintf(op, "<P>Send comments and suggestions to <A href=\"mailto:%s?subject=%s\">%s</A>.\n", state.mailto, state.mailto, sfstrbase(subject));
      if (state.author || state.corporation || state.company || state.location)
      {
            t = (unsigned char*)"<P>";
            u = (unsigned char*)"<BR>";
            if (state.author)
            {
                  sfprintf(op, "%s%s\n", t, state.author);
                  t = u;
            }
            if (state.organization)
            {
                  sfprintf(op, "%s%s\n", t, state.organization);
                  t = u;
            }
            if (state.corporation || state.company)
            {
                  sfputr(op, (char*)t, -1);
                  t = u;
                  if (state.corporation)
                        sfputr(op, state.corporation, state.company ? ' ' : '\n');
                  if (state.company)
                        sfputr(op, state.company, '\n');
            }
            if (state.address)
            {
                  sfprintf(op, "%s%s\n", t, state.address);
                  t = u;
            }
            if (state.location)
            {
                  sfprintf(op, "%s%s\n", t, state.location);
                  t = u;
            }
            if (state.phone)
            {
                  sfprintf(op, "%s%s\n", t, state.phone);
                  t = u;
            }
      }
      sfstrclose(subject);
      if (!state.footer)
            sfprintf(op, "<P>%s\n", fmttime("%B %d, %Y", state.date));
      if (state.toolbar && (subject = find(state.toolbar, NiL, 1)))
      {
            sfmove(subject, sfstdout, SF_UNBOUND, -1);
            sfclose(subject);
      }
      tag(op, END(OP_body), STACK, 0, NiL, 0);
      tag(op, END(OP_html), STACK, 0, NiL, 0);
      sfputc(op, '\n');
}

int
main(int argc, char** argv)
{
      register int            n;
      register char*          s;
      char*             v;
      Dir_t*                  x;
      Dir_t*                  lastdir;
      Dir_t*                  lastmac;
      Sfio_t*                 ip;
      Sfio_t*                 op;
      Sfio_t*                 script;

      NoP(argc);
      error_info.id = "troff2html";
      state.dirs = lastdir = &dot;
      init();
      script = 0;
      for (;;)
      {
            switch (optget(argv, usage))
            {
            case 0:
                  break;
            case 'i':
                  if (!(op = sfopen(NiL, opt_info.arg, "r")))
                        error(ERROR_SYSTEM|2, "%s: cannot read", opt_info.arg);
                  else
                  {
                        if (!(s = sfreserve(op, SF_UNBOUND, 0)) || (n = sfvalue(op)) <= 0 || s[n - 1] != '\n')
                              error(1, "%s: invalid info file", opt_info.arg);
                        else
                        {
                              s[n] = 0;
                              stropt(s, options, sizeof(*options), setopt, NiL);
                        }
                        sfclose(op);
                  }
                  continue;
            case 'm':
                  if (!(x = newof(0, Dir_t, 1, 0)))
                        error(ERROR_SYSTEM|3, "out of space [macros]");
                  x->name = opt_info.arg;
                  if (state.macros)
                        lastmac = lastmac->next = x;
                  else
                        state.macros = lastmac = x;
                  continue;
            case 'r':
                  if (*(s = opt_info.arg))
                  {
                        opt_info.num = expression(s + 1, NiL, 0);
                        s[1] = 0;
                        nr(s, opt_info.num, 0, 0);
                  }
                  continue;
            case 's':
                  if (!script && !(script = sfstropen()))
                        error(ERROR_SYSTEM|3, "out of space [script]");
                  sfputr(script, opt_info.arg, '\n');
                  continue;
            case 'v':
                  state.verbose = 1;
                  continue;
            case 'I':
                  if (streq(opt_info.arg, "-"))
                        dot.name[0] = 0;
                  else if (!(x = newof(0, Dir_t, 1, 0)))
                        error(ERROR_SYSTEM|3, "out of space [dir]");
                  else
                  {
                        x->name = opt_info.arg;
                        lastdir = lastdir->next = x;
                  }
                  continue;
            case '?':
                  error(ERROR_USAGE|4, "%s", opt_info.arg);
                  continue;
            case ':':
                  if (opt_info.name[1] != '-')
                  {
                        error(2, "%s", opt_info.arg);
                        continue;
                  }
                  if (!(v = strchr(argv[opt_info.index - 1], '=')))
                        v = opt_info.name + 2;
                  else if (!(v = sfprints("%s=%s", opt_info.name + 2, fmtquote(v + 1, "\"", "\"", strlen(v + 1), FMT_ALWAYS))))
                        error(ERROR_SYSTEM|3, "out of space");
                  stropt(v, options, sizeof(*options), setopt, options);
                  continue;
            }
            break;
      }
      if (!dot.name[0])
            state.dirs = state.dirs->next;
      argv += opt_info.index;
      if (error_info.errors)
            error(ERROR_USAGE|4, "%s", optusage(NiL));
      if (!(op = sfstropen()))
            error(ERROR_SYSTEM|3, "out of space [output]");
      if (script)
      {
            pushin("script", 1, NiL, use(script), NiL);
            process(NiL, NiL, op);
            sfstrclose(script);
      }
      for (x = state.macros; x; x = x->next)
            if (ip = find(x->name, &v, -1))
                  process(v, ip, op);
      if (!(state.input = *argv))
            process(NiL, sfstdin, op);
      else
            while (s = *argv++)
                  if (ip = find(s, &v, 1))
                        process(v, ip, op);
      if (state.out)
      {
            if (state.it.center)
            {
                  state.it.center = 0;
                  code_1(END(OP_center));
            }
            while (state.list > state.list_stack)
            {
                  if (state.list->dl)
                        code_1(END(OP_dl));
                  state.list--;
            }
            code_1(OP_hr);
            trigger(&state.fini);
            process(NiL, NiL, op);
            html((unsigned char*)use(op), sfstdout);
      }
      exit(error_info.errors != 0);
}

Generated by  Doxygen 1.6.0   Back to index