Program XLIST(input,output);

{This program produces a cross-reference listing for a
Pascal program. Occurences only are listed. No distinction is
made between definitions and references. It will also give a
graphical representation of the block structure of the program.

Note: This program, originally written by N. Wirth,
uses the 'quadratic quotient' hash method. It was
adapted for UCSD Pascal (1.4 - the public domain version)
by Shawn Fanning (in 1978) and subsequently adapted for
Pascal/MT+ by Mike Lehman (in 1981). This version was then
modified be Warren A. Smith to try to get back to iso stan-
dard pascal and to add the additional feature of mapping
out the compound statements. It was adapted for Turbo Pascal
by Ron Finger in July 1984. This is a public domain program.}

{$I-}
{$V-}
Const
      P  = 749;        {SIZE of HASHTABLE}
      NK =  45;        {NO. of KEYWORDS}
      PAGESIZE = 57;   {LINES PER PAGE}
      ALFALEN  =  8;   {SIZE of IDENTIFIERS}
      REFSPERLINE = 17;
      REFSPERITEM =  5;
      NESTMAX = 10 ;
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 : 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,LST: Text;
    LSTFILENAME : String[14];
    INPUT_LINE : String[120];
    LAST_KEY,PAGE_NUM,NESTLVL:Integer;
    ABORT,LITERAL,ACOMMENT,BCOMMENT,EOL,NESTUP,NESTDN,NODOT:Boolean;
    BAR : Char ;
    FILENAME,FILETITLE:String[14];
    DATE:String[20];
    LDATE,LTITLE:Byte;

FUNCTION TAB (NUM : Integer) : Char ;
  Var
      I : Integer ;
  Begin
  For I := 1 to NUM do
    Write (LST, ' ') ;
  TAB := CHR(0)
  End ; { TAB }

Procedure TITLELINE;
Begin
  If PAGE_NUM > 1 then
    Writeln(LST,^L);
  Writeln(LST);
  Writeln(LST);
  Write(LST,'File: ',FILETITLE);
  Write(LST,TAB(15),'Cross-Reference & Block Listing',TAB(15));
  If LDATE>5 then
    Write(LST,'Date: ',DATE);
  Write(LST,TAB(50-(LDATE+LTITLE)));
  Writeln (LST,'Page ', PAGE_NUM:1);
  Writeln (LST) ;
  PAGE_NUM := PAGE_NUM + 1
End ; {TITLELINE}

Procedure LPWRITELN;
Var
  I : Integer;
Begin
  BUF[BUFCURSOR]:=CHR(13);
  BUFCURSOR:=BUFCURSOR+1;
  For I := 0 to BUFCURSOR-1 do
    Write(LST,BUF[I]);
  Writeln(LST);
  BUFCURSOR:=0;
  LINECOUNT:=LINECOUNT+1;
  If (LINECOUNT MOD PAGESIZE) = 0 then
    TITLELINE;
End;

Procedure INITIALIZE;
Var
  I : Integer;
Begin { INITIALIZE }
   FF:=CHR(12);
   ERROR := FALSE;
   For I := 0 to P do
      T[I].KEY := '        ';
   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';
   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 ';

   LINECOUNT:= 1;
   TOP := P;
   PAGE_NUM := 1 ;
   LITERAL := FALSE ;
   ACOMMENT := FALSE ;
   BCOMMENT := FALSE ;
   NESTLVL := 0 ;
   LAST_KEY := 0 ;
   BAR := '|' ;
   CH  := ' '
End; { INITIALIZE }

Procedure OPENFILES;
Var
    I,NUMBLOCKS,OPENERRNUM: Integer;
    OPENOK: Boolean;
    LISTOPTION: Char;
