Random Access I/O in early BASIC
There are numerous random access I/O methods. We'll discuss one prominent method used in many BASICs. It orginated in DEC BASIC-PLUS and lives on in QB64pe and FreeBASIC.
Earlier I wrote about strings, and how there are two prominent ways of manipulating strings. There are even more ways of handling random access I/O. The goal of random access I/O is to allow programs to fetch and store records in a non-linear fashion. For example, a banking program could handle a transaction as follows. The program could jump to record #10 to fetch an account balance, deduct $10 from the balance, and then go to record #3 to fetch and update the receiving account’s balance by adding $10.
The following is a simple program to demonstrate how strings can be stored in a random access file:
10 OPEN "file.dat" AS #1 LEN = 40
20 FIELD #1, 40 AS S$
25 INPUT "Read or write";A$: IF A$="w" GOTO 30 ELSE IF A$="r" GOTO 60
26 IF A$<>"e" GOTO 25
27 CLOSE #1: GOTO 100
30 INPUT "rec #, string? ", R, I$
40 LSET S$ = I$
45 PUT #1, R
50 GOTO 25
60 INPUT "rec #? ", R
70 GET #1, R
80 PRINT S$
90 GOTO 25
100 END
The above program uses a file, named file.dat, that contains fixed-length records composed of 40-byte character strings. The record length appears in two places; the LEN = 40 parameter on the file OPEN statement and the FIELD statement on line 20. FIELD statements map string variables to the file buffer which in this case is S$. The buffer receives information when using the GET statement, and writes information by using the PUT statement.
You’ll notice that the PUT and GET statements only have two parameters; one for the file number, and one for the record number. These statements use the file buffer as an implicit parameter, and the FIELD statement has tied the variable S$ to the buffer. Reading a record deposits the record in S$. Writing a record takes the information from S$ and writes it to the file.
The FIELD statement allows you to use multiple string variables as long as the total number of bytes does not exceed the buffer length specified in the OPEN statement. You also must use the LSET or RSET commands to change values in the buffer (as opposed to using LET or implicit LET). Both of these commands trim the values to the field lengths specified in the FIELD statement. If the value is shorter than the FIELD length, LSET will left justify the string with spaces, and RSET will right justify the string by putting spaces in the front of the string.
The FIELD statement only allows string variables to be attached to the buffer, but often you’ll have record formats where you need to have integer and floating values. To do this, you need to recognize that integers are stored in two bytes, and floating point values are stored in four bytes or eight bytes. GW-BASIC has functions like MKI$, and CVI to make strings out of integers, and integers out of strings. The strings are not human readable strings, they are the internal bit-for-bit representation of an integer mapped onto a two-byte string. Similar functions are available for single and double-precision floating point numbers.
History
This form of Random Access I/O first appeared in DEC BASIC-PLUS in 1971, and then in IBM’s BASICA in 1981, and then went on to influence GW-BASIC, and QBasic. It lasted through Visual BASIC 6.
QBasic had the most elegant implementation of this form of Random Access I/O. Instead of using the FIELD statement, QBasic used user-defined Record Types. With this approach, you did not need to specify a buffer, and you could directly PUT and GET variables that were instances of a Record Type. In other words, GET #1, RECNUM, MYREC, or PUT #1, RECNUM, MYREC. This link documents the GET statement in Visual Basic 6. The QBasic approach is also used in FreeBASIC and QB64pe.
As usual, I would be willing to bet this approach was mimicked in other BASICs. I believe the TRS-80 used this approach. Please leave your comments below.
The TRS-80 Model I Disk BASIC was much cruder. All records were 256 bytes longer (1 sector). If you wanted to use 40-byte “logical records”, you’ve have to use some tricky math to find which physical record (sector) your logical record fell in, and then “how far into” that sector it actually appeared. So in our example of 40-byte records, record #10 would be in sector 2, starting at character position 144 (assuming we had logic to “split” record #7 *across* sectors 1 & 2). The code to place those 40 bytes of record #10 in R$ might be
100 OPEN “R”, FILE”, 1
…
1000 GET 1, 7
1010 FIELD 1, 143 AS SP$, 40 AS R$