RopeFileImpl.mesa
Russ Atkinson, April 19, 1983 9:43 am
DIRECTORY
FileIO USING [CapabilityFromStream, Open],
IO USING [Close, GetBlock, GetLength, SetIndex, STREAM],
RopeFile USING [],
Rope,
RopeInline USING [NewText],
RTFilesExtra;
RopeFileImpl: CEDAR MONITOR
LOCKS handle USING handle: Handle
IMPORTS FileIO, IO, Rope, RopeInline, RTFilesExtra
EXPORTS RopeFile
= BEGIN
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
Handle: TYPE = REF RopeFileRep;
RopeFileRep: TYPE = MONITORED RECORD [
file: STREAMNIL,
locked: BOOLFALSE,
lockChange: CONDITION,
bufSize: NAT ← 0,
offset: INT ← 0,
data: SEQUENCE buffers: NAT OF Buf];
Buf: TYPE = REF BufRec;
BufRec: TYPE = RECORD [start: INT, len: NAT, text: REF TEXT];
AcquireLock: ENTRY PROC [handle: Handle] = {
WHILE handle.locked DO WAIT handle.lockChange; ENDLOOP;
handle.locked ← TRUE;
};
ReleaseLock: ENTRY PROC [handle: Handle] = {
IF NOT handle.locked THEN ERROR;
handle.locked ← FALSE;
BROADCAST handle.lockChange;
};
Create: PUBLIC PROC
[name: ROPE, start: INT ← 0, len: INTLAST[INT],
bufSize: INT ← 512, buffers: NAT ← 4,
useMap: BOOLTRUE, raw: BOOLTRUE]
RETURNS [rope: ROPE] = {
handle: Handle;
fileStream: STREAM ← FileIO.Open[
fileName: name,
raw: raw,
streamBufferParms: [bufferSize: 2, bufferSwapUnitSize: 1]];
filesize: INTIO.GetLength[fileStream];
bestBuffers: INT;
IF start < 0 THEN start ← 0;
IF len <= 0 THEN RETURN [NIL];
IF len = LAST[INT] OR start+len > filesize THEN len ← filesize - start;
IO.SetIndex[fileStream, start];
IF buffers = 0 THEN buffers ← 1;
IF bufSize < 64 THEN bufSize ← 64;
IF bufSize > 16*1024 THEN bufSize ← 16*1024;
bestBuffers ← (len + bufSize - 1) / bufSize;
IF bestBuffers < buffers THEN buffers ← bestBuffers;
IF len < 16*1024 AND len <= (bestBuffers * bufSize) THEN TRUSTED {
text: Rope.Text ← RopeInline.NewText[len];
[] ← IO.GetBlock[fileStream, LOOPHOLE[text], 0, len];
IO.Close[fileStream];
RETURN [text];
};
IF NOT RTFilesExtra.IncFileUse[FileIO.CapabilityFromStream[fileStream], FALSE]
THEN ERROR;
handle ← NEW[RopeFileRep[buffers]];
handle.bufSize ← bufSize;
handle.file ← fileStream;
handle.offset ← start;
FOR i: NAT IN [0..buffers) DO
handle[i] ← NEW[BufRec ← [start: 0, len: 0, text: NEW[TEXT[bufSize]]]];
ENDLOOP;
rope ← Rope.MakeRope[handle, len, MyFetch, IF useMap THEN MyMap ELSE NIL];
};
MyFetch: Rope.FetchType = {
[data: REF, index: INT] RETURNS [CHAR]
handle: Handle = NARROW[data];
last: NAT = handle.buffers-1;
c: CHAR;
IF handle.file = NIL THEN ERROR;
AcquireLock[handle];
index ← index + handle.offset;
FOR i: NAT IN [0..last] DO
buf: Buf ← handle[i];
delta: INT ← index - buf.start;
SELECT TRUE FROM
delta >= 0 AND delta < buf.len => {
this buffer contains the character range
};
i < last =>
we have not come to the last buffer
LOOP;
ENDCASE => {
need to swap in new buffer
length: NAT ← handle.bufSize;
start: INT = index - (delta ← index MOD length);
IO.SetIndex[handle.file, start];
length ← IO.GetBlock[handle.file, buf.text, 0, length];
IF length = 0 THEN {ReleaseLock[handle]; ERROR};
buf.start ← start;
buf.len ← length;
};
IF i # 0 THEN {
swap to speed searches
handle[i] ← handle[i-1];
handle[i-1] ← buf;
};
c ← buf.text[delta];
EXIT;
ENDLOOP;
ReleaseLock[handle];
RETURN [c];
};
MyMap: Rope.MapType = TRUSTED {
[base: REF, start, len: INT, action: ActionType] RETURNS [quit: BOOL ← FALSE]
handle: Handle ← NARROW[base];
last: NAT = handle.buffers-1;
IF handle.file = NIL THEN ERROR;
start ← start + handle.offset;
WHILE len > 0 DO
buf: Buf ← NIL;
delta: INT ← 0;
subLen: NAT = 32;
string: STRING ← [subLen];
pos: NAT ← delta;
spos: NAT ← 0;
chars: NAT ← 0;
AcquireLock[handle];
FOR i: NAT IN [0..last] DO
buf ← handle[i];
delta ← start - buf.start;
SELECT TRUE FROM
delta >= 0 AND delta < buf.len => {
this buffer contains the character range
};
i < last =>
we have not come to the last buffer
LOOP;
ENDCASE => {
need to swap in new buffer
length: NAT ← handle.bufSize;
bpos: INT = start - (delta ← start MOD length);
IO.SetIndex[handle.file, bpos];
length ← IO.GetBlock[handle.file, buf.text, 0, length];
IF length = 0 THEN {ReleaseLock[handle]; ERROR};
buf.start ← bpos;
buf.len ← length;
};
IF i # 0 THEN {
swap to speed searches
handle[i] ← handle[i-1];
handle[i-1] ← buf;
};
EXIT;
ENDLOOP;
At this point we have found a good buffer, so we snatch a sub-buffer from it
pos ← delta;
chars ← MIN[buf.len-pos, subLen];
IF len < chars THEN chars ← len;
FOR j: NAT IN [0..chars) DO
string[j] ← buf.text[pos+j];
ENDLOOP;
we will not touch the buffer until the next time around (if any)
ReleaseLock[handle];
Now yield all of the chars in the sub-buffer
FOR j: NAT IN [0..chars) DO
IF action[string[j]] THEN RETURN [TRUE];
ENDLOOP;
len ← len - chars;
start ← start + chars;
ENDLOOP;
RETURN [FALSE];
};
SubstrCreate: PUBLIC PROC
[name: ROPE, start: INT ← 0, len: INTLAST[INT]] RETURNS [rope: ROPE] = {
RETURN [Create[name, start, len]];
};
SimpleCreate: PUBLIC PROC [name: ROPE] RETURNS [rope: ROPE] = {
RETURN [Create[name]];
};
END.