Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#include "a.h"

enum
{
        MAXREQ = 100,
        MAXRAW = 40,
        MAXESC = 60,
        MAXLINE = 1024,
        MAXIF = 20,
        MAXARG = 10,
};

typedef struct Esc Esc;
typedef struct Req Req;
typedef struct Raw Raw;

/* escape sequence handler, like for \c */
struct Esc
{
        Rune r;
        int (*f)(void);
        int mode;
};

/* raw request handler, like for .ie */
struct Raw
{
        Rune *name;
        void (*f)(Rune*);
};

/* regular request handler, like for .ft */
struct Req
{
        int argc;
        Rune *name;
        void (*f)(int, Rune**);
};

int             dot = '.';
int             tick = '\'';
int             backslash = '\\';

int             inputmode;
Req             req[MAXREQ];
int             nreq;
Raw             raw[MAXRAW];
int             nraw;
Esc             esc[MAXESC];
int             nesc;
int             iftrue[MAXIF];
int             niftrue;

int isoutput;
int linepos;


void
addraw(Rune *name, void (*f)(Rune*))
{
        Raw *r;
        
        if(nraw >= nelem(raw)){
                fprint(2, "too many raw requets\n");
                return;
        }
        r = &raw[nraw++];
        r->name = erunestrdup(name);
        r->f = f;
}

void
delraw(Rune *name)
{
        int i;
        
        for(i=0; i<nraw; i++){
                if(runestrcmp(raw[i].name, name) == 0){
                        if(i != --nraw){
                                free(raw[i].name);
                                raw[i] = raw[nraw];
                        }
                        return;
                }
        }
}

void
renraw(Rune *from, Rune *to)
{
        int i;
        
        delraw(to);
        for(i=0; i<nraw; i++)
                if(runestrcmp(raw[i].name, from) == 0){
                        free(raw[i].name);
                        raw[i].name = erunestrdup(to);
                        return;
                }
}


void
addreq(Rune *s, void (*f)(int, Rune**), int argc)
{
        Req *r;

        if(nreq >= nelem(req)){
                fprint(2, "too many requests\n");
                return;
        }
        r = &req[nreq++];
        r->name = erunestrdup(s);
        r->f = f;
        r->argc = argc;
}

void
delreq(Rune *name)
{
        int i;

        for(i=0; i<nreq; i++){
                if(runestrcmp(req[i].name, name) == 0){
                        if(i != --nreq){
                                free(req[i].name);
                                req[i] = req[nreq];
                        }
                        return;
                }
        }
}

void
renreq(Rune *from, Rune *to)
{
        int i;
        
        delreq(to);
        for(i=0; i<nreq; i++)
                if(runestrcmp(req[i].name, from) == 0){
                        free(req[i].name);
                        req[i].name = erunestrdup(to);
                        return;
                }
}

void
addesc(Rune r, int (*f)(void), int mode)
{
        Esc *e;
        
        if(nesc >= nelem(esc)){
                fprint(2, "too many escapes\n");
                return;
        }
        e = &esc[nesc++];
        e->r = r;
        e->f = f;
        e->mode = mode;
}

/*
 * Get the next logical character in the input stream.
 */
int
getnext(void)
{
        int i, r;

next:
        r = getrune();
        if(r < 0)
                return -1;
        if(r == Uformatted){
                br();
                assert(!isoutput);
                while((r = getrune()) >= 0 && r != Uunformatted){
                        if(r == Uformatted)
                                continue;
                        outrune(r);
                }
                goto next;
        }
        if(r == Uunformatted)
                goto next;
        if(r == backslash){
                r = getrune();
                if(r < 0)
                        return -1;
                for(i=0; i<nesc; i++){
                        if(r == esc[i].r && (inputmode&esc[i].mode)==inputmode){
                                if(esc[i].f == e_warn)
                                        warn("ignoring %C%C", backslash, r);
                                r = esc[i].f();
                                if(r <= 0)
                                        goto next;
                                return r;
                        }
                }
                if(inputmode&(ArgMode|CopyMode)){
                        ungetrune(r);
                        r = backslash;
                }
        }
        return r;
}

