9.10 Getting Physical With Records

The discussion to this point has been rather abstract, and has dealt with records strictly in the context of representation and assignment. If records are to be of any use, they must be capable of being stored to and retrieved from the secondary storage (disk or tape drive). That is, one needs to be able to move records from one plane of existence to another in order to make efficient use of the machine. Just as in order to assign a record it is necessary to know the type of every field, so also one must take advantage of knowing the details of the structure in order to write one to a text stream. If one had:

  Name = ARRAY [0 .. 10] OF CHAR;
  StudentData =
      lastName, firstName : Name;
      mark1, mark2 : REAL;
      grade : CHAR

and, supposing that classMember were of type StudentData, one would need to write the code:

WITH classMember
    WriteString (outChan, firstName);
    WriteString (outChan, lastName);
    WriteReal (outChan, mark1, 12);
    WriteReal (outChan, mark2, 12);
    WriteChar (outChan, grade);

to send the data to an output device, one item per line. This code would also suffice whether the output device were the screen, a printer, or a sequential file. This could be achieved in the usual ways:

1. Use STextIO routines without the channel parameter and the non-standard routine OpenOutput to redirect the standard output to a file, or

2. Use StreamFile or SeqFile as appropriate to open a specific stream channel to outChan and print as above.

As in earlier cases too, if one sent all that text to a disk file by either method, there would be quite a lot of valuable storage wasted because the ASCII text version of a real number would take up about six times as many storage locations as it the raw memory version does. Further, separate routines for displaying records contents on the screen would be required anyway, because one surely would want some static information to organize the data visually thus:

Name: Meier, Johanna
Marks: 84.7 and 92.6
Final Grade: A

and such static tags as Name: as may be necessary for screen output certainly ought not to be incorporated into every one of the records as they go to the stored file.

So, in this case again, it would be better to use RawIO and send each record as an entire entity to the disk file in raw form thus:

RawIO.Write (classMember );

Likewise, if one had a large number of records stored in an array as:

  Name = ARRAY [0 .. 20] OF CHAR;
  Item =
      name : Name;
      price : REAL;
      quantity : CARDINAL;
      bin : CHAR;
    END;  (* Item *)
  Items = ARRAY [1 .. max] OF Item;
  stock : Items;
  dataChan : ChanId;

one could save such items to a disk file using:

  SeqFile.Rewrite (dataChan);
  FOR count := 1 TO max
      RawIO.Write (dataChan, stock [count]);
  SeqFile.Close (dataChan);

or, even just

  RawIO.Write (dataChan, stock)

to send the entire array at once. Likewise, the items can be read using:

      numItems := 0;
        RawIO.Read (dataChan, stock [numItems + 1]);
        IF IOResult.ReadResult (dataChan) = allRight
            INC (numItems);
      UNTIL (numItems = max) OR (IOResult.ReadResult (dataChan) # allRight)

or, read the entire array, if that is the way the data was originally written out:

  RawIO.Read (dataChan, stock);

This example is elaborated on in the extended illustration at the end of this chapter.

9.10.1 RawIO in Non-Standard Modula-2

As a last word on this example, it should be observed that the ISO procedures Read and Write in RawIO are high-level implementations of somewhat relatively low-level ideas. Corresponding procedures may or may not exist in some non standard implementations.

If there are such procedures, they are probably found in the module Files/Filer/FileSystem and have definitions such as:

PROCEDURE ReadRec(file : File; VAR rec : ARRAY OF BYTE);
  (* This is a safe high level procedure for reading records *)

PROCEDURE WriteRec(file : File; rec : ARRAY OF BYTE);
  (* This is a safe high level procedure for writing records *)

where SYSTEM.BYTE takes the place of SYSTEM.LOC.

On the other hand, it might be necessary to manufacture one's own procedures with these names from other routines that one already has in order to achieve results similar to this. For instance, there may be a low level regular or function procedure:


somewhere on the system--again, most likely in Files/Filer/FileSystem or in another module that might be called ByteBlockIO.

The parameters are: a variable of type File, the starting address of the memory location to which one is going to read, and the number of bytes required.

If ReadBytes is implemented as a function procedure (check the manual) then the CARDINAL returned is the number of bytes actually read. If the latter is less than the number requested, a variable EOF will also be set to TRUE. Now, one could perhaps code:

  ReadBytes (inFile, ADR (person), SIZE (person))
END ReadRec;

and likewise for WriteRec.

Because such routines are tied to the machine on which they are executed, and because non-standard versions of Modula-2 have more variation in the area of file handling I/O than in anything else, the names and syntax will in those cases vary widely from one version of Modula-2 to another.