DIRECTORY Ascii USING [Digit], Commander, CommanderOps, ImagerFont USING [Find, Font, RopeEscapement], IO, NodeProps USING [PutProp], PFS, PFSNames, UserProfile USING [Token, Number], Real USING [Fix], RefText USING [ObtainScratch, ReleaseScratch], Rope, SymTab USING [Create, Delete, Fetch, Ref, Store, Val], TextNode USING [Ref], TiogaFileOps USING [AddLooks, CreateRoot, InsertAsLastChild, Ref, SetContents, SetFormat, SetStyle, Store], TiogaMenuOps USING [Open, Load], Vector2 USING [VEC], ViewerClasses USING [Viewer], ViewerOps USING [CreateViewer, OpenIcon, SetViewer, PaintViewer], RuntimeError USING [BoundsFault]; ManImpl: CEDAR PROGRAM IMPORTS Ascii, Commander, CommanderOps, ImagerFont, IO, NodeProps, PFS, PFSNames, UserProfile, Real, RefText, Rope, SymTab, TiogaFileOps, TiogaMenuOps, ViewerOps, RuntimeError ~ { ROPE: TYPE ~ Rope.ROPE; FileError: ERROR [huh: ROPE] ~ CODE; FileInfo: PROC [fileName: ROPE] ~ { ENABLE PFS.Error => { ERROR FileError[error.explanation] }; path: PFSNames.PATH ~ PFS.PathFromRope[fileName]; fullPath: PFSNames.PATH ฌ PFS.FileLookup[path, NIL]; IF ( fullPath = NIL ) THEN FileError["File not found"]; }; StreamOpen: PROC [fileName: ROPE] RETURNS [s: IO.STREAM] ~ { ENABLE PFS.Error => { ERROR FileError[error.explanation] }; path: PFSNames.PATH ~ PFS.PathFromRope[fileName]; s ฌ PFS.StreamOpen[path]; }; OpenReadStream: PROC [fileName: ROPE] RETURNS [s: IO.STREAM] ~ { ENABLE PFS.Error => { ERROR FileError[error.explanation] }; path: PFSNames.PATH ~ PFS.PathFromRope[fileName]; s ฌ PFS.StreamOpen[path]; }; OpenRelativeStream: PROC [shortName: ROPE, openStream: IO.STREAM] RETURNS [s: IO.STREAM] ~ { ENABLE PFS.Error => { ERROR FileError[error.explanation] }; path: PFSNames.PATH ~ PFS.PathFromRope[shortName]; currentFile: PFS.OpenFile ~ PFS.OpenFileFromStream[openStream]; fullPath: PFS.PATH ~ PFS.GetInfo[currentFile].fullFName; openPath: PFS.PATH ~ PFSNames.Cat[PFSNames.Parent[PFSNames.Parent[fullPath]], path]; s ฌ PFS.StreamOpen[openPath]; }; PATH: TYPE ~ PFSNames.PATH; shortPattern: PATH ~ PFS.PathFromRope["man*"]; NonStandard: ERROR; -- Indicates a nonstandard manpage file name PageNameFromFile: PROC [fileName: ROPE] RETURNS [name: ROPE ฌ NIL, section: ROPE ฌ NIL] ~ { extPos: INT ~ fileName.FindBackward["."]; namePos: INT ~ fileName.FindBackward["/"]; IF extPos = -1 OR namePos = -1 OR namePos >= (extPos - 1) THEN { ERROR NonStandard; } ELSE { name ฌ fileName.Substr[namePos+1, extPos - namePos - 1]; section ฌ fileName.Substr[extPos+1, fileName.Length[]-extPos]; } }; ManSubDirectories: PROC [cmd: Commander.Handle, section: ROPE] RETURNS [dirs: LIST OF PFSNames.PATH] ~ { RecordDirectoryName: PFS.InfoProc ~ { IF fileType = PFS.tDirectory THEN { rope: ROPE ฌ IF section # NIL THEN PFS.RopeFromPath[fullFName] ELSE NIL; IF section = NIL OR section.Fetch[0] = rope.Fetch[rope.Length[]-1] THEN { dirs ฌ CONS[fullFName, dirs]; } } }; manPath: ROPE ฌ "/usr/man"; j: INT ฌ 0; WITH CommanderOps.GetProp[cmd, $MANPATH] SELECT FROM rope: ROPE => manPath ฌ rope; ENDCASE; FOR i: INT ฌ 0, j+1 UNTIL i > Rope.Size[manPath] DO j ฌ Rope.SkipTo[manPath, i, ":"]; IF i < j THEN { dir: PATH ~ PFS.PathFromRope[Rope.Substr[manPath, i, j-i]]; path: PATH ~ PFSNames.Cat[dir, shortPattern]; PFS.EnumerateForInfo[path, RecordDirectoryName ! PFS.Error => CONTINUE]; }; ENDLOOP; }; FindMatches: PROC [cmd: Commander.Handle, cmdName: ROPE, mode: ATOM, section: ROPE] RETURNS [files: LIST OF ROPE ฌ NIL] ~ { n: NAT ฌ 0; failed: BOOL ฌ FALSE; oldSection: ROPE ฌ NIL; RecordFileName: PFS.NameProc ~ { ENABLE NonStandard => GOTO Punt; rope: ROPE ~ PFS.RopeFromPath[name]; IF ( mode = $First ) THEN { IF files = NIL THEN { files ฌ CONS[rope, files]; [section: oldSection] ฌ PageNameFromFile[rope]; } ELSE { section: ROPE; [section: section] ฌ PageNameFromFile[rope]; IF Rope.Compare[section, oldSection, FALSE] = less THEN { files.first ฌ rope; [section: oldSection] ฌ PageNameFromFile[rope]; }; } } ELSE { files ฌ CONS[rope, files]; IF ( (n ฌ n.SUCC) >= UserProfile.Number["Man.MaxMatches", 10] ) THEN { failed ฌ TRUE; continue ฌ FALSE; RETURN } } EXITS Punt => RETURN; }; dirs: LIST OF PFSNames.PATH ~ ManSubDirectories[cmd, section]; FOR tail: LIST OF PFSNames.PATH ฌ dirs, tail.rest WHILE ( tail # NIL ) DO pathRope: ROPE ~ IF section = NIL THEN cmdName.Concat[".*"] ELSE cmdName.Concat[Rope.Concat[".", section.Concat["*"]]]; path: PATH ~ PFSNames.Cat[tail.first, PFS.PathFromRope[pathRope]]; PFS.EnumerateForNames[path, RecordFileName ! PFS.Error => CONTINUE]; IF ( failed ) THEN { SIGNAL TooMany; EXIT; }; IF ( n > 0 AND mode = $First ) THEN EXIT; ENDLOOP; }; manHost: ROPE ฌ "localhost"; tmpDir: ROPE ~ "/tmp/"; TemporaryFile: PROC [short: ROPE] RETURNS [t: ROPE] ~ { NoSlash: PROC [old: CHAR] RETURNS [CHAR] = {RETURN [IF old='/ THEN '# ELSE old]}; t ฌ Rope.Concat[tmpDir, Rope.Translate[base: short, translator: NoSlash]]; }; blank: ROPE ~ " "; slash: ROPE ~ "/"; cedarStyle: ROPE ~ "cedar"; times: ROPE ~ "Xerox/TiogaFonts/TimesRoman"; abstract: ROPE ~ "abstract"; block: ROPE ~ "block"; body: ROPE ~ "body"; head: ROPE ~ "head"; item: ROPE ~ "item"; title: ROPE ~ "title"; verbose: BOOL ฌ FALSE; emPeru: REAL ฌ 1.0; emPeri: REAL ฌ 0.02; Cmd: TYPE ~ { noCmd, as, b, bi, br, ds, fi, ft, hp, i, ib, ip, ir, lp, ne, nf, nx, pp, re, rb, ri, rs, rm, rn, sb, sh, sm, so, ss, ta, th, tp, tx, notImplem, comment, other }; Look: TYPE ~ RECORD [ start, len: INT, look: CHAR ['a..'z] ]; Data: TYPE ~ REF DataRec; DataRec: TYPE ~ RECORD [ root: TiogaFileOps.Ref ฌ NIL, nodeStack: LIST OF TiogaFileOps.Ref ฌ NIL, symbols: SymTab.Ref, format: ROPE ฌ NIL, txt: ROPE ฌ NIL, props: LIST OF Look ฌ NIL, level: NAT ฌ 1, prevLook: CHAR ฌ ' , escC, muteComments: BOOL ฌ FALSE, fill: BOOL ฌ TRUE, badOps: ROPE ฌ NIL ]; Parse: PUBLIC PROC [s, err: IO.STREAM, root: TiogaFileOps.Ref] ~ { line: ROPE; cmd: Cmd ฌ noCmd; badOp: ROPE; data: Data ฌ NEW[DataRec ฌ [root, LIST[TiogaFileOps.InsertAsLastChild[root]], CreateTab[]]]; TiogaFileOps.SetStyle[root, cedarStyle]; UNTIL IO.EndOf[s] DO [cmd, line, badOp] ฌ ParseRequest[s, err, data]; SELECT cmd FROM noCmd => AddText[line, data]; as => AddToSymbol[line, data]; b => Bold[line, data]; bi => JoinBI[line, data]; br => JoinBR[line, data]; ds => DefSymbol[line, data]; fi => Fill[data]; ft => ChFont[line, data]; hp => Item[NIL, data]; i => Italics[line, data]; ib => JoinIB[line, data]; ip => Item[line, data]; ir => JoinIR[line, data]; lp => Break[data]; nf => NoFill[data]; nx => s ฌ Next[line, s, err]; pp => Break[data]; re => BreakAndPop[data]; rb => JoinRB[line, data]; ri => JoinRI[line, data]; rm => RemoveSymbol[line, data]; rn => RenameSymbol[line, data]; rs => BreakAndPush[data]; so => Source[line, s, err, root]; sb => SmallBold[line, data]; sh => Head[line, data]; sm => Small[line, data]; ss => SubHead[line, data]; th => Title[line, data]; tp => Item[NIL, data]; tx => AddText[line, data]; -- Don't know how to "resolve" titles notImplem => BadOp[badOp, data]; --explicitly discard these macro comment => Comments[line, data]; other => BadOp[badOp, data]; -- by default ENDCASE => ERROR; --Cmd definition is not compatible with Parse IF line#NIL AND data.fill THEN CloseLook[' , NIL, data]; ENDLOOP; [] ฌ Validate[data]; IF (NOT verbose AND data.badOps # NIL) THEN IO.PutF1[err, "Undefined ops: %g\n", IO.rope[data.badOps]]; }; Source: PROC [line: ROPE, current: IO.STREAM, err: IO.STREAM, root: TiogaFileOps.Ref] ~ { s: IO.STREAM ฌ Next[line, current, err]; IF ( s # NIL ) THEN Parse[s, err, root]; }; Next: PROC [line: ROPE, current: IO.STREAM, err: IO.STREAM] RETURNS [s: IO.STREAM ฌ NIL] ~ { ris: IO.STREAM ~ IO.RIS[line]; shortName: ROPE ~ MyGetRawToken[ris]; ris.Close[]; IF ( shortName = NIL ) THEN RETURN; IF (shortName.Fetch[] # '/) THEN { s ฌ OpenRelativeStream[shortName, current ! FileError => { IO.PutRope[err, huh]; GOTO Failed }] } ELSE { s ฌ OpenReadStream[shortName ! FileError => { IO.PutRope[err, huh]; GOTO Failed }] }; EXITS Failed => NULL; }; MyGetRawToken: PROC [s: IO.STREAM] RETURNS [token: ROPE] ~ { [] ฌ IO.SkipWhitespace[s]; IF IO.PeekChar[s ! IO.EndOfStream => GOTO NoToken]='" THEN { [] ฌ IO.GetChar[s]; --consumes the leading " token _ IF IO.EndOf[s] THEN NIL ELSE IO.GetTokenRope[s, LiteralProc].token; [] _ IO.GetChar[s ! IO.EndOfStream => CONTINUE]; --consumes the trailing ", if any IF NOT IO.EndOf[s] AND IO.PeekChar[s]='" THEN token ฌ token.Cat["""", MyGetRawToken[s]]; -- double " } ELSE token _ IO.GetTokenRope[s, SpaceProc].token; IF token#NIL AND token.Fetch[token.Length[]-1]='\\ THEN { token _ token.Substr[0, token.Length[]-1]; token _ token.Cat[blank, MyGetRawToken[s]]; }; EXITS NoToken => token _ NIL; }; MyGetToken: PROC [s: IO.STREAM, data: Data] RETURNS [token: ROPE] ~ { token _ MyGetRawToken[s]; token _ Filter[token, data]; }; GetSixWords: PROC [s: IO.STREAM, data: Data] ~ { n: NAT _ 0; WHILE NOT IO.EndOf[s] DO data.txt _ Rope.Cat[data.txt, MyGetToken[s, data], blank]; n _ n+1; IF n=6 THEN RETURN; ENDLOOP; }; MySkipWhitespace: PROC [s: IO.STREAM] ~ { WHILE NOT IO.EndOf[s] DO c: CHAR _ IO.GetChar[s ! IO.EndOfStream => GOTO Eof]; SELECT c FROM ' , '\t => NULL; ENDCASE => {IO.Backup[s, c]; RETURN}; ENDLOOP; EXITS Eof => NULL; }; MyGetLineRope: PROC [stream: IO.STREAM] RETURNS [line: ROPE _ NIL] = { bufMax: NAT ~ 256; buffer: REF TEXT ~ RefText.ObtainScratch[bufMax]; bLen: NAT _ 0; chars: INT _ 0; { ENABLE UNWIND => RefText.ReleaseScratch[buffer]; DO char: CHAR _ IO.GetChar[stream ! IO.EndOfStream => IF chars > 0 THEN EXIT ELSE REJECT]; IF char = '\l THEN {-- instead of \n IF bLen>0 AND buffer[bLen-1]='\\ THEN { bLen _ bLen-1; char _ ' ; --concealed \l } ELSE EXIT; }; chars _ chars + 1; IF bLen = bufMax THEN { buffer.length _ bLen; line _ Rope.Concat[line, Rope.FromRefText[buffer]]; bLen _ 0; }; buffer[bLen] _ char; bLen _ bLen+1; ENDLOOP; }; buffer.length _ bLen; IF bLen # 0 THEN line _ Rope.Concat[line, Rope.FromRefText[buffer]]; RefText.ReleaseScratch[buffer]; RETURN [line]; }; GetCmd: PROC [s: IO.STREAM] RETURNS [cmd: ROPE _ NIL] ~ { buffer: REF TEXT ~ RefText.ObtainScratch[2]; buffer[0] _ IO.GetChar[s]; IF ( buffer[0] IN [IO.NUL..IO.SP] ) THEN { RefText.ReleaseScratch[buffer]; RETURN; }; -- no cmd on the line buffer[1] _ IO.GetChar[s]; buffer.length _ IF ( buffer[1] IN [IO.NUL..IO.SP] ) THEN 1 ELSE 2; cmd _ Rope.FromRefText[buffer]; RefText.ReleaseScratch[buffer]; }; ParseRequest: PROC [s: IO.STREAM, err: IO.STREAM, data: Data] RETURNS [cmd: Cmd _ noCmd, text: ROPE _ NIL, badOp: ROPE ฌ NIL] ~ { IF IO.EndOf[s] OR IO.PeekChar[s] # '. THEN { text _ MyGetLineRope[s]; RETURN }; [] _ IO.GetChar[s]; --skip the "." MySkipWhitespace[s]; { cmdName: ROPE _ GetCmd[s]; IF ( cmdName = NIL ) THEN { cmd _ comment; RETURN }; MySkipWhitespace[s]; IF ( NOT IO.EndOf[s] ) THEN text _ MyGetLineRope[s]; WITH data.symbols.Fetch[cmdName].val SELECT FROM refC: REF Cmd => cmd _ refC^; ENDCASE => { cmd _ other; badOp ฌ Rope.Concat[".", cmdName]; IF ( err # NIL AND verbose) THEN { IO.PutFL[err, "op [.%g] undefined\n", LIST[IO.rope[cmdName]] ]; IO.PutFL[err, " text: %g\n", LIST[IO.rope[text]] ]; }; }; IF ( cmd # comment ) THEN data.muteComments _ TRUE; } }; NextTwo: PROC [s: IO.STREAM] RETURNS [pair: ROPE] ~ INLINE { one: CHAR ~ IO.GetChar[s]; two: CHAR ~ IO.GetChar[s]; pair _ Rope.FromChar[one]; pair _ pair.Concat[Rope.FromChar[two]]; }; Filter: PROC [line: ROPE, data: Data] RETURNS [token: ROPE _ NIL] ~ { s: IO.STREAM _ IO.RIS[line]; deltaPos: INT _ 0; DO IF NOT IO.EndOf[s] AND IO.PeekChar[s] # '\\ THEN token _ token.Concat[IO.GetTokenRope[s, EscapeProc].token]; IF NOT IO.EndOf[s] AND IO.PeekChar[s] = '\\ THEN { c: CHAR _ IO.GetChar[s]; --skip the escape c _ IO.GetChar[s]; SELECT c FROM '0 => token _ token.Concat["\031"]; -- 031C '| => token _ token.Concat["\035"]; -- 035C '^ => NULL; '& => NULL; '* => { c: CHAR _ IO.GetChar[s]; symb: ROPE _ IF ( c = '( ) THEN NextTwo[s] ELSE Rope.FromChar[c]; WITH data.symbols.Fetch[symb].val SELECT FROM text: ROPE => { index: INT ~ IO.GetIndex[s]; line _ text.Concat[line.Substr[index]]; s _ IO.RIS[line]; }; ENDCASE => { NULL }; }; 'c => data.escC _ TRUE; 'd => { deltaPos _ deltaPos+1; SELECT deltaPos FROM 0 => CloseLook['u, token, data]; ENDCASE => OpenLook['d, token, data]; }; 'e => token _ token.Concat["\\"]; 'h => { size: REAL; MySkipWhitespace[s]; IF IO.GetChar[s]='' THEN { padding: ROPE; nChars: INT; c: CHAR ฌ IO.PeekChar[s]; IF c='\\ OR c = '| THEN size _ Width[s, data] ELSE size _ IO.GetReal[s ! IO.Error => GOTO noWidth]; c _ IO.GetChar[s]; SELECT c FROM '' => IO.Backup[s, c]; -- m is default 'm => NULL; 'u => nChars _ Real.Fix[size/emPeru]; 'i => nChars _ Real.Fix[size/emPeri]; ENDCASE => NULL; --Well.. c _ IO.GetChar[s]; -- should be '', good spot for a breakpoint THROUGH [1..nChars] DO padding _ Rope.Concat[padding, "\035"]; -- 035C ENDLOOP; token _ token.Concat[padding]; EXITS noWidth => {} } }; 'u => { deltaPos _ deltaPos-1; SELECT deltaPos FROM 0 => CloseLook['d, token, data]; ENDCASE => OpenLook['u, token, data]; }; -- up 1/2 line 't => token _ token.Concat["\t"]; '\l => token _ token.Concat[blank]; 'f => { font: CHAR _ IO.GetChar[s]; SELECT font FROM 'R, '1 => ChangeLook['r, token, data]; 'I, '2 => ChangeLook['i, token, data]; 'B, '3 => ChangeLook['b, token, data]; 'P => ChangeLook[data.prevLook, token, data]; ENDCASE => NULL; -- IF debug THEN ... }; 's => { size: CHAR _ IO.GetChar[s]; SELECT size FROM '0 => {CloseLook['s, token, data]; CloseLook['l, token, data];}; '- => {OpenLook['s, token, data]; [] _ IO.GetChar[s];}; '+ => {OpenLook['l, token, data]; [] _ IO.GetChar[s];}; ENDCASE => NULL; -- IF debug THEN ... }; '( => { c _ IO.GetChar[s]; SELECT c FROM '* => { cc: CHAR _ IO.GetChar[s]; IF cc='* THEN token _ token.Concat["*"] ELSE {--greek char OpenLook['g, token, data]; token _ token.Concat[Rope.FromChar[cc]]; CloseLook['g, token, data]; }; -- they are not mapped quite the same way as ours, oh well... }; 'a => IF IO.GetChar[s]='p THEN token _ token.Concat["~"]; 'b => IF IO.GetChar[s]='u THEN token _ token.Concat["%"]; 'e => { SELECT IO.GetChar[s] FROM 'm => token _ token.Concat["%"]; 'q => token _ token.Concat["="]; ENDCASE; }; 'g => IF IO.GetChar[s]='a THEN token _ token.Concat["`"]; 'l => IF IO.GetChar[s]='q THEN token _ token.Concat["``"]; 'm => IF IO.GetChar[s]='i THEN token _ token.Concat["-"]; 'p => IF IO.GetChar[s]='l THEN token _ token.Concat["+"]; 'r => IF IO.GetChar[s]='q THEN token _ token.Concat["''"]; ENDCASE => { -- display unknown escapes in fixed pitch OpenLook['f, token, data]; token _ token.Cat[Rope.FromChar[c], Rope.FromChar[IO.GetChar[s]]]; CloseLook['f, token, data]; }; }; ENDCASE => token _ token.Concat[Rope.FromChar[c]]; } ELSE RETURN; ENDLOOP; }; Width: PROC [s: IO.STREAM, data: Data] RETURNS [size: REAL _ 0] ~ { newData: Data _ NEW[DataRec _ [symbols: data.symbols, txt: data.txt]]; props: LIST OF Look _ data.props; rope: ROPE; IF IO.PeekChar[s]='| THEN [] ฌ IO.GetChar[s]; -- dunno what | means IF IO.GetChar[s]#'\\ THEN RETURN; IF IO.GetChar[s]#'w THEN RETURN; IF IO.GetChar[s]#'' THEN RETURN; rope _ IO.GetTokenRope[s, QuoteProc].token; IF IO.GetChar[s]#'' THEN RETURN; UNTIL props=NIL DO IF props.first.len=0 THEN OpenLook[props.first.look, NIL, newData]; props _ props.rest; ENDLOOP; IF newData.props=NIL THEN OpenLook['r, NIL, newData]; -- at least give it the r look rope _ Filter[rope, newData]; CloseLook[' , NIL, newData]; UNTIL newData.props=NIL DO fname: ROPE ~ times.Concat[SELECT newData.props.first.look FROM 'i => "10I", 'b => "10B", 's => "8", ENDCASE => "10"]; font: ImagerFont.Font _ ImagerFont.Find[fname]; IF newData.props.first.start IN [0..rope.Size) THEN { vec: Vector2.VEC ~ ImagerFont.RopeEscapement[font, rope, newData.props.first.start, newData.props.first.len]; size _ size+vec.x; }; newData.props _ newData.props.rest; ENDLOOP; }; GetLine: PROC [s: IO.STREAM] RETURNS [line: ROPE] ~ { line _ IO.GetTokenRope[s, LineProc ! IO.EndOfStream => GOTO Return].token; IF line.Fetch[line.Length[]-1]='\\ THEN -- "concealed" newline line _ line.Cat[blank, GetLine[s]]; EXITS Return => {}; }; ChangeLook: PROC [char: CHAR, txt: ROPE, data: Data] ~ { CloseLook[' , txt, data]; OpenLook[char, txt, data]; }; OpenLook: PROC [char: CHAR, txt: ROPE, data: Data] ~ { start: INT _ Rope.Length[data.txt]; addLen: INT _ Rope.Length[txt]; IF char IN ['a..'z] THEN data.props _ CONS[[start+addLen, 0, char], data.props]; }; CloseLook: PROC [char: CHAR, txt: ROPE, data: Data] ~ { start: INT _ Rope.Length[data.txt]; addLen: INT _ Rope.Length[txt]; props: LIST OF Look _ data.props; UNTIL props=NIL DO IF char=' THEN { IF props.first.len=0 THEN IF props.first.look#'d AND props.first.look#'u THEN { props.first.len _ addLen+start-props.first.start; data.prevLook _ props.first.look; RETURN; } } ELSE IF props.first.look=char THEN { validClose: BOOL _ addLen+start-props.first.len-props.first.start<0 OR props.first.len=0; IF validClose THEN { props.first.len _ addLen+start-props.first.start; data.prevLook _ props.first.look; }; }; props _ props.rest; ENDLOOP; IF char=' THEN data.prevLook _ 'r; }; AddLook: PROC [char: CHAR, txt: ROPE, data: Data] ~ { start: INT _ Rope.Length[data.txt]; len: INT _ Rope.Length[txt]; IF char IN ['a..'z] THEN data.props _ CONS[[start, len, char], data.props]; }; OneLook: PROC [line: ROPE, char: CHAR, data: Data] ~ { AddLook[char, line, data]; data.txt _ Rope.Concat[data.txt, line]; }; DoAddOneLook: PROC [line: ROPE, char: CHAR, data: Data] ~ { IF line=NIL THEN OpenLook[char, NIL, data] ELSE { ris: IO.STREAM _ IO.RIS[line]; UNTIL IO.EndOf[ris] DO token: ROPE _ MyGetToken[ris, data]; IF NOT Rope.IsEmpty[token] THEN OneLook[token, char, data]; IF data.escC THEN data.escC _ FALSE ELSE data.txt _ Rope.Concat[data.txt, blank]; ENDLOOP; }; }; DoAddTwoLook: PROC [line: ROPE, char1, char2: CHAR, data: Data] ~ { IF line=NIL THEN { OpenLook[char1, NIL, data]; OpenLook[char2, NIL, data]; } ELSE { token: ROPE _ Filter[line, data]; AddLook[char1, token, data]; AddLook[char2, token, data]; data.txt _ Rope.Cat[data.txt, token, IF data.escC THEN NIL ELSE blank]; IF data.escC THEN data.escC _ FALSE }; }; JoinTwoLooks: PROC [line: ROPE, char1, char2: CHAR, data: Data] ~ { ris: IO.STREAM _ IO.RIS[line]; parity: BOOL _ TRUE; UNTIL IO.EndOf[ris] DO token: ROPE _ MyGetToken[ris, data]; IF NOT Rope.IsEmpty[token] THEN OneLook[token, IF parity THEN char1 ELSE char2, data]; parity _ NOT parity; ENDLOOP; IF data.escC THEN data.escC _ FALSE ELSE data.txt _ Rope.Concat[data.txt, blank]; }; ChFont: PROC [line: ROPE, data: Data] ~ { s: IO.STREAM _ IO.RIS[line]; font: CHAR _ IF IO.EndOf[s] THEN 0C ELSE IO.GetChar[s]; SELECT font FROM 'R, '1 => ChangeLook['r, NIL, data]; 'I, '2 => ChangeLook['i, NIL, data]; 'B, '3 => ChangeLook['b, NIL, data]; 'P => ChangeLook[data.prevLook, NIL, data]; ENDCASE => NULL; -- IF debug THEN ... }; GetSymbol: PROC [line: ROPE] RETURNS [symb, text: ROPE _ NIL] ~ { len: INT _ Rope.Length[line]; index: INT _ IF line.Fetch[1] IN [IO.NUL..IO.SP] THEN 1 ELSE 2; symb _ Rope.Substr[base: line, len: index]; WHILE index CONTINUE]; tokenChapter ฌ MyGetToken[ris, data ! IO.EndOfStream => CONTINUE]; tokenDate ฌ MyGetToken[ris, data ! IO.EndOfStream => CONTINUE]; data.txt ฌ IO.PutFR["\t\t\t%g(%g)", [rope[tokenTiltle]], [rope[tokenChapter]]]; data.level ฌ 1; data.format ฌ title; Validate[data]; data.txt ฌ tokenDate; data.level ฌ 2; data.format ฌ abstract; Validate[data]; Break[data]; }; Head: PROC [line: ROPE, data: Data] ~ { ris: IO.STREAM ฌ IO.RIS[line]; Validate[data]; data.level ฌ 1; data.format ฌ head; GetSixWords[ris, data ! IO.EndOfStream => CONTINUE]; Break[data]; }; SubHead: PROC [line: ROPE, data: Data] ~ { ris: IO.STREAM ฌ IO.RIS[line]; Validate[data]; data.level ฌ 2; data.format ฌ head; GetSixWords[ris, data ! IO.EndOfStream => CONTINUE]; Break[data]; }; Item: PROC [line: ROPE, data: Data] ~ { Validate[data]; data.level ฌ 3; data.format ฌ IF data.fill THEN item ELSE block; IF line#NIL THEN { ris: IO.STREAM ฌ IO.RIS[line]; txt: ROPE ฌ MyGetToken[ris, data]; data.txt ฌ IO.PutFR1["%g\t", [rope[txt]]]; }; }; Fill: PROC [data: Data] ~ { data.fill ฌ TRUE; Break[data]; }; NoFill: PROC [data: Data] ~ { data.fill ฌ FALSE; Break[data]; }; Break: PROC [data: Data] ~ { Validate[data]; data.level ฌ 2; data.format ฌ IF data.fill THEN body ELSE block; }; BreakAndPush: PROC [data: Data] ~ { Validate[data]; data.level ฌ 3; data.format ฌ IF data.fill THEN body ELSE block; }; BreakAndPop: PROC [data: Data] ~ { Validate[data]; data.level ฌ 2; data.format ฌ IF data.fill THEN body ELSE block; }; Validate: PROC [data: Data] ~ { IF data.nodeStack=NIL THEN RETURN; IF NOT Rope.IsEmpty[data.txt] THEN { node: TiogaFileOps.Ref; SELECT data.level FROM 1 => { node ฌ TiogaFileOps.InsertAsLastChild[data.root]; data.nodeStack ฌ LIST[node]; }; 2 => { node ฌ TiogaFileOps.InsertAsLastChild[data.nodeStack.first]; data.nodeStack.rest ฌ LIST[node]; }; 3 => { IF data.nodeStack.rest=NIL THEN { data.nodeStack.rest ฌ LIST[TiogaFileOps.InsertAsLastChild[data.nodeStack.first]]; TiogaFileOps.SetContents[data.nodeStack.rest.first, blank]; TiogaFileOps.SetFormat[data.nodeStack.rest.first, "body"]; }; node ฌ TiogaFileOps.InsertAsLastChild[data.nodeStack.rest.first]; data.nodeStack.rest.rest ฌ LIST[node]; }; ENDCASE => { node ฌ TiogaFileOps.InsertAsLastChild[data.nodeStack.first]; data.nodeStack.rest ฌ LIST[node]; }; TiogaFileOps.SetContents[node, data.txt]; CloseLook[' , NIL, data]; UNTIL data.props=NIL DO l: Look ฌ data.props.first; TiogaFileOps.AddLooks[x: node, start: l.start, len: l.len, look: l.look, root: data.root]; data.props ฌ data.props.rest; ENDLOOP; data.txt ฌ NIL; TiogaFileOps.SetFormat[node, data.format]; } ELSE data.props ฌ NIL; data.level ฌ 2; }; EscapeProc: IO.BreakProc = { RETURN[SELECT char FROM '\\ => break, ENDCASE => other] }; LineProc: IO.BreakProc = { RETURN[SELECT char FROM '\l, '\n => sepr, ENDCASE => other] }; LiteralProc: IO.BreakProc = { RETURN[SELECT char FROM '" => break, ENDCASE => other] }; QuoteProc: IO.BreakProc = { RETURN[SELECT char FROM '' => break, ENDCASE => other] }; SpaceProc: IO.BreakProc = { RETURN[SELECT char FROM ' , '\t, '\l, '\n => sepr, ENDCASE => other] }; CreateRoot: PROC RETURNS [TiogaFileOps.Ref] ~ { root: TiogaFileOps.Ref ~ TiogaFileOps.CreateRoot[]; node: REF ~ root; textnode: TextNode.Ref ~ NARROW[node]; NodeProps.PutProp[n: textnode, name: $NewlineDelimiter, value: Rope.Flatten["\n"]]; RETURN [root] }; MakeTitle: PROC [fileName: ROPE] RETURNS [ROPE] ~ { ENABLE { RuntimeError.BoundsFault => GOTO default; }; nameEnd: INT ~ fileName.SkipTo[skip: "."] - 1; sectionStart: INT ~ nameEnd + 2; sectionEnd: INT ~ fileName.SkipTo[pos: sectionStart, skip: "."] - 1; nameStart: INT ฌ nameEnd; name, section: ROPE; WHILE nameStart > 0 DO IF fileName.Fetch[nameStart] = '/ THEN EXIT; nameStart ฌ nameStart - 1; ENDLOOP; nameStart ฌ nameStart + 1; name ฌ fileName.Substr[start: nameStart, len: nameEnd-nameStart+1]; section ฌ fileName.Substr[start: sectionStart, len: sectionEnd-sectionStart+1]; RETURN [IO.PutFR["%g (%g)", IO.rope[name], IO.rope[section]]]; EXITS default => RETURN [fileName]; }; FormatManPage: PROC [fileName: ROPE, tempName: ROPE, stderr: IO.STREAM] ~ { root: TiogaFileOps.Ref ~ CreateRoot[]; title: ROPE ~ MakeTitle[fileName]; viewer: ViewerClasses.Viewer ~ ViewerOps.CreateViewer[$Text, [name: title, label: title]]; s: IO.STREAM ~ OpenReadStream[fileName]; Parse[s, stderr, root]; ViewerOps.SetViewer[viewer, root, FALSE, $TiogaDocument]; ViewerOps.OpenIcon[viewer]; TiogaFileOps.Store[root, tempName]; -- cache the result viewer.file ฌ tempName; }; LoadFromCache: PROC [fileName: ROPE, tempName: ROPE, stderr: IO.STREAM] ~ { root: TiogaFileOps.Ref ~ CreateRoot[]; title: ROPE ~ MakeTitle[fileName]; viewer: ViewerClasses.Viewer ~ ViewerOps.CreateViewer[$Text, [name: title, label: title]]; ViewerOps.SetViewer[viewer, root, FALSE, $TiogaDocument]; ViewerOps.OpenIcon[viewer]; TiogaMenuOps.Load[viewer, tempName]; viewer.name ฌ title; ViewerOps.PaintViewer[viewer, caption]; }; FormatManPage2: PROC [fileName: ROPE, tempName: ROPE, stderr: IO.STREAM] ~ { root: TiogaFileOps.Ref ~ CreateRoot[]; s: IO.STREAM ~ StreamOpen[fileName]; Parse[s, stderr, root]; TiogaFileOps.Store[root, tempName]; -- cache the result [] ฌ TiogaMenuOps.Open[fileName: tempName]; }; ModeFromProfile: PROC RETURNS [ATOM] ~ { choice: ROPE ~ UserProfile.Token[key: "Man.SearchRule", default: "All"]; IF Rope.Equal[choice, "First", FALSE] THEN RETURN [$First] ELSE IF Rope.Equal[choice, "List", FALSE] THEN RETURN [$List] ELSE RETURN [$All]; }; ReportFile: PROC [cmd: Commander.Handle, fileName: ROPE, seen: LIST OF ROPE ฌ NIL] RETURNS [seenOut: LIST OF ROPE] ~ { name: ROPE; section: ROPE; inList: BOOL ฌ FALSE; seenOut ฌ seen; [name, section] ฌ PageNameFromFile[fileName ! NonStandard => GOTO Failed]; FOR each: LIST OF ROPE ฌ seenOut, each.rest WHILE each # NIL DO IF Rope.IsPrefix[section, each.first, FALSE] THEN { inList ฌ TRUE; EXIT; }; ENDLOOP; IF inList THEN { IO.PutF1[cmd.out, "localman %g\n", IO.rope[fileName]]; } ELSE { IO.PutFL[cmd.out, "man %g %g\n", LIST[IO.rope[section], IO.rope[name]]]; seenOut ฌ CONS[section, seenOut]; }; EXITS Failed => IO.PutF1[cmd.out, "Nonstandard: %g\n", IO.rope[fileName]]; }; TooMany: SIGNAL ~ CODE; ManCmd: Commander.CommandProc ~ { ENABLE TooMany => { msg ฌ "too many matches" ; RESUME }; stderr: IO.STREAM ~ cmd.err; args: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd ! CommanderOps.Failed => { msg ฌ errorMsg; GO TO Failed }]; IF ( args.argc < 2 ) THEN { msg ฌ "wrong arguments"; GOTO Failed }; { firstChar: CHARACTER ~ args[1].Fetch[0]; cmdName: ROPE ฌ NIL; mode: ATOM ฌ ModeFromProfile[]; section: ROPE ฌ NIL; sectionsSeen: LIST OF ROPE ฌ NIL; fileNames: LIST OF ROPE; IF firstChar = '- THEN { SELECT args[1].Fetch[1 ! RuntimeError.BoundsFault => { msg ฌ "wrong arguments"; GOTO Failed }] FROM 'f, 'F => mode ฌ $First; 'a, 'A => mode ฌ $All; 'l, 'L => mode ฌ $List; ENDCASE => { msg ฌ "wrong arguments"; GOTO Failed }; IF ( args.argc < 3 ) THEN { msg ฌ "wrong arguments"; GOTO Failed } ELSE cmdName ฌ args[2]; } ELSE IF Ascii.Digit[firstChar] THEN { section ฌ args[1]; mode ฌ $Section; -- Internal mode to indicate that a specific section is selected IF ( args.argc < 3 ) THEN { msg ฌ "wrong arguments"; GOTO Failed } ELSE cmdName ฌ args[2]; } ELSE { cmdName ฌ args[1]; }; IF cmdName = NIL THEN { msg ฌ "wrong arguments"; GOTO Failed; }; fileNames ฌ FindMatches[cmd, cmdName, mode, section]; IF ( fileNames = NIL ) THEN { msg ฌ cmdName.Concat[": not found"]; GOTO Failed }; FOR tail: LIST OF ROPE ฌ fileNames, tail.rest WHILE ( tail # NIL ) DO fileName: ROPE ~ tail.first; IF mode = $List THEN { sectionsSeen ฌ ReportFile[cmd, fileName, sectionsSeen]; } ELSE { tempName: ROPE ~ TemporaryFile[fileName.Concat[".tioga"]]; found: BOOL ฌ TRUE; FileInfo[tempName ! FileError => { found ฌ FALSE; CONTINUE }]; IF ( NOT found ) THEN FormatManPage[fileName, tempName, stderr ! FileError => { msg ฌ huh; GOTO Failed }] ELSE LoadFromCache[fileName, tempName, stderr]; } ENDLOOP; }; EXITS Failed => { result ฌ $Failed }; }; LocalManCmd: Commander.CommandProc ~ { stderr: IO.STREAM ~ cmd.err; args: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd ! CommanderOps.Failed => { msg ฌ errorMsg; GO TO Failed }]; IF ( args.argc < 2 ) THEN { msg ฌ "wrong arguments"; GOTO Failed }; { fileName: ROPE ~ args[1]; tempName: ROPE ~ TemporaryFile[fileName.Concat[".man.tioga"]]; FormatManPage2[fileName, tempName, stderr ! FileError => { msg ฌ huh; GOTO Failed }] }; EXITS Failed => { result ฌ $Failed }; }; UsageMsg: ROPE = "Tioga access to the Unix(tm) man pages. Usage: man [-f | -a | -l | sectionNumber] name The first argument (optional) specifies the mode. The switches are: -f First page: lowest section number, earliest directory in $MANPATH -a All pages -l List of pages: produces a list of the commands to access each specific page Alternatively, a sectionNumber may be specified to access the page from that section. The second argument is the name of the desired page. "; CreateTab: PROC RETURNS [tab: SymTab.Ref] ~ { cc: PROC [name: ROPE, cmd: Cmd] ~ INLINE { ref: REF Cmd ~ NEW[Cmd ฌ cmd]; [] ฌ tab.Store[name, ref]; }; tab ฌ SymTab.Create[]; cc["B", b]; cc["BI", bi]; cc["BR", br]; cc["DT", notImplem]; cc["HP", hp]; cc["I", i]; cc["IB", ib]; cc["IP", ip]; cc["IR", ir]; cc["IX", notImplem]; cc["LP", lp]; cc["PD", notImplem]; cc["PP", pp]; cc["RE", re]; cc["RB", rb]; cc["RI", ri]; cc["RS", rs]; cc["SB", sb]; cc["SH", sh]; cc["SM", sm]; cc["SS", ss]; cc["TH", th]; cc["TP", tp]; cc["TX", tx]; cc["as", as]; cc["br", pp]; cc["ds", ds]; cc["fi", fi]; cc["ft", ft]; cc["hy", notImplem]; cc["ne", notImplem]; cc["nf", nf]; cc["nh", notImplem]; cc["nx", nx]; cc["rm", rm]; cc["rn", rn]; cc["so", so]; cc["sp", hp]; cc["ta", notImplem]; cc["ti", tp]; cc["\\""", comment]; }; Commander.Register["man", ManCmd, UsageMsg]; Commander.Register["LocalMan", LocalManCmd, "file\n Tioga access to a (local) man page"]; }. ( ManImpl.mesa Copyright ำ 1988, 1990, 1991, 1992 by Xerox Corporation. All rights reserved. Christian Le Cocq July 29, 1988 Bill Jackson (bj), March 22, 1990 8:30 pm PST Michael Plass, May 27, 1992 11:20 am PDT Weiser, November 1, 1991 8:41 am PST Jim Thornton July 20, 1993 2:15 pm PDT Derived from CedarChest7.0's ManImpl.mesa of 23-Aug-88 12:45:40 PDT translates (most of the) troff -man source into Tioga document format File Stuff IF ( shortName.Fetch[] # '/ ) THEN manDir.Cat[slash, shortName]; Open a STREAM to a file specified with a relative path (eg. man3/textdomain.3). The correct file is found relative to the file that is already open. This procedure is used only for included files. manifest constants Text/Tioga Formats Types & Globals Operations keeps only the first comments Looks Procs Symbols Simple Formatting Operations Break Procs Actual conversion Produce a meaningful title for the viewer, by extracting manual page name and section number from the filename. Profile Operations List Production Commander Operations Help Init ส0e–(cedarcode) style•NewlineDelimiter ™code•Mark outsideHeaderšœ ™ Kšœ ฯeœC™NK™K™-K™(K™$K™&K˜KšœC™CK™—šฯk ˜ Kšžœžœ ˜K˜ Kšœ ˜ Kšœ žœ˜.Kšžœ˜Kšœ žœ ˜Kšžœ˜Kšœ ˜ Kšœ žœ˜"Kšœžœ˜Kšœžœ!˜.K˜Kšœžœ*˜6Kšœ žœ˜Kšœ žœY˜kKšœ žœ˜ Kšœžœžœ˜Kšœžœ ˜Kšœ žœ2˜AKšœ žœ˜!K˜—K™EK™šฯnœžœž˜Kšžœžœ'žœ žœm˜ณKšžœžœžœ˜headšฯz ™ KšŸ œžœžœžœ˜$K˜šŸœžœ žœ˜#Kšžœžœ žœ ˜;Kšœžœžœ˜1Kšœžœžœžœ˜4Kšžœžœžœ˜8Kšœ˜K˜—š Ÿ œžœ žœžœžœžœ˜M˜—M˜K˜—šŸœžœ"žœžœžœžœ žœ˜hšŸœžœ ˜%šžœ žœ žœ˜$Kšœžœžœ žœžœžœžœžœ˜Hšžœ žœžœ/žœ˜IKšœžœ˜K˜—K˜—Kšœ˜—Kšœ žœ˜Kšœžœ˜ šžœ%žœž˜4Kšœžœ˜Kšžœ˜—šžœžœ žœž˜3Kšœ!˜!šžœžœ˜Kšœžœžœ,˜;Kšœžœ#˜-Kšžœ<žœ˜IKšœ˜—Kšžœ˜—K˜K˜—šŸ œžœ"žœžœ žœžœ žœžœžœžœ˜{Kšœžœ˜ Kšœžœžœ˜Kšœ žœžœ˜šŸœžœ ˜ šž˜Kšœžœ˜K˜—Kšœžœžœ˜$šžœžœ˜šžœ žœžœ˜Kšœžœ˜K˜/K˜—šžœ˜Kšœ žœ˜K˜,šžœ#žœ žœ˜9K˜K˜/K˜—K˜—K˜—šžœ˜Kšœžœ˜Kš žœ žœ0žœ žœ žœžœ˜pK˜K˜—šž˜Kšœžœ˜—K˜—Kšœžœžœ žœ#˜>šžœžœžœ žœžœ žœžœ˜JKš œ žœžœ žœžœžœ7˜wKšœžœžœ˜BKšžœ*žœ žœ˜Dšžœ žœ˜Kšžœ ˜Kšžœ˜K˜—Kšžœ žœžœžœ˜)Kšžœ˜—Kšœ˜——š ™Kšœ žœ˜Kšœžœ ˜K˜š Ÿ œžœ žœžœžœ˜7KšŸœžœžœžœžœžœžœžœžœ˜QKšœJ˜JKšœ˜—K˜Kšœžœ˜Kšœžœ˜Kšฯb œžœ ˜Kšกœžœ!˜,—š ™Kšœ žœ˜Kšœžœ ˜Kšœžœ ˜Kšœžœ ˜Kšœžœ ˜Kšœžœ ˜—š ™Kšœ žœžœ˜Kšœžœ˜Kšœžœ˜K˜Kšœžœฆ˜ฏK˜Kš œžœžœžœžœ ˜=K˜Kšœžœžœ ˜šœ žœžœ˜Kšœžœ˜Kšœ žœžœžœ˜*Kšœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœžœžœ˜Kšœžœ˜Kšœ žœ˜Kšœ˜Kšœžœžœ˜Kšœžœž˜Kšœžœž˜Kšœ˜——š  ™ š Ÿœžœžœ žœžœ˜BKšœžœ˜ Kšœ˜Kšœžœ˜ Kšœ žœžœ6˜\Kšœก œ˜(šžœžœ ž˜K˜0šžœž˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ žœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜K˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜K˜!Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ žœ˜Kšœฯc&˜AKšœ!ข ˜AKšœ ˜ Kšœข ˜*Kšžœžœข-˜?—Kš žœžœžœ žœžœ˜8Kšžœ˜—Kšœ˜Kšžœžœ žœžœžœžœ#žœ˜gK˜K™—šŸœžœžœ žœžœžœžœ˜YKšœžœžœ˜(Kšžœžœžœ˜(K˜K˜—šŸœžœžœ žœžœžœžœžœžœžœžœ˜\Kš œžœžœžœžœ˜Kšœ žœ˜%K˜ Kšžœžœžœžœ˜#šžœžœ˜"Kšœ;žœžœ ˜_K˜—šžœ˜Kšœ.žœžœ ˜RK˜—Kšžœ žœ˜K˜K˜—š Ÿ œžœžœžœžœ žœ˜šžœ ž˜Kšœ/˜/Kšžœ˜—Kšœ˜Kšžœ˜K˜—K˜—šœ˜Kšœ˜šžœ ž˜Kšœ ˜ Kšžœ˜%—Kšœข˜—Kšœ!˜!Kšœ#˜#šœ˜Kšœžœžœ ˜šžœž˜Kšœ&˜&Kšœ'˜'Kšœ&˜&Kšœ-˜-Kšžœžœข˜%—K˜—šœ˜Kšœžœžœ ˜šžœž˜Kšœ@˜@Kšœ'žœ˜7Kšœ'žœ˜7Kšžœžœข˜%—K˜—šœ˜Kšœžœ ˜šžœž˜ šœ˜Kšœžœžœ ˜Kšžœžœ˜'šžœข ˜Kšœ˜Kšœ(˜(Kšœ˜Kšœข=˜@—K˜—Kšœžœžœžœ˜9K•CharSets5๏šœžœžœžœ˜9šœ˜šžœžœ ž˜K–๏šœ ˜ Kšœ ˜ Kšžœ˜—Kšœ˜—Kšœžœžœžœ˜9Kšœžœžœžœ˜:Kšœžœžœžœ˜9Kšœžœžœžœ˜9Kšœžœžœžœ˜:šžœข)˜6Kšœ˜Kšœ2žœ˜BKšœ˜K˜——Kšœ˜—Kšžœ+˜2—K˜—Kšžœžœ˜ Kšžœ˜—K˜K˜—š Ÿœžœžœžœžœžœ ˜CKšœžœ3˜FKšœžœžœ˜!Kšœžœ˜ Kš žœžœžœžœ ข˜CKšžœžœžœžœ˜!Kšžœžœžœžœ˜ Kšžœžœžœžœ˜ Kšœžœ"˜+Kšžœžœžœžœ˜ šžœžœž˜Kšžœžœžœ ˜CKšœ˜Kšžœ˜—Kš žœžœžœžœ ข˜TKšœ˜Kšœžœ ˜šžœžœž˜Kš œžœกœžœžœ&žœ ˜vKšœ/˜/šžœžœžœ˜5Kšœ žœ]˜mK˜Kšœ˜—Kšœ#˜#Kšžœ˜—K˜K˜—š Ÿœžœžœžœžœžœ˜5Kšœžœžœžœ˜Jšžœ!žœข˜?Kšœ#˜#—šž˜K˜ —K˜——š  ™ šŸ œžœžœžœ˜8Kšœ˜Kšœ˜K˜K˜—šŸœžœžœžœ˜6Kšœžœ˜#Kšœžœ˜Kšžœžœ žœžœ&˜PK˜K˜—šŸ œžœžœžœ˜7Kšœžœ˜#Kšœžœ˜Kšœžœžœ˜!šžœžœž˜šžœ žœ˜šžœž˜šžœžœžœ˜6Kšœ1˜1Kšœ!˜!Kšžœ˜K˜—Kšœ˜——šžœžœžœ˜$Kšœ žœ4žœ˜Yšžœ žœ˜Kšœ1˜1Kšœ!˜!Kšœ˜—K˜—Kšœ˜Kšžœ˜—Kšžœ žœ˜#K˜K˜—šŸœžœžœžœ˜5Kšœžœ˜#Kšœžœ˜Kšžœžœ žœžœ!˜KK˜K˜—šŸœžœžœžœ˜6Kšœ˜K˜'K˜K˜—šŸ œžœžœžœ˜;Kšžœžœžœžœ˜*šžœ˜Kš œžœžœžœžœ˜–[self: STREAM]šžœžœ ž˜Kšœžœ˜$Kšžœžœžœ˜;Kšžœ žœ ž˜#Kšžœ)˜-Kšžœ˜—K˜—K˜K˜—šŸ œžœžœžœ˜Cšžœžœžœ˜Kšœžœ˜Kšœžœ˜K˜—šžœ˜Kšœžœ˜!Kšœ˜Kšœ˜Kš œ%žœ žœžœžœ˜GKšžœ žœ ž˜#K˜—K˜K˜—šŸ œžœžœžœ˜CKš œžœžœžœžœ˜Kšœžœžœ˜–[self: STREAM]šžœžœ ž˜Kšœžœ˜$Kš žœžœžœžœžœžœ˜VKšœ žœ˜Kšžœ˜—Kšžœ žœ ž˜#Kšžœ)˜-K˜K˜—šŸœžœžœ˜)Kš œžœžœžœžœ˜Kš œžœžœžœ žœžœžœ ˜7šžœž˜Kšœžœ˜$Kšœžœ˜%Kšœžœ˜$Kšœ žœ˜+Kšžœžœข˜%—K˜——š ™š Ÿ œžœžœžœžœžœ˜AKšœžœ˜Kšœžœžœžœžœžœžœžœžœžœ˜?K–9[base: ROPE, start: INT _ 0, len: INT _ 2147483647]šœ+˜+šžœ žœžœžœžœžœžœž˜;Kšœ˜Kšžœ˜—Kšžœ žœžœ˜Kšžœžœ˜-Kšžœ žœžœ˜Kšœ-˜-K˜K˜—šŸ œžœžœ˜.Kšœ žœ˜K˜Kšžœžœžœ ˜>–1[x: SymTab.Ref, key: ROPE, val: SymTab.Val]šžœžœž˜*Kšœžœ7˜AKšžœ)˜0—K˜K˜—šŸ œžœžœ˜,Kšœ žœ˜K˜K–1[x: SymTab.Ref, key: ROPE, val: SymTab.Val]šœ!˜!K˜K˜—šŸ œžœžœ˜/Kšœ žœ˜K˜K–1[x: SymTab.Ref, key: ROPE, val: SymTab.Val]šœ˜K˜K˜—šŸ œžœžœ˜/Kšœ žœ˜Kšœžœ˜ K˜K–1[x: SymTab.Ref, key: ROPE, val: SymTab.Val]šœ ˜ šžœžœ˜Kšœžœ˜Kš œžœ žœžœžœžœžœ˜,Kšœžœ žœžœ˜1K˜—šœ˜Kšœžœ˜K–[x: SymTab.Ref, key: ROPE]šœ&˜&Kšžœžœ žœžœ˜Kšœ!˜!K˜—K˜——š ™KšŸœžœžœ2˜HKšŸœžœžœ2˜KKšŸœžœžœ2˜IKšŸ œžœžœ6˜QKšŸœžœžœ6˜NKšŸœžœžœ6˜NKšŸœžœžœ6˜NKšŸœžœžœ6˜NKšŸœžœžœ6˜NKšŸœžœžœ6˜N—š  ™ šŸœžœžœ˜+Kšžœžœžœ2˜OK˜K˜—šŸœžœžœ˜&Kšžœžœžœ˜*Kšžœ8˜K˜šž˜Kšœ žœ ˜—K˜K˜—š Ÿ œžœ žœ žœ žœžœ˜KKšœ&˜&Kšœžœ˜"K˜ZKšœžœžœ˜(Kšœ˜Kšœ"žœ˜9Kšœ˜Kšœ$ข˜7K˜K˜K˜—š Ÿ œžœ žœ žœ žœžœ˜KKšœ&˜&Kšœžœ˜"K˜ZKšœ"žœ˜9Kšœ˜K˜$K˜K˜'K˜K˜K˜—š Ÿœžœ žœ žœ žœžœ˜LKšœ&˜&Kšœžœžœ˜$Kšœ˜Kšœ$ข˜7Kšœ+˜+K˜——š ™šŸœžœžœžœ˜(Kšœžœ<˜HKšžœžœžœžœ ˜:Jš žœžœžœžœžœ˜=Jšžœžœ˜K˜——š ™šŸ œžœ#žœžœžœžœžœžœ žœžœžœ˜vMšœžœ˜ Mšœ ž˜Mšœžœžœ˜M˜Mšœ=žœ ˜Jš žœžœžœžœžœžœž˜?šžœ$žœžœ˜3Mšœ žœ˜Mšžœ˜M˜—Mšžœ˜—šžœžœœ˜Mšžœ!žœ˜6M˜—šžœ˜Mšžœžœžœžœ˜HMšœ žœ˜!M˜—M˜šž˜Mšœ žœ%žœ˜D—M˜——š ™KšŸœžœžœ˜K˜šŸœ˜!Kšžœ)žœ˜8Kšœžœžœ ˜šœ:˜:Kšœ+žœžœ ˜;—Kšžœžœžœ ˜C˜Kšœ ž œ˜(Kšœ žœžœ˜Kšœžœ˜Kšœ žœžœ˜Kš œžœžœžœžœ˜!Kšœ žœžœžœ˜šžœžœ˜šžœJžœ ž˜cK˜K˜K˜Kšžœžœ ˜4—Kšžœžœžœ ˜BKšžœ˜K˜—šžœžœžœ˜%K˜Kšœข@˜RKšžœžœžœ ˜BKšžœ˜K˜—šžœ˜K˜K˜—šžœ žœžœ˜K˜Kšžœ˜ K˜—K˜5Kšžœžœžœ(žœ ˜Qš žœžœžœžœžœ žœž˜EKšœ žœ˜šžœžœ˜K˜7K˜—šžœ˜Kšœ žœ,˜:Kšœžœžœ˜Kšœ+žœžœ˜>šžœžœ˜šžœ)˜-Kšœžœ ˜*—Kšžœ,˜0—K˜—Kšžœ˜—K˜—Kšžœ ˜%K˜K˜—šŸ œ˜&Kšœžœžœ ˜šœ:˜:Kšœ+žœžœ ˜;—Kšžœžœžœ ˜Cšœ˜Kšœ žœ ˜Kšœ žœ0˜>šœ)˜)Kšœžœ ˜*—K˜—Kšžœ ˜%K˜——š ™šœ žœ˜J˜า——š ™šŸ œžœžœ˜-šœžœžœžœ˜*Kšœžœžœ ˜Kšœ˜K˜—Kšœ˜Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ˜Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ˜Kšœ ˜ Kšœ˜Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ˜Kšœ˜Kšœ ˜ Kšœ˜Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ˜Kšœ ˜ Kšœ˜K˜K˜——K˜,KšœY˜YK˜Kšœ˜K˜—K˜—…—{ะฐ]