Begin { OPEN }
   Writeln;
   ABORT := FALSE ;
   Repeat
      NODOT := TRUE;
      Write('Filename: ( CR to quit): ');
      READLN( FILENAME );
        ABORT := Length(FILENAME) <= 0;
        If NOT ABORT then
          Begin
            For I := 1 to LENGTH(FILENAME) do
              Begin
                FILENAME[I] := UPcase(FILENAME[I]) ;
                If FILENAME[I] = '.' then
                  NODOT := False
              End;
            If NODOT then
              FILENAME := FILENAME + '.PAS';
            ASSIGN(INFILE,FILENAME);
            RESET(INFILE);
            OPENERRNUM := IORESULT;
            OPENOK := ( OPENERRNUM = 0);
            If NOT OPENOK then
              Writeln(FILENAME,' not found')
            Else
              FILETITLE := FILENAME;
              If POS(':',FILETITLE) = 2 then
                DELETE(FILETITLE,1,2);
              LTITLE := LENGTH(FILETITLE);
        End;
   Until OPENOK OR ABORT;

   If NOT ABORT then
     Begin
       Write('Destination file or device (CR for LST:): ');
       READLN(LSTFILENAME);
       If LENGTH (LSTFILENAME) <= 0 then
         LSTFILENAME := 'LST:' ;
       For I := 1 to LENGTH(LSTFILENAME) do
         LSTFILENAME[I] := UPcase(LSTFILENAME[I]) ;
       ASSIGN(LST,LSTFILENAME);
       Rewrite(LST);
  End;
   If NOT ABORT then
     Begin
       Repeat
         Write('Do you want a listing (Y/N)? ');
         READLN( LISTOPTION );
         LISTOPTION := UPcase(LISTOPTION);
       Until LISTOPTION IN ['Y','N'];
         LISTING := LISTOPTION = 'Y';
         If LDATE=5 then
           Begin
             Write('Date: ');
             READLN(DATE);
             LDATE:=LENGTH(DATE)+5
           End;
     End
End; {open}

Procedure PUTALFA(S:ALFA);
Begin
  MOVE(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]:=' ';
  MOVE(A,BUF[BUFCURSOR],7);
  BUFCURSOR:=BUFCURSOR+7;
End;

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,NEXTREF: Integer;
    X: ITEMPTR;
    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 OUTPUT_LINE (BUF : BUFFER) ;
  Var
    I : Integer ;
  Procedure FILL_LINE (Var LINE : BUFFER) ;
    Var
      I : Integer ;
    Begin { FILL_LINE }
    I := 1 ;
    While (LINE[I] = ' ') do
      Begin
      LINE[I] := '-' ;
      I := I + 1
      End
    End ; { FILL_LINE }

  Procedure PRTNEST (Var LINE : BUFFER) ;

    Var COL : Integer ;

    Begin { PRTNEST }
    For COL := 1 to NESTLVL - 1 do
      Write (LST, BAR, '  ') ;
    If NESTLVL > 0 then
      If NESTUP OR NESTDN then
        Begin
        If NESTDN then
          Begin
          Write (LST, BAR, '  ') ;
          Write (LST, 'E--') ;
          For COL := NESTLVL+2 to NESTMAX do
            Write (LST, '---')
          End
        Else
          Begin
          Write (LST, 'B--') ;
          For COL := NESTLVL+1 to NESTMAX do
            Write (LST, '---')
          End ;
        FILL_LINE (LINE)
        End
      Else
        Begin
        Write (LST, BAR, '  ') ;
        For COL := NESTLVL+1 to NESTMAX do
          Write (LST, '   ')
        End
    Else
      If NESTDN then
        Begin
        Write (LST, 'E--') ;
        For COL := 2 to NESTMAX do
          Write (LST, '---') ;
        FILL_LINE (LINE)
        End
      Else
        For COL := 1 to NESTMAX do
          Write (LST, '   ')
    End ; { PRTNEST }
        
  Begin { OUTPUT_LINE }
  If ((LINECOUNT MOD PAGESIZE) = 0) OR (PAGE_NUM = 1) then
    Begin
      If LISTING then
        TITLELINE;
    If (LSTFILENAME <> 'CON:') AND ((LINECOUNT MOD PAGESIZE) = 0) then
      Writeln (OUTPUT, '< ', LINECOUNT:4, ',', MEMAVAIL:5, ' >')
    End ;
  Write (LST, LINECOUNT:4, '  ') ;
  PRTNEST (BUF) ;
  For I := 1 to BUFCURSOR do
    Write (LST, BUF[I]) ;
  Writeln (LST) ;
  If LSTFILENAME <> 'CON:' then
    Write (OUTPUT, '.')
  End ; { OUTPUT_LINE }

Procedure GETNEXTCHAR;
Var I : Integer;

Begin { GETNEXTCHAR }
If BUFCURSOR >= LENGTH (INPUT_LINE) then
  Begin
  EOL := TRUE ;
  CH := ' ' ;
  ERROR := EOF(INFILE)
  End
