DIRECTORY FS USING [defaultStreamOptions, GetName, nullOpenFile, OpenFile, OpenFileFromStream, StreamOpen, StreamOptions], IO USING [Close, GetBlock, GetLength, SetIndex, STREAM], Process USING [Detach], RopeFile USING [], Rope USING [ActionType, FetchType, MakeRope, Map, MapType, NewText, PieceActionType, PieceMap, ROPE, Size, Text], SafeStorage USING [CantEstablishFinalization, EnableFinalization, EstablishFinalization, FinalizationQueue, FQNext, NewFQ, ReEstablishFinalization]; RopeFileImpl: CEDAR MONITOR LOCKS handle USING handle: Handle IMPORTS FS, IO, Process, Rope, SafeStorage EXPORTS RopeFile SHARES Rope = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; Handle: TYPE = REF RopeFileRep; RopeFileRep: TYPE = MONITORED RECORD [ lockChange: CONDITION, next: Handle _ NIL, file: STREAM _ NIL, offset: INT _ 0, locked: BOOL _ FALSE, bufSize: NAT _ 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] = INLINE { WHILE handle.locked DO WAIT handle.lockChange; ENDLOOP; handle.locked _ TRUE; }; ReleaseLock: ENTRY PROC [handle: Handle] = INLINE { 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, raw: BOOL _ TRUE] RETURNS [rope: ROPE] = { options: FS.StreamOptions _ FS.defaultStreamOptions; stream: STREAM _ NIL; IF raw THEN options[tiogaRead] _ FALSE; stream _ FS.StreamOpen[ fileName: name, accessOptions: $read, streamOptions: options, streamBufferParms: [vmPagesPerBuffer: 2, nBuffers: 1]]; RETURN [FromStream[stream, start, len, bufSize, buffers]]; }; FromStream: PUBLIC PROC [stream: STREAM, start: INT _ 0, len: INT _ LAST[INT], bufSize: INT _ 512, buffers: NAT _ 4] RETURNS [rope: ROPE] = { handle: Handle _ NIL; filesize: INT _ 0; bestBuffers: INT _ 0; IF start < 0 THEN start _ 0; IF len <= 0 THEN RETURN [NIL]; filesize _ IO.GetLength[stream]; IF len = LAST[INT] OR start+len > filesize THEN len _ filesize - start; IO.SetIndex[stream, 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 _ Rope.NewText[len]; [] _ IO.GetBlock[stream, LOOPHOLE[text], 0, len]; IO.Close[stream]; RETURN [text]; }; handle _ NEW[RopeFileRep[buffers]]; handle.bufSize _ bufSize; handle.file _ stream; 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, MyMap]; AcquireLock[dummyRopeFile]; handle.next _ dummyRopeFile.next; dummyRopeFile.next _ handle; ropesOutstanding _ ropesOutstanding + 1; SafeStorage.EnableFinalization[handle]; ReleaseLock[dummyRopeFile]; }; MyFetch: Rope.FetchType = { 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 => { }; i < last => LOOP; ENDCASE => { 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 { handle[i] _ handle[i-1]; handle[i-1] _ buf; }; c _ buf.text[delta]; EXIT; ENDLOOP; ReleaseLock[handle]; RETURN [c]; }; MyMap: Rope.MapType = TRUSTED { 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 => { }; i < last => LOOP; ENDCASE => { 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 { handle[i] _ handle[i-1]; handle[i-1] _ buf; }; EXIT; ENDLOOP; 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; ReleaseLock[handle]; 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]]; }; FileFromRope: PUBLIC PROC [rope: ROPE] RETURNS [FS.OpenFile _ FS.nullOpenFile] = TRUSTED { WITH r: rope SELECT FROM node => WITH r: r SELECT FROM object => WITH r.base SELECT FROM h: Handle => RETURN [FS.OpenFileFromStream[h.file]]; ENDCASE; ENDCASE; ENDCASE; }; AppendChars: PUBLIC PROC [buffer: REF TEXT, rope: ROPE, start: INT _ 0, len: INT _ LAST[INT]] RETURNS [charsMoved: NAT _ 0] = TRUSTED { action: Rope.PieceActionType = TRUSTED { charAction: Rope.ActionType = TRUSTED { buffer[bPos] _ c; bPos _ bPos + 1; }; WITH r: base SELECT FROM text => { pos: NAT _ start; chars: NAT = len; FOR j: NAT IN [0..chars) DO buffer[bPos+j] _ r[pos+j]; ENDLOOP; bPos _ bPos + chars; RETURN; }; node => WITH r: r SELECT FROM object => WITH r.base SELECT FROM handle: Handle => { 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; pos: NAT _ delta; spos,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 => { }; i < last => LOOP; ENDCASE => { 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 { handle[i] _ handle[i-1]; handle[i-1] _ buf; }; EXIT; ENDLOOP; pos _ delta; chars _ buf.len-pos; IF len < chars THEN chars _ len; FOR j: NAT IN [0..chars) DO buffer[bPos+j] _ buf.text[pos+j]; ENDLOOP; ReleaseLock[handle]; len _ len - chars; start _ start + chars; bPos _ bPos + chars; ENDLOOP; RETURN; }; ENDCASE; ENDCASE; ENDCASE; quit _ Rope.Map[base, start, len, charAction]; }; bPos: NAT _ buffer.length; bLim: NAT = buffer.maxLength; bRem: NAT = bLim-bPos; rem: INT _ Rope.Size[rope]-start; IF bRem < len THEN len _ bRem; IF rem < len THEN len _ rem; [] _ Rope.PieceMap[rope, start, len, action]; charsMoved _ len; buffer.length _ bPos; }; RopeFileFinalizer: PROC = { DO ref: REF _ NIL; ref _ SafeStorage.FQNext[fq]; -- genuflect three times to the GC's conservative scanner WITH ref SELECT FROM rf: Handle => { lag: Handle _ dummyRopeFile; found: BOOL _ FALSE; IF rf = dummyRopeFile THEN LOOP; AcquireLock[dummyRopeFile]; AcquireLock[rf]; FOR each: Handle _ dummyRopeFile.next, each.next WHILE each # NIL DO IF each = rf THEN { lag.next _ each.next; found _ TRUE; EXIT; }; lag _ each; ENDLOOP; IF found AND rf.file # NIL THEN { ENABLE UNWIND => {ReleaseLock[rf]; ReleaseLock[dummyRopeFile]}; IO.Close[rf.file]; rf.file _ NIL; ropesOutstanding _ ropesOutstanding - 1; }; ReleaseLock[rf]; ReleaseLock[dummyRopeFile]; }; ENDCASE; ENDLOOP; }; FilesOnQueue: PROC RETURNS [nameList: LIST OF ROPE _ NIL] = { AcquireLock[dummyRopeFile]; FOR each: Handle _ dummyRopeFile.next, each.next WHILE each # NIL DO name: ROPE _ "NIL file!?!?!?!?"; stream: STREAM = each.file; IF stream # NIL THEN name _ FS.GetName[FS.OpenFileFromStream[stream]].fullFName; nameList _ CONS[name, nameList]; ENDLOOP; ReleaseLock[dummyRopeFile]; }; FinalizationProblem: SIGNAL = CODE; dummyRopeFile: Handle _ NIL; -- for synchronization ropesOutstanding: INT _ 0; -- protected by lock fq: SafeStorage.FinalizationQueue = SafeStorage.NewFQ[32]; -- for finalization TRUSTED { ok: BOOL _ TRUE; SafeStorage.EstablishFinalization[CODE[RopeFileRep], 1, fq ! SafeStorage.CantEstablishFinalization => {ok _ FALSE; CONTINUE}]; IF NOT ok THEN { ok _ TRUE; SafeStorage.ReEstablishFinalization[CODE[RopeFileRep], 1, fq ! SafeStorage.CantEstablishFinalization => {ok _ FALSE; CONTINUE}]; IF NOT ok THEN SIGNAL FinalizationProblem; }; dummyRopeFile _ NEW[RopeFileRep]; Process.Detach[FORK RopeFileFinalizer[]]; }; END. ˜RopeFileImpl.mesa Russ Atkinson, October 28, 1983 4:59 pm Paul Rovner, December 14, 1983 11:31 am T Y P E S this makes the rope include the formatting information splice this ropeFile onto the global list [data: REF, index: INT] RETURNS [CHAR] this buffer contains the character range we have not come to the last buffer need to swap in new buffer swap to speed searches [base: REF, start, len: INT, action: ActionType] RETURNS [quit: BOOL _ FALSE] this buffer contains the character range we have not come to the last buffer need to swap in new buffer swap to speed searches At this point we have found a good buffer, so we snatch a sub-buffer from it we will not touch the buffer until the next time around (if any) Now yield all of the chars in the sub-buffer Returns the file (if any) used by the given rope. Appends characters from the given rope (which need not be a RopeFile rope) onto the specified buffer. The move will stop at the end of the specified subrope OR end of the rope OR the end of the buffer. It is the user's responsibility to determine if enough characters have been moved. A BoundsFault will occur if start NOT IN [0..rope.Length[]]. PROC [base: ROPE, start: INT, len: INT] RETURNS [quit: BOOL _ FALSE] PROC [c: CHAR] RETURNS [quit: BOOL _ FALSE] Our special case this buffer contains the character range we have not come to the last buffer need to swap in new buffer swap to speed searches At this point we have found the right buffer, so move chars. We will not touch the buffer until the next time around (if any). 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! note: always use the following order in acquiring/releasing locks splice out this file 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. Useful little crock for debugging Ê÷˜šœ™Jšœ'™'Jšœ'™'—J˜šÏk ˜ šœ˜Jšœg˜g—Jšœœ(œ˜8Jšœœ ˜Jšœ œ˜šœ˜ JšœTœ˜f—šœ ˜Jšœ‚˜‚—J˜—J˜šÐbl œ ˜Jšœœ˜!Jš œœ˜*Jšœ ˜Jšœ˜ Jšœ˜J˜—šœ ™ J˜Jšœœœ˜Jšœœœœ˜Jšœœœ ˜J˜šœ œ œœ˜&Jšœ œ˜Jšœœ˜Jšœœœ˜Jšœœ˜Jšœœœ˜Jšœ œ˜Jšœœ œœ˜$J˜—Jšœœœ˜Jš œœœ œœœœ˜=J˜—šÏn œœœœ˜3Jšœœœœ˜7Jšœœ˜J˜J˜—šŸ œœœœ˜3Jšœœœœ˜ Jšœœ˜Jš œ˜J˜J˜—šŸœœ˜š œœ œ œœœ˜2Jš œ œœ œœ˜7—Jšœœ˜Jšœ œœ˜4Jšœœœ˜šœ˜ Jšœ6™6Jšœœ˜—šœ œ ˜Jšœ˜Jšœ˜Jšœ˜Jšœ7˜7—Jšœ4˜:J˜J˜—šŸ œœ˜Jšœ œ œ œœœ œœ˜\Jšœœ˜Jšœœ˜Jšœ œ˜Jšœ œ˜J˜Jšœ œ ˜Jšœ œœœ˜J˜Jšœ œ˜ J˜Jš œœœœœ˜GJšœ˜Jšœ œ ˜ Jšœœ˜"Jšœœ˜,Jšœ,˜,Jšœœ˜4šœœœœ˜>Jšœ$˜$Jšœœœ˜1Jšœ˜Jšœ˜J˜—Jšœ œ˜#J˜Jšœ˜J˜šœœœ˜Jšœ œ#œœ ˜GJšœ˜—Jšœ2˜2Jšœ)™)Jšœ˜Jšœ!˜!Jšœ˜Jšœ(˜(Jšœ'˜'Jšœ˜J˜J˜—šœ˜Jšœ&™&Jšœœ˜Jšœœ˜Jšœœ˜Jšœœœœ˜ Jšœ˜J˜šœœœ ˜J˜Jšœœ˜šœœ˜šœ œ˜#Jšœ(™(Jšœ˜—šœ ˜ Jšœ#™#Jšœ˜—šœ˜ Jšœ™Jšœœ˜Jšœœœ ˜0Jšœ˜ Jšœ œ,˜7Jšœ œœ˜0J˜J˜J˜——šœœ˜Jšœ™J˜J˜J˜—Jšœ˜Jšœ˜Jšœ˜—J˜Jšœ˜ J˜J˜—šœœ˜JšœM™MJšœœ˜Jšœœ˜Jšœœœœ˜ Jšœ˜šœ ˜Jšœ œ˜Jšœœ˜Jšœœ˜Jšœœ ˜Jšœœ ˜Jšœœ˜Jšœœ˜Jšœ˜šœœœ ˜J˜Jšœ˜šœœ˜šœ œ˜#Jšœ(™(Jšœ˜—šœ ˜ Jšœ#™#Jšœ˜—šœ˜ Jšœ™Jšœœ˜Jšœœœ ˜/Jšœ˜Jšœ œ,˜7Jšœ œœ˜0Jšœ˜J˜J˜——šœœ˜Jšœ™J˜J˜J˜—Jšœ˜Jšœ˜—JšœL™LJšœ ˜ Jšœœ˜!Jšœ œ ˜ šœœœ ˜Jšœ˜Jšœ˜—Jšœ@™@J˜Jšœ,™,šœœœ ˜Jšœœœœ˜(Jšœ˜—Jšœ˜J˜Jšœ˜—Jšœœ˜J˜J˜—šŸ œœ˜Jšœœ œ œœœœœ˜KJšœ˜"J˜J˜—š Ÿ œœœœœœ˜?Jšœ˜J˜J˜—šŸ œœ˜Jš œœœœ œœ˜@J™1šœ œ˜šœ˜šœœ˜˜ šœœ˜Jšœ œœ˜4Jšœ˜——Jšœ˜——Jšœ˜—J˜J˜—šŸ œœ˜Jšœ œœœ œ œœœ˜DJšœœœ˜)Jš œžœœmœ!œœ™Üšœœ˜(Jšœœ œœœœœ™Dšœœ˜'Jš œœœœœ™+Jšœ˜Jšœ˜Jšœ˜—šœ œ˜˜ Jšœœ ˜Jšœœ˜šœœœ ˜Jšœ˜Jšœ˜—Jšœ˜Jšœ˜J˜—šœ˜šœœ˜˜ šœœ˜šœ˜Jšœ™Jšœœ˜Jšœœœœ˜ Jšœ˜šœ ˜Jšœ œ˜Jšœœ˜Jšœœ ˜Jšœ œ˜Jšœ˜šœœœ ˜J˜Jšœ˜šœœ˜šœ œ˜#Jšœ(™(Jšœ˜—šœ ˜ Jšœ#™#Jšœ˜—šœ˜ Jšœ™Jšœœ˜Jšœœœ ˜/Jšœ˜Jšœ œ,˜7Jšœ œœ˜0Jšœ˜J˜J˜——šœœ˜Jšœ™J˜J˜J˜—Jšœ˜Jšœ˜—Jšœ<™