edited by Teitelman April 19, 1983 4:09 pm
implements various streams.
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.
decided not to raise signal but to call this procedure instead.
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];
};
Nowhere Stream
noWhereStream is a convenient output stream which discards its characters
noWhereStream: PUBLIC IO.STREAM ← CreateProcsStream[
CreateRefStreamProcs[putChar: NoWherePutChar, name: "NoWhere"], NIL];
NoWherePutChar: PROCEDURE[self: STREAM, char: CHARACTER] = {};
NoInput Stream
noInputStream is a input stream for which CharsAvail is FALSE, EndOf is TRUE, but any attempt to get characters raises IO.StreamClosed
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]};
Input Stream From a Rope, RIS
CreateInputStreamFromRope is used when user has a rope in hand and wants to read characters from it. Characters can be loaded into an already existing stream by specifying a non-Null argument for oldStream. Note that no echoing of input is performed.
RIScount: INT ← 0;
InputRopeStreamData: TYPE = REF InputRopeStreamRecord;
InputRopeStreamRecord: TYPE = RECORD[
rope: ROPENIL,
pos: INT ← 0
];
InputRopeStreamProcs: REF StreamProcs ← NIL;
RIS, CreateInputStreamFromRope: PUBLIC PROCEDURE [rope: ROPE, oldStream: STREAMNIL] 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
Output Stream To a Rope, ROS
h puts chars into a rope which can be retrieved via GetOutputStreamRope.
ROSCount: INT ← 0;
ROSNumChars: INT ← 0;
OutputRopeStreamData: TYPE = REF OutputRopeStreamRecord; -- describes the streamData field for streams that read from ropes.
OutputRopeStreamRecord: TYPE = RECORD[rope: ROPENIL, 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: BOOLEANFALSE] = {
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
Input Stream From Ref Text
CreateInputStreamFromText is used when user has a string in hand and wants to read characters from it. Characters can be loaded into an already existing stream by specifying a non-Null argument for oldStream. Note that no echoing of input is performed.
TextStreamData: TYPE = REF TextStreamRecord;
TextStreamRecord: TYPE = RECORD[text: REF READONLY TEXTNIL, i: CARDINAL ← 0];
TextStreamProcs: REF StreamProcs ← NIL;
CreateInputStreamFromText: PUBLIC PROCEDURE [text: REF READONLY TEXTNIL, oldStream: STREAMNIL] 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
Output Stream to Ref Text
h puts characters into text. If oldStream is non-NIL, it is reused rather than allocating space for a new handle.
StreamToTextProcs: REF StreamProcs ← NIL;
CreateOutputStreamToText: PUBLIC PROCEDURE [text: REF TEXT, oldStream: STREAMNIL] 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 TEXTNARROW[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 TEXTNARROW[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 TEXTNARROW[self.streamData, REF TEXT];
IF text.length > 0 THEN text.length ← text.length - 1;
}; -- of StreamToTextEraseChar
StreamToTextReset: PROC[self: STREAM] = {
text: REF TEXTNARROW[self.streamData, REF TEXT];
text.length ← 0;
}; -- of StreamToTextReset
StreamToTextGetIndex: PROC[self: STREAM] RETURNS[INT] = {
text: REF TEXTNARROW[self.streamData, REF TEXT];
RETURN[text.length];
}; -- of StreamToTextReset
StreamToTextSetIndex: PROC[self: STREAM, index: INT] = {
text: REF TEXTNARROW[self.streamData, REF TEXT];
text.length ← index;
}; -- of StreamToTextSetIndex
Edited Stream
creates an edited stream on top of a given stream. implements ^A, ^W etc.
EditedStreamData: TYPE = REF EditedStreamRecord;
EditedStreamRecord: TYPE = RECORD[
buffer: REF TEXT,
ready: REF TEXT,
readyPos: INT ← 0,
used to use ready.length and get the characters from the end, and reset the length, i.e. in reverse order. This change was made so that could define a GetIndex and SetIndex, which was done in order to allow calls to MesaScanner on an edited stream.
lastReadyLength: NAT, -- for ESC
deliverWhen: DeliverWhenProc,
echoTo: STREAMNIL
];
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
Note: for the purposes of get and set index, think of the editedstream as a rope input stream for which the rope is the characters currently available to the stream, i.e. those already delivered..
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
erases last character from buffer, and if being echoed, from echo stream
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;
move any unread characters in ready over. (Can this ever happen?)
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[BOOLEANFALSE] = {
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
Buffered Stream
creates an output stream on top of out such that characters are buffered until (a) the bufferSize is exceeded (actually there is no buffer now, and bufferSize will be changed to howOften), or (b) deliverWhen[char] is TRUE, or (c) an explicit Flush is performed, at which point the characters are output to out using PutBlock, while out is effectively locked until the flush is finished.
IF deliverWhen = NIL is equivalent to a procedure which always returns FALSE.
IF bufferSize is 0, then buffer is effectively infinite, and a rope output stream is used to store the characters.
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
Dribble Stream
Dribble Streams. Characters input from or output to handle will also be output to the indicated handle.
DribbleStreamData: TYPE = REF DribbleStreamRecord;
DribbleStreamRecord: TYPE = RECORD[
dribbleTo: STREAM,
echoTo: STREAM,
singleCharacter: CHARACTERNUL,
autoFlush, singleFlg: BOOLEANFALSE,
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: NATMIN[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
Filter Comments Stream
FilterCommentsStreamData: TYPE = REF FilterCommentsStreamRecord;
FilterCommentsStreamRecord: TYPE = RECORD[
lastChar: CHARACTERNULL
];
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
Edited on April 13, 1983 11:17 am, by Teitelman
activate on DEL so client can flush unwanted characters by doing a WHILE CharsAvail DO GetChar.
changes to: EditedStreamAppend
Edited on April 19, 1983 4:07 pm, by Teitelman
"I would like to be able to do a GetLength[] on a strm created by ROS".
changes to: ROS, OutputRopeStreamLength, OutputRopeStreamClose