implement Parseman; include "sys.m"; include "bufio.m"; include "parseman.m"; sys : Sys; bufio : Bufio; Iobuf : import bufio; 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 { 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; link : string; viewer : Viewman; 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(fd : ref Sys->FD, metrics : Metrics, ql : int, viewer : Viewman, setline : chan of list of (int, Text)) { iob := bufio->fopen(fd, Sys->OREAD); state := ref ParseState (metrics, ql, 0, nil, nil, 0, 0, metrics.indent, FONT_ROMAN, 0, 0, 1, nil, 0, 1, 0, "", viewer, setline); while ((l := iob.gets('\n')) != nil) { if (l[len l -1] == '\n') l = l[0: len l - 1]; 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(state : ref ParseState, t : string) { if (t == nil) { if (state.verbatim) { blank := Text(state.curfont, state.curattr, "", 0, ""); state.setline <- = (0, blank)::nil; } return; } ntlsetindent := state.ntlsetindent; state.ntlsetindent = 0; if (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(state : ref ParseState, t : string) { for (n := 0; n < len t; n++) if (t[n] == ' ' || t[n] == '\t') break; macro := t[0:n]; params : list of string; quote := 0; esc := 0; p := ""; for (; n < len t; n++) { if (!esc) { esc = 0; case t[n] { ' ' or '\t' => if (!quote) { if (p != nil) { params = p :: params; p = ""; } continue; } '"' => quote = !quote; continue; '\\' => esc = 1; } } p[len p] = t[n]; } if (p != nil) params = p :: params; plist : list of string; for (; params != nil; params = tl params) plist = hd params :: plist; params = plist; case macro { "br" => state.brk(); "nf" => state.verbatim = 1; "fi" => state.verbatim = 0; "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; 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(state : ref ParseState, t : string) : list of Text { # 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.addstring(state : self ref ParseState, s : string) { t := Text(state.curfont, state.curattr, s, state.heading, state.link); state.addtext(t::nil); } ParseState.addtext(state : self ref ParseState, 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; if (state.indent + state.margin > state.curwidth || state.curline == nil) { indent = state.indent + state.margin; state.curwidth = indent; addspace = 0; } text := hd t; 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; } } } t = tl t; if (end != len text.text) { if (state.curline == nil) { # couldn't fit (sub)item in end = prevend; t2.text = text.text[0:end]; if (addspace) t2.text = " " + t2.text; state.curline = (indent, t2) :: state.curline; } state.brk(); if (end != 0) text.text = text.text[nextstart:]; t = text :: t; } addspace = 0; } } ParseState.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.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(state : ref ParseState, f1, f2 : int, strs : list of string) { 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] < '0' || s[i] > '9') return 0; return 1; }