xref: /plan9/sys/src/cmd/htmlroff/roff.c (revision 426d2b71458df9b491ba6c167f699b3f1f7b0428)
1 #include "a.h"
2 
3 enum
4 {
5 	MAXREQ = 100,
6 	MAXRAW = 40,
7 	MAXESC = 60,
8 	MAXLINE = 1024,
9 	MAXIF = 20,
10 	MAXARG = 10,
11 };
12 
13 typedef struct Esc Esc;
14 typedef struct Req Req;
15 typedef struct Raw Raw;
16 
17 /* escape sequence handler, like for \c */
18 struct Esc
19 {
20 	Rune r;
21 	int (*f)(void);
22 	int mode;
23 };
24 
25 /* raw request handler, like for .ie */
26 struct Raw
27 {
28 	Rune *name;
29 	void (*f)(Rune*);
30 };
31 
32 /* regular request handler, like for .ft */
33 struct Req
34 {
35 	int argc;
36 	Rune *name;
37 	void (*f)(int, Rune**);
38 };
39 
40 int		dot = '.';
41 int		tick = '\'';
42 int		backslash = '\\';
43 
44 int		inputmode;
45 Req		req[MAXREQ];
46 int		nreq;
47 Raw		raw[MAXRAW];
48 int		nraw;
49 Esc		esc[MAXESC];
50 int		nesc;
51 int		iftrue[MAXIF];
52 int		niftrue;
53 
54 int isoutput;
55 int linepos;
56 
57 
58 void
addraw(Rune * name,void (* f)(Rune *))59 addraw(Rune *name, void (*f)(Rune*))
60 {
61 	Raw *r;
62 
63 	if(nraw >= nelem(raw)){
64 		fprint(2, "too many raw requets\n");
65 		return;
66 	}
67 	r = &raw[nraw++];
68 	r->name = erunestrdup(name);
69 	r->f = f;
70 }
71 
72 void
delraw(Rune * name)73 delraw(Rune *name)
74 {
75 	int i;
76 
77 	for(i=0; i<nraw; i++){
78 		if(runestrcmp(raw[i].name, name) == 0){
79 			if(i != --nraw){
80 				free(raw[i].name);
81 				raw[i] = raw[nraw];
82 			}
83 			return;
84 		}
85 	}
86 }
87 
88 void
renraw(Rune * from,Rune * to)89 renraw(Rune *from, Rune *to)
90 {
91 	int i;
92 
93 	delraw(to);
94 	for(i=0; i<nraw; i++)
95 		if(runestrcmp(raw[i].name, from) == 0){
96 			free(raw[i].name);
97 			raw[i].name = erunestrdup(to);
98 			return;
99 		}
100 }
101 
102 
103 void
addreq(Rune * s,void (* f)(int,Rune **),int argc)104 addreq(Rune *s, void (*f)(int, Rune**), int argc)
105 {
106 	Req *r;
107 
108 	if(nreq >= nelem(req)){
109 		fprint(2, "too many requests\n");
110 		return;
111 	}
112 	r = &req[nreq++];
113 	r->name = erunestrdup(s);
114 	r->f = f;
115 	r->argc = argc;
116 }
117 
118 void
delreq(Rune * name)119 delreq(Rune *name)
120 {
121 	int i;
122 
123 	for(i=0; i<nreq; i++){
124 		if(runestrcmp(req[i].name, name) == 0){
125 			if(i != --nreq){
126 				free(req[i].name);
127 				req[i] = req[nreq];
128 			}
129 			return;
130 		}
131 	}
132 }
133 
134 void
renreq(Rune * from,Rune * to)135 renreq(Rune *from, Rune *to)
136 {
137 	int i;
138 
139 	delreq(to);
140 	for(i=0; i<nreq; i++)
141 		if(runestrcmp(req[i].name, from) == 0){
142 			free(req[i].name);
143 			req[i].name = erunestrdup(to);
144 			return;
145 		}
146 }
147 
148 void
addesc(Rune r,int (* f)(void),int mode)149 addesc(Rune r, int (*f)(void), int mode)
150 {
151 	Esc *e;
152 
153 	if(nesc >= nelem(esc)){
154 		fprint(2, "too many escapes\n");
155 		return;
156 	}
157 	e = &esc[nesc++];
158 	e->r = r;
159 	e->f = f;
160 	e->mode = mode;
161 }
162 
163 /*
164  * Get the next logical character in the input stream.
165  */
166 int
getnext(void)167 getnext(void)
168 {
169 	int i, r;
170 
171 next:
172 	r = getrune();
173 	if(r < 0)
174 		return -1;
175 	if(r == Uformatted){
176 		br();
177 		assert(!isoutput);
178 		while((r = getrune()) >= 0 && r != Uunformatted){
179 			if(r == Uformatted)
180 				continue;
181 			outrune(r);
182 		}
183 		goto next;
184 	}
185 	if(r == Uunformatted)
186 		goto next;
187 	if(r == backslash){
188 		r = getrune();
189 		if(r < 0)
190 			return -1;
191 		for(i=0; i<nesc; i++){
192 			if(r == esc[i].r && (inputmode&esc[i].mode)==inputmode){
193 				if(esc[i].f == e_warn)
194 					warn("ignoring %C%C", backslash, r);
195 				r = esc[i].f();
196 				if(r <= 0)
197 					goto next;
198 				return r;
199 			}
200 		}
201 		if(inputmode&(ArgMode|CopyMode)){
202 			ungetrune(r);
203 			r = backslash;
204 		}
205 	}
206 	return r;
207 }
208 
209 void
ungetnext(Rune r)210 ungetnext(Rune r)
211 {
212 	/*
213 	 * really we want to undo the getrunes that led us here,
214 	 * since the call after ungetnext might be getrune!
215 	 */
216 	ungetrune(r);
217 }
218 
219 int
_readx(Rune * p,int n,int nmode,int line)220 _readx(Rune *p, int n, int nmode, int line)
221 {
222 	int c, omode;
223 	Rune *e;
224 
225 	while((c = getrune()) == ' ' || c == '\t')
226 		;
227 	ungetrune(c);
228 	omode = inputmode;
229 	inputmode = nmode;
230 	e = p+n-1;
231 	for(c=getnext(); p<e; c=getnext()){
232 		if(c < 0)
233 			break;
234 		if(!line && (c == ' ' || c == '\t'))
235 			break;
236 		if(c == '\n'){
237 			if(!line)
238 				ungetnext(c);
239 			break;
240 		}
241 		*p++ = c;
242 	}
243 	inputmode = omode;
244 	*p = 0;
245 	if(c < 0)
246 		return -1;
247 	return 0;
248 }
249 
250 /*
251  * Get the next argument from the current line.
252  */
253 Rune*
copyarg(void)254 copyarg(void)
255 {
256 	static Rune buf[MaxLine];
257 	int c;
258 	Rune *r;
259 
260 	if(_readx(buf, sizeof buf, ArgMode, 0) < 0)
261 		return nil;
262 	r = runestrstr(buf, L("\\\""));
263 	if(r){
264 		*r = 0;
265 		while((c = getrune()) >= 0 && c != '\n')
266 			;
267 		ungetrune('\n');
268 	}
269 	r = erunestrdup(buf);
270 	return r;
271 }
272 
273 /*
274  * Read the current line in given mode.  Newline not kept.
275  * Uses different buffer from copyarg!
276  */
277 Rune*
readline(int m)278 readline(int m)
279 {
280 	static Rune buf[MaxLine];
281 	Rune *r;
282 
283 	if(_readx(buf, sizeof buf, m, 1) < 0)
284 		return nil;
285 	r = erunestrdup(buf);
286 	return r;
287 }
288 
289 /*
290  * Given the argument line (already read in copy+arg mode),
291  * parse into arguments.  Note that \" has been left in place
292  * during copy+arg mode parsing, so comments still need to be stripped.
293  */
294 int
parseargs(Rune * p,Rune ** argv)295 parseargs(Rune *p, Rune **argv)
296 {
297 	int argc;
298 	Rune *w;
299 
300 	for(argc=0; argc<MAXARG; argc++){
301 		while(*p == ' ' || *p == '\t')
302 			p++;
303 		if(*p == 0)
304 			break;
305 		argv[argc] = p;
306 		if(*p == '"'){
307 			/* quoted argument */
308 			if(*(p+1) == '"'){
309 				/* empty argument */
310 				*p = 0;
311 				p += 2;
312 			}else{
313 				/* parse quoted string */
314 				w = p++;
315 				for(; *p; p++){
316 					if(*p == '"' && *(p+1) == '"')
317 						*w++ = '"';
318 					else if(*p == '"'){
319 						p++;
320 						break;
321 					}else
322 						*w++ = *p;
323 				}
324 				*w = 0;
325 			}
326 		}else{
327 			/* unquoted argument - need to watch out for \" comment */
328 			for(; *p; p++){
329 				if(*p == ' ' || *p == '\t'){
330 					*p++ = 0;
331 					break;
332 				}
333 				if(*p == '\\' && *(p+1) == '"'){
334 					*p = 0;
335 					if(p != argv[argc])
336 						argc++;
337 					return argc;
338 				}
339 			}
340 		}
341 	}
342 	return argc;
343 }
344 
345 /*
346  * Process a dot line.  The dot has been read.
347  */
348 void
dotline(int dot)349 dotline(int dot)
350 {
351 	int argc, i;
352 	Rune *a, *argv[1+MAXARG];
353 
354 	/*
355 	 * Read request/macro name
356 	 */
357 	a = copyarg();
358 	if(a == nil || a[0] == 0){
359 		free(a);
360 		getrune();	/* \n */
361 		return;
362 	}
363 	argv[0] = a;
364 	/*
365 	 * Check for .if, .ie, and others with special parsing.
366 	 */
367 	for(i=0; i<nraw; i++){
368 		if(runestrcmp(raw[i].name, a) == 0){
369 			raw[i].f(raw[i].name);
370 			free(a);
371 			return;
372 		}
373 	}
374 
375 	/*
376 	 * Read rest of line in copy mode, invoke regular request.
377 	 */
378 	a = readline(ArgMode);
379 	if(a == nil){
380 		free(argv[0]);
381 		return;
382 	}
383 	argc = 1+parseargs(a, argv+1);
384 	for(i=0; i<nreq; i++){
385 		if(runestrcmp(req[i].name, argv[0]) == 0){
386 			if(req[i].argc != -1){
387 				if(argc < 1+req[i].argc){
388 					warn("not enough arguments for %C%S", dot, req[i].name);
389 					free(argv[0]);
390 					free(a);
391 					return;
392 				}
393 				if(argc > 1+req[i].argc)
394 					warn("too many arguments for %C%S", dot, req[i].name);
395 			}
396 			req[i].f(argc, argv);
397 			free(argv[0]);
398 			free(a);
399 			return;
400 		}
401 	}
402 
403 	/*
404 	 * Invoke user-defined macros.
405 	 */
406 	runmacro(dot, argc, argv);
407 	free(argv[0]);
408 	free(a);
409 }
410 
411 /*
412  * newlines are magical in various ways.
413  */
414 int bol;
415 void
newline(void)416 newline(void)
417 {
418 	int n;
419 
420 	if(bol)
421 		sp(eval(L("1v")));
422 	bol = 1;
423 	if((n=getnr(L(".ce"))) > 0){
424 		nr(L(".ce"), n-1);
425 		br();
426 	}
427 	if(getnr(L(".fi")) == 0)
428 		br();
429 	outrune('\n');
430 }
431 
432 void
startoutput(void)433 startoutput(void)
434 {
435 	char *align;
436 	double ps, vs, lm, rm, ti;
437 	Rune buf[200];
438 
439 	if(isoutput)
440 		return;
441 	isoutput = 1;
442 
443 	if(getnr(L(".paragraph")) == 0)
444 		return;
445 
446 	nr(L(".ns"), 0);
447 	isoutput = 1;
448 	ps = getnr(L(".s"));
449 	if(ps <= 1)
450 		ps = 10;
451 	ps /= 72.0;
452 	USED(ps);
453 
454 	vs = getnr(L(".v"))*getnr(L(".ls")) * 1.0/UPI;
455 	vs /= (10.0/72.0);	/* ps */
456 	if(vs == 0)
457 		vs = 1.2;
458 
459 	lm = (getnr(L(".o"))+getnr(L(".i"))) * 1.0/UPI;
460 	ti = getnr(L(".ti")) * 1.0/UPI;
461 	nr(L(".ti"), 0);
462 
463 	rm = 8.0 - getnr(L(".l"))*1.0/UPI - getnr(L(".o"))*1.0/UPI;
464 	if(rm < 0)
465 		rm = 0;
466 	switch(getnr(L(".j"))){
467 	default:
468 	case 0:
469 		align = "left";
470 		break;
471 	case 1:
472 		align = "justify";
473 		break;
474 	case 3:
475 		align = "center";
476 		break;
477 	case 5:
478 		align = "right";
479 		break;
480 	}
481 	if(getnr(L(".ce")))
482 		align = "center";
483 	if(!getnr(L(".margin")))
484 		runesnprint(buf, nelem(buf), "<p style=\"line-height: %.1fem; text-indent: %.2fin; margin-top: 0; margin-bottom: 0; text-align: %s;\">\n",
485 			vs, ti, align);
486 	else
487 		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",
488 			vs, lm, ti, rm, align);
489 	outhtml(buf);
490 }
491 void
br(void)492 br(void)
493 {
494 	if(!isoutput)
495 		return;
496 	isoutput = 0;
497 
498 	nr(L(".dv"), 0);
499 	dv(0);
500 	hideihtml();
501 	if(getnr(L(".paragraph")))
502 		outhtml(L("</p>"));
503 }
504 
505 void
r_margin(int argc,Rune ** argv)506 r_margin(int argc, Rune **argv)
507 {
508 	USED(argc);
509 
510 	nr(L(".margin"), eval(argv[1]));
511 }
512 
513 int inrequest;
514 void
runinput(void)515 runinput(void)
516 {
517 	int c;
518 
519 	bol = 1;
520 	for(;;){
521 		c = getnext();
522 		if(c < 0)
523 			break;
524 		if((c == dot || c == tick) && bol){
525 			inrequest = 1;
526 			dotline(c);
527 			bol = 1;
528 			inrequest = 0;
529 		}else if(c == '\n'){
530 			newline();
531 			itrap();
532 			linepos = 0;
533 		}else{
534 			outtrap();
535 			startoutput();
536 			showihtml();
537 			if(c == '\t'){
538 				/* XXX do better */
539 				outrune(' ');
540 				while(++linepos%4)
541 					outrune(' ');
542 			}else{
543 				outrune(c);
544 				linepos++;
545 			}
546 			bol = 0;
547 		}
548 	}
549 }
550 
551 void
run(void)552 run(void)
553 {
554 	t1init();
555 	t2init();
556 	t3init();
557 	t4init();
558 	t5init();
559 	t6init();
560 	t7init();
561 	t8init();
562 	/* t9init(); t9.c */
563 	t10init();
564 	t11init();
565 	/* t12init(); t12.c */
566 	t13init();
567 	t14init();
568 	t15init();
569 	t16init();
570 	t17init();
571 	t18init();
572 	t19init();
573 	t20init();
574 	htmlinit();
575 	hideihtml();
576 
577 	addreq(L("margin"), r_margin, 1);
578 	nr(L(".margin"), 1);
579 	nr(L(".paragraph"), 1);
580 
581 	runinput();
582 	while(popinput())
583 		;
584 	dot = '.';
585 	if(verbose)
586 		fprint(2, "eof\n");
587 	runmacro1(L("eof"));
588 	closehtml();
589 }
590 
591 void
out(Rune * s)592 out(Rune *s)
593 {
594 	if(s == nil)
595 		return;
596 	for(; *s; s++)
597 		outrune(*s);
598 }
599 
600 void (*outcb)(Rune);
601 
602 void
inroman(Rune r)603 inroman(Rune r)
604 {
605 	int f;
606 
607 	f = getnr(L(".f"));
608 	nr(L(".f"), 1);
609 	runmacro1(L("font"));
610 	outrune(r);
611 	nr(L(".f"), f);
612 	runmacro1(L("font"));
613 }
614 
615 void
Brune(Rune r)616 Brune(Rune r)
617 {
618 	if(r == '&')
619 		Bprint(&bout, "&amp;");
620 	else if(r == '<')
621 		Bprint(&bout, "&lt;");
622 	else if(r == '>')
623 		Bprint(&bout, "&gt;");
624 	else if(r < Runeself || utf8)
625 		Bprint(&bout, "%C", r);
626 	else
627 		Bprint(&bout, "%S", rune2html(r));
628 }
629 
630 void
outhtml(Rune * s)631 outhtml(Rune *s)
632 {
633 	Rune r;
634 
635 	for(; *s; s++){
636 		switch(r = *s){
637 		case '<':
638 			r = Ult;
639 			break;
640 		case '>':
641 			r = Ugt;
642 			break;
643 		case '&':
644 			r = Uamp;
645 			break;
646 		case ' ':
647 			r = Uspace;
648 			break;
649 		}
650 		outrune(r);
651 	}
652 }
653 
654 void
outrune(Rune r)655 outrune(Rune r)
656 {
657 	switch(r){
658 	case ' ':
659 		if(getnr(L(".fi")) == 0)
660 			r = Unbsp;
661 		break;
662 	case Uformatted:
663 	case Uunformatted:
664 		abort();
665 	}
666 	if(outcb){
667 		if(r == ' ')
668 			r = Uspace;
669 		outcb(r);
670 		return;
671 	}
672 	/* writing to bout */
673 	switch(r){
674 	case Uempty:
675 		return;
676 	case Upl:
677 		inroman('+');
678 		return;
679 	case Ueq:
680 		inroman('=');
681 		return;
682 	case Umi:
683 		inroman(0x2212);
684 		return;
685 	case Utick:
686 		r = '\'';
687 		break;
688 	case Ubtick:
689 		r = '`';
690 		break;
691 	case Uminus:
692 		r = '-';
693 		break;
694 	case '\'':
695 		Bprint(&bout, "&rsquo;");
696 		return;
697 	case '`':
698 		Bprint(&bout, "&lsquo;");
699 		return;
700 	case Uamp:
701 		Bputrune(&bout, '&');
702 		return;
703 	case Ult:
704 		Bputrune(&bout, '<');
705 		return;
706 	case Ugt:
707 		Bputrune(&bout, '>');
708 		return;
709 	case Uspace:
710 		Bputrune(&bout, ' ');
711 		return;
712 	case 0x2032:
713 		/*
714 		 * In Firefox, at least, the prime is not
715 		 * a superscript by default.
716 		 */
717 		Bprint(&bout, "<sup>");
718 		Brune(r);
719 		Bprint(&bout, "</sup>");
720 		return;
721 	}
722 	Brune(r);
723 }
724 
725 void
r_nop(int argc,Rune ** argv)726 r_nop(int argc, Rune **argv)
727 {
728 	USED(argc);
729 	USED(argv);
730 }
731 
732 void
r_warn(int argc,Rune ** argv)733 r_warn(int argc, Rune **argv)
734 {
735 	USED(argc);
736 	warn("ignoring %C%S", dot, argv[0]);
737 }
738 
739 int
e_warn(void)740 e_warn(void)
741 {
742 	/* dispatch loop prints a warning for us */
743 	return 0;
744 }
745 
746 int
e_nop(void)747 e_nop(void)
748 {
749 	return 0;
750 }
751