-- CedarDLMapper.mesa; Edited by Schroeder on March 21, 1983 8:32 am
-- Last Edited by: RWeaver, February 10, 1984 10:50 am
-- Hal Murray May 6, 1985 9:25:03 pm PDT
-- John Larson, November 24, 1985 7:17:08 pm PST
DIRECTORY
Basics USING [Comparison],
Buttons USING [Button, ButtonProc, Create],
Containers USING [ChildXBound, ChildYBound, Container, Create],
Labels USING [Create, Label, Set],
Menus USING [CreateEntry, CreateMenu, InsertMenuEntry, Menu, MenuProc],
Rules USING [Create, Rule],
ViewerIO USING [CreateViewerStreams],
ViewerOps USING [CreateViewer, PaintViewer, SetMenu, SetOpenHeight],
ViewerClasses USING [Viewer],
ViewerTools USING [MakeNewTextViewer, GetContents, SetContents, SetSelection],
FS USING [StreamOpen],
GVBasics USING [oldestTime],
GVNames USING [CheckStamp, GetList, GetMembers,
GetRemark, ListType, MemberInfo, RemarkInfo, RListHandle],
Histogram,
IO USING [card, Flush, noWhereStream, PutF, PutFR, PutRope, STREAM, time],
RedBlackTree USING [Create, EnumerateIncreasing, Insert, Key, Lookup, Table, UserData],
Rope USING [Cat, Compare, Find, Length, ROPE, SkipOver, SkipTo, Substr];
CedarDLMapper: CEDAR MONITOR IMPORTS
Buttons, Containers, Labels, Menus, Rules, ViewerIO, ViewerOps, ViewerTools,
FS, GVNames, Histogram, IO, RedBlackTree, Rope
= BEGIN
ROPE: TYPE = Rope.ROPE;
entryHeight: CARDINAL = 15;
entryVSpace: CARDINAL = 10;
entryHSpace: CARDINAL = 10;
heightSoFar: CARDINAL ← 0;
container: Containers.Container;
currentGroups, totalGroups: Labels.Label;
currentName: Labels.Label;
exceptions: ViewerClasses.Viewer;
inStr, outStr: IO.STREAM;
fullFileName, conFileName, ARPAFileName, registries: ViewerClasses.Viewer;
BuildOuter: PROC =
BEGIN
myMenu: Menus.Menu ← Menus.CreateMenu[];
container ← Containers.Create[ info: [name: "DLMap", scrollable: FALSE] ];
Menus.InsertMenuEntry[menu: myMenu,
entry: Menus.CreateEntry[name: "DoIt", proc: DoIt]
];
Menus.InsertMenuEntry[menu: myMenu,
entry: Menus.CreateEntry[name: "Stop", proc: StopIt]
];
ViewerOps.SetMenu[viewer: container, menu: myMenu];
BuildUserInput[];
BuildUserFeedback[];
BuildExceptions[];
ViewerOps.SetOpenHeight[viewer: container, clientHeight: heightSoFar];
ViewerOps.PaintViewer[viewer: container, hint: all];
END;
BuildUserInput: PROC =
BEGIN
Bl1, Bl2, Bl3, Bl4: Buttons.Button;
heightSoFar ← heightSoFar+entryVSpace/2;
Bl1 ← Buttons.Create[info: [name: "Full map file name:", parent: container, border: FALSE,
wy: heightSoFar], proc: ForceSelToFullFileName];
fullFileName ← ViewerTools.MakeNewTextViewer[info: [parent: container,
wx: Bl1.wx+Bl1.ww+entryHSpace, wy: heightSoFar, ww: 0, wh: entryHeight,
data: defaultFullFileName, scrollable: FALSE, border: FALSE], paint: FALSE];
Containers.ChildXBound[container, fullFileName];
heightSoFar ← heightSoFar+entryVSpace+Bl1.wh;
Bl2 ← Buttons.Create[info: [name: "Condensed map file name:", parent: container,
border: FALSE, wy: heightSoFar], proc: ForceSelToConFileName];
conFileName ← ViewerTools.MakeNewTextViewer[info: [parent: container,
wx: Bl2.wx+Bl2.ww+entryHSpace, wy: heightSoFar, ww: 0, wh: entryHeight,
data: defaultConFileName, scrollable: FALSE, border: FALSE], paint: FALSE];
Containers.ChildXBound[container, conFileName];
heightSoFar ← heightSoFar+entryVSpace+Bl2.wh;
Bl3 ← Buttons.Create[info: [name: "Arpa DL map file name:", parent: container,
border: FALSE, wy: heightSoFar], proc: ForceSelToARPAFileName];
ARPAFileName ← ViewerTools.MakeNewTextViewer[info: [parent: container,
wx: Bl3.wx+Bl3.ww+entryHSpace, wy: heightSoFar, ww: 0, wh: entryHeight,
data: defaultARPAFileName, scrollable: FALSE, border: FALSE], paint: FALSE];
Containers.ChildXBound[container, ARPAFileName];
heightSoFar ← heightSoFar+entryVSpace+Bl3.wh;
Bl4 ← Buttons.Create[info: [name: "Registries:", parent: container, border: FALSE,
wy: heightSoFar], proc: ForceSelToRegistries];
registries ← ViewerTools.MakeNewTextViewer[info: [parent: container,
wx: Bl4.wx+Bl4.ww+entryHSpace, wy: heightSoFar, ww: 0, wh: 2*entryHeight,
data: defaultRegistries, scrollable: TRUE, border: FALSE], paint: FALSE];
Containers.ChildXBound[container, registries];
heightSoFar ← heightSoFar+entryVSpace+registries.wh;
END;
ForceSelToFullFileName: Buttons.ButtonProc =
BEGIN
IF mouseButton#red
THEN ViewerTools.SetContents[fullFileName, defaultFullFileName]
ELSE ViewerTools.SetSelection[fullFileName];
END;
ForceSelToConFileName: Buttons.ButtonProc =
BEGIN
IF mouseButton#red
THEN ViewerTools.SetContents[conFileName, defaultConFileName]
ELSE ViewerTools.SetSelection[conFileName];
END;
ForceSelToARPAFileName: Buttons.ButtonProc =
BEGIN
IF mouseButton#red
THEN ViewerTools.SetContents[ARPAFileName, defaultARPAFileName]
ELSE ViewerTools.SetSelection[ARPAFileName];
END;
ForceSelToRegistries: Buttons.ButtonProc =
BEGIN
IF mouseButton#red
THEN ViewerTools.SetContents[registries, defaultRegistries]
ELSE ViewerTools.SetSelection[registries];
END;
BuildUserFeedback: PROC =
BEGIN
lg, ln, of: Labels.Label;
r1: Rules.Rule;
r1 ← Rules.Create[info: [parent: container, wx: 0, wy: heightSoFar, ww: 0, wh: 1]];
Containers.ChildXBound[container, r1];
heightSoFar ← heightSoFar+entryVSpace;
lg ← Labels.Create[info: [name: "DL's:", parent: container,
wx: 0, wy: heightSoFar, border: FALSE]];
currentGroups ← Labels.Create[info: [name: "0000", parent: container,
wx: lg.wx+lg.ww+entryHSpace, wy: heightSoFar, border: TRUE]];
Labels.Set[currentGroups, "0"];
of ← Labels.Create[info: [name: "of", parent: container,
wx: currentGroups.wx+currentGroups.ww+entryHSpace, wy: heightSoFar, border: FALSE]];
totalGroups ← Labels.Create[info: [name: "0000", parent: container,
wx: of.wx+of.ww+entryHSpace, wy: heightSoFar, border: TRUE]];
Labels.Set[totalGroups, "0"];
heightSoFar ← heightSoFar+entryVSpace+of.wh;
ln ← Labels.Create[info: [name: "Current Name:", parent: container,
wx: 0, wy: heightSoFar, border: FALSE]];
currentName ← Labels.Create[info: [name: "", parent: container,
wx: ln.wx+ln.ww+entryHSpace, wy: heightSoFar, border: FALSE]];
Labels.Set[currentName, "none"];
Containers.ChildXBound[container, currentName];
heightSoFar ← heightSoFar+entryVSpace+of.wh;
END;
BuildExceptions: PROC =
BEGIN
r2: Rules.Rule;
r2 ← Rules.Create[info: [parent: container,
wx: 0, wy: heightSoFar, ww: 0, wh: 1]];
Containers.ChildXBound[container, r2];
heightSoFar ← heightSoFar+entryVSpace;
exceptions ← ViewerOps.CreateViewer[flavor: $Typescript,
info: [name: "", parent: container, wx: 0, wy: heightSoFar, wh: 5*entryHeight,
border: FALSE]];
heightSoFar ← heightSoFar+entryVSpace+exceptions.wh;
Containers.ChildXBound[container, exceptions];
Containers.ChildYBound[container, exceptions];
[in: inStr, out: outStr] ← ViewerIO.CreateViewerStreams[name: "", viewer: exceptions,
backingFile: "DLMap.log"];
END;
--**************** start of operational code ****************
mapperStopped: ERROR = CODE;
mappingInProgress: BOOLEAN ← FALSE;
allRegistries: ROPE ← NIL;
defaultRegistries: ROPE = "DC DLOS EOSA ES FX Henr LB OGC OSDA PA PASA RX Siemens STHQ SV WBST X XRCC";
defaultFullFileName: ROPE = "///GV/FullDLMap.txt";
defaultConFileName: ROPE = "///GV/DLMap.txt";
default
ARPAFileName:
ROPE = "///GV/ARPA-DLMap.txt";
stop:
BOOLEAN ←
FALSE;
DoIt: Menus.MenuProc =
BEGIN
IF ReserveMapper[]
THEN
BEGIN
Map[ ! mapperStopped => {outStr.PutF["\nStopped at %t.\n\n",
IO.time[]];
CONTINUE} ];
ReleaseMapper[];
END;
END;
-- DoIt--
StopIt: Menus.MenuProc =
{SetStop[]};
ReserveMapper:
ENTRY
PROCEDURE
RETURNS [
BOOLEAN] =
BEGIN
IF mappingInProgress
THEN
RETURN [
FALSE]
ELSE {mappingInProgress ←
TRUE;
RETURN[
TRUE]};
END;
ReleaseMapper:
ENTRY
PROCEDURE =
{mappingInProgress ←
FALSE; stop ←
FALSE};
SetStop:
ENTRY
PROCEDURE =
{stop ←
TRUE};
StopSet:
ENTRY
PROCEDURE
RETURNS [
BOOLEAN] =
INLINE
{
RETURN [stop]};
Stopped:
PROCEDURE
RETURNS [
BOOLEAN] =
{
IF StopSet[]
THEN
ERROR mapperStopped
ELSE
RETURN[
FALSE]};
ReportAllDown:
PROCEDURE [dot:
BOOLEAN, name:
ROPE] =
BEGIN
IF dot
THEN outStr.PutRope["."]
ELSE outStr.PutF["\nAll down: %g. ", [rope[name]]];
END;
ForWordsInRopeDo:
PROCEDURE [r:
ROPE, work:
PROCEDURE[
ROPE]
RETURNS[done:
BOOLEAN]]
RETURNS [stopped:
BOOLEAN] =
BEGIN
parsePosition:
CARDINAL ← Rope.SkipOver[r, 0, " "];
-- skip initial blanks
UNTIL parsePosition = Rope.Length[r]
DO
--parse registry input--
start:
CARDINAL = parsePosition;
parsePosition ← Rope.SkipTo[r, parsePosition, " "];
IF work[Rope.Substr[r, start, parsePosition-start]]
THEN
RETURN [stopped:
TRUE];
parsePosition ← Rope.SkipOver[r, parsePosition, " "];
ENDLOOP;
RETURN [stopped:
FALSE]
END;
-- ForWordsInRopeDo --
CheckRegistryValidity:
PROCEDURE [reg:
ROPE]
RETURNS [done:
BOOLEAN] =
BEGIN
printDot:
BOOLEAN ←
FALSE;
UNTIL Stopped[]
DO
SELECT GVNames.CheckStamp[Rope.Cat[reg, ".gv"]]
FROM
group =>
NULL;
individual, notFound =>
BEGIN
outStr.PutF["%g%g\n\n", [rope[reg]], [rope[" is not a valid registry."]]];
ERROR mapperStopped
END;
allDown =>
{ReportAllDown[printDot, reg]; printDot←
TRUE;
LOOP};
ENDCASE =>
ERROR;
EXIT;
ENDLOOP;
RETURN [done:
FALSE]
END;
--CheckRegistryValidity--
CreateOutputFile:
PROCEDURE[n:
ROPE]
RETURNS [
IO.
STREAM] =
{
RETURN [
IF Rope.Length[n] = 0
THEN
IO.noWhereStream
ELSE
FS.StreamOpen[n, create]]};
GroupNotFound:
ERROR =
CODE;
GetRList:
PROCEDURE [n:
ROPE, t: GVNames.ListType]
RETURNS [l:
ROPE] =
BEGIN
printDot:
BOOLEAN ←
FALSE;
UNTIL Stopped[]
DO
WITH GVNames.GetList[n, GVBasics.oldestTime, t]
SELECT
FROM
g: GVNames.MemberInfo[group] =>
BEGIN
AddNameToRope:
PROCEDURE[m:
ROPE]
RETURNS [done:
BOOLEAN] =
BEGIN
IF l #
NIL
THEN l ← Rope.Cat[l, ", "];
l ← Rope.Cat[l, m];
done ←
FALSE
END;
l ←
NIL;
EnumerateRList[g.members, AddNameToRope];
END;
a: GVNames.MemberInfo[allDown] =>
{ReportAllDown[printDot, n]; printDot←
TRUE;
LOOP};
ENDCASE =>
ERROR GroupNotFound;
EXIT;
ENDLOOP;
END;
--GetRList--
GetMembers:
PROCEDURE [g:
ROPE]
RETURNS [rLH: GVNames.RListHandle, c:
CARDINAL] =
BEGIN
printDot:
BOOLEAN ←
FALSE;
UNTIL Stopped[]
DO
WITH GVNames.GetMembers[g]
SELECT
FROM
g: GVNames.MemberInfo[group] => {rLH ← g.members; c ← g.count};
a: GVNames.MemberInfo[allDown] =>
{ReportAllDown[printDot, g]; printDot←
TRUE;
LOOP};
ENDCASE =>
ERROR GroupNotFound;
EXIT;
ENDLOOP;
END;
--GetMembers--
GetRemark:
PROCEDURE [g:
ROPE]
RETURNS [r:
ROPE] =
BEGIN
printDot:
BOOLEAN ←
FALSE;
UNTIL Stopped[]
DO
info: GVNames.RemarkInfo;
[info, r] ← GVNames.GetRemark[g];
SELECT info
FROM
group =>
IF Rope.Length[r] = 0
THEN r ←
NIL;
allDown =>
{ReportAllDown[printDot, g]; printDot←
TRUE;
LOOP};
ENDCASE =>
ERROR GroupNotFound;
EXIT;
ENDLOOP;
END;
--GetRemark--
CardToRope:
PROCEDURE [c:
CARDINAL]
RETURNS [
ROPE] =
INLINE
{
RETURN [
IO.PutFR[v1:
IO.card[c]]]};
DLInfo:
TYPE =
RECORD [
name:
ROPE,
printable, notFound:
BOOLEAN,
in, ind:
CARDINAL,
remark, owners, friends:
ROPE,
sub:
LIST
OF
REF DLInfo ];
GetKey:
SAFE
PROC [data: RedBlackTree.UserData]
RETURNS [key: RedBlackTree.Key] =
CHECKED
BEGIN
WITH data
SELECT
FROM
r:
ROPE => key ← r;
m:
REF DLInfo => key ← m.name;
ENDCASE =>
ERROR;
END;
MyCompare:
SAFE
PROC [k: RedBlackTree.Key, data: RedBlackTree.UserData]
RETURNS [Basics.Comparison] =
CHECKED
BEGIN
a: ROPE ← NARROW[k];
b: ROPE ← NARROW[GetKey[data]];
RETURN [Rope.Compare[a, b, FALSE]]
END; --MyCompare--
EnumerateRList: PROCEDURE [r: GVNames.RListHandle,
p: PROCEDURE [ROPE] RETURNS [BOOLEAN] ] =
{FOR r ← r, r.rest UNTIL r=NIL OR p[r.first] DO ENDLOOP};
Map: PROCEDURE =
BEGIN
GetEntry: PROCEDURE [n: ROPE] RETURNS [d: REF DLInfo] =
BEGIN
d ← NARROW[RedBlackTree.Lookup[tree, n]];
IF d = NIL THEN BEGIN
d ← NEW [DLInfo ← [name:n, printable:FALSE, notFound:FALSE, in:0, ind:0,
remark:NIL, owners:NIL, friends:NIL, sub:NIL]];
RedBlackTree.Insert[tree, d, n];
END;
END; --GetEntry--
EnumerateRegistry: PROCEDURE [reg: ROPE] RETURNS [BOOLEAN] =
BEGIN
rG: ROPE = Rope.Cat["groups.", reg];
regRLH: GVNames.RListHandle;
Labels.Set[currentName, rG];
[regRLH, ] ← GetMembers[rG ! GroupNotFound =>
{outStr.PutF["\n%g not found?", [rope[rG]]]; ERROR mapperStopped}];
EnumerateRList[regRLH, GroupWork];
RETURN[FALSE]
END; --EnumerateRegsitry--
GroupWork: PROCEDURE [group: ROPE] RETURNS [done: BOOLEAN] =
BEGIN
ContentWork: PROCEDURE [m: ROPE] RETURNS [done: BOOLEAN] =
BEGIN
done ← FALSE;
IF Rope.SkipTo[m, 0, "^"] = Rope.Length[m]
THEN gDI.ind ← gDI.ind + 1
ELSE BEGIN
end: LIST OF REF DLInfo;
mDI: REF DLInfo = GetEntry[m];
mDI.in ← mDI.in + 1;
IF gDI.sub = NIL
THEN gDI.sub ← CONS[mDI, NIL]
ELSE FOR end ← gDI.sub, end.rest UNTIL end.rest = NIL DO
REPEAT FINISHED => end.rest ← CONS[mDI, NIL];
ENDLOOP;
END;
END; --ContentWork--
gDI: REF DLInfo ← NIL;
rLH: GVNames.RListHandle;
count: CARDINAL;
notFound: BOOLEAN ← FALSE;
Labels.Set[currentName, group];
Labels.Set[totalGroups, CardToRope[totalGrp ← totalGrp + 1]];
[rLH, count] ← GetMembers[group ! GroupNotFound => {notFound ← TRUE; CONTINUE}];
IF NOT notFound THEN gram.DataPoint[count];
IF Rope.SkipTo[group, 0, "^"] = Rope.Length[group] THEN RETURN [done: FALSE];
gDI ← GetEntry[group];
gDI.printable ← TRUE;
IF notFound
THEN gDI.notFound ← TRUE
ELSE BEGIN
IF fFH#IO.noWhereStream THEN BEGIN -- need extra info for the full listing --
EnumerateRList[rLH, ContentWork];
gDI.owners ← GetRList[group, owners];
END;
gDI.remark ← GetRemark[group];
gDI.friends ← GetRList[group, friends];
END;
RETURN [done: FALSE]
END; -- GroupWork--
AlreadyDoneThis: SIGNAL [new: REF DLInfo] RETURNS [BOOLEAN] = CODE;
WC: PROCEDURE [r1, r2, r3, r4: ROPE ← NIL] =
BEGIN
IF r1#NIL THEN cFH.PutRope[r1];
IF r2#NIL THEN cFH.PutRope[r2];
IF r3#NIL THEN cFH.PutRope[r3];
IF r4#NIL THEN cFH.PutRope[r4]
END;
WF: PROCEDURE [r1, r2, r3, r4: ROPE ← NIL] =
BEGIN
IF r1#NIL THEN fFH.PutRope[r1];
IF r2#NIL THEN fFH.PutRope[r2];
IF r3#NIL THEN fFH.PutRope[r3];
IF r4#NIL THEN fFH.PutRope[r4]
END;
WA: PROCEDURE [r1, r2, r3, r4: ROPE ← NIL] =
BEGIN
IF r1#NIL THEN aFH.PutRope[r1];
IF r2#NIL THEN aFH.PutRope[r2];
IF r3#NIL THEN aFH.PutRope[r3];
IF r4#NIL THEN aFH.PutRope[r4]
END;
W: PROCEDURE [r1, r2, r3, r4: ROPE ← NIL] = {WF[r1, r2, r3, r4]; WC[r1, r2, r3, r4]};
PrintDLEntry: SAFE PROCEDURE [dl: RedBlackTree.UserData] RETURNS [BOOLEAN] =
TRUSTED BEGIN
dlInfo: REF DLInfo = NARROW [dl];
IF NOT dlInfo.printable OR dlInfo.notFound THEN RETURN[FALSE];
Labels.Set[currentName, dlInfo.name];
Labels.Set[currentGroups, CardToRope[currentGrp ← currentGrp+1]];
W["\n"];
IF Rope.Find[dlInfo.remark, "@"] > -1 THEN {
WA[dlInfo.name]; WA[" - ", dlInfo.remark, "\n"]};
W[dlInfo.name];
IF dlInfo.remark#NIL THEN {W[" - ", dlInfo.remark]; dlInfo.remark ← NIL};
WF["\n\t", CardToRope[dlInfo.ind], " individual", IF dlInfo.ind=1 THEN NIL ELSE "s"];
IF dlInfo.in#0 THEN
WF["; in ", CardToRope[dlInfo.in], " other DL", IF dlInfo.in=1 THEN NIL ELSE "s"];
WF["\n"];
WF["\tOwners: "];
IF dlInfo.owners=NIL
THEN WF[" Default", "\n"]
ELSE {WF[dlInfo.owners, "\n"]; dlInfo.owners ← NIL};
WF["\tFriends: "];
IF dlInfo.friends=NIL
THEN WF[" None", "\n"]
ELSE {WF[dlInfo.friends, "\n"]; dlInfo.friends ← NIL};
PrintSubstructure[dlInfo ! AlreadyDoneThis => RESUME [new=dlInfo]];
RETURN[FALSE]
END;
PrintSubstructure: PROCEDURE[top: REF DLInfo] =
BEGIN
dots: ROPE = ". . . . . . . . . . "; -- 10 dot/space for indenting --
indention ← indention+1;
FOR dlList: LIST OF REF DLInfo ← top.sub, dlList.rest UNTIL dlList = NIL DO
dl: REF DLInfo = dlList.first;
WF[Rope.Substr[dots, 0, MIN[indention*2, Rope.Length[dots]]], dl.name, " - "];
SELECT TRUE FROM
(NOT dl.printable) =>
WF["not expanded.\n"];
(dl.notFound) =>
WF["not found.\n"];
(indention=1 AND top.ind=0 AND dlList=top.sub AND dlList.rest=NIL) =>
WF["is the only contained group; see that entry.\n"];
(SIGNAL AlreadyDoneThis[dl]) =>
WF["**loop**\n"];
ENDCASE =>
BEGIN
WF[CardToRope[dl.ind], "\n"];
PrintSubstructure[dl ! AlreadyDoneThis => IF new=dl THEN RESUME[TRUE] ];
END;
ENDLOOP;
indention ← indention-1;
END; --PrintSubstructure--
tree: RedBlackTree.Table = RedBlackTree.Create[GetKey, MyCompare];
inputRegs: ROPE = ViewerTools.GetContents[registries];
aFH: IO.STREAM = CreateOutputFile[ViewerTools.GetContents[ARPAFileName]];
cFH: IO.STREAM = CreateOutputFile[ViewerTools.GetContents[conFileName]];
fFH: IO.STREAM = CreateOutputFile[ViewerTools.GetContents[fullFileName]];
currentGrp, totalGrp: CARDINAL ← 0;
indention: CARDINAL ← 0;
gram: Histogram.Gram = Histogram.NewGram[0, 2000];
IF cFH=IO.noWhereStream AND fFH=IO.noWhereStream AND aFH=IO.noWhereStream THEN
{outStr.PutRope["\nNo output files."]; RETURN};
outStr.PutF["Running at %t.", IO.time[]];
Labels.Set[currentName, "none"];
Labels.Set[totalGroups, "0"];
Labels.Set[currentGroups, "0"];
[] ← ForWordsInRopeDo[inputRegs, CheckRegistryValidity];
[] ← ForWordsInRopeDo[inputRegs, EnumerateRegistry];
W["Distribution list map of ", inputRegs];
WA["Arpanet redistribution list map of ", inputRegs];
W[".\n\nProduced at ", IO.PutFR[v1: IO.time[]]];
WA[".\n\nProduced at ", IO.PutFR[v1: IO.time[]]];
WC["\n\nTry using the ""Maintain"" program to add/remove yourself to/from a DL. "];
WC["If ""Maintain"" responds with ""you're not allowed to do that"", then send a message to ""Owners-firstPart^.reg"" requesting to be added/removed."];
W["\n\n"];
WA["\n\n"];
[] ← RedBlackTree.EnumerateIncreasing[tree, PrintDLEntry];
W["\n\nEnd of listing.\n"];
WA["\n\nEnd of listing.\n"];
fFH.Flush[];
cFH.Flush[];
aFH.Flush[];
gram.Print[outStr, "\n\nNumber of members in a group."];
outStr.PutF["\nDone at %t.\n\n", IO.time[]];
END; --Map--
BuildOuter[];
END.