-- Grapevine: pattern matching on names
-- [Ivy]<Birrell>NameMatch.mesa
-- Andrew Birrell  28-Jan-82 11:52:35

DIRECTORY
BodyDefs,
FrameDefs,
IODefs,
LaurelExecDefs,
NameInfoDefs,
StringDefs,
String,
SystemDefs,
Time;

NameMatch: PROGRAM
IMPORTS FrameDefs, IODefs, LaurelExecDefs, NameInfoDefs, StringDefs,
        String, SystemDefs, Time =

BEGIN

LowerCase: PROC[c: CHARACTER] RETURNS[CHARACTER] = INLINE
   { RETURN[ IF c IN ['A..'Z] THEN 'a + (c-'A) ELSE c ] };

ReadString: PROC[prompt, s: STRING] =
   BEGIN
   DO BEGIN
      IODefs.WriteString[prompt];
      IODefs.ReadID[s ! IODefs.Rubout => GOTO del;
                        IODefs.LineOverflow => GOTO long];
      EXIT
      EXITS
         del => IODefs.WriteLine[" XXX"L];
         long => IODefs.WriteLine[" too long!"L];
      END;
   ENDLOOP;
   END;

InfoType: TYPE = { stamp, mailboxes, owners, friends, members,
                   remark, none };

Match: PROC[pattern: STRING, set: NameInfoDefs.RListHandle,
            info: InfoType] =
   BEGIN
   count: CARDINAL ← 0;
   TestMatch: PROC[name: STRING] RETURNS[done: BOOLEAN] =
      BEGIN
      pPos: CARDINAL ← 0;
      pBase: CARDINAL ← 0;
      nPos: CARDINAL ← 0;
      literal: BOOLEAN ← FALSE;
      star: BOOLEAN ← FALSE;
      match: BOOLEAN ← FALSE;
      DO IF nPos = name.length THEN { match ← (pPos=pattern.length); EXIT };
         SELECT TRUE FROM
           literal, pPos = pattern.length => NULL;
           pattern[pPos] = '' => { literal ← TRUE; pPos←pPos+1; LOOP };
           pattern[pPos] = '* =>
             { star ← TRUE; pPos←pPos+1 ; pBase←pPos; literal←FALSE; LOOP };
         ENDCASE => NULL;
         IF pPos = pattern.length
         OR LowerCase[pattern[pPos]] # LowerCase[name[nPos]]
         THEN { IF star THEN pPos ← pBase ELSE EXIT }
         ELSE pPos ← pPos+1;
         literal ← FALSE; nPos ← nPos+1;
      ENDLOOP;
      IF match
      THEN BEGIN
           -- This statement dedicated to MDS --
           IF info # stamp
           THEN { IODefs.WriteString[name]; IODefs.WriteString[" ... "L] };
           SELECT info FROM
               stamp =>
                 BEGIN
                 info: NameInfoDefs.ExpandInfo = NameInfoDefs.Expand[name];
                 stamp: BodyDefs.Timestamp;
                 WITH i: info SELECT FROM
                   individual =>
                     { NameInfoDefs.Close[i.sites]; stamp ← i.stamp };
                   group =>
                     { NameInfoDefs.Close[i.members]; stamp ← i.stamp };
                 ENDCASE;
                 IF info.type NOT IN [group..individual]
                 THEN BEGIN
                      IODefs.WriteString[name];
                      IODefs.WriteString[" ... "L];
                      BadName[info.type];
                      END
                 ELSE BEGIN
                      s: STRING = [24];
                      String.AppendLongNumber[s, stamp.time, 10];
                      IODefs.WriteString[s];
                      IODefs.WriteString[" "L];
                      IODefs.WriteString[name];
                      IODefs.WriteString[" ... "L];
                      s.length ← 0;
                      Time.Append[s, Time.Unpack[stamp.time]];
                      IODefs.WriteLine[s];
                      END;
                 END;
               mailboxes =>
                 BEGIN
                 info: NameInfoDefs.ExpandInfo = NameInfoDefs.Expand[name];
                 WITH i: info SELECT FROM
                   individual =>
                     TypeRList[i.sites, full, "invalid recipient"L];
                   group =>
                     { IODefs.WriteString["(fwd) "L];
                       TypeRList[i.members, full, "silly"L] };
                 ENDCASE => BadName[info.type];
                 IODefs.WriteLine[""L];
                 END;
               owners =>
                 BEGIN
                 TypeMembers[NameInfoDefs.GetOwners[name], full, "default"L];
                 IODefs.WriteLine[""L];
                 END;
               friends =>
                 BEGIN
                 TypeMembers[NameInfoDefs.GetFriends[name], full, "none"L];
                 IODefs.WriteLine[""L];
                 END;
               members =>
                 BEGIN
                 TypeMembers[NameInfoDefs.GetMembers[name], brief, "empty"L];
                 IODefs.WriteLine[" member(s)"L];
                 END;
               remark =>
                 BEGIN
                 remark: STRING = [64];
                 nameInfo: NameInfoDefs.RemarkInfo =
                                       NameInfoDefs.GetRemark[name, remark];
                 SELECT nameInfo FROM
                   group => IODefs.WriteLine[remark];
                 ENDCASE => BadName[nameInfo];
                 END;
               none => IODefs.WriteLine[""L];
           ENDCASE => ERROR;
           count ← count+1;
           END;
      done ← FALSE;
      END;
   IODefs.WriteLine[" ..."L];
   NameInfoDefs.Enumerate[set, TestMatch];
   IODefs.WriteDecimal[count]; IODefs.WriteLine[" matches"L];
   END;

