#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <bio.h>

enum
{
       SSIZE = 10,

       Maxnh=  8,              /* highest NH level */
       HH=     4,              /* heading level used for SH and NH */
       Maxmstack=      10,     /* deepest macro/string nesting */
       Narg=   20,             /* max args to a macro */
       Maxsstack=      5,      /* deepest nesting of .so's */
       Nline=  1024,
       Maxget= 10,
       Maxif = 20,
       Maxfsp = 100,

       /* list types */
       Lordered = 1,
       Lunordered,
       Ldef,
       Lother,
};

char *delim = "$$";
char *basename;
char *title;
int eqnmode;

int     quiet;
float   indent; /* from .in */
Biobuf  bout;
int     isup;
int     isdown;
int     debug;

int nh[Maxnh];
int ifwastrue[Maxif];

int list, listnum, example;
int hangingau, hangingdt, hanginghead, hangingcenter;
int indirective, paragraph, sol, titleseen, ignore_nl, weBref;
void dohangingcenter(void);

typedef struct Goobie Goobie;
typedef struct Goobieif Goobieif;
struct Goobie
{
       char *name;
       void (*f)(int, char**);
};

typedef void F(int, char**);
typedef void Fif(char*, char*);

struct Goobieif
{
       char *name;
       Fif *f;
};

/* if, ie */
Fif g_as, g_ds, g_el, g_ie, g_if;
Goobieif gtabif[] = {
       { "as", g_as },
       { "ds", g_ds },
       { "if", g_if },
       { "ie", g_ie },
       { "el", g_el },
       { nil, nil },
       };

/* pseudo ops */
F g_notyet, g_ignore, g_hrule, g_startgif;

/* ms macros */
F g_AU, g_B, g_BI, g_CW, g_I, g_IP, g_LP, g_PP, g_SH, g_NH;
F g_P1, g_P2, g_TL, g_R, g_AB, g_AE, g_EQ, g_TS, g_TE, g_FS, g_FE;
F g_PY, g_IH, g_MH, g_HO, g_BX, g_QS, g_QE, g_RS, g_RE;

/* pictures macro */
F g_BP;

/* real troff */
F g_br, g_ft, g_sp, g_de, g_lf, g_so, g_rm, g_in;
F g_nr, g_ig, g_RT, g_BS, g_BE, g_LB, g_ta;

/* macros to include ML in output */
F g__H, g__T;

Goobie gtab[] =
{
       { "_T", g__T, },
       { "_H", g__H, },
       { "1C", g_ignore, },
       { "2C", g_ignore, },
       { "AB", g_AB, },
       { "AE", g_AE, },
       { "AI", g_ignore, },
       { "AU", g_AU, },
       { "B",  g_B, },
       { "B1", g_hrule, },
       { "B2", g_hrule, },
       { "BI", g_BI, },
       { "BP", g_BP, },
       { "BT", g_ignore, },
       { "BX", g_BX, },
       { "CW", g_CW, },
       { "CT", g_ignore, },
       { "DA", g_ignore, },
       { "DE", g_P2, },
       { "DS", g_P1, },
       { "EG", g_ignore, },
       { "EN", g_ignore, },
       { "EQ", g_startgif, },
       { "FE", g_FE, },
       { "FP", g_ignore, },
       { "FS", g_FS, },
       { "HO", g_HO, },
       { "I",  g_I, },
       { "IH", g_IH, },
       { "IM", g_ignore, },
       { "IP", g_IP, },
       { "KE", g_ignore, },
       { "KF", g_ignore, },
       { "KS", g_ignore, },
       { "LG", g_ignore, },
       { "LP", g_LP, },
       { "LT", g_ignore, },
       { "MF", g_ignore, },
       { "MH", g_MH, },
       { "MR", g_ignore, },
       { "ND", g_ignore, },
       { "NH", g_NH, },
       { "NL", g_ignore, },
       { "P1", g_P1, },
       { "P2", g_P2, },
       { "PE", g_ignore, },
       { "PF", g_ignore, },
       { "PP", g_PP, },
       { "PS", g_startgif, },
       { "PY", g_PY, },
       { "QE", g_QE, },
       { "QP", g_QS, },
       { "QS", g_QS, },
       { "R",          g_R, },
       { "RE", g_RE, },
       { "RP", g_ignore, },
       { "RS", g_RS, },
       { "SG", g_ignore, },
       { "SH", g_SH, },
       { "SM", g_ignore, },
       { "TA", g_ignore, },
       { "TE", g_ignore, },
       { "TH", g_TL, },
       { "TL", g_TL, },
       { "TM", g_ignore, },
       { "TR", g_ignore, },
       { "TS", g_startgif, },
       { "UL", g_I, },
       { "UX", g_ignore, },
       { "WH", g_ignore, },
       { "RT",         g_RT, },

       { "br", g_br, },
       { "ti",         g_br, },
       { "nf", g_P1, },
       { "fi",         g_P2, },
       { "ft",         g_ft, },
       { "sp",         g_sp, },
       { "rm",         g_rm, },
       { "de",         g_de, },
       { "am",         g_de, },
       { "lf",         g_lf, },
       { "so",         g_so, },
       { "ps",         g_ignore },
       { "vs",         g_ignore },
       { "nr",         g_nr },
       { "in",         g_in },
       { "ne",         g_ignore },
       { "ig",         g_ig },
       { "BS",         g_BS },
       { "BE",         g_BE },
       { "LB",         g_LB },
       { nil, nil },
};

typedef struct Entity Entity;
struct Entity
{
       char *name;
       int value;
};

