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: STREAM ← NIL,
locked: BOOL ← FALSE,
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:
INT ←
LAST[
INT],
bufSize: INT ← 512, buffers: NAT ← 4,
useMap: BOOL ← TRUE, raw: BOOL ← TRUE]
RETURNS [rope: ROPE] = {
handle: Handle;
fileStream:
STREAM ← FileIO.Open[
fileName: name,
raw: raw,
streamBufferParms: [bufferSize: 2, bufferSwapUnitSize: 1]];
filesize: INT ← IO.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: INT ← LAST[INT]] RETURNS [rope: ROPE] = {
RETURN [Create[name, start, len]];
};
SimpleCreate:
PUBLIC
PROC [name:
ROPE]
RETURNS [rope:
ROPE] = {
RETURN [Create[name]];
};