-- file VirtCM.Mesa
-- edited by Schroeder, January 8, 1981 11:58 AM.
-- edited by Brotz, May 26, 1982 4:36 PM.

DIRECTORY
ByteBltDefs USING [ByteBlt],
exD: FROM "ExceptionDefs" USING [SysBug],
Inline USING [COPY],
Stream USING [Block],
vmD: FROM "VirtualMgrDefs" USING [CharIndex, CMOCharMapTableSize,
CMOMaxCharPerPage, ComposedMessagePtr, EnsureCMBackingFile, GetMessageChar,
InsertStringInMessage, InsertSubstringInMessage, MakeBufferEmpty, MessageRange,
PageByteIndex, PageNumber, VoidCharCache],
VMDefs USING [AllocatePage, GetFileLength, Mark, Page, PageByteIndex, PageInCache,
PageNumber, Position, ReadPage, Release, SetFileLength, UsePage];

VirtCM: PROGRAM
IMPORTS ByteBltDefs, exD, Inline, vmD, VMDefs
EXPORTS vmD =

BEGIN
OPEN vmD;


MessageOverflow: PUBLIC ERROR = CODE;


MapCharIndexToPageByte: PUBLIC PROCEDURE [cm: ComposedMessagePtr,
index: CharIndex] RETURNS [page: PageNumber, byte: PageByteIndex] =
-- Get the page#, byte# pair for the index-th character of the compose message.
BEGIN
c: CharIndex ← 0;
FOR page IN [0 .. cm.filePageFF) DO
c ← c + cm.charMap[page].count; -- c is # of 1st char off page.
IF c > index THEN EXIT;
REPEAT FINISHED => exD.SysBug[];
ENDLOOP;
byte ← index - (c - cm.charMap[page].count);
END; -- of MapCharIndexToPageByte --


MergeBufferWithNeighbors: PUBLIC PROCEDURE
[cm: ComposedMessagePtr, pn: PageNumber, index: CharIndex]
RETURNS [npn: PageNumber, firstIndex: CharIndex] =
-- If possible, the logical page in cm.buffer and the logical pages immediately preceding
-- and following this page are compacted. The resulting compacted page remains in
-- cm.buffer, and the char map table is updated. "index" is the first CharIndex on some
-- logical page "pn" at the time of the call to MergeBufferWithNeighbors. At the return
-- of this procedure, "npn" is the logical page which contains "index", and "firstIndex"
-- is the firstCharIndex on page "npn".
BEGIN

MergeTwoPages: PROCEDURE [pn: PageNumber] =
BEGIN
to, from: Stream.Block;
firstPage, nextPage: VMDefs.Page;
firstCount: CARDINAL = cm.charMap[pn].count;
nextCount: CARDINAL = cm.charMap[pn + 1].count;
bufferIsFirst: BOOLEAN = (pn = cm.logicalPageNumber);
neighborFilePN: PageNumber = cm.charMap[IF bufferIsFirst THEN pn + 1 ELSE pn].page;
IF (~bufferIsFirst OR nextCount > 0)
AND firstCount + nextCount <= CMOMaxCharPerPage
AND VMDefs.PageInCache[[cm.file, neighborFilePN]] THEN
BEGIN
otherPage: VMDefs.Page ← VMDefs.ReadPage[[cm.file, neighborFilePN]];
IF bufferIsFirst THEN {firstPage ← cm.buffer; nextPage ← otherPage}
ELSE {firstPage ← otherPage; nextPage ← cm.buffer};
to ← Stream.Block[blockPointer: LONG[firstPage], startIndex: firstCount,
stopIndexPlusOne: firstCount + nextCount];
from ← Stream.Block[blockPointer: LONG[nextPage], startIndex: 0,
stopIndexPlusOne: nextCount];
[] ← ByteBltDefs.ByteBlt[to: to, from: from];
cm.charMap[pn].count ← firstCount + nextCount;
cm.charMap[pn + 1].count ← 0;
VMDefs.Release[nextPage];
cm.buffer ← firstPage;
cm.bufferState ← dirty;
SELECT pn + 1 FROM
= npn => {npn ← npn - 1; firstIndex ← firstIndex - firstCount};
< npn => npn ← npn - 1;
ENDCASE;
cm.logicalPageNumber ← pn;
RemoveLP[cm, pn + 1];
END;
END; -- of MergeTwoPages --