Entity entity[] =
{
       { "&#SPACE;",   L' ', },
       { "&#RS;",              L'\n', },
       { "&#RE;",              L'\r', },
       { "&quot;",             L'"', },
       { "&AElig;",    L'Æ', },
       { "&Aacute;",   L'Á', },
       { "&Acirc;",    L'Â', },
       { "&Agrave;",   L'À', },
       { "&Aring;",    L'Å', },
       { "&Atilde;",   L'Ã', },
       { "&Auml;",     L'Ä', },
       { "&Ccedil;",   L'Ç', },
       { "&ETH;",              L'Ð', },
       { "&Eacute;",   L'É', },
       { "&Ecirc;",    L'Ê', },
       { "&Egrave;",   L'È', },
       { "&Euml;",     L'Ë', },
       { "&Iacute;",   L'Í', },
       { "&Icirc;",            L'Î', },
       { "&Igrave;",   L'Ì', },
       { "&Iuml;",             L'Ï', },
       { "&Ntilde;",   L'Ñ', },
       { "&Oacute;",   L'Ó', },
       { "&Ocirc;",    L'Ô', },
       { "&Ograve;",   L'Ò', },
       { "&Oslash;",   L'Ø', },
       { "&Otilde;",   L'Õ', },
       { "&Ouml;",     L'Ö', },
       { "&THORN;",    L'Þ', },
       { "&Uacute;",   L'Ú', },
       { "&Ucirc;",    L'Û', },
       { "&Ugrave;",   L'Ù', },
       { "&Uuml;",     L'Ü', },
       { "&Yacute;",   L'Ý', },
       { "&aacute;",   L'á', },
       { "&acirc;",    L'â', },
       { "&aelig;",    L'æ', },
       { "&agrave;",   L'à', },
       { "&amp;",              L'&', },
       { "&aring;",    L'å', },
       { "&atilde;",   L'ã', },
       { "&auml;",     L'ä', },
       { "&ccedil;",   L'ç', },
       { "&eacute;",   L'é', },
       { "&ecirc;",    L'ê', },
       { "&egrave;",   L'è', },
       { "&eth;",              L'ð', },
       { "&euml;",     L'ë', },
       { "&gt;",               L'>', },
       { "&iacute;",   L'í', },
       { "&icirc;",            L'î', },
       { "&igrave;",   L'ì', },
       { "&iuml;",             L'ï', },
       { "&lt;",               L'<', },
       { "&ntilde;",   L'ñ', },
       { "&oacute;",   L'ó', },
       { "&ocirc;",    L'ô', },
       { "&ograve;",   L'ò', },
       { "&oslash;",   L'ø', },
       { "&otilde;",   L'õ', },
       { "&ouml;",     L'ö', },
       { "&szlig;",            L'ß', },
       { "&thorn;",    L'þ', },
       { "&uacute;",   L'ú', },
       { "&ucirc;",    L'û', },
       { "&ugrave;",   L'ù', },
       { "&uuml;",     L'ü', },
       { "&yacute;",   L'ý', },
       { "&yuml;",     L'ÿ', },
       { "&#161;",     L'¡', },
       { "&#162;",     L'¢', },
       { "&#163;",     L'£', },
       { "&#164;",     L'¤', },
       { "&#165;",     L'¥', },
       { "&#166;",     L'¦', },
       { "&#167;",     L'§', },
       { "&#168;",     L'¨', },
       { "&#169;",     L'©', },
       { "&#170;",     L'ª', },
       { "&#171;",     L'«', },
       { "&#172;",     L'¬', },
       { "&#173;",     L'­', },
       { "&#174;",     L'®', },
       { "&#175;",     L'¯', },
       { "&#176;",     L'°', },
       { "&#177;",     L'±', },
       { "&#178;",     L'²', },
       { "&#179;",     L'³', },
       { "&#180;",     L'´', },
       { "&#181;",     L'µ', },
       { "&#182;",     L'¶', },
       { "&#183;",     L'·', },
       { "&#184;",     L'¸', },
       { "&#185;",     L'¹', },
       { "&#186;",     L'º', },
       { "&#187;",     L'»', },
       { "&#188;",     L'¼', },
       { "&#189;",     L'½', },
       { "&#190;",     L'¾', },
       { "&#191;",     L'¿', },

       { "*",                  L'•', },
       { "&#164;",     L'□', },
       { "&#186;",     L'◊', },
       { "(tm)",               L'™', },
       {"&#913;",              L'Α',},
       {"&#914;",              L'Β',},
       {"&#915;",              L'Γ',},
       {"&#916;",              L'Δ',},
       {"&#917;",              L'Ε',},
       {"&#918;",              L'Ζ',},
       {"&#919;",              L'Η',},
       {"&#920;",              L'Θ',},
       {"&#921;",              L'Ι',},
       {"&#922;",              L'Κ',},
       {"&#923;",              L'Λ',},
       {"&#924;",              L'Μ',},
       {"&#925;",              L'Ν',},
       {"&#926;",              L'Ξ',},
       {"&#927;",              L'Ο',},
       {"&#928;",              L'Π',},
       {"&#929;",              L'Ρ',},
       {"&#930;",              L'΢',},
       {"&#931;",              L'Σ',},
       {"&#932;",              L'Τ',},
       {"&#933;",              L'Υ',},
       {"&#934;",              L'Φ',},
       {"&#935;",              L'Χ',},
       {"&#936;",              L'Ψ',},
       {"&#937;",              L'Ω',},
       {"&#945;",              L'α',},
       {"&#946;",              L'β',},
       {"&#947;",              L'γ',},
       {"&#948;",              L'δ',},
       {"&#949;",              L'ε',},
       {"&#950;",              L'ζ',},
       {"&#951;",              L'η',},
       {"&#952;",              L'θ',},
       {"&#953;",              L'ι',},
       {"&#954;",              L'κ',},
       {"&#955;",              L'λ',},
       {"&#956;",              L'μ',},
       {"&#957;",              L'ν',},
       {"&#958;",              L'ξ',},
       {"&#959;",              L'ο',},
       {"&#960;",              L'π',},
       {"&#961;",              L'ρ',},
       {"&#962;",              L'ς',},
       {"&#963;",              L'σ',},
       {"&#964;",              L'τ',},
       {"&#965;",              L'υ',},
       {"&#966;",              L'φ',},
       {"&#967;",              L'χ',},
       {"&#968;",              L'ψ',},
       {"&#969;",              L'ω',},

       { "<-",         L'←', },
       { "^",                  L'↑', },
       { "->",         L'→', },
       { "v",                  L'↓', },
       { "!=",         L'≠', },
       { "<=",         L'≤', },
       { "...",                L'⋯', },
       {"&isin;",              L'∈', },

       {"&#8211;",     L'–', },
       {"&#8212;",     L'—', },

       { "CYRILLIC XYZZY",     L'й', },
       { "CYRILLIC XYZZY",     L'ъ', },
       { "CYRILLIC Y",         L'ь', },
       { "CYRILLIC YA",        L'я', },
       { "CYRILLIC YA",        L'ё', },
       { "&#191;",             L'ℱ', },

       { nil, 0 },
};

typedef struct Troffspec Troffspec;
struct Troffspec
{
       char *name;
       char *value;
};

Troffspec tspec[] =
{
       { "A*", "&Aring;", },
       { "o\"", "&ouml;", },
       { "ff", "ff", },
       { "fi", "fi", },
       { "fl", "fl", },
       { "Fi", "ffi", },
       { "ru", "_", },
       { "em", "&#173;", },
       { "14", "&#188;", },
       { "12", "&#189;", },
       { "co", "&#169;", },
       { "de", "&#176;", },
       { "dg", "&#161;", },
       { "fm", "&#180;", },
       { "rg", "&#174;", },
       { "bu", "*", },
       { "sq", "&#164;", },
       { "hy", "-", },
       { "pl", "+", },
       { "mi", "-", },
       { "mu", "&#215;", },
       { "di", "&#247;", },
       { "eq", "=", },
       { "==", "==", },
       { ">=", ">=", },
       { "<=", "<=", },
       { "!=", "!=", },
       { "+-", "&#177;", },
       { "no", "&#172;", },
       { "sl", "/", },
       { "ap", "&", },
       { "~=", "~=", },
       { "pt", "oc", },
       { "gr", "GRAD", },
       { "->", "->", },
       { "<-", "<-", },
       { "ua", "^", },
       { "da", "v", },
       { "is", "Integral", },
       { "pd", "DIV", },
       { "if", "oo", },
       { "sr", "-/", },
       { "sb", "(~", },
       { "sp", "~)", },
       { "cu", "U", },
       { "ca", "(^)", },
       { "ib", "(=", },
       { "ip", "=)", },
       { "mo", "C", },
       { "es", "&Oslash;", },
       { "aa", "&#180;", },
       { "ga", "`", },
       { "ci", "O", },
       { "L1", "DEATHSTAR", },
       { "sc", "&#167;", },
       { "dd", "++", },
       { "lh", "<=", },
       { "rh", "=>", },
       { "lt", "(", },
       { "rt", ")", },
       { "lc", "|", },
       { "rc", "|", },
       { "lb", "(", },
       { "rb", ")", },
       { "lf", "|", },
       { "rf", "|", },
       { "lk", "|", },
       { "rk", "|", },
       { "bv", "|", },
       { "ts", "s", },
       { "br", "|", },
       { "or", "|", },
       { "ul", "_", },
       { "rn", " ", },
       { "**", "*", },
       { "tm", "&#153", },
       { nil, nil, },
};

