{********************************************************
**
**  EDIT #5 - 12 July 1980
**
**  PROGRAM TITLE:      Concordance
**
**  WRITTEN BY:         Raymond E. Penley
**  DATE WRITTEN:       26 January 1980
**
**  WRITTEN FOR:        Personal pleasure
**                      Donated to Pascal/Z users Gp
**
**  PROGRAM SUMMARY:
**
**      Examine a piece of text and produce a list,
**      in alphabetical order, of all the distinct
**      words which appear in the text.
**
**  INPUT AND OUTPUT FILES:
**
**      INPUT FILE: DRIVE: BASIC FILE NAME . EXTENSION
**      OUT FILE:   DRIVE: BASIC FILE NAME . CCD
**
               *************                   }
PROGRAM CONCORDANCE;

label   9;{abort}
const
 alfa_len      = 16;    { length of words }
 c4            = MAXINT;{ max line number }
 Clearcode     = 26;    { clear screen    }
 default       = 255;
 dflt_str_len  = default;
 LLmax         = default;{ max line length }
 LLmin         = 72;   { Min line length }
 space         = ' ';
 StrMax        = 255;

type
 alfa          = STRING alfa_len;
 byte          = 0..255;
 charname = (lletter, uletter, digit, blank, quote, atab,
             EndOfLine, FileMark, otherchar );
 charinfo = RECORD
               name : charname;
               valu : char
            END;
 dfltstr       = STRING default;{ default length for all strings }
 ItemRecords  = record
                  item  :alfa;
                  Next  :^ItemRecords
                end;
 ItemPointers = ^ItemRecords;
 str0          = string 0;
 str255        = string StrMax;

