--GroupsImpl.mesa --Last edited by Newman.es: May 6, 1983 --Alto/Mesa/Laurel 6.1 version DIRECTORY Ascii USING [CR, DEL, ESC, SP], BinaryTree USING [CompareTrees, DestroyTree, Enter, EnumerateTree, MergeTrees, Present, ReportProc, Tree, UserProc], BodyDefs USING [maxRNameLength, oldestTime, RName, Timestamp], exD: FROM "ExceptionDefs" USING [AppendStringToExceptionLine, ClearExceptionsRegion, DisplayExceptionStringOnLine, ExceptionLineOverflow], IODefs USING [LineOverflow, ReadChar, ReadID, Rubout, WriteChar, WriteDecimal, WriteLine, WriteString], LaurelExecDefs USING [MakeMenuCommandCallable], MailParseDefs USING [endOfInput, endOfList, FinalizeParse, InitializeParse, ParseHandle, ParseError, ParseNameList], NameInfoDefs USING [CheckStamp, Close, Enumerate, GetMembers, IsInList, MemberInfo, Membership, MembershipLevel, NameType, RListHandle], NameInfoSpecialDefs USING [CleanUp], Runtime USING [GetBcdTime], Storage USING [EmptyString, Node, Free, Prune], StreamDefs USING [FileNameError, NewByteStream, Read, StreamError, StreamHandle, userAbort], String USING [AppendChar, AppendString, DeleteSubString, EquivalentSubStrings, LowerCase, SubStringDescriptor], Time USING [Append, Unpack]; GroupsImpl: PROGRAM IMPORTS BinaryTree, exD, IODefs, LaurelExecDefs, NameInfoDefs, MailParseDefs, NameInfoSpecialDefs, Runtime, Storage, StreamDefs, String, Time = BEGIN OPEN Ascii; Command: TYPE = {expand, isMember, union, intersect, subtract, inReg, notInReg}; Tree: TYPE = BinaryTree.Tree; mdsZoneRecord: RECORD [POINTER TO MDSZoneProcs] ← [@mdsZoneProcs]; MDSZoneProcs: TYPE = MACHINE DEPENDENT RECORD [ alloc: PROC [MDSZone, CARDINAL] RETURNS [POINTER], free: PROC [MDSZone, POINTER]]; mdsZoneProcs: MDSZoneProcs ← [Alloc, Free]; Alloc: PROC [z: MDSZone, c: CARDINAL] RETURNS [POINTER] = {RETURN [Storage.Node [c]]}; Free: PROC [z: MDSZone, p: POINTER] = {Storage.Free [p]}; z: MDSZone = LOOPHOLE [@mdsZoneRecord]; aborted: BOOLEAN ← FALSE; level: NameInfoDefs.MembershipLevel ← upArrow; maxRNameLength: CARDINAL = BodyDefs.maxRNameLength; alreadySeen: Tree ← NIL; WriteString: PROC [STRING] = IODefs.WriteString; WriteLine: PROC [STRING] = IODefs.WriteLine; WriteCR: PROC = {IODefs.WriteChar [CR]}; TerminateDataSink: PROC [ctMembers: CARDINAL] = BEGIN IF ctMembers = 0 THEN WriteLine [";(empty)"L] ELSE BEGIN WriteString["; ("L]; IODefs.WriteDecimal[ctMembers]; WriteString[" member"L]; IF ctMembers > 1 THEN WriteString["s"L]; WriteLine[")"L]; END; END; DestroyTreeNil: PROC [tree: Tree, z: MDSZone] RETURNS [nil: Tree ← NIL] = INLINE {BinaryTree.DestroyTree [tree, z]}; PutLevel: PROC [level: NameInfoDefs.MembershipLevel] = BEGIN WriteString [SELECT level FROM closure => "closure"L, upArrow => "upArrow"L, ENDCASE => "direct"L]; END; -- of PutLevel CompareGroups: PROC [ group1, group2: BodyDefs.RName, option: Command] = BEGIN ctMembers: CARDINAL ← 0; tree1, tree2: Tree; Report: BinaryTree.ReportProc = -- [id: Sv, membership: Membership] BEGIN good: BOOLEAN = SELECT option FROM intersect => (membership = both), subtract => (membership = first), union => TRUE, ENDCASE => ERROR; IF good THEN BEGIN aborted← StreamDefs.userAbort; IF aborted THEN RETURN; IF ctMembers # 0 THEN WriteString[", "L]; WriteString[id]; ctMembers ← ctMembers + 1; END; END; -- of Report tree1 ← ExpandFileOrGroup[group1 ! BadFile => GOTO return]; IF aborted THEN { BinaryTree.DestroyTree[tree1, z]; WriteLine["Aborted"L]; RETURN}; tree2 ← ExpandFileOrGroup[ group2 ! BadFile => {BinaryTree.DestroyTree[tree1, z]; GOTO return}]; IF aborted THEN { BinaryTree.DestroyTree[tree1, z]; BinaryTree.DestroyTree[tree2, z]; WriteLine["Aborted"L]; RETURN}; WriteString[ SELECT option FROM intersect => "Intersection"L, subtract => "Subtraction"L, union => "Union"L, ENDCASE => ERROR]; WriteString[" of group "L]; WriteString[group1]; WriteString[IF option = subtract THEN " minus "L ELSE " and "L]; WriteString["group "L]; WriteString[group2]; WriteString [" ("L]; PutLevel [level]; WriteString["): "L]; BinaryTree.CompareTrees[tree1, tree2, Report]; IF aborted THEN {WriteCR[]; WriteLine ["Aborted"L]} ELSE TerminateDataSink [ctMembers]; BinaryTree.DestroyTree[tree1, z]; BinaryTree.DestroyTree[tree2, z]; EXITS return => {}; END; -- of CompareGroups RegistryFilter: PROC [group: BodyDefs.RName, registry: STRING, include: BOOLEAN] = BEGIN tree: Tree; ctMembers: CARDINAL ← 0; ListUser: BinaryTree.UserProc = -- [id: Sv] BEGIN hasReg: BOOLEAN ← HasRegistry [id, registry]; IF hasReg # include THEN RETURN; aborted ← StreamDefs.userAbort; IF aborted THEN RETURN; IF ctMembers # 0 THEN WriteString[", "L]; WriteString[id]; ctMembers ← ctMembers + 1; END; -- of ListUser IF Storage.EmptyString [registry] THEN { WriteLine["Empty registry name!"L]; RETURN}; tree ← ExpandFileOrGroup[group ! BadFile => GOTO return]; IF aborted THEN { BinaryTree.DestroyTree[tree, z]; WriteLine["Aborted"L]; RETURN}; IF registry[0] = '. THEN DeleteFirstChar [registry]; --strip off "." if present WriteString [IF include THEN "Intersection"L ELSE "Subtraction"L]; WriteString[" of group "L]; WriteString[group]; WriteString[IF include THEN " and "L ELSE " minus "L]; WriteString["registry "L]; WriteString[registry]; WriteString [" ("L]; PutLevel [level]; WriteString["): "L]; BinaryTree.EnumerateTree[tree, ListUser]; IF aborted THEN {WriteCR[]; WriteLine ["Aborted"L]} ELSE TerminateDataSink [ctMembers]; BinaryTree.DestroyTree [tree, z]; EXITS return => {}; END; HasRegistry: PROC [name, registry: STRING] RETURNS [BOOLEAN] = BEGIN ssName, ssReg: String.SubStringDescriptor; IF name.length < registry.length THEN RETURN [FALSE]; ssName ← [name, name.length-registry.length, registry.length]; ssReg ← [registry, 0, registry.length]; RETURN [String.EquivalentSubStrings [@ssName, @ssReg]]; END; --ExpandFileOrGroup should be called only at the top level of an expansion (since a file can't be nested in either a group or another file). It destroys the "alreadySeen" tree, built up by ExpandFile/ExpandGroup, when it is done. ExpandFileOrGroup: PROC [name: STRING] RETURNS [tree: Tree ← NIL] = BEGIN IF name[0] = '@ THEN BEGIN DeleteFirstChar [name]; tree ← ExpandFile[name ! BadFile => alreadySeen ← DestroyTreeNil [alreadySeen, z]]; END ELSE tree ← ExpandGroup[name]; alreadySeen ← DestroyTreeNil [alreadySeen, z]; END; BadFile: ERROR = CODE; --raised whenever ExpandFile runs into any trouble ExpandFile: PROC [fileName: STRING] RETURNS [tree: Tree ← NIL] = BEGIN stream: StreamDefs.StreamHandle; where: {inList, endOfList, endOfInput} ← inList; getChar: PROC RETURNS [ch: CHARACTER] = BEGIN IF where = endOfList THEN {where ← endOfInput; RETURN[MailParseDefs.endOfInput]}; ch ← stream.get[stream ! StreamDefs.StreamError => {where ← endOfList; ch ← MailParseDefs.endOfList; CONTINUE}]; END; -- of getChar --Errors used to abort parsing UnqualifiedName: ERROR [badName: STRING] = CODE; NestedFileIllegal: ERROR [badName: STRING] = CODE; UserAbort: ERROR = CODE; process: PROC [name, registry: STRING, isFile, isNested: BOOLEAN] RETURNS [write: BOOLEAN ← FALSE] = BEGIN fullName: STRING; fullNameLength: CARDINAL; IF StreamDefs.userAbort THEN ERROR UserAbort; IF isFile THEN ERROR NestedFileIllegal [name]; IF registry.length = 0 THEN ERROR UnqualifiedName[name]; fullNameLength ← name.length + 1 --"."-- + registry.length; fullName ← z.NEW [StringBody[fullNameLength]]; String.AppendString[to: fullName, from: name]; String.AppendChar[fullName, '.]; String.AppendString[to: fullName, from: registry]; tree ← AddNameOrGroup[fullName, tree]; z.FREE [@fullName]; END; -- of process error: BOOLEAN ← FALSE; parse: MailParseDefs.ParseHandle; exD.DisplayExceptionStringOnLine["Expanding "L, 1]; exD.AppendStringToExceptionLine[fileName, 1 ! exD.ExceptionLineOverflow => CONTINUE]; stream ← StreamDefs.NewByteStream [fileName, StreamDefs.Read ! StreamDefs.FileNameError => BEGIN WriteString ["Cannot acquire file "L]; WriteLine [fileName]; ERROR BadFile; END]; parse ← MailParseDefs.InitializeParse [getChar]; MailParseDefs.ParseNameList [parse, process ! UnqualifiedName => BEGIN WriteString ["Unqualified name not allowed: """]; WriteString [badName]; WriteString [""" in file "L]; WriteLine[fileName]; error ← TRUE; CONTINUE; END; NestedFileIllegal => BEGIN WriteString ["Nested file not allowed: "]; WriteString [badName]; WriteString [""" in file "L]; WriteLine[fileName]; error ← TRUE; CONTINUE; END; MailParseDefs.ParseError => BEGIN WriteString ["Syntax error in file "L]; WriteLine[fileName]; error ← TRUE; CONTINUE; END; UserAbort => {aborted ← TRUE; CONTINUE}]; MailParseDefs.FinalizeParse [parse]; stream.destroy [stream]; IF error THEN {BinaryTree.DestroyTree[tree, z]; ERROR BadFile}; END; -- of ExpandFile ExpandGroup: PROC [groupName: BodyDefs.RName] RETURNS [tree: Tree ← NIL] = BEGIN members: NameInfoDefs.RListHandle; enumproc: PROC [name: BodyDefs.RName] RETURNS [done: BOOLEAN ← FALSE] = BEGIN IF StreamDefs.userAbort THEN {aborted ← TRUE; RETURN[done: TRUE]}; tree ← AddNameOrGroup[name, tree]; IF aborted THEN RETURN[done: TRUE]; --return from all recursions END; -- of enumproc exD.DisplayExceptionStringOnLine["Expanding "L, 1]; exD.AppendStringToExceptionLine[groupName, 1 ! exD.ExceptionLineOverflow => CONTINUE]; members ← GetMembers[groupName ! NotAGroup => GOTO return]; alreadySeen ← BinaryTree.Enter [alreadySeen, groupName, z]; NameInfoDefs.Enumerate[members, enumproc]; NameInfoDefs.Close[members]; EXITS return => {}; END; AddNameOrGroup: PROC [name: STRING, tree: Tree] RETURNS [mergedTree: Tree] = BEGIN IF ShouldExpand [name] THEN BEGIN newTree: Tree; IF BinaryTree.Present [alreadySeen, name] THEN RETURN [tree]; newTree ← ExpandGroup[name]; mergedTree ← BinaryTree.MergeTrees[tree, newTree, z]; --consumes "tree" and "newTree" END ELSE mergedTree ← BinaryTree.Enter[tree, name, z]; END; -- of AddNameOrGroup NotAGroup: ERROR = CODE; GetMembers: PROCEDURE [ name: BodyDefs.RName, oldStamp: BodyDefs.Timestamp ← BodyDefs.oldestTime] RETURNS [members: NameInfoDefs.RListHandle] = BEGIN info: NameInfoDefs.MemberInfo ← NameInfoDefs.GetMembers[name, oldStamp]; WITH i: info SELECT FROM group => RETURN[i.members]; ENDCASE => ERROR NotAGroup; END; GetRName: PROC [prompt, rName: STRING, individualOk: BOOLEAN] RETURNS [punt, individual: BOOLEAN] = BEGIN type: NameInfoDefs.NameType; WriteString [prompt]; IODefs.ReadID [rName ! IODefs.LineOverflow, IODefs.Rubout => {WriteLine [" XXX"L]; GOTO Leave;}]; IF rName.length = 0 THEN {WriteLine [" XXX"L]; GOTO Leave}; IF rName[0] = '@ THEN RETURN[FALSE, FALSE]; --file name type ← NameInfoDefs.CheckStamp [rName]; SELECT type FROM individual => IF NOT individualOk THEN {WriteLine [" not a valid group"L]; GOTO Leave;} ELSE individual ← TRUE; group => individual ← FALSE; ENDCASE => {WriteLine [" not a valid individual or group"L]; GOTO Leave;}; punt ← FALSE; EXITS Leave => punt ← TRUE; END; -- of GetRName ShouldExpand: PROC [name: BodyDefs.RName] RETURNS [yes: BOOLEAN] = BEGIN SELECT level FROM direct => RETURN [FALSE]; upArrow => RETURN [HasUpArrow [name]]; ENDCASE => RETURN [NameInfoDefs.CheckStamp [name] = group]; END; -- of ShouldExpand HasUpArrow: PROC [name: BodyDefs.RName] RETURNS [yes: BOOLEAN ← FALSE] = BEGIN FOR i: CARDINAL IN [0..name.length) DO SELECT name[i] FROM '↑ => RETURN[TRUE]; '. => RETURN[FALSE]; ENDCASE; ENDLOOP; END; DeleteFirstChar: PROC [s: STRING] = BEGIN ss: String.SubStringDescriptor ← [s, 0, 1]; String.DeleteSubString[@ss] END; IsMemberCommand: PROC = BEGIN name: STRING ← [maxRNameLength]; group: STRING ← [maxRNameLength]; pass: BOOLEAN; [pass,] ← GetRName["Membership check of: "L, name, TRUE]; IF pass THEN RETURN; [pass,] ← GetRName[" in group: "L, group, FALSE]; IF pass THEN RETURN; WriteCR[]; IF name[0] = '@ OR group[0] = '@ THEN {WriteString ["Membership check not implemented for file name arguments"L]; RETURN}; WriteString[name]; WriteString[" is"L]; IF NameInfoDefs.IsInList [group, name, level, self, members] # yes THEN WriteString["n't"L]; WriteString[" in group "L]; WriteString[group]; WriteString[" ("L]; PutLevel [level]; WriteLine [")"L]; NameInfoSpecialDefs.CleanUp[]; END; -- of IsMemberCommand ExpandCommand: PROC = BEGIN group: STRING ← [maxRNameLength]; ctMembers: CARDINAL ← 0; pass: BOOLEAN; tree: Tree; ListUser: BinaryTree.UserProc = -- [id: Sv] BEGIN aborted ← StreamDefs.userAbort; IF aborted THEN RETURN; IF ctMembers # 0 THEN WriteString[", "L]; WriteString[id]; ctMembers ← ctMembers + 1; END; -- of ListUser [pass, ] ← GetRName ["Expand group: "L, group, FALSE]; IF pass THEN RETURN; WriteCR[]; tree ← ExpandFileOrGroup[group ! BadFile => GOTO return]; IF aborted THEN { BinaryTree.DestroyTree[tree, z]; WriteLine["Aborted"L]; RETURN}; IF tree = NIL THEN {WriteLine [" is empty"L]; RETURN;}; WriteString["Expansion of group "L]; WriteString[group]; WriteString [" ("L]; PutLevel [level]; WriteString["): "L]; BinaryTree.EnumerateTree[tree, ListUser]; IF aborted THEN {WriteCR[]; WriteLine ["Aborted"L]} ELSE TerminateDataSink [ctMembers]; BinaryTree.DestroyTree[tree, z]; EXITS return => {}; END; -- of ExpandCommand DoubleGroupCommand: PROC [command: Command] = BEGIN group1: STRING ← [maxRNameLength]; group2: STRING ← [maxRNameLength]; pass: BOOLEAN; what: STRING ← SELECT command FROM subtract => "Subtracting group: "L, union => "Uniting groups: "L, ENDCASE => "Intersecting groups: "L; [pass, ] ← GetRName [what, group1, FALSE]; IF pass THEN RETURN; what ← IF command = subtract THEN " minus group: "L ELSE " and: "L; [pass, ] ← GetRName [what, group2, FALSE]; IF pass THEN RETURN; WriteCR[]; CompareGroups[group1, group2, command]; END; -- of DoubleGroupCommand RegistryFilterCommand: PROC = BEGIN include: BOOLEAN ← TRUE; group: STRING ← [maxRNameLength]; reg: STRING ← [maxRNameLength]; pass: BOOLEAN; [pass, ] ← GetRName ["Registry filter of group: "L, group, FALSE]; IF pass THEN RETURN; WriteString [" against registry: "L]; IODefs.ReadID[reg ! IODefs.LineOverflow, IODefs.Rubout => {WriteLine [" XXX"L]; GOTO out}]; IF reg.length = 0 THEN {WriteLine [" XXX"L]; GOTO out}; IF reg[0] = '~ THEN {include ← FALSE; DeleteFirstChar [reg]; IF reg.length = 0 THEN {WriteLine [" XXX"L]; GOTO out}}; WriteCR[]; RegistryFilter [group, reg, include]; EXITS out => {}; END; ChangeLevel: PROC = BEGIN char: CHARACTER; WriteString ["Change membership level (Closure, Direct, UpArrow): "L]; PutLevel [level]; WriteString [" ← "L]; SELECT (char ← String.LowerCase [IODefs.ReadChar []]) FROM 'c => level ← closure; 'd => level ← direct; 'u => level ← upArrow; ENDCASE => {WriteLine [" ??? "L]; RETURN}; PutLevel [level]; WriteCR[]; END; -- of ChangeLevel Salutations: PROC [version: STRING] = BEGIN OPEN Time; time: STRING ← [20]; WriteCR[]; WriteString [version]; WriteString [", of "L]; Append [time, Unpack [Runtime.GetBcdTime []]]; time.length ← time.length - 3; -- remove the seconds WriteLine [time]; time.length ← 0; Append [time, Unpack []]; WriteLine [time]; WriteCR[]; LaurelExecDefs.MakeMenuCommandCallable[user]; LaurelExecDefs.MakeMenuCommandCallable[newMail]; LaurelExecDefs.MakeMenuCommandCallable[mailFile]; LaurelExecDefs.MakeMenuCommandCallable[display]; LaurelExecDefs.MakeMenuCommandCallable[delete]; LaurelExecDefs.MakeMenuCommandCallable[undelete]; LaurelExecDefs.MakeMenuCommandCallable[moveTo]; LaurelExecDefs.MakeMenuCommandCallable[copy]; END; -- of proc Salutations --TTY stuff Confirm: PROC [prompt: STRING, outputCR: BOOLEAN] RETURNS [confirmed: BOOLEAN] = BEGIN DO WriteString [prompt]; WriteString [" [confirm] "L]; SELECT String.LowerCase [IODefs.ReadChar []] FROM 'y, CR, ESC, SP => confirmed ← TRUE; 'n, DEL => confirmed ← FALSE; ENDCASE => BEGIN WriteLine ["Yes (or CR) or No (or DEL)?"L]; LOOP; END; EXIT; ENDLOOP; WriteString [IF confirmed THEN "Yes"L ELSE "No"L]; IF outputCR THEN WriteCR[]; END; -- of proc Confirm MainLoop: PROC = BEGIN char: CHARACTER; Version: STRING = "Groups 6.1"L; Salutations [Version]; DO aborted ← StreamDefs.userAbort ← FALSE; WriteString ["=> "L]; SELECT (char ← String.LowerCase [IODefs.ReadChar []]) FROM 'e => ExpandCommand []; 'i => DoubleGroupCommand [intersect]; 'l => ChangeLevel[]; 'm => IsMemberCommand[]; 'q => IF Confirm ["Quit"L, TRUE] THEN EXIT; 'r => RegistryFilterCommand[]; 's => DoubleGroupCommand [subtract]; 'u => DoubleGroupCommand [union]; ENDCASE => BEGIN WriteLine ["? - Commands: E (Expansion), I (Intersection), L (change expansion Level) M (Membership check), Q (Quit), R (Registry filter), S (Subtraction), or U (Union)"L]; LOOP; END; exD.ClearExceptionsRegion[]; ENDLOOP; WriteCR[]; [] ← Storage.Prune []; END; -- of proc MainLoop -- GroupsImpl's mainline code: MainLoop[]; END.