<> <> <> <<>> 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; <> 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; <> LogMessage: PUBLIC PROC [message: LONG STRING, requestNumber: INT _ -1, userName: LONG STRING _ NIL] = { IF usingDisk = Process.GetCurrent[] THEN { IF writeLineProc = NIL THEN ERROR; <> 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.