var
 Look     : char;      { Character read in from File }
 cline    : integer;   { current line number }
 currchar,             { Current operative character }
 nextchar : CharInfo;  { Look-ahead character }
 CON_wanted,
 DEBUG,
 error_flag: BOOLEAN;
 Fbuffer  : dfltstr;   { Format buffer - before final Print }
 flushing : (KNOT, DBL, STD, LIT);
 ID       : alfa;      { Identifier storage }
 idlen    : byte;      { Identifier Length }
 ListHead  :ItemPointers;
 tab      : char;
 TextFile,             { Input file }
 Work_File: TEXT;      { Output file }
 wordcount: integer;   { total # of words in file }
 xeof,                 { EOF status AFTER a read }
 xeoln    : boolean;   { EOLN status after a read }

Function length(x: str255): integer; external;
Procedure setlength(var x: str0; y: integer); external;
Function index(x,y: str255): integer; external;

PROCEDURE Error( enumb : byte);
begin
 CASE enumb of
   0:  writeln('Fatal error!');
   1:  writeln('Exceeded buffer limits on read');
   2:  {-reserved-};
   3:  writeln('File not found');
   4:  {-reserved-}
  end{ of case };
 error_flag := true
end;

PROCEDURE InsertItem( Newitem  :alfa);
{*
**      From the book - PASCAL An Introduction
**      to Methodical Programming
**      Authors:
**      W. Findlay and D.A. Watt
               ******                  }
VAR     entry,
       PriorEntry,
       Newentry        :ItemPointers;
       found           :boolean;

  Procedure INSERTWORD;
  begin{ CREATE the New entry and Insert it in position }
    New(Newentry);
    Newentry^.item := Newitem;
    Newentry^.Next := entry;
    If entry = ListHead then
      ListHead := Newentry
    Else
      PriorEntry^.Next := Newentry;
  end{-of InsertWord-};

begin
 { FIND the position where the New item will be Inserted }
 entry := ListHead;
 found := false;
 While NOT found AND (entry <> NIL) do
   WITH entry^ DO
     If (item < Newitem) then
       begin
       PriorEntry := entry;
       entry := Next
       end
     Else
       found := true;
 If found then{-Crate a new entry in the list If necessary-}
   begin
   If (entry^.item <> Newitem) then InsertWord{ at position `entry` }
   end
 Else
   InsertWord{ at end of list }
end{-of InsertItem-};

PROCEDURE WriteItems;
CONST     Sail = '***   INDEX   ***';
var       entry  :ItemPointers;
begin
 Writeln(Work_File, Sail);
 If CON_wanted then writeln(Sail);
 entry := ListHead;
 While entry <> NIL DO
   WITH entry^ DO
     begin
     Writeln(Work_File, item);
     If CON_wanted then writeln(item);
     entry := Next
     end
end{--of WriteItems-};

Procedure ReadC(var nextchar : charinfo;
               var currchar : charinfo );
{ revised 4 Jan 80, rep }
begin{ Terminator status module.
  Stores terminator status "AFTER" a read.
  NOTE this play on words - after one char is
  actually "PRIOR TO" the next character               }
 xeoln := EOLN(textfile);
 xeof  := EOF(textfile);
{ read byte module }
 If NOT xeof then
       READ(Textfile, Look);
{ current operative character module }
 currchar := nextchar;
 With NextChar do begin{ Look-ahead character name module }
   If xeof then
     name := FileMark
   Else If xeoln then
          name := EndOfLine
   Else If LooK IN ['a'..'z'] then { lower case }
          name := lletter
   Else If LooK IN ['A'..'Z'] then { upper case }
          name := uletter
   Else If LooK IN ['0'..'9'] then { digit }
          name := digit
   Else If LooK = '''' then
          name := quote
   Else If LooK = TAB then
          name := atab
   Else If LooK = space then
          name := blank
   Else name := otherchar;
   CASE name of{ store character value module }
       EndOfLine,
       FileMark:       Valu := space;
       Else:           Valu := LooK
   end{ case name of };
 End{ Look-ahead character name module };
end{ ReadC };

PROCEDURE GetL( var Fbuffer : dfltstr );
{               *****
       Get a line of text into users buffer.
       Flushes comment lines:
       Flushes lines of Literals:  'this is it'
       Ignores special characters & tabs:
       Recognizes End of File and End of Line.
GLOBAL
       flushing : (KNOT, DBL, STD, LIT);
       Fbuffer = dfltstr
       LLmax   = 0..Max Line length;
               *****                           }
var     state : (scanning, terminal, overflow);
begin { GetL }
  setlength(fbuffer,0);
  error_flag := false;
  state := scanning;
 REPEAT
   ReadC(Nextchar, Currchar);
   If (length(fbuffer) >= LLmax) then{ exceeded length of buffer }
     begin{ reset EOLN }
       state := overflow;
       READLN(fbuffer);{ reset EOLN }
       error(1)
     end
   Else
     begin
       If (currchar.name IN [FileMark,EndOfLine]) then
         state:=terminal{ end of line or end of file };
       CASE flushing of
           KNOT:
               CASE currchar.name of
               lletter, uletter, digit, blank:
                       begin{ store }
                       append(fbuffer,currchar.valu);
                       end;
               atab, quote, otherchar:
                       begin{   Flush comments -convert
                                tabs & other chars to spaces }
                       If (currchar.valu='(') and (nextchar.valu='*')
                         then flushing := DBL
                       Else If (currchar.valu='{') then
                          flushing := STD
                       Else If currchar.name=quote then
                          flushing := LIT;
                       { convert to a space }
                       append(fbuffer,space);
                       end;
               else:   { end of line -or- file mark }
                       append(fbuffer,currchar.valu)
               end{ case currchar name of };
           DBL:  { scanning for a closing  - double comment }
               If (currchar.valu ='*') and (nextchar.valu =')')
                 then flushing := KNOT;
           STD:  { scanning for a closing curley  }
                 If currchar.valu = '}' then
                     flushing := KNOT;
           LIT:  { scanning for a closing quote }
                 If currchar.name = quote then
                   flushing := KNOT
       end{ flushing case }
     end{ Else }
 UNTIL (state<>scanning);
end{-of GetL-};

PROCEDURE ReadWord;
{        Analyze the Line into "words"          }
const   space = ' ';
var     Cpos : byte; { Current Position pointer }
begin{ ReadWord }
Cpos := 1; { start at the beginning of a line }
While (Cpos < length(fbuffer)) Do
 begin
  { skip spaces }
  while (Cpos < length(Fbuffer)) AND (fbuffer[Cpos]=space) Do Cpos:=Cpos+1;
  Setlength(ID,0);{ start with a null array }
  while (Cpos < length(fbuffer)) AND (fbuffer[Cpos ] <> space) Do
   begin{ accept only non-spaces }
    If (length(ID)<alfa_len) then append(ID,fbuffer[ Cpos ]);
    Cpos := Cpos +1;
   end{ while };
  while (length(ID)<alfa_len) Do append(ID,space);
{}If DEBUG then writeln('   ',ID);
  InsertItem(ID);
  WordCount := WordCount + 1;
end;
end{-of ReadWord-};

Procedure SKIP(n : byte);
var     i : byte;
begin   For i:=1 to N do writeln
end;

Function ConnectFiles: boolean;
const   dflt_extension = '.CCD';
       fid_len = 14;   { Max length CP/M file names }
type    FID     = string fid_len;
var     File_ID,
       New_ID  : FID;
       ix,jx   : byte;

       Procedure PAD(var ID: fid; required: byte);
       const   space = ' ';
       begin
         while (length(ID)<required) Do append(ID,space);
       end;

begin{-GETID-}
 ConnectFiles := true;
 Setlength(File_ID,0);
 writeln;
 write('Enter <Drive:><File name>  ');
 readln(File_ID);
 If (length(File_ID)>fid_len) then
   setlength(File_ID,fid_len)
 Else
   PAD(File_ID, fid_len);
 RESET(File_ID, TextFile);
 If EOF(TextFile) then{ ABORT }
   begin
     error(3);
     ConnectFiles := false;
   end
 Else
   begin
   ix := index(File_ID,'.'); { search for an extension }
   jx := index(File_ID,' '); { search for the first space }
   If (ix=0) then{ no extension was specified }
     Setlength(File_ID,jx-1)
   Else
     Setlength(File_ID,ix-1);
   Setlength(New_ID,0);
   append(New_ID, File_ID);
   append(New_ID, dflt_extension);
   PAD(New_ID, fid_len);
   REWRITE(New_ID, Work_File);
   end;
End{ of ConnectFiles };

Procedure Initialize;
var     ch: char;
begin
 ListHead := NIL;  { MAKE the LIST EMPTY }
 cline := 0; { current line counter }
 wordcount := 0;
 idlen := 0;
 tab   := chr(9);  { ASCII Tab character }
 flushing := KNOT{ flushing };
{-INITIALIZE look-ahead char-}
 nextchar.name := blank;
 nextchar.valu := space;

 writeln;
 WRITE('DEBUG?');READ(Ch);
 DEBUG := ((Ch='Y') or (Ch='y'));
 writeln;
 WRITE('Output to Console?');READ(Ch);
 CON_wanted := ((Ch='Y') or (Ch='y'));
end;

PROCEDURE Clear(code : byte);
{       device dependent routine        }
begin   WRITELN( CHR(code) )
end;

Procedure Sign_On;
begin
 Clear(clearcode);
 writeln;
 writeln(' ':20,'***   C O N C O R D A N C E   ***');
 SKIP(4);
end;

Begin{ main body of Concordance }
 Sign_On;
 If NOT ConnectFiles then {ABORT} goto 9;
 Initialize;
 SKIP(4);
 cline:= cline +1;
 GetL(Fbuffer) { attempt to read the first line };
 while ((currchar.name<>filemark) AND (NOT error_flag)) do
   begin
{}    If DEBUG then writeln('line',cline:5,'  ',fbuffer);
     ReadWord{Analyze the Text into single 'words' };
     If cline=c4 then cline:=0;
     cline := cline +1;
     GetL(Fbuffer) { attempt to read another line of text };
   end{ while };
 Clear(clearcode);
 WriteItems;   { Write all the Items in order }
 writeln;
 writeln(' ':18, '***   SUMMARY   ***');
 writeln('Total # lines =',cline -1);
 writeln('Total # words =', wordcount);
 writeln;
9:{ABORT};
end{ of ConCordance }.