💾 Archived View for mirrors.apple2.org.za › archive › ground.icaen.uiowa.edu › MiscInfo › Programmin… captured on 2023-01-29 at 10:16:22.

View Raw

More Information

-=-=-=-=-=-=-


On 30 May 2002 05:10:41 -0700, ludditetech@mac.com (Simon Williams)
wrote:

>Thanks for that very informative response... just one further
>question:
>
>Suppose the user is a jerk and enters an invalid or non-existent file
>name, the resulting SYNTAX ERROR or OUT OF DATA with all the beeping
>is quite unlovely... is there a way to keep the program running and
>merely ask the user to re-enter the filename?

Comments
~~~~~~~~
Sorry I didn't have time to reply to this earlier.  Life has gotten
insane for me this past week.

I see Charlie has given you information about using ONERR GOTO.
Unfortunately, in this case it doesn't quite work the way you are
thinking.  At least, not under ProDOS.  I can't remember if DOS 3.3
acts the same way or not.

When you use an open command under ProDOS, it will create the file if
it doesn't exist so it will not return a "file not found" error.  It
will return errors if you try to read data out of the empty file,
however, so you could trap that type of error in your ONERR GOTO
routine.  PEEK(222) will return 5 (END OF DATA) if you try reading
data from an empty file or try reading data past then end of the file
and that is what Charlie's program looks for in the ONERR routine.

Just so you have it, here is a list of ProDOS errors that BASIC.SYSTEM
can return:

     0  - no error occurred
     2  - RANGE ERROR
     3  - NO DEVICE CONNECTED
     4  - WRITE PROTECTED
     5  - END OF DATA
     6  - PATH NOT FOUND
     7  - PATH NOT FOUND
     8  - I/O ERROR
     9  - DISK FULL
     10 - FILE LOCKED
     11 - INVALID PARAMETER
     12 - NO BUFFERS AVAILABLE
     13 - FILE TYPE MISMATCH
     14 - PROGRAM TOO LARGE
     15 - NOT DIRECT COMMAND
     16 - SYNTAX ERROR
     17 - DIRECTORY FULL
     18 - FILE NOT OPEN
     19 - DUPLICATE FILE NAME
     20 - FILE BUSY
     21 - FILE(S) STILL OPEN
     22 - DIRECT COMMAND

Another way is to check the directory for the file yourself but this
can be a little involved but here my reader program from before with
added code to handle it this way with a few semi-useful subroutines:

New Sample Code
~~~~~~~~~~~~~~~
5 HOME: D$ = CHR$(4)
10 PRINT D$;"PREFIX": INPUT PF$: DP$ = PF$ + "DATA/"

100 PRINT "Press <RETURN> to quit.": INPUT "View what file? "; X$
105 IF X$ = "" THEN END
110 GOSUB 1005: IF A$ = "" THEN PRINT "That file doesn't exist.": GOTO
          100
115 IF A% = 0 THEN PRINT "That file is empty.": GOTO 100
120 PRINT D$;"OPEN";DP$;X$
125 PRINT D$;"READ";DP$;X$
130 INPUT A, B, C
135 PRINT D$;"CLOSE"
140 PRINT X$;"'s three favourite numbers are: ";A;", ";B;" and ";C
145 END

1000 REM Checked to see if pathname in X$ exists and whether it
          contains data
1001 REM Returns filename in A$, filetype in FT$ and file length in A%
1002 REM If A$ = "" then filename in X$ was not found
1003 REM If A% = 0 then file is empty.
1004 REM For directories, A% will return a multiple of 512
1005 GOSUB 2005: GOSUB 3005: IF A% <> 0 THEN A$ = "": FT$ = "": A% =
          0: RETURN
1010 OP$ = DP$: IF LEFT$(X$, 1) = "/" THEN OP$ = ""
1015 A% = LEN(X$)
1020 A$ = MID$(X$, A%, 1): IF A$ <> "/" AND A% > 1 THEN A% = A% - 1:
          GOTO 1020
1025 IF A$ = "/" THEN OP$ = OP$ + LEFT$(X$, A%): GOTO 1035
1030 A% = A% - 1
1035 OF$ = RIGHT$(X$, LEN(X$) - A%)
1040 PRINT D$;"OPEN";OP$;",TDIR"
1045 PRINT D$;"READ";OP$
1050 INPUT A$: INPUT A$: INPUT A$
1055 INPUT A$: IF MID$(A$, 2, LEN(OF$)) = OF$ OR A$ = "" THEN 1065
1060 GOTO 1055
1065 PRINT D$;"CLOSE";OP$
1070 IF A$ = "" THEN A% = 0: RETURN
1075 FT$ = MID$(A$, 18, 3): A% = VAL(MID$(A$, 64, 8)): A$ = OF$
1080 RETURN