IF cm.bufferState = empty THEN exD.SysBug[];
npn ← pn;
firstIndex ← index;
IF cm.logicalPageNumber > 0 THEN MergeTwoPages[cm.logicalPageNumber - 1];
IF cm.logicalPageNumber + 1 < cm.filePageFF THEN MergeTwoPages[cm.logicalPageNumber];
END; -- of MergeBufferWithNeighbors --


GetCMBuffer: PUBLIC PROCEDURE [cm: ComposedMessagePtr, pn: PageNumber,
index: CharIndex, newPage: BOOLEAN, tryCompaction: BOOLEAN]
RETURNS [firstIndex: CharIndex, npn: PageNumber] =
-- Obtains a buffer page that contains index. At the time of the call, "index" is the first
-- character index on logical page "pn". Due to compaction, "index" may move to a
-- different page and may not be the first index on that page any more. The actual
-- page obtained is returned in "npn" and the first index on this page is returned in
-- "firstIndex".
-- If "newPage" is TRUE, then new logical page "pn" is obtained. Due to compaction, the
-- actual page obtained may be different, and its number is likewise returned in "npn".
-- Compaction will be tried only if "tryCompaction" is TRUE.
-- Any compaction will be reflected in cm’s charMap.
BEGIN
npn ← pn;
firstIndex ← index;
IF ~newPage AND cm.bufferState # empty AND cm.logicalPageNumber = pn
THEN RETURN;
IF cm.bufferState # empty THEN
BEGIN
IF tryCompaction AND (newPage OR ~VMDefs.PageInCache[[cm.file, pn]]) THEN
[npn, firstIndex] ← MergeBufferWithNeighbors[cm, pn, index];
IF ~newPage AND cm.logicalPageNumber = npn THEN RETURN;
MakeBufferEmpty[cm];
END;
cm.buffer ← IF newPage
THEN AllocateNewCMPage[cm, npn]
ELSE VMDefs.ReadPage[[cm.file, cm.charMap[npn].page], 2];
cm.bufferState ← clean;
cm.logicalPageNumber ← npn;
END; -- of GetCMBuffer --


SetGetCacheForCMPage: PUBLIC PROCEDURE [cm: ComposedMessagePtr, pn: PageNumber,
index: CharIndex, newPage: BOOLEAN, tryCompaction: BOOLEAN] =
-- If "newPage" is TRUE, insert a new logical page at number "pn" and set g.first and
-- g.free to index (the first character to be put in the page). If "newPage" is FALSE
-- then get the logical page with number pn into the page cache and set the get cache
-- to reference the contained characters.
-- Errors: MessageOverflow ( possible on "new" only).
BEGIN
[cm.get.first, pn] ← GetCMBuffer[cm, pn, index, newPage, tryCompaction
! MessageOverflow => VoidCharCache[@cm.get]];
cm.get.free ← cm.get.first + cm.charMap[pn].count;
cm.get.floor ← 0;
END; -- of SetGetCacheForCMPage --


InitComposedMessage: PUBLIC PROCEDURE [cm: ComposedMessagePtr, s: STRING] =
-- The composeMessage is initialized to contain the string. All buffer pages are destroyed.
BEGIN
cm.open ← TRUE;
VoidCharCache[@cm.get];
cm.textLength ← 0;
cm.filePageFF ← 0;
cm.inserting ← FALSE;
MakeBufferEmpty[cm];
IF s # NIL THEN
{StartMessageInsertion[cm, 0]; InsertStringInMessage[cm, s]; StopMessageInsertion[cm]};
END; -- of InitComposedMessage --


