Classic Computer Magazine Archive COMPUTE! ISSUE 72 / MAY 1986 / PAGE 112

Managing Files
From
Atari ST BASIC

Wiliam Sanders

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:

10 CLEARW 2:FULLW 2
20 FOR X=1 TO 110 STEP 10
30 NA$="NAME#"+STR$(X)
40 GOSUB 200
50 NEXT
60 END

Now enter:

SAVE "PART1"

After you have done that, enter NEW and do the next program.

200 REM *****************
210 REM CENTERING ROUTINE
220 REM *****************
230 L=40-INT(LEN(NA$)/2)
240 PRINT TAB(L)NA$
250 RETURN

Next enter:

SAVE "PART2"

    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.)

10 FULLW 2:CLEARW 2
20 GOTOXY 1,10:INPUT "HOW MANY
   ENTRIES";N%
30 DIM NA$(N%):CLEARW 2
40 FOR X=1 TO N%
50 GOTOXY 1,10:PRINT"NAME#
   ";X;SPACE$(40)
60 GOTOXY 9,10:INPUT NA$(X)
70 NEXT X
100 REM ***********************
110 REM PUT DATA INTO SEQUEN-
    TIAL FILE
120 REM ***********************
130 OPEN "O",#1,"XMAS.DAT"
140 FOR X=1 TO N%
150 PRINT#1,NA$(X)
160 NEXT X
170 CLOSE

    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 check
CLOSE

    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:

10 FULLW 2:CLEARW 2
20 REM **********************
30 REM READ FROM SEQUENTIAL
   FILE
40 REM **********************
50 OPEN "I",#1,"XMAS.DAT"
60 WHILE NOT EOF(1)
70 INPUT#1,NA$
80 PRINT NA$
90 WEND
100 CLOSE

    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:

  1. Count the number of elements in a file and put them into an array.
  2. Enter the new elements at the end of the array, and overwrite the old file with the combined data in the array.


10 FULLW 2:CLEARW 2
20 REM ********************
30 REM READ FROM SEQUENTIAL
FILE
40 REM ********************
50 INPUT "HOW MANY NEW NAMES
   TO ADD";NN
60 OPEN "I",#1,"XMAS.DAT"
70 WHILE NOT EOF(1)
80 INPUT#1,NA$
90 N=N+1
100 WEND
110 CLOSE
200 REM ********************
210 REM LOAD DATA INTO ARRAY
220 REM ********************
230 OPEN "I",#1,"XMAS.DAT"
240 DIM NA$(N+NN)
250 FOR X=1 TO N
260 INPUT#1,NA$(X)
270 NEXT
280 CLOSE
300 REM ************
310 REM ADD NEW DATA
320 REM ************
330 FOR X=1 TO NN
340 INPUT "NAME PLEASE";NA$(X+N)
350 NEXT
400 REM *******************
410 REM COMBINE OLD AND NEW
420 REM *******************
430 N%=N+NN
440 OPEN "O",#1,"XMAS.DAT"
450 FOR X=1 TO N%
460 PRINT#1,NA$(X)
470 NEXT
480 CLOSE

    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.

File Manager

10 REM ****
20 REM MENU
30 REM ****
40 FULLW 2:CLEARW 2:RESTORE:
   CLEAR
50 FOR X=1 TO 4:READ CHOICE$
60 GOTOXY 5,X*3:PRINT X;",
   ";CHOICE$
70 NEXT X
80 GOTOXY 3,17:PRINT "CHOOSE BY
   NUMBER";
90 A$=INPUT$(1):A=VAL(A$)
100 ON A GOSUB CREATE,APPEND,
    VIEW, EXIT
110 DATA CREATE NEW FILE,ADD TO
    FILE,READ FILE,QUIT
120 GOTO 40
130 REM ***************
140 REM CREATE NEW FILE
150 REM ***************
160 CREATE:
170 FULLW 2:CLEARW 2
180 INPUT "NAME OF FILE";NF$
190 GOTOXY 1,10:INPUT "HOW
    MANY ENTRIES";N%
200 DIM NA$(N%):CLEARW 2
210 FOR X=1 TO N%
220 GOTOXY 1,10:PRINT "NAME#
    ";X;SPACE$(40)
230 GOTOXY 9,10: INPUT NA$(X)
240 NEXT X
250 REM *********************
260 REM PUT DATA INTO SEQUEN-
    TIAL FILE