2000 REM Convert string X$ to all upper case
2005 A$ = "": FOR A = 1 TO LEN(X$)
2010 A% = ASC(MID$(X$, A, 1))
2015 IF A% > 96 AND A% < 123 THEN A$ = A$ + CHR$(A% - 32): GOTO 2025
2020 A$ = A$ + CHR$(A%)
2025 NEXT A
2030 X$ = A$: RETURN 

3000 REM Check X$ to see if it contains a valid pathname
3001 REM Returns A% as 0 meaning pathname is valid
3002 REM Returns A% as 1 meaning first character is not a letter
3003 REM Returns A% as 2 meaning there is at least one invalid
          character in pathname
3004 REM Returns A% as 3 meaning there is a file/directory name is
          longer than 15 characters
3005 A$ = X$
3010 IF LEFT$(A$, 1) = "/" THEN A$ = RIGHT$(A$, LEN(A$) - 1)
3015 IF LEFT$(A$, 1) < "A" OR LEFT$(A$, 1) > "Z" THEN A% = 1: RETURN
3020 B% = 1
3025 C$ = MID$(A$, B%, 1): IF C$ <> "/" AND C$ <> "." AND (C$ < "A" OR
          C$ > "Z") AND (C$ < "0" OR C$ > "9") THEN A% = 2: RETURN
3030 IF C$ <> "/" AND B% < LEN(A$) THEN B% = B% + 1: GOTO 3025
3035 IF B% > 16 THEN A% = 3: RETURN
3040 IF LEN(A$) > B% THEN A$ = RIGHT$(A$, LEN(A$) - B%): GOTO 3020
3045 A% = 0: RETURN

New Sample Code Description
~~~~~~~~~~~~~~~~~~~~~~~~~~~
What's different in this version is, to start, I've renumbered
everything and use line numbers incrementing by 5 instead of 10.

The first block of code clears the screen and initializes variables.
D$ is the CTRL-D we discussed before.  I also do a ProDOS PREFIX
command from within the program and the first input command after that
will get the current ProDOS prefix.  I then set the DataPath (DP$) to
that prefix + the word "DATA/".  The trailing slash is needed in this
case as we will be adding other data to the end of it.

The second block of code is almost the same as my last example.  I've
added an option to allow the user to quit by simply pressing return
instead of entering data.  Then in lines 110 and 115 I've added a call
to a subroutine that will ensure the data typed in by the user is all
in upper case, verify that the pathname we are looking for is valid
and will then make sure it exists and has data in it.  The rest is
pretty much the same.  One note, however, is that you could add a line
106 that says X$ = DP$ + X$ and then replace the ;DP$;X$ parts of
lines 120 and 125 to just ;X$.  The subroutine at line 1000 can take
either a full or partial pathname so either way works.

The next block I will skip over for now as the first thing is does is
call the other two subroutines.

The subroutine at line 2000 will make sure that all alphabetic
characters in X$ are uppercase and will leave all other characters as
they were.  It simply does a loop looking at each character in X$ to
see if it is between "a" and "z" (ASCII 97-122) and if it is, change
it to the same character in the range "A" to "Z" (ASCII 65-90).  It
builds a new copy of the string as it goes the the loop and after the
loop finishes, it sets X$ to that new copy which no longer has any
lower case letters in it.

The subroutine at line 3000 checks to see if the full or partial
pathname in X$ is a valid ProDOS path.  It first sets A$ to X$ so that
the modifications I make as I parse the path don't end up changing the
original value of X$.  Line 3010 just checks to see if the first
character is a slash ("/").  If it is we are dealing with a full
pathname but I'm not worried about that so I strip that character off.
Line 3015 checks to make sure the first character in the string is a
letter as ProDOS filenames must start with a letter and if it isn't,
it sets A% to 1 and returns.  3020 initializes a counter which keeps
track of where in the file/directory name I am.  Line 3025 checks to
make sure that all subsequent characters are either a slash, period,
upper case letter or number as these are the only valid characters in
ProDOS filenames.  If there is an invalid character in the
file/directory name, A% gets set to 2 and the subroutine returns to
the calling routine.  Line 3030 just checks to see if we are at the
end of a directory name (C$ = "/"), the end of a filename (B% =
LEN(A$)) and if not, keeps looping until we are.  Line 3035 then
checks to see if the file/directory name is longer than 15 characters,
the maximum length of ProDOS filenames and if so, sets A% to 3 and
returns.  Line 3040 checks to see if we've finished processing the
entire string and if we haven't, goes back to 3020 to continue with
the next file/directory name.  3045 just sets A% to 0 as everything
came out okay and returns.

