Classic Computer Magazine Archive COMPUTE! ISSUE 40 / SEPTEMBER 1983 / PAGE 255

Relative Files For VIC-20
And Commodore 64
Part I

Jim Butterfield, Associate Editor

You can use relative files with your VIC or 64 computer and 1540/1541 disk drive. If you have the appropriate IEEE interface, you can do the same job on the 4040, 8050, or other recent Commodore disk units. It takes a little more work, and careful programming. But it can be done.
    All the examples given here will work on all PETs and CBMs. On 4.0 BASIC, there are easier ways, but this will work.



Binary Numbers: High And Low
We'll be talking about some numbers packed into ASCII characters. In the expression CHR$(N), we can't use a value of N greater than 255. Sometimes we will want to send larger numbers. For example, if we want to select record number 1000 of a relative file, we'll need to split it into two parts. The "high" part would be the number divided by 256; the low part would be the remainder. So a value of 1000 would split up into a high part of 3 and a low part of 232, since 1000 divided by 256 gives 3 with 232 remainder.
    When we send a number this way, we almost always send the low part first. So to send 1000, we'll eventually send to the disk:
    CHR$(232);CHR$(3).
    In Part II, we'll indicate a number that is split in this manner with the terms "High" and "Low."

Creating A Relative File
Decide how long you want a record to be. For example, you might have a file that will contain a name, a set of initials, and a date. You could allow 15 characters for the name; two characters for the initials; and seven characters for the date. Additionally, you'd need two extra characters as "separators" between the three data fields, giving a total of up to 26 characters in a record. You can go as high as 254, but no higher.
    When we create a relative file, we must give the record length. After it is created, we don't need to specify the length: the disk will remember.
    Let's open a relative file using direct statements. You can do this in a program, of course; but you may find it interesting to see things happening. First, however, we'll set up a program to allow us to check for errors on the command channel. Enter this program:

100 INPUT#15,E,E$,E1,E2
110 PRINT E;E$;E1,E2

Now type, as a direct command: OPEN 15,8,15. This will open the command channel for us. Anytime we want to look at a disk error condition, we can type GOTO 100, and the error will be printed.
    We're ready to open our relative file. Type:

OPEN 1,8,2,"RANDFIL,L,"+CHR$(26)

That does the job. The name of the new file is RANDFIL. The L stands for length: don't forget to put a comma both before and after. Finally, the CHR$(26) gives the length of our record. We don't need to use all 26 characters, but we must not exceed this value when we write a record.

Positioning To A Specific Record
We've created the file, but we have not written any records yet. It's a good idea to bring enough records into existence to fill more than one disk sector, which takes up 254 bytes. In the case of 26-character records, this means that we should create at least ten records.
    We could do this with ten PRINT#1 statements, but I'd like to show you another way. Let's position directly to record number 10 and write something there. Automatically, all missing records (in this case, 1 to 9) will come into existence. So we'd better learn how to position a relative file.
    Now, we send our "position" command down the command channel. To identify to the disk which file we want to position, we use the secondary address. For our relative file in progress, that would be 2. That's important: secondary address, not logical file number. Now, another thing about the disk: it likes to see you add 96 to the secondary address, so we should send 98.
    We have said that we want to go to record number 10. We must split this number up into high and low byte: we get 0 high and 10 low. Finally, we want to choose the start of the record, or position 1. Let's put it all together and type in:

PRINT#15,"P"+CHR$(96+2)+CHR$(10)+CHR$(0)+CHR$(1)

You'll see the disk error light go on - we'll account for this in a moment. To review: P for position; 96+2 for secondary address 2; 10 and 0 for record number 10; and 1 for the start of the record.
    Why did the error light go on? Because there is no record number ten - yet. You may type GOTO 100 and look at the error notice: you'll see RECORD NOT PRESENT, which makes sense. The moment we write something, we'll bring this missing record into existence. Let's do that:

PRINT#1,"DOAKES"+CHR$(13)+"J"+CHR$(13)+"AUG15";

We have just written record number ten. Note that we are using a Return character to separate the fields (name, initial, date), and note that the PRINT statement ends with a semicolon. This seems puzzling: it doesn't work that way on sequential files. Let's give the golden rule for writing relative files:

Rule: One PRINT statement writes one and only one record to a relative file.

So the semicolon at the end does not change anything: we've written a complete record. And the Return characters in the middle do not change anything: we've written only one record.
    Let's tie up this file for the moment. Close it with:

CLOSE 1

No need to close the command channel.

After Creation
Let's write a program to read and write this little file that we have created. Here we go:

100 OPEN 4,8,5,"RANDFIL"

I've changed the logical file number and secondary address just to prove that it doesn't matter. Note that we don't need to specify the length, once the file exists.

110 OPEN 15,8,15

    Now for the main user interface. We'll ask for a record number, and quit if the user types zero:

200 INPUT"RECORD NUMBER";R
210 IF R<1 GOTO 600

Let's position to the record:

220 R0=INT(R/256):R1=R-R0*256

R0 is the high part of the number, and R1 is low. Now we can position:

230 PRINT#15,"P"+CHR$(101)+CHR$(R1)+CHR$(R0)+CHR$(1)

We remember that 101 is 96 plus 5. Let's look for an error:

240 INPUT#15,E,E$,E1,E1
250 IF E<>0 THEN PRINT E$:GOTO 200

We've positioned at a valid record. Now let's ask if we want to read or write:

300 INPUT"R OR W";C$
310 IF C$="R" GOTO 400
320 IF C$="W" GOTO 500
330 GOTO 200

Now for the reading part. We are already positioned, but first we must learn another golden rule, this one for reading records:

Rule: Variable ST signals end of record with value 64.

This, too, is different from sequential files, where ST signals end-of-file. Now we can read however many fields are in the record, since we'll detect the end of record in ST:

400 F=0
410 F=F+I:INPUT#4,F$
420 PRINT "FIELD";F;":";F$
430 IF ST=0 GOTO 410
440 GOTO 200

Thus, we keep reading until we have gathered all the fields within the record. Now, for the writing part. We've been here before:

500 FOR F=1 TO 3
510 PRINT"FIELD";F;
520 INPUT F$(F)
530 NEXT F
540 PRINT#4,F$(1)+CHR$(13)+F$(2)+CHR$(13)+F$(3);
550 GOTO 200

That's it except for quitting. We must remember to close our file:

600 CLOSE 4

    Try playing with this program. You might like to try for nonexistent records, or writing records that are too large to fit. You'll quickly see how it all works. Note the curiosity: the character "pi," or CHR$(255), is stored in unused records.
    It's not too hard and can be very useful. With relative records, you can go directly to any chosen part of your file. You can read or write as desired.
    It's another tool for effective use of your computer.

Copyright © 1983 Jim Butterfield