-- 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 --(635)\f1