| #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; |
| } |