home *** CD-ROM | disk | FTP | other *** search
/ POINT Software Programming / PPROG1.ISO / pascal / swag / sorting.swg / 0054_Fast Sort Text Files.pas < prev   
Encoding:
Pascal/Delphi Source File  |  1995-02-28  |  30.4 KB  |  891 lines

  1. {$A+,B-,D-,E-,F-,G-,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X-,Y-}
  2. {$M 65520,1024,655360}
  3.  
  4. (* {$DEFINE NoDupes} *)
  5. (* uncomment the above line if you don't want duplicate lines *)
  6.  
  7. (* {$DEFINE NoPlus} *)
  8. (* uncomment the above line if you want to delete '+' characters
  9.    terminating lines  *)
  10.  
  11. Program TSort;
  12.  (*
  13.    sort text file(s) in the current directory
  14.    sort inputfile(s) outputfile
  15.      case insensitive sort, skipping empty and duplicate lines
  16.  
  17.      inputfile(s) up to 248.
  18.        DOS wildcards supported for input files
  19.        input files are not altered
  20.        abort if file error input file (file does not exist, read error)
  21.  
  22.      outputfile:
  23.        if outputfile already does exist, it won't be sorted in memory,
  24.        but instead only file merged with the temporary files
  25.        ( so it has to be sorted already! ).
  26.  
  27.      setting DOS errorlevel to 0 on success, 1 if an error occurred.
  28.  
  29.    the more files to merge together, the slower the filemerge.
  30.    all textlines will be written to temporary files, so there must be
  31.    free disk space of at least the total size of the files to sort.
  32.  
  33.    if necessary, increase files= in config.sys and reboot,
  34.    or run Quarterdeck's files.com or a similar program
  35.    to increase the number of filehandles allowed by DOS
  36.    (max 99 for DOS 2.x; max 254 for DOS 3.x or later).
  37.  
  38.    Author: Eddy Thilleman, september 1994
  39.    written in Borland Pascal version 7.01
  40.    Donated to the public domain. No rights reserved.
  41.  *)
  42.  
  43. Uses
  44.   Dos;
  45.  
  46. const
  47.   NumbFiles= 254;
  48. type
  49.   fht      = array[1..NumbFiles] of byte;
  50. var
  51.   NewFHT   : fht;
  52.   OldFHT   : longint;
  53.   OldSize  : word;
  54.  
  55. Const
  56.   NoFAttr : word =   $1C;  { dir-, volume-, system attributen }
  57.   FAttr   : word =   $23;  { readonly-, hidden-, archive attributes }
  58.   MaxNrLines = 10000;  { max # lines to sort in memory in one run }
  59.   MaxNrFiles =   248;  { max 249 open files (248 temp. files + 1 dest.file) }
  60.   BufSize    =  8192;  { 8 KB for input- and output buffers }
  61.   SmallBufS  =  1024;  { 1 KB for input temp.files }
  62.  
  63. Type
  64.   String3   = String[ 3];
  65.   String12  = String[12];
  66.   LineStr   = String;
  67.   ptrLine   = ^LineStr;
  68.   BufType   = array [1..BufSize] of char;
  69.   SmallBufT = array [1..SmallBufS] of char;
  70.   tTxtFile  = record
  71.                 TxtFile  : text;
  72.                 Line     : string;
  73.                 EndOfFile: boolean;
  74.                 Error    : boolean;
  75.                 SmallBuf : SmallBufT;
  76.               end;
  77.   pTxtFile  = ^tTxtFile;
  78.  
  79. Const
  80.   WhiteSpace : string3 = #00#09#255;
  81.  
  82. Var
  83.   MarkPtr   : pointer;        { marks start of Heapmemory }
  84.   aPtrLines : array [1..MaxNrLines] of ptrLine;
  85.   aPtrFiles : array [1..MaxNrFiles] of pTxtFile;
  86.   Line0     : String;         { temporary line }
  87.   Line1     : String;         { temporary line 1 for upper case }
  88.   Line2     : String;         { temporary line 2 for upper case }
  89.   NrLine    : word;           { current # of line in memory }
  90.   NrLines   : word;           { number of lines in memory   }
  91.   InputFile : text;           { input file   }
  92.   OutputFile: text;           { output file  }
  93.   DestFile  : String;         { filename of destination file }
  94.   SourceBuf : BufType;        { source text buffer      }
  95.   DestBuf   : BufType;        { destination text buffer }
  96.   FR        : SearchRec;      { FileRecord              }
  97.   FMask     : String12;       { FileMask                }
  98.   TempDir   : String3;        { temporary directory     }
  99.   TempFile  : String;         { temporary output file   }
  100.   TempNr    : byte;           { for name temp. file     }
  101.   tNr,tMaxNr: byte;           { for name temp. file     }
  102.   Temp      : String3;        { name for temp. file     }
  103.   Exists    : boolean;
  104.   ParamNr   : byte;
  105.   OldExitProc : Pointer;
  106.   t         : ptrLine;
  107.   Ready     : boolean;
  108.   divisor   : word;           { divisor for showing # of lines merged
  109.                                 inversely proportional to # of files  }
  110.   fName     : string12;       { for padding filename }
  111.   display   : string[79];
  112.   number    : string[ 5];
  113.  
  114.  
  115. procedure SetCursorOff; assembler;
  116. asm
  117.   mov  AH,$01
  118.   mov  CX,$2B0C
  119.   int  $10
  120. end;
  121.  
  122. procedure SetCursorOn; assembler;
  123. asm
  124.   mov  AH,$01
  125.   mov  CX,$0B0C
  126.   int  $10
  127. end;
  128.  
  129.  
  130. function HeapFunc( Size: word ): byte; far; assembler;
  131.   { return value of
  132.       0 : failure, run-time error, immediate abortion
  133.       1 : failure, New or GetMem returns a nil pointer
  134.       2 : success, retry
  135.     Borland Pascal Language Guide, page 265
  136.     "HeapError variable"
  137.   }
  138. asm
  139.   mov  ax, 1
  140. end  { HeapFunc };
  141.  
  142.  
  143. procedure MakeNewFHT;
  144.   { create a new expanded file handle table }
  145. begin
  146.   Oldsize := MemW[PrefixSeg:$32];            { Store the old FHT size     }
  147.   OldFHT  := MemL[PrefixSeg:$34];            { Store the old FHT address  }
  148.   FillChar(NewFHT,NumbFiles,$ff);            { Fill new table with 255    }
  149.   MemW[PrefixSeg:$32] := NumbFiles;          { Put new size in the psp    }
  150.   MemL[PrefixSeg:$34] := longint(@NewFHT);   { new FHT address in psp     }
  151.   move(Mem[PrefixSeg:$19],NewFHT,$15);       { put contents of old to new }
  152. end; { MakeNewFHT }
  153.  
  154.  
  155. function OpenTextFile( var InF: text; name: string; var buffer; size: word ): boolean;
  156. begin
  157.   Assign( InF, Name );
  158.   SetTextBuf( InF, buffer, size );
  159.   Reset( InF );
  160.   OpenTextFile := (IOResult = 0);
  161. end  { OpenTextFile };
  162.  
  163.  
  164. function CreateTextFile( var OutF: text; name: string; var buffer: BufType ): boolean;
  165. begin
  166.   Assign( OutF, Name );
  167.   SetTextBuf( OutF, buffer );
  168.   Rewrite( OutF );
  169.   CreateTextFile := (IOResult = 0);
  170. end  { CreateTextFile };
  171.  
  172.  
  173. function Exist( Name : string ) : Boolean;
  174.   { Return true if directory or file with the same name is found}
  175. var
  176.   F    : file;
  177.   Attr : Word;
  178. begin
  179.   Assign( F, Name );
  180.   GetFAttr( F, Attr );
  181.   Exist := (DosError = 0)
  182. end;
  183.  
  184.  
  185. function fExist( fName: string ) : boolean;
  186. begin
  187.   fExist := ( FSearch(fName,'') <> '' );
  188. end;
  189.  
  190.  
  191. procedure UniekeEntry( var Naam : string3 );
  192. const
  193.   min   = 128;
  194. var
  195.   Nbyte : array [0..3] of byte absolute Naam;
  196.   Exists : boolean;
  197.  
  198. begin
  199.   Nbyte [0] :=  3;  { filename of 3 characters }
  200.  
  201.   Exists := True;
  202.   Nbyte [1] := 255;
  203.   while (Nbyte [1] >= min) and Exists do
  204.   begin
  205.     Nbyte [2] := 255;
  206.     while (Nbyte [2] >= min) and Exists do
  207.     begin
  208.       Nbyte [3] := 255;
  209.       while (Nbyte [3] >= min) and Exists do
  210.       begin
  211.         Exists := Exist( Naam );
  212.         if Exists then dec (Nbyte [3]);
  213.       end;
  214.       if Exists then dec (Nbyte [2]);
  215.     end;
  216.     if Exists then dec (Nbyte [1]);
  217.   end;
  218. end;  { UniekeEntry }
  219.  
  220.  
  221. function fRename( var Source, Dest: string ): boolean; assembler;
  222.   { rename file or move file on same drive  }
  223.   { *no* error checking!                    }
  224.   { source and dest will be zero terminated }
  225.   { by adding the ASCII zero char to both   }
  226.   { so there must be room left for one char }
  227.   { but that is not checked                 }
  228.   { (byte length is not affected)           }
  229. asm     push  ds          { save ds                       }
  230.         xor   ax, ax      { clear ax                      }
  231.  
  232.         lds   si, source  { DS:SI = @source               }
  233.         mov   al, [si]    { load length byte              }
  234.         inc   si          { point to first char           }
  235.         mov   dx, si      { DS:DX = @source (for dos)     }
  236.         add   si, ax      { get beyond end of string      }
  237.         mov   [si], ah    { zero terminated string        }
  238.  
  239.         les   di, dest    { ES:DI = @dest                 }
  240.         mov   al, [di]    { load length byte              }
  241.         inc   di          { point to first char           }
  242.         mov   si, di      { ES:DI = @dest (for dos)       }
  243.         add   si, ax      { get beyond end of string      }
  244.         mov   [si], ah    { zero terminated string        }
  245.  
  246.         mov   ah, 56h     { dos function rename file      }
  247.         mov   cl, 23h     { file attribute mask           }
  248.         int   21h         { call dos to rename file       }
  249.  
  250.         mov   ax, 0       { assume false return value     }
  251.         jc    @exit       { error, return false           }
  252.         inc   ax          { return value true             }
  253. @exit:  pop   ds          { restore ds                    }
  254. end;  { fRename }
  255.  
  256.  
  257. procedure StrCopy( var Str1, Str2: string ); assembler;
  258.   { copy str1 to str2 }
  259. asm     mov   dx, ds      { save DS                       }
  260.         lds   si, str1    { load in DS:SI pointer to str1 }
  261.         cld               { string operations forward     }
  262.         les   di, str2    { load in ES:DI pointer to str2 }
  263.         xor   ch, ch      { clear CH                      }
  264.         mov   cl, [si]    { length str1 --> CX            }
  265.         inc   cx          { include length byte           }
  266.     rep movsb             { copy str1 to str2             }
  267. @exit:  mov   ds, dx      { finished, restore DS          }
  268. end  { StrCopy };
  269.  
  270.  
  271. procedure Byte2zStr( n, width: byte; var str: string ); assembler;
  272.   { Byte to string with leading zeros }
  273. asm
  274.         std                 { string operations backwards }
  275.         mov   al, [n]       { numeric value to convert    }
  276.         mov   cl, [width]   { width of str                }
  277.         xor   ch, ch        { clear ch                    }
  278.         jcxz  @exit         { done?                       }
  279.         les   di, str       { adress of str               }
  280.         mov   [di], cl      { length of str               }
  281.         add   di, cx        { start with last char str    }
  282. @start: aam                 { divide al by 10             }
  283.         add   al, 30h       { convert remainder to char   }
  284.         stosb               { store digit                 }
  285.         xchg  al, ah        { swap remainder and quotient }
  286.         dec   cl            { count down                  }
  287.         jcxz  @exit         { done?                       }
  288.         jmp   @start        { next digit                  }
  289. @exit:
  290. end  { Byte2zStr };
  291.  
  292.  
  293. procedure Upper( var Str: String ); assembler;
  294. asm      mov   dx, ds                 { save DS                   }
  295.          mov   bx, 1961h              { upper- and lower limit    }
  296.          lds   si, str                { DS:SI = @str              }
  297.          cld                          { string operations forward }
  298.          lodsb                        { load length byte          }
  299.          mov   cl, al                 { and put it in cx          }
  300.          xor   ch, ch                 { clear ch                  }
  301.          shr   cx, 1                  { divide length by 2        }
  302.          jnc   @part1                 { if lenght even, part1     }
  303.          lodsb                        { load first char           }
  304.          sub   al, bl                 { lowercase letter?         }
  305.          cmp   al, bh                 { lowercase letter?         }
  306.          ja    @part1                 { if no lowercase letter    }
  307.   @loop: sub   byte ptr[si-1],'a'-'A' { convert to uppercase      }
  308.  @part1: jcxz  @exit                  { done                      }
  309.          lodsw                        { load next two chars       }
  310.          sub   al, bl                 { lowercase letter?         }
  311.          cmp   al, bh                 { lowercase letter?         }
  312.          ja    @part2                 { if no lowercase letter    }
  313.          sub   byte ptr[si-2],'a'-'A' { convert to uppercase      }
  314.  @part2: dec   cx                     { count down                }
  315.          sub   ah, bl                 { lowercase letter?         }
  316.          cmp   ah, bh                 { lowercase letter?         }
  317.          ja    @part1                 { if no lowercase letter    }
  318.          jmp   @loop                  { convert to uppercase      }
  319.   @exit: mov   ds, dx
  320. end; { Upper }
  321.  
  322.  
  323. procedure White2Space( var Str: string; const WhiteSpace: string ); assembler;
  324.   { replace white space chars in Str by spaces
  325.     the string WhiteSpace contains the chars to replace }
  326. asm     push  ds                 { save DS                       }
  327.         cld                      { string operations forwards    }
  328.         les   di, str            { ES:DI points to Str           }
  329.         xor   cx, cx             { clear cx                      }
  330.         mov   cl, [di]           { length Str in cl              }
  331.         jcxz  @exit              { if length of Str = 0, exit    }
  332.         inc   di                 { point to 1st char of Str      }
  333.         mov   dx, cx             { store length of Str           }
  334.         mov   bx, di             { pointer to Str                }
  335.         lds   si, WhiteSpace     { DS:SI points to WhiteSpace    }
  336.         mov   ah, [si]           { load length of WhiteSpace     }
  337.  
  338. @start: cmp   ah, 0              { more chars WhiteSpace left?   }
  339.         jz    @exit              { no, exit                      }
  340.         inc   si                 { point to next char WhiteSpace }
  341.         mov   al, [si]           { next char to hunt             }
  342.         dec   ah                 { ah counting down              }
  343.         xor   dh, dh             { clear dh                      }
  344.         mov   cx, dx             { restore length of Str         }
  345.         mov   di, bx             { restore pointer to Str        }
  346.         mov   dh, ' '            { space char                    }
  347. @scan:
  348.   repne scasb                    { the hunt is on                }
  349.         jnz   @next              { white space found?            }
  350.         mov   [di-1], dh         { yes, replace that one         }
  351. @next:  jcxz  @start             { if no more chars in Str       }
  352.         jmp   @scan              { if more chars in Str          }
  353. @exit:  pop   ds                 { we are finished.              }
  354. end  { White2Space };
  355.  
  356.  
  357. procedure RTrim( var Str: string ); assembler;
  358.   { remove trailing spaces from str }
  359. asm     { setup }
  360.         std                      { string operations backwards   }
  361.         les   di, str            { ES:DI points to Str           }
  362.         xor   cx, cx             { clear cx                      }
  363.         mov   cl, [di]           { length Str in cl              }
  364.         jcxz  @exit              { if length of Str = 0, exit    }
  365.         mov   bx, di             { bx points to Str              }
  366.         add   di, cx             { start with last char in Str   }
  367.         mov   al, ' '            { hunt for spaces               }
  368.  
  369.         { remove trailing spaces }
  370.    repe scasb                    { the hunt is on                }
  371.         jz    @null              { only spaces?                  }
  372.         inc   cx                 { no, don't lose last char      }
  373. @null:  mov   [bx], cl           { overwrite length byte of Str  }
  374. @exit:
  375. end  { RTrim };
  376.  
  377.  
  378. procedure LTrim( var Str: string ); assembler;
  379.   { remove leading white space from str }
  380. asm     push  ds                 { save DS                            }
  381.         cld                      { string operations forward          }
  382.         lds   si, str            { DS:SI points to Str                }
  383.         xor   cx, cx             { clear cx                           }
  384.         mov   cl, [si]           { length Str --> cl                  }
  385.         jcxz  @exit              { if length Str = 0, exit            }
  386.         mov   bx, si             { save pointer to length byte of Str }
  387.         inc   si                 { 1st char of Str                    }
  388.         mov   di, si             { pointer to 1st char of Str --> di  }
  389.         mov   al, ' '            { hunt for spaces                    }
  390.         xor   dx, dx             { clear dx                           }
  391.  
  392.         { look for leading spaces }
  393.    repe scasb                    { the hunt is on                     }
  394.         jz    @done              { if only spaces, we are done        }
  395.         inc   cx                 { no, don't lose 1st non-blank char  }
  396.         dec   di                 { no, don't lose 1st non-blank char  }
  397.         mov   dx, cx             { new lenght of Str                  }
  398.         xchg  di, si             { swap si and di                     }
  399.     rep movsb                    { move remaining part of Str         }
  400. @done:  mov   [bx], dl           { new length of Str                  }
  401. @exit:  pop   ds                 { finished, restore DS               }
  402. end  { LTrim };
  403.  
  404.  
  405. procedure Pad( var Str: String; len: byte ); assembler;
  406.   { pad str with spaces while length str < len }
  407.   { len must not be greater than size( str )   }
  408.   { this is not checked!                       }
  409. asm
  410.              les   di, str            { ES:DI = @str               }
  411.              cld                      { string operations forward  }
  412.              xor   ax, ax             { clear ax                   }
  413.              mov   al, [di]           { load length byte in al     }
  414.              and   al, al             { length str = 0?            }
  415.              jz    @exit              { yes, done                  }
  416.  
  417.              xor   cx, cx             { clear cx                   }
  418.              mov   cl, len            { load new length            }
  419.              mov   bl, cl             { store new length           }
  420.              sub   cl, al             { len - length str           }
  421.              jna   @exit              { length str >= len          }
  422.  
  423.              mov   [di], bl           { set new length             }
  424.              add   di, ax             { get to end of str          }
  425.              inc   di                 { get beyond end of str      }
  426.              mov   ax, '  '           { fill with spaces           }
  427.              shr   cx, 1              { (len-length) / 2           }
  428.              jnc   @pad               { if (len-lenght) even, pad  }
  429.              mov   [di], al           { if odd # of spaces to fill }
  430.              jcxz  @exit              { if only one space, exit    }
  431.              inc   di                 { next destination           }
  432.    @pad: rep stosw                    { pad with spaces            }
  433.   @exit:
  434. end; { Pad }
  435.  
  436.  
  437. function LineOK( var str: string ) : Boolean; assembler;
  438.   { Line contains chars > ASCII 20h ? }
  439. asm     mov   dx, ds         { save DS                          }
  440.         xor   ax, ax         { assume false return value        }
  441.         xor   cx, cx         { clear cx                         }
  442.         lds   si, str        { load in DS:SI pointer to Str     }
  443.         mov   cl, [si]       { length Str --> cx                }
  444.         jcxz  @exit          { if no characters, exit           }
  445.         inc   si             { point to 1st character           }
  446.  
  447.         { look for chars > ASCII 20h }
  448. @start: mov   bl, [si]       { load character                   }
  449.         cmp   bl, ' '        { char > ASCII 20h?                }
  450.         ja    @yes           { yes, return true                 }
  451.         inc   si             { next character                   }
  452.         dec   cx             { count down                       }
  453.         jcxz  @exit          { if no more characters left, exit }
  454.         jmp   @start         { try again                        }
  455. @yes:   mov   ax, 1          { return value true                }
  456. @exit:  mov   ds, dx         { restore DS                       }
  457. end  { LineOK };
  458.  
  459.  
  460. procedure Sorting( min, max: word );
  461. var
  462.   n : byte;
  463.   x : longint;
  464.  
  465.   {$S+}
  466.   function IsLess( i1, i2: word ): boolean;
  467.   begin
  468.     StrCopy( aPtrLines[i1]^, Line1 );
  469.     StrCopy( aPtrLines[i2]^, Line2 );
  470.     Upper( Line1 );
  471.     Upper( Line2 );
  472.     IsLess := ( Line1 < Line2 );
  473.   end;
  474.  
  475.   procedure Swap( var a, b: ptrLine );
  476.   begin
  477.     t := a;
  478.     a := b;
  479.     b := t;
  480.   end;
  481.  
  482.   procedure QuickSort( left, right: word );
  483.     { Case insensitive QuickSort }
  484.   var
  485.     lower, upper, middle: word;
  486.   begin
  487.     lower  := left;
  488.     upper  := right;
  489.     middle := (left+right) div 2;
  490.     repeat
  491.       while IsLess( lower , middle ) do inc( lower );
  492.       while IsLess( middle, upper  ) do dec( upper );
  493.       if lower <= upper then
  494.       begin
  495.         swap( aPtrLines[lower], aPtrLines[upper] );  { swap pointers }
  496.         inc( lower );
  497.         dec( upper );
  498.       end;
  499.     until lower > upper;
  500.     if left  < upper then QuickSort( left , upper );
  501.     if lower < right then QuickSort( lower, right );
  502.   end  { QuickSort };
  503.   {$S-}
  504.  
  505.   function Sorted: boolean;
  506.   Var
  507.     i: word;
  508.   begin
  509.     Sorted := True;
  510.     x := 0;
  511.     For i := 1 to Pred( Max ) do
  512.       if IsLess( Succ( i ), i ) then
  513.       begin
  514.         Sorted := False;
  515.         inc( x );
  516.       end;
  517.     { end for i loop }
  518.   end;
  519.  
  520. begin  { Sorting }
  521.   n := 0;
  522.   Str( NrLines:5, number );
  523.   display := fName + ':' + Temp + '  ' + number + ' lines   Sorting ';
  524.   while not Sorted do
  525.   begin
  526.     inc( n );
  527.     write( #13, display, n:5,' ',x:5 );
  528.     QuickSort( min, max );
  529.   end;
  530.   writeln( #13, display, n:5,' ',x:5 );
  531. end;  { Sorting }
  532.  
  533.  
  534. procedure TestLines;
  535. var
  536.   i   : word;
  537.   len : byte;
  538.  
  539.   procedure TrimLine;
  540.   begin
  541.     White2Space( aPtrLines[i]^, WhiteSpace );  { white space to spaces   }
  542.     RTrim( aPtrLines[i]^ );                    { remove trailing spaces  }
  543.     len := length( aPtrLines[i]^ );
  544.   end;
  545.  
  546. {$IFDEF NoPlus}
  547.   procedure TrimPlus;
  548.   begin
  549.     TrimLine;
  550.     while aPtrLines[i]^[len] = '+' do
  551.     begin
  552.       dec( len );
  553.       aPtrLines[i]^[0] := chr( len );
  554.       TrimLine;
  555.     end;
  556.   end;
  557. {$ENDIF}
  558.  
  559. begin
  560.   for i := 1 to NrLines do
  561.   begin
  562.     len := length( aPtrLines[i]^ );
  563. {$IFDEF NoPlus}
  564.     TrimPlus;
  565. {$ELSE}
  566.     TrimLine;
  567. {$ENDIF}
  568.     if ((len = 0) or not LineOK( aPtrLines[i]^ )) then
  569.       aPtrLines[i] := nil;             { invalid Line }
  570.   end;
  571. end;  { TestLine }
  572.  
  573.  
  574. procedure Process( var SourceFile : string12 );
  575. begin
  576.   if OpenTextFile( InputFile, SourceFile, SourceBuf, BufSize ) then
  577.   begin
  578.     while not EOF( InputFile ) and (IOResult = 0) do
  579.     begin
  580.       inc( TempNr );
  581.       Byte2zStr( TempNr, 3, Temp );
  582.       TempFile := TempDir + '\' + Temp;
  583.       write( fName, ':', Temp, '  ' );
  584.       if CreateTextFile( OutputFile, TempFile, DestBuf ) then
  585.       begin
  586.         { read lines from input files }
  587.         Mark( MarkPtr );
  588.         NrLine := 1;
  589.         if (Length( Line0 ) = 0) then ReadLn( InputFile, Line0 );
  590.         GetMem( aPtrLines[NrLine], Length( Line0 ) + 1 );
  591.  
  592.         while not EOF(InputFile) and (IOResult = 0)
  593.             and (NrLine <= MaxNrLines) and (aPtrLines[NrLine] <> nil) do
  594.         begin
  595.           StrCopy( Line0, aPtrLines[NrLine]^ );
  596.           ReadLn( InputFile, Line0 );
  597.           Inc( NrLine );
  598.           if (NrLine <= MaxNrLines) then
  599.             GetMem( aPtrLines[NrLine], Length( Line0 )+1 );
  600.         end; { while not memory full }
  601.  
  602.         if ((NrLine <= MaxNrLines) and (aPtrLines[NrLine] <> nil)) then
  603.         begin
  604.           if EOF(InputFile) then
  605.           begin
  606.             aPtrLines[NrLine]^ := Line0;
  607.             Line0 := '';
  608.           end;
  609.         end
  610.         else
  611.           Dec( NrLine );
  612.         NrLines := NrLine;
  613.         Write( NrLines:5, ' lines' );
  614.  
  615.         { Trim Lines }
  616.         TestLines;
  617.  
  618.         { sort pointers }
  619.         Sorting( 1, NrLines );
  620.  
  621.         { write sorted lines in temp files }
  622.         for NrLine := 1 to NrLines do
  623.         begin
  624.           if (aPtrLines[NrLine] <> nil) then
  625.             Writeln( OutputFile, aPtrLines[NrLine]^ );
  626.           if (IOResult <> 0) then
  627.           begin
  628.             writeln( 'Error reading ', TempFile );
  629.             halt( 1 );
  630.           end;
  631.           aPtrLines[NrLine]^ := '';
  632.           aPtrLines[NrLine] := nil;
  633.         end;
  634.         Release( MarkPtr );
  635.         Close( OutputFile );
  636.       end  { if CreateTextFile }
  637.       else
  638.       begin
  639.         writeln(' error creating file ', TempFile );
  640.         Halt( 1 );
  641.       end;  {if CreateTextFile }
  642.     end;  {while not eof}
  643.     Close( InputFile );
  644.   end   { if OpenTextFile }
  645.   else
  646.     writeln(' error opening file ', SourceFile );
  647.   { endif OpenTextFile }
  648. end  { Sorting };
  649.  
  650.  
  651. procedure MergeSort;
  652. var nr: byte;
  653.     count: longint;
  654.  
  655. {$IFDEF NoDupes}
  656.   function IsEqual( i1, i2: word ): boolean;
  657.   begin
  658.     StrCopy( aPtrFiles[i1]^.Line, Line1 );
  659.     StrCopy( aPtrFiles[i2]^.Line, Line2 );
  660.     Upper( Line1 );
  661.     Upper( Line2 );
  662.     IsEqual := ( Line1 = Line2 );
  663.   end;
  664. {$ENDIF}
  665.  
  666.   function IsLess( i1, i2: word ): boolean;
  667.   begin
  668.     StrCopy( aPtrFiles[i1]^.Line, Line1 );
  669.     StrCopy( aPtrFiles[i2]^.Line, Line2 );
  670.     Upper( Line1 );
  671.     Upper( Line2 );
  672.     IsLess := ( Line1 < Line2 );
  673.   end;
  674.  
  675. begin
  676.   tNr := 1;
  677.   tMaxNr := TempNr;
  678.   if TempNr > MaxNrFiles then tMaxNr := MaxNrFiles;
  679.   Mark( MarkPtr );
  680.  
  681.   New( aPtrFiles[tNr] );
  682.   while (tNr < tMaxNr) and (aPtrFiles[tNr] <> nil) do
  683.   begin
  684.     Inc( tNr );
  685.     New( aPtrFiles[tNr] );
  686.   end;
  687.   if (aPtrFiles[tNr] = nil) then dec( tNr );
  688.  
  689.   tMaxNr := tNr;
  690.   for tNr := 1 to tMaxNr do    { open temp files and read first line }
  691.   begin
  692.     Byte2zStr( tNr, 3, Temp );
  693.     TempFile := TempDir + '\' + Temp;
  694.     if not OpenTextFile( aPtrFiles[tNr]^.TxtFile, TempFile, aPtrFiles[tNr]^.SmallBuf, SmallBufS ) then
  695.     begin
  696.       writeln( 'Error opening ', TempFile );
  697.       halt( 1 );
  698.     end;
  699.     ReadLn( aPtrFiles[tNr]^.TxtFile, aPtrFiles[tNr]^.Line );
  700.     if (IOResult <> 0) then
  701.     begin
  702.       writeln( 'Error reading ', TempFile );
  703.       halt( 1 );
  704.     end;
  705.     aPtrFiles[tNr]^.EndOfFile := EOF( aPtrFiles[tNr]^.TxtFile );
  706.     aPtrFiles[tNr]^.Error := (IOResult <> 0);
  707.   end;
  708.   divisor := (4000 div tMaxNr);
  709.  
  710.   if CreateTextFile( OutputFile, DestFile, DestBuf ) then
  711.   begin
  712.     count := 0;
  713.     nr := 1;
  714.     Ready := False;
  715.     while not Ready do
  716.     begin
  717.       for tNr := 1 to tMaxNr do      { take alphabetically the first line }
  718.       begin
  719.         if tNr <> nr then
  720.         begin
  721.           if Length( aPtrFiles[tNr]^.Line ) > 0 then
  722.           begin
  723. {$IFDEF NoDupes}
  724.             while IsEqual( tNr, nr )
  725.             and not aPtrFiles[tNr]^.EndOfFile
  726.             and not aPtrFiles[tNr]^.Error
  727.             do     { no duplicates }
  728.             begin
  729.               ReadLn( aPtrFiles[tNr]^.TxtFile, aPtrFiles[tNr]^.Line );
  730.               aPtrFiles[tNr]^.Error := (IOResult <> 0);
  731.               aPtrFiles[tNr]^.EndOfFile := EOF( aPtrFiles[tNr]^.TxtFile );
  732.             end;
  733. {$ENDIF}
  734.             if IsLess( tNr, nr ) then
  735.               nr := tNr;
  736.           end;  { if Length( aPtrFiles[tNr]^.Line ) > 0 }
  737.         end;  { if tNr <> nr }
  738.       end;  { for tNr := 1 to tMaxNr loop }
  739.  
  740.       if Length( aPtrFiles[nr]^.Line ) > 0 then
  741.       begin
  742.         StrCopy( aPtrFiles[nr]^.Line, Line1 );
  743.         Upper( Line1 );
  744. {$IFDEF NoDupes}
  745.         if (Line1 <> Line0) then
  746.         begin
  747. {$ENDIF}
  748.           writeln( OutputFile, aPtrFiles[nr]^.Line );
  749.           if (IOResult <> 0) then
  750.           begin
  751.             writeln( 'Error writing ', DestFile );
  752.             halt( 1 );
  753.           end;
  754.           inc( count );
  755.           if (count mod divisor) = 0 then write( #13,'Merging ', count:7 );
  756. {$IFDEF NoDupes}
  757.         end;
  758. {$ENDIF}
  759.         StrCopy( aPtrFiles[nr]^.Line, Line0 );       { last written line }
  760.         Upper( Line0 );
  761.         aPtrFiles[nr]^.Line := '';
  762.       end;
  763.  
  764.       StrCopy( aPtrFiles[nr]^.Line, Line1 );
  765.       Upper( Line1 );
  766.       while (not aPtrFiles[nr]^.EndOfFile and not aPtrFiles[nr]^.Error)
  767.       and (
  768. {$IFDEF NoDupes}
  769.       (Line1 = Line0) or
  770. {$ENDIF}
  771.       (Length( aPtrFiles[nr]^.Line ) = 0)) do
  772.       begin
  773.         ReadLn( aPtrFiles[nr]^.TxtFile, aPtrFiles[nr]^.Line );
  774.         aPtrFiles[nr]^.Error := (IOResult <> 0);
  775.         aPtrFiles[nr]^.EndOfFile := EOF( aPtrFiles[nr]^.TxtFile );
  776.         StrCopy( aPtrFiles[nr]^.Line, Line1 );
  777.         Upper( Line1 );
  778.       end;
  779.  
  780.       if Length( aPtrFiles[nr]^.Line ) = 0 then
  781.       begin
  782.         tNr := 1;        { the first non-empty line }
  783.         while Length( aPtrFiles[tNr]^.Line ) = 0 do inc( tNr );
  784.         if (tNr <= tMaxNr) then nr := tNr;
  785.       end;
  786.  
  787.       Ready := True;
  788.       tNr := 1;
  789.       while (tNr <= tMaxNr) and Ready do         { check for more lines }
  790.       begin
  791.         if (Length( aPtrFiles[tNr]^.Line ) > 0) then Ready := False;
  792.         inc( tNr );
  793.       end;
  794.     end;  { while not Ready }
  795.     Close( OutputFile );
  796.     Writeln( #13,'Merged ', count:7, ' lines' );
  797.   end;  { if CreateTextFile }
  798.  
  799.   for tNr := 1 to tMaxNr do
  800.   begin
  801.     Close( aPtrFiles[tNr]^.TxtFile );      { close and delete all temp files }
  802.     Erase( aPtrFiles[tNr]^.TxtFile );
  803.   end;
  804.   Release( MarkPtr );
  805. end  { MergeSort };
  806.  
  807.  
  808. {$F+}
  809. procedure OurExitProc;
  810. begin
  811.   ExitProc := OldExitProc;
  812.  
  813.   { Restore Old File Handle Table }
  814.   MemW[PrefixSeg:$32] := OldSize;
  815.   MemL[PrefixSeg:$34] := OldFHT;
  816.  
  817.   SetCursorOn;
  818. end;
  819. {$F-}
  820.  
  821.  
  822. begin
  823.   {set up our exit handler}
  824.  
  825.   OldExitProc := ExitProc;
  826.   ExitProc := @OurExitProc;
  827.  
  828.   if ParamCount > 1 then           { parameters: inputfile(s) outputfile }
  829.   begin
  830.     SetCursorOff;
  831.     Line0 := '';
  832.     UniekeEntry( TempDir );
  833.     if not Exists then
  834.     begin
  835.       MkDir( TempDir );
  836.       if (IOResult=0) then
  837.       begin
  838.         HeapError := @HeapFunc;
  839.         DestFile := ParamStr( ParamCount );
  840.         TempNr := 0;
  841.  
  842.         if fExist( DestFile ) then
  843.         begin                              { if outputfile already exist }
  844.           inc( TempNr );
  845.           Byte2zStr( TempNr, 3, Temp );
  846.           TempFile := TempDir + '\' + Temp;   { move it to the temp directory }
  847.           if fRename( DestFile, TempFile ) then
  848.             writeln( DestFile, ':', Temp, '  ' )
  849.           else
  850.             dec( TempNr );
  851.         end;  { if fExist( DestFile ) }
  852.  
  853.         for ParamNr := 1 to (ParamCount-1) do         { all inputfile(s) }
  854.         begin
  855.           FMask := ParamStr( ParamNr );               { filemask         }
  856.           FindFirst(FMask, FAttr, FR);
  857.           while DosError = 0 do
  858.           begin
  859.             StrCopy( FR.Name, fName );
  860.             Pad( fName, 12 );
  861.             Process( FR.Name );
  862.             FindNext( FR );
  863.           end;
  864.         end;  { all inputfile(s) }
  865.  
  866.         { if one temp file rename it to destination, else merge sort }
  867.         if TempNr = 1 then
  868.         begin
  869.           Byte2zStr( TempNr, 3, Temp );
  870.           TempFile := TempDir + '\' + Temp;
  871.           if not fRename( TempFile, DestFile ) then
  872.             writeln( 'Could not rename ',TempFile,' to ',DestFile );
  873.           {}
  874.         end
  875.         else
  876.         begin
  877.           MakeNewFHT;
  878.           MergeSort;
  879.         end;
  880.         RmDir( TempDir );     { remove temporary directory }
  881.       end   { if IOResult=0 }
  882.       else
  883.         writeln( 'Cannot create temporary directory!' );
  884.       { }
  885.     end;  { if not Exists TempDir }
  886.   end   { if ParamCount > 1 }
  887.   else
  888.     WriteLn( 'Sort inputfile(s) outputfile ' );
  889.   { }
  890. end.
  891.