-- RefTextImpl.mesa, Operations on mutable garbage-collected strings (REF TEXT).
-- MBrown on May 2, 1982 11:13 pm
-- Russ Atkinson, June 7, 1982 6:01 pm

DIRECTORY
    RefText,
    Runtime USING [BoundsFault],
    SafeStorage USING [NewZone];

RefTextImpl: CEDAR MONITOR
  IMPORTS Runtime, SafeStorage
  EXPORTS RefText
  = BEGIN

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

  z: ZONE = SafeStorage.NewZone[];

  New: PUBLIC PROC [nChars: NAT] RETURNS [REF TEXT] = {
    RETURN [z.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 TEXT ← ALL[NIL];
  reserved: ARRAY TextIndex OF LIST OF REF TEXT ← ALL[NIL];

  InterestingQuantity: TYPE = {obtainCalled, nCharsTooLarge, availEmpty};
  Counts: ARRAY InterestingQuantity OF INT ← ALL[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;
    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 TEXT ← CONS[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.