Streams

File read and write operations are handled by the stream type. stream is actually a family of types, with different 'modes' specialised for handling common vector file operations. 2019.11 supports two modes: mode 1 handles the reading of files which contain data fields and comments, while mode 2 handles the writing of files which contain data fields.

Assume, for example, that file 'test.dat' contains:

/* comments
 */

vector 1 data 10 // comment
vector /* */ 2 data 20
vector 3 data 30
// comment
/* */ vector 4 /* */ data 42

This file can be read as follows:

// this program produces the output:
// offset 0: vector is 1; data is 10
// offset 1: vector is 2; data is 20
// offset 2: vector is 3; data is 30
// offset 3: vector is 4; data is 42

stream s1 {
   mode 1;
   file "test.dat";
   format "vector %i data %i", f1, f2;
} a;

main() {
   int i;
   for(i = 0; i < a`size; i++) {
      a = i;                    // set the file offset
      report("offset %d: vector is %d; data is %d\n", a`offset, a.f1, a.f2);
   }
}

Stream a was declared to contain 'members' f1 and f2, in much the same way that structures have members. These members are automatically updated with the contents of the corresponding data fields when the stream is positioned. The format string should be constructed in the same way as a scanf format string: in this case, the testbench expects to find the word 'vector' as the first non-whitespace token on the line, followed by an integer (%i) which is scanned into f1, followed by the word 'data', and so on, until the format string is exhausted. The entire file is read and checked during compilation, rather than at runtime; the compiler will report an error if it cannot be scanned according to the format string.

Mode 1 streams are positioned whenever an int is assigned to the stream. Assigning 0 to the stream rewinds it to the start of the file.

Mode 2 streams are declared in the same way as mode 1 streams, and are used to create data files. These files can be read by a mode 1 stream. This program creates a file named 'out.dat' (the '../vectors' directory must already exist):

stream s1 {
   mode   2;
   file   "../vectors/out.dat"; 
   format "%d %b %6.2f", f1, f2, f3;
};

main() {
   stream s1 a;
   for(int i = 0; i < 10; i++) {
      set_members(i, a);      // assign data to fields a.f1 through a.f3
      ++a;                    // write the current line, advance the stream
      assert(a`size == i+1);  // `size returns the number of lines in the stream
   }
}

// set fields f1, f2, and f3 in the current line of 's'. note that 's'
// must be passed by reference, or the compiler will raise an error
void set_members(int indx, stream s1& s) {
   s.f1 = indx;
   s.f2 = indx;
   s.f3 = (real2)indx;
}

On completion, 'out.dat' contains:

0 0   0.00
1 1   1.00
2 10   2.00
3 11   3.00
4 100   4.00
5 101   5.00
6 110   6.00
7 111   7.00
8 1000   8.00
9 1001   9.00

Mode 1 and 2 streams provide no operators for opening and closing the stream; these operations are carried out automatically when the stream comes into, or goes out of, scope. Global and static streams are opened at the start of program execution, and remain open throughout the lifetime of the program. If it is necessary for a local stream to retain state between function calls then that stream should be declared as static.