(*====================================================================*)
(*  PROGRAM TITLE: PASCAL CROSS-REFERENCING PROGRAM                   *)
(*                                                                    *)
(*  PROGRAM FILE: XREF.SRC                                            *)
(*                                                                    *)
(*  LAST UPDATE:  09-MAR-81 by Mike Lehman                            *)
(*								      *)
(*	NOTE: THIS PROGRAM WAS ORIGINALLY WRITTEN BY N. WIRTH AND     *)
(*	ADAPTED FOR UCSD PASCAL (I.4 - THE PUBLIC DOMAIN VERSION)     *)
(*	BY SHAWN FANNING (IN 1978) AND SUBSEQUENTLY ADAPTED FOR	      *)
(*	PASCAL/MT+ BY MIKE LEHMAN (IN 1981) AND IS A PUBLIC DOMAIN    *)
(*	PROGRAM.  IF YOU MAKE REVISIONS, ETC. PLEASE LEAVE THE AUTHOR *)
(*	AND MODIFIERS NAME IN THE SOURCE FILE.  THANK YOU.	      *)
(*                                                                    *)
(*  PROGRAM SUMMARY:                                                  *)
(*                                                                    *)
(*     THIS PROGRAM PRODUCES A CROSS-REFERENCE LISTING FOR ANY        *)
(*   PASCAL PROGRAM.  OCCURENCES ONLY ARE LISTED.  NO DISTINCTION IS  *)
(*   MADE BETWEEN DEFINITIONS AND REFERENCES.                         *)
(*====================================================================*)



PROGRAM XREF;

(*CROSS REFERENCE GENERATOR FOR PASCAL PROGRAMS.  N.WIRTH, 7.5.74*)
(*'QUADRATIC QUOTIENT' HASH METHOD*)

CONST
      P  = 749;           (*SIZE OF HASHTABLE*)
      NK =  45;             (*NO. OF KEYWORDS*)
      ALFALEN  =  8;
      REFSPERLINE = 15;
      REFSPERITEM =  5;


TYPE
     ALFA = PACKED ARRAY[1..ALFALEN] OF CHAR;
     INDEX = 0..P;
     ITEMPTR = ^ITEM;
     WORD = RECORD
               KEY: ALFA;
               FIRST, LAST: ITEMPTR;
               FOL: INDEX
            END ;
     NUMREFS = 1..REFSPERITEM;
     REFTYPE = (COUNT, PTR);
     ITEM = RECORD
               REF   : ARRAY[NUMREFS] OF INTEGER;
               CASE REFTYPE OF
                  COUNT: (REFNUM: NUMREFS);
                  PTR: (NEXT: ITEMPTR)
            END ;
     BUFFER = PACKED ARRAY[0..131] OF CHAR;

VAR
    TOP: INDEX;        (*TOP OF CHAIN LINKING ALL ENTRIES IN T*)
    I,LINECOUNT,BUFCURSOR: INTEGER;        (*CURRENT LINE NUMBER*)
    FF,CH: CHAR;          (*CURRENT CHAR SCANNED *)
    BUF,BUF1,BUF2: ^BUFFER;
    T: ARRAY [INDEX] OF WORD;        (*HASH TABLE*)
    KEY: ARRAY [1..NK] OF ALFA;      (* RESERVED KEYWORD TABLE *)
    ERROR,                           (* ERROR FLAG *)
    LISTING: BOOLEAN;                (* LISTING OPTION *)
    INFILE: TEXT;
    LST : TEXT;			
    LSTFILENAME : STRING;
    INPUT_LINE : STRING;

PROCEDURE INITIALIZE;
VAR
  I : INTEGER;

PROCEDURE FIRSTHALF;
BEGIN
   KEY[ 1] := 'AND     ';
   KEY[ 2] := 'ARRAY   ';
   KEY[ 3] := 'BEGIN   ';
   KEY[ 4] := 'BOOLEAN ';
   KEY[ 5] := 'CASE    ';
   KEY[ 6] := 'CHAR    ';
   KEY[ 7] := 'CONST   ';
   KEY[ 8] := 'DIV     ';
   KEY[ 9] := 'DOWNTO  ';
   KEY[10] := 'DO      ';
   KEY[11] := 'ELSE    ';
   KEY[12] := 'END     ';
   KEY[13] := 'EXIT    ';
   KEY[14] := 'FILE    ';
   KEY[15] := 'FOR     ';
   KEY[16] := 'FUNCTION';
