The most frequently asked questions about Pascal/MT+ pertain to files and how to use them. This appendix is in response to requests for more information. Because working from a simple example is the most effective way in which to understand a concept, we have included program samples for each area of file handling and text describing the features implemented in that program.
The terms and definitions included here are arranged to build up the concepts of files as you read through. If you are already familiar with Pascal files, then continue to Section 2 on file operations in Pascal/MT+.
+--------+--------+--------+--------+--------+--------+ |00001000|00000000|00100001|00000001|00000001|00000000| +--------+--------+--------+--------+--------+--------+ | record 0 | record 1 | record 2 |
This file contains the integers 8, 33, and 1 (stored inverted in this sample). The smallest retrievable element is two bytes. See the explanations of untyped files or byte files if you want to treat this file differently than a file of integers. Files may be of the standard Pascal scalar types: BOOLEAN, INTEGER, CHAR, or REAL. Files may also be of the structured types STRING, arrays, and records. The predefined type TEXT is used for ASCII files. Text files are similar to FILE of CHAR except that they are subdivided into lines, and numbers written to them are converted to ASCII (and may be formatted) and numbers read from them are converted to binary. A line is a sequence of characters terminated by an end of line character which is usually a carriage return/line feed. Also, unlike FILE of CHAR, TEXT files will accept PACKED ARRAY[1..N] OF CHAR or ARRAY[1..N] OF CHAR (writing an UNPACKED ARRAY is not ISO standard), and STRINGS as data. A boolean value is converted to the ASCII sequence 'TRUE' or 'FALSE' on write but the reverse is not true. For further explanations on typed and text files, see the operations section.
A non-ISO standard concept regarding files is the UNTYPED file. This is used for fast block input and output (entire sectors are read or written) regardless of the kind of data contained in the file. Further information on untyped files may be found in Section 6
+--------+--------+--------+--------+--------+--------+ |00000001|00000000| | | | | +--------+--------+--------+--------+--------+--------+ +----------++--------+--------+ | FIB ||00000010|00000000| window variable +----------++--------+--------+
To write the contents of I to the file, the window variable must contain 2 (F := I.puts the contents of I into the window variable) and be 'positioned' over the second element of the file. Given the command PUT(F) described in the operations section, the number 2 is written to the file.
Sample programs and explanations demonstrate the use of file operation procedures in Pascal/MT+. You will see how to open, create, read, write, delete, close, and randomly access files. Demonstrated also are the use of typed and text files; the file status functions IORESULT, EOF, and EOLN; and how to assign to a window variable.
Figure 1 lists a program named WRITE READ FILE_DEMO which creates a typed file on disk, writes data to the file, closes the file, then re-opens the file to read the data back. The procedures used to perform these are ASSIGN, REWRITE, RESET, IORESULT, PUT, GET, and CLOSE. WRITE is used to display the results on the terminal. The output work is done in WRITEFILE and the input work is done in READFILE. Creating, opening, and closing the file is done in the main body of the program.
The WRITELN statements on lines 37, 43, 46, and 49 write the string passed to them to the default OUTPUT file (the console). This procedure and READLN are discussed later in this section under TEXT files.
First note the form of the declaration of OUTFILE. It is declared to be of type CHFILE which is defined as a FILE OF CHAR in the TYPE declaration section (lines 3 and 4). This is done because the file is passed as a parameter to the WRITEFILE and READFILE routines and a parameter list cannot declare a new type. For example, the following parameter declaration is illegal in Pascal because only type identifiers are allowed in a parameter list:
PROCEDURE WRITEFILE( VAR F : FILE OF CHAR);
Figure 1 : File Input and Output.
1 0 PROGRAM WRITE_READ_FILE_DEMO; 2 0 3 0 TYPE 4 1 CHFILE = FILE OF CHAR; 5 1 VAR 6 1 OUTFILE : CHFILE; 7 1 RESULT : INTEGER; 8 1 FILENAME: STRING[16]; 9 1 10 1 PROCEDURE WRITEFILE( VAR F : CHFILE); 11 1 VAR CH: CHAR; 12 2 BEGIN 13 2 FOR CH:= '0' TO '9' DO 14 2 BEGIN 15 3 F^ := CH;{CHR(I + ORD('0'));} 16 3 PUT(F) 17 3 END; 18 2 END; 19 1 20 1 PROCEDURE READFILE( VAR F : CHFILE); 21 1 VAR I : INTEGER; 22 2 CH : CHAR; 23 2 BEGIN 24 2 FOR I := 0 TO 9 DO 25 2 BEGIN 26 3 CH := F^; 27 3 GET(F); 28 3 WRITELN(CH); 29 3 END; 30 2 END; 31 1 32 1 BEGIN 33 1 FILENAME := 'TEST.DAT'; 34 1 ASSIGN(OUTFILE,FILENAME); 35 1 REWRITE(OUTFILE); 36 1 IF IORESULT = 255 THEN 37 1 WRITELN('Error creating ',FILENAME) 38 1 ELSE 39 1 BEGIN 40 2 WRITEFILE(OUTFILE); 41 2 CLOSE(OUTFILE,RESULT); 42 2 IF RESULT = 255 THEN 43 2 WRITELN('Error closing ',FILENAME) 44 2 ELSE 45 2 BEGIN 46 3 WRITELN('Successful close of ',FILENAME); 47 3 RESET(OUTFILE); 48 3 IF IORESULT = 255 THEN 49 3 WRITELN('Cannot open ',FILENAME) 50 3 ELSE 51 3 READFILE(OUTFILE) 52 3 END; 53 2 END; 54 1 END. PROCEDURE ASSIGN(VAR F : FILE VARIABLE; STR : STRING);
Purpose: Associate the file variable F with an external file on disk named in STR.
ASSIGN is the first file operation to be executed in line 34. This procedure associates a file variable (OUTFILE) with an external file on a disk given in FILENAME ( in this case it is 'TEST.DAT'). The string passed to ASSIGN is placed into the FIB and the name is interpreted. After executing the ASSIGN procedure, the file variable passed to the ASSIGN procedure is always associated with the disk file named in the name parameter until, or unless, another ASSIGN is done to the file variable.
PROCEDURE REWRITE(VAR F : FILE VARIABLE);
Purpose: Create a file on disk using the name in the FIB (either filled in by the ASSIGN statement previously or null (if null a temporary file is created)
The REWRITE procedure is called in line 35 of Figure 1. Executing this procedure causes the creation of a file with the name contained in the FIB of F. Any existing files by that name are deleted so NEVER use REWRITE on a file which contains useable data. In this example, the file on disk will be named 'TEST.DAT' and is located on the default disk (because no other disk was specified in the file name string passed to ASSIGN).
If no previous ASSIGN had been performed, the name field of the FIB is empty and a temporary file is created with the name 'PASTMP00.$$$.' Temporaray files are generaly used for scratch pad memory and data which is not needed after execution of the program. The digits at the last two positions in the name are used to give each temporary file a unique name so the user may have up to 100 temporary files.
The EOF function and the EOLN function return true because OUTFILE is an output file. OUTFILE is open only for writing sequentially and is ready to receive data into its first element. See the text with Figure 4 for considerations on random access after a REWRITE. If the operation is not successful, the IORESULT function returns a 255 in this case (see line 36).
FUNCTION IORESULT : INTEGER;
Purpose : Return the integer value indicating status of file operation.
The value of this function is set after any input or output operation and may be checked at any time. Note in Figure 1 it is called after each file operation in lines 36, 42, and 46. It is used here to stop the program if a file operation did not work as planned. Note that you cannot 'WRITE(IORESULT)' because IORESULT is reset to 0 after each I/O operation. The meaning of the values returned by IORESULT is presented in Chapter 3.
PROCEDURE PUT(VAR F : FILE VARIABLE);
Purpose : Transfer the contents of the window variable associated with F to the next available record in the file.
Procedure WRITEFILE, beginning on line 9 of Figure 1, writes the characters '0' to '9' to the TEST.DAT file. The PUT procedure is what causes the data to be written to the file. Always before executing a PUT, an assignment is made to the window variable as in line 15. Following is a diagram of what is occurring :
+--------+ |00110000| Window variable after assignment (line 15) and CH +--------+ is equal to '0'. +--------+--------+--------+--------+--------+--------+ | | | | | | |.......... +--------+--------+--------+--------+--------+--------+ File before any PUT statement is executed. +--------+ |00110000| Window variable after PUT in line 16. +--------+ +--------+--------+--------+--------+--------+--------+ |00110000| | | | | |.......... +--------+--------+--------+--------+--------+--------+ File after the first PUT is executed in the FOR loop in Figure 1, lines 13 through 17.
PROCEDURE WRITE; PROCEDURE WRITE(expression,...,expression); PROCEDURE WRITE(VAR F:FILE VARIABLE,expression,...,expression);
Purpose : Shorthand for 'F^ := data; PUT(F);' Also performs conversions to ASCII on numbers when F is a TEXT file.
Expression includes contents of variables, strings, array elements, constants, and expressions. When a file variable is not specified, the default OUTPUT file is assumed. The WRITE procedure does exactly the same operations on the file as lines 15 and 16. It executes an assignment followed by a PUT and is merely a shorthand version. GET and PUT are provided because the ISO standard requires them and in some versions of Pascal, such as UCSD Pascal, WRITE can only be used on TEXT files.
PROCEDURE CLOSE(VAR F : FILE VARIABLE; RESULT : INTEGER);
Purpose : Flush the buffer in the FIB associated with F so all data is written to the disk.
The next statment to be executed after returning from WRITEFILE is line 41, where the file is closed. CLOSE must be executed to assure that the data written to 'TEST.DAT' is actually saved on the disk. Up until this point the data is written to the buffer in memory and now must be saved by flushing the buffer. RESULT is the value returned by the operating system indicating whether the close is successful. It has the same value as IORESULT returns. It is included as a parameter to maintain compatibility with previous versions of the compiler. In this program a value of 255 means an error closing the file, any other value indicates success.
PROCEDURE RESET(VAR F : FILE VARIABLE);
Purpose : Open an existing file for reading. The window variable is moved to the beginning of the file. For random access, the file is open for reading and writing.
After checking RESULT, the procedure RESET is called in line 47. RESET opens an existing file for reading and resets the window variable to the beginning of the file. F" is assigned the first element of F. If F is already open, RESET calls CLOSE. EOF and EOLN return FALSE. If a RESET is done on a file that does not exist, IORESULT contains a 255. All other values of IORESULT indicate success. In the sample program OUTFILE is opened by the RESET procedure so that it may be read. Below is a diagram of the file and window variable after the RESET is executed in line 47. Note that with non-console typed files such as OUTFILE, the procedure RESET does an initial GET which moves the first element of the file (in this case the ASCII value for the number 0) into the window variable.
+--------+ |00110000| Window variable (OUTFILE^) after RESET (line 47). +--------+ +--------+--------+--------+--------+--------+--------+ |00110000|00110001|00110010|00110011|00110100|00110101|.... +--------+--------+--------+--------+--------+--------+
The initial GET is not performed on console files or untyped files. The reason for this is that the user would always have to type a character before his / her program could execute because the GET procedure is waiting for a character.
PROCEDURE GET(VAR F : FILE VARIABLE);
Purpose : Transfer the currently accessible record to the window variable and advance the window variable.
After checking that the RESET procedure is successful, procedure READFILE is called in line 51. This procedure reads each element of the file passed to it (in this case the element is a character) and writes that element to the screen. READFILE begins on line 20 The work is done in the FOR loop of lines 24 through 29.
The GET procedure advances the window variable by one element and moves the contents of the file pointed to into the window variable. If no next element exists EOF becomes TRUE. See Section 3 on TEXT files for more details on GET and TEXT files. The diagram below describes what is happening within the FOR loop on lines 26 and 27 the first time through the loop.
+--------+ |00110000| Window variable (OUTFILE) after line 26 +--------+ +--------+--------+--------+--------+--------+--------+ |00110000|00110001|00110010|00110011|00110100|00110101|... +--------+--------+--------+--------+--------+--------+ After executing line 26, CR contains the ASCII for 0 (00110000). After executing line 27, the window variable is advanced. +--------+ |00110001| Window variable after GET in line 27. +--------+ +--------+--------+--------+--------+--------+--------+ |00110000|00110001|00110010|00110011|00110100|00110101|.... +--------+--------+--------+--------+--------+--------+
Line 28 writes the contents of CR to the default output file which is the console. Procedure READFILE displays the characters '0' through '9' in a column on the console. Calling CLOSE after a RESET is not necessary in the sequential case because the file already exists on the disk and has not been altered in any way. If OUTFILE is accessed randomly, a CLOSE might be necessary.
PROCEDURE READ(data, data,...,data); PROCEDURE READ(VAR F : FILE VARIABLE , data, data, ..., data);
Purpose: When used with non-console files execute 'data := F^; GET(F);' for each data item read. When used with console files execute 'GET(F); data := F^;'. If F is not specified the default INPUT file is used. See the section on TEXT files for information on conversions.
The READ procedure is exactly the same as an assignment and a call to GET. If READ is used rather than GET in the current example, the FOR loop body would look like this:
FOR I := 0 TO 9 DO BEGIN READ(CH); WRITELN CH) END;
Reading past end-of-file on console input results in crashing the system.
A TEXT file is a file of ASCII characters which is subdivided into lines. A line is a sequence of characters terminated by a nonprintable end-of-line indicator, usually a carriage return and a line feed-character. It is similar to a file of CHAR except that automatic conversion of numbers is performed when they are read from and written to the file. Also, variables of type STRING may be read from a text file and BOOLEANs, STRINGs, and PACKED ARRAYs may be written to text files. Access to a TEXT file is via GET and PUT for character I/O (which do not do conversions), READ and WRITE which have been defined in earlier in this section, and READLN and WRITELN which are used in Figure 2 and defined in this section.
The format of a TEXT file in memory is a FIB and a 1-byte window variable. On disk, the file looks as this sample below in which a carriage return is represented by '>', linefeed by '/' and end of file by '#.'
+-------------------------------------------------------------+ This is a line>/This is the next line>/This is the last line>/# +-------------------------------------------------------------+
FUNCTION EOLN : BOOLEAN; FUNCTION EOLN(VAR F : TEXT) BOOLEAN;
Purpose: Indicate the state of the file by returning true only when the window variable is over the end-of-line character. When no file is specified the default INPUT file is assumed.
This is a function which returns true on disk text files when the last valid character on a line is read using a READ statement. Because the seqence of statements for a READ (on non-console files) is 'CH := F^; GET(F);', the window variable is positioned over the end- of-line character immediately after the last character is read. Thus, EOLN returns TRUE on NON-CONSOLE TEXT files when the last character is read. Also, a BLANK character is returned instead of the end-of-line character. The above sequence is reversed on CONSOLE files (READ is an initial call to GET followed by an assignment from the window variable). For this reason, using CONSOLE files, EOLN will return true after the carraige return I line feed is read instead of after the last character as in disk files. A blank is still returned in the character.
FUNCTION EOF; FUNCTION EOF(VAR F : FILE) : BOOLEAN;
Purpose: Indicate the state of a file by returning true only when the window variable is over an end-of-file character. When no file is specified the default INPUT file is assumed.
EOF is a function which returns true when the end-of-file character is read. It is similar to EOLN in that the last character read will set EOF to true on NON-CONSOLE files. On CONSOLE files EOF is true only when the end-of-file indicator is entered. Reading past end-of-file on console files is not supported (the system can crash). Reading past the end of the file on disk files is not supported. A blank is returned by the window variable when EOF is true. Also, note that on non-text files EOF may not become true at the end of the valid data as the data may not fill up the entire last sector of the file.
Figure 2 is a program which simply writes data to a text file and reads it back to be displayed on the output device. The procedure WRITEDATA actually writes to the TEXT file and the procedure READDATA retrieves the information stored in the file. The program is divided into a main body and two procedures to demonstrate the usefulness of breaking up code into blocks which perform certain functions. This makes code much easier to read and debug.
The file is declared in line 3. Note that the declaration is NOT 'VAR F : FILE of TEXT'. TEXT is treated as a special version of FILE of CHAR, so FILE of TEXT translates to FILE of FILE of CHAR (nonsensical).
The program begins execution on line 25 with a call to the ASSIGN procedure. Lines 25 through 29 create a TEXT file named TEXT.TST on the logged in drive. If the file creation is successful then the sample data is initialized in lines 31 and 32 followed by a call to the WRITEDATA routine in line 33. WRTITEDATA uses the WRITELN procedure which is only used with TEXT files.
PROCEDURE WRITE; PROCEDURE WRITELN; PROCEDURE WRITELN(expr,expr,...expr); PROCEDURE WRITELN(F); PROCEDURE WRITELN(F,expr,expr,...expr);
Purpose: Put the data into the file associated with F, ending the output with and end-of-line character. If no file is specified the expressions are written to the OUTPUT file. A writeln with no expressions merely outputs a carraige return / line feed. The WRITE procedure is redefined as a conversion rather than a replacement for PUT.
This procedure writes the data passed to it to the file named, placing an end-of-line character after the last item of data written. If no file is named it is written to the default OUTPUT file. Data may be literal and named constants, integers, reals, subranges, enumerated, booleans, strings, and packed arrays of characters, but may not be structured types such as records. Numeric data is converted to ASCII and strings are treated as arrays of characters (the length byte is not written to the file).
In Figure 2 three lines which make up the body of WRITEDATA (9, 10, and 11) do the actual file output. Line 9 sends the contents of the variable string S followed by a carraige return / line feed to the TEXT file F. Line 10 formats the contents of I in a field of four spaces and sends this formatted output to the file F. The real number literal in line 11 is formatted into a field of nine spaces, four of which must be to the right of the decimal place. This formatted number is then written to the file F. The field format may be specified for any data type. For non-real numbers only the field width is specified, not the number of places after the decimal point. The data is right justified in the field. If a number is larger than the 6.5 significant digits can represent, the output is always expressed in exponential notation. Also, if the field width is too small to express the number it is written in exponential notation. For further information on formatting consult a Pascal textbook and experiment.
The body of the WRITEDATA procedure could have been written in the following manner with the same results.
WRITELN(F,S); WRITELN(F,I:4, 45.6789 : 9 : 4);
Control returns to the main body of the program and line 34 is executed. If the CLOSE is successful, the RESET in line 39 opens the file F (which is still associated with 'TEXT.TXT' on the disk), moving the window variable to the beginning in preparation for reading data from the file F. Following a successful RESET the procedure READDATA is called to read back the information placed in 'TEXT.TST' and display it on the console.
Stmt Nest Source Statement 1 0 PROGRAM TEXTIO_DEMO; 2 0 3 0 VAR F : TEXT; 4 1 I : INTEGER; 5 1 S : STRING; 6 1 7 1 PROCEDURE WRITEDATA; 8 1 BEGIN 9 2 WRITELN(F,S); 10 2 WRITE(F,I:4); 11 2 WRITELN(F,45.6789:9:4); 12 2 END; 13 1 14 1 PROCEDURE READDATA; 15 1 VAR R : REAL; 16 2 BEGIN 17 2 READLN(F,S); 18 2 READ(F,I); 19 2 READ(F,R); 20 2 WRITELN(S); 21 2 WRITELN(I:4,' ',R:9:4); 22 2 END; 23 1 24 1 BEGIN 25 1 ASSIGN(F,'TEXT.TST'); 26 1 REWRITE(F); 27 1 IF IORESULT = 255 THEN 28 1 WRITELN('Error creating') 29 1 ELSE 30 1 BEGIN 31 2 I := 35; 32 2 S := 'THIS IS A STRING'; 33 2 WRITEDATA; 34 2 CLOSE(F,I); 35 2 IF IORESULT = 255 THEN 36 2 WRITELN('Error closing') 37 2 ELSE 38 2 BEGIN 39 3 RESET(F); 40 3 IF IORESULT = 255 THEN 41 3 WRITELN('Error opening') 42 3 ELSE 43 3 READDATA; 44 3 END; 45 2 END; 46 1 END. 46 0 --------------------------- 46 0 Normal End of Input Reached
Figure 2 : Text Files
PROCEDURE READ; PROCEDURE READLN; PROCEDURE READLN(F); PROCEDURE READLN( F, variable, variable,...,variable);
Purpose: Read from the file associated with F into the variables listed. In all cases, read until an end-of-line character is found, skipping any unread data, and advance to the beginning of the next line. READ redefined to perform conversion of reals, booleans, and integers.
READLN, like WRITELN, has as parameters an optional file variable and any number of variables to receive the data from the file. If the file variable is not specified, input is taken from the default INPUT file, the keyboard. The variables in the parameter list are the same type as the data being read from the file. However, no type checking is done so it is up to the user to construct a parameter list compatible to the format of his / her file. Any numbers are converted on input but the formatting is lost. Numbers must be separated from each other and other data types by a blank or a carraige return linefeed.
READLN recognizes but does not transmit the end-of-line character. Its action is to read data until it encounters an end-of- line and advance the window variable to the beginning of the next line. The data in 'TEXT.TST' looks like the following:
THIS IS A STRING>/ 35 45.6789>/#
After reading the string in the first line to read the integer 35 one must use READ and not READLN. If a READLN were used here, the 35 would be read properly because the first blank terminates the number. However, the window variable would be advanced past the real number to the end of the file. Then, if one tries to read the real, all one gets is EOF and then one wonders what happened to the real number known to be out there.
STRINGS must always be read with a READLN because they are terminated with end-of-line characters. If the data to the file had been 'THIS IS A STRING 35>/', the value returned for S would be the entire line including the ASCII 35.
Lines 20 and 21 write the data to the console in the same format as it is contained in the file.
After executing READDATA, the program is finished. A CLOSE is not necessary since the data in 'TEXT.TST' is not altered in any way since the last CLOSE on that file.
Writing to the printer is very simple, as demonstrated in Figure 3. A file variable is declared to be of type TEXT as in line 5 of Figure 3. This file variable is ASSIGNed to the printer in line 11. The filename 'LST: passed to ASSIGN means that F is to be associated with the list device so that all data written to F is routed to the printer. REWRITE is called to open the list device for writing. Note that a CLOSE is not necessary since the data has already been written and the buffer does not need to be flushed. Lines 23 and 25 use standard Pascal formatting directives. In line 23, R is to be written in a field seven characters long with three digits to the right of the decimal place.
Stmt Nest Source Statement 1 0 PROGRAM PRINTER; 2 0 (* WRITE DATA AND TEXT TO THE PRINTER *) 3 0 4 0 VAR 5 1 F : TEXT; 6 1 I : INTEGER; 7 1 S : STRING; 8 1 R : REAL; 9 1 10 1 BEGIN 11 1 ASSIGN(F,'LST:'); 12 1 REWRITE(F); 13 1 IF IORESULT = 255 THEN 14 1 WRITELN('Error rewriting file') 15 1 ELSE 16 1 BEGIN 17 2 S := 'THIS LINE IS A STRING'; 18 2 I := 55; 19 2 R := 3.141563; 20 2 WRITE(F,S); 21 2 WRITE(F,I); 22 2 WRITELN(F); 23 2 WRITELN(F,R:7:3); 24 2 WRITE(F,I,R); 25 2 WRITE(F,I:4,R:7:3); 26 2 WRITELN (F) ; 27 2 WRITELN(F,'THIS IS THE END.') 28 2 END 29 2 END. 29 0 --------------------------- 29 0 Normal End of Input Reached
Figure 3 Writing to a Printer and Number Formatting
In this section the procedures SEEKREAD and SEEKWRITE will be discussed along with the Pascal/MT+ intrinsics for randomly accessing a file. The sample program RANDOM DEMO shows the use of these procedures, plus the declaration, creation and opening of a file to be accessed randomly. Also shown in this program are one use of sets, the ELSE on the CASE statement, the EXIT statement, WRITE, WRITELN, READ, and READLN.
A random file is merely a typed Pascal file which is accessed via the random access procedures SEEKREAD and SEEKWRITE. Thus, any file may be a random file; there is no special syntax when declaring a file which makes it a random instead of a sequential file. The random access routines allow the user to specify the relative record number desired. This is different than sequential access in that the user must access record 0 before record 1, etc. Up to 65,536 records may be randomly accessed.
The sample program in Figure 4a and 4b, RANDOM DEMO, shows the use of random file access. This program either creates or uses an existing file of records of type PERSON. Each record in the file contains two strings; the name of a person and the address of that person. It loops on lines 69 through 80 allowing the user to read any existing record via.the procedure READRECS or to write to any record via the procedure WRITEREC. Let us examine the main body of the program first and then see what the procedures READREC, WRITEREC, and ERRCHK are doing.
Program execution begins on line 59 by writing a prompt questioning the user if he wants to create a file or open an existing file. The file is created if desired and in any case the file is RESET in line 68. This brings up an important point. With random files, a file that has been RESET may either be read from (using SEEKREAD) or written to (using SEEKWRITE). This is unlike sequential files which may only be read after a RESET. Also, a new file created using REWRITE may be accessed via SEEKREAD after data has been written to the file. The repeat loop allows the process of reading and writing to continue until stopped with a 'Q' input. Suppose the response to the question R)ead W)rite or Q)uit is 'R.' Procedure READRECS is called which uses SEEKREAD to read a record.
Stmt Nest Source Statement 1 0 PROGRAM RANDOM_DEMO; 2 0 3 0 TYPE 4 1 PERSON = RECORD 5 1 NAME : STRING; 6 1 ADDRESS : STRING; 7 1 END; 8 1 9 1 VAR 10 1 BF : FILE OF PERSON; 11 1 S : STRING; 12 1 I : INTEGER; 13 1 ERROR : BOOLEAN; 14 1 CH : CHAR; 15 1 16 1 EXTERNAL PROCEDURE @HLT; 17 1 18 1 PROCEDURE ERRCHK; 19 1 BEGIN 20 2 ERROR:= TRUE; (*DEFAULT*) 21 2 CASE IORESULT OF 22 2 0 : BEGIN WRITELN('Successful'); ERROR := FALSE END; 23 3 1 : WRITELN('Reading unwritten data'); 24 3 2 : WRITELN('CP/M error'); 25 3 3 : WRITELN('Seeking to unwritten extent'); 26 3 4 : WRITELN('CP/M error'); 27 3 5 : WRITELN('Seek past physical end of disk') 28 3 ELSE 29 3 WRITELN('Unrecognizable error code') 30 3 END; 31 2 END; 32 1 33 1 PROCEDURE READRECS; 34 1 BEGIN 35 2 WRITE('Record number? '); 36 2 READLN(I); 37 2 SEEKREAD(BF,I); 38 2 ERRCHK; 39 2 IF ERROR THEN 40 2 EXIT; 41 2 WRITELN(BF^.NAME,'/',BF^.ADDRESS); 42 2 END; 43 1 44 1 PROCEDURE WRITERECS; 45 1 BEGIN 46 2 WRITE('Name?'); 47 2 READLN(S); 48 2 BF^.NAME := S; 49 2 WRITE('Address?'); 50 2 READLN(S); 51 2 BF^.ADDRESS := S; 52 2 WRITE('Record number? '); 53 2 READLN(I); 54 2 SEEKWRITE(BF,I); 55 2 ERRCHK; 56 2 END; 57 1 58 1 BEGIN 59 1 WRITE('Create file?'); 60 1 READLN(S); 61 1 IF S[1] IN ['Y','y'] THEN 62 1 BEGIN 63 2 ASSIGN(BF,'B:BIG.FIL'); 64 2 REWRITE(BF); 65 2 CLOSE(BF,I); 66 2 END; 67 1 ASSIGN(BF,'B:BIG.FIL'); 68 1 RESET(BF); 69 1 REPEAT 70 2 WRITE('R)ead, W)rite or Q)uit? '); 71 2 READ(CH); 72 2 WRITELN; 73 2 CASE CH OF 74 2 'R','r': READRECS; 75 3 'W','w': WRITERECS; 76 3 'Q','q': @HLT 77 3 ELSE 78 3 WRITELN('Enter R, W or Q only') 79 3 END 80 2 UNTIL FALSE; 81 1 END. 81 0 --------------------------- 81 0 Normal End of Input Reached
Figure 4 : Random File Input and Output.
PROCEDURE SEEKREAD(VAR F : FILE; RECORD_NUMBER : 0..largest word in system);
Purpose : Transfer the contents of the record designated in RECORD_NUMBER to the window variable for F. Uses CP/M function call 33.
The file parameter, F, is any typed file which has been made accessible via an ASSIGN followed by a RESET or a REWRITE (if there is data to be read). RECORD_NUMBER is the relative record desired, where 0 is the record at the beginning of the file. The record is retrieved (if it exists) and assigned to the window variable buffer. If it does not exist because a record past the end of the file is requested, an error value is returned in IORESULT. In this program note that the procedure ERRCHK checks IORESULT for errors based on the CP/M system.
The procedure READRECS asks for a record number, reads the record from the file, and writes it directly from the window variable to the screen,if the record exists. Line 37 contains the call to SEEKREAD giving it the filename and the record number. From this it is obvious that if record 0 and 2 contain data, an attempt to read record 1 will be allowed, although record 1 does not contain any data. Thus, the user must be careful about accessing unwritten records which the system is not able to detect as an error. The information retrieved is written in line 41. Note how the window buffer is used as a pointer as if it were declared like a pointer to a record type. If the user desires to save the data elsewhere, an assignment to a data structure of the same type as the file (in this case type PERSON) is made.
VAR TEMP : PERSON; ...... ...TEMP := BF^;
Note that once a file has been accessed via a SEEKREAD (or a SEEKWRITE) it must be CLOSED and reopened in order to access with the sequential methods listed earlier (GET and READ, PUT and WRITE).
PROCEDURE SEEKWRITE(VAR F :ANY FILE; RECORD_NUMBER: 0..largest word );
Purpose : Put the contents of the window variable associated with F into the disk file at the relative record location designated in RECORD_NUMBER. Uses CP/M function call 34.
SEEKWRITE is used in procedure WRITERECS which is found on lines 44 through 56. WRITERECS asks the user for the data required to fill a record of type PERSON (lines 46 through 51), asks the user for the record number to be written (lines 52 and 53), and calls SEEKWRITE (line 54) to write the data to the disk. ERRCHK is called in line 55. Assignment is made to the window variable in lines 48 and 51. The file looks like the diagram below after writing data to records 0, 1, and 3.
+---------------+ | Name.... | | Address..... | representation of PERSON record. ----------------+ +---------------+---------------+---------------+---------------+ | Gremlin B. H.| Fred Gnome | Unwritten dat | MT Monster | | Nottingham | Under the Hill| Garbage...... | Front Cover | +---------------+---------------+---------------+---------------+ Record 0 Record 1 Record 2 Record 3
Records within a file written with SEEKWRITE are stored contiguously on the disk, regardless of the number of sectors occupied by a record, Because this is true a file created using SEEKWRITE may be accessed after a CLOSE and RESET using sequential access methods.
Redirected I/O is an alternative to using the get-character and put-character routines in the run-time package and is useful when the regular CP/M I/O is not desired. Also, this feature is good for converting numbers into strings and strings into numbers. In fact, the sample program in figure 5 demonstrates this application.
The put-character routine, WIR, begins on line 8. It writes to a global string, CONV. GETCH, the get-character function, begins on line 28. This routine gets its character input from the global string, CONV.
The test program code begins on line 39, and the first statements initialize the variables required by WIR and GETCH. CONVERTING is a boolean which true when WIR is writing a number to CONV. CONV is initialized to the empty string so its length byte is 0. The test variable, I, is assigned 2438 in line 42 and written to the console via the regular WRITELN statement in line 43.
Line 44 is the first instance of redirected I/O in this program. The address of WIR is passed to the WRITELN routine so that WIR is used instead of the put-character routine in the run-time package. The number, I, is converted by the run-time routines into characters which are passed to WIR for "output" to the string, CONV. In this way the contents of I is converted to a string. WIR must always be called with a WRITELN as it uses the carriage return as a signal that the number is complete.
WIR checks first whether the character it receives is a linefeed ($OA) and exits if it is. Line 12 checks to see if conversion of a number has already begun. If so, and the character is not a carraige return ($OD) which indicates the end of the number, the new character is concatenated onto the existing characters in CONV in line 14. If this is a new number, CONV is set to the null string in line 19. Again, the check is made for a carraige return in the case where no characters are passed. If a character is passed, it is concatenated onto the string. CONVERTING is set to TRUE.
After returning from WIR, 'I' is assigned 0 in line 45 50 that when the GETCH routine is called it is clear that the old value, 2438, is "read" from CONV. Line 47 writes out the contents of CONV which by this point is 2438.
Finally, the read procedure uses GETCH in line 48 to read a number into I from CONV. GETCH continues to return characters until it empties .CONV, as indicated in line 30. Line 32 shows the assignment to GETCH of the first character in CONV. This character is deleted from CONV in line 33 50 a new first character is returned the next call to GETCH. When the string is empty, a blank is returned, which the system requires to indicate the end of a read.
Stmt Nest Source Statement 1 0 PROGRAM CONV_DEMO; 2 0 3 0 VAR 4 1 I : INTEGER; 5 1 CONV : STRING; 6 1 CONVERTING : BOOLEAN; 7 1 8 1 PROCEDURE WIR(CH : CHAR); 9 1 BEGIN 10 2 IF CH = CHR($0A) THEN (*Done, ignore linefeed *) 11 2 EXIT; 12 2 IF CONVERTING THEN 13 2 IF CH <> CHR($0D) THEN (*Not at end of string *) 14 2 CONV :=CONCAT(CONV,CH) 15 2 ELSE 16 2 CONVERTING := FALSE (*reached end-done*) 17 2 ELSE (*First call, new string *) 18 2 BEGIN 19 3 CONV := ''; 20 3 IF CH <> CHR($0D) THEN 21 3 BEGIN 22 4 CONV := CONCAT(CONV,CH); 23 4 CONVERTING := TRUE 24 4 END 25 4 END; 26 2 END; 27 1 28 1 FUNCTION GETCH:CHAR; 29 1 BEGIN 30 2 IF LENGTH(CONV) > 0 THEN (*Something left to convert*) 31 2 BEGIN 32 3 GETCH := CONV[1]; 33 3 DELETE(CONV,1,1); 34 3 END 35 3 ELSE 36 2 GETCH := ' '; (*Return blank-No more chars*) 37 2 END; 38 1 39 1 BEGIN (* Main Program *) 40 1 CONVERTING := FALSE; 41 1 CONV := ''; 42 1 I := 2438; 43 1 WRITELN('I=',I); 44 1 WRITELN([ADDR(WIR)],I); (* Field width may be given *) 45 1 I := 0; 46 1 WRITELN('I=',I); 47 1 WRITELN('CONV=',CONV); 48 1 READ([ADDR(GETCH)],I); (* READLN may not be used *) 49 1 WRITELN('I=',I); 50 1 END. 50 0 --------------------------- 50 0 Normal End of Input Reached
Figure 5: Redirected I/O
Figures 6 through 9 are four different implementations of a simple file transfer procedure named TRANSFER. Figure 10 is the main body which calls the transfer routine. Following Figure 10 is a table listing code and data size and execution speed for the programs listed in Figures 6 through 9.
The BLOCKREAD and BLOCKWRITE implementation is shown in Figure 6. Here untyped files use a large 2K buffer to transfer data.
PROCEDURE BLOCKREAD(F : FILE VARIABLE; BUFFER : ANYTYPE; VAR IOR : INTEGER; SZ, RB : INTEGER);
Purpose : Transfer the data to the BUFFER variable for the specified number of bytes (SZ) starting at the relative block number (RB). Direct CP/M disk access using function call 20.
On CP/M the RB parameter is in the range -1..64K. The extent is calculated and opened, if necessary, when RB is greater than 127. If RB is -1 sequential access is assumed and the next 128-byte sector is read. The code in Figure 6 could always use -1 for RB and function in the same manner as it does now.
SZ is the number of bytes to be read. It must be at least 128 bytes and may no larger than the buffer. Lines 25 and 28 show that the SZ parameter may be calculated using the SIZEOF function.
PROCEDURE BLOCKWRITE(F : FILE VARIABLE; BUFFER : ANYTYPE; VAR IOR : INTEGER; SZ,RB : INTEGER);
Purpose : Transfer user data from the BUFFER to the file F, starting at relative block number (RB) for SZ bytes. Provides CP/M level disk access using function call 21. As in BLOCKREAD, when RB is -1 sequential disk access is assumed.
The variable 'I' is used for the RB parameter. In line 29 it is incremented by 16 which is the number of bytes in BUF, 2048, divided by the number of bytes in a sector, 128. This means that each time through the loop 16 sectors of 128 bytes each are read into BUF from the source file and written to the the destination file.
RESULT is 0 if all data is read and nonzero if no data exists in the next record. The program as it is written works when transfering a file whose size is an even multiple of 2K. For example, if the source file is 9K bytes in size, the final 1K would not be written because RESULT returns a non-zero after the BLOCKREAD in line 25. Using 128-byte buffer would guarantee transfering all of the data.
Figure 7 shows the same file transfer procedure implemented using the GNB and WNB routines. This method appears to transfer one byte at a time but actually uses a 2K buffer for reading and writing bytes. Therefore, it is much faster than GET and PUT.
FUNCTION GNB(F : FILE OF PAOC) : CHAR;
Purpose: PAOC is any type which is a packed array of CHAR in the optimum range of 128..4095. Provide high speed byte level access to F.
Because the file used by GNB is a file of a Packed Array of Character type there are boundary conditions on CP/M-80 which may cause confusion. On these systems the run-time support routines read the data from the disk file into a one sector buffer in the FIB and then transfer the data into the window variable. If the data in the file is not an even multiple of the window variable size then the EOF flag in the FIB will be set "early" even though there is valid data in the window variable. The GNB routine will properly return the partial window variable data and then begin returning hex FF bytes when the data is totally exhausted. If processing a TEXT file on CP/M (8080 or 8086) using GNB one can look for a hex 1A character to determine the true end of data.
FUNCTION WNB(F : FILE OF CHAR, CH : CHAR) : BOOLEAN;
Purpose: Write to F one byte at a time. Returns a true value if an error occurs while writing that byte.
ON CP/M : If the window variable is not full when a file accessed with WNB is CLOSEd, the entire window variable is still written to the file and the file may contain "garbage" data at the end. It is suggested that you always "flush" the window variable using either hex 1A if the file is an ascii file, or hex FF if the file is not ascii, prior to closing the file.
Figure 8 presents the file transfer procedure using SEEKREAD and SEEKWRITE. Because these two routines have been discussed in a previous section, this Figure is present only for comparisions. Note that as in BLOCKI/O, if the last portion of data from the source file does not fill the sector, in this case the 2K bytes which is the window variable for file variable A, then IORESULT returns a 1 indicating end-of-file. However, the final portion of code which does not fill up the 2K buffer is never written to the destination file.
Figure 9 transfers files using GET and PUT which is much slower than any of these buffered methods. See previous sections on the use of GET and PUT.
Stmt Nest Source Statement 1 0 PROGRAM FILE_TRANSFER; 2 0 3 0 (*----------------------------------------------------------*) 4 0 (* Transfer file a to file b using BLOCKREAD and BLOCKWRITE *) 5 0 (*----------------------------------------------------------*) 6 0 7 0 CONST 8 1 BUFSZ = 2047; 9 1 TYPE 10 1 PAOC = ARRAY[1..BUFSZ] OF CHAR; 11 1 FYLE = FILE; 12 1 13 1 VAR 14 1 A,B : FYLE; 15 1 NAME : STRING; 16 1 BUF : PAOC; 17 1 18 1 PROCEDURE TRANSFER(VAR SRC: FYLE; VAR DEST : FYLE); 19 1 VAR 20 2 RESULT,I : INTEGER; 21 2 QUIT : BOOLEAN; 22 2 BEGIN 23 2 I := 0; 24 2 REPEAT 25 3 BLOCKREAD(SRC,BUF,RESULT,SIZEOF(BUF),I); 26 3 IF RESULT = 0 THEN 27 3 BEGIN 28 4 BLOCKWRITE(DEST,BUF,RESULT,SIZEOF(BUF),I); 29 4 I := I + SIZEOF(BUF) DIV 128 30 4 END 31 4 ELSE 32 3 QUIT := TRUE; 33 3 UNTIL QUIT; 34 2 CLOSE(DEST,RESULT); 35 2 IF RESULT = 255 THEN 36 2 WRITELN('Error closing destination file') 37 2 END; 38 1 (* MAIN PROGRAM IN FIGURE 10 *)
Figure 6: File Transfer with BLOCKREAD and BLOCKWRITE
Stmt Nest Source Statement 1 0 PROGRAM FILE_TRANSFER; 2 0 3 0 (*--------------------------------------------------*) 4 0 (* Transfer file a to file b using GNB and WNB *) 5 0 (*--------------------------------------------------*) 6 0 7 0 CONST 8 1 BUFSZ = 2047; 9 1 TYPE 10 1 PAOC = ARRAY[1..BUFSZ] OF CHAR; 11 1 TFILE = FILE OF PAOC; 12 1 CHFILE = FILE OF CHAR; 13 1 VAR 14 1 A : TFILE; 15 1 B : CHFILE; 16 1 NAME : STRING; 17 1 18 1 PROCEDURE TRANSFER(VAR SRC : TFILE; VAR DEST : CHFILE); 19 1 VAR 20 2 CH : CHAR; 21 2 RESULT : INTEGER; 22 2 ABORT : BOOLEAN; 23 2 BEGIN 24 2 ABORT := FALSE; 25 2 WHILE (NOT EOF(SRC)) AND (NOT ABORT) DO 26 2 BEGIN 27 3 CH := GNB(SRC); 28 3 IF WNB(DEST,CH) THEN 29 3 BEGIN 30 4 WRITELN('Error writing character'); 31 4 ABORT := TRUE; 32 4 END; 33 3 END; 34 2 CLOSE(DEST,RESULT); 35 2 IF RESULT = 255 THEN 36 2 WRITELN('Error closing ') 37 2 END; 38 1 (* MAIN PROGRAM IN FIGURE 10 *)
Figure 7: File Transfer with GNB and WNB
Stmt Nest Source Statement 1 0 PROGRAM FILE_TRANSFER; 2 0 3 0 (*-------------------------------------------------------*) 4 0 (* Transfer file a to file b using SEEKREAD and SEEKWRITE*) 5 0 (*-------------------------------------------------------*) 6 0 7 0 CONST 8 1 BUFSZ = 2047; 9 1 10 1 TYPE 11 1 PAOC = ARRAY[0..BUFSZ] OF CHAR; 12 1 TFILE = FILE OF PAOC; 13 1 CHFILE = FILE OF PAOC; 14 1 VAR 15 1 A : TFILE; 16 1 B : TFILE; 17 1 NAME : STRING; 18 1 PROCEDURE TRANSFER(VAR SRC: TFILE; VAR DEST : TFILE); 19 1 VAR 20 2 CH : CHAR; 21 2 RESULT2,RESULT,I : INTEGER; 22 2 ABORT : BOOLEAN; 23 2 BEGIN 24 2 CH := 'A'; 25 2 RESULT := 0; 26 2 I := 0; 27 2 WHILE RESULT <> 1 DO 28 2 BEGIN 29 3 SEEKREAD(SRC,I); 30 3 RESULT := IORESULT; 31 3 IF RESULT = 0 THEN 32 3 BEGIN 33 4 DEST^ := SRC^; 34 4 SEEKWRITE(DEST,I); 35 4 END; 36 3 I := I + 1; 37 3 END; 38 2 39 2 CLOSE(DEST,RESULT); 40 2 IF RESULT = 255 THEN 41 2 WRITELN('Error closing destination file') 42 2 END; 43 1 (* MAIN PROGRAM IN FIGURE 10 *)
Figure 8: File Transfer with SEEKREAD and SEEKWRITE
Stmt Nest Source Statement 1 0 PROGRAM FILE_TRANSFER; 2 0 3 0 (*--------------------------------------------------*) 4 0 (* Transfer file a to file b using GET and PUT *) 5 0 (*--------------------------------------------------*) 6 0 7 0 TYPE 8 1 CHFILE = FILE OF CHAR; 9 1 VAR 10 1 A,B : CHFILE; 11 1 NAME : STRING; 12 1 13 1 PROCEDURE TRANSFER(VAR SRC : CHFILE; VAR DEST : CHFILE); 14 1 VAR 15 2 RESULT : INTEGER; 16 2 BEGIN 17 2 WHILE NOT EOF(SRC) DO 18 2 BEGIN 19 3 DEST^ := SRC^; 20 3 PUT(DEST); 21 3 GET(SRC); 22 3 END; 23 2 24 2 CLOSE(DEST,RESULT); 25 2 IF RESULT = 255 THEN 26 2 WRITELN('Error closing destination file') 27 2 END; 28 1 (* MAIN PROGRAM IN FIGURE 10 *)
Figure 9: File Transfer with GET and PUT
BEGIN WRITE('Source? '); READLN(NAME); ASSIGN(A,NAME); RESET(A); IF IORESULT = 255 THEN BEGIN WRITELN('Cannot open ',NAME); EXIT END; WRITE('Destination? '); READLN(NAME); ASSIGN(B,NAME); REWRITE(B); IF IORESULT = 255 THEN BEGIN WRITELN('Cannot open ',NAME); EXIT END; TRANSFER (A, B) END.
Figure 10: File Transfer: Main Program Body for Figures 6 Through 9
The following table shows code and data size and execution speed of the above file transfer procedures on a 4MHz Z80 with no wait states, and a single density, single sided, 8" floppy disk. Sizes are in decimal bytes and the speed is in seconds. The file transferred was 8K bytes exactly. These numbers will not be identical for all releases of the compiler, so if your version is not the same size and speed as that listed there is nothing amiss. The data demonstrates only the size and speed differences among four file access methods.
Transfer Method | BLOCK I/O | GNB/WNB | SEEK I/O | GET/PUT |
---|---|---|---|---|
Compiled Code | 520 | 519 | 530 | 477 |
Compiled Data | 2532 | 2534 | 4584 | 482 |
Total Code | 7317 | 7161 | 9243 | 6764 |
Total Data | 3576 | 3577 | 5697 | 1494 |
Total Size | 10893 | 10738 | 14940 | 8258 |
Speed | 7.8 | 18.4 | 8.6 | 35.1 |
A sample program is not provided for the following routines as the use of these is obvious or duplicates a previously described set of file routines.
PROCEDURE OPEN (F: FILE VARIABLE, TITLE : STRING; VAR RESULT INTEGER);
Purpose : Identical to the sequence 'ASSIGN(F,TITLE); RESET(F);'.
PROCEDURE CLOSEDEL (F : FILE VARIABLE; VAR RESULT : INTEGER);
Purpose : Close file F and delete it. Used with temporary files. Exactly the same as CLOSE followed by PURGE.
PROCEDURE PURGE (F : FILE VARIABLE);
Purpose : Delete the file associated with F from the disk. An ASSIGN must be executed sometime before the call to PURGE so that the file control block for F contains the name of the file to be deleted. On some operating systems other than CP/M (80 or 86) the file may be required to be closed before this procedure can function properly. In this case CLOSEDEL is a useful procedure.