BadName: PROC[nameInfo: NameInfoDefs.NameType] =
   BEGIN
   IODefs.WriteString[ SELECT nameInfo FROM
       group => "is a group!"L,
       individual => "is an individual"L,
       allDown => "can't contact R-Server"L,
       notFound => "doesn't exist!"L,
     ENDCASE => "I don't understand!" ];
   END;

Amount: TYPE = {full,brief};

TypeMembers: PROC[info: NameInfoDefs.MemberInfo, amount: Amount,
                  default: STRING] =
   BEGIN
   WITH i: info SELECT FROM
     group => TypeRList[i.members, amount, default];
   ENDCASE => BadName[info.type];
   END;

TypeRList: PROC[list: NameInfoDefs.RListHandle, amount: Amount,
                default: STRING] =
   BEGIN
   total: CARDINAL ← 0;
   Work: PROC[name: STRING] RETURNS[done: BOOLEAN] =
      BEGIN
      done ← FALSE;
      IF amount = full
      THEN BEGIN
           IF total # 0 THEN IODefs.WriteString[", "L];
           IODefs.WriteString[name];
           END;
      total ← total+1;
      END;
   NameInfoDefs.Enumerate[list, Work];
   IF total = 0
   THEN IODefs.WriteString[default]
   ELSE IF amount = brief THEN IODefs.WriteDecimal[total];
   NameInfoDefs.Close[list];
   END;


