(*---------------------------------------------------------------------------
  :Program.    Cross.mod
  :Author.     Andreas Pahl
  :Address.    Zikadenweg 22, D-1000 Berlin 19
  :Phone.      (0)30/302 55 37
  :Version.    1.1
  :Date.       17.Sep.1990
  :Copyright.  PD
  :Language.   Modula
  :Translator. M2Amiga v3.3d
  :Contents.   CrossReference-Lister
  :Update.     17.Sep.1990 [Thomas Clever] (Fehler behoben; Parameter-
  :Update.       übergabe verbessert; 30% kürzer; 100% schneller)
  :Bugs.       Kann globalen und lokalen Bezeichner mit selbem Namen aber
  :Bugs.         verschiedenem Typ nicht unterscheiden.
---------------------------------------------------------------------------*)

(*

    Ideen zur Erweiterung:
    ----------------------

    - Ausgabedatei wählen können
    - Zeilenbreite variabel
    - Anzeigen, wo der Bezeichner definiert ist (Welches Modul, welche
      Prozedur)
    - Was er alles anzeigen soll, irgendwie vom Benutzer wählen lassen
      (aber mit Voreinstellung)
    - Mit Modlist verbinden
    - Schachtelungstiefe ausgeben
    - Smart-Linker auf Sourceebene
    - Zu jeder Prozedur alle Bezeichner herausschreiben, die in ihr
      verwendet, aber nicht deklariert werden. Hilfreich für Programm-
      modifikationen und gegen unerwünschte Seiteneffekte

*)


MODULE Cross;


FROM Arts        IMPORT returnVal, Terminate, CurrentLevel;
FROM ASCII       IMPORT eol,cr,lf;
FROM Arguments   IMPORT GetArg, NumArgs;
FROM Conversions IMPORT ValToStr;
FROM FileSystem  IMPORT Lookup, Close, ReadChar, File, Response, WriteChar,
                        WriteBytes;
FROM Str         IMPORT Compare, Length;
FROM SYSTEM      IMPORT ADR, ADDRESS;
FROM Terminal    IMPORT ReadLn, WriteLn, WriteString, waitCloseGadget;
IMPORT Heap;


CONST

   Eintraege = 73;                    (* Anzahl der Eintraege in ResWort *)

   Ueberschrift   = "Bezeichner           Anzahl Typ                Zeilen";
   Unterstreichen = "-----------------------------------------------------------------------";


TYPE

   (* Erklärung der Datenstruktur siehe Dokumentation                     *)

   ListPointer = POINTER TO ListNode;
   ListNode    = RECORD
                   Nummer : CARDINAL;       (* Zeilennummer               *)
                   Next   : ListPointer;
                 END;

   Trie = POINTER TO Node;
   Node = RECORD
            Buchstabe   : CHAR;
            Typ         : CHAR;             (* Typ des Bezeichners        *)
            Anzahl      : CARDINAL;         (* Häufigkeit des Bezeichners *)
            Erster,                         (* Liste der Zeilennummern    *)
            Letzter     : ListPointer;
            Wortende    : BOOLEAN;          (* Markiert Wortende im Trie  *)
            horizontal,
            vertikal    : Trie;
          END;

   String = ARRAY [0..80] OF CHAR;


VAR

   ResWort     : ARRAY [1..Eintraege] OF String;
                       (* Darin sind die reservierten Bezeichner abgelegt *)

   Wort           : String;                 (* enthält aktuelles Wort     *)
   Ende           : BOOLEAN;                (* TRUE = Ende eines Wortes   *)
   OutputFile,                              (* Ausgabe-File               *)
   QuellFile      : File;                   (* zu untersuchende Source    *)
   OutputName,                              (* Dos-Name des Ausgabe-Files *)
   DosName        : String;                 (* Dos-Name des Sourcefiles   *)
   Zeichen        : CHAR;                   (* gelesenes Zeichen          *)
   Baum           : Trie;                   (* Da wird alles gespeichert  *)
   ZeilenNummer   : CARDINAL;
   Len            : INTEGER;                (* Für GetArg                 *)
   BezeichnerTyp  : CHAR;
   actual         : LONGINT;                (* Für WriteBytes             *)
   AltesZeichen   : CHAR;                   (* Das vorhergehende Zeichen  *)
                                            (* muß gespeichert werden, um *)
                                            (* Kommentare zu erkennen     *)



