|  | #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, "&"); | 
|  | else if(r == '<') | 
|  | Bprint(&bout, "<"); | 
|  | else if(r == '>') | 
|  | Bprint(&bout, ">"); | 
|  | 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, "’"); | 
|  | return; | 
|  | case '`': | 
|  | Bprint(&bout, "‘"); | 
|  | 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; | 
|  | } |