DIRECTORY Ascii USING [Upper, Lower], Basics USING [Comparison], BasicTime USING [Now], Commander USING [CommandProc, Register], CommandTool USING [ArgumentVector, Parse], DefaultRemoteNames USING [Get], DFUtilities USING [DirectoryItem, FileItem, ParseFromStream, ProcessItemProc, SyntaxError], EditSpan USING [Copy], FS USING [ComponentPositions, EnumerateForNames, Error, ExpandName, NameProc, StreamOpen], IO USING [BreakProc, Close, EndOf, EndOfStream, Error, GetChar, GetTokenRope, PutF, PutF1, PutFR, PutFR1, RIS, rope, SetIndex, SkipWhitespace, STREAM, time], List USING [Nconc1], NodeProps USING [PutProp, true], Process USING [CheckForAbort], PutGet USING [FromFile], RedBlackTree USING [Compare, Create, DuplicateKey, EnumerateIncreasing, GetKey, Insert, UserData, Size, Table], Rope USING [Cat, Compare, Concat, Equal, Fetch, Find, IsEmpty, Length, Match, ROPE, Substr, Translate], TextNode USING [Body, Level, Location, MakeNodeLoc, NodeRope, nullLocation, nullSpan, Ref, Root, Span, StepForward], TiogaFileOps USING [AddLooks, CreateRoot, InsertAsLastChild, InsertNode, NodeBody, SetContents, SetFormat, Store], UserCredentials USING [Get], UserProfile USING [Token], ViewerIO USING [CreateViewerStreams]; CatalogImpl: CEDAR PROGRAM IMPORTS Ascii, BasicTime, Commander, CommandTool, DefaultRemoteNames, DFUtilities, EditSpan, FS, IO, List, NodeProps, RedBlackTree, Process, PutGet, Rope, TextNode, TiogaFileOps, UserCredentials, UserProfile, ViewerIO EXPORTS TiogaFileOps -- export NodeBody to convince Compiler we know that TiogaFileOps.Ref is really TextNode.Ref ~ BEGIN ROPE: TYPE ~ Rope.ROPE; Ref: TYPE ~ REF NodeBody; NodeBody: PUBLIC TYPE ~ TextNode.Body; CatalogCmdProc: Commander.CommandProc ~ { systemHost: ROPE _ DefaultRemoteNames.Get[].systemHost; serverName: ROPE _ Rope.Substr[systemHost, 1, systemHost.Length-2]; -- strip off the brackets catalogName: ROPE; catalogFileName: ROPE; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd]; quietly: BOOLEAN _ FALSE; i: NAT _ 1; WHILE i < argv.argc DO IF Rope.Fetch[argv[i], 0] = '- THEN { SELECT Ascii.Lower[Rope.Fetch[argv[i], 1]] FROM 's => { i _ i+1; -- consume following option as the server name IF i >= argv.argc THEN RETURN [$Failure, "-server option must be followed by a server name; please supply one."]; serverName _ argv[i]; }; 'q => quietly _ TRUE; ENDCASE => RETURN [$Failure, Rope.Concat[argv[i], " is an unrecognized option; use only -server or -quietly"]]; } ELSE { IF catalogName # NIL THEN RETURN [$Failure, "multiple catalog names supplied; only one allowed."]; catalogName _ argv[i]; }; i _ i+1; ENDLOOP; IF catalogName = NIL THEN RETURN; catalogFileName _ Catalog[serverName, catalogName, IF quietly THEN NIL ELSE cmd.out]; RETURN [NIL, Rope.Cat["\n", catalogFileName, " created."]]; }; Catalog: PROC [serverName: ROPE, catalogName: ROPE, out: IO.STREAM _ NIL] RETURNS [catalogFileName: ROPE] ~ { dfFilePattern: ROPE _ Rope.Cat["/", serverName, "/", catalogName, "/Top/*.DF!H"]; TryThisDFFile: FS.NameProc ~ { base, ext: ROPE; [base, ext] _ ParseFileName[fullFName]; Process.CheckForAbort[]; IF Rope.Equal[s1~ext, s2~"DF", case~FALSE] THEN { AddCatalogEntry[doc~catalogDoc, packageName~base, dfFileName~fullFName]; }; RETURN[continue~TRUE]; }; catalogDoc: CatalogDoc ~ InitializeCatalogDoc[catalogName]; logName: ROPE ~ Rope.Concat[catalogName, "Catalog.log"]; catalogDoc.out _ out; catalogDoc.log _ ViewerIO.CreateViewerStreams[ name: logName, backingFile: logName, editedStream: FALSE].out; IO.PutF[catalogDoc.log, "Catalog of %g, %g\n\n", IO.rope[catalogName], IO.time[]]; FS.EnumerateForNames[pattern~dfFilePattern, proc~TryThisDFFile]; FinishCatalogDoc[catalogDoc]; RETURN [catalogDoc.fileName] }; CatalogDoc: TYPE ~ REF CatalogDocRec; CatalogDocRec: TYPE ~ RECORD[ out: IO.STREAM, -- for recording our progress log: IO.STREAM, -- for logging errors fileName: ROPE, packageName: ROPE, root: Ref, latestLevel1Node: Ref, latestLevel2Node: Ref, latestLevel3Node: Ref, commandIndex: IndexTable, keywordIndex: IndexTable ]; InitializeCatalogDoc: PROC [catalogName: ROPE] RETURNS [doc: CatalogDoc] ~ { UpperCase: PROC [r: ROPE] RETURNS [ROPE] ~ { upper: PROC [old: CHAR] RETURNS [new: CHAR] ~ { new _ Ascii.Upper[old] }; RETURN[Rope.Translate[base: r, translator: upper]]; }; PutPropRope: PROC [n: TextNode.Ref, name: ATOM, value: ROPE] ~ { NodeProps.PutProp[n: n, name: name, value: value]; }; doc _ NEW[CatalogDocRec]; doc.commandIndex _ CreateIndexTable[]; doc.keywordIndex _ CreateIndexTable[]; doc.fileName _ Rope.Concat[catalogName, "Catalog.tioga"]; doc.latestLevel1Node _ NIL; doc.root _ TiogaFileOps.CreateRoot[]; PutPropRope[doc.root, $Postfix, "(firstPageNumber) 0 .def (firstHeadersAfterPage) 0 .def"]; AppendNode[level~1, doc~doc, r~doc.fileName, format~NIL]; NodeProps.PutProp[doc.latestLevel1Node, $Comment, NodeProps.true]; AppendNode[level~2, doc~doc, format~NIL, r~IO.PutFR["%g %t", IO.rope[UserProfile.Token["EditorComforts.LastEdited", UserCredentials.Get[].name]], IO.time[BasicTime.Now[]]]]; NodeProps.PutProp[doc.latestLevel2Node, $Comment, NodeProps.true]; AppendNode[level~1, doc~doc, r~Rope.Concat[UpperCase[catalogName], " PACKAGE CATALOG"], format~"unleaded"]; TiogaFileOps.AddLooks[x~doc.latestLevel1Node, start~0, len~LAST[INT], look~'s, root~doc.root]; PutPropRope[doc.latestLevel1Node, $Mark, "centerHeader"]; AppendNode[level~1, doc~doc, r~"CEDAR 6.0  FOR INTERNAL XEROX USE ONLY", format~"unleaded"]; TiogaFileOps.AddLooks[x~doc.latestLevel1Node, start~0, len~LAST[INT], look~'s, root~doc.root]; PutPropRope[doc.latestLevel1Node, $Mark, "centerFooter"]; AppendNode[level~1, doc~doc, r~Rope.Concat["Package Catalog\nfor ", catalogName], format~"title"]; AppendNode[level~1, doc~doc, r~"c Copyright 1985 by Xerox Corporation. All rights reserved.", format~"abstract"]; TiogaFileOps.AddLooks[x~doc.latestLevel1Node, start~0, len~1, look~'m, root~doc.root]; TiogaFileOps.AddLooks[x~doc.latestLevel1Node, start~0, len~LAST[INT], look~'s, root~doc.root]; AppendNode[level~1, doc~doc, r~"Abstract: This catalog is a list of interesting packages and tools. The catalog is automatically created from the collection of maintainer-supplied entries.", format~"abstract"]; TiogaFileOps.AddLooks[x~doc.latestLevel1Node, start~0, len~8, look~'b, root~doc.root]; AppendNode[level~1, doc~doc, r~"XEROX\t\t\tXerox Corporation\n\t\t\t\tPalo Alto Research Center\n\t\t\t\t3333 Coyote Hill Road\n\t\t\t\tPalo Alto, California 94304\n\nFor Internal Xerox Use Only", format~"boilerplate"]; TiogaFileOps.AddLooks[x~doc.latestLevel1Node, start~0, len~5, look~'q, root~doc.root]; TiogaFileOps.AddLooks[x~doc.latestLevel1Node, start~5, len~LAST[INT], look~'o, root~doc.root]; TiogaFileOps.AddLooks[x~doc.latestLevel1Node, start~115, len~LAST[INT], look~'b, root~doc.root]; TiogaFileOps.AddLooks[x~doc.latestLevel1Node, start~115, len~LAST[INT], look~'x, root~doc.root]; AppendNode[level~1, doc~doc, r~"Catalog Components", format~"head"]; }; FinishCatalogDoc: PROC [doc: CatalogDoc] ~ { Process.CheckForAbort[]; IF NOT IsTableEmpty[doc.commandIndex] THEN AddIndex[doc, command]; IF NOT IsTableEmpty[doc.keywordIndex] THEN AddIndex[doc, keyword]; Process.CheckForAbort[]; [] _ TiogaFileOps.Store[filename~doc.fileName, x~doc.root]; IF doc.log#NIL THEN IO.Close[doc.log]; }; AddCatalogEntry: PROC [doc: CatalogDoc, packageName: ROPE, dfFileName: ROPE] ~ { lastDirectoryPath: ROPE _ NIL; packageDoc: ROPE ~ Rope.Concat[packageName, "Doc.tioga"]; documentationFileName: ROPE _ NIL; documentationFiles: ROPE _ NIL; listOfCommands: LIST OF REF ANY _ NIL; TryThisDFItem: DFUtilities.ProcessItemProc ~ { WITH item SELECT FROM directory: REF DFUtilities.DirectoryItem => { lastDirectoryPath _ directory.path1; }; file: REF DFUtilities.FileItem => { base, ext, shortName: ROPE _ NIL; [base, ext, shortName] _ ParseFileName[file.name ! FS.Error => IF error.group#bug THEN { Log[doc, IO.PutFR1["FS.Error... %g", IO.rope[error.explanation]]]; GOTO Fail; }; ]; SELECT TRUE FROM Rope.Equal["load", ext, FALSE] => { listOfCommands _ List.Nconc1[listOfCommands, base]; }; Rope.Equal["tioga", ext, FALSE] --AND Rope.Match["*Doc", base, FALSE]-- => { IF NOT Rope.Match["*Documentation*", lastDirectoryPath, FALSE] THEN { Log[doc, IO.PutFR1["%g is not in the proper Documentation directory.", IO.rope[shortName]]]; }; IF documentationFiles.IsEmpty THEN documentationFiles _ shortName ELSE documentationFiles _ Rope.Cat[documentationFiles, ", ", shortName]; IF Rope.Equal[shortName, packageDoc, FALSE] THEN documentationFileName _ Rope.Concat[lastDirectoryPath, file.name]; RETURN; }; ENDCASE => NULL; EXITS Fail => NULL; }; ENDCASE => NULL; }; dfStream: IO.STREAM _ NIL; Process.CheckForAbort[]; BeginPackage[doc, packageName]; dfStream _ FS.StreamOpen[fileName~dfFileName ! FS.Error => { Log[doc, IO.PutFR1["FS.Error... %g", IO.rope[error.explanation]]]; GO TO FileProblem; }; ]; DFUtilities.ParseFromStream[in~dfStream, proc~TryThisDFItem, filter~[comments~FALSE, filterA~all, filterB~public, filterC~all] ! DFUtilities.SyntaxError => { Log[doc, IO.PutFR1["DFUtilities.SyntaxError... %g", IO.rope[reason]]]; CONTINUE; }; ]; IO.Close[dfStream]; Process.CheckForAbort[]; { -- create the catalog entry author, creator, maintainer: ROPE _ NIL; abstractSpan: TextNode.Span _ TextNode.nullSpan; keywords: ROPE; singlePageDocument: BOOLEAN; IF documentationFileName.IsEmpty THEN Log[doc, "No package documentation."] ELSE { [abstractSpan: abstractSpan, author: author, creator: creator, maintainer: maintainer, keywords: keywords, singlePageDocument: singlePageDocument] _ ExtractStuffFromDocFile[doc, documentationFileName]; IF author.IsEmpty[] AND maintainer.IsEmpty[] THEN { Log[doc, "No author or maintainer listed in documentation."] }; }; AppendNode[level~2, doc~doc, r~packageName, format~"head"]; AppendNode[level~3, doc~doc, r~Rope.Cat["DF file: ", packageName, ".df"], format~"indent"]; TiogaFileOps.AddLooks[x~doc.latestLevel3Node, start~0, len~8, look~'b, root~doc.root]; IF NOT creator.IsEmpty THEN { AppendNode[level~3, doc~doc, r~creator, format~"indent"]; TiogaFileOps.AddLooks[x~doc.latestLevel3Node, start~0, len~11 --Created by:--, look~'b, root~doc.root]; }; IF NOT maintainer.IsEmpty THEN { AppendNode[level~3, doc~doc, r~maintainer, format~"indent"]; TiogaFileOps.AddLooks[x~doc.latestLevel3Node, start~0, len~14 --Maintained by:--, look~'b, root~doc.root]; }; IF NOT documentationFiles.IsEmpty AND NOT (Rope.Equal[documentationFiles, packageDoc, FALSE] AND singlePageDocument) THEN { AppendNode[level~3, doc~doc, r~Rope.Cat["Documentation: ", documentationFiles], format~"indent"]; TiogaFileOps.AddLooks[x~doc.latestLevel3Node, start~0, len~14, look~'b, root~doc.root]; }; IF NOT author.IsEmpty THEN { AppendNode[level~3, doc~doc, r~Rope.Cat["Author: ", author], format~"indent"]; TiogaFileOps.AddLooks[x~doc.latestLevel3Node, start~0, len~7 --Author:--, look~'b, root~doc.root]; }; IF NOT keywords.IsEmpty THEN { CommaSeparated: IO.BreakProc ~ { RETURN [SELECT char FROM ', => sepr, ENDCASE => other] }; s: IO.STREAM _ IO.RIS[keywords]; firstBlank: INT _ Rope.Find[keywords, " "]; IO.SetIndex[self~s, index~firstBlank]; -- position past "Keywords:" [] _ IO.SkipWhitespace[s]; WHILE NOT IO.EndOf[s] DO ENABLE IO.Error, IO.EndOfStream => EXIT; keyword: ROPE _ NIL; keyword _ IO.GetTokenRope[stream~s, breakProc~CommaSeparated].token; IF NOT keyword.IsEmpty THEN InsertIndexTerm[doc.keywordIndex, keyword, packageName]; [] _ IO.GetChar[s]; [] _ IO.SkipWhitespace[s]; ENDLOOP; IO.Close[s]; AppendNode[level~3, doc~doc, r~keywords, format~"indent"]; TiogaFileOps.AddLooks[x~doc.latestLevel3Node, start~0, len~10, look~'b, root~doc.root]; }; { contents: ROPE _ NIL; FOR l: LIST OF REF ANY _ listOfCommands, l.rest WHILE l # NIL DO commandName: ROPE _ NARROW[l.first]; InsertIndexTerm[doc.commandIndex, commandName, packageName]; contents _ IF contents.IsEmpty THEN Rope.Concat["Commands: ", commandName] ELSE Rope.Cat[contents, ", ", commandName]; ENDLOOP; IF NOT contents.IsEmpty THEN { AppendNode[level~3, doc~doc, r~contents, format~"indent"]; TiogaFileOps.AddLooks[x~doc.latestLevel3Node, start~0, len~9, look~'b, root~doc.root]; }; }; IF abstractSpan # TextNode.nullSpan THEN { [] _ EditSpan.Copy[dest~TextNode.MakeNodeLoc[doc.latestLevel3Node], source~abstractSpan, destRoot~doc.root, sourceRoot~TextNode.Root[abstractSpan.start.node]]; FOR node: Ref _ Forward[doc.latestLevel3Node], Forward[node] WHILE TextNode.Level[node] >= 3 DO TiogaFileOps.SetFormat[node, "indent"]; ENDLOOP; }; }; EXITS FileProblem => NULL; }; ExtractStuffFromDocFile: PROC [ doc: CatalogDoc, documentationFile: ROPE ] RETURNS [ abstractSpan: TextNode.Span _ TextNode.nullSpan, author, creator, maintainer, keywords: ROPE _ NIL, singlePageDocument: BOOLEAN _ FALSE ] ~ { tiogaDocRoot: Ref ~ PutGet.FromFile[documentationFile ! FS.Error => { Log[doc, IO.PutFR1["FS.Error... %g", IO.rope[error.explanation]]]; GOTO Quit; }; ]; prev: Ref _ NIL; hasCopyright: BOOL _ FALSE; AbstractNode: PROC [node: Ref, begin: BOOL _ FALSE] ~ { nodeLoc: TextNode.Location ~ TextNode.MakeNodeLoc[node]; IF abstractSpan.start=TextNode.nullLocation THEN { IF begin OR hasCopyright THEN abstractSpan.start _ abstractSpan.end _ nodeLoc; } ELSE { IF abstractSpan.end.node=prev THEN abstractSpan.end _ nodeLoc; }; }; RopeOf: PROC [n: Ref] RETURNS [ROPE] ~ { RETURN[TextNode.NodeRope[n]] }; FOR node: Ref _ Forward[tiogaDocRoot], Forward[prev _ node] WHILE node#NIL DO SELECT node.formatName FROM $authors => { IF NOT author.IsEmpty[] THEN author _ author.Concat["; "]; author _ author.Concat[RopeOf[node]]; }; $abstract => { contents: ROPE ~ RopeOf[node]; shortContents: ROPE ~ contents.Substr[len: 100]; SELECT TRUE FROM Rope.Match[pattern: "Copyright*", object: shortContents, case: FALSE] OR Rope.Match[pattern: "* Copyright *", object: shortContents, case: FALSE] => { hasCopyright _ TRUE; }; Rope.Match[pattern: "Abstract*", object: shortContents, case: FALSE] => { AbstractNode[node, TRUE]; }; Rope.Match[pattern: "Created by*", object: shortContents, case: FALSE] => { creator _ contents; }; Rope.Match[pattern: "Maintained by*", object: shortContents, case: FALSE] => { maintainer _ contents; }; Rope.Match[pattern: "Keyword*", object: shortContents, case: FALSE] => { keywords _ contents; }; ENDCASE => { AbstractNode[node]; }; }; $boilerplate => { peek: Ref ~ Forward[node]; -- peek to see if there are any following nodes IF peek=NIL THEN singlePageDocument _ TRUE; EXIT; }; ENDCASE; ENDLOOP; EXITS Quit => NULL; }; AddIndex: PROC [doc: CatalogDoc, kindOf: {command, keyword}] ~ { indexTable: IndexTable _ IF kindOf = command THEN doc.commandIndex ELSE doc.keywordIndex; lastTerm: ROPE; PerIndexTerm: EnumerateProc ~ { IF NOT Rope.Equal[term, lastTerm, FALSE] THEN AppendNode[level~2, doc~doc, r~Rope.Cat[term, ": ", location], format~"item"] ELSE doc.latestLevel2Node.rope _ Rope.Cat[doc.latestLevel2Node.rope, ", ", location]; lastTerm _ term; RETURN [FALSE]; }; AppendNode[level~1, doc~doc, r~IF kindOf = command THEN "Command Index" ELSE "Keyword Index", format~"head"]; EnumerateIndexTerms[indexTable, PerIndexTerm]; }; ParseFileName: PROC [fileName: ROPE] RETURNS [base, ext, shortName: ROPE] ~ { cp: FS.ComponentPositions; fullName: ROPE; [fullName, cp] _ FS.ExpandName[fileName]; base _ Rope.Substr[fullName, cp.base.start, cp.base.length]; ext _ Rope.Substr[fullName, cp.ext.start, cp.ext.length]; shortName _ Rope.Substr[fullName, cp.base.start, cp.ext.start+cp.ext.length-cp.base.start]; }; AppendNode: PROC [doc: CatalogDoc, level: NAT, r: ROPE, format: ROPE] ~ { InnerAppendNode: PROC [subtree: Ref, latest: Ref, r: ROPE, format: ROPE] RETURNS [newNode: Ref] ~ { IF subtree = NIL THEN ERROR; newNode _ IF latest = NIL THEN TiogaFileOps.InsertNode[x~subtree, child~TRUE] ELSE TiogaFileOps.InsertAsLastChild[x~subtree, prevLast~latest]; TiogaFileOps.SetContents[x~newNode, txt~r]; TiogaFileOps.SetFormat[x~newNode, format~format]; }; SELECT level FROM 1 => { doc.latestLevel1Node _ InnerAppendNode[doc.root, doc.latestLevel1Node, r, format]; doc.latestLevel2Node _ doc.latestLevel3Node _ NIL; }; 2 => { doc.latestLevel2Node _ InnerAppendNode[doc.latestLevel1Node, doc.latestLevel2Node, r, format]; doc.latestLevel3Node _ NIL; }; 3 => { doc.latestLevel3Node _ InnerAppendNode[doc.latestLevel2Node, doc.latestLevel3Node, r, format]; }; ENDCASE; }; Forward: PROC [node: Ref] RETURNS [next: Ref] ~ { RETURN [TextNode.StepForward[node]]; }; BeginPackage: PROC [doc: CatalogDoc, packageName: ROPE] ~ { out: IO.STREAM ~ doc.out; IF out#NIL THEN out.PutF1["%g ... ", IO.rope[packageName]]; doc.packageName _ packageName; }; Log: PROC [doc: CatalogDoc, msg: ROPE] ~ { log: IO.STREAM ~ doc.log; IF log#NIL THEN log.PutF["%g: %g\n", IO.rope[doc.packageName], IO.rope[msg]]; }; IndexTable: TYPE ~ RedBlackTree.Table; CreateIndexTable: PROC [] RETURNS [IndexTable] ~ { RETURN [RedBlackTree.Create[getKey: GetIndexKey, compare: CompareIndexKeys]]; }; IsTableEmpty: PROC [table: IndexTable] RETURNS [BOOLEAN] ~ { RETURN [RedBlackTree.Size[table] = 0]; }; Pair: TYPE ~ REF PairRec; PairRec: TYPE ~ RECORD[ term: ROPE, location: ROPE ]; InsertIndexTerm: PROC [table: IndexTable, term: ROPE, location: ROPE] ~ { data: REF _ NEW[PairRec _ [term, location]]; RedBlackTree.Insert[table, data, data ! RedBlackTree.DuplicateKey => CONTINUE]; }; GetIndexKey: RedBlackTree.GetKey ~ { RETURN [data]; }; CompareIndexKeys: RedBlackTree.Compare ~ { pair1: Pair _ NARROW[k]; pair2: Pair _ NARROW[data]; comparison: Basics.Comparison _ Rope.Compare[pair1.term, pair2.term, FALSE]; IF comparison = equal THEN comparison _ Rope.Compare[pair1.location, pair2.location, FALSE]; RETURN [comparison]; }; EnumerateProc: TYPE ~ PROC [term: ROPE, location: ROPE] RETURNS [BOOLEAN]; EnumerateIndexTerms: PROC [table: IndexTable, proc: EnumerateProc] ~ { ApplyProc: PROC [data: RedBlackTree.UserData] RETURNS [stop: BOOL _ FALSE] ~ { pair: Pair _ NARROW[data]; RETURN [proc[pair.term, pair.location]]; }; RedBlackTree.EnumerateIncreasing[table, ApplyProc]; }; Commander.Register[ key~"Catalog", proc~CatalogCmdProc, doc~"Catalog [-quietly] [-server ] make a catalog of software packages stored as DF files in ///Top/*.DF" ]; END. ~CatalogImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Rick Beach, October 5, 1985 11:52:43 am PDT Doug Wyatt, October 14, 1985 6:10:53 pm PDT PROC [fullFName: ROPE] RETURNS [continue: BOOLEAN]; Use this to force the type of a string literal to be ROPE rather than REF TEXT. each component is added underneath this heading ProcessItemProc: TYPE = PROC [item: REF ANY] RETURNS [stop: BOOL _ FALSE]; command name is the name of the .Load file the list of documentation files either has multiple files (since its not just PackageDoc.Tioga) or points to a long document (since its not a single page document) PROC [char: CHAR] RETURNS [CharClass]; Begin with this node if it says "Abstract: ..." or we've seen the copyright notice. Continue the abstract if this node is contiguous. Documentation may contain several abstract format nodes The following heuristics are used to determine the span of the abstract: Begin the abstract if the node begins with "Abstract" Begin the abstract at the node following the copyright notice which is a node matching "Copyright *" or "* Copyright *" End the abstract if the node does not have abstract format End the abstract if the node begins with "Keyword", "Created by", or "Mainained by" collect head nodes here for cumulative table of contents PROC [data: UserData] RETURNS [Key] PROC [k: Key, data: UserData] RETURNS [Basics.Comparison] ʘcode™Kšœ Ïmœ1™˜Qš¡ œ˜Kšžœ žœžœ žœ™3Kšœ žœ˜Kšœ'˜'K˜šžœ"žœžœ˜1KšœH˜HK˜—Kšžœ žœ˜K˜—Kšœ;˜;Kšœ žœ+˜8K˜Kšœbžœ˜mKšžœ/žœžœ ˜RK˜@K˜Kšžœ˜K˜K˜—Kšœ žœžœ˜%šœžœžœ˜Kšœžœžœ ˜-Kšœžœžœ ˜%Kšœ žœ˜Kšœ žœ˜K˜ K˜K˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜K˜—š¡œžœžœžœ˜Lš ¡ œžœžœžœžœ˜,Kš œžœžœžœžœ˜IKšžœ-˜3K˜—š¡ œžœžœ žœ˜@Kšœ5žœ žœžœ™OK˜2K˜—Kšœžœ˜Kšœ&˜&Kšœ&˜&K˜9Kšœžœ˜K˜šœ%˜%Kšœ[˜[K˜—šœ4žœ˜9KšœB˜BK˜—š œ$žœžœžœSžœ˜­KšœB˜BK˜—šœDÏsœ˜kKšœ;žœžœ˜^Kšœ9˜9K˜—šœ ¢'œ˜]Kšœ;žœžœ˜^Kšœ9˜9K˜—šœb˜bK˜—šœr˜rKšœV˜VKšœ;žœžœ˜^K˜—šœÓ˜ÓKšœV˜VK˜—šœÛ˜ÛKšœV˜VKšœ;žœžœ˜^Kšœ=žœžœ˜`Kšœ=žœžœ˜`K˜—šœD˜DK™/—K˜K˜—š¡œžœ˜,K˜Kšžœžœ žœ˜BKšžœžœ žœ˜BK˜Kšœ;˜;Kšžœ žœžœžœ˜&K˜K˜—š¡œžœ žœžœ˜PKšœžœžœ˜Kšœ žœ)˜9Kšœžœžœ˜"Kšœžœžœ˜Kš œžœžœžœžœžœ˜&š¡ œ!˜.Kš¡œžœžœžœžœžœžœžœ™Jšžœžœž˜šœ žœ˜-Kšœ$˜$Kšœ˜—šœžœ˜#Kšœžœžœ˜!šœ2˜2šžœ žœžœ˜%Kšœ žœžœ˜BKšžœ˜ Kšœ˜—Kšœ˜—šžœžœž˜šœžœ˜#K™*Kšœ3˜3Kšœ˜—šœžœ 'œ˜Lšžœžœ2žœžœ˜EKšœ žœ<žœ˜\Kšœ˜—Kšžœžœ˜AKšžœD˜Hšžœ#žœž˜0KšœB˜B—Kšžœ˜Kšœ˜—Kšžœžœ˜—Kšžœ žœ˜Kšœ˜—Kšžœžœ˜—K˜—Kšœ žœžœžœ˜K˜K˜Kšœ˜K˜šœ.˜.šœ ˜ Kšœ žœžœ˜BKšžœžœ ˜Kšœ˜—Kšœ˜—šœNžœ-˜€šœ˜Kšœ žœ)žœ˜FKšžœ˜ Kšœ˜—Kšœ˜—Kšžœ˜K˜K˜K˜šœ ˜Kšœžœžœ˜(K˜0Kšœ žœ˜Kšœžœ˜Kšžœžœ&˜Kšžœ˜KšœÉ˜Éšžœžœžœ˜3Kšœ<˜K™1—K˜—K˜—Kš ¡œžœ žœžœžœ˜Hšžœ9žœžœž˜Mšžœž˜šœ ˜ Kšžœžœžœ˜:Kšœ%˜%K˜—šœ˜K™7™HK™5K™wK™:K™S—Kšœ žœ˜Kšœžœ˜0šžœžœž˜Kšœ?žœ˜EšžœCžœ˜PKšœžœ˜K˜—šœ>žœ˜IJšœžœ˜K˜—šœ@žœ˜KKšœ˜K˜—šœCžœ˜NKšœ˜K˜—šœ=žœ˜HKšœ˜K˜—šžœ˜ K˜K˜——K˜—šœ˜Kšœ /˜JKšžœžœžœžœ˜+K™8Kšžœ˜K˜—Kšžœ˜—Kšžœ˜—Kšžœ žœ˜K˜K˜—š¡œžœ2˜@Kšœžœžœžœ˜YKšœ žœ˜š¡ œ˜šžœžœžœ˜(KšžœN˜RKšžœQ˜U—K˜Kšžœžœ˜K˜—Kšœžœžœžœ!˜mK˜.K˜K˜—š ¡ œžœ žœžœžœ˜MKšœ˜Kšœ žœ˜Kšœžœ˜)Kšœ<˜