{$C-,R-,V-}
{          BREAK DOWN  -- a text analysis and generation program
                  copyright 1985 by Neil J. Rubenking
      based on the program TRAVESTY, from the Nov. 1984 BYTE magazine

  NOTE that the "KEY" that indexes the DATA files is not included in the
  DATA files.  This saves about 20% on the DATA file size, and that 20%
  can be important.  It also means that you cannot restore a "corrupted"
  INDEX file, but that's not likely to be a problem.  Also note that the
  KEY values in the INDEX file always take MaxKeyLen+1 bytes, even if the
  "order" is smaller.  If you want to try orders greater than 8, change
  the value of MaxKeyLen and recompile.
}

program BreakDown;
const
  outCharNum     = 34;   { If you change the number of characters tracked,
                           you will have to change this constant.         }
  MaxKeyLen      = 7;    { MaxKeyLen is one less that the maximum order. }
  lineWidth      = 55;   { lines less than this length will be considered
                           to have ended "early", with a hard <CR> }

{TURBO-Access constants}
const

  MaxDataRecSize = OutCharNum;
  PageSize       = 48;     { You can experiment with these  }
  Order          = 24;     { constants, which are described }
  PageStackSize  = 16;     { in not-quite-enough detail in  }
  MaxHeight      = 8;      { the TURBO TOOLBOX manual       }

{$I access.box}
{$I getkey.box}
{$I AddKey.box}
{$I DelKey.box}


type
  char_set      = set of char;
  choices       = array[1..outCharNum] of byte;
  line          = string[90];
  chunkString   = string[MaxKeyLen];
  filename_type = string[14];

var
  Breakout, worked                                        : boolean;
  ordr, N, co                                             : byte;
  chars_to_output, KeyNum, Totl_to_out,  counter, AllRecs : integer;
  ShowRecs                                                : real;
  Ch, OutDrive, InxDrive, DatDrive                        : char;
  outChars                                                : string[40];
  source, outFile, BSource                                : text;
  sourceName, DatName, OutName, InxName, OldName,
  BSourceName, BDatName, BInxName                         : filename_type;
  OkayChars, PuncChars, NumbChars                         : char_set;
  sourceLine                                              : line;
  NoChance, AR, BR                                        : choices;
  lookChunk                                               : chunkString;
  DatF, BDatF                              : datafile;     {TOOLBOX types}
  IndexF, BIndexF                          : IndexFile;    {TOOLBOX types}

{@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}
procedure BreakMessage; external 'BREK2.TXT';
{@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}
procedure PlayMessage(offset : integer);
var N : integer;
begin
  N := 0;
  repeat
    write(chr(MEM[CSeg:Offset + N]));
    N := N + 1;
  until MEM[CSeg:N+Offset] = $1A;
end;
{@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}
function rep(CH : char ; BY : byte):line;
var
  temp : line;            { "rep" produces a string of BY repetitions of }
  N    : byte;            { the character CH.                            }
begin
  temp := '';
  for N := 1 to BY do
    temp := temp + CH;
  rep := temp;
end;
{@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}
procedure RevVideo;
begin
  textColor(black);
  textBackGround(white);
end;
{@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}
function LowCase(CC : char):char;
begin
  if CC in ['A'..'Z'] then LowCase := chr(ord(CC)+32)
    else LowCase := CC;
