-- AtomsPrivateImpl.mesa
-- Simple hash-table implementation of CedarPrivateAtoms.MakeAtom
-- Last Modified On 8-Mar-82 10:30:26 by Paul Rovner 

DIRECTORY
        Inline,
        Rope USING[ROPE, Text],
        RopeInline USING[NewText, InlineFlatten],
        RTBasic USING[Type],
        RTLoader USING[AcquireBasicLiterals],
        RTTypesBasic USING[GetCanonicalType, Type],
        RTTypesBasicPrivate USING[NotifyAtomRecType],
        SafeStorage USING[NewZone],
        AtomsPrivate;

AtomsPrivateImpl: MONITOR -- protects the ATOM dictionary
  IMPORTS Inline, RopeInline, RTLoader, RTTypesBasic, RTTypesBasicPrivate, SafeStorage
  EXPORTS AtomsPrivate  
  SHARES Rope
  
= BEGIN OPEN AtomsPrivate;

-- CONSTANTS
HashSize: INTEGER = 4093;  -- prime

-- TYPES
String: TYPE = LONG POINTER TO READONLY TEXT;
Atom: TYPE = REF AtomRec;
AtomDictionaryIndex: TYPE = [0..HashSize);
AAtomDict: TYPE = ARRAY AtomDictionaryIndex OF Atom;
ComparisonOutcome: TYPE = {less, greater, equal};

-- VARIABLES
atomDictionary: REF AAtomDict ← NEW[AAtomDict ← ALL[NIL]];
atomZone: ZONE = SafeStorage.NewZone[sr: quantized];

-- PROCEDURES

