-- File PieceTableImpl.mesa -- last modified by Sweet on 16-Apr-82 10:55:18 -- last modified by Satterthwaite, December 28, 1982 9:17 am DIRECTORY Environment USING [Block, Byte, bytesPerPage, wordsPerPage], CIFS: TYPE USING [OpenFile, Close, GetFC, Open, create, replace, write], File: TYPE USING [Capability, ShowCapability], FileStream: TYPE USING [Create, GetCapability, SetIndex], Inline USING [LowHalf], Heap USING [Create--Uniform--, Delete], PieceTable, Space: TYPE USING [ Handle, Create, Delete, LongPointer, Map, nullHandle, virtualMemory], Stream; PieceTableImpl: PROGRAM IMPORTS CIFS, File, FileStream, Inline, Heap, Space, Stream EXPORTS PieceTable SHARES File = BEGIN OPEN PieceTable; nullFile: CIFS.OpenFile = NIL; -- ****************** global data ****************** z: UNCOUNTED ZONE ← NIL; -- NB: all CIFS.OpenFile's are protected through REFs or global frames zVM: Space.Handle ← Space.nullHandle; vPosOfCurrentPiece, savedScratchPos: LONG CARDINAL; currentPieceByte: CARDINAL; -- INVARIANTS: -- 0 < currentPieceByte <= current.length OR -- 0 = currentPieceByte AND currentPiece = pieceHead -- (i.e., posn'd within piece, beginning ok only if 1st piece) -- savedScratchPos used when lastScratchPiece = NullPiece to tell -- where to do next put to scratch file pieceHead, lastScratchPiece: PieceIndex; current: PieceIndex; scratchAtEOF: BOOL; -- means currentPiece = lastScratchPiece AND -- currentPieceByte = current.length scratch: PFS; active: Stream.Handle; pool: LONG POINTER TO PFS; -- INVARIANTS: -- IF active # NIL THEN -- active is a stream on current.file and -- GetPosition[active] = current.position + currentPieceByte. -- ****************** procedures ****************** PTError: PUBLIC SIGNAL = CODE; -- something went wrong Append: PUBLIC PROC RETURNS [pos: LONG CARDINAL] = BEGIN UNTIL current.next = pieceHead DO vPosOfCurrentPiece ← vPosOfCurrentPiece + current.length; current ← current.next; ENDLOOP; currentPieceByte ← current.length; pos ← currentPieceByte + vPosOfCurrentPiece; END; AppendWord: PUBLIC PROC RETURNS [pos: LONG CARDINAL] = BEGIN pos ← Append[]; IF CARDINAL[Inline.LowHalf[pos]] MOD 2 = 0 THEN RETURN; PutZeros[1]; pos ← pos + 1; END; AppendQuadWord: PUBLIC PROC RETURNS [pos: LONG CARDINAL] = BEGIN slop: CARDINAL; pos ← Append[]; slop ← CARDINAL[Inline.LowHalf[pos]] MOD 8; IF slop = 0 THEN RETURN; PutZeros[8 - slop]; pos ← pos + 8 - slop; END; AppendPage: PUBLIC PROC RETURNS [pos: LONG CARDINAL] = BEGIN slop: CARDINAL; pos ← Append[]; slop ← CARDINAL[Inline.LowHalf[pos]] MOD Environment.bytesPerPage; IF slop = 0 THEN RETURN; PutZeros[Environment.bytesPerPage - slop]; pos ← pos + Environment.bytesPerPage - slop; END; BreakCurrentPiece: PROC = BEGIN newPos: LONG CARDINAL = current.position + currentPieceByte; newLength: CARDINAL; new: PieceIndex; IF currentPieceByte = current.length THEN RETURN; newLength ← current.length - currentPieceByte; new ← NewFilePiece[current.file, newPos, newLength]; current.length ← currentPieceByte; LinkIn[new]; IF current = lastScratchPiece THEN lastScratchPiece ← new; END; buffer: LONG POINTER ← NIL; -- allocated and freed by Store bufptr, bufmax: CARDINAL; CopyBytes: PROC [from, to: Stream.Handle, count: CARDINAL] = BEGIN OPEN Stream; fromBlock: Environment.Block; WHILE count # 0 DO tcount: CARDINAL ← MIN[count, bufmax]; IF bufptr + tcount > bufmax THEN FlushBuffer[to]; fromBlock ← [buffer, bufptr, bufptr+tcount]; [bytesTransferred: tcount] ← GetBlock[from, fromBlock]; bufptr ← bufptr + tcount; count ← count - tcount; ENDLOOP; END; CopyFromFile: PUBLIC PROC [ file: CIFS.OpenFile, position: LONG CARDINAL, length: CARDINAL] = BEGIN new: PieceIndex; BreakCurrentPiece[]; -- see if it is contiguous with current piece IF current # pieceHead AND SameFile[current.file, file] AND current.position + current.length = position THEN {currentPieceByte ← current.length ← current.length + length; RETURN}; -- nope, make a new piece new ← NewFilePiece[file, position, length]; LinkIn[new]; vPosOfCurrentPiece ← vPosOfCurrentPiece + current.length; current ← new; currentPieceByte ← current.length; active ← NIL; END; CopyCurrentToScratch: PROC = BEGIN -- copy contents of currentPiece into scratch file scratchPos: LONG CARDINAL = NextScratchPos[]; FileStream.SetIndex[scratch.stream, scratchPos]; SetupStream[]; -- make current piece's stream active CopyBytes[ from: active, to: scratch.stream, count: current.length]; current.position ← scratchPos; current.file ← scratch.file; lastScratchPiece ← current; active ← NIL; -- since we screwed up whatever was setup END; Delete: PUBLIC PROC [count: INTEGER] = BEGIN -- delete "count" characters at current VPos (to left if negative) -- tries to avoid creating extra pieces as Cut would -- if there are fewer character to right or left, signals PTError -- There are characters in the current piece that stay, so -- current, vPosOfCurrentPiece and currentPieceByte remain valid remaining: CARDINAL; p: PieceIndex; after: PieceIndex; cByte: CARDINAL; IF count < 0 THEN BEGIN Move[count]; count ← -count END; p ← current; cByte ← currentPieceByte; WHILE count # 0 DO remaining ← p.length - cByte; IF CARDINAL[count] < remaining THEN BEGIN -- lies in the middle of this piece shrink: PieceIndex; IF cByte = 0 THEN shrink ← p ELSE BEGIN -- can happen only the first time through BreakCurrentPiece[]; shrink ← current.next; END; shrink.position ← shrink.position + count; shrink.length ← shrink.length - count; EXIT END; -- delete rest of this piece after ← p.next; IF cByte = 0 AND p # pieceHead THEN RemovePiece[p] ELSE p.length ← cByte; p ← after; cByte ← 0; count ← count - remaining; IF p = pieceHead AND count # 0 THEN {SIGNAL PTError; RETURN}; ENDLOOP; active ← NIL; END; Finalize: PUBLIC PROC = BEGIN p: PieceIndex; IF scratch.stream # NIL THEN { scratch.stream.Delete[]; scratch.stream ← NIL}; IF scratch.file # nullFile THEN { CIFS.Close[scratch.file]; scratch.file ← NIL}; scratch.next ← NIL; active ← NIL; WHILE pool # NIL DO next: LONG POINTER TO PFS ← pool.next; pool.stream.Delete[]; pool.stream ← NIL; pool.file ← nullFile; z.FREE[@pool]; pool ← next; ENDLOOP; IF pieceHead = NIL THEN RETURN; p ← pieceHead.next; WHILE p # pieceHead DO next: PieceIndex = p.next; z.FREE[@p]; p ← next; ENDLOOP; z.FREE[@pieceHead]; pieceHead ← current ← NullPiece; Heap.Delete[z]; z ← NIL; Space.Delete[zVM]; zVM ← Space.nullHandle; END; FlushBuffer: PROC [to: Stream.Handle] = BEGIN IF bufptr = 0 THEN RETURN; to.PutBlock[[buffer, 0, bufptr], FALSE]; bufptr ← 0; END; GetByte: PUBLIC PROC RETURNS [byte: Environment.Byte] = BEGIN IF current = NullPiece THEN {SIGNAL PTError; RETURN[0]}; IF currentPieceByte = current.length THEN BEGIN p: PieceIndex = current.next; IF p = pieceHead THEN {SIGNAL PTError; RETURN[0]}; vPosOfCurrentPiece ← vPosOfCurrentPiece + current.length; current ← p; currentPieceByte ← 0; SetupStream[]; -- currentPiece > 0 invariant will soon be true again END; IF current.file = nullFile THEN byte ← 0 ELSE BEGIN IF active = NIL THEN SetupStream[]; byte ← active.GetByte[]; END; currentPieceByte ← currentPieceByte + 1; END; GetPlace: PUBLIC PROC RETURNS [Place] = BEGIN RETURN [[pi: current, pos: vPosOfCurrentPiece, filePos: current.position]]; END; GetWord: PUBLIC PROC RETURNS [UNSPECIFIED] = BEGIN bytes: RECORD [left, right: Environment.Byte]; bytes.left ← GetByte[]; bytes.right ← GetByte[]; RETURN [bytes]; END; GetVPos: PUBLIC PROC RETURNS [pos: LONG CARDINAL] = BEGIN RETURN[vPosOfCurrentPiece + currentPieceByte]; END; Initialize: PUBLIC PROC = BEGIN IF z # NIL THEN { Heap.Delete[z]; z ← NIL; IF zVM # Space.nullHandle THEN Space.Delete[zVM]}; zVM ← Space.Create[size: 256, parent: Space.virtualMemory]; z ← Heap.Create--Uniform--[parent: zVM, initial: 50, increment: 10, swapUnit--Size--: 10--, objectSize: SIZE[Piece]--]; vPosOfCurrentPiece ← savedScratchPos ← 0; currentPieceByte ← 0; lastScratchPiece ← NullPiece; pieceHead ← current ← z.NEW [Piece ← [ prev: NULL, length: 0, position: 0, next: NULL, file: nullFile]]; pieceHead.next ← pieceHead.prev ← pieceHead; scratchAtEOF ← FALSE; scratch ← [NIL, nullFile, NIL]; active ← NIL; pool ← NIL; END; Length: PUBLIC PROC RETURNS [l: LONG CARDINAL] = BEGIN p: PieceIndex ← current; l ← vPosOfCurrentPiece; DO IF p = pieceHead THEN EXIT; l ← l + p.length; p ← p.next; ENDLOOP; END; LinkIn: PROC [new: PieceIndex] = BEGIN right: PieceIndex; IF new = NullPiece THEN {SIGNAL PTError; RETURN}; new.prev ← current; right ← current.next; current.next ← new; new.next ← right; right.prev ← new; END; Move: PUBLIC PROC [dist: LONG INTEGER] = BEGIN cLength: CARDINAL; adist: LONG CARDINAL; IF dist = 0 THEN RETURN; active ← NIL; -- no matter what we do IF dist > 0 THEN BEGIN -- move right -- adjust to move from beginning of current piece adist ← dist + currentPieceByte; currentPieceByte ← 0; DO IF (cLength ← current.length) >= adist THEN EXIT; adist ← adist - cLength; vPosOfCurrentPiece ← vPosOfCurrentPiece + cLength; current ← current.next; IF current = pieceHead THEN {SIGNAL PTError; RETURN}; ENDLOOP; currentPieceByte ← Inline.LowHalf[adist]; END ELSE BEGIN -- move left -- adjust to move from end of current piece cLength ← current.length; adist ← cLength - currentPieceByte - dist; currentPieceByte ← cLength; UNTIL adist < cLength DO adist ← adist - cLength; IF current = pieceHead THEN EXIT; current ← current.prev; cLength ← current.length; vPosOfCurrentPiece ← vPosOfCurrentPiece - cLength; ENDLOOP; IF current = pieceHead THEN BEGIN currentPieceByte ← 0; IF adist # 0 THEN {SIGNAL PTError; RETURN}; END ELSE currentPieceByte ← cLength - Inline.LowHalf[adist]; END; END; NewFilePiece: PROC [ file: CIFS.OpenFile, position: LONG CARDINAL, length: CARDINAL] RETURNS [new: PieceIndex] = BEGIN new ← z.NEW[Piece ← [ next: NullPiece, length: length, position: position, prev: NullPiece, file: file]]; END; NextScratchPos: PROC RETURNS [pos: LONG CARDINAL] = BEGIN -- returns position of end of scratch file IF scratch.file = nullFile THEN BEGIN scratch.file ← CIFS.Open[ "/local/PTEdit.scratch$", CIFS.create+CIFS.replace+CIFS.write]; scratch.stream ← FileStream.Create[scratch.file.GetFC]; pos ← 0; END ELSE IF lastScratchPiece = NullPiece THEN pos ← savedScratchPos ELSE pos ← lastScratchPiece.position + lastScratchPiece.length; END; NewScratchPiece: PROC RETURNS [PieceIndex] = BEGIN -- returns zero length piece at end of scratch file RETURN[lastScratchPiece ← NewFilePiece[scratch.file, NextScratchPos[], 0]]; END; PutByte: PUBLIC PROC [byte: Environment.Byte] = BEGIN new: PieceIndex; IF current = lastScratchPiece THEN {IF active = NIL THEN SetupStream[]} ELSE scratchAtEOF ← FALSE; IF scratchAtEOF THEN BEGIN scratch.stream.PutByte[byte]; currentPieceByte ← currentPieceByte + 1; current.length ← current.length + 1; RETURN END; BreakCurrentPiece[]; new ← NewScratchPiece[]; LinkIn[new]; vPosOfCurrentPiece ← vPosOfCurrentPiece + current.length; current ← new; currentPieceByte ← 0; -- the currentPieceByte > 0 invariant temporarily false SetupStream[]; -- will set scratchAtEOF = TRUE scratch.stream.PutByte[byte]; currentPieceByte ← currentPieceByte + 1; -- not just currentPieceByte ← 1 to allow crossjump current.length ← current.length + 1; END; PutWord: PUBLIC PROC [word: UNSPECIFIED] = BEGIN bytes: RECORD [left, right: Environment.Byte] = word; PutByte[bytes.left]; PutByte[bytes.right]; END; PutZeros: PUBLIC PROC [count: CARDINAL] = BEGIN CopyFromFile[file: nullFile, position: 0, length: count]; END; RemovePiece: PROC [p: PieceIndex] = BEGIN left, right: PieceIndex; IF p = pieceHead THEN {SIGNAL PTError; RETURN}; left ← p.prev; right ← p.next; z.FREE [@p]; left.next ← right; right.prev ← left; END; SameFile: PROC [f1, f2: CIFS.OpenFile] RETURNS [BOOL] = BEGIN IF f1 = nullFile THEN RETURN[f2 = nullFile]; IF f2 = nullFile THEN RETURN[FALSE]; RETURN [SameFC[f1.GetFC, f2.GetFC]] END; SameFC: PROC [f1, f2: File.Capability] RETURNS [BOOL] = INLINE BEGIN RETURN [f1.ShowCapability[].fID = f2.ShowCapability[].fID] END; SetupStream: PROC = -- called after currentPiece and currentPieceByte are changed BEGIN IF current.file = nullFile THEN scratchAtEOF ← FALSE ELSE BEGIN SetupFileStream[current.file, current.position + currentPieceByte]; scratchAtEOF ← current = lastScratchPiece AND current.length = currentPieceByte; END; END; SetupFileStream: PROC [file: CIFS.OpenFile, pos: LONG CARDINAL] = BEGIN l, p: LONG POINTER TO PFS ← NIL; IF SameFile[file, scratch.file] THEN active ← scratch.stream ELSE BEGIN FOR p ← pool, p.next UNTIL p = NIL DO IF SameFile[p.file, file] THEN BEGIN -- move to front of list IF l # NIL THEN {l.next ← p.next; p.next ← pool; pool ← p}; -- otherwise, pool = p EXIT END; l ← p; REPEAT FINISHED => BEGIN assert: BOOL[TRUE..TRUE] = (SIZE[PFS] <= SIZE[Piece]); l ← z.NEW[PFS ← [ file: file, stream: FileStream.Create[file.GetFC], next: pool]]; pool ← l; END; ENDLOOP; active ← pool.stream; END; FileStream.SetIndex[active, pos]; END; SetVPos: PUBLIC PROC [pos: LONG CARDINAL, near: PlacePtr ← NIL] = BEGIN IF near # NIL THEN BEGIN current ← near.pi; vPosOfCurrentPiece ← near.pos + current.position - near.filePos; currentPieceByte ← current.length; active ← NIL; END; IF pos = 0 THEN BEGIN current ← pieceHead; currentPieceByte ← 0; vPosOfCurrentPiece ← 0; END ELSE Move[pos - (vPosOfCurrentPiece + currentPieceByte)]; END; buffSize: CARDINAL = 6; Store: PUBLIC PROC [outStream: Stream.Handle] = BEGIN file: File.Capability; -- allocate buffer for copy scratch: Space.Handle ← Space.Create[size: buffSize, parent: Space.virtualMemory]; scratch.Map[]; buffer ← scratch.LongPointer; bufptr ← 0; bufmax ← Environment.wordsPerPage*buffSize; file ← FileStream.GetCapability[outStream]; -- first make sure destination is not part of source current ← pieceHead; vPosOfCurrentPiece ← 0; currentPieceByte ← 0; DO current ← current.next; IF current = pieceHead THEN EXIT; IF current.file # nullFile AND SameFC[current.file.GetFC, file] THEN CopyCurrentToScratch[]; ENDLOOP; -- now copy a piece at a time -- current = pieceHead DO current ← current.next; IF current = pieceHead THEN EXIT; IF current.file = nullFile THEN BEGIN IF bufptr # 0 THEN FlushBuffer[outStream]; THROUGH [0..current.length) DO outStream.PutByte[0]; ENDLOOP; END ELSE BEGIN SetupStream[]; CopyBytes[ from: active, to: outStream, count: current.length]; END; ENDLOOP; IF bufptr # 0 THEN FlushBuffer[outStream]; Finalize[]; buffer ← NIL; Space.Delete[scratch]; END; END.