typedef struct Font Font;
struct Font
{
       char    *start;
       char    *end;
};
Font bfont = { "<B>", "</B>" };
Font ifont = { "<I>", "</I>" };
Font bifont = { "<B><I>", "</I></B>" };
Font cwfont = { "<TT>", "</TT>" };
Font *fstack[Maxfsp];
int fsp = -1;

typedef struct String String;
struct String
{
       String *next;
       char *name;
       char *val;
};
String *numregs, *strings;
char *strstack[Maxmstack];
char *mustfree[Maxmstack];
int strsp = -1;
int elsetop = -1;

typedef struct Mstack Mstack;
struct Mstack
{
       char *ptr;
       char *argv[Narg+1];
};
String *macros;
Mstack mstack[Maxmstack];
int msp = -1;

typedef struct Srcstack Srcstack;
struct Srcstack
{
       char    filename[256];
       int     fd;
       int     lno;
       int     rlno;
       Biobuf  in;
};
Srcstack sstack[Maxsstack];
Srcstack *ssp = &sstack[-1];

char token[128];

void    closel(void);
void    closefont(void);

void*
emalloc(uint n)
{
       void *p;

       p = mallocz(n, 1);
       if(p == nil){
               fprint(2, "ms2html: malloc failed: %r\n");
               exits("malloc");
       }
       return p;
}


/* define a string variable */
void
dsnr(char *name, char *val, String **l)
{
       String *s;

       for(s = *l; s != nil; s = *l){
               if(strcmp(s->name, name) == 0)
                       break;
               l = &s->next;
       }
       if(s == nil){
               s = emalloc(sizeof(String));
               *l = s;
               s->name = strdup(name);
       } else
               free(s->val);
       s->val = strdup(val);
}

void
ds(char *name, char *val)
{
       dsnr(name, val, &strings);
}

/* look up a defined string */
char*
getds(char *name)
{
       String *s;

       for(s = strings; s != nil; s = s->next)
               if(strcmp(name, s->name) == 0)
                       break;
       if(s != nil)
               return s->val;
       return "";
}

char *
getnr(char *name)
{
       String *s;

       for(s = numregs; s != nil; s = s->next)
               if(strcmp(name, s->name) == 0)
                       break;
       if(s != nil)
               return s->val;
       return "0";
}

void
pushstr(char *p)
{
       if(p == nil)
               return;
       if(strsp >= Maxmstack - 1)
               return;
       strstack[++strsp] = p;
}


/* lookup a defined macro */
char*
getmacro(char *name)
{
       String *s;

       for(s = macros; s != nil; s = s->next)
               if(strcmp(name, s->name) == 0)
                       return s->val;
       return nil;
}

enum
{
       Dstring,
       Macro,
       Input,
};
int lastsrc;

void
pushsrc(char *name)
{
       Dir *d;
       int fd;

       if(ssp == &sstack[Maxsstack-1]){
               fprint(2, "ms2html: .so's too deep\n");
               return;
       }
       d = nil;
       if(name == nil){
               d = dirfstat(0);
               if(d == nil){
                       fprint(2, "ms2html: can't stat %s: %r\n", name);
                       return;
               }
               name = d->name;
               fd = 0;
       } else {
               fd = open(name, OREAD);
               if(fd < 0){
                       fprint(2, "ms2html: can't open %s: %r\n", name);
                       return;
               }
       }
       ssp++;
       ssp->fd = fd;
       Binit(&ssp->in, fd, OREAD);
       snprint(ssp->filename, sizeof(ssp->filename), "%s", name);
       ssp->lno = ssp->rlno = 1;
       free(d);
}

/* get next logical byte.  from stdin or a defined string */
int
getrune(void)
{
       int i;
       Rune r;
       int c;
       Mstack *m;

       while(strsp >= 0){
               i = chartorune(&r, strstack[strsp]);
               if(r != 0){
                       strstack[strsp] += i;
                       lastsrc = Dstring;
                       return r;
               }
               if (mustfree[strsp]) {
                       free(mustfree[strsp]);
                       mustfree[strsp] = nil;
               }
               strsp--;
       }
       while(msp >= 0){
               m = &mstack[msp];
               i = chartorune(&r, m->ptr);
               if(r != 0){
                       m->ptr += i;
                       lastsrc = Macro;
                       return r;
               }
               for(i = 0; m->argv[i] != nil; i++)
                       free(m->argv[i]);
               msp--;
       }

       lastsrc = Input;
       for(;;) {
               if(ssp < sstack)
                       return -1;
               c = Bgetrune(&ssp->in);
               if(c >= 0){
                       r = c;
                       break;
               }
               close(ssp->fd);
               ssp--;
       }

       return r;
}

void
ungetrune(void)
{
       switch(lastsrc){
       case Dstring:
               if(strsp >= 0)
                       strstack[strsp]--;
               break;
       case Macro:
               if(msp >= 0)
                       mstack[msp].ptr--;
               break;
       case Input:
               if(ssp >= sstack)
                       Bungetrune(&ssp->in);
               break;
       }
}

int vert;

char*
changefont(Font *f)
{
       token[0] = 0;
       if(fsp == Maxfsp)
               return token;
       if(fsp >= 0 && fstack[fsp])
               strcpy(token, fstack[fsp]->end);
       if(f != nil)
               strcat(token, f->start);
       fstack[++fsp] = f;
       return token;
}

char*
changebackfont(void)
{
       token[0] = 0;
       if(fsp >= 0){
               if(fstack[fsp])
                       strcpy(token, fstack[fsp]->end);
               fsp--;
       }
       if(fsp >= 0 && fstack[fsp])
               strcat(token, fstack[fsp]->start);
       return token;
}

char*
changesize(int amount)
{
       static int curamount;
       static char buf[200];
       int i;

       buf[0] = 0;
       if (curamount >= 0)
               for (i = 0; i < curamount; i++)
                       strcat(buf, "</big>");
       else
               for (i = 0; i < -curamount; i++)
                       strcat(buf, "</small>");
       curamount = 0;
       if (amount >= 0)
               for (i = 0; i < amount; i++)
                       strcat(buf, "<big>");
       else
               for (i = 0; i < -amount; i++)
                       strcat(buf, "<small>");
       curamount = amount;
       return buf;
}