PROCEDURE Allocate( VAR adr: ADDRESS; size: LONGINT );
BEGIN
  Heap.Allocate( adr, size );
  IF adr = NIL THEN
    WriteLn;
    WriteString(" !!! NOT ENOUGH MEMORY !!!");
    WriteLn; WriteLn;
    returnVal := 10;                  (* nützlich für Batch-Verarbeitung *)
    Terminate( CurrentLevel() );
  END;
END Allocate;



PROCEDURE LiesZeichen(VAR Zeichen : CHAR);

(* Liest ein Zeichen aus dem File aus, übergeht Kommentare und Strings    *)
(* und erkennt das Wortende.                                              *)


   PROCEDURE Read ( VAR Zeichen : CHAR );

   (* Liest ein Zeichen aus dem QuellFile und inkrementiert den
      Zeilenzähler gegebenenfalls.                                        *)

   BEGIN
      ReadChar(QuellFile,Zeichen);
      IF (Zeichen = eol) OR (Zeichen = cr) THEN
         INC(ZeilenNummer);
      END;
   END Read;


BEGIN
   Read(Zeichen);

   (* Strings sollen überlesen werden *)

   IF Zeichen = '"' THEN
      REPEAT
         Read(Zeichen);
      UNTIL Zeichen = '"';
      Read(Zeichen);
   END;
   IF Zeichen = "'" THEN
      REPEAT
         Read(Zeichen);
      UNTIL Zeichen = "'";
      Read(Zeichen);
   END;

   (* Kommentare sollen auch überlesen werden *)

   IF (Zeichen = "*") AND (AltesZeichen = "(") THEN
      REPEAT
         AltesZeichen := Zeichen;
         Read(Zeichen)
      UNTIL (Zeichen = ")") AND (AltesZeichen = "*");
   END;

   (* Tritt ein Nichtbuchstabe auf, ist das Wort beendet *)

   IF NOT (  ( (Zeichen >= 'a') AND (Zeichen <= 'z') )
          OR ( (Zeichen >= 'A') AND (Zeichen <= 'Z') )
          OR ( (Zeichen >= '0') AND (Zeichen <= '9') ) ) THEN
      Ende := TRUE;
   END;

   AltesZeichen := Zeichen;

END LiesZeichen;



PROCEDURE Insert(VAR Knoten : Trie; level : CARDINAL);

VAR

   NeuerKnoten       : Trie;                (* Knoten zum Einfügen in     *)
                                            (* den Trie                   *)
   NeuerListNode     : ListPointer;         (* Knoten zum Einfügen in die *)
                                            (* Liste der Zeilennummern    *)


   PROCEDURE Wandern();

   (* Wandert im Baum eine Ebene tiefer, falls nicht das Wortende         *)
   (* detektiert wurde. Zusätzlich wird der Bezeichnertyp bestimmt.       *)


   BEGIN
      LiesZeichen(Zeichen);
      IF Ende = FALSE THEN
         Insert(Knoten^.vertikal,level+1);
      ELSE
         Knoten^.Wortende := TRUE;          (* Das Wort ist beendet, am   *)
                                            (* aktuellen Knoten wird dies *)
                                            (* markiert.                  *)
         Wort[level+1] := 0C;
         IF Knoten^.Anzahl = 0 THEN         (* Wenn das Wort zum ersten   *)
            Knoten^.Typ := BezeichnerTyp;   (* auftaucht, wird der Typ    *)
         END;                               (* des Bezeichners gespeichert*)

                                            (* Jetzt wird getestet, ob das*)
                                            (* beendete Wort, den aktu-   *)
                                            (* ellen Bezeichnertyp ändert *)
         IF Compare(Wort,"FROM") = 0 THEN
            BezeichnerTyp := "F";
         END;
         IF Compare(Wort,"IMPORT") = 0 THEN
            BezeichnerTyp := "I";
         END;
         IF Compare(Wort,"MODULE") = 0 THEN
            BezeichnerTyp := "M";
         END;
         IF Compare(Wort,"PROCEDURE") = 0 THEN
            BezeichnerTyp := "P";
         END;
         IF Compare(Wort,"CONST") = 0 THEN
            BezeichnerTyp := "C";
         END;
         IF Compare(Wort,"TYPE") = 0 THEN
            BezeichnerTyp := "T";
         END;
         IF Compare(Wort,"VAR") = 0 THEN
            BezeichnerTyp := "V";
         END;
         IF Compare(Wort,"BEGIN") = 0 THEN
            BezeichnerTyp := "B";
         END;

         Knoten^.Anzahl := Knoten^.Anzahl + 1;
                                            (* Zeilennummer in die Liste  *)
                                            (* der Zeilennummern einfügen *)
         Allocate(NeuerListNode,SIZE(ListPointer));
         NeuerListNode^.Nummer := ZeilenNummer;
         NeuerListNode^.Next := NIL;
         IF Knoten^.Erster = NIL THEN
            Knoten^.Erster  := NeuerListNode;
            Knoten^.Letzter := NeuerListNode;
         ELSE
            Knoten^.Letzter^.Next := NeuerListNode;
            Knoten^.Letzter       := NeuerListNode;
         END;
      END;
   END Wandern;

