This excerpt from COMPUTE! Books' new
title The Elementary Atari ST demonstrates how to work with the disk
system and how to create sequential text files and random access files.
A simple program for keeping and updating an address book illustrates
various file-handling techniques.
While programming, you will often find that subroutines which you wrote
for one application can be used in subsequent programs with no
modifications or with minor changes. ST BASIC provides the MERGE
command so that you can transfer these subroutines between programs
without retyping them. You have to make sure your subroutines do not
contain the same line numbers as other routines with which they will be
MERGEd, but, otherwise, MERGE is a simple procedure. For example, enter
and save this program:

You now have two programs saved as files. Neither
program will work by itself. If you tried to load them with the LOAD or
OLD command, as soon as you loaded the second one, the first one would
be wiped out. However, with the MERGE command, you can load them
separately. Once they are both loaded, you can run the combined
program, and, if you want, you can even save it as a BAS file. Key in
this sequence:

MERGE "PART1"MERGE "PART2"RUN

At this point everything should work just fine. Now
enter:

SAVE "COMBINE"

You now have a BASIC file made up of the two combined files. As you
collect useful subroutines, you can keep a record of their line number
ranges, and it will be possible to write a program simply by MERGEing
several subroutines.

Sequential Text Files
Of the two kinds of text files we will discuss, sequential files are
simpler to work with. Random access files are a little trickier, but
can be accessed faster than sequential files. Using sequential and
random access files, it is possible to enter data from a program and
store it as a text file. You can add to it, change it, and retrieve
data from the file.
Creating sequential files. The first step is to
write a formatting program which will create a sequential file. To
create a file:

OPEN "O",File#,"FILENAME"PRINT# or PRINT# USING or WRITE#CLOSE

These statements take care o£ everything we need in a sequential
file.
Now let's begin writing our address book program,
which will store the names of a known number of people who sent us
Christmas cards. (Then next Christmas we can check the file to see to
whom we should send cards.)

After you enter the program, run it, and remember
the number of names you entered. Save the pro gram under the name
MAKEFILE; we will come back to it later. Enter FILES from BASIC to make
sure there is a file called XMAS.DAT that was created by our MAKEFILE
program.
The next step is to read our files, using:

OPEN "I","FILENAME"INPUT# or LINE INPUT#EOF checkCLOSE

The next program will OPEN XMAS, INPUT the file,
check EOF (end-of-file), CLOSE the file, and then PRINT out the
contents to the screen. Notice the similarities and differences between
it and our previous program for writing files:

After you run the program, save it under the
filename READFILE. Using the EOF function, you do not have to know the
number of files you entered. If there are more files, EOF(1) (with 1
being the file number), then the value of NOT EOF(1) will equal 0. If
the value of NOT EOF(1) is equal to -1, then the program has found the
end-offile.
Using the WHILE-WEND statement, we check for the
case where EOF is not true. When this condition is met, the program
exits the loop. Notice that as soon as we INPUT# the data from the XMAS
file, we printed it to the screen using the normal PRINT statement.Appending
sequential files. So far, so good. We have a program that
outputs a list of names into a data file and one that inputs those
names back to us. What happens, though, if we want to add some names to
our file? Some versions of BASIC have an Append statement along with
Open and Input. However, while ST BASIC does not have such a statement,
it is a simple matter to append a sequential file. It involves two
steps:

Count the number of elements in a file and put them into an
array.

Enter the new elements at the end of the array, and
overwrite the old file with the combined data in the array.

You can use this method of appending files for
simple record keeping. If you're really ambitious, it is not too
difficult to edit the array while it is in memory and change the data.
However, we will soon be discussing random access files, and the random
files are probably better suited for creating files that will require a
good deal of manipulation.
Now we've seen how to output and input elements of a
single file. However, since filenames are essentially nothing but
strings, we could use variables to do much of the work automatically.
This next example, "File Manager," will create, append, and read any
text file you want. It handles only a single string element, but you
can change that if you want. Save the program under the name FILEMAN.A shortcut.
FILEMAN is relatively long, but certain
parts of it are very similar to earlier routines we have written.
Rather than retyping everything, we will use the MERGE, EDIT, and RENUM
functions. First, enter lines 10-120 from FILEMAN below, the MENU
block, and save these lines as FILEMAN. Load MAKEFILE, your program to
create sequential files. Then enter:

RENUM 130,10