end;
{@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}
procedure DoHeader(act1, AFile, act2, BFile : filename_type);
begin
  ClrScr;                                { This produces a header that tells}
  RevVideo;                              { what BREAK DOWN is doing, with a }
  Write(#218,rep(#196,78),#191,#179);    { reverse-video box around it.     }
  HighVideo;
  write('  BREAK DOWN is now ',act1,' ',AFile,act2,BFile);
  write(rep(' ',49-length(AFile)-length(act1)-length(act2)-length(BFile)));
  write('ORDER ',ordr:2);
  RevVideo;
  write(#179,#212,rep(#205,78),#190);
  HighVideo;
end;
{@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}
function exists(ThisFile : filename_type):boolean;
var
  tempFile : text;  {We can get away with assigning a text file to ANY
                     filename because we aren't going to do any input/output}
begin
  assign(tempFile,ThisFile);
  {$I-}                                   { Here we set I/O error checking   }
  reset(tempFile);                        { OFF and do a RESET.  If the file }
  {$I+}                                   { exists, there's no error, and    }
  if IOResult = 0 then exists := true     { IOResult = 0.  If not, IOResult  }
    else exists := false;                 { holds the error number.          }
  close(tempFile);
end;
{@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}
procedure Process(VAR FromName, ToName : filename_type;
                                 drive : char;
                                   ext : chunkString);
begin
  if ordr < 10 then               { If the order is 9 or less, put that  }
    ext[3] := chr(48+ordr)        { digit in the middle of the extension.}
  else ext[3] := chr(55+ordr);    { For 10 and up, use A, B, C, &c.      }
  ToName := FromName;
  if pos('.',ToName) <> 0 then             { IF an extension is included,  }
    delete(ToName,pos('.',ToName),4);      { delete it.  Then add the new  }
  ToName := ToName + ext;                  { extension.                    }

  if UpCase(drive) in ['A'..'Z'] then  {IF the drive character is valid, then}
    if pos(':',ToName) <> 0 then           { if a drive has been specified,}
      ToName[1] := drive                   { just change the first char -- }
    else ToName := drive + ':' + ToName;   { else add drive and ':'        }
end;
{@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}
procedure initialize(mode : char);
                    { modes are M for Make a new file,
                                O for Open an existing file,
                                G for (Open a file and) Generate,
                                B for Open another existing file  }

  { The procedures SetUp and SetUpB exist solely for the purpose of
    breaking up the action into graspable chunks.                  }
  {==========================================================================}
  procedure SetUp;
  begin
    if (exists(sourceName)) or (mode = 'O') then
      begin
        process(sourceName, DatName, DatDrive, '.DAT');
        case mode of
          'M': MakeFile( DatF, DatName, OutCharNum);
          'O': OpenFile( DatF, DatName, OutCharNum);
        end;
        if OK then
          begin
            process(sourceName, InxName, InxDrive, '.INX');
            case mode of
              'M': MakeIndex(IndexF,InxName,MaxKeyLen,0);
              'O': OpenIndex(IndexF,InxName,MaxKeyLen,0);
            end;
            if not OK then
            case mode of
              'M': writeLn('Cannot create index file');
              'O': WriteLn('Index file does not exist');
            end;
          end
            else
              case mode of
                'M': writeLn('Cannot create data file');
                'O': WriteLn('Data file does not exist');
              end;
            worked := OK;
      end { if exists }
    else
      begin
        WriteLn('Source file does not exist.');
        worked := false;
      end;
  end;
  {==========================================================================}
  procedure SetUpB;
  begin
    process(BsourceName, BDatName, DatDrive, '.DAT');
    OpenFile( BDatF, BDatName, OutCharNum);
    if OK then
      begin
        process(BsourceName, BInxName, InxDrive, '.INX');
        OpenIndex(BIndexF,BInxName,MaxKeyLen,0);
        if not OK then
          WriteLn('Secondary Index file does not exist');
      end
    else
      WriteLn('Secondary Data file does not exist');
    worked := OK;
  end;
  {==========================================================================}

begin
  mode := upCase(mode);
  if mode = 'B' then WriteLn('Name of second source file: ')
    else WriteLn('  Name of main source file: ');
  WriteLn('       Drive for DATA file: ');
  WriteLn('      Drive for INDEX file: ');
  if mode = 'G' then
    WriteLn('          Drive for output: ')
  else WriteLn;
  DatDrive := ' '; InxDrive := ' '; outDrive := ' ';
  GotoXY(29,WhereY-4);
  if mode = 'B' then read(BsourceName)
    else
      begin
        Read(sourceName);
        if sourceName = '' then          { If you just hit <return> when }
          begin                          { prompted for a SourceName,    }
            if OldName <> '' then        { the default is whatever the   }
              begin                      { most recent previous name was.}
                sourceName := OldName;
                GotoXY(29,WhereY);
                write(sourceName);
              end;
          end
        else
          OldName := SourceName;
      end;
                                     { The data file for fff.xxx will be  }
  GotoXY(29,WhereY+1);               { called fff.DnT, where n is the     }
  read(DatDrive);                    { order of the BreakDown.  The index }
  GotoXY(29,WhereY+1);               { will be fff.InX, and any output    }
  read(InxDrive);                    { file will be fff.OnT               }
  if mode = 'G' then                 {    If the order is 10 or more, "n" }
    begin                            { will be a letter, starting with    }
      GotoXY(29,WhereY+1);           { A for 10.                          }
      read(outDrive);
    end;
  WriteLn;                                        { The source file only has}
  if mode = 'G' then mode := 'O';                 { to be present if we're  }
                                                  { [M]aking a new BreakDown}
  if mode = 'B' then SetUpB
    else SetUp;
end;
{@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}
procedure Merge;
var
  RC, matches       : real;
  BRecNum, ARecNum  : integer;
  {==========================================================================}
  procedure Combine(VAR AA,BB : choices);
  begin
    if CH = 'C' then
      begin
        for co := 1 to outCharNum do
          begin
            if AA[co] + BB[co] > 0 then
              if AA[co] + BB[co]*RC < 255.0 then
                AA[co] := AA[co] + (trunc(BB[co]*RC) and $FF)
              else AA[co] := $FF;
          end;
      end
    else
      begin
        for co := 1 to OutCharNum do
          begin
            if AA[co] + BB[co] > 0 then
              if AA[co] + BB[co] < $FF then
                AA[co] := AA[co] + BB[co]
              else AA[co] := $FF;
          end;
      end;
  end;
  {==========================================================================}
  procedure GetConstant;
  begin
    repeat
      GotoXY(1,WhereY); ClrEOl;
      Write('Multiply by what constant? (0.01 to 100)');
      read(RC);
    until (RC > 0.01) and (RC <= 100 );
  end;
  {==========================================================================}
  procedure DoMerge;
  var
    BOK : boolean;
  begin
    AllRecs := UsedRecs(BDatF);
    ShowRecs := AllRecs;
    if ShowRecs < 0 then ShowRecs := ShowRecs + 65536.0;
    if CH = 'C' then GetConstant
      else RC := 1.0;
    ClrScr;
    ClearKey(BIndexF);                   { NextKey after ClearKey gives us }
    NextKey(BIndexF,BRecNum,lookChunk);  { the very first key.             }
    BOK := OK;
    counter := 1;
    matches := 0;
    GetRec(BDatF,BRecNum,BR);            { We Get the Record corresponding }
    while BOK do                         { to that first key.              }
      begin
        if counter mod 10 = 0 then
          begin
            GotoXY(1,1);CLrEOL;
            write(counter:6,' out of ',ShowRecs:6:0);
          end;
        FindKey(IndexF,ARecNum,lookChunk);
        if OK then                            { If that same key is in the   }
          begin                               { index of the file into which }
            matches := matches + 1;           { we're merging, combine the   }
            GetRec(DatF,ARecNum,AR);          { frequency tables and write   }
            combine(AR,BR);                   { combined table back to disk. }
            PutRec(DatF,ARecNum,AR);          { . . .}
          end
        else
          begin
            AddRec(DatF,ARecNum,BR);          { Otherwise, Add the Record }
            AddKey(IndexF,ARecNum,LookChunk); { and its Key.              }
          end;
        NextKey(BIndexF,BRecNum,LookChunk);   { Get the next key, . . .}
        BOK := OK;
        GetRec(BDatF,BRecNum,BR);             { . . . and its record,  }
        counter := counter + 1;           { and increment the counter. }
      end;
    CloseFile(DatF);
    CloseFile(BDatF);
    CloseIndex(IndexF);
    CloseIndex(BIndexF);
  end;
  {==========================================================================}
begin
  GotoXY(1,1);
  DelLine;
  WriteLn('MERGING');
  initialize('O');
  if worked then
    initialize('B');
  if worked then
    begin
      ClrScr;
      DoHeader('merging',BSourceName,' into ',SourceName);
      window(1,4,80,25);
      ClrScr;
      WriteLn(SourceName,'''s DAT and INX files will be permanently changed.  You can');
      WriteLn('multiply the frequencies of ',BSourceName,' by a constant from 1/100 to');
      WriteLn('100, though a non-zero frequency will never be reduced to zero, nor will');
      WriteLn('it grow larger than 255.');
      WriteLn;
      WriteLn('[G]o ahead, set a multiplying [C]onstant, or [Q]uit?');
      repeat
        read(Kbd,CH);
      until UpCase(CH) in ['G','C','Q'];
      CH := UpCase(CH);
      if CH <> 'Q' then DoMerge;
    end;
  WriteLn;
  writeLn(matches:1:0,' records matched existing records in ',DatName);
  WriteLn('Press a key to return to main menu.');
  repeat until Keypressed; Read(Kbd);
  window(1,1,80,25);
end;
{@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}
procedure Analyze;
var
  NumOver           : integer;
  OldRecs, MadeRecs : real;
  {==========================================================================}
  procedure ReadSource;
  var
    HoldThatLine  : Line;
    linePos       : byte;
    NxCh          : char;
    {------------------------------------------------------------------}
    procedure CleanUp(VAR aLine : line);
    var
      shortLine : boolean;
    begin
      while pos(#9,aLine) <> 0 do              { Replace TABs with five   }
        begin                                  { spaces.  This is just for}
          insert('     ',aLine,pos(#9,aLine)); { measuring line length.   }
          delete(aLine,pos(#9,aLine),1);
        end;
      if length(aLine) < lineWidth then   { If the line is "short", then we }
        shortLine := true                 { suppose it to end with a HARD   }
      else ShortLine := false;            { Carriage Return (end paragraph).}
      for co := 1 to length(aLine) do
        begin
          if aLine[co] in OkayChars then     { Okay characters get converted}
            aLine[co] := LowCase(aLine[co])  { to lower case.               }
          else
            if aLine[co] = '"' then         { Double quotes turn into single}
              aLine[co] := #39
            else
              if aLine[co] in PuncChars then  {Punctuation that is "not Okay" }
                aLine[co] := ' '              {gets spaced out. It is treated }
                                              {separately because you might   }
                                              {want to convert all punctuation}
                                              {into, say, commas. }
              else
                if aLine[co] in NumbChars then  { Numbers turn into # symbols}
                  aLine[co] := '#'
                else aLine[co] := ' ';          { Anything else is spaced out.}
        end;
      while pos('  ',aLine) <> 0 do        { Eliminate multiple spaces }
        delete(aLine,pos('  ',aLine),1);
      while pos('##',aLine) <> 0 do        { Reduce numbers to a single "#"}
        delete(aLine,pos('##',aLine),1);
      while pos(' ,',aLine) <> 0 do        { Eliminate spaces AHEAD of commas}
        delete(aLine,pos(' ,',aLine),1);
      while pos(' .',aLine) <> 0 do        { . . . and periods }
        delete(aLine,pos(' .',aLine),1);
      aLine := ' ' + aLine;
      if (ShortLine) or (aLine = ' ') then    { Add a paragraph symbol to  }
        aLine := aLine + #20;                 { the end of any short lines.}
    end;
    {------------------------------------------------------------------}
    procedure FeedIn(aLine : line);
    begin
      repeat
        NxCh := aLine[linePos];                { Locate the NEXT character.  }
        FindKey(IndexF, KeyNum, LookChunk);  { See if the current "chunk"  }
                                               { is already on record.       }
        if OK then                             { If it is, call up its record}
          begin                                { and add one to the chances  }
            GetRec(DatF,KeyNum,AR);       { of it begin followed by NxCh}
                                                        { UNLESS the chances }
            if AR[pos(NxCh,outChars)] < $FF then   { for NxCh are at the}
              AR[pos(NxCh,outChars)] :=            { max of 255 already.}
               AR[pos(NxCh,outChars)] + 1
              else NumOver := NumOver + 1;
            PutRec(DatF,KeyNum,AR);
          end
        else
          begin
                                               {If the "chunk" was not on}
                                               { record yet, create it, }
            AR := NoChance;               { set all the chances to }
            AR[pos(NxCh,outChars)] := 1;  { zero, and set the NxCh }
                                               { chance to one.         }
            AddRec(DatF,KeyNum,AR);
            AddKey(IndexF,KeyNum,LookChunk);
          end;
        LookChunk := copy(LookChunk,2,ordr-2); {Now drop the first char}
        LookChunk := LookChunk + NxCh;        {of the chunk, add the NxCh}
        LinePos := LinePos + 1;               {to it, and advance the LinePos}

      until (LinePos > length(aLine)); { Do it until the whole line is in,}
      LinePos := 1;                    { then reset the LinePos.          }
    end;
    {------------------------------------------------------------------}
  begin
    NumOver := 0;
    reset(source);
    ReadLn(source,sourceLine);
    CleanUp(sourceLine);
    while length(sourceLine) < ordr  do          { To start, we must be sure }
      begin                                      { to have a line long enough}
        ReadLn(source,HoldThatLine);             { to extract a "chunk" from.}
        sourceLine := sourceLine + HoldThatLine;
        CleanUp(sourceLine);
      end;
    WriteLn(sourceLine);
    LookChunk := copy(sourceLine,1,ordr-1);   { Extract the first chunk, and}
    HoldThatLine  := LookChunk;               { save it to tack on the end. }
    linePos := ordr;
    NxCh  := sourceLine[LinePos];
    FeedIn(sourceLine);
    BreakOut := false;
    while (not EOF(source)) and (not breakout) do
      begin
        ReadLn(source,sourceLine);
        CleanUp(sourceLine);
        WriteLn(sourceLine);
        FeedIn(sourceLine);
        if keypressed then BreakOut := true;  { The BreakDown can take a long
                                                time -- if you press a key,
                                                the program shuts down grace-
                                                fully, without losing what it
                                                has done.  }
      end;
    FeedIn(HoldThatLine);
    WriteLn(HoldThatLine);
    WriteLn; WriteLn;
    Write('Successfully read in ',sourceName);
    MadeRecs := UsedRecs(DatF);
    if MadeRecs < 0 then MadeRecs := 65536. + MadeRecs;
    if upCase(CH) = 'N' then
      WriteLn('  Produced ',MadeRecs:1:0,' records.')
    else WriteLn('  Added ',(MadeRecs - OldRecs):1:0,' records.');
    if NumOver > 0 then
      WriteLn(NumOver,' entries have hit the max of 255.');
    CloseFile(DatF);
    CloseIndex(IndexF);
  end;
  {==========================================================================}
begin
  GotoXY(1,1);
  DelLine;
  WriteLn('¯¯ANALYZING®®');
  WriteLn;
  WriteLn('[N]ew source, or [A]dd to existing?');
  repeat
    read(Kbd,CH);
  until upCase(CH) in ['N','A'];
  case upCase(CH) of
    'N': begin
           initialize('M');
           assign(source,sourceName);
         end;
    'A': begin
           Write('Name of NEW source: ');
           ReadLn(sourceName);
           assign(source,sourceName);
           initialize('O');
           OldRecs := UsedRecs(DatF);
           if OldRecs < 0 then OldRecs := 65536. + OldRecs;
         end;
  end;
  if worked then
    begin
      DoHeader('analyzing',sourceName,'','');
      window(1,4,80,25);
      GotoXY(1,1);
      ReadSource;
    end;
  WriteLn('Press a key to return to main menu.');
  repeat until keypressed; Read(Kbd);
  window(1,1,80,25);
end;
{@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}
procedure Generate;
  {==========================================================================}
  procedure WriteTravesty;
  label
    PunkOut;
  var
    NxCh                    : char;
    aRecNum                 : integer;
    RealTot, rando          : real;
    NextCap, Done, Nearly   : boolean;
    {------------------------------------------------------------------}
    procedure CheckForCapsAndLineEnd;
    begin
      if NextCap then
        if NxCh in ['a'..'z'] then  { If we're waiting to capitalize, do }
          begin                     { it only to an alphabetic character.}
            NxCh := UpCase(NxCh);
            NextCap := false;
          end;
      if NxCh in ['.','?'] then    { Capitalize the next ALPHA character }
        NextCap := true;           { after a . or a ?                    }
      if NxCh = #20 then
        begin                        {   If you hit a paragraph marker,  }
          WriteLn(OutFile,SourceLine);  { end the line and print it out.    }
          writeLn(SourceLine);
          SourceLine := '';
          NextCap := true;    { Capitalize the first char of the new line.}
        end
      else
        begin
          SourceLine := SourceLine + NxCh;
          if (outChars[N] = ' ') and (length(SourceLine) > lineWidth) then
            begin
              WriteLn(OutFile,SourceLine); { End a line at the next space     }
              writeLn(SourceLine);         { after max line width is reached. }
              SourceLine := '';
            end;
        end;  { all about whether to end the line}
      if Nearly then                   { "Nearly" means that the max char }
        if NxCh = ' ' then             { count has been reached.  As soon }
          begin                        { as we hit a space, we're done.   }
            done := true;              { For good looks, we append a final}
            SourceLine := SourceLine + '.';  { period.                          }
          end;
      if keypressed then BreakOut := true;
    end;
    {------------------------------------------------------------------}

  begin
    Assign(outFile, outName);                    { We can't directly get the }
    ReWrite(outFile);                            { KEY for a given record #, }
    lookChunk := ' ' + chr(trunc(random(26))+97);{ so we use SearchKey, which}
    SearchKey(IndexF,aRecNum,lookChunk);         { returns the KEY and # of  }
    repeat                                       { first entry that's >= the }
      NextKey(IndexF,aRecNum,LookChunk);          { string supplied.  Then we }
    until LookChunk[1] = ' ';                     { NextKey 'til we find a    }
    SourceLine := LookChunk;                      { suitable one.             }
    SourceLine[2] := upCase(SourceLine[2]);     { Capitalize the first letter . . .}
    Totl_to_out := ordr-1;
    NextCap     := false;
    randomize;
    Breakout := false;
    Nearly   := false;
    Done     := false;
    while (not DONE) and (not BreakOut) do
      begin
        Totl_to_out := Totl_to_out + 1;
        if totl_to_out = chars_to_output then    { When the max is hit, set }
          Nearly := true;                        { "nearly" to true.  At the}
        RealTot := 0;                            { next space, you're DONE  }
        FindKey(IndexF,KeyNum,LookChunk);
        if OK then
          begin
            GetRec(DatF,KeyNum,AR);
            for N := 1 to outCharNum do    { Total up all the  }
              RealTot := RealTot + AR[N];  { "chances" figures }
          end
        else
          begin                        { This should never happen, but }
            WriteLn(SourceLine,'<<<'); { just in case . . .            }
            Write(chr(7));
            WriteLn('Didn''t find record of string >',LookChunk,'<');
            Goto punkOut;
          end;
          rando := random*RealTot;   { Select a random number less than total}
          N := 0;                    { and "count off" chances until you use }
          repeat                     { it up -- that's the next character.   }
              N := N + 1;
              RealTot := RealTot - AR[N];
          until (RealTot < rando) or (N > outCharNum);
        if N > length(outChars) then   { This should never happen! }
          begin
            writeLn(chr(7),chr(7),'Error in chances table for >',LookChunk,'<');
            Goto PunkOut;
          end;
        delete(LookChunk,1,1);         { Knock off the first character of the}
        NxCh := outChars[N];           { current chunk, and tack on the newly}
        LookChunk := LookChunk + NxCh; { chosen next character.              }
        CheckForCapsAndLineEnd;

      end;  { of the big WHILE }
    WriteLn(OutFile,SourceLine);      { Be sure to write the very last line! }
    writeLn(SourceLine);
    WriteLn; WriteLn;
    Write('total number of chars output ',Totl_to_out);
    WriteLn(' of requested ',chars_to_output);
    PunkOut:
    close(outFile);
    closeFile(datF);
    closeIndex(indexF);
  end;
  {==========================================================================}
begin
  GotoXY(1,1);
  DelLine;
  WriteLn('¯¯GENERATING®®');
  WriteLn;
  initialize('G');
  if worked then
    begin
      Write('How many characters to output?');
      read(chars_to_output);
      process(sourceName,outName, outDrive, '.OUT');
      DoHeader('generating',OutName,'','');
      window(1,4,80,25);
      GotoXY(1,1);
      WriteTravesty;
    end;
  WriteLn('Press a key to go back to menu.');
  repeat until keypressed; Read(Kbd);
  window(1,1,80,25);
end;
{@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}
procedure List;
  {==========================================================================}
  procedure DoList;
  label
    enough;
  var
    M        : integer;
    StrRecs  : filename_type;
  begin
    Write('View a particular record?');
    read(CH); WriteLn;
    if upCase(CH) = 'Y' then
      begin
        WriteLn('Which ',ordr-1,'-letter sequence?');
        lookChunk := '';
        for N := 1 to ordr-1 do
          begin
            repeat
              read(Kbd,CH);
            until pos(CH,outChars) <> 0;
            write(CH);
            lookChunk := lookChunk + CH;
          end;
        FindKey(IndexF,M,lookChunk);
        if not OK then
          begin
            Write(chr(7),'"',lookChunk,'" is not in this list.');
            ClearKey(IndexF);
            NextKey(IndexF,M,lookChunk);
          end;
      end
    else
      begin
        ClearKey(IndexF);
        NextKey(IndexF,M,LookChunk);
      end;
    AllRecs := UsedRecs(DatF);
    ShowRecs := AllRecs;
    if ShowRecs < 0 then ShowRecs := ShowRecs + 65536.0;
    str(ShowRecs:1:0,StrRecs);
    StrRecs := ':  ' + StrRecs;
    DoHeader('listing',DatName,StrRecs,' records.');
    textcolor(LightBlue);        { Blue = underlined on many mono monitors. }
    write(rep(' ',ordr+1));      { Here we write a heading line. }
    for N := 1 to outCharNum do
      write(outChars[N]:2);
    WriteLn;
    window(1,5,80,25);
    GotoXY(1,1);
    co := 0;
    while OK do
      begin
        co := co + 1;
        GetRec(DatF,M,AR);                   { Get each record and show }
                                             { the chunk it represents, }
        Write('|',LookChunk:(ordr-1),'|');   { along with its chances.  }
        for N := 1 to outCharNum do
          if AR[N] <> 0 then write(AR[N]:2)
            else write('  ');
        WriteLn;
        if co >= 20 then
          begin
            write('Press a key to see more--or [Q]uit');
            repeat until keypressed;
            read(Kbd,CH);
            if upCase(CH) = 'Q' then GoTo enough;
            ClrScr;
            co := 0;
          end;
        NextKey(IndexF,M,LookChunk); { Go thru the list in order by taking
                                       the Next Key again and again.      }
      end;  {while}
    Enough:
    CH := ' ';
    textColor(white);
  end;
  {==========================================================================}
begin
  GotoXY(1,1);
  DelLine;
  WriteLn('¯¯LISTING®®');
  WriteLn;
  initialize('O');
  if worked then DoList;
  Write('Press a key to return to main menu.');
  repeat until Keypressed; Read(Kbd);
  window(1,1,80,25);
end;
{@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}
begin
  PuncChars := ['!'..'&','('..'+',':'..'>','['..'`','{'..'~','@','/'];
  NumbChars := ['0'..'9'];
  OkayChars := ['a'..'z','-',#39,'A'..'Z','.',',','?'];
  Outchars  := 'abcdefghijklmnopqrstuvwxyz -.,?#' + #20 + #39;
  { NOTICE:  if you add a char to OutChars, change the constant OutCharNum }
  for N := 1 to OutCharNum do noChance[N] := 0;
  ClrScr;
  PlayMessage(ofs(BreakMessage));
  repeat until keypressed;
  Read(Kbd);
  oldName := '';
  ClrScr;
  repeat
    InitIndex;
    ClrScr;
    RevVideo;
    Write('[A]nalyze a text, [G]enerate a travesty, [L]ist, [M]erge,');
    WriteLn(' or [Q]uit?');
    HighVideo;
    repeat
      read(Kbd,CH);
    until upCase(Ch) in ['A','G','L','M','Q'];
    if UpCase(CH) <> 'Q' then
      begin
        repeat
          Write('What "order"? (3..',MaxKeyLen+1,') ');
          read(ordr);
        until ordr in [3..MaxKeyLen+1];   { if you just hit <return> here, the
                                            most recent "order" will be used.}
        DelLine;
      end;
    case upCase(ch) of
      'A': Analyze;
      'M': Merge;
      'G': Generate;
      'L': List;
    end;
  until upCase(ch) = 'Q';
end.