void
ungetnext(Rune r)
{
        /*
         * really we want to undo the getrunes that led us here,
         * since the call after ungetnext might be getrune!
         */
        ungetrune(r);
}

int
_readx(Rune *p, int n, int nmode, int line)
{
        int c, omode;
        Rune *e;

        while((c = getrune()) == ' ' || c == '\t')
                ;
        ungetrune(c);
        omode = inputmode;
        inputmode = nmode;
        e = p+n-1;
        for(c=getnext(); p<e; c=getnext()){
                if(c < 0)
                        break;
                if(!line && (c == ' ' || c == '\t'))
                        break;
                if(c == '\n'){
                        if(!line)
                                ungetnext(c);
                        break;
                }
                *p++ = c;
        }
        inputmode = omode;
        *p = 0;
        if(c < 0)
                return -1;
        return 0;
}

/*
 * Get the next argument from the current line.
 */
Rune*
copyarg(void)
{
        static Rune buf[MaxLine];
        int c;
        Rune *r;
        
        if(_readx(buf, sizeof buf, ArgMode, 0) < 0)
                return nil;
        r = runestrstr(buf, L("\\\""));
        if(r){
                *r = 0;
                while((c = getrune()) >= 0 && c != '\n')
                        ;
                ungetrune('\n');
        }
        r = erunestrdup(buf);   
        return r;
}

/*
 * Read the current line in given mode.  Newline not kept.
 * Uses different buffer from copyarg!
 */
Rune*
readline(int m)
{
        static Rune buf[MaxLine];
        Rune *r;

        if(_readx(buf, sizeof buf, m, 1) < 0)
                return nil;
        r = erunestrdup(buf);
        return r;
}

/*
 * Given the argument line (already read in copy+arg mode),
 * parse into arguments.  Note that \" has been left in place
 * during copy+arg mode parsing, so comments still need to be stripped.
 */
int
parseargs(Rune *p, Rune **argv)
{
        int argc;
        Rune *w;

        for(argc=0; argc<MAXARG; argc++){
                while(*p == ' ' || *p == '\t')
                        p++;
                if(*p == 0)
                        break;
                argv[argc] = p;
                if(*p == '"'){
                        /* quoted argument */
                        if(*(p+1) == '"'){
                                /* empty argument */
                                *p = 0;
                                p += 2;
                        }else{
                                /* parse quoted string */
                                w = p++;
                                for(; *p; p++){
                                        if(*p == '"' && *(p+1) == '"')
                                                *w++ = '"';
                                        else if(*p == '"'){
                                                p++;
                                                break;
                                        }else
                                                *w++ = *p;
                                }
                                *w = 0;
                        }       
                }else{
                        /* unquoted argument - need to watch out for \" comment */
                        for(; *p; p++){
                                if(*p == ' ' || *p == '\t'){
                                        *p++ = 0;
                                        break;
                                }
                                if(*p == '\\' && *(p+1) == '"'){
                                        *p = 0;
                                        if(p != argv[argc])
                                                argc++;
                                        return argc;
                                }
                        }
                }
        }
        return argc;
}

/*
 * Process a dot line.  The dot has been read.
 */