BEGIN
   IF Knoten = NIL THEN                     (* Wenn Baum leer,            *)
      Allocate(NeuerKnoten,SIZE(Node));     (* dann Wurzel generieren     *)
      NeuerKnoten^.Wortende := FALSE;
      NeuerKnoten^.Anzahl := 0;
      NeuerKnoten^.Buchstabe := Zeichen;
      NeuerKnoten^.Erster := NIL;
      NeuerKnoten^.Letzter := NIL;
      Wort[level] := Zeichen;
      Knoten := NeuerKnoten;
      Wandern;
   ELSIF ORD(Knoten^.Buchstabe) > ORD(Zeichen) THEN
                                            (* Wenn Buchstabe > Zeichen   *)
      Allocate(NeuerKnoten,SIZE(Node));
      NeuerKnoten^.Wortende := FALSE;
      NeuerKnoten^.Anzahl := 0;
      NeuerKnoten^.Buchstabe := Zeichen;
      NeuerKnoten^.Erster := NIL;
      NeuerKnoten^.Letzter := NIL;
      Wort[level] := Zeichen;
      NeuerKnoten^.horizontal := Knoten;    (* dann neuen Knoten vor dem  *)
      Knoten := NeuerKnoten;                (* alten horizontal einfügen  *)
      Wandern;
   ELSIF ORD(Knoten^.Buchstabe) < ORD(Zeichen) THEN
                                            (* Wenn Buchstabe < Zeichen   *)
                                            (* dann einen Knoten horizon- *)
                                            (* tal weiterwandern          *)
      Insert(Knoten^.horizontal,level);
   ELSE                                     (* Wenn Buchstabe = Zeichen   *)
      Wort[level] := Zeichen;               (* dann einen Knoten vertikal *)
      Wandern;                              (* weiterwandern              *)
   END;
END Insert;


PROCEDURE NeueZeile;

(* Geht beim OutputFile auf die nächste Zeile                             *)

BEGIN
   (* WriteChar(OutputFile,cr); *)
   WriteChar(OutputFile,lf);
END NeueZeile;



PROCEDURE WriteCard(Val,Digits:CARDINAL);

(* Wandelt einen CARDINAL in einen String und schreibt ihn ins OutputFile *)


VAR

   Error : BOOLEAN;
   Str   : ARRAY [0..35] OF CHAR;

BEGIN
   ValToStr(Val,FALSE,Str,10,Digits," ",Error);
   IF NOT Error THEN
      WriteBytes(OutputFile,ADR(Str),Length(Str),actual);
   END;
END WriteCard;



PROCEDURE PrintTrie(Knoten : Trie; level : CARDINAL);

(* Wandert rekursiv durch den Trie und gibt die Worte aus                 *)


