<> <> DIRECTORY Ascii USING [BS, ControlA, ControlW, ControlQ, ControlR, CR, ESC, DEL, NUL], IO USING [ChangeDeliverWhen, CreateProcsStream, CreateRefStreamProcs, DeliverWhenProc, EndOf, EndOfStream, EraseChar, Error, Flush, GetChar, GetBufferContents, PeekChar, PutChar, PutBlock, Reset, ROPE, SetEcho, Signal, STREAM, StreamProcs, UncheckedImplements, Zone], IOExtras USING [], RefText USING [ObtainScratch, ReleaseScratch], Rope USING [Fetch, Concat, Length, FromRefText] ; SomeStreamsImpl: CEDAR MONITOR LOCKS bufferedStreamData USING bufferedStreamData: REF BufferedOutputStreamRecord IMPORTS IO, RefText, Rope EXPORTS IO, IOExtras SHARES IO = BEGIN OPEN Ascii, IO; BufferOverFlow: PUBLIC SIGNAL[text: REF TEXT] RETURNS[REF TEXT] = CODE; -- e.g. line buffering and type more characters before CR then there is room in buffer. value returned can be a bigger text, with characters copied over, or else can be same size, with length having been reset, etc. <> EnlargeBuffer: PROC [text: REF TEXT] RETURNS[newBuffer: REF TEXT] = { newBuffer _ NEW[TEXT[2 * text.maxLength]]; FOR i: NAT IN [0..text.maxLength) DO newBuffer[i] _ text[i]; ENDLOOP; newBuffer.length _ text.maxLength; RETURN[newBuffer]; }; <> <> noWhereStream: PUBLIC IO.STREAM _ CreateProcsStream[ CreateRefStreamProcs[putChar: NoWherePutChar, name: "NoWhere"], NIL]; NoWherePutChar: PROCEDURE[self: STREAM, char: CHARACTER] = {}; <> <> noInputStream: PUBLIC STREAM _ CreateProcsStream[streamProcs: CreateRefStreamProcs[ getChar: NoInputGetChar, endOf: NoInputEndOf, name: "NoInput" ], streamData: NIL]; NoInputGetChar: PROC [self: IO.STREAM] RETURNS[CHAR] = {ERROR IO.Error[StreamClosed, self]; }; NoInputEndOf: PROC [self: IO.STREAM] RETURNS[BOOLEAN] = {RETURN[TRUE]}; <> <> RIScount: INT _ 0; InputRopeStreamData: TYPE = REF InputRopeStreamRecord; InputRopeStreamRecord: TYPE = RECORD[ rope: ROPE _ NIL, pos: INT _ 0 ]; InputRopeStreamProcs: REF StreamProcs _ NIL; RIS, CreateInputStreamFromRope: PUBLIC PROCEDURE [rope: ROPE, oldStream: STREAM _ NIL] RETURNS [h: STREAM] = { IF oldStream # NIL THEN BEGIN data: InputRopeStreamData = NARROW[oldStream.streamData]; data.rope _ rope; data.pos _ 0; RETURN[oldStream]; END; IF InputRopeStreamProcs = NIL THEN InputRopeStreamProcs _ CreateRefStreamProcs[ getChar: InputRopeStreamGetChar, endOf: InputRopeStreamEndOf, reset: InputRopeStreamReset, getIndex: InputRopeStreamGetIndex, setIndex: InputRopeStreamSetIndex, getLength: InputRopeStreamGetLength, backup: InputRopeStreamBackup, -- because most applications will want to do formatted input, e.g. getint, etc. name: "Input From Rope" ]; h _ CreateProcsStream[InputRopeStreamProcs, Zone.NEW[InputRopeStreamRecord _ [rope: rope, pos: 0]]]; RIScount _ RIScount + 1; }; -- of CreateStreamFromRope InputRopeStreamGetChar: PROC[self: STREAM] RETURNS [char: CHARACTER] = { data: InputRopeStreamData = NARROW[self.streamData]; IF data.pos >= Rope.Length[data.rope] THEN ERROR EndOfStream[self]; char _ Rope.Fetch[data.rope, data.pos]; data.pos _ data.pos + 1; }; -- of InputRopeStreamGetChar InputRopeStreamEndOf: PROC[self: STREAM] RETURNS [BOOLEAN] = { data: InputRopeStreamData = NARROW[self.streamData]; RETURN[data.pos >= Rope.Length[data.rope]]; }; -- of InputRopeStreamEndOf InputRopeStreamReset: PROC[self: STREAM] = { data: InputRopeStreamData = NARROW[self.streamData]; data.pos _ 0; }; -- of InputRopeStreamReset InputRopeStreamGetIndex: PROC[self: STREAM] RETURNS[INT] = { data: InputRopeStreamData = NARROW[self.streamData]; RETURN[data.pos]; }; -- of InputRopeStreamGetIndex InputRopeStreamSetIndex: PROC[self: STREAM, index: INT] = { data: InputRopeStreamData = NARROW[self.streamData]; data.pos _ index; }; -- of InputRopeStreamSetIndex InputRopeStreamGetLength: PROC[self: STREAM] RETURNS[length: INT] = { data: InputRopeStreamData = NARROW[self.streamData]; RETURN[Rope.Length[data.rope]]; }; -- of InputRopeStreamGetLength InputRopeStreamBackup: PROC[self: STREAM, char: CHARACTER] = { data: InputRopeStreamData = NARROW[self.streamData]; IF data.pos = 0 OR Rope.Fetch[data.rope, data.pos - 1] # char THEN Error[IllegalBackup, self]; data.pos _ data.pos - 1; }; -- of InputRopeStreamBackup <> <> ROSCount: INT _ 0; ROSNumChars: INT _ 0; OutputRopeStreamData: TYPE = REF OutputRopeStreamRecord; -- describes the streamData field for streams that read from ropes. OutputRopeStreamRecord: TYPE = RECORD[rope: ROPE _ NIL, text: REF TEXT]; OutputRopeStreamProcs: REF StreamProcs _ NIL; ROS, CreateOutputStreamToRope: PUBLIC PROC RETURNS [h: STREAM] = { IF OutputRopeStreamProcs = NIL THEN OutputRopeStreamProcs _ CreateRefStreamProcs[ putChar: OutputRopeStreamPutChar, reset: OutputRopeStreamReset, flush: OutputRopeStreamFlush, close: OutputRopeStreamClose, getLength: OutputRopeStreamLength, name: "Output To Rope" ]; ROSCount _ ROSCount + 1; RETURN[CreateProcsStream[OutputRopeStreamProcs, Zone.NEW[OutputRopeStreamRecord _ [rope: NIL, text: RefText.ObtainScratch[100]]]]]; }; -- of CreateStreamFromRope OutputRopeStreamPutChar: PROC[self: STREAM, char: CHARACTER] = { data: OutputRopeStreamData = NARROW[self.streamData]; text: REF TEXT _ data.text; IF text.length = text.maxLength THEN {data.rope _ Rope.Concat[data.rope, Rope.FromRefText[text]]; text.length _ 0}; text[text.length] _ char; text.length _ text.length + 1; }; -- of OutputRopeStreamPutChar OutputRopeStreamReset: PROC[self: STREAM] = { data: OutputRopeStreamData = NARROW[self.streamData]; data.rope _ NIL; data.text.length _ 0; }; -- of OutputRopeStreamReset OutputRopeStreamFlush: PROC[self: STREAM] = { data: OutputRopeStreamData = NARROW[self.streamData]; text: REF TEXT _ data.text; IF data.text # NIL AND text.length # 0 THEN {data.rope _ Rope.Concat[data.rope, Rope.FromRefText[text]]; text.length _ 0}; }; -- of OutputRopeStreamFlush OutputRopeStreamLength: PROC[self: STREAM] RETURNS [length: INT] = { data: OutputRopeStreamData = NARROW[self.streamData]; IF data.text # NIL THEN length _ data.text.length ELSE length _ 0; length _ length + Rope.Length[data.rope]; }; -- of OutputRopeStreamReset OutputRopeStreamClose: PROC[self: STREAM, abort: BOOLEAN _ FALSE] = { data: OutputRopeStreamData = NARROW[self.streamData]; RefText.ReleaseScratch[data.text]; data.text _ NIL; }; -- of OutputRopeStreamReset GetOutputStreamRope: PUBLIC PROC [self: STREAM] RETURNS [ROPE] = { data: OutputRopeStreamData = NARROW[self.streamData]; OutputRopeStreamFlush[self]; ROSNumChars _ ROSNumChars + Rope.Length[data.rope]; RETURN[data.rope]; }; -- of GetOutputStreamRope <> <> TextStreamData: TYPE = REF TextStreamRecord; TextStreamRecord: TYPE = RECORD[text: REF READONLY TEXT _ NIL, i: CARDINAL _ 0]; TextStreamProcs: REF StreamProcs _ NIL; CreateInputStreamFromText: PUBLIC PROCEDURE [text: REF READONLY TEXT _ NIL, oldStream: STREAM _ NIL] RETURNS [STREAM] = { IF TextStreamProcs = NIL THEN TextStreamProcs _ CreateRefStreamProcs[ getChar: TextStreamGetChar, reset: TextStreamReset, endOf: TextStreamEndOf, getIndex: TextStreamGetIndex, setIndex: TextStreamSetIndex, getLength: TextStreamGetLength, name: "Input From Text" ]; IF oldStream # NIL THEN BEGIN data: TextStreamData = NARROW[oldStream.streamData]; data.text _ text; data.i _ 0; RETURN[oldStream]; END; oldStream _ CreateProcsStream[TextStreamProcs, Zone.NEW[TextStreamRecord _ [text: text, i: 0]]]; RETURN[oldStream]; }; -- of CreateInputStreamFromText TextStreamGetChar: PROC[self: STREAM] RETURNS [char: CHARACTER] = { data: TextStreamData = NARROW[self.streamData]; IF data.i >= data.text.length THEN ERROR EndOfStream[self]; char _ data.text[data.i]; data.i _ data.i + 1; }; -- of TextStreamGetChar TextStreamReset: PROC[self: STREAM] = { data: TextStreamData = NARROW[self.streamData]; data.i _ 0; }; -- of TextStreamReset TextStreamEndOf: PROC[self: STREAM] RETURNS [BOOLEAN] = { data: TextStreamData = NARROW[self.streamData]; RETURN[data.i >= data.text.length]; }; -- of TextStreamEndOf TextStreamGetIndex: PROC[self: STREAM] RETURNS[INT] = { data: TextStreamData = NARROW[self.streamData]; RETURN[data.i]; }; -- of TextStreamGetIndex TextStreamSetIndex: PROC[self: STREAM, index: INT] = { data: TextStreamData = NARROW[self.streamData]; data.i _ index; }; -- of TextStreamSetIndex TextStreamGetLength: PROC[self: STREAM] RETURNS[length: INT] = { data: TextStreamData = NARROW[self.streamData]; RETURN[data.text.length]; }; -- of TextStreamGetLength <> <> StreamToTextProcs: REF StreamProcs _ NIL; CreateOutputStreamToText: PUBLIC PROCEDURE [text: REF TEXT, oldStream: STREAM _ NIL] RETURNS [STREAM] = { IF StreamToTextProcs = NIL THEN StreamToTextProcs _ CreateRefStreamProcs[ putChar: StreamToTextPutChar, reset: StreamToTextReset, getIndex: StreamToTextGetIndex, setIndex: StreamToTextSetIndex, eraseChar: StreamToTextEraseChar, name: "Output To Text" ]; IF oldStream = NIL THEN oldStream _ CreateProcsStream[StreamToTextProcs, text] ELSE {text: REF TEXT _ NARROW[oldStream.streamData]; -- just to make sure is of right type. oldStream.streamData _ text}; Reset[oldStream]; RETURN[oldStream]; }; -- of CreateOutputStreamToText StreamToTextPutChar: PROC[self: STREAM, char: CHARACTER] = { text: REF TEXT _ NARROW[self.streamData, REF TEXT]; WHILE text.length >= text.maxLength DO text _ -- SIGNAL BufferOverFlow -- EnlargeBuffer[text]; self.streamData _ text; ENDLOOP; text[text.length] _ char; text.length _ text.length + 1; }; -- of StreamToTextPutChar StreamToTextEraseChar: PROC[self: STREAM, char: CHARACTER] = { text: REF TEXT _ NARROW[self.streamData, REF TEXT]; IF text.length > 0 THEN text.length _ text.length - 1; }; -- of StreamToTextEraseChar StreamToTextReset: PROC[self: STREAM] = { text: REF TEXT _ NARROW[self.streamData, REF TEXT]; text.length _ 0; }; -- of StreamToTextReset StreamToTextGetIndex: PROC[self: STREAM] RETURNS[INT] = { text: REF TEXT _ NARROW[self.streamData, REF TEXT]; RETURN[text.length]; }; -- of StreamToTextReset StreamToTextSetIndex: PROC[self: STREAM, index: INT] = { text: REF TEXT _ NARROW[self.streamData, REF TEXT]; text.length _ index; }; -- of StreamToTextSetIndex <> <> EditedStreamData: TYPE = REF EditedStreamRecord; EditedStreamRecord: TYPE = RECORD[ buffer: REF TEXT, ready: REF TEXT, readyPos: INT _ 0, <> lastReadyLength: NAT, -- for ESC deliverWhen: DeliverWhenProc, echoTo: STREAM _ NIL ]; EditedStreamProcs: REF StreamProcs _ NIL; IsACR: PUBLIC DeliverWhenProc = {RETURN[char = CR]}; CreateEditedStream: PUBLIC PROCEDURE [in: STREAM, echoTo: STREAM, deliverWhen: DeliverWhenProc _ IsACR] RETURNS [h: STREAM] = { IF EditedStreamProcs = NIL THEN EditedStreamProcs _ CreateRefStreamProcs[ getChar: EditedStreamGetChar, reset: EditedStreamReset, endOf: EditedStreamEndOf, charsAvail: EditedStreamAvail, getIndex: EditedStreamGetIndex, setIndex: EditedStreamSetIndex, setEcho: EditedStreamSetEcho, backup: EditedStreamBackup, eraseChar: EditedStreamEraseChar, name: "Edited" ]; h _ CreateProcsStream[ streamProcs: EditedStreamProcs, streamData: Zone.NEW[EditedStreamRecord _ [ buffer: NEW[TEXT[256]], ready: NEW[TEXT[256]], lastReadyLength: 0, deliverWhen: deliverWhen]], backingStream: in ]; UncheckedImplements[ self: h, operation: ChangeDeliverWhen, via: ChangeDeliverWhen1, procRef: Zone.NEW[PROC [self: STREAM, proc: DeliverWhenProc] RETURNS [oldProc: DeliverWhenProc] _ ChangeDeliverWhen1], key: $ChangeDeliverWhen]; UncheckedImplements[ self: h, operation: GetBufferContents, via: GetBufferContents1, procRef: Zone.NEW[PROC [self: STREAM] RETURNS [buffer: ROPE] _ GetBufferContents1], key: $GetBufferContents]; [] _ SetEcho[in, NIL ! Error => CONTINUE]; [] _ SetEcho[h, echoTo]; -- echoing done at this level since dont want control-a's w's etc to be echoed, and also characters should be echoed when they are inserted in the buffer, not when they are returned as value of getchar. }; -- of CreateEditedStream ChangeDeliverWhen1: PROC [self: STREAM, proc: DeliverWhenProc] RETURNS [oldProc: DeliverWhenProc] = { data: EditedStreamData = NARROW[self.streamData]; oldProc _ data.deliverWhen; data.deliverWhen _ proc; }; GetBufferContents1: PROC [self: STREAM] RETURNS [buffer: ROPE] = { data: EditedStreamData = NARROW[self.streamData]; RETURN[Rope.FromRefText[IF data.buffer.length # 0 THEN data.buffer ELSE data.ready]]; }; EditedStreamReset: PROCEDURE[self: STREAM] = { data: EditedStreamData = NARROW[self.streamData]; data.buffer.length _ 0; data.ready.length _ 0; data.readyPos _ 0; Reset[self.backingStream]; IF data.echoTo # NIL THEN Reset[data.echoTo]; }; -- of EditedStreamReset EditedStreamFlush: PROCEDURE[self: STREAM] = { data: EditedStreamData = NARROW[self.streamData]; data.buffer.length _ 0; data.ready.length _ 0; data.readyPos _ 0; IF data.echoTo # NIL THEN Flush[data.echoTo]; }; -- of EditedStreamFlush EditedStreamEndOf: PROCEDURE[self: STREAM] RETURNS [BOOLEAN] = { data: EditedStreamData = NARROW[self.streamData]; RETURN[data.readyPos >= data.ready.length AND EndOf[self.backingStream]]; }; -- of EditedStreamEndOf <> EditedStreamGetIndex: PROCEDURE[self: STREAM] RETURNS [INT] = { data: EditedStreamData = NARROW[self.streamData]; RETURN[data.readyPos]; }; -- of EditedStreamGetIndex EditedStreamSetIndex: PROCEDURE[self: STREAM, index: INT] = { data: EditedStreamData = NARROW[self.streamData]; IF index > data.ready.length OR index < 0 THEN ERROR IO.Error[BadIndex, self]; data.readyPos _ index; }; -- of EditedStreamSetIndex EditedStreamSetEcho: PROCEDURE[self: STREAM, echoTo: STREAM] RETURNS [oldEcho: STREAM] = { data: EditedStreamData = NARROW[self.streamData]; oldEcho _ data.echoTo; data.echoTo _ echoTo; }; -- of EditedStreamSetEcho EditedStreamAvail: PROCEDURE[self: STREAM] RETURNS [BOOLEAN] = { data: EditedStreamData = NARROW[self.streamData]; RETURN[data.readyPos < data.ready.length -- OR CharsAvail[self.backingStream] --]; -- CharsAvail means available now }; -- of EditedStreamAvail <> EditedStreamEraseChar: PROCEDURE[self: STREAM, char: CHARACTER] = { data: EditedStreamData = NARROW[self.streamData]; IF data.buffer.length > 0 THEN BEGIN char _ data.buffer[data.buffer.length - 1]; data.buffer.length _ data.buffer.length - 1; IF data.echoTo # NIL THEN data.echoTo.EraseChar[char]; END ELSE NULL; -- Signal[EmptyBuffer, self]; }; -- of EditedStreamEraseChar EditedStreamGetChar: PROCEDURE[self: STREAM] RETURNS[char: CHARACTER] = { data: EditedStreamData = NARROW[self.streamData]; GetCharFromBuffer: PROC = INLINE { char _ data.ready[data.readyPos]; data.readyPos _ data.readyPos + 1; }; -- of GetCharFromBuffer DO IF data.readyPos < data.ready.length THEN {GetCharFromBuffer[]; RETURN}; char _ GetChar[self.backingStream]; -- gets character from stream underneath. Don't want to be in monitor at this point. IF EditedStreamAppend[self, char] AND data.deliverWhen[char, self] THEN EditedStreamDeliver[self]; -- EditedStreamAppend appends it to the buffer, checking for control-a etc., echoes the character. Value of FALSE means doesnt make any sense to check this char, e.g. was ^A or whatever. ENDLOOP; }; -- of EditedStreamGetChar EditedStreamBackup: PROCEDURE[self: STREAM, char: CHARACTER] = { data: EditedStreamData = NARROW[self.streamData]; IF data.readyPos <= 0 OR data.ready[data.readyPos - 1] # char THEN Error[IllegalBackup, self]; data.readyPos _ data.readyPos - 1; }; -- of EditedStreamBackup EditedStreamDeliver: PROC [self: STREAM] = { data: EditedStreamData = NARROW[self.streamData]; bufferLength: NAT _ data.buffer.length; readyLength: NAT _ data.ready.length; readyPos: INT _ data.readyPos; <> FOR i: NAT IN [readyPos..readyLength) DO data.ready[i - readyPos] _ data.ready[i]; ENDLOOP; readyLength _ readyLength - readyPos; data.readyPos _ readyPos _ 0; WHILE bufferLength + readyLength >= data.ready.maxLength DO data.ready _ -- SIGNAL BufferOverFlow -- EnlargeBuffer[data.ready]; ENDLOOP; FOR i: NAT IN [0..bufferLength) DO data.ready[readyLength + i] _ data.buffer[i]; ENDLOOP; data.buffer.length _ 0; data.ready.length _ readyLength + bufferLength; data.lastReadyLength _ data.ready.length; }; -- of EditedStreamDeliver EditedStreamAppend: PROCEDURE[self: STREAM, char: CHARACTER] RETURNS[BOOLEAN _ FALSE] = { data: EditedStreamData = NARROW[self.streamData]; EraseWord: PROC = INLINE{ state: {between, alpha, done} _ between; IF data.buffer.length = 0 THEN {-- Signal[EmptyBuffer, self]; -- RETURN}; UNTIL data.buffer.length = 0 DO char: CHARACTER _ data.buffer[data.buffer.length-1]; SELECT char FROM IN ['A..'Z], IN ['a..'z], IN ['0..'9] => state _ alpha; ENDCASE => IF state = alpha THEN state _ done; IF state = done THEN EXIT; data.buffer.length _ data.buffer.length -1; IF data.echoTo # NIL THEN data.echoTo.EraseChar[char]; ENDLOOP; }; -- of EraseWord EraseLine: PROC = INLINE{ IF data.buffer.length = 0 THEN {-- Signal[EmptyBuffer, self]; -- RETURN}; UNTIL data.buffer.length = 0 DO -- deletes entire line. for buffered streams that accept more than one line of input. char: CHARACTER _ data.buffer[data.buffer.length - 1]; IF char = CR THEN EXIT; data.buffer.length _ data.buffer.length - 1; IF data.echoTo # NIL THEN data.echoTo.EraseChar[char]; ENDLOOP; }; -- of EraseLine PrintLine: PROC = INLINE{ IF data.echoTo = NIL THEN RETURN; PutChar[data.echoTo, CR]; FOR i: NAT IN [0..data.buffer.length) DO IF data.echoTo # NIL THEN PutChar[data.echoTo, data.buffer[i]]; ENDLOOP; }; -- of PrintLine AppendChar: PROC [self: STREAM] = { WHILE data.buffer.length >= data.buffer.maxLength DO data.buffer _ -- SIGNAL BufferOverFlow -- EnlargeBuffer[data.buffer]; ENDLOOP; data.buffer[data.buffer.length] _ char; data.buffer.length _ data.buffer.length + 1; IF data.echoTo # NIL THEN PutChar[data.echoTo, char]; }; -- of AppendChar SELECT char FROM Ascii.DEL => {EditedStreamDeliver[self]; Signal[Rubout, self]}; Ascii.ControlA, Ascii.BS => EditedStreamEraseChar[self, char -- char argument ignored -- ] ; Ascii.ControlW => EraseWord[]; Ascii.ControlQ => EraseLine[]; Ascii.ControlR => PrintLine[]; ENDCASE => IF char = ESC AND data.buffer.length = 0 THEN FOR i: NAT IN [0..data.lastReadyLength-1) DO -- skip the character that caused the deliver, typically CR. char _ data.ready[i]; AppendChar[self]; ENDLOOP ELSE {AppendChar[self]; RETURN[TRUE]; }; RETURN[FALSE]; }; -- of EditedStreamAppend <> <> <> <> BufferedOutputStreamData: TYPE = REF BufferedOutputStreamRecord; -- describes the streamData field for streams that read from ropes. BufferedOutputStreamRecord: TYPE = MONITORED RECORD[ deliverWhen: DeliverWhenProc, buffer: REF TEXT, howOften: INT, pos: INT _ 0 ]; BufferedOutputStreamProcs: REF StreamProcs _ NIL; CreateBufferedOutputStream: PUBLIC PROC [stream: STREAM, deliverWhen: DeliverWhenProc _ IsACR, bufferSize: NAT _ 256] RETURNS [s: STREAM] = { IF BufferedOutputStreamProcs = NIL THEN BufferedOutputStreamProcs _ CreateRefStreamProcs[ putChar: BufferedOutputStreamPutChar, eraseChar: BufferedOutputStreamEraseChar, reset: BufferedOutputStreamReset, flush: BufferedOutputStreamFlush, currentPosition: BufferedOutputStreamPosition, -- mostly for performance, doesn't cost since we have to look at each character anyway. name: "Buffered" ]; s _ CreateProcsStream[ streamProcs: BufferedOutputStreamProcs, streamData: Zone.NEW[BufferedOutputStreamRecord _ [ deliverWhen: deliverWhen, howOften: bufferSize, -- change name of this to howOften buffer: NEW[TEXT[100]] ]], backingStream: stream]; }; -- of CreateBufferedOutputStream maxBufferSize: INT _ 16384; BufferedOutputStreamPutChar: PROC[self: STREAM, char: CHARACTER] = { data: BufferedOutputStreamData = NARROW[self.streamData]; buffer: REF TEXT _ data.buffer; IF buffer.length = buffer.maxLength THEN { IF buffer.length < maxBufferSize THEN data.buffer _ buffer _ EnlargeBuffer[buffer] -- dont confuse buffer size overflowing with desire to flush ELSE self.Flush[]; -- can't have more than 2^15 characters in a ref text. }; buffer[buffer.length] _ char; buffer.length _ buffer.length + 1; IF char = CR THEN data.pos _ 0 ELSE data.pos _ data.pos + 1; IF (data.howOften > 0 AND buffer.length >= data.howOften) OR (data.deliverWhen # NIL AND data.deliverWhen[char, self]) THEN self.Flush[]; }; -- of BufferedOutputStreamPutChar BufferedOutputStreamEraseChar: PROC[self: STREAM, char: CHARACTER] = { data: BufferedOutputStreamData = NARROW[self.streamData]; buffer: REF TEXT _ data.buffer; IF buffer.length = 0 THEN EraseChar[self.backingStream, char] ELSE IF char # buffer[buffer.length - 1] THEN ERROR IO.Error[IllegalBackup, self] ELSE buffer.length _ buffer.length - 1; }; -- of BufferedOutputStreamEraseChar BufferedOutputStreamReset: PROC[self: STREAM] = { data: BufferedOutputStreamData = NARROW[self.streamData]; data.buffer.length _ 0; data.pos _ 0; self.backingStream.Reset[]; }; -- of BufferedOutputStreamReset BufferedOutputStreamFlush: PROC[self: STREAM] = { data: BufferedOutputStreamData = NARROW[self.streamData]; self.backingStream.PutBlock[data.buffer]; data.buffer.length _ 0; }; -- of BufferedOutputStreamFlush BufferedOutputStreamPosition: PROC[self: STREAM] RETURNS[position: INT] = { data: BufferedOutputStreamData _ NARROW[self.streamData]; RETURN[data.pos]; }; -- of BufferedOutputStreamPosition <> <> DribbleStreamData: TYPE = REF DribbleStreamRecord; DribbleStreamRecord: TYPE = RECORD[ dribbleTo: STREAM, echoTo: STREAM, singleCharacter: CHARACTER _ NUL, autoFlush, singleFlg: BOOLEAN _ FALSE, flushCount, pos: INT _ 0, flushEveryNChars: INT ]; DribbleStreamProcs: REF StreamProcs _ NIL; CreateDribbleStream: PUBLIC PROCEDURE [stream: STREAM, dribbleTo: STREAM, flushEveryNChars: INT _ -1] RETURNS [s: STREAM] = { IF DribbleStreamProcs = NIL THEN DribbleStreamProcs _ CreateRefStreamProcs[ getChar: DribbleStreamGetChar, putChar: DribbleStreamPutChar, putBlock: DribbleStreamPutBlock, -- cant default, since default putblock calls backing stream which may have a putBlock, but which will not cause characters to be dribbled. flush: DribbleStreamFlush, eraseChar: DribbleStreamEraseChar, backup: DribbleStreamBackup, -- backup implemented here, rather than defaulting to stream below so that character not dribbled twice setEcho: DribbleStreamSetEcho, currentPosition: DribbleStreamPosition, name: "Dribble" ]; s _ CreateProcsStream[ streamProcs: DribbleStreamProcs, streamData: Zone.NEW[DribbleStreamRecord _ [ dribbleTo: dribbleTo, echoTo: dribbleTo, autoFlush: flushEveryNChars > 0, flushEveryNChars: flushEveryNChars]], backingStream: stream ]; }; -- of CreateDribbleStream DribbleStreamGetChar: PROC[self: STREAM] RETURNS[char: CHARACTER] = { data: DribbleStreamData = NARROW[self.streamData]; IF data.singleFlg THEN {data.singleFlg _ FALSE; RETURN[data.singleCharacter]}; char _ GetChar[self.backingStream]; data.singleCharacter _ char; IF data.echoTo # NIL THEN PutChar[data.echoTo, char]; }; -- of DribbleStreamGetChar DribbleStreamPutChar: PROC[self: STREAM, char: CHARACTER] = { data: DribbleStreamData = NARROW[self.streamData]; IF char = CR THEN data.pos _ 0 ELSE data.pos _ data.pos + 1; PutChar[data.dribbleTo, char]; PutChar[self.backingStream, char]; IF data.autoFlush THEN {data.flushCount _ data.flushCount + 1; IF data.flushCount >= data.flushEveryNChars THEN {data.dribbleTo.Flush[]; data.flushCount _ 0}; }; }; -- of DribbleStreamPutChar DribbleStreamPutBlock: PROC[self: STREAM, block: REF READONLY TEXT, startIndex: NAT, stopIndexPlusOne: NAT] = { data: DribbleStreamData = NARROW[self.streamData]; IF block # NIL THEN {end: NAT _ MIN[block.length, stopIndexPlusOne]; FOR i: NAT IN [startIndex..end) DO PutChar[self, block[i]]; ENDLOOP; }; }; -- of DribbleStreamPutBlock DribbleStreamBackup: PROCEDURE[self: STREAM, char: CHARACTER] = { data: DribbleStreamData = NARROW[self.streamData]; IF data.singleFlg OR data.singleCharacter # char THEN Error[IllegalBackup, self]; data.singleCharacter _ char; data.singleFlg _ TRUE; }; -- of DribbleStreamBackup DribbleStreamEraseChar: PROC[self: STREAM, char: CHARACTER] = { data: DribbleStreamData = NARROW[self.streamData]; EraseChar[data.dribbleTo, char]; EraseChar[self.backingStream, char]; }; -- of DribbleStreamEraseChar DribbleStreamSetEcho: PROC[self: STREAM, echoTo: STREAM] RETURNS[oldEcho: STREAM] = { data: DribbleStreamData = NARROW[self.streamData]; oldEcho _ data.echoTo; data.echoTo _ echoTo; }; -- of DribbleStreamSetEcho DribbleStreamFlush: PROC[self: STREAM] = { data: DribbleStreamData = NARROW[self.streamData]; Flush[data.dribbleTo]; Flush[self.backingStream]; }; -- of DribbleStreamFlush DribbleStreamReset: PROC[self: STREAM] = { data: DribbleStreamData = NARROW[self.streamData]; Reset[data.dribbleTo]; Reset[self.backingStream]; }; -- of DribbleStreamReset DribbleStreamPosition: PROC [self: STREAM] RETURNS[position: INT] = { data: DribbleStreamData = NARROW[self.streamData]; RETURN[data.pos]; }; -- of DribbleStreamPosition <> FilterCommentsStreamData: TYPE = REF FilterCommentsStreamRecord; FilterCommentsStreamRecord: TYPE = RECORD[ lastChar: CHARACTER _ NULL ]; FilterCommentsStreamProcs: REF IO.StreamProcs _ NIL; CreateFilterCommentsStream: PUBLIC PROCEDURE [onStream: IO.STREAM] RETURNS [IO.STREAM] = { IF FilterCommentsStreamProcs = NIL THEN FilterCommentsStreamProcs _ IO.CreateRefStreamProcs[ getChar: FilterCommentsStreamGetChar, name: "Filter Comments" ]; RETURN[IO.CreateProcsStream[streamProcs: FilterCommentsStreamProcs, backingStream: onStream, streamData: NEW[FilterCommentsStreamRecord _ []]]]; }; -- of CreateFilterCommentsStream FilterCommentsStreamGetChar: PROC[self: IO.STREAM] RETURNS [char: CHARACTER] = { data: FilterCommentsStreamData = NARROW[self.streamData]; SELECT char _ self.backingStream.GetChar[] FROM '- => IF data.lastChar # '\\ AND NOT self.backingStream.EndOf[] AND self.backingStream.PeekChar[] = '- THEN -- is a comment. skip to end of comment. {[] _ self.backingStream.GetChar[]; -- discard the -. DO SELECT (char _ self.backingStream.GetChar[]) FROM '\n => EXIT; -- and return the CR. '\\ => [] _ self.backingStream.GetChar[]; -- skip the next character. '- => IF self.backingStream.GetChar[] = '- THEN -- end of comment {char _ self.backingStream.GetChar[]; EXIT}; ENDCASE; ENDLOOP; }; ENDCASE; data.lastChar _ char; }; -- of FilterCommentsStreamGetChar END. 11-Apr-82 20:47:14 implemented erasechar for editedstream, primarily so could do ESC right in userexec. August 23, 1982 11:36 am removed CreateOutputStreamtoSTRING. moved it to CoPilotIO. October 4, 1982 8:52 pm fixed dribble stream so that flush will flush the backing stream in the case that autoFlush has been specified. April 13, 1983 11:18 am fixed dribble stream so that an explicit call to flush flushes both streams., October <> <> <> <> <<"I would like to be able to do a GetLength[] on a strm created by ROS".>> <> <<>>