-- RecipientList.mesa
-- Edited by Levin, October 16, 1980 5:07 PM
-- Edited by Schroeder, November 19, 1980 11:00 AM
-- Edited by Brotz, November 12, 1981 3:02 PM

DIRECTORY
Inline USING [BITOR, BITXOR],
LaurelSendDefs USING [],
Storage USING [FreePages, FreeString, Pages, String],
vmD: FROM "VirtualMgrDefs" USING [AllocateComposedMessageObject, CharIndex,
ComposedMessagePtr, FreeVirtualMessageObject, GetMessageChar, GetMessageSize,
InitComposedMessage, InsertMessageChar, StartMessageInsertion, StopMessageInsertion];

RecipientList: PROGRAM
IMPORTS Inline, Storage, vmD
EXPORTS LaurelSendDefs =

BEGIN

-- Declarations for recipient list data structures

pageSize: CARDINAL = 256;
Byte: TYPE = [0 .. 255];

Recipient: TYPE = RECORD [index: vmD.CharIndex, link: RecipientHandle];
RecipientHandle: TYPE = POINTER TO Recipient;

maxOverflow: CARDINAL = (pageSize - pointerSize) / SIZE[Recipient];
pointerSize: CARDINAL = 1; -- hack: SIZE[Overflow] doesn’t work
maxBins: CARDINAL = 119; -- assumed to be less than maxOverflow

BinNumber: TYPE = [0 .. maxBins);
OverflowSlot: TYPE = [0 .. maxOverflow);

Bins: TYPE = ARRAY BinNumber OF Recipient;

OverflowPage: TYPE = RECORD
[slots: ARRAY OverflowSlot OF Recipient,
link: Overflow];
Overflow: TYPE = POINTER TO OverflowPage;

LengthStructure: TYPE = MACHINE DEPENDENT RECORD
[SELECT OVERLAID * FROM
chars => [left, right: CHARACTER],
word => [val: CARDINAL],
ENDCASE];

bins: POINTER TO Bins;
overflow: Overflow;
overflowFF: OverflowSlot;

stringSpace: vmD.ComposedMessagePtr ← NIL;

longestRecipientSeen: CARDINAL;
recipients: CARDINAL;

emptyBin: Recipient = [index: LAST[vmD.CharIndex], link: NIL];


InitializeRecipientList: PUBLIC PROCEDURE =
-- initializes the storage of recipient names.
BEGIN
i: BinNumber;
p: POINTER;
bins ← overflow ← p ← Storage.Pages[1];
FOR i IN BinNumber DO bins[i] ← emptyBin; ENDLOOP;
overflowFF ← LAST[BinNumber]+1;
overflow.link ← NIL;
longestRecipientSeen ← recipients ← 0;
stringSpace ← vmD.AllocateComposedMessageObject[];
vmD.InitComposedMessage[stringSpace, NIL];
END; -- of InitializeRecipientList --


FlushRecipientList: PUBLIC PROCEDURE =
-- releases the storage for recipient names.
BEGIN
p: Overflow;
UNTIL overflow = NIL DO
p ← overflow.link;
Storage.FreePages[overflow];
overflow ← p;
ENDLOOP;
vmD.FreeVirtualMessageObject[stringSpace];
END; -- of FlushRecipientList --


EnumerateRecipientList: PUBLIC PROCEDURE [space: CARDINAL,
proc: PROCEDURE[s: STRING] RETURNS[BOOLEAN]]
RETURNS [outcome: BOOLEAN] =
-- runs through the recipient list, calling ’proc’ with each one
BEGIN
s: STRING;
i: BinNumber;
j: CARDINAL;
index: vmD.CharIndex;
recipient: RecipientHandle;
outcome ← FALSE;
IF recipients ~= 0 THEN
BEGIN
s ← Storage.String[longestRecipientSeen+ space];
FOR i IN BinNumber DO
recipient ← @bins[i];
IF recipient↑ ~= emptyBin THEN
DO
s.length ← GetStringLength[index ← recipient.index];
index ← index + 2;
FOR j IN [0 .. s.length) DO
s[j] ← vmD.GetMessageChar[stringSpace, index+j];
ENDLOOP;
IF (outcome ← proc[s ! UNWIND => Storage.FreeString[s]]) THEN
GO TO Done;
IF (recipient ← recipient.link) = NIL THEN EXIT;
ENDLOOP;
REPEAT
Done => NULL;
ENDLOOP;
Storage.FreeString[s];
END;
END; -- of EnumerateRecipientList --


