In the penultimate article on Basic programming, David Matthewman looks at reading and writing files
Files are a common and useful means of transferring data to and from a program. The program reads the data in the file in much the same way as it reads data from DATA statements, which were covered last month.
There are a number of advantages to reading data from files. The data can easily be changed without editing the program, and the program can itself write data to a file.
Basic provides a number of powerful commands for manipulating files.
First, open your file. There are three related Basic commands to do this. All take a string variable containing the filename and return an integer containing the file handle.
The file handle is a number that the Risc OS filing system will use to refer internally to the file. You will never need to know the value of this handle, but you should check that it is not zero, as this indicates an error when opening the file.
The three commands are:
handle%=OPENIN(filename$) handle%=OPENOUT(filename$) handle%=OPENUP(filename$) |
OPENIN opens a file for input, OPENOUT opens a file for output and OPENUP opens one for both.
A file of the right name must exist when using OPENIN, but if it exists when using the other two commands it will be deleted and overwritten.
Once the file has been opened, EXT#handle% will give the size of the file, and PTR#handle% the current position in the file, in bytes. Both of these can be set, for example:
PTR#handle%=PTR#handle%+4 |
would move the file pointer on four bytes - one word - and
EXT#handle%=EXT#handle%-1024 |
would make a file 1k shorter.
At the simplest level, files can be read and written one byte at a time using BGET# and BPUT#.
Both of these increase the value of PTR# by one automatically, and so sequences of BGET#s and BPUT#s can be used to read and write blocks of bytes.
When the last byte in the file has been read, EOF#handle% is set to TRUE. This can be tested for to avoid running off the end of the file. The central loop of Listing 1 is of the form:
WHILE NOT EOF#handle% var%=BGET#handle% ... do something ... ENDWHILE |
which reads in the entire file, byte by byte.
Listing 1 takes a text file and prints it to the screen, printing the percentage of the file read between each line.
Listing 1 |
REM >Listing1 REM Reads and displays a text file : ON ERROR REPORT:PRINT " at line ";ERL:END file$="<BackToBasics$Dir>.File1" handle%=OPENIN(file$) IF handle%=0 THEN PRINT "File ";file$;" not found.":END WHILE NOT EOF#handle% byte%=BGET#handle% IF (byte%=10) OR (byte%>=32) THEN PRINT CHR$(byte%); ELSE PRINT "." ENDIF IF (byte%=10) PRINT 'INT(PTR#handle%*100/EXT#handle%);"%" ENDWHILE CLOSE#handle% END |
In order to run Listing 1, you must first run the SetDir file in the BasicProg directory. This tells the program where to find the File1 file.
If you copy the files in the BasicProg directory to another directory, remember to copy the SetDir file and run it to let the program know where it can now find the File1 file.
When you have finished with a file, you should use CLOSE#handle% to let the filing system know. This is essential when writing to a file, as output is buffered and the last block of a file will not be written if it is not CLOSE#ed.
Reading one byte at a time is all very well, but sometimes we want to read several bytes at one go, to input a string or a large number. The statements to read and write blocks of data are:
INPUT#handle%,<data> PRINT#handle%,<data>
where <data> can be one or more string, integer or real variables.
Listing 2 shows how to use these statements in a program. As in listing 1, the program loops until the end of the file is found.
Listing 2 |
REM >Listing2 REM This program will copy a text file, numbering the lines : ON ERROR REPORT:PRINT " at line ";ERL:END filein$="<BackToBasics$Dir>.File2" fileout$="<BackToBasics$Dir>.File3" handlein%=OPENIN(filein$) IF handlein%=0 THEN PRINT "File ";filein$;" not found.":END handleout%=OPENOUT(fileout$) i%=0 WHILE NOT EOF#handlein% INPUT#handlein%,in$ PRINT#handleout%,i%,in$ i%+=1 ENDWHILE CLOSE#handlein% CLOSE#handleout% *SETTYPE <BackToBasics$Dir>File3 Text END |
This time though, instead of reading the file byte by byte, each INPUT# statement reads a string from File2, and each OUTPUT# statement writes the string back to File3, prefixing each line with a line number.
Because listing 2 writes output to a file, it cannot be run from the cover disc and must be copied to another disc first. Remember to copy at least the SetVar and File2 programs as well.
Line 18 of listing 2 sets File3's filetype to TEXT. The filetype affects the file's appearence on the desktop, and dictates what happens to the file when it is run. When a TEXT file is run, Risc OS loads it into a text editor.
If you look at File2 and File3 in a text editor, you will see that they are not in a 'plain text' format. All the strings are backwards and the numbers are unrecognisable.
This is because Basic reads and writes variables using INPUT# and PRINT# in a special format related to the format in which it stores them in memory.
Strings are stored with the characters written in reverse order, prefixed by zero and the string length. Integer variables are five bytes long: &40 and four bytes of data.
Reals are six bytes long: &FF, four bytes representing the digits of the number - the mantissa Ä and one byte for the exponent. In the case of the numbers this makes storage considerably more compact, at the expense of easy readability. If you haven't recognised the text already, then inserting the line
PRINT in$
in the WHILE loop in listing 2 will display it.
There are a number of operating system commands which, while not themselves part of the Basic language, are very useful for manipulating files.
There are two ways of using them from Basic. The first is to use a star (*) command as in:
*LOAD Sprites 3000
This is unfortunately very limiting. The two parameters, Sprites and 3000 cannot be variables and must be the verbatim name and load address of the file to be loaded.
Furthermore, there cannot be any statements on a line after a star command - the usual method of separating statements with a colon doesn't work.
Fortunately, Basic provides another way of using operating system commands, OSCLI. This is used as follows:
OSCLI("LOAD Sprites 3000")
The text inside the brackets is sent to the operating system as before, but it may be a string variable, or even several variables joined together. For example:
OSCLI("LoAD "+filename$+" "+STR$(address%))
would be a valid statement, and generally of more use in a program.
In addition, because OSCLI is a valid Basic statement and not an operating system command, it can be followed on the same line by other Basic statements separated by colons.
Operating system commands which may be useful for file manipulation in Basic include:
*LOAD *SAVE *DELETE *MOUNT *RUN *SETTYPE
the last of which appears in listing 2 and was mentioned earlier.
The syntax of these commands can be found in the user guide that came with your computer - there isn't room to describe it in detail here.
That's enough on files for this month. I'm sure you're itching to rush off and experiment with your new-found skills in loading and saving data.
Next month I will wrap up the series with a look at how to access the memory of the machine directly, and will have a round up of what books are available, which tackle Basic programming more extensively than is possible here.
Click here to download the example files
Source: | Acorn User 143 - June 1994 |
Publication: | Acorn User |
Contributor: | David Matthewman |