(* Cross reference as above, but using a hash table instead
  of a binary tree to store the words encountered. *)

(***************************************************************************************)
(* MODULE crossref--This module is a cross reference generator.  A file which is       *)
(* specified by the user is read and a cross reference table of all the words is built.*)
(* A word consists of a letter and any combination of letters and digits thereafter    *)
(* until a separator, i.e. blanks, ends of lines, special characters, is read.  Quotes *)
(* and comments are ignored.  The cross reference table is a hash table which stores   *)
(* the words and the number of the line on which the word appeared.  When the table    *)
(* is generated, its contents are printed on the screen.  This program is the MODULA-2 *)
(* translation of the PASCAL program 11.2.                                             *)



MODULE crossref;

FROM InOut IMPORT                              (* get necessary i/o files             *)
    Read, WriteString, Write, WriteLn, WriteCard, OpenInput, CloseInput, Done;
FROM Storage IMPORT                            (* get NEW procedure                   *)
    ALLOCATE;

CONST  ff         = 14c;                       (* clear the screen constant           *)
      eol        = 36c;                       (* end of line constant                *)
      quote      = 42c;                       (* double quote mark                   *)
      wordlen    = 10;                        (* maximum word length                 *)
      numperline = 8;                         (* no of line numbers per display line *)
      digpernum  = 6;                         (* maximum number of digits per number *)
      maxline    = 9999;                      (* maximum number of lines in file     *)
      prime      = 997;                       (* number of hash table entries        *)
      blank      = " ";                       (* blank character constant            *)
      filetype   = "TEXT";                    (* default filename extension          *)

TYPE   index    = [0..prime];                  (* range of hash table                 *)
      alfa     = ARRAY[1..wordlen] OF CHAR;   (* word string                         *)
      relation = (equal,less,greater);        (* used for string comparisons         *)
      itemref  = POINTER TO item;
      word     = RECORD                       (* hash table entries                  *)
                   key        : alfa;         (* word found in text                  *)
                   first, last: itemref;      (* pointer to cross reference list     *)
                   follow     : index         (* hash number of next entry           *)
                 END;
      item     = RECORD                       (* cross reference list                *)
                   lineno: [0..maxline];      (* line number word occurred on        *)
                   next  : itemref            (* pointer to next item in list        *)
                 END;

VAR    i        : index;                       (* index to hash table                 *)
      top      : index;                       (* current hash table entry            *)
      idcntr   : INTEGER;                     (* index to id array                   *)
      id       : alfa;                        (* contains the current word           *)
      free     : alfa;                        (* blank word                          *)
      table    : ARRAY [0..prime] OF word;    (* hash table                          *)
      current  : CARDINAL;                    (* current line number                 *)
      ch       : CHAR;                        (* current character                   *)
      tablefull: BOOLEAN;                     (* flags if table gets full            *)


PROCEDURE compare(j,k:alfa):relation;
(**************************************************************************************)
(* This function compares the two strings j and k to see how they compare.  If j = k  *)
(* then the value of equal is returned.  If j < k then the value is less is returned  *)
(* and if j > k tthen the value greater is returned.                                  *)
(**************************************************************************************)


VAR compvalue : relation;                     (* function value                       *)
   through   : BOOLEAN;                      (* flags when through with loop         *)
   i         : INTEGER;                      (* array index                          *)

BEGIN
 compvalue := equal;                         (* initializations                      *)
 i         := 1;
 through   := FALSE;
 WHILE (NOT through) & (i <= 10) DO          (* compare the two strings              *)
   IF CAP(j[i]) = CAP(k[i]) THEN
     INC(i)
   ELSE
     through := TRUE;
     IF CAP(j[i]) < CAP(k[i]) THEN
       compvalue := less
     ELSE
       compvalue := greater
     END
   END
 END;
 RETURN compvalue
END compare;



PROCEDURE search():BOOLEAN;
(**************************************************************************************)
(* This function searches the hash table to see if an entry for the current word      *)
(* already exists.  This is done by calculating the hash value of the current word.   *)
(* If no entry exists at the hash value slot in the table, then an entry is created   *)
(* for that word and an item list created.  If the entry already exists, then only a  *)
(* new item node is created and added to the item list.  If the hash slot is already  *)
(* occupied by a different word, then the hash table is searched for an empty slot.   *)
(* If one is found, then it is filled in with the current word, etc.  If no empty     *)
(* slot can be found, then a message is printed indicating table overflow and the     *)
(* procedure quits, returning the value of FALSE.                                     *)
(**************************************************************************************)

VAR  hash         : CARDINAL;                   (* contains hash value                *)
    addvalue     : index;                      (* contains search increment value    *)
    done         : BOOLEAN;                    (* flags when finished                *)
    full         : BOOLEAN;                    (* flags if table is full             *)
    x            : itemref;                    (* pointer to current item list       *)
    compvalue    : relation;                   (* contains result of compare         *)