ReplaceRangeInMessage: PUBLIC PROCEDURE [to, from: MessageRange] =
-- The characters contained in the to range are overwritten with the characters contained in
-- the from range. to and from must be in different VMOs. to’s VMO must be a CM, and
-- from’s VMO can be a CM or a DM.
BEGIN -- Quick & dirty implementation. [But it may be the best one. MDS]
WITH cm: to.message SELECT FROM
CM => {DeleteRangeInMessage[to]; InsertRangeInMessage[to.start, @cm, from]};
ENDCASE => exD.SysBug[];
END; -- of ReplaceRangeInMessage --


InsertRangeInMessage: PUBLIC PROCEDURE
[targetIndex: CharIndex, targetMessage: ComposedMessagePtr, from: MessageRange] =
-- The characters contained in the from range are inserted in the target message just before
-- the targetIndex character. targetMessage and from must be in different VMOs. The
-- former must be a CM, and the latter can be a CM or a DM.
BEGIN
OPEN g: from.message.get;
firstByteToCopy, copyCount: CARDINAL;
currentFromIndex: CharIndex;
IF targetMessage = from.message THEN exD.SysBug[];
StartMessageInsertion[targetMessage, targetIndex];
currentFromIndex ← from.start;
UNTIL currentFromIndex >= from.end DO
-- set the get cache of the from message for the currentFromIndex
[] ← GetMessageChar[from.message, currentFromIndex];
firstByteToCopy ← currentFromIndex + g.floor - g.first;
copyCount ← MIN[from.end - currentFromIndex, g.free - currentFromIndex];
InsertSubstringInMessage[targetMessage,
LOOPHOLE[from.message.buffer - 2, STRING], firstByteToCopy, copyCount
! MessageOverflow => GO TO Overflow];
currentFromIndex ← currentFromIndex + copyCount;
ENDLOOP;
StopMessageInsertion[targetMessage];
EXITS
Overflow => {AbandonMessageInsertion[targetMessage]; ERROR MessageOverflow};
END; -- of InsertRangeInMessage --


DeleteRangeInMessage: PUBLIC PROCEDURE [from: MessageRange] =
-- The characters in the range [from.start .. from.end) are deleted from msg.
-- WARNING: This procedure is very intricate! Be careful if you modify it!
BEGIN
pn: PageNumber;
bottomByte, topCount, holeSize: CARDINAL;
to, fromBlock: Stream.Block;
WITH cm: from.message SELECT FROM
CM => BEGIN
OPEN g: cm.get;
IF from.end > cm.textLength OR from.start > from.end THEN exD.SysBug[];
IF (holeSize ← from.end - from.start) = 0 THEN RETURN; -- Null delete.
IF from.start IN [g.first .. g.free) THEN
{pn ← cm.logicalPageNumber; bottomByte ← from.start - g.first}
ELSE [pn, bottomByte] ← MapCharIndexToPageByte[@cm, from.start];
IF bottomByte # 0 AND holeSize >= (topCount ← cm.charMap[pn].count - bottomByte)
THEN BEGIN -- tail, but not all, of first page is to be removed
cm.charMap[pn].count ← cm.charMap[pn].count - topCount;
cm.textLength ← cm.textLength - topCount;
IF from.start < g.free THEN g.free ← g.free - topCount;
IF from.start <= g.first THEN g.first ← g.first - topCount;
IF (holeSize ← holeSize - topCount) = 0 THEN RETURN;
bottomByte ← 0;
pn ← pn + 1;
END;
UNTIL holeSize < (topCount ← cm.charMap[pn].count - bottomByte) DO
--remove whole pages
RemoveLP[@cm, pn];
IF (holeSize ← holeSize - topCount) = 0 THEN RETURN;
ENDLOOP;
-- if we get here then characters must be shifted down in the last page
IF from.start ~IN [g.first .. g.free) THEN
BEGIN
SetGetCacheForCMPage[@cm, pn, from.start - bottomByte, FALSE, TRUE];
pn ← cm.logicalPageNumber;
bottomByte ← from.start - g.first;
END;
to ← Stream.Block[blockPointer: LONG[cm.buffer], startIndex: bottomByte,
stopIndexPlusOne: cm.charMap[pn].count - holeSize];
fromBlock ← Stream.Block[blockPointer: LONG[cm.buffer],
startIndex: bottomByte + holeSize, stopIndexPlusOne: cm.charMap[pn].count];
[] ← ByteBltDefs.ByteBlt[to: to, from: fromBlock];
--FOR index IN [bottomByte + holeSize .. cm.charMap[pn].count) DO
-- cm.buffer.chars[index - holeSize] ← cm.buffer.chars[index];
-- ENDLOOP;
cm.bufferState ← dirty;
cm.charMap[pn].count ← (g.free ← g.free - holeSize) - g.first;
cm.textLength ← cm.textLength - holeSize;
END; --of CM case.
ENDCASE => exD.SysBug[];
END; -- of DeleteRangeInMessage --