VAR

   Zaehler     : CARDINAL;                  (* Zählt die Nummern pro      *)
                                            (* Ausgabezeile               *)
   pointer     : ListPointer;               (* Zum Durchwandern der Liste *)
                                            (* der Zeilennummern          *)



   PROCEDURE ReserviertesWort(Wort : String) : BOOLEAN;

   (* Überprüft, ob das übergebene Wort ein reservierter Bezeichner ist   *)

   VAR
      (* ResWort ist global um Stack zu entlasten *)
      Index       : CARDINAL;               (* Indexzähler für ResWort    *)
      Reserviert  : BOOLEAN;

   BEGIN
     Reserviert := FALSE;
     Index := 1;
     WHILE (Index <= Eintraege) AND NOT Reserviert DO
        Reserviert := (Compare(Wort,ResWort[Index]) = 0);
        INC(Index);
     END;
     RETURN Reserviert;
   END ReserviertesWort;


BEGIN
   IF Knoten^.Wortende = TRUE THEN
      Wort[level] := Knoten^.Buchstabe;
      Wort[level+1] := 0C;
      IF NOT ReserviertesWort(Wort) THEN    (* Ausgabe des Wortes usw.    *)
         WriteBytes(OutputFile,ADR(Wort),Length(Wort),actual);
         WriteCard(Knoten^.Anzahl,25-level);
         WriteBytes(OutputFile,ADR("   "),3,actual);
         WriteChar(OutputFile,Knoten^.Typ);
         WriteChar(OutputFile," ");
         Zaehler := 0;
         pointer := Knoten^.Erster;
         REPEAT
            IF Zaehler = 8 THEN
               Zaehler := 0;
               NeueZeile;
               WriteBytes(OutputFile,ADR("                               "),31,actual);
            END;
            WriteCard(pointer^.Nummer,5);
            INC(Zaehler);
            pointer := pointer^.Next;
         UNTIL pointer = NIL;
         NeueZeile;
      END;
   END;
   IF Knoten^.vertikal # NIL THEN           (* Es geht doch nichts über   *)
      Wort[level] := Knoten^.Buchstabe;     (* eine schöne Rekursion      *)
      PrintTrie(Knoten^.vertikal,level+1);
   END;
   IF Knoten^.horizontal # NIL THEN
      Wort[level] := Knoten^.Buchstabe;
      PrintTrie(Knoten^.horizontal,level);
   END;
END PrintTrie;



PROCEDURE Initialisierung();

(* Schreiben des Headers und Öffnen der Files                             *)

VAR
  len : INTEGER;

BEGIN
   WriteLn;
   WriteString("CrossReference-Lister    V1.1         © Andreas Pahl");
   WriteLn;
   WriteLn;
   WriteString("Sourcelisting : ");
   IF NumArgs() >= 1 THEN               (* Source als Argument übergeben? *)
      GetArg(1,DosName,Len);
      WriteString(DosName);
      WriteLn;
   ELSE                                     (* sonst abfragen             *)
      WriteLn;
      WriteLn;
      WriteString("in>");
      ReadLn( DosName, len );  (* statt InOut.ReadString(DosName); *)
   END;
   Lookup(QuellFile,DosName,1024,FALSE);
   IF QuellFile.res # done THEN
      WriteLn;
      WriteString("Datei konnte nicht geöffnet werden.");
      WriteLn;
      returnVal := 10;                  (* nützlich für Batch-Verarbeitung *)
      Terminate( CurrentLevel() );
   END;
   WriteLn;

   IF NumArgs() >= 2 THEN               (* Quelle als Argument übergeben? *)
     WriteString("Ausgabedatei : ");
     GetArg(2,OutputName,Len);
     WriteString(OutputName);
     WriteLn;
   ELSE                                     (* sonst abfragen             *)
     WriteString("Ausgabedatei : (prt: für Ausgabe auf Drucker)");
     WriteLn;
     WriteString("out>");
     ReadLn( OutputName, len );  (* ReadString(OutputName); *)
   END;
   Lookup(OutputFile,OutputName,1024,TRUE);
   IF OutputFile.res # done THEN
      WriteLn;
      WriteString("Datei konnte nicht geöffnet werden.");
      WriteLn;
      Close(QuellFile);
      returnVal := 10;                  (* nützlich für Batch-Verarbeitung *)
      Terminate( CurrentLevel() );
   END;
END Initialisierung;



