-- file VirtualMgrDefs.Mesa
-- edited by Schroeder, January 13, 1980 7:22 PM.
-- edited by Brotz, August 13, 1982 3:49 PM.
-- edited by Horning, February 17, 1978 6:24 PM.
-- edited by Kierr, February 3, 1978 4:15 PM.
-- edited by Levin, January 16, 1981 11:27 AM.
-- edited by Taft, May 9, 1983 4:30 PM

DIRECTORY
exD: FROM "ExceptionDefs" USING [Exception],
VMDefs USING [FileHandle, Page, PageByteIndex, PageNumber];

vmD: DEFINITIONS =

BEGIN

-- Purpose. This division manages virtual structures for the message system in such a
-- way that their virtualization is all but transparent to the several clients. Departments
-- in this division include: the TOC Department, which maintains the table of contents
-- of message files; the Message Department, which maintains messages as virtual
-- strings; and the Editor Department, which supplies needed support in editing strings.


-- TOC Department of the Virtual Storage Division manages the table of contents. The
-- TOC is a virtual structure: it includes an index and a cache of demand paged
-- buffers. The fact that the TOC is virtual is invisible to the client. An anticipated
-- error condition is having a message file too large to fit into the virtual TOC structure,
-- since there is a limit on the number of messages in a TOC. In this case, Laurel must
-- fail in a soft mode: the user will be warned that his TOC contains only the initial
-- part of the full TOC, and she will be expected to move some of the messages to
-- another message file.


TOCOverflow: ERROR;
-- May be raised by ExtendTOC or LoadTOC. The resulting TOC is valid for a prefix of
-- the corresponding mail file, but some messages at the end of the mail file will not be
-- indexed in the TOC.


CreateTOC: PROCEDURE RETURNS [toc: TOCHandle];
-- Allocates the TOC data structures and returns a handle to same.


WaitForLock: PROCEDURE [toc: TOCHandle] RETURNS [key: CARDINAL];
-- Busy waits until it can return a non-zero key for toc. The key returned must be
-- passed to any other virtual TOC procedures.


LockTOC: PRIVATE PROCEDURE [toc: TOCHandle] RETURNS [key: CARDINAL];
-- Returns either 0, meaning that the toc is already locked, or some key # 0. A caller
-- that receives a 0 must not call any other procedures requiring the TOC key. A non-0
-- key may be used by the caller in subsequent calls to VirtTOC procedures until the
-- toc is unlocked by explicit call to UnlockTOC.


UnlockTOC: PROCEDURE [toc: TOCHandle, key: CARDINAL];
-- Relinquishes the lock on this TOC handle.


DestroyTOC: PROCEDURE [toc: TOCHandle, key: CARDINAL];
-- Deallocates all structures associated with a mail file TOC. The caller must not reuse the
-- handle after calling this procedure.


CleanupTOC: PROCEDURE [toc: TOCHandle, key: CARDINAL, option: CleanupTOCOption];
-- DMS should call this procedure prior to exiting, so that this department can have a
-- chance to do any last minute housekeeping, such as restoring any dirty cache pages
-- to the TOC file and closing the TOC file. CleanupTOC should also be called when
-- changing from one message file to another. This procedure can be called even if
-- there is no TOC file currently opened.
-- Option = resetChanges causes all changed entries to be marked unchanged and all
-- deleted entries to be removed.
-- Option = dontResetChanges causes changed and deleted entries are left as they are.
-- Option = delete causes the TOC file to be deleted.


LoadTOC: PROCEDURE [toc: TOCHandle, key: CARDINAL, TOCFileName: STRING,
mailFileHandle: VMDefs.FileHandle, option: LoadTOCOption]
RETURNS [firstUnSeen: TOCIndex];
-- Initializes the TOC virtual structure from the indicated file. If the file doesn’t contain
-- a recognizable TOC then an empty TOC is created in the file. Before calling this
-- procedure, the client is expected to have called CleanupTOC if another TOC
-- previously was in use. firstUnSeen returns the index of the first unseen entry found
-- in the TOC, or 0 if no unseen entries are found. If option=new then an empty TOC
-- is created in the file provided.
-- May raise TOCOverflow if the mail file contains more messages than can be held in
-- the TOC.