AppendMessageChar: PUBLIC PROCEDURE [cm: ComposedMessagePtr, char: CHARACTER] =
-- The character, char, is appended to the end of the msg.
-- May raise MessageOverflow.
BEGIN
StartMessageInsertion[cm, cm.textLength];
InsertMessageChar[cm, char];
StopMessageInsertion[cm];
END; -- of AppendMessageChar --


UnAppendMessageChar: PUBLIC PROCEDURE [cm: ComposedMessagePtr] =
-- This routine is used to process a backspace. It deletes the last character in the msg.
BEGIN
DeleteRangeInMessage[MessageRange[cm.textLength - 1, cm.textLength, cm]];
END; -- of UnAppendMessageChar --


StartMessageInsertion: PUBLIC PROCEDURE [cm: ComposedMessagePtr, where: CharIndex] =
-- Initializes a ComposedMessage for insertion just before the character "where". This
-- procedure must be called before the other insertion procedures, and eventually
-- followed by either StopMessageInsertion or AbandonMessageInsertion.
BEGIN
IF cm.inserting THEN exD.SysBug[];
IF where > cm.textLength THEN exD.SysBug[];
cm.inserting ← TRUE;
cm.insertionStart ← cm.insertionStop ← where;
END; -- of StartMessageInsertion --


InsertMessageChar: PUBLIC PROCEDURE [cm: ComposedMessagePtr, char: CHARACTER] =
-- The character is appended to the current insertion.
-- May raise MessageOverflow.
BEGIN
OPEN g: cm.get;
IF ~cm.inserting THEN exD.SysBug[];
IF cm.insertionStop # g.free OR g.free >= g.first + CMOMaxCharPerPage THEN
SetGetCacheForInserting[cm];
cm.buffer.chars[g.free - g.first] ← char;
cm.textLength ← cm.textLength + 1;
cm.charMap[cm.logicalPageNumber].count ← (g.free ← g.free + 1) - g.first;
cm.bufferState ← dirty;
cm.insertionStop ← cm.insertionStop + 1;
END; -- of InsertMessageChar --


