xref: /inferno-os/appl/cmd/cook.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement Cook;
2
3include "sys.m";
4	sys: Sys;
5	FD: import Sys;
6
7include "draw.m";
8	draw: Draw;
9
10include "bufio.m";
11	B: Bufio;
12	Iobuf: import B;
13
14include "string.m";
15	S: String;
16	splitl, splitr, splitstrl, drop, take, in, prefix, tolower : import S;
17
18include "brutus.m";
19	Size6, Size8, Size10, Size12, Size16, NSIZE,
20	Roman, Italic, Bold, Type, NFONT, NFONTTAG,
21	Example, Caption, List, Listelem, Label, Labelref,
22	Exercise, Heading, Nofill, Author, Title,
23	Index, Indextopic,
24	DefFont, DefSize, TitleFont, TitleSize, HeadingFont, HeadingSize: import Brutus;
25
26# following are needed for types in brutusext.m
27include "tk.m";
28	tk: Tk;
29include "tkclient.m";
30
31include "brutusext.m";
32	SGML, Text, Par, Extension, Float, Special, Celem,
33	FLatex, FLatexProc, FLatexBook, FLatexPart, FLatexSlides, FHtml: import Brutusext;
34
35include "strinttab.m";
36	T: StringIntTab;
37
38Cook: module
39{
40	init:	fn(ctxt: ref Draw->Context, args: list of string);
41};
42
43# keep this sorted by name
44tagstringtab := array[] of { T->StringInt
45	("Author", Author),
46	("Bold.10", Bold*NSIZE + Size10),
47	("Bold.12", Bold*NSIZE + Size12),
48	("Bold.16", Bold*NSIZE + Size16),
49	("Bold.6", Bold*NSIZE + Size6),
50	("Bold.8", Bold*NSIZE + Size8),
51	("Caption", Caption),
52	("Example", Example),
53	("Exercise", Exercise),
54	("Extension", Extension),
55	("Float", Float),
56	("Heading", Heading),
57	("Index", Index),
58	("Index-topic", Indextopic),
59	("Italic.10", Italic*NSIZE + Size10),
60	("Italic.12", Italic*NSIZE + Size12),
61	("Italic.16", Italic*NSIZE + Size16),
62	("Italic.6", Italic*NSIZE + Size6),
63	("Italic.8", Italic*NSIZE + Size8),
64	("Label", Label),
65	("Label-ref", Labelref),
66	("List", List),
67	("List-elem", Listelem),
68	("No-fill", Nofill),
69	("Par", Par),
70	("Roman.10", Roman*NSIZE + Size10),
71	("Roman.12", Roman*NSIZE + Size12),
72	("Roman.16", Roman*NSIZE + Size16),
73	("Roman.6", Roman*NSIZE + Size6),
74	("Roman.8", Roman*NSIZE + Size8),
75	("SGML", SGML),
76	("Title", Title),
77	("Type.10", Type*NSIZE + Size10),
78	("Type.12", Type*NSIZE + Size12),
79	("Type.16", Type*NSIZE + Size16),
80	("Type.6", Type*NSIZE + Size6),
81	("Type.8", Type*NSIZE + Size8),
82};
83
84# This table must be sorted
85fmtstringtab := array[] of { T->StringInt
86	("html", FHtml),
87	("latex", FLatex),
88	("latexbook", FLatexBook),
89	("latexpart", FLatexPart),
90	("latexproc", FLatexProc),
91	("latexslides", FLatexSlides),
92};
93
94Transtab: adt
95{
96	ch:		int;
97	trans:	string;
98};
99
100# Order doesn't matter for these table
101
102ltranstab := array[] of { Transtab
103	('$', "\\textdollar{}"),
104	('&', "\\&"),
105	('%', "\\%"),
106	('#', "\\#"),
107	('_', "\\textunderscore{}"),
108	('{', "\\{"),
109	('}', "\\}"),
110	('~', "\\textasciitilde{}"),
111	('^', "\\textasciicircum{}"),
112	('\\', "\\textbackslash{}"),
113	('+', "\\textplus{}"),
114	('=', "\\textequals{}"),
115	('|', "\\textbar{}"),
116	('<', "\\textless{}"),
117	('>', "\\textgreater{}"),
118	(' ', "~"),
119	('-', "-"),  # needs special case ligature treatment
120	('\t', " "),   # needs special case treatment
121};
122
123htranstab := array[] of { Transtab
124	('α', "&alpha;"),
125	('Æ', "&AElig;"),
126	('Á', "&Aacute;"),
127	('Â', "&Acirc;"),
128	('À', "&Agrave;"),
129	('Å', "&Aring;"),
130	('Ã', "&Atilde;"),
131	('Ä', "&Auml;"),
132	('Ç', "&Ccedil;"),
133	('Ð', "&ETH;"),
134	('É', "&Eacute;"),
135	('Ê', "&Ecirc;"),
136	('È', "&Egrave;"),
137	('Ë', "&Euml;"),
138	('Í', "&Iacute;"),
139	('Î', "&Icirc;"),
140	('Ì', "&Igrave;"),
141	('Ï', "&Iuml;"),
142	('Ñ', "&Ntilde;"),
143	('Ó', "&Oacute;"),
144	('Ô', "&Ocirc;"),
145	('Ò', "&Ograve;"),
146	('Ø', "&Oslash;"),
147	('Õ', "&Otilde;"),
148	('Ö', "&Ouml;"),
149	('Þ', "&THORN;"),
150	('Ú', "&Uacute;"),
151	('Û', "&Ucirc;"),
152	('Ù', "&Ugrave;"),
153	('Ü', "&Uuml;"),
154	('Ý', "&Yacute;"),
155	('æ', "&aElig;"),
156	('á', "&aacute;"),
157	('â', "&acirc;"),
158	('à', "&agrave;"),
159	('α', "&alpha;"),
160	('&', "&amp;"),
161	('å', "&aring;"),
162	('ã', "&atilde;"),
163	('ä', "&auml;"),
164	('β', "&beta;"),
165	('ç', "&ccedil;"),
166	('⋯', "&cdots;"),
167	('χ', "&chi;"),
168	('©', "&copy;"),
169	('⋱', "&ddots;"),
170	('δ', "&delta;"),
171	('é', "&eacute;"),
172	('ê', "&ecirc;"),
173	('è', "&egrave;"),
174	('—', "&emdash;"),
175	(' ', "&emsp;"),
176	('–', "&endash;"),
177	('ε', "&epsilon;"),
178	('η', "&eta;"),
179	('ð', "&eth;"),
180	('ë', "&euml;"),
181	('γ', "&gamma;"),
182	('>', "&gt;"),
183	('í', "&iacute;"),
184	('î', "&icirc;"),
185	('ì', "&igrave;"),
186	('ι', "&iota;"),
187	('ï', "&iuml;"),
188	('κ', "&kappa;"),
189	('λ', "&lambda;"),
190	('…', "&ldots;"),
191	('<', "&lt;"),
192	('μ', "&mu;"),
193	(' ', "&nbsp;"),
194	('ñ', "&ntilde;"),
195	('ν', "&nu;"),
196	('ó', "&oacute;"),
197	('ô', "&ocirc;"),
198	('ò', "&ograve;"),
199	('ω', "&omega;"),
200	('ο', "&omicron;"),
201	('ø', "&oslash;"),
202	('õ', "&otilde;"),
203	('ö', "&ouml;"),
204	('φ', "&phi;"),
205	('π', "&pi;"),
206	('ψ', "&psi;"),
207	(' ', "&quad;"),
208	('"', "&quot;"),
209	('®', "&reg;"),
210	('ρ', "&rho;"),
211	('­', "&shy;"),
212	('σ', "&sigma;"),
213	('ß', "&szlig;"),
214	('τ', "&tau;"),
215	('θ', "&theta;"),
216	(' ', "&thinsp;"),
217	('þ', "&thorn;"),
218	('™', "&trade;"),
219	('ú', "&uacute;"),
220	('û', "&ucirc;"),
221	('ù', "&ugrave;"),
222	('υ', "&upsilon;"),
223	('ü', "&uuml;"),
224	('∈', "&varepsilon;"),
225	('ϕ', "&varphi;"),
226	('ϖ', "&varpi;"),
227	('ϱ', "&varrho;"),
228	('⋮', "&vdots;"),
229	('ς', "&vsigma;"),
230	('ϑ', "&vtheta;"),
231	('ξ', "&xi;"),
232	('ý', "&yacute;"),
233	('ÿ', "&yuml;"),
234	('ζ', "&zeta;"),
235	('−', "-"),
236};
237
238# For speedy lookups of ascii char translation, use asciitrans.
239# It should be initialized by ascii elements from one of above tables
240asciitrans := array[128] of string;
241
242stderr: ref FD;
243infilename := "";
244outfilename := "";
245linenum := 0;
246fin : ref Iobuf = nil;
247fout : ref Iobuf = nil;
248debug := 0;
249fmt := FLatex;
250
251init(nil: ref Draw->Context, argv: list of string)
252{
253	sys = load Sys Sys->PATH;
254	S = load String String->PATH;
255	B = load Bufio Bufio->PATH;
256	draw = load Draw Draw->PATH;
257	tk = load Tk Tk->PATH;
258	T = load StringIntTab StringIntTab->PATH;
259	stderr = sys->fildes(2);
260
261	for(argv = tl argv; argv != nil; ) {
262		s := hd argv;
263		tlargv := tl argv;
264		case s {
265		"-f" =>
266			if(tlargv == nil)
267				usage();
268			fnd: int;
269			(fnd, fmt) = T->lookup(fmtstringtab, hd(tlargv));
270			if(!fnd) {
271				sys->fprint(stderr, "unknown format: %s\n", hd(tlargv));
272				exit;
273			}
274			argv = tlargv;
275		"-o" =>
276			if(tlargv == nil)
277				usage();
278			outfilename = hd(tlargv);
279			argv = tlargv;
280		"-d" =>
281			debug = 1;
282		"-dd" =>
283			debug = 2;
284		* =>
285			if(tlargv == nil)
286				infilename = s;
287			else
288				usage();
289		}
290		argv = tl argv;
291	}
292	if(infilename == "") {
293		fin = B->fopen(sys->fildes(0), sys->OREAD);
294		infilename = "<stdin>";
295	}
296	else
297		fin = B->open(infilename, sys->OREAD);
298	if(fin == nil) {
299		sys->fprint(stderr, "cook: error opening %s: %r\n", infilename);
300		exit;
301	}
302	if(outfilename == "") {
303		fout = B->fopen(sys->fildes(1), sys->OWRITE);
304		outfilename = "<stdout>";
305	}
306	else
307		fout = B->create(outfilename, sys->OWRITE, 8r664);
308	if(fout == nil) {
309		sys->fprint(stderr, "cook: error creating %s: %r\n", outfilename);
310		exit;
311	}
312	line0 := fin.gets('\n');
313	if(line0 != "<SGML>\n") {
314		parse_err("not an SGML file\n");
315		exit;
316	}
317	linenum = 1;
318	e := parse(SGML);
319	findpars(e, 1, nil);
320	e = delemptystrs(e);
321	(e, nil) = canonfonts(e, DefFont*NSIZE+DefSize, DefFont*NSIZE+DefSize);
322	mergeadjs(e);
323	findfloats(e);
324	cleanexts(e);
325	cleanpars(e);
326	if(debug) {
327		fout.puts("After Initial transformations:\n");
328		printelem(e, "", 1);
329		fout.flush();
330	}
331	case fmt {
332	FLatex or FLatexProc or FLatexBook or FLatexPart or FLatexSlides =>
333		latexconv(e);
334	FHtml =>
335		htmlconv(e);
336	}
337	fin.close();
338	fout.close();
339}
340
341usage()
342{
343	sys->fprint(stderr, "Usage: cook [-f (latex|html)] [-o outfile] [infile]\n");
344	exit;
345}
346
347parse_err(msg: string)
348{
349	sys->fprint(stderr, "%s:%d: %s\n", infilename, linenum, msg);
350}
351
352# Parse into elements.
353# Assumes tags are balanced.
354# String elements are split so that there is never an internal newline.
355parse(id: int) : ref Celem
356{
357	els : ref Celem = nil;
358	elstail : ref Celem = nil;
359	for(;;) {
360		c := fin.getc();
361		if(c == Bufio->EOF) {
362			if(id == SGML)
363				break;
364			else {
365				parse_err(sys->sprint("EOF while parsing %s", tagname(id)));
366				return nil;
367			}
368		}
369		if(c == '<') {
370			tag := "";
371			start := 1;
372			i := 0;
373			for(;;) {
374				c = fin.getc();
375				if(c == Bufio->EOF) {
376					parse_err("EOF in middle of tag");
377					return nil;
378				}
379				if(c == '\n') {
380					linenum++;
381					parse_err("newline in middle of tag");
382					break;
383				}
384				if(c == '>')
385					break;
386				if(i == 0 && c == '/')
387					start = 0;
388				else
389					tag[i++] = c;
390			}
391			(fnd, tid) := T->lookup(tagstringtab, tag);
392			if(!fnd) {
393				if(prefix("Extension ", tag)) {
394					el := ref Celem(Extension, tag[10:], nil, nil, nil, nil);
395					if(els == nil) {
396						els = el;
397						elstail = el;
398					}
399					else {
400						el.prev = elstail;
401						elstail.next = el;
402						elstail = el;
403					}
404				}
405				else
406					parse_err(sys->sprint("unknown tag <%s>\n", tag));
407				continue;
408			}
409			if(start) {
410				el := parse(tid);
411				if(el == nil)
412					return nil;
413				if(els == nil) {
414					els = el;
415					elstail = el;
416				}
417				else {
418					el.prev = elstail;
419					elstail.next = el;
420					elstail = el;
421				}
422			}
423			else {
424				if(tid != id) {
425					parse_err(sys->sprint("<%s> ended by </%s>",
426						tagname(id), tag));
427					continue;
428				}
429				break;
430			}
431		}
432		else {
433			s := "";
434			i := 0;
435			for(;;) {
436				if(c == Bufio->EOF)
437					break;
438				if(c == '<') {
439					fin.ungetc();
440					break;
441				}
442				if(c == ';' && i >=3 && s[i-1] == 't' && s[i-2] == 'l' && s[i-3] == '&') {
443					i -= 2;
444					s[i-1] = '<';
445					s = s[0:i];
446				}
447				else
448					s[i++] = c;
449				if(c == '\n') {
450					linenum++;
451					break;
452				}
453				else
454					c = fin.getc();
455			}
456			if(s != "") {
457				el := ref Celem(Text, s, nil, nil, nil, nil);
458				if(els == nil) {
459					els = el;
460					elstail = el;
461				}
462				else {
463					el.prev = elstail;
464					elstail.next = el;
465					elstail = el;
466				}
467			}
468		}
469	}
470	ans := ref Celem(id, "", els, nil, nil, nil);
471	if(els != nil)
472		els.parent = ans;
473	return ans;
474}
475
476# Modify tree e so that blank lines become Par elements.
477# Only do it if parize is set, and unset parize when descending into TExample's.
478# Pass in most recent TString or TPar element, and return updated most-recent-TString/TPar.
479# This function may set some TString strings to ""
480findpars(e: ref Celem, parize: int, prevspe: ref Celem) : ref Celem
481{
482	while(e != nil) {
483		prevnl := 0;
484		prevpar := 0;
485		if(prevspe != nil) {
486			if(prevspe.tag == Text && len prevspe.s != 0
487			   && prevspe.s[(len prevspe.s)-1] == '\n')
488				prevnl = 1;
489			else if(prevspe.tag == Par)
490				prevpar = 1;
491		}
492		if(e.tag == Text) {
493			if(parize && (prevnl || prevpar) && e.s[0] == '\n') {
494				if(prevnl)
495					prevspe.s = prevspe.s[0 : (len prevspe.s)-1];
496				e.tag = Par;
497				e.s = nil;
498			}
499			prevspe = e;
500		}
501		else {
502			nparize := parize;
503			if(e.tag == Example)
504				nparize = 0;
505			prevspe = findpars(e.contents, nparize, prevspe);
506		}
507		e = e.next;
508	}
509	return prevspe;
510}
511
512# Delete any empty strings from e's tree and return modified e.
513# Also, delete any entity that has empty contents, except the
514# Par ones
515delemptystrs(e: ref Celem) : ref Celem
516{
517	if(e.tag == Text) {
518		if(e.s == "")
519			return nil;
520		else
521			return e;
522	}
523	if(e.tag == Par || e.tag == Extension || e.tag == Special)
524		return e;
525	h := e.contents;
526	while(h != nil) {
527		hnext := h.next;
528		hh := delemptystrs(h);
529		if(hh == nil)
530			delete(h);
531		h = hnext;
532	}
533	if(e.contents == nil)
534		return nil;
535	return e;
536}
537
538# Change tree under e so that any font elems contain only strings
539# (by pushing the font changes down).
540# Answer an be a list, so return beginning and end of list.
541# Leave strings bare if font change would be to deffont,
542# and adjust deffont appropriately when entering Title and
543# Heading environments.
544canonfonts(e: ref Celem, curfont, deffont: int) : (ref Celem, ref Celem)
545{
546	f := curfont;
547	head : ref Celem = nil;
548	tail : ref Celem = nil;
549	tocombine : ref Celem = nil;
550	if(e.tag == Text) {
551		if(f == deffont) {
552			head = e;
553			tail = e;
554		}
555		else {
556			head = ref Celem(f, nil, e, nil, nil, nil);
557			e.parent = head;
558			tail = head;
559		}
560	}
561	else if(e.contents == nil) {
562		head = e;
563		tail = e;
564	}
565	else if(e.tag < NFONTTAG) {
566		f = e.tag;
567		allstrings := 1;
568		for(g := e.contents; g != nil; g = g.next) {
569			if(g.tag != Text)
570				allstrings = 0;
571			tail = g;
572		}
573		if(allstrings) {
574			if(f == deffont)
575				head = e.contents;
576			else {
577				head = e;
578				tail = e;
579			}
580		}
581	}
582	if(head == nil) {
583		if(e.tag == Title)
584			deffont = TitleFont*NSIZE+TitleSize;
585		else if(e.tag == Heading)
586			deffont = HeadingFont*NSIZE+HeadingSize;
587		for(h := e.contents; h != nil; ) {
588			prev := h.prev;
589			next := h.next;
590			excise(h);
591			(e1, en) := canonfonts(h, f, deffont);
592			splicebetween(e1, en, prev, next);
593			if(prev == nil)
594				head = e1;
595			tail = en;
596			h = next;
597		}
598		tocombine = head;
599		if(e.tag >= NFONTTAG) {
600			e.contents = head;
601			head.parent = e;
602			head = e;
603			tail = e;
604		}
605	}
606	if(tocombine != nil) {
607		# combine adjacent font changes to same font
608		r := tocombine;
609		while(r != nil) {
610			if(r.tag < NFONTTAG && r.next != nil && r.next.tag == r.tag) {
611				for(v := r.next; v != nil; v = v.next) {
612					if(v.tag != r.tag)
613						break;
614					if(v == tail)
615						tail = r;
616				}
617				# now r up to, not including v, all change to same font
618				for(p := r.next; p != v; p = p.next) {
619					append(r.contents, p.contents);
620				}
621				r.next = v;
622				if(v != nil)
623					v.prev = r;
624				r = v;
625			}
626			else
627				r = r.next;
628		}
629	}
630	head.parent = nil;
631	return (head, tail);
632}
633
634# Remove Pars that appear just before or just after Heading, Title, Examples, Extensions
635# Really should worry about this happening at different nesting levels, but in
636# practice this happens all at the same nesting level
637cleanpars(e: ref Celem)
638{
639	for(h := e.contents; h != nil; h = h.next) {
640		cleanpars(h);
641		if(h.tag == Title || h.tag == Heading || h.tag == Example || h.tag == Extension) {
642			hp := h.prev;
643			hn := h.next;
644			if(hp !=nil && hp.tag == Par)
645				delete(hp);
646			if(hn != nil && hn.tag == Par)
647				delete(hn);
648		}
649	}
650}
651
652# Remove a single tab if it appears before an Extension
653cleanexts(e: ref Celem)
654{
655	for(h := e.contents; h != nil; h = h.next) {
656		cleanexts(h);
657		if(h.tag == Extension) {
658			hp := h.prev;
659			if(hp != nil && stringof(hp) == "\t")
660				delete(hp);
661		}
662	}
663}
664
665mergeable := array[] of { List, Exercise, Caption,Index, Indextopic };
666
667# Merge some adjacent elements (which were probably created separate
668# because of font changes)
669mergeadjs(e: ref Celem)
670{
671	for(h := e.contents; h != nil; h = h.next) {
672		hn := h.next;
673		domerge := 0;
674		if(hn != nil) {
675			for(i := 0; i < len mergeable; i++) {
676				mi := mergeable[i];
677				if(h.tag == mi && hn.tag == mi)
678					domerge = 1;
679			}
680		}
681		if(domerge) {
682			append(h.contents, hn.contents);
683			delete(hn);
684		}
685		else
686			mergeadjs(h);
687	}
688}
689
690# Find floats: they are paragraphs with Captions at the end.
691findfloats(e: ref Celem)
692{
693	lastpar : ref Celem = nil;
694	for(h := e.contents; h != nil; h = h.next) {
695		if(h.tag == Par)
696			lastpar = h;
697		else if(h.tag == Caption) {
698			ne := ref Celem(Float, "", nil, nil, nil, nil);
699			if(lastpar == nil)
700				flhead := e.contents;
701			else
702				flhead = lastpar.next;
703			insertbefore(ne, flhead);
704			# now move flhead ... h into contents of ne
705			ne.contents = flhead;
706			flhead.parent = ne;
707			flhead.prev = nil;
708			ne.next = h.next;
709			if(ne.next != nil)
710				ne.next.prev = ne;
711			h.next = nil;
712			h = ne;
713		}
714		else
715			findfloats(h);
716	}
717}
718
719insertbefore(e, ebefore: ref Celem)
720{
721	e.prev = ebefore.prev;
722	if(e.prev == nil) {
723		e.parent = ebefore.parent;
724		ebefore.parent = nil;
725		e.parent.contents = e;
726	}
727	else
728		e.prev.next = e;
729	e.next = ebefore;
730	ebefore.prev = e;
731}
732
733insertafter(e, eafter: ref Celem)
734{
735	e.next = eafter.next;
736	if(e.next != nil)
737		e.next.prev = e;
738	e.prev = eafter;
739	eafter.next = e;
740}
741
742# remove e from its list, leaving siblings disconnected
743excise(e: ref Celem)
744{
745	next := e. next;
746	prev := e.prev;
747	e.next = nil;
748	e.prev = nil;
749	if(prev != nil)
750		prev.next = nil;
751	if(next != nil)
752		next.prev = nil;
753	e.parent = nil;
754}
755
756splicebetween(e1, en, prev, next: ref Celem)
757{
758	if(prev != nil)
759		prev.next = e1;
760	e1.prev = prev;
761	en.next = next;
762	if(next != nil)
763		next.prev = en;
764}
765
766append(e1, e2: ref Celem)
767{
768	e1last := last(e1);
769	e1last.next = e2;
770	e2.prev = e1last;
771	e2.parent = nil;
772}
773
774last(e: ref Celem) : ref Celem
775{
776	if(e != nil)
777		while(e.next != nil)
778			e = e.next;
779	return e;
780}
781
782succ(e: ref Celem) : ref Celem
783{
784	if(e == nil)
785		return nil;
786	if(e.next != nil)
787		return e.next;
788	return succ(e.parent);
789}
790
791delete(e: ref Celem)
792{
793	ep := e.prev;
794	en := e.next;
795	eu := e.parent;
796	if(ep == nil) {
797		if(eu != nil)
798			eu.contents = en;
799		if(en != nil)
800			en.parent = eu;
801	}
802	else
803		ep.next = en;
804	if(en != nil)
805		en.prev = ep;
806}
807
808# return string represented by e, peering through font changes
809stringof(e: ref Celem) : string
810{
811	if(e != nil) {
812		if(e.tag == Text)
813			return e.s;
814		if(e.tag < NFONTTAG)
815			return stringof(e.contents);
816	}
817	return "";
818}
819
820# remove any initial whitespace from e and its sucessors,
821dropwhite(e: ref Celem)
822{
823	if(e == nil)
824		return;
825	del := 0;
826	if(e.tag == Text) {
827		e.s = drop(e.s, " \t\n");
828		if(e.s == "")
829			del = 1;;
830	}
831	else if(e.tag < NFONTTAG) {
832		dropwhite(e.contents);
833		if(e.contents == nil)
834			del = 1;
835	}
836	if(del) {
837		enext := e.next;
838		delete(e);
839		dropwhite(enext);
840	}
841
842}
843
844firstchar(e: ref Celem) : int
845{
846	s := stringof(e);
847	if(len s >= 1)
848		return s[0];
849	return -1;
850}
851
852lastchar(e: ref Celem) : int
853{
854	if(e == nil)
855		return -1;
856	while(e.next != nil)
857		e = e.next;
858	s := stringof(e);
859	if(len s >= 1)
860		return s[len s -1];
861	return -1;
862}
863
864tlookup(t: array of Transtab, v: int) : string
865{
866	n := len t;
867	for(i := 0; i < n; i++)
868		if(t[i].ch == v)
869			return t[i].trans;
870	return "";
871}
872
873initasciitrans(t: array of Transtab)
874{
875	n := len t;
876	for(i := 0; i < n; i++) {
877		c := t[i].ch;
878		if(c < 128)
879			asciitrans[c] = t[i].trans;
880	}
881}
882
883tagname(id: int) : string
884{
885	name := T->revlookup(tagstringtab, id);
886	if(name == nil)
887		name = "_unknown_";
888	return name;
889}
890
891printelem(e: ref Celem, indent: string, recurse: int)
892{
893	fout.puts(indent);
894	if(debug > 1) {
895		fout.puts(sys->sprint("%x: ", e));
896		if(e != nil && e.parent != nil)
897			fout.puts(sys->sprint("(parent %x): ", e.parent));
898	}
899	if(e == nil)
900		fout.puts("NIL\n");
901	else if(e.tag == Text || e.tag == Special || e.tag == Extension) {
902		if(e.tag == Special)
903			fout.puts("S");
904		else if(e.tag == Extension)
905			fout.puts("E");
906		fout.puts("«");
907		fout.puts(e.s);
908		fout.puts("»\n");
909	}
910	else {
911		name := tagname(e.tag);
912		fout.puts("<" + name + ">\n");
913		if(recurse && e.contents != nil)
914			printelems(e.contents, indent + "    ", recurse);
915	}
916}
917
918printelems(els: ref Celem, indent: string, recurse: int)
919{
920	for(; els != nil; els = els.next)
921		printelem(els, indent, recurse);
922}
923
924check(e: ref Celem, msg: string)
925{
926	err := checke(e);
927	if(err != "") {
928		fout.puts(msg + ": tree is inconsistent:\n" + err);
929		printelem(e, "", 1);
930		fout.flush();
931		exit;
932	}
933}
934
935checke(e: ref Celem) : string
936{
937	err := "";
938	if(e.tag == SGML && e.next != nil)
939		err = sys->sprint("root %x has a next field\n", e);
940	ec := e.contents;
941	if(ec != nil) {
942		if(ec.parent != e)
943			err += sys->sprint("node %x contents %x has bad parent %x\n", e, ec, e.parent);
944		if(ec.prev != nil)
945			err += sys->sprint("node %x contents %x has non-nil prev %x\n", e, ec, e.prev);
946		p := ec;
947		for(h := ec.next; h != nil; h = h.next) {
948			if(h.prev != p)
949				err += sys->sprint("node %x comes after %x, but prev is %x\n", h, p, h.prev);
950			if(h.parent != nil)
951				err += sys->sprint("node %x, not first in siblings, has parent %x\n", h, h.parent);
952			p = h;
953		}
954		for(h = ec; h != nil; h = h.next) {
955			err2 := checke(h);
956			if(err2 != nil)
957				err += err2;
958		}
959	}
960	return err;
961}
962
963# Translation to Latex
964
965# state bits
966SLT, SLB, SLI, SLS6, SLS8, SLS12, SLS16, SLE, SLO, SLF : con (1<<iota);
967
968SLFONTMASK : con SLT|SLB|SLI|SLS6|SLS8|SLS12|SLS16;
969SLSIZEMASK : con SLS6|SLS8|SLS12|SLS16;
970
971# fonttag-to-state-bit table
972lftagtostate := array[NFONTTAG] of {
973	Roman*NSIZE+Size6 => SLS6,
974	Roman*NSIZE+Size8 => SLS8,
975	Roman*NSIZE+Size10 => 0,
976	Roman*NSIZE+Size12 => SLS12,
977	Roman*NSIZE+Size16 => SLS16,
978	Italic*NSIZE+Size6 => SLI | SLS6,
979	Italic*NSIZE+Size8 => SLI | SLS8,
980	Italic*NSIZE+Size10 => SLI,
981	Italic*NSIZE+Size12 => SLI | SLS12,
982	Italic*NSIZE+Size16 => SLI | SLS16,
983	Bold*NSIZE+Size6 => SLB | SLS6,
984	Bold*NSIZE+Size8 => SLB | SLS8,
985	Bold*NSIZE+Size10 => SLB,
986	Bold*NSIZE+Size12 => SLB | SLS12,
987	Bold*NSIZE+Size16 => SLB | SLS16,
988	Type*NSIZE+Size6 => SLT | SLS6,
989	Type*NSIZE+Size8 => SLT | SLS8,
990	Type*NSIZE+Size10 => SLT,
991	Type*NSIZE+Size12 => SLT | SLS12,
992	Type*NSIZE+Size16 => SLT | SLS16
993};
994
995lsizecmd := array[] of { "\\footnotesize", "\\small", "\\normalsize", "\\large", "\\Large"};
996llinepos : int;
997lslidenum : int;
998LTABSIZE : con 4;
999
1000latexconv(e: ref Celem)
1001{
1002	initasciitrans(ltranstab);
1003
1004	case fmt {
1005	FLatex or FLatexProc =>
1006		if(fmt == FLatex) {
1007			fout.puts("\\documentclass{article}\n");
1008			fout.puts("\\def\\encodingdefault{T1}\n");
1009		}
1010		else {
1011			fout.puts("\\documentclass[10pt,twocolumn]{article}\n");
1012			fout.puts("\\def\\encodingdefault{T1}\n");
1013			fout.puts("\\usepackage{latex8}\n");
1014			fout.puts("\\bibliographystyle{latex8}\n");
1015		}
1016		fout.puts("\\usepackage{times}\n");
1017		fout.puts("\\usepackage{brutus}\n");
1018		fout.puts("\\usepackage{unicode}\n");
1019		fout.puts("\\usepackage{epsf}\n");
1020		title := lfindtitle(e);
1021		authors := lfindauthors(e);
1022		abstract := lfindabstract(e);
1023		fout.puts("\\begin{document}\n");
1024		if(title != nil) {
1025			fout.puts("\\title{");
1026			llinepos = 0;
1027			lconvl(title, 0);
1028			fout.puts("}\n");
1029			if(authors != nil) {
1030				fout.puts("\\author{");
1031				for(l := authors; l != nil; l = tl l) {
1032					llinepos = 0;
1033					lconvl(hd l, SLO|SLI);
1034					if(tl l != nil)
1035						fout.puts("\n\\and\n");
1036				}
1037				fout.puts("}\n");
1038			}
1039			fout.puts("\\maketitle\n");
1040		}
1041		fout.puts("\\pagestyle{empty}\\thispagestyle{empty}\n");
1042		if(abstract != nil) {
1043			if(fmt == FLatexProc) {
1044				fout.puts("\\begin{abstract}\n");
1045				llinepos = 0;
1046				lconvl(abstract, 0);
1047				fout.puts("\\end{abstract}\n");
1048			}
1049			else {
1050				fout.puts("\\section*{Abstract}\n");
1051				llinepos = 0;
1052				lconvl(abstract, 0);
1053			}
1054		}
1055	FLatexBook =>
1056		fout.puts("\\documentclass{ibook}\n");
1057		fout.puts("\\usepackage{brutus}\n");
1058		fout.puts("\\usepackage{epsf}\n");
1059		fout.puts("\\begin{document}\n");
1060	FLatexSlides =>
1061		fout.puts("\\documentclass[portrait]{seminar}\n");
1062		fout.puts("\\def\\encodingdefault{T1}\n");
1063		fout.puts("\\usepackage{times}\n");
1064		fout.puts("\\usepackage{brutus}\n");
1065		fout.puts("\\usepackage{unicode}\n");
1066		fout.puts("\\usepackage{epsf}\n");
1067		fout.puts("\\centerslidesfalse\n");
1068		fout.puts("\\slideframe{none}\n");
1069		fout.puts("\\slidestyle{empty}\n");
1070		fout.puts("\\pagestyle{empty}\n");
1071		fout.puts("\\begin{document}\n");
1072		lslidenum = 0;
1073	}
1074
1075	llinepos = 0;
1076	if(e.tag == SGML)
1077		lconvl(e.contents, 0);
1078
1079	if(fmt == FLatexSlides && lslidenum > 0)
1080		fout.puts("\\vfill\\end{slide*}\n");
1081	if(fmt != FLatexPart)
1082		fout.puts("\\end{document}\n");
1083}
1084
1085lconvl(el: ref Celem, state: int)
1086{
1087	for(e := el; e != nil; e = e.next) {
1088		tag := e.tag;
1089		op := "";
1090		cl := "";
1091		parlike := 1;
1092		nstate := state;
1093		if(tag < NFONTTAG) {
1094			parlike = 0;
1095			ss := lftagtostate[tag];
1096			if((state & SLFONTMASK) != ss) {
1097				t := state & SLT;
1098				b := state & SLB;
1099				i := state & SLI;
1100				newt := ss & SLT;
1101				newb := ss & SLB;
1102				newi := ss & SLI;
1103				op = "{";
1104				cl = "}";
1105				if(t && !newt)
1106					op += "\\rmfamily";
1107				else if(!t && newt)
1108					op += "\\ttfamily";
1109				if(b && !newb)
1110					op += "\\mdseries";
1111				else if(!b && newb)
1112					op += "\\bfseries";
1113				if(i && !newi)
1114					op += "\\upshape";
1115				else if(!i && newi) {
1116					op += "\\itshape";
1117					bc := lastchar(e.contents);
1118					ac := firstchar(e.next);
1119					if(bc != -1 && bc != ' ' && bc != '\n' && ac != -1 && ac != '.' && ac != ',')
1120						cl = "\\/}";
1121				}
1122				if((state & SLSIZEMASK) != (ss & SLSIZEMASK)) {
1123					nsize := 2;
1124					if(ss & SLS6)
1125						nsize = 0;
1126					else if(ss & SLS8)
1127						nsize = 1;
1128					else if(ss & SLS12)
1129						nsize = 3;
1130					else if(ss & SLS16)
1131						nsize = 4;
1132					# examples shrunk one size
1133					if((state & SLE) && nsize > 0)
1134							nsize--;
1135					op += lsizecmd[nsize];
1136				}
1137				fc := firstchar(e.contents);
1138				if(fc == ' ')
1139					op += "{}";
1140				else
1141					op += " ";
1142				nstate = (state & ~SLFONTMASK) | ss;
1143			}
1144		}
1145		else
1146			case tag {
1147			Text =>
1148				parlike = 0;
1149				if(state & SLO) {
1150					asciitrans[' '] = "\\ ";
1151					asciitrans['\n'] = "\\\\\n";
1152				}
1153				s := e.s;
1154				n := len s;
1155				for(k := 0; k < n; k++) {
1156					c := s[k];
1157					x := "";
1158					if(c < 128)
1159						x = asciitrans[c];
1160					else
1161						x = tlookup(ltranstab, c);
1162					if(x == "") {
1163						fout.putc(c);
1164						if(c == '\n')
1165							llinepos = 0;
1166						else
1167							llinepos++;
1168					}
1169					else {
1170						# split up ligatures
1171						if(c == '-' && k < n-1 && s[k+1] == '-')
1172								x = "-{}";
1173						# Avoid the 'no line to end here' latex error
1174						if((state&SLO) && c == '\n' && llinepos == 0)
1175								fout.puts("\\ ");
1176						else if((state&SLO) && c == '\t') {
1177							nspace := LTABSIZE - llinepos%LTABSIZE;
1178							llinepos += nspace;
1179							while(nspace-- > 0)
1180								fout.puts("\\ ");
1181
1182						}
1183						else {
1184							fout.puts(x);
1185							if(x[len x - 1] == '\n')
1186								llinepos = 0;
1187							else
1188								llinepos++;
1189						}
1190					}
1191				}
1192				if(state & SLO) {
1193					asciitrans[' '] = nil;
1194					asciitrans['\n'] = nil;
1195				}
1196			Example =>
1197				if(!(state&SLE)) {
1198					op = "\\begin{example}";
1199					cl = "\\end{example}\\noindent ";
1200					nstate |= SLE | SLO;
1201				}
1202			List =>
1203				(n, bigle) := lfindbigle(e.contents);
1204				if(n <= 2) {
1205					op = "\\begin{itemize}\n";
1206					cl = "\\end{itemize}";
1207				}
1208				else {
1209					fout.puts("\\begin{itemizew}{");
1210					lconvl(bigle.contents, nstate);
1211					op = "}\n";
1212					cl = "\\end{itemizew}";
1213				}
1214			Listelem =>
1215				op = "\\item[{";
1216				cl = "}]";
1217			Heading =>
1218				if(fmt == FLatexProc)
1219					op = "\n\\Section{";
1220				else
1221					op = "\n\\section{";
1222				cl = "}\n";
1223				nstate = (state & ~SLFONTMASK) | (SLB | SLS12);
1224			Nofill =>
1225				op = "\\begin{nofill}";
1226				cl = "\\end{nofill}\\noindent ";
1227				nstate |= SLO;
1228			Title =>
1229				if(fmt == FLatexSlides) {
1230					op = "\\begin{slide*}\n" +
1231						"\\begin{center}\\Large\\bfseries ";
1232					if(lslidenum > 0)
1233						op = "\\vfill\\end{slide*}\n" + op;
1234					cl = "\\end{center}\n";
1235					lslidenum++;
1236				}
1237				else {
1238					if(stringof(e.contents) == "Index") {
1239						op = "\\printindex\n";
1240						e.contents = nil;
1241					}
1242					else {
1243						op = "\\chapter{";
1244						cl = "}\n";
1245					}
1246				}
1247				nstate = (state & ~SLFONTMASK) | (SLB | SLS16);
1248			Par =>
1249				op = "\n\\par\n";
1250				while(e.next != nil && e.next.tag == Par)
1251					e = e.next;
1252			Extension =>
1253				e.contents = convextension(e.s);
1254				if(e.contents != nil)
1255					e.contents.parent = e;
1256			Special =>
1257				fout.puts(e.s);
1258			Float =>
1259				if(!(state&SLF)) {
1260					isfig := lfixfloat(e);
1261					if(isfig) {
1262						op = "\\begin{figure}\\begin{center}\\leavevmode ";
1263						cl = "\\end{center}\\end{figure}";
1264					}
1265					else {
1266						op = "\\begin{table}\\begin{center}\\leavevmode ";
1267						cl = "\\end{center}\\end{table}";
1268					}
1269					nstate |= SLF;
1270				}
1271			Caption=>
1272				if(state&SLF) {
1273					op = "\\caption{";
1274					cl = "}";
1275					nstate = (state & ~SLFONTMASK) | SLS8;
1276				}
1277				else {
1278					op = "\\begin{center}";
1279					cl = "\\end{center}";
1280				}
1281			Label or Labelref =>
1282				parlike = 0;
1283				if(tag == Label)
1284					op = "\\label";
1285				else
1286					op = "\\ref";
1287				cl = "{" + stringof(e.contents) + "}";
1288				e.contents = nil;
1289			Exercise =>
1290				lfixexercise(e);
1291				op = "\\begin{exercise}";
1292				cl = "\\end{exercise}";
1293			Index or Indextopic =>
1294				parlike = 0;
1295				if(tag == Index)
1296					lconvl(e.contents, nstate);
1297				fout.puts("\\showidx{");
1298				lconvl(e.contents, nstate);
1299				fout.puts("}");
1300				lconvindex(e.contents, nstate);
1301				e.contents = nil;
1302			}
1303		if(op != "")
1304			fout.puts(op);
1305		if(e.contents != nil) {
1306			if(parlike)
1307				llinepos = 0;
1308			lconvl(e.contents, nstate);
1309			if(parlike)
1310				llinepos = 0;
1311		}
1312		if(cl != "")
1313			fout.puts(cl);
1314	}
1315}
1316
1317lfixfloat(e: ref Celem) : int
1318{
1319	dropwhite(e.contents);
1320	fstart := e.contents;
1321	fend := last(fstart);
1322	hasfig := 0;
1323	hastab := 0;
1324	if(fend.tag == Caption) {
1325		dropwhite(fend.prev);
1326		if(fend.prev != nil && stringof(fstart) == "\t")
1327			delete(fend.prev);
1328		# If fend.contents is "YYY " <Label> "." rest
1329		# where YYY is Figure or Table,
1330		# then replace it with just rest, and move <Label>
1331		# after the caption.
1332		# Probably should be more robust about what to accept.
1333		ec := fend.contents;
1334		s := stringof(ec);
1335		if(s == "Figure ")
1336			hasfig = 1;
1337		else if(s == "Table ")
1338			hastab = 1;
1339		if(hasfig || hastab) {
1340			ec2 := ec.next;
1341			ec3 : ref Celem = nil;
1342			ec4 : ref Celem = nil;
1343			if(ec2 != nil && ec2.tag == Label) {
1344				ec3 = ec2.next;
1345				if(ec3 != nil && stringof(ec3) == ".")
1346					ec4 = ec3.next;
1347			}
1348			if(ec4 != nil) {
1349				dropwhite(ec4);
1350				ec4 = ec3.next;
1351				if(ec4 != nil) {
1352					excise(ec);
1353					excise(ec2);
1354					excise(ec3);
1355					fend.contents = ec4;
1356					ec4.parent = fend;
1357					insertafter(ec2, fend);
1358				}
1359			}
1360		}
1361	}
1362	return !hastab;
1363}
1364
1365lfixexercise(e: ref Celem)
1366{
1367	dropwhite(e.contents);
1368	ec := e.contents;
1369	# Expect:
1370	#     "Exercise " <Label> ":" rest
1371	#  If so, drop the first and third.
1372	# Or
1373	#	"Exercise:" rest
1374	# If so, drop the first.
1375	s := stringof(ec);
1376	if(s == "Exercise ") {
1377		ec2 := ec.next;
1378		ec3 : ref Celem = nil;
1379		ec4 : ref Celem = nil;
1380			if(ec2 != nil && ec2.tag == Label) {
1381				ec3 = ec2.next;
1382				if(ec3 != nil && stringof(ec3) == ":")
1383					ec4 = ec3.next;
1384			}
1385			if(ec4 != nil) {
1386				dropwhite(ec4);
1387				ec4 = ec3.next;
1388				if(ec4 != nil) {
1389					excise(ec);
1390					excise(ec3);
1391					e.contents = ec2;
1392					ec2.parent = e;
1393					ec2.next = ec4;
1394					ec4.prev = ec2;
1395				}
1396			}
1397	}
1398	else if(s == "Exercise:") {
1399		dropwhite(ec.next);
1400		e.contents = ec.next;
1401		excise(ec);
1402		if(e.contents != nil)
1403			e.contents.parent = e;
1404	}
1405}
1406
1407# convert content list headed by e to \\index{...}
1408lconvindex(e: ref Celem, state: int)
1409{
1410	fout.puts("\\index{");
1411	g := lsplitind(e);
1412	gp := g;
1413	needat := 0;
1414	while(g != nil) {
1415		gnext := g.next;
1416		s := stringof(g);
1417		if(s == "!" || s == "|") {
1418			if(gp != g) {
1419				g.next = nil;
1420				g.s = "";
1421				lprintindsort(gp);
1422				if(needat) {
1423					fout.puts("@");
1424					lconvl(gp, state);
1425				}
1426			}
1427			fout.puts(s);
1428			gp = gnext;
1429			needat = 0;
1430			if(s == "|") {
1431				if(g == nil)
1432					break;
1433				g = gnext;
1434				# don't lconvl the Text items, so
1435				# that "see{" and "}" come out untranslated.
1436				# (code is wrong if stuff inside see is plain
1437				# text but with special tex characters)
1438				while(g != nil) {
1439					gnext = g.next;
1440					g.next = nil;
1441					if(g.tag != Text)
1442						lconvl(g, state);
1443					else
1444						fout.puts(g.s);
1445					g = gnext;
1446				}
1447				gp = nil;
1448				break;
1449			}
1450		}
1451		else {
1452			if(g.tag != Text)
1453				needat = 1;
1454		}
1455		g = gnext;
1456	}
1457	if(gp != nil) {
1458		lprintindsort(gp);
1459		if(needat) {
1460			fout.puts("@");
1461			lconvl(gp, state);
1462		}
1463	}
1464	fout.puts("}");
1465}
1466
1467lprintindsort(e: ref Celem)
1468{
1469	while(e != nil) {
1470		fout.puts(stringof(e));
1471		e = e.next;
1472	}
1473}
1474
1475# return copy of e
1476lsplitind(e: ref Celem) : ref Celem
1477{
1478	dummy := ref Celem;
1479	for( ; e != nil; e = e.next) {
1480		te := e;
1481		if(e.tag < NFONTTAG)
1482			te = te.contents;
1483		if(te.tag != Text)
1484			continue;
1485		s := te.s;
1486		i := 0;
1487		for(j := 0; j < len s; j++) {
1488			if(s[j] == '!' || s[j] == '|') {
1489				if(j > i) {
1490					nte := ref Celem(Text, s[i:j], nil, nil, nil, nil);
1491					if(e == te)
1492						ne := nte;
1493					else
1494						ne = ref Celem(e.tag, nil, nte, nil, nil, nil);
1495					append(dummy, ne);
1496				}
1497				append(dummy, ref Celem(Text, s[j:j+1], nil, nil, nil, nil));
1498				i = j+1;
1499			}
1500		}
1501		if(j > i) {
1502			nte := ref Celem(Text, s[i:j], nil, nil, nil, nil);
1503			if(e == te)
1504				ne := nte;
1505			else
1506				ne = ref Celem(e.tag, nil, nte, nil, nil, nil);
1507			append(dummy, ne);
1508		}
1509	}
1510	return dummy.next;
1511}
1512
1513# return key part of an index entry corresponding to e list
1514indexkey(e: ref Celem) : string
1515{
1516	s := "";
1517	while(e != nil) {
1518		s += stringof(e);
1519		e = e.next;
1520	}
1521	return s;
1522}
1523
1524# find title, excise it from e, and return contents as list
1525lfindtitle(e: ref Celem) : ref Celem
1526{
1527	if(e.tag == Title) {
1528		ans := e.contents;
1529		delete(e);
1530		return ans;
1531	}
1532	else if (e.contents != nil) {
1533		for(h := e.contents; h != nil; h = h.next) {
1534			a := lfindtitle(h);
1535			if(a != nil)
1536				return a;
1537		}
1538	}
1539	return nil;
1540}
1541
1542# find authors, excise them from e, and return as list of lists
1543lfindauthors(e: ref Celem) : list of ref Celem
1544{
1545	if(e.tag == Author) {
1546		a := e.contents;
1547		en := e.next;
1548		delete(e);
1549		rans : list of ref Celem = a :: nil;
1550		if(en != nil) {
1551			e = en;
1552			while(e != nil) {
1553				if(e.tag == Par) {
1554					en = e.next;
1555					if(en.tag == Author) {
1556						delete(e);
1557						a = en.contents;
1558						for(y := a; y != nil; ) {
1559							yn := y.next;
1560							if(y.tag == Par)
1561								delete(y);
1562							y = yn;
1563						}
1564						e = en.next;
1565						delete(en);
1566						rans = a :: rans;
1567					}
1568					else
1569						break;
1570				}
1571				else
1572					break;
1573			}
1574		}
1575		ans : list of ref Celem = nil;
1576		while(rans != nil) {
1577			ans = hd rans :: ans;
1578			rans = tl rans;
1579		}
1580		return ans;
1581	}
1582	else if (e.contents != nil) {
1583		for(h := e.contents; h != nil; h = h.next) {
1584			a := lfindauthors(h);
1585			if(a != nil)
1586				return a;
1587		}
1588	}
1589	return nil;
1590}
1591
1592# find section called abstract, excise it from e, and return as list
1593lfindabstract(e: ref Celem) : ref Celem
1594{
1595	if(e.tag == Heading) {
1596		c := e.contents;
1597		if(c.tag == Text && c.s == "Abstract") {
1598			for(h2 := e.next; h2 != nil; h2 = h2.next) {
1599				if(h2.tag == Heading)
1600					break;
1601			}
1602			ans := e.next;
1603			ans.prev = nil;
1604			ep := e.prev;
1605			eu := e.parent;
1606			if(ep == nil) {
1607				if(eu != nil)
1608					eu.contents = h2;
1609				if(h2 != nil)
1610					h2.parent = eu;
1611			}
1612			else
1613				ep.next = h2;
1614			if(h2 != nil) {
1615				ansend := h2.prev;
1616				ansend.next = nil;
1617				h2.prev = ep;
1618			}
1619			return ans;
1620		}
1621	}
1622	else if (e.contents != nil) {
1623		for(h := e.contents; h != nil; h = h.next) {
1624			a := lfindabstract(h);
1625			if(a != nil)
1626				return a;
1627		}
1628	}
1629	return nil;
1630}
1631
1632# find biggest list element with longest contents in e list
1633lfindbigle(e: ref Celem) : (int, ref Celem)
1634{
1635	ans : ref Celem = nil;
1636	maxlen := 0;
1637	for(h := e; h != nil; h = h.next) {
1638		if(h.tag == Listelem) {
1639			n := 0;
1640			for(p := h.contents; p != nil; p = p.next) {
1641				if(p.tag == Text)
1642					n += len p.s;
1643				else if(p.tag < NFONTTAG) {
1644					q := p.contents;
1645					if(q.tag == Text)
1646						n += len q.s;
1647				}
1648			}
1649			if(n > maxlen) {
1650				maxlen = n;
1651				ans = h;
1652			}
1653		}
1654	}
1655	return (maxlen, ans);
1656}
1657
1658# Translation to HTML
1659
1660# state bits
1661SHA, SHO, SHFL, SHDT: con (1<<iota);
1662
1663htmlconv(e: ref Celem)
1664{
1665	initasciitrans(htranstab);
1666
1667	fout.puts("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n");
1668	fout.puts("<HTML>\n");
1669
1670	if(e.tag == SGML) {
1671		# Conforming 3.2 documents require a Title.
1672		# Use the Title tag both for the document title and
1673		# for an H1-level heading.
1674		# (SHDT state bit enforces: Font change markup, etc., not allowed in Title)
1675		fout.puts("<TITLE>\n");
1676		title := hfindtitle(e);
1677		if(title != nil)
1678			hconvl(title.contents, SHDT);
1679		else if(infilename != "")
1680			fout.puts(infilename);
1681		else
1682			fout.puts("An HTML document");
1683		fout.puts("</TITLE>\n");
1684		fout.puts("<BODY>\n");
1685		hconvl(e.contents, 0);
1686		fout.puts("</BODY>\n");
1687	}
1688
1689	fout.puts("</HTML>\n");
1690}
1691
1692hconvl(el: ref Celem, state: int)
1693{
1694	for(e := el; e != nil; e = e.next) {
1695		tag := e.tag;
1696		op := "";
1697		cl := "";
1698		nstate := state;
1699		if(tag == Text) {
1700			s := e.s;
1701			n := len s;
1702			for(k := 0; k < n; k++) {
1703				c := s[k];
1704				x := "";
1705				if(c < 128) {
1706					if(c == '\n' && (state&SHO))
1707						x = "\n\t";
1708					else
1709						x = asciitrans[c];
1710				}
1711				else
1712					x = tlookup(htranstab, c);
1713				if(x == "")
1714					fout.putc(c);
1715				else
1716					fout.puts(x);
1717			}
1718		}
1719		else if(!(state&SHDT))
1720			case tag {
1721			Roman*NSIZE+Size6 =>
1722				op = "<FONT SIZE=1>";
1723				cl = "</FONT>";
1724				nstate |= SHA;
1725			Roman*NSIZE+Size8 =>
1726				op = "<FONT SIZE=2>";
1727				cl = "</FONT>";
1728				nstate |= SHA;
1729			Roman*NSIZE+Size10 =>
1730				if(state & SHA) {
1731					op = "<FONT SIZE=3>";
1732					cl = "</FONT>";
1733					nstate &= ~SHA;
1734				}
1735			Roman*NSIZE+Size12 =>
1736				op = "<FONT SIZE=4>";
1737				cl = "</FONT>";
1738				nstate |= SHA;
1739			Roman*NSIZE+Size16 =>
1740				op = "<FONT SIZE=5>";
1741				cl = "</FONT>";
1742				nstate |= SHA;
1743			Italic*NSIZE+Size6 =>
1744				op = "<I><FONT SIZE=1>";
1745				cl = "</FONT></I>";
1746				nstate |= SHA;
1747			Italic*NSIZE+Size8 =>
1748				op = "<I><FONT SIZE=2>";
1749				cl = "</FONT></I>";
1750				nstate |= SHA;
1751			Italic*NSIZE+Size10 =>
1752				if(state & SHA) {
1753					op =  "<I><FONT SIZE=3>";
1754					cl = "</FONT></I>";
1755					nstate &= ~SHA;
1756				}
1757				else {
1758					op = "<I>";
1759					cl = "</I>";
1760				}
1761			Italic*NSIZE+Size12 =>
1762				op = "<I><FONT SIZE=4>";
1763				cl = "</FONT></I>";
1764				nstate |= SHA;
1765			Italic*NSIZE+Size16 =>
1766				op = "<I><FONT SIZE=5>";
1767				cl = "</FONT></I>";
1768				nstate |= SHA;
1769			Bold*NSIZE+Size6 =>
1770				op = "<B><FONT SIZE=1>";
1771				cl = "</FONT></B>";
1772				nstate |= SHA;
1773			Bold*NSIZE+Size8 =>
1774				op = "<B><FONT SIZE=2>";
1775				cl = "</FONT></B>";
1776				nstate |= SHA;
1777			Bold*NSIZE+Size10 =>
1778				if(state & SHA) {
1779					op =  "<B><FONT SIZE=3>";
1780					cl = "</FONT></B>";
1781					nstate &= ~SHA;
1782				}
1783				else {
1784					op = "<B>";
1785					cl = "</B>";
1786				}
1787			Bold*NSIZE+Size12 =>
1788				op = "<B><FONT SIZE=4>";
1789				cl = "</FONT></B>";
1790				nstate |= SHA;
1791			Bold*NSIZE+Size16 =>
1792				op = "<B><FONT SIZE=5>";
1793				cl = "</FONT></B>";
1794				nstate |= SHA;
1795			Type*NSIZE+Size6 =>
1796				op = "<TT><FONT SIZE=1>";
1797				cl = "</FONT></TT>";
1798				nstate |= SHA;
1799			Type*NSIZE+Size8 =>
1800				op = "<TT><FONT SIZE=2>";
1801				cl = "</FONT></TT>";
1802				nstate |= SHA;
1803			Type*NSIZE+Size10 =>
1804				if(state & SHA) {
1805					op =  "<TT><FONT SIZE=3>";
1806					cl = "</FONT></TT>";
1807					nstate &= ~SHA;
1808				}
1809				else {
1810					op = "<TT>";
1811					cl = "</TT>";
1812				}
1813			Type*NSIZE+Size12 =>
1814				op = "<TT><FONT SIZE=4>";
1815				cl = "</FONT></TT>";
1816				nstate |= SHA;
1817			Type*NSIZE+Size16 =>
1818				op = "<TT><FONT SIZE=5>";
1819				cl = "</FONT></TT>";
1820				nstate |= SHA;
1821			Example =>
1822				op = "<P><PRE>\t";
1823				cl = "</PRE><P>\n";
1824				nstate |= SHO;
1825			List =>
1826				op = "<DL>";
1827				cl = "</DD></DL>";
1828				nstate |= SHFL;
1829			Listelem =>
1830				if(state & SHFL)
1831					op = "<DT>";
1832				else
1833					op = "</DD><DT>";
1834				cl = "</DT><DD>";
1835				# change first-list-elem state for this level
1836				state &= ~SHFL;
1837			Heading =>
1838				op = "<H2>";
1839				cl = "</H2>\n";
1840			Nofill =>
1841				op = "<P><PRE>";
1842				cl = "</PRE>";
1843			Title =>
1844				op = "<H1>";
1845				cl = "</H1>\n";
1846			Par =>
1847				op = "<P>\n";
1848			Extension =>
1849				e.contents = convextension(e.s);
1850			Special =>
1851				fout.puts(e.s);
1852			}
1853		if(op != "")
1854			fout.puts(op);
1855		hconvl(e.contents, nstate);
1856		if(cl != "")
1857			fout.puts(cl);
1858	}
1859}
1860
1861# find title, if there is one, and return it (but leave it in contents too)
1862hfindtitle(e: ref Celem) : ref Celem
1863{
1864	if(e.tag == Title)
1865		return e;
1866	else if (e.contents != nil) {
1867		for(h := e.contents; h != nil; h = h.next) {
1868			a := hfindtitle(h);
1869			if(a != nil)
1870				return a;
1871		}
1872	}
1873	return nil;
1874}
1875
1876Exten: adt
1877{
1878	name: string;
1879	mod: Brutusext;
1880};
1881
1882extens: list of Exten = nil;
1883
1884convextension(s: string) : ref Celem
1885{
1886	for(i:=0; i<len s; i++)
1887		if(s[i] == ' ')
1888			break;
1889	if(i == len s) {
1890		sys->fprint(stderr, "badly formed extension %s\n", s);
1891		return nil;
1892	}
1893	modname := s[0:i];
1894	s = s[i+1:];
1895	mod: Brutusext = nil;
1896	for(le := extens; le != nil; le = tl le) {
1897		el := hd le;
1898		if(el.name == modname)
1899			mod = el.mod;
1900	}
1901	if(mod == nil) {
1902		file := modname;
1903		if(i < 4 || file[i-4:i] != ".dis")
1904			file += ".dis";
1905		if(file[0] != '/')
1906			file = "/dis/wm/brutus/" + file;
1907		mod = load Brutusext file;
1908		if(mod == nil) {
1909			sys->fprint(stderr, "can't load extension module %s: %r\n", file);
1910			return nil;
1911		}
1912		mod->init(sys, draw, B, tk, nil);
1913		extens = Exten(modname, mod) :: extens;
1914	}
1915	f := infilename;
1916	if(f == "<stdin>")
1917		f = "";
1918	(ans, err) := mod->cook(f, fmt, s);
1919	if(err != "") {
1920		sys->fprint(stderr, "extension module %s cook error: %s\n", modname, err);
1921		return nil;
1922	}
1923	return ans;
1924}
1925