ExtendTOC: PROCEDURE
[toc: TOCHandle, key: CARDINAL, fp: TOCFixedPartPtr, s: STRING];
-- The TOC data and string are incorporated in the TOC structure at the end. After this
-- procedure is called, the information and string can be modified with
-- PutTOC(FixedPart/String) and accessed with GetTOC(FixedPart/String). Care is taken
-- to allocate a disk page for each new page of the TOC that is assigned.
-- May raise TOCOverflow.


SetTOCValidity: PROCEDURE [toc: TOCHandle, valid: BOOLEAN];
-- Flags the TOC as indicated by ’valid’. If, when LoadTOC is called, the TOC is
-- invalid, LoadTOC will return an error code.


GetTOCFixedPart: PROCEDURE
[toc: TOCHandle, key: CARDINAL, index: TOCIndex, fp: TOCFixedPartPtr];
-- Obtains the index’th TOCEntry data portion and copies it into the structure pointed at
-- by fP.


GetTOCString: PROC [toc: TOCHandle, key: CARDINAL, index: TOCIndex, s: STRING];
-- Obtains the index’th TOCEntry string and copies as much as will fit into s.


PutTOCFixedPart: PROCEDURE [toc: TOCHandle, key: CARDINAL, index: TOCIndex,
fp: TOCFixedPartPtr, changed: BOOLEAN ← TRUE];
-- Obtains the index’th TOCEntry and copies portions of the data structure pointed to by
-- toc into it. Also sets the changed flag of that TOCEntry to "changed" and marks the
-- TOC page as having been written.


PutTOCString: PROC [toc: TOCHandle, key: CARDINAL, index: TOCIndex, s: STRING];
-- Obtains the index’th TOCEntry and copies as much of s as will fit into the string
-- portion of this entry. Marks the TOC page as having been written.


FirstFreeTOCIndex: PROCEDURE [toc: TOCHandle, key: CARDINAL]
RETURNS [index: TOCIndex];
-- This procedure returns the TOC Index value of the next entry available after the
-- current TOC, which is equal to the number of entries in the TOC and one greater
-- than the real last TOC Index.


FirstChangedTOCIndex: PROCEDURE [toc: TOCHandle] RETURNS [index: TOCIndex];
-- Returns the TOC Index value of the 1st (i.e. the numerically lowest) TOC entry which
-- has been changed via PutTOCFixedPart. This is the first TOC entry which has the
-- "changed" BOOLEAN as true. ExtendTOC does not normally set this true (as does
-- PutTOCFixedPart), but will respect that setting if the user does so. If no TOC entries
-- have changed=TRUE, then the first free (ref. GetFirstFreeTOCIndex) index is
-- returned.


-- The Message Department of the Virtual Storage Division manages message strings, i.e.
-- virtual messages. Note that the GetMessageChar procedure is designed to shield the
-- client from the virtual structure of message storage.

-- There are 2 kinds (i.e. variants) of virtual message objects: display and compose. The
-- Editor Department deals with the latter, and this department deal with both.
-- Procedures which require one or the other type so specify in their arguments’ types.
-- Procedures that can take either kind of virtual message object take
-- VirtualMessagePtrs.


MessageOverflow: ERROR;
-- May be raised by any procedure that inserts into a ComposedMessage. The limit of
-- any ComposedMessage is slightly less than 64K characters.


AllocateDisplayMessageObject: PROCEDURE RETURNS [DisplayMessagePtr];


FreeVirtualMessageObject: PROCEDURE [vm: VirtualMessagePtr];


LoadDisplayMessage: PROCEDURE [toc: TOCHandle, key: CARDINAL,
index: TOCIndex, msg: DisplayMessagePtr];
-- Initializes msg to be the messsage string pointed to by the index TOC entry. Any
-- previous contents of msg are lost.


