{$R-,I-,S-}

  {*********************************************************}
  {*                     DIFF.PAS 4.03                     *}
  {*                   Difference finder                   *}
  {*     An example program for Turbo Professional 4.0     *}
  {*        Copyright (c) TurboPower Software 1987.        *}
  {* Portions copyright (c) Sunny Hill Software 1985, 1986 *}
  {*     and used under license to TurboPower Software     *}
  {*                 All rights reserved.                  *}
  {*********************************************************}

program Diff;
  {-Compare two text files}
  { Use Extended, Expanded, or Normal memory for storage}

uses
  Dos,
  TpString,
  TpDos,
  TpAsciiz,
  TpExtMem,
  TpEms;

const
  MaxFile = 6000;            {Max number of lines in each file - absolute max 13104}
  MaxOver = 6001;            {MaxFile+1}
  MaxSym = 6000;             {Symbol table size, set to ~MaxFile - absolute max 13104}
  TopSym = 5999;             {MaxSym-1}
  Unused = $FFFF;            {Implies symbol table entry not in use}
  FileBufSize = 8192;        {Size of input file buffer}

  MaxExtPages = 94;          {Maximum number of pages of Extended memory}
  ExtPageSize = 16384;       {Size of an Extended memory page in bytes}

  MaxEmsPages = 128;         {Maximum number of pages of Expanded memory}
  EmsPageSize = 16384;       {Size of an EMS memory page in bytes}
  PutPageWin = 0;            {Physical EMS page showing put buffer}
  GetPageWin = 1;            {Physical EMS page showing get buffer}

type

  SymNum = 0..TopSym;
  LineNum = 0..MaxOver;
  LineCnt = 0..MaxOver;

  LineRec =
  record
    Matched : Boolean;
    Index : Word;
    SymIndex : Word;
  end;

  {Text storage}
  StorageType = (Unknown, HeapMem, ExpMem, ExtMem);
  StorageRec =
  record
    case StorageType of
      Unknown,
      HeapMem : (Data : AsciizPtr);
      ExpMem : (EmsHandle, EmsOfs : Word);
      ExtMem : (ExtIndex, ExtOfs : Word);
  end;

  {EMS and Extended memory management}
  ExtBuffer = array[1..ExtPageSize] of Char;
  ExtBufferPtr = ^ExtBuffer;
  ExtPageArray = array[1..MaxExtPages] of HugePtr;
  ExtPageArrayPtr = ^ExtPageArray;
  EmsBuffer = array[1..EmsPageSize] of Char;
  EmsBufferPtr = ^EmsBuffer;
  EmsPageArray = array[1..MaxEmsPages] of Word;
  EmsPageArrayPtr = ^EmsPageArray;

  {Divide Symbol record into two parts so that an array of them
   can have as many elements as does the Line array}

  SymRec1 =
  record
    HashVal : Word;
    Oline : LineNum;
    Ocount : 0..2;
  end;

  SymRec2 =
  record
    Ncount : 0..2;
    Line : AsciizPtr;
  end;

  LineArray = array[LineNum] of LineRec;
  SymTable1 = array[SymNum] of SymRec1;
  SymTable2 = array[SymNum] of SymRec2;

  TextBuffer = array[1..FileBufSize] of Char;