InsertRecipientInList: PUBLIC PROCEDURE [s: STRING] =
-- inserts ’s’ in the list of recipients, checking first to ensure it is not already present.
BEGIN
recipient: RecipientHandle;
insertionPoint: vmD.CharIndex;
i: CARDINAL;

LowerCase: PROCEDURE[ch: CHARACTER] RETURNS [CHARACTER] = INLINE
BEGIN
caseBit: WORD = 40B;
RETURN[Inline.BITOR[ch, caseBit]]
END; -- of LowerCase --

Hash: PROCEDURE RETURNS [bin: BinNumber] =
BEGIN
HashStructure: TYPE =
MACHINE DEPENDENT RECORD[
SELECT OVERLAID * FROM
chars => [length: LengthStructure, s0, s1: CHARACTER],
words => [w1, w2: WORD],
ENDCASE];
h: HashStructure;
h ← SELECT s.length FROM
0 => [chars[length: [word[val: 0]], s0: 0C, s1: 0C]],
1 => [chars[length: [word[val: 1]], s0: LowerCase[s[0]], s1: 0C]],
ENDCASE =>
[chars[length: [word[val: s.length]], s0: LowerCase[s[0]], s1: LowerCase[s[1]]]];
RETURN[Inline.BITXOR[h.w1, h.w2] MOD maxBins]
END; -- of Hash --

EquivalentRecipient: PROCEDURE[index: vmD.CharIndex] RETURNS [BOOLEAN] =
BEGIN
i: Byte;
IF GetStringLength[index] ~= s.length THEN RETURN[FALSE];
index ← index + 2; -- skip over length field
FOR i IN [0 .. s.length) DO
IF LowerCase[vmD.GetMessageChar[stringSpace, index + i]] ~= LowerCase[s[i]] THEN
RETURN[FALSE];
ENDLOOP;
RETURN[TRUE]
END; -- of EquivalentRecipient --

recipient ← @bins[Hash[]];
IF recipient↑ ~= emptyBin THEN -- bin non-empty
BEGIN
DO
IF EquivalentRecipient[recipient.index] THEN RETURN; -- s duplicates existing entry
IF recipient.link = NIL THEN EXIT ELSE recipient ← recipient.link;
ENDLOOP;
recipient.link ← @overflow.slots[overflowFF];
IF overflowFF = LAST[OverflowSlot] THEN
BEGIN
p: POINTER;
newOverflow: Overflow;
newOverflow ← p ← Storage.Pages[1];
newOverflow.link ← overflow;
overflow ← newOverflow;
overflowFF ← FIRST[OverflowSlot];
END
ELSE overflowFF ← overflowFF+1;
recipient ← recipient.link;
END;
BEGIN
OPEN vmD;
length: LengthStructure = [word[val: s.length]];
insertionPoint ← GetMessageSize[stringSpace];
StartMessageInsertion[stringSpace, insertionPoint];
InsertMessageChar[stringSpace, length.left];
InsertMessageChar[stringSpace, length.right];
FOR i IN [0 .. s.length) DO InsertMessageChar[stringSpace, s[i]]; ENDLOOP;
StopMessageInsertion[stringSpace];
END;
recipient↑ ← [index: insertionPoint, link: NIL];
longestRecipientSeen ← MAX [longestRecipientSeen, s.length];
recipients ← recipients + 1;
END; -- of InsertRecipientInList --


GetRecipients: PUBLIC PROCEDURE RETURNS [CARDINAL] =
{RETURN[recipients]};


GetStringLength: PROCEDURE [index: vmD.CharIndex] RETURNS [CARDINAL] =
-- extracts the string length for the recipient beginning at ’index’.
BEGIN
length: LengthStructure;
length.left ← vmD.GetMessageChar[stringSpace, index];
length.right ← vmD.GetMessageChar[stringSpace, index + 1];
RETURN[length.val]
END; -- of GetStringLength --


END. -- of RecipientList --