Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#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;
        Tm *t;

        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, "<A href=http://www.lucent.com/copyright.html>\n");
        t = localtime(time(nil));
        Bprint(&bout, "Copyright</A> &#169; %d Alcatel-Lucent Inc.  All rights reserved.\n",
                        t->year+1900);
        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)
{
        dohanginghead();
        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();

        dohanginghead();
        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();
}