DefaultRegistry: PROC[default, to: STRING] =
   BEGIN
   defPos: CARDINAL ← default.length;
   WHILE defPos > 0
   DO defPos ← defPos-1;
      IF default[defPos] = '. THEN { defPos←defPos+1; EXIT };
   ENDLOOP;
   FOR i: CARDINAL DECREASING IN [0..to.length)
   DO IF to[i] = '. THEN EXIT;
   REPEAT
   FINISHED =>
      BEGIN
      IF to.length < to.maxlength
      THEN { to[to.length] ← '.; to.length ← to.length+1 };
      FOR j: CARDINAL IN [defPos..default.length)
      WHILE to.length < to.maxlength
      DO to[to.length] ← default[j]; to.length ← to.length+1; ENDLOOP;
      END;
   ENDLOOP;
   END;

NameType: TYPE = { individual, group, dead };
mode: NameType;
info: InfoType;
pattern: STRING = [64];
regName: STRING = [64];

IF FrameDefs.IsBound[LaurelExecDefs.MakeMenuCommandCallable]
THEN BEGIN
   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;

DO regGroupName: STRING = [64];
   groupInfo: NameInfoDefs.MemberInfo;

   [] ← SystemDefs.PruneHeap[];

   DO IODefs.WriteString["Groups, Individuals, or Quit? "L];
      SELECT LowerCase[IODefs.ReadChar[]] FROM
        'g => { mode ← group; IODefs.WriteLine["Groups"L]; EXIT };
        'i => { mode ← individual; IODefs.WriteLine["Individuals"L]; EXIT };
        'd => { mode ← dead; IODefs.WriteLine["Deleted names"L]; EXIT };
        'q => { IODefs.WriteLine["Quit"L]; GOTO ended };
      ENDCASE => IODefs.WriteLine["?"L];
   ENDLOOP;

   BEGIN
      ReadString["Registry: "L, regName];
      StringDefs.AppendString[regGroupName, 
         SELECT mode FROM
           group => "Groups."L,
           individual => "Individuals.",
           dead => "Dead.",
         ENDCASE => ERROR];
      StringDefs.AppendString[regGroupName, regName];
   END;

   IODefs.WriteString[".  Enumerating registry ... "L];
   groupInfo ← NameInfoDefs.GetMembers[regGroupName];
   WITH g: groupInfo SELECT FROM
     group =>
       BEGIN
       IODefs.WriteLine["ok"L];
       DO ReadString["Pattern: "L, pattern];
          IF pattern.length = 0 THEN EXIT;
          DefaultRegistry[regGroupName, pattern];
          IODefs.WriteLine[""L];

          SELECT mode FROM
            group =>
              DO IODefs.WriteString[
                    "Stamp, Friends, Members, Owners, Remark, None, or Exit? "L];
                 SELECT LowerCase[IODefs.ReadChar[]] FROM
                   's => { info ← stamp; IODefs.WriteString["Stamp"L]; EXIT };
                   'f => { info ← friends; IODefs.WriteString["Friends"L]; EXIT };
                   'm => { info ← members; IODefs.WriteString["Members"L]; EXIT };
                   'o => { info ← owners; IODefs.WriteString["Owners"L]; EXIT };
                   'r => { info ← remark; IODefs.WriteString["Remark"L]; EXIT };
                   'n => { info ← none; IODefs.WriteString["None"L]; EXIT };
                   'e => { IODefs.WriteString["Exit"L]; GOTO enumerateAgain };
                 ENDCASE => IODefs.WriteLine["?"L];
              ENDLOOP;
            individual =>
              DO IODefs.WriteString[
                    "Mailboxes, None, or Exit? "L];
                 SELECT LowerCase[IODefs.ReadChar[]] FROM
                   'm => { info ← mailboxes; IODefs.WriteString["Mailboxes"L]; EXIT };
                   'n => { info ← none; IODefs.WriteString["None"L]; EXIT };
                   'e => { IODefs.WriteString["Exit"L]; GOTO enumerateAgain };
                 ENDCASE => IODefs.WriteLine["?"L];
              ENDLOOP;
          ENDCASE => info ← none;

          Match[pattern, g.members, info];

       REPEAT
       enumerateAgain => NULL;
       ENDLOOP;

       NameInfoDefs.Close[g.members];
       IODefs.WriteLine[""L];
       END;
     notFound => IODefs.WriteLine["Unknown registry"L];
     allDown => IODefs.WriteLine["Can't contact R-Server"L];
   ENDCASE => ERROR;

REPEAT

   ended => NULL;

ENDLOOP;

END.