(************************************************
**  PROGRAM TITLE:	Name and Address
**			Version 3.0
**
**  WRITTEN BY:		Raymond E. Penley
**  DATE WRITTEN:	26 June 1980
**
**  ORIGINAL PROGRAM:
**	A General Purpose Permuted Keyword Index Program
**	Written by:	Randy Reitz
**			26 Maple St
**			Chatham Township, N.J. 07928
**
**	Date written:	June 1980
**
**  WRITTEN FOR-S100 Microsystems Magazine
**
**  Donated to PASCAL/Z USERS GROUP, july 1980
** 
***********************************************)
Program NameAndAddress;
label	9999; { abort }
const
  Program_title = 'NAME AND ADDRESS';
  Sort_message  = 'Sort by 1) Name, 2) Address, or 3) Zip Code? ';
  default	= 80 ;
  dflt_str_len	= default;	{ default length for a string }
  dflt_margin	= 1;		{ Left margin default }
  fid_length	= 14;		{max file name length}
  line_len	= default;
  n		= 10;		{Maximun # of delimeters}
  name$field$width	= 20;	{ Name line width }
  address$field$width	= 40;	{ Address line width }
  Zip$field$width	=  5;	{ ZIP Code line width}
  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
		name,		{ Name line }
		address,	{ Address line    }
		Zip  : dfltstr	{ ZIP Code line   }
	     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;		{ CP/M File Identifier <FID> }
  margin,			{ left margin }
  num		: integer;	{ occurrences of "P"/"S" delimeters }
  root		: links;
  Ploc,				{ location of "P" delimeters }
  Sloc		: INDEXES;	{ location of "S" delimeters }
  sort		: 0..255;
  size,				{ size of current file }
  this_line	: integer;	{ current line counter }
  termination	: boolean;	{ Program termination flag }
  wrk1		: text;		{ the input file <FCB> }

  (*********************************************)

(*---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	{ NAME Key }
	   Newkey := newx^.stuff.name;
	   Thiskey := this^.stuff.name;
	   end;
	2: begin	{ ADDRESS Key }
	   Newkey := newx^.stuff.address;
	   Thiskey := this^.stuff.address;
	   end;
	3: begin	{ ZIP Code Key }
	   Newkey := newx^.stuff.Zip;
	   Thiskey := this^.stuff.Zip;
	   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);
{
	---Address format---

	Name			line 1
	Address			line 2
	Zip Code		line 3
	<blank line>		line 4
}
var	thiskey: dfltstr;
begin
  CASE sort of
    1: Thiskey := ptr^.stuff.name;	{ Name }
    2: Thiskey := ptr^.stuff.address;	{ Address }
    3: Thiskey := ptr^.stuff.Zip	{ Zip Code }
  End{case};
  If (ptr^.Llink<>nil) AND (Thiskey>=low) then
    TRAVERSE(ptr^.Llink);
  If (thiskey >= low) AND (thiskey <= high) then
    begin{ Write an address }
      With ptr^.stuff do begin
	writeln(' ':margin, name : name$field$width );
	writeln(' ':margin, address : address$field$width );
	writeln(' ':margin, Zip : Zip$field$width );
	writeln;
	end{with};
      this_line := this_line + 1;
      If (this_line*6)+1 > screen_lines then PAUSE;
    end{ Write an address };
  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,
	newname,
	newaddress,
	newZip  : dfltstr;
begin
  NEW(p);
  CASE sort of
    1:	begin
	COPY(newname, LINE, ploc[I]+1, sloc[ 1 ]-ploc[I] );
	COPY(temp1, LINE, 1, ploc[I] );
	APPEND(newname,temp1);
        end;
   2,3: If (LINE[1]=space) then
	  COPY(newname, LINE, 2, sloc[1]-1)
	Else
	  COPY(newname, LINE, 1, sloc[1])
   End{case};
  COPY(newaddress, LINE, sloc[1]+1, (sloc[2]-sloc[1])-1);
  If (length(newaddress) > address$field$width) then
       setlength(newaddress,address$field$width);
  COPY(newZip, LINE, sloc[2]+1, length(LINE)-sloc[2] );
  newname[1]	:= Ucase(newname[1]);
  newaddress[1] := Ucase(newaddress[1]);
  newZip[1]	:= Ucase(newZip[1]);
  With p^.stuff do begin
     name := newname;		{ Name line }
     address := newaddress;	{ Address line }
     Zip := newZip		{ ZIP Code }
     end{with};
  p^.Llink := nil;
  p^.Rlink := nil;
  ENTER(p);
end{of CREATIT};

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(' ':12,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 In the Data File---}
  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};
  {---     Read is complete	  ---}
  {---Announce no of records found---}
  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;
  write('Enter left margin? ');
  READLN(margin);
  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 Name and Address }.
