home *** CD-ROM | disk | FTP | other *** search
/ Piper's Pit BBS/FTP: ibm 0000 - 0009 / ibm0000-0009 / ibm0003.tar / ibm0003 / TPOWER54.ZIP / DEMOSRC.ARC / DIFF.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1989-07-10  |  24.8 KB  |  923 lines

  1. {$S-,R-,V-,I-,B-,F-}
  2.  
  3. {*********************************************************}
  4. {*                     DIFF.PAS 5.07                     *}
  5. {*                   Difference finder                   *}
  6. {*     An example program for Turbo Professional 5.0     *}
  7. {*        Copyright (c) TurboPower Software 1987.        *}
  8. {* Portions copyright (c) Sunny Hill Software 1985, 1986 *}
  9. {*     and used under license to TurboPower Software     *}
  10. {*                 All rights reserved.                  *}
  11. {*********************************************************}
  12.  
  13. program Diff;
  14.   {-Compare two text files}
  15.   { Use Extended, Expanded, or Normal memory for storage}
  16.  
  17. uses
  18.   Dos,
  19.   TpString,
  20.   TpDos,
  21.   TpAsciiz,
  22.   TpExtMem,
  23.   TpEms;
  24.  
  25. const
  26.   MaxFile = 6000;            {Max number of lines in each file - absolute max 13104}
  27.   MaxOver = 6001;            {MaxFile+1}
  28.   MaxSym = 6000;             {Symbol table size, set to ~MaxFile - absolute max 13104}
  29.   TopSym = 5999;             {MaxSym-1}
  30.   Unused = $FFFF;            {Implies symbol table entry not in use}
  31.   FileBufSize = 8192;        {Size of input file buffer}
  32.  
  33.   MaxExtPages = 94;          {Maximum number of pages of Extended memory}
  34.   ExtPageSize = 16384;       {Size of an Extended memory page in bytes}
  35.  
  36.   MaxEmsPages = 128;         {Maximum number of pages of Expanded memory}
  37.   EmsPageSize = 16384;       {Size of an EMS memory page in bytes}
  38.   PutPageWin = 0;            {Physical EMS page showing put buffer}
  39.   GetPageWin = 1;            {Physical EMS page showing get buffer}
  40.  
  41. type
  42.  
  43.   SymNum = 0..TopSym;
  44.   LineNum = 0..MaxOver;
  45.   LineCnt = 0..MaxOver;
  46.  
  47.   LineRec =
  48.     record
  49.       Matched : Boolean;
  50.       Index : Word;
  51.       SymIndex : Word;
  52.     end;
  53.  
  54.   {Text storage}
  55.   StorageType = (Unknown, HeapMem, ExpMem, ExtMem);
  56.   StorageRec =
  57.     record
  58.       case StorageType of
  59.         Unknown,
  60.         HeapMem : (Data : AsciizPtr);
  61.         ExpMem : (EmsHandle, EmsOfs : Word);
  62.         ExtMem : (ExtIndex, ExtOfs : Word);
  63.     end;
  64.  
  65.   {EMS and Extended memory management}
  66.   ExtBuffer = array[1..ExtPageSize] of Char;
  67.   ExtBufferPtr = ^ExtBuffer;
  68.   ExtPageArray = array[1..MaxExtPages] of HugePtr;
  69.   ExtPageArrayPtr = ^ExtPageArray;
  70.   EmsBuffer = array[1..EmsPageSize] of Char;
  71.   EmsBufferPtr = ^EmsBuffer;
  72.   EmsPageArray = array[1..MaxEmsPages] of Word;
  73.   EmsPageArrayPtr = ^EmsPageArray;
  74.  
  75.   {Divide Symbol record into two parts so that an array of them
  76.    can have as many elements as does the Line array}
  77.  
  78.   SymRec1 =
  79.     record
  80.       HashVal : Word;
  81.       Oline : LineNum;
  82.       Ocount : 0..2;
  83.     end;
  84.  
  85.   SymRec2 =
  86.     record
  87.       Ncount : 0..2;
  88.       Line : AsciizPtr;
  89.     end;
  90.  
  91.   LineArray = array[LineNum] of LineRec;
  92.   SymTable1 = array[SymNum] of SymRec1;
  93.   SymTable2 = array[SymNum] of SymRec2;
  94.  
  95.   TextBuffer = array[1..FileBufSize] of Char;
  96.  
  97. var
  98.  
  99.   {Comparison algorithm}
  100.   OldMax : Word;             {Line count in old file}
  101.   NewMax : Word;             {Line count in new file}
  102.   SymsAvail : Word;          {Free symbol entries}
  103.   OA : ^LineArray;           {Points to array of old lines}
  104.   NA : ^LineArray;           {Points to array of new lines}
  105.   ST1 : ^SymTable1;          {Points to one half of symbol table}
  106.   ST2 : ^SymTable2;          {Points to other half of symbol table}
  107.  
  108.   {File input}
  109.   Oname : string[64];        {Old file name}
  110.   Nname : string[64];        {New file name}
  111.   F : Text;                  {Input file}
  112.   FB : TextBuffer;           {Buffer for input file}
  113.   CurLine : Asciiz;          {Current line of text}
  114.  
  115.   {Status reporting}
  116.   StdErr : Text;             {Output for status reporting}
  117.   Diffs : LongInt;           {Number of differences}
  118.   LastStatLen : Word;        {Length of last line counter for status}
  119.   NextStep : LongInt;        {Next order of magnitude where LastStatLen increases}
  120.   TimeVal : LongInt;         {Timer reading in milliseconds}
  121.   Debug : Boolean;           {True for debug output}
  122.  
  123.   {Extended storage management}
  124.   DataLoc : StorageType;     {Where the text is stored}
  125.   PB : Pointer;              {Put buffer for extended/expanded memory}
  126.   GB : Pointer;              {Get buffer for extended/expanded memory}
  127.   PutPage : Word;            {Current memory page in put buffer}
  128.   GetPage : Word;            {Current memory page in get buffer}
  129.   EP : Pointer;              {Index of extended/expanded memory pages}
  130.   NextFree : Word;           {Next free location in current put page}
  131.   EmsPages : Word;           {Number of EMS pages allocated}
  132.   SaveExitProc : Pointer;    {ExitProc chain}
  133.  
  134.   procedure DeallocateEms;
  135.     {-Deallocate all EMS memory used}
  136.   var
  137.     i : Word;
  138.   begin
  139.     for i := 1 to EmsPages do
  140.       if DeallocateEmsHandle(EmsPageArrayPtr(EP)^[i]) then
  141.         ;
  142.   end;
  143.  
  144.   {$F+}
  145.   function HeapFunc(Size : Word) : Integer;
  146.     {-Return nil pointer if insufficient memory}
  147.   begin
  148.     HeapFunc := 1;
  149.   end;
  150.  
  151.   procedure MainExitProc;
  152.     {-Clean up when DIFF is done}
  153.   begin
  154.     {Restore previous exit handler}
  155.     ExitProc := SaveExitProc;
  156.  
  157.     {Deallocate expanded/extended memory, if any}
  158.     case DataLoc of
  159.       ExpMem : DeallocateEms;
  160.       ExtMem : {Deallocated by TpExtMem exitproc} ;
  161.       HeapMem : {Deallocated by DOS} ;
  162.     end;
  163.   end;
  164.   {$F-}
  165.  
  166.   procedure OpenStdErr(var StdErr : Text);
  167.     {-Open StdErr for status reporting}
  168.   const
  169.     StdErrBuf : Char = #0;   {Buffer for status reporting}
  170.   begin
  171.     if OpenStdDev(StdErr, 2) then
  172.       {Force buffer flush every character}
  173.       SetTextBuf(StdErr, StdErrBuf, 1)
  174.     else begin
  175.       WriteLn('Error opening StdErr');
  176.       Halt(1);
  177.     end;
  178.   end;
  179.  
  180.   procedure FatalError(msg : string);
  181.     {-Report error message and halt}
  182.   begin
  183.     WriteLn(StdErr);
  184.     if msg <> '' then
  185.       WriteLn(StdErr, msg);
  186.     Halt(1);
  187.   end;
  188.  
  189.   procedure Unrecognized(arg : string);
  190.     {-Report a command line error}
  191.   begin
  192.     FatalError('Unrecognized command line argument '+arg);
  193.   end;
  194.  
  195.   procedure OutOfSymbols;
  196.     {-Report a common error}
  197.   begin
  198.     FatalError('Insufficient symbol table space to process files');
  199.   end;
  200.  
  201.   procedure OutOfMemory;
  202.     {-Report a common error}
  203.   begin
  204.     FatalError('Insufficient memory to process files');
  205.   end;
  206.  
  207.   procedure UnableToMap;
  208.     {-Report a common error}
  209.   begin
  210.     FatalError('Unable to map EMS page window');
  211.   end;
  212.  
  213.   procedure WriteError;
  214.     {-Report a common error}
  215.   begin
  216.     FatalError('Error writing output');
  217.   end;
  218.  
  219.   function Ms2S(ms : LongInt) : string;
  220.     {-Convert milliseconds to seconds in a string}
  221.   var
  222.     s : string;
  223.   begin
  224.     Str(ms, s);
  225.     {Pad out to three decimal places}
  226.     while Length(s) < 3 do
  227.       s := '0'+s;
  228.     {Truncate to nearest tenth of a second}
  229.     Dec(s[0]);
  230.     Dec(s[0]);
  231.     {Insert decimal point}
  232.     Insert('.', s, Length(s));
  233.     {Insert leading zero}
  234.     if Length(s) = 2 then
  235.       s := '0'+s;
  236.     Ms2S := s;
  237.   end;
  238.  
  239.   procedure UpdateLineCount(Fline : LongInt);
  240.     {-Update the line counter on the status line}
  241.   begin
  242.     Write(StdErr, CharStr(^H, LastStatLen), '(', Fline, ')');
  243.     if Fline >= NextStep then
  244.       repeat
  245.         Inc(LastStatLen);
  246.         NextStep := 10*NextStep;
  247.       until NextStep > Fline;
  248.   end;
  249.  
  250.   procedure GetParameters;
  251.     {-Parse the command line for parameters}
  252.   var
  253.     i : Integer;
  254.     arg : string;
  255.   begin
  256.     Oname := '';
  257.     Nname := '';
  258.     DataLoc := Unknown;
  259.     Debug := False;
  260.  
  261.     i := 1;
  262.     while i <= ParamCount do begin
  263.       arg := ParamStr(i);
  264.  
  265.       if (Length(arg) = 2) and ((arg[1] = '-') or (arg[1] = '/')) then
  266.         case Upcase(arg[2]) of
  267.           'D' : Debug := True;
  268.           'N' : DataLoc := HeapMem;
  269.         else
  270.           Unrecognized(arg);
  271.         end
  272.       else if Oname = '' then
  273.         Oname := stupcase(arg)
  274.       else if Nname = '' then
  275.         Nname := stupcase(arg)
  276.       else
  277.         Unrecognized(arg);
  278.  
  279.       Inc(i);
  280.     end;
  281.  
  282.     if (Oname = '') or (Nname = '') then begin
  283.       WriteLn(StdErr, 'Usage: DIFF FileA FileB [/N] [>Differences]');
  284.       FatalError('  /N  Force text storage to use normal memory');
  285.     end;
  286.  
  287.   end;
  288.  
  289.   function MaxFreeLoc : StorageType;
  290.     {-Return the storage location with largest free space}
  291.   var
  292.     HeapFree : LongInt;
  293.     ExtFree : LongInt;
  294.     EmsFree : LongInt;
  295.   begin
  296.     ExtFree := LongInt(1024)*extmemavail;
  297.     if Debug then
  298.       WriteLn('extended storage: ', ExtFree, ' bytes');
  299.     if EmsInstalled then begin
  300.       EmsFree := EmsPagesAvail;
  301.       if EmsFree = $FFFF then
  302.         {Error}
  303.         EmsFree := 00
  304.       else
  305.         EmsFree := LongInt(16384)*EmsFree;
  306.     end else
  307.       EmsFree := 00;
  308.     if Debug then
  309.       WriteLn('expanded storage: ', EmsFree, ' bytes');
  310.     HeapFree := MemAvail;
  311.     if Debug then
  312.       WriteLn('normal storage: ', HeapFree, ' bytes');
  313.  
  314.     if (EmsFree > HeapFree) then
  315.       {Give expanded memory priority over extended}
  316.       MaxFreeLoc := ExpMem
  317.     else if (ExtFree > HeapFree) then
  318.       MaxFreeLoc := ExtMem
  319.     else
  320.       MaxFreeLoc := HeapMem;
  321.  
  322.   end;
  323.  
  324.   procedure GetMemChk(var P; Size : Word);
  325.     {-Allocate heap space, halting on error}
  326.   var
  327.     Pt : Pointer absolute P;
  328.   begin
  329.     GetMem(Pt, Size);
  330.     if Pt = nil then
  331.       OutOfMemory;
  332.   end;
  333.  
  334.   procedure Initialize;
  335.     {-Initialize globals}
  336.   const
  337.     DataName : array[StorageType] of string[8] =
  338.     ('unknown', 'normal', 'ems', 'extended');
  339.   var
  340.     s : SymNum;
  341.   begin
  342.  
  343.     {Take over heap error control - forcing nil return on failure of getmem}
  344.     HeapError := @HeapFunc;
  345.  
  346.     {Set up exit proc}
  347.     SaveExitProc := ExitProc;
  348.     ExitProc := @MainExitProc;
  349.  
  350.     {Allocate space for symbol table and line arrays}
  351.     GetMemChk(ST1, SizeOf(SymTable1));
  352.     GetMemChk(ST2, SizeOf(SymTable2));
  353.     GetMemChk(OA, SizeOf(LineArray));
  354.     GetMemChk(NA, SizeOf(LineArray));
  355.  
  356.     {Initialize symbol table}
  357.     for s := 0 to TopSym do
  358.       with ST1^[s], ST2^[s] do begin
  359.         HashVal := Unused;
  360.         Oline := MaxOver;
  361.         Ocount := 0;
  362.         Ncount := 0;
  363.       end;
  364.  
  365.     OldMax := 0;
  366.     NewMax := 0;
  367.     SymsAvail := MaxSym;
  368.     Diffs := 00;
  369.     PutPage := 0;
  370.     GetPage := 0;
  371.  
  372.     if DataLoc = Unknown then
  373.       {Determine where the input text is best stored}
  374.       DataLoc := MaxFreeLoc;
  375.  
  376.     if Debug then
  377.       WriteLn(StdErr, 'Using ', DataName[DataLoc], ' storage');
  378.  
  379.     {Initialize expanded or extended memory work areas}
  380.     case DataLoc of
  381.       ExtMem :
  382.         begin
  383.           {Force allocation of extended page on first storage attempt}
  384.           NextFree := Succ(ExtPageSize);
  385.           {Allocate put and get buffers and extended page map array}
  386.           GetMemChk(PB, SizeOf(ExtBuffer));
  387.           GetMemChk(GB, SizeOf(ExtBuffer));
  388.           GetMemChk(EP, SizeOf(ExtPageArray));
  389.         end;
  390.  
  391.       ExpMem :
  392.         begin
  393.           {Force allocation of expanded page on first storage attempt}
  394.           NextFree := Succ(EmsPageSize);
  395.           {Initialize the page frame pointers}
  396.           PB := EmsPageFramePtr;
  397.           GB := Ptr(Seg(PB^), Ofs(PB^)+EmsPageSize);
  398.           EmsPages := 0;
  399.           {Allocate expanded page map array}
  400.           GetMemChk(EP, SizeOf(EmsPageArray));
  401.         end;
  402.     end;
  403.  
  404.   end;
  405.  
  406.   procedure WritePage(Page : Word);
  407.     {-Write put buffer from normal memory to extended memory}
  408.   begin
  409.     if Page > 0 then
  410.       MoveExtMem(PtrToHuge(PB), ExtPageArrayPtr(EP)^[Page], ExtPageSize shr 1);
  411.   end;
  412.  
  413.   procedure ReadPage(Page : Word);
  414.     {-Read page from extended memory to get buffer in normal memory}
  415.   begin
  416.     MoveExtMem(ExtPageArrayPtr(EP)^[Page], PtrToHuge(GB), ExtPageSize shr 1);
  417.   end;
  418.  
  419.   function PutLine(var A : Asciiz) : AsciizPtr;
  420.     {-Put line into text storage, returning a StorageRec}
  421.   var
  422.     Alen : Word;
  423.     P : AsciizPtr;
  424.     HP : HugePtr;
  425.   begin
  426.     Alen := Succ(LenAsc(A));
  427.  
  428.     case DataLoc of
  429.       HeapMem :
  430.         begin
  431.           GetMemChk(P, Alen);
  432.           Move(A, P^, Alen);
  433.         end;
  434.  
  435.       ExtMem :
  436.         begin
  437.           {Will new line fit into current page?}
  438.           if NextFree+Alen > ExtPageSize then begin
  439.             {No - Flush old put page to extended memory}
  440.             WritePage(PutPage);
  441.             {Allocate a new put page}
  442.             HP := GetExtMem(ExtPageSize shr 10);
  443.             if HP = nil then
  444.               OutOfMemory;
  445.             Inc(PutPage);
  446.             ExtPageArrayPtr(EP)^[PutPage] := HP;
  447.             NextFree := 1;
  448.           end;
  449.           {Store the data into the normal memory put buffer}
  450.           Move(A, ExtBufferPtr(PB)^[NextFree], Alen);
  451.           {Initialize the storage record for this line}
  452.           with StorageRec(P) do begin
  453.             ExtIndex := PutPage;
  454.             ExtOfs := NextFree;
  455.           end;
  456.           {Update the next free index}
  457.           Inc(NextFree, Alen);
  458.         end;
  459.  
  460.       ExpMem :
  461.         begin
  462.           {Will new line fit into current page?}
  463.           if NextFree+Alen > EmsPageSize then begin
  464.             {No - Allocate a new storage element}
  465.             PutPage := AllocateEmsPages(1);
  466.             if PutPage = $FFFF then
  467.               OutOfMemory;
  468.             {Maintain a page array so we can deallocate what we allocated}
  469.             Inc(EmsPages);
  470.             EmsPageArrayPtr(EP)^[EmsPages] := PutPage;
  471.             {Map put page window to point to new page}
  472.             if not(MapEmsPage(PutPage, 0, PutPageWin)) then
  473.               UnableToMap;
  474.             NextFree := 1;
  475.           end;
  476.           {Store the line in expanded memory}
  477.           Move(A, EmsBufferPtr(PB)^[NextFree], Alen);
  478.           {Initialize the storage record for this line}
  479.           with StorageRec(P) do begin
  480.             EmsHandle := PutPage;
  481.             EmsOfs := NextFree;
  482.           end;
  483.           {Update the next free index}
  484.           Inc(NextFree, Alen);
  485.         end;
  486.  
  487.     end;
  488.  
  489.     PutLine := P;
  490.   end;
  491.  
  492.   function GetLine(P : AsciizPtr) : AsciizPtr;
  493.     {-Return a pointer to Asciiz}
  494.   begin
  495.     case DataLoc of
  496.       HeapMem :
  497.         GetLine := P;
  498.  
  499.       ExtMem :
  500.         with StorageRec(P) do
  501.           if ExtIndex = PutPage then
  502.             {Line is in the put buffer already (not flushed)}
  503.             GetLine := @ExtBufferPtr(PB)^[ExtOfs]
  504.           else begin
  505.             if GetPage <> ExtIndex then begin
  506.               {Get a different page from extended memory}
  507.               GetPage := ExtIndex;
  508.               ReadPage(GetPage);
  509.             end;
  510.             {Line is in the get buffer}
  511.             GetLine := @ExtBufferPtr(GB)^[ExtOfs];
  512.           end;
  513.  
  514.       ExpMem :
  515.         with StorageRec(P) do begin
  516.           if GetPage <> EmsHandle then begin
  517.             {Map a different expanded memory page into the Get page}
  518.             GetPage := EmsHandle;
  519.             if not(MapEmsPage(GetPage, 0, GetPageWin)) then
  520.               UnableToMap;
  521.           end;
  522.           GetLine := @EmsBufferPtr(GB)^[EmsOfs];
  523.         end;
  524.  
  525.     end;
  526.   end;
  527.  
  528.   function Hash(var A : Asciiz) : Word;
  529.     {-Compute hash of a}
  530.   inline
  531.   ($5E/                      {pop si           ;Offset of Asciiz into SI}
  532.     $58/                     {pop ax           ;Segment into AX}
  533.     $8C/$DA/                 {mov dx,ds        ;Save DS}
  534.     $8E/$D8/                 {mov ds,ax        ;DS:SI => Asciiz}
  535.     $FC/                     {cld              ;Forward}
  536.     $31/$DB/                 {xor bx,bx        ;BX will hold hash}
  537.     $31/$C0/                 {xor ax,ax        ;Assure AH clear}
  538.     {next:}
  539.     $AC/                     {lodsb            ;Next character of Asciiz}
  540.     $09/$C0/                 {or ax,ax         ;Is it the last?}
  541.     $74/$04/                 {jz done          ;Yes, we're done}
  542.     $01/$C3/                 {add bx,ax        ;No, add to hash}
  543.     $E2/$F7/                 {loop next        ;Get next character}
  544.     {done:}
  545.     $89/$D8/                 {mov ax,bx        ;Return result in AX}
  546.     $09/$C0/                 {or  ax,ax        ;Is AX zero?}
  547.     $74/$01/                 {jz  leave}
  548.     $48/                     {dec ax           ;Don't return FFFF}
  549.     {leave:}
  550.     $8E/$DA);                {mov ds,dx        ;Get DS back}
  551.  
  552.   function Store(var A : Asciiz) : SymNum;
  553.     {-Store text of line, and return symbol table entry}
  554.   var
  555.     s : SymNum;
  556.     h : Word;
  557.     symh : Word;
  558.     done : Boolean;
  559.   begin
  560.     {Compute hash of a}
  561.     h := Hash(A);
  562.  
  563.     {Probe symbol table for unused entry}
  564.     s := h mod MaxSym;
  565.     done := False;
  566.  
  567.     repeat
  568.       with ST1^[s], ST2^[s] do begin
  569.         symh := HashVal;
  570.         if symh = Unused then begin
  571.           {Empty symbol table entry}
  572.           done := True;
  573.           Dec(SymsAvail);
  574.           HashVal := h;
  575.           Line := PutLine(A);
  576.         end else if symh = h then begin
  577.           {Symbol entry used - chance or duplicate line?}
  578.           if CompAsc(A, GetLine(Line)^) = AscEqual then
  579.             {Duplicate line}
  580.             done := True
  581.           else if SymsAvail < 2 then
  582.             OutOfSymbols
  583.           else
  584.             {Hash matched by chance, search for empty slot}
  585.             s := Succ(s) mod MaxSym;
  586.         end else if SymsAvail < 2 then
  587.           OutOfSymbols
  588.         else
  589.           {Collision due to previous bumping, search for empty slot}
  590.           s := Succ(s) mod MaxSym;
  591.       end;
  592.     until done;
  593.  
  594.     Store := s;
  595.   end;
  596.  
  597.   procedure ReadFile(Fname : string; OldFile : Boolean; var LA : LineArray; var Max : Word);
  598.     {-Read in file fname, build its linearray, and update the symbol table}
  599.   var
  600.     c : LineCnt;
  601.     s : SymNum;
  602.   begin
  603.  
  604.     {Assure file exists, and open it for reading}
  605.     if not(existfile(Fname)) then
  606.       FatalError(Fname+' not found');
  607.     Assign(F, Fname);
  608.     SetTextBuf(F, FB, FileBufSize);
  609.     Reset(F);
  610.  
  611.     {Display line counter}
  612.     Write(StdErr, Fname, '(0)');
  613.     LastStatLen := 3;
  614.     NextStep := 10;
  615.     c := 0;
  616.  
  617.     repeat
  618.  
  619.       {Read next line}
  620.       if not(ReadLnAsc(F, CurLine)) then
  621.         FatalError('Error reading '+Fname);
  622.  
  623.       {Keep count of lines, report status}
  624.       Inc(c);
  625.       if c and 63 = 0 then
  626.         UpdateLineCount(c);
  627.  
  628.       {Store the line, and return its symbol table entry}
  629.       s := Store(CurLine);
  630.  
  631.       {Update counters in symbol table}
  632.       with ST1^[s], ST2^[s] do
  633.         if OldFile then begin
  634.           Oline := c;
  635.           if Ocount < 2 then
  636.             Inc(Ocount);
  637.         end else
  638.           if Ncount < 2 then
  639.             Inc(Ncount);
  640.  
  641.       {Update the line array}
  642.       with LA[c] do begin
  643.         Matched := False;
  644.         Index := s;
  645.         SymIndex := s;
  646.       end;
  647.  
  648.     until eof(F) or (c >= MaxFile);
  649.  
  650.     {Create sentinel}
  651.     with LA[Succ(c)] do begin
  652.       Matched := True;
  653.       Index := MaxOver;
  654.       SymIndex := MaxOver;
  655.     end;
  656.  
  657.     {Return maximum}
  658.     Max := c;
  659.     Close(F);
  660.  
  661.     {Complete status line}
  662.     UpdateLineCount(c);
  663.     WriteLn(StdErr);
  664.  
  665.   end;
  666.  
  667.   procedure MatchUp(o, n : LineNum);
  668.     {-Mark two lines as matching each other}
  669.   begin
  670.     with OA^[o] do begin
  671.       Matched := True;
  672.       Index := n;
  673.     end;
  674.     with NA^[n] do begin
  675.       Matched := True;
  676.       Index := o;
  677.     end;
  678.   end;
  679.  
  680.   procedure FindUnique;
  681.     {-Match up unique lines between old and new files}
  682.   var
  683.     s : SymNum;
  684.     n : LineNum;
  685.   begin
  686.     for n := 1 to NewMax do begin
  687.       s := NA^[n].Index;
  688.       with ST1^[s], ST2^[s] do
  689.         if (Ocount = 1) and (Ncount = 1) then
  690.           MatchUp(Oline, n);
  691.     end;
  692.   end;
  693.  
  694.   procedure Resolve(var o, n : LineNum);
  695.     {-Find the smaller block to move}
  696.   var
  697.     xo : LineNum;
  698.     xn : LineNum;
  699.     first : LineNum;
  700.     last : LineNum;
  701.     t : Word;
  702.     s : SymNum;
  703.   begin
  704.     {Get length of block starting at OA^[o]}
  705.     xo := o;
  706.     repeat
  707.       t := Succ(OA^[xo].Index);
  708.       Inc(xo);
  709.     until not(OA^[xo].Matched) or (t <> OA^[xo].Index);
  710.  
  711.     {Get length of block starting at NA^[n]}
  712.     xn := n;
  713.     repeat
  714.       t := Succ(NA^[xn].Index);
  715.       Inc(xn);
  716.     until not(NA^[xn].Matched) or (t <> NA^[xn].Index);
  717.  
  718.     {Which block was smaller?}
  719.     if (xo-o) < (xn-n) then begin
  720.       {Move block down}
  721.       first := o;
  722.       last := Pred(xo);
  723.       o := xo;
  724.     end else begin
  725.       {Move block up}
  726.       first := NA^[n].Index;
  727.       last := first+Pred(xn-n);
  728.       n := xn;
  729.     end;
  730.  
  731.     {Break the matches}
  732.     for t := first to last do begin
  733.       s := OA^[t].SymIndex;
  734.       xo := ST1^[s].Oline;
  735.       xn := OA^[xo].Index;
  736.       with OA^[xo] do begin
  737.         Matched := False;
  738.         Index := s;
  739.       end;
  740.       with NA^[xn] do begin
  741.         Matched := False;
  742.         Index := s;
  743.       end;
  744.     end;
  745.   end;
  746.  
  747.   procedure BlockMoves;
  748.     {-Find apparent block moves and transform into single line differences}
  749.   var
  750.     o : LineNum;
  751.     n : LineNum;
  752.   begin
  753.     o := 1;
  754.     n := 1;
  755.  
  756.     repeat
  757.       {Skip deletions from old file}
  758.       while not(OA^[o].Matched) do
  759.         Inc(o);
  760.  
  761.       {Skip insertions into new file}
  762.       while not(NA^[n].Matched) do
  763.         Inc(n);
  764.  
  765.       if (n > NewMax) or (o > OldMax) then
  766.         {Done}
  767.         Exit;
  768.  
  769.       if OA^[o].Index = n then begin
  770.         {O and n match, skip over them}
  771.         Inc(o);
  772.         Inc(n);
  773.       end else
  774.         Resolve(o, n);
  775.  
  776.     until False;
  777.   end;
  778.  
  779.   procedure Sweep;
  780.     {-Spread unique line matches through the file}
  781.   var
  782.     o : LineNum;
  783.     o1 : LineNum;
  784.     n : LineNum;
  785.     n1 : LineNum;
  786.     nm1 : LineNum;
  787.   begin
  788.  
  789.     {Set up seed matches at ends of new file}
  790.     NA^[0].Index := 0;
  791.     NA^[Succ(NewMax)].Index := Succ(OldMax);
  792.  
  793.     {Toward end of file}
  794.     for n := 0 to Pred(NewMax) do
  795.       if NA^[n].Matched or (n = 0) then begin
  796.         n1 := Succ(n);
  797.         if not(NA^[n1].Matched) then begin
  798.           o := NA^[n].Index;
  799.           if o < OldMax then begin
  800.             o1 := Succ(o);
  801.             if (OA^[o1].Matched = NA^[n1].Matched) and (OA^[o1].Index = NA^[n1].Index) then
  802.               MatchUp(o1, n1);
  803.           end;
  804.         end;
  805.       end;
  806.  
  807.     {Toward beginning of file}
  808.     nm1 := Succ(NewMax);
  809.     for n := nm1 downto 2 do
  810.       if NA^[n].Matched or (n = nm1) then begin
  811.         n1 := Pred(n);
  812.         if not(NA^[n1].Matched) then begin
  813.           o := NA^[n].Index;
  814.           if o > 1 then begin
  815.             o1 := Pred(o);
  816.             if (OA^[o1].Matched = NA^[n1].Matched) and (OA^[o1].Index = NA^[n1].Index) then
  817.               MatchUp(o1, n1);
  818.           end;
  819.         end;
  820.       end;
  821.   end;
  822.  
  823.   procedure WriteOutput(var A : Asciiz);
  824.     {-Write one line of output}
  825.   begin
  826.     if not(WriteAsc(Output, A)) then
  827.       WriteError;
  828.     WriteLn(Output);
  829.     if IoResult <> 0 then
  830.       WriteError;
  831.   end;
  832.  
  833.   procedure WriteBlocks;
  834.     {-Output differences in groups of inserts and deletes}
  835.   var
  836.     o : LineNum;
  837.     n : LineNum;
  838.     t : LineNum;
  839.   begin
  840.     o := 1;
  841.     n := 1;
  842.  
  843.     repeat
  844.  
  845.       if not(OA^[o].Matched) then begin
  846.         {Old line was deleted}
  847.         t := o;
  848.         repeat
  849.           Inc(o);
  850.           Inc(Diffs);
  851.         until OA^[o].Matched;
  852.         WriteLn(Output, '****Delete lines ', long2str(t), '-', long2str(Pred(o)), ' of ', Oname);
  853.         if IoResult <> 0 then
  854.           WriteError;
  855.         while t < o do begin
  856.           WriteOutput(GetLine(ST2^[OA^[t].Index].Line)^);
  857.           Inc(t);
  858.         end;
  859.       end;
  860.  
  861.       if not(NA^[n].Matched) then begin
  862.         {New line was inserted}
  863.         t := n;
  864.         repeat
  865.           Inc(n);
  866.           Inc(Diffs);
  867.         until NA^[n].Matched;
  868.         WriteLn(Output, '****Insert lines ', long2str(t), '-', long2str(Pred(n)), ' from ', Nname);
  869.         if IoResult <> 0 then
  870.           WriteError;
  871.         while t < n do begin
  872.           WriteOutput(GetLine(ST2^[NA^[t].Index].Line)^);
  873.           Inc(t);
  874.         end;
  875.       end;
  876.  
  877.       {Skip over matched lines}
  878.       while OA^[o].Matched and NA^[n].Matched and (o <= OldMax) do begin
  879.         Inc(o);
  880.         Inc(n);
  881.       end;
  882.  
  883.     until (OA^[o].SymIndex = MaxOver) and (NA^[n].SymIndex = MaxOver);
  884.   end;
  885.  
  886. begin
  887.  
  888.   {Open StdErr for status reporting}
  889.   OpenStdErr(StdErr);
  890.   WriteLn(StdErr, 'File Compare. Copyright (c) 1987 by TurboPower Software. Version 5.07');
  891.   WriteLn(StdErr);
  892.  
  893.   {Get parameters from command line}
  894.   GetParameters;
  895.  
  896.   {Initialize the data structures}
  897.   Initialize;
  898.  
  899.   TimeVal := timems;
  900.  
  901.   {Read in the files}
  902.   ReadFile(Oname, True, OA^, OldMax);
  903.   ReadFile(Nname, False, NA^, NewMax);
  904.  
  905.   {Compare the two files}
  906.   FindUnique;
  907.   BlockMoves;
  908.   Sweep;
  909.  
  910.   {Get elapsed time for read-in and comparison}
  911.   TimeVal := timems-TimeVal;
  912.  
  913.   WriteBlocks;
  914.  
  915.   Write(StdErr, Diffs, ' difference');
  916.   if Diffs <> 1 then
  917.     Write(StdErr, 's');
  918.   WriteLn(StdErr, ' found in ', Ms2S(TimeVal), ' seconds');
  919.   if Diffs <> 0 then
  920.     {Return code to indicate differences}
  921.     Halt(1);
  922. end.
  923.