/* get next logical character.  expand it with escapes */
char*
getnext(void)
{
       int r;
       Entity *e;
       Troffspec *t;
       Rune R;
       char str[4];
       static char buf[8];

       r = getrune();
       if(r < 0)
               return nil;
       if(r > 128 || r == '<' || r == '>'){
               for(e = entity; e->name; e++)
                       if(e->value == r)
                               return e->name;
               sprint(buf, "&#%d;", r);
               return buf;
       }

       if (r == delim[eqnmode]){
               if (eqnmode == 0){
                       eqnmode = 1;
                       return changefont(&ifont);
               }
               eqnmode = 0;
               return changebackfont();
       }
       switch(r){
       case '\\':
               r = getrune();
               if(r < 0)
                       return nil;
               switch(r){
               case ' ':
                       return " ";

               /* chars to ignore */
               case '&':
               case '|':
               case '%':
                       return "";

               /* small space in troff, nothing in nroff */
               case '^':
                       return getnext();

               /* ignore arg */
               case 'k':
                       getrune();
                       return getnext();

               /* comment */
               case '"':
                       while(getrune() != '\n')
                               ;
                       return "\n";
               /* ignore line */
               case '!':
                       while(getrune() != '\n')
                               ;
                       ungetrune();
                       return getnext();

               /* defined strings */
               case '*':
                       r = getrune();
                       if(r == '('){
                               str[0] = getrune();
                               str[1] = getrune();
                               str[2] = 0;
                       } else {
                               str[0] = r;
                               str[1] = 0;
                       }
                       pushstr(getds(str));
                       return getnext();

               /* macro args */
               case '$':
                       r = getrune();
                       if(r < '1' || r > '9'){
                               token[0] = '\\';
                               token[1] = '$';
                               token[2] = r;
                               token[3] = 0;
                               return token;
                       }
                       r -= '0';
                       if(msp >= 0)
                               pushstr(mstack[msp].argv[r]);
                       return getnext();

               /* special chars */
               case '(':
                       token[0] = getrune();
                       token[1] = getrune();
                       token[2] = 0;
                       for(t = tspec; t->name; t++)
                               if(strcmp(token, t->name) == 0)
                                       return t->value;
                       return "&#191;";

               /* ignore immediately following newline */
               case 'c':
                       r = getrune();
                       if (r == '\n') {
                               sol = ignore_nl = 1;
                               if (indirective)
                                       break;
                               }
                       else
                               ungetrune();
                       return getnext();

               /* escape backslash */
               case 'e':
                       return "\\";

               /* font change */
               case 'f':
                       r = getrune();
                       switch(r){
                       case '(':
                               str[0] = getrune();
                               str[1] = getrune();
                               str[2] = 0;
                               token[0] = 0;
                               if(strcmp("BI", str) == 0)
                                       return changefont(&bifont);
                               else if(strcmp("CW", str) == 0)
                                       return changefont(&cwfont);
                               else
                                       return changefont(nil);
                       case '3':
                       case 'B':
                               return changefont(&bfont);
                       case '2':
                       case 'I':
                               return changefont(&ifont);
                       case '4':
                               return changefont(&bifont);
                       case '5':
                               return changefont(&cwfont);
                       case 'P':
                               return changebackfont();
                       case 'R':
                       default:
                               return changefont(nil);
                       }

               /* number register */
               case 'n':
                       r = getrune();
                       if (r == '(') /*)*/ {
                               r = getrune();
                               if (r < 0)
                                       return nil;
                               str[0] = r;
                               r = getrune();
                               if (r < 0)
                                       return nil;
                               str[1] = r;
                               str[2] = 0;
                               }
                       else {
                               str[0] = r;
                               str[1] = 0;
                               }
                       pushstr(getnr(str));
                       return getnext();

               /* font size */
               case 's':
                       r = getrune();
                       switch(r){
                       case '0':
                               return changesize(0);
                       case '-':
                               r = getrune();
                               if (!isdigit(r))
                                       return getnext();
                               return changesize(-(r - '0'));
                       case '+':
                               r = getrune();
                               if (!isdigit(r))
                                       return getnext();
                               return changesize(r - '0');
                       }
                       return getnext();
               /* vertical movement */
               case 'v':
                       r = getrune();
                       if(r != '\''){
                               ungetrune();
                               return getnext();
                       }
                       r = getrune();
                       if(r != '-')
                               vert--;
                       else
                               vert++;
                       while(r != '\'' && r != '\n')
                               r = getrune();
                       if(r != '\'')
                               ungetrune();

                       if(vert > 0)
                               return "^";
                       return getnext();


               /* horizontal line */
               case 'l':
                       r = getrune();
                       if(r != '\''){
                               ungetrune();
                               return "<HR>";
                       }
                       while(getrune() != '\'')
                               ;
                       return "<HR>";

               /* character height and slant */
               case 'S':
               case 'H':
                       r = getrune();
                       if(r != '\''){
                               ungetrune();
                               return "<HR>";
                       }
                       while(getrune() != '\'')
                               ;
                       return getnext();

               /* digit-width space */
               case '0':
                       return " ";

               /*for .if, .ie, .el */
               case '{':
                       return "\\{"; /*}*/
               case '}':
                       return "";
               /* up and down */
               case 'u':
                       if (isdown) {
                               isdown = 0;
                               return "</sub>";
                       }
                       isup = 1;
                       return "<sup>";
               case 'd':
                       if (isup) {
                               isup = 0;
                               return "</sup>";
                       }
                       isdown = 1;
                       return "<sub>";
               }
               break;
       case '&':
               if(msp >= 0 || strsp >= 0)
                       return "&";
               return "&amp;";
       case '<':
               if(msp >= 0 || strsp >= 0)
                       return "<";
               return "&#60;";
       case '>':
               if(msp >= 0 || strsp >= 0)
                       return ">";
               return "&#62;";
       }
       if (r < Runeself) {
               token[0] = r;
               token[1] = 0;
               }
       else {
               R = r;
               token[runetochar(token,&R)] = 0;
       }
       return token;
}

/* if arg0 is set, read up to (and expand) to the next whitespace, else to the end of line */
char*
copyline(char *p, char *e, int arg0)
{
       int c;
       Rune r;
       char *p1;

       while((c = getrune()) == ' ' || c == '\t')
               ;
       for(indirective = 1; p < e; c = getrune()) {
               if (c < 0)
                       goto done;
               switch(c) {
               case '\\':
                       break;
               case '\n':
                       if (arg0)
                               ungetrune();
                       goto done;
               case ' ':
               case '\t':
                       if (arg0)
                               goto done;
               default:
                       r = c;
                       p += runetochar(p,&r);
                       continue;
               }
               ungetrune();
               p1 = getnext();
               if (p1 == nil)
                       goto done;
               if (*p1 == '\n') {
                       if (arg0)
                               ungetrune();
                       break;
               }
               while((*p = *p1++) && p < e)
                       p++;
       }
done:
       indirective = 0;
       *p++ = 0;
       return p;
}

char*
copyarg(char *p, char *e, int *nullarg)
{
       int c, quoted, last;
       Rune r;

       *nullarg = 0;
       quoted = 0;
       do{
               c = getrune();
       } while(c == ' ' || c == '\t');

       if(c == '"'){
               quoted = 1;
               *nullarg = 1;
               c = getrune();
       }

       if(c == '\n')
               goto done;

       last = 0;
       for(; p < e; c = getrune()) {
               if (c < 0)
                       break;
               switch(c) {
               case '\n':
                       ungetrune();
                       goto done;
               case '\\':
                       r = c;
                       p += runetochar(p,&r);
                       if(last == '\\')
                               r = 0;
                       break;
               case ' ':
               case '\t':
                       if(!quoted && last != '\\')
                               goto done;
                       r = c;
                       p += runetochar(p,&r);
                       break;
               case '"':
                       if(quoted && last != '\\')
                               goto done;
                       r = c;
                       p += runetochar(p,&r);
                       break;
               default:
                       r = c;
                       p += runetochar(p,&r);
                       break;
               }
               last = r;
       }
done:
       *p++ = 0;
       return p;

}

int
parseargs(char *p, char *e, char **argv)
{
       int argc;
       char *np;
       int nullarg;

       indirective = 1;
       *p++ = 0;
       for(argc = 1; argc < Narg; argc++){
               np = copyarg(p, e, &nullarg);
               if(nullarg==0 && np == p+1)
                       break;
               argv[argc] = p;
               p = np;
       }
       argv[argc] = nil;
       indirective = 0;


       return argc;
}