var

  {Comparison algorithm}
  OldMax : Word;             {Line count in old file}
  NewMax : Word;             {Line count in new file}
  SymsAvail : Word;          {Free symbol entries}
  OA : ^LineArray;           {Points to array of old lines}
  NA : ^LineArray;           {Points to array of new lines}
  ST1 : ^SymTable1;          {Points to one half of symbol table}
  ST2 : ^SymTable2;          {Points to other half of symbol table}

  {File input}
  Oname : string[64];        {Old file name}
  Nname : string[64];        {New file name}
  F : Text;                  {Input file}
  FB : TextBuffer;           {Buffer for input file}
  CurLine : Asciiz;          {Current line of text}

  {Status reporting}
  StdErr : Text;             {Output for status reporting}
  Diffs : LongInt;           {Number of differences}
  LastStatLen : Word;        {Length of last line counter for status}
  NextStep : LongInt;        {Next order of magnitude where LastStatLen increases}
  TimeVal : LongInt;         {Timer reading in milliseconds}
  Debug : Boolean;           {True for debug output}

  {Extended storage management}
  DataLoc : StorageType;     {Where the text is stored}
  PB : Pointer;              {Put buffer for extended/expanded memory}
  GB : Pointer;              {Get buffer for extended/expanded memory}
  PutPage : Word;            {Current memory page in put buffer}
  GetPage : Word;            {Current memory page in get buffer}
  EP : Pointer;              {Index of extended/expanded memory pages}
  NextFree : Word;           {Next free location in current put page}
  EmsPages : Word;           {Number of EMS pages allocated}
  SaveExitProc : Pointer;    {ExitProc chain}

  procedure DeallocateEms;
    {-Deallocate all EMS memory used}
  var
    i : Word;
  begin
    for i := 1 to EmsPages do
      if DeallocateEmsHandle(EmsPageArrayPtr(EP)^[i]) then
        ;
  end;

  {$F+}
  function HeapFunc(Size : Word) : Integer;
    {-Return nil pointer if insufficient memory}
  begin
    HeapFunc := 1;
  end;

  procedure MainExitProc;
    {-Clean up when DIFF is done}
  begin
    {Restore previous exit handler}
    ExitProc := SaveExitProc;

    {Deallocate expanded/extended memory, if any}
    case DataLoc of
      ExpMem : DeallocateEms;
      ExtMem : {Deallocated by TpExtMem exitproc} ;
      HeapMem : {Deallocated by DOS} ;
    end;
  end;
  {$F-}

  procedure OpenStdErr(var StdErr : Text);
    {-Open StdErr for status reporting}
  const
    StdErrBuf : Char = #0;   {Buffer for status reporting}
  begin
    if OpenStdDev(StdErr, 2) then
      {Force buffer flush every character}
      SetTextBuf(StdErr, StdErrBuf, 1)
    else begin
      WriteLn('Error opening StdErr');
      Halt(1);
    end;
  end;

  procedure FatalError(msg : string);
    {-Report error message and halt}
  begin
    WriteLn(StdErr);
    if msg <> '' then
      WriteLn(StdErr, msg);
    Halt(1);
  end;

  procedure Unrecognized(arg : string);
    {-Report a command line error}
  begin
    FatalError('Unrecognized command line argument '+arg);
  end;

  procedure OutOfSymbols;
    {-Report a common error}
  begin
    FatalError('Insufficient symbol table space to process files');
  end;

  procedure OutOfMemory;
    {-Report a common error}
  begin
    FatalError('Insufficient memory to process files');
  end;

  procedure UnableToMap;
    {-Report a common error}
  begin
    FatalError('Unable to map EMS page window');
  end;

  procedure WriteError;
    {-Report a common error}
  begin
    FatalError('Error writing output');
  end;

  function Ms2S(ms : LongInt) : string;
    {-Convert milliseconds to seconds in a string}
  var
    s : string;
  begin
    Str(ms, s);
    {Pad out to three decimal places}
    while Length(s) < 3 do
      s := '0'+s;
    {Truncate to nearest tenth of a second}
    Dec(s[0]);
    Dec(s[0]);
    {Insert decimal point}
    Insert('.', s, Length(s));
    {Insert leading zero}
    if Length(s) = 2 then
      s := '0'+s;
    Ms2S := s;
  end;

  procedure UpdateLineCount(Fline : LongInt);
    {-Update the line counter on the status line}
  begin
    Write(StdErr, CharStr(^H, LastStatLen), '(', Fline, ')');
    if Fline >= NextStep then
      repeat
        Inc(LastStatLen);
        NextStep := 10*NextStep;
      until NextStep > Fline;
  end;

  procedure GetParameters;
    {-Parse the command line for parameters}
  var
    i : Integer;
    arg : string;
  begin
    Oname := '';
    Nname := '';
    DataLoc := Unknown;
    Debug := False;

    i := 1;
    while i <= ParamCount do begin
      arg := ParamStr(i);

      if (Length(arg) = 2) and ((arg[1] = '-') or (arg[1] = '/')) then
        case Upcase(arg[2]) of
          'D' : Debug := True;
          'N' : DataLoc := HeapMem;
        else
          Unrecognized(arg);
        end
      else if Oname = '' then
        Oname := stupcase(arg)
      else if Nname = '' then
        Nname := stupcase(arg)
      else
        Unrecognized(arg);

      Inc(i);
    end;

    if (Oname = '') or (Nname = '') then begin
      WriteLn(StdErr, 'Usage: DIFF FileA FileB [/N] [>Differences]');
      FatalError('  /N  Force text storage to use normal memory');
    end;

  end;

  function MaxFreeLoc : StorageType;
    {-Return the storage location with largest free space}
  var
    HeapFree : LongInt;
    ExtFree : LongInt;
    EmsFree : LongInt;
  begin
    ExtFree := LongInt(1024)*extmemavail;
    if debug then
      writeln('extended storage: ',extfree,' bytes');
    if EmsInstalled then begin
      EmsFree := EmsPagesAvail;
      if EmsFree = $FFFF then
        {Error}
        EmsFree := 00
      else
        EmsFree := LongInt(16384)*EmsFree;
    end else
      EmsFree := 00;
    if debug then
      writeln('expanded storage: ',emsfree,' bytes');
    HeapFree := MemAvail;
    if debug then
      writeln('normal storage: ',heapfree,' bytes');

    if (EmsFree > HeapFree) then
      {Give expanded memory priority over extended}
      MaxFreeLoc := ExpMem
    else if (ExtFree > HeapFree) then
      MaxFreeLoc := ExtMem
    else
      MaxFreeLoc := HeapMem;

  end;

  procedure GetMemChk(var P; Size : Word);
    {-Allocate heap space, halting on error}
  var
    Pt : Pointer absolute P;
  begin
    GetMem(Pt, Size);
    if Pt = nil then
      OutOfMemory;
  end;

  procedure Initialize;
    {-Initialize globals}
  const
    DataName : array[StorageType] of string[8] =
    ('unknown', 'normal', 'ems', 'extended');
  var
    s : SymNum;
  begin

    {Take over heap error control - forcing nil return on failure of getmem}
    HeapError := @HeapFunc;

    {Set up exit proc}
    SaveExitProc := ExitProc;
    ExitProc := @MainExitProc;

    {Allocate space for symbol table and line arrays}
    GetMemChk(ST1, SizeOf(SymTable1));
    GetMemChk(ST2, SizeOf(SymTable2));
    GetMemChk(OA, SizeOf(LineArray));
    GetMemChk(NA, SizeOf(LineArray));

    {Initialize symbol table}
    for s := 0 to TopSym do
      with ST1^[s], ST2^[s] do begin
        HashVal := Unused;
        Oline := MaxOver;
        Ocount := 0;
        Ncount := 0;
      end;

    OldMax := 0;
    NewMax := 0;
    SymsAvail := MaxSym;
    Diffs := 00;
    PutPage := 0;
    GetPage := 0;

    if DataLoc = Unknown then
      {Determine where the input text is best stored}
      DataLoc := MaxFreeLoc;

    if Debug then
      WriteLn(StdErr, 'Using ', DataName[DataLoc], ' storage');

    {Initialize expanded or extended memory work areas}
    case DataLoc of
      ExtMem :
        begin
          {Force allocation of extended page on first storage attempt}
          NextFree := Succ(ExtPageSize);
          {Allocate put and get buffers and extended page map array}
          GetMemChk(PB, SizeOf(ExtBuffer));
          GetMemChk(GB, SizeOf(ExtBuffer));
          GetMemChk(EP, SizeOf(ExtPageArray));
        end;

      ExpMem :
        begin
          {Force allocation of expanded page on first storage attempt}
          NextFree := Succ(EmsPageSize);
          {Initialize the page frame pointers}
          PB := EmsPageFramePtr;
          GB := Ptr(Seg(PB^), Ofs(PB^)+EmsPageSize);
          EmsPages := 0;
          {Allocate expanded page map array}
          GetMemChk(EP, SizeOf(EmsPageArray));
        end;
    end;

  end;

  procedure WritePage(Page : Word);
    {-Write put buffer from normal memory to extended memory}
  begin
    if Page > 0 then
      MoveExtMem(PtrToHuge(PB), ExtPageArrayPtr(EP)^[Page], ExtPageSize shr 1);
  end;

  procedure ReadPage(Page : Word);
    {-Read page from extended memory to get buffer in normal memory}
  begin
    MoveExtMem(ExtPageArrayPtr(EP)^[Page], PtrToHuge(GB), ExtPageSize shr 1);
  end;

  function PutLine(var A : Asciiz) : AsciizPtr;
    {-Put line into text storage, returning a StorageRec}
  var
    Alen : Word;
    P : AsciizPtr;
    HP : HugePtr;
  begin
    Alen := Succ(LenAsc(A));

    case DataLoc of
      HeapMem :
        begin
          GetMemChk(P, Alen);
          Move(A, P^, Alen);
        end;

      ExtMem :
        begin
          {Will new line fit into current page?}
          if NextFree+Alen > ExtPageSize then begin
            {No - Flush old put page to extended memory}
            WritePage(PutPage);
            {Allocate a new put page}
            HP := GetExtMem(ExtPageSize shr 10);
            if HP = nil then
              OutOfMemory;
            Inc(PutPage);
            ExtPageArrayPtr(EP)^[PutPage] := HP;
            NextFree := 1;
          end;
          {Store the data into the normal memory put buffer}
          Move(A, ExtBufferPtr(PB)^[NextFree], Alen);
          {Initialize the storage record for this line}
          with StorageRec(P) do begin
            ExtIndex := PutPage;
            ExtOfs := NextFree;
          end;
          {Update the next free index}
          Inc(NextFree, Alen);
        end;

      ExpMem :
        begin
          {Will new line fit into current page?}
          if NextFree+Alen > EmsPageSize then begin
            {No - Allocate a new storage element}
            PutPage := AllocateEmsPages(1);
            if PutPage = $FFFF then
              OutOfMemory;
            {Maintain a page array so we can deallocate what we allocated}
            Inc(EmsPages);
            EmsPageArrayPtr(EP)^[EmsPages] := PutPage;
            {Map put page window to point to new page}
            if not(MapEmsPage(PutPage, 0, PutPageWin)) then
              UnableToMap;
            NextFree := 1;
          end;
          {Store the line in expanded memory}
          Move(A, EmsBufferPtr(PB)^[NextFree], Alen);
          {Initialize the storage record for this line}
          with StorageRec(P) do begin
            EmsHandle := PutPage;
            EmsOfs := NextFree;
          end;
          {Update the next free index}
          Inc(NextFree, Alen);
        end;

    end;

    PutLine := P;
  end;

  function GetLine(P : AsciizPtr) : AsciizPtr;
    {-Return a pointer to Asciiz}
  begin
    case DataLoc of
      HeapMem :
        GetLine := P;

      ExtMem :
        with StorageRec(P) do
          if ExtIndex = PutPage then
            {Line is in the put buffer already (not flushed)}
            GetLine := @ExtBufferPtr(PB)^[ExtOfs]
          else begin
            if GetPage <> ExtIndex then begin
              {Get a different page from extended memory}
              GetPage := ExtIndex;
              ReadPage(GetPage);
            end;
            {Line is in the get buffer}
            GetLine := @ExtBufferPtr(GB)^[ExtOfs];
          end;

      ExpMem :
        with StorageRec(P) do begin
          if GetPage <> EmsHandle then begin
            {Map a different expanded memory page into the Get page}
            GetPage := EmsHandle;
            if not(MapEmsPage(GetPage, 0, GetPageWin)) then
              UnableToMap;
          end;
          GetLine := @EmsBufferPtr(GB)^[EmsOfs];
        end;

    end;
  end;

  function Hash(var A : Asciiz) : Word;
    {-Compute hash of a}
    inline
  ($5E/                      {pop si           ;Offset of Asciiz into SI}
   $58/                      {pop ax           ;Segment into AX}
   $8C/$DA/                  {mov dx,ds        ;Save DS}
   $8E/$D8/                  {mov ds,ax        ;DS:SI => Asciiz}
   $FC/                      {cld              ;Forward}
   $31/$DB/                  {xor bx,bx        ;BX will hold hash}
   $31/$C0/                  {xor ax,ax        ;Assure AH clear}
   {next:}
   $AC/                      {lodsb            ;Next character of Asciiz}
   $09/$C0/                  {or ax,ax         ;Is it the last?}
   $74/$04/                  {jz done          ;Yes, we're done}
   $01/$C3/                  {add bx,ax        ;No, add to hash}
   $E2/$F7/                  {loop next        ;Get next character}
   {done:}
   $89/$D8/                  {mov ax,bx        ;Return result in AX}
   $09/$C0/                  {or  ax,ax        ;Is AX zero?}
   $74/$01/                  {jz  leave}
   $48/                      {dec ax           ;Don't return FFFF}
   {leave:}
   $8E/$DA);                 {mov ds,dx        ;Get DS back}

  function Store(var A : Asciiz) : SymNum;
    {-Store text of line, and return symbol table entry}
  var
    s : SymNum;
    h : Word;
    symh : Word;
    done : Boolean;
  begin
    {Compute hash of a}
    h := Hash(A);

    {Probe symbol table for unused entry}
    s := h mod MaxSym;
    done := False;

    repeat
      with ST1^[s], ST2^[s] do begin
        symh := HashVal;
        if symh = Unused then begin
          {Empty symbol table entry}
          done := True;
          Dec(SymsAvail);
          HashVal := h;
          Line := PutLine(A);
        end else if symh = h then begin
          {Symbol entry used - chance or duplicate line?}
          if CompAsc(A, GetLine(Line)^) = AscEqual then
            {Duplicate line}
            done := True
          else if SymsAvail < 2 then
            OutOfSymbols
          else
            {Hash matched by chance, search for empty slot}
            s := Succ(s) mod MaxSym;
        end else if SymsAvail < 2 then
          OutOfSymbols
        else
          {Collision due to previous bumping, search for empty slot}
          s := Succ(s) mod MaxSym;
      end;
    until done;

    Store := s;
  end;

  procedure ReadFile(Fname : string; OldFile : Boolean; var LA : LineArray; var Max : Word);
    {-Read in file fname, build its linearray, and update the symbol table}
  var
    c : LineCnt;
    s : SymNum;
  begin

    {Assure file exists, and open it for reading}
    if not(existfile(Fname)) then
      FatalError(Fname+' not found');
    Assign(F, Fname);
    SetTextBuf(F, FB, FileBufSize);
    Reset(F);

    {Display line counter}
    Write(StdErr, Fname, '(0)');
    LastStatLen := 3;
    NextStep := 10;
    c := 0;

    repeat

      {Read next line}
      if not(ReadLnAsc(F, CurLine)) then
        FatalError('Error reading '+fname);

      {Keep count of lines, report status}
      Inc(c);
      if c and 63 = 0 then
        UpdateLineCount(c);

      {Store the line, and return its symbol table entry}
      s := Store(CurLine);

      {Update counters in symbol table}
      with ST1^[s], ST2^[s] do
        if OldFile then begin
          Oline := c;
          if Ocount < 2 then
            Inc(Ocount);
        end else
          if Ncount < 2 then
            Inc(Ncount);

      {Update the line array}
      with LA[c] do begin
        Matched := False;
        Index := s;
        SymIndex := s;
      end;

    until eof(F) or (c >= MaxFile);

    {Create sentinel}
    with LA[Succ(c)] do begin
      Matched := True;
      Index := MaxOver;
      SymIndex := MaxOver;
    end;

    {Return maximum}
    Max := c;
    Close(F);

    {Complete status line}
    UpdateLineCount(c);
    WriteLn(StdErr);

  end;

  procedure MatchUp(o, n : LineNum);
    {-Mark two lines as matching each other}
  begin
    with OA^[o] do begin
      Matched := True;
      Index := n;
    end;
    with NA^[n] do begin
      Matched := True;
      Index := o;
    end;
  end;

  procedure FindUnique;
    {-Match up unique lines between old and new files}
  var
    s : SymNum;
    n : LineNum;
  begin
    for n := 1 to NewMax do begin
      s := NA^[n].Index;
      with ST1^[s], ST2^[s] do
        if (Ocount = 1) and (Ncount = 1) then
          MatchUp(Oline, n);
    end;
  end;

  procedure Resolve(var o, n : LineNum);
    {-Find the smaller block to move}
  var
    xo : LineNum;
    xn : LineNum;
    first : LineNum;
    last : LineNum;
    t : Word;
    s : SymNum;
  begin
    {Get length of block starting at OA^[o]}
    xo := o;
    repeat
      t := Succ(OA^[xo].Index);
      Inc(xo);
    until not(OA^[xo].Matched) or (t <> OA^[xo].Index);

    {Get length of block starting at NA^[n]}
    xn := n;
    repeat
      t := Succ(NA^[xn].Index);
      Inc(xn);
    until not(NA^[xn].Matched) or (t <> NA^[xn].Index);

    {Which block was smaller?}
    if (xo-o) < (xn-n) then begin
      {Move block down}
      first := o;
      last := Pred(xo);
      o := xo;
    end else begin
      {Move block up}
      first := NA^[n].Index;
      last := first+Pred(xn-n);
      n := xn;
    end;

    {Break the matches}
    for t := first to last do begin
      s := OA^[t].SymIndex;
      xo := ST1^[s].Oline;
      xn := OA^[xo].Index;
      with OA^[xo] do begin
        Matched := False;
        Index := s;
      end;
      with NA^[xn] do begin
        Matched := False;
        Index := s;
      end;
    end;
  end;

  procedure BlockMoves;
    {-Find apparent block moves and transform into single line differences}
  var
    o : LineNum;
    n : LineNum;
  begin
    o := 1;
    n := 1;

    repeat
      {Skip deletions from old file}
      while not(OA^[o].Matched) do
        Inc(o);

      {Skip insertions into new file}
      while not(NA^[n].Matched) do
        Inc(n);

      if (n > NewMax) or (o > OldMax) then
        {Done}
        Exit;

      if OA^[o].Index = n then begin
        {O and n match, skip over them}
        Inc(o);
        Inc(n);
      end else
        Resolve(o, n);

    until False;
  end;

  procedure Sweep;
    {-Spread unique line matches through the file}
  var
    o : LineNum;
    o1 : LineNum;
    n : LineNum;
    n1 : LineNum;
    nm1 : LineNum;
  begin

    {Set up seed matches at ends of new file}
    NA^[0].Index := 0;
    NA^[Succ(NewMax)].Index := Succ(OldMax);

    {Toward end of file}
    for n := 0 to Pred(NewMax) do
      if NA^[n].Matched or (n = 0) then begin
        n1 := Succ(n);
        if not(NA^[n1].Matched) then begin
          o := NA^[n].Index;
          if o < OldMax then begin
            o1 := Succ(o);
            if (OA^[o1].Matched = NA^[n1].Matched) and (OA^[o1].Index = NA^[n1].Index) then
              MatchUp(o1, n1);
          end;
        end;
      end;

    {Toward beginning of file}
    nm1 := Succ(NewMax);
    for n := nm1 downto 2 do
      if NA^[n].Matched or (n = nm1) then begin
        n1 := Pred(n);
        if not(NA^[n1].Matched) then begin
          o := NA^[n].Index;
          if o > 1 then begin
            o1 := Pred(o);
            if (OA^[o1].Matched = NA^[n1].Matched) and (OA^[o1].Index = NA^[n1].Index) then
              MatchUp(o1, n1);
          end;
        end;
      end;
  end;

  procedure WriteOutput(var A : Asciiz);
    {-Write one line of output}
  begin
    if not(WriteAsc(Output, A)) then
      WriteError;
    WriteLn(Output);
    if IoResult <> 0 then
      WriteError;
  end;

  procedure WriteBlocks;
    {-Output differences in groups of inserts and deletes}
  var
    o : LineNum;
    n : LineNum;
    t : LineNum;
  begin
    o := 1;
    n := 1;

    repeat

      if not(OA^[o].Matched) then begin
        {Old line was deleted}
        t := o;
        repeat
          Inc(o);
          Inc(Diffs);
        until OA^[o].Matched;
        WriteLn(Output, '****Delete lines ', long2str(t), '-', long2str(Pred(o)), ' of ',oname);
        if IoResult <> 0 then
          WriteError;
        while t < o do begin
          WriteOutput(GetLine(ST2^[OA^[t].Index].Line)^);
          Inc(t);
        end;
      end;

      if not(NA^[n].Matched) then begin
        {New line was inserted}
        t := n;
        repeat
          Inc(n);
          Inc(Diffs);
        until NA^[n].Matched;
        WriteLn(Output, '****Insert lines ', long2str(t), '-', long2str(Pred(n)), ' from ',nname);
        if IoResult <> 0 then
          WriteError;
        while t < n do begin
          WriteOutput(GetLine(ST2^[NA^[t].Index].Line)^);
          Inc(t);
        end;
      end;

      {Skip over matched lines}
      while OA^[o].Matched and NA^[n].Matched and (o <= OldMax) do begin
        Inc(o);
        Inc(n);
      end;

    until (OA^[o].SymIndex = MaxOver) and (NA^[n].SymIndex = MaxOver);
  end;

begin

  {Open StdErr for status reporting}
  OpenStdErr(StdErr);
  writeln(stderr,'File Compare. Copyright (c) 1987 by TurboPower Software. Version 4.03');
  writeln(stderr);

  {Get parameters from command line}
  GetParameters;

  {Initialize the data structures}
  Initialize;

  TimeVal := timems;

  {Read in the files}
  ReadFile(Oname, True, OA^, OldMax);
  ReadFile(Nname, False, NA^, NewMax);

  {Compare the two files}
  FindUnique;
  BlockMoves;
  Sweep;

  {Get elapsed time for read-in and comparison}
  TimeVal := timems-TimeVal;

  WriteBlocks;

  Write(StdErr, Diffs, ' difference');
  if Diffs <> 1 then
    Write(StdErr, 's');
  WriteLn(StdErr, ' found in ', Ms2S(TimeVal), ' seconds');
  if Diffs <> 0 then
    {Return code to indicate differences}
    Halt(1);
end.