BEGIN
 full     := FALSE;                            (* initialize                         *)
 hash     := 0;
 done     := FALSE;
 addvalue := 1;
 NEW(x);                                       (* get a new item list node           *)
 x^.lineno  := current;                        (* fill in current line number        *)
 x^.next := NIL;                               (* set next link to nil               *)
 FOR i := 1 TO wordlen                         (* calculate hash value               *)
   DO
    hash := (hash + ORD(id[i])) MOD prime
   END;
 REPEAT                                        (* continue searching until done      *)
   compvalue := compare(id, table[hash].key);  (* compare id to key to see if equal  *)
   IF compvalue = equal THEN                   (* if word entry already exists       *)
     done := TRUE;                             (* flag to end loop                   *)
     table[hash].last^.next := x;              (* link last item node to new node    *)
     table[hash].last := x                     (* link table pointer to new last node*)
   ELSE
     compvalue := compare(free,table[hash].key);
     IF compvalue = equal THEN                 (* if no entry exists                 *)
       WITH table[hash] DO
         key := id;                            (* fill in current word               *)
         first := x;                           (* link to item node                  *)
         last  := x;
         follow := top                         (* fill in last hash table entry      *)
       END;
       top  := hash;                           (* set to current hash table entry    *)
       done := TRUE
     ELSE                                      (* collision occurred                 *)
       hash := hash + addvalue;                (* incrmt hash to check next entry    *)
       addvalue := addvalue + 2;               (* increment displacement             *)
       IF  hash >= prime THEN                  (* if hash value greater than length  *)
       hash := hash - prime                    (* reset hash value                   *)
       END;
       IF addvalue = prime THEN                (* if table is full                   *)
         done := TRUE;                         (* flag that search is through        *)
         full := TRUE;                         (* flag that table is full            *)
         WriteString("Table Overflow");
         WriteLn
       END
     END
   END
 UNTIL done;
 RETURN full
END search;


PROCEDURE printtable;
(**************************************************************************************)
(* This procedure prints out the cross reference table.  It lists each word and the    *)
(* line numbers on which that word occurred.  Printtable has an internal procedure    *)
(* printword that handles printing the word and its line references.  The cross      *)
(* reference table is printed out in alphabetical order.                              *)
(**************************************************************************************)

VAR  hold      : index;                          (* contains the current entry index  *)
    least     : index;                          (* contains index to least word      *)
    move      : index;                          (* used to search for least word     *)
    compvalue : relation;                       (* contains compare result           *)

PROCEDURE printword(w: word);


 VAR numcnt: INTEGER;                           (* keeps track line nos on screen    *)
     x     : itemref;                           (* pointer to current item node      *)

 BEGIN
   Write(blank);
   WriteString(w.key);
   x := w.first;
   numcnt := 0;
   REPEAT                                       (* do until all line numbers printed *)
     IF numcnt = numperline THEN                (* if need a new line for line nos   *)
       numcnt := 0;                             (* reset counter                     *)
       WriteLn;
       Write(blank);
       WriteString(free)
     END;
     INC(numcnt);
     WriteCard(x^.lineno,digpernum);            (* write the line number             *)
     Write(blank);                              (* move to next item node            *)
     x := x^.next
   UNTIL x = NIL;
   WriteLn;
 END printword;

BEGIN
 hold := top;                                   (* start at last entry to be added   *)
 WHILE hold <> prime                            (* do for all of the table           *)
   DO
     least := hold;                             (* initialize for alphabetic search  *)
     move  := table[hold].follow;
     WHILE move <> prime                        (* search table for least entry      *)
       DO
         compvalue := compare(table[move].key,table[least].key);
         IF compvalue = less THEN
           least := move
         END;
         move := table[move].follow
       END;
     printword(table[least]);                    (* print the word and its line nos  *)
     IF least <> hold THEN                       (* make sure entry won't get printed*)
       table[least].key   := table[hold].key;
       table[least].first := table[hold].first;
       table[least].last  := table[hold].last
     END;
     hold := table[hold].follow                  (* move to the next entry           *)
    END
END printtable;


BEGIN                                              (* ***MAIN PROCEDURE***            *)

 current   := 0;                                  (* initialize                      *)
 top       := prime;
 tablefull := FALSE;
 FOR i := 1 TO wordlen DO
   free[i] := blank
 END;
 OpenInput(filetype);                             (* request filename and open file  *)
 IF NOT Done THEN                                 (* if file does not exist quit     *)
   WriteString("Error--file DK.file.TEXT does not exist")
 ELSE                                             (* otherwise continue              *)
   FOR i := 1 TO prime DO                         (* more initialization             *)
     table[i].key := free
   END;
   Read(ch);                                      (* get the first character         *)
   WHILE NOT tablefull DO                         (* do while table is not full      *)
     WHILE Done DO                                (* do while end of file not reached*)
       IF current = maxline THEN                  (* counter exceeds allowed line no *)
         current := 0                             (* reset counter                   *)
       END;
       INC(current);
       WriteCard(current,digpernum);              (* write current line no to screen *)
       Write(blank);
       WHILE (ch <> eol) & (Done) DO              (* while not at end of file line   *)             id := free;
         IF (CAP(ch)>= "A") & (CAP(ch)<="Z") THEN (* see if alphabetic               *)
           idcntr    := 0;
           REPEAT                                 (* get the word and put in id      *)
             IF idcntr < wordlen THEN
               INC(idcntr);
               id[idcntr] := ch;
             END;
             Write(ch);
             Read(ch);
            UNTIL ((CAP(ch)<"A") OR (CAP(ch)>"Z")) & ((ch<"0") OR (ch>"9"));
           tablefull := search()                  (* call search to add to table     *)
         ELSE                                     (* if not a word                   *)
           IF ch = quote THEN                     (* if a quote ignore between quotes*)
             REPEAT
               Write(ch);
               Read(ch)
             UNTIL ch = quote
           ELSIF ch = "{" THEN                    (* if a brace ignore between braces*)
             REPEAT
               Write(ch);
               Read(ch)
             UNTIL ch = "}"
           END;
         Write(ch);
         Read(ch);
       END;                                       (* end if alphabetic statement     *)
     END;                                         (* end while not eol loop          *)
     WriteLn;
     Read(ch);
   END;                                           (* end while not eof loop          *)
   tablefull := TRUE;                             (* exit outer loop so can print tab*)
 END;
 CloseInput;                                      (* close the input file            *)
 (* Write(ff) *)
 printtable;                                      (* print the table                 *)
 END
END crossref.