Each file in the Pascal environment is represented by a pointer to a files structure in the heap. At the location addressed by the pointer is the element in the file's window variable. Behind this window variable is information about the file, at the following offsets:
-14 | FBUF | Pointer to i/o buffer | |
-12 | FCHAIN | Chain to next file | |
-10 | FLEV | Pointer to associated block mark | |
-8 | PFNAME | Name of file for error messages | |
-6 | FNAME | Name of associated file | |
-4 | FUNIT | Unit number packed with flags | |
-2 | FSIZE | Size of elements in the file | |
0 | File window element |
Here FBUF is a pointer to the input or output buffer for the file. The standard system routines getc and putc are used and provide block buffered input/output, with 512 characters normally transferred at each read or write.
The files in the Pascal environment, with the exception of input and output are all linked together on a single file chain through the FCHAIN links. For each file the FLEV pointer gives its associated block mark. These are used to free files at block exit as described in section 3.3 below.
The NAME and PFNAME give the associated file name for the file and the name to be used when printing error diagnostics respectively. Although these names are usually the same, input and output usually have no associated file name so the distinction is necessary.
The FUNIT word contains the unit number on which the file is open as well as a set of flags. These flags and their representations are:
EOF | 00400 | At end-of-file |
EOLN | 01000 | At end-of-line |
SYNC | 02000 | File window is out of sync |
TEMP | 04000 | File is temporary |
FREAD | 02000 | File is open for reading |
FWRITE | 04000 | File is open for writing |
FTEXT | 08000 | File is a text file; process EOLN |
The EOF and EOLN bits here reflect the associated built-in function values. TEMP indicates that the file has a generated temporary name and that it should therefore be removed when its block exits. FREAD and FWRITE indicate that reset and rewrite respectively have been performed on the file so that input or output operations should be attempted. FTEXT indicates the file is a text file so that EOLN processing should be done, with newline characters turned into blanks, etc.
The SYNC bit, when true, indicates that there is no usable image in the file buffer window. As discussed in the Berkeley Pascal User's Manual, it is necessary, because of the interactive environment, to have ``input^'' essentially undefined at the beginning of execution so that a program may print a prompt before the user is required to type input. The SYNC bit implements this. When it is set, it indicates that before the element in the window can be used the Pascal system must actually put something there. This is never done until necessary.
All the variables in the Pascal runtime environment are cleared to zero on block entry. This is necessary for simple processing of files. If a file is unused, its pointer will be nil. All references to an inactive file are thus references through a nil pointer. If the Pascal system did not clear storage to zero before execution it would not be possible to detect inactive files in this simple way; it would probably be necessary to generate (possibly complicated) code to initialize each file on block entry.
When a file is first mentioned in a reset or rewrite call, a buffer of the form described above is associated with it, and the necessary information about the file is placed in this buffer. The file is also linked into the active file chain. This chain is kept sorted by block mark address, the FLEV entries.
When block exit occurs the interpreter must free the files which are in use in the block and their associated buffers. This is simple and efficient because the files in the active file chain are sorted by increasing block mark address. This means that the files for the current block will be at the front of the chain. For each file which is no longer accessible the interpreter first flushes the files buffer if it is an output file. The interpreter then returns the file buffer and the files structure and window to the free space in the heap and removes the file from the active file chain.
Flushing all the file buffers at abnormal termination, or on a call to the procedure flush or message is quite easy. The Pascal system simply flushes the file output and each file on the file chain which has the FWRITE bit set in its flags word.
For the purposes of input-output, px maintains a notion of an active file. Each operation which references a file makes the file it will be using the active file and then performs its operation. A subtle point here is that one may do a procedure call to write which involves a call to a function which references another file, thereby destroying the active file set up before the write. For this reason, the active file is saved at block entry in the block mark and restored at block exit. *
* would probably be better to dispense with the notion of active file and use another mechanism which did not involve extra overhead on each procedure and function call.
Files in Pascal can be used in two distinct ways: as the object of read, write, get, and put calls, or indirectly as though they were pointers. It should be noted that the second use as pointers must be careful not to destroy the active file in a reference such as
write(output, input¢¬)
The fundamental operator related to the use of a file is FNIL. This takes the file variable, as a pointer, insures that the pointer is not nil, and also that a usable image is in the file window, by forcing the SYNC bit to be cleared.
The rest of the uses of files and the file operations may be summarized by a simple example:
write('*')
which generates the code
UNITOUT CONC '*' WRITC
Here the operator UNITOUT is an abbreviated form of the operator UNIT which sets the active file, when the file to be made active is output. Thus to write a character to the file output it is only necessary to make output the active file, to place the character to be output on the stack, and to do a WRITC write character operation.
Files other than output differ from this example only in that the operator UNIT is used preceded by an evaluation of the file variable expression. Thus
writeln(f)
RV f UNIT WRITELN
Write widths are easily handled here by packing information about the presence or absence of width specifications and their types into the sub-operation code and pushing the values of the write widths onto the top of the stack.
One other operation worth mentioning is DEFNAME which is used to implement the program statement association of file names. DEFNAME simply allocates the files (section 3.1) area for the given file as though it had been the object of a reset or rewrite call, initializing the FNAME field, but omitting the system interactions associated with and actual reset or rewrite.