DIRECTORY Basics, BasicTime, Commander, CommanderOps, Convert, IO, PFS, PFSNames, Prop, Rope, RedBlackTree, TiogaAccess; GenerateRequireInfo: CEDAR MONITOR IMPORTS BasicTime, Commander, CommanderOps, Convert, IO, PFS, PFSNames, Prop, Rope, RedBlackTree, TiogaAccess ~ BEGIN PATH: TYPE ~ PFS.PATH; STREAM: TYPE ~ IO.STREAM; ROPE: TYPE ~ Rope.ROPE; FileEntry: TYPE = REF FileEntryRep; FileEntryRep: TYPE = RECORD [ lookupName: ROPE, -- without version number fullNamePath: PATH, fullNameRope: ROPE, shortName: ROPE, isCMFile: BOOL ¬ FALSE, processed: BOOL ¬ FALSE, requireList: ROPE, runList: ROPE, invoker: ROPE -- for real files, name of require file ]; rbTable: RedBlackTree.Table; bangH: PATH ~ PFS.PathFromRope["!H"]; starBangH: PATH ~ PFS.PathFromRope["*!H"]; defaultEnumPath: ROPE ~ "/Cedar/Commands/"; maxSanityCount: INT ¬ 7; verbose: BOOL ¬ FALSE; GenerateRequireInfo: ENTRY Commander.CommandProc = { ENABLE UNWIND => NULL; out: STREAM ¬ cmd.out; enumRope, outFile: ROPE; enumPath: PATH; cmdEntryList: LIST OF FileEntry ¬ NIL; numFiles: INT ¬ 0; fileListStream: STREAM; eachEntryList: LIST OF FileEntry; sanityCount: INT ¬ 0; mightbeCMName, arg: ROPE; count: CARD ¬ 0; CheckCount: PROC = { IF ( count ¬ count.SUCC ) MOD 10 = 0 THEN out.PutF1["(%g) ", [cardinal[count]] ] ELSE out.PutChar['.]; }; CollectCmdFiles: PFS.InfoProc = { short, lookupName: ROPE; new: FileEntry ¬ NIL; IF fileType = PFS.tDirectory THEN RETURN; -- ignore directories short ¬ PFSNames.ComponentRope[PFSNames.ShortName[fullFName]]; IF short.Equal["."] OR short.Equal[".."] THEN RETURN; -- ignore these lookupName ¬ PFS.RopeFromPath[PFSNames.StripVersionNumber[fullFName]]; new ¬ NEW[FileEntryRep ¬ [ lookupName: lookupName, fullNamePath: fullFName, fullNameRope: lookupName, shortName: PFSNames.ComponentRope[PFSNames.ShortName[fullFName]], isCMFile: TRUE ]]; cmdEntryList ¬ CONS[new, cmdEntryList]; RedBlackTree.Insert[rbTable, new, lookupName]; }; EachEntry: RedBlackTree.EachNode = { WITH data SELECT FROM entry: FileEntry => { IF NOT entry.processed THEN eachEntryList ¬ CONS[entry, eachEntryList]; RETURN; }; ENDCASE => out.PutRope["\n**Non-FileEntry in RedBlackTree table\n"]; }; WriteInvertedInfoOnFile: RedBlackTree.EachNode = { WITH data SELECT FROM entry: FileEntry => { IF entry.invoker = NIL THEN RETURN; fileListStream.PutF["%g (%g)\n\n", [rope[entry.shortName]], [rope[entry.invoker]] ]; }; ENDCASE => out.PutRope["\n**Non-FileEntry in RedBlackTree table\n"]; }; arg ¬ CommanderOps.NextArgument[cmd]; IF arg.Equal["-o"] THEN { outFile ¬ CommanderOps.NextArgument[cmd]; enumRope ¬ CommanderOps.NextArgument[cmd]; } ELSE enumRope ¬ arg; IF enumRope = NIL THEN enumRope ¬ defaultEnumPath; enumPath ¬ PFS.PathFromRope[enumRope]; out.PutF["\nStarting GenerateRequireInfo %g @ %g\n\n", [rope[enumRope]], [time[BasicTime.Now[]]] ]; rbTable ¬ RedBlackTree.Create[getKey: GetKey, compare: Compare]; IF ( mightbeCMName ¬ CommanderOps.NextArgument[cmd] ) # NIL THEN { DO new: FileEntry ¬ NIL; fullFName: PATH; fullFName ¬ PFS.FileInfo[PFS.AbsoluteName[PFS.PathFromRope[mightbeCMName], enumPath] ! PFS.Error => { out.PutF1["\n***Couldn't find %g\n", [rope[mightbeCMName]] ]; CONTINUE }].fullFName; IF fullFName # NIL THEN { lookupName: ROPE ~ PFS.RopeFromPath[PFSNames.StripVersionNumber[fullFName]]; new ¬ NEW[FileEntryRep ¬ [ lookupName: lookupName, fullNamePath: fullFName, fullNameRope: lookupName, shortName: PFSNames.ComponentRope[PFSNames.ShortName[fullFName]], isCMFile: TRUE]]; cmdEntryList ¬ CONS[new, cmdEntryList]; RedBlackTree.Insert[rbTable, new, lookupName]; }; IF ( mightbeCMName ¬ CommanderOps.NextArgument[cmd] ) = NIL THEN EXIT; ENDLOOP; } ELSE PFS.EnumerateForInfo[PFS.AbsoluteName[starBangH, enumPath], CollectCmdFiles]; FOR ceL: LIST OF FileEntry ¬ cmdEntryList, ceL.rest UNTIL ceL = NIL DO DoOneFile[out, cmd, ceL.first]; CheckCount[]; ceL.first.processed ¬ TRUE; ENDLOOP; DO RedBlackTree.EnumerateIncreasing[rbTable, EachEntry]; IF eachEntryList = NIL THEN EXIT; FOR eeL: LIST OF FileEntry ¬ eachEntryList, eeL.rest UNTIL eeL = NIL DO DoOneFile[out, cmd, eeL.first]; CheckCount[]; eeL.first.processed ¬ TRUE; ENDLOOP; eachEntryList ¬ NIL; IF ( sanityCount ¬ sanityCount + 1 ) > maxSanityCount THEN { out.PutF1["\n** sanityCount > %g, exitting\n", [integer[maxSanityCount]] ]; EXIT }; IF verbose THEN out.PutF1["\n~~~ sanityCount: %g\n", [integer[sanityCount]] ]; ENDLOOP; DumpToFile[out, outFile, rbTable]; out.PutF1["Finished at %g\n", [time[BasicTime.Now[]]] ]; }; DoOneFile: PROC[out: STREAM, cmd: Commander.Handle, entry: FileEntry] = { ENABLE PFS.Error => { out.PutF1["\n**PFS.Error: %g\n", [rope[error.explanation]] ]; GOTO quitThisOne; }; strm: STREAM ¬ PFS.StreamOpen[entry.fullNamePath]; which: ROPE ¬ PFS.RopeFromPath[entry.fullNamePath]; this, line: ROPE; alreadyIgnoring: BOOL ¬ FALSE; IF verbose THEN out.PutF1["Starting %g\n", [rope[which]] ]; UNTIL strm.EndOf[] DO from: STREAM; line ¬ IO.GetLineRope[strm]; from ¬ IO.RIS[line]; this ¬ NextArgToken[from]; IF this = NIL THEN LOOP; SELECT TRUE FROM this.Equal["Require", FALSE] => ProcessRequireLine[out, from, line, entry]; this.Equal["Run", FALSE] => ProcessRunLine[out, from, line, entry]; this.Equal["From", FALSE] => ProcessFromLine[out, from, line, which, entry]; this.Equal["Install", FALSE] => ProcessInstallLine[out, from, line, entry]; this.Equal["Alias", FALSE] => NULL; this.Equal["Echo", FALSE] => NULL; this.Equal["CD", FALSE] => NULL; this.Equal["Synonym", FALSE] => NULL; this.Equal[";", FALSE] => NULL; this.Equal["UnixCommand", FALSE] => NULL; this.Equal["interp", FALSE] => NULL; this.Equal["RunGlobalDefaultSwitches", FALSE] => NULL; this.Equal["CommanderViewer", FALSE] => NULL; this.Equal["repaint", FALSE] => NULL; ENDCASE => { IF NOT alreadyIgnoring THEN out.PutF1["~~From %g, ignoring:\n", [rope[which]] ]; out.PutF1["\t%g\n", [rope[line]] ]; alreadyIgnoring ¬ TRUE; }; ENDLOOP; strm.Close[]; IF verbose THEN out.PutF1["Done with %g\n", [rope[which]] ]; EXITS quitThisOne => {}; }; ProcessRunLine: PROC[out, from: STREAM, line: ROPE, caller: FileEntry] = { relPath: PATH ¬ PFSNames.Directory[caller.fullNamePath]; fullFName: PATH; lookupEntry: FileEntry; insideMinusU: BOOL ¬ FALSE; DO next: ROPE ¬ NextArgToken[from]; IF next = NIL THEN RETURN; IF ( next.Fetch[0] = '- ) THEN { SELECT next.Fetch[1] FROM 'u, 'U => insideMinusU ¬ TRUE; 'x, 'X => insideMinusU ¬ FALSE; 'p, 'P => [] ¬ NextArgToken[from]; ENDCASE => NULL; LOOP; }; IF insideMinusU THEN LOOP; -- ignore these lookupEntry ¬ LookupSomethingToRun[out, next, relPath]; IF lookupEntry # NIL THEN AddToRunList[out, caller, lookupEntry, FALSE] ELSE out.PutF["\nCould not find %g (%g)\n(%g)\n", [rope[next]], [rope[caller.fullNameRope]], [rope[line]] ]; ENDLOOP; }; LookupSomethingToRun: PROC[out: STREAM, file: ROPE, relPath: PATH] RETURNS[lookupEntry: FileEntry ¬ NIL] = { lookupPath: PFS.PATH ¬ PFSNames.Cat[relPath, PFS.PathFromRope[Rope.Cat["sun4-o3/", file, ".c2c.o"]]]; IF ( lookupEntry ¬ CheckAndEnter[out, lookupPath, TRUE, TRUE] ) # NIL THEN RETURN; lookupPath ¬ PFSNames.Cat[relPath, PFS.PathFromRope[Rope.Concat["sun4-o3/", file]]]; IF ( lookupEntry ¬ CheckAndEnter[out, lookupPath, TRUE, TRUE] ) # NIL THEN RETURN; lookupPath ¬ PFSNames.Cat[relPath, PFS.PathFromRope[Rope.Cat["sun4/", file, ".c2c.o"]]]; IF ( lookupEntry ¬ CheckAndEnter[out, lookupPath, TRUE, TRUE] ) # NIL THEN RETURN; lookupPath ¬ PFSNames.Cat[relPath, PFS.PathFromRope[Rope.Concat["sun4/", file]]]; IF ( lookupEntry ¬ CheckAndEnter[out, lookupPath, TRUE, TRUE] ) # NIL THEN RETURN; lookupPath ¬ PFSNames.Cat[relPath, PFS.PathFromRope[file]]; lookupEntry ¬ CheckAndEnter[out, lookupPath, TRUE, TRUE]; }; ProcessRequireLine: PROC[out, from: STREAM, line: ROPE, caller: FileEntry] = { lookupPath: PATH; this: FileEntry; world, component, resource: ROPE; world ¬ NextArgToken[from]; component ¬ NextArgToken[from]; resource ¬ NextArgToken[from]; IF ( world = NIL ) OR ( component = NIL ) OR ( resource = NIL ) THEN { out.PutF1["\n*** problems with the line: %g\n", [rope[line]] ]; RETURN; }; lookupPath ¬ PFS.PathFromRope[IO.PutFR["/%g/%g/%g.require", [rope[world]], [rope[component]], [rope[resource]] ]]; this ¬ CheckAndEnter[out, lookupPath, FALSE, FALSE]; IF this = NIL THEN RETURN; AddToRequireList[caller, this.fullNameRope]; }; ProcessInstallLine: PROC[out, from: STREAM, line: ROPE, caller: FileEntry] = { relPath: PATH ¬ PFSNames.Directory[caller.fullNamePath]; lookupPath: PATH; lookupEntry: FileEntry; resource: ROPE ¬ NextArgToken[from]; IF ( resource = NIL ) THEN { out.PutF1["\n*** problems with the line: %g\n", [rope[line]] ]; RETURN; }; lookupPath ¬ PFSNames.Cat[relPath, PFS.PathFromRope[resource.Concat[".require"]]]; lookupEntry ¬ CheckAndEnter[out, lookupPath, FALSE, FALSE]; IF lookupEntry = NIL THEN RETURN; AddToRequireList[caller, lookupEntry.fullNameRope]; }; ProcessFromLine: PROC[out, from: STREAM, line, which: ROPE, caller: FileEntry] = { lookupEntry: FileEntry; dir, shouldBeRun, resource: ROPE; dir ¬ NextArgToken[from]; shouldBeRun ¬ NextArgToken[from]; IF NOT shouldBeRun.Equal["Run", FALSE] THEN { out.PutF["\n* (from %g) found %g instead of Run in %g\n", [rope[which]], [rope[shouldBeRun]], [rope[line]] ]; RETURN; }; resource ¬ NextArgToken[from]; IF ( dir = NIL ) OR ( resource = NIL ) THEN { out.PutF1["\n*** problems with the line: %g\n", [rope[line]] ]; RETURN; }; lookupEntry ¬ LookupSomethingToRun[out, resource, PFS.PathFromRope[dir]]; IF lookupEntry # NIL THEN AddToRunList[out, caller, lookupEntry, TRUE] ELSE out.PutF[" (from %g), couldn't find %g, %g\n", [rope[which]], [rope[dir]], [rope[resource]] ]; }; CheckAndEnter: PROC[out: STREAM, path: PATH, markAsDone, useShortName: BOOL] RETURNS[this: FileEntry] = { fullFName: PATH; fullFName ¬ PFS.FileInfo[name: path ! PFS.Error => { IF verbose THEN out.PutF["\n*PFS.Error[%g]\n\tasking for %g\n", [rope[error.explanation]], [rope[PFS.RopeFromPath[path]]] ]; fullFName ¬ NIL; CONTINUE; } ].fullFName; IF fullFName = NIL THEN RETURN[NIL]; this ¬ NEW[FileEntryRep ¬ [ lookupName: NIL, fullNamePath: fullFName, fullNameRope: PFS.RopeFromPath[PFSNames.StripVersionNumber[fullFName]], shortName: PFSNames.ComponentRope[PFSNames.ShortName[fullFName]], processed: markAsDone]]; this.lookupName ¬ IF useShortName THEN this.shortName ELSE this.fullNameRope; WITH RedBlackTree.Lookup[rbTable, this.lookupName] SELECT FROM entry: FileEntry => RETURN[entry]; ENDCASE; RedBlackTree.Insert[rbTable, this, this.lookupName]; RETURN[this]; }; AddToRunList: PROC[out: STREAM, caller, lookupEntry: FileEntry, useFullName: BOOL] = { who: ROPE ~ IF useFullName THEN lookupEntry.fullNameRope ELSE lookupEntry.shortName; IF caller.runList = NIL THEN caller.runList ¬ who ELSE caller.runList ¬ caller.runList.Cat[" ", who]; IF lookupEntry.invoker # NIL THEN out.PutF["%g is already being invoked by %g\n", [rope[lookupEntry.fullNameRope]], [rope[lookupEntry.invoker]] ]; lookupEntry.invoker ¬ caller.fullNameRope; }; AddToRequireList: PROC[caller: FileEntry, who: ROPE] = { IF caller.requireList = NIL THEN caller.requireList ¬ who ELSE caller.requireList ¬ caller.requireList.Cat[" ", who]; }; NextArgToken: PROC[from: STREAM] RETURNS[token: ROPE ¬ NIL] = { [] ¬ from.SkipWhitespace[]; token ¬ from.GetTokenRope[IO.IDProc ! IO.EndOfStream => CONTINUE].token; }; GetKey: RedBlackTree.GetKey = { RETURN [data]; }; Compare: RedBlackTree.Compare = { key: ROPE ¬ NIL; WITH k SELECT FROM ent: FileEntry => key ¬ ent.lookupName; rope: ROPE => key ¬ rope; ENDCASE => ERROR; WITH data SELECT FROM ent: FileEntry => RETURN [Rope.Compare[key, ent.lookupName, FALSE]]; ENDCASE; ERROR; }; DumpToFile: PROC[out: STREAM, outName: ROPE, rbTable: RedBlackTree.Table] = { tc: TiogaAccess.TiogaChar ¬ [charSet: 0, char: '\n, looks: ALL[FALSE], format: NIL, comment: TRUE, endOfNode: TRUE, deltaLevel: 1, propList: Prop.Put[propList: NIL, key: $NewlineDelimiter, val: Rope.Flatten["\n"]] ]; PutCharB: Rope.ActionType = { tc.char ¬ c; TiogaAccess.Put[writer, tc] }; PutRope: PROC [ rope: ROPE ] = { [] ¬ rope.Map[action: PutCharB] }; PutRopeBold: PROC [ rope: ROPE ] = { tc.looks['b] ¬ TRUE; [] ¬ rope.Map[action: PutCharB]; tc.looks['b] ¬ FALSE; }; PutRopeItalic: PROC [ rope: ROPE ] = { tc.looks['i] ¬ TRUE; [] ¬ rope.Map[action: PutCharB]; tc.looks['i] ¬ FALSE; }; EndNode: PROC [ delta: INTEGER ¬ 0, format: ATOM ¬ NIL ] = { tc.char ¬ '\n; tc.format ¬ format; tc.deltaLevel ¬ delta; tc.endOfNode ¬ TRUE; TiogaAccess.Put[writer, tc]; tc.endOfNode ¬ FALSE; }; DumpRequireInfo: RedBlackTree.EachNode = { WITH data SELECT FROM entry: FileEntry => { level: INT ¬ 0; IF entry.invoker # NIL THEN RETURN; tc.format ¬ $block; PutRopeBold[entry.fullNameRope]; IF entry.requireList # NIL THEN { EndNode[1, $block]; PutRopeItalic["Requires: "]; EndNode[1, $block]; tc.format ¬ $ragged; PutRope[entry.requireList]; EndNode[-1, $ragged]; level ¬ 1; }; IF entry.runList # NIL THEN { IF level = 0 THEN EndNode[1, $block]; PutRopeItalic["Runs: "]; EndNode[1, $block]; tc.format ¬ $ragged; PutRope[entry.runList]; EndNode[-1, $ragged]; level ¬ 1; }; IF level = 1 THEN EndNode[-1, $block]; }; ENDCASE => out.PutRope["\n**Non-FileEntry in RedBlackTree table\n"]; }; DumpInvertedInfo: RedBlackTree.EachNode = { WITH data SELECT FROM entry: FileEntry => { IF entry.invoker = NIL THEN RETURN; PutRopeBold[entry.shortName]; PutRope[" "]; PutRopeItalic[entry.invoker]; EndNode[0, $block]; }; ENDCASE => out.PutRope["\n**Non-FileEntry in RedBlackTree table\n"]; }; defaultName: ROPE ~ "Cedar.requireInfo"; writer: TiogaAccess.Writer ¬ TiogaAccess.Create[]; tyme: BasicTime.GMT = BasicTime.Now[]; IF outName = NIL THEN outName ¬ defaultName; out.PutF1["\nStarting to generate %g\n", [rope[outName]] ]; TiogaAccess.Put[writer, tc]; tc.propList ¬ NIL; tc.comment ¬ TRUE; tc.endOfNode ¬ FALSE; PutRope[outName]; EndNode[1]; PutRope[ IO.PutFR1["Copyright Σ %g by Xerox Corporation. All rights reserved.", [rope[Convert.RopeFromInt[BasicTime.Unpack[tyme].year]]] ] ]; EndNode[]; PutRope[IO.PutFR1["Written %g", [time[tyme]] ]]; EndNode[-1]; PutRope["Command and Require files"]; EndNode[1, $head]; tc.comment ¬ FALSE; RedBlackTree.EnumerateIncreasing[rbTable, DumpRequireInfo]; EndNode[-1, $block]; -- seems like it should be -2, but ... tc.comment ¬ TRUE; PutRope["Modules and which require file runs them"]; tc.comment ¬ FALSE; EndNode[1, $head]; RedBlackTree.EnumerateIncreasing[rbTable, DumpInvertedInfo]; TiogaAccess.WriteFile[writer, outName]; out.PutF1["\nFinished %g\n", [rope[outName]] ]; }; SetSanityCount: Commander.CommandProc = { intR: ROPE ¬ CommanderOps.NextArgument[cmd]; IF intR = NIL THEN RETURN; maxSanityCount ¬ Convert.IntFromRope[intR ! Convert.Error => CONTINUE]; }; ShowSanityCount: Commander.CommandProc = { cmd.out.PutF1["SanityCount: %g\n", [integer[maxSanityCount]] ] }; SetVerbose: Commander.CommandProc = { verbose ¬ TRUE }; UnSetVerbose: Commander.CommandProc = { verbose ¬ FALSE }; Commander.Register["GenerateRequireInfo", GenerateRequireInfo, "Usage is: GenerateRequireInfo {-o outfile} {dir} {optional list of files in dir}"]; Commander.Register["gri", GenerateRequireInfo, "Usage is: GenerateRequireInfo {-o outfile} {dir} {optional list of files in dir}"]; Commander.Register["SetSanityCount", SetSanityCount]; Commander.Register["ShowSanityCount", ShowSanityCount]; Commander.Register["SetVerbose", SetVerbose]; Commander.Register["UnSetVerbose", UnSetVerbose]; END. .. B GenerateRequireInfo.mesa Copyright Σ 1991, 1992 by Xerox Corporation. All rights reserved. Willie-s, May 12, 1992 3:42 pm PDT we assume it is not in the table already [data: RedBlackTree.UserData] RETURNS [stop: BOOL _ FALSE] visiting the nodes entered from the .cm & .command files [data: RedBlackTree.UserData] RETURNS [stop: BOOL _ FALSE] called for each item in the table to write name on fileListStream. up to now, we've just looked at the .command & .cm files now everything should be in the tree, so dump the tree out.PutF["*line: %g\n\tthis: %g\n", [rope[line]], [rope[this]] ]; here is something to run out.PutF["~& LookupPath: %g\n", [rope[PFS.RopeFromPath[path]]] ]; be careful to do the lookup with the lookupName out.PutF["~* Added with fullNamePath: %g\n", [rope[PFS.RopeFromPath[fullFName]]] ]; [data: RedBlackTree.UserData] RETURNS [RedBlackTree.Key] [k: RedBlackTree.Key, data: RedBlackTree.UserData] RETURNS [Basics.Comparison] First Char: [charSet: 0, char: '\n, looks: LOOKS[], format: NIL, comment: FALSE, endOfNode: TRUE, deltaLevel: 1, propList: LIST[^[key: $FromTiogaFile, val: $Yes]]] Comment Char: [charSet: 0, char: 'F, looks: LOOKS[], format: $code, comment: TRUE, endOfNode: FALSE, deltaLevel: 0, propList: NIL] Normal Char: [charSet: 0, char: 'F, looks: LOOKS[], format: $code, comment: FALSE, endOfNode: FALSE, deltaLevel: 0, propList: NIL] Comment end of node (next node nested): [charSet: 0, char: '\n, looks: LOOKS[], format: $code, comment: TRUE, endOfNode: TRUE, deltaLevel: 1, propList: NIL] [data: RedBlackTree.UserData] RETURNS [stop: BOOL _ FALSE] called for each item in the table to write name on fileListStream. IF level = 1 THEN EndNode[-2, $block] ELSE EndNode[-1, $block]; [data: RedBlackTree.UserData] RETURNS [stop: BOOL _ FALSE] called for each item in the table to write name on fileListStream. Κt–(cedarcode) style•NewlineDelimiter ™šœ™Jšœ Οeœ7™BJ™"J™—IcodešΟk œ6žœžœ2˜xK˜šΟnœžœž˜"Kšžœ.žœžœ1˜mKšœž˜K˜Kšžœžœžœžœ˜Kšžœžœžœžœ˜Kšžœžœžœ˜K˜Kšœ žœžœ˜#šœžœžœ˜Kšœ žœΟc˜+Kšœžœ˜Kšœžœ˜Kšœ žœ˜Kšœ žœžœ˜Kšœ žœžœ˜Kšœ žœ˜Kšœ žœ˜Kšœ žœ '˜5K˜—K˜Kšœ˜Kšœžœžœ˜%Kšœ žœžœ˜*Kšœžœ˜+Kšœžœ˜Kšœ žœžœ˜K˜šŸœžœ˜4Kšžœžœžœ˜Kšœžœ ˜Kšœžœ˜Kšœ žœ˜Kšœžœžœ žœ˜&Kšœ žœ˜Kšœžœ˜Kšœžœžœ ˜!Kšœ žœ˜Kšœžœ˜šœžœ˜K˜—šŸ œžœ˜Kš žœžœžœžœ(žœ˜fK˜—K˜šŸœžœ ˜!Kšœžœ˜Kšœžœ˜Kš žœ žœ žœžœ ˜?K˜>Kš žœžœžœžœ ˜EK™(Kšœ žœ6˜FKšœžœ©žœ˜ΊKšœžœ˜'Kšœ.˜.K˜K˜—šŸ œ˜$KšΠck:™:K™8šžœžœž˜˜Kšžœžœžœžœ˜GKšžœ˜K˜—Kšžœ=˜D—K˜K˜—•StartOfExpansion> -- [data: RedBlackTree.UserData] RETURNS [stop: BOOL _ FALSE]šΟbœ˜2Kš‘:™:K™Cšžœžœž˜˜Kšžœžœžœžœ˜#KšœT˜T—K˜Kšžœ=˜D—K˜K˜—K˜%šžœžœ˜K˜)K˜*K˜Kšžœ˜K˜—Kšžœ žœžœ˜2Kšœ žœ˜&Kšœc˜cK˜K–@[getKey: RedBlackTree.GetKey, compare: RedBlackTree.Compare]˜@K˜šžœ6žœžœ˜Bšž˜Kšœžœ˜Kšœ ž˜šœ žœ žœžœ'˜TKšœžœKžœ˜f—šžœ žœžœ˜Kšœ žœžœ6˜Lšœžœ˜Kšœ˜Kšœ˜Kšœ˜KšœA˜AKšœ žœ˜—Kšœžœ˜'Kšœ.˜.K˜—Kšžœ6žœžœžœ˜FKšžœ˜—K˜Kšžœžœžœ5˜R—K˜š žœžœžœ$žœžœž˜FKšœ˜K˜ Kšœžœ˜Kšžœ˜—K˜K™8šž˜Kšœ5˜5Kšžœžœžœžœ˜!š žœžœžœ%žœžœž˜GKšœ˜K˜ Kšœžœ˜Kšžœ˜—Kšœžœ˜šžœ4žœ˜Kšœžœ˜"Kšžœ˜—K˜Kšœ4˜4Kšœ3žœ™SKšžœ˜ K˜K˜—šŸ œžœžœ/žœ˜VKš œžœžœ žœžœ˜TKšžœžœžœžœ2˜hšžœžœž˜!Kšœp˜p—K˜*K˜K˜—šŸœžœžœ˜8Kšžœžœžœžœ:˜xK˜K˜—š Ÿ œžœžœžœžœžœ˜?K˜Kšœžœ žœžœ˜HK˜K˜—–< -- [data: RedBlackTree.UserData] RETURNS [RedBlackTree.Key]šŸœ˜Kš‘8™8Kšžœ˜K˜K˜—–R -- [k: RedBlackTree.Key, data: RedBlackTree.UserData] RETURNS [Basics.Comparison]šŸœ˜!Kš‘N™NKšœžœžœ˜šžœžœž˜K˜'Kšœžœ˜Kšžœžœ˜—šžœžœž˜Kšœžœ$žœ˜DKšžœ˜—Kšžœ˜K˜K˜—šŸ œžœžœ žœ"˜Mšœ ™ Kš œžœ žœ žœ žœžœ$™——šœ ™ Kš œžœžœ žœžœ™t—šœ ™ Kš œžœžœ žœžœ™u—šœ'™'Kš œžœžœ žœžœ™t—Kš œ;žœžœ žœ žœ žœ.žœ5˜ΨK–* -- [c: CHAR] RETURNS [quit: BOOL _ FALSE]šŸœA˜IKšŸœžœ žœ)˜CšŸ œžœ žœ˜$Kšœžœ˜K–T[base: ROPE, start: INT _ 0, len: INT _ 2147483647, action: Rope.ActionType]˜ Kšœžœ˜K˜—šŸ œžœ žœ˜&Kšœžœ˜K–T[base: ROPE, start: INT _ 0, len: INT _ 2147483647, action: Rope.ActionType]˜ Kšœžœ˜K˜—š Ÿœžœ žœžœžœ˜ -- [data: RedBlackTree.UserData] RETURNS [stop: BOOL _ FALSE]š’œ˜*Kš‘:™:K™Cšžœžœž˜˜Kšœžœ˜Kšžœžœžœžœ˜#K˜Kšœ ˜ šžœžœžœ˜!Kšœ˜Kšœ˜Kšœ˜K˜Kšœ˜Kšœ˜K˜ K˜—šžœžœžœ˜Kšžœ žœ˜%Kšœ˜Kšœ˜K˜Kšœ˜Kšœ˜K˜ Kšœ˜—Kšžœ žœžœ™?Kšžœ žœ˜&—K˜Kšžœ=˜D—K˜—–> -- [data: RedBlackTree.UserData] RETURNS [stop: BOOL _ FALSE]š’œ˜+Kš‘:™:K™Cšžœžœž˜˜Kšžœžœžœžœ˜#Kšœ˜Kšœ˜Kšœ˜Kšœ˜—K˜Kšžœ=˜D—K˜K˜—Kšœ žœ˜(K˜2Kšœžœ˜&Kšžœ žœžœ˜-K˜;K˜Kšœ˜Kšœžœ˜Kšœ žœ˜Kšœžœ˜Kšœ˜Kšœ ˜ Kšœ žœƒ˜ŽKšœ ˜ Kšœžœ&˜0Kšœ ˜ K˜K˜%Kšœ˜K˜Kšœ žœ˜Kšœ;˜;Kšœ &˜;K˜Kšœ žœ˜K˜4Kšœ žœ˜Kšœ˜K˜Kšœ<˜žœ˜HK˜K˜—šŸœ˜(K˜CK˜—šŸ œ&žœ˜7K˜—šŸ œ&žœ˜:K˜—Kšœ“˜“Kšœƒ˜ƒKšœ5˜5Kšœ7˜7Kšœ-˜-šœ1˜1K˜—šžœ˜K˜—K˜K˜K˜K˜—K˜—…—