BEGIN   (* Hauptprogramm *)

  (* Header ausgeben, Programm-Argumente verarbeiten, Files öffnen *)

  Initialisierung;

  (* Alle Reservierten Worte und Vordefinierten Bezeichner von Seite 5-15
     des Handbuches zur Version 3.2d. Zwei fehlen aber in der Auflistung :

     FOR als Reserviertes Wort
     VAL als Vordefinierter Bezeichner

  *)

  ResWort[ 1]:="AND";           ResWort[21]:="LOOP";
  ResWort[ 2]:="ARRAY";         ResWort[22]:="MOD";
  ResWort[ 3]:="BEGIN";         ResWort[23]:="MODULE";
  ResWort[ 4]:="BY";            ResWort[24]:="NOT";
  ResWort[ 5]:="CASE";          ResWort[25]:="OF";
  ResWort[ 6]:="CONST";         ResWort[26]:="OR";
  ResWort[ 7]:="DEFINITION";    ResWort[27]:="POINTER";
  ResWort[ 8]:="DIV";           ResWort[28]:="PROCEDURE";
  ResWort[ 9]:="DO";            ResWort[29]:="QUALIFIED";
  ResWort[10]:="ELSE";          ResWort[30]:="RECORD";
  ResWort[11]:="ELSIF";         ResWort[31]:="REPEAT";
  ResWort[12]:="END";           ResWort[32]:="RETURN";
  ResWort[13]:="EXIT";          ResWort[33]:="SET";
  ResWort[14]:="EXPORT";        ResWort[34]:="THEN";
  ResWort[15]:="FOR";           ResWort[35]:="TO";
  ResWort[16]:="FROM";          ResWort[36]:="TYPE";
  ResWort[17]:="IF";            ResWort[37]:="UNTIL";
  ResWort[18]:="IMPLEMENTATION";ResWort[38]:="VAR";
  ResWort[19]:="IMPORT";        ResWort[39]:="WHILE";
  ResWort[20]:="IN";            ResWort[40]:="WITH";

  ResWort[41]:="BPOINTER";      ResWort[51]:="DEC";
  ResWort[42]:="CODE";          ResWort[52]:="EXCL";
  ResWort[43]:="FORWARD";       ResWort[53]:="FALSE";
  ResWort[44]:="REM";           ResWort[54]:="FLOAT";
  ResWort[45]:="ABS";           ResWort[55]:="HALT";
  ResWort[46]:="BOOLEAN";       ResWort[56]:="HIGH";
  ResWort[47]:="CAP";           ResWort[57]:="INC";
  ResWort[48]:="CARDINAL";      ResWort[58]:="INCL";
  ResWort[49]:="CHAR";          ResWort[59]:="INTEGER";
  ResWort[50]:="CHR";           ResWort[60]:="LONGCARD";

  ResWort[61]:="LONGINT";       ResWort[66]:="ODD";
  ResWort[62]:="LONGREAL";      ResWort[67]:="ORD";
  ResWort[63]:="MAX";           ResWort[68]:="PROC";
  ResWort[64]:="MIN";           ResWort[69]:="REAL";
  ResWort[65]:="NIL";           ResWort[70]:="SIZE";

  ResWort[71]:="TRUE";          ResWort[72]:="TRUNC";
  ResWort[73]:="VAL";

  (* Es geht los: *)

  Allocate(Baum,SIZE(Node));
  Baum := NIL;
  Ende := FALSE;
  ZeilenNummer := 1;
  LOOP
    LiesZeichen(Zeichen);
    IF QuellFile.eof THEN
      EXIT;
    END;
    IF NOT Ende THEN
      (* kein Bezeichner darf mit einer Ziffer beginnen *)
      IF (Zeichen >= "0") AND (Zeichen <= "9") THEN
        (* Ziffern überlesen *)
        REPEAT
          Ende := FALSE;
          LiesZeichen(Zeichen);
          IF QuellFile.eof THEN EXIT END;
        UNTIL Ende;
      ELSE
        Insert(Baum,0);
      END;
    END;
    Ende := FALSE;
  END;
  Close(QuellFile);

  (* Ausgabe der Überschrift der Tabelle                                 *)

  WriteBytes(OutputFile,ADR(Ueberschrift),SIZE(Ueberschrift),actual);
  NeueZeile;
  WriteBytes(OutputFile,ADR(Unterstreichen),SIZE(Unterstreichen),actual);
  NeueZeile;

  PrintTrie(Baum,0);

  Close(OutputFile);

  waitCloseGadget := FALSE;
END Cross.