void
dodirective(void)
{
       char *p, *e;
       Goobie *g;
       Goobieif *gif;
       char line[Nline], *line1;
       int i, argc;
       char *argv[Narg];
       Mstack *m;

       /* read line, translate special bytes */
       e = line + sizeof(line) - UTFmax - 1;
       line1 = copyline(line, e, 1);
       if (!line[0])
               return;
       argv[0] = line;

       /* first look through user defined macros */
       p = getmacro(argv[0]);
       if(p != nil){
               if(msp == Maxmstack-1){
                       fprint(2, "ms2html: macro stack overflow\n");
                       return;
               }
               argc = parseargs(line1, e, argv);
               m = &mstack[++msp];
               m->ptr = p;
               memset(m->argv, 0, sizeof(m->argv));
               for(i = 0; i < argc; i++)
                       m->argv[i] = strdup(argv[i]);
               return;
       }

       /* check for .if or .ie */
       for(gif = gtabif; gif->name; gif++)
               if(strcmp(gif->name, argv[0]) == 0){
                       (*gif->f)(line1, e);
                       return;
               }

       argc = parseargs(line1, e, argv);

       /* try standard ms macros */
       for(g = gtab; g->name; g++)
               if(strcmp(g->name, argv[0]) == 0){
                       (*g->f)(argc, argv);
                       return;
               }

       if(debug)
               fprint(2, "stdin %d(%s:%d): unknown directive %s\n",
                       ssp->lno, ssp->filename, ssp->rlno, line);
}

void
printarg(char *a)
{
       char *e, *p;

       e = a + strlen(a);
       pushstr(a);
       while(strsp >= 0 && strstack[strsp] >= a && strstack[strsp] < e){
               p = getnext();
               if(p == nil)
                       return;
               Bprint(&bout, "%s", p);
       }
}

void
printargs(int argc, char **argv)
{
       argc--;
       argv++;
       while(--argc > 0){
               printarg(*argv++);
               Bprint(&bout, " ");
       }
       if(argc == 0)
               printarg(*argv);
}

void
dohangingdt(void)
{
       switch(hangingdt){
       case 3:
               hangingdt--;
               break;
       case 2:
               Bprint(&bout, "<dd>");
               hangingdt = 0;
               break;
       }

}

void
dohangingau(void)
{
       if(hangingau == 0)
               return;
       Bprint(&bout, "</I></DL>\n");
       hangingau = 0;
}

void
dohanginghead(void)
{
       if(hanginghead == 0)
               return;
       Bprint(&bout, "</H%d>\n", hanginghead);
       hanginghead = 0;
}

/*
*  convert a man page to html and output
*/
void
doconvert(void)
{
       char c, *p;

       pushsrc(nil);

       sol = 1;
       Bprint(&bout, "<html>\n");
       Bflush(&bout);
       for(;;){
               p = getnext();
               if(p == nil)
                       break;
               c = *p;
               if(c == '.' && sol){
                       dodirective();
                       dohangingdt();
                       ssp->lno++;
                       ssp->rlno++;
                       sol = 1;
               } else if(c == '\n'){
                       if (ignore_nl)
                               ignore_nl = 0;
                       else {
                               if(hangingau)
                                       Bprint(&bout, "<br>\n");
                               else
                                       Bprint(&bout, "%s", p);
                               dohangingdt();
                               }
                       ssp->lno++;
                       ssp->rlno++;
                       sol = 1;
               } else{
                       Bprint(&bout, "%s", p);
                       ignore_nl = sol = 0;
               }
       }
       dohanginghead();
       dohangingdt();
       closel();
       if(fsp >= 0 && fstack[fsp])
               Bprint(&bout, "%s", fstack[fsp]->end);
       Bprint(&bout, "<br>&#32;<br>\n");
       Bprint(&bout, "</body></html>\n");
}

static void
usage(void)
{
       sysfatal("usage: ms2html [-q] [-b basename] [-d '$$'] [-t title]");
}

void
main(int argc, char **argv)
{
       quiet = 1;
       ARGBEGIN {
       case 't':
               title = EARGF(usage());
               break;
       case 'b':
               basename = EARGF(usage());
               break;
       case 'q':
               quiet = 0;
               break;
       case 'd':
               delim = EARGF(usage());
               break;
       case '?':
       default:
               usage();
       } ARGEND;

       Binit(&bout, 1, OWRITE);

       ds("R", "&#174;");

       doconvert();
       exits(nil);
}

void
g_notyet(int, char **argv)
{
       fprint(2, "ms2html: .%s not yet supported\n", argv[0]);
}

void
g_ignore(int, char **argv)
{
       if(quiet)
               return;
       fprint(2, "ms2html: line %d: ignoring .%s\n", ssp->lno, argv[0]);
}

void
g_PP(int, char**)
{
       dohanginghead();
       closel();
       closefont();
       Bprint(&bout, "<P>\n");
       paragraph = 1;
}

void
g_LP(int, char**)
{
       dohanginghead();
       closel();
       closefont();
       Bprint(&bout, "<br>&#32;<br>\n");
}

/* close a list */
void
closel(void)
{
       g_P2(1, nil);
       dohangingau();
       if(paragraph){
               Bprint(&bout, "</P>\n");
               paragraph = 0;
       }
       switch(list){
       case Lordered:
               Bprint(&bout, "</ol>\n");
               break;
       case Lunordered:
               Bprint(&bout, "</ul>\n");
               break;
       case Lother:
       case Ldef:
               Bprint(&bout, "</dl>\n");
               break;
       }
       list = 0;

}


void
g_IP(int argc, char **argv)
{
       switch(list){
       default:
               closel();
               if(argc > 1){
                       if(strcmp(argv[1], "1") == 0){
                               list = Lordered;
                               listnum = 1;
                               Bprint(&bout, "<OL>\n");
                       } else if(strcmp(argv[1], "\\(bu") == 0){
                               list = Lunordered;
                               Bprint(&bout, "<UL>\n");
                       } else {
                               list = Lother;
                               Bprint(&bout, "<DL COMPACT>\n");
                       }
               } else {
                       list = Lother;
                       Bprint(&bout, "<DL>\n");
               }
               break;
       case Lother:
       case Lordered:
       case Lunordered:
               break;
       }

       switch(list){
       case Lother:
               Bprint(&bout, "<DT>");
               if(argc > 1)
                       printarg(argv[1]);
               else
                       Bprint(&bout, "<DT>&#32;");
               Bprint(&bout, "<DD>\n");
               break;
       case Lordered:
       case Lunordered:
               Bprint(&bout, "<LI>\n");
               break;
       }
}

/*
*  .5i is one <DL><DT><DD>
*/
void
g_in(int argc, char **argv)
{
       float   f;
       int     delta, x;
       char    *p;

       f = indent/0.5;
       delta = f;
       if(argc <= 1){
               indent = 0.0;
       } else {
               f = strtod(argv[1], &p);
               switch(*p){
               case 'i':
                       break;
               case 'c':
                       f = f / 2.54;
                       break;
               case 'P':
                       f = f / 6;
                       break;
               default:
               case 'u':
               case 'm':
                       f = f * (12 / 72);
                       break;
               case 'n':
                       f = f * (6 / 72);
                       break;
               case 'p':
                       f = f / 72.0;
                       break;
               }
               switch(argv[1][0]){
               case '+':
               case '-':
                       indent += f;
                       break;
               default:
                       indent = f;
                       break;
               }
       }
       if(indent < 0.0)
               indent = 0.0;
       f = (indent/0.5);
       x = f;
       delta = x - delta;
       while(delta < 0){
               Bprint(&bout, "</DL>\n");
               delta++;
       }
       while(delta > 0){
               Bprint(&bout, "<DL><DT><DD>\n");
               delta--;
       }
}