Else
  Begin
  BUFCURSOR := BUFCURSOR + 1 ;
  CH := INPUT_LINE [BUFCURSOR] ;
  BUF [BUFCURSOR] := CH ;
  CH := UPcase(CH)
  End
End; { GETNEXTCHAR }

Procedure GETIDENTIFIER;
Var
    J,K,I: Integer;
    ID: ALFA;

Begin { GETIDENTIFIER }
   I := 0;
   ID := '        ';
   Repeat
      If I < ALFALEN
         then
            Begin
               I := I+1;
               ID[I] := CH
            End;
      GETNEXTCHAR
   Until ( NOT(((CH>='A') AND (CH<='Z')) OR (CH='_')
                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)
   Else
     Begin
       If (K=3) OR ((K=5) AND (LAST_KEY<>32)) OR     { Begin or CASE }
          (K=32) OR (K=33) then                      { Record or Repeat }
         Begin
           LAST_KEY := K ;
           If NESTLVL = NESTMAX then
             Write (LST, '----Too many levels')
           Else
             Begin
               NESTLVL := NESTLVL + 1 ;
               NESTUP := TRUE
             End
         End ;
       If (K=12) OR (K=40) then          { End or Until }
         If NESTLVL = 0 then
           Write (LST, '----Nesting error  ')
         Else
           Begin
             NESTLVL := NESTLVL - 1 ;
             NESTDN := TRUE
           End
     End

End; { GETIDENTIFIER }

Begin { CROSSREF }
   LDATE:=5;
   Repeat
   INITIALIZE;
   OPENFILES;
   While NOT EOF(INFILE) AND (NOT ABORT) do
      Begin
      BUFCURSOR:= 0;
      NESTUP := FALSE ;
      NESTDN := FALSE ;
      READLN (INFILE, INPUT_LINE) ;
      If LENGTH (INPUT_LINE) > 0 then
        Begin
        EOL := FALSE ;
        BUFCURSOR := BUFCURSOR + 1 ;
        CH := INPUT_LINE [BUFCURSOR] ;
        BUF [BUFCURSOR] := CH ;
        CH := UPcase (CH)
        End
      Else
        Begin
        EOL := TRUE ;
        CH := ' '
        End ;
      While NOT EOL do
        Begin
        If ((CH >= 'A') AND (CH <= 'Z')) AND (NOT LITERAL) AND
           (NOT ACOMMENT) AND (NOT BCOMMENT) then
          GETIDENTIFIER
        Else
          If (CH = '''') OR LITERAL then
            Begin
              Repeat
                GETNEXTCHAR;
              Until (CH = '''') OR (ERROR) OR EOL;
              LITERAL := EOL ;
              GETNEXTCHAR
            End
          Else
            If (CH = '{') OR ACOMMENT then
              Begin
                While (CH <> '}') AND (NOT ERROR) AND (NOT EOL) do
                  GETNEXTCHAR ;
                ACOMMENT := EOL ;
                GETNEXTCHAR
              End
            Else
              If (CH = '(') OR BCOMMENT then
                Begin
                  If NOT BCOMMENT then
                    GETNEXTCHAR;
                  If (CH = '*') OR BCOMMENT then
                    Begin
                      If NOT BCOMMENT then
                        GETNEXTCHAR;
                      Repeat
                        While (CH <> '*') AND (NOT ERROR) AND (NOT EOL) do
                          GETNEXTCHAR ;
                        BCOMMENT := EOL ;
                        If NOT EOL then
                          GETNEXTCHAR
                      Until (CH = ')') OR ERROR OR EOL ;
                      If NOT EOL then
                        GETNEXTCHAR
                    End
                End
              Else
                GETNEXTCHAR;

        End; { While }
      EOL := FALSE ;
      If LISTING then
        OUTPUT_LINE (BUF) ;
      LINECOUNT := LINECOUNT + 1
      End ;
   If NOT ABORT then
     Begin
     TITLELINE;
     LINECOUNT := 0;
     BUFCURSOR := 0;
     PRINTTABLE;
     Writeln(LST,^L);
     CLOSE(LST);
     If IOresult <> 0 then
       Writeln('Error closing output file')
     End;
  Until LENGTH(FILENAME) <= 0
End.
