The techniques previously discussed for data storage and retrieval are simple to use and moderately efficient for storage of text. However, they are very inefficient for storage of numeric information. MicroDOS offers another method which, while more complex, offers much more efficient use of the disk. In this method data is read and written using "Field Buffers" which are special holding areas in memory. MicroDOS provides four Field Buffers which may be defined by the user. Data fields may be defined within these buffers for easier manipulation by the BASIC program. How these buffers are used is best shown with an example. Suppose we want to keep a list of names, addresses, and phone numbers on disk. Each name will be 20 characters or less, each address will be 40 characters or less, and each phone number will be 7 characters. Using the technique previously discussed each entry in the list would require a sector -- one for name, one for address, and one for phone.
DEF FIELD #N,20 AS NA$,40 AS AD$,7 AS PH$
The 'DEF FIELD' statement is used to name areas within a Field Buffer for easier manipulation by the BASIC program. 'N' is a numeric expression identifying which Field Buffer is to be referenced. In this case three fields are defined. They are 'NA$' (name) as 20 characters, 'AD$' (address) as 40, and 'PH$' (phone) as 7. Suppose that sector 48 of drive 2 contains the following string: "R. Shack 1111 Tandy Trail 5550000"
The following program shows how this data is read and accessed using a Field Buffer:
100 GET #1,20048 'READ THE SECTOR
110 DEF FIELD #1,20 AS NA$,40 AS AD$,7 AS PH$
120 PRINT NA$
130 PRINT AD$
140 PRINT PH$
Result if RUN:
R. Shack
1111 Tandy Trail
5550000
Note how the DEF FIELD allowed data to be "broken down" into pieces for simpler processing. In addition three separate data items are stored on a single sector. While this is a great improvement, there is more that can be done. Notice that out of 255 characters possible on a sector, this example uses only 67 of them, wasting 188 characters! A few calculations show that the 67 character sequence can be put in one sector 3 times, leaving 54 characters unused.
How is this done?
First we need to get some terminology out of the way. Whenever we have a group of fields which logically "belong" together we will call that grouping a "record". In the preceding example one "record" consists of a name, an address, and a phone number. What we would like to do is get three records on one sector.
There are several ways this can be done. Probably the most obvious is to give each field in each record a different name as N1$, A1$, P1$, N2$, A2$, and so on. This requires a lot of typing and will usually require a longer statement than the 255 characters allowed by Level II. If the fields are processed one at a time we can put a "dummy" record at the front of the field definitions to skip over the unwanted records. In the following program a sector is read and all three records in it are printed:
100 GET #1,20048 'READ THE SECTOR
110 FOR I=0 TO 2 'LOOP 3 TIMES
120 DEF FIELD #1,I*67 AS XX$,20 AS NA$,40 AS AD$,7 AS PH$
130 PRINT NA$ 'PRINT CURRENT RECORD
140 PRINT AD$
150 PH$
160 NEXT I
Notice that this program looks pretty much like the previous one except for the addition of the FOR--NEXT loop and a new field 'XX$'. The first time through the loop (I=0) the dummy field XX$ will have a length of zero. This will position NA$, AD$, and PH$ as the first items in the sector as in the previous example. However, in the second pass through the loop (I=1) XX$ has a length of 67 (one record). The fields NA$, AD$, and PH$ are now offset 67 characters into the sector. We are actually accessing a second record in the same sector.
If all this seems hard to understand, keep in mind that the field variables NA$, AD$, and PH$ are not data items in themselves, but are actually NAMES of data items. As an example of how this works suppose that your newspaper deliverer is very dull-witted and only leaves a newspaper at house number 123. Further suppose that there is only one sign with the numbers 123 on it for use by the entire neighborhood. The only way for everyone to get a newspaper is to move this number from house to house as each paper is delivered. Accessing data in a multi-record Field Buffer works the same way. The variable names ("house numbers") are moved from record to record ("house to house") so the proper data is retrieved.
But suppose we wish to access all the records of a sector at once. How can we do this? The best way is by using arrays. By mapping each element of an array over a record in the Field Buffer we can have access to all records at once. Using our name, address, and phone number example we could do it this way:
100 DIM NA$(2),AD$(2),PH$(2) 'DEFINE ARRAYS
110 GET #1,20048 'READ THE SECTOR
120 FOR I=0 TO 2 'START LOOP
130 DEF FIELD #1,I*67 AS XX$,20 AS NA$(I),40 AS AD$(I),7 AS PH$(I)
140 NEXT I
200 FOR J=0 TO 2 'PRINT LOOP
210 PRINT NA$(J)
220 PRINT AD$(J)
230 PRINT PH$(J)
240 NEXT J
At first glance you may be tempted to say that this won't work. It does look a lot like the previous example, but the array subscripts are the big difference. Because of subscripting NA$(0) is NOT the same name as NA$(1). So what we are doing is giving each record in the sector a unique name rather than moving the same names from record to record.
Thus far we have had a great deal of discussion about how to read data into a Field Buffer and access it, but nothing at all about how the data gets on the disk in the first place. Before data can be put into a Field Buffer the fields need to be defined as they were before using the 'DEF FIELD' statement. Getting data into these defined fields is where things get tricky. Recall that we said earlier that the variables created by the 'DEF FIELD' statement were NAMES of data items rather than data items themselves. Suppose we try the following example program:
100 DEF FIELD #1,10 AS A$
110 A$="HELLO"
120 DEF FIELD #1,10 AS B$
130 PRINT B$
When we print 'B$' we expect to get a friendly "HELLO" greeting. Surprise! We get nothing of the sort! (What we get depends on whatever garbage was in Field Buffer 1 before we started.) What happened? Examining the program, line 100 seems alright. We named the first 10 characters of Field Buffer #1 as 'A$'. The "gotcha" comes in line 110. It appears that we are moving "HELLO" into variable 'A$', but in reality what we are doing is moving the NAME 'A$' to the data item "HELLO". Thus we can print A$ and get "HELLO", but we can't seem to find it in the Field Buffer. Yes, this is confusing, but it is done this way to make the computer more efficient. So how do we get data into the miserable Field Buffer?
LSET A$="HELLO" or RSET A$="HELLO"
Enter the heroes! The statements 'LSET' and 'RSET' are used to MOVE data from one variable to another. Data is left-justified or right-justified depending on which form is used. If we had used 'LSET' in line 110 of our previous example program, the result would have been that the first 10 characters of Field Buffer #1 would contain "HELLO ". Notice the five trailing spaces. These are provided automatically by 'LSET'. Use of 'RSET' would have resulted in " HELLO". If, however, we had defined 'A$' as having only 4 characters, both 'LSET' and 'RSET' would have resulted in 'A$' containing a four-letter word describing a place that is warm year-round and I don't mean Florida!
Now that we know how to get data INTO a Field Buffer we will look at an example program to produce the data that we read in our previous examples, the name, address, and phone number records.
100 FOR I=0 TO 2 'START LOOP
110 DEF FIELD #1,I*67 AS XX$,20 AS NA$,40 AS AD$,7 AS PH$
120 INPUT "ENTER DATA";N$,A$,P$ 'GET DATA
130 LSET NA$=N$ 'PUT IN FIELD BUFFER
140 LSET AD$=A$
150 LSET PH$=P$
160 NEXT I 'CONTINUE LOOP
200 PUT #1,20048
The last line, 200, actually records the data on disk. In previous discussions of disk I/O using string variables, the amount of data written to disk was always equal to the length of the string variable from which the data was written. Likewise, the string variable into which the data was read from the disk, is made the same length as the incoming data. When using Field Buffers MicroDOS must have a similar method to determine how many characters to actually write. The rule that is used is that the length of the Field Buffer is assumed to be as long as the total length of fields defined by the longest DEF FIELD statement. If necessary a dummy DEF FIELD can be executed to establish the proper length for the Field Buffer. Following a GET to a Field Buffer the length of the Field Buffer is set to the length of the data read. However, the length may be changed by subsequent DEF FIELD statements. For this reason, it is best to do a DEF FIELD after a GET and before a PUT so the proper length will be written to disk.
While this scheme may seem restrictive, it does insure that no "garbage" data is transferred to or from the disk. The programmer may determine the current length of any Field Buffer using the function 'LOF'. The format of the function call is 'LOF(N)' where 'N' is an expression indicating which Field Buffer's length is to be returned. Note that '#' is not required preceding the Field Buffer number expression. Programmers familiar with the use of 'FIELD' in Radio Shack Disk BASIC should pay particular attention to the concept of length in conjunction with Field Buffers. Radio Shack Disk BASIC assumes a length of 255 bytes and always transfers that many characters with each GET or PUT. MicroDOS users desiring this feature should always do a dummy DEF FIELD for 255 characters immediately before each PUT.
Our discussions involving I/O using Field Buffers have all been oriented toward strings up to this point. How does one handle numeric data efficiently? Since only string fields can be defined in the DEF FIELD statement, numeric data must be converted to string form. Earlier we did this with the 'STR$' function. This results in needlessly long strings, however, there is a better way.
MKI$(I), MKS$(S), and MKD$(D)
The 'MKX' functions make numbers into strings. These strings, however, contain the numbers in their internal form and are not directly printable. 'MKI$' makes its integer argument ('I') into a string two characters long. 'MKS$' makes a 4 character string out of its parameter 'S', and 'MKD$' builds an 8 character string from the parameter 'D'. Let us refer to an earlier example of storing the mathematical constant PI on disk:
100 PI=3.14159 'DEFINE CONSTANT
110 DEF FIELD #1,4 AS PI$ 'DEFINE FIELD
120 LSET PI$=MKS$(PI) 'CONVERT TO STRING
130 PUT #1,10045 'WRITE TO DISK
In using the 'MKX' functions one must be very careful to define strings of the proper length for the type of number being converted. Refer to the following table:
| NUMBER TYPE | REQUIRED STRING LENGTH |
| Integer | 2 characters |
| Single prec. | 4 characters |
| Double prec. | 8 characters |
These functions solve only half of the problem. There still must be a way to get strings back into numeric form.
CVI(I$), CVS(S$), and CVD(D$)
The 'CVX' functions perform the string to number conversion. 'CVI' converts string parameter 'I$' to an integer. 'CVS' converts 'S$' to a single precision floating point number and 'CVD' converts 'D$' to double precision. Note that these functions CANNOT be used to change the type of a number, i.e. do not use 'CVI' on a string that was created using 'MKS$'. As a partial protection against this, a 'Function call' error will occur if the string parameter for any 'CVX' function is not the proper length for its number type.