void
g_HP(int, char**)
{
       switch(list){
       default:
               closel();
               list = Ldef;
               hangingdt = 1;
               Bprint(&bout, "<DL><DT>\n");
               break;
       case Ldef:
               if(hangingdt)
                       Bprint(&bout, "<DD>");
               Bprint(&bout, "<DT>");
               hangingdt = 1;
               break;
       }
}

void
g_SH(int, char**)
{
       dohanginghead();
       dohangingcenter();
       closel();
       closefont();
       Bprint(&bout, "<H%d>", HH);
       hanginghead = HH;
}

void
g_NH(int argc, char **argv)
{
       int i, level;

       closel();
       closefont();

       dohangingcenter();
       if(argc == 1)
               level = 0;
       else {
               level = atoi(argv[1])-1;
               if(level < 0 || level >= Maxnh)
                       level = Maxnh - 1;
       }
       nh[level]++;

       Bprint(&bout, "<H%d>", HH);
       hanginghead = HH;

       Bprint(&bout, "%d", nh[0]);
       for(i = 1; i <= level; i++)
               Bprint(&bout, ".%d", nh[i]);
       Bprint(&bout, " ");

       for(i = level+1; i < Maxnh; i++)
               nh[i] = 0;
}

void
g_TL(int, char**)
{
       char *p, *np;
       char name[128];

       closefont();

       if(!titleseen){
               if(!title){
                       /* get base part of filename */
                       p = strrchr(ssp->filename, '/');
                       if(p == nil)
                               p = ssp->filename;
                       else
                               p++;
                       strncpy(name, p, sizeof(name));
                       name[sizeof(name)-1] = 0;

                       /* dump any extensions */
                       np = strchr(name, '.');
                       if(np)
                               *np = 0;
                       title = p;
               }
               Bprint(&bout, "<title>\n");
               Bprint(&bout, "%s\n", title);
               Bprint(&bout, "</title>\n");
               Bprint(&bout, "<body BGCOLOR=\"#FFFFFF\" TEXT=\"#000000\" LINK=\"#0000FF\" VLINK=\"#330088\" ALINK=\"#FF0044\">\n");
               titleseen = 1;
       }

       Bprint(&bout, "<center>");
       hangingcenter = 1;
       Bprint(&bout, "<H%d>", 1);
       hanginghead = 1;
}

void
dohangingcenter(void)
{
       if(hangingcenter){
               Bprint(&bout, "</center>");
               hangingcenter = 1;
       }
}

void
g_AU(int, char**)
{
       closel();
       dohanginghead();
       Bprint(&bout, "<DL><DD><I>");
       hangingau = 1;
}

void
pushfont(Font *f)
{
       if(fsp == Maxfsp)
               return;
       if(fsp >= 0 && fstack[fsp])
               Bprint(&bout, "%s", fstack[fsp]->end);
       if(f != nil)
               Bprint(&bout, "%s", f->start);
       fstack[++fsp] = f;
}

void
popfont(void)
{
       if(fsp >= 0){
               if(fstack[fsp])
                       Bprint(&bout, "%s", fstack[fsp]->end);
               fsp--;
       }
}

/*
*  for 3 args print arg3 \fxarg1\fP arg2
*  for 2 args print arg1 \fxarg2\fP
*  for 1 args print \fxarg1\fP
*/
void
font(Font *f, int argc, char **argv)
{
       if(argc == 1){
               pushfont(nil);
               return;
       }
       if(argc > 3)
               printarg(argv[3]);
       pushfont(f);
       printarg(argv[1]);
       popfont();
       if(argc > 2)
               printarg(argv[2]);
       Bprint(&bout, "\n");
}

void
closefont(void)
{
       if(fsp >= 0 && fstack[fsp])
               Bprint(&bout, "%s", fstack[fsp]->end);
       fsp = -1;
}

void
g_B(int argc, char **argv)
{
       font(&bfont, argc, argv);
}

void
g_R(int argc, char **argv)
{
       font(nil, argc, argv);
}

void
g_BI(int argc, char **argv)
{
       font(&bifont, argc, argv);
}

void
g_CW(int argc, char **argv)
{
       font(&cwfont, argc, argv);
}

char*
lower(char *p)
{
       char *x;

       for(x = p; *x; x++)
               if(*x >= 'A' && *x <= 'Z')
                       *x -= 'A' - 'a';
       return p;
}

void
g_I(int argc, char **argv)
{
       int anchor;
       char *p;

       anchor = 0;
       if(argc > 2){
               p = argv[2];
               if(p[0] == '(')
               if(p[1] >= '0' && p[1] <= '9')
               if(p[2] == ')'){
                       anchor = 1;
                       Bprint(&bout, "<A href=\"/magic/man2html/%c/%s\">",
                               p[1], lower(argv[1]));
               }
       }
       font(&ifont, argc, argv);
       if(anchor)
               Bprint(&bout, "</A>");
}

void
g_br(int, char**)
{
       if(hangingdt){
               Bprint(&bout, "<dd>");
               hangingdt = 0;
       }else
               Bprint(&bout, "<br>\n");
}

void
g_P1(int, char**)
{
       if(example == 0){
               example = 1;
               Bprint(&bout, "<DL><DT><DD><TT><PRE>\n");
       }
}

void
g_P2(int, char**)
{
       if(example){
               example = 0;
               Bprint(&bout, "</PRE></TT></DL>\n");
       }
}

void
g_SM(int, char **argv)
{
       Bprint(&bout, "%s", argv[1]);
}

void
g_ft(int argc, char **argv)
{
       if(argc < 2){
               pushfont(nil);
               return;
       }

       switch(argv[1][0]){
       case '3':
       case 'B':
               pushfont(&bfont);
               break;
       case '2':
       case 'I':
               pushfont(&ifont);
               break;
       case '4':
               pushfont(&bifont);
               break;
       case '5':
               pushfont(&cwfont);
               break;
       case 'P':
               popfont();
               break;
       case 'R':
       default:
               pushfont(nil);
               break;
       }
}

void
g_sp(int argc, char **argv)
{
       int n;

       n = 1;
       if(argc > 1){
               n = atoi(argv[1]);
               if(n < 1)
                       n = 1;
               if(argv[1][strlen(argv[1])-1] == 'i')
                       n *= 4;
       }
       if(n > 5){
               Bprint(&bout, "<br>&#32;<br>\n");
               Bprint(&bout, "<HR>\n");
               Bprint(&bout, "<br>&#32;<br>\n");
       } else
               for(; n > 0; n--)
                       Bprint(&bout, "<br>&#32;<br>\n");
}

void
rm_loop(char *name, String **l)
{
       String *s;
       for(s = *l; s != nil; s = *l){
               if(strcmp(name, s->name) == 0){
                       *l = s->next;
                       free(s->name);
                       free(s->val);
                       free(s);
                       break;
                       }
               l = &s->next;
               }
       }

void
g_rm(int argc, char **argv)
{
       Goobie *g;
       char *name;
       int i;

       for(i = 1; i < argc; i++) {
               name = argv[i];
               rm_loop(name, &strings);
               rm_loop(name, &macros);
               for(g = gtab; g->name; g++)
                       if (strcmp(g->name, name) == 0) {
                               g->f = g_ignore;
                               break;
                               }
               }
       }

void
g_AB(int, char**)
{
       closel();
       dohangingcenter();
       Bprint(&bout, "<center><H4>ABSTRACT</H4></center><DL><DD>\n");
}

void
g_AE(int, char**)
{
       Bprint(&bout, "</DL>\n");
}

