home *** CD-ROM | disk | FTP | other *** search
/ CD Actual 13 / CDA13.ISO / cdactual / demobin / share / program / Pascal / AIM.ZIP / AIMDEXP.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1991-06-29  |  34.1 KB  |  1,047 lines

  1.  
  2.         {Compiler directives}
  3.  
  4. {D+}                    {debug information}
  5. {T-}                    {create .TPM file for debugging}
  6. {F-}                    {automatically force far calls}
  7. {V-}                    {var string checking}
  8. {L+}                    {link buffer in memory}
  9. {$R-}                   {range checking}
  10. {$B+}                   {boolean complete evaluation}
  11. {$S+}                   {stack checking}
  12. {$I+}                   {I/O checking}
  13. {$N-}                   {numeric coprocessor}
  14. {$M 65500,0,655360}     {memory sizes}
  15.  
  16. {***************************************************************************}
  17. {*                                                                         *}
  18. {*      AIMDEXP.PAS         Copyright - Matt Goodrich                      *}
  19. {*                              Jun 29, 1991                               *}
  20. {*                                                                         *}
  21. {*   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -   *}
  22. {*                                                                         *}
  23. {*      Maintenance History :                                              *}
  24. {*                                                                         *}
  25. {*  1.A  MG  6/29/91 - Initial Keyin.                                      *}
  26. {*                                                                         *}
  27. {***************************************************************************}
  28.  
  29. PROGRAM AIMDEXP;
  30.  
  31. USES
  32.  
  33.   CRT, MISCSTUF, AIMUNIT;
  34.  
  35.  
  36. {$I AIMVAR.PAS}  {inclusion with shared variable definitions}
  37.  
  38. CONST
  39.  
  40.   Version           = '1.A';    {version number of this program}
  41.  
  42.  
  43.   EOF_Mark          = CHR (26);
  44.   CarrRet           = CHR (13);
  45.   LineFeed          = CHR (10);
  46.  
  47.   WorkArrayMax      = 16383;    {The max seems to be 16383.}
  48.   ReadBufferMaxSize = 32000;
  49.  
  50.  
  51. TYPE
  52.   IntPtr            = ^INTEGER;
  53.  
  54.   WrkArrPtr         = ARRAY [1..WorkArrayMax] OF LONGINT;
  55.   WrkArrType        = ^WrkArrPtr;
  56.  
  57.  
  58.  
  59.  
  60.  
  61. VAR
  62.   BytesPerHash    : LONGINT;
  63.   BytesToWrit     : LONGINT;
  64.   BytesToWrite    : LONGINT;
  65.   BytesWritten    : LONGINT;
  66.   DashPos         : INTEGER;
  67.   DataFileSize    : LONGINT;
  68.   EOFReached      : STRING [ 1];
  69.   InRecnumBuffEnd : LONGINT;
  70.   InRecnumToGet   : LONGINT;
  71.   NewByte         : BYTE;
  72.   NextBuffRec     : INTEGER;
  73.   NumRecsInBuff   : INTEGER;
  74.   NumbDataRecs    : LONGINT;
  75.   OldBlockOn      : LONGINT;
  76.   OldByteOn       : LONGINT;
  77.   OldHash         : LONGINT;
  78.   One             : LONGINT;
  79.   ParamKeys       : STRING [99];
  80.   ParamLen        : STRING [ 7];
  81.   ReadBuffer      : ARRAY [1..ReadBufferMaxSize] OF CHAR;
  82.   ReadBufferSize  : LONGINT;
  83.   RecToProcess    : LONGINT;
  84.   SeekPosData     : LONGINT;
  85.   SizeAimFile     : LONGINT;
  86.   StrPtr          : INTEGER;
  87.   StringToAdd     : STRING [ 3];
  88.   WorkArray       : WrkArrType;
  89.   WorkArrayCtr    : LONGINT;
  90.   WorkPtr         : INTPTR;
  91.  
  92. {***************************************************************************}
  93. {***************************************************************************}
  94. {***************************************************************************}
  95. {***************************************************************************}
  96. {***************************************************************************}
  97. {                                                                           }
  98. {                 Some notes on how AIMDEXP works:                          }
  99. {                                                                           }
  100. { It figures out how big the aimfile needs to be based on how many records  }
  101. { are in the datafile (plus a little extra).  It then creates an empty      }
  102. { aimdex file (hex 00 or all bits off).                                     }
  103. {                                                                           }
  104. { It then gets a record from the datafile, calculates the hash value for    }
  105. { each of the triplets, then 'flicks on' the bits in the aimdex that        }
  106. { correspond to that datafile record and the various hash values.           }
  107. {                                                                           }
  108. { Now in order to speed things up, I did a couple of things.  First of all, }
  109. { it doesn't read the datafile records one at a time, but instead does a    }
  110. { block read (of about 32K bytes) and if the requested record is in that    }
  111. { block, it gets it from there.  If not, it reads in the next block.        }
  112. {                                                                           }
  113. { The other trick is that it doesn't actually do the aimfile writes one     }
  114. { triplet at a time.  Instead, with each triplet that gets processed, it    }
  115. { adds an element to a work array that consists of the hash value and the   }
  116. { datafile record number.  That array is defined as LONGINT where the first }
  117. { four digits are the hash value, and the last six digits are the record    }
  118. { number.  When the array gets full, it is sorted (using a recursive        }
  119. { quicksort algorithm) which leaves it looking something like:              }
  120. {                                                                           }
  121. {                                                                           }
  122. {                         hash     rec                                      }
  123. {                        value    number                                    }
  124. {                        -----    ------                                    }
  125. {                            1       47                                     }
  126. {                            2       11                                     }
  127. {                            3       11                                     }
  128. {                            3       42                                     }
  129. {                            7      116                                     }
  130. {                            7      118                                     }
  131. {                            7      148                                     }
  132. {                            8       55                                     }
  133. {                            .        .                                     }
  134. {                            .        .                                     }
  135. {                         1009       32                                     }
  136. {                                                                           }
  137. { Now we go through the work array to 'flick on' the appropriate bits in    }
  138. { the aimdex file. We're writing one aimdex record per hash value, so we do }
  139. { 'control break' logic to read the aimdex record for that hash value,      }
  140. { 'flick on' all the appropriate bits, then write the record to the aimdex  }
  141. { file.  It's complicated by the fact that 2 particular elements that have  }
  142. { the same hash value, may be in 2 different blocks in the aimdex file and  }
  143. { thus we need a 'control break' for when the block number changes too (in  }
  144. { the above example, recnums 118 & 148 may fall into different blocks).     }
  145. {                                                                           }
  146. {***************************************************************************}
  147. {***************************************************************************}
  148. {***************************************************************************}
  149. {***************************************************************************}
  150. {***************************************************************************}
  151.  
  152. PROCEDURE DispHelpScreen;
  153.  
  154. BEGIN
  155.  
  156.    Disp_String ('Basic syntax is:', 1, 8, Normal_Video);
  157.  
  158.    Disp_String ('AIMDEXP  <infile>  <outfile>  <keyspec>  [-F=nnn]',
  159.             10, 10, Normal_Video);
  160.  
  161.    Disp_String ('An example might be:', 1, 14, Normal_Video);
  162.  
  163.    Disp_String ('AIMDEXP  CUSTFILE.TXT  CUSTFILE.AIM  8-28,39-45,52-57',
  164.             10, 16, Normal_Video);
  165.  
  166. END;
  167.  
  168. {***************************************************************************}
  169.  
  170. PROCEDURE DispSomeStuff;
  171.  
  172. BEGIN
  173.   Disp_String ('Data file  : ' + Aim.DataFileName, 1,  3, Normal_Video);
  174.   Disp_String ('Aim  file  : ' + Aim.AimFileName,  1,  4, Normal_Video);
  175.   Disp_String ('Key fields : ' + ParamKeys,    1,  6, Normal_Video);
  176. END;
  177.  
  178. {***************************************************************************}
  179.  
  180. PROCEDURE ProcessParamKeys;
  181.  
  182. {---------------------------------------------------------------------------}
  183. { Process the parameter with the aimdex keys.  It typically looks something }
  184. { like '11-15,22-39,43-46'.  We want to break that up into the arrays       }
  185. { KeyBeg and KeyEnd.  KeyBeg [1] would be '11', KeyEnd [1] would be '15',   }
  186. { etc.                                                                      }
  187. {---------------------------------------------------------------------------}
  188.  
  189. VAR
  190.   I             : INTEGER;
  191.   TempStringBeg : STRING [ 4];
  192.   TempStringEnd : STRING [ 4];
  193.   TempString99  : STRING [99];
  194.  
  195. BEGIN
  196.  
  197.   ParamKeys := PARAMSTR (3);
  198.  
  199.   Aim.NumbValidKeys := 0;
  200.  
  201.   { -- Initialize the arrays to nothing defined.}
  202.   FOR I := 1 TO AimMaxKeys DO
  203.     BEGIN
  204.       Aim.KeyBegCol [I] := 0;
  205.       Aim.KeyEndCol [I] := 0;
  206.     END;
  207.  
  208.  
  209.  
  210.  
  211.   TempString99 := '';
  212.  
  213.   FOR I:= 1 TO LENGTH (ParamKeys) DO
  214.     BEGIN
  215.       IF COPY (ParamKeys, I, 1) = ','
  216.         THEN BEGIN
  217.                Aim.NumbValidKeys := Aim.NumbValidKeys + 1;
  218.                IF Aim.NumbValidKeys > AimMaxKeys
  219.                    THEN AimFatalError (150, '');
  220.  
  221.                DashPos := POS ('-', TempString99);
  222.                IF (DashPos > 0)
  223.                  THEN BEGIN
  224.                         TempStringBeg := COPY (TempString99, 1, DashPos - 1);
  225.                         VAL (TempStringBeg, Aim.KeyBegCol [Aim.NumbValidKeys],
  226.                                     ValError);
  227.                         IF ValError <> 0
  228.                            THEN AimFatalError (20, TempStringBeg);
  229.  
  230.                         TempStringEnd := COPY (TempString99, DashPos + 1,
  231.                                LENGTH (TempString99) - DashPos);
  232.                         VAL (TempStringEnd, Aim.KeyEndCol [Aim.NumbValidKeys],
  233.                                     ValError);
  234.                         IF ValError <> 0
  235.                            THEN AimFatalError (21, TempStringEnd);
  236.  
  237.                         TempString99 := '';
  238.                       END
  239.  
  240.                  ELSE BEGIN
  241.                         { -- Someday I may allow keys of 1 digit.}
  242.                         AimFatalError (22, TempString99);
  243.                       END;
  244.  
  245.              END
  246.  
  247.         ELSE BEGIN
  248.                TempString99 := TempString99 + COPY (ParamKeys, I, 1);
  249.              END;
  250.     END;
  251.  
  252.  
  253.  
  254.  
  255.   { -- Build a key from the string after the last comma.}
  256.   Aim.NumbValidKeys := Aim.NumbValidKeys + 1;
  257.   IF Aim.NumbValidKeys > AimMaxKeys
  258.      THEN AimFatalError (151, '');
  259.  
  260.   DashPos := POS ('-', TempString99);
  261.   IF (DashPos > 0)
  262.      THEN BEGIN
  263.              TempStringBeg := COPY (TempString99, 1, DashPos - 1);
  264.              VAL (TempStringBeg, Aim.KeyBegCol [Aim.NumbValidKeys], ValError);
  265.              IF ValError <> 0
  266.                 THEN AimFatalError (23, TempStringBeg);
  267.  
  268.              TempStringEnd := COPY (TempString99, DashPos + 1,
  269.                            LENGTH (TempString99) - DashPos);
  270.              VAL (TempStringEnd, Aim.KeyEndCol [Aim.NumbValidKeys], ValError);
  271.              IF ValError <> 0
  272.                 THEN AimFatalError (24, TempStringEnd);
  273.           END
  274.  
  275.      ELSE BEGIN
  276.              { -- Someday I may allow keys of 1 digit.}
  277.              AimFatalError (25, TempString99);
  278.           END;
  279.  
  280.  
  281.  
  282.   { -- A little more edit checking here}
  283.   FOR I := 1 TO Aim.NumbValidKeys DO
  284.     BEGIN
  285.       IF (Aim.KeyEndCol [I] - Aim.KeyBegCol [I] < 2)
  286.         THEN AimFatalError (26, '- they must be at least 3 chars');
  287.  
  288.       IF (Aim.KeyEndCol [I] - Aim.KeyBegCol [I] > 255)
  289.         THEN AimFatalError (27, '- key is too long');
  290.  
  291.       IF (Aim.KeyEndCol [I] > Aim.RecLenData-2)
  292.         THEN AimFatalError (28, '- key is bigger than datafile record');
  293.  
  294.       IF (Aim.KeyBegCol [I] < 1)
  295.         THEN AimFatalError (29, '- key is less than 1');
  296.     END;
  297.  
  298. END;
  299.  
  300. {***************************************************************************}
  301.  
  302. PROCEDURE OpenDataFile;
  303.  
  304. BEGIN
  305.  
  306.  ASSIGN (Aim.DataFile, Aim.DataFileName);
  307.  
  308.  {$I-}
  309.  RESET  (Aim.Datafile, 1);
  310.  {$I+}
  311.  
  312.  IF IORESULT <> 0
  313.     THEN BEGIN
  314.             AimFatalError (71, Aim.DataFileName);
  315.          END;
  316. END;
  317.  
  318. {***************************************************************************}
  319.  
  320. PROCEDURE OpenAimFile;
  321.  
  322. BEGIN
  323.  
  324.  ASSIGN (Aim.AimFile, Aim.AimFileName);
  325.  
  326.  {$I-}
  327.  RESET  (Aim.Aimfile, 1);
  328.  {$I+}
  329.  
  330.  IF IORESULT <> 0
  331.     THEN BEGIN
  332.             AimFatalError (51, Aim.AimFileName);
  333.          END;
  334. END;
  335.  
  336. {***************************************************************************}
  337.  
  338. PROCEDURE Read1stDataRec;
  339.  
  340. {---------------------------------------------------------------------------}
  341. { Get the record length of the data file by reading the first record.  Read }
  342. { in a block and search for a line feed character to denote the EOR.        }
  343. {---------------------------------------------------------------------------}
  344.  
  345. VAR
  346.   EndLoop  : STRING [ 1];
  347.   I        : INTEGER;
  348.  
  349. BEGIN
  350.  
  351.   SEEK (Aim.DataFile, 0);
  352.   BLOCKREAD (Aim.DataFile, ReadBuffer, RecLenDataMax, CharsRead);
  353.  
  354.   IF CharsRead <= 1
  355.     THEN Aim.RecLenData := 0
  356.  
  357.     ELSE BEGIN
  358.            I := 1;
  359.  
  360.            EndLoop := No;
  361.            WHILE (EndLoop = No) DO
  362.              BEGIN
  363.                IF I > RecLenDataMax
  364.                  THEN AimFatalError (90, '');
  365.  
  366.                IF ReadBuffer [I] = LineFeed
  367.                  THEN BEGIN
  368.                         Aim.RecLenData := I;
  369.                         EndLoop        := Yes;
  370.                       END
  371.                  ELSE BEGIN
  372.                         I := I + 1;
  373.                       END;
  374.              END;
  375.  
  376.          END;
  377.  
  378. END;
  379.  
  380. {***************************************************************************}
  381.  
  382. PROCEDURE GetRecLenData;
  383.  
  384. {---------------------------------------------------------------------------}
  385. { Process the parameter for the length of the datafile records ('-F=nnn').  }
  386. { If they didn't specify it, read the first record in the datafile and      }
  387. { assume it to be the correct record length.                                }
  388. {---------------------------------------------------------------------------}
  389.  
  390. VAR
  391.   TempString04 : STRING [ 4];
  392.  
  393. BEGIN
  394.   ParamLen := PARAMSTR (4);
  395.  
  396.   IF ParamLen = ''
  397.      THEN BEGIN
  398.             Read1stDataRec;
  399.             IF Aim.RecLenData < 1
  400.                THEN AimFatalError (120, '');
  401.           END
  402.  
  403.      ELSE BEGIN
  404.             { -- skip past '-F='}
  405.             TempString04 := COPY (ParamLen, 4, LENGTH (ParamLen) - 3);
  406.             VAL (TempString04, Aim.RecLenData, ValError);
  407.             IF ValError <> 0
  408.               THEN AimFatalError (100, TempString04);
  409.             Aim.RecLenData := Aim.RecLenData + 2;
  410.           END;
  411.  
  412. END;
  413.  
  414. {***************************************************************************}
  415.  
  416. PROCEDURE GetNumbDataRecs;
  417.  
  418. {---------------------------------------------------------------------------}
  419. { Calculate the number of records in the datafile, based on the file size   }
  420. { in bytes and the record length.                                           }
  421. {---------------------------------------------------------------------------}
  422.  
  423. BEGIN
  424.  
  425.   DataFileSize := FILESIZE (Aim.DataFile);   {Gives size in bytes.}
  426.   DataFileSize := DataFileSize - 1;          {Minus 1 for the EOF marker.}
  427.  
  428.   { -- Filesize must be an even multiple of the record length.}
  429.   IF (DataFileSize / Aim.RecLenData) <> INT (DataFileSize / Aim.RecLenData)
  430.     THEN BEGIN
  431.             AimFatalError (13, Aim.DataFileName);
  432.          END;
  433.  
  434.   NumbDataRecs := DataFileSize DIV Aim.RecLenData;
  435.  
  436.   { -- Max is around 1 million}
  437.   IF NumbDataRecs > 999990
  438.      THEN AimFatalError (140,'');
  439. END;
  440.  
  441. {***************************************************************************}
  442.  
  443. PROCEDURE ExchangeValues (X, Y : INTEGER);
  444.  
  445. {---------------------------------------------------------------------------}
  446. { Exchange two elements of the work array. This gets used when partitioning }
  447. { and sorting the array.                                                    }
  448. {---------------------------------------------------------------------------}
  449.  
  450. VAR
  451.   TempLongInt : LONGINT;
  452.  
  453. BEGIN
  454.  
  455.    TempLongInt    := WorkArray^ [X];
  456.    WorkArray^ [X] := WorkArray^ [Y];
  457.    WorkArray^ [Y] := TempLongInt;
  458.  
  459. END;
  460.  
  461. {***************************************************************************}
  462.  
  463. PROCEDURE Partition (VAR L  : LONGINT;
  464.                      VAR R  : LONGINT;
  465.                      VAR L1 : LONGINT;
  466.                      VAR R1 : LONGINT;
  467.                      VAR L2 : LONGINT;
  468.                      VAR R2 : LONGINT);
  469.  
  470. {---------------------------------------------------------------------------}
  471. { This routine is used by the quicksort.  It divides an array into 2        }
  472. { smaller 'halves'. It does it by evaluating the first element, and moving  }
  473. { all smaller items to the 'left' and all larger items to the 'right',      }
  474. { leaving that first element roughly in the middle.                         }
  475. {---------------------------------------------------------------------------}
  476.  
  477. VAR
  478.  I,J : LONGINT;
  479.  
  480. BEGIN
  481.  
  482.   IF (L >= R)
  483.     THEN BEGIN
  484.             L1 := L;
  485.             R1 := R;
  486.             L2 := L;
  487.             R2 := R;
  488.             EXIT;
  489.          END;
  490.  
  491.   I := L;
  492.   J := R;
  493.  
  494.   WHILE (1=1) DO
  495.     BEGIN
  496.  
  497.       WHILE (WorkArray^ [I] <= WorkArray^ [J]) AND (I < J) DO
  498.          BEGIN
  499.            J := J - 1;
  500.          END;
  501.  
  502.       IF (I < J)
  503.          THEN ExchangeValues (I, J)
  504.          ELSE BEGIN
  505.                 L1 := L;
  506.                 IF I > L
  507.                   THEN R1 := I-1
  508.                   ELSE R1 := L;
  509.                 L2 := I+1;
  510.                 R2 := R;
  511.                 EXIT;
  512.               END;
  513.  
  514.       WHILE (WorkArray^ [I] <= WorkArray^ [J]) AND (I < J) DO
  515.          BEGIN
  516.            I := I + 1;
  517.          END;
  518.  
  519.       IF (I < J)
  520.          THEN ExchangeValues (I, J)
  521.          ELSE BEGIN
  522.                 L1 := L;
  523.                 R1 := J-1;
  524.                 IF J < R
  525.                    THEN L2 := J+1
  526.                    ELSE L2 := J;
  527.                 R2 := R;
  528.                 EXIT;
  529.               END;
  530.  
  531.     END;
  532.  
  533. END;
  534.  
  535. {***************************************************************************}
  536.  
  537. PROCEDURE QSortWorkArray (VAR L : LONGINT;
  538.                           VAR R : LONGINT);
  539.  
  540. {---------------------------------------------------------------------------}
  541. { This routine sorts the work array with a recursive quicksort.  First      }
  542. { divides it into 2 roughly equal halves where one is made up of all items  }
  543. { less than some value, and the other half is all items greater than that   }
  544. { value.  It then performs the sort on each half.                           }
  545. {---------------------------------------------------------------------------}
  546.  
  547. VAR
  548.   L1, R1, L2, R2 : LONGINT;
  549.  
  550. BEGIN
  551.  
  552.   Partition (L, R, L1, R1, L2, R2);
  553.  
  554.   IF L1 < R1
  555.     THEN QSortWorkArray (L1, R1);
  556.  
  557.   IF L2 < R2
  558.     THEN QSortWorkArray (L2, R2);
  559.  
  560. END;
  561.  
  562. {***************************************************************************}
  563.  
  564. PROCEDURE SortWorkArray;
  565.  
  566. {---------------------------------------------------------------------------}
  567. { Sort the work array (by hash value).                                      }
  568. {---------------------------------------------------------------------------}
  569.  
  570. VAR
  571.   I,J           : LONGINT;
  572.   TempLongInt   : LONGINT;
  573.  
  574. BEGIN
  575.  
  576.    Disp_Number (WorkArrayCtr, 33, 12, Normal_Video, 7, 0);
  577.    Disp_String ('Sorting work array...', 50, 12, Normal_Video);
  578.  
  579.    QSortWorkArray (One, WorkArrayCtr);
  580.  
  581.    GOTOXY (50, 12);      { -- Clear the 'Sorting' message from screen.}
  582.    CLREOL;
  583.  
  584. END;
  585.  
  586. {***************************************************************************}
  587.  
  588. PROCEDURE FlushWorkArray;
  589.  
  590. {---------------------------------------------------------------------------}
  591. { Use the values in the work array to turn on the appropriate bits in the   }
  592. { aimdex file.                                                              }
  593. {---------------------------------------------------------------------------}
  594.  
  595. VAR
  596.   I           : LONGINT;
  597.   TempLongInt : LONGINT;
  598.  
  599. BEGIN
  600.  
  601.   { -- Empty array, nothing to do.}
  602.   IF WorkArrayCtr <= 0
  603.     THEN EXIT;
  604.  
  605.   { -- Figure out which block the 'prime read' is on.}
  606.   TempLongInt := WorkArray^[1] MOD 1000000;
  607.   OldBlockOn := ((TempLongInt-1) DIV (AimBlockSize * 8)) + 1;
  608.   OldByteOn  := (((TempLongInt-1) MOD (AimBlockSize * 8)) DIV 8) + 1;
  609.  
  610.   { -- Get the hash value for that first item.  Save for control break.}
  611.   TempLongInt := WorkArray^[1] DIV 1000000;
  612.   OldHash := TempLongInt;
  613.  
  614.   { -- Prime read - for control break.}
  615.   SeekPos := (OldBlockOn-1) * 1010 * AimBlockSize +
  616.              (OldHash * AimBlockSize);
  617.   SEEK (Aim.AimFile, SeekPos);
  618.   BLOCKREAD  (Aim.AimFile, AimBuffer, AimBlockSize, CharsRead);
  619.  
  620.  
  621.  
  622.  
  623.   { -- Process each item in the work array.}
  624.   FOR I:= 1 TO WorkArrayCtr DO
  625.     BEGIN
  626.       IF I MOD 100 = 0
  627.          THEN Disp_Number (I, 33, 13, Normal_Video, 7, 0);
  628.  
  629.       { -- Calculate which block & byte we are on.}
  630.       TempLongInt := WorkArray^[I] MOD 1000000;
  631.       Aim.BlockOn     := ((TempLongInt-1) DIV (AimBlockSize * 8)) + 1;
  632.       Aim.ByteOn      := (((TempLongInt-1) MOD (AimBlockSize * 8)) DIV 8) + 1;
  633.  
  634.  
  635.       { -- If the hash value or the block number have changed, then write the }
  636.       { -- aimdex record.                                                     }
  637.       IF ((WorkArray^[I] DIV 1000000) <> OldHash) OR
  638.          (Aim.BlockOn <> OldBlockOn)
  639.          THEN BEGIN
  640.  
  641.                 SeekPos := (OldBlockOn-1) * 1010 * AimBlockSize +
  642.                            (OldHash * AimBlockSize);
  643.                 SEEK (Aim.AimFile, SeekPos);
  644.                 BLOCKWRITE (Aim.AimFile, AimBuffer, AimBlockSize, CharsWrit);
  645.  
  646.                 OldHash := WorkArray^[I] DIV 1000000;  {save for control break}
  647.                 OldBlockOn := Aim.BlockOn;
  648.  
  649.                 SeekPos := (Aim.BlockOn-1) * 1010 * AimBlockSize  +
  650.                            ((WorkArray^[I] DIV 1000000) * AimBlockSize);
  651.                 SEEK (Aim.AimFile, SeekPos);
  652.                 BLOCKREAD  (Aim.AimFile, AimBuffer, AimBlockSize, CharsRead);
  653.  
  654.               END;
  655.  
  656.  
  657.       { -- 'Flick on' the appropriate bit in the aimdex Buffer.}
  658.       NewByte := AimBuffer [Aim.ByteOn];
  659.  
  660.       WhichBit := ((WorkArray^ [I] MOD 1000000)-1) MOD 8 + 1;
  661.       CASE WhichBit OF
  662.          1 : NewByte := NewByte OR $80;
  663.          2 : NewByte := NewByte OR $40;
  664.          3 : NewByte := NewByte OR $20;
  665.          4 : NewByte := NewByte OR $10;
  666.          5 : NewByte := NewByte OR $08;
  667.          6 : NewByte := NewByte OR $04;
  668.          7 : NewByte := NewByte OR $02;
  669.          8 : NewByte := NewByte OR $01;
  670.       END;
  671.  
  672.       AimBuffer [Aim.ByteOn] := NewByte;
  673.  
  674.     END;
  675.  
  676.  
  677.  
  678.  
  679.   { -- Do last control break}
  680.   SeekPos := (OldBlockOn-1) * 1010 * AimBlockSize +
  681.              (OldHash * AimBlockSize);
  682.   SEEK (Aim.AimFile, SeekPos);
  683.   BLOCKWRITE (Aim.AimFile, AimBuffer, AimBlockSize, CharsWrit);
  684.  
  685.  
  686.   { -- Reset the work array back to zero, to begin all over again.}
  687.   WorkArrayCtr := 0;
  688.  
  689.   Disp_Number (0, 30, 12, Normal_Video, 10, 0);
  690.   Disp_Number (0, 30, 13, Normal_Video, 10, 0);
  691. END;
  692.  
  693. {***************************************************************************}
  694.  
  695. PROCEDURE AddToAimdex;
  696.  
  697. {---------------------------------------------------------------------------}
  698. { Add a hash numb/record numb to the work array.  If the work array is      }
  699. { full, sort it, then 'flush it out' (ie, use it to write to the aimdex     }
  700. { file.)                                                                    }
  701. {---------------------------------------------------------------------------}
  702.  
  703. BEGIN
  704.  
  705.   AimGetHashNumb (StringToAdd, HashNumb);
  706.  
  707.   IF WorkArrayCtr >= WorkArrayMax
  708.     THEN BEGIN
  709.            SortWorkArray;
  710.            FlushWorkArray;
  711.          END;
  712.  
  713.   WorkArrayCtr := WorkArrayCtr + 1;
  714.   WorkArray^ [WorkArrayCtr] := HashNumb * 1000000 + RecToProcess;
  715.  
  716. END;
  717.  
  718. {***************************************************************************}
  719.  
  720. PROCEDURE ProcessInRecord;
  721.  
  722. {---------------------------------------------------------------------------}
  723. { Process the next input record.                                            }
  724. {---------------------------------------------------------------------------}
  725.  
  726. VAR
  727.   I,J        : INTEGER;
  728.  
  729. BEGIN
  730.  
  731.   { -- If you want to ignore deleted records by way of an "I'm a deleted }
  732.   { -- record" byte, put that logic here, and don't bother processing    }
  733.   { -- the datafile record.                                              }
  734.  
  735.   RecToProcess := RecToProcess + 1;
  736.  
  737.   { -- Do this loop once for each key field.}
  738.   FOR I:= 1 TO Aim.NumbValidKeys DO
  739.     BEGIN
  740.  
  741.        { -- Do this loop once for each triplet.}
  742.        FOR J:= Aim.KeyBegCol [I] TO (Aim.KeyEndCol [I]-2) DO
  743.           BEGIN
  744.             StringToAdd := DataBuffer [J];
  745.             IF (J+1 <= Aim.KeyEndCol [I])
  746.               THEN StringToAdd := StringToAdd + DataBuffer [J + 1]
  747.               ELSE StringToAdd := StringToAdd + Bla;
  748.             IF (J+2 <= Aim.KeyEndCol [I])
  749.               THEN StringToAdd := StringToAdd + DataBuffer [J + 2]
  750.               ELSE StringToAdd := StringToAdd + Bla;
  751.  
  752.             { -- Ignore any all blank triplets.  If you want to ignore      }
  753.             { -- records that had been overwritten with a delete character, }
  754.             { -- you should check for that here.                            }
  755.             IF StringToAdd <> '   '
  756.               THEN BEGIN
  757.                      Convert_Upper (StringToAdd);
  758.                      AddToAimdex;
  759.                    END;
  760.           END;
  761.  
  762.     END;
  763.  
  764. END;
  765.  
  766. {***************************************************************************}
  767.  
  768. PROCEDURE GetNextInRec;
  769.  
  770. {---------------------------------------------------------------------------}
  771. { This routine gets the next record and puts it into DataBuffer.  It buffers}
  772. { the read by doing BLOCKREADs into ReadBuffer.  If the next record is in   }
  773. { ReadBuffer, just load DataBuffer from there.  If it isn't, do another     }
  774. { BLOCKREAD of the datafile.                                                }
  775. {---------------------------------------------------------------------------}
  776.  
  777. VAR
  778.   I           : INTEGER;
  779.   DispPctDone : REAL;
  780.  
  781. BEGIN
  782.   { -- Is the desired record past the last record in the read buffer?}
  783.   IF InRecnumToGet > InRecnumBuffEnd
  784.     THEN BEGIN
  785.            { -- Read the next buffer full.}
  786.            SEEK (Aim.DataFile, SeekPosData);
  787.            BLOCKREAD (Aim.DataFile, ReadBuffer, ReadBufferSize, CharsRead);
  788.            IF CharsRead = 0
  789.              THEN AimFatalError (14, '');
  790.  
  791.            { -- Number of bytes read should be an even multiple of record }
  792.            { -- length.  Subtract one for EOF marker if the entire buffer }
  793.            { -- didn't get filled.                                        }
  794.            IF (CharsRead < ReadBufferSize) AND
  795.               (((CharsRead-1) / Aim.RecLenData) <> INT ((CharsRead-1) /
  796.               Aim.RecLenData))
  797.              THEN AimFatalError (15, Aim.DataFileName);
  798.  
  799.            IF CharsRead = ReadBufferSize
  800.              THEN NumRecsInBuff := ReadBufferSize DIV Aim.RecLenData
  801.              ELSE NumRecsInBuff := ((CharsRead-1) DIV Aim.RecLenData) + 1;
  802.  
  803.            InRecnumBuffEnd := InRecnumBuffEnd + NumRecsInBuff;
  804.  
  805.            { -- The count included EOF, so subtract one when displaying.}
  806.            Disp_Number (InRecnumBuffEnd-1, 31, 10, Normal_Video, 9, 0);
  807.  
  808.            IF NumbDataRecs > 0
  809.              THEN BEGIN
  810.                     DispPctDone := ((InRecnumBuffEnd-1) / NumbDataRecs) * 100 ;
  811.                     Disp_Number (DispPctDone,  43, 10, Normal_Video, 3, 0);
  812.                   END;
  813.  
  814.            { -- Position the file pointer for the next BLOCKREAD.}
  815.            SeekPosData := SeekPosData + CharsRead;
  816.            NextBuffRec := 1;     {Pointer to record in ReadBuffer.}
  817.          END;
  818.  
  819.  
  820.  
  821.   { -- If pointing to EOF, then we're finished.}
  822.   IF ReadBuffer [NextBuffRec * Aim.RecLenData - Aim.RecLenData + 1] = EOF_Mark
  823.     THEN BEGIN
  824.             EOFReached := Yes;
  825.          END
  826.  
  827.     ELSE BEGIN
  828.            { -- Make sure it has EOR markers}
  829.            IF ReadBuffer [NextBuffRec * Aim.RecLenData] <> LineFeed
  830.               THEN AimFatalError (16, Aim.DataFileName);
  831.            IF ReadBuffer [NextBuffRec * Aim.RecLenData - 1] <> CarrRet
  832.               THEN AimFatalError (17, Aim.DataFileName);
  833.  
  834.            { -- Transfer from ReadBuffer to DataBuffer.}
  835.            FOR I:= 1 TO Aim.RecLenData DO
  836.              BEGIN
  837.                DataBuffer [I] := ReadBuffer [NextBuffRec * Aim.RecLenData
  838.                                     - Aim.RecLenData + I];
  839.              END;
  840.  
  841.            NextBuffRec := NextBuffRec + 1;
  842.  
  843.          END;
  844.  
  845. END;
  846.  
  847. {***************************************************************************}
  848.  
  849. PROCEDURE CreateEmptyAimFile;
  850.  
  851. {---------------------------------------------------------------------------}
  852. { This creates a completely empty aimdex file (all null characters) that is }
  853. { the correct size.                                                         }
  854. {---------------------------------------------------------------------------}
  855.  
  856. VAR
  857.   I             : INTEGER;
  858.   EndWritLoop   : STRING [ 1];
  859.   WhoCares      : INTEGER;
  860.  
  861. BEGIN
  862.  
  863.  ASSIGN (Aim.AimFile, Aim.AimFileName);
  864.  
  865.  { -- This extra erase is because of the Novell 'PURGE' feature.}
  866.  {$I-}
  867.  ERASE (Aim.AimFile);
  868.  {$I+}
  869.  WhoCares := IORESULT;
  870.  
  871.  
  872.  {$I-}
  873.  REWRITE (Aim.AimFile, 1);
  874.  {$I+}
  875.  IF IORESULT <> 0
  876.     THEN BEGIN
  877.             AimFatalError (110, Aim.AimFileName);
  878.          END;
  879.  
  880.  
  881.  
  882.  
  883.  
  884.  
  885.  
  886.  FOR I := 1 TO ReadBufferMaxSize DO
  887.    BEGIN
  888.      ReadBuffer [I] := CHR (00);   {Null character}
  889.    END;
  890.  
  891.  { -- Calculate how many bytes the aimdex file needs to be.}
  892.  BytesPerHash  := (NumbDataRecs DIV 8) + 126;      {126 = 1008 empty slots}
  893.  Aim.BlocksPerHash := (BytesPerHash DIV AimBlockSize) + 1;
  894.  SizeAimFile := Aim.BlocksPerHash * 1010 * AimBlockSize;
  895.  
  896.  Disp_String ('Size of file to build...', 1,  9, Normal_Video);
  897.  Disp_String ('Bytes written so far...',  1, 10, Normal_Video);
  898.  Disp_Number (SizeAimFile, 26,  9, Normal_Video, 11, 0);
  899.  
  900.  
  901.  { -- Loop to write a big block of bytes, until we've written the full file.}
  902.  BytesWritten := 0;
  903.  EndWritLoop := No;
  904.  WHILE (EndWritLoop = No) DO
  905.    BEGIN
  906.      IF (BytesWritten + ReadBufferMaxSize) <= SizeAimFile
  907.         THEN BytesToWrite := ReadBufferMaxSize
  908.         ELSE BytesToWrite := SizeAimFile - BytesWritten;
  909.  
  910.      BLOCKWRITE (Aim.AimFile, ReadBuffer, BytesToWrite, CharsWrit);
  911.      IF CharsWrit <> BytesToWrite
  912.        THEN BEGIN
  913.                AimFatalError (41, Aim.AimFileName);
  914.             END;
  915.  
  916.      BytesWritten := BytesWritten + BytesToWrite;
  917.      IF BytesWritten >= SizeAimFile
  918.         THEN EndWritLoop := Yes;
  919.  
  920.      Disp_Number (BytesWritten, 26, 10, Normal_Video, 11, 0);
  921.  
  922.    END;
  923.  
  924.  
  925.  
  926.  AimWritHeaderRec (Aim);
  927.  
  928.  CLOSE (Aim.AimFile);
  929.  GOTOXY (1,  9);
  930.  CLREOL;
  931.  GOTOXY (1, 10);
  932.  CLREOL;
  933.  
  934. END;
  935.  
  936. {***************************************************************************}
  937.  
  938. PROCEDURE DoItAll;
  939.  
  940. {---------------------------------------------------------------------------}
  941. { This is the main loop that builds the whole aimdex file.                  }
  942. {---------------------------------------------------------------------------}
  943.  
  944. BEGIN
  945.  
  946.   Disp_String ('Total data records in file :', 1, 9, Normal_Video);
  947.   Disp_Number (NumbDataRecs,  31, 9, Normal_Video, 9, 0);
  948.  
  949.   Disp_String ('Data records read in so far:                 %', 1, 10,
  950.                         Normal_Video);
  951.  
  952.   Disp_String ('Triplets in work array      :', 1, 12, Normal_Video);
  953.   Disp_String ('Writing work array to aimdex:', 1, 13, Normal_Video);
  954.  
  955.  
  956.   ReadBufferSize := (ReadBufferMaxSize DIV Aim.RecLenData) * Aim.RecLenData;
  957.  
  958.   MARK (WorkPtr);     {Dynamically allocate memory for work array}
  959.   NEW (WorkArray);
  960.  
  961.  
  962.   WorkArrayCtr    := 0;
  963.   RecToProcess    := 0;
  964.   SeekPosData     := 0;
  965.   InRecnumBuffEnd := 0;
  966.   InRecnumToGet   := 1;
  967.   EOFReached      := No;
  968.  
  969.   GetNextInRec;        {Prime read}
  970.  
  971.   WHILE (EOFReached = No) DO
  972.     BEGIN
  973.         ProcessInRecord;
  974.         InRecnumToGet := InRecnumToGet + 1;
  975.  
  976.         GetNextInRec;   {eventually returns EOFReached = Yes}
  977.     END;
  978.  
  979.   SortWorkArray;       {Do the last 'Control Break'}
  980.   FlushWorkArray;
  981.  
  982.   RELEASE (WorkPtr);
  983.  
  984. END;
  985.  
  986. {***************************************************************************}
  987. {***************************************************************************}
  988.  
  989. BEGIN    {Main Procedure AIMDEXP}
  990.  
  991.  Ins_Mode := Yes;      {Initialize for Keyin_String routine}
  992.  One      := 1;
  993.  Cursor_Off;           {I don't like cursors flying all over the screen}
  994.  
  995.  CLRSCR;
  996.  
  997.  Disp_String ('Progam: AIMDEXP',  1,  1, Normal_Video);
  998.  Disp_String ('Version: ' + Version,  60,  1, Normal_Video);
  999.  
  1000.  
  1001.  IF PARAMCOUNT < 3
  1002.    THEN BEGIN
  1003.           DispHelpScreen;
  1004.           AimHaltProgram (1);  {Return DOS errorlevel of 1.}
  1005.         END;
  1006.  
  1007.  
  1008.  IF (HeaderBuffSize > AimBlockSize)
  1009.     THEN BEGIN
  1010.             AimFatalError (131, '');
  1011.          END;
  1012.  
  1013.  
  1014.  Aim.DataFileName := PARAMSTR (1);
  1015.  Aim.AimFileName  := PARAMSTR (2);
  1016.  Convert_Upper (Aim.DataFileName);     {upper case fetishist}
  1017.  Convert_Upper (Aim.AimFileName);
  1018.  
  1019.  OpenDataFile;
  1020.  
  1021.  GetRecLenData;
  1022.  
  1023.  GetNumbDataRecs;      {Find out how many records are in the datafile.}
  1024.  
  1025.  ProcessParamKeys;     {Process the aimdex keys parameter, eg '5-12,20-25'}
  1026.  
  1027.  DispSomeStuff;
  1028.  
  1029.  CreateEmptyAimFile;
  1030.  
  1031.  OpenAimFile;
  1032.  
  1033.  DoItAll;              {Fill in the empty aimdex file}
  1034.  
  1035.  CLOSE (Aim.DataFile);
  1036.  CLOSE (Aim.AimFile);
  1037.  
  1038.  Disp_String ('Aimdexing completed successfully.', 1, 22, Normal_Video);
  1039.  
  1040.  AimHaltProgram (0);      {Return DOS errorlevel of 0.}
  1041.  
  1042. END.
  1043.  
  1044. {***************************************************************************}
  1045. {***                         End of AIMDEXP.PAS                          ***}
  1046. {***************************************************************************}
  1047.