If you are new to BBC BASIC, or you are experiencing difficulty with 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.
The programs listed in these notes are included with the supplied example programs. They are provided for demonstration and learning purposes and are not intended to be taken and used as working programs without modification to suit your needs. 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 were once used for small micro computers, diskettes for medium sized systems and magnetic tape and large disks for big machines. Nowadays, all but the smallest machines incorporate a hard disk or SSD with several gigabytes of storage. 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.
Life is easier with a computer. 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. 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 it's very fast, 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 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 files are handled by BBC BASIC 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 one, 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.
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. It is worth noting that using the PTR#file_num= statement has the side-effect of flushing the appropriate BBC BASIC file buffer.
If an attempt to open a file fails because of a sharing violation, the OPENOUT or OPENUP call will return the value zero. In the case of OPENUP this is the same value which is returned if the file is not found; you can distinguish between the two cases by attempting to open the file with OPENIN. If that succeeds, the problem must have been a sharing violation.
Windows™ accepts a composite file name (file specification) in the following format:
The drivename is a single letter followed by a colon and denotes the disk (or other) drive on which the file will be found or created, e.g. C:DRIVENAME\PATHNAME\FILENAME.EXTension
The pathname is the name of the directory or the path to the directory (folder) in which the file will be found or created. Directories are separated by the backslash character, e.g. WINDOWS\SYSTEM
The filename and optional extension identify a single file within the directory (folder). Whenever a filename without an extension is given, BBC BASIC will append .BBC as the default extension. If you want to maintain compatibility with MS-DOS™ you should restrict filenames to eight characters or fewer and extensions to three characters or fewer; you should also use only capital letters and numbers.
OPENIN OPENUP OPENOUT EXT# PTR# INPUT# BGET# GET$ PRINT# BPUT# CLOSE# END EOF#
When you open the file, a channel number (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 channel number.
You use the channel number for all subsequent access to the file (with the exception of the Operating System commands).
If the system has been unable to open the file, the channel number returned will be 0. This will occur if you try to open a non-existent file for input (OPENIN or OPENUP) or a read-only file for output (OPENOUT or OPENUP) or whenever the file cannot be opened for another reason.
You always need to store the channel number because it must be used for all the other file statements 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:file_num = OPENOUT "PHONENUMS.DAT"
phonenums=OPENOUT "PHONENUMS.DAT" opfile=OPENOUT opfile$
OPENIN will fail (channel number returned = 0) if the file does not already exist.address=OPENIN "ADDRESS.DAT" check_file=OPENIN check_file$
If you try to write to a file opened with OPENIN you will get an 'Access denied' error (error number 189).
A file may be opened any number of times with OPENIN, but only once with OPENUP or OPENOUT. See the sub-section Networking - shared files for more details.
OPENUP will fail (channel number returned = 0) if the file does not already exist.address=OPENUP "ADDRESS.DAT" check_file=OPENUP check_file$
A file may be opened once with OPENUP and any number of times with OPENIN. See the earlier sub-section Networking - Shared Files for more details.
CLOSE#fnum CLOSE#0
For this to work correctly the data must have been written with a PRINT# statement. READ# can be used as an alternative to INPUT#INPUT#data,name$,age,height,sex$
PRINT#data,name$,age,height,sex$
EXT# can also be used to change the length of a file:length = EXT#fnum
EXT#fnum = newlength
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).oldptr = PTR#fnum PTR#fnum = newptr
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 BBC BASIC file buffer. This can be useful when you need to ensure that the data has actually been written to the storage medium (or sent to a remote device).
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# 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.
or, more expedientlybyte=BGET#fnum char$=CHR$(byte)
char$=CHR$(BGET#fnum)
BPUT#fnum,&1B BPUT#fnum,house_num BPUT#fnum,ASC "E"
You may notice that we have cheated a little in that a procedure is called to close the files and end the program without returning. This saves using a GOTO, but leaves the return address on the stack. However, ending a program clears the stack and no harm is done. You should not use this sort of trick anywhere else in a program. If you do you will quickly use up memory.
REM F-WSER1 : REM EXAMPLE OF WRITING TO A SERIAL CHARACTER DATA FILE : REM This program opens a data file and writes serial REM character data to it. The use of OPENOUT ensures that, REM even if the file existed before, it is cleared before REM being written to. : OSCLI "CD """+@usr$+"""" : phonenos=OPENOUT "PHONENOS.DAT" PRINT "File Name PHONENOS.DAT Opened as Channel ";phonenos PRINT REPEAT INPUT "Name ? " name$ IF name$="" THEN PROC_end INPUT "Phone Number ? " phone$ PRINT PRINT#phonenos,name$,phone$ UNTIL FALSE : DEF PROC_end CLOSE#phonenos END
REM F-RSER1 : REM EXAMPLE OF READING A SERIAL CHARACTER FILE : REM This program opens a previously written serial file REM and reads it. : OSCLI "CD """+@usr$+"""" : phonenos=OPENIN "PHONENOS.DAT" PRINT "File Name PHONENOS.DAT Opened as Channel ";phonenos PRINT REPEAT INPUT#phonenos,name$,phone$ PRINT name$,phone$ UNTIL EOF#phonenos : CLOSE#phonenos END
REM F-WESER1 : REM EXAMPLE OF WRITING TO THE END OF A SERIAL DATA FILE : REM This program opens a file and sets PTR to the end REM before writing more data to it. : REM A function is used to open the file. : OSCLI "CD """+@usr$+"""" : phonenos=FN_openend("PHONENOS.DAT") PRINT "File Name PHONENOS.DAT Opened as Channel ";phonenos PRINT REPEAT INPUT "Name ? " name$ IF name$="" THEN PROC_end INPUT "Phone Number ? " phone$ PRINT PRINT#phonenos,name$,phone$ UNTIL FALSE : DEF PROC_end CLOSE#phonenos END : : REM Open the file 'AT END'. : REM If the file does not already exist, it is created REM with OPENOUT. PTR# is left at zero and the file REM number is returned. If the file exists, PTR# is REM set to the end and the file number returned. : DEF FN_openend(name$) LOCAL fnum fnum=OPENUP(name$) IF fnum=0 THEN fnum=OPENOUT(name$): =fnum PTR#fnum=EXT#fnum =fnum
REM F-WSER2 : REM EXAMPLE OF WRITING TO A MIXED NUMERIC/CHAR DATA FILE : REM This program opens a data file and writes numeric REM and character data to it. The use of OPENOUT REM ensures that, even if the file existed before, REM it is cleared before being written to. : REM Functions are used to accept and validate the REM data before writing it to the file. : OSCLI "CD """+@usr$+"""" : stats=OPENOUT("STATS.DAT") PRINT "File Name STATS.DAT Opened as Channel ";stats PRINT REPEAT name$=FN_name IF name$="" THEN PROC_end age=FN_age height=FN_height sex$=FN_sex PRINT PRINT#stats,name$,age,height,sex$ UNTIL FALSE : DEF PROC_end PRINT "The file is ";EXT#stats;" bytes long" CLOSE#stats END : : REM Accept a name from the keyboard and make sure it REM consists only of spaces and upper or lower case REM characters. Leading spaces are automatically REM ignored on input. : DEF FN_name LOCAL name$,FLAG,n REPEAT FLAG=TRUE INPUT "Name ? " name$ IF name$<>"" THEN FOR I=1 TO LEN(name$) n=ASC(MID$(name$,I,1)) IF NOT(n=32 OR n>64 AND n<91 OR n>96 AND n<123) THEN FLAG=FALSE NEXT IF NOT FLAG THEN PRINT "No funny characters please !!!" ENDIF UNTIL FLAG =name$ : : REM Accept the age from the keyboard and round to one REM place of decimals. Ages of 0 and less or 150 or REM more are considered to be in error. : DEF FN_age LOCAL age REPEAT INPUT "What age ? " age IF age<=0 OR age >=150 THEN PRINT "No impossible ages please !!!" UNTIL age>0 AND age<150 =INT(age*10+.5)/10 : : REM Accept the height in centimeters from the keyboard REM and round to an integer. Heights of 50 or less and REM 230 or more are considered to be in error. : DEF FN_height LOCAL height REPEAT INPUT "Height in centimeters ? " height IF height<=50 OR height>=230 THEN PRINT "Verry funny !!!" UNTIL height>50 AND height<230 =INT(height+.5) : : REM Accept the sex from the keyboard. Only words REM beginning with upper or lower case M or F are OK. REM The returned string is truncated to 1 character. : DEF FN_sex LOCAL sex$,FLAG REPEAT FLAG=TRUE INPUT "Male or Female - M or F ? " sex$ IF sex$<>"" THEN sex$=CHR$(ASC(MID$(sex$,1,1)) AND 95) IF sex$<>"M" AND sex$<>"F" THEN FLAG=FALSE IF NOT FLAG THEN PRINT "No more sex(es) please !!!" UNTIL FLAG =sex$
REM F-RSER2 : REM EXAMPLE OF READING FROM A MIXED NUMERIC/CHAR DATA FILE : REM This program opens a data file and reads numeric and REM character data from it. : OSCLI "CD """+@usr$+"""" : stats=OPENIN("STATS.DAT") PRINT "File Name STATS.DAT Opened as Channel ";stats PRINT REPEAT INPUT#stats,name$,age,height,sex$ PRINT "Name ";name$ PRINT "Age ";age PRINT "Height in centimeters ";height IF sex$="M" THEN PRINT "Male" ELSE PRINT "Female" PRINT UNTIL EOF#stats : CLOSE#stats END
REM F-WESER2 : REM EXAMPLE OF WRITING AT THE END OF A MIXED NUMERIC/CHAR REM DATA FILE : REM This program opens a data file, sets PTR to its end REM and then writes numeric and character data to it. : REM Functions are used to accept and validate the data REM before writing it to the file. : OSCLI "CD """+@usr$+"""" : stats=FN_open("STATS.DAT") PRINT "File Name STATS.DAT Opened as Channel ";stats PRINT REPEAT name$=FN_name IF name$="" THEN PROC_end age=FN_age height=FN_height sex$=FN_sex PRINT PRINT#stats,name$,age,height,sex$ UNTIL FALSE : DEF PROC_end PRINT "The file is ";EXT#stats;" bytes long" CLOSE#stats END : : REM Open the file. If it exists, set PTR# to the end and REM return the file number. If it does not exist, open it, REM leave PTR# as it is and return the file number. : DEF FN_open(name$) LOCAL fnum fnum=OPENUP(name$) IF fnum=0 THEN fnum=OPENOUT(name$): =fnum PTR#fnum=EXT#fnum =fnum : : REM Accept a name from the keyboard and make sure it REM consists of spaces and upper or lower case characters. REM Leading spaces are automatically ignored on input. : DEF FN_name LOCAL name$,FLAG,n REPEAT FLAG=TRUE INPUT "Name ? " name$ IF name$<>"" THEN FOR I=1 TO LEN(name$) n=ASC(MID$(name$,I,1)) IF NOT(n=32 OR n>64 AND n<91 OR n>96 AND n<123) THEN FLAG=FALSE NEXT IF NOT FLAG THEN PRINT "No funny characters please !!!" ENDIF UNTIL FLAG =name$ : : REM Accept the age from the keyboard and round to one place REM of decimals. decimals. Ages of 0 and less or 150 or REM more are in error. : DEF FN_age LOCAL age REPEAT INPUT "What age ? " age IF age<=0 OR age >=150 THEN PRINT "No impossible ages please !!!" UNTIL age>0 AND age<150 =INT(age*10+.5)/10 : : REM Accept the height in centimeters from the keyboard and REM round to an integer. Heights of 50 or less and 230 or REM more are in error. : DEF FN_height LOCAL height REPEAT INPUT "Height in centimeters ? " height IF height<=50 OR height>=230 THEN PRINT "Very funny !!!" UNTIL height>50 AND height<230 =INT(height+.5) : : REM Accept the sex from the keyboard. Only words beginning REM with upper or lower case M or F are acceptable. The REM returned string is truncated to 1 character. : DEF FN_sex LOCAL sex$,FLAG REPEAT FLAG=TRUE INPUT "Male or Female - M or F ? " sex$ IF sex$<>"" THEN sex$=CHR$(ASC(MID$(sex$,1,1)) AND 95) IF sex$<>"M" AND sex$<>"F" THEN FLAG=FALSE IF NOT FLAG THEN PRINT "No more sex(es) please !!!" UNTIL FLAG =sex$
REM F-WSTD : REM EXAMPLE OF WRITING A COMPATIBLE DATA FILE : REM This program opens a data file and writes numeric and REM character data to it in a compatible format. Numerics REM are changed to strings before they are written and the REM data items are separated by commas. Each record is REM terminated by CR LF and the file is terminated by a REM Control Z. : REM Functions are used to accept and validate the data REM before writing it to the file. : OSCLI "CD """+@usr$+"""" : compat=OPENOUT("COMPAT.DAT") PRINT "File Name COMPAT.DAT Opened as Channel ";compat PRINT REPEAT name$=FN_name IF name$="" THEN PROC_end age=FN_age height=FN_height sex$=FN_sex PRINT record$=name$+","+STR$(age)+","+STR$(height)+","+sex$ PRINT#compat,record$ BPUT#compat,&0A UNTIL FALSE : DEF PROC_end BPUT#compat,&1A CLOSE#compat END : : REM Accept a name from the keyboard and make sure it REM consists only of spaces and upper or lower case REM characters. Leading spaces are automatically ignored REM on input. : DEF FN_name LOCAL name$,FLAG,n REPEAT FLAG=TRUE INPUT "Name ? " name$ IF name$<>"" THEN FOR I=1 TO LEN(name$) n=ASC(MID$(name$,I,1)) IF NOT(n=32 OR n>64 AND n<91 OR n>96 AND n<123) THEN FLAG=TRUE NEXT IF NOT FLAG THEN PRINT "No funny characters please !!!" ENDIF UNTIL FLAG =name$ : : REM Accept the age from the keyboard and round to one place REM of decimals. Ages of 0 and less or 150 or more are REM considered to be in error. : DEF FN_age LOCAL age REPEAT INPUT "What age ? " age IF age<=0 OR age >=150 THEN PRINT "No impossible ages please !!!" UNTIL age>0 AND age<150 =INT(age*10+.5)/10 : : REM Accept the height in centimeters from the keyboard and REM round to an integer. Heights of 50 or less and 230 or REM more are considered to be in error. : DEF FN_height LOCAL height REPEAT INPUT "Height in centimeters ? " height IF height<=50 OR height>=230 THEN PRINT "Verry funny !!!" UNTIL height>50 AND height<230 =INT(height+.5) : : REM Accept the sex from the keyboard. Only words beginning REM with upper or lower case M or F are acceptable. The REM returned string is truncated to 1 character. : DEF FN_sex LOCAL sex$,FLAG REPEAT FLAG=TRUE INPUT "Male or Female - M or F ? " sex$ IF sex$<>"" THEN sex$=CHR$(ASC(MID$(sex$,1,1)) AND 95) IF sex$<>"M" AND sex$<>"F" THEN FLAG=FALSE IF NOT FLAG THEN PRINT "No more sex(es) please !!!" UNTIL FLAG =sex$
REM F-RSTD : REM EXAMPLE OF READING A COMPATIBLE DATA FILE : REM This program opens a data file and reads numeric and REM character data from it. The data is read a byte at a REM time and the appropriate action taken depending on REM whether it is a character, a comma, or a control char. : OSCLI "CD """+@usr$+"""" : compat=OPENUP("COMPAT.DAT") PRINT "File Name COMPAT.DAT Opened as Channel ";compat PRINT REPEAT name$=FN_read PRINT "Name ";name$ age=VAL(FN_read) PRINT "Age ";age height=VAL(FN_read) PRINT "Height in centimeters ";height sex$=FN_read IF sex$="M" THEN PRINT "Male" ELSE PRINT "Female" PRINT UNTIL FALSE : : REM Read a data item from the file. Treat commas and CRs REM as data item terminators and Control Z as the file REM terminator. Since we are not interested in reading a REM record at a time, the record terminator CR LF is of no REM special interest to us. We use the CR, along with REM commas, as a data item separator and discard the LF. : DEF FN_read LOCAL data$,byte$,byte dat$="" REPEAT byte=BGET#compat IF byte=&1A OR EOF#compat THEN CLOSE#compat: END IF NOT(byte=&0A OR byte=&0D OR byte=&2C) THEN data$=data$+CHR$(byte) UNTIL byte=&0D OR byte=&2C =data$
We haven't finished yet, however. We need to be able to tell whether any one record is 'live' or empty (or deleted). To do this we need an extra byte at the start of each record which we set to one value for 'empty' and another for 'live'. In all the examples we use 0 to indicate 'empty' and NOT 0 to indicate 'live'. We are writing character data to the file so we could use the first byte of the name string as the indicator because the lowest ASCII code we will be storing is 32 (space). You can't do this for mixed data files because this byte could hold a data value of zero. Because of this, we have chosen to use an additional byte for the indicator in all the examples.
Our logical record thus consists of:
Thus the maximum amount of data in each record is 83 bytes. Because we cannot tell in advance how big each record needs to be (and we may want to change it later), we must assume that ALL the records will be this length. Since most of the records will be smaller than this, we are going to waste quite a lot of space in our random access file, but this is the penalty we pay for convenience and comparative simplicity.
1 indicator byte 31 bytes for the name 51 bytes for the remarks
When we write the data to the file, we could insist that each field was treated as a fixed length field by packing each string out with spaces to make it the 'correct' length. This would force each field to start at its 'proper' byte within the record. We don't need to do this, however, because we aren't going to randomly access the fields within the record; we know the order of the fields within the record and we are going to read them sequentially into appropriately named variables. We can write the fields to the file with each field following on immediately behind the previous one. All the 'spare' room is now left at the end of the record and not split up at the end of each field.
To start with, let's call the first record on the file 'record zero', the second record 'record 1', the third record 'record 2', etc. The first byte of 'record zero' is at byte zero on the file. The first byte of 'record 1' is at byte 83 on the file. The first byte of 'record 2' is at byte 166 (2*83) on the file. And so on. So, the start point of any record can be calculated by:
Now, we need to set PTR# to the position of this byte in order to access the record. If the record number was held in 'recno' and the channel number in 'fnum', we could do this directly by:first_byte= 83*record_number
However, we may want to do this in several places in the program so it would be better to define and use a function to set PTR# as illustrated below.PTR#fnum=83*recno
Whilst the computer is quite happy with the first record being 'record zero', us mere humans find it a little confusing. What we need is to be able to call the first record 'record 1', etc. We could do this without altering the function which calculates the start position of each record, but we would waste the space allocated to 'record 0' since we would never use it. We want to call it 'record 1' and the program wants to call it 'record 0'. We can change the function to cater for this. If we subtract 1 from the record number before we multiply it by the record length, we will get the result we want. Record 1 will start at byte zero, record 2 will start at byte 83, etc. Our function now looks like this:PTR#fnum=FN_ptr(recno) DEF FN_ptr(record)=83*record
In our example so far we have used a record length of 83. If we replace this with a variable 'rec_len' we have a general function which we can use to calculate the start position of any record in the file in any program (you will need to set rec_len to the appropriate value at the start of the program). The function now becomes:DEF FN_ptr(record)=83*(record-1)
We use this function (or something very similar to it) in the following three example programs using random access files.DEF FN_ptr(record)=rec_len*(record-1)
REM F-RAND1 : REM VERY SIMPLE RANDOM ACCESS PROGRAM : REM This program maintains a random access file of names REM and remarks. There is room for a maximum of 20 REM entries. Each name can be up to a maximum of 30 REM characters long and each remark up to 50 characters. REM The first byte of the record is set non zero (in fact REM &FF) if there is a record present. This gives a REM maximum record length of 1+31+51=83. (Don't forget REM the CRs.) : OSCLI "CD """+@usr$+"""" : bell$=CHR$(7) temp$=STRING$(50," ") maxrec=20 rec_len=83 ans$="" CLS WIDTH 0 fnum=OPENUP "RANDONE.DAT" IF fnum=0 fnum=FN_setup("RANDONE.DAT") REPEAT REPEAT INPUT '"Enter record number: "ans$ IF ans$="0" CLOSE#fnum:CLS:END IF ans$="" record=record+1 ELSE record=VAL(ans$) IF record<1 OR record>maxrec PRINT bell$; UNTIL record>0 AND record<=maxrec PTR#fnum=FN_ptr(record) PROC_display INPUT '"Do you wish to change this record",ans$ PTR#fnum=FN_ptr(record) IF FN_test(ans$) PROC_modify UNTIL FALSE END : : DEF FN_test(A$) =LEFT$(A$,1)="Y" OR LEFT$(A$,1)="y" : : DEF FN_ptr(record)=rec_len*(record-1) REM This makes record 1 start at PTR# = 0 : : DEF PROC_display PRINT '"Record number ";record' flag=BGET#fnum IF flag=0 PROC_clear:ENDPROC INPUT#fnum,name$,remark$ PRINT name$;" ";remark$ ' ENDPROC : : DEF PROC_clear PRINT "Record empty" name$="" remark$="" ENDPROC : : DEF PROC_modify PRINT '"(Enter <RETURN> for no change or DELETE to delete)"' INPUT "Name ",temp$ temp$=LEFT$(temp$,30) IF temp$<>"" name$=temp$ INPUT "Remark ",temp$ temp$=LEFT$(temp$,50) IF temp$<>"" remark$=temp$ INPUT '"Confirm update record",ans$ IF NOT FN_test(ans$) ENDPROC IF name$="DELETE" BPUT#fnum,0:ENDPROC BPUT#fnum,255 PRINT#fnum,name$,remark$ ENDPROC : : DEF FN_setup(fname$) PRINT "Setting up the database file" fnum=OPENOUT(fname$) FOR record=1 TO maxrec BPUT#fnum,0 PTR#fnum=FN_ptr(record) NEXT =fnum
REM F-RAND2 REM SIMPLE DATABASE PROGRAM REM Written by R T Russell Jan 1983 REM Modified for BBCBASIC(86) by Doug Mounter Dec 1985 : REM This is a simple database program. You are asked for REM the name of the file you wish to use. If the file REM does not already exist, you are asked to specify the REM number and format of the records. If the file does REM already exist, the file specification is read from REM the file. : OSCLI "CD """+@usr$+"""" : @%=10 bell$=CHR$(7) CLS WIDTH 0 INPUT '"Enter the filename of the data file: "filename$ fnum=OPENUP(filename$) IF fnum=0 fnum=FN_setup(filename$) ELSE PROC_readgen PRINT : REPEAT REPEAT INPUT '"Enter record number: "ans$ IF ans$="0" CLOSE#fnum:CLS:END IF ans$="" record=record+1 ELSE record=VAL(ans$) IF record<1 OR record>maxrec PRINT bell$; UNTIL record>0 AND record<=maxrec PTR#fnum=FN_ptr(record) PROC_display INPUT '"Do you wish to change this record",ans$ PTR#fnum=FN_ptr(record) IF FN_test(ans$) PROC_modify UNTIL FALSE END : : DEF FN_test(A$) =LEFT$(A$,1)="Y" OR LEFT$(A$,1)="y" : : DEF FN_ptr(record)=base+rec_len*(record-1) : : DEF FN_setup(filename$) PRINT "New file." fnum=OPENOUT(filename$) REPEAT INPUT "Enter the number of records (max 1000): "maxrec UNTIL maxrec>0 AND maxrec<1001 REPEAT INPUT "Enter number of fields per record (max 20): "fields UNTIL fields>0 AND fields<21 DIM title$(fields),size(fields),A$(fields) FOR field=1 TO fields PRINT '"Enter title of field number ";field;": "; INPUT ""title$(field) PRINT REPEAT INPUT "Maximum size of field (characters)",size(field) UNTIL size(field)>0 AND size(field)<256 NEXT field rec_len=1 PRINT#fnum,maxrec,fields FOR field=1 TO fields PRINT#fnum,title$(field),size(field) rec_len=rec_len+size(field)+1 NEXT field base=PTR#fnum : FOR record=1 TO maxrec PTR#fnum=FN_ptr(record) BPUT#fnum,0 NEXT =fnum : : DEF PROC_readgen rec_len=1 INPUT#fnum,maxrec,fields DIM title$(fields),size(fields),A$(fields) FOR field=1 TO fields INPUT#fnum,title$(field),size(field) rec_len=rec_len+size(field)+1 NEXT field base=PTR#fnum ENDPROC : : DEF PROC_display PRINT '"Record number ";record' flag=BGET#fnum IF flag=0 PROC_clear:ENDPROC FOR field=1 TO fields INPUT#fnum,A$(field) PRINT title$(field);" ";A$(field) NEXT field ENDPROC : : DEF PROC_clear FOR field=1 TO fields A$(field)="" NEXT ENDPROC : : DEF PROC_modify PRINT '"(Enter <RETURN> for no change)"' FOR field=1 TO fields REPEAT PRINT title$(field);" "; INPUT LINE ""A$ IF A$="" PRINT TAB(POS,VPOS-1)title$(field);" ";A$(field) REM TAB(POS,VPOS-1) moves the cursor up 1 line UNTIL LEN(A$)<=size(field) IF A$<>"" A$(field)=A$ NEXT field INPUT '"Confirm update record",ans$ IF NOT FN_test(ans$) ENDPROC IF A$(1)="DELETE" BPUT#fnum,0:ENDPROC BPUT#fnum,255 FOR field=1 TO fields PRINT#fnum,A$(field) NEXT field ENDPROC
The following section of code is the command loop. You are offered a choice of functions until you eventually select function 0. The CASE statement is used for menu selection processing:REM F-RAND3 : REM Written by Doug Mounter - Jan 1982 REM Modified for BBCBASIC(86) Dec 1985 : REM EXAMPLE OF A RANDOM ACCESS FILE : REM This is a simple inventory program. It uses the REM item's part number as the key and stores: REM The item description - character max length 30 REM The quantity in stock - numeric REM The re-order level - numeric REM The unit price - numeric REM In addition, the first byte of the record is used REM as a valid data flag. Set to 0 if empty, D if the REM record has been deleted or V if the record is REM valid. REM This gives a MAX record length of 47 bytes REM (Don't forget the CR after the string) : PROC_initialise inventry=FN_open("INVENTRY.DAT")
This is the data entry function. You can delete or amend an entry or enter a new one. Have a look at the definition of FN_getrec for an explanation of the ASC"V" in its parameters:REPEAT CLS PRINT TAB(5,3);"If you want to:-"' PRINT TAB(10);"End This Session";TAB(55);"Type 0" PRINT TAB(10);"Amend or Create an Entry";TAB(55);"Type 1" PRINT TAB(10);"Display the Inventory for One Part";TAB(55);"Type 2" PRINT TAB(10);"Change the Stock Level of One Part";TAB(55);"Type 3" PRINT TAB(10);"Display All Items Below Reorder Level";TAB(55);"Type 4" PRINT TAB(10);"Recover a Previously Deleted Item";TAB(55);"Type 5" PRINT TAB(10);"List Deleted Items";TAB(55);"Type 6" PRINT TAB(10);"Set Up a New Inventory";TAB(55);"Type 9" REPEAT PRINT TAB(5,15);bell$; PRINT "Please enter the appropriate number (0 to 6 or 9) "; function$=GET$ UNTIL function$>"/" AND function$<"8" OR function$="9" function=VAL(function$) CASE function OF WHEN 1: PROCcreateentry WHEN 2: PROCdisplaypart WHEN 3: PROCchangepart WHEN 4: PROCreorder WHEN 5: PROCrecover WHEN 6: PROClistdeleted WHEN 9: PROCnew ENDCASE UNTIL function=0 CLS PRINT "Inventory File Closed" '' CLOSE#inventry END
This subroutine allows you to look at a record without the ability to change or delete it:REM AMEND/CREATE AN ENTRY DEF PROCcreateentry REPEAT CLS PRINT "AMEND/CREATE" partno=FN_getpartno flag=FN_getrec(partno,ASC"V") PROC_display(flag) PRINT'"Do you wish to "; IF flag PRINT "change this entry ? "; ELSE PRINT "enter data ? "; IF (GET AND &5F)<>ASC"N" flag=FN_amend(partno):PROC_cteos PROC_write(partno,flag,type) PRINT bell$;"Do you wish to amend/create another record ? "; UNTIL (GET AND &5F)=ASC"N" ENDPROC
The purpose of this subroutine is to allow you to update the stock level without having to amend the rest of the record:REM DISPLAY AN ENTRY DEF PROCdisplaypart REPEAT CLS PRINT "DISPLAY" partno=FN_getpartno flag=FN_getrec(partno,ASC"V") PROC_display(flag) PRINT ' PRINT "Do you wish to view another record ? "; UNTIL (GET AND &5F)=ASC"N" ENDPROC
This subroutine goes through the file in stock number order and lists all those items where the current stock is below the reorder level. You can interrupt the process at any time by pushing a key:REM CHANGE THE STOCK LEVEL FOR ONE PART DEF PROCchangepart REPEAT CLS PRINT "CHANGE STOCK" partno=FN_getpartno flag=FN_getrec(partno,ASC"V") REPEAT PROC_display(flag) PROC_cteos REPEAT PRINT TAB(0,12);:PROC_cteol INPUT "What is the stock change ? " temp$ change=VAL(temp$) UNTIL INT(change)=change AND stock+change>=0 IF temp$="" THEN flag=FALSE ELSE stock=stock+change PROC_display(flag) PRINT'"Is this correct ? "; temp$=GET$ ENDIF UNTIL NOT flag OR temp$="Y" OR temp$="y" PROC_write(partno,flag,ASC"V") PRINT return$;bell$; PRINT "Do you want to update any more stock levels ? "; UNTIL (GET AND &5F)=ASC"N" ENDPROC
Deleted entries are not actually removed from the file, just marked as deleted. This subroutine makes it possible for you to correct the mistake you made by deleting data you really wanted. If you have never used this type of program seriously, you won't believe how useful this is:REM DISPLAY ITEMS BELOW REORDER LEVEL DEF PROCreorder partno=1 REPEAT CLS PRINT "ITEMS BELOW REORDER LEVEL"' line_count=2 REPEAT flag=FN_getrec(partno,ASC"V") IF (flag AND stock<reord) THEN PRINT "Part Number ";partno PRINT desc$;" Stock ";stock;" Reorder Level ";reord PRINT line_count=line_count+3 ENDIF partno=partno+1 temp$=INKEY$(0) UNTIL partno>maxpartno OR line_count>20 OR temp$<>"" PRINT TAB(0,23);bell$;"Press any key to continue or E to end "; temp$=GET$ UNTIL partno>maxpartno OR temp$="E" OR temp$="e" partno=0 ENDPROC
This subroutine lists all the deleted entries so you can check you really don't want the data:REM RECOVER A DELETED ENTRY DEF PROCrecover REPEAT CLS PRINT "RECOVER DELETED RECORDS" partno=FN_getpartno flag=FN_getrec(partno,ASC"D") PROC_display(flag) PRINT IF flag THEN PRINT "If you wish to recover this entry type Y "; temp$=GET$ IF temp$="Y" OR temp$="y" PROC_write(partno,flag,ASC"V") ENDIF PRINT return$;bell$;"Do you wish to recover another record ? "; UNTIL (GET AND &5F)=ASC"N" ENDPROC
This is where all the variables that you usually write as CHR$(#) go. Then you can find them if you want to change them:REM LIST DELETED ENTRIES DEF PROClistdeleted partno=1 REPEAT CLS PRINT "DELETED ITEMS"' line_count=2 REPEAT flag=FN_getrec(partno,ASC"D") IF flag THEN PRINT "Part Number ";partno PRINT "Description ";desc$' line_count=line_count+3 ENDIF partno=partno+1 temp$=INKEY$(0) UNTIL partno>maxpartno OR line_count>20 OR temp$<>"" PRINT TAB(0,23);bell$;"Press any key to continue or E to end "; UNTIL partno>maxpartno OR (GET AND &5F)=ASC"E" partno=0 ENDPROC : : REM REINITIALISE THE INVENTORY DATA FILE DEF PROCnew CLS PRINT TAB(0,3);bell$;"Are you sure you want to set up a new inventory?" PRINT "You will DESTROY ALL THE DATA YOU HAVE ACCUMULATED so far." PRINT "If you are SURE you want to do it, enter YES" INPUT "Otherwise, just hit Return ",temp$ IF temp$="YES" PROC_setup(inventry) ENDPROC
It's a new file, so we won't go through the warning bit.REM INITIALISE ALL THE VARIOUS PRESETS ETC : DEF PROC_initialise CLS bell$=CHR$(7) return$=CHR$(13) rec_length=47 partno=0 desc$=STRING$(30," ") temp$=STRING$(40," ") WIDTH 0 OSCLI "CD """+@usr$+"""" ENDPROC : REM OPEN THE FILE AND RETURN THE FILE NUMBER : REM If the file already exists, the largest permitted REM part number is read into maxpartno. REM If it is a new file, file initialisation is carried REM out and the largest permitted part number is REM written as the first record. : DEF FN_open(name$) fnum=OPENUP(name$) IF fnum>0 INPUT#fnum,maxpartno: =fnum fnum=OPENOUT(name$) CLS PROC_setup(fnum) =fnum
Ask for the required part number. If a null is entered, make the next part number one more than the last:REM SET UP THE FILE : REM Ask for the maximum part number required, write REM it as the first record and then write 0 in to REM the first byte of every record. : DEF PROC_setup(fnum) REPEAT PRINT TAB(0,12);bell$;:PROC_cteos INPUT "What is the highest part number required (Max 4999) ",maxpartno UNTIL maxpartno>0 AND maxpartno<5000 AND INT(maxpartno)=maxpartno PTR#fnum=0 PRINT#fnum,maxpartno FOR partno=1 TO maxpartno PTR#fnum=FN_ptr(partno) BPUT#fnum,0 NEXT partno=0 ENDPROC
Part numbers run from 1 up. The record for part number 1 starts at byte 5 of the file. The start position could have been calculated as (part-no -1) *record_length + 5. The expression below works out to the same thing, but it executes quicker:REM GET AND RETURN THE REQUIRED PART NUMBER : DEF FN_getpartno REPEAT PRINT TAB(0,5);bell$;:PROC_cteos PRINT "Enter a Part Number Between 1 and ";maxpartno ' IF partno<>maxpartno THEN PRINT "The Next Part Number is ";partno+1; PRINT " Just hit Return to get this"' ENDIF INPUT "What is the Part Number You Want ",partno$ IF partno$<>"" THEN partno=VAL(partno$) ELSE IF partno=maxpartno partno=0 ELSE partno=partno+1 ENDIF PRINT TAB(35,9);partno;:PROC_cteol UNTIL partno>0 AND partno<maxpartno+1 AND INT(partno)=partno =partno : : REM GET THE RECORD FOR THE PART NUMBER : REM Return TRUE if the record exists and FALSE if not REM If the record does not exist, load desc$ with "No Record" REM The remainder of the record is set to 0 : DEF FN_getrec(partno,type) stock=0 reord=0 price=0 PTR#inventry=FN_ptr(partno) test=BGET#inventry IF test=0 desc$="No Record": =FALSE IF test<>type THEN IF type=86 desc$="Record Deleted" ELSE desc$="Record Exists" =FALSE ENDIF INPUT#inventry,desc$ INPUT#inventry,stock,reord,price =TRUE
This function amends the record as required and returns with flag=TRUE if any amendment has taken place. It also sets the record type indicator (valid deleted or no record) to ASC"V" or ASC"D" as appropriate:REM CALCULATE THE VALUE OF PTR FOR THIS RECORD : DEF FN_ptr(partno)=partno*rec_length+5-rec_length
Write the record to the file if necessary (flag=TRUE):REM AMEND THE RECORD : DEF FN_amend(partno) PRINT return$;:PROC_cteol:PRINT TAB(0,4); PRINT "Please Complete the Details for Part Number ";partno PRINT "Just hit Return to leave the entry as it is"' flag=FALSE type=ASC"V" INPUT "Description - Max 30 Chars " temp$ IF temp$="DELETE" type=ASC"D": =TRUE temp$=LEFT$(temp$,30) IF temp$<>"" desc$=temp$:flag=TRUE IF desc$="No Record" OR desc$="Record Deleted" =FALSE INPUT "Current Stock Level " temp$ IF temp$<>"" stock=VAL(temp$):flag=TRUE INPUT "Reorder Level " temp$ IF temp$<>"" reord=VAL(temp$):flag=TRUE INPUT "Unit Price " temp$ IF temp$<>"" price=VAL(temp$):flag=TRUE =flag
Print the record details to the screen. If the record is not of the required type (V or D) or it does not exist, stop after printing the description. The description holds "Record Exists" or "Record Deleted" or valid data as set by FN_getrec:REM WRITE THE RECORD : DEF PROC_write(partno,flag,type) IF NOT flag ENDPROC PTR#inventry=FN_ptr(partno) BPUT#inventry,type PRINT#inventry,desc$,stock,reord,price ENDPROC
The following two procedures rely on there being 80 characters per line and 25 lines (for example when MODE 3 or 16 is selected):REM DISPLAY THE RECORD DETAILS : DEF PROC_display(flag) PRINT TAB(0,5);:PROC_cteos PRINT "Part Number ";partno' PRINT "Description ";desc$ IF NOT flag ENDPROC PRINT "Current Stock Level ";stock PRINT "Reorder Level ";reord PRINT "Unit Price ";price ENDPROC
REM There are no 'native' clear to end of line/screen REM vdu procedures. The following 2 procedures clear to REM the end of the line/screen. : DEF PROC_cteol LOCAL x,y x=POS:y=VPOS REPEAT VDU 32 : UNTIL POS=0 PRINT TAB(x,y); ENDPROC : : DEF PROC_cteos LOCAL x,y x=POS:y=VPOS WHILE VPOS<24 VDU 32 : ENDWHILE REPEAT VDU 32 : UNTIL POS=0 PRINT TAB(x,y); ENDPROC
NAME.NDX (index file)
maxrec 5 bytes |
length 5 bytes |
index$(1) 1 to 31 bytes |
index(1) 5 bytes |
index$(2) 1 to 31 bytes |
index(2) 5 bytes |
etc. |
Where index(n) points to a record in the data file as follows:
ADDRESS.DTA (data file)
Phone Num 1 to 31 bytes |
Address 1 1 to 31 bytes |
Address 2 1 to 31 bytes |
Address 3 1 to 31 bytes |
Address 4 1 to 31 bytes |
Post Code 1 to 31 bytes |
maxrec | Is the maximum number of records permitted in this file. The practical limit is governed by the amount of memory available for the index arrays which are held in memory. |
length | Is the number of entries in the index. |
index(n) | Is the value of PTR#datanum just prior to the first byte of the data for this entry being written to it. In the Random File examples this value was calculated and it increased by a constant amount for every record. |
Move everything below the entry you want deleted up one and subtract 1 from the length:REM F-INDEX REM EXAMPLE OF AN INDEXED FILE : REM Written by Doug Mounter - Feb 1982 REM Modified for BBCBASIC(86) - Dec 1985 : REM This is a simple address book filing system. It will REM accept names, telephone numbers and addresses and store REM them in a file called ADDRESS.DTA. The index is in REM name order and is kept in a file called NAME.NDX. REM All the fields are character and the maximum length REM of any field is 30. : PROC_initialise PROC_open_files ON ERROR IF ERR<>17 PRINT:REPORT:PRINT" At line ";ERL:END REPEAT CLS PRINT TAB(5,3);"If you want to:-" ' PRINT TAB(10);"End This Session";TAB(55);"Type 0" PRINT TAB(10);"Enter Data";TAB(55);"Type 1" PRINT TAB(10);"Search For/Delete an Entry";TAB(55);"Type 2" PRINT TAB(10);"List in Alphabetical Order";TAB(55);"Type 3" PRINT TAB(10);"Reorganize the Data File and Index";TAB(55);"Type 4"; REPEAT PRINT TAB(5,11); PRINT "Please enter the appropriate number (0 to 4) "; function$=GET$ PRINT return$;:PROC_cteol UNTIL function$>"/" AND function$<"5" function=VAL(function$) PRINT TAB(54,function+5);"<====<"; ON function PROC_enter,PROC_search,PROC_list,PROC_reorg ELSE UNTIL function=0 CLS PROC_close_files *ESC ON PRINT "Address Book Files Closed"'' END : : REM ENTER DATA : DEF PROC_enter flag=TRUE temp$="" i=1 REPEAT REPEAT PROC_get_data CASE TRUE OF WHEN length=maxrec OR data$(1)="": flag=FALSE WHEN data$(1)="+" OR data$(1)="-": PROC_message("Bad Data") OTHERWISE: i=FN_find_place(0,data$(1)) IF i>0 PROC_message("Duplicate Record") PRINT '"Is this data correct ? "; temp$=FN_yesno IF temp$="N" PROC_message("Data NOT Accepted") ENDCASE UNTIL NOT flag OR temp$<>"N" PROC_cteos IF flag THEN PROC_put_index(i,data$(1),PTR#datanum) FOR i=2 TO 7 PRINT#datanum,data$(i) NEXT ENDIF UNTIL NOT flag ENDPROC : : REM SEARCH FOR AN ENTRY : DEF PROC_search i=0 REPEAT PRINT TAB(0,11);:PROC_cteol INPUT "What name do you want to look for ",name$ IF name$<>"" THEN IF name$="DELETE" PROC_delete(i) ELSE i=FN_display(i,name$) ENDIF UNTIL name$="" ENDPROC : : REM LIST IN ALPHABETICAL ORDER : DEF PROC_list entry=1 REPEAT CLS line_count=0 REPEAT PRINT TAB(0,line_count); PROC_read_data(entry) PROC_print_data entry=entry+1 line_count=line_count+8 temp$=INKEY$(0) UNTIL entry>length OR line_count>16 OR temp$<>"" PROC_message("Push any key to continue or E to end ") UNTIL entry>length OR (GET AND &5F)=ASC"E" ENDPROC : : REM REORGANIZE THE DATA FILE AND INDEX : DEF PROC_reorg entry=1 PRINT TAB(0,13);"Reorganizing the Data File" ' newdata=OPENOUT"ADDRESS.BAK" REPEAT PROC_read_data(entry) index(entry)=PTR#newdata FOR i=2 TO 7 PRINT#newdata,data$(i) NEXT entry=entry+1 UNTIL entry>length CLOSE#newdata : CLOSE#datanum PRINT "Re-naming the Data File" ' *REN ADDRESS.BAK ADDRESS.$$$ PRINT "*"; *REN ADDRESS.DTA ADDRESS.BAK PRINT "*"; *REN ADDRESS.$$$ ADDRESS.DTA PRINT "*"; datanum=OPENUP "ADDRESS.DTA" ENDPROC : : REM INITIALISE VARIABLES AND ARRAYS : DEF PROC_initialise CLS *ESC OFF esc$=CHR$(27) bell$=CHR$(7) return$=CHR$(13) maxrec=100 OSCLI "CD """+@usr$+"""" : REM The maximum record number, maxrec, is read in REM PROC_read_index if the file already exists. : DIM message$(7) FOR i=1 TO 7 READ message$(i) NEXT DATA Name,Phone Number,Address,-- " --,-- " --,-- " --,Post Code : DIM data$(7) FOR i=1 TO 7 data$(i)=STRING$(30," ") NEXT temp$=STRING$(255," ") temp$="" : ENDPROC : : REM OPEN THE FILES : DEF PROC_open_files indexnum=OPENUP"NAME.NDX" datanum=OPENUP"ADDRESS.DTA" IF indexnum=0 OR datanum=0 PROC_setup ELSE PROC_read_index PTR#datanum=EXT#datanum ENDPROC : : REM SET UP NEW INDEX AND DATA FILES : DEF PROC_setup CLS PRINT TAB(0,13);"Setting Up Address Book" indexnum=OPENOUT"NAME.NDX" datanum=OPENOUT"ADDRESS.DTA" length=0 PRINT#indexnum,maxrec,length CLOSE#indexnum DIM index$(maxrec+1),index(maxrec+1) index$(0)="" index(0)=0 index$(1)=CHR$(&FF) index(1)=0 ENDPROC : : REM READ INDEX AND LENGTH OF DATA FILE : DEF PROC_read_index CLS INPUT#indexnum,maxrec,length DIM index$(maxrec+1), index(maxrec+1) index$(0)="" index(0)=0 FOR i=1 TO length INPUT#indexnum,index$(i),index(i) NEXT CLOSE#indexnum index$(length+1)=CHR$(&FF) index(length+1)=0 ENDPROC : : REM WRITE INDEX AND CLOSE FILES : DEF PROC_close_files indexnum=OPENOUT"NAME.NDX" PRINT#indexnum,maxrec,length FOR i=1 TO length PRINT#indexnum,index$(i),index(i) NEXT CLOSE#0 ENDPROC : : REM WRITE A MESSAGE AT LINE 23 : DEF PROC_message(line$) LOCAL x,y x=POS y=VPOS PRINT TAB(0,23);:PROC_cteol:PRINT bell$;line$; PRINT TAB(x,y); ENDPROC : : REM GET A Y/N ANSWER : DEF FN_yesno LOCAL temp$ temp$=GET$ IF temp$="y" OR temp$="Y" ="Y" IF temp$="n" OR temp$="N" ="N" ="" : : REM CLEAR 9 LINES FROM PRESENT POSITION : DEF PROC_clear9 LOCAL x,y,n x=POS:y=VPOS:PRINT return$; FOR n=1 TO 9 : REPEAT VDU 32 : UNTIL POS=0 : NEXT n PRINT TAB(x,y); ENDPROC : : REM GET INPUT DATA - LIMIT TO 30 CHAR : DEF PROC_get_data LOCAL i PRINT TAB(0,13); PROC_clear9 IF length=maxrec PROC_message("Address Book Full") FOR i=1 TO 7 PRINT TAB(10);message$(i);TAB(25); INPUT temp$ data$(i)=LEFT$(temp$,30) IF data$(1)="" i=7 NEXT ENDPROC : : REM FIND AND DISPLAY THE REQUESTED DATA : DEF FN_display(i,name$) PRINT TAB(0,12);:PROC_cteos i=FN_find_place(i,name$) IF i<0 PROC_message("Name Not Known - Next Highest Given") PROC_read_data(i) PRINT PROC_print_data =i
Get the start of the position of the start of the data record for entry 'i' in the index and read it into the buffer array data$(). Save the current value of the data file pointer on entry and restore it before leaving:REM DELETE THE ENTRY FROM THE INDEX : DEF PROC_delete(i) INPUT "Are you SURE ",temp$ PRINT TAB(0,VPOS-1);:PROC_cteos IF temp$<>"YES" ENDPROC IF i<0 i=-i FOR i=i TO length index$(i)=index$(i+1) index(i)=index(i+1) NEXT length=length-1 ENDPROC
Move all the directory entries from position i onwards down the index (in fact you have to start at the end and work back). Slot the new entry in the gap made at position i and add 1 to the length:REM READ DATA FOR ENTRY i : DEF PROC_read_data(i) PTRdata=PTR#datanum IF i<0 i=-i PTR#datanum=index(i) data$(1)=index$(i) FOR i=2 TO 7 INPUT#datanum,data$(i) NEXT PTR#datanum=PTRdata ENDPROC : : REM PRINT data$() ON VDU : DEF PROC_print_data LOCAL i FOR i=1 TO 7 IF data$(i)<>"" PRINT TAB(10);message$(i);TAB(25);data$(i) IF data$(1)=CHR$(&FF) i=7 NEXT ENDPROC
This function looks in the index for the string entry$. If it finds it it returns with i set to its position in the index. If not, i is set to minus the position of the next highest string (in other words, the position you wish to put the a new entry). Thus if a part of the index looked like:REM PUT A NEW ENTRY IN INDEX AT POSITION i : DEF PROC_put_index(i,entry$,ptr) LOCAL j IF i<0 i=-i FOR j=length+1 TO i STEP -1 index$(j+1)=index$(j) index(j+1)=index(j) NEXT index$(i)=entry$ index(i)=ptr length=length+1 ENDPROC
and you entered with FRED, it would return 35. However if you entered with GEORGE, it would return -36.
(34) BERT (35) FRED (36) JOHN
The function consists of two parts. The first looks at the entry$ to see if it should just up or down the entry number by 1, taking account of wrap-around at the start and end of the index. The second part is the binary chop advertised with such telling wit in the introduction to indexed files. Since we enter this function with the entry pointer i set to its previous value, we must cater for a negative value.
Here, at last, T H E B I N A R Y C H O PREM FIND ENTRY IN INDEX OR PLACE TO PUT IT : DEF FN_find_place(i,entry$) LOCAL top,bottom IF i<0 i=-i IF entry$="+" AND i<length =i+1 IF entry$="+" AND i=length =1 IF entry$="-" AND i>1 =i-1 IF entry$="-" AND i<2 =length
This bit moves the pointer up the index to the first of any duplicate entries.top=length+1 bottom=0 i=(top+1) DIV 2 IF entry$<>index$(i) i=FN_search(entry$) REPEAT IF entry$=index$(i-1) i=i-1
The two following procedures rely on mode 3 or 7 being selected. They will not work properly if a graphics mode has been selected or if some characters on the screen have attributes set.UNTIL entry$<>index$(i-1) IF entry$=index$(i) =i ELSE =-i : : REM DO THE SEARCHING FOR FN_find_place : DEF FN_search(entry$) REPEAT IF entry$>index$(i) bottom=i ELSE top=i i=(top+bottom+1) DIV 2: REM round UNTIL entry$=index$(i) OR top=bottom+1 =i
Well, that's it. Apart from the following notes on the binary chop you have read it all.REM There are no 'native' clear to end of line/screen REM vdu procedures. The following 2 procedures clear to REM the end of the line/screen. : DEF PROC_cteol LOCAL x,y x=POS:y=VPOS REPEAT VDU 32 : UNTIL POS=0 PRINT TAB(x,y); ENDPROC : : DEF PROC_cteos LOCAL x,y x=POS:y=VPOS WHILE VPOS<24 VDU 32 : ENDWHILE REPEAT VDU 32 : UNTIL POS=0 PRINT TAB(x,y); ENDPROC
Let's try searching the list of numbers below for the number 14.
bottom> (1) 3 Set bottom to the lowest position in the list, and top to the highest. Set the pointer to (top+bottom)/2. Is that entry 14? No it's more, so set top to the current value of pointer and repeat the process. (2) 6 (3) 8 (4) 14 pointer> (5) 19 (6) 23 (7) 34 (8) 45 top> (9) 61
bottom> (1) 3 Set the pointer to (top+bottom)/2. Is that entry 14? No it's less, so set bottom to the current value of pointer and try again. (2) 6 pointer> (3) 8 (4) 14 top> (5) 19 (6) 23 (7) 34 (8) 45 (9) 61
As you can imagine, things are not always as simple as this carefully chosen example. You have to cater for the number not being there, and for the list being empty. There are a number of ways of doing this, but the easiest is to add two numbers of your choice to the list. Make the first entry the most negative number the computer can hold, and the last entry the most positive. This will prevent you ever trying to search outside the list. Preventing a perpetual loop when the number you want is not in the list is quite simple, just exit when 'top' is equal to 'bottom'+1. If you have not found the number by then, it's not in the list.
(1) 3 Set the pointer to (top+bottom)/2. Is that entry 14? Yes, so exit with the pointer set to the position in the list of the number you are looking for. (2) 6 bottom> (3) 8 pointer> (4) 14 top> (5) 19 (6) 23 (7) 34 (8) 45 (9) 61
You can use this routine to add numbers to the list in order. If you can't find the number, you exit with the position it should go in the list. Just move all the numbers under it down one slot and put the new number in. This works just as well when the list is empty except for your two 'end markers'.
Have a look at the flow chart below and work through a couple of dry runs with a short list of numbers. You may think that it's not worth doing it this way and that a 'linear search' would be as quick. Try it with a list of 100 numbers. It should take you no more than 7 goes to find the number. The AVERAGE number of comparisons required for a linear search would be 50.
CONTENTS |
CONTINUE |