and then enter:

MERGE FILEMAN

Compare the listings of previous programs we have
written to each block in the following listing. When you find a match,
follow the procedure described above to merge the routines into their
proper places in FILEMAN as you build your File Manager program.

Hand Me A Line
LINE INPUT and LINE INPUT # can be very handy commands for reading and
writing sequential files. For example, let's say you want to enter a
name, address, and phone number into an array, store the array on disk,
and later read it back. With LINE INPUT, it is possible to use a single
string or string array variable to put all that information in at once.
Likewise, when retrieving information from the disk, you can get a
whole line by using LINE INPUT #.
This is especially useful when you are reading a
file with an unknown format. For example, let's say that you want to
read the contents of a disk, but don't know whether it is composed of
strings or numeric values, and you don't know their order. By using
LINE INPUT # and a string variable, you can read the file line by line
rather than variable by variable.
To see how LINE INPUT works, enter the following
program. When you run it, be sure to include commas between the name,
address, and phone number. Unlike the INPUT statement, commas entered
from the keyboard when using LINE INPUT will not result in an error
message.

The program does not do anything with files, but it
would be a simple matter to have it PRINT # to the disk instead of to
the screen. Change the block beginning at line 200 to write the file to
disk. The name, address, and phone are in one string with the
delimiters preserved.
Now, we're going to write information to disk using
several variables, and then, using LINE IN PUT #, we are going to read
the disk with a single string variable. This will show you how to read
a line of variables that were stored either as separate variables or as
a single LINE INPUTed variable.

As you saw when the program executed, the variables,
along with their printed format established in line 150, were read and
displayed with a single string variable. This is where LINE INPUT # can
save time and guessing. Of course, it would have been even simpler to
use LINE INPUT when we entered the information originally, but the
program was designed to show you how LINE INPUT # works when reading
files created with several variables.
We also introduced another way to determine the end
of a file. While the EOF statement is the pre ferred method, you can
also use ON ERROR GOTO to jump out of an error. When the end-of-file
error occurs, the program jumps to the line that CLOSEs the file. Be
careful in using ON ERROR GOTO, because there will be times when some
bug in your program will cause an error rather than the error condition
you intended to trap for.

PRINT # USING And
Files
A final way to store information on disks is with PRINT # USING, which
sends data to the disk much like the PRINT USING statemem outputs to
the screen. The format is slightly different, but the statement works
essentially the same way. PRINT # USING is very handy in programs which
process formatted numeric data:

When you read your file, all of the data will be
formatted for you. Instead of using the variables you originally
employed, use LINE INPUT #. Thus, the following program will read and
display your information as you wrote it to disk:

Random Access Files
Random access files are like containers of equal size into which you
store data. You first decide how big a container you will need, based
on the maximum size of the material you will be putting in the box.
Each character in a string takes one byte. Therefore, if your maximum
length for a given string is ten, it will be necessary to allocate a
total of ten bytes. With numbers, storage is different. Here's a chart
for quick reference on how much memory space to allocate for the
different kinds of data:

Type

Allocation

String

1 byte per character

Integer

2 bytes per number

Single-precision

4 bytes per number

Double-precision

8 bytes per number

All entries into a random access file must be in
string format, including numbers; we will examine the functions for
doing that later. For now, we will concentrate on entering data as
normal strings.
For the most part, the process of creating and
reading random access files looks very much like sequential files, but
there are important differences. For instance, when you OPEN a random
access file, you must include the length of the file. First, as we did
with sequential files, we OPEN the file and place the filename in
quotes. However, instead of writing the mode, we indicate the file
number and the length of our file. Here is the format:

OPEN "R",#1,"NAMEFI",128

With this statement we can either write to the disk file or read from
it. Unlike with sequential files, we do not indicate whether the mode
is output or input when we OPEN a random access file.
Random access files can be undivided or divided.
Undivided files use the same length for every entry. For the most part,
it is pointless to use undivided files unless you are entering a single
field, such as a list of names with no other information, or when you
put all the information into a single string as we did with LINE INPUT
#. It is more useful to divide random access files into sections called
fields, with each field having a maximum length. The FIELD statement
expects a file number, width, and string variable:

FIELD #1,20 AS A$,10 AS B$,2 AS C$