void
dotline(int dot)
{
        int argc, i;
        Rune *a, *argv[1+MAXARG];

        /*
         * Read request/macro name
         */
        a = copyarg();
        if(a == nil || a[0] == 0){
                free(a);
                getrune();      /* \n */
                return;
        }
        argv[0] = a;
        /*
         * Check for .if, .ie, and others with special parsing.
         */
        for(i=0; i<nraw; i++){
                if(runestrcmp(raw[i].name, a) == 0){
                        raw[i].f(raw[i].name);
                        free(a);
                        return;
                }       
        }

        /*
         * Read rest of line in copy mode, invoke regular request.
         */
        a = readline(ArgMode);
        if(a == nil){
                free(argv[0]);
                return;
        }
        argc = 1+parseargs(a, argv+1);
        for(i=0; i<nreq; i++){
                if(runestrcmp(req[i].name, argv[0]) == 0){
                        if(req[i].argc != -1){
                                if(argc < 1+req[i].argc){
                                        warn("not enough arguments for %C%S", dot, req[i].name);
                                        free(argv[0]);
                                        free(a);
                                        return;
                                }
                                if(argc > 1+req[i].argc)
                                        warn("too many arguments for %C%S", dot, req[i].name);
                        }
                        req[i].f(argc, argv);
                        free(argv[0]);
                        free(a);
                        return;
                }
        }

        /*
         * Invoke user-defined macros.
         */
        runmacro(dot, argc, argv);
        free(argv[0]);
        free(a);
}

/*
 * newlines are magical in various ways.
 */
int bol;
void
newline(void)
{
        int n;

        if(bol)
                sp(eval(L("1v")));
        bol = 1;
        if((n=getnr(L(".ce"))) > 0){
                nr(L(".ce"), n-1);
                br();
        }
        if(getnr(L(".fi")) == 0)
                br();
        outrune('\n');
}

void
startoutput(void)
{
        char *align;
        double ps, vs, lm, rm, ti;
        Rune buf[200];

        if(isoutput)
                return;
        isoutput = 1;

        if(getnr(L(".paragraph")) == 0)
                return;

        nr(L(".ns"), 0);
        isoutput = 1;
        ps = getnr(L(".s"));
        if(ps <= 1)
                ps = 10;
        ps /= 72.0;
        USED(ps);

        vs = getnr(L(".v"))*getnr(L(".ls")) * 1.0/UPI;
        vs /= (10.0/72.0);      /* ps */
        if(vs == 0)
                vs = 1.2;

        lm = (getnr(L(".o"))+getnr(L(".i"))) * 1.0/UPI;
        ti = getnr(L(".ti")) * 1.0/UPI;
        nr(L(".ti"), 0);

        rm = 8.0 - getnr(L(".l"))*1.0/UPI - getnr(L(".o"))*1.0/UPI;
        if(rm < 0)
                rm = 0;
        switch(getnr(L(".j"))){
        default:
        case 0:
                align = "left";
                break;
        case 1:
                align = "justify";
                break;
        case 3:
                align = "center";
                break;
        case 5:
                align = "right";
                break;
        }
        if(getnr(L(".ce")))
                align = "center";
        if(!getnr(L(".margin")))
                runesnprint(buf, nelem(buf), "<p style=\"line-height: %.1fem; text-indent: %.2fin; margin-top: 0; margin-bottom: 0; text-align: %s;\">\n",
                        vs, ti, align);
        else
                runesnprint(buf, nelem(buf), "<p style=\"line-height: %.1fem; margin-left: %.2fin; text-indent: %.2fin; margin-right: %.2fin; margin-top: 0; margin-bottom: 0; text-align: %s;\">\n",
                        vs, lm, ti, rm, align);
        outhtml(buf);
}
void
br(void)
{
        if(!isoutput)
                return;
        isoutput = 0;

        nr(L(".dv"), 0);
        dv(0);
        hideihtml();
        if(getnr(L(".paragraph")))
                outhtml(L("</p>"));
}

void
r_margin(int argc, Rune **argv)
{
        USED(argc);

        nr(L(".margin"), eval(argv[1]));
}

int inrequest;
void
runinput(void)
{
        int c;
        
        bol = 1;
        for(;;){
                c = getnext();
                if(c < 0)
                        break;
                if((c == dot || c == tick) && bol){
                        inrequest = 1;
                        dotline(c);
                        bol = 1;
                        inrequest = 0;
                }else if(c == '\n'){
                        newline();
                        itrap();
                        linepos = 0;
                }else{
                        outtrap();
                        startoutput();
                        showihtml();
                        if(c == '\t'){
                                /* XXX do better */
                                outrune(' ');
                                while(++linepos%4)
                                        outrune(' ');
                        }else{
                                outrune(c);
                                linepos++;
                        }
                        bol = 0;
                }
        }
}

