The Z88 supports a fairly uniform device-independent I/O system, so although calls do exist wich will explicitly send data to, say, the screen or the serial port, for all operations other than reading from standard input and writing to standard output, it is usual to regard such devices as files and use a standard file I/O interface.
This section will cover file access for applications, while noting that the approach remains the same if device names are substituted for the filename, thus opening a stream to the device. Major pseudo-file are:
:SCR.0 The screen. Write only. :PRT.0 The printer. Write only. :COM.0 The serial interface :NUL.0 Null device. Output discarded, always end of file for input. :INP.0 Standard input. Read only. Normally the keyboard, but may be redirected by the CLI. :OUT.0 Standard output. Write only. Normally the screen, but again, may be redirected.
Note that the terminating numbers are optional, ie. ':COM' is equivalent to ':COM.0'. As an example of how devices are accessed using file I/O look at this BBC BASIC program:
The program sends 'Hello' to the serial port. The extra line feed character is necessary as both <CR> and <LF> are necessary for a true newline when writing to an actual display device and BBC BASIC's PRINT# command only sends a <CR>.
This can be broken into three basic stages:
The open file call must be provided with a filename and an access mode. The access mode is open for input, output or update, although other options are available for working with directories and DOR's. The call returns a handle which is unique to the particular access of the file, but note that more than one handle may be associated with any particular file. A file may be open for input from several source, but may only be opened for update or output by one source. An attempt to open a file for output or updating, which is already open for output or updating will result in an "In use" error, RC_USE. Other reasons for not being able to open files are poor filenames, read or write protection of a device, non-existence (in the case of opening for input or update), a lack of memory or lack of handles. There are only a finite supply of handles - about 150. The call used for opening files and is GN_Opf.
All file access is referenced by the file handle. Access to RAM files is random with a sequential pointer into the file which can be both read and written. Both byte by byte transfer, and transfer of whole blocks is possible. Calls for file access are OS_Pb (put byte), OS_Gb (get byte) and OS_Mv (move bytes between a file and memory). Timed versions of the byte transfer calls exists (OS_Pbt and OS_Gbt) for use with devices like the printer and serial port. Three file attributes can be read, and two written. PTR (pointer) is the file pointer and gives the location wthin the file. EXT (extent) gives the size of the file and finally EOF (end of file) is the read only attribute which is true when the file pointer is at the end of the file ie. when PTR = EXT. These attributes may be read using OS_Frm (file read miscellaneous) and written with OS_Fwm (file write miscellaneous).
The close call needs only be supplied with the file handle. When closed, a file is free for updates from other sources, its handle is released, any memory used for buffers is freed, etc. The call used to close files and streams is GN_Cl.
An open file may not be deleted and an attempt to do this will result in an "In use" (RC_USE) error. Note that because opening a file for output will attempt delete the file if it already exists, the "In use" error also occurs in this context.
Filenames come in two forms, only one of which is actually given a name. Ordinary, as it were, filenames can have as much or as little information as is required to uniquely identify a file. For example "fred.txt" is an ordinary filename, or even "fred.*", which will probably be sufficient to find the same file. An explicit filename, on the other hand, is one which includes all the information associated with the file, including it's full name, any directory within which it resides, and any in which that may reside, and finally the device where the file is stored. So we might have ":RAM.1/maindir/fred.txt" as an explicit filename.
Each part of the explicit filename divided by slashes (or backslashes) is called a segment, so the above name consists of three segments which are a device, a directory and a filename respectively. Segments will always come in this order, but there may be several directories in the explicit name. Each segment consists of a name which is up to twelve characters long and an optional extension, separated from the name by a full stop, which may be up to three characters long. All device names are preceeded by a colon. The system provides various calls for breaking down and assembling filenames in terms of segments, such as GN_Esa (read and write specific segments), GN_Fex (produce explicit filename) and GN_Fcm (compress an explicit filename). The open file call, GN_Opf, in the course of its operation produces from the filename supplied an explicit filename which can be returned in full, or in a cut down form, to the user. Other useful filename processing calls are GN_Pfs (parse filename segment) and GN_Prs (parse filename), which not only check for correct syntax, but also return information as to what has been specified in the filename, eg. whether wildcards have been used, or a specific device mentioned. The wildcard facilities are covered later in "Wildcards".
The above rules apply to the RAM filing system accessed with the :RAM.x device (x stands for the slot number 0-3). The EPROM file area can be accessed directly by the :EPR.x device.
The file transfer calls may be suspended in circumstances. If the stream being used is associated either the serial port or printer then RC_SUSP will be returned if the machine is switched off and on again, or if a battery low interrupt occurs. If escape detection is enabled then <ESC> will cause file transfer to abort and RC_ESC to be returned. A more serious case is where the stream is associated with the keyboard. If this is the situation, pre-emption can occur when reading from the stream and the possible return codes will be RC_SUSP, RC_DRAW and RC_QUIT.
If a lot of data is to be transferred between memory and a stream then doing the work a byte at a time can be quite a slow process. Each time a byte is transferred with these calls, the operating system must be paged in and various checks and action carried out, all of which generates a considerable overhead. The OS_Mv call reduces this overhead by transferring a large number of bytes between memory and a stream all in one go, and so can operate much more quickly. It can be useful for operations like loading and saving files.
GN_OpfOpen a file, returns a file handle used for subsequent access GN_ClClose a file or stream OS_GbGet byte from file or stream OS_GbtAs OS_Gb but with timeout OS_PbPut byte to file or stream OS_PbtAs OS_Pb but with timeout OS_MvMove bytes from stream to memory, or from memory to stream OS_FrmRead file or stream parameters PTR, EXT and EOF OS_FwmWrite file parameters PTR and EXT
; very simple example of opening and closing a file
; open a file and display full filename assumes that on entry
; HL points to filename and that DE points to a 40 byte buffer
; entry point is main
include "fileio.def" ; file I/O definition calls, parameters
include "stdio.def" ; standard I/O definition calls, parameters
include "errors.def" ; error code definitions
.main push de ; save DE for future reference
ld b, 0 ; indicate HL a local address
ld c, 40 ; size of expanded name buffer
ld a, OP_IN ; open file for input
oz GN_Opf ; open...
jr nc, no_error
oz GN_Err ; display error in error box
pop de ; balance stack
.no_error xor a
ld (de),a ; terminate explicit filename
ld hl, file_mess
oz GN_Sop ; output constant string
pop hl ; get start address of expanded name
oz GN_Sop ; output expanded name
oz GN_Nln ; newline
oz GN_Cl ; now close the file
.file_mess defm "Using: ", 0