FlushDisplayMessage: PROCEDURE [msg: DisplayMessagePtr, key: CARDINAL];
-- Notifies the virtual manager that a displayed message is no longer in use.


CheckpointDisplayMessage: PROCEDURE [msg: DisplayMessagePtr, key: CARDINAL];
-- In cases where the mail file is to be written (as when appending to the file), this
-- procedure updates the mail file to contain all changes in msg. msg is manipulated so
-- that subsequent accesses to its contents will require initial reading from the mail file
-- once again.


GetMessageChar: PROCEDURE [vm: VirtualMessagePtr, index: CharIndex]
RETURNS [CHARACTER];
-- Obtains the index’th character (starting at zero) from the virtual structure vs.
-- Maintains a cache for quick access to the index’th character or its neighbors on the
-- next call.


PutMessageChar: PROCEDURE
[vm: VirtualMessagePtr, index: CharIndex, char: CHARACTER];
-- The character at position index is overwritten with char.


GetMessageSize: PROCEDURE [msg: VirtualMessagePtr] RETURNS [CharIndex] =
INLINE {RETURN [msg.textLength]};
-- Returns the size of the message, which is equal to the first free CharIndex.


ComposedMessage: PROC [vmp: VirtualMessagePtr] RETURNS [ComposedMessagePtr] =
-- Narrows vmp to cmp.
INLINE {WITH cm: vmp SELECT FROM CM => RETURN[@cm]; ENDCASE => ERROR};


DisplayMessage: PROC [vmp: VirtualMessagePtr] RETURNS [DisplayMessagePtr] =
-- Narrows vmp to dmp.
INLINE {WITH dm: vmp SELECT FROM DM => RETURN[@dm]; ENDCASE => ERROR};


-- The Editor Department of the Virtual Storage Division supports the editor used to edit
-- messages and and provides virtual backing storage for any operations that have such
-- requirements. It deals in terms of messages which are ComposedMessageObjects, a
-- variant of VMOs.


AllocateComposedMessageObject: PROCEDURE RETURNS [ComposedMessagePtr];


InitComposedMessage: PROCEDURE [cm: ComposedMessagePtr, s: STRING];
-- The composeMessage is initialized to contain the string.


CleanupCMs: PROCEDURE;
-- Truncates backing files for all CMs.


ReplaceRangeInMessage: 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.


InsertRangeInMessage: 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.
-- May raise MessageOverflow.


DeleteRangeInMessage: PROCEDURE [from: MessageRange];
-- The characters in the range [from.first .. from.end) are deleted from msg.


InsertFileInMessage: PROCEDURE
[targetIndex: CharIndex, targetMessage: ComposedMessagePtr, file: STRING];
-- Inserts the entire contents of the file in the message starting just before the targetIndex.
-- May raise opD.FileOpError or MessageOverflow.


PutRangeInFile:
PROCEDURE [from: MessageRange, file: STRING, concurrenceNeeded: BOOLEAN,
UserConcurs: PROCEDURE [exD.Exception] RETURNS [BOOLEAN]];
-- Overwrites the entire contents of the file with the characters from the message range.
-- Pseudo CR’s are converted to blanks. If file not empty and concurrenceNeeded, then
-- UserConcurs is called and the put proceeds only if TRUE is returned.
-- May raise opD.FileError.


AppendMessageChar: PROCEDURE [cm: ComposedMessagePtr, char: CHARACTER];
-- The character, char, is appended to the end of the msg.
-- May raise MessageOverflow.


UnAppendMessageChar: PROCEDURE [cm: ComposedMessagePtr];
-- This routine is used to process a backspace. It deletes the last character in the msg.


StartMessageInsertion: 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.


InsertMessageChar: PROCEDURE [cm: ComposedMessagePtr, char: CHARACTER];
-- The character is appended to the current insertion.
-- May raise MessageOverflow.


UnInsertMessageChar: 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.


InsertSubstringInMessage: 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.


InsertStringInMessage: PROCEDURE [cm: ComposedMessagePtr, s: STRING] = INLINE
{InsertSubstringInMessage[cm, s, 0, s.length]};
-- Inserts all of "s" at the current insertion point.
-- May raise MessageOverflow.


