 PROGRAM SRCCOM;  (* SOURCE COMPARE *)
{  I GOT THIS PROGRAM FROM THE PASCAL/Z USERS PROGRAM DISKS   }
{  THEY GOT IT FROM THE UCSD PASCAL USERS GROUP               }
{  Revised 5/3/82 by Henry Lucas                            }
  
{Program to compare two sourcecode files and output the differences,
 if any.  Useful to compare two similar textfiles to find out whether and 
 where they have been changed.  Part of original UCSD I.4 release--author
 is unknown.}

CONST
  VERSION = 'v203 5-3-82';
  MINLINESFORMATCH = 6;
  MAXTRY = 150;  (*<<<10/27/77 GLH. LIMIT ON SEARCH AHEAD FOR MATCH*)
  LINELENGTH = 132;

 TYPE

  LINEPOINTER = ^LINE;
  LINE =
   RECORD  (*<<<10/26/77 GLH*)
    NEXTLINE : LINEPOINTER;
    IMAGE : STRING[LINELENGTH]
   END;

  STREAM =
   RECORD
    CURSOR, HEAD, TAIL : LINEPOINTER;
    CURSORLINENO, HEADLINENO, TAILLINENO : INTEGER;
    ENDFILE : BOOLEAN
   END;

 VAR

  TITLEA, TITLEB: STRING;  (*<<<10/27/77 GLH*)
  FILEA, FILEB  : TEXT;
  A, B : STREAM;
  MATCH : BOOLEAN;
  ENDFILE : BOOLEAN;
  TEMPLINE : STRING[LINELENGTH];  (*<<<10/26/77 GLH*)
  FREELINES : LINEPOINTER;
  SAME : BOOLEAN;
  STR : STRING;
  MAXPTR : WORD;

  @sfp : external word;
  EXTERNAL PROCEDURE @HLT;

 PROCEDURE COMPARE;

 FUNCTION ENDSTREAM(VAR X : STREAM) : BOOLEAN;
  BEGIN (* ENDSTREAM *)
   ENDSTREAM := (X.CURSOR = NIL) AND X.ENDFILE
  END;  (* ENDSTREAM *)

  PROCEDURE MARK(VAR X : STREAM);

   (* CAUSES BEGINNING OF STREAM TO BE POSITIONED BEFORE *)
   (* CURRENT STREAM CURSOR.  BUFFERS GET RECLAIMED, LINE *)
   (* COUNTERS RESET, ETC. *)

   PROCEDURE COLLECT(FWA, LWAPLUS1 : LINEPOINTER);
    VAR P : LINEPOINTER;
   BEGIN (* COLLECT *)
    WHILE FWA <> LWAPLUS1 DO
     BEGIN P := FWA^.NEXTLINE;
      FWA^.NEXTLINE := FREELINES;  FREELINES := FWA;
      FWA := P
     END
   END;  (* COLLECT *)

  BEGIN (* MARK *)
   IF X.HEAD <> NIL THEN
    BEGIN
     COLLECT(X.HEAD, X.CURSOR);
     X.HEAD := X.CURSOR;  X.HEADLINENO := X.CURSORLINENO;
     IF X.CURSOR = NIL THEN
      BEGIN  X.TAIL := NIL;  X.TAILLINENO := X.CURSORLINENO  END
    END
  END;  (* MARK *)

  PROCEDURE MOVECURSOR(VAR X : STREAM;  VAR FILEX : TEXT);

   (* FILEX IS THE INPUT FILE ASSOCIATED WITH STREAM X.  THE *)
   (* CURSOR FOR X IS MOVED FORWARD ONE LINE, READING FROM X *)
   (* IF NECESSARY, AND INCREMENTING THE LINE COUNT.  ENDFILE *)
   (* IS SET IF EOF ENCOUNTERED ON EITHER STREAM. *)

   PROCEDURE READLINE;
    VAR
     NEWLINE : LINEPOINTER;
   BEGIN (* READLINE *)
    IF NOT X.ENDFILE THEN
     BEGIN
      (*<<<10/26/77 GLH. CHANGED WAY CHARS GET INTO TEMPLINE*)
      READLN(FILEX, TEMPLINE);
      NEWLINE := FREELINES;
      IF NEWLINE <> NIL THEN FREELINES := FREELINES^.NEXTLINE
        ELSE BEGIN
         NEW(NEWLINE);
         MAXPTR:=WRD(NEWLINE) + WRD(SIZEOF(LINE));
         IF MAXPTR > @SFP THEN
          BEGIN
            WRITELN(' NOT ENOUGH SPACE: STOPPING');
            @HLT
          END
        END;

      NEWLINE^.IMAGE := TEMPLINE;  (*<<<10/26/77 GLH*)
      NEWLINE^.NEXTLINE := NIL;
      IF X.TAIL = NIL THEN
       BEGIN  X.HEAD := NEWLINE;
        X.TAILLINENO := 1;  X.HEADLINENO := 1
       END
      ELSE
       BEGIN X.TAIL^.NEXTLINE := NEWLINE;
        X.TAILLINENO := X.TAILLINENO + 1
       END;
      X.TAIL := NEWLINE;
      X.ENDFILE := EOF(FILEX);
     END
   END;  (* READLINE *)

  BEGIN (* MOVECURSOR *)
    IF X.CURSOR <> NIL THEN
    BEGIN
     IF X.CURSOR = X.TAIL THEN READLINE;
     X.CURSOR := X.CURSOR^.NEXTLINE;
     IF X.CURSOR = NIL THEN ENDFILE := TRUE;
     X.CURSORLINENO := X.CURSORLINENO + 1
    END
   ELSE
    IF NOT X.ENDFILE THEN (* BEGINNING OF STREAM *)
     BEGIN
      READLINE; X.CURSOR := X.HEAD;
      X.CURSORLINENO := X.HEADLINENO
     END
    ELSE  (* END OF STREAM *)
     ENDFILE := TRUE;
  END;  (* MOVECURSOR *)

  PROCEDURE BACKTRACK(VAR X : STREAM;  VAR XLINES : INTEGER);

   (* CAUSES THE CURRENT POSITION OF STREAM  THE NEW CURRENT *)
   (* THE LINE COUNTER IS RETURNED IN XLINES.  IT IS THE NUMBER *)
   (* OF THE CURRENT LINE (BEFORE BACKTRACK) RELATIVE TO BEGINNING *)
   (* OF STREAM. *)

  BEGIN (* BACKTRACK *)
   XLINES := X.CURSORLINENO + 1 - X.HEADLINENO;
   X.CURSOR := X.HEAD;  X.CURSORLINENO := X.HEADLINENO;
   ENDFILE := ENDSTREAM(A) OR ENDSTREAM(B)
  END;  (* BACKTRACK *)

  PROCEDURE COMPARELINES(VAR MATCH : BOOLEAN);
   (* COMPARE THE CURRENT LINES OF STREAMS A AND B, RETURNING *)
   (* MATCH TO SIGNAL THEIR (NON-) EQUIVALENCE.  EOF ON BOTH STREAMS *)
   (* IS CONSIDERED A MATCH, BUT EOF ON ONLY ONE STREAM IS A MISMATCH *)

  BEGIN (* COMPARELINES *)
   IF (A.CURSOR = NIL) OR (B.CURSOR = NIL) THEN
    MATCH := ENDSTREAM(A) AND ENDSTREAM(B)
   ELSE
      MATCH := (A.CURSOR^.IMAGE = B.CURSOR^.IMAGE)
  END;  (* COMPARELINES *)

  PROCEDURE FINDMISMATCH;

  BEGIN (* FINDMISMATCH *)
   (* NOT ENDFILE AND MATCH *)
   REPEAT  (* COMPARENEXTLINES *)
    MOVECURSOR(A, FILEA); MOVECURSOR(B,FILEB);
    MARK(A); MARK(B);
    COMPARELINES(MATCH)
   UNTIL ENDFILE OR NOT MATCH;
  END;  (* FINDMISMATCH *)

  PROCEDURE FINDMATCH;

   VAR
      TRYCOUNT : INTEGER;

   PROCEDURE SEARCH(VAR X : STREAM; (* STREAM TO SEARCH *)
                    VAR FILEX : TEXT;
                    VAR Y : STREAM; (* STREAM TO LOOKAHEAD *)
                    VAR FILEY : TEXT);

    (* LOOK AHEAD ONE LINE ON STREAM Y, AND SEARCH FOR THAT LINE *)
            (* BACKTRACKING ON STREAM X. *)

    VAR
     COUNT : INTEGER;  (* NUMBER OF LINES BACKTRACKED ON X *)

    PROCEDURE CHECKFULLMATCH;
     (* FROM THE CURRENT POSITIONS IN X AND Y, WHICH MATCH, *)
     (* MAKE SURE THAT THE NEXT MINLINESFORMATCH-1 LINES ALSO *)
     (* MATCH, OR ELSE SET MATCH := FALSE.  *)
     VAR
      N : INTEGER;
      SAVEXCUR, SAVEYCUR : LINEPOINTER;
      SAVEXLINE, SAVEYLINE : INTEGER;
    BEGIN (* CHECKFULLMATCH *)
     SAVEXCUR := X.CURSOR;  SAVEYCUR := Y.CURSOR;
     SAVEXLINE := X.CURSORLINENO;  SAVEYLINE := Y.CURSORLINENO;
     COMPARELINES(MATCH);
     N := MINLINESFORMATCH - 1;
     WHILE MATCH AND (N <> 0) DO
      BEGIN  MOVECURSOR(X, FILEX);  MOVECURSOR(Y, FILEY);
       COMPARELINES(MATCH);  N := N - 1
      END;
     X.CURSOR := SAVEXCUR;  X.CURSORLINENO := SAVEXLINE;
     Y.CURSOR := SAVEYCUR;  Y.CURSORLINENO := SAVEYLINE;
    END;  (* CHECKFULLMATCH *)

   BEGIN (* SEARCH *)
    MOVECURSOR(Y, FILEY);  BACKTRACK(X, COUNT);
    CHECKFULLMATCH;  COUNT := COUNT - 1;
     WHILE (COUNT <> 0) AND NOT MATCH DO
     BEGIN
      MOVECURSOR(X, FILEX);  COUNT := COUNT - 1;
      CHECKFULLMATCH
     END
   END;  (* SEARCH *)

   PROCEDURE PRINTMISMATCH;
    VAR
     EMPTYA, EMPTYB : BOOLEAN;

    PROCEDURE WRITETEXT(P, Q : LINEPOINTER);
    BEGIN (* WRITETEXT *)
     WRITELN;
     WHILE (P <> NIL) AND (P <> Q) DO
      BEGIN  WRITE(' * ');
        WRITELN (P^.IMAGE);
       P := P^.NEXTLINE
      END;
     IF P = NIL THEN WRITELN(' *** EOF ***');
     WRITELN
    END;  (* WRITETEXT *)

    PROCEDURE WRITELINENO(VAR X : STREAM);
     VAR
      F, L : INTEGER;
    BEGIN (* WRITELINENO *)
     F := X.HEADLINENO;  L := X.CURSORLINENO - 1;
     WRITE('LINE');
     IF F = L THEN WRITE(' ', F)
     ELSE WRITE('S ', F, ' TO ', L);
     IF X.CURSOR = NIL THEN WRITE(' (BEFORE EOF)');
    END;  (* WRITELINENO *)

    PROCEDURE PRINTEXTRATEXT(VAR X : STREAM; XNAME : STRING;
                             VAR Y : STREAM; YNAME : STRING);
    BEGIN (* PRINTEXTRATEXT *)
                             WRITE(' EXTRA TEXT ON ', XNAME, ', ');
     WRITELINENO(X);  WRITELN;
     IF Y.HEAD = NIL THEN
      WRITELN(' BEFORE EOF ON ', YNAME)
     ELSE
      WRITELN(' BETWEEN LINES ', Y.HEADLINENO-1, ' AND ',
              Y.HEADLINENO, ' OF ', YNAME);
     WRITETEXT(X.HEAD, X.CURSOR)
    END;  (* PRINTEXTRATEXT *)

   BEGIN (* PRINTMISMATCH *)
    WRITELN(' ***********************************');
    EMPTYA := (A.HEAD = A.CURSOR);
    EMPTYB := (B.HEAD = B.CURSOR);
    IF EMPTYA OR EMPTYB THEN
     IF EMPTYA THEN PRINTEXTRATEXT(B, TITLEB, A, TITLEA)
     ELSE PRINTEXTRATEXT(A, TITLEA, B, TITLEB)
    ELSE
     BEGIN
      WRITELN(' MISMATCH:');  WRITELN;
      WRITE(' ', TITLEA, ', ');  WRITELINENO(A);  WRITELN(':');
      WRITETEXT(A.HEAD, A.CURSOR);
      WRITE(' ', TITLEB, ', ');  WRITELINENO(B);  WRITELN(':');
      WRITETEXT(B.HEAD, B.CURSOR)
     END
   END;  (* PRINTMISMATCH *)

  BEGIN (* FINDMATCH *)
        TRYCOUNT := 0;
        WHILE (NOT MATCH) AND (TRYCOUNT <= MAXTRY) DO
          BEGIN
                  SEARCH(A, FILEA, B, FILEB);
            TRYCOUNT := TRYCOUNT+1;
          END;
        IF NOT MATCH THEN
          BEGIN
            TRYCOUNT:=0;
            WHILE (NOT MATCH) AND (TRYCOUNT<=MAXTRY) DO
                BEGIN
                  SEARCH(B, FILEB, A, FILEA);
                  TRYCOUNT:=TRYCOUNT+1;
                END;
          END;
        PRINTMISMATCH;
        IF (NOT MATCH) AND (TRYCOUNT>MAXTRY) THEN
          BEGIN  MARK(A); MARK(B) END;
  END;  (* FINDMATCH *)



 BEGIN (* COMPARE *)
  ENDFILE := FALSE;  MATCH := TRUE;  (* I.E., BOI MATCHES BOI *)
  REPEAT
   IF MATCH THEN FINDMISMATCH ELSE BEGIN SAME := FALSE; FINDMATCH END
  UNTIL ENDFILE AND MATCH;
  MARK(A);  MARK(B);  (* MARK END OF FILES, THEREBY DISPOSING BUFFERS *)
 END;  (* COMPARE *)

 PROCEDURE INITSTREAM(VAR X : STREAM; VAR FILEX : TEXT);
 BEGIN (* INITSTREAM *)
  X.CURSOR := NIL;  X.HEAD := NIL;  X.TAIL := NIL;
  X.CURSORLINENO := 0;  X.HEADLINENO := 0;  X.TAILLINENO := 0;
  X.ENDFILE := EOF(FILEX);
                 END;  (* INITSTREAM *)


BEGIN (* SRCCOM *)
 WRITELN('INPUT FILE NAME:'); READLN(TITLEA);
 ASSIGN(FILEA, TITLEA);
 WRITELN('SECOND INPUT FILE NAME:'); READLN(TITLEB);
 ASSIGN(FILEB, TITLEB);
 RESET(FILEA);  RESET(FILEB);
 INITSTREAM(A, FILEA);  INITSTREAM(B, FILEB);
 FREELINES := NIL;
 WRITELN('Source Compare [', VERSION, ']' );
 WRITELN;
 IF EOF(FILEA) THEN
  BEGIN WRITELN(TITLEA, ' IS EMPTY.');
   IF EOF(FILEB) THEN WRITELN(TITLEB, ' IS EMPTY.')
  END
 ELSE
  IF EOF(FILEB) THEN WRITELN(TITLEB, ' IS EMPTY.')
  ELSE
   BEGIN  SAME := TRUE;
    COMPARE;
    IF SAME THEN WRITELN('No differences encountered.');
   END;
 WRITELN('  MAXIMUM POINTER = ',ord(maxptr));
END.  (* SRCCOM *)
 

