IOSimpleStreamsImpl.mesa
Copyright Ó 1985, 1986, 1987, 1991, 1992 by Xerox Corporation. All rights reserved.
MBrown on October 25, 1983 1:24 pm
Russ Atkinson (RRA) July 25, 1985 0:07:13 am PDT
Michael Plass, September 23, 1991 10:55 am PDT
Carl Hauser, July 22, 1988 9:01:21 am PDT
Doug Wyatt, May 20, 1992 4:28 pm PDT
TO DO
remove "debugRopeFromROS" flag when no longer needed
adjust buffer size or Concat test in PutRope?
DIRECTORY
Basics USING [RawBytes, UnsafeBlock],
IO,
IOUtils,
RefText,
Rope USING [ROPE, AppendChars, Concat, Fetch, FromRefText, Size, Substr, Text, UnsafeMoveChars];
IOSimpleStreamsImpl: CEDAR PROGRAM
IMPORTS IO, IOUtils, RefText, Rope
EXPORTS IO
= BEGIN
STREAM: TYPE = IO.STREAM;
ROPE: TYPE = Rope.ROPE;
StreamProcs: TYPE = IO.StreamProcs;
Nowhere Stream
noWhereStream: PUBLIC STREAM ¬ IO.CreateStream[IO.CreateStreamProcs[
variety: $output, class: $Null, putChar: NoWherePutChar, close: NoWhereClose], NIL];
NoWherePutChar: PROC [self: STREAM, char: CHAR] = {};
NoWhereClose: PROC [self: STREAM, abort: BOOL] = {};
NoInput Stream
noInputStream: PUBLIC STREAM ¬ IO.CreateStream[
streamProcs: IO.CreateStreamProcs[
variety: $input, class: $Null,
getChar: NoInputGetChar,
endOf: NoInputEndOf,
backup: NoInputBackup,
close: NoInputClose],
streamData: NIL];
NoInputGetChar: PROC [self: STREAM] RETURNS [CHAR] = {
ERROR IO.EndOfStream[self] };
NoInputBackup: PROC [self: STREAM, char: CHAR] = {
ERROR IO.Error[$IllegalBackup, self] };
NoInputEndOf: PROC [self: STREAM] RETURNS [BOOL] = {
RETURN[TRUE] };
NoInputClose: PROC [self: STREAM, abort: BOOL] = { };
Input Stream from ROPE (RIS)
InputRopeStreamData: TYPE = REF InputRopeStreamRecord;
InputRopeStreamRecord: TYPE = RECORD [
rope: ROPE,
length: INT, -- length of rope
start: INT -- current stream index is start+bufferIndex
];
InputRopeStreamProcs: REF StreamProcs = IO.CreateStreamProcs[
variety: $input, class: $ROPE,
getBlock: InputRopeStreamGetBlock,
unsafeGetBlock: InputRopeStreamUnsafeGetBlock,
getChar: InputRopeStreamGetChar,
endOf: InputRopeStreamEndOf,
reset: InputRopeStreamReset,
getIndex: InputRopeStreamGetIndex,
setIndex: InputRopeStreamSetIndex,
getLength: InputRopeStreamGetLength,
backup: InputRopeStreamBackup,
close: RopeStreamClose
];
RIS: PUBLIC PROC [rope: ROPE, oldStream: STREAM] RETURNS [STREAM] = {
size: INT ~ Rope.Size[rope];
self: STREAM ~ IF oldStream#NIL THEN oldStream ELSE NEW[IO.STREAMRecord];
data: InputRopeStreamData ~ WITH self.streamData SELECT FROM
oldData: InputRopeStreamData => oldData,
ENDCASE => NEW[InputRopeStreamRecord];
smallBufSize: NAT ~ 128;
largeBufSize: NAT ~ 2048;
bufSize: NAT ~ IF size<=smallBufSize THEN smallBufSize ELSE largeBufSize;
buffer: REF TEXT ~ IF self.buffer#NIL AND self.buffer.maxLength>=bufSize
THEN self.buffer ELSE RefText.ObtainScratch[bufSize];
data­ ¬ [rope: rope, length: size, start: 0];
self­ ¬ [streamProcs: InputRopeStreamProcs, streamData: data, buffer: buffer];
RETURN[self];
};
RISNormalize: PROC [self: STREAM] RETURNS [InputRopeStreamData] ~ INLINE {
Ensures that data.start is current stream index, and buffer is empty.
data: InputRopeStreamData = NARROW[self.streamData];
data.start ¬ data.start+self.bufferIndex;
self.bufferIndex ¬ self.bufferInputLength ¬ 0;
RETURN[data];
};
InputRopeStreamGetChar: PROC [self: STREAM] RETURNS [char: CHAR] = {
i: NAT ~ self.bufferIndex;
IF i < self.bufferInputLength
THEN { self.bufferIndex ¬ i+1; RETURN[self.buffer[i]] }
ELSE {
data: InputRopeStreamData ~ RISNormalize[self];
self.buffer.length ¬ 0;
self.bufferInputLength ¬ Rope.AppendChars[self.buffer, data.rope, data.start];
IF self.bufferInputLength > 0
THEN { self.bufferIndex ¬ 1; RETURN[self.buffer[0]] }
ELSE ERROR IO.EndOfStream[self];
};
};
InputRopeStreamGetBlock: PROC [self: STREAM, block: REF TEXT, startIndex: NAT, count: NAT] RETURNS [nBytesRead: NAT] = {
data: InputRopeStreamData ~ RISNormalize[self];
index: INT ~ data.start;
block.length ¬ startIndex;
nBytesRead ¬ Rope.AppendChars[buffer: block, rope: data.rope, start: index, len: count];
data.start ¬ index+nBytesRead;
};
InputRopeStreamUnsafeGetBlock: UNSAFE PROC [self: STREAM, block: Basics.UnsafeBlock]
RETURNS [nBytesRead: INT] = {
data: InputRopeStreamData ~ RISNormalize[self];
index: INT ~ data.start;
TRUSTED { nBytesRead ¬ Rope.UnsafeMoveChars[block: block, rope: data.rope, start: index] };
data.start ¬ index+nBytesRead;
};
InputRopeStreamEndOf: PROC [self: STREAM] RETURNS [BOOL] = {
data: InputRopeStreamData = NARROW[self.streamData];
RETURN[(data.start+self.bufferIndex) >= data.length];
};
InputRopeStreamBackup: PROC [self: STREAM, char: CHAR] = {
data: InputRopeStreamData ~ RISNormalize[self];
Assume this is called only if IO.Backup couldn't simply decrement bufferIndex.
IF data.start>0 AND Rope.Fetch[data.rope, data.start-1]=char
THEN data.start ¬ data.start-1
ELSE ERROR IO.Error[$IllegalBackup, self];
};
InputRopeStreamReset: PROC [self: STREAM] = {
data: InputRopeStreamData ~ RISNormalize[self];
data.start ¬ data.length;
};
InputRopeStreamGetIndex: PROC [self: STREAM] RETURNS [INT] = {
data: InputRopeStreamData = NARROW[self.streamData];
RETURN[data.start+self.bufferIndex];
};
InputRopeStreamSetIndex: PROC [self: STREAM, index: INT] = {
data: InputRopeStreamData = NARROW[self.streamData];
IF index NOT IN[0..data.length] THEN ERROR IO.Error[$BadIndex, self];
IF index IN[data.start..data.start+self.bufferInputLength]
THEN self.bufferIndex ¬ index-data.start
ELSE { self.bufferIndex ¬ self.bufferInputLength ¬ 0; data.start ¬ index };
};
InputRopeStreamGetLength: PROC [self: STREAM] RETURNS [length: INT] = {
data: InputRopeStreamData = NARROW[self.streamData];
RETURN[data.length];
};
RopeStreamClose: PROC [self: STREAM, abort: BOOL] = {
RefText.ReleaseScratch[self.buffer];
self­ ¬ [streamProcs: IOUtils.closedStreamProcs];
};
GetRope: PUBLIC PROC [self: STREAM, len: INT ¬ INT.LAST,
demand: BOOL ¬ FALSE] RETURNS [rope: ROPE ¬ NIL] = {
ropeSize: INT ¬ 0;
WITH self.streamData SELECT FROM
data: InputRopeStreamData => {
index: INT ~ data.start+self.bufferIndex; -- current stream index
rope ¬ Rope.Substr[data.rope, index, len];
ropeSize ¬ Rope.Size[rope];
InputRopeStreamSetIndex[self, index+ropeSize];
};
ENDCASE => {
bufSize: NAT = 512;
stackSize: NAT = 10;
s: ARRAY [0..stackSize) OF ROPE ¬ ALL[NIL];
buf: REF TEXT ¬ RefText.ObtainScratch[bufSize];
residual: INT ¬ len;
UNTIL residual = 0 OR IO.EndOf[self] DO
chunkSize: NAT ~ MIN[residual, bufSize];
bytesRead: [0..NAT.LAST] ¬ IO.GetBlock[self: self, block: buf, startIndex: 0, count: chunkSize];
IF bytesRead=0 THEN EXIT;
residual ¬ residual - bytesRead;
rope ¬ Rope.FromRefText[buf];
FOR i: NAT IN [0..stackSize) DO
IF s[i] = NIL THEN {s[i] ¬ rope; rope ¬ NIL; EXIT};
IF i = stackSize-1 THEN {s[i] ¬ Rope.Concat[s[i], rope]; rope ¬ NIL; EXIT};
rope ¬ Rope.Concat[s[i], rope];
s[i] ¬ NIL;
ENDLOOP;
ENDLOOP;
IF rope # NIL THEN ERROR;
FOR i: NAT IN [0..stackSize) DO
IF s[i] # NIL THEN rope ¬ Rope.Concat[s[i], rope];
ENDLOOP;
ropeSize ¬ Rope.Size[rope];
IF ropeSize+residual # len THEN ERROR;
RefText.ReleaseScratch[buf];
};
IF demand AND ropeSize<len THEN ERROR IO.EndOfStream[self];
};
Output Stream to ROPE (ROS)
OutputRopeStreamProcs: REF StreamProcs = IO.CreateStreamProcs[
variety: $output, class: $ROPE,
putChar: OutputRopeStreamPutChar,
eraseChar: OutputRopeStreamEraseChar,
reset: OutputRopeStreamReset,
getIndex: OutputRopeStreamGetIndex,
getLength: OutputRopeStreamGetIndex, -- getLength=getIndex
close: RopeStreamClose
];
OutputRopeStreamData: TYPE = ROPE;
ROS: PUBLIC PROC [oldStream: STREAM] RETURNS [STREAM] = {
self: STREAM ~ IF oldStream#NIL THEN oldStream ELSE NEW[IO.STREAMRecord];
buffer: REF TEXT ¬ self.buffer;
IF buffer=NIL OR buffer.maxLength<10 THEN buffer ¬ RefText.ObtainScratch[256];
self­ ¬ [streamProcs: OutputRopeStreamProcs, streamData: NIL, buffer: buffer, bufferOutputLength: buffer.maxLength, bufferIndex: 0];
RETURN[self];
};
ROSNormalize: PROC [self: STREAM] RETURNS [rope: ROPE] ~ {
Ensures that streamData is the entire output rope, and buffer is empty.
rope ¬ NARROW[self.streamData]; -- previous rope, possibly NIL
IF self.bufferIndex>0 THEN {
self.buffer.length ¬ self.bufferIndex;
self.streamData ¬ rope ¬ Rope.Concat[rope, Rope.FromRefText[self.buffer]];
self.bufferIndex ¬ 0;
};
};
OutputRopeStreamPutChar: PROC [self: STREAM, char: CHAR] = {
i: NAT ~ self.bufferIndex;
IF i < self.bufferOutputLength
THEN { self.bufferIndex ¬ i+1; self.buffer[i] ¬ char }
ELSE { [] ¬ ROSNormalize[self]; self.bufferIndex ¬ 1; self.buffer[0] ¬ char };
};
OutputRopeStreamEraseChar: PROC [self: STREAM, char: CHAR] = {
IF self.bufferIndex > 0
THEN self.bufferIndex ¬ self.bufferIndex-1
ELSE {
rope: ROPE ~ ROSNormalize[self];
size: INT ~ Rope.Size[rope];
IF size>0 THEN self.streamData ¬ Rope.Substr[rope, 0, size-1];
};
};
OutputRopeStreamReset: PROC [self: STREAM] = {
self.streamData ¬ NIL;
self.bufferIndex ¬ 0;
};
OutputRopeStreamGetIndex: PROC [self: STREAM] RETURNS [INT] = {
IF self.streamData=NIL
THEN RETURN[self.bufferIndex]
ELSE RETURN[Rope.Size[NARROW[self.streamData]]+self.bufferIndex];
};
debugRopeFromROS: BOOL ¬ FALSE; -- crock until important clients get fixed
RopeFromROS: PUBLIC PROC [self: STREAM, close: BOOL] RETURNS [ROPE] = {
IF self.streamProcs=OutputRopeStreamProcs
THEN {
rope: ROPE ~ ROSNormalize[self];
IF close THEN RopeStreamClose[self, FALSE];
RETURN[IF debugRopeFromROS AND Rope.Size[rope]=0 THEN NIL ELSE rope];
}
ELSE ERROR IO.Error[$NotImplementedForThisStream, self];
};
PutRope: PUBLIC PROC [self: STREAM, r: ROPE, start: INT ¬ 0, len: INT ¬ INT.LAST] = {
ref: REF ~ r;
rem: INT ~ Rope.Size[r]-CARD[start];
IF rem<len THEN len ¬ rem;
IF len<=0 THEN RETURN;
IF ISTYPE[ref, ROPE] AND self.streamProcs=OutputRopeStreamProcs
AND (self.bufferIndex=0 OR len>NAT[self.bufferOutputLength-self.bufferIndex])
THEN self.streamData ¬ Rope.Concat[ROSNormalize[self], Rope.Substr[r, start, len]]
ELSE WITH ref SELECT FROM
t: REF TEXT => IO.PutBlock[self, t, start, len];
t: Rope.Text => IO.PutBlock[self, RefText.TrustTextRopeAsText[t], start, len];
ENDCASE => TRUSTED {
max: CARD ~ 64;
buffer: PACKED ARRAY [0..max) OF BYTE;
base: POINTER TO Basics.RawBytes ~ LOOPHOLE[@buffer];
WHILE len > 0 DO
moved: NAT ~ Rope.UnsafeMoveChars[[base, 0, MIN[len, max]], r, start];
IO.UnsafePutBlock[self, [base, 0, moved]];
start ¬ start + moved;
len ¬ len - moved;
ENDLOOP;
};
};
Input Stream from REF TEXT (TIS)
InputTextStreamProcs: REF StreamProcs = IO.CreateStreamProcs[
variety: $input, class: $TEXT,
getChar: InputTextStreamGetChar,
endOf: InputTextStreamEndOf,
reset: InputTextStreamReset,
getIndex: InputTextStreamGetIndex,
setIndex: InputTextStreamSetIndex,
getLength: InputTextStreamGetLength,
backup: InputTextStreamBackup
];
TIS: PUBLIC PROC [text: REF READONLY TEXT, oldStream: STREAM] RETURNS [STREAM] = {
self: STREAM ~ IF oldStream#NIL THEN oldStream ELSE NEW[IO.STREAMRecord];
self­ ¬ [streamProcs: InputTextStreamProcs, streamData: NIL];
TRUSTED { self.buffer ¬ LOOPHOLE[text] }; -- loophole because text is READONLY
self.bufferInputLength ¬ MIN[text.length, text.maxLength];
RETURN[self];
};
InputTextStreamGetChar: PROC [self: STREAM] RETURNS [CHAR] = {
i: NAT ~ self.bufferIndex;
IF i < self.bufferInputLength
THEN { self.bufferIndex ¬ i+1; RETURN[self.buffer[i]] }
ELSE ERROR IO.EndOfStream[self];
};
InputTextStreamEndOf: PROC [self: STREAM] RETURNS [BOOL] = {
RETURN[self.bufferIndex>=self.bufferInputLength];
};
InputTextStreamReset: PROC [self: STREAM] = {
self.bufferIndex ¬ self.bufferInputLength;
};
InputTextStreamBackup: PROC [self: STREAM, char: CHAR] = {
IF self.bufferIndex>0 AND self.buffer[self.bufferIndex-1]=char
THEN self.bufferIndex ¬ self.bufferIndex-1
ELSE ERROR IO.Error[$IllegalBackup, self];
};
InputTextStreamGetIndex: PROC [self: STREAM] RETURNS [INT] = {
RETURN[self.bufferIndex];
};
InputTextStreamSetIndex: PROC [self: STREAM, index: INT] = {
IF index IN[0..self.bufferInputLength]
THEN self.bufferIndex ¬ index
ELSE ERROR IO.Error[$BadIndex, self];
};
InputTextStreamGetLength: PROC [self: STREAM] RETURNS [length: INT] = {
RETURN[self.bufferInputLength];
};
Output Stream to REF TEXT (TOS)
OutputTextStreamProcs: REF StreamProcs = IO.CreateStreamProcs[
variety: $output, class: $TEXT,
putChar: OutputTextStreamPutChar,
eraseChar: OutputTextStreamEraseChar,
reset: OutputTextStreamReset,
getLength: OutputTextStreamGetLength,
getIndex: OutputTextStreamGetIndex
];
TOS: PUBLIC PROC [text: REF TEXT, oldStream: STREAM] RETURNS [STREAM] = {
self: STREAM ~ IF oldStream#NIL THEN oldStream ELSE NEW[IO.STREAMRecord];
self­ ¬ [streamProcs: OutputTextStreamProcs, streamData: NIL];
self.buffer ¬ IF text#NIL THEN text ELSE RefText.New[128];
self.bufferOutputLength ¬ self.buffer.maxLength;
RETURN[self];
};
TextFromTOS: PUBLIC PROC [self: STREAM] RETURNS [REF TEXT] = {
IF self.streamProcs=OutputTextStreamProcs
THEN { self.buffer.length ¬ self.bufferIndex; RETURN[self.buffer] }
ELSE ERROR IO.Error[$NotImplementedForThisStream, self];
};
AppendOutput: PUBLIC PROC [to: REF TEXT, from: PROC [STREAM]] RETURNS [REF TEXT] ~ {
self: STREAM ~ NEW[IO.STREAMRecord ¬ [streamProcs: OutputTextStreamProcs,
buffer: to, bufferOutputLength: to.maxLength, bufferIndex: to.length]];
from[self];
self.buffer.length ¬ self.bufferIndex;
RETURN[self.buffer];
};
OutputTextStreamPutChar: PROC [self: STREAM, char: CHAR] = {
i: NAT ~ self.bufferIndex;
IF i < self.bufferOutputLength
THEN { self.bufferIndex ¬ i+1; self.buffer[i] ¬ char }
ELSE {
self.buffer.length ¬ i;
self.buffer ¬ RefText.AppendChar[self.buffer, char];
self.bufferIndex ¬ self.buffer.length;
self.bufferOutputLength ¬ self.buffer.maxLength;
IF NOT(self.bufferIndex = i+1 AND self.buffer[i] = char) THEN ERROR; -- sanity check
};
};
OutputTextStreamEraseChar: PROC [self: STREAM, char: CHAR] = {
IF self.bufferIndex>0 THEN self.bufferIndex ¬ self.bufferIndex-1;
};
OutputTextStreamReset: PROC [self: STREAM] = {
self.bufferIndex ¬ 0;
};
OutputTextStreamGetIndex: PROC [self: STREAM] RETURNS [INT] = {
RETURN[self.bufferIndex];
};
OutputTextStreamGetLength: PROC [self: STREAM] RETURNS [INT] = {
RETURN[self.bufferIndex];
};
END.
Change Log
Changed by MBrown on October 25, 1983 1:24 pm
Fixed output rope stream and output text stream to implement EraseChar. Streams are prepared for Close to replace the stream procs with IOUtils.closedStreamProcs. RopeFromROS is somewhat more efficient in the normal case. RopeFromROS returns NIL when possible (work around bug in interpreter).
Russ Atkinson (RRA) July 22, 1985 8:51:08 pm PDT
Fixed RIS to use Rope.AppendChars for input, sped up UnsafeGetBlock too
Doug Wyatt, August 15, 1991 12:23:45 pm PDT
For Cedar10.0, rewrote rope and text streams to use new buffering scheme.