Now, going back to the subroutine at 1000, it calls the routine at
2000 to convert X$ to upper case.  It then calls the routine at 3000
and checks for an error from that routine and if it got one, it sets
A$ and FT$ to null strings (""") and sets A% to 0 and returns.  You
could actually change this slightly so that you could pass the errors
that you got from the routine at 3000 back to the calling routine so
that they could put up a more informative reason why the program
couldn't process the filename the user gave as at present, this
program will just display the "That file doesn't exist" message if the
problem is actually that the filename is invalid.  If the pathname is
okay, then we set OurPath (OP$) to the DataPath and then check to see
if we are dealing with a full pathname or a partial pathname.  If it
is a full pathname (path starts with a slash), then we reset OurPath
to a null string.  1015 initializes a counter to work our way
backwards through X$ to find out the actual filename and to pull out
any the full or partial directory path to that file.  1020 does the
actual loop.  1025 pulls out any directory information from X$ if
there is any there and 1035 pulls out the file name and puts it in
OurFile (OF$).  1030 is needed because A% is out by one if X$ holds
only a file name (no slashes in X$) so we need to adjust it.  This
line is skipped if there were slashes in X$.  Line 1040 - 1065 open
OurPath which is where OurFile should be stored and searches through
the directory to make sure it is there.  Note that line 1040 ends in
;",TDIR".  This tells ProDOS the type of file we are opening, in this
case, it is a directory.  Line 1050 just pulls the top three lines of
the file out which contain the pathname, a line with headers for each
column in the directory listing and a blank line.  Line 1055 reads in
a line, checks to see if it is listing OurFile or whether it is
another blank line and keeps looping if it isn't.  If it is either of
those it just to 1065 to close OurPath and then checks to see if we
got the blank line that indicated the end of the directory.  If we
did, it sets A$ and FT$ to null strings and A% to 0 and returns as we
didn't find the file.  If we did find the file, line 1075 set FT$ to
the file type, A% to the ENDFILE column which I believe almost always
lists the length of the file in bytes and sets A$ to OurFile to let
the calling routine know that we found that file.

Wrap Up
~~~~~~~
After spending the time doing up this code and while writing up the
description of how it works, I realize that some of this isn't
necessary and that ProDOS and an ONERR handling would probably deal
with it better.  Also, this version could also benefit from using an
ONERR routine to at least catch FILE TYPE MISMATCH errors.  One way to
avoid most of them is to use the FT$ that I added at the last minute
to the subroutine in line 1000.  Just change line 120 to:

120 PRINT D$:"OPEN";DP$;X$;",T";FT$

and that will solve that problem but it is still possible to run into
a FILE TYPE MISMATCH in the the subroutine at line 1000 as it assumes
that OP$ will be a valid directory and not pointing to some other file
type.  There are other problems that an ONERR routine would be able to
catch in this sample too, the file type one is just a sample.

That being said, I really enjoyed putting this together and I'm sure
there is some interesting bits of code in there that others can
benefit from so I'm posting it anyways.  The section where I read
through the directory is always useful.  The only other thing to
mention about it is after the second blank line (the one that tells my
program we've reached the end of the directory listing) there is
another line that lists the blocks free, blocks used and total blocks
of the current volume which is sometimes handy to know.

Finally, I will mention one other way that you could make sure users
only pick valid files and that is to give them a menu of files that
they are allowed to choose from.  I wrote a program launcher called
Selector v1.0 which you could use to look at how I did a menu system.
I can't seem to locate the final version but I did find a BINscii
encoded version of the beta I released and it is identical to the
final if I remember correctly.  It can be downloaded from:

     ftp://ground.ecn.uiowa.edu/2/apple8/Pgms/selector10beta.bsq

This is a multi-column menu system, though, and you might prefer a
single column version but there should be source code available to do
this and I'm sure I've got some lying around here somewhere too.  If
not, I can always right a new one.  :-)

By the way, there may be typos and other problems (almost certainly
grammatical errors :-) in my comments but it is just a tad too long
for me to spend the time to proof read it all again.  My apologies in
advance for any mistakes.

-- 
 Jeff Blakeney - Dean of the Apple II University in the
                 Apple II Community on Syndicomm.com
 CUT the obvious from my address if you want to e-mail me