270 REM *********************
280 OPEN "O",#1,NF$
290 FOR X=1 TO N%
300 PRINT#1,NA$(X)
310 NEXT X
320 CLOSE
330 RETURN
340 REM ********************
350 REM ADD TO EXISTING FILE
360 REM ********************
370 APPEND:
380 FULLW 2:CLEARW 2
390 REM ***********************
400 REM READ FROM EXISTING FILE
410 REM ***********************
420 INPUT "NAME OF FILE TO
    APPEND";NF$
430 INPUT "HOW MANY NEW NAMES
    TO ADD";NN
440 OPEN "I",#I,NF$
450 WHILE NOT EOF(1)
460 INPUT#1,NA$
470 N=N+1
480 WEND
490 CLOSE
500 REM ********************
510 REM LOAD DATA INTO ARRAY
520 REM ******'*************
530 OPEN "I",#1,NF$
540 DIM NA$(N+NN)
550 FOR X=1 TO N
560 INPUT#1,NA$(X)
570 NEXT
580 CLOSE
590 REM ************
600 REM ADD NEW DATA
610 REM ************
620 FOR X=1 TO NN
630 INPUT "NAME PLEASE";NA$(X+N)
640 NEXT
650 REM "******************
660 REM COMBINE OLD AND NEW
670 REM *******************
680 N%=N+NN
690 OPEN "O",#1,NF$
700 FOR X=1 TO N%
710 PRINT#1,NA$(X)
720 NEXT
730 CLOSE
740 RETURN
750 REM *************************
760 REM READ FROM SEQUENTIAL FILE
770 REM *************************
780 VIEW:
790 FULLW 2:CLEARW 2
800 INPUT "FILE TO READ";NF$
810 OPEN "I",#1,NF$
820 WHILE NOT EOF(1)
830 INPUT#1,NA$
840 PRINT NA$
850 WEND
860 CLOSE
870 PRINT :PRINT "HIT ANY KEY TO
    RETURN TO MENU"
880 W$=INPUT$(1)
890 RETURN
900 REM ************
910 REM QUIT PROGRAM
920 REM ************
930 EXIT:
940 END

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.



10 REM **********
20 REM LINE INPUT
30 REM **********
40 FULLW 2:CLEARW 2
50 GOTOXY 5,5:INPUT "HOW MANY
   ENTRIES";N%
60 DIM NAP$(N%)
70 CLEARW 2
80 FOR X=1 TO N%
90 LINE INPUT "Name, Address, Phone
   ";NAP$(X)
100 NEXT X
200 REM ***********************
210 REM PRINT RESULTS TO SCREEN
220 REM ***********************
230 CLEARW 2
240 FOR X=1 T ON%
250 PRINT NAP$(X)
260 NEXT

    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.

10 FULLW 2:CLEARW 2
20 GOTOXY 5,5:INPUT "HOW MAN
   Y ENTRIES";N%
30 GOTOXY 5,5: PRINT SPACE$(40):DI
   M NA$(N%),AD$(N%),PH$(N%)
40 FOR X=1 TO N%
50 INPUT "NAME";NA$(X)
60 INPUT "ADDRESS";AD$(X)
70 INPUT "PHONE";PH$(X)
80 NEXT X
100 REM **************
110 REM OUTPUT TO DISK
120 REM **************
130 OPEN "O",#1,"NAMEAD"
140 FOR X=1 TO N%
150 PRINT#1,NA$(X);",";AD$(X);",";PH$(
    X)
160 NEXT X
170 CLOSE
200 REM *********************
210 REM READ WITH LINE INPUT#
220 REM *********************
230 CLEARW 2:GOTOXY 5,5:PRINT "Hi
    t any key to continue";
240 AN$=INPUT$(1):CLEARW 2
250 OPEN "I",#1,"NAMEAD"
260 ON ERROR GOTO 300
270 LINE INPUT#1,NAP$
280 PRINT NAP$
290 GOTO 270
300 CLOSE
310 LOOK$=INPUT$(1)
320 END

    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:

10 REM ******************
20 REM ENTER NUMERIC DATA
30 REM ******************
40 FULLW 2:CLEARW 2
50 GOTOXY 5,5:INPUT "HOW MAN
   Y ENTRIES";N%
