1 ! ! M A I L 2 ! Program: MAIL Version: V06A Edit: 02 Edit Date: 25-Sep-79 Author: Brant Cheikes System: PDP-11 RSTS/E V06C-03 or later Affiliation: Nassau Community College ! 5 ! ! Program Description MAIL allows any user to send messages to another user in the form of files named MAIL.BOX written into the recipient's account. To send or receive mail, the user must be registered, which involves entering his name for entry in the ledger file of registered users. Complete security is attained through protection codes, e.g. no user can read another's mail, unless of course he is privileged. Suggested protection code for MAIL.BAC: <232> ! 6 ! ! The MAIL system consists of: ! ! MAIL.BAS - the sender/receiver program ! PSTOFC.BAS - the user services program ! MAILUP.BAS - the MAIL utility program [privileged users only] ! CLEAN.BAS - the MAIL data file cleaner [priv only] ! MAIL.HLP - the MAIL help file explaining MAIL and the MAIL ! system. ! ! Data files are created and copied by MAILUP. ! 8 ! ! The MAIL Algorithm ! ! Regarding MAIL, a user can be in one of 3 possible states. One can ! be registered, not registered, or with suspended service. These ! conditions are determined by certain tests. This is how a user's ! state is determined, and, if he is registered, how his name is ! retrieved. ! ! A%(254%,254%) is the DIMension of the virtual array file ACCT.MAI. ! Any user's state can be determined by determining the SGN of the ! value at position A%(X%,Y%) where X% is the user's project number ! and Y% is the user's programmer number. Say you want to test account ! 2,99. First you get the value V% of A%(2,99). Now get its SGN S%. ! This test determines the state of account 2,99: ! ! If S% = 1% then the user in 2,99 is registered. ! ! If S% = 0% then 2,99 is not registered. ! ! If S% = -1% then 2,99's MAIL service has been suspended. ! ! 9 ! Retrieving User's Registration Name ! ! Upon determining SGN of account value is positive (SGN is 1%), a ! program may go on to retrieve the name of the user in account ! 2,99. This is how it is done: ! ! Get the actual integer value of A%(2,99) V%. This value must ! be broken down to yield two values, the RECORD value and the ! position value of the name in NAMES.MAI. ! ! Names are stored in NAMES.MAI in logical records of 32 bytes. In ! this manner, there are 16 records to a block, each capable of ! holding a 32 byte ASCII character name. Names are addressed by ! their BLOCK number and logical record value OFFSET in NAMES.MAI, ! where the offset of the position equals (Position number (an ! integer between 1 and 16) - 1%) * 32% (the number of bytes in ! each logical record. Therefore, by FIELDing RECORD R% (the ! record number), P% (the offset value) AS G$ (garbage),32% AS ! N$, the program can put the actual name of user 2,99 into N$. ! ! The address values are stored in V%, the high byte containing ! the RECORD value, and the low byte containing the offset value ! / 2%. To get the values, the program executes these commands: ! ! R% = SWAP%(V%) AND 255% ! P% = V% AND 255% ! P% = P% + P% ! ! This puts the RECORD value into R% and the offset value into P%. ! ! To get the name, the program does this: ! ! GET #3%, RECORD R% ! FIELD #3%,P% AS G$,32% AS N$ ! ! This puts the name addressed into N$. This is how the MAIL programs ! address and fetch the name of any registered user. ! ! Complete examples of this algorithm can be found in the definition ! of FNN$. An example of the SGN test can be found at line 80. ! 10 ! ! Modification Options ! ! The system manager may want to change the account where the ! MAIL data files are stored using MAILUP. If so, line 20 MUST ! be changed to read: ! ! 20 A1$="(x,y)" where x is the account project number and y ! is the account programmer number. A1$ may also ! be defined as a logical or an account substitution ! character such as '%' for 1,4. ! ! NOTE: MAIL system programs and HELP files must be kept together ! in one account. Data files must also be kept together but ! they may be kept together in an account different from the ! MAIL programs. ! ! LOGIN Modifications ! ! To make the MAIL system more powerful, a search for MAIL.BOX in ! the user's account may be added to the NOTICE PRINTER section of ! LOGIN along with a message to be printed if such a file is found. ! 11 ! ! Channel Usage ! ! Channel Used for ! ! 1% Accessing keyboard for dialogue ! ! 2% Accessing ACCT.MAI ! ! 3% Accessing NAMES.MAI ! ! 4% Reading or writing MAIL.BOX ! ! 5% Disk file input for mail ! ! ! The Data Files ! ! File Description ! ! ACCT.MAI Contains test flags on accounts and ! indexes into NAMES.MAI for name retrieval. ! Virtual Array I/O used with it. ! ! NAMES.MAI Stores the names of all registered users. ! ! MAIL.BOX Contains messages sent by MAIL ! 13 ! ! Variable Description ! ! Variable Use ! ! A%( , ) Virtual array matrix ! A% ASCII value of each char. in FNL$ ! A$ Account work string ! A1$ Holds data file storage account ! A2$ Holds MAIL system program account ! C% INSTR value looking for comma in FNM% ! F% Disk file input flag ! F$ File specification for disk file ! G% 'Start at chr #' for INSTR in FNL$ ! G1% INSTR value looking for a space in FNL$ ! G$ Work string in FNL$ ! Also used for storage of garbage bytes in FIELD ! statements and other work ! G3$ FNL$ work string - holds final mixed case name ! H$ Header for MAIL.BOX ! I% Integer work variable ! I$ NUM1$ value of A(proj,prog) address in FNN$ ! L$ Holds lines from MAIL.BOX ! M$ Holds text input during sending to MAIL.BOX ! N$ Holds names ! N5$ FNR$ work string for PUTting names ! P% Holds position value into NAMES.MAI ! P1% Holds user project number ! P2% Holds user programmer number ! P3% Recipient's project # ! P4% Recipient's programmer # ! P5% Holds value of next available NAMES.MAI name ! slot ! Q$ Holds answers to questions ! R% Holds RECORD value into NAMES.MAI ! R5% Holds next available RECORD for entry of a name ! into NAMES.MAI ! V$ MAIL version/edit level ! X% Integer work variable ! X$ Work string ! X1$,X2$ FIELD statement work strings ! Y% Another work integer variable ! 15 ! Modification History Version/Edit Edit Date Reason V06A-01 30-Jul-79 New release V06A-02 25-Sep-79 General repairs 19 ! M A I N P R O G R A M C O D I N G ! 20 A1$="$" 30 A2$="$" 35 ! About lines 20 & 30... Line 20 is the data file account pointer. It, when added to the filename in the data file OPEN, opens the data files which exist in a different account. It is this variable that makes MAIL and PSTOFC able to access the data files from an account different from the account that the data files exist in. Line 30 contains a reference variable. It is used in informational messages in MAIL and PSTOFC to direct the user to the programs in the accounts where they exist. Basically, A2$ tells the user where the MAIL package programs are to be RUN and where any comprehensive help files reside on disk account-wise. These lines are also in PSTOFC and serve the same purpose. ! 37 V$ = "V06A-02" ! Set up version/edit level 40 ON ERROR GOTO 30000 \ X$=SYS(CHR$(6%)+CHR$(-7%)) \ OPEN 'KB:' AS FILE 1% \ OPEN A1$+'ACCT.MAI' FOR INPUT AS FILE 2%,MODE 8192% \ OPEN A1$+'NAMES.MAI' FOR INPUT AS FILE 3%,MODE 8192% ! Open all files and set CTRL/C trapping 50 DIM #2%,A%(254%,254%) ! Dimension virtual array file 60 P%=PEEK(PEEK(PEEK(520%)+8%)+24%) \ P1%=SWAP%(P%) AND 255% \ P2%=P% AND 255% ! PEEK the PPN - P1% is the proj, P2% is the prog # 70 PRINT \ PRINT "MAIL "+V$+" "+ CVT$$(RIGHT(SYS(CHR$(6%)+CHR$(9%)),3%),4%) \ PRINT "System Postman" \ PRINT ! Output the system header 80 S%=SGN(A%(P1%,P2%)) \ IF S%=1% THEN 1000 90 PRINT "According to my records you are not eligible to" \ & "send or receive mail." \ GOTO 10000 1000 ! G R E E T T H E C U S T O M E R ! 1010 N$=FNN$(P1%,P2%) \ PRINT FNG$;LEFT(N$,INSTR(1%,N$," ")-1%);"." \ OPEN 'MAIL.BOX' FOR INPUT AS FILE 4% \ PRINT "You have received mail. Would you like me" \ & "to read it to you"; 1020 INPUT Q$ \ Q$ = LEFT(Q$,1%) \ IF Q$="Y" THEN 1030 ELSE IF Q$="N" THEN 1060 ELSE PRINT "Please answer Y[ES] or N[O]. Would you like me" \ & "to read your mail to you"; \ GOTO 1020 1030 PRINT 1040 INPUT LINE #4% L$ \ PRINT L$; \ GOTO 1040 1050 PRINT \ PRINT "[End of mail]" \ CLOSE #4% \ PRINT 1052 INPUT #1% "Would you like me to erase your mail? ";Q$ \ Q$ = LEFT(Q$,1%) IF LEN(Q$) \ IF Q$='N' THEN 1060 ELSE IF Q$ = '' OR Q$ = 'Y' THEN X$ = SYS(CHR$(6%)+CHR$(-21%)+CHR$(255%)) \ KILL 'MAIL.BOX' \ X$ = SYS(CHR$(6%)+CHR$(-21%)+CHR$(0%)) \ PRINT "Mail deleted." \ PRINT \ GOTO 1070 ! Read mail; Delete it? YES is default. Drop privileges temporarily ! before deleting to make sure file is not erase protected 1055 PRINT "Please answer Y[ES] or N[O]." \ PRINT \ GOTO 1052 ! Invalid response entered 1060 PRINT \ PRINT "Fine." \ PRINT ! Don't read mail 1070 ! G E T R E C I P I E N T ' S A C C O U N T ! 1075 F% = 0% ! Reset the disk file input flag 1080 PRINT "Please enter your recipient's account number." \ INPUT LINE #1% A$ \ A$=CVT$$(A$,4%+64%) \ IF A$='' THEN F% = -1% \ GOTO 1080 ! Read in account and get rid of garbage bytes ! Set proper flag and re-prompt user if a RETURN was struck, ! signalling that the user intends to do input not from the ! terminal, but from a file on disk. 1082 IF INSTR(1%,A$,'/') THEN F% = 0% IF F% ! If slash switch used, do not go prompt for a file if a RETURN was ! once typed to the account entry prompt 1085 GOTO 2100 IF F% ! Skip ahead if we will be doing disk file input 1090 ! A C C O U N T C H E C K I N G S E C T I O N ! 1100 I%=FNM%(A$) \ PRINT \ ON I% GOTO 1110,1120,1130 ! Dispatch based on validity check of FNM% 1110 PRINT "I'm sorry, but your recipient's mail service has been" \ & "suspended. I can't deliver any mail at this time." \ PRINT \ GOTO 1070 ! Service suspended - no delivery (Oh really?) 1120 PRINT "That account is not registered to receive mail. Sorry." \ PRINT \ GOTO 1070 ! Still no delivery - recipient not registered 1130 ! S E N D M A I L T O R E C I P I E N T ! 1140 OPEN A$+'MAIL.BOX<60>' AS FILE 4%,MODE 2% \ IF (STATUS AND 1024%) THEN CLOSE #4% \ PRINT "Mail is presently being delivered to your recipient." \ PRINT "Why don't you try again in a few minutes? The mail should" \ PRINT "be finished and sent by then. You may, however, send mail" \ PRINT "to a different recipient." \ PRINT \ GOTO 1070 ! Make sure the mailman isn't busy sending into the recipient's account 1142 GOTO 2000 IF F% ! Go try for an open on the specified disk file if disk file ! input requested 1145 X$=MID(SYS(CHR$(6%)+CHR$(9%)),2%,1%) \ X%=ASCII(X$)/2% ! Get KB number 1150 H$="Mail from "+FNN$(P1%,P2%)+" "+FNA$+" KB"+NUM1$(X%)+" "+ DATE$(0%)+" "+CVT$$(TIME$(0%),2%) \ PRINT #4% H$ \ PRINT #4% ! Form and print header to mail file (MAIL.BOX) 1155 GOTO 2020 IF F% ! Skip to proper routine if we are doing input from disk files 1160 X$=SYS(CHR$(6%)+CHR$(16%)+CHR$(0%)+CHR$(255%)+STRING$(17%,0%)+ CHR$(128%)) \ PRINT "Type in your message. Hit the ESC ('ESCAPE') key to" \ PRINT "terminate and send the mail." \ PRINT ! Set terminal so that ESC acts as a delimiter & tell user what to do 1170 INPUT LINE #1% M$ \ IF INSTR(1%,M$,CHR$(27%)) THEN 1190 ELSE PRINT #4% M$; \ GOTO 1170 1180 ! C L O S E & S E N D M A I L ! 1190 PRINT #4% LEFT(M$,INSTR(1%,M$,CHR$(27%))-1%) \ PRINT #4% \ CLOSE #4% \ PRINT 1195 PRINT \ PRINT "Your mail has been delivered." \ PRINT \ GOTO 1070 ! Send last line w/o ESC. Add extra print to KB and mail file 2000 ! ! D i s k F i l e I n p u t 2010 ON ERROR GOTO 2030 \ X$ = SYS(CHR$(6%)+CHR$(-21%)+CHR$(255%)) \ OPEN F$ FOR INPUT AS FILE 5%,MODE 8192% \ X$ = SYS(CHR$(6%)+CHR$(-21%)+CHR$(0%)) ! Set a modular error trap and open the specified input file ! Drop privileges before file open to used monitor protections ! against read access 2015 ON ERROR GOTO 30000 \ GOTO 1145 ! Once file is open, go back and print header 2020 ON ERROR GOTO 2030 2025 INPUT LINE #5% M$ \ PRINT #4% M$; \ GOTO 2025 ! Do loop line transferral 2030 IF ERL=2010% THEN PRINT "?Cannot open file - ";FNE$;" - file ";F$ \ PRINT \ ON ERROR GOTO 30000 \ X$ = SYS(CHR$(6%)+CHR$(-21%)+CHR$(0%)) \ RESUME 1070 ! If error on open, tell user and skip out, resetting standard ! error handler routine 2040 PRINT #4% \ CLOSE #4%,5% \ ON ERROR GOTO 30000 \ RESUME 1195 ! Close all files; reset error trap; go tell user mail was sent 2100 ! ! P r o m p t U s e r F o r A F i l e 2110 PRINT \ PRINT "Input file: "; \ INPUT LINE #1% F$ \ F$ = CVT$$(F$,4%) \ IF F$ = '' OR INSTR(1%,F$,'KB:') THEN F% = 0% \ GOTO 1090 ! Prompt user for a file from which the mail is to be sent; if ! user hits return or enters a string with a device specification ! of KB:, then reset flag and prepare to do terminal input 2120 F% = -1% \ GOTO 1090 ! Set disk file input flag 10000 ! S U B R O U T I N E S ! 10010 S%=SGN(A%(P1%,P2%)) \ IF S%=0% THEN 10030 10020 PRINT "Your mail privilege has been suspended by the system" \ & "manager. If you wish it restored, see him." \ PRINT \ GOTO 32000 10030 INPUT "Would you like to register";Q$ \ IF Q$="YES" THEN 10040 ELSE IF Q$="NO" THEN 32000 ELSE PRINT "Please answer YES or NO. "; \ GOTO 10030 10040 PRINT \ PRINT "OK then, enter your full name. Example: JOHN SMITH" \ PRINT "(Limit 22 characters)" \ INPUT #1% N$ \ CLOSE #2%,3% ! Get name and close files in preparation to get write access ! For reasons of neatness, limit name length to 22 characters 10050 OPEN A1$+'ACCT.MAI' AS FILE 2% \ IF (STATUS AND 1024%) THEN CLOSE #2% \ SLEEP 1% \ GOTO 10050 ! If no write access, try again until write access is obtained 10055 OPEN A1$+'NAMES.MAI' AS FILE 3%,MODE 2% \ IF (STATUS AND 1024%) THEN CLOSE #3% \ SLEEP 1% \ GOTO 10055 ! Go for write access on NAMES.MAI 10060 X$=FNR$(N$) \ CLOSE #2%,3% \ OPEN A1$+'ACCT.MAI' AS FILE 2%,MODE 8192% \ OPEN A1$+'NAMES.MAI' AS FILE 3%,MODE 8192% \ PRINT \ PRINT "You are now eligible to send and receive mail." \ PRINT \ GOTO 1000 ! Register user; re-open file read-only mode; notify user 20000 ! F U N C T I O N S ! 20010 DEF FNA$="["+NUM1$(P1%)+","+NUM1$(P2%)+"]" ! Format account for mail header 20020 DEF FNE$=CVT$$(RIGHT(SYS(CHR$(6%)+CHR$(9%)+CHR$(ERR)),3%),4%) 20030 ! FNL$ puts names into mixed case ! 20040 DEF FNL$(X$) 20050 FOR X%=1% TO LEN(X$) \ G$=MID(X$,X%,1%) \ A%=ASCII(G$) \ IF A%=32% OR A%=39% OR A%=45% OR A%=46% THEN G3$=G3$+G$ \ GOTO 20080 ! Make sure G$ isn't a space, apostrophe, hyphen, or a period. If so, add it to G3$. I love them G$'s!! 20060 G$=CHR$(A%+32%) \ G3$=G3$+G$ 20080 IF A%=39% OR A%=45% THEN X%=X%+1% \ G3$=G3$+MID(X$,X%,1%) 20085 NEXT X% 20090 ! At this time, ALL letters are lower case. Now bring up letters following spaces to UPPER CASE. 20100 G$=CHR$(ASCII(LEFT(G3$,1%))-32%) \ G3$=G$+RIGHT(G3$,2%) ! UC first letter 20110 G%=1% ! Set loop INSTR pointer 20120 G1%=INSTR(G%,G3$," ") \ IF G1%=0% THEN 20160 20130 G3$=LEFT(G3$,G1%)+CHR$(ASCII(MID(G3$,G1%+1%,1%))-32%)+ RIGHT(G3$,G1%+2%) ! Re-form string after UCing letter 20140 G%=G1%+1% \ GOTO 20120 20160 FNL$=G3$ ! Name is now in Mixed Case 20165 G3$='' 20170 FNEND 20180 DEF FNM%(X$) ! FNM% checks 'mailability' of recipient's account 20190 I% = INSTR(1%,X$,'/') \ GOTO 20200 UNLESS I% \ F$ = RIGHT(X$,I%+1%) \ X$ = LEFT(X$,I%-1%) \ F% = -1% ! Get a file specification if '/' switch used 20200 IF INSTR(1%,X$,"*") OR INSTR(1%,X$,"(") OR INSTR(1%,X$,")") OR INSTR(1%,X$,",")=0% OR LEN(X$)>7% THEN 20220 ! THAT should do the trick! 20210 C%=INSTR(1%,X$,",") \ P3%=VAL(LEFT(X$,C%-1%)) \ P4%=VAL(RIGHT(X$,C%+1%)) \ A$='['+X$+']' ! Get proj & prog - rectify string version of account for mailing 20215 S%=SGN(A%(P3%,P4%)) \ IF S%=0% THEN 20260 ELSE IF S%=-1% THEN 20270 ELSE 20280 ! Check mailability 20220 PRINT \ PRINT "That account number is unrecognizable to me." 20230 INPUT "Do you need help formatting it";Q$ \ Q$ = LEFT(Q$,1%) \ IF Q$="Y" THEN 20250 ELSE IF Q$="N" THEN 20240 ELSE PRINT "Please answer the question with a Y[ES] or a N[O]." \ PRINT \ GOTO 20230 20240 PRINT \ PRINT "OK then, try again." \ PRINT \ GOTO 1070 20250 PRINT \ & "Account must NOT contain wildcards such as '*'. Also, do not" \ & "enclose the account number in parentheses. A comma must separate" \ & "the project number from the programmer number, i.e. 150,10 etc." 20255 PRINT "You MUST know the account number of your recipient!!" \ & "If you don't, please use the LIST command of "+A2$+"PSTOFC." \ PRINT \ GOTO 1070 20260 FNM%=2% \ GOTO 20290 ! Account not registered 20270 FNM%=1% \ GOTO 20290 ! Account's service suspended 20280 FNM%=3% ! All clear - go ahead and send the mail 20290 FNEND 20330 DEF FNN$(X%,Y%) ! This function returns the name of person addressed by X% & Y% 20340 I% = A%(X%,Y%) \ R% = SWAP%(I%) AND 255% \ P% = I% AND 255% \ P% = P% + P% ! Get RECORD (R%) and position (P%) of name in name storage file 20350 GET #3%,RECORD R% \ FIELD #3%,P% AS G$,22% AS N$ \ N$=CVT$$(N$,4%+128%) \ FNN$=FNL$(N$) 20360 FNEND 20370 DEF FNR$(X$) ! This function registers the user 20380 IF LEN(X$)>22% THEN PRINT "That name is too long for my ledger. Please shorten it somehow." \ INPUT "Name";X$ \ GOTO 20380 20385 IF LEN(X$)<3% OR INSTR(1%,X$," ")=0% THEN PRINT "What is your FULL name"; \ INPUT X$ \ GOTO 20380 ! If no last name or too short, re-prompt user 20390 GET #3%,RECORD 1% \ FIELD #3%,2% AS X1$,2% AS X2$ \ R%=CVT$%(X1$) \ P%=CVT$%(X2$) ! Get position in REC I/O file to write new name to 20395 ! A little more documentation... ! ! In NAMES.MAI, names are stored in logical slots of 32 bytes. ! The result of this is that a maximum of 16 names can be stored ! in each block of NAMES.MAI. However, the first record of NAMES ! is different. The first slot is used to hold the 'MAIL index', ! which is the record and position of the next available name slot. ! The two values are in CVT%$ format, taking up 4 bytes. Because ! of this, record 1 of NAMES.MAI holds only 15 names. ! ! The other 28 bytes of the first name slot are reserved for ! future functionality. ! 20400 P5%=P%+32% \ R5%=R% \ IF P5%>480% THEN P5%=0% \ R5%=R%+1% 20410 LSET X1$=CVT%$(R5%) \ LSET X2$=CVT%$(P5%) \ PUT #3%,RECORD 1% ! Add 32% to P%. If it goes over 480 (highest offset value possible) then add 1% to R%. Use R5% and P5% so as not to change values of P% and R%. Finally, PUT values back into file for next user's registration. (Whew!) 20420 GET #3%,RECORD R% ! Try to clear up a problem that's been buggin' me 20430 FIELD #3%,P% AS G$,32% AS N5$ \ LSET N5$=CVT$$(X$,32%)+SPACE$(32%-LEN(X$)) \ PUT #3%,RECORD R% ! Force uppercase and PUT name into name storage file 20440 A%(P1%,P2%)=SWAP%(R%) OR (P%/2%) ! Set index value in ACCT.MAI to indicate that user is registered. ! Index holds RECORD (R%) and offset (P%) of name in NAMES.MAI 20450 FNEND 20500 DEF FNG$ ! Define the function to greet the user. MAIL greets the user so ! that he can make sure that his name is correct on the MAIL user ! ledger. 20510 X$ = "Good " \ T = TIME(0%) ! Initialize string; get seconds since midnight 20520 IF T < 43200 THEN X$ = X$ + "morning, " ELSE IF T < 64800 THEN X$ = X$ + "afternoon, " ELSE X$ = X$ + "evening, " ! Set the time period 20530 FNG$ = X$ 20540 FNEND 30000 ! E R R O R H A N D L I N G S E C T I O N ! 30010 IF ERR=28% THEN 32000 30020 IF ERL=1010% THEN RESUME 1070 30030 IF ERL=1040% THEN RESUME 1050 30035 IF ERL=1052% AND ERR=10% THEN PRINT "I am unable to erase your mail due to system security restraints." \ PRINT \ X$ = SYS(CHR$(6%)+CHR$(-21%)+CHR$(0%)) \ RESUME 1070 30040 IF ERL=20420% THEN RESUME 20430 30050 IF ERL=20210% OR ERL=20215% THEN RESUME 20220 30055 IF ERL=1140% AND ERR=5% THEN PRINT "That account doesn't exist. I can't send mail there, sorry." \ PRINT \ RESUME 1070 ! Can't mail to non-existent account, ya know! 30080 IF ERR=11% THEN RESUME 32000 30090 IF ERL=40% AND ERR=5% THEN PRINT "My data files are inaccessible. Please notify the system manager" \ & "of this problem so that this error condition can be fixed." \ PRINT \ RESUME 32000 30100 IF ERL=40% AND ERR=10% THEN PRINT \ PRINT "MAIL has been temporarily disabled - try again later" \ RESUME 32000 ! If either CLEAN or MAILUP is running and has shut down the MAIL ! system, tell user he can't use MAIL and abort 30200 PRINT "?Unexpected error - ";FNE$;" at line";ERL \ PRINT \ GOTO 32767 32000 ! E X I T ! 32010 CLOSE #1%,2%,3% \ PRINT \ PRINT "Good bye!" \ X$=SYS(CHR$(9%)) ! Print closing message 32767 END