home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / INFO / TURBOPAS / MISC.ZIP / COMPARE.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1986-02-02  |  16.5 KB  |  497 lines

  1. {.F-}
  2. {
  3. compare two similar text files for mismatches
  4.  
  5. call as COMPARE FileA FileB [Output Redirection]
  6.  
  7. modified Kim Kokkonen, TurboPower Software, 1986
  8.      minimize code for Turbo Pascal version 3.0.
  9. modified Willett Kempton, mich state u, 1984
  10.      add options for ANSI output and CPM support.
  11. written and copyright by James F. Miner, University of Minnesota, 1977
  12.      for CDC mainframe.
  13. }
  14. {.F+}
  15.  
  16. {$P128}
  17.  
  18. PROGRAM compare;
  19.  
  20. CONST
  21.   linelength = 128;           {maximum significant input line length}
  22.   minlinesformatch = 4;       {number of consecutive equivalent
  23.                               lines to end a mis-match}
  24.   markunequalcolumns = 5;     {unequal lines are paired, and columns
  25.                               marked, if mismatch < this length}
  26.   minread = 40;               {try for multiple reads (=1 saves heap)}
  27.   prespace = 7;               {spaces before text on reportfile lines}
  28.   tab = 9;                    {ascii tab for aligning ^ indicator}
  29.   barchar = '|';              {separator between line number and text}
  30.  
  31. TYPE
  32.   textfile = Text[512];
  33.   argstrtype = STRING[64];
  34.   linepointer = ^line;
  35.   lineimagetype = ARRAY[1..linelength] OF Char;
  36.  
  37.   line =                      {single line buffer}
  38.   RECORD
  39.     nextline : linepointer;
  40.     Length : 0..linelength;
  41.     image : lineimagetype
  42.   END;
  43.  
  44.   stream =                    {bookkeeping for each input file}
  45.   RECORD
  46.     name : Char;
  47.     cursor, head, tail : linepointer;
  48.     cursorlineno, headlineno, taillineno : Integer;
  49.     endfile : Boolean
  50.   END;
  51.  
  52. VAR
  53.   filea, fileb : textfile;
  54.   a, b : stream;
  55.   endfile : Boolean;          {true if end of stream a or b}
  56.   freelines : linepointer;    {free list of line buffers}
  57.   same : Boolean;             {true if no mis-matches occur}
  58.   linestoolong : Boolean;     {true if some lines not completely checked}
  59.  
  60.   PROCEDURE comparefiles;
  61.   VAR
  62.     match : Boolean;
  63.  
  64.     FUNCTION endstream(VAR x : stream) : Boolean;
  65.     BEGIN                     {endstream}
  66.       endstream := (x.cursor = NIL) AND x.endfile
  67.     END;                      {endstream}
  68.  
  69.     PROCEDURE Mark(VAR x : stream);
  70.       {causes beginning of stream to be positioned before}
  71.       {current stream cursor.  buffers get reclaimed, line}
  72.       {counters reset, etc.}
  73.     VAR
  74.       p : linepointer;
  75.     BEGIN                     {mark}
  76.       WITH x DO IF head <> NIL THEN BEGIN
  77.         WHILE head <> cursor DO BEGIN
  78.           {reclaim buffers}
  79.           WITH head^ DO BEGIN
  80.             p := nextline;
  81.             nextline := freelines;
  82.             freelines := head
  83.           END;
  84.           head := p
  85.         END;
  86.         headlineno := cursorlineno;
  87.         IF cursor = NIL THEN BEGIN
  88.           tail := NIL;
  89.           taillineno := cursorlineno
  90.         END
  91.       END
  92.     END;                      {mark}
  93.  
  94.     PROCEDURE movecursor(VAR x : stream; VAR filex : textfile);
  95.       {filex is the input file associated with stream x.  the}
  96.       {cursor for x is moved forward one line, reading from x}
  97.       {if necessary, and incrementing the line count.  endfile}
  98.       {is set if eof is encountered on either stream.}
  99.  
  100.       PROCEDURE readline;
  101.         {read from filex}
  102.       CONST
  103.         ordcr = 13;
  104.         mask = 127;           {ascii values; to equate cr and ri}
  105.       VAR
  106.         newline : linepointer;
  107.         c, c2 : 0..linelength;
  108.         ch : Char;
  109.         morereads : Integer;
  110.       BEGIN                   {readline}
  111.         morereads := minread;
  112.         WHILE (NOT x.endfile) AND (morereads > 0) DO BEGIN
  113.           {allocate space for the line}
  114.           newline := freelines;
  115.           IF newline <> NIL THEN
  116.             freelines := freelines^.nextline
  117.           ELSE BEGIN
  118.             {need more from heap}
  119.             {should check for heap exhaustion here}
  120.             New(newline);
  121.             newline^.Length := linelength; {new: must blank fill}
  122.           END;
  123.           c := 0;
  124.           WITH newline^ DO BEGIN
  125.             IF NOT EoF(filex) THEN
  126.               {first char of line}
  127.               Read(filex, ch);
  128.             WHILE ((Ord(ch) AND mask) <> ordcr)
  129.             AND (c < linelength)
  130.             AND (NOT EoF(filex)) DO BEGIN
  131.               c := Succ(c);
  132.               image[c] := ch;
  133.               Read(filex, ch);
  134.             END;
  135.             WHILE ((Ord(ch) AND mask) <> ordcr) AND (NOT EoF(filex)) DO BEGIN
  136.               Read(filex, ch);
  137.               linestoolong := True;
  138.             END;
  139.             IF NOT EoF(filex) THEN Read(filex, ch); {should be the lf}
  140.           END;
  141.  
  142.           WHILE (newline^.image[c] = ' ') AND (c > 1) DO c := Pred(c);
  143.           WITH newline^ DO
  144.             IF c < Length THEN
  145.               FOR c2 := Succ(c) TO Length DO image[c2] := ' ';
  146.           newline^.Length := c;
  147.           newline^.nextline := NIL;
  148.           IF x.tail = NIL THEN BEGIN
  149.             x.head := newline;
  150.             x.taillineno := 1;
  151.             x.headlineno := 1
  152.           END
  153.           ELSE BEGIN
  154.             x.tail^.nextline := newline;
  155.             x.taillineno := Succ(x.taillineno)
  156.           END;
  157.           x.tail := newline;
  158.           x.endfile := EoF(filex);
  159.           morereads := Pred(morereads);
  160.         END
  161.       END;                    {readline}
  162.  
  163.     BEGIN                     {movecursor}
  164.       IF x.cursor <> NIL THEN BEGIN
  165.         IF x.cursor = x.tail THEN readline;
  166.         x.cursor := x.cursor^.nextline;
  167.         IF x.cursor = NIL THEN endfile := True;
  168.         x.cursorlineno := Succ(x.cursorlineno)
  169.       END ELSE IF NOT x.endfile THEN BEGIN
  170.         {beginning of stream}
  171.         readline;
  172.         x.cursor := x.head;
  173.         x.cursorlineno := x.headlineno
  174.       END ELSE
  175.         {end of stream }
  176.         endfile := True;
  177.     END;                      {movecursor}
  178.  
  179.     PROCEDURE backtrack(VAR x : stream; VAR xlines : Integer);
  180.       {causes the current position of stream x to become that}
  181.       {of the last mark operation.  i.e., the current line  }
  182.       {when the stream was marked last becomes the new cursor. }
  183.       {xlines is set to the number of lines from the new cursor }
  184.       {to the old cursor, inclusive.}
  185.     BEGIN                     {backtrack}
  186.       xlines := Succ(x.cursorlineno)-x.headlineno;
  187.       x.cursor := x.head; x.cursorlineno := x.headlineno;
  188.       endfile := endstream(a) OR endstream(b)
  189.     END;                      {backtrack}
  190.  
  191.     PROCEDURE comparelines(VAR match : Boolean);
  192.       {compare the current lines of streams a and b, returning}
  193.       {match to signal their (non-) equivalence.  eof on both streams}
  194.       {is considered a match, but eof on only one stream is a mismatch}
  195.     BEGIN                     {comparelines}
  196.       IF (a.cursor = NIL) OR (b.cursor = NIL) THEN
  197.         match := endstream(a) AND endstream(b)
  198.       ELSE BEGIN
  199.         match := (a.cursor^.Length = b.cursor^.Length);
  200.         IF match THEN
  201.           match := (a.cursor^.image = b.cursor^.image)
  202.       END
  203.     END;                      {comparelines}
  204.  
  205.     PROCEDURE findmismatch;
  206.     BEGIN                     {findmismatch}
  207.       {not endfile and match}
  208.       REPEAT                  {comparenextlines}
  209.         movecursor(a, filea);
  210.         movecursor(b, fileb);
  211.         Mark(a);
  212.         Mark(b);
  213.         comparelines(match)
  214.       UNTIL endfile OR NOT match;
  215.     END;                      {findmismatch}
  216.  
  217.     PROCEDURE findmatch;
  218.     VAR
  219.       advanceb : Boolean;     {toggle one-line lookahead between streams}
  220.  
  221.       PROCEDURE search(VAR x : stream; {stream to search}
  222.                        VAR filex : textfile;
  223.                        VAR y : stream; {stream to lookahead}
  224.                        VAR filey : textfile);
  225.         {look ahead one line on stream y, and search for that line}
  226.         {backtracking on stream x.}
  227.       VAR
  228.         count : Integer;      {number of lines backtracked on x}
  229.  
  230.         PROCEDURE checkfullmatch;
  231.           {from the current positions in x and y, which match,}
  232.           {make sure that the next minlinesformatch-1 lines also}
  233.           {match, or else set match := false. }
  234.         VAR
  235.           n : Integer;
  236.           savexcur, saveycur : linepointer;
  237.           savexline, saveyline : Integer;
  238.         BEGIN                 {checkfullmatch}
  239.           savexcur := x.cursor;
  240.           saveycur := y.cursor;
  241.           savexline := x.cursorlineno;
  242.           saveyline := y.cursorlineno;
  243.           comparelines(match);
  244.           n := Pred(minlinesformatch);
  245.           WHILE match AND (n <> 0) DO BEGIN
  246.             movecursor(x, filex);
  247.             movecursor(y, filey);
  248.             comparelines(match);
  249.             n := Pred(n)
  250.           END;
  251.           x.cursor := savexcur;
  252.           x.cursorlineno := savexline;
  253.           y.cursor := saveycur;
  254.           y.cursorlineno := saveyline;
  255.         END;                  {checkfullmatch}
  256.  
  257.       BEGIN                   {search}
  258.         movecursor(y, filey);
  259.         backtrack(x, count);
  260.         checkfullmatch;
  261.         count := Pred(count);
  262.         WHILE (count <> 0) AND NOT match DO BEGIN
  263.           movecursor(x, filex);
  264.           count := Pred(count);
  265.           checkfullmatch;
  266.         END
  267.       END;                    {search}
  268.  
  269.       PROCEDURE printmismatch;
  270.       VAR
  271.         emptya, emptyb : Boolean;
  272.  
  273.         PROCEDURE writeoneline(name : Char; l : Integer; p : linepointer);
  274.         VAR i : Integer;
  275.         BEGIN                 {writeoneline}
  276.           Write(name, l:5, barchar);
  277.           IF p^.Length <> 0 THEN
  278.             FOR i := 1 TO p^.Length DO Write(p^.image[i]);
  279.           WriteLn;
  280.         END;                  {writeoneline }
  281.  
  282.         PROCEDURE writetext(VAR x : stream);
  283.           {write from x.head to one line before x.cursor}
  284.         VAR
  285.           p, q : linepointer; lineno : Integer;
  286.         BEGIN                 {writetext}
  287.           p := x.head; q := x.cursor; lineno := x.headlineno;
  288.           WHILE (p <> NIL) AND (p <> q) DO BEGIN
  289.             writeoneline(x.name, lineno, p);
  290.             p := p^.nextline;
  291.             lineno := Succ(lineno);
  292.           END;
  293.           IF p = NIL THEN WriteLn(' *** eof ***');
  294.           {writeln}
  295.         END;                  {writetext}
  296.  
  297.         PROCEDURE writepairs(pa, pb : linepointer; la, lb : Integer);
  298.           {this writes from the head to the cursor, like procedure writetext.}
  299.           {unlike procedure writetext, this writes from both files at once,  }
  300.           {compares columns within lines, and marks unequal columns    }
  301.         VAR
  302.           tempa, tempb : lineimagetype;
  303.           col, maxcol : Integer;
  304.         BEGIN                 {writepairs}
  305.           REPEAT
  306.             writeoneline('a', la, pa);
  307.             writeoneline('b', lb, pb);
  308.             tempa := pa^.image;
  309.             tempb := pb^.image;
  310.             IF pa^.Length > pb^.Length THEN
  311.               maxcol := pa^.Length
  312.             ELSE
  313.               maxcol := pb^.Length;
  314.             Write(' ':prespace);
  315.             FOR col := 1 TO maxcol DO
  316.               IF tempa[col] <> tempb[col] THEN
  317.                 Write('^')
  318.               ELSE BEGIN
  319.                 IF tempa[col] = Chr(tab) THEN
  320.                   Write(Chr(tab))
  321.                 ELSE
  322.                   Write(' ');
  323.               END;
  324.             WriteLn;
  325.             pa := pa^.nextline;
  326.             la := Succ(la);
  327.             pb := pb^.nextline;
  328.             lb := Succ(lb);
  329.           UNTIL (pa = a.cursor) OR (pa = NIL);
  330.         END;                  {writepairs}
  331.  
  332.         PROCEDURE writelineno(VAR x : stream);
  333.         VAR
  334.           f, l : Integer;
  335.         BEGIN                 {writelineno}
  336.           Write(' file', x.name, ', ');
  337.           f := x.headlineno;
  338.           l := Pred(x.cursorlineno);
  339.           Write('line');
  340.           IF f = l THEN
  341.             Write(' ', f)
  342.           ELSE
  343.             Write('s ', f, ' - ', l);
  344.           IF x.cursor = NIL THEN Write(' (before eof)');
  345.         END;                  {writelineno}
  346.  
  347.         PROCEDURE printextratext(VAR x, y : stream);
  348.         BEGIN                 {printextratext}
  349.           Write('extra text:  on file', x.name, ', ');
  350.           IF y.head = NIL THEN
  351.             WriteLn(' before eof on file', y.name)
  352.           ELSE
  353.             WriteLn(' between lines ', Pred(y.headlineno), ' and ',
  354.             y.headlineno, ' of file', y.name);
  355.           WriteLn;
  356.           writetext(x);
  357.         END;                  {printextratext}
  358.  
  359.       BEGIN                   {printmismatch}
  360.         WriteLn;
  361.         WriteLn(' ':prespace,
  362.         '*************************************************************');
  363.         emptya := (a.head = a.cursor);
  364.         emptyb := (b.head = b.cursor);
  365.         IF emptya OR emptyb THEN
  366.           IF emptya THEN
  367.             printextratext(b, a)
  368.           ELSE
  369.             printextratext(a, b)
  370.         ELSE BEGIN
  371.           Write('mismatch:   ');
  372.           writelineno(a);
  373.           Write('  not equal to ');
  374.           writelineno(b);
  375.           WriteLn(':');
  376.           WriteLn;
  377.           IF (markunequalcolumns > Pred(a.cursorlineno-a.headlineno)) AND
  378.           ((a.cursorlineno-a.headlineno) = (b.cursorlineno-b.headlineno))
  379.           THEN
  380.             writepairs(a.head, b.head, a.headlineno, b.headlineno)
  381.           ELSE BEGIN
  382.             writetext(a);
  383.             WriteLn(' ':prespace, '----------------');
  384.             writetext(b)
  385.           END;
  386.         END
  387.       END;                    {printmismatch}
  388.  
  389.     BEGIN                     {findmatch}
  390.       {not match}
  391.       advanceb := True;
  392.       REPEAT
  393.         IF NOT endfile THEN
  394.           advanceb := NOT advanceb
  395.         ELSE
  396.           advanceb := endstream(a);
  397.         IF advanceb THEN
  398.           search(a, filea, b, fileb)
  399.         ELSE
  400.           search(b, fileb, a, filea)
  401.       UNTIL match;
  402.       printmismatch;
  403.     END;                      {findmatch}
  404.  
  405.   BEGIN                       {comparefiles}
  406.     match := True;            {i.e., beginnings-of-files match}
  407.     REPEAT
  408.       IF match THEN
  409.         findmismatch
  410.       ELSE BEGIN
  411.         same := False;
  412.         findmatch
  413.       END
  414.     UNTIL endfile AND match;
  415.   END;                        {comparefiles}
  416.  
  417.   PROCEDURE initialize;
  418.     {setup files, using names from command line}
  419.   VAR
  420.     argfilea, argfileb, argfilec : argstrtype;
  421.     nfiles : Integer;
  422.     argumentsok : Boolean;
  423.  
  424.     FUNCTION resetok(VAR filex : textfile; fname : argstrtype) : Boolean;
  425.     BEGIN                     {resetok}
  426.       {$I-}
  427.       Assign(filex, fname);
  428.       Reset(filex);
  429.       resetok := (IOResult = 0);
  430.       {$I+}
  431.     END;                      {resetok}
  432.  
  433.     PROCEDURE initstream(namechar : Char; VAR x : stream;
  434.                          VAR filex : textfile; arg : argstrtype);
  435.     BEGIN                     {initstream}
  436.       IF resetok(filex, arg) THEN
  437.         x.endfile := EoF(filex)
  438.       ELSE BEGIN
  439.         Write(' error   cannot find');
  440.         argumentsok := False;
  441.       END;
  442.       WITH x DO BEGIN
  443.         name := namechar;
  444.         cursor := NIL;
  445.         head := NIL;
  446.         tail := NIL;
  447.         cursorlineno := 0;
  448.         headlineno := 0;
  449.         taillineno := 0;
  450.         WriteLn(' file', name, ': ', arg);
  451.       END;
  452.     END;                      {initstream}
  453.  
  454.   BEGIN                       {initialize}
  455.     nfiles := ParamCount;
  456.     IF (nfiles <> 2) THEN BEGIN
  457.       WriteLn('Usage:  COMPARE  FileA  FileB  [Output Redirection]');
  458.       argumentsok := False;
  459.     END ELSE BEGIN
  460.       argfilea := ParamStr(1);
  461.       argfileb := ParamStr(2);
  462.       argumentsok := resetok(filea, argfilea) AND resetok(fileb, argfileb);
  463.       Write('compare  (options:');
  464.       WriteLn(' rematch on ', minlinesformatch, ' lines.)');
  465.       initstream('a', a, filea, argfilea);
  466.       initstream('b', b, fileb, argfileb);
  467.       endfile := a.endfile OR b.endfile;
  468.       linestoolong := False;
  469.       same := True;
  470.       freelines := NIL;
  471.     END;
  472.     IF NOT argumentsok THEN endfile := True;
  473.   END;                        {initialize}
  474.  
  475.   PROCEDURE summarize;
  476.   BEGIN
  477.     WriteLn;
  478.     IF same THEN
  479.       WriteLn(' ', Pred(a.cursorlineno), ' lines read; no differences.')
  480.     ELSE
  481.       WriteLn(' files are different.');
  482.     IF linestoolong THEN BEGIN
  483.       WriteLn;
  484.       WriteLn(' warning:  some lines were longer than ', linelength, ' characters.');
  485.       WriteLn(' ':11, 'they were not compared past that point.');
  486.     END;
  487.   END;                        {summarize}
  488.  
  489. BEGIN                         {compare}
  490.   WriteLn;
  491.   initialize;
  492.   IF NOT endfile THEN BEGIN
  493.     comparefiles;
  494.     summarize;
  495.   END;
  496. END.                          {compare}
  497.