-- PDQueueImpl.mesa
-- Copyright (C) 1984, Xerox Corporation.  All rights reserved.
-- Michael Plass, April 10, 1985 5:28:48 pm PST
-- Tim Diebert,  5-Sep-86 14:00:44
-- 
DIRECTORY Time, Checksum, Environment, Inline, PDInterpSysCalls,
   PDQueue, String, Process;
   
PDQueueImpl: MONITOR
   IMPORTS Time, Checksum, Inline, PDInterpSysCalls, String, Process
   EXPORTS PDQueue
   = BEGIN
   
   Request: TYPE = PDQueue.Request;
      
   readProc: PROC [address: LONG POINTER, nwords: CARDINAL] ← NIL;
      
   writeProc: PROC [address: LONG POINTER, nwords: CARDINAL] ← NIL;
      
   maxStateWords: CARDINAL ← 16*1024;
      
   deferSaving: BOOLEAN ← FALSE;
   saveDeferred: BOOLEAN ← FALSE;
   state: LONG POINTER TO StringSeqRep ← NIL;
   messages: LONG POINTER TO StringSeqRep ← NIL;
      
   StringSeqRep: TYPE = MACHINE DEPENDENT RECORD [
      nStrings: CARDINAL,
      end: CARDINAL,
      mark: CARDINAL,
      nMarked: CARDINAL,
      nDiscarded: CARDINAL ← 0,
      check: CARDINAL,
      nWords: CARDINAL,
      stringSpace: SEQUENCE COMPUTED CARDINAL OF WORD
      ];
      
   StringRep: TYPE = RECORD [
      nChars: CARDINAL,
      char: PACKED ARRAY [0..0) OF CHAR
      ];
      
   Validate: INTERNAL PROC [s: LONG POINTER TO StringSeqRep] RETURNS [ok: BOOLEAN] = {
      nStrings: CARDINAL ← 0;
      nMarked: CARDINAL ← 0;
      offset: CARDINAL ← 0;
      IF s.check # CalculateCheck[s] THEN RETURN [FALSE];
      IF s.mark > s.end THEN RETURN [FALSE];
      WHILE offset < s.end DO
         words: CARDINAL;
         nStrings ← nStrings + 1;
         IF offset > s.mark AND nMarked = 0 THEN RETURN [FALSE];
         IF offset >= s.mark THEN nMarked ← nMarked + 1;
         words ← (s[offset]+Environment.bytesPerWord-1)/Environment.bytesPerWord+1;
         offset ← offset + words;
         ENDLOOP;
      IF offset # s.end THEN RETURN [FALSE];
      IF nStrings # s.nStrings THEN RETURN [FALSE];
      IF nMarked # s.nMarked THEN RETURN [FALSE];
      RETURN [TRUE]
      };
      
   DiscardMessages: INTERNAL PROC [maxWords: CARDINAL] = {
      WHILE messages.end > maxWords DO Discard[messages, 1] ENDLOOP;
      };
      
   Discard: INTERNAL PROC [s: LONG POINTER TO StringSeqRep, n: CARDINAL] = {
      newStart: CARDINAL ← StringOffset[s, n];
      Inline.LongCOPY[from: @(s[newStart]), nwords: s.end-newStart, to: @(s[0])];
      s.nStrings ← s.nStrings - n;
      s.end ← s.mark ← s.end-newStart;
      s.nDiscarded ← s.nDiscarded + n;
      };
      
   StringOffset: INTERNAL PROC [s: LONG POINTER TO StringSeqRep, n: CARDINAL] RETURNS [offset: CARDINAL] = {
      offset ← 0;
      IF n = 0 THEN RETURN;
      IF n > s.nStrings THEN ERROR;
      FOR i: NAT IN [0..n) DO
         offset ← NextStringOffset[s, offset];
         ENDLOOP;
      };
      
   NextStringOffset: INTERNAL PROC [s: LONG POINTER TO StringSeqRep, offset: CARDINAL] RETURNS [CARDINAL] = INLINE {
      IF offset >= s.end THEN ERROR
      ELSE {
         words: CARDINAL ← (s[offset]+Environment.bytesPerWord-1)/Environment.bytesPerWord+1;
         IF words > s.end-offset THEN ERROR;
         offset ← offset + words;
         RETURN [offset];
         };
      };
      
   Append: INTERNAL PROC [s: LONG POINTER TO StringSeqRep, string: LONG STRING] = {
      body: LONG POINTER TO StringRep;
      chars: CARDINAL ← IF string = NIL THEN 0 ELSE string.length;
      words: CARDINAL ← (string.length+Environment.bytesPerWord-1)/Environment.bytesPerWord+1;
      IF s.mark # s.end THEN ERROR;
      IF s.end+words > s.nWords THEN ERROR;
      body ← LOOPHOLE[@(s[s.end])];
      body.nChars ← chars;
      FOR i: NAT IN [0..chars) DO
         body.char[i] ← string[i];
         ENDLOOP;
      IF chars MOD 2 = 1 THEN body.char[chars] ← '\000;
      s.mark ← s.end ← s.end+words;
      s.nStrings ← s.nStrings + 1;
      };
      
   AppendToString: INTERNAL PROC [string: LONG STRING, s: LONG POINTER TO StringSeqRep, offset: CARDINAL] = {
      body: LONG POINTER TO StringRep;
      IF offset >= s.end THEN ERROR;
      IF offset+(s[offset]+Environment.bytesPerWord-1)/Environment.bytesPerWord+1 > s.end THEN ERROR;
      body ← LOOPHOLE[@(s[offset])];
      FOR i: NAT IN [0..body.nChars) DO
         string[string.length] ← body.char[i];
         string.length ← string.length + 1;
         ENDLOOP;
      };
      
   MoveToString: INTERNAL PROC [string: LONG STRING, s: LONG POINTER TO StringSeqRep, offset: CARDINAL] = {
      string.length ← 0;
      AppendToString[string, s, offset];
      };
      
   GetNumberDiscardedMessages: ENTRY PROC RETURNS [CARDINAL] = {
      IF messages = NIL THEN Restore[];
      RETURN [messages.nDiscarded];
      };
      
   GetString: ENTRY PROC [string: LONG STRING, s: LONG POINTER TO StringSeqRep, n: CARDINAL] RETURNS [ok: BOOLEAN] = {
      string.length ← 0;
      IF n-s.nDiscarded > 32767 THEN RETURN [TRUE];
         -- funny condition due to unsigned arithmetic . . . nDiscarded may wrap
      IF n-s.nDiscarded >= s.nStrings THEN RETURN [FALSE];
      AppendToString[string, s, StringOffset[s, n-s.nDiscarded]];
      RETURN [TRUE];
      };
      
   MoveMarkedStrings: INTERNAL PROC [from, to: LONG POINTER TO StringSeqRep] = {
      nwords: CARDINAL ← from.end-from.mark;
      IF to.nWords < to.mark+nwords THEN ERROR;
      to.nStrings ← to.nStrings - to.nMarked;
      to.nMarked ← 0;
      to.end ← to.mark;
      Inline.LongCOPY[from: @(from[from.mark]), nwords: nwords, to: @(to[to.mark])];
      to.nStrings ← to.nStrings + from.nMarked;
      to.nMarked ← from.nMarked;
      to.end ← to.end + nwords;
      from.nStrings ← from.nStrings-from.nMarked;
      from.nMarked ← 0;
      from.end ← from.mark;
      };
      
   CalculateCheck: INTERNAL PROC [s: LONG POINTER TO StringSeqRep] RETURNS [CARDINAL] = {
      cs: CARDINAL ← Checksum.ComputeChecksum[nWords: 5, p: s];
      cs ← Checksum.ComputeChecksum[cs: cs, nWords: 1, p: s+6];
      RETURN [cs];
      };
      
   Save: INTERNAL PROC = {
      IF deferSaving THEN {saveDeferred ← TRUE; RETURN};
      IF state = NIL THEN ERROR;
      DiscardMessages[state.nWords-state.end];
      messages.nMarked ← messages.nStrings;
      messages.mark ← 0;
      MoveMarkedStrings[from: messages, to: state];
      state.check ← CalculateCheck[state];
      IF NOT Validate[state] THEN ERROR;
      IF writeProc # NIL THEN {
         usingDisk ← Process.GetCurrent[];
         writeProc[state, SIZE[StringSeqRep[state.end]]];
         usingDisk ← NIL;
         };
      MoveMarkedStrings[from: state, to: messages];
      messages.nMarked ← 0;
      messages.mark ← messages.end;
      saveDeferred ← FALSE;
      };
      
   Restore: INTERNAL PROC = {
      IF state # NIL THEN ERROR;
      IF readProc # NIL THEN {
         temp: LONG POINTER TO StringSeqRep ← PDInterpSysCalls.AllocateSpace[256];
         usingDisk ← Process.GetCurrent[];
         readProc[temp, 256];
         usingDisk ← NIL;
         IF temp.check = CalculateCheck[temp] THEN {
            words: CARDINAL ← SIZE[StringSeqRep[temp.end]];
            wordsAllocated: CARDINAL ← MAX[maxStateWords, words+255]/256*256;
            IF wordsAllocated <= maxStateWords THEN {
               state ← PDInterpSysCalls.AllocateSpace[wordsAllocated];
               usingDisk ← Process.GetCurrent[];
               readProc[state, words];
               usingDisk ← NIL;
               state.nWords ← wordsAllocated-SIZE[StringSeqRep[0]];
               IF Validate[state] THEN {
                  AllocMessages[];
                  MoveMarkedStrings[from: state, to: messages];
                  messages.nMarked ← 0;
                  messages.mark ← messages.end;
                  firstUnprintedMessage ← messages.nDiscarded+messages.nStrings-24;
                     -- it should be ok if this wraps around.
                  }
               ELSE {
                  PDInterpSysCalls.FreeSpace[state];
                  state ← NIL
                  };
               };
            };
         PDInterpSysCalls.FreeSpace[temp];
         temp ← NIL;
         };
      IF state = NIL THEN InternalReset[];
      };
      
   AllocMessages: INTERNAL PROC = {
      wordsAllocated: CARDINAL ← SIZE[StringSeqRep[state.nWords]];
      IF messages # NIL THEN {PDInterpSysCalls.FreeSpace[messages]; messages ← NIL};
      messages ← PDInterpSysCalls.AllocateSpace[wordsAllocated];
      messages.nWords ← wordsAllocated-SIZE[StringSeqRep[0]];
      messages.nStrings ← messages.end ← messages.mark ← messages.nMarked ← messages.nDiscarded ← 0;
      };
      
   InternalReset: INTERNAL PROC = {
      IF state # NIL THEN {PDInterpSysCalls.FreeSpace[state]; state ← NIL};
      state ← PDInterpSysCalls.AllocateSpace[maxStateWords];
      state.nWords ← maxStateWords-SIZE[StringSeqRep[0]];
      state.nStrings ← state.end ← state.mark ← state.nMarked ← state.nDiscarded ← 0;
      AllocMessages[];
      };
      
   Reset: PUBLIC ENTRY PROC = {InternalReset[]; Save[]};
      
   newRequest: CONDITION;
      
   stringsPerRequest: CARDINAL = 6;
      
   requestLimit: CARDINAL ← 1000;
      -- Requests are numbered modulo this number.
      
   ReqNumberFromStringNumber: PROC [stringNumber: CARDINAL] RETURNS [requestNumber: CARDINAL] = {
      requestNumber ← (stringNumber/stringsPerRequest) MOD requestLimit;
      };
      
   DecodeCopies: PROC [tag: LONG STRING] RETURNS [copies: CARDINAL] = {
      copies ← ORD[tag[2]]*256+ORD[tag[3]];
      };
      
   QueueRequest: PUBLIC ENTRY PROC [request: Request] RETURNS [requestNumber: INT] = {
      ENABLE UNWIND => NULL;
      tag: LONG STRING ← [4];
      wordsNeeded: INT ← 2*(INT[4]+request.fileName.length+request.requestTime.length+request.requestor.length+request.requestorPassword.length+request.separator.length) + stringsPerRequest*(SIZE[StringRep]+1);
      IF state = NIL THEN Restore[];
      IF state.nStrings >= stringsPerRequest*requestLimit THEN RETURN [-1];
      IF state.end+wordsNeeded+10 >= state.nWords THEN RETURN [-1];
      requestNumber ← ReqNumberFromStringNumber[state.nStrings+state.nDiscarded];
      String.AppendString[tag, "  "];
      String.AppendChar[tag, VAL[Inline.HighByte[CARDINAL[request.copies]]]];
      String.AppendChar[tag, VAL[Inline.LowByte[CARDINAL[request.copies]]]];
      Append[state, tag];
      Append[state, request.fileName];
      Append[state, request.requestTime];
      Append[state, request.requestor];
      Append[state, request.requestorPassword];
      Append[state, request.separator];
      IF stringsPerRequest # 6 THEN ERROR;
      BEGIN
         msg: LONG STRING ← [250];
         String.AppendString[msg, request.fileName];
         IF request.copies#1 THEN {
            String.AppendString[msg, " ("];
            String.AppendDecimal[msg, request.copies];
            String.AppendString[msg, " copies)"];
            };
         InternalLogMessage[msg, requestNumber, request.requestor];
         END;
         Save[];
      NOTIFY newRequest;
      };
      
   reprintCopies: CARDINAL ← 0;
   reprintCancelled: BOOLEAN ← FALSE;
      
   Reprint: PUBLIC ENTRY PROC [copies: CARDINAL] = {
      reprintCancelled ← FALSE;
      reprintCopies ← reprintCopies + copies;
      NOTIFY newRequest;
      };
      
   CancelReprint: PUBLIC ENTRY PROC = {
      reprintCancelled ← TRUE;
      reprintCopies ← 0;
      };
      
   ReprintCancelled: PUBLIC PROC RETURNS [BOOLEAN] = {RETURN [reprintCancelled]};
      
   printingSuspended: BOOLEAN ← FALSE;
      
   GetSuspended: PUBLIC ENTRY PROC RETURNS [BOOLEAN] = {RETURN [printingSuspended]};
   SetSuspended: PUBLIC ENTRY PROC [suspended: BOOLEAN] RETURNS [old: BOOLEAN] = {
      old ← printingSuspended;
      printingSuspended ← suspended;
      NOTIFY newRequest;
      };
      
   abortCurrent: BOOLEAN;
      
   FirstRequest: ENTRY PROC RETURNS [requestNumber: CARDINAL] = {
      ENABLE UNWIND => NULL;
      IF state = NIL THEN Restore[];
      requestNumber ← ReqNumberFromStringNumber[state.nDiscarded];
      };
      
   FindRequest: ENTRY PROC [requestNumber: CARDINAL, tag: LONG STRING, request: Request] = {
      ENABLE UNWIND => NULL;
      stringNumber: CARDINAL ← requestNumber*stringsPerRequest;
      offset: CARDINAL ← 0;
      IF state = NIL THEN Restore[];
      WHILE stringNumber < state.nDiscarded DO stringNumber ← stringNumber + requestLimit*stringsPerRequest ENDLOOP;
      IF stringNumber-state.nDiscarded >= state.nStrings THEN RETURN;
      offset ← StringOffset[state, stringNumber-state.nDiscarded];
      MoveToString[tag, state, offset]; offset ← NextStringOffset[state, offset];
      MoveToString[request.fileName, state, offset]; offset ← NextStringOffset[state, offset];
      MoveToString[request.requestTime, state, offset]; offset ← NextStringOffset[state, offset];
      MoveToString[request.requestor, state, offset]; offset ← NextStringOffset[state, offset];
      MoveToString[request.requestorPassword, state, offset]; offset ← NextStringOffset[state, offset];
      MoveToString[request.separator, state, offset];
      IF stringsPerRequest # 6 THEN ERROR;
      };
      
   GetRequest: ENTRY PROC [request: Request] RETURNS [requestNumber: INT, copies: CARDINAL] = {
      ENABLE UNWIND => NULL;
      tag: LONG STRING ← [4];
      IF state = NIL THEN Restore[];
      DO
         offset: CARDINAL ← 0;
         WHILE printingSuspended OR (state.nStrings = 0 AND reprintCopies = 0) DO WAIT newRequest ENDLOOP;
         abortCurrent ← FALSE;
         reprintCancelled ← FALSE;
         IF reprintCopies # 0 THEN {
            requestNumber ← -1;
            copies ← reprintCopies;
            reprintCopies ← 0;
            RETURN;
            };
         requestNumber ← ReqNumberFromStringNumber[state.nDiscarded];
         MoveToString[tag, state, offset]; offset ← NextStringOffset[state, offset];
         MoveToString[request.fileName, state, offset]; offset ← NextStringOffset[state, offset];
         MoveToString[request.requestTime, state, offset]; offset ← NextStringOffset[state, offset];
         MoveToString[request.requestor, state, offset]; offset ← NextStringOffset[state, offset];
         MoveToString[request.requestorPassword, state, offset]; offset ← NextStringOffset[state, offset];
         MoveToString[request.separator, state, offset];
         copies ← DecodeCopies[tag];
         IF stringsPerRequest # 6 THEN ERROR;
         IF tag[0] = '  THEN EXIT;
         Discard[state, stringsPerRequest];
         WHILE state.nDiscarded >= stringsPerRequest*requestLimit DO
            state.nDiscarded ← state.nDiscarded - stringsPerRequest*requestLimit;
            ENDLOOP;
         ENDLOOP;
      };
      
   RemoveRequest: ENTRY PROC [requestNumber: CARDINAL] = {
      ENABLE UNWIND => NULL;
      IF state = NIL THEN Restore[];
      IF requestNumber # ReqNumberFromStringNumber[state.nDiscarded] THEN ERROR;
      Discard[state, stringsPerRequest];
      WHILE state.nDiscarded >= stringsPerRequest*requestLimit DO
         state.nDiscarded ← state.nDiscarded - stringsPerRequest*requestLimit;
         ENDLOOP;
      Save[];
      };
      
   DeferSaving: PUBLIC ENTRY PROC = {
      ENABLE UNWIND => NULL;
      deferSaving ← TRUE;
      };
      
   DoDeferredSave: PUBLIC ENTRY PROC = {
      ENABLE UNWIND => NULL;
      deferSaving ← FALSE;
      IF saveDeferred THEN Save[];
      };
      
   DoRequest: PUBLIC PROC [action: PROC [request: Request, requestNumber: CARDINAL, abort: LONG POINTER TO BOOLEAN]] = {
      fileName: LONG STRING ← [80];
      requestTime: LONG STRING ← [20];
      requestor: LONG STRING ← [80];
      requestorPassword: LONG STRING ← [80];
      separator: LONG STRING ← [80];
      requestNumber: INT;
      body: LONG POINTER TO StringRep;
      copies: CARDINAL;
      aborted: BOOLEAN ← FALSE;
      [requestNumber, copies] ← GetRequest[[fileName, requestTime, requestor, requestorPassword, separator, 0]];
      IF requestNumber < 0 THEN {
         LogMessage["Reprint."];
         action[[NIL, NIL, NIL, NIL, NIL, copies], NAT.LAST, @abortCurrent ! ABORTED => {aborted ← TRUE; CONTINUE}];
         LogMessage["Reprint completed."];
         }
      ELSE {
         body ← LOOPHOLE[@(state[0])];
         IF body.nChars # 4 THEN ERROR;
         body.char[1] ← 'P;
         LogMessage["Started.", requestNumber, requestor];
         action[[fileName, requestTime, requestor, requestorPassword, separator, copies], requestNumber, @abortCurrent ! ABORTED => {aborted ← TRUE; CONTINUE}];
         IF aborted THEN LogMessage["Aborted.", requestNumber, requestor]
         ELSE LogMessage["Completed.", requestNumber, requestor];
         RemoveRequest[requestNumber];
         };
      };
      
   CancelRequest: PUBLIC ENTRY PROC [requestNumber: CARDINAL] RETURNS [ok: BOOLEAN] = {
      stringNumber: CARDINAL ← requestNumber*stringsPerRequest;
      body: LONG POINTER TO StringRep;
      IF state = NIL THEN Restore[];
      WHILE stringNumber < state.nDiscarded DO
         stringNumber ← stringNumber+requestLimit*stringsPerRequest
         ENDLOOP;
      IF stringNumber-state.nDiscarded >= state.nStrings THEN RETURN [FALSE];
      body ← LOOPHOLE[@(state[StringOffset[state, stringNumber-state.nDiscarded]])];
      IF body.nChars # 4 THEN ERROR;
      IF body.char[0] # '  THEN RETURN [FALSE];
      body.char[0] ← 'X;
      IF stringNumber = state.nDiscarded THEN abortCurrent ← TRUE;
      Save[];
      RETURN [TRUE];
      };
      -- 
   RequestStatus: TYPE = PDQueue.RequestStatus;
   CheckRequest: PUBLIC PROC [requestNumber: CARDINAL, action: PROC [request: Request, status: RequestStatus]] = {
      tag: LONG STRING ← [4];
      status: RequestStatus;
      fileName: LONG STRING ← [80];
      requestTime: LONG STRING ← [20];
      requestor: LONG STRING ← [80];
      requestorPassword: LONG STRING ← [80];
      separator: LONG STRING ← [80];
      tag.length ← 0;
      FindRequest[requestNumber, tag, [fileName, requestTime, requestor, requestorPassword, separator, 0]];
      IF tag.length = 0 THEN status ← notFound
      ELSE IF tag[0] = 'X THEN status ← canceled
      ELSE IF tag[1] = 'P THEN status ← printing
      ELSE status ← waiting;
      action[[fileName, requestTime, requestor, requestorPassword, separator, DecodeCopies[tag]], status];
      };
      
   CountRequests: PUBLIC ENTRY PROC RETURNS [requestCount: NAT] = {
      IF state = NIL THEN Restore[];
      requestCount ← state.nStrings/stringsPerRequest;
      };
      
   EnumerateRequests: PUBLIC PROC [action: PROC [requestNumber: CARDINAL, request: Request, status: RequestStatus] RETURNS [continue: BOOLEAN]] = {
      firstRequest: NAT ← FirstRequest[];
      nRequests: NAT ← CountRequests[];
      requestNumber: NAT ← firstRequest MOD requestLimit;
      THROUGH [0..nRequests) DO
         continue: BOOLEAN ← TRUE;
         myAction: PROC [request: Request, status: RequestStatus] = {
            IF status = printing OR status = waiting THEN continue ← action[requestNumber, request, status];
            };
         
         CheckRequest[requestNumber MOD requestLimit, myAction];
         IF NOT continue THEN EXIT;
         requestNumber ← (requestNumber + 1) MOD requestLimit;
         ENDLOOP;
      };
      
   RegisterDisk: PUBLIC ENTRY PROC [read, write: PROC [address: LONG POINTER, nwords: CARDINAL], maxWords: CARDINAL] = {
      IF state # NIL THEN {
         PDInterpSysCalls.FreeSpace[state];
         state ← NIL;
         };
      readProc ← read;
      writeProc ← write;
      maxStateWords ← maxWords;
      };
      
   usingDisk: PROCESS ← NIL;
      -- Records the process using the disk under the monitor, to avoid deadlocks in case of disk errors.
   LogMessage: PUBLIC PROC [message: LONG STRING, requestNumber: INT ← -1, userName: LONG STRING ← NIL] = {
      IF usingDisk = Process.GetCurrent[] THEN {
         IF writeLineProc = NIL THEN ERROR;
            -- Remote debug if it ever gets this bad.
         writeLineProc["Help!" ! ABORTED => CONTINUE];
         writeLineProc[message ! ABORTED => CONTINUE];
         }
      ELSE LogMessageEntry[message, requestNumber, userName];
      };
      
   LogMessageEntry: ENTRY PROC [message: LONG STRING, requestNumber: INT ← -1, userName: LONG STRING ← NIL] = {
      InternalLogMessage[message, requestNumber, userName];
      };
      
   InternalLogMessage: INTERNAL PROC [message: LONG STRING, requestNumber: INT ← -1, userName: LONG STRING ← NIL] = {
      msg: LONG STRING ← [250];
      Time.AppendCurrent[msg];
      msg[msg.length] ← ' ; msg.length ← msg.length + 1;
      IF requestNumber >= 0 THEN {
         String.AppendChar[msg, 'R];
         FOR i: CARDINAL ← requestLimit/10, i/10 WHILE i#0 DO
            String.AppendDecimal[msg, (requestNumber/i) MOD 10];
            ENDLOOP;
         String.AppendChar[msg, ' ];
         };
      String.AppendString[msg, message];
      IF userName#NIL THEN {
         String.AppendString[msg, " ("];
         String.AppendString[msg, userName];
         String.AppendChar[msg, ')];
         };
      IF state = NIL THEN Restore[];
      DiscardMessages[messages.nWords-SIZE[StringBody[msg.length]]];
      Append[messages, msg];
      TryToPrintMessages[];
      };
      
   loggingMessages: BOOLEAN ← FALSE;
   firstUnprintedMessage: CARDINAL ← 0;
   writeLineProc: PROC[LONG STRING] ← NIL;
      
   RegisterTTY: PUBLIC ENTRY PROC [writeLine: PROC[LONG STRING]] = {
      writeLineProc ← writeLine;
      TryToPrintMessages[];
      };
      
   TryToPrintMessages: INTERNAL PROC = {
      msg: LONG STRING ← [250];
      GetMsg: INTERNAL PROC [n: CARDINAL] RETURNS [ok: BOOLEAN] = {
         msg.length ← 0;
         IF n-messages.nDiscarded > 32767 THEN RETURN [TRUE];
            -- funny condition due to unsigned arithmetic . . . nDiscarded may wrap
         IF n-messages.nDiscarded >= messages.nStrings THEN RETURN [FALSE];
         AppendToString[msg, messages, StringOffset[messages, n-messages.nDiscarded]];
         RETURN [TRUE];
         };
      IF NOT loggingMessages OR writeLineProc = NIL THEN RETURN;
      FOR i: CARDINAL ← firstUnprintedMessage, i+1 UNTIL NOT GetMsg[i] DO
         writeLineProc[msg ! ABORTED => CONTINUE];
         firstUnprintedMessage ← i+1;
         ENDLOOP;
      };
      
   SetLogState: PUBLIC ENTRY PROC [logging: BOOLEAN] RETURNS [old: BOOLEAN] = {
      old ← loggingMessages;
      loggingMessages ← logging;
      IF messages # NIL THEN TryToPrintMessages[];
      };
      
   EnumerateMessages: PUBLIC PROC [action: PROC[message: LONG STRING] RETURNS [continue: BOOLEAN]] = {
      msg: LONG STRING ← [250];
      FOR i: CARDINAL ← GetNumberDiscardedMessages[], i+1 UNTIL NOT GetString[msg, messages, i] DO
         IF NOT action[msg].continue THEN EXIT;
         ENDLOOP;
      };
   
   END.