END;

PROCEDURE SECONDHALF;
BEGIN
   KEY[17] := 'GOTO    ';
   KEY[18] := 'IF      ';
   KEY[19] := 'IN      ';
   KEY[20] := 'INPUT   ';
   KEY[21] := 'INTEGER ';
   KEY[22] := 'MOD     ';
   KEY[23] := 'NIL     ';
   KEY[24] := 'NOT     ';
   KEY[25] := 'OF      ';
   KEY[26] := 'OR      ';
   KEY[27] := 'OUTPUT  ';
   KEY[28] := 'PACKED  ';
   KEY[29] := 'PROCEDUR';
   KEY[30] := 'PROGRAM ';
   KEY[31] := 'REAL    ';
   KEY[32] := 'RECORD  ';
   KEY[33] := 'REPEAT  ';
   KEY[34] := 'SET     ';
   KEY[35] := 'STRING  ';
   KEY[36] := 'TEXT    ';
   KEY[37] := 'THEN    ';
   KEY[38] := 'TO      ';
   KEY[39] := 'TYPE    ';
   KEY[40] := 'UNTIL   ';
   KEY[41] := 'VAR     ';
   KEY[42] := 'WHILE   ';
   KEY[43] := 'WITH    ';
   KEY[44] := 'WRITE   ';
   KEY[45] := 'WRITELN ';
END;

BEGIN (* INITIALIZE *)
   WRITELN;
   WRITELN('Pascal/MT+ Program Xref Utility -- Release 5.2');
   WRITELN('This program is public domain');
   WRITELN;
   FF:=CHR(12);
   NEW(BUF1);
   NEW(BUF2);
   BUF:=BUF1;
   ERROR   := FALSE;
   FOR I := 0 TO P DO
      T[I].KEY := '        ';
   FIRSTHALF;
   SECONDHALF;
   LINECOUNT:= 0;
   BUFCURSOR:= 0;
   TOP := P;
   CH  := ' '
END; (* INITIALIZE *)



PROCEDURE OPENFILES;
VAR
    NUMBLOCKS: INTEGER;
    OPENOK: BOOLEAN;
    OPENERRNUM : INTEGER;
    LISTOPTION: CHAR;
    FILENAME: STRING;

BEGIN (* OPEN *)
   REPEAT
      WRITELN;
      WRITE( 'Input file ? ' );
      READLN( FILENAME );
      IF LENGTH(FILENAME) >0 THEN
	BEGIN
          ASSIGN(INFILE, FILENAME );
	  RESET(INFILE)
	END;
      OPENERRNUM := IORESULT;
      OPENOK     := ( OPENERRNUM <> 255 );
      IF NOT OPENOK THEN
        WRITELN( '*** INPUT OPEN ERROR #', OPENERRNUM );
   UNTIL OPENOK;

   WRITE('Output file name? ');
   READLN(LSTFILENAME);
   ASSIGN(LST,LSTFILENAME);
   REWRITE(LST);

   WRITE( 'Do you want a listing ? ' );
   READ( LISTOPTION );
   LISTING := NOT(LISTOPTION = 'N');
   IF LISTING THEN PUTNUMBER(0);
   READLN(INFILE,INPUT_LINE);
   WRITELN;
END; (* OPEN *)


PROCEDURE LPWRITELN;
VAR
  I : INTEGER;
BEGIN
  BUF^[BUFCURSOR]:=CHR(13);
  BUFCURSOR:=BUFCURSOR+1;
  FOR I := 0 TO BUFCURSOR-1 DO
    WRITE(LST,BUF^[I]);
  IF BUF = BUF1 THEN BUF:=BUF2 ELSE BUF:=BUF1;
  BUFCURSOR:=0;
  LINECOUNT:=LINECOUNT+1;
  IF (LINECOUNT MOD 60) = 0 THEN
    PAGE(LST);
END;

PROCEDURE PUTALFA(S:ALFA);
BEGIN
  MOVELEFT(S[1],BUF^[BUFCURSOR],8);
  BUFCURSOR:=BUFCURSOR+8;
END;

PROCEDURE PUTNUMBER(NUM: INTEGER);
VAR I,IPOT:INTEGER;
    A: ALFA;
    CH: CHAR;
    ZAP:BOOLEAN;
    
