%%HP: T(3)A(R)F(.);
\<<  @ Start of Program @
@ PHONE V1.4 by Joe Ervin.  18-JAN-1991 @
@ NEW FEATURES FOR V1.4.
@ Added handling of addresses, in addition to names and numbers.  
@ Included YN menu definition, rather than using an external reference.
@ Added RCLF and STOF to preserve the state of the system.  Also enforce
@ STD display mode during execution.  Ensures a nice display during PHSEARCH.
@
@ NEW FEATURES FOR V1.3.   
@ Added a PREV menu choice in the PHSEARCH routine to allow the user to
@ move backwards through the list of matches.

CLEAR
{} @ Dummy value to hold place for PHNAMES. @
{} @ Dummy value to hold place for PHNUMBERS. @
{}
0 
0
\-> PHNAMES PHNUMBERS ADDRESSES PHSIZE FLAGS
   @ Creates local storage for names and numbers. @

\<< @ Surounds the procedures as well as main routine. @

\<<  @Start definition of PHSEARCH routine.@ 
CLEAR

0 0 0 {} 0 


\-> 
        SRCHNAME  @ Local variable to hold the search name. @
        DONE      @ Local variable to detect when search has finished. @
        MATCHNUM  @ Local variable to store the number of matches.  @
        MATCHLIST     @ Stores list of matches. @
        MATCHINDEX    @ Used to index into the match list. @


