blob: bdc51a3e18445698250613de211cc1e9022b92b7 [file] [log] [blame]
#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;
}