-- RecipientList.mesa
-- Edited by Levin, October 16, 1980 5:07 PM
-- Edited by Schroeder, November 19, 1980 11:00 AM
-- Edited by Brotz, Wednesday Nov. 5, 1980 5:31 pm PST

DIRECTORY
exD: FROM "ExceptionDefs",
gsD: FROM "GlobalStorageDefs",
Inline,
LaurelSendDefs,
ovD: FROM "OverviewDefs",
Storage,
vmD: FROM "VirtualMgrDefs";

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

BEGIN

-- Declarations for recipient list data structures

pageSize: CARDINAL = 256;

Byte: TYPE = [0..255];

Recipient: TYPE = RECORD[index: ovD.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.ComposeMessagePtr ← NIL;

longestRecipientSeen: CARDINAL;
recipients: CARDINAL;

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


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


FlushRecipientList: PUBLIC PROCEDURE =
-- releases the storage for recipient names.
BEGIN
p: Overflow;
UNTIL overflow = NIL DO
p ← overflow.link;
gsD.ReturnMemoryPages[1, 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: ovD.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: ovD.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: ovD.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 --

Insert: PROCEDURE[ch: CHARACTER] =
BEGIN
IF vmD.InsertMessageChar[stringSpace, ch] ~= ovD.ok THEN exD.SysBug[];
END; -- of Insert --

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 ← gsD.GetMemoryPages[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];
Insert[length.left]; Insert[length.right];
FOR i IN [0 .. s.length) DO Insert[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: ovD.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 --