-- RefTextImpl.mesa, Operations on mutable garbage-collected strings (REF TEXT).
-- MBrown on September 17, 1983 5:34 pm
-- Russ Atkinson on June 7, 1982 6:01 pm
-- Paul Rovner on August 8, 1983 11:39 am

DIRECTORY
RefText USING [BoundsFault, ErrorCode, InlineAppendChar],
Rope USING [ROPE, Map];

RefTextImpl: CEDAR MONITOR
IMPORTS RefText, Rope
EXPORTS RefText
= BEGIN
ROPE: TYPE = Rope.ROPE;

ReserveChars: PUBLIC PROC [to: REF TEXT, nChars: NAT] RETURNS [REF TEXT] = {
newMinLength: INT = INT[to.length] + INT[nChars]; -- ERROR PointerFault if to=NIL
IF newMinLength <= to.maxLength THEN RETURN [to];
IF newMinLength > NAT.LAST OR to.length > to.maxLength THEN RefText.BoundsFault[];
{ expandBy: NAT = MAX[16, to.maxLength, nChars];
newLength: NAT =
IF expandBy > NAT.LAST-to.maxLength THEN NAT.LAST ELSE expandBy+to.maxLength;
newText: REF TEXT = NEW[TEXT[newLength]];
FOR i: NAT IN [0..to.length) DO newText[i] ← to[i] ENDLOOP;
newText.length ← to.length;
RETURN [newText];
}
};

Append: PUBLIC PROC [
to: REF TEXT, from: REF READONLY TEXT, start: NAT, len: NAT]
RETURNS [REF TEXT] = {
j: NAT ← to.length; -- ERROR PointerFault if to=NIL
IF start > from.length OR j > to.maxLength THEN RefText.BoundsFault[];
len ← MIN[from.length-start, len];
IF len > to.maxLength-j THEN to ← ReserveChars[to, len];
FOR i: NAT IN [start..start+len) DO
to[j] ← from[i];
j ← j + 1;
ENDLOOP;
to.length ← j;
RETURN [to];
};

AppendChar: PUBLIC PROC [to: REF TEXT, from: CHAR] RETURNS [REF TEXT] = {
IF to.length >= to.maxLength THEN { -- ERROR PointerFault if to=NIL
to ← ReserveChars[to, 1];
};
to[to.length] ← from;
to.length ← to.length + 1;
RETURN [to];
};

AppendRope: PUBLIC PROC [to: REF TEXT, from: ROPE, start: NAT, len: NAT]
RETURNS [REF TEXT] = {
Put: PROC [c: CHAR] RETURNS [quit: BOOL] = {
to ← RefText.InlineAppendChar[to, c];
RETURN [quit: FALSE]
};
[] ← Rope.Map[base: from, start: start, len: len, action: Put];
RETURN [to];
};

New: PUBLIC PROC [nChars: NAT] RETURNS [REF TEXT] = {
RETURN [NEW[TEXT[nChars]]] };

TextIndex: TYPE = [0..1];
TextMaxLength: ARRAY TextIndex OF NAT = [100, 512];
NTextsToAllocate: ARRAY TextIndex OF NAT = [8, 2];
-- There are 10 overhead words per text (4 in the TEXT, 6 in the LIST), plus
--6 overhead words per TextIndex value.
available: ARRAY TextIndex OF LIST OF REF TEXTALL[NIL];
reserved: ARRAY TextIndex OF LIST OF REF TEXTALL[NIL];

InterestingQuantity: TYPE = {obtainCalled, nCharsTooLarge, availEmpty};
Counts: ARRAY InterestingQuantity OF INTALL[0];
Bump: INTERNAL PROC [q: InterestingQuantity] = INLINE { Counts[q] ← Counts[q] + 1 };

Error: PUBLIC ERROR [ec: RefText.ErrorCode] = CODE;

ObtainScratch: PUBLIC ENTRY PROC [nChars: NAT] RETURNS [REF TEXT] = {
i: TextIndex ← 0;
avail: LIST OF REF TEXT;
Bump[obtainCalled];
FOR i IN [0 .. LAST[TextIndex]] DO
IF nChars <= TextMaxLength[i] THEN EXIT;
REPEAT FINISHED => {
Bump[nCharsTooLarge]; RETURN [New[nChars]] }; -- too large for pool
ENDLOOP;
IF (avail ← available[i].rest) = NIL THEN {
-- Give last element of reserved[i] to the collector, allocate a new one, and put it in avail.
r: LIST OF REF TEXT;
Bump[availEmpty];
r ← reserved[i];
UNTIL r.rest.rest = NIL DO r ← r.rest ENDLOOP;
avail ← r.rest; r.rest ← NIL; avail.first ← New[TextMaxLength[i]] };
-- Move first element of available[i] to front of reserved[i].
available[i].rest ← avail.rest;
avail.rest ← reserved[i].rest; reserved[i].rest ← avail;
IF avail.first.length # 0 THEN RETURN WITH ERROR Error[clientModifiedReleasedText];
RETURN [avail.first] };

ReleaseScratch: PUBLIC ENTRY PROC [t: REF TEXT] = {
i: TextIndex ← 0;
r, l: LIST OF REF TEXT;
IF t = NIL THEN RETURN;
FOR i IN [0 .. LAST[TextIndex]] DO
IF t.maxLength = TextMaxLength[i] THEN EXIT;
REPEAT FINISHED => RETURN; -- no good for pool
ENDLOOP;
r ← reserved[i];
WHILE (l ← r.rest) # NIL DO
IF l.first = t THEN {
r.rest ← l.rest;
l.rest ← available[i].rest; available[i].rest ← l;
t.length ← 0;
EXIT };
r ← l;
ENDLOOP;
};

InitializePool: PROC [] = {
FOR i: TextIndex IN TextIndex DO
reserved[i] ← CONS[NIL, NIL];
-- construct a list of NTextsToAllocate[i]+1 nodes.
FOR j: NAT IN [0..NTextsToAllocate[i]] DO
l: LIST OF REF TEXTCONS[NIL, available[i]];
available[i] ← l;
ENDLOOP;
ENDLOOP;
FOR i: TextIndex IN TextIndex DO
-- allocate a TEXT of maxLength TextMaxLength[i] for each node but the first.
l: LIST OF REF TEXT ← available[i].rest;
WHILE l # NIL DO
l.first ← New[TextMaxLength[i]];
l ← l.rest;
ENDLOOP;
ENDLOOP;
};

InitializePool[];

END.