void
run(void)
{
        t1init();
        t2init();
        t3init();
        t4init();
        t5init();
        t6init();
        t7init();
        t8init();
        /* t9init(); t9.c */
        t10init();
        t11init();
        /* t12init(); t12.c */
        t13init();
        t14init();
        t15init();
        t16init();
        t17init();
        t18init();
        t19init();
        t20init();
        htmlinit();
        hideihtml();
        
        addreq(L("margin"), r_margin, 1);
        nr(L(".margin"), 1);
        nr(L(".paragraph"), 1);

        runinput();
        while(popinput())
                ;
        dot = '.';
        if(verbose)
                fprint(2, "eof\n");
        runmacro1(L("eof"));
        closehtml();
}

void
out(Rune *s)
{
        if(s == nil)
                return;
        for(; *s; s++)
                outrune(*s);
}

void (*outcb)(Rune);

void
inroman(Rune r)
{
        int f;
        
        f = getnr(L(".f"));
        nr(L(".f"), 1);
        runmacro1(L("font"));
        outrune(r);
        nr(L(".f"), f);
        runmacro1(L("font"));
}

void
Brune(Rune r)
{
        if(r == '&')
                Bprint(&bout, "&amp;");
        else if(r == '<')
                Bprint(&bout, "&lt;");
        else if(r == '>')
                Bprint(&bout, "&gt;");
        else if(r < Runeself || utf8)
                Bprint(&bout, "%C", r);
        else
                Bprint(&bout, "%S", rune2html(r));
}

void
outhtml(Rune *s)
{
        Rune r;
        
        for(; *s; s++){
                switch(r = *s){
                case '<':
                        r = Ult;
                        break;
                case '>':
                        r = Ugt;
                        break;
                case '&':
                        r = Uamp;
                        break;
                case ' ':
                        r = Uspace;
                        break;
                }
                outrune(r);
        }
}

void
outrune(Rune r)
{
        switch(r){
        case ' ':
                if(getnr(L(".fi")) == 0)
                        r = Unbsp;
                break;
        case Uformatted:
        case Uunformatted:
                abort();
        }
        if(outcb){
                if(r == ' ')
                        r = Uspace;
                outcb(r);
                return;
        }
        /* writing to bout */
        switch(r){
        case Uempty:
                return;
        case Upl:
                inroman('+');
                return;
        case Ueq:
                inroman('=');
                return;
        case Umi:
                inroman(0x2212);
                return;
        case Utick:
                r = '\'';
                break;
        case Ubtick:
                r = '`';
                break;
        case Uminus:
                r = '-';
                break;
        case '\'':
                Bprint(&bout, "&rsquo;");
                return;
        case '`':
                Bprint(&bout, "&lsquo;");
                return;
        case Uamp:
                Bputrune(&bout, '&');
                return;
        case Ult:
                Bputrune(&bout, '<');
                return;
        case Ugt:
                Bputrune(&bout, '>');
                return;
        case Uspace:
                Bputrune(&bout, ' ');
                return;
        case 0x2032:
                /*
                 * In Firefox, at least, the prime is not
                 * a superscript by default.
                 */
                Bprint(&bout, "<sup>");
                Brune(r);
                Bprint(&bout, "</sup>");
                return;
        }
        Brune(r);
}

void
r_nop(int argc, Rune **argv)
{
        USED(argc);
        USED(argv);
}

void
r_warn(int argc, Rune **argv)
{
        USED(argc);
        warn("ignoring %C%S", dot, argv[0]);
}

int
e_warn(void)
{
        /* dispatch loop prints a warning for us */
        return 0;
}

int
e_nop(void)
{
        return 0;
}