-- file ProtoHashTab.mesa
-- last edited by Satterthwaite, December 29, 1982 12:45 pm

DIRECTORY
  Alloc: TYPE USING [
    Base, Handle, Index, Notifier, Selector, AddNotify, DropNotify, Trim, Words],
  BcdOps: TYPE USING [NameString],
  Environment: TYPE USING [charsPerWord],
  HashOps: TYPE USING [],
  HashTypes: TYPE USING [HTIndex, htNull, hvLength],
  Inline: TYPE USING [BITAND, BITXOR],
  Strings: TYPE USING [
    String, SubString, SubStringDescriptor,
    AppendChar, AppendSubString, EqualSubStrings, EquivalentSubStrings];

HashTab: PROGRAM 
    IMPORTS Alloc, Inline, Strings 
    EXPORTS HashOps={
  OPEN HashTypes;

 -- types
 
  HTRecord: TYPE~RECORD [
    link: HTIndex,
    offset: CARDINAL];  -- gives both loc and length of name in packed string 

  HVIndex: TYPE~NAT[0..hvLength);

  SubString: TYPE~Strings.SubString;
  SubStringDescriptor: TYPE~Strings.SubStringDescriptor;

 -- tables defining the current symbol table

  table: Alloc.Handle ← NIL;
  htType, ssType: Alloc.Selector;

  hashVec: LONG POINTER TO ARRAY HVIndex OF HTIndex;
  ht: LONG DESCRIPTOR FOR ARRAY --HTIndex-- OF HTRecord;

  htb: Alloc.Base;         	-- hash table
  ssb: BcdOps.NameString;  -- id string

  UpdateBases: Alloc.Notifier~{
    htb ← base[htType];  ssb ← base[ssType];
    hashVec ← htb;
    ht ← DESCRIPTOR[htb + hashVec↑.LENGTH*HTIndex.SIZE, ht.LENGTH]};

  AllocateHash: PROC RETURNS [hti: HTIndex]~{
    next: Alloc.Index~table.Words[htType, HTRecord.SIZE];
    hti ← ht.LENGTH;
    ht ← DESCRIPTOR[ht.BASE, ht.LENGTH+1];
    ht[hti] ← HTRecord[link~htNull, offset~ssb.string.length+1];
    RETURN [hti-1]};


 -- variables for building the symbol string

  ssw: Alloc.Index;

  initialized: BOOL←FALSE;

  Initialize: PUBLIC PROC [ownTable: Alloc.Handle, htTable, ssTable: Alloc.Selector]~{ 
    IF initialized THEN Finalize[];  hashVec ← NIL;
    table ← ownTable;  htType ← htTable; ssType ← ssTable;
    table.AddNotify[UpdateBases];
    Reset[];
    initialized ← TRUE};
  
  Finalize: PUBLIC PROC~{
    table.DropNotify[UpdateBases]; table ← NIL;
    initialized ← FALSE};
  
  Reset: PUBLIC PROC~{
    nullSS: SubStringDescriptor←[base~"null"L, offset~0, length~0];
    table.Trim[ssType, 0];
    table.Trim[htType, 0];
    [] ← table.Words[htType, hvLength*HTIndex.SIZE];
    hashVec ← htb;
    hashVec↑ ← ALL[htNull];
    ht ← DESCRIPTOR[htb + hashVec↑.LENGTH*HTIndex.SIZE, 0];
    ssw ← table.Words[ssType, StringBody[0].SIZE] + StringBody[0].SIZE;
    ssb.string ← [length~0, maxlength~0, text~];
    [] ← AllocateHash[];
    IF EnterString[@nullSS] # htNull THEN ERROR};
  

 -- hash entry creation

  EnterString: PUBLIC PROC [s: SubString] RETURNS [hti: HTIndex]~{
    hvi: HVIndex~HashValue[s];
    desc: SubStringDescriptor←[base~@ssb.string, offset~, length~];
    charsPerWord: CARDINAL~Environment.charsPerWord;
    offset, length, nw: CARDINAL;
    ssi: Alloc.Index;
    FOR hti ← hashVec[hvi], ht[hti].link UNTIL hti = htNull DO
      SubStringForHash[@desc, hti];
      IF Strings.EqualSubStrings[s, @desc] THEN RETURN [hti];
      ENDLOOP;
    offset ← ssb.string.length;  length ← s.length + 1;
    nw ← (offset + length+ (charsPerWord-1) - ssb.string.maxlength)/charsPerWord;
    IF nw # 0 THEN {  
      IF (ssi ← table.Words[ssType, nw]) # ssw THEN ERROR;
      ssw ← ssw + nw;
      ssb.string ← [
	text~,
	length~ssb.string.length, maxlength~ssb.string.maxlength+nw*charsPerWord]};
    Strings.AppendChar[@ssb.string, LOOPHOLE[s.length, CHAR]];
    Strings.AppendSubString[@ssb.string, s];
    hti ← AllocateHash[];
    ht[hti].link ← hashVec[hvi];
    hashVec[hvi] ← hti;
    RETURN};

 -- the following copied from SymbolPack.mesa

  HashValue: PROC [s: SubString] RETURNS [HVIndex]~{
    CharBits: PROC [CHAR, WORD] RETURNS [WORD]~LOOPHOLE[Inline.BITAND];
    mask: WORD~0DFh; -- masks out ASCII case shifts
    n: CARDINAL~s.length;
    b: Strings.String~s.base;
    v: WORD←CharBits[b[s.offset], mask]*7Fh;
    IF n # 0 THEN v ← v + CharBits[b[s.offset+(n-1)], mask];
    RETURN [Inline.BITXOR[v, n*0Fh] MOD hashVec↑.LENGTH]};

  FindString: PUBLIC PROC [s: SubString] RETURNS [hti: HTIndex]~{
    desc: SubStringDescriptor;
    ss: SubString~@desc;
    FOR hti ← hashVec[HashValue[s]], ht[hti].link UNTIL hti = htNull DO
      SubStringForHash[ss, hti];
      IF Strings.EqualSubStrings[s, ss] THEN RETURN;
      ENDLOOP;
    RETURN [htNull]};

  FindEquivalentString: PUBLIC PROC [s: SubString] RETURNS [hti: HTIndex]~{
    desc: SubStringDescriptor;
    ss: SubString~@desc;
    FOR hti ← hashVec[HashValue[s]], ht[hti].link UNTIL hti = htNull DO
      SubStringForHash[ss, hti];
      IF Strings.EquivalentSubStrings[s, ss] THEN RETURN;
      ENDLOOP;
    RETURN [htNull]};

  SubStringForHash: PUBLIC PROC [s: SubString, hti: HTIndex]~{
    s.base ← @ssb.string;
    IF hti = htNull THEN s.offset ← s.length ← 0
    ELSE {s.offset ← ht[hti].offset; s.length ← ssb.size[ht[hti].offset]}};

  }.