Chapter 8 INPUT/OUTPUT A SIMPLE OUTPUT PROGRAM ______________________________________________________________ Examine the file named SIMPLOUT.MOD for an ================ example of the simple output functions. SIMPLOUT.MOD This program is limited to writing only to ================ the monitor but we will get to files and printer output shortly. First we must 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 procedures imported as a part of a complete module 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 imported 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 to the procedure in 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 by Modula-2 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 the WriteString procedures from both library modules, we would get a compile error, so this is the only way to use both procedures with the same name from different modules. WHAT IS A LIBRARY MODULE? ______________________________________________________________ What we have been calling a library module is more properly termed a "module" and is the biggest benefit that Modula-2 enjoys over many of the other available programming languages. This is the quality that gives Modula-2 the ability to have 8-1 Chapter 8 - Input/Output 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, such as WriteString, WriteLn, etc. 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, in this tutorial, we 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 an excellent place for you to stop and spend some time reading your reference manual to gain some familiarity with your particular compiler's capability. 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 header is given along with the formal parameters, with the actual code of the procedure omitted. We will study about this in Part III of this tutorial. The part that is shown is the definition module which only gives the calling requirements which the user must know in order to use the system. The implementation module which gives the actual program code of the procedure is usually not given by compiler writers, and is not needed by users anyway. As you study the library modules, you will find procedures to handle strings and variables and procedures to do 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. 8-2 Chapter 8 - Input/Output BACK TO THE PROGRAM SIMPLOUT.MOD ______________________________________________________________ Notice that in lines 13, 17, and 22, three different ways are used to call a procedure named WriteLn, even though there are actually only two procedures (that happen to do the same thing). The observant student will realize that lines 17 and 22 are calling the same procedure, the one in the InOut module, and line 13 is calling a procedure in the Terminal module. 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 execute it. NOW FOR SOME SIMPLE INPUT PROCEDURES ______________________________________________________________ Examine the program named SIMPLIN.MOD for =============== our first example of a program with some SIMPLIN.MOD data input procedures. 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 is our first program that has the ability to read from an external device, and it will be limited to reading from 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 or groups of printable ASCII characters. After each word or group is read, it is simply displayed on the monitor for your inspection. ONE CHARACTER AT A TIME ______________________________________________________________ The group of statements in lines 26 through 29 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 8-3 Chapter 8 - Input/Output 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 the ASCII value that is returned when we hit the return key. It is therefore equal to the End-Of-Line character, which explains 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 type 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 reading 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 8-4 Chapter 8 - Input/Output happens when errors occur. After you understand what this program does, we will proceed to a study of input and output of data to or from a file. FILE INPUT/OUTPUT ______________________________________________________________ Examine the file named FILEIO.MOD for your ================ first example of file reading and writing. FILEIO.MOD 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 present example program redirects the input and output to files for an illustration of how to do it. Note that there is a much more general method of file input and output available which will be illustrated in the next few example programs. 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 period 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 period 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 is good practice to check the flag anyway. It will be apparent when we get to 8-5 Chapter 8 - Input/Output 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. When we arrive at line 25, we should have 2 files ready for use, one being an input file and the outer being an output file. HOW DO I USE THE OPENED FILES? ______________________________________________________________ Anytime you use this technique to open a file for writing, any output procedure from InOut will now be redirected to that file. Anytime you use this technique to open a file for reading, any input 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 because 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 8-6 Chapter 8 - Input/Output reason for the silly file 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 ______________________________________________________________ Examine the file named VARYFILE.MOD for an ================ example using the complete FileSystem VARYFILE.MOD module. 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 chapters 9 and 16.) It will be up to you to see that the procedure calls are in order with your compiler, and where they differ, to modify them. BACK TO THE PROGRAM NAMED VARYFILE ______________________________________________________________ This time we import several procedures from the library module named FileSystem for I/O use. 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 internal 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, the external filename, 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 name InFile.res will return the value notdone. (That refers to one variable named res which is a part of the record InFile.) 8-7 Chapter 8 - Input/Output WHY TWO FILENAMES? ______________________________________________________________ Two filenames are required in order to make Modula-2 available on a wide variety of computers. In this case we are using the name InFile for our internal filename and this name must follow all of the Modula-2 rules for naming an identifier. The external name however must follow all the rules for naming a file as dictated by the particular operating system, we only store it in the string named NameOfFile. Each compiler must tell you how to name an external file to be compatible with their particular implementation. The procedure named Lookup associates the internal and external filenames with each other. Note that the variable named 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. Of course, each procedure you wish to use must be imported properly. 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 execute it to observe its operation. NOW FOR MULTIPLE FILE OPERATIONS ______________________________________________________________ Examine the file named PRINTFLE.MOD for an ================ example program that uses 4 files at once, PRINTFLE.MOD and still writes to the monitor. 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 internal filename identifier, a record of type File, and each has its own external filename. The three output files are firmly fixed to certain filenames, rather than ask the operator for names, and the third filename, listed in line 26, 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 8-8 Chapter 8 - Input/Output 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 provided that your programs are not too large. 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 8-9 Chapter 8 - Input/Output 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. PROGRAMMING EXERCISES ______________________________________________________________ 1. Write a program that reads any Modula-2 source file and lists it on the monitor with the number of characters in each line. List the number of lines in the program also. 2. Write a program that reads any Modula-2 source file and counts the number of times the word END appears in the source file. 8-10