-- File PieceTableImpl.mesa
-- Last Modified by Sweet on 4-Mar-81 11:45:21
-- Last Modified by Lewis on October 10, 1980 11:56 AM
DIRECTORY
Environment USING [Block, Byte, bytesPerPage, wordsPerPage],
Heap USING [systemZone],
Inline USING [LowHalf],
PieceTable,
Segments,
Storage,
Stream,
Streams;
PieceTableImpl: PROGRAM
IMPORTS Heap, Inline, Segments, Storage, Stream, Streams
EXPORTS PieceTable =
BEGIN OPEN PieceTable;
-- ****************** imported stuff ********************
z: UNCOUNTED ZONE = Heap.systemZone;
-- ****************** global data ******************
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: BOOLEAN;
-- means currentPiece = lastScratchPiece AND
-- currentPieceByte = current.length
scratch: PFS;
active: Streams.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 PROCEDURE 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 PROCEDURE 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 PROCEDURE 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 PROCEDURE 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: PRIVATE PROCEDURE =
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: POINTER ← NIL; -- allocated and freed by Store
bufptr, bufmax: CARDINAL;
CopyBytes: PRIVATE PROCEDURE [from, to: Streams.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 PROCEDURE [
file: Segments.FHandle,
position: LONG CARDINAL,
length: CARDINAL] =
BEGIN
new: PieceIndex;
BreakCurrentPiece[];
-- see if it is contiguous with current piece
IF current # pieceHead AND 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: PRIVATE PROCEDURE =
BEGIN -- copy contents of currentPiece into scratch file
scratchPos: LONG CARDINAL = NextScratchPos[];
Streams.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 PROCEDURE [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 PROCEDURE =
BEGIN
p: PieceIndex;
IF scratch.stream # NIL THEN
{Streams.Destroy[scratch.stream]; scratch ← [NIL, NIL, NIL]};
active ← NIL;
WHILE pool # NIL DO
next: LONG POINTER TO PFS ← pool.next;
Streams.Destroy[pool.stream];
z.FREE[@pool];
pool ← next;
ENDLOOP;
p ← pieceHead.next;
WHILE p # pieceHead DO
next: PieceIndex = p.next;
z.FREE[@p];
p ← next;
ENDLOOP;
z.FREE[@pieceHead];
pieceHead ← current ← NullPiece;
END;
FlushBuffer: PRIVATE PROC [to: Streams.Handle] =
BEGIN
block: Environment.Block ← [buffer, 0, bufptr];
IF bufptr = 0 THEN RETURN;
Stream.PutBlock[to, block, FALSE];
bufptr ← 0;
END;
GetByte: PUBLIC PROCEDURE 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 = NIL THEN byte ← 0
ELSE
BEGIN
IF active = NIL THEN SetupStream[];
byte ← Streams.GetByte[active];
END;
currentPieceByte ← currentPieceByte + 1;
END;
GetPlace: PUBLIC PROCEDURE RETURNS [Place] =
BEGIN
RETURN [[current, vPosOfCurrentPiece]];
END;
GetWord: PUBLIC PROCEDURE RETURNS [UNSPECIFIED] =
BEGIN
bytes: RECORD [left, right: Environment.Byte];
bytes.left ← GetByte[];
bytes.right ← GetByte[];
RETURN [bytes];
END;
GetVPos: PUBLIC PROCEDURE RETURNS [pos: LONG CARDINAL] =
BEGIN
RETURN[vPosOfCurrentPiece + currentPieceByte];
END;
Initialize: PUBLIC PROCEDURE =
BEGIN
vPosOfCurrentPiece ← savedScratchPos ← 0;
currentPieceByte ← 0;
lastScratchPiece ← NullPiece;
pieceHead ← current ← z.NEW [Piece ← [
prev: NULL,
length: 0,
position: 0,
next: NULL,
file: NIL]];
pieceHead.next ← pieceHead.prev ← pieceHead;
scratchAtEOF ← FALSE;
scratch ← [NIL, NIL, NIL];
active ← NIL;
pool ← NIL;
END;
Length: PUBLIC PROCEDURE 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: PRIVATE PROCEDURE [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 PROCEDURE [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: PRIVATE PROCEDURE [
file: Segments.FHandle,
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: PRIVATE PROCEDURE RETURNS [pos: LONG CARDINAL] =
BEGIN -- returns position of end of scratch file
IF scratch.file = NIL THEN
BEGIN OPEN Segments;
scratch.file ← NewFile["PTEdit.scratch$"L, AllAccess];
scratch.stream ← Streams.CreateStream[scratch.file, AllAccess];
pos ← 0;
END
ELSE IF lastScratchPiece = NullPiece THEN pos ← savedScratchPos
ELSE pos ← lastScratchPiece.position + lastScratchPiece.length;
END;
NewScratchPiece: PRIVATE PROCEDURE RETURNS [PieceIndex] =
BEGIN -- returns zero length piece at end of scratch file
RETURN[lastScratchPiece ←
NewFilePiece[scratch.file, NextScratchPos[], 0]];
END;
PutByte: PUBLIC PROCEDURE [byte: Environment.Byte] =
BEGIN
new: PieceIndex;
IF current = lastScratchPiece THEN
{IF active = NIL THEN SetupStream[]}
ELSE scratchAtEOF ← FALSE;
IF scratchAtEOF THEN
BEGIN
Streams.PutByte[scratch.stream, 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
Streams.PutByte[scratch.stream, byte];
currentPieceByte ← currentPieceByte + 1;
-- not just currentPieceByte ← 1 to allow crossjump
current.length ← current.length + 1;
END;
PutWord: PUBLIC PROCEDURE [word: UNSPECIFIED] =
BEGIN
bytes: RECORD [left, right: Environment.Byte] = word;
PutByte[bytes.left];
PutByte[bytes.right];
END;
PutZeros: PUBLIC PROCEDURE [count: CARDINAL] =
BEGIN
CopyFromFile[file: NIL, position: 0, length: count];
END;
RemovePiece: PRIVATE PROCEDURE [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;
SetupStream: PRIVATE PROCEDURE =
-- called after currentPiece and currentPieceByte are changed
BEGIN
IF current.file = NIL THEN scratchAtEOF ← FALSE
ELSE
BEGIN
SetupFileStream[current.file, current.position + currentPieceByte];
scratchAtEOF ← current = lastScratchPiece AND
current.length = currentPieceByte;
END;
END;
SetupFileStream: PRIVATE PROCEDURE [
file: Segments.FHandle, pos: LONG CARDINAL] =
BEGIN
l, p: LONG POINTER TO PFS ← NIL;
IF file = scratch.file THEN active ← scratch.stream
ELSE
BEGIN
FOR p ← pool, p.next UNTIL p = NIL DO
IF 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 OPEN Streams;
l ← z.NEW[PFS ← [
file: file,
stream: CreateStream[file, Read],
next: pool]];
pool ← l;
END;
ENDLOOP;
active ← pool.stream;
END;
Streams.SetIndex[active, pos];
END;
SetVPos: PUBLIC PROCEDURE [pos: LONG CARDINAL, near: PlacePtr ← NIL] =
BEGIN
IF near # NIL THEN
BEGIN
current ← near.pi;
vPosOfCurrentPiece ← near.pos;
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 PROCEDURE [outStream: Streams.Handle] =
BEGIN OPEN Streams;
file: Segments.FHandle;
-- allocate buffer for copy
buffer ← Storage.Pages[buffSize];
bufptr ← 0;
bufmax ← Environment.wordsPerPage*buffSize;
file ← FileFromStream[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 = 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 = NIL THEN
BEGIN
IF bufptr # 0 THEN FlushBuffer[outStream];
THROUGH [0..current.length) DO PutByte[outStream, 0]; ENDLOOP;
END
ELSE
BEGIN
SetupStream[];
CopyBytes[
from: active,
to: outStream,
count: current.length];
END;
ENDLOOP;
IF bufptr # 0 THEN FlushBuffer[outStream];
Destroy[outStream];
Finalize[];
END;
END.