StopMessageInsertion: PROCEDURE [cm: ComposedMessagePtr];
-- The current insertion is terminated, and the inserted characters become part of the
-- message.


AbandonMessageInsertion: PROCEDURE [cm: ComposedMessagePtr];
-- The current insertion is discarded, and the inserted characters go away.


-- Internal procedures common to several modules


SearchTOCTable: PROCEDURE [toc: TOCHandle, index: TOCIndex]
RETURNS [pn: PageNumber];
-- Maps a TOCIndex to a TOC file page number.


FindTOCAddress: PROCEDURE [toc: TOCHandle, index: TOCIndex]
RETURNS [hta: HardTOCAddress];
-- You’d better be careful, cache activity may cause the pointers returned to become
-- dangling references.
-- Leaves the toc page containing index in toc.buffer.


TOCAddressOnPage: PROCEDURE
[toc: TOCHandle, index: TOCIndex, page: VMDefs.Page, pn: PageNumber]
RETURNS [TOCFixedPartPtr];


VoidCharCache: PROCEDURE [c: POINTER TO CharCache];


InvalidateCaches: PROCEDURE;
-- Invalidates caches for all CM and DM objects and frees up the virtual memory
-- buffers they are holding onto.


MapCharIndexToPageByte: PROCEDURE [cm: ComposedMessagePtr, index: CharIndex]
RETURNS [page: PageNumber, byte: PageByteIndex];


SetGetCacheForCMPage: PROCEDURE [cm: ComposedMessagePtr, pn: PageNumber,
index: CharIndex, newPage: BOOLEAN, tryCompaction: BOOLEAN];
-- If option is "new" then insert a new logical page at number lpn and set g.first and
-- g.free to index (the first character to be put in the page). If option is "old" then get
-- the logical page with number lpn into the page cache and set the get cache to
-- reference the contained characters.


GetBuffer: PROCEDURE
[vop: VirtualObjectPtr, pn: PageNumber, newPage: BOOLEAN];
-- Obtains a buffer for page number "pn" of "vop". If "pageState" = new, then a new
-- page is created at this "pn" (it better be the last page). If "pageState" = old, then
-- existing page "pn" is read from vop’s file.


GetCMBuffer: 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.


MergeBufferWithNeighbors:
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".


MakeBufferEmpty: PROCEDURE [vop: VirtualObjectPtr];
-- Writes the buffer associated with "vop" if dirty.


EnsureCMBackingFile: PROCEDURE [cm: ComposedMessagePtr];
-- Slides a backing file under the cm when needed.


-- Data Structures and Types.

CharIndex: TYPE = CARDINAL;

PageNumber: TYPE = VMDefs.PageNumber;
PageByteIndex: TYPE = VMDefs.PageByteIndex;

TOCMinStringSize: CARDINAL = 12; --smallest "maxlength".

TOCPageTableSize: CARDINAL = 64; -- app. 7-9 entries fit on a page

TOCType: CARDINAL = 1; -- For TOCPageHeader.garbageDetector.

VirtualObjectPtr: TYPE = POINTER TO VirtualObject;

VirtualObject: TYPE = RECORD
[file: VMDefs.FileHandle,
buffer: VMDefs.Page,
logicalPageNumber: PageNumber, -- for buffer.
bufferState: BufferState,
open: BOOLEAN,
vOVar: SELECT VOType:* FROM
TOC =>
[pageTable: POINTER TO TOCPageTable,
filePageFF: PageNumber, -- Next avail page in TOC file.
indexFF: TOCIndex, -- Next avail message index.
firstChange: TOCIndex, -- 0 or 1st entry "changed".
mailFile: VMDefs.FileHandle, -- from New/Virtualize-TOC.
dmList: DMList,
runArray: TOCRunArray,
runs: CARDINAL,
maxRuns: CARDINAL,
key: CARDINAL,
keyHolder: PROGRAM],
VMO =>
[next: VirtualMessagePtr,
textLength: CharIndex, -- in bytes,
get: CharCache, -- For GetMessageChar look-ahead.
formatStart: CharIndex,
formatEnd: CharIndex,
vMOVar: SELECT vmoType: VMOType FROM
DM => -- DisplayMessage.
[firstPage: PageNumber,
firstByte: PageByteIndex,
toc: TOCHandle,
index: TOCIndex],
CM => -- ComposedMessage.
[inserting: BOOLEAN, --following for TRUE only
insertionStart: CharIndex,
insertionStop: CharIndex,
charMap: POINTER TO CMOCharMapTable,
filePageFF: PageNumber],
ENDCASE],
ENDCASE];

