ref: df03eca7e7cbfadf1cf8c8c5a94390eaa5013a8b
dir: /appl/lib/parseman.b/
implement Parseman; include "sys.m"; sys: Sys; include "bufio.m"; bufio: Bufio; Iobuf: import bufio; include "man.m"; FONT_LITERAL: con -1; init(): string { sys = load Sys Sys->PATH; bufio = load Bufio Bufio->PATH; if (bufio == nil) return sys->sprint("cannot load module: %r"); return nil; } ParseState: adt[T] for{ T => textwidth: fn(t: self T, text: Text): int; }{ metrics: Metrics; ql: int; # quote Literal text margin: int; mstack: list of int; istack: list of int; indent: int; ntlsetindent: int; #copy prevailindent to indent on n.t.l prevailindent: int; curfont: int; curattr: int; verbatim: int; pspace: int; curline: list of (int, Text); # most recent first curwidth: int; newpara: int; heading: int; igto: string; link: string; viewer: T; setline: chan of list of (int, Text); # addstring() is simply an addtext() of the current font addstring: fn(s: self ref ParseState, s: string); addtext: fn(s: self ref ParseState, t: list of Text); brk: fn(s: self ref ParseState); paragraph: fn( s: self ref ParseState); }; parseman[T](fd: ref Sys->FD, metrics: Metrics, ql: int, viewer: T, setline: chan of list of (int, Text)) for{ T => textwidth: fn(t: self T, text: Text): int; } { iob := bufio->fopen(fd, Sys->OREAD); state := ref ParseState[T](metrics, ql, 0, nil, nil, 0, 0, metrics.indent, FONT_ROMAN, 0, 0, 1, nil, 0, 1, 0, "", nil, viewer, setline); while ((l := iob.gets('\n')) != nil) { if (l[len l -1] == '\n') l = l[0: len l - 1]; if (state.igto != nil && state.igto != l) continue; state.igto = nil; parseline(state, l); } state.pspace = 2; state.pspace = 1; state.paragraph(); footer := Text(FONT_ROMAN, 0, "Inferno Manual", 0, nil); textw := state.viewer.textwidth(footer); #should do 'center' in addtext (state.justify = CENTER) state.indent = (state.metrics.pagew - textw) / 2; state.addtext(footer::nil); state.brk(); setline <- = nil; } parseline[T](state: ref ParseState[T], t: string) for{ T => textwidth: fn(t: self T, text: Text): int; } { if (t == nil) { if (state.verbatim) { blank := Text(state.curfont, state.curattr, "", 0, ""); state.setline <- = (0, blank)::nil; } else state.paragraph(); return; } ntlsetindent := state.ntlsetindent; state.ntlsetindent = 0; if (t[0] == '.' || t[0] == '\'') parsemacro(state, t[1:]); else { state.addtext(parsetext(state, t)); if (state.verbatim) state.brk(); } if (ntlsetindent) { state.indent = state.prevailindent; if (state.curwidth + state.metrics.en > state.indent + state.margin) state.brk(); } } parsemacro[T](state: ref ParseState[T], t: string) for{ T => textwidth: fn(t: self T, text: Text): int; } { for (n := 0; n < len t && n < 2; n++) if (t[n] == ' ' || t[n] == '\t') break; macro := t[0:n]; params: list of string; quote := 0; param := 0; esc := 0; p := ""; for (; n < len t; n++) { if (esc) esc = 0; else { case t[n] { ' ' or '\t' => if (!quote) { if (param) { params = p :: params; p = ""; param = 0; } continue; } '"' => param = 1; quote = !quote; continue; '\\' => esc = 1; } } param = 1; p[len p] = t[n]; } if (param) params = p :: params; plist: list of string; for (; params != nil; params = tl params) plist = hd params :: plist; params = plist; case macro { "ig" => igto := ".."; if (params != nil) igto = "." + hd params; state.brk(); state.igto = igto; "sp" => sp := "1"; if(params != nil) sp = hd params; d := tval(state.metrics, sp, 'v'); gap := d / state.metrics.V; if (gap < 1) gap = 1; while (gap--) state.paragraph(); "br" => state.brk(); "nf" => state.verbatim = 1; "fi" => state.verbatim = 0; "ti" => state.brk(); #i := 0; #if(params != nil) # i = tval(state.metrics, hd params, 'n'); #state.ntlsetindent = 1; #state.prevailindent = i; "in" => state.brk(); #i := 0; #if(params != nil) # i = tval(state.metrics, hd params, 'n'); #state.indent = i; #state.prevailindent = state.indent; "1C" => state.brk(); # not implemented "2C" => state.brk(); # not implemented "BI" => altattr(state, FONT_BOLD, FONT_ITALIC, params); "BR" => altattr(state, FONT_BOLD, FONT_ROMAN, params); "IB" => altattr(state, FONT_ITALIC, FONT_BOLD, params); "IR" => # need to determine link if params of valid form state.link = convlink(params);; altattr(state, FONT_ITALIC, FONT_ROMAN, params); state.link = nil; "RB" => altattr(state, FONT_ROMAN, FONT_BOLD, params); "RI" => altattr(state, FONT_ROMAN, FONT_ITALIC, params); "B" => state.curfont = FONT_BOLD; if (params != nil) { for (; params != nil; params = tl params) { textl := parsetext(state, hd params); for (; textl != nil; textl = tl textl) state.addtext(hd textl::nil); } state.curfont = FONT_ROMAN; } "I" => state.curfont = FONT_ITALIC; if (params != nil) { for (; params != nil; params = tl params) { textl := parsetext(state, hd params); for (; textl != nil; textl = tl textl) state.addtext(hd textl::nil); } state.curfont = FONT_ROMAN; } "SM"=> state.curattr |= ATTR_SMALL; if (params != nil) { for (; params != nil; params = tl params) state.addstring(hd params); state.curattr &= ~ATTR_SMALL; } "L" => state.curfont = FONT_LITERAL; if (params != nil) { str := "`"; for (pl := params; pl != nil;) { str += hd pl; if ((pl = tl pl) != nil) str += " "; else break; } str += "'"; state.addstring(str); state.curfont = FONT_ROMAN; } "LR" => if (params != nil) { l := Text(FONT_LITERAL, state.curattr, hd params, 0, nil); t: list of Text; params = tl params; if (params == nil) t = l :: nil; else { r := Text(FONT_ROMAN, state.curattr, hd params, 0, nil); t = l :: r :: nil; } state.addtext(t); } "RL" => if (params != nil) { r := Text(FONT_ROMAN, state.curattr, hd params, 0, nil); t: list of Text; params = tl params; if (params == nil) t = r :: nil; else { l := Text(FONT_LITERAL, state.curattr, hd params, 0, nil); t = r :: l :: nil; } state.addtext(t); } "DT" => # not yet supported ; "EE" => state.brk(); state.verbatim = 0; state.curfont = FONT_ROMAN; "EX" => state.brk(); state.verbatim = 1; state.curfont = FONT_BOLD; "HP" => state.paragraph(); i := state.metrics.indent; if (params != nil) i = tval(state.metrics, hd params, 'n'); state.prevailindent = state.indent + i; "IP" => state.paragraph(); i := state.metrics.indent; if (params != nil) { tag := hd params; params = tl params; state.addtext(parsetext(state, tag)); if (params != nil) i = tval(state.metrics, hd params, 'n'); } state.indent = state.metrics.indent + i; state.prevailindent = state.indent; "PD" => state.pspace = 1; if (params != nil) { v := tval(state.metrics, hd params, 'v') / state.metrics.V; state.pspace = v; } "LP" or "PP" => state.paragraph(); state.prevailindent = state.indent; "RE" => state.brk(); if (state.mstack == nil || state.istack == nil) break; state.margin = hd state.mstack; state.mstack = tl state.mstack; state.prevailindent = hd state.istack; state.indent = state.prevailindent; state.istack = tl state.istack; "RS" => state.brk(); i := state.prevailindent - state.metrics.indent; if (params != nil) i = tval(state.metrics, hd params, 'n'); state.mstack = state.margin :: state.mstack; state.istack = state.prevailindent :: state.istack; state.margin += i; state.indent = 2 * state.metrics.indent; state.prevailindent = state.indent; "SH" => state.paragraph(); state.prevailindent = state.indent; state.curfont = FONT_ROMAN; state.curattr = 0; state.indent = 0; state.heading = 1; state.verbatim = 0; for (pl := params; pl != nil; pl = tl pl) state.addstring(hd pl); state.heading = 0; state.brk(); state.newpara = 1; state.pspace = 1; "SS" => state.paragraph(); state.prevailindent = state.indent; state.curfont = FONT_ROMAN; state.curattr = 0; state.indent = state.metrics.ssindent; state.heading = 2; for (pl := params; pl != nil; pl = tl pl) state.addstring(hd pl); state.heading = 0; state.brk(); state.newpara = 1; state.pspace = 1; "TF" => state.brk(); state.pspace = 0; i := state.metrics.indent; if (params != nil) { str := hd params; text := Text(FONT_BOLD, 0, str, 0, nil); w := state.viewer.textwidth(text) + 2*state.metrics.em; if (w > i) i = w; } state.indent = state.metrics.indent;; state.prevailindent = state.indent + i; "TH" => state.brk(); if (len params < 2) break; str := hd params + "(" + hd tl params + ")"; txt := Text(FONT_ROMAN, 0, str, 0, nil); txtw := state.viewer.textwidth(txt); state.indent = 0; state.addtext(txt::nil); state.indent = state.metrics.pagew - txtw; state.addtext(txt::nil); state.indent = 0; state.brk(); "TP" => state.paragraph(); if (state.prevailindent == state.metrics.indent) state.prevailindent += state.metrics.indent; state.indent = state.metrics.indent; state.ntlsetindent = 1; if (params != nil) { i := tval(state.metrics, hd params, 'n'); if (i == 0) i = state.metrics.indent; state.prevailindent = state.indent + i; } * => ; } if (state.verbatim) state.brk(); } parsetext[T](state: ref ParseState[T], t: string): list of Text for{ T => textwidth: fn(t: self T, text: Text): int; } { # need to do better here - spot inline font changes etc # we also currently cannot support troff tab stops textl: list of Text; line := ""; curfont := state.curfont; prevfont := state.curfont; # should perhaps be in State step := 1; for (i := 0; i < len t; i += step) { step = 1; ch := t[i]; if (ch == '\\') { i++; width := len t - i; if (width <= 0) break; case t[i] { '-' or '.' or '\\' => ch = t[i]; ' ' => ch = ' '; 'e' => ch = '\\'; '|' or '&' => continue; '(' => if (width > 3) width = 3; step = width; if (step != 3) continue; case t[i+1:i+3] { "bu" => ch = '•'; "em" => ch = '—'; "mi" => ch = '-'; "mu" => ch = '×'; "*m" => ch = 'µ'; "*G" => ch = 'Γ'; "*p" => ch = 'π'; "*b" => ch = 'β'; "<=" => ch = '≤'; "->" => ch = '→'; * => continue; } 'f' => if (width == 1) continue; if (t[i+1] == '(') { if (width > 4) width = 4; step = width; continue; } i++; case t[i] { '0' or 'R' => curfont = FONT_ROMAN; '1' or 'I' => curfont = FONT_ITALIC; '2' => # should be bold but our 'bold' font is constant width curfont = FONT_ROMAN; '5' or 'L' => curfont = FONT_BOLD; 'P' => curfont = prevfont; } continue; '*' => if (width == 1) continue; case t[i+1] { 'R' => step = 2; ch = '®'; '(' => if (width > 4) width = 4; step = width; continue; } * => i--; } } if (curfont != state.curfont) { if (line != "") { txt := Text(state.curfont, state.curattr, line, state.heading, state.link); line = ""; textl = txt :: textl; } prevfont = state.curfont; state.curfont = curfont; } line[len line] = ch; } if (line != "") { txt := Text(state.curfont, state.curattr, line, state.heading, state.link); textl = txt :: textl; } state.curfont = curfont; r: list of Text; for (; textl != nil; textl = tl textl) r = hd textl :: r; return r; } ParseState[T].addstring(state: self ref ParseState[T], s: string) { t := Text(state.curfont, state.curattr, s, state.heading, state.link); state.addtext(t::nil); } ParseState[T].addtext(state: self ref ParseState[T], t: list of Text) { #dumptextlist(t); # on setting a line copy state.prevailindent to state.indent # # always make sure that current indent is achieved # # if FONT_LITERAL and state.ql then convert to FONT_BOLD and # quote the text before any other processing state.newpara = 0; addspace := 1; while (t != nil) { # this scheme is inadequate... # results in mixed formatting at end of line getting split up # e.g. # .IR man (1) # can get split at the '(' indent := 0; spacew := 0; text := hd t; t = tl t; if (state.indent + state.margin > state.curwidth || state.curline == nil) { indent = state.indent + state.margin; state.curwidth = indent; addspace = 0; if (!state.verbatim) { text.text = trim(text.text); while (text.text == "" && t != nil) { text = hd t; t = tl t; text.text = trim(text.text); } } } if (text.font == FONT_LITERAL) { if (state.ql) text.text = "`" + text.text + "'"; text.font = FONT_BOLD; } if (addspace) { (nil, previtem) := hd state.curline; if (previtem.text[len previtem.text -1] == ' ') addspace = 0; else { space := Text(previtem.font, previtem.attr, " ", 0, nil); spacew = state.viewer.textwidth(space); } } # it doesn't fit - try to word wrap... t2 := text; end := len text.text; prevend := end; nextstart := 0; while (end > 0) { t2.text = text.text[0:end]; tlen := state.viewer.textwidth(t2); if (state.verbatim || state.curwidth + spacew + tlen <= state.metrics.pagew) { # easy - just add it! state.curwidth += spacew+tlen; if (addspace) { t2.text = " " + t2.text; addspace = 0; } state.curline = (indent, t2) :: state.curline; indent = 0; break; } prevend = end; for (; end > 0; end--) { if (t2.text[end-1] == ' ') { nextstart = end; for (; end >0 && t2.text[end-1] == ' '; end--) ; break; } } } if (end != len text.text) { # couldn't fit whole item onto line if (state.curline == nil) { # couldn't fit (sub)item on empty line - add it anyway # as there is nowhere else to put it end = prevend; t2.text = text.text[0:end]; state.curline = (indent, t2) :: state.curline; if (nextstart != 0) { text.text = text.text[nextstart:]; t = text :: t; } } else { # already stuff on line and we have consumed upto nexstart of # the current item if (end != 0) text.text = text.text[nextstart:]; t = text :: t; } state.brk(); } addspace = 0; } } trim(s: string): string { for (spi :=0; spi < len s && s[spi] == ' '; spi++) ; return s[spi:]; } ParseState[T].brk(state: self ref ParseState) { if (state.curline != nil) { line: list of (int, Text); for (l := state.curline; l != nil; l = tl l) line = hd l :: line; state.setline <- = line; state.curline = nil; state.curwidth = 0; } state.indent = state.prevailindent; } ParseState[T].paragraph(state: self ref ParseState) { state.brk(); if (state.newpara == 0) { blank := Text(state.curfont, state.curattr, "", 0, ""); for (i := 0; i < state.pspace; i++) state.setline <- = (0, blank)::nil; state.newpara = 1; } state.curattr = 0; state.curfont = FONT_ROMAN; state.indent = state.metrics.indent; # state.prevailindent = state.indent; state.ntlsetindent = 0; } # convert troff 'values' into output 'dots' tval(m: Metrics, v: string, defunits: int): int { if (v == nil) return 0; units := v[len v -1]; val: real; case units { 'i' or 'c' or 'P' or 'm' or 'n' or 'p' or 'u' or 'v' => val = real v[0:len v - 1]; * => val = real v; units = defunits; } r := 0; case units { 'i' => r = int (real m.dpi * val); 'c' => r = int ((real m.dpi * val)/2.54); 'P' => r = int ((real m.dpi * val)/ 6.0); 'm' => r = int (real m.em * val); 'n' => r = int (real m.en * val); 'p' => r = int ((real m.dpi * val)/72.0); 'u' => r = int val; 'v' => r = int (real m.V * val); } return r; } altattr[T](state: ref ParseState[T], f1, f2: int, strs: list of string) for{ T => textwidth: fn(t: self T, text: Text): int; } { index := 0; textl: list of Text; prevfont := state.curfont; for (; strs != nil; strs = tl strs) { str := hd strs; f := f1; if (index++ & 1) f = f2; state.curfont = f; newtext := parsetext(state, str); for (; newtext != nil; newtext = tl newtext) textl = hd newtext :: textl; } orderedtext: list of Text; for (; textl != nil; textl = tl textl) orderedtext = hd textl :: orderedtext; state.addtext(orderedtext); state.curfont = prevfont; } dumptextlist(t: list of Text) { sys->print("textlist["); for (; t != nil; t = tl t) { s := hd t; sys->print("(%s)", s.text); } sys->print("]\n"); } convlink(params: list of string): string { # merge the texts s := ""; for (; params != nil; params = tl params) s = s + (hd params); for (i := 0; i < len s; i ++) if (s[i] == '(') break; if (i+1 >= len s) return nil; cmd := s[0:i]; i++; s = s[i:]; for (i = 0; i < len s; i++) if (s[i] == ')') break; section := s[0:i]; if (section == nil || !isint(section)) return nil; return section + " " + cmd; } isint(s: string): int { for (i := 0; i < len s; i++) if (s[i] != '.' && (s[i] < '0' || s[i] > '9')) return 0; return 1; }