RopeFileImpl.mesa
Russ Atkinson, August 26, 1983 4:02 pm
DIRECTORY
Commander USING [CommandProc, Register],
FileIO USING [CapabilityFromStream, Open, OpenFailed],
IO USING [Close, GetBlock, GetLength, PutRope, SetIndex, STREAM],
Process USING [Detach],
PutGet USING [FromRope],
RopeFile USING [],
Rope USING
[Concat, FetchType, Flatten, Index, MakeRope, MapType, Match, ROPE, Size, SkipOver, SkipTo, Text],
RopeInline USING [NewText],
RTFilesExtra USING [DecFileUse, IncFileUse],
RTTypesBasic USING
[CantEstablishFinalization, EstablishFinalization, FinalizationQueue, FQNext, NewFQ, ReEstablishFinalization],
TEditImpl USING [InitTEditDocument],
TiogaMenuOps USING [DefaultMenus],
TiogaOps USING [FindDef],
ViewerClasses USING [Viewer],
ViewerOps USING [OpenIcon],
ViewerTools USING [MakeNewTextViewer];
RopeFileImpl: CEDAR MONITOR
LOCKS handle USING handle: Handle
IMPORTS
Commander, FileIO, IO, Process, PutGet, Rope, RopeInline, RTFilesExtra, RTTypesBasic, TEditImpl, TiogaMenuOps, TiogaOps, ViewerOps, ViewerTools
EXPORTS RopeFile
= BEGIN
T Y P E S
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
Handle: TYPE = REF RopeFileRep;
RopeFileRep: TYPE = MONITORED RECORD [
lockChange: CONDITION,
next: Handle ← NIL,
file: STREAMNIL,
locked: BOOLFALSE,
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 <= (buffers * 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];
splice this ropeFile onto the global list
AcquireLock[dummyRopeFile];
handle.next ← dummyRopeFile.next;
dummyRopeFile.next ← handle;
ReleaseLock[dummyRopeFile];
};
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]];
};
extensionList: LIST OF ROPELIST["", ".mesa", ".tioga", ".df"];
OpenHuge: Commander.CommandProc = {
line: ROPE ← cmd.commandLine;
last: INT ← 0;
out: STREAM ← cmd.out;
DO
start: INT ← line.SkipOver[last, " ,;\t\n"];
next: INT ← line.SkipTo[start, " ,;\t\n"];
name: ROPE ← line.Flatten[start, next-start];
fullName: ROPENIL;
rope: ROPENIL;
ext: ROPENIL;
IF name.Size[] = 0 THEN RETURN;
out.PutRope["Opening "];
out.PutRope[name];
out.PutRope["..."];
last ← next;
IF Rope.Match["*.*", name]
THEN {
rope ← Create[
name: fullName ← name
! FileIO.OpenFailed => CONTINUE];
IF rope = NIL THEN {
pos: INT = Rope.Index[name, 0, "."];
prefix: ROPE = Rope.Flatten[name, 0, pos];
ext ← Rope.Flatten[name, pos+1];
IF ext.Size[] # 0 THEN
rope ← Create[
name: fullName ← prefix.Concat[".mesa"]
! FileIO.OpenFailed => CONTINUE];
};
}
ELSE {
FOR list: LIST OF ROPE ← extensionList, list.rest WHILE list # NIL DO
rope ← Create[
name: fullName ← name.Concat[list.first]
! FileIO.OpenFailed => LOOP];
IF rope # NIL THEN EXIT;
ENDLOOP;
};
IF rope # NIL
THEN {
v: ViewerClasses.Viewer ← ViewerTools.MakeNewTextViewer[
info: [name: fullName, column: left, iconic: TRUE],
paint: FALSE];
v.data ← PutGet.FromRope[rope];
v.file ← name;
TEditImpl.InitTEditDocument[v];
TiogaMenuOps.DefaultMenus[v];
IF ext.Size[] # 0 THEN [] ← TiogaOps.FindDef[v, ext];
ViewerOps.OpenIcon[v];
out.PutRope["done.\n"];
}
ELSE {
out.PutRope["failed!\n"];
};
ENDLOOP;
};
RopeFileFinalizer: PROC = {
This procedure closes the files associated with rope files as part of finalizing the ropes in question. We have to be quite careful to lock things properly, which is why there is a dummyRopeFile. The dummyRopeFile is used to hold on to the ropes created by Create, and to synchronize insertinon of new rope files and deletion of old rope files. Remember: paranoia pays!
DO
ref: REF ← RTTypesBasic.FQNext[fq];
WITH ref SELECT FROM
rf: Handle => {
note: always use the following order in acquiring/releasing locks
lag: Handle ← dummyRopeFile;
found: BOOLFALSE;
IF rf = dummyRopeFile THEN LOOP;
AcquireLock[dummyRopeFile];
AcquireLock[rf];
FOR each: Handle ← dummyRopeFile.next, each.next WHILE each # NIL DO
IF each = rf THEN {
splice out this file
lag.next ← each.next;
found ← TRUE;
EXIT;
};
lag ← each;
ENDLOOP;
IF found AND rf.file # NIL THEN {
Only close the file if it is on the list AND has been opened. This makes us insensitive to multiple occurences of rope files on the finalization queue.
ENABLE UNWIND => {ReleaseLock[rf]; ReleaseLock[dummyRopeFile]};
RTFilesExtra.DecFileUse[FileIO.CapabilityFromStream[rf.file]];
IO.Close[rf.file];
rf.file ← NIL;
};
ReleaseLock[rf];
ReleaseLock[dummyRopeFile];
};
ENDCASE;
ENDLOOP;
};
dummyRopeFile: Handle ← NIL; -- for synchronization
fq: RTTypesBasic.FinalizationQueue = RTTypesBasic.NewFQ[32]; -- for finalization
RTTypesBasic.EstablishFinalization[
CODE[RopeFileRep], 1, fq
! RTTypesBasic.CantEstablishFinalization =>
RTTypesBasic.ReEstablishFinalization[CODE[RopeFileRep], 1, fq]];
dummyRopeFile ← NEW[RopeFileRep];
TRUSTED {
Process.Detach[FORK RopeFileFinalizer[]];
};
Commander.Register["openHuge", OpenHuge, "Opens huge files in a somewhat unsafe fashion."];
END.