BEGIN
  ZAP:=TRUE;
  IPOT:=10000;
  A[1]:=' ';
  FOR I:= 2 TO 6 DO
    BEGIN
      CH:=CHR(NUM DIV IPOT + ORD('0'));
      IF I <> 6 THEN
        IF ZAP THEN
           IF CH = '0' THEN
             CH:=' '
           ELSE ZAP:=FALSE;
      A[I]:=CH;
      NUM:=NUM MOD IPOT;
      IPOT:=IPOT DIV 10;
    END;
  A[7]:=' ';
  MOVELEFT(A,BUF^[BUFCURSOR],7);
  BUFCURSOR:=BUFCURSOR+7;
END;


PROCEDURE GETNEXTCHAR;
VAR I : INTEGER;
BEGIN

    IF LENGTH(INPUT_LINE) = 0 THEN
      READLN(INFILE,INPUT_LINE);

    IF LENGTH(INPUT_LINE) = 0 THEN
      CH := ' '
    ELSE
      BEGIN
	CH:=INPUT_LINE[1];
        DELETE(INPUT_LINE,1,1)
      END;

    IF EOF(INFILE) THEN ERROR:=TRUE
    ELSE
      BEGIN
        BUF^[BUFCURSOR]:=CH;
        BUFCURSOR:=BUFCURSOR+1;
        IF LENGTH(INPUT_LINE) = 0 THEN
          BEGIN
            BUF^[BUFCURSOR]:=CHR(13);
            BUFCURSOR:=BUFCURSOR+1;
            LINECOUNT:= LINECOUNT +1;
            IF LISTING THEN
              BEGIN
		IF LSTFILENAME <> 'CON:' THEN
		  WRITE('.');
		FOR I := 0 TO BUFCURSOR-1 DO
                  WRITE(LST,BUF^[I]);
                IF BUF = BUF2 THEN BUF:=BUF1 ELSE BUF:=BUF2;
                BUFCURSOR:=0;
                PUTNUMBER(LINECOUNT);
              END
            ELSE
	      BEGIN
		BUFCURSOR:=0;
		WRITE('.')
	      END;

            IF (LINECOUNT MOD 60) = 0 THEN
              BEGIN
                IF LISTING THEN PAGE(LST);
                WRITELN(OUTPUT,'< ',LINECOUNT:4,',',MEMAVAIL:5,' >');
              END;
           END;
       END;
 END; (* GETNEXTCHAR *)


PROCEDURE SEARCH( ID: ALFA );          (*MODULO P HASH SEARCH*)
(*GLOBAL: T, TOP*)
VAR
    I,J,H,D  : INTEGER;
    X    : ITEMPTR;
    F    : BOOLEAN;

BEGIN
   J:=0;
   FOR I:= 1 TO ALFALEN DO
     J:= J*10+ORD(ID[I]);
   H  := ABS(J) MOD P;
   F  := FALSE;
   D  := 1;
   REPEAT
      IF T[H].KEY = ID
         THEN
            BEGIN (*FOUND*)
               F := TRUE;
               IF T[H].LAST^.REFNUM = REFSPERITEM
                  THEN
                     BEGIN
                         NEW(X);
                         X^.REFNUM := 1;
                         X^.REF[1] := LINECOUNT;
                         T[H].LAST^.NEXT:= X;
                         T[H].LAST      := X;
                     END
                 ELSE
                    WITH T[H].LAST^ DO
                       BEGIN
                          REFNUM      := REFNUM + 1;
                          REF[REFNUM] := LINECOUNT
                       END
            END
         ELSE
            IF T[H].KEY = '        '
               THEN
                  BEGIN (*NEW ENTRY*)
                     F  := TRUE;
                     NEW(X);
                     X^.REFNUM := 1;
                     X^.REF[1] := LINECOUNT;
                     T[H].KEY   := ID;
                     T[H].FIRST := X;
                     T[H].LAST  := X;
                     T[H].FOL   := TOP;
                     TOP := H
                  END
               ELSE
                  BEGIN (*COLLISION*)
                     H := H+D;
                     D := D+2;
                     IF H >= P
                        THEN
                           H := H - P;
                     IF D = P
                        THEN
                           BEGIN
                              WRITELN(OUTPUT,'TBLE OVFLW');
                              ERROR := TRUE
                           END ;
                  END
   UNTIL F OR ERROR
