<> <> <> <> <> <> <> <> DIRECTORY Ascii, Atom, EditedStream, IO, IOUtils, Rope, RefText, RuntimeError USING [BoundsFault]; IOEditedStreamImpl: CEDAR PROGRAM IMPORTS IO, IOUtils, RefText, Rope, RuntimeError EXPORTS EditedStream SHARES IO --for representation of StreamProcs = BEGIN STREAM: TYPE = IO.STREAM; ROPE: TYPE = Rope.ROPE; StreamProcs: TYPE = IO.StreamProcs; TypeOfSetEcho: TYPE = PROC [self: STREAM, echoTo: STREAM]; TypeOfGetEcho: TYPE = PROC [self: STREAM] RETURNS [oldEcho: STREAM]; DeliverWhenProc: TYPE = EditedStream.DeliverWhenProc; TypeOfGetDeliverWhen: TYPE = PROC [self: STREAM] RETURNS [proc: DeliverWhenProc, context: REF ANY]; TypeOfSetDeliverWhen: TYPE = PROC [self: STREAM, proc: DeliverWhenProc, context: REF ANY _ NIL]; TypeOfAppendBufferChars: TYPE = PROC [stream: STREAM, chars: ROPE]; TypeOfUnAppendBufferChars: TYPE = PROC [stream: STREAM, nChars: NAT]; TypeOfSetMode: TYPE = PROC [stream: STREAM, stuff: ROPE, pendingDelete: BOOL, echoAsterisks: BOOL]; InlineLookupProc: PROC [self: STREAM, operation: ATOM] RETURNS [proc: REF ANY] = INLINE { FOR l: Atom.PropList _ self.streamProcs.propList, l.rest UNTIL l = NIL DO IF l.first.key = operation THEN RETURN[l.first.val]; ENDLOOP; }; GetDeliverWhen: PUBLIC PROC [self: STREAM] RETURNS [proc: DeliverWhenProc, context: REF ANY] = { p: REF ANY = InlineLookupProc[self, $GetDeliverWhen]; IF p # NIL THEN { [proc, context] _ (NARROW[p, REF TypeOfGetDeliverWhen])^ [self]; RETURN } ELSE ERROR IO.Error[$NotImplementedForThisStream, self]; }; SetDeliverWhen: PUBLIC PROC [self: STREAM, proc: DeliverWhenProc, context: REF ANY] = { p: REF ANY = InlineLookupProc[self, $SetDeliverWhen]; IF p # NIL THEN { (NARROW[p, REF TypeOfSetDeliverWhen])^ [self, proc, context]; RETURN } ELSE ERROR IO.Error[$NotImplementedForThisStream, self]; }; AppendBufferChars: PUBLIC PROC [stream: STREAM, chars: ROPE] = { p: REF ANY = InlineLookupProc[stream, $AppendBufferChars]; IF p # NIL THEN { (NARROW[p, REF TypeOfAppendBufferChars])^ [stream, chars]; RETURN } ELSE ERROR IO.Error[$NotImplementedForThisStream, stream]; }; UnAppendBufferChars: PUBLIC PROC [stream: STREAM, nChars: NAT] = { p: REF ANY = InlineLookupProc[stream, $UnAppendBufferChars]; IF p # NIL THEN { (NARROW[p, REF TypeOfUnAppendBufferChars])^ [stream, nChars]; RETURN } ELSE ERROR IO.Error[$NotImplementedForThisStream, stream]; }; SetMode: PUBLIC PROC [stream: STREAM, stuff: ROPE, pendingDelete: BOOL, echoAsterisks: BOOL] = { p: REF ANY = InlineLookupProc[stream, $SetMode]; IF p # NIL THEN { (NARROW[p, REF TypeOfSetMode])^ [stream, stuff, pendingDelete, echoAsterisks]; RETURN } ELSE ERROR IO.Error[$NotImplementedForThisStream, stream]; }; <> Rubout: PUBLIC ERROR [stream: STREAM] = CODE; <> EditedStreamData: TYPE = REF EditedStreamRecord; EditedStreamRecord: TYPE = RECORD[ ready: REF TEXT, readyPos: INT _ 0, -- ready[readyPos .. ready.length) are the already-activated characters buffer: REF TEXT, echoStream: STREAM _ NIL, deliverWhen: DeliverWhenProc, context: REF ANY, echoAsterisks: BOOL _ FALSE, pendingDelete: BOOL _ FALSE ]; EditedStreamProcs: REF StreamProcs; IsACR: PUBLIC DeliverWhenProc = { RETURN [appendChar: TRUE, activate: char = IO.CR] }; Create: PUBLIC PROC [in: STREAM, echoTo: STREAM, deliverWhen: DeliverWhenProc, context: REF ANY] RETURNS [STREAM] = { h: STREAM _ IO.CreateStream[ streamProcs: EditedStreamProcs, streamData: NEW[EditedStreamRecord _ [ buffer: NEW[TEXT[256]], ready: NEW[TEXT[256]], deliverWhen: deliverWhen, context: context]], backingStream: in ]; SetEcho[in, NIL]; SetEcho[h, echoTo]; RETURN [h] }; EditedStreamAppendBufferChars: PROC [stream: STREAM, chars: ROPE] = { data: EditedStreamData = NARROW[stream.streamData]; Append1: PROC [c: CHAR] RETURNS [quit: BOOL] = { AppendBufferChar[data, c]; RETURN [quit: FALSE] }; [] _ chars.Map[action: Append1]; }; AppendBufferChar: PROC [data: EditedStreamData, char: CHAR] = INLINE { data.buffer _ RefText.InlineAppendChar[data.buffer, char]; IF data.echoStream # NIL THEN { IF data.echoAsterisks AND char > IO.SP THEN data.echoStream.PutChar['*] ELSE data.echoStream.PutChar[char]; }; }; EditedStreamUnAppendBufferChars: PUBLIC PROC [stream: STREAM, nChars: NAT] = { data: EditedStreamData = NARROW[stream.streamData]; FOR i: NAT IN [0 .. MIN[nChars, data.buffer.length]) DO UnAppendBufferChar[data] ENDLOOP; }; UnAppendBufferChar: PROC [data: EditedStreamData] = INLINE { IF data.echoStream # NIL THEN { char: CHAR = data.buffer[data.buffer.length - 1]; IF data.echoAsterisks AND char > IO.SP THEN data.echoStream.EraseChar['*] ELSE data.echoStream.EraseChar[char] }; data.buffer.length _ data.buffer.length - 1; }; EditedStreamSetMode: PROC [stream: STREAM, stuff: ROPE, pendingDelete: BOOL, echoAsterisks: BOOL] = { data: EditedStreamData = NARROW[stream.streamData]; data.buffer.length _ 0; data.readyPos _ data.ready.length; data.pendingDelete _ pendingDelete; data.echoAsterisks _ echoAsterisks; AppendBufferChars[stream, stuff]; }; EditedStreamGetDeliverWhen: PROC [self: STREAM] RETURNS [proc: DeliverWhenProc, context: REF ANY] = { data: EditedStreamData = NARROW[self.streamData]; RETURN [data.deliverWhen, data.context]; }; EditedStreamSetDeliverWhen: PROC [self: STREAM, proc: DeliverWhenProc, context: REF ANY] = { data: EditedStreamData = NARROW[self.streamData]; data.deliverWhen _ proc; data.context _ context; }; EditedStreamGetChar: PROC [self: STREAM] RETURNS [char: CHAR] = { data: EditedStreamData = NARROW[self.streamData]; IsEditCommand: PROC [char: CHAR] RETURNS [BOOL] = { RETURN [SELECT char FROM Ascii.DEL, Ascii.ControlA, Ascii.BS, Ascii.ControlW, Ascii.ControlQ => TRUE, ENDCASE => FALSE]; }; BackChar: PROC = { <> IF data.buffer.length > 0 THEN { UnAppendBufferChar[data]; } }; BackWord: PROC = { <> alphaSeen: BOOL _ FALSE; UNTIL data.buffer.length = 0 DO SELECT data.buffer[data.buffer.length - 1] FROM IN ['A..'Z], IN ['a..'z], IN ['0..'9] => alphaSeen _ TRUE; ENDCASE => IF alphaSeen THEN EXIT; UnAppendBufferChar[data]; ENDLOOP; }; BackLine: PROC = { <> UNTIL data.buffer.length = 0 DO IF data.buffer[data.buffer.length - 1] = IO.CR THEN EXIT; UnAppendBufferChar[data]; ENDLOOP; }; DO IF data.readyPos < data.ready.length THEN { char _ data.ready[data.readyPos]; data.readyPos _ data.readyPos + 1; RETURN [char]; }; { appendChar, activate: BOOL; char _ self.backingStream.GetChar[ ! IO.EndOfStream => IF data.buffer.length = 0 THEN REJECT ELSE GOTO activateBuffer]; [appendChar: appendChar, activate: activate] _ data.deliverWhen[char, data.buffer, self, data.context]; IF data.pendingDelete THEN { data.pendingDelete _ FALSE; IF NOT activate AND appendChar AND NOT IsEditCommand[char] THEN UnAppendBufferChars[self, data.buffer.length]; }; IF appendChar THEN { SELECT char FROM Ascii.DEL => { ENABLE UNWIND => data.buffer.length _ 0; ERROR Rubout[self]; }; Ascii.ControlA, Ascii.BS => BackChar[]; Ascii.ControlW => BackWord[]; Ascii.ControlQ => BackLine[]; Ascii.ESC => IF data.buffer.length = 0 THEN { FOR i: NAT IN [0..data.ready.length-1) DO AppendBufferChar[data, data.ready[i]]; ENDLOOP }; ENDCASE => AppendBufferChar[data, char]; }; IF activate THEN GOTO activateBuffer; EXITS activateBuffer => { data.ready.length _ 0; data.ready _ RefText.Append[data.ready, data.buffer]; data.readyPos _ 0; data.buffer.length _ 0; data.echoAsterisks _ FALSE; } } ENDLOOP; }; EditedStreamEndOf: PROC [self: STREAM] RETURNS [BOOL] = { data: EditedStreamData = NARROW[self.streamData]; RETURN[data.readyPos = data.ready.length AND self.backingStream.EndOf[]]; }; EditedStreamCharsAvail: PROC [self: STREAM, wait: BOOL] RETURNS [INT] = { data: EditedStreamData = NARROW[self.streamData]; IF data.readyPos < data.ready.length THEN RETURN [data.ready.length-data.readyPos]; RETURN[self.backingStream.CharsAvail[wait]]; }; EditedStreamBackup: PROC [self: STREAM, char: CHAR] = { <> data: EditedStreamData = NARROW[self.streamData]; IF data.readyPos = 0 OR data.ready[data.readyPos - 1] # char THEN IO.Error[$IllegalBackup, self]; data.readyPos _ data.readyPos - 1; }; EditedStreamSetEcho: PROC [self: STREAM, echoTo: STREAM] = { data: EditedStreamData = NARROW[self.streamData]; data.echoStream _ echoTo; }; EditedStreamGetEcho: PROC [self: STREAM] RETURNS [STREAM] = { data: EditedStreamData = NARROW[self.streamData]; RETURN [data.echoStream]; }; EditedStreamReset: PROC [self: STREAM] = { data: EditedStreamData = NARROW[self.streamData]; data.buffer.length _ 0; data.ready.length _ 0; data.readyPos _ 0; self.backingStream.Reset[]; IF data.echoStream # NIL THEN data.echoStream.Reset[]; }; <> <<>> SetEchoData: TYPE = REF SetEchoRecord; SetEchoRecord: TYPE = RECORD [echoStream: STREAM, buffer: REF TEXT]; setEchoProcs: REF StreamProcs; SetEcho: PUBLIC PROC [self: STREAM, echoTo: STREAM] = { origSelf: STREAM _ self; DO proc: REF ANY _ InlineLookupProc[self, $SetEcho]; IF proc # NIL THEN { (NARROW[proc, REF TypeOfSetEcho])^ [self, echoTo]; RETURN; } ELSE IF self.backingStream # NIL THEN self _ self.backingStream ELSE EXIT; ENDLOOP; <> IF echoTo = NIL THEN RETURN; IOUtils.AmbushStream[ self: origSelf, streamProcs: setEchoProcs, streamData: NEW[SetEchoRecord _ [echoStream: NIL, buffer: RefText.New[8]]]]; DefaultSetEchoSetEcho[origSelf, echoTo]; }; GetEcho: PUBLIC PROC [self: STREAM] RETURNS [oldEcho: STREAM] = { origSelf: STREAM _ self; DO proc: REF ANY _ InlineLookupProc[self, $GetEcho]; IF proc # NIL THEN RETURN[(NARROW[proc, REF TypeOfGetEcho])^ [self] ] ELSE IF self.backingStream # NIL THEN self _ self.backingStream ELSE EXIT; ENDLOOP; RETURN [NIL]; }; DefaultSetEchoSetEcho: PROC [self: STREAM, echoTo: STREAM] = { data: SetEchoData = NARROW[self.streamData]; data.echoStream _ echoTo; IF echoTo = NIL AND data.buffer.length = 0 THEN IOUtils.UnAmbushStream[self]; }; DefaultSetEchoGetEcho: PROC [self: STREAM] RETURNS [oldEcho: STREAM] = { data: SetEchoData = NARROW[self.streamData]; RETURN[data.echoStream]; }; DefaultSetEchoBackup: PROC [self: STREAM, char: CHAR] = { <> data: SetEchoData = NARROW[self.streamData]; data.buffer _ RefText.InlineAppendChar[data.buffer, char ! RuntimeError.BoundsFault => ERROR IO.Error[$BufferOverflow, self]]; }; DefaultSetEchoGetChar: PROC [self: STREAM] RETURNS [char: CHAR] = { data: SetEchoData = NARROW[self.streamData]; IF data.buffer.length > 0 THEN { data.buffer.length _ data.buffer.length - 1; char _ data.buffer[data.buffer.length]; RETURN[char]; }; char _ self.backingStream.GetChar[]; IF data.echoStream # NIL THEN data.echoStream.PutChar[char]; }; DefaultSetEchoGetBlock: PROC [self: STREAM, block: REF TEXT, startIndex: NAT, count: NAT] RETURNS [nBytesRead: NAT] = { data: SetEchoData = NARROW[self.streamData]; nBytesRead _ 0; WHILE data.buffer.length > 0 DO IF count = 0 OR startIndex >= block.maxLength THEN RETURN [nBytesRead]; data.buffer.length _ data.buffer.length - 1; block[startIndex] _ data.buffer[data.buffer.length]; startIndex _ startIndex + 1; count _ count - 1; nBytesRead _ nBytesRead + 1; ENDLOOP; nBytesRead _ nBytesRead + self.backingStream.GetBlock[block, startIndex, count]; IF data.echoStream # NIL THEN data.echoStream.PutBlock[block, startIndex, block.length-startIndex]; RETURN [nBytesRead]; }; DefaultSetEchoUnsafeGetBlock: UNSAFE PROC [self: STREAM, block: IO.UnsafeBlock] RETURNS [nBytesRead: INT] = UNCHECKED { data: SetEchoData = NARROW[self.streamData]; nBytesRead _ 0; IF block.startIndex < 0 OR block.count < 0 THEN ERROR RuntimeError.BoundsFault; WHILE data.buffer.length > 0 DO IF block.count = 0 THEN RETURN [nBytesRead]; data.buffer.length _ data.buffer.length - 1; block.base^[block.startIndex] _ data.buffer[data.buffer.length]-0C; block.startIndex _ block.startIndex + 1; block.count _ block.count - 1; nBytesRead _ nBytesRead + 1; ENDLOOP; block.count _ self.backingStream.UnsafeGetBlock[block]; IF data.echoStream # NIL THEN data.echoStream.UnsafePutBlock[block]; RETURN [nBytesRead + block.count]; }; DefaultSetEchoEndOf: PROC [self: STREAM] RETURNS [BOOL] = { data: SetEchoData = NARROW[self.streamData]; RETURN[data.buffer.length = 0 AND self.backingStream.EndOf[]]; }; DefaultSetEchoCharsAvail: PROC [self: STREAM, wait: BOOL] RETURNS [INT] = { data: SetEchoData = NARROW[self.streamData]; IF data.buffer.length > 0 THEN RETURN [data.buffer.length]; RETURN[self.backingStream.CharsAvail[wait]]; }; DefaultSetEchoReset: PROC [self: STREAM] = { data: SetEchoData = NARROW[self.streamData]; data.buffer.length _ 0; self.backingStream.Reset[]; }; <> AddStreamProcs: PUBLIC PROC [ to: REF IO.StreamProcs, setEcho: PROC [self: STREAM, echoTo: STREAM], getEcho: PROC [self: STREAM] RETURNS [oldEcho: STREAM] ] RETURNS [REF IO.StreamProcs] = { IF setEcho # NIL THEN IOUtils.StoreProc[to, $SetEcho, NEW[TypeOfSetEcho _ setEcho]]; IF getEcho # NIL THEN IOUtils.StoreProc[to, $GetEcho, NEW[TypeOfGetEcho _ getEcho]]; RETURN [to]; }; <> EditedStreamProcs _ AddStreamProcs[to: IO.CreateStreamProcs[ variety: $input, class: $Edited, getChar: EditedStreamGetChar, endOf: EditedStreamEndOf, charsAvail: EditedStreamCharsAvail, backup: EditedStreamBackup, reset: EditedStreamReset], setEcho: EditedStreamSetEcho, getEcho: EditedStreamGetEcho]; IOUtils.StoreProc[EditedStreamProcs, $GetDeliverWhen, NEW[TypeOfGetDeliverWhen _ EditedStreamGetDeliverWhen]]; IOUtils.StoreProc[EditedStreamProcs, $SetDeliverWhen, NEW[TypeOfSetDeliverWhen _ EditedStreamSetDeliverWhen]]; IOUtils.StoreProc[EditedStreamProcs, $AppendBufferChars, NEW[TypeOfAppendBufferChars _ EditedStreamAppendBufferChars]]; IOUtils.StoreProc[EditedStreamProcs, $UnAppendBufferChars, NEW[TypeOfUnAppendBufferChars _ EditedStreamUnAppendBufferChars]]; IOUtils.StoreProc[EditedStreamProcs, $SetMode, NEW[TypeOfSetMode _ EditedStreamSetMode]]; setEchoProcs _ AddStreamProcs[to: IO.CreateStreamProcs[ variety: $inputOutput, class: $SetEcho, getChar: DefaultSetEchoGetChar, getBlock: DefaultSetEchoGetBlock, unsafeGetBlock: DefaultSetEchoUnsafeGetBlock, endOf: DefaultSetEchoEndOf, charsAvail: DefaultSetEchoCharsAvail, backup: DefaultSetEchoBackup, reset: DefaultSetEchoReset], setEcho: DefaultSetEchoSetEcho, getEcho: DefaultSetEchoGetEcho]; END.