void
g_FS(int, char **)
{
       char *argv[3];

       argv[0] = "IP";
       argv[1] = nil;
       argv[2] = nil;
       g_IP(1, argv);
       Bprint(&bout, "NOTE:<I> ");
}

void
g_FE(int, char **)
{
       Bprint(&bout, "</I><DT>&#32;<DD>");
       closel();
       Bprint(&bout, "<br>\n");
}

void
g_de(int argc, char **argv)
{
       int r;
       char *p, *cp;
       String *m;
       int len;

       if(argc < 2)
               return;

       m = nil;
       len = 0;
       if(strcmp(argv[0], "am") == 0){
               for(m = macros; m != nil; m = m->next)
                       if(strcmp(argv[1], m->name) == 0){
                               len = strlen(m->val);
                               break;
                       }

               if(m == nil){
                       /* nothing to append to */
                       for(;;){
                               p = Brdline(&ssp->in, '\n');
                               if(p == nil)
                                       break;
                               p[Blinelen(&ssp->in)-1] = 0;
                               if(strcmp(p, "..") == 0)
                                       break;
                       }
                       return;
               }
       }

       if(m == nil){
               m = emalloc(sizeof(*m));
               m->next = macros;
               macros = m;
               m->name = strdup(argv[1]);
               m->val = nil;
               len = 0;
       }

       /* read up to a .. removing double backslashes */
       for(;;){
               p = Brdline(&ssp->in, '\n');
               if(p == nil)
                       break;
               p[Blinelen(&ssp->in)-1] = 0;
               if(strcmp(p, "..") == 0)
                       break;
               m->val = realloc(m->val, len + Blinelen(&ssp->in)+1);
               cp = m->val + len;
               while(*p){
                       r = *p++;
                       if(r == '\\' && *p == '\\')
                               p++;
                       *cp++ = r;
               }
               *cp++ = '\n';
               len = cp - m->val;
               *cp = 0;
       }
}

void
g_hrule(int, char**)
{
       Bprint(&bout, "<HR>\n");
}

void
g_BX(int argc, char **argv)
{
       Bprint(&bout, "<HR>\n");
       printargs(argc, argv);
       Bprint(&bout, "<HR>\n");
}

void
g_IH(int, char**)
{
       Bprint(&bout, "Bell Laboratories, Naperville, Illinois, 60540\n");
}

void
g_MH(int, char**)
{
       Bprint(&bout, "Bell Laboratories, Murray Hill, NJ, 07974\n");
}

void
g_PY(int, char**)
{
       Bprint(&bout, "Bell Laboratories, Piscataway, NJ, 08854\n");
}

void
g_HO(int, char**)
{
       Bprint(&bout, "Bell Laboratories, Holmdel, NJ, 07733\n");
}

void
g_QS(int, char**)
{
       Bprint(&bout, "<BLOCKQUOTE>\n");
}

void
g_QE(int, char**)
{
       Bprint(&bout, "</BLOCKQUOTE>\n");
}

void
g_RS(int, char**)
{
       Bprint(&bout, "<DL><DD>\n");
}

void
g_RE(int, char**)
{
       Bprint(&bout, "</DL>\n");
}

int gif;

void
g_startgif(int, char **argv)
{
       int fd;
       int pfd[2];
       char *e, *p;
       char name[32];
       Dir *d;

       if(strcmp(argv[0], "EQ") == 0)
               e = ".EN";
       else if(strcmp(argv[0], "TS") == 0)
               e = ".TE";
       else if(strcmp(argv[0], "PS") == 0)
               e = ".PE";
       else
               return;

       if(basename)
               p = basename;
       else{
               p = strrchr(sstack[0].filename, '/');
               if(p != nil)
                       p++;
               else
                       p = sstack[0].filename;
       }
       snprint(name, sizeof(name), "%s.%d.gif", p, gif++);
       fd = create(name, OWRITE, 0664);
       if(fd < 0){
               fprint(2, "ms2html: can't create %s: %r\n", name);
               return;
       }

       if(pipe(pfd) < 0){
               fprint(2, "ms2html: can't create pipe: %r\n");
               close(fd);
               return;
       }
       switch(rfork(RFFDG|RFPROC)){
       case -1:
               fprint(2, "ms2html: can't fork: %r\n");
               close(fd);
               return;
       case 0:
               dup(fd, 1);
               close(fd);
               dup(pfd[0], 0);
               close(pfd[0]);
               close(pfd[1]);
               execl("/bin/troff2gif", "troff2gif", nil);
               fprint(2, "ms2html: couldn't exec troff2gif: %r\n");
               _exits(nil);
       default:
               close(fd);
               close(pfd[0]);
               fprint(pfd[1], ".ll 7i\n");
       /*      fprint(pfd[1], ".EQ\ndelim %s\n.EN\n", delim); */
       /*      fprint(pfd[1], ".%s\n", argv[0]); */
               for(;;){
                       p = Brdline(&ssp->in, '\n');
                       if(p == nil)
                               break;
                       ssp->lno++;
                       ssp->rlno++;
                       if(write(pfd[1], p, Blinelen(&ssp->in)) < 0)
                               break;
                       if(strncmp(p, e, 3) == 0)
                               break;
               }
               close(pfd[1]);
               waitpid();
               d = dirstat(name);
               if(d == nil)
                       break;
               if(d->length == 0){
                       remove(name);
                       free(d);
                       break;
               }
               free(d);
               fprint(2, "ms2html: created auxiliary file %s\n", name);
               Bprint(&bout, "<br><img src=\"%s\"><br>\n", name);
               break;
       }
}

void
g_lf(int argc, char **argv)
{
       if(argc > 2)
               snprint(ssp->filename, sizeof(ssp->filename), argv[2]);
       if(argc > 1)
               ssp->rlno = atoi(argv[1]);
}

void
g_so(int argc, char **argv)
{
       ssp->lno++;
       ssp->rlno++;
       if(argc > 1)
               pushsrc(argv[1]);
}


void
g_BP(int argc, char **argv)
{
       int fd;
       char *p, *ext;
       char name[32];
       Dir *d;

       if(argc < 2)
               return;

       p = strrchr(argv[1], '/');
       if(p != nil)
               p++;
       else
               p = argv[1];


       ext = strrchr(p, '.');
       if(ext){
               if(strcmp(ext, ".jpeg") == 0
               || strcmp(ext, ".gif") == 0){
                       Bprint(&bout, "<br><img src=\"%s\"><br>\n", argv[1]);
                       return;
               }
       }


       snprint(name, sizeof(name), "%s.%d%d.gif", p, getpid(), gif++);
       fd = create(name, OWRITE, 0664);
       if(fd < 0){
               fprint(2, "ms2html: can't create %s: %r\n", name);
               return;
       }

       switch(rfork(RFFDG|RFPROC)){
       case -1:
               fprint(2, "ms2html: can't fork: %r\n");
               close(fd);
               return;
       case 0:
               dup(fd, 1);
               close(fd);
               execl("/bin/ps2gif", "ps2gif", argv[1], nil);
               fprint(2, "ms2html: couldn't exec ps2gif: %r\n");
               _exits(nil);
       default:
               close(fd);
               waitpid();
               d = dirstat(name);
               if(d == nil)
                       break;
               if(d->length == 0){
                       remove(name);
                       free(d);
                       break;
               }
               free(d);
               fprint(2, "ms2html: created auxiliary file %s\n", name);
               Bprint(&bout, "<br><img src=\"%s\"><br>\n", name);
               break;
       }
}

