Pascal Programming/Files

Declaring a FileEdit

In Pascal, a file is declared using the data type of its contents.

var
   StoredNumbers: file of Integer;
   TruthValues: file of Boolean;

In the above example, StoredNumbers is a file for storing integer numbers. On the storage medium, it will contain some sequence of integers, each stored in binary code, just as they would be in memory. This is called a "binary file". Writing to the file will copy an integer out of memory; reading from the file will copy it into memory. How to actually read and write the values will be introduced shortly.

Not just integers, but also other data can be stored in binary files. In the previous example, a Boolean file was also declared with the filename TruthValues. It would be a file filled only with some sequence of the values True and False. User-defined types such as records can also be stored in files. For example, we might imagine an app that keeps a list of all the computers someone uses to code software. For each machine, it could store the serial number; the name of the manufacturer; whether the computer was a desktop or some other device; and whether it is a personal, school, or work computer.

type
   Device = (Desktop, Laptop, Smartphone, Tablet);
   Usage = (Personal, School, Work);
   Computer = record
      whatKind: Device;
      serial, manufacturer: String;
      purpose: Usage;
   end;
var
   ListOfComputers: file of Computer;

File OutputEdit

After a file has been declared, it may be actually used to write data to the disk or other medium. First, we name the file using the Assign command. Next, we open the file with Rewrite. Then, values are sent to the file using Write. Finally, the file is Closed.

Here is a simple example that writes the first 20 perfect squares to a file.

program OutSquares;
var
   Number, Square: Integer;
   TwentySquares: file of Integer;
begin
   assign(TwentySquares, 'perfect_squares.txt');
   rewrite(TwentySquares);
   for Number := 1 to 20 do begin
      Square := Number * Number;
      write(TwentySquares, Square)
   end;
   close(TwentySquares)
end.

Notice that TwentySquares was the first argument to the Write procedure. Whenever we are writing to a storage file instead of the console, we need to specify the file identifier first, then the value to be written.

File InputEdit

If data has already been written to a file, it may be also be read back into memory. We still use Assign and Close, but opening the file is instead done with the Reset procedure. Reset tells the computer that the file is for input instead of output. We use the Read command to retrieve a value, again specifying the file itself as the first argument.

Let's write a program to read the twenty values stored in the previous example back into the computer's memory. We can store them all at once using an array. Assuming we already ran the OutSquares program, the perfect squares will be in a file called "perfect_squares.txt".

program InSquares;
var
   N: 1..20;
   SquaresList: array [1..20] of Integer;
   OldFile: file of Integer;
begin
   assign(OldFile, 'perfect_squares.txt');
   reset(OldFile);
   for N := 1 to 20 do read(OldFile, SquaresList [N]);
   close(OldFile)
   {Now that the numbers are in memory, we could do more with them if we wished.}
end.

In the above case, we already knew that there were only twenty numbers in the file, but this is not always the case. Sometimes the size of the file is not known ahead of time. If we had let the user choose how many perfect squares to store, the input program would need to be prepared for any possible number of inputs. To do this, Pascal supplies the EOF function, which stands for "End of File". EOF is true only when there are no items left in the file to read. EOF can be true even when a file is first opened if the file happens to be empty. The function acts as a signal for "no more data, or no data at all."

Let's write another example that will read all of the numbers in a binary file and display them on the console. The computer will use EOF to decide when to stop.

program NumberDump;
var
   Number: Integer;
   Storage: file of Integer;
   Filename: String;
begin
   write('Enter the name of a binary file containing only integers:');
   readln(FileName);
   assign(Storage, Filename);
   reset(Storage);
   if EOF(Storage) then
      writeln('There are no integers to be displayed.')
   else repeat
      read(Storage, Number);
      writeln(Number)
   until EOF(Storage);
   close(Storage)
end.

Text FilesEdit

Imagine we declared a file like this:

var
   Characters: file of Char;

It would store the binary representations, not of integers or records, but of text characters. It would be able to store letters and punctuation marks in the encoding used by your computer. This could include words, sentences, or even paragraphs. The file could even include numbers, written out digit-by-digit. These would be human-readable data that you could easily view using a text editor. This would be a "text file".

In fact, we can freely use text files in Pascal, and there is even a shortcut for declaring them:

var
   Characters: Text;

Text files are unique since they are storing all data as human-readable text. You can use them for not just individual characters, but also numbers or strings. They can be organized in lines, so that you can use the same WRITELN and READLN procedures used in console input. The difference is that you must still include the file identifier as the first argument.

The following example procedure will write the names, ages, and weights of several people to a text file. Each person's name will be on one line, followed by the age and weight on the next line. The filename and the number of people will be taken as arguments.

procedure WriteData(Filename: String; HowMany: Integer);
   var
      Person: String;
      Age, Weight, Count: Integer;
      DataFile: Text;
   begin
      {First prepare the file for output.}
      assign(DataFile, Filename);
      rewrite(DataFile);
      {Now start a loop.}
      for Count := 1 to HowMany do begin
         {Read each person's input from console.}
         write('Person #', Count, ' Name?');
         readln(Person);
         write('Age? ');
         readln(Age);
         write('Weight in kilograms? ');
         readln(Weight);
         {Write to the file.}
         writeln(DataFile, Person);  {Name on a line by itself}
         writeln(DataFile, Age, ' ', Weight) {Age and Weight, with a space between}
      end;
      {We're done!}
      close(DataFile)
   end;

Notice in the above example that the Age and Weight are written with a space between them. This is so that human readers or the computer can recognize them as separate numbers. If they were written on the same line with nothing between them, they would look like one number instead of two. Age 30 and Weight 91 would fuse into 3091. Be careful of this pitfall in text files.

We can easily write a procedure to read and display the same type of file. Thanks to EOF, we don't need to know how many people are in the file.

procedure ReadData(FileName: String);
   var
      Person: String;
      Age, Weight: Integer;
      DataFile: Text;
   begin
      {Prepare the file for input.}
      assign(DataFile, Filename);
      reset(Filename);
      {Start the input loop.}
      while not EOF(DataFile) do begin
         {Read each person's data from the file.}
         readln(DataFile, Person);
         readln(DataFile, Age, Weight);
         {Write to console.}
         write(Person, ' is ', Age, ' years old');
         write('and weighs ', Weight, ' kilograms');
         writeln('.')
      end;
      {Done!}
      close(DataFile)
   end;