|
by Guy Cousineau
This chapter
will cover the mechanics of data file manipulation in SMARTBASIC. We
will also touch on some examples of data file structure and some
productivity hints. The first part will only deal with the basic
mechanics of data file access. Article 2 will further refine the
strategies.
Before examining the commands for accessing data files, a bit about buffers.
SMARTBASIC has 3 1K buffers that are used for a variety of purposes. These are
located at D400, D800, and DC00. The last one resides just below the start of
the EOS which starts at E000. The first buffer is used to read the directory of
the medium. Even on directories greater than 1K, this is the only buffer used
for directories. The other 2 can be used for file access. They are used, for
example, when loading or saving a basic program. They can also be used to open
and read data files.
BASIC
automatically assigns one or the other to the file being accessed.
You can have 2 files open simultaneously but they must be on the same
drive. If you try to open 2 files on 2 different mediums, you will
get a NO BUFFERS AVAILABLE error since the directory buffer is
currently in use for the first file. This means that you cannot use
DATA FILE READ commands to transfer a file from one medium to another
unless you first close the input file before opening the output file.
The file
commands illustrated in this article, with the exception of MON and
NOMON, must be used in CONTROL-D format as was outlined in the
previous chapter. Accordingly, the sample programs illustrating the
commands will show a ^D in the print statements to signify the
CONTROL-D character. The OPEN command is used to open an existing file or create a new one. IF the file does not exist it is automatically created. This in itself can create problems. If you issue the OPEN command when you have the wrong disk in the drive, you will wind up creating an empty file. Your controlling program may behave strangely when it discovers that there is no data.
10PRINT "^DOPEN filename"
Alternately, the controlling program may prompt the user for a file name and pass it to the OPEN command:
10INPUT "File to read ";f$ 20PRINT "^DOPEN ";f$
Note that there
is a space after OPEN; it is required so BASIC can parse the OPEN
command. When you first create a DATA file the next thing you want to do is write data to it. The open command only makes the file available for access. You must tell BASIC that you want to write to it.
10INPUT "File to read ";f$ 20PRINT "^DOPEN ";f$ 30PRINT "^DWRITE ";f$
Since you may have more than one file open, the WRITE command must also include the file name. If you try to WRITE to a file that has not been OPENed, BASIC will complain. After the WRITE command has been issued, all PRINT statements will go to the file; nothing goes to the screen. Thus it is impossible to send informational messages to the screen while the WRITE command is in progress. All PRINT commands will be formatted in the file the same way that they would have appeared on the screen. The conclusion of a PRINT statement send a carriage return to the file to specify the end of the line. Thus, to send more than one STRING to the data file, you must use the ";" or "," characters:
10INPUT "File to read ";f$ 20PRINT "^DOPEN ";f$ 30PRINT "^DWRITE ";f$ 40PRINT "My name is Guy Cousineau" 50PRINT "This data is being sent to ";f$ 60PRINT 1, 2/3, 5.18, 564*765
Once you are finished writing to a file, you must CLOSE it to record the file on the medium, and to restore normal screen output:
10INPUT "File to read ";f$ 20PRINT "^DOPEN ";f$ 30PRINT "^DWRITE ";f$ 40PRINT "My name is Guy Cousineau" 50PRINT "This data is being sent to ";f$ 60PRINT 1, 2/3, 5.18, 564*765 70PRINT "^DCLOSE ";f$
Note again the
space after CLOSE and that the file name must be specified. Now we
have a program that will create a data file. Run this program, BOOT
SMARTWRITER, and have a look at the file. It should look like: My name is Guy Cousineau This data is being sent to testfile 1 .66666666 5.18 431460
Because the
print command on line 60 was ALL-IN-ONE-LINE, that is exactly the way
it will appear on the screen. DATA files may have lengths up to 128
characters, but if you intend to read them from SMARTWRITER, you
should limit line length to 80 characters.
Now return to
BASIC and run the test program again using the same file name. What
do you think will happen? Go back to SMARTWRITER and look at the
data file again; it looks just the same as before. That is because
the WRITE command always writes to the start of the file. This poses
a serious problem. If you supply the wrong name for your data file,
and it is a file that already exists, you will erase any previous
information on that file....that's a great way to lose a valuable
program. There is no fix for this; just be careful in choosing your
file names. There is a further complication. Run this program using the same TESTFILE name as before.
10INPUT "File to read ";f$ 20PRINT "^DOPEN ";f$ 30PRINT "^DWRITE ";f$ 40FOR x=1 TO 100 50PRINT "This is data line number ";x 60NEXT x 70PRINT "^DCLOSE ";f$
2 things will
happen. The program will abort with an error message, and your file
will still be open since the CLOSE command was not executed. The
latest revision of SMARTBASIC allows you to partially recover from
this problem by letting you issue the CLOSE command without the
control-d. When your program aborts, before doing anything else,
type the following:
CLOSE filename(the file name you used) or PRINT"^DCLOSE ";f$
There is disk
activity and the file closes. Check the file size and you will see
that it is 1K. If you take out your disk editor and consult the
catalog, you will see that the bytes-used-in-last-block is close to
if not exactly 1024. Run the program again using a file name that
does not exist on the medium; this time everything goes normally
provided you have sufficient room on the medium. You have now
created a 3K data file.
Why did this
happen? When the file was created, it only occupied 1K on the
medium. The subsequent write operation tried to go over this 1K
boundary and the file entry could not handle the extra size. This
will happen even if the file was the last one on the medium. This is
a drawback which could probably be fixed when the file is the last in
entry in the directory, I just have not had the ambition to try it.
We can learn one lesson from this. When creating a file which will
be updated later, you should blank fill it to a suitable length.
Thus when you read in your data, manipulate it, and write it back,
there will be sufficient room allocated in the directory. Once a file has been created, how do you get at the information stored on the file? That's when the READ command comes into play. Recreate the data file which contained your name and some numbers, and run the following program:
10INPUT "File to read ";f$ 20PRINT "^DOPEN ";f$ 30PRINT "^DREAD ";f$ 40INPUT name$ 50INPUT df$ 60INPUT number$ 70PRINT name$ 80PRINT df$ 90PRINT number$ 100INPUT "What do I do now ";a$
When you run the program, you will see a "?" appear on the screen for each INPUT statement. This is because the READ command has channelled normal keyboard input to the file and INPUT without a string prints a "?". Before we handle that problem, what happened at line 100? We got an OUT OF DATA error. That is because we did not close the file and BASIC is still trying to read input from the file. Since it has reached the end of the file, it aborts with an error....so let's close the file:
10INPUT "File to read ";f$ 20PRINT "^DOPEN ";f$ 30PRINT "^DREAD ";f$ 40INPUT name$ 50INPUT df$ 60INPUT number$ 65PRINT "^DCLOSE ";f$ 70PRINT name$ 80PRINT df$ 90PRINT number$ 100INPUT "What do I do now ";a$
Something is still going wrong! The CLOSE command appeared on the screen and I still get the annoying error message. Now we come back to those annoying question marks. Because they get printed on the screen, the CONTROL-D preceding the CLOSE command did not appear as the first character on the line. So let's get rid of the "?" by using a different INPUT statement.
10INPUT "File to read ";f$ 20PRINT "^DOPEN ";f$ 30PRINT "^DREAD ";f$ 40INPUT "";name$ 50INPUT "";df$ 60INPUT "";number$ 65PRINT:PRINT "^DCLOSE ";f$:REM make sure close is on a line 70PRINT name$ 80PRINT df$ 90PRINT number$ 100INPUT "What do I do now ";a$
I have added a
null string to each input statement which will cancel the "?".
Also, the additional PRINT statement in line 65 makes sure that the
^D will appear at the beginning of a line. Let's use the POSITION command to move to record number 3. POSITION is a way of advancing in a file by skipping the number of records specified. You can only skip forward. POSITION cannot be used to reset the file pointer backwards. Thus POSITION should have been named SKIP. The POSITION command clears any previous READ command and the READ command must be re-issued. The POSITION command includes the file name, a comma, and an "R" followed by the number of records to skip. Here's our new program.
10INPUT "File to read ";f$ 20PRINT "^DOPEN ";f$ 30PRINT "^DPOSITION ";f$",r2":REM skip 2 records 50PRINT "^DREAD ";f$ 60INPUT "";number$ 65PRINT:PRINT "^DCLOSE ";f$:REM make sure close is on a line 70PRINT name$ 80PRINT df$ 90PRINT number$ 100INPUT "What do I do now ";a$
and our data file looked like this:
My name is Guy Cousineau This data is being sent to testfile 1 .66666666 5.18 431460
Note that when the read program gets the third line, the numbers are read in as strings and it would be difficult to decode these numbers. One way is to use GET statements in your read routine. This modified program will read a number and print a new line after each one:
10INPUT "File to read ";f$ 20PRINT "^DOPEN ";f$ 30PRINT "^DREAD ";f$ 40INPUT "";name$ 50INPUT "";df$ 60x= 1 REM read first number 70GET q$:REM read a character from the file 80IF q$>="0" and q$ <="9" GOTO 150 90IF q$="." GOTO 150:REM include decimal point as well 100IF nonum=0 THEN PRINT:New line if first non number 110IF nonum=0 then x=x+1:REM that's one more number 120nonum=1:REM make sure we don't do this again 130if x=4 GOTO 200:REM we have read the last one 140GOTO 70:REM read another character 150nonum=0:REM we have a number 160PRINT q$;:REM print out one digit 170GOTO 70 200PRINT:PRINT "^DCLOSE ";f$:REM make sure close is on a line
That was a lot
of work! It would be even more work to interpret the digits and turn
them into a numeric variable. The moral is: don't concatenate
numbers on a line if you want to read them back as numbers! Print
out each number on a separate line when you create your data file.
The file will not be much bigger, just longer when you look at it. Let's consider a practical example. The following program paints random blocks on a GR screen. The latter half saves the screen to a file called GRSCREEN.
10GR 20FOR z=1 to 100 30COLOR=INT(RND(1)*16) 40x=INT(RND(1)*40) 50y=INT(RND(1)*40) 60PLOT x,y 70NEXT z 80PRINT "^DOPEN GRSCREEN" 90PRINT "^DWRITE GRSCREEN" 100FOR x=0 TO 39 110FOR y=0 TO 39 120PRINT SCRN(x,y) 130NEXT:NEXT 140PRINT:PRINT:"^DCLOSE GRSCREEN
The double loop from 100 to 140 interprets the screen colour and saves it to file. Later on, you want to restore that GR screen:
10GR 20PRINT"^DOPEN GRSCREEN" 30PRINT"^DREAD GRSCREEN" 40FOR x=0 TO 39 50FOR y=0 TO 39 60INPUT"";c 70COLOR=c 80PLOT x,y 90NEXT:NEXT 100PRINT:PRINT"^DCLOSE GRSCREEN"
When you write games, you may wish to have instructions that the player can view on screen prior to starting the game. If your program is very long, these PRINT or DATA lines will eat up valuable RAM space. Here's another application for a DATA file. Create an "A" file called GAMEDOC using the OPEN WRITE CLOSE commands. Just print a few blank lines into the file. Then, using SMARTWRITER, load in the GAMEDOC file and set your margins to 10 and 41. This will give you a 31 column screen compatible with SMARTBASIC. At the end of your file, put in an "@" character to signify the end of file. Do NOT use the "@" anywhere else in the file. Write in as many lines of text as you want, using any other punctuation. Then type in and RUN the following program:
10PRINT "^DOPEN GAMEDOC" 20PRINT "^DREAD GAMEDOC" 30GET q$ 40PRINT q$; 50IF q$="@" GOTO 100 60IF q$="." THEN FOR x= 1 TO 1000:NEXT 70IF q$=CHR$(13) THEN FOR x=1 to 2000:NEXT 80IF q$="," THEN for x=1 to 500:NEXT 90GOTO 30 100PRINT:PRINT"^DCLOSE GAMEDOC"
This short
routine will read in a document file of any length, and it takes up
only a few lines in your program. Lines 50 to 80 put in pauses for
periods, carriage returns, and commas. You may add in more pauses
for other characters if you wish. You can also experiment with the
wait values until you reach a comfortable reading speed. As we have seen, the GET and INPUT commands are channelled to the file when READ is active. But what if I want some user input? Lets' say I want to give the opportunity to abort the file read without crashing the program. This can be done using the last key press register. First, make sure that there is a <CR> in the register prior to starting by adding these 2 lines:
5INPUT "Press <CR> to start, any other key will abort";x 85IF PEEK(64885)<>13 GOTO 100
Line 5 forces a
<CR> by the use of an INPUT statement...this must be done prior
to opening the file. Line 85 makes a jump to the CLOSE command if
any key is pressed while viewing the documentation. You could even
interpret a special character which would pause the listing, or even
one to reduce or increase the pauses. We'll let you figure out how
to do that. Reading DATA files of unknown length can always cause problems. Where is the end of the file. Rather than rely on an end of file marker, you can use error trapping:
10PRINT "^DOPEN GAMEDOC" 20PRINT "^DREAD GAMEDOC" 25ONERR GOTO 95 30GET q$ 40PRINT q$; 60IF q$="." THEN FOR x= 1 TO 1000:NEXT 70IF q$=CHR$(13) THEN FOR x=1 to 2000:NEXT 80IF q$="," THEN for x=1 to 500:NEXT 90GOTO 30 95CLRERR 100PRINT:PRINT"^DCLOSE GAMEDOC"
The new line 25 sets an error trap to branch to the close routine. Note that pressing ^C will also generate an error that will close the file. The previous line 50 has been removed since we no longer need an end of file marker. Line 95 is crucial. If you do not clear the error trap, any subsequent errors would jump back to the close command and could lock up your program. You can use multiple error traps to make sure your program does not crash at any stage of a data file access operation:
5ONERR GOTO 200 10PRINT "^DOPEN GAMEDOC" 20PRINT "^DREAD GAMEDOC" 25ONERR GOTO 95 30GET q$ 40PRINT q$; 60IF q$="." THEN FOR x= 1 TO 1000:NEXT 70IF q$=CHR$(13) THEN FOR x=1 to 2000:NEXT 80IF q$="," THEN for x=1 to 500:NEXT 90GOTO 30 95CLRERR 100PRINT:PRINT"^DCLOSE GAMEDOC" 110END 200PRINT "Could not open data file" 210PRINT "Press 'q' to abort" 220PRINT "or any key to retry" 230GET q$ 240GOTO 5
In a situation
where you have accumulated a lot of data in memory and you are trying
to write it out to a data file, these kind of traps are essential. Back to an earlier problem. Remember in the last article when we tried to create our DATA file twice? Each OPEN WRITE sequence resets to the start of the file. But what if you want to add data to a file? Here's where you use APPEND. APPEND is smart: it knows you want to WRITE to the file name supplied. Thus OPEN and WRITE are replaced by APPEND. APPEND also knows that your file might wind up being bigger: it therefore recopies the entire file to the end of the directory prior to the write operation. This may take several seconds for a large file, (several minutes on tape). Try out the following program:
10INPUT "File to append ";f$ 20PRINT "^DAPPEND ";f$ 30INPUT "data (<CR> to end ";x$ 40PRINT x$ 50IF x$<>"" GOTO 30 60PRINT:PRINT"^DCLOSE ";f$
Run this program
several times, adding a few new lines of text". Once that is
completed, take out your favourite directory management utility (mine
is FILEMANAGER) and look at the tail end of the directory. You will
see several versions of your data file, a few bytes bigger each time.
You can clearly see that several append operations will quickly
gobble up all the remaining directory entries on your medium.
One additional
warning about writing to data files. When SMARTBASIC opens a file at
the end of the directory, it reserves all the remaining space to the
end of the disk/tape for the file. If you fail to close the file
properly, the storage medium will be rendered unusable.
MON and NOMON
refer to the program monitor. There are 4 control functions: MON Cmonitor commands MON Imonitor INPUT MON Omonitor OUTPUT MON Lmonitor LISTING
Several MON or
NOMON instructions can be issued at once with something like: NOMON c,o
to turn off the
monitor for output and commands. Command monitor will echo the OPEN
READ WRITE APPEND commands to the screen; this might be useful in
debugging. The INPUT monitor will echo characters read in using
INPUT or GET statements. The OUTPUT monitor will echo the data
written with PRINT statements. The LIST monitor will echo a program
as it is being loaded. Try MON L and follow with LOAD programname.
TECHNICAL SECTION:
OPEN executes at
24497 (5FB1). It gets the file name and record size if any (see next
article on random files). It tries to open the file and if there is
none a new file is created. It then allocates either of the 2 file
buffers to the opened file.
CLOSE executes
at 24612 (6024). It gets the file name and the drive, and checks to
see if the file is using buffer 1 or 2. It then trims the file and
flushes out any remaining characters. It then restores normal
keyboard input and closes the $$$$1 (or $$$$2) temporary file name.
If the close is successful, the file is renamed to the requested name
and releases the file buffer.
READ executes at
22049 (5621). It checks the file name against the buffers allocated.
It then decides if the file is random or sequential and jumps to the
appropriate handling routine. For sequential files, it is just a
matter of checking the buffers and setting the READ-FROM-MEDIUM
vector which channels INPUT and GET to read from the file.. For
random files, a complex series of calculations are made to determine
the location of the requested record.
WRITE executes
at 22455 (57B7). It also checks the file name buffer and decides
which output routine to select.
APPEND executes
at 21477 (53E5). After the syntax checking, it tediously copied the
file byte by byte to the temporary file at the end of the directory.
It then jumps to the middle of the WRITE routine to set up the
buffers.
POSITION
executes at 21715 (54D3). It check that the file is opened and looks
for the comma and "R" syntax characters. It then sets a
counter for the number of records to skip. The file is read and
carriage returns counted until they reach the requested skip. MON and NOMON share the same routine. They have different entry points which set up pointers to the execution vectors for the CIOL options. NOMON starts at 23042 (5A02) and MON at 23047 (5A07). It looks for the CIOL option character and aborts on error. Once a legal option is found, the execution address is extracted and executed. The routine takes over again to look for another option character. This continues until the end of the command or an error is detected. |