InsertSubstringInMessage: PUBLIC PROCEDURE
[cm: ComposedMessagePtr, source: STRING, first: CARDINAL, charsToCopy: CARDINAL] =
-- "source"["first" .. "first"+"charsToCopy") are inserted in "cm" at the current insertion
-- point. "source".length and "source".maxlength are not referenced.
-- May raise MessageOverflow.
BEGIN
OPEN g: cm.get;
count, firstFreeByte: CARDINAL;
to, from: Stream.Block;
IF ~cm.inserting THEN exD.SysBug[];
WHILE charsToCopy > 0 DO
SetGetCacheForInserting[cm];
firstFreeByte ← g.free - g.first;
count ← MIN[charsToCopy, CMOMaxCharPerPage - firstFreeByte];
to ← Stream.Block[blockPointer: LONG[cm.buffer], startIndex: firstFreeByte,
stopIndexPlusOne: firstFreeByte + count];
from ← Stream.Block[blockPointer: LONG[@source.text], startIndex: first,
stopIndexPlusOne: first + count];
[] ← ByteBltDefs.ByteBlt[to: to, from: from];
--FOR index IN [0 .. count) DO
-- cm.buffer.chars[firstFreeByte + index] ← source[first + index];
-- ENDLOOP;
cm.bufferState ← dirty;
cm.textLength ← cm.textLength + count;
cm.charMap[cm.logicalPageNumber].count ← firstFreeByte + count;
cm.insertionStop ← cm.insertionStop + count;
g.free ← g.free + count;
first ← first + count;
charsToCopy ← charsToCopy - count;
ENDLOOP;
END; -- of InsertSubstringInMessage --


SetGetCacheForInserting: PRIVATE PROCEDURE [cm: ComposedMessagePtr] =
-- Arranges the Get Cache to locate the page on which the insertion is to take place, and
-- makes sure that the insertion point is g.free.
-- Error: MessageOverflow.
BEGIN
OPEN g: cm.get;
page: PageNumber;
byte: CARDINAL;

-- internal procedure

BifurcateMessage: PROCEDURE =
-- Force a page break between char cm.insertionStop-1 & cm.insertionStop. page and byte
-- are the logical page number and offset corresponding to ndx. The customer is
-- presumed to have checked that cm.insertionStop - 1 is not already at the end of a
-- page and that cm.insertionStop # 0.
-- Sets get cache to logical page of cM.insertionStop-1.
-- WARNING: DON’T FOOL AROUND WITH THIS PROCEDURE UNLESS YOU KNOW
-- WHAT YOU ARE DOING!
-- ErrorCodes: MessageOverflow.
BEGIN
ndx: CharIndex = cm.insertionStop;
copyCount, copyStart, fromCount: CARDINAL;
toBuffer: VMDefs.Page;
to, from: Stream.Block;

copyCount ← cm.charMap[page].count - byte; -- size of the tail to be moved
--make a new page to copy tail onto
toBuffer ← AllocateNewCMPage[cm, page + 1];
fromCount ← cm.charMap[page].count; -- # of chars stored on from page now
SetGetCacheForCMPage[cm, page, ndx - (fromCount - copyCount), FALSE, FALSE];
copyStart ← fromCount - copyCount;
to ← Stream.Block[blockPointer: LONG[toBuffer], startIndex: 0, stopIndexPlusOne: copyCount];
from ← Stream.Block[blockPointer: LONG[cm.buffer], startIndex: copyStart,
stopIndexPlusOne: copyStart + copyCount];
[] ← ByteBltDefs.ByteBlt[to: to, from: from];
--FOR cx IN [0 .. copyCount) DO
-- toBuffer.chars[cx] ← cm.buffer.chars[copyStart + cx];
-- ENDLOOP;
g.free ← g.free - copyCount;
cm.charMap[page].count ← copyStart;
cm.charMap[page + 1].count ← copyCount;
VMDefs.Mark[toBuffer];
VMDefs.Release[toBuffer];
END; -- of BifurcateMessage --

