diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
| commit | 37da2899f40661e3e9631e497da8dc59b971cbd0 (patch) | |
| tree | cbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/cook.b | |
| parent | 54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff) | |
20060303a
Diffstat (limited to 'appl/cmd/cook.b')
| -rw-r--r-- | appl/cmd/cook.b | 1924 |
1 files changed, 1924 insertions, 0 deletions
diff --git a/appl/cmd/cook.b b/appl/cmd/cook.b new file mode 100644 index 00000000..0d333a4d --- /dev/null +++ b/appl/cmd/cook.b @@ -0,0 +1,1924 @@ +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; +} |