/* insert straight HTML into output */
void
g__H(int argc, char **argv)
{
       int i;

       for(i = 1; i < argc; i++)
               Bprint(&bout, "%s ", argv[i]);
       Bprint(&bout, "\n");
}

/* HTML page title */
void
g__T(int argc, char **argv)
{
       if(titleseen)
               return;

       Bprint(&bout, "<title>\n");
       printargs(argc, argv);
       Bprint(&bout, "</title></head><body>\n");
       titleseen = 1;
}

void
g_nr(int argc, char **argv)
{
       char *val;

       if (argc > 1) {
               if (argc == 2)
                       val = "0";
               else
                       val = argv[2];
               dsnr(argv[1], val, &numregs);
       }
}

void
zerodivide(void)
{
       fprint(2, "stdin %d(%s:%d): division by 0\n",
               ssp->lno, ssp->filename, ssp->rlno);
}

int
numval(char **pline, int recur)
{
       char *p;
       int neg, x, y;

       x = neg = 0;
       p = *pline;
       while(*p == '-') {
               neg = 1 - neg;
               p++;
       }
       if (*p == '(') {
               p++;
               x = numval(&p, 1);
               if (*p != ')')
                       goto done;
               p++;
       }
       else while(*p >= '0' && *p <= '9')
               x = 10*x + *p++ - '0';
       if (neg)
               x = -x;
       if (recur)
           for(;;) {
               switch(*p++) {
               case '+':
                       x += numval(&p, 0);
                       continue;
               case '-':
                       x -= numval(&p, 0);
                       continue;
               case '*':
                       x *= numval(&p, 0);
                       continue;
               case '/':
                       y = numval(&p, 0);
                       if (y == 0) {
                               zerodivide();
                               x = 0;
                               goto done;
                       }
                       x /= y;
                       continue;
               case '<':
                       if (*p == '=') {
                               p++;
                               x = x <= numval(&p, 0);
                               continue;
                       }
                       x = x < numval(&p, 0);
                       continue;
               case '>':
                       if (*p == '=') {
                               p++;
                               x = x >= numval(&p, 0);
                               continue;
                       }
                       x = x > numval(&p, 0);
                       continue;
               case '=':
                       if (*p == '=')
                               p++;
                       x = x == numval(&p, 0);
                       continue;
               case '&':
                       x &= numval(&p, 0);
                       continue;
               case ':':
                       x |= numval(&p, 0);
                       continue;
               case '%':
                       y = numval(&p, 0);
                       if (!y) {
                               zerodivide();
                               goto done;
                       }
                       x %= y;
                       continue;
               }
               --p;
               break;
       }
done:
       *pline = p;
       return x;
}

int
iftest(char *p, char **bp)
{
       char *p1;
       int c, neg, rv;

       rv = neg = 0;
       if (*p == '!') {
               neg = 1;
               p++;
       }
       c = *p;
       if (c >= '0' && c <= '9' || c == '+' || c == '-' || c == '('/*)*/) {
               if (numval(&p,1) >= 1)
                       rv = 1;
               goto done;
       }
       switch(c) {
       case 't':
       case 'o':
               rv = 1;
       case 'n':
       case 'e':
               p++;
               goto done;
       }
       for(p1 = ++p; *p != c; p++)
               if (!*p)
                       goto done;
       for(p++;;) {
               if (*p != *p1++) {
                       while(*p && *p++ != c);
                       goto done;
               }
               if (*p++ == c)
                       break;
       }
       rv = 1;
done:
       if (neg)
               rv = 1 - rv;
       while(*p == ' ' || *p == '\t')
               p++;
       *bp = p;
       return rv;
}

void
scanline(char *p, char *e, int wantnl)
{
       int c;
       Rune r;

       while((c = getrune()) == ' ' || c == '\t') ;
       while(p < e) {
               if (c < 0)
                       break;
               if (c < Runeself) {
                       if (c == '\n') {
                               if (wantnl)
                                       *p++ = c;
                               break;
                       }
                       *p++ = c;
               }
               else {
                       r = c;
                       p += runetochar(p, &r);
               }
               c = getrune();
       }
       *p = 0;
}

void
pushbody(char *line)
{
       char *b;

       if (line[0] == '\\' && line[1] == '{' /*}*/ )
               line += 2;
       if (strsp < Maxmstack - 1) {
               pushstr(b = strdup(line));
               mustfree[strsp] = b;
       }
}

void
skipbody(char *line)
{
       int c, n;

       if (line[0] != '\\' || line[1] != '{' /*}*/ )
               return;
       for(n = 1;;) {
               while((c = getrune()) != '\\')
                       if (c < 0)
                               return;
               c = getrune();
               if (c == '{')
                       n++;
               else if ((c == '}' && (c = getrune()) == '\n' && !--n)
                       || c < 0)
                       return;
       }
}

int
ifstart(char *line, char *e, char **bp)
{
       int it;
       char *b;

       b = copyline(line, e, 1);
       ungetrune();
       b[-1] = getrune();
       scanline(b, e, 1);
       it = iftest(line, bp);
       return it;
}

void
g_ie(char *line, char *e)
{
       char *b;

       if (elsetop >= Maxif-1) {
               fprint(2, "ms2html: .ie's too deep\n");
               return;
       }
       if (ifwastrue[++elsetop] = ifstart(line, e, &b))
               pushbody(b);
       else
               skipbody(b);
}

void
g_if(char *line, char *e)
{
       char *b;

       if (ifstart(line, e, &b))
               pushbody(b);
       else
               skipbody(b);
}

void
g_el(char *line, char *e)
{
       if (elsetop < 0)
               return;
       scanline(line, e, 1);
       if (ifwastrue[elsetop--])
               skipbody(line);
       else
               pushbody(line);
}

void
g_ig(int argc, char **argv)
{
       char *p, *s;

       s = "..";
       if (argc > 1)
               s = argv[1];
       for(;;) {
               p = Brdline(&ssp->in, '\n');
               if(p == nil)
                       break;
               p[Blinelen(&ssp->in)-1] = 0;
               if(strcmp(p, s) == 0)
                       break;
       }
}

void
g_ds(char *line, char *e)
{
       char *b;

       b = copyline(line, e, 1);
       if (b > line) {
               copyline(b, e, 0);
               if (*b == '"')
                       b++;
               ds(line, b);
       }
}

void
g_as(char *line, char *e)
{
       String *s;
       char *b;

       b = copyline(line, e, 1);
       if (b == line)
               return;
       copyline(b, e, 0);
       if (*b == '"')
               b++;
       for(s = strings; s != nil; s = s->next)
               if(strcmp(line, s->name) == 0)
                       break;

       if(s == nil){
               ds(line, b);
               return;
       }

       s->val = realloc(s->val, strlen(s->val) + strlen(b) + 1);
       strcat(s->val, b);
}

void
g_BS(int argc, char **argv)
{
       int i;

       if (argc > 1 && !weBref) {
               Bprint(&bout, "<a href=\"%s\"", argv[1]);
               for(i = 2; i < argc; i++)
                       Bprint(&bout, " %s", argv[i]);
               Bprint(&bout, ">");
               weBref = 1;
       }
}

void
g_BE(int, char**)
{
       if (weBref) {
               Bprint(&bout, "</a>");
               weBref = 0;
       }
}

void
g_LB(int argc, char **argv)
{
       if (argc > 1) {
               if (weBref)
                       g_BE(0,nil);
               Bprint(&bout, "<a name=\"%s\"></a>", argv[1]);
       }
}

void
g_RT(int, char**)
{
       g_BE(0,nil);
       dohanginghead();
       closel();
       closefont();
}