-- start code
DO --may have to do twice if cm is to be compacted
BEGIN
ENABLE MessageOverflow => IF CompactCM[cm] THEN LOOP;
BEGIN -- for EXITs
IF cm.insertionStop = 0 THEN GO TO GetFirstPage;
IF cm.insertionStop - 1 IN [g.first .. g.free) THEN
BEGIN
IF g.free = cm.insertionStop THEN
BEGIN
IF g.free < g.first + CMOMaxCharPerPage THEN RETURN -- hooray!
ELSE {page ← cm.logicalPageNumber; GO TO GetNewPage}
END
ELSE BEGIN
page ← cm.logicalPageNumber;
byte ← cm.insertionStop - g.first;
GOTO BifurcatePage;
END;
END;
[page, byte] ← MapCharIndexToPageByte[cm, cm.insertionStop - 1];
byte ← byte + 1; -- up to place for the new character
IF byte < cm.charMap[page].count THEN GO TO BifurcatePage;
IF byte = CMOMaxCharPerPage THEN GO TO GetNewPage;
GO TO GetOldPage;
EXITS
GetFirstPage => SetGetCacheForCMPage[cm, 0, 0, TRUE, TRUE];
GetNewPage => SetGetCacheForCMPage[cm, page + 1, cm.insertionStop, TRUE, TRUE];
GetOldPage => SetGetCacheForCMPage[cm, page, cm.insertionStop -byte, FALSE, FALSE];
BifurcatePage => BifurcateMessage[];
END; -- of EXITS block --
RETURN;
END; -- of ENABLE --
ENDLOOP;
END; -- of SetGetCacheForInserting --


UnInsertMessageChar: PUBLIC PROCEDURE [cm: ComposedMessagePtr] =
-- This routine is used to process a backspace. It deletes the last character in an insertion,
-- and is a nop if the insertion is empty.
BEGIN
OPEN g: cm.get;
IF ~cm.inserting THEN exD.SysBug[];
IF cm.insertionStop = cm.insertionStart THEN RETURN;
DeleteRangeInMessage
[MessageRange[start: cm.insertionStop - 1, end: cm.insertionStop, message: cm]];
cm.insertionStop ← cm.insertionStop - 1;
END; -- of UnInsertMessageChar --


StopMessageInsertion: PUBLIC PROCEDURE [cm: ComposedMessagePtr] =
-- The current insertion is terminated, and the inserted characters become part of the
-- message.
BEGIN
IF ~cm.inserting THEN exD.SysBug[] ELSE cm.inserting ← FALSE;
END; -- of StopMessageInsertion --


AbandonMessageInsertion: PUBLIC PROCEDURE [cm: ComposedMessagePtr] =
-- The current insertion is discarded, and the inserted characters go away.
BEGIN --Simple implementation.
range: MessageRange ← [cm.insertionStart, cm.insertionStop, cm];
-- Save range over StopMessageInsertion call.
StopMessageInsertion[cm];
DeleteRangeInMessage[range];
END; -- of AbandonMessageInsertion --


AllocateNewCMPage: PROCEDURE [cm: ComposedMessagePtr, pn: PageNumber]
RETURNS [page: VMDefs.Page] =
-- Reuses last logical page in the file if its count is zero; otherwise allocates a new logical
-- page and file page. Returns pointer to mte containing new page and the (possibly
-- different) logical page number of the new page.
-- Error: MessageOverflow.
-- WARNING: THIS PROCEDURE MAY CHANGE THE MEANING OF LPN’s
BEGIN
filePage, pg: PageNumber;
fileLength: VMDefs.Position;
IF cm.filePageFF < pn THEN exD.SysBug[];
IF cm.filePageFF > 0 AND cm.charMap[cm.filePageFF - 1].count = 0
THEN filePage ← cm.charMap[cm.filePageFF - 1].page -- reuse an old file page
ELSE BEGIN
-- need a new file page
IF cm.filePageFF = CMOCharMapTableSize THEN ERROR MessageOverflow;
filePage ← cm.filePageFF;
cm.filePageFF ← cm.filePageFF + 1;
IF filePage > 0 THEN
BEGIN
EnsureCMBackingFile[cm];
fileLength ← VMDefs.GetFileLength[cm.file];
IF filePage >= fileLength.page THEN VMDefs.SetFileLength[cm.file, [filePage + 4, 0]];
END;
END;
page ← IF cm.file = NIL
THEN VMDefs.AllocatePage[]
ELSE VMDefs.UsePage[[cm.file, filePage]];
FOR pg DECREASING IN (pn .. cm.filePageFF) DO -- Adjust charMap.
cm.charMap[pg] ← cm.charMap[pg - 1];
ENDLOOP;
cm.charMap[pn] ← [count: 0, page: filePage]; -- create entry for new LP
IF cm.bufferState # empty AND cm.logicalPageNumber >= pn THEN
cm.logicalPageNumber ← cm.logicalPageNumber + 1;
END; -- of AllocateNewCMPage --


