Chapter 8 - Input/Output
A SIMPLE OUTPUT PROGRAM
Load and display the file named SIMPLOUT.MOD for an example of the simple output functions.
SIMPLOUT.MOD
(* Chapter 8 - Program 1 *)
MODULE SimplOut;
FROM InOut IMPORT WriteString, WriteLn; (* unqualified *)
IMPORT InOut; (* This imports every procedure in InOut *)
IMPORT Terminal; (* This imports every procedure in Terminal *)
VAR Index : CARDINAL;
BEGIN
WriteString("This is from InOut, ");
InOut.WriteString("and so is this.");
Terminal.WriteLn;
Terminal.WriteString("This is from Terminal, ");
Terminal.WriteString('and so is this.');
WriteLn;
FOR Index := 1 TO 10 DO
InOut.WriteCard(Index,5);
END;
InOut.WriteLn;
END SimplOut.
This program is limited to writing only to the monitor but we will get to files and printer output shortly. We must first establish some basic principles for use with library procedures.
The first line of the declaration part of the program imports our two familiar procedures "WriteString" and "WriteLn" in the same manner we are used to. The next line imports every procedure in "InOut" and makes them available for use in the program without specifically naming each one in the IMPORT list. The third line imports every procedure from "Terminal" so that they too are available for our use. The procedures that are imported explicitly can be used in exactly the same manner that we have been using them all along, simply name the procedure with any arguments they use. The others can only be used with a "qualifier" that tells which library module they come from.
An example is the easiest way to describe their use so refer to the program before you. Line 11 uses the explicitly defined procedure from "InOut", line 12 uses the same procedure from "InOut", and line 15 uses the procedure of the same name from "Terminal". Line 11 uses the unqualified procedure call from "InOut", and lines 12 and 15 use the qualified method of calling the procedures from both library modules.
In this case, the two procedures do the same thing, but it is not required that procedures with the same name do the same thing. By adding the library module name to the front of the procedure name with a period between them, we tell the system which of the two procedures we wish to use. If we tried to explicitly import both "WriteString" procedures we would get a compile error, so this is the way to use the same name twice.
WHAT IS A LIBRARY MODULE?
What I have been calling a library module is more properly termed a "module" and is the biggest benefit that Modula-2 enjoys over other programming languages. This is the quality that gives Modula-2 the ability to have separately compiled modules, because a module is a compilation unit. When you get to Part III of this tutorial, you will learn how to write your own modules containing your own favorite procedures, and call them in any program in the same manner that you have been calling the procedures provided by your compiler writer.
None of the procedures you have been importing are part of the Modula-2 language, they are extensions to the language provided for you by your compiler writer. Since they are not standard parts of the language, they may vary from compiler to compiler. For that reason, I have tried to use those defined by Niklaus Wirth in his definition of the language, and no others.
STUDY YOUR REFERENCE MANUAL
This would be a good place for you to stop and spend some time reading your reference manual. Look up the section in your manual that is probably called the "Library" and read through some of the details given there. You will find that there are many things listed there that you will not understand at this point, but you will also find many things there that you do understand. Each module will have a number of procedures that are "exported" so that you can "import" them and use them. Each procedure will have a definition of what arguments are required in order to use it. Most of these definitions should be understandable to you. One thing you will find is that only the "PROCEDURE procname;" is given along with the arguments, with the actual code of the procedure omitted. We will study about this in "Part III" also. The part that is shown is the "DEFINITION MODULE" which only gives the calling requirements. The "IMPLEMENTATION MODULE" which gives the actual program code of the procedure is usually not given by compiler writers.
As you study the library modules, you will find procedures to handle strings, variables and conversions between the two. You will find "mouse" drivers, "BIOS" calls to the inner workings of your operating system, and many other kinds of procedures. All of these procedures are available for you to use in your programs. They have been written, debugged, and documented for your use once you learn to use them. In addition, you will have the ability to add to this list by creating your own modules containing your own procedures.
BACK TO THE PROGRAM "SIMPLOUT"
Notice that in lines 13, 17, and 22, three different ways are used to call "WriteLn", even though there are actually only two procedures (that happen to do the same thing). A little time spent here will be time well spent in preparing for the next few programs. When you think you understand this program, compile and run it.
NOW FOR SOME SIMPLE INPUT PROCEDURES
Load the program named SIMPLIN.MOD for our first example of a program with some data input procedures.
SIMPLIN.MOD
(* Chapter 8 - Program 2 *)
MODULE SimplIn;
FROM InOut IMPORT WriteString, WriteCard, WriteLn, Write,
ReadString, ReadCard, Read, EOL;
VAR Count, Number : CARDINAL;
List : ARRAY[1..6] OF CARDINAL;
StringOfData : ARRAY[1..80] OF CHAR;
Alpha : CHAR;
BEGIN
(* Example of reading in a word at a time *)
WriteString("Input three words of information.");
WriteLn;
FOR Count := 1 TO 3 DO (* Read 3 words in *)
ReadString(StringOfData);
WriteString("---->");
WriteString(StringOfData);
WriteLn;
END;
(* Example of character reading *)
WriteLn;
WriteString("Input 50 characters.");
WriteLn;
FOR Count := 1 TO 50 DO
Read(Alpha);
Write(Alpha);
END;
WriteLn;
(* Example of reading in a line at a time. *)
WriteLn;
WriteString("Input three lines of information.");
WriteLn;
FOR Count := 1 TO 3 DO (* count three lines *)
Number := 1;
REPEAT (* repeat until an end-of-line is found *)
Read(Alpha);
Write(Alpha);
IF Alpha <> EOL THEN
StringOfData[Number] := Alpha;
Number := Number + 1;
END;
UNTIL Alpha = EOL;
StringOfData[Number] := 0C; (* End of string indicator *)
WriteString("---->");
WriteString(StringOfData);
WriteLn;
END;
(* Example of reading CARDINAL numbers in *)
WriteLn;
WriteString("Enter 6 CARDINAL numbers.");
WriteLn;
FOR Count := 1 TO 6 DO
ReadCard(List[Count]);
WriteLn; (* New line for separation of numbers *)
END;
WriteLn;
FOR Count := 1 TO 6 DO (* Now, write the numbers out *)
WriteCard(List[Count],8);
END;
WriteLn;
END SimplIn.
In every program we have run so far in this tutorial, all data has been stored right in the program statements. It would be a very sad computer that did not have the ability to read variable data in from the keyboard and files. This program is our first that can read from an external device, and it will be limited to only the keyboard.
This program is broken up into four groups of statements, each illustrating some aspect of reading data from the keyboard. This could have been four separate files but it will be easier to compile and run one file.
Beginning with line 14 we have an example of the "ReadString" procedure which reads characters until it receives a space, a tab, a return, or some other nonprintable character. This loop will read three words on one line, one word on each of three lines, or any combination to get three words of groups of printable ASCII characters. After each word or group is read, it is simply "printed" to the monitor for your inspection.
ONE CHARACTER AT A TIME
The next group of statements is a loop in which 50 ASCII characters are read in and immediately echoed out to the monitor. It should be evident to you that the characters are read one at a time, and since the same variable is used for each character, they are not stored or saved in any way. In actual practice, the characters would be stored for whatever purpose you intend to use them for. When you run this part of the program, it will seem like the computer is simply acting like a word processor, echoing your input back to the monitor.
ONE LINE AT A TIME
The next section, beginning in line 32, reads in a full line before writing it out to the monitor. In this program we are introduced to the "EOL" which is a constant defined by the system for our use. It must be imported from "InOut" just like the procedures are, and it is a constant that is equal to that ASCII value that is returned when we hit the "return" key. It is therefore equal to the End-Of-Line character, and that is how it got its name. If we compare the input character to it, we can determine when we get to the End-Of-Line. That is exactly what this loop does. It continues to read characters until we find an EOL, then it terminates the input loop and displays the line of data. Notice that this time we do not simply read the data and ignore it but instead add it character by character to the ARRAY named "StringOfData". Of course, the next time through the loop we overwrite it. The careful student will also notice that, in line 45 we wrote a zero character in the character of the line just past the end of the line. The zero is to indicate the end-of-string for the string handling procedures. This portion of the program is easy, but will require a little time on your part to completely dissect it.
READING IN SOME NUMBERS, CARDINAL
Beginning in line 51, we have an example of reading 6 CARDINAL numbers in a loop. The procedure "ReadCard" will, when invoked by your program, read as many digits as you give it. When it reads any character other than a 0 through 9, it will terminate and return the number to your calling program. Notice that this time all 6 numbers are read in, stored, and when all are in, they are all displayed on one line. This should be easy for you to decipher.
COMPILING AND RUNNING THIS PROGRAM
There is no program that you have studied here that is as important for you to compile and run as this one is. You should spend considerable time running this program and comparing the results with the listing. Enter some invalid data when you are running the "ReadCard" portion of it to see what it does. When you are running the "line at a time" portion, try to enter more than 80 characters to see what it will do with it. This is a good point for you to learn what happens when errors occur. After you understand what this program does, we will proceed to file input and output.
FILE INPUT/OUTPUT
Load and display the file named FILEIO.MOD for your first file reading and writing.
FILEIO.MOD
(* Chapter 8 - Program 3 *)
MODULE FileIO;
FROM InOut IMPORT WriteString, WriteInt, WriteLn, Write, Read,
OpenInput, OpenOutput, CloseInput, CloseOutput,
Done;
IMPORT Terminal; (* This is used for monitor output *)
(* and InOut is used for the file *)
(* input and output. *)
VAR Character : CHAR;
BEGIN
REPEAT (* open the input file *)
Terminal.WriteString("Enter Input filename - ");
OpenInput("MOD");
UNTIL Done; (* Quit when open is successful *)
REPEAT (* open the output file *)
Terminal.WriteString("Enter Output filename - ");
OpenOutput("DOG");
UNTIL Done; (* quit when the open is successful *)
REPEAT (* character read/write loop - quit at EOF *)
Read(Character);
IF Done THEN (* Done = FALSE at EOF *)
Write(Character);
END;
UNTIL NOT Done;
CloseInput;
CloseOutput;
END FileIO.
The library module named "InOut" has the ability to either read and write from/to the keyboard and monitor, or to read and write from/to files. The program before you redirects the input and output to files for an illustration of how to do it.
Line 16 requests the operator, namely you, to enter a filename to be used for input. There is nothing different about this statement than the others you have been using. The next line requests the system to open a file for inputting, and part of the procedure "OpenInput" is to go to the keyboard waiting for the filename to be typed in. So the message in line 16 is in preparation for what we know will happen in line 17. Whatever filename is typed in is opened for reading if it is found on the disk. The "MOD" in the parentheses is a default extension supplied, (this can be any extension you desire). If no extension is supplied by the operator, and if the filename does not have a "." following it, ".MOD" will be added to the filename. If the system can find the requested filename.extension, the "Done" flag is made TRUE and we can test it. In this example, if the flag is returned FALSE, we ask the operator to try again until he finally inputs a filename that exists on the default disk/directory.
NOW TO OPEN AN OUTPUT FILE
Once again, in line 21, we request a filename for output anticipating the operation of the "OpenOutput" in line 22. Line 22 waits for a keyboard input of a filename and if the filename entered has no extension, it adds the extension ".DOG" and attempts to open the file for writing. When you input the filename, adding a "." to the end of the filename will prevent the extension being added. If the Filename.extension does not exist, it will be created for you. If it does exist, it's contents will be erased.
It is nearly assured that the file will be created and the "Done" flag will be supplied as TRUE, but it would be good practice to check the flag anyway. It will be apparent when we get to the program on printer output, that it is impossible to open a file with certain names, one being "PRN", because the name is reserved for printer identification and the "Done" flag will be returned FALSE.
HOW DO I USE THE OPENED FILES?
Anytime you use this technique to open a file for writing, any procedure from InOut will now be redirected to that file. Anytime you use this technique to open a file for reading, any procedure from InOut will access the file named instead of the keyboard. In addition, the library module named "RealInOut" will also be redirected with "InOut". Any time you read or write, instead of using the keyboard and monitor, the input and output files will be used. The input and output will be the same except for where it goes to and comes from, and it is possible to only open one and leave the other intact. Thus input can be from a file, and output can still go to the monitor.
When I/O is redirected, the module "Terminal" is still available for use with the monitor and keyboard as I/O using this module can not be redirected. The module named "Terminal" does not have the flexibility of input and output that is found in "InOut" so it is a little more difficult to use.
There is a major drawback when using "InOut" with the I/O redirected. You are limited to one file for input and one file for output at one time. Finally, this method cannot be used to open a "fixed" or prenamed file, since it always surveys the keyboard for the filename. It will probably come as no surprise to you that all of these limitations will be overcome with another method given in the next two programs.
The program itself should be easy to follow, once you realize that the flag named "Done" returns TRUE when a valid character is found following a "Read", and FALSE when an End-Of-File (EOF) is detected. The "Done" flag is set up following each operation so its use is dictated by which procedure was called last. The program simply copies all characters from one file to another. When completed, the two procedures named "CloseInput" and "CloseOutput" are called to do just that, to close the files and once again make the I/O available to the keyboard and monitor. In this case, however, we immediately terminate the program without taking advantage of the return to normal.
Compile and run this program, being careful not to give it the name of an existing file for output, or it will overwrite the old data in the file and copy new data into it. That is the reason for the extension "DOG". Few people will have a file with that extension. For input, use the present filename (FILEIO.MOD), for output, use "STUFF", "STUFF.", and "STUFF.STU", observing the resulting new filename each time.
THE COMPLETE FILESYSTEM
Load and display the file named VARYFILE.MOD for an example using the complete "FileSystem" module.
VARYFILE.MOD
(* Chapter 8 - Program 4 *)
MODULE VaryFile;
FROM FileSystem IMPORT Lookup, Close, File, Response, ReadChar;
FROM InOut IMPORT Write, WriteString, ReadString, WriteLn;
VAR NameOfFile : ARRAY[1..15] OF CHAR;
InFile : File;
Character : CHAR;
BEGIN
REPEAT (* repeat until a good filename is found *)
WriteLn;
WriteString("Enter name of file to display ---> ");
ReadString(NameOfFile);
Lookup(InFile,NameOfFile,FALSE);
UNTIL InFile.res = done; (* good filename found *)
REPEAT (* character read/display loop - quit at InFile.eof *)
ReadChar(InFile,Character);
IF NOT InFile.eof THEN
Write(Character);
END;
UNTIL InFile.eof; (* quit when eof is found *)
Close(InFile);
END VaryFile.
As stated earlier, Modula-2 does not have any input/output methods defined as part of the language. This is because the I/O available on computers is so diverse, there would be no way of defining a method that could be used on all computers. To eliminate the problem, Niklaus Wirth simply defined no I/O as part of the language, but he did suggest a few standard modules to perform the basic I/O tasks. Since they are only suggestions, compiler writers are not constrained to follow them, but in the interest of portability, most will. A very limited subset of all of the procedures are the only ones that will be used in the tutorial portion of this course. (A few other procedures will be used in the example programs given in those chapters.) It will be up to you to see that the procedures are in order with your compiler, and where they differ, to modify them. A few notes are available for your assistance in the COMPILER.DOC file on your distribution disk for those compilers available at the time of release of this tutorial.
BACK TO THE PROGRAM NAMED VARYFILE
This time we IMPORT several procedures from the library module named "FileSystem" for I/O use. This time, we ask for the input filename and store it internally in a string variable. This implies that we can also define the filename as a constant that is carried in the program, making it possible to use a certain preprogrammed filename for input. We use the procedure "Lookup" to open the file. This procedure uses three arguments within the parentheses, the first being the symbolic filename which is a record of information about the file. (We will come to records later, don't worry too much about it at this point.) The second argument is the name of the file on disk we wish to access, and the third argument is a boolean variable or constant. If it is TRUE, and the file name is not found, a new file of that name will be created. If it is FALSE, and the file is not found, a new file will not be created, and the record variable "InFile.res" will return the value "notdone". (That refers to one variable named "res" which is a part of the record "InFile".)
Note that the variable "InFile", is a record composed of many parts, but for the immediate future we only need to be concerned with its definition. It is defined as a variable of type "File" which is imported from the module named "FileSystem". Until you study the lesson in this tutorial on records, simply copy the method used here for file Input/Output.
Once the file is opened, you can use any of the procedures included in the "FileSystem" module, being careful to follow the rules given in your library documentation. The remainder of the program should be self- explanatory and will be left to your inspection. With this example in hand, spend some time studying your "FileSystem" module to become familiar with it, then compile the program and run it to observe its operation.
NOW FOR MULTIPLE FILE OPERATIONS
Load and display the file named PRINTFLE.MOD for an example program that uses 4 files at once, and still writes to the monitor.
PRINTFLE.MOD
(* Chapter 8 - Program 5 *)
MODULE PrintFle;
FROM FileSystem IMPORT Lookup, Close, File, Response, ReadChar,
WriteChar;
FROM InOut IMPORT Write, WriteString, ReadString, WriteLn;
VAR NameOfFile : ARRAY[1..15] OF CHAR;
InFile : File;
OutFile : File;
CapFile : File;
PrtFile : File;
Character : CHAR;
BEGIN
REPEAT (* repeat until a good filename is found *)
WriteLn;
WriteString("Enter name of file to display,");
WriteString(" store, and print ---> ");
ReadString(NameOfFile);
Lookup(InFile,NameOfFile,FALSE);
UNTIL InFile.res = done; (* good filename found *)
Lookup(OutFile,"ANYNAME.TXT",TRUE);
Lookup(CapFile,"CAPSONLY.TXT",TRUE);
Lookup(PrtFile,"PRN",TRUE);
WriteLn;
REPEAT (* character read/display loop - quit at InFile.eof *)
ReadChar(InFile,Character);
IF NOT InFile.eof THEN
Write(Character);
WriteChar(OutFile,Character);
WriteChar(CapFile,CAP(Character));
WriteChar(PrtFile,Character);
END;
UNTIL InFile.eof; (* quit when eof is found *)
Close(InFile);
Close(OutFile);
Close(CapFile);
Close(PrtFile);
END PrintFle.
This program is very similar to the last in that it opens one file for reading, but it opens three files for writing. Each of the four files has its own identifier, a record of type "File", and each has its own filename. The three output files are firmly fixed to certain filenames, rather than ask the operator for names, and the third filename is a very special name, "PRN". This is not a file but is the access to the printer. Anything written to this file will go to your line printer, so you should turn your printer on in anticipation of running it. Your compiler may also allow a few other names such as "LPT0", "LPT1", etc, and there may be other names reserved for serial I/O such as to talk to a modem, a joystick, etc. You will need to consult your compiler documentation for a complete list of special names.
The program itself is very simple and similar to the last one. A character is read from the input file, and output to the three output files and to the monitor. In the case of "CapFile", the character is capitalized before it is output simply to indicate to you that the files are indeed different. Study it until you understand it, then compile and run it. Look at the contents of the new files to see if they are correct.
MORE NEAT THINGS WE CAN DO WITH FILES
There are many more things that you can do with the "FileSystem" module. It is possible to open a file, begin reading until you come to a selected position, and change to a write file to overwrite some of the data with new data. You can write to a file, change it to a read file, reset it to the beginning, and read the data back out. You can rename a file, or delete it. It will be up to you to study the documentation for your "FileSystem" module, and learn how to use it effectively.
YOU ARE AT A SPECIAL POINT IN MODULA-2
With the completion of this chapter, you have arrived at a very special point in your study of Modula-2. Many people arrive at this point in a language and quit studying, preferring to use the language in a somewhat limited sense rather than to go on and learn the advanced topics. If your needs are few, you can quit here also and be well assured that you can write many programs with Modula-2. In fact there will be very few times when you cannot do all that you wish to do. However, if you choose to go on to the advanced topics, you will find that some of the programming chores will be greatly simplified.
Whether you decide to go on to the advanced topics or not, it would be wise for you to stop at this point and begin using what you have learned to actually write some programs for your own personal use. Everybody has need occasionally for a program to do some sort of translation of data in a text file for example. Write programs to do some data shuffling from file to file changing the format in some way. You should be able to think up several programs that you would find useful.
Spend some time studying and running the programs in the next chapter, then modify them to suit your needs, building up a few utilities for your software collection. The best way to learn to program is to program. You have all of the tools you need to get started, so you would do well to get started. Adding some programming experience will be a big help if you decide to continue your study into the advanced features of Modula-2.