The above statement sets the width of A$ to 20, B$
to 10, and C$ to 2. When the file is OPENed, the LENgth value (the last
value entered in the OPEN statement) must equal the sum of the FIELD
values. In the above example, the length must be 20 + 10 + 2 = 32. When
OPENing the R file (R is used for both input and output), the last
value would be 32.

OPEN "R",#1,"FILENAME",32

To illustrate using random access files, let's
modify our address book program. We will call the file we create
HOMETOWN, using three strings. Before we can enter the data into a
random access file, we have to use the LSET statement to store our
records in their respective fields. Moreover, the variable names we
LSET cannot be the same ones we INPUT. Therefore, we have two sets of
variables, one for INPUT and one for LSET. The nice thing about LSET is
that it automatically pads the strings with sufficient spaces to fit
the field exactly, or it truncates the string if it is too long.
Here is a list of our variable names:

NA$ for a person's name

LSET = N$

CT$ for the city's name

LSET = C$

SC$ for the state's mailing
code

LSET = S$

Since we'll be dealing with the names of people and
cities and thus the fields will be of differing lengths, we'll have to
decide on a maximum-size name. Longer names will be truncated to this
specified size. This process is extremely important in working with
random access files since we are limited to the number of bytes
specified when we OPEN a file. Without the truncate feature, entries
over the maximum length would spill over into the next record.
Therefore, we will limit the length of a name to 20, a city to 10, and
states to the two-character abbreviations employed by the post office:

N$

= 20

C$

= 10

S$

= 2

Total

= 32

Using these values, we can now write a program to
enter a single record into a random access file:

That was a lot of work to enter one simple record,
but be patient and we will do more. Now, we will GET# a record from a
random access file. As in writing to random access files, we must OPEN
the file with a specified length and read it in terms of a specified
record. The following program will read record 1 in the HOMETOWN file:

We had to write quite a lot just to write and read a
single record, but this illustrates how random ac cess files operate.
Now we can deal with multiple records with our HOMETOWN example.
Our next task is to create a sequential file to keep
track of our pointers in the random access file. Basically, a pointer
routine will check the sequential file and tell us which record number
was the last one we wrote, and then it will move the pointer to the
next record number. For instance, if there are ten records in a random
access file, we want the pointer 10 to be stored somewhere we can
easily get it. When we want to add to a random access file, we can then
find the value 10, add 1 to it, and begin writing our record at
position 11. We will call this file HOMEPOINT.

The first time you run this program, you must
initialize the POINT file. To do this, enter GOTO 400 the first time
you run the program.
Now that we have several records in our file, we
will need a way to get them out again. Here's where our counter
variable POINTER comes in handy. First, we will read POINTER to see how
many records there are and then loop through the records to GET# them
all. Notice that in line 60, we first INPUT#1 POINTER and after
INPUTing it into memory, it is used in the FORNEXT loop in line 150 to
pull all the records out.

By adding a few lines and calculating a few more
bytes, you can expand our example program into a very useful,
customized address list. The program already enters names, cities, and
states. All you have to add are addresses and zip codes, and there you
have it. By attaching a subroutine to send it to your printer, you
could generate your own mailing list program.

More File-Handling
Commands
Most file applications deal with strings, but there are many
applications which require the use of numbers instead. Instead of using
STR$ to convert numbers into strings, we can use MKI$, MKS$, and MKD$.
The I, 5, and D in the commands stand for Integer, Single-precision,
and Double-precision number conversions. Unfortunately, these
conversions translate your numbers into ASCII code, so you have to
convert them back to numbers using CVI, CVS, and CVD before you use
them in arithmetic operations.
Let's see how they are used with files. First,
create a numeric variable TOTAL. For a single precision number, TOTAL
would take four bytes, or two for integers and eight for double
precision. We will call our string SUM$, so we would define our FIELD
as:

FIELD #2, 4 AS SUM$

Then with LSET, we would put:

LSET SUM$ = MKS$(TOTAL)

Finally, once we read SUM$ from our file, if we want to reconvert it to
a numeric variable, we would enter:

TOTAL = CVS(SUM$)

By making the conversions to and from string and
numeric variables, we can store strings in ran dom access files, yet
use numeric variables in programs where the values are used as real
numbers. This applies only to random access files, for we saw how we
could store and update numeric variables in sequential files with no
conversions.
There is a great deal more you can do with files;
this introductory look at them just scratches the sur face. It is
possible to make database systems that search for individual records,
change individual records, sort records, and more.