CompactCM: PRIVATE PROCEDURE [cm: ComposedMessagePtr]
RETURNS [worked: BOOLEAN] =
-- Compacts the cm.
-- WARNING: THIS PROCEDURE CHANGES THE MEANING OF LPN’s
BEGIN
OPEN g: cm.get;
buffer: VMDefs.Page = VMDefs.AllocatePage[];
count, place: CARDINAL;
pageNumber: PageNumber;
savedInsertionStart, savedInsertionStop: CARDINAL;
savedInserting: BOOLEAN ← cm.inserting;
IF cm.textLength > CMOMaxCharPerPage * CARDINAL[CMOCharMapTableSize - 1]
THEN RETURN[FALSE];
IF savedInserting THEN
BEGIN
savedInsertionStart ← cm.insertionStart;
savedInsertionStop ← cm.insertionStop;
cm.inserting ← FALSE;
END;
place ← cm.charMap[0].count;
pageNumber ← 0;
StartMessageInsertion[cm, place];
UNTIL place >= cm.textLength DO
[] ← GetMessageChar[cm, place];
IF cm.logicalPageNumber = pageNumber THEN {place ← g.free; LOOP};
count ← g.free - g.first;
Inline.COPY[cm.buffer, (count + 1) / 2, buffer];
RemoveLP[cm, cm.logicalPageNumber];
[] ← InsertSubstringInMessage[cm, LOOPHOLE[buffer - 2, STRING], 0, count];
place ← place + count;
pageNumber ← cm.logicalPageNumber;
ENDLOOP;
StopMessageInsertion[cm];
IF savedInserting THEN
BEGIN
cm.insertionStart ← savedInsertionStart;
cm.insertionStop ← savedInsertionStop;
cm.inserting ← TRUE;
END;
VMDefs.Release[buffer];
RETURN[TRUE];
END; -- of CompactCM --


RemoveLP: PROCEDURE [cm: ComposedMessagePtr, pn: PageNumber] =
-- removes designated LP from within file and places it at end for possible later reuse.
-- WARNING: THIS PROCEDURE CHANGES THE MEANING OF LPN’s
BEGIN
filePage: PageNumber = cm.charMap[pn].page;
count: CARDINAL = cm.charMap[pn].count;
IF pn >= cm.filePageFF THEN exD.SysBug[];
IF cm.bufferState # empty AND pn = cm.logicalPageNumber THEN
{VoidCharCache[@cm.get]; VMDefs.Release[cm.buffer]; cm.bufferState ← empty};
cm.textLength ← cm.textLength - count;
FOR pg: PageNumber IN (pn .. cm.filePageFF) DO
cm.charMap[pg - 1] ← cm.charMap[pg];
ENDLOOP;
cm.charMap[cm.filePageFF - 1] ← [count: 0, page: filePage];
IF cm.bufferState # empty AND cm.logicalPageNumber > pn THEN
BEGIN
cm.logicalPageNumber ← cm.logicalPageNumber - 1;
cm.get.first ← cm.get.first - count;
cm.get.free ← cm.get.free - count;
END;
END; -- of RemoveLP --


END. -- of VirtCM --