GetAtom: PUBLIC PROC[pName: Rope.ROPE] RETURNS[ATOM] =
  { t: Rope.Text = RopeInline.InlineFlatten[pName];
    this, prev: Atom;
    hash: AtomDictionaryIndex;
    l: CARDINAL;
    
    IF t = NIL THEN RETURN[NIL];
    l ← t.length;
    IF l MOD 2 = 1  -- fill residue char with 0
     THEN { w: CARDINAL = Inline.BITSHIFT[t[l-1], 8];
            p: LONG POINTER TO UNSPECIFIED;
            l ← l + 1;
            p ← LOOPHOLE[t, LONG POINTER TO UNSPECIFIED] + SIZE[TEXT[0]] + l/2 - 1;
            IF p↑ # w THEN p↑ ← w};
    [this, prev, hash] ← LookUpAtom[t];
    IF this # NIL THEN RETURN[LOOPHOLE[this]];

  -- here if not found. Make a new ATOM.
    this ← atomZone.NEW[AtomRec ← [pName: t]];
    InsertAtom[atom: this, prev: prev, hash: hash];
    RETURN[LOOPHOLE[this]]};

UnsafeMakeAtom: PUBLIC PROC[pName: String] RETURNS[ATOM] =
  { this, prev: Atom;
    hash: AtomDictionaryIndex;
    l: CARDINAL;
    oddLength: BOOLEAN ← FALSE;
    rt: Rope.Text;
    
    IF pName = NIL THEN RETURN[NIL];
    l ← pName.length;
    IF l MOD 2 = 1  -- fill residue char with 0 (NOTE violation of READONLY!!!)
     THEN { w: CARDINAL = Inline.BITSHIFT[pName[l-1], 8];
            p: LONG POINTER TO UNSPECIFIED;
            oddLength ← TRUE;
            l ← l + 1;
            p ← LOOPHOLE[pName + SIZE[TEXT[0]] + l/2 - 1];
            IF p↑ # w THEN p↑ ← w -- NOTE violation of READONLY!!!
          };
    [this, prev, hash] ← LookUpAtom[LOOPHOLE[pName, Rope.Text]];
    IF this # NIL THEN RETURN[LOOPHOLE[this]];
  -- here if not found. Make a new ATOM.
    rt ← RopeInline.NewText[l];
    rt.length ← pName.length;
    FOR i: CARDINAL IN [0..pName.length) DO rt[i] ← pName[i] ENDLOOP;
    IF oddLength THEN {rt.length ← l; rt[l-1] ← LOOPHOLE[0]; rt.length ← l-1};
    this ← atomZone.NEW[AtomRec ← [pName: rt]];
    InsertAtom[atom: this, prev: prev, hash: hash];
    RETURN[LOOPHOLE[this]]};

EnumerateAtoms: PUBLIC PROC[ callee: PROC[ATOM] RETURNS[stop: BOOLEAN] ]
     RETURNS[ATOM--NIL if was not stopped--] =
  { FOR atom: ATOM ← Next[NIL], Next[atom]
     UNTIL atom = NIL DO IF callee[atom] THEN RETURN[atom]; ENDLOOP;
    RETURN[NIL]};

Next: ENTRY PROC[atom: ATOM ← NIL] RETURNS[ATOM] = INLINE
  { a: Atom = LOOPHOLE[atom];
    IF a # NIL AND a.link # NIL THEN RETURN[a.link];
    FOR adi: CARDINAL ← (IF a = NIL THEN 0 ELSE (Hash[LOOPHOLE[a.pName]] + 1)), adi + 1
     UNTIL adi = HashSize
      DO IF atomDictionary[adi] # NIL THEN RETURN[LOOPHOLE[atomDictionary[adi]]];
     ENDLOOP;
    RETURN[NIL]};

        -- returns [NIL,ptr to last atom, hash index] if not found
LookUpAtom: ENTRY PROC[s: Rope.Text]
      RETURNS[atom: Atom, prev: Atom, hash: AtomDictionaryIndex] =
INLINE
  { prev ← NIL;
    hash ← Hash[s];
    FOR atom ← atomDictionary[hash], LOOPHOLE[atom.link] UNTIL atom = NIL
      DO
        SELECT CompareStrings[s, atom.pName] FROM
          equal => RETURN;
          greater => {atom ← NIL; EXIT};
          ENDCASE;
        prev ← atom;
       ENDLOOP};

InsertAtom: ENTRY PROC[atom: Atom, prev: Atom, hash: AtomDictionaryIndex] = INLINE
  { IF prev = NIL
     THEN {atom.link ← LOOPHOLE[atomDictionary[hash]]; atomDictionary[hash] ← atom}
     ELSE {atom.link ← prev.link; prev.link ← LOOPHOLE[atom]}};

CompareStrings: INTERNAL PROC[leftString, rightString: Rope.Text]
        RETURNS[ComparisonOutcome] = INLINE
  { lLength: CARDINAL = (leftString.length+1)/2;
    rLength: CARDINAL = (rightString.length+1)/2;
    lp: LONG POINTER TO CARDINAL = LOOPHOLE[leftString, LONG POINTER TO CARDINAL] + SIZE[TEXT[0]];
    rp: LONG POINTER TO CARDINAL = LOOPHOLE[rightString, LONG POINTER TO CARDINAL] + SIZE[TEXT[0]];
    FOR i: CARDINAL IN [0..MIN[lLength, rLength]) DO
        lw: CARDINAL = (lp + i)↑;
        rw: CARDINAL = (rp + i)↑;
        IF lw < rw THEN RETURN[less] ELSE IF lw > rw THEN RETURN[greater];
     ENDLOOP;
    RETURN[IF lLength = rLength THEN equal ELSE
                           IF lLength < rLength THEN less ELSE greater]};


        -- stolen from TexHash.mesa
Hash: INTERNAL PROC[s: Rope.Text] RETURNS[AtomDictionaryIndex] = INLINE
  { acc: CARDINAL ← 0;
    FOR i: CARDINAL IN [0..MIN[7, s.length]) DO acc ← 7*acc+LOOPHOLE[s[i], CARDINAL];
     ENDLOOP;
    RETURN[acc MOD HashSize]};

-- START HERE

atomRecType: RTTypesBasic.Type = RTTypesBasic.GetCanonicalType[CODE[AtomRec]];

RTTypesBasicPrivate.NotifyAtomRecType[atomRecType];
RTLoader.AcquireBasicLiterals[atomRecType];

END.