60 GOTOXY 5,5:PRINT SPACE$(20):DI
   M AMOUNT(N%)
70 FOR X=1 TO N%
80 GOTOXY 10,5:PRINT SPACE$(2
   0):GOTOXY 5,5:INPUT "HOW MUC
   H";AMOUNT(X)
90 NEXT X
100 REM ***********************
    ***
110 REM WRITE TO DISK WITH PRIN
    T #, USING
120 REM ***********************
    ***
130 GOTOXY 5,5:PRINT SPACE$(20)
140 OPEN "O",#1,"EXPENSES"
150 FOR X=1 TO N%
160 TOTAL=TOTAL+AMOUNT(X)
170 PRINT #1,USING "####.##";AM
    OUNT(X),TOTAL
180 NEXT X
190 CLOSE
200 PRINT "Hit any key":LOOK$=INP
    UT$(1)

    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:

300 OPEN "I",#1,"EXPENSES"
310 ON ERROR GOTO 350
320 LINE INPUT#1,EXPENSE$
330 PRINT EXPENSE$
340 GOTO 320
350 CLOSE
360 PRINT "Hit any key":LOOK$=
    INPUT$(1)

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:

10 CLEARW 2:NR%=1
20 GOTOXY 1,4:INPUT "NAME";NA$
30 GOTOXY 1,6:INPUT "CITY";CT$
40 GOTOXY 1,8:INPUT "STATE
   CODE";SC$
100 REM *******************
110 REM WRITE SINGLE RECORD
120 REM *******************
130 OPEN "R",#1,"HOMETOWN",32
140 FIELD #1,20 AS N$,10 AS C$,2 AS
    S$
150 LSET N$=NA$
160 LSET C$=CT$
170 LSET S$=SC$
180 PUT #1,NR%
190 CLOSE
200 END

    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:

10 CLEARW 2:NR%=1
20 REM ******************
30 REM READ SINGLE RECORD
40 REM ******************
50 OPEN "R",#1,"HOMETOWN",32
60 FIELD #1,20 AS N$,10 AS C$,2 AS S$
70 GET #1,NR%
80 PRINT N$:PRINT C$;",";S$
90 CLOSE
100 END

    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.

10 CLEARW 2:GOTOXY 1,1
20 INPUT "How many new entries";NE%
30 FOR X=1 TO NE%
40 GOTOXY 4,4: PRINT SPACE$(25):GO
   TOXY 1,4: INPUT "NAME";NA$
50 GOTOXY 4,6:PRINT SPACE$(25):GO
   TOXY 1,6:INPUT "CITY";CT$
60 GOTOXY 7,8:PRINT SPACE$(25):GO
   TOXY 1,8:INPUT "STATE CODE";S
   C$
70 IF X=1 THEN GOSUB 100 ELSE GOS
   UB 200
80 NEXT X
90 GOTO 400
100 REM *****************
110 REM FIND LAST POINTER
120 REM *****************
130 OPEN "I",#1;"POINT"
140 INPUT #1,POINTER%
150 CLOSE
200 REM *****************
210 REM WRITE RANDOM FILE
220 REM *****************
230 POINTER%=POINTER%+1
240 OPEN "R",#2,"HOMETOWN",32
250 FIELD #2,20 AS N$,10 AS C$,2 A
    S S$
260 LSET N$=NA$
270 LSET C$=CT$
280 LSET S$=SC$
290 PUT #2,POINTER%
300 CLOSE
310 RETURN
400 REM **************
410 REM UPDATE POINTER
420 REM **************
430 OPEN "O",#1,"POINT"
440 PRINT#1,POINTER%
450 CLOSE
460 END

    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.

10 FULLW 2:CLEARW 2
20 REM *****************
30 REM FIND LAST POINTER
40 REM *****************
50 OPEN "I",#1,"POINT"
60 INPUT #1,POINTER%
70 CLOSE
100 REM ****************
110 REM READ RANDOM FILE
120 REM **********'*****
130 OPEN "R",#2,"HOMETOWN",32
140 FIELD #2,20 AS N$,10 AS C$,2 AS
    S$
150 FOR X=1 TO POINTER%
160 GET #2,X
170 PRINT N$:PRINT C$",";S$:PRINT
180 NEXT
190 CLOSE
200 LOOK$=INPUT$(1)

    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.