-- 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.