END (*SEARCH*) ;



PROCEDURE PRINTWORD(W: WORD);
VAR
    L: INTEGER;
    X: ITEMPTR;
    NEXTREF : INTEGER;
    THISREF: NUMREFS;
BEGIN
   PUTALFA(W.KEY);
   X := W.FIRST;
   L := 0;
   REPEAT
      IF L = REFSPERLINE
         THEN
            BEGIN
               L := 0;
               LPWRITELN;
               PUTALFA('        ');
            END ;
      L := L+1;
      THISREF := (L-1) MOD REFSPERITEM + 1;
      NEXTREF := X^.REF[ THISREF ];
      IF THISREF = X^.REFNUM
         THEN
            X := NIL
         ELSE
            IF THISREF = REFSPERITEM
               THEN
                  X := X^.NEXT;
      PUTNUMBER(NEXTREF);
   UNTIL X = NIL;
  LPWRITELN;
END (*PRINTWORD*) ;



PROCEDURE PRINTTABLE;

VAR
    I,J,M: INDEX;

BEGIN
   I := TOP;
   WHILE I <> P DO
      BEGIN (*FIND MINIMAL WORD*)
         M := I;
         J := T[I].FOL;
         WHILE J <> P DO
            BEGIN
               IF T[J].KEY < T[M].KEY
                  THEN
                     M := J;
               J := T[J].FOL
            END ;
         PRINTWORD(T[M]);
         IF M <> I THEN 
           BEGIN
             T[M].KEY:=T[I].KEY;
             T[M].FIRST:=T[I].FIRST;
             T[M].LAST:=T[I].LAST;
           END;
         I := T[I].FOL
      END
END (*PRINTTABLE*) ;



PROCEDURE GETIDENTIFIER;
VAR
    J,K,I: INTEGER;
    ID: ALFA;

BEGIN (* GETIDENTIFIER *)
   I := 0;
   ID := '        ';
   REPEAT
      IF I < ALFALEN
         THEN
            BEGIN
               I := I+1;
               IF ('a' <= CH) AND (CH <= 'z')
                  THEN
                     ID[I] := CHR( ORD(CH) - ORD('a') + ORD('A') )
                  ELSE
                     ID[I] := CH
            END;
      GETNEXTCHAR
   UNTIL ( NOT(((CH>='A') AND (CH<='Z')) OR ((CH>='a') AND (CH<='z'))
                OR ((CH>='0') AND (CH<='9')))) OR (ERROR);
   I := 1;
   J := NK;
   REPEAT
      K := (I+J) DIV 2;      (*BINARY SEARCH*)
      IF KEY[K] <= ID
         THEN
            I := K+1;

      IF KEY[K] >= ID
         THEN
            J := K-1;

   UNTIL I > J;
   IF KEY[K] <> ID THEN SEARCH(ID);
END; (* GETIDENTIFIER *)

BEGIN (* CROSSREF *)
   INITIALIZE;

   OPENFILES;
   WHILE (NOT(EOF(INFILE))) AND (NOT( ERROR)) DO
      BEGIN
        IF ((CH>='A') AND (CH<='Z')) THEN
          GETIDENTIFIER
        ELSE
          IF (CH = '''') THEN
            BEGIN
              REPEAT
                GETNEXTCHAR;
              UNTIL (CH = '''') OR (ERROR);
              GETNEXTCHAR;
            END
          ELSE
            IF CH = '(' THEN
              BEGIN
                GETNEXTCHAR;
                IF CH = '*' THEN
                  BEGIN
                    GETNEXTCHAR;
                    WHILE (CH <> ')') AND (NOT(ERROR)) DO
                      BEGIN
                        WHILE (CH <> '*') AND (NOT(ERROR)) DO
                          GETNEXTCHAR;
                        GETNEXTCHAR;
                      END;
                    GETNEXTCHAR;
                  END;
              END
            ELSE
              GETNEXTCHAR;

      END; (* WHILE *)
   PAGE(LST);
   LINECOUNT := 0;
   BUFCURSOR := 0;
   PRINTTABLE;
   PAGE(LST);
   CLOSE(LST,I);
   IF I = 255 THEN
     WRITELN('Error closing output file')
END.
