ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/cmd/cook.b/
implement Cook;
include "sys.m";
sys: Sys;
FD: import Sys;
include "draw.m";
draw: Draw;
include "bufio.m";
B: Bufio;
Iobuf: import B;
include "string.m";
S: String;
splitl, splitr, splitstrl, drop, take, in, prefix, tolower : import S;
include "brutus.m";
Size6, Size8, Size10, Size12, Size16, NSIZE,
Roman, Italic, Bold, Type, NFONT, NFONTTAG,
Example, Caption, List, Listelem, Label, Labelref,
Exercise, Heading, Nofill, Author, Title,
Index, Indextopic,
DefFont, DefSize, TitleFont, TitleSize, HeadingFont, HeadingSize: import Brutus;
# following are needed for types in brutusext.m
include "tk.m";
tk: Tk;
include "tkclient.m";
include "brutusext.m";
SGML, Text, Par, Extension, Float, Special, Celem,
FLatex, FLatexProc, FLatexBook, FLatexPart, FLatexSlides, FHtml: import Brutusext;
include "strinttab.m";
T: StringIntTab;
Cook: module
{
init: fn(ctxt: ref Draw->Context, args: list of string);
};
# keep this sorted by name
tagstringtab := array[] of { T->StringInt
("Author", Author),
("Bold.10", Bold*NSIZE + Size10),
("Bold.12", Bold*NSIZE + Size12),
("Bold.16", Bold*NSIZE + Size16),
("Bold.6", Bold*NSIZE + Size6),
("Bold.8", Bold*NSIZE + Size8),
("Caption", Caption),
("Example", Example),
("Exercise", Exercise),
("Extension", Extension),
("Float", Float),
("Heading", Heading),
("Index", Index),
("Index-topic", Indextopic),
("Italic.10", Italic*NSIZE + Size10),
("Italic.12", Italic*NSIZE + Size12),
("Italic.16", Italic*NSIZE + Size16),
("Italic.6", Italic*NSIZE + Size6),
("Italic.8", Italic*NSIZE + Size8),
("Label", Label),
("Label-ref", Labelref),
("List", List),
("List-elem", Listelem),
("No-fill", Nofill),
("Par", Par),
("Roman.10", Roman*NSIZE + Size10),
("Roman.12", Roman*NSIZE + Size12),
("Roman.16", Roman*NSIZE + Size16),
("Roman.6", Roman*NSIZE + Size6),
("Roman.8", Roman*NSIZE + Size8),
("SGML", SGML),
("Title", Title),
("Type.10", Type*NSIZE + Size10),
("Type.12", Type*NSIZE + Size12),
("Type.16", Type*NSIZE + Size16),
("Type.6", Type*NSIZE + Size6),
("Type.8", Type*NSIZE + Size8),
};
# This table must be sorted
fmtstringtab := array[] of { T->StringInt
("html", FHtml),
("latex", FLatex),
("latexbook", FLatexBook),
("latexpart", FLatexPart),
("latexproc", FLatexProc),
("latexslides", FLatexSlides),
};
Transtab: adt
{
ch: int;
trans: string;
};
# Order doesn't matter for these table
ltranstab := array[] of { Transtab
('$', "\\textdollar{}"),
('&', "\\&"),
('%', "\\%"),
('#', "\\#"),
('_', "\\textunderscore{}"),
('{', "\\{"),
('}', "\\}"),
('~', "\\textasciitilde{}"),
('^', "\\textasciicircum{}"),
('\\', "\\textbackslash{}"),
('+', "\\textplus{}"),
('=', "\\textequals{}"),
('|', "\\textbar{}"),
('<', "\\textless{}"),
('>', "\\textgreater{}"),
(' ', "~"),
('-', "-"), # needs special case ligature treatment
('\t', " "), # needs special case treatment
};
htranstab := array[] of { Transtab
('α', "α"),
('Æ', "Æ"),
('Á', "Á"),
('Â', "Â"),
('À', "À"),
('Å', "Å"),
('Ã', "Ã"),
('Ä', "Ä"),
('Ç', "Ç"),
('Ð', "Ð"),
('É', "É"),
('Ê', "Ê"),
('È', "È"),
('Ë', "Ë"),
('Í', "Í"),
('Î', "Î"),
('Ì', "Ì"),
('Ï', "Ï"),
('Ñ', "Ñ"),
('Ó', "Ó"),
('Ô', "Ô"),
('Ò', "Ò"),
('Ø', "Ø"),
('Õ', "Õ"),
('Ö', "Ö"),
('Þ', "Þ"),
('Ú', "Ú"),
('Û', "Û"),
('Ù', "Ù"),
('Ü', "Ü"),
('Ý', "Ý"),
('æ', "&aElig;"),
('á', "á"),
('â', "â"),
('à', "à"),
('α', "α"),
('&', "&"),
('å', "å"),
('ã', "ã"),
('ä', "ä"),
('β', "β"),
('ç', "ç"),
('⋯', "&cdots;"),
('χ', "χ"),
('©', "©"),
('⋱', "&ddots;"),
('δ', "δ"),
('é', "é"),
('ê', "ê"),
('è', "è"),
('—', "&emdash;"),
(' ', " "),
('–', "&endash;"),
('ε', "ε"),
('η', "η"),
('ð', "ð"),
('ë', "ë"),
('γ', "γ"),
('>', ">"),
('í', "í"),
('î', "î"),
('ì', "ì"),
('ι', "ι"),
('ï', "ï"),
('κ', "κ"),
('λ', "λ"),
('…', "&ldots;"),
('<', "<"),
('μ', "μ"),
(' ', " "),
('ñ', "ñ"),
('ν', "ν"),
('ó', "ó"),
('ô', "ô"),
('ò', "ò"),
('ω', "ω"),
('ο', "ο"),
('ø', "ø"),
('õ', "õ"),
('ö', "ö"),
('φ', "φ"),
('π', "π"),
('ψ', "ψ"),
(' ', "&quad;"),
('"', """),
('®', "®"),
('ρ', "ρ"),
('', "­"),
('σ', "σ"),
('ß', "ß"),
('τ', "τ"),
('θ', "θ"),
(' ', " "),
('þ', "þ"),
('™', "™"),
('ú', "ú"),
('û', "û"),
('ù', "ù"),
('υ', "υ"),
('ü', "ü"),
('∈', "ϵ"),
('ϕ', "ϕ"),
('ϖ', "ϖ"),
('ϱ', "ϱ"),
('⋮', "&vdots;"),
('ς', "&vsigma;"),
('ϑ', "&vtheta;"),
('ξ', "ξ"),
('ý', "ý"),
('ÿ', "ÿ"),
('ζ', "ζ"),
('−', "-"),
};
# For speedy lookups of ascii char translation, use asciitrans.
# It should be initialized by ascii elements from one of above tables
asciitrans := array[128] of string;
stderr: ref FD;
infilename := "";
outfilename := "";
linenum := 0;
fin : ref Iobuf = nil;
fout : ref Iobuf = nil;
debug := 0;
fmt := FLatex;
init(nil: ref Draw->Context, argv: list of string)
{
sys = load Sys Sys->PATH;
S = load String String->PATH;
B = load Bufio Bufio->PATH;
draw = load Draw Draw->PATH;
tk = load Tk Tk->PATH;
T = load StringIntTab StringIntTab->PATH;
stderr = sys->fildes(2);
for(argv = tl argv; argv != nil; ) {
s := hd argv;
tlargv := tl argv;
case s {
"-f" =>
if(tlargv == nil)
usage();
fnd: int;
(fnd, fmt) = T->lookup(fmtstringtab, hd(tlargv));
if(!fnd) {
sys->fprint(stderr, "unknown format: %s\n", hd(tlargv));
exit;
}
argv = tlargv;
"-o" =>
if(tlargv == nil)
usage();
outfilename = hd(tlargv);
argv = tlargv;
"-d" =>
debug = 1;
"-dd" =>
debug = 2;
* =>
if(tlargv == nil)
infilename = s;
else
usage();
}
argv = tl argv;
}
if(infilename == "") {
fin = B->fopen(sys->fildes(0), sys->OREAD);
infilename = "<stdin>";
}
else
fin = B->open(infilename, sys->OREAD);
if(fin == nil) {
sys->fprint(stderr, "cook: error opening %s: %r\n", infilename);
exit;
}
if(outfilename == "") {
fout = B->fopen(sys->fildes(1), sys->OWRITE);
outfilename = "<stdout>";
}
else
fout = B->create(outfilename, sys->OWRITE, 8r664);
if(fout == nil) {
sys->fprint(stderr, "cook: error creating %s: %r\n", outfilename);
exit;
}
line0 := fin.gets('\n');
if(line0 != "<SGML>\n") {
parse_err("not an SGML file\n");
exit;
}
linenum = 1;
e := parse(SGML);
findpars(e, 1, nil);
e = delemptystrs(e);
(e, nil) = canonfonts(e, DefFont*NSIZE+DefSize, DefFont*NSIZE+DefSize);
mergeadjs(e);
findfloats(e);
cleanexts(e);
cleanpars(e);
if(debug) {
fout.puts("After Initial transformations:\n");
printelem(e, "", 1);
fout.flush();
}
case fmt {
FLatex or FLatexProc or FLatexBook or FLatexPart or FLatexSlides =>
latexconv(e);
FHtml =>
htmlconv(e);
}
fin.close();
fout.close();
}
usage()
{
sys->fprint(stderr, "Usage: cook [-f (latex|html)] [-o outfile] [infile]\n");
exit;
}
parse_err(msg: string)
{
sys->fprint(stderr, "%s:%d: %s\n", infilename, linenum, msg);
}
# Parse into elements.
# Assumes tags are balanced.
# String elements are split so that there is never an internal newline.
parse(id: int) : ref Celem
{
els : ref Celem = nil;
elstail : ref Celem = nil;
for(;;) {
c := fin.getc();
if(c == Bufio->EOF) {
if(id == SGML)
break;
else {
parse_err(sys->sprint("EOF while parsing %s", tagname(id)));
return nil;
}
}
if(c == '<') {
tag := "";
start := 1;
i := 0;
for(;;) {
c = fin.getc();
if(c == Bufio->EOF) {
parse_err("EOF in middle of tag");
return nil;
}
if(c == '\n') {
linenum++;
parse_err("newline in middle of tag");
break;
}
if(c == '>')
break;
if(i == 0 && c == '/')
start = 0;
else
tag[i++] = c;
}
(fnd, tid) := T->lookup(tagstringtab, tag);
if(!fnd) {
if(prefix("Extension ", tag)) {
el := ref Celem(Extension, tag[10:], nil, nil, nil, nil);
if(els == nil) {
els = el;
elstail = el;
}
else {
el.prev = elstail;
elstail.next = el;
elstail = el;
}
}
else
parse_err(sys->sprint("unknown tag <%s>\n", tag));
continue;
}
if(start) {
el := parse(tid);
if(el == nil)
return nil;
if(els == nil) {
els = el;
elstail = el;
}
else {
el.prev = elstail;
elstail.next = el;
elstail = el;
}
}
else {
if(tid != id) {
parse_err(sys->sprint("<%s> ended by </%s>",
tagname(id), tag));
continue;
}
break;
}
}
else {
s := "";
i := 0;
for(;;) {
if(c == Bufio->EOF)
break;
if(c == '<') {
fin.ungetc();
break;
}
if(c == ';' && i >=3 && s[i-1] == 't' && s[i-2] == 'l' && s[i-3] == '&') {
i -= 2;
s[i-1] = '<';
s = s[0:i];
}
else
s[i++] = c;
if(c == '\n') {
linenum++;
break;
}
else
c = fin.getc();
}
if(s != "") {
el := ref Celem(Text, s, nil, nil, nil, nil);
if(els == nil) {
els = el;
elstail = el;
}
else {
el.prev = elstail;
elstail.next = el;
elstail = el;
}
}
}
}
ans := ref Celem(id, "", els, nil, nil, nil);
if(els != nil)
els.parent = ans;
return ans;
}
# Modify tree e so that blank lines become Par elements.
# Only do it if parize is set, and unset parize when descending into TExample's.
# Pass in most recent TString or TPar element, and return updated most-recent-TString/TPar.
# This function may set some TString strings to ""
findpars(e: ref Celem, parize: int, prevspe: ref Celem) : ref Celem
{
while(e != nil) {
prevnl := 0;
prevpar := 0;
if(prevspe != nil) {
if(prevspe.tag == Text && len prevspe.s != 0
&& prevspe.s[(len prevspe.s)-1] == '\n')
prevnl = 1;
else if(prevspe.tag == Par)
prevpar = 1;
}
if(e.tag == Text) {
if(parize && (prevnl || prevpar) && e.s[0] == '\n') {
if(prevnl)
prevspe.s = prevspe.s[0 : (len prevspe.s)-1];
e.tag = Par;
e.s = nil;
}
prevspe = e;
}
else {
nparize := parize;
if(e.tag == Example)
nparize = 0;
prevspe = findpars(e.contents, nparize, prevspe);
}
e = e.next;
}
return prevspe;
}
# Delete any empty strings from e's tree and return modified e.
# Also, delete any entity that has empty contents, except the
# Par ones
delemptystrs(e: ref Celem) : ref Celem
{
if(e.tag == Text) {
if(e.s == "")
return nil;
else
return e;
}
if(e.tag == Par || e.tag == Extension || e.tag == Special)
return e;
h := e.contents;
while(h != nil) {
hnext := h.next;
hh := delemptystrs(h);
if(hh == nil)
delete(h);
h = hnext;
}
if(e.contents == nil)
return nil;
return e;
}
# Change tree under e so that any font elems contain only strings
# (by pushing the font changes down).
# Answer an be a list, so return beginning and end of list.
# Leave strings bare if font change would be to deffont,
# and adjust deffont appropriately when entering Title and
# Heading environments.
canonfonts(e: ref Celem, curfont, deffont: int) : (ref Celem, ref Celem)
{
f := curfont;
head : ref Celem = nil;
tail : ref Celem = nil;
tocombine : ref Celem = nil;
if(e.tag == Text) {
if(f == deffont) {
head = e;
tail = e;
}
else {
head = ref Celem(f, nil, e, nil, nil, nil);
e.parent = head;
tail = head;
}
}
else if(e.contents == nil) {
head = e;
tail = e;
}
else if(e.tag < NFONTTAG) {
f = e.tag;
allstrings := 1;
for(g := e.contents; g != nil; g = g.next) {
if(g.tag != Text)
allstrings = 0;
tail = g;
}
if(allstrings) {
if(f == deffont)
head = e.contents;
else {
head = e;
tail = e;
}
}
}
if(head == nil) {
if(e.tag == Title)
deffont = TitleFont*NSIZE+TitleSize;
else if(e.tag == Heading)
deffont = HeadingFont*NSIZE+HeadingSize;
for(h := e.contents; h != nil; ) {
prev := h.prev;
next := h.next;
excise(h);
(e1, en) := canonfonts(h, f, deffont);
splicebetween(e1, en, prev, next);
if(prev == nil)
head = e1;
tail = en;
h = next;
}
tocombine = head;
if(e.tag >= NFONTTAG) {
e.contents = head;
head.parent = e;
head = e;
tail = e;
}
}
if(tocombine != nil) {
# combine adjacent font changes to same font
r := tocombine;
while(r != nil) {
if(r.tag < NFONTTAG && r.next != nil && r.next.tag == r.tag) {
for(v := r.next; v != nil; v = v.next) {
if(v.tag != r.tag)
break;
if(v == tail)
tail = r;
}
# now r up to, not including v, all change to same font
for(p := r.next; p != v; p = p.next) {
append(r.contents, p.contents);
}
r.next = v;
if(v != nil)
v.prev = r;
r = v;
}
else
r = r.next;
}
}
head.parent = nil;
return (head, tail);
}
# Remove Pars that appear just before or just after Heading, Title, Examples, Extensions
# Really should worry about this happening at different nesting levels, but in
# practice this happens all at the same nesting level
cleanpars(e: ref Celem)
{
for(h := e.contents; h != nil; h = h.next) {
cleanpars(h);
if(h.tag == Title || h.tag == Heading || h.tag == Example || h.tag == Extension) {
hp := h.prev;
hn := h.next;
if(hp !=nil && hp.tag == Par)
delete(hp);
if(hn != nil && hn.tag == Par)
delete(hn);
}
}
}
# Remove a single tab if it appears before an Extension
cleanexts(e: ref Celem)
{
for(h := e.contents; h != nil; h = h.next) {
cleanexts(h);
if(h.tag == Extension) {
hp := h.prev;
if(hp != nil && stringof(hp) == "\t")
delete(hp);
}
}
}
mergeable := array[] of { List, Exercise, Caption,Index, Indextopic };
# Merge some adjacent elements (which were probably created separate
# because of font changes)
mergeadjs(e: ref Celem)
{
for(h := e.contents; h != nil; h = h.next) {
hn := h.next;
domerge := 0;
if(hn != nil) {
for(i := 0; i < len mergeable; i++) {
mi := mergeable[i];
if(h.tag == mi && hn.tag == mi)
domerge = 1;
}
}
if(domerge) {
append(h.contents, hn.contents);
delete(hn);
}
else
mergeadjs(h);
}
}
# Find floats: they are paragraphs with Captions at the end.
findfloats(e: ref Celem)
{
lastpar : ref Celem = nil;
for(h := e.contents; h != nil; h = h.next) {
if(h.tag == Par)
lastpar = h;
else if(h.tag == Caption) {
ne := ref Celem(Float, "", nil, nil, nil, nil);
if(lastpar == nil)
flhead := e.contents;
else
flhead = lastpar.next;
insertbefore(ne, flhead);
# now move flhead ... h into contents of ne
ne.contents = flhead;
flhead.parent = ne;
flhead.prev = nil;
ne.next = h.next;
if(ne.next != nil)
ne.next.prev = ne;
h.next = nil;
h = ne;
}
else
findfloats(h);
}
}
insertbefore(e, ebefore: ref Celem)
{
e.prev = ebefore.prev;
if(e.prev == nil) {
e.parent = ebefore.parent;
ebefore.parent = nil;
e.parent.contents = e;
}
else
e.prev.next = e;
e.next = ebefore;
ebefore.prev = e;
}
insertafter(e, eafter: ref Celem)
{
e.next = eafter.next;
if(e.next != nil)
e.next.prev = e;
e.prev = eafter;
eafter.next = e;
}
# remove e from its list, leaving siblings disconnected
excise(e: ref Celem)
{
next := e. next;
prev := e.prev;
e.next = nil;
e.prev = nil;
if(prev != nil)
prev.next = nil;
if(next != nil)
next.prev = nil;
e.parent = nil;
}
splicebetween(e1, en, prev, next: ref Celem)
{
if(prev != nil)
prev.next = e1;
e1.prev = prev;
en.next = next;
if(next != nil)
next.prev = en;
}
append(e1, e2: ref Celem)
{
e1last := last(e1);
e1last.next = e2;
e2.prev = e1last;
e2.parent = nil;
}
last(e: ref Celem) : ref Celem
{
if(e != nil)
while(e.next != nil)
e = e.next;
return e;
}
succ(e: ref Celem) : ref Celem
{
if(e == nil)
return nil;
if(e.next != nil)
return e.next;
return succ(e.parent);
}
delete(e: ref Celem)
{
ep := e.prev;
en := e.next;
eu := e.parent;
if(ep == nil) {
if(eu != nil)
eu.contents = en;
if(en != nil)
en.parent = eu;
}
else
ep.next = en;
if(en != nil)
en.prev = ep;
}
# return string represented by e, peering through font changes
stringof(e: ref Celem) : string
{
if(e != nil) {
if(e.tag == Text)
return e.s;
if(e.tag < NFONTTAG)
return stringof(e.contents);
}
return "";
}
# remove any initial whitespace from e and its sucessors,
dropwhite(e: ref Celem)
{
if(e == nil)
return;
del := 0;
if(e.tag == Text) {
e.s = drop(e.s, " \t\n");
if(e.s == "")
del = 1;;
}
else if(e.tag < NFONTTAG) {
dropwhite(e.contents);
if(e.contents == nil)
del = 1;
}
if(del) {
enext := e.next;
delete(e);
dropwhite(enext);
}
}
firstchar(e: ref Celem) : int
{
s := stringof(e);
if(len s >= 1)
return s[0];
return -1;
}
lastchar(e: ref Celem) : int
{
if(e == nil)
return -1;
while(e.next != nil)
e = e.next;
s := stringof(e);
if(len s >= 1)
return s[len s -1];
return -1;
}
tlookup(t: array of Transtab, v: int) : string
{
n := len t;
for(i := 0; i < n; i++)
if(t[i].ch == v)
return t[i].trans;
return "";
}
initasciitrans(t: array of Transtab)
{
n := len t;
for(i := 0; i < n; i++) {
c := t[i].ch;
if(c < 128)
asciitrans[c] = t[i].trans;
}
}
tagname(id: int) : string
{
name := T->revlookup(tagstringtab, id);
if(name == nil)
name = "_unknown_";
return name;
}
printelem(e: ref Celem, indent: string, recurse: int)
{
fout.puts(indent);
if(debug > 1) {
fout.puts(sys->sprint("%x: ", e));
if(e != nil && e.parent != nil)
fout.puts(sys->sprint("(parent %x): ", e.parent));
}
if(e == nil)
fout.puts("NIL\n");
else if(e.tag == Text || e.tag == Special || e.tag == Extension) {
if(e.tag == Special)
fout.puts("S");
else if(e.tag == Extension)
fout.puts("E");
fout.puts("«");
fout.puts(e.s);
fout.puts("»\n");
}
else {
name := tagname(e.tag);
fout.puts("<" + name + ">\n");
if(recurse && e.contents != nil)
printelems(e.contents, indent + " ", recurse);
}
}
printelems(els: ref Celem, indent: string, recurse: int)
{
for(; els != nil; els = els.next)
printelem(els, indent, recurse);
}
check(e: ref Celem, msg: string)
{
err := checke(e);
if(err != "") {
fout.puts(msg + ": tree is inconsistent:\n" + err);
printelem(e, "", 1);
fout.flush();
exit;
}
}
checke(e: ref Celem) : string
{
err := "";
if(e.tag == SGML && e.next != nil)
err = sys->sprint("root %x has a next field\n", e);
ec := e.contents;
if(ec != nil) {
if(ec.parent != e)
err += sys->sprint("node %x contents %x has bad parent %x\n", e, ec, e.parent);
if(ec.prev != nil)
err += sys->sprint("node %x contents %x has non-nil prev %x\n", e, ec, e.prev);
p := ec;
for(h := ec.next; h != nil; h = h.next) {
if(h.prev != p)
err += sys->sprint("node %x comes after %x, but prev is %x\n", h, p, h.prev);
if(h.parent != nil)
err += sys->sprint("node %x, not first in siblings, has parent %x\n", h, h.parent);
p = h;
}
for(h = ec; h != nil; h = h.next) {
err2 := checke(h);
if(err2 != nil)
err += err2;
}
}
return err;
}
# Translation to Latex
# state bits
SLT, SLB, SLI, SLS6, SLS8, SLS12, SLS16, SLE, SLO, SLF : con (1<<iota);
SLFONTMASK : con SLT|SLB|SLI|SLS6|SLS8|SLS12|SLS16;
SLSIZEMASK : con SLS6|SLS8|SLS12|SLS16;
# fonttag-to-state-bit table
lftagtostate := array[NFONTTAG] of {
Roman*NSIZE+Size6 => SLS6,
Roman*NSIZE+Size8 => SLS8,
Roman*NSIZE+Size10 => 0,
Roman*NSIZE+Size12 => SLS12,
Roman*NSIZE+Size16 => SLS16,
Italic*NSIZE+Size6 => SLI | SLS6,
Italic*NSIZE+Size8 => SLI | SLS8,
Italic*NSIZE+Size10 => SLI,
Italic*NSIZE+Size12 => SLI | SLS12,
Italic*NSIZE+Size16 => SLI | SLS16,
Bold*NSIZE+Size6 => SLB | SLS6,
Bold*NSIZE+Size8 => SLB | SLS8,
Bold*NSIZE+Size10 => SLB,
Bold*NSIZE+Size12 => SLB | SLS12,
Bold*NSIZE+Size16 => SLB | SLS16,
Type*NSIZE+Size6 => SLT | SLS6,
Type*NSIZE+Size8 => SLT | SLS8,
Type*NSIZE+Size10 => SLT,
Type*NSIZE+Size12 => SLT | SLS12,
Type*NSIZE+Size16 => SLT | SLS16
};
lsizecmd := array[] of { "\\footnotesize", "\\small", "\\normalsize", "\\large", "\\Large"};
llinepos : int;
lslidenum : int;
LTABSIZE : con 4;
latexconv(e: ref Celem)
{
initasciitrans(ltranstab);
case fmt {
FLatex or FLatexProc =>
if(fmt == FLatex) {
fout.puts("\\documentclass{article}\n");
fout.puts("\\def\\encodingdefault{T1}\n");
}
else {
fout.puts("\\documentclass[10pt,twocolumn]{article}\n");
fout.puts("\\def\\encodingdefault{T1}\n");
fout.puts("\\usepackage{latex8}\n");
fout.puts("\\bibliographystyle{latex8}\n");
}
fout.puts("\\usepackage{times}\n");
fout.puts("\\usepackage{brutus}\n");
fout.puts("\\usepackage{unicode}\n");
fout.puts("\\usepackage{epsf}\n");
title := lfindtitle(e);
authors := lfindauthors(e);
abstract := lfindabstract(e);
fout.puts("\\begin{document}\n");
if(title != nil) {
fout.puts("\\title{");
llinepos = 0;
lconvl(title, 0);
fout.puts("}\n");
if(authors != nil) {
fout.puts("\\author{");
for(l := authors; l != nil; l = tl l) {
llinepos = 0;
lconvl(hd l, SLO|SLI);
if(tl l != nil)
fout.puts("\n\\and\n");
}
fout.puts("}\n");
}
fout.puts("\\maketitle\n");
}
fout.puts("\\pagestyle{empty}\\thispagestyle{empty}\n");
if(abstract != nil) {
if(fmt == FLatexProc) {
fout.puts("\\begin{abstract}\n");
llinepos = 0;
lconvl(abstract, 0);
fout.puts("\\end{abstract}\n");
}
else {
fout.puts("\\section*{Abstract}\n");
llinepos = 0;
lconvl(abstract, 0);
}
}
FLatexBook =>
fout.puts("\\documentclass{ibook}\n");
fout.puts("\\usepackage{brutus}\n");
fout.puts("\\usepackage{epsf}\n");
fout.puts("\\begin{document}\n");
FLatexSlides =>
fout.puts("\\documentclass[portrait]{seminar}\n");
fout.puts("\\def\\encodingdefault{T1}\n");
fout.puts("\\usepackage{times}\n");
fout.puts("\\usepackage{brutus}\n");
fout.puts("\\usepackage{unicode}\n");
fout.puts("\\usepackage{epsf}\n");
fout.puts("\\centerslidesfalse\n");
fout.puts("\\slideframe{none}\n");
fout.puts("\\slidestyle{empty}\n");
fout.puts("\\pagestyle{empty}\n");
fout.puts("\\begin{document}\n");
lslidenum = 0;
}
llinepos = 0;
if(e.tag == SGML)
lconvl(e.contents, 0);
if(fmt == FLatexSlides && lslidenum > 0)
fout.puts("\\vfill\\end{slide*}\n");
if(fmt != FLatexPart)
fout.puts("\\end{document}\n");
}
lconvl(el: ref Celem, state: int)
{
for(e := el; e != nil; e = e.next) {
tag := e.tag;
op := "";
cl := "";
parlike := 1;
nstate := state;
if(tag < NFONTTAG) {
parlike = 0;
ss := lftagtostate[tag];
if((state & SLFONTMASK) != ss) {
t := state & SLT;
b := state & SLB;
i := state & SLI;
newt := ss & SLT;
newb := ss & SLB;
newi := ss & SLI;
op = "{";
cl = "}";
if(t && !newt)
op += "\\rmfamily";
else if(!t && newt)
op += "\\ttfamily";
if(b && !newb)
op += "\\mdseries";
else if(!b && newb)
op += "\\bfseries";
if(i && !newi)
op += "\\upshape";
else if(!i && newi) {
op += "\\itshape";
bc := lastchar(e.contents);
ac := firstchar(e.next);
if(bc != -1 && bc != ' ' && bc != '\n' && ac != -1 && ac != '.' && ac != ',')
cl = "\\/}";
}
if((state & SLSIZEMASK) != (ss & SLSIZEMASK)) {
nsize := 2;
if(ss & SLS6)
nsize = 0;
else if(ss & SLS8)
nsize = 1;
else if(ss & SLS12)
nsize = 3;
else if(ss & SLS16)
nsize = 4;
# examples shrunk one size
if((state & SLE) && nsize > 0)
nsize--;
op += lsizecmd[nsize];
}
fc := firstchar(e.contents);
if(fc == ' ')
op += "{}";
else
op += " ";
nstate = (state & ~SLFONTMASK) | ss;
}
}
else
case tag {
Text =>
parlike = 0;
if(state & SLO) {
asciitrans[' '] = "\\ ";
asciitrans['\n'] = "\\\\\n";
}
s := e.s;
n := len s;
for(k := 0; k < n; k++) {
c := s[k];
x := "";
if(c < 128)
x = asciitrans[c];
else
x = tlookup(ltranstab, c);
if(x == "") {
fout.putc(c);
if(c == '\n')
llinepos = 0;
else
llinepos++;
}
else {
# split up ligatures
if(c == '-' && k < n-1 && s[k+1] == '-')
x = "-{}";
# Avoid the 'no line to end here' latex error
if((state&SLO) && c == '\n' && llinepos == 0)
fout.puts("\\ ");
else if((state&SLO) && c == '\t') {
nspace := LTABSIZE - llinepos%LTABSIZE;
llinepos += nspace;
while(nspace-- > 0)
fout.puts("\\ ");
}
else {
fout.puts(x);
if(x[len x - 1] == '\n')
llinepos = 0;
else
llinepos++;
}
}
}
if(state & SLO) {
asciitrans[' '] = nil;
asciitrans['\n'] = nil;
}
Example =>
if(!(state&SLE)) {
op = "\\begin{example}";
cl = "\\end{example}\\noindent ";
nstate |= SLE | SLO;
}
List =>
(n, bigle) := lfindbigle(e.contents);
if(n <= 2) {
op = "\\begin{itemize}\n";
cl = "\\end{itemize}";
}
else {
fout.puts("\\begin{itemizew}{");
lconvl(bigle.contents, nstate);
op = "}\n";
cl = "\\end{itemizew}";
}
Listelem =>
op = "\\item[{";
cl = "}]";
Heading =>
if(fmt == FLatexProc)
op = "\n\\Section{";
else
op = "\n\\section{";
cl = "}\n";
nstate = (state & ~SLFONTMASK) | (SLB | SLS12);
Nofill =>
op = "\\begin{nofill}";
cl = "\\end{nofill}\\noindent ";
nstate |= SLO;
Title =>
if(fmt == FLatexSlides) {
op = "\\begin{slide*}\n" +
"\\begin{center}\\Large\\bfseries ";
if(lslidenum > 0)
op = "\\vfill\\end{slide*}\n" + op;
cl = "\\end{center}\n";
lslidenum++;
}
else {
if(stringof(e.contents) == "Index") {
op = "\\printindex\n";
e.contents = nil;
}
else {
op = "\\chapter{";
cl = "}\n";
}
}
nstate = (state & ~SLFONTMASK) | (SLB | SLS16);
Par =>
op = "\n\\par\n";
while(e.next != nil && e.next.tag == Par)
e = e.next;
Extension =>
e.contents = convextension(e.s);
if(e.contents != nil)
e.contents.parent = e;
Special =>
fout.puts(e.s);
Float =>
if(!(state&SLF)) {
isfig := lfixfloat(e);
if(isfig) {
op = "\\begin{figure}\\begin{center}\\leavevmode ";
cl = "\\end{center}\\end{figure}";
}
else {
op = "\\begin{table}\\begin{center}\\leavevmode ";
cl = "\\end{center}\\end{table}";
}
nstate |= SLF;
}
Caption=>
if(state&SLF) {
op = "\\caption{";
cl = "}";
nstate = (state & ~SLFONTMASK) | SLS8;
}
else {
op = "\\begin{center}";
cl = "\\end{center}";
}
Label or Labelref =>
parlike = 0;
if(tag == Label)
op = "\\label";
else
op = "\\ref";
cl = "{" + stringof(e.contents) + "}";
e.contents = nil;
Exercise =>
lfixexercise(e);
op = "\\begin{exercise}";
cl = "\\end{exercise}";
Index or Indextopic =>
parlike = 0;
if(tag == Index)
lconvl(e.contents, nstate);
fout.puts("\\showidx{");
lconvl(e.contents, nstate);
fout.puts("}");
lconvindex(e.contents, nstate);
e.contents = nil;
}
if(op != "")
fout.puts(op);
if(e.contents != nil) {
if(parlike)
llinepos = 0;
lconvl(e.contents, nstate);
if(parlike)
llinepos = 0;
}
if(cl != "")
fout.puts(cl);
}
}
lfixfloat(e: ref Celem) : int
{
dropwhite(e.contents);
fstart := e.contents;
fend := last(fstart);
hasfig := 0;
hastab := 0;
if(fend.tag == Caption) {
dropwhite(fend.prev);
if(fend.prev != nil && stringof(fstart) == "\t")
delete(fend.prev);
# If fend.contents is "YYY " <Label> "." rest
# where YYY is Figure or Table,
# then replace it with just rest, and move <Label>
# after the caption.
# Probably should be more robust about what to accept.
ec := fend.contents;
s := stringof(ec);
if(s == "Figure ")
hasfig = 1;
else if(s == "Table ")
hastab = 1;
if(hasfig || hastab) {
ec2 := ec.next;
ec3 : ref Celem = nil;
ec4 : ref Celem = nil;
if(ec2 != nil && ec2.tag == Label) {
ec3 = ec2.next;
if(ec3 != nil && stringof(ec3) == ".")
ec4 = ec3.next;
}
if(ec4 != nil) {
dropwhite(ec4);
ec4 = ec3.next;
if(ec4 != nil) {
excise(ec);
excise(ec2);
excise(ec3);
fend.contents = ec4;
ec4.parent = fend;
insertafter(ec2, fend);
}
}
}
}
return !hastab;
}
lfixexercise(e: ref Celem)
{
dropwhite(e.contents);
ec := e.contents;
# Expect:
# "Exercise " <Label> ":" rest
# If so, drop the first and third.
# Or
# "Exercise:" rest
# If so, drop the first.
s := stringof(ec);
if(s == "Exercise ") {
ec2 := ec.next;
ec3 : ref Celem = nil;
ec4 : ref Celem = nil;
if(ec2 != nil && ec2.tag == Label) {
ec3 = ec2.next;
if(ec3 != nil && stringof(ec3) == ":")
ec4 = ec3.next;
}
if(ec4 != nil) {
dropwhite(ec4);
ec4 = ec3.next;
if(ec4 != nil) {
excise(ec);
excise(ec3);
e.contents = ec2;
ec2.parent = e;
ec2.next = ec4;
ec4.prev = ec2;
}
}
}
else if(s == "Exercise:") {
dropwhite(ec.next);
e.contents = ec.next;
excise(ec);
if(e.contents != nil)
e.contents.parent = e;
}
}
# convert content list headed by e to \\index{...}
lconvindex(e: ref Celem, state: int)
{
fout.puts("\\index{");
g := lsplitind(e);
gp := g;
needat := 0;
while(g != nil) {
gnext := g.next;
s := stringof(g);
if(s == "!" || s == "|") {
if(gp != g) {
g.next = nil;
g.s = "";
lprintindsort(gp);
if(needat) {
fout.puts("@");
lconvl(gp, state);
}
}
fout.puts(s);
gp = gnext;
needat = 0;
if(s == "|") {
if(g == nil)
break;
g = gnext;
# don't lconvl the Text items, so
# that "see{" and "}" come out untranslated.
# (code is wrong if stuff inside see is plain
# text but with special tex characters)
while(g != nil) {
gnext = g.next;
g.next = nil;
if(g.tag != Text)
lconvl(g, state);
else
fout.puts(g.s);
g = gnext;
}
gp = nil;
break;
}
}
else {
if(g.tag != Text)
needat = 1;
}
g = gnext;
}
if(gp != nil) {
lprintindsort(gp);
if(needat) {
fout.puts("@");
lconvl(gp, state);
}
}
fout.puts("}");
}
lprintindsort(e: ref Celem)
{
while(e != nil) {
fout.puts(stringof(e));
e = e.next;
}
}
# return copy of e
lsplitind(e: ref Celem) : ref Celem
{
dummy := ref Celem;
for( ; e != nil; e = e.next) {
te := e;
if(e.tag < NFONTTAG)
te = te.contents;
if(te.tag != Text)
continue;
s := te.s;
i := 0;
for(j := 0; j < len s; j++) {
if(s[j] == '!' || s[j] == '|') {
if(j > i) {
nte := ref Celem(Text, s[i:j], nil, nil, nil, nil);
if(e == te)
ne := nte;
else
ne = ref Celem(e.tag, nil, nte, nil, nil, nil);
append(dummy, ne);
}
append(dummy, ref Celem(Text, s[j:j+1], nil, nil, nil, nil));
i = j+1;
}
}
if(j > i) {
nte := ref Celem(Text, s[i:j], nil, nil, nil, nil);
if(e == te)
ne := nte;
else
ne = ref Celem(e.tag, nil, nte, nil, nil, nil);
append(dummy, ne);
}
}
return dummy.next;
}
# return key part of an index entry corresponding to e list
indexkey(e: ref Celem) : string
{
s := "";
while(e != nil) {
s += stringof(e);
e = e.next;
}
return s;
}
# find title, excise it from e, and return contents as list
lfindtitle(e: ref Celem) : ref Celem
{
if(e.tag == Title) {
ans := e.contents;
delete(e);
return ans;
}
else if (e.contents != nil) {
for(h := e.contents; h != nil; h = h.next) {
a := lfindtitle(h);
if(a != nil)
return a;
}
}
return nil;
}
# find authors, excise them from e, and return as list of lists
lfindauthors(e: ref Celem) : list of ref Celem
{
if(e.tag == Author) {
a := e.contents;
en := e.next;
delete(e);
rans : list of ref Celem = a :: nil;
if(en != nil) {
e = en;
while(e != nil) {
if(e.tag == Par) {
en = e.next;
if(en.tag == Author) {
delete(e);
a = en.contents;
for(y := a; y != nil; ) {
yn := y.next;
if(y.tag == Par)
delete(y);
y = yn;
}
e = en.next;
delete(en);
rans = a :: rans;
}
else
break;
}
else
break;
}
}
ans : list of ref Celem = nil;
while(rans != nil) {
ans = hd rans :: ans;
rans = tl rans;
}
return ans;
}
else if (e.contents != nil) {
for(h := e.contents; h != nil; h = h.next) {
a := lfindauthors(h);
if(a != nil)
return a;
}
}
return nil;
}
# find section called abstract, excise it from e, and return as list
lfindabstract(e: ref Celem) : ref Celem
{
if(e.tag == Heading) {
c := e.contents;
if(c.tag == Text && c.s == "Abstract") {
for(h2 := e.next; h2 != nil; h2 = h2.next) {
if(h2.tag == Heading)
break;
}
ans := e.next;
ans.prev = nil;
ep := e.prev;
eu := e.parent;
if(ep == nil) {
if(eu != nil)
eu.contents = h2;
if(h2 != nil)
h2.parent = eu;
}
else
ep.next = h2;
if(h2 != nil) {
ansend := h2.prev;
ansend.next = nil;
h2.prev = ep;
}
return ans;
}
}
else if (e.contents != nil) {
for(h := e.contents; h != nil; h = h.next) {
a := lfindabstract(h);
if(a != nil)
return a;
}
}
return nil;
}
# find biggest list element with longest contents in e list
lfindbigle(e: ref Celem) : (int, ref Celem)
{
ans : ref Celem = nil;
maxlen := 0;
for(h := e; h != nil; h = h.next) {
if(h.tag == Listelem) {
n := 0;
for(p := h.contents; p != nil; p = p.next) {
if(p.tag == Text)
n += len p.s;
else if(p.tag < NFONTTAG) {
q := p.contents;
if(q.tag == Text)
n += len q.s;
}
}
if(n > maxlen) {
maxlen = n;
ans = h;
}
}
}
return (maxlen, ans);
}
# Translation to HTML
# state bits
SHA, SHO, SHFL, SHDT: con (1<<iota);
htmlconv(e: ref Celem)
{
initasciitrans(htranstab);
fout.puts("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n");
fout.puts("<HTML>\n");
if(e.tag == SGML) {
# Conforming 3.2 documents require a Title.
# Use the Title tag both for the document title and
# for an H1-level heading.
# (SHDT state bit enforces: Font change markup, etc., not allowed in Title)
fout.puts("<TITLE>\n");
title := hfindtitle(e);
if(title != nil)
hconvl(title.contents, SHDT);
else if(infilename != "")
fout.puts(infilename);
else
fout.puts("An HTML document");
fout.puts("</TITLE>\n");
fout.puts("<BODY>\n");
hconvl(e.contents, 0);
fout.puts("</BODY>\n");
}
fout.puts("</HTML>\n");
}
hconvl(el: ref Celem, state: int)
{
for(e := el; e != nil; e = e.next) {
tag := e.tag;
op := "";
cl := "";
nstate := state;
if(tag == Text) {
s := e.s;
n := len s;
for(k := 0; k < n; k++) {
c := s[k];
x := "";
if(c < 128) {
if(c == '\n' && (state&SHO))
x = "\n\t";
else
x = asciitrans[c];
}
else
x = tlookup(htranstab, c);
if(x == "")
fout.putc(c);
else
fout.puts(x);
}
}
else if(!(state&SHDT))
case tag {
Roman*NSIZE+Size6 =>
op = "<FONT SIZE=1>";
cl = "</FONT>";
nstate |= SHA;
Roman*NSIZE+Size8 =>
op = "<FONT SIZE=2>";
cl = "</FONT>";
nstate |= SHA;
Roman*NSIZE+Size10 =>
if(state & SHA) {
op = "<FONT SIZE=3>";
cl = "</FONT>";
nstate &= ~SHA;
}
Roman*NSIZE+Size12 =>
op = "<FONT SIZE=4>";
cl = "</FONT>";
nstate |= SHA;
Roman*NSIZE+Size16 =>
op = "<FONT SIZE=5>";
cl = "</FONT>";
nstate |= SHA;
Italic*NSIZE+Size6 =>
op = "<I><FONT SIZE=1>";
cl = "</FONT></I>";
nstate |= SHA;
Italic*NSIZE+Size8 =>
op = "<I><FONT SIZE=2>";
cl = "</FONT></I>";
nstate |= SHA;
Italic*NSIZE+Size10 =>
if(state & SHA) {
op = "<I><FONT SIZE=3>";
cl = "</FONT></I>";
nstate &= ~SHA;
}
else {
op = "<I>";
cl = "</I>";
}
Italic*NSIZE+Size12 =>
op = "<I><FONT SIZE=4>";
cl = "</FONT></I>";
nstate |= SHA;
Italic*NSIZE+Size16 =>
op = "<I><FONT SIZE=5>";
cl = "</FONT></I>";
nstate |= SHA;
Bold*NSIZE+Size6 =>
op = "<B><FONT SIZE=1>";
cl = "</FONT></B>";
nstate |= SHA;
Bold*NSIZE+Size8 =>
op = "<B><FONT SIZE=2>";
cl = "</FONT></B>";
nstate |= SHA;
Bold*NSIZE+Size10 =>
if(state & SHA) {
op = "<B><FONT SIZE=3>";
cl = "</FONT></B>";
nstate &= ~SHA;
}
else {
op = "<B>";
cl = "</B>";
}
Bold*NSIZE+Size12 =>
op = "<B><FONT SIZE=4>";
cl = "</FONT></B>";
nstate |= SHA;
Bold*NSIZE+Size16 =>
op = "<B><FONT SIZE=5>";
cl = "</FONT></B>";
nstate |= SHA;
Type*NSIZE+Size6 =>
op = "<TT><FONT SIZE=1>";
cl = "</FONT></TT>";
nstate |= SHA;
Type*NSIZE+Size8 =>
op = "<TT><FONT SIZE=2>";
cl = "</FONT></TT>";
nstate |= SHA;
Type*NSIZE+Size10 =>
if(state & SHA) {
op = "<TT><FONT SIZE=3>";
cl = "</FONT></TT>";
nstate &= ~SHA;
}
else {
op = "<TT>";
cl = "</TT>";
}
Type*NSIZE+Size12 =>
op = "<TT><FONT SIZE=4>";
cl = "</FONT></TT>";
nstate |= SHA;
Type*NSIZE+Size16 =>
op = "<TT><FONT SIZE=5>";
cl = "</FONT></TT>";
nstate |= SHA;
Example =>
op = "<P><PRE>\t";
cl = "</PRE><P>\n";
nstate |= SHO;
List =>
op = "<DL>";
cl = "</DD></DL>";
nstate |= SHFL;
Listelem =>
if(state & SHFL)
op = "<DT>";
else
op = "</DD><DT>";
cl = "</DT><DD>";
# change first-list-elem state for this level
state &= ~SHFL;
Heading =>
op = "<H2>";
cl = "</H2>\n";
Nofill =>
op = "<P><PRE>";
cl = "</PRE>";
Title =>
op = "<H1>";
cl = "</H1>\n";
Par =>
op = "<P>\n";
Extension =>
e.contents = convextension(e.s);
Special =>
fout.puts(e.s);
}
if(op != "")
fout.puts(op);
hconvl(e.contents, nstate);
if(cl != "")
fout.puts(cl);
}
}
# find title, if there is one, and return it (but leave it in contents too)
hfindtitle(e: ref Celem) : ref Celem
{
if(e.tag == Title)
return e;
else if (e.contents != nil) {
for(h := e.contents; h != nil; h = h.next) {
a := hfindtitle(h);
if(a != nil)
return a;
}
}
return nil;
}
Exten: adt
{
name: string;
mod: Brutusext;
};
extens: list of Exten = nil;
convextension(s: string) : ref Celem
{
for(i:=0; i<len s; i++)
if(s[i] == ' ')
break;
if(i == len s) {
sys->fprint(stderr, "badly formed extension %s\n", s);
return nil;
}
modname := s[0:i];
s = s[i+1:];
mod: Brutusext = nil;
for(le := extens; le != nil; le = tl le) {
el := hd le;
if(el.name == modname)
mod = el.mod;
}
if(mod == nil) {
file := modname;
if(i < 4 || file[i-4:i] != ".dis")
file += ".dis";
if(file[0] != '/')
file = "/dis/wm/brutus/" + file;
mod = load Brutusext file;
if(mod == nil) {
sys->fprint(stderr, "can't load extension module %s: %r\n", file);
return nil;
}
mod->init(sys, draw, B, tk, nil);
extens = Exten(modname, mod) :: extens;
}
f := infilename;
if(f == "<stdin>")
f = "";
(ans, err) := mod->cook(f, fmt, s);
if(err != "") {
sys->fprint(stderr, "extension module %s cook error: %s\n", modname, err);
return nil;
}
return ans;
}