<> <> <> DIRECTORY Ascii USING [BS, ControlA, ControlQ, ControlR, ControlV, ControlW, ControlX, CR, DEL, FF, SP, TAB], Basics USING [UnsafeBlock, HighByte, HighHalf, LowByte, LowHalf, RawBytes], Convert USING [IntFromRope, RopeFromInt], IO, Process USING [Abort, DisableTimeout, EnableAborts], PrincOpsUtils USING [IsBound], RefText USING [Append, ObtainScratch, ReleaseScratch, TrustTextAsRope], Rope USING [ROPE, ToRefText], TTY USING [CharStatus, CreateTTYInstance, -- DateFormat, -- EchoClass, Handle --, NumberFormat-- ], TTYConstants, TTYStream USING [SendAttention, SetSST, SubSequenceType, WaitAttention]; TTYImpl: MONITOR LOCKS h USING h: Handle IMPORTS Basics, Convert, IO, PrincOpsUtils, Process, RefText, Rope, TTY, TTYStream EXPORTS TTY = BEGIN Handle: PUBLIC TYPE = REF Object; Object: PUBLIC TYPE = MONITORED RECORD [ stream: IO.STREAM _ NIL, echo: TTY.EchoClass _ plain, newline: BOOLEAN _ TRUE, inputAborted: BOOLEAN _ FALSE, next: Handle, buffer: Q _ Init[], backingStream: IO.STREAM _ NIL, altStream: AltHandle _ NIL, inputProcess: PROCESS _ NIL, inputCondition: CONDITION, tty: TTY.Handle _ NIL]; AltHandle: PUBLIC TYPE = REF AltObject; AltObject: TYPE = RECORD [s: IO.STREAM, next: AltHandle]; list: Handle _ NIL; OutOfInstances: PUBLIC ERROR = CODE; NoDefaultInstance: PUBLIC ERROR = CODE; aborted: BYTE = TTYConstants.aborted; notAborted: BYTE = TTYConstants.notAborted; normal: TTYStream.SubSequenceType = TTYConstants.normal; setBackingSize: TTYStream.SubSequenceType = TTYConstants.setBackingSize; removeChars: TTYStream.SubSequenceType = TTYConstants.removeChars; blinkDisplay: TTYStream.SubSequenceType = TTYConstants.blinkDisplay; Create: PUBLIC SAFE PROCEDURE [name: Rope.ROPE _ NIL, backingStream, ttyImpl: IO.STREAM _ NIL] RETURNS [h: Handle] = TRUSTED { h _ NEW[Object _ [stream: NIL, backingStream: NIL, next: list]]; IF ttyImpl = NIL THEN SELECT PrincOpsUtils.IsBound[LOOPHOLE[TTY.CreateTTYInstance]] FROM TRUE => [ttyImpl, backingStream] _ TTY.CreateTTYInstance[name, backingStream, h.tty]; FALSE => ERROR NoDefaultInstance; ENDCASE; h.backingStream _ backingStream; h.stream _ ttyImpl; list _ h; Process.DisableTimeout[@h.inputCondition]; Process.EnableAborts[@h.inputCondition]; h.inputProcess _ FORK ListenForInput[h]}; Destroy: PUBLIC SAFE PROC [h: Handle, deleteBackingFile: BOOLEAN _ FALSE] = TRUSTED { P: ENTRY PROCEDURE [h: Handle] = { ENABLE UNWIND => NULL; IF list = h THEN list _ h.next ELSE FOR l: Handle _ list, l.next UNTIL l.next = NIL DO IF l.next = h THEN {l.next _ l.next.next; EXIT}; ENDLOOP; IF ~IsEmpty[h.buffer] THEN Empty[h.buffer]}; TTYStream.SetSST[h.stream, TTYConstants.deleteBackingFile]; TTYStream.SetSST[h.stream, normal]; Process.Abort[h.inputProcess]; JOIN h.inputProcess; P[h]; IO.Close[h.stream]; }; ListenForInput: PROCEDURE [h: Handle] = TRUSTED BEGIN DoIt: ENTRY PROCEDURE [h: Handle] = { ENABLE UNWIND => NULL; wakeUp: BOOLEAN = IsEmpty[h.buffer]; BlockToQR[block, h.buffer]; IF wakeUp THEN BROADCAST h.inputCondition}; AbortClient: ENTRY PROCEDURE [h: Handle] = { h.inputAborted _ TRUE; BROADCAST h.inputCondition}; blockSize: CARDINAL = 20; block: Basics.UnsafeBlock; storage: PACKED ARRAY [0..blockSize) OF BYTE; block _ [base: LOOPHOLE[LONG[@storage]], startIndex: 0, count: blockSize]; DO block.count _ blockSize; block.count _ IO.UnsafeGetBlock[h.stream, block ! ABORTED => EXIT]; DoIt[h]; ENDLOOP; AbortClient[h]; END; GetEcho: PUBLIC ENTRY SAFE PROCEDURE [h: Handle] RETURNS [TTY.EchoClass] = TRUSTED { ENABLE UNWIND => NULL; InternalCheckState[h]; RETURN[h.echo]}; SetEcho: PUBLIC ENTRY SAFE PROCEDURE [h: Handle, new: TTY.EchoClass] RETURNS [old: TTY.EchoClass] = TRUSTED { ENABLE UNWIND => NULL; InternalCheckState[h]; old _ h.echo; h.echo _ new}; CharsAvailable: PUBLIC ENTRY SAFE PROCEDURE [h: Handle] RETURNS [CARDINAL] = TRUSTED { ENABLE UNWIND => NULL; InternalCheckState[h]; RETURN[Count[h.buffer]]; -- note that this count is incorrect if there are alternate input streams --}; GetChar: PUBLIC ENTRY SAFE PROCEDURE [h: Handle] RETURNS [c: CHARACTER] = TRUSTED { ENABLE UNWIND => NULL; advanceAltStream: BOOLEAN _ FALSE; temp: AltHandle; WHILE h.altStream # NIL DO c _ IO.GetChar[h.altStream.s ! IO.EndOfStream => {advanceAltStream _ TRUE; CONTINUE}]; IF ~advanceAltStream THEN RETURN[c]; IO.Close[h.altStream.s]; temp _ h.altStream; h.altStream _ h.altStream.next; advanceAltStream _ FALSE; ENDLOOP; WHILE IsEmpty[h.buffer] DO InternalCheckState[h]; WAIT h.inputCondition ENDLOOP; c _ GetF[h.buffer]}; PopAlternateInputStreams: PUBLIC ENTRY SAFE PROC [h: Handle, howMany: CARDINAL _ 1] = TRUSTED BEGIN ENABLE UNWIND => NULL; temp: AltHandle; FOR i: CARDINAL IN [0..howMany) WHILE h.altStream # NIL DO IO.Close[h.altStream.s]; temp _ h.altStream; h.altStream _ h.altStream.next; ENDLOOP; END; PushAlternateInputStream: PUBLIC ENTRY SAFE PROCEDURE [h: Handle, stream: IO.STREAM] = TRUSTED BEGIN ENABLE UNWIND => NULL; h.altStream _ NEW[AltObject _ [s: stream, next: h.altStream]]; END; PutBackChar: PUBLIC ENTRY SAFE PROCEDURE [h: Handle, c: CHARACTER] = TRUSTED { ENABLE UNWIND => NULL; InternalCheckState[h]; PutR[h.buffer, c]; -- note that this does not work if there are alternate input streams --}; ResetUserAbort: PUBLIC ENTRY SAFE PROCEDURE [h: Handle] = TRUSTED { ENABLE UNWIND => NULL; InternalCheckState[h]; TTYStream.SendAttention[h.stream, notAborted]}; UserAbort: PUBLIC ENTRY SAFE PROCEDURE [h: Handle] RETURNS [BOOLEAN] = TRUSTED { ENABLE UNWIND => NULL; InternalCheckState[h]; RETURN[SELECT TTYStream.WaitAttention[h.stream] FROM aborted => TRUE, ENDCASE => FALSE]}; SetUserAbort: PUBLIC ENTRY SAFE PROCEDURE [h: Handle] = TRUSTED { ENABLE UNWIND => NULL; InternalCheckState[h]; TTYStream.SendAttention[h.stream, aborted]}; BlinkDisplay: PUBLIC SAFE PROC [h: Handle] = TRUSTED { CheckState[h]; TTYStream.SetSST[h.stream, blinkDisplay]; TTYStream.SetSST[h.stream, normal]}; NewLine: PUBLIC ENTRY SAFE PROCEDURE [h: Handle] RETURNS [BOOLEAN] = TRUSTED { ENABLE UNWIND => NULL; InternalCheckState[h]; RETURN[h.newline]}; PutChar: PUBLIC SAFE PROC [h: Handle, c: CHARACTER] = TRUSTED { P: ENTRY PROCEDURE [h: Handle] = { ENABLE UNWIND => NULL; h.newline _ c = Ascii.CR}; CheckState[h]; P[h]; IO.PutChar[h.stream, c]}; PutString, PutLongString, PutText: PROCEDURE [h: Handle, s: REF TEXT] = TRUSTED { P: ENTRY PROCEDURE [h: Handle] = { ENABLE UNWIND => NULL; h.newline _ s[s.length - 1] = Ascii.CR}; CheckState[h]; IF s = NIL THEN RETURN; IO.UnsafePutBlock[h.stream, [LOOPHOLE[@s.text], 0, s.length]]; P[h]}; RemoveCharacter, RemoveCharacters: PUBLIC SAFE PROC [h: Handle, n: CARDINAL] = TRUSTED { CheckState[h]; TTYStream.SetSST[h.stream, removeChars]; IO.PutChar[h.stream, LOOPHOLE[Basics.HighByte[n]]]; IO.PutChar[h.stream, LOOPHOLE[Basics.LowByte[n]]]; TTYStream.SetSST[h.stream, normal]}; <> NoBackingFile: PUBLIC ERROR = CODE; BackingStream: PUBLIC SAFE PROC [h: Handle] RETURNS [IO.STREAM] = TRUSTED { CheckState[h]; RETURN[h.backingStream]}; SetBackingSize: PUBLIC SAFE PROC [h: Handle, size: LONG CARDINAL] = TRUSTED { lowHalf, highHalf: CARDINAL; CheckState[h]; highHalf _ Basics.HighHalf[size]; lowHalf _ Basics.LowHalf[size]; TTYStream.SetSST[h.stream, setBackingSize]; IO.PutChar[h.stream, LOOPHOLE[Basics.HighByte[highHalf]]]; IO.PutChar[h.stream, LOOPHOLE[Basics.LowByte[highHalf]]]; IO.PutChar[h.stream, LOOPHOLE[Basics.HighByte[lowHalf]]]; IO.PutChar[h.stream, LOOPHOLE[Basics.LowByte[lowHalf]]]; TTYStream.SetSST[h.stream, normal]}; <> GetDecimal: PUBLIC SAFE PROC [h: Handle] RETURNS [i: INTEGER] = TRUSTED { s: REF TEXT _ RefText.ObtainScratch[10]; CheckState[h]; [] _ GetEditedString[h, s, IsAtom]; i _ Convert.IntFromRope[RefText.TrustTextAsRope[s], 10]; RefText.ReleaseScratch[s]; RETURN[i]}; GetID: PUBLIC SAFE PROC [h: Handle, s: REF TEXT] = TRUSTED { CheckState[h]; [] _ GetEditedString[h, s, IsAtom]}; GetLine: PUBLIC SAFE PROC [h: Handle, s: REF TEXT] = TRUSTED { CheckState[h]; [] _ GetEditedString[h, s, IsCR]; PutChar[h, Ascii.CR]}; GetPassword: PUBLIC SAFE PROC [h: Handle, s: REF TEXT] = TRUSTED { old: TTY.EchoClass; CheckState[h]; old _ SetEcho[h, stars]; IF old = none THEN [] _ SetEcho[h, none]; [] _ GetEditedString[h, s, IsAtom ! UNWIND => [] _ SetEcho[h, old]]; [] _ SetEcho[h, old]}; GetLongDecimal: PUBLIC SAFE PROC [h: Handle] RETURNS [n: INT] = TRUSTED { s: REF TEXT _ RefText.ObtainScratch[32]; CheckState[h]; [] _ GetEditedString[h, s, IsAtom]; n _ Convert.IntFromRope[RefText.TrustTextAsRope[s], 10]; RefText.ReleaseScratch[s]; RETURN[n]}; GetLongNumber: PUBLIC SAFE PROC [h: Handle, default: LONG UNSPECIFIED, radix: CARDINAL, showDefault: BOOLEAN] RETURNS [n: LONG UNSPECIFIED] = TRUSTED { s: REF TEXT _ RefText.ObtainScratch[32]; r: REF TEXT _ RefText.ObtainScratch[32]; CheckState[h]; IF showDefault THEN { IF radix = 10 AND LOOPHOLE[default, LONG INTEGER] < 0 THEN {s[0] _ '-; s.length _ 1; default _ -default}; r _ Rope.ToRefText[Convert.RopeFromInt[default, radix]]; s _ RefText.Append[s, r, radix]}; <> [] _ GetEditedString[h, s, IsAtom]; n _ Convert.IntFromRope[RefText.TrustTextAsRope[s], 10]; RefText.ReleaseScratch[s]; RefText.ReleaseScratch[r]; RETURN[n]}; GetLongOctal: PUBLIC SAFE PROC [h: Handle] RETURNS [n: LONG UNSPECIFIED] = TRUSTED { s: REF TEXT _ RefText.ObtainScratch[32]; CheckState[h]; [] _ GetEditedString[h, s, IsAtom]; n _ Convert.IntFromRope[RefText.TrustTextAsRope[s], 8]; RefText.ReleaseScratch[s]; RETURN[n]}; GetNumber: PUBLIC SAFE PROC [h: Handle, default: UNSPECIFIED, radix: CARDINAL, showDefault: BOOLEAN] RETURNS [n: UNSPECIFIED] = TRUSTED { sDefault: LONG UNSPECIFIED _ LONG[default]; result: LONG UNSPECIFIED; result _ GetLongNumber[h, sDefault, radix, showDefault]; n _ Basics.LowHalf[LOOPHOLE[result]]; RETURN[n]}; GetOctal: PUBLIC SAFE PROC [h: Handle] RETURNS [n: UNSPECIFIED] = TRUSTED { result: LONG UNSPECIFIED; result _ GetLongOctal[h]; n _ Basics.LowHalf[LOOPHOLE[result]]; RETURN[n]}; GetString: PUBLIC SAFE PROC [h: Handle, s: REF TEXT, t: PROCEDURE [c: CHARACTER] RETURNS [status: TTY.CharStatus]] = TRUSTED { CheckState[h]; PutChar[h, GetEditedString[h, s, t]]}; LineOverflow: PUBLIC SAFE SIGNAL [s: REF TEXT] RETURNS [ns: REF TEXT] = CODE; Rubout: PUBLIC SAFE SIGNAL = CODE; GetEditedString: PUBLIC SAFE PROC [h: Handle, s: REF TEXT, t: PROCEDURE [c: CHARACTER] RETURNS [status: TTY.CharStatus]] RETURNS [c: CHARACTER] = TRUSTED { WeirdEcho: PROCEDURE [c: CHARACTER] = { SELECT GetEcho[h] FROM none => NULL; plain => PutChar[h, c]; stars => PutChar[h, '*] ENDCASE}; WeirdErase: PROCEDURE [c: CHARACTER] = { SELECT GetEcho[h] FROM none => NULL; plain =>RemoveCharacters[h,SELECT c FROM Ascii.CR, Ascii.BS, Ascii.FF => 0, IN [0C..Ascii.SP) => 2, IN [Ascii.SP..'~] => 1, Ascii.TAB => 1, -- ARGH!!!!!! ENDCASE => 0]; stars => RemoveCharacter[h, 1] ENDCASE}; WeirdPutString: PROCEDURE = INLINE { FOR i: CARDINAL IN [0..s.length) DO WeirdEcho[s[i]] ENDLOOP}; KillString: PROCEDURE = { FOR i: CARDINAL DECREASING IN [0..s.length) DO WeirdErase[s[i]] ENDLOOP; s.length _ 0}; firstChar: BOOLEAN _ TRUE; CheckState[h]; WeirdPutString[]; -- show whatever default string is passed in, if any DO SELECT t[c _ GetChar[h]] FROM stop => RETURN; ignore => LOOP; ok =>SELECT c FROM Ascii.DEL => SIGNAL Rubout; Ascii.ControlA, Ascii.BS => -- backspace IF s.length > 0 THEN WeirdErase[s[s.length _ s.length - 1]]; Ascii.ControlW, Ascii.ControlQ => { -- backword <, the and are to be removed.>> state: {ti, v, li} _ ti; FOR i: CARDINAL DECREASING IN [0..s.length) DO SELECT s[i] FROM IN ['A..'Z], IN ['a..'z], IN ['0..'9] => IF state = ti THEN state _ v; ENDCASE => IF state = v THEN state _ li; IF state = li THEN GO TO Done; WeirdErase[s[i]]; REPEAT Done => s.length _ i + 1; FINISHED => s.length _ 0; ENDLOOP}; Ascii.ControlX => KillString[]; Ascii.ControlR => { -- refresh-- IF GetEcho[h] # none THEN {PutChar[h, Ascii.CR]; WeirdPutString[]}; LOOP}; Ascii.ControlV => { -- dont parse next char IF firstChar THEN KillString[]; WHILE s.length >= s.maxLength DO s _ SIGNAL LineOverflow[s] ENDLOOP; WeirdEcho[s[s.length] _ c _ GetChar[h]]; s.length _ s.length + 1}; ENDCASE => { IF firstChar THEN KillString[]; WHILE s.length >= s.maxLength DO s _ SIGNAL LineOverflow[s] ENDLOOP; WeirdEcho[s[s.length] _ c]; s.length _ s.length + 1}; ENDCASE; firstChar _ FALSE; ENDLOOP}; IsAtom: PROCEDURE [c: CHARACTER] RETURNS [TTY.CharStatus] = { RETURN[IF c = Ascii.SP OR c = Ascii.CR THEN stop ELSE ok]}; IsCR: PROCEDURE [c: CHARACTER] RETURNS [TTY.CharStatus] = { RETURN[IF c = Ascii.CR THEN stop ELSE ok]}; CheckState: ENTRY PROCEDURE [h: Handle] = INLINE { ENABLE UNWIND => NULL; IF h.inputAborted THEN ERROR ABORTED}; InternalCheckState: PROCEDURE [h: Handle] = INLINE { IF h.inputAborted THEN ERROR ABORTED}; <> OutString: PROCEDURE [s: REF TEXT, clientData: Handle] = { P: ENTRY PROCEDURE [h: Handle] = { ENABLE UNWIND => NULL; h.newline _ s[s.length - 1] = Ascii.CR}; IF s = NIL THEN RETURN; IO.UnsafePutBlock[clientData.stream, [LOOPHOLE[@s.text, LONG POINTER] + SIZE[TEXT[0]], 0, s.length]]; P[clientData]}; PutBlank, PutBlanks: PUBLIC SAFE PROC [h: Handle, n: CARDINAL] = TRUSTED { s: REF TEXT _ RefText.ObtainScratch[n*2]; CheckState[h]; FOR i: CARDINAL IN [0 .. s.maxLength) DO s[i] _ ' ; ENDLOOP; s.length _ n; OutString[s, h]; RefText.ReleaseScratch[s]}; PutBlock: PUBLIC SAFE PROC [h: Handle, block: Basics.UnsafeBlock] = TRUSTED { CheckState[h]; IO.UnsafePutBlock[h.stream, block]}; <> <> PutDecimal: PUBLIC SAFE PROC [h: Handle, n: INTEGER] = TRUSTED { CheckState[h]; IO.PutF[h.stream, "%g", IO.int[n]]}; PutLine: PUBLIC SAFE PROC [h: Handle, s: REF TEXT] = TRUSTED { CheckState[h]; PutString[h, s]; PutChar[h, Ascii.CR]}; PutLongDecimal: PUBLIC SAFE PROC [h: Handle, n: LONG INTEGER] = TRUSTED { CheckState[h]; IO.PutF1[h.stream, "%g", IO.int[n]]}; <> <> <> <> <> <> <<>> <> <> <> <> <> <> <> <> <> <> BYTE: PRIVATE TYPE = [0..255]; Q: TYPE = REF QHead; QHead: TYPE = RECORD [front, rear: REF QElement]; NullQHead: QHead = [NIL, NIL]; QElement: PRIVATE TYPE = RECORD [ flink, rlink: REF QElement _ NIL, front, rear: CARDINAL _ 0, data: PACKED SEQUENCE maxLen: CARDINAL OF BYTE]; pQE: PRIVATE TYPE = REF QElement; defaultElementSize: PRIVATE CARDINAL = 20; EmptyQ: SAFE SIGNAL [q: Q] = CODE; Count: PROCEDURE [q: Q] RETURNS [elements: CARDINAL _ 0] = BEGIN IF q = NIL THEN RETURN; FOR qp: pQE _ q.front, qp.flink UNTIL qp = NIL DO elements _ elements + (IF qp.front > qp.rear THEN qp.maxLen - qp.front + qp.rear + 1 ELSE qp.rear - qp.front + 1); ENDLOOP; RETURN END; IsEmpty: PROCEDURE [q: Q] RETURNS [BOOLEAN] = { RETURN[q = NIL OR q.front = NIL]}; Empty: PROCEDURE [q: Q] = BEGIN UNTIL IsEmpty[q] DO qf: pQE _ q.front; nqf: pQE = qf.flink; <> IF nqf = NIL THEN q.rear _ NIL ELSE nqf.rlink _ NIL; q.front _ nqf; ENDLOOP; RETURN END; Init: PROCEDURE RETURNS [Q] = {RETURN[NIL]}; GetF: PROCEDURE [q: Q] RETURNS [i: UNSPECIFIED [0..255]] = BEGIN qf: pQE; qff: CARDINAL; IF q = NIL OR q.front = NIL THEN {SIGNAL EmptyQ[q]; RETURN[0]}; qf _ q.front; i _ qf[qff _ qf.front]; IF qf.front = qf.rear THEN BEGIN -- element empty nf: pQE = qf.flink; IF nf = NIL THEN q.rear _ NIL ELSE nf.rlink _ NIL; <> q.front _ nf; END ELSE BEGIN qff _ IF qff = qf.maxLen - 1 THEN 0 ELSE qff + 1; qf.front _ qff; END; RETURN END; GetR: PROCEDURE [q: Q] RETURNS [i: UNSPECIFIED [0..255]] = BEGIN qr: pQE; qrr: CARDINAL; IF q = NIL OR q.rear = NIL THEN {SIGNAL EmptyQ[q]; RETURN[0]}; qr _ q.rear; i _ qr[qrr _ qr.rear]; IF qr.rear = qr.front THEN BEGIN -- element empty nr: pQE = qr.rlink; IF nr = NIL THEN q.front _ NIL ELSE nr.flink _ NIL; <> q.rear _ nr; END ELSE BEGIN qrr _ IF qrr = 0 THEN qr.maxLen - 1 ELSE qrr - 1; qr.rear _ qrr; END; RETURN END; PutR: PROCEDURE [q: Q, i: UNSPECIFIED [0..255]] = BEGIN qr: pQE; qrr: CARDINAL; IF q = NIL THEN RETURN; qr _ q.rear; IF qr = NIL OR FullElement[qr] THEN BEGIN nr: pQE _ NEW[QElement[defaultElementSize]]; nr.rlink _ qr; IF qr = NIL THEN q.front _ nr ELSE qr.flink _ nr; q.rear _ qr _ nr; qrr _ 0; END ELSE qrr _ IF qr.rear = qr.maxLen - 1 THEN 0 ELSE qr.rear + 1; qr[qrr] _ i; qr.rear _ qrr; RETURN END; BlockToQR: PROCEDURE [block: Basics.UnsafeBlock, q: Q] = BEGIN FOR i: INT IN [block.startIndex..block.startIndex+block.count) DO base: LONG POINTER TO Basics.RawBytes _ LOOPHOLE[block.base]; PutR[q, base[i]] ENDLOOP; END; FullElement: PROCEDURE [qe: pQE] RETURNS [BOOLEAN] = {OPEN qe; RETURN[(IF rear < front THEN maxLen + rear - front ELSE rear - front) = maxLen - 1]}; END....