(*
**  PROGRAM TITLE:      AUTHOR
**
**  WRITTEN BY:         Raymond E. Penley
**  DATE WRITTEN:       24 June 1980
**
**  WRITTEN FOR:        Pascal/Z Users Group
**
**  Original program
**      A General Purpose Keyword In Context Program
**               by:    Randy Reitz
**                      26 Maple St
**                      Chatham Township, N.J. 07928
**                      June 1980
**
**  DONATED TO PASCAL/Z USERS GROUP, July 1980
*)
Program AUTHOR;
label   9999; { abort }
const
 Program_title = 'AUTHOR';
 Sort_message  = 'Sort by 1) TITLE, 2) AUTHOR, or 3) DATE? ';
 default       = 80 ;
 dflt_str_len  = default;      { default length for a string }
 fid_length    = 14;           {max file name length}
 line_len      = default;
 n             = 10;
 title$field$width  = 56;
 author$field$width = 14;
 date$field$width   =  8;
 Pdelim        = '^';          { the "P" delimeter }
 Sdelim        = '/';          { the "S" delimeter }
 space         = ' ';
 screen_lines  = 24; {# of viewing lines on consle device }
 StrMax        = 255;

type
 dfltstr = STRING dflt_str_len;
 fid     = STRING FID_LENGTH;
 INDEXES = array[1..n] of integer;
 str0    = STRING 0 ;
 str1    = STRING 1;
 str255  = STRING Strmax ;
 Mstring = STRING Strmax;

 links   = ^entry;

{}stuffing = record
               title,
               author,
               date  : dfltstr
            end;

 entry   = record
{}              stuff: stuffing;
               Rlink,
               Llink: links
           end;
var
 bad_lines     : integer;      { count of # of bad lines }
 bell          : char;
 cix           : char;
 error         : boolean;
 High,
 LINE,
 Low           : dfltstr;
 i             : integer;      { global index }
 in_file       : fid;
 num           : integer;      { occurrences of "P"/"S" delimeters }
 root          : links;
 Ploc,                         { location of "P" delimeters }
 Sloc          : INDEXES;      { location of "S" delimeters }
 sort          : 0..n;
 size,                         { size of current file }
 this_line     : integer;      { current line counter }
 termination   : boolean;      { Program termination flag }
 wrk1          : text;         { the input file }

 (*********************************************)

(*---This is how we get string functions in Pascal/Z---*)
Function length(x: str255): integer; external;
Function index(x,y: str255): integer; external;
Procedure setlength(var x: str0; y: integer); external;

Procedure KEYIN(VAR cix: char); external;
(*---Direct Keyboard onput of a single char---*)

Procedure COPY( {    TO     } VAR dest : dfltstr;
               {   FROM    } THIS : MSTRING ;
               {STARTING AT} POSN : INTEGER ;
               {# OF CHARS } LEN  : INTEGER ) ;
{  COPY(NEW_NAME, NBUF, NAME_ADDR, NAME_LEN);   }
{  COPY(A_STRING, A_STRING, 5, 5);              }
{
GLOBAL    default = default line length;
         dfltstr = STRING default;
         StrMax = 255;
         MSTRING = STRING StrMax;              }
LABEL   9;
VAR     ix   : 1..StrMax;
begin
 SETLENGTH(dest,0);  {length returned string=0}
 If (len + posn) > default then{EXIT}goto 9;
 IF ((len+posn-1) <= LENGTH(this))
    and (len > 0) and (posn > 0) then
    FOR ix:=1 to len do
        APPEND(dest, this[posn+ix-1]);
9: {Any error returns dest with a length of ZERO.}
End{of COPY};

PROCEDURE CONCAT({New_String} VAR C : dfltstr ;
                {Arg1_str  }     A : Mstring ;
                {Arg2_str  }     B : Mstring );
{  CONCAT(New_string, Arg1, Arg2);   }
{ An error returns length of new_string=0 }
{
GLOBAL    default = default line length;
         dfltstr = STRING default;
         StrMax = 255;
         Mstring = STRING StrMax;              }
var     ix : 1..StrMax;
begin
 SETLENGTH(C,0);
 If (LENGTH(A) + LENGTH(B)) <= default then
   begin
       APPEND(C,A);
       APPEND(C,B);
   end;
End{of CONCAT};

Function UCASE(ch: char): char;
begin
 If ch IN ['a'..'z'] then
   UCASE := chr(ord(ch) - 32)
 Else
   UCASE := ch
end;

Procedure FINDR( PAT       : str1;
                VAR S     : dfltstr;
                VAR where : INDEXES;
                VAR cnt   : integer );
var     ix, cum : integer;
       temp   : dfltstr;
begin
 cum := 0;
 cnt := 0;
 where[1] := 0;
 Repeat
   COPY(temp, S, cum+1, length(S)-cum);
   ix := INDEX(temp, pat);
   cum := cum + ix;
   If (ix>0) then
     begin
       S[cum] := space;
       cnt := cnt + 1;
       where[cnt] := cum;
       where[cnt+1] := 0;
     end;
 Until (ix=0) OR (cum=length(S));
end{of FINDR};

Procedure ENTER(newx: links);
var     this, next: links;
       Newkey, Thiskey: dfltstr;
begin
 If (root=nil) then
   root := newx
 Else
   begin
     next := root;
     Repeat
       this := next;
       CASE sort of
       1: begin
          Newkey := newx^.stuff.title;
          Thiskey := this^.stuff.title;
          end;
       2: begin
          Newkey := newx^.stuff.author;
          Thiskey := this^.stuff.author;
          end;
       3: begin
          Newkey := newx^.stuff.date;
          Thiskey := this^.stuff.date;
          end
       End{case};
       If Newkey <= Thiskey then
         next := this^.Llink
       Else
         next := this^.Rlink;
     Until next=nil;
     If Newkey <= Thiskey then
       this^.Llink := newx
     Else
       this^.Rlink := newx;
   end
End{of Enter};

Procedure PAUSE;
var     dummy: char;
begin
 this_line := 0;
 write('Press return <cr> to continue');
 readln(dummy);
End{of Pause};

Procedure TRAVERSE(ptr: links);
var     thiskey: dfltstr;
begin
 CASE sort of
   1: Thiskey := ptr^.stuff.title;
   2: Thiskey := ptr^.stuff.author;
   3: Thiskey := ptr^.stuff.date
 End{case};
 If (ptr^.Llink<>nil) AND (Thiskey>=low) then
   TRAVERSE(ptr^.Llink);
{}If (thiskey >= low) AND (thiskey <= high) then
   begin{ Write a line }
     With ptr^.stuff do begin
       CASE sort of
         1:    begin  { TITLE || AUTHOR || DATE }
               write( title : title$field$width );
               write( author : author$field$width );
               writeln( date : date$field$width );
               end;
         2:    begin  { AUTHOR || TITLE || DATE }
               write( author : author$field$width );
               write( title : title$field$width );
               writeln( date : date$field$width );
               end;
         3:    begin  { DATE || TITLE || AUTHOR }
               write( date : date$field$width );
               write( title : title$field$width );
               writeln( author : author$field$width );
               end
       End{case};
       end{with};
     this_line := this_line + 1;
     If (this_line*6+1 > screen_lines) then PAUSE;
   end{ Write a line };
{}If (ptr^.Rlink<>nil) AND (Thiskey <= high) then
   TRAVERSE(ptr^.Rlink);
End{of TRAVERSE};

Procedure CREATIT;
{
GLOBAL  I : integer;    <passed from main program>
}
var     p: links;
       temp1,
       newtitle,
       newauthor,
       newdate  : dfltstr;
begin
 NEW(p);
 CASE sort of
   1:  begin
{}      COPY(newtitle, LINE, ploc[I]+1, sloc[ 1 ]-ploc[I] );
       COPY(temp1, LINE, 1, ploc[I] );
       APPEND(newtitle,temp1);
       end;
   2,3:If (LINE[1]=space) then
{}        COPY(newtitle, LINE, 2, sloc[1]-1)
       Else
{}        COPY(newtitle, LINE, 1, sloc[1])
  End{case};
{} COPY(newauthor, LINE, sloc[1]+1, (sloc[2]-sloc[1])-1);
  If (length(newauthor) > author$field$width) then
      setlength(newauthor,author$field$width);
  newdate := '19';
  COPY(temp1, LINE, sloc[2]+1, length(LINE)-sloc[2] );
  APPEND(newdate, temp1);
{} newtitle[1]  := Ucase(newtitle[1]);
{} newauthor[1] := Ucase(newauthor[1]);
{} newdate[1]   := Ucase(newdate[1]);
  With p^.stuff do begin
    title := newtitle;
    author := newauthor;
    date := newdate
    end{with};
  p^.Llink := nil;
  p^.Rlink := nil;
  ENTER(p);
end{of CREATIT};

Procedure Read_Data_File;
begin
 Readln(wrk1,LINE);
 while not EOF(wrk1) do
   begin
     FINDR(Sdelim, LINE, sloc, num);
     error := (num<>2);
     FINDR(Pdelim, LINE, ploc, num);
     error := (error OR (num=0));
     If sort IN [2,3] then num := 1;
     If not error then
       For i:=1 to num do
         begin CREATIT; size := SUCC(size) end
     Else
       begin
         writeln(bell,'***BAD LINE***',bell);
         bad_lines := bad_lines + 1;
         writeln(LINE)
       end;
     READLN(wrk1,LINE)
   end{while};
End{of Read_Data_File};

Procedure GETID( MESSAGE : dfltstr; VAR ID: FID );
{
GLOBAL  FID_LENGTH = 14;
       dfltstr    = STRING dflt_str_len;
       fid      = STRING FID_LENGTH;           }
const   space = ' ';
begin
 setlength(ID,0);
 writeln;
 write(message);
 READLN(ID);
 while length(ID)<FID_LENGTH do APPEND(ID,space);
End{---of GETID---};

Procedure CLEAR;
var     ix :1..25;
begin
 for ix:=1 to 25 do writeln
end;

Procedure Initialize;
begin
 CLEAR;
 writeln(' ':22,Program_title);
 writeln;writeln;writeln;writeln;
 root := nil;
 bell := chr(7);
 size := 0;
 bad_lines := 0;
 GETID('Enter data file name ->', in_file);
 RESET(in_file,wrk1);
end{of initialize};

Begin{ of Program KeyWordInContext }
 Initialize;
 If EOF(wrk1) then
   begin
     writeln('File ', in_file, 'not found');
     {EXIT}goto 9999;
   end;
 REPEAT
   writeln;
   write(Sort_messge);
   KEYIN(cix);Writeln(cix);
   sort := ORD(cix) - ORD('0');
 UNTIL sort IN [1,2,3];
 Read_Data_File;
 writeln('Sort complete with ', size:3, ' records entered.');
 If bad_lines > 0 then
   writeln('There are ', bad_lines:3, ' bad lines in the data file.');
 writeln;
 writeln('Enter range for output.');
 Termination := false;
 REPEAT
   setlength(low,0);
   setlength(high,0);
{}  writeln;
   write('Low string (<ctrl-C> to quit) ->');
   readln(low);
   If not termination then
     begin{ low string }
       low[1] := UCASE(low[1]);
       write('High string ->');
       readln(high);
       If not termination then
         begin{ high string }
           high[1] := UCASE(high[1]);
           this_line := 0;
           CLEAR;
           TRAVERSE(root)
         end{ high string }
     end{ low string }
 UNTIL Termination;
9999:{ file not found }
End{ of Program AUTHOR }.