BufferState: TYPE = {empty, clean, dirty};

CharCache: TYPE = RECORD
-- To access the char "i" of message "msg":
-- "i" must be in [first .. free)
-- Then use: msg.buffer[floor + i - first]
[first, -- first char on page.
free: CharIndex, -- first char off page.
floor: VMDefs.PageByteIndex]; -- Space at beginning of buffer.

TOC: TYPE = TOC VirtualObject;
TOCHandle: TYPE = POINTER TO TOC;

TOCIndex: TYPE = CARDINAL; -- Note: 0 not used.

TOCRunRange: TYPE = RECORD [low, high: TOCIndex];
TOCRunArrayBlk: TYPE = ARRAY [0 .. 0) OF TOCRunRange;
TOCRunArray: TYPE = POINTER TO TOCRunArrayBlk;

TOCFixedPartPtr: TYPE = POINTER TO TOCFixedPart;

TOCFixedPart: TYPE = RECORD
[changed,
deleted,
seen,
bogus: BOOLEAN, --this entry spans bad data--
mark: CHARACTER,
firstPage: PageNumber,
firstByte: PageByteIndex,
bugTrap: [0 .. 127], --←bugTrapValue, Early warning device.
offsetToHeader,
textLength: CharIndex]; -- in bytes

bugTrapValue: [0 .. 127] = 131B; -- for "quick fail".

TOCPageHeaderBlk: TYPE = RECORD -- One of these at beginning of each toc file page.
[numberOfEntries: CARDINAL,
garbageDetector: CARDINAL]; -- gets TOCType
TOCPageHeader: TYPE = POINTER TO TOCPageHeaderBlk;

TOCPageTable: TYPE = ARRAY[0 .. TOCPageTableSize) OF TOCPageTableEntry;

TOCPageTableEntry: TYPE = RECORD [firstTOCIndex: TOCIndex];

HardTOCAddress: TYPE = POINTER; -- to a TOC entry.

VMOType: TYPE = {DM, CM};

DMListBlk: TYPE = RECORD
[next: DMList,
dm: DisplayMessagePtr];
DMList: TYPE = POINTER TO DMListBlk;

CMOCharMapTableSize: CARDINAL = 128; -- Max pages/CMO file.

CMOMaxCharPerPage: CARDINAL = 511; -- Waste 1 to save table space.

VirtualMessagePtr: TYPE = POINTER TO VirtualMessageObject;
VirtualMessageObject: TYPE = VMO VirtualObject;
DisplayMessagePtr: TYPE = POINTER TO DisplayMessageObject;
DisplayMessageObject: TYPE = DM VirtualMessageObject;
ComposedMessagePtr: TYPE = POINTER TO ComposedMessageObject;
ComposedMessageObject: TYPE = CM VirtualMessageObject;

MessageRange: TYPE = RECORD
[start: CharIndex,
end: CharIndex,
message: VirtualMessagePtr];

CMOCharMapTable: TYPE = ARRAY PageNumber[0 .. CMOCharMapTableSize) OF
RECORD
[count: CARDINAL[0 .. CMOMaxCharPerPage], -- Good chars/page.
page: PageNumber[0 .. CMOCharMapTableSize)]; -- corresp fpn.

GetMtPtrState: TYPE = {new, active, inactive};

CleanupTOCOption: TYPE = {resetChanges, dontResetChanges, delete};

LoadTOCOption: TYPE = {new, old};

END. -- of VirtualMgrDefs --