Back To Basics - Part 11

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.

Opening and closing 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.

Byte by byte

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.

Formatted input

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.

*commands

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