Using Integral BASIC Data Files GVG / 26 Oct 1986 Integral Tech BASIC data files exist as files in the the Integral's UN*X file system and, as such, share the same characteristics of all other IPC files. They exist in a hierarchical file system (where file directories may contain either files or other directories, and these other directories may contain files or their own directories, and so on), and are handled by the operating system in 1024-byte blocks. On top of this, however, Integral BASIC imposes its own structure on data files. Each file can be divided up into "logical records" that may divide the file into portions of arbitrary size that can be accessed sequentially or at random. (Please note that the underlying file still exists in 1024-byte blocks; the logical record structure is simply for BASIC's own use.) Let's start by writing a program to sequentially store several items in a data file: 10 ! Declare and initialize variables. 20 ! 30 OPTION BASE 1 40 ! 50 DIM Income_Name$(2) @ REAL Target_Income 60 ! 70 Income_Name$(1)="Payroll" @ Income_Name$(2)="Investments" 80 Target_Income(1)=1680.56 @ Target_Income(2)=345.67 90 ! 100 ! Create a data file. 110 ! 120 CREATE "/disc/Oct_84_Income",1 ! File has 1 logical record (256 bytes). 130 ASSIGN# 1 TO "/disc/Oct_84_Income" ! Assign a buffer to it. 140 ! 150 PRINT# 1 ; Income_Name$(1),Target_Income(1) 160 PRINT# 1 ; Income_Name$(2),Target_Income(2) 170 ! 180 ASSIGN# 1 TO "*" ! Close file, release buffer. 190 ! 200 END This example stores a string, a floating-point number, another string, and another floating-point number. The statement: 120 CREATE "/disc/Oct_84_Income",1 - creates the data file on a floppy disc named "/disc" in a file named "Oct_84_Income". The file is created with 1 logical record; the logical record has a default value of 256 bytes. As I mentioned earlier, though, the file is still in 1024-byte blocks, and can never be smaller than 1024 bytes. By the way, for sequential access, the logical record size does not limit the size of the file; BASIC will expand the file automatically if a BASIC program tries to write data into it beyond the end of the logical record. The statement: 130 ASSIGN# 1 TO "/disc/Oct_84_Income" - assigns a "buffer" (buffer 1, to be precise) to the file; the BASIC program writes to this buffer to send data to the data file. The PRINT# 1 statements then write data to the file indirectly through this buffer. (If you're wondering: why all this nonsense with buffers? - the answer is simple: BASIC stores data in a buffer that is not written to a file until the buffer is full or the file is closed. This reduces the number of file accesses needed by the program and improves performance.) Finally, the statement: 180 ASSIGN# 1 TO "*" - closes the file and releases the buffer for general use. Reading the file sequentially is similar: 10 ASSIGN# 1 TO "/disc/Oct_84_Income" ! Assign buffer to file. 20 ! 30 OPTION BASE 1 40 ! 50 DIM Income_Name$(2) @ REAL Target_Income(2) 60 ! 70 ASSIGN# 1 TO "/disc/Oct_84_Income" ! Assign buffer to file. 80 ! 90 READ# 1 ; Income_Name$(1),Target_Income(1) 100 READ# 1 ; Income_Name$(2),Target_Income(2) 110 ! 120 DISP 130 DISP "Category Target" 140 DISP "----------- -------" 150 DISP Income_Name$(1),Target_Income(1) 160 DISP Income_Name$(2),Target_Income(2) 170 ! 180 ASSIGN# 1 TO "*" 190 ! 200 END This program is very similar to the first, except that there is no CREATE statement (since the file already exists) and that READ# statements are used instead of PRINT# statements. Note that you must be very careful to read the data items from the file in the SAME ORDER that they were printed into it originally. In these last two examples, there was only one logical record, and therefore no reason to do any more work in specifying the logical record than to declare that one existed. However, in some cases, it is necessary to specify the size of the logical record in more detail. The size of the logical record is a function of the size (in bytes) of the data stored in the record. Each data element in a record consists of a number of bytes used to store the data itself, preceded by a "type field" that declares what the data type is. The size of each element is: REAL 1-byte type field + 8 bytes data SHORT 1-byte type field + 8 bytes data INTEGER 1-byte type field + 4 bytes data string 3-byte type field + 1 byte per character + 3 bytes if string crosses a record boundary REAL array element 1-byte type field + 8 bytes data SHORT array element 1-byte type field + 4 bytes data INTEGER array element 1-byte type field + 4 bytes data string array element same size as individual string The logical record does not HAVE to be EXACTLY the size needed for the data in the record; it can be larger. However, the extra space in the record is never used and is wasted. For example, in the last two examples, the precise size of the logical record would be: Income_Name$(1)="Payroll" 3 + 7 = 10 Income_Name$(2)="Investments" 3 + 11 = 14 Target_Income(1)=1680.56 1 + 8 = 9 Target_Income(2)=345.67 1 + 8 = 9 ---- total = 42 The appropriate logical record size for this data is then 42 bytes. The next two examples use multiple records and random record access: 10 OPTION BASE 1 20 DIM Income_Name$(2) @ REAL Target_Income(2) 30 ! 40 DATA 20177.56,1789.2,33789.45,1560,10175.78,562.33,17056.33,987 50 DATA 107894.56,25343,25000,4000,94788.76,11754.99,55000,7095 60 DATA 550784.26,50325.94,17539,2450.61,72894.77,10999.99,22765,3765.09 70 ! 80 Income_Name$(1)="Payroll" @ Income_Name$(2)="Investments" 90 ! 100 CREATE "/Tgt_Income_84",12,42 110 ASSIGN# 1 TO "/Tgt_Income_84" 120 ! 130 FOR Client=1 TO 12 140 PRINT# 1,Client ! Random "seek". 150 FOR Category=1 TO 2 160 READ Target_Income(Category) 170 PRINT# 1 ; Income_Name$(Category),Target_Income(Category) 180 NEXT Category 190 NEXT Client 200 ! 210 ASSIGN# 1 TO "*" 220 END This program sets up twelve logical records, one per each client. The CREATE statement reflects the file structure by creating 12 records of 42 bytes each: 80 CREATE "/disc/Tgt_Income_84",12,42 The statement: 110 PRINT# 1,Client - sets a "file pointer" to the beginning of the record whose number is given by the contents of the variable "Client". (You may find the syntax of this command confusing. The syntax of the PRINT# statement is: PRINT# [, ] [; , , ... ] As this shows, a record number is optional; data - if any - follows the record number after a semicolon. If a record number is specified and no data, all the PRINT# statement does is move the "file pointer" to the beginning of the specified record.) The next program reads the same data back in - but from the last record to the first: 10 OPTION BASE 1 20 ! 30 DIM Income_Name$(2) @ REAL Target_Income(2) 40 ! 50 ASSIGN# 7 TO "/Tgt_Income_84" 60 ! 70 FOR Client=12 TO 1 STEP -1 ! Access records in reverse order. 80 READ# 7,Client ! Random "seek". 90 DISP "Client:";Client 100 DISP "---------" 110 FOR Category=1 TO 2 120 READ# 7 ; Income_Name$(Category),Target_Income(Category) 130 DISP "Income name:",Income_Name$(Category) ! Serial read. 140 DISP "Target income:",Target_Income(Category) 150 DISP 160 NEXT Category 170 NEXT Client 180 ! 190 ASSIGN# 7 TO "*" 200 END Pretty straightforward, right? A final note: in many cases (particularly when you want to transfer data to other programs in the UN*X environment or to other computers) it is much more convenient to use HP-UX text files to store data (numeric or otherwise).