I/O in ZPL

This page is meant to give a summary of how to use I/O in ZPL programs. If you find inconsistencies between this document and your use of ZPL, or bugs in the I/O features of ZPL, please let us know at zpl-bugs@cs.washington.edu.

I/O Overview

All ZPL I/O routines are of two types, writes and reads. Writes are used to output expressions to the console or a file while reads assign or match their arguments from the console or a file. ZPL supports text and binary I/O formats. These are based on ANSI C's text (fprintf/fscanf) and binary (fwrite/fread) formats, so nitty-gritty details not addressed in this document will often be covered in a C reference manual. Feel free to send any unanswered questions to us at zpl-info@cs.washington.edu.

Text I/O

Text I/O is performed in ZPL using the write, writeln, and read statements. Each of these statements takes an optional file variable as its first argument, followed by an arbitrary number of expression arguments to be written or read. If the file argument is not used, the console will be used to perform the I/O.

The write statement writes its arguments to the output file in the order that they are specified. The writeln statement functions identically, but follows its last argument with a linefeed character (`\n'). Similarly, the read statement reads its arguments from the input file in the order that they are specified. As in C, whitespace is generally ignored when reading in arguments, so there is no readln statement.

For example, the following code fragment:

     var number:integer;

     begin
       value := 10;
       writeln("The value of number is: ",number);
       write("Enter a new number: ");
       read(number);
     end;

will write "The value of number is 10" to the console. On the next line, the prompt "Enter a new number: " will be written, at which point the user will be able to type in a value to be stored in number.

Array Element Order

Arrays are written and read in row-major order over the range specified by the enclosing region scope. For example, an array written out in the scope [1..M,1..N] will be written in the order [1,1], [1,2], ..., [1,N], [2,1], ... [2,N], ... [M,N]. (In higher-dimensional arrays, the higher dimensions are iterated over more quickly).

Formatting Text I/O arguments

Each argument in a text I/O statement can be preceded by an optional C-style formatting string (e.g. "%6d", "%3.8f", "%.4ld") to control how it is written. This string should precede the argument that it applies to, using a colon as a separator (e.g. writeln("%6.6d":number);). See the C reference for your compiler's fprintf/fscanf options to see what string formats are supported. ZPL uses default formats which were chosen to make array elements line up column-wise. Users who are concerned with the esthetics of their output will almost certainly want to override these defaults.

For example, to use the default formatting, one might write:

     writeln("dx = ", dx, "; dy = ", dy);

To the dx and dy arguments:

     writeln("dx = ", "%9.4f":dx, "; dy = ", "%9.4f":dy);

Note that these formatting strings can optionally be factored together with the string constants as follows:

     writeln("dx = %10.4f":dx, "; dy = %10.4f":dy);

When formatting strings are supplied for array arguments, the is applied to each element of the array.

Using Files

Files in ZPL are represented by variables of type file. They are associated with disk files using the open and close functions, which are prototyped as follows:

     prototype open(filename:string; 
                    mode:string):file;
     prototype close(afile:file);

The open function takes a filename as its first argument, and a string indicating whether the file should be read or written as its second. The read/write modes are as in C. For convenience, the built-in constant strings file_read and file_write are provided and can be used as the second argument.

Once a file is opened, it can be used as the first argument of a ZPL I/O statement to direct the write or read to use that file. For example, the following code:

     var outfile:file;

     begin
       outfile := open("http://www.cs.washington.edu/research/zpl/est.dat",file_write);
       writeln(outfile,"%6.6d":number);
       close(outfile);
     end;

would result in the value of number being written to a file named test.dat.

ZPL provides the constant files zout, zin, and zerr as a means of referring to the standard output, input, and error streams provided in C. These can be redirected in UNIX using the traditional mechanisms.

Binary I/O

ZPL's binary I/O works identically to its text I/O, but using the routines bwrite and bread. Binary output has the advantages of generally being faster, more compact, and more precise. Because it uses the binary representation of the specific machine, however, binary files are not portable between machines.

On most platforms, it is possible to mix text and binary formats within a single file, though the user has to be cautious, since binary I/O is more sensitive to whitespace than text I/O.

Performance Issues

I/O is slow by nature, and potentially slower in a parallel program, due to the synchronization that is generally required between processors, shared hardware resources, etc. For this reason, users should typically avoid using I/O in performance critical sections of code.

In general, binary I/O to a file will be faster than text I/O due to the fixed size of elements' binary representations. This allows all processors to write their data in parallel. In contrast, text I/O (or binary console I/O) sequentializes all writes and reads, since the amount of space required per element is variable.

Specifying Elementwise I/O Routines

ZPL currently allows the user to supply I/O routines for any parallel array to control how each element of the array is read or written. This can be especially useful for performing I/O with arrays of complex types (such as arrays of records, or arrays of indexed arrays). When the array is written or read, the supplied routine will be invoked for each element within the array, passing the element into the routine as an argument.

The prototype for such routines is as follows:

     prototype io_elem(afile:file;
                       var elem:type; 
                       {:string});

afile is the file to which element should be read or written. is the string that was provided to the array in the write or read statement. Note that the argument is optional and can be left off (or simply ignored).

Such functions are bound to the array being written or read using the bind_write_func and bind_read_func calls. These calls take two arguments, the array and the function to be used for its elements. The following is an example:

     var A:[R] array [1..2] of integer;

     procedure write_2ary_array(outfile:file;
                                var elem:array [1..2] 
                                         of integer;
                                :string);
     begin
       write(outfile,"[", :elem[1], ",",
                     :elem[2], "] ");
     end;

     ...

     bind_write_func(A,write_2ary_array);
     writeln(outfile,"%2.2d":A);

This piece of code declares an array of indexed arrays and creates a function to write out those indexed arrays in a 2-tuple notation. It then binds the function to the array. The writeln() statement will therefore call this function on each element of the array, passing in the elements and the control string. This results in the array being written out roughly as follows:

     [00,31] [22,47] [02,13] etc...

Further Questions?

If you have further questions about I/O in ZPL, please don't hesitate to contact us at zpl-info@cs.washington.edu.