@ What we are going to do now is to step through each name in the list, and @
@ for each name, we will do a pattern match of the search name within each@ 
@ name in the list. If the searchname is found within a given name, then @
@ the index corresponding to the matched name will be added to MATCHLIST. @
@ This routine uses flag 1 to indicate success or failure to the calling @
@ routine. (SET = success, CLEAR = failure - i.e. no matches.  @ 
@ Flag 2 is also used to indicate whether the main routine should exit @
@ or prompt the user for another choice whether to search or edit the @
@ phone list. @

\<< @This delimiter surrounds PHSEARCH's local variables@
@ The following are definitions for four different menus which
@ are used by the PHSEARCH facility.  These definitions are defined
@ here as local variables for reasons of performace;  it ran
@ kinda slow when I built these menus in real time.  Defining them
@ here and then just calling them speeds things up considerably.

@ Here is the menu definition for MENADR @
        {{"Next" \<< CONT \>> } {"Prev" \<<  \-> X
        \<< CASE
        'X==1' 
          THEN 
            IF 'MATCHNUM>1' @ Do nothing if only 1 match.  
            THEN 
              'MATCHNUM-1' EVAL CONT 
            ELSE X CONT
            END 
          END 
        'X==2' THEN MATCHNUM CONT END @ So next index will be 1.
        'X-2' EVAL CONT  @ Do this by default to back up one index.
        END  \>>  @ end of case statement 1.
        \>> }                                
        {} { "Addr" \<< 6 SF \-> X
        \<< CASE
        'X==1' THEN IF 'MATCHNUM>1' @ Do nothing if only 1 match.  
          THEN 
            'MATCHNUM' EVAL CONT 
          ELSE X CONT
          END 
        END 
        'X-1' EVAL CONT @ Do this by default to replay last index.
        END \>> @ end of case statement 2.
        \>>}
        {} {"Exit" 
        \<< 1 'DONE' STO CONT \>>} }

@ Here is the menu definition for MENNUM
        {{"Next" \<< CONT \>> } {"Prev" \<<  \-> X
        \<< CASE
          'X==1' THEN IF 'MATCHNUM>1' @ Do nothing if only 1 match.  
          THEN 
            'MATCHNUM-1' EVAL CONT 
          ELSE X CONT
          END 
        END 
        @So next index will be MATCHNUM.
        'X==2' THEN MATCHNUM CONT END @ So next index will be 1.
        'X-2' EVAL CONT @ Do this by default to back up one index.
        END \>> @ end of case statement 3.
        \>> }                                
        {} { "Num" \<< 6 CF \-> X
        \<< CASE
        'X==1' THEN IF 'MATCHNUM>1' @ Do nothing if only 1 match.  
          THEN 
            'MATCHNUM' EVAL CONT 
          ELSE X CONT
          END 
        END 
        'X-1' EVAL CONT @ Do this by default to replay last index.
        END \>> @ end of case statement 4.
        \>> } {} {"Exit" 
        \<< 1 'DONE' STO CONT \>>} }

@ Here is the menu definition for MENPICKADR
        {{"Next" \<< CONT \>> } {"Prev" \<<  \-> X
        \<< CASE
          'X==1' THEN IF 'MATCHNUM>1' @ Do nothing if only 1 match.  
          THEN 
            'MATCHNUM-1' EVAL CONT 
          ELSE X CONT
          END 
        END 
        'X==2' THEN MATCHNUM CONT END @ So next index will be 1.
        'X-2' EVAL CONT @ Do this by default to back up one index.
        END \>> @ end of case statement 5.
        \>> }                   
        {"Pick" 
        \<<IF DUP 1 == THEN DROP MATCHNUM ELSE 1 - END GET 1 'DONE' STO 
        4 CF CONT \>>} 
        { "Addr" \<< 6 SF \-> X
        \<< CASE
        'X==1' THEN IF 'MATCHNUM>1' @ Do nothing if only 1 match.  
          THEN 
            'MATCHNUM' EVAL CONT 
          ELSE X CONT
          END 
        END 
        'X-1' EVAL CONT @ Do this by default to replay last index.
        END \>> @ end of case statement 6.
        \>>}
        {} {"Exit" 
        \<< 1 'DONE' STO 1 CF 2 SF CONT \>>} } 


@ Here is the menu definition for MENPICKNUM
        {{"Next" \<< CONT \>> } {"Prev" \<<  \-> X
        \<< CASE
          'X==1' THEN IF 'MATCHNUM>1' @ Do nothing if only 1 match.  
          THEN 
            'MATCHNUM-1' EVAL CONT 
          ELSE X CONT
          END 
        END 
        'X==2' THEN MATCHNUM CONT END @ So next index will be 1.
        'X-2' EVAL CONT @ Do this by default to back up one index.
        END \>> @ end of case statement 7.
        \>> }                   
        {"Pick" 
        \<<IF DUP 1 == THEN DROP MATCHNUM ELSE 1 - END GET 1 'DONE' STO 
        4 CF CONT \>>} 
        { "Num" \<< 6 CF \-> X
        \<< CASE
        'X==1' THEN IF 'MATCHNUM>1' @ Do nothing if only 1 match.  
          THEN 
            'MATCHNUM' EVAL CONT 
          ELSE X CONT
          END 
        END 
        'X-1' EVAL CONT @ Do this by default to replay last index.
        END \>> @ end of case statement 8.
        \>> } {} {"Exit" 
        \<< 1 'DONE' STO 1 CF 2 SF CONT \>>} } 

\->     MENADR       @ Menu definitions used within PHSEARCH.
        MENNUM
        MENPICKADR
        MENPICKNUM


\<< @ This delimeter surrounds the defining procedure for the above
    @ menu definitions.

{} TMENU  @ Just to clear the menu from the main routine. @
"Enter name..." {":Name:" \Ga} INPUT @Prompt user for name to search.@

IF DUP SIZE 6 \<=   @ If no name was entered. @ 
THEN 
 1 SF  @ To indicate successfull completion of search routine. @
 2 SF  @ To cause the calling routine to ask again: SEARCH or EDIT.  @
 4 CF @ Just in case this routine was called from PHEDIT. @
ELSE
  DUP SIZE 7 SWAP SUB 'SRCHNAME' STO
  PHNAMES  OBJ\->@ Puts the list of names on the stack.@
  IF SRCHNAME "*" SAME THEN 3 SF END
  1 SWAP FOR I  @ Step through the whole list of names. @
    SRCHNAME  @ Put the string to search for on the stack. @
    IF POS 3 FS? OR 
    @ If SRCHNAME exists anywhere within the name...@  
    THEN
      'MATCHNUM'  INCR DROP @ Add one to the number of matches. @
      'PHSIZE - I + 1' EVAL 
      'MATCHLIST' STO+  @ Add the match index to list of match indices. @
    END  @ End this match check. @
  NEXT   @ End loop checking for matches. @
  3 CF

  IF MATCHNUM 0 \=/   @ IF there were at least some matches...@
  THEN 
    1 SF  2 CF   @ To indicate success to the calling routine. @
    MATCHLIST 
    1  @ To initialize index into matchlist. @
    DO   @ Display list of matches until done. @
      GETI  @ Fetch the next item pointed to by the matchindex. @ 
      'MATCHINDEX' STO DUP 
      IF 1 == THEN MATCHNUM ELSE DUP 1 - END
      @ Now display which match we are looking at. @
      CLLCD
      "Match " SWAP + " of "  + MATCHNUM + 
      1 DISP
      ":Name:" PHNAMES MATCHINDEX GET + 
      @ Concatenates label to name. @
      "
"
      +  @ Adds a LF to the end of the NAME string.
      IF 6 FC? @ If we are in "numbers" mode...
      THEN
        ":Number:" PHNUMBERS MATCHINDEX GET +  
        @ Concatenates label to number. @
        + @ Concatenates the NUMBER string to the NAME string.
        4 DISP
      ELSE 
        "
"
        ADDRESSES MATCHINDEX GET +   
        @ Concatenates label to address. @
        + @ Concatenates the ADDRESS string to the NAME string.
        3 DISP
      END
      3 FREEZE 

@ Now we need to call in the proper menu definition depending on what 
@ display mode we are in (flag 6) and whether PHSEARCH was called from
@ PHEDIT or from the main routine.
      IF 4 FC?  @ PHSEARCH was called from main routine.
      THEN 
        IF 6 FC? @ If in "numbers" mode...
        THEN  @ We need to add a menu item to switch to "addresses" mode.
          MENADR
        ELSE  @ We are in "address" mode...  
          MENNUM
        END
      ELSE  @ PHSEARCH was called from PHEDIT.
        IF 6 FC? @ If in "numbers" mode...
        THEN  @ We need to add a menu item to switch to "addresses" mode.
          MENPICKADR
        ELSE     @ We are in "address" mode...  
          MENPICKNUM
        END
      END

      TMENU HALT  @ Display the menu and stop for input.
    UNTIL DONE
    END
  ELSE  @  If there were no matches...@
    "The specified name
was not found" @ To indicate search failure. @
    1 CF 2 CF @ To indicate failure to the calling routine. @
    CLLCD 1200 .1 BEEP 2 DISP 1 WAIT
  END
END
\>> @ This delimeter surrounds the defining procedure for the 
    @ menu definitions.

\>>
\>> @Ends definition of PHSEARCH routine.@ 

\-> PHSEARCH @ Creates the subroutine PHSEARCH. @
\<<
\<< @ Definition of the PHEDIT routine starts here. @
\-> P1 @ Take the parameter passed in on the stack. @
\<< IF P1 "INT" SAME 
THEN 

@ At this point, the local variables "PHNAMES" and "PHNUMBERS" contain the @
@ names and numbers of the list to be edited.  Now we need to present the @
@ user with a meaningful display from which to choose actions.  The actions @
@ allowed include "MODIFY" and "ADD".  The following code prompts the user @
@ to choose between these two options, and then performs the simple editing @
@ function. @

    "Do you want to add
to the list, modify
an existing entry,
or delete an entry?" 
    CLLCD 1 DISP 3
    FREEZE {{"Add" \<<"ADD" CONT\>>} {"Mdfy" \<<"MODIFY" 5 CF CONT\>>} 
    {"Del"  \<<"MODIFY" 5 SF CONT \>>} {} {} 
    {"EXIT" \<<2 SF CONT\>>}} TMENU HALT

ELSE CLEAR "ADD" @ This is for when PHEDIT was called by main with @
                 @ P1 of "NEW". @
END 

@ This is for the case when PHEDIT was called by the main routine for  @
@ the case where there was no phone list. @

IF 2 FC?C 
THEN 

{} TMENU  @ Clear the menu from what was previously there. @
0 0 0 0 \-> X TEMP LEN LEN2 DONE
\<< CASE  
@ Note the creation of the scratch variables 'TEMP', 'LEN', 'LEN2'.@
@ 'TEMP' is scratch for the name, LEN is scratch for the name's length, @
@ and 'LEN2' is scratch for the length of the phone number. @
@ DONE is used for the repeat loop. @
@ X gets the "ADD" or "MODIFY" string from the above prompt. @

  X "ADD" SAME @ Here we are adding to the list of names. @
      THEN
      DO 
        "Enter name..." {":Name:" \Ga} INPUT 
        @ Prompts the user to enter the name for this entry. @
        @ Now we need to build up the command line for the INPUT command @
        @ which will pull in the number for this entry.  We want to @
        @ display the name above where the user is going to enter the @
        @ number. @
        DUP SIZE 'LEN' STO 'TEMP' STO 
        IF LEN 6 > 
        THEN 
          "Enter number..." TEMP EVAL "
:Number:" + { 2 9 }
          \Ga 3 \->LIST INPUT 

          @ The current prompt shows the name and prompts for @
          @ the phone number. @

          @ Now save the new information and update PHSIZE.
          DUP DUP SIZE 'LEN2' STO 7 LEN SUB PHNAMES + 'PHNAMES' STO 
          LEN 10 +
          LEN2 SUB PHNUMBERS + 'PHNUMBERS' STO 
          PHNAMES SIZE 'PHSIZE' STO

          @ Now we need to prompt for the address information...
          "Enter address..." TEMP EVAL "
:Addr:" + { 2 7 }
          \Ga 3 \->LIST INPUT 

          @ The current prompt shows the name and prompts for @
          @ the address. @

          @ Now save the new ADDRESS information.
          DUP SIZE 'LEN2' STO 
          LEN 8 +
          LEN2 SUB ADDRESSES + 'ADDRESSES' STO 

        ELSE 
          1 'DONE' STO
        END
      UNTIL DONE 
      END
    END  @"ADD"@

    X "MODIFY" SAME 
      THEN         @ User wants to edit the list @

      CLEAR
      0 0 ""  \-> INDEX  @ Stores the index of the entry to be edited. @
                DONE     @ Used to terminate DO UNTIL loop. @
                EDITSTRING   @ Stores the modified string. @

\<<
      @ Basically what I want to do here is to call the PHSEARCH routine @
      @ in a way that it will get the user to pick a specific entry.  @
      @ I want to use the PHSEARCH routine to do this since so much of @
      @ the code needed to do this is alread done.  The result of the @
      @ PHSEARCH routine is to return an index indicating the exact @
      @ entry which is to be edited.  This routine then needs only @
      @ display it in an input statement in such a way that the user @
      @ can modify it as it sits on the display.  When the user presses @
      @ ENTER, the modified entries (name and number) will be written @
      @ back into the database. @
      @ This routine also checks flag 5 to determine whether the user @
      @ wants to delete the record or simply modify it. @

      DO 
        2 CF 4 SF PHSEARCH EVAL @ Get the index of entry to be modified. @
      UNTIL 1 FS?C @ Repeat search routine until user selects an entry @
                   @ or gives up. @
      END  
      4 CF   @ Flag 4 was set only to indicate to PHSEARCH to use "pick" in @
                @ the menu so user could choose the item to edit.
      IF 2 FC?C  @ If flag 2 is set, then the user must have given up @
                 @ trying to select an entry to edit. @
      THEN  @ Let's edit the selected entry @
      'INDEX' STO 
        IF 5 FC?C 
        THEN 
          DO 
  @ This section of code was modified in v1.4 to handle the editing of 
  @ addresses.  For this reason, I use several IFTE commands below to 
  @ differentiate between editing the PHNUMBERS field and the 
  @ ADDRESSES field. 

            INDEX DUP 
            PHNAMES SWAP GET @ Gets the name to be modified. @
            SWAP 
            6 FC? 'PHNUMBERS' 'ADDRESSES' IFTE 
            @ Gets PHNUMBERS or ADDRESSES, depending on the 
            @ state of flag 6.

            SWAP GET   @ Gets number/address to be modified. @

            @ The stack now has
            @           2: name
            @           1: number/addr

            @ Now reformat the information for the INPUT command.

            6 FC? ":Number:" ":Addr:" IFTE 
            SWAP +
            SWAP ":Name:" SWAP +   @ Adds tags to the name and num/addr.
            "
"
            + SWAP +  @ Creates one long string with a carraige return @
            @ to separate the name and num/addr strings. @


            @ Now display this information using INPUT so the user can @
            @ edit the strings. @


            "Edit entry..."  SWAP 0
            2 \->LIST {} TMENU INPUT

            @ At this point, the user has been prompted with the name and @
            @ number to be modified.  The keyboard is in overstrike mode @
            @ and the cursor was left at the end of the number. @

            @ Now we must recover the new name and number from the @
            @ string on the stack. @

            DUP 'EDITSTRING' STO   @ Save a copy of the string.
            IF DUP ":Name:" POS 1 ==   @ Test for existence of the name...
            SWAP DUP 
            6 FC? ":Number:" ":Addr:" IFTE 
            POS                   @ ...and number tags within the string.
            DUP 'TEMP' STO  @ Save location of num/addr tag.
            ROT AND    
            THEN    @ The user entered valid data. @

              @ Update the database with new name.@
              DUP 7 TEMP 2 - SUB @ Extract the modified name. @
              1 \->LIST PHNAMES SWAP INDEX SWAP REPL
              'PHNAMES' STO    @ Save modified name in PHNAMES. 

              @ Update the database with the new number/address.
              TEMP 
              6 FC? 8 6 IFTE 
              + EDITSTRING SIZE SUB @ Extract the new name/addr.
              1 \->LIST
              6 FC? 'PHNUMBERS' 'ADDRESSES' IFTE 
              SWAP INDEX SWAP REPL 
              6 FC? "'PHNUMBERS'" "'ADDRESSES'" IFTE 
              OBJ\-> STO 

              1 'DONE' STO 
              CLEAR
            ELSE CLLCD 1200 .1 BEEP "Invalid Entry" 2 DISP 2 WAIT
            END
          UNTIL DONE
          END
        ELSE    @ The user wants to delete the entry.  @
          CLEAR INDEX DUP 
          PHNAMES SWAP GET @ Gets the name to be modified. @
          SWAP PHNUMBERS SWAP GET   @ Gets number to be modified. @

          @ The stack now has           @
          @                   2: name   @
          @                   1: number @

          @ Now reformat the information for the INPUT command. @

          ":Number:" SWAP +
          SWAP ":Name:" SWAP +   @ Adds tags to the name and number. @
          "
"
          + SWAP +  @ Creates one long string with a carraige return @
          @ to separate the name and number strings. @


          @ Now display this information and prompt user to verify that @
          @ this record should be deleted.   

          CLLCD  "Delete entry?"  1 DISP
          4 DISP 1200 .1 BEEP 3 FREEZE 
          { { "Yes" \<< 1 CONT \>> } { } { } { } { } 
          { "No" \<< 0 CONT \>> } }
          TMENU HALT
          IF   @ yes @
          THEN 
            PHNAMES  OBJ\->  DROP @ Put the list of names on the stack. @
            PHSIZE INDEX 1 - - ROLL 
            DROP   @ Deletes the entry pointed to by INDEX. @
            PHSIZE 1 - \->LIST 'PHNAMES' STO  

            PHNUMBERS  OBJ\->  DROP @ Put the list of names on the stack. @
            PHSIZE INDEX 1 - - ROLL 
            DROP  @ Deletes the entry pointed to by INDEX. @
            PHSIZE 1 - \->LIST 'PHNUMBERS' STO  

            ADDRESSES  OBJ\->  DROP @ Put the list of names on the stack. @
            PHSIZE INDEX 1 - - ROLL 
            DROP  @ Deletes the entry pointed to by INDEX. @
            'PHSIZE'  DECR \->LIST 'ADDRESSES' STO  
          END
        END
      ELSE 5 CF 
      END
\>>
    END
  END  
\>>    @end case@
END  @IF 2 FC?C

ADDRESSES PHNUMBERS PHNAMES 3 \->LIST 'PHDAT' STO


\>> @ Surrounds PHEDIT's parameters. @
\>> @ Ends the definition of PHEDIT and pushes PHEDIT on the stack @
    @ to be defined later.@





 \-> PHEDIT @ Defines the subroutine "PHEDIT", which edits the database. @
           @ PHEDIT takes as parameters a single string which must have the @
           @ value "NEW", indicating a new list should be created, @
           @ or "INT", indicating the old list should be prompted as to 
           @ whether the current list is to be added to or edited. @

\<< @ The main routine starts here. @
64 STWS  @ Sets the word size to 64.  Needed for RCLF
RCLF DUP 'FLAGS' STO
2 {#0d} REPL STOF  @ Clear user flags.
STD  @ Set display to STD mode.  Looks nicer.
IF 'PHDAT' VTYPE -1 ==   
  THEN
  CLLCD 
  "Phone Database does" 2 DISP "not exist.  Do you"
  3 DISP "want to create it?"
  4 DISP 3 FREEZE 
  { { "Yes" \<< 1 CONT \>> } { } { } { } { } 
  { "No" \<< 0 CONT \>> } }
  TMENU HALT  @ Prompt the user for yes/no @
  IF 
    THEN  
    {} DUP 'PHNAMES' STO 'PHNUMBERS' STO 
    "NEW" PHEDIT  EVAL @ tells PHEDIT to go directly to the @
                             @ edit input mode on a new list @
  @ After the user exits PHEDIT, control falls down to main routine below.@
    ELSE

"You must create a
phone database before
any further action can
be taken."
      2 DISP CLEAR 2 MENU  1200 .1 BEEP 3 FREEZE  3 WAIT
      0 DOERR         @ Obviously, the user is a pinhead. @
    END 

  END 

@ We need to start the main routine.@
0 \-> DONE \<<
  @ First we need to suck in the database. @
     {} DUP 'PHNAMES' STO 'PHNUMBERS' STO 
  CLEAR  @ Clear the stack, so the following DEPTH command will work.
  PHDAT EVAL

@ At this point, we need to determine if the current database is in
@ the new format used with V1.4 and later.  If the database does not 
@ contain the list of ADDRESSES, then we need to add them and write it back
@ out.
  IF  DEPTH 2 == 
  THEN @ We must update the database structure.
    CLLCD "Reformating PHDAT.
Please wait..." 2 DISP 1 WAIT
    DUP @ Create a spare copy of PHNAMES.
    {}  @ Put a null list on the stack.
    SWAP SIZE 1 SWAP FOR I "" + NEXT  @ Creates a list of null strings.
    3 ROLLD 3 \->LIST
    'PHDAT' STO @ Adds the empty address list to the database.
    CLEAR PHDAT EVAL
  END    

  'PHNAMES' STO  'PHNUMBERS' STO @ copies the database in the local  @
                                 @ variables 'PHNAMES' , 'PHNUMBERS' @
  'ADDRESSES' STO                @ and 'ADDRESSES'.
  PHNAMES SIZE 'PHSIZE' STO  @ Indicates the number of names in the list. @

  DO 
  @ Here we want to ask the user whether we are searching or editing. @
"Do you want to 
search the list or
edit the list?" 
  CLLCD 1 DISP 3 FREEZE
  {{"SRCH" \<< DO PHSEARCH EVAL UNTIL 1 FS?C END CONT \>>}
  {"EDIT" \<< "INT" PHEDIT  EVAL CONT \>>} 
  {} 
  {} {} {"Exit" \<<1 'DONE' STO CONT \>>}} TMENU HALT
  @ "INT" for PHEDIT here to tell PHEDIT to prompt the user as to @
  @ whether he wants to edit (modify) the phone list or simply add to it. @

  UNTIL DONE @ Keep on doing this loop until DONE. @
  END
  \>> @ DO @

  CLEAR 2 MENU 
  FLAGS STOF  @ Resets the state of the system and user flags.
\>> @ Defines bounds for which PHSEARCH is defined. 
\>> @ Main Routine. 
\>> @ Main routine and procedures. 
\>> @ End of Program. 
