If you are new to BBCBASIC(86), or you are experiencing difficulty with disk files you might find these notes useful. Some of the concepts and procedures described are quite complicated and require an understanding of file structures. If you have trouble understanding these parts, don't worry. Try the examples and write some programs for yourself and then go back and read the notes again. As you become more comfortable with the fundamentals, the complicated bits become easier.
You will find the programs listed in these notes on your BBCBASIC(86) distribution disk (possibly with a few additions). The programs are for demonstration and learning purposes; they are not intended to be taken and used as working programs without modification to suit your needs. They are definitely NOT copyright and, if you want to, you are free to incorporate any of the code in the programs you write. Use them, change them, or ignore them as you wish. There is only one proviso; the programs have been tested and used a number of times, but we cannot say with certainty that they are bug free. Remember, debugging is the art of taking bugs out - programming is the art of putting them in.
All computers are able to store and retrieve information from a non-volatile medium. (In other words, you don't lose the information when the power gets turned off.) Audio cassettes are used for small micro computers, diskettes for medium sized systems and magnetic tape and large disks for big machines. In order to be able to find the information you want, the information has to be organised in some way. All the information on one general subject is gathered together into a FILE. Within the file, information on individual items is grouped together into RECORDS.
This is very similar to the way a cassette is searched for a particular file. You put the cassette in the recorder, type in the name of the file you want and push play. You then go and make a cup of tea whilst the computer reads through all the files until it comes to the one you want. Because the cassette is read serially from start to end, it's very difficult to do it any other way.
Life is easier with a computer that uses diskettes (or disks). There is an index which tells the computer where to look for each of the files and the serial search for the file is not necessary. However, once you have found the file, you still need to read through it to find the record you want.
There are a number of ways to overcome this problem. We will consider the two simplest; random access (or relative) files and indexed files.
What happens when you close an account? You can't tear out the page because that would upset the numbering system. All you can do is draw a line through it - in effect, turn it into a blank page. Before long, quite a number of pages will be 'blank' and a growing proportion of your book is wasted.
With other forms of 'numbering', say by the letters of the alphabet, you could still not guarantee to fill all the pages. You would have to provide room for the Zs, but you may never get one. When you started entering data, most of the pages would be blank and the book would only gradually fill up.
The same happens with this sort of file on a diskette. A random file which has a lot of empty space in it is described as sparse. Most random files start this way and most never get more than about ¾ full. Count the number of empty 'slots' in your address book and see what proportion this is of the total available.
If we had an index at the front of the book we could scan the index for the name and then turn to the appropriate page. We would still be wasting a lot of space because some names, addresses etc are longer than others and our 'slots' must be large enough to hold the longest.
Suppose we numbered all the character positions in the book and we could easily move to any character position. We could write all the names, addresses, etc, one after the other and our index would tell us the character position for the start of each name and address. There would be no wasted space and we would still be able to turn directly to the required name.
What would happen when we wanted to cancel an entry? We would just delete the name from the index. The entry would stay in its original place in the book, but we would never refer to it. Similarly, if someone changed their address, we would just write the name and the new address immediately after the last entry in the book and change the start position in the index. Every couple of years we would rewrite the address book, leaving out those items not referenced in the index and up-date the index (or write another one).
This is not a practical way to run a paper and pencil address book because it's not possible to turn directly to the 3423rd character in a book, and the saving in space would not be worth the tedium involved. However, with BBC BASIC you can turn to a particular character in a file and the tedium only takes a couple of seconds, so it's well worth doing.
Serial files cannot be used to access particular records from within the file quickly and easily. In order to do this with the minimum access time, random access files are necessary. However, a random file generally occupies more space on the disk than a serial file holding the same amount of data because the records must be a fixed length and some of the records will be empty.
Most versions of BASIC only offer serial and random files, but because of the way that disk files are handled by BBC BASIC (both on the BBC computer and MS-DOS (PC DOS) computers using BBCBASIC(86)), it is possible to construct indexed, and even linked, files as well. Indexed files take a little longer to access than random files and it is necessary to have a separate index file, but they are generally the best space/speed compromise for files holding a large amount of data.
Because of the character by character action of the write/read process, it is possible (in fact, necessary) to keep track of your position within the file. BBC BASIC does this for you automatically and provides a pointer PTR (a pseudo-variable) which holds the position of the NEXT character (byte) to be written/read. Every time a character is written/read PTR is incremented by 1, but it is possible to set PTR to any number you like. This ability to 'jump around' the file enables you to construct both random (relative) and indexed files.
BBC BASIC provides the facility for completely free-format binary data files. Any file which can be read by the computer, from any source and in any data format, can be processed using the BGET, BPUT and PTR functions.
If more files are opened, data is physically passed to or from the extra files a byte at a time. Consequently, reading from or writing to file handles 5, 6, 7 and 8 is quicker than files with a higher handle number.
Apart from the speed increase, file buffering should be transparent to the user and, unless you are doing something unconventional, you can forget about it.
Versions 4.80 and later of BBC BASIC (86) buffer data for the first 12 files opened (file handles 5 to 16 inclusive).
In particular, characters sent to an output device will not actually be transmitted until BBCBASIC(86)'s buffer has filled. Similarly, characters received from an input device will not be seen by BBCBASIC(86) until the buffer fills. This may be unacceptable, for example, when communicating with a remote device such as a modem.
There may also be problems if you try to mix BBCBASIC(86)'s OPEN and CLOSE operations with direct DOS calls from assembler for transferring data. Generally, however, this will work satisfactorily.
One solution is to open 4 'dummy' files at the start of your program. This will use the 4 buffered file handles. With versions 4.80 and later of BBC BASIC (86) it is necessary to open 12 'dummy' files.
It is also worth noting that using the PTR#file_num= statement has the side-effect of flushing the appropriate BBCBASIC(86) file buffer to DOS.
The effect is that any file can be opened at the same time, once with OPENUP and any number of times with OPENIN. (Only one process can be writing to the file, but any number can be reading from it.)
OPENIN Open in the 'read-only, deny none' mode. OPENUP Open in the 'read-write, deny write' mode. OPENOUT Open in the 'compatibility' mode.
OPENOUT, which creates a new file or empties an existing one, is mutually exclusive with the other OPEN types; if you wish to create a new file which can be read, concurrently, by other processes you should open it with OPENOUT, immediately close it and then re-open it with OPENUP.
If an attempt to open a file fails because of a sharing violation, an 'Access denied' error (error number 189) will occur. Since this error may be trapped (using ON ERROR LOCAL or ON ERROR), you can write your program in such a way that it re-tries to open the file following an 'Access denied' error.
The MS-DOS (PC DOS) operating system allows a composite file name in the following format:
The drivename is a single letter followed by a colon and denotes the disk drive on which the file will be found or created.DRIVENAME\PATHNAME\FILENAME.EXTension
The pathname is the name of the directory or the path to the directory in which the file will be found or created. The name of each directory can be up to 8 characters long.
The file name can be up to 8 characters long, and the extension up to three characters. Whenever a file name without an extension is given, BBCBASIC(86) will append .BBC as the default extension.
The first example will save the program to a file named FRED.BBC. The second will save COMPOUND.BBC.SAVE filename SAVE "FRED" A$="COMPOUND" SAVE A$
You can specify a drivename and a path as well as the file name. The following example will save the current program to a file called TEST.BBC in directory called PROGS which is in a directory called BBCBASIC on drive D:. (The path is BBCBASIC\PROGS)
As with SAVE, you can specify a drive name and path. The example below loads the program saved previously as an example of the SAVE command.LOAD filename LOAD "FRED" A$="HEATING" LOAD A$
As with SAVE and LOAD, you can specify a drive name and/or a path.CHAIN filename CHAIN "GAME1" A$="PART2" CHAIN A$
You now write this file, in ASCII format, to an intermediate file by listing it with a SPOOL file active.
Finally, you load program one again and use *EXEC to read in lines from the intermediate file as if they were being typed from the keyboard. The first line (>LIST) and the last (>*SPOOL) will be ignored because they cause syntax errors, but the rest of the lines will be added to your program as if you had typed them in.
Suppose the first program was called 'PROG1', the second 'PROG2' and the intermediate file was to be called 'PROG2.BAS'. The following sequence would merge the two programs.
(This has saved PROG2 in ASCII format to the file PROG2.BAS)LOAD "PROG2" RENUMBER 10000 (Put PROG2 at end) *SPOOL PROG2.BAS LIST *SPOOL
(Save the merged program with a different name just in case!)LOAD "PROG1" *EXEC PROG2.BAS SAVE "PROG3"
Load the first program (with the lower line numbers) in the normal way. Then, find out the top address of the program less 3 by typing
This will print the address in hex (nnnn) at which the first byte of the second program file must be loaded. Finally, load the second program by typingPRINT ~TOP-3<Enter>
*LOAD "PROG2" nnnn<Enter> OLD<Enter>
The full command *DELETE and the CP/M compatible commands *ERA and *ERASE are synonymous with *DEL.*DEL filename *DEL FRED *DEL PHONE.DTA
To delete a file whose name is known only at run-time, use the OSCLI command. It's a bit clumsy, but a lot better than the original specification for BBCBASIC allowed. This time all of the command, including the DEL, must be supplied as the argument for the OSCLI command. You can use OSCLI for erasing a file whose name is a constant, but you must include all of the command line - in quotes this time.
You can include a drive name and/or a path in both the *DEL and *OSCLI command formats.fname$="FRED" OSCLI "DEL "+fname$ fname$="PHONE.DTA" command$="DEL " OSCLI command$+fname$ OSCLI "ERA FRED"
Although MS-DOS (PC DOS) will allow you to do so, it is bad practice to erase an open file.
Once again, if you want to rename files whose names are only known at run-time, you must use the OSCLI command.*REN file1 file2 *REN FRED1 FRED2 *REN PHONE PHONE.DTA
Because MS-DOS (PC DOS) refers to files by their handles, it does not get confused if you rename an open file. However, in all probability, the same cannot be said for you.fname1$="FRED1" fname2$="FRED2" OSCLI "REN "+fname1$+" "+fname2$ fname1$="FRED1" fname2$="FRED2" command$="REN " OSCLI command$+fname2$+"="+fname1$
*DIR List *.BBC files on the current drive. *.B:*.DTA List *.DTA files on drive B.
To use the UNLIST utility, save your program with the default .BBC extension to its name and type
Do NOT add the extension .BBC to the file name.*UNLIST filename<Enter>
To display a brief explanation of UNLIST, just type
For example, to UNLIST a program file called 'TEST.BBC', type*UNLIST<Enter>
If you want to use UNLIST, you must not use calculated line numbers in GOTO, GOSUB or RESTORE statements.*UNLIST TEST<Enter>
If there is insufficient memory for COMMAND.COM and UNLIST.COM to be resident at the same time as BBCBASIC(86), you will need to exit BBCBASIC(86) before running UNLIST. If you run UNLIST from the MS-DOS prompt, you don't need the '*' at the start.
CRUNCH is particularly useful when used in conjunction with the BBCRUN and BIGRUN utilities to create a stand-alone executable file from a BBC BASIC program.
To use the CRUNCH utility, save your program with the default .BBC extension to its name and type
Do NOT add the extension .BBC to the file name.*CRUNCH filename<Enter>
To display a brief explanation of CRUNCH, just type
For example, to CRUNCH a program file called 'TEST.BBC', type*CRUNCH<Enter>
If you want to use CRUNCH, you must not use calculated line numbers in GOTO, GOSUB or RESTORE statements, and you must not access variables by name in DATA statements or using the EVAL function.*CRUNCH TEST<Enter>
If there is insufficient memory for COMMAND.COM and CRUNCH.COM to be resident at the same time as BBCBASIC(86), you will need to exit BBCBASIC(86) before running CRUNCH. If you run CRUNCH from the MS-DOS prompt, you don't need the '*' at the start.
OPENIN OPENUP OPENOUT EXT# PTR# INPUT# BGET# PRINT# BPUT# CLOSE# END EOF#
When you open the file, a file handle (an integer number) is returned by the interpreter and you will need to store it for future use. (The open commands are, in fact, functions which open the appropriate file and return its file handle.)
You use the file handle for all subsequent access to the file. (With the exception of the STAR commands outlined previously.)
If the system has been unable to open the file, the handle returned will be 0. This will occur if you try to open a non-existent file in the input mode (OPENIN or OPENUP).
MS-DOS (PC DOS) imposes a limit on the number of files you can have open at any one time. The default limit is 8. This is not as generous as it seems, because MS-DOS always opens 5 files for its own use. This leaves 3 for your program. If you attempt to have more files open at one time than MS-DOS allows, you will get a 'Too many open files' error. The file you attempted to open will appear in the directory, but you will not be able to use it. You can alter (increase) the number of open files allowed by adding a 'FILES=nn' command in your CONFIG.SYS file. See your MS-DOS Users Guide for details.
The files opened by MS-DOS (PC DOS) are listed below with their file handles. Since these files are always open, you can write to them as you would any other file. Thus,
will print the message 'Hello Fred' on the screen. It's not good practise to send normal output to the screen in this way because BBCBASIC(86) cannot keep track of the cursor position (and hence POS, VPOS and COUNT). It is, however, a reasonable way to write to the printer or the auxiliary (serial) device. It is also useful to be able to send messages to the screen via, for example, STDERR whilst sending the console output to a printer with *OPT 2. (See also *OPT in the Operating System Interface section.)PRINT#1,"Hello Fred"
Handle Device Purpose Remarks 0 STDIN Standard Input Can't be accessed directly. 1 STDOUT Standard Output 2 STDERR Standard Error 3 STDAUX Serial device or modem 4 STDPRN Printer
You always need to store the file handle because it must be used for all the other file commands and functions. If you choose a variable with the same name as the file, you will make programs which use a number of files easier to understand.OPENOUT filename file_num=OPENOUT "PHONENUMS"
On a networked system, OPENOUT opens the file in 'compatibility' mode and the file is not available to any other user. If you wish to create a new file which can be read, concurrently by other users, you should open it with OPENOUT, immediately close it and re-open it with OPENUP. See the earlier sub-section Networking - Shared Files for more details.phonenums=OPENOUT "PHONENUMS" opfile=OPENOUT opfile$
You will be unable to open for input (file handle returned = 0) if the file does not already exist.OPENIN filename address=OPENIN "ADDRESS" check_file=OPENIN check_file$
If you try to write to a file opened for input you will get an 'Access denied' error (error number 189).
On a networked system, OPENIN opens a file in the 'read-only, deny none' mode. Any user may open the file as many times as they like in this mode. The file may also be opened once (by any user) with OPENUP. See the earlier sub-section Networking - Shared Files for more details.
You will be unable to open for update (file handle returned = 0) if the file does not already exist.OPENUP filename address=OPENUP "ADDRESS" check_file=OPENUP check_file$
On a networked system, OPENUP opens a file in the 'read-write, deny write' mode. A file may be opened once with OPENUP and any number of times by any number of users with OPENIN. See the earlier sub-section Networking - Shared Files for more details.
When a file is closed its file buffer (if it has one) will be flushed to MS-DOS before the file is closed.CLOSE#fnum
READ# can be used as an alternative to INPUT#INPUT#fnum,var data=OPENIN "DATA" : INPUT#data,name$,age,height,sex$ : :
String variables are written as the character bytes in the string plus a carriage-return. Numeric variables are written as 5 bytes of binary data.PRINT#fnum,var
data=OPENOUT "DATA" : : PRINT#data,name$,age,height,sex$ : :
In the case of a sparse random-access file the value returned is the length of the file to the last byte actually written to the file. Although much of the file may well be unused, writing this 'last byte' reserved physical space on the disk for a file of this length. Thus it is possible to write a single byte to a file and get a 'Disk full' error.EXT#fnum
When the file is OPENED, PTR# is set to zero. However, you can set PTR# to any value you like. (Even beyond the end of the file - so take care).PTR#fnum
Reading or writing, using INPUT# and PRINT#, (and BGET# and BPUT# - explained later), takes place at the current position of the pointer. The pointer is automatically updated following a read or write operation.
A file opened with OPENUP may be extended by setting PTR# to its end (PTR# = EXT#), and then writing the new data to it. You must remember to CLOSE such a file in order to update its directory entry with its new length. A couple of examples of this are included in the sections on serial and indexed files.
Using a 'PTR#fnum=' statement will flush the appropriate BBCBASIC(86) file buffer to MS-DOS.
Attempting to read beyond the current end of file will not give rise to an error. Either zero or a null string will be returned depending on the type of variable read.eof=EOF#fnum
EOF# is only really of use when dealing with serial (sequential) files. It indicates that PTR# is greater than the recorded length of the file (found by using EXT#). When reading a serial file, EOF# would go true when the last byte of the file had been read.
EOF# is only true if PTR# is set beyond the last byte written to in the file. It will NOT be true if an attempt has been made to read from an empty area of a sparse random access file. Reading from an empty area of a sparse file will return garbage. Because of this, it is difficult to tell which records of an uninitialised random access file have had data written to them and which are empty. These files need to be initialised and the unused records marked as empty.
Writing to a byte beyond the current end of file updates the file length immediately, whether the record is physically written to the disk at that time or not.
or, more expedientlyBGET#fnum byte=BGET#fnum char$=CHR$(byte)
BPUT#fnum,var BPUT#fnum,&1B BPUT#fnum,house_num BPUT#fnum,ASC "E"