Chapter 6 - Arrays, Types, and Constants
Load the program named ARRAYS.MOD and we will go right to our first example of an array.
ARRAYS.MOD
(* Chapter 6 - Program 1 *)
MODULE Arrays;
FROM InOut IMPORT WriteString, WriteCard, WriteLn;
VAR Index : CARDINAL;
Automobiles : ARRAY [1..12] OF CARDINAL;
BEGIN (* main program *)
FOR Index := 1 TO 12 DO
Automobiles[Index] := Index + 10;
END;
Automobiles[7] := 54; (* example, change one value of array *)
WriteString("This is the first program with an array.");
WriteLn;
WriteLn; (* end of data initialization *)
FOR Index := 1 TO 12 DO (* display the data now *)
WriteString("Automobile number");
WriteCard(Index,3);
WriteString(" has the value of");
WriteCard(Automobiles[Index],3);
WriteLn;
END;
END Arrays.
An array is simply a list made up of several of the same type of element. Notice the VAR definition in the sample program and specifically the variable named "Automobiles". The reserved word ARRAY followed by the square brackets with a range of numbers contained within them is the proper way to define an array of, in this case, CARDINAL type variables. This defines 12 different CARDINAL type variables, each of which is capable of storing one CARDINAL number. The names of the twelve variables are given by Automobiles[1], Automobiles[2], ... Automobiles[12]. The variable name is "Automobiles" and the array subscripts are the numbers 1 through 12. The variables are true CARDINAL type variables and can be assigned values, or they can be used in calculations or in nearly anyplace in a program where it is legal to use a CARDINAL type variable. One place they cannot be used is as the index for a FOR loop since a simple variable type is required there.
WHAT GOOD ARE ARRAYS?
Notice lines 10 through 12 of the program. In these lines, each of the 12 variables is assigned a value. When "Index" is 1, then "Automobiles[1]" is assigned 11, then when "Index" is 2, "Automobiles[2]" is assigned 12, etc.
If the 12 variables were defined as 12 separate variables of whatever names we chose for them, we could not assign them values in a loop but would have to assign each one independently. In this instance, we are generating nonsense data but in a real program, this loop could be reading in a series of data from a file such as would be done with a database. The advantage of the array should be very clear, especially if we were to change the array limits to several thousand elements.
The statement in line 13 assigns a value to one of the elements at random to illustrate the method. Notice that the 7th element of the array named "Automobiles" is assigned the value of 54. The address of this data is therefore the variable name "Automobiles[7]" and the data contained in that address is 54. We have therefore assigned values to the 12 variables by a nonsensical but known scheme, and now we can use the 12 variables in any way that is legal within Modula-2.
The next loop causes the 12 values to be displayed on the monitor in a neat orderly fashion. In line 20 we display the index of the variable in question, and in line 22, we display the actual variable. Keep in mind that the index could have been INTEGER and still be used to display an array of type CARDINAL provided we defined "Index" as an integer and always used it as such. Spend enough time with this program so that you thoroughly understand it, then compile and run it.
WHAT ABOUT AN ILLEGAL SUBSCRIPT?
Modula-2 does very strong "type checking" and limit checking. If, in the above program, you tried to assign a value to "Automobiles[13]", which doesn't exist, a run time error would be generated and the program would cease. This is one of the advantages of Modula-2 over some of the older programming languages. Some compilers have the ability to enable or disable this feature.
MULTIPLY DIMENSIONED ARRAYS
Load the file named ARRAYS2.MOD for an example of a program with two-dimensional arrays.
ARRAYS2.MOD
(* Chapter 6 - Program 2 *)
MODULE Arrays2;
FROM InOut IMPORT WriteString, WriteInt, WriteLn;
VAR Index, Count : CARDINAL;
Checkerboard : ARRAY[1..8] OF ARRAY[1..8] OF CARDINAL;
Value : ARRAY[1..8],[1..8] OF CARDINAL;
BEGIN
FOR Index := 1 TO 8 DO
FOR Count := 1 TO 8 DO
Checkerboard[Index,Count] := Index + 3*Count;
Value[Index,Count] := Index + 2*Checkerboard[Index,Count];
END; (* of Count loop *)
END; (* of Index loop *)
WriteString("Output of checkerboard");
WriteLn;
FOR Index := 1 TO 8 DO
FOR Count := 1 TO 8 DO
WriteInt(Checkerboard[Index,Count],6);
END; (* of Count loop *)
WriteLn;
END; (* of Index loop *)
Value[3,5] := 77; (* change a few of the values *)
Value[3,6] := 3;
Value[Value[3,6],7] := 2; (* same as Value[3,7] := 2; *)
WriteLn;
WriteString("Output of Value matrix");
WriteLn;
FOR Count := 1 TO 8 DO
FOR Index := 1 TO 8 DO
WriteInt(Value[Count,Index],6);
END; (* of Index loop *)
WriteLn;
END; (* of Count loop *)
END Arrays2.
In this program, the VAR section contains the "Checkerboard" variable which is defined as an 8 element ARRAY in which each element is an 8 element ARRAY, therefore being an 8 by 8 square ARRAY. Each element is capable of storing one CARDINAL type variable. The variable "Value" is defined the same way except that the method of definition is slightly different. The two methods result in the same type and number of variables.
In lines 11 through 16 we have two nested FOR loops. The outer loop causes "Index" to count from 1 to 8 and for each value of "Index", the variable "Count" counts through the values 1 to 8 also. The net result is that we evaluate the assignments in lines 13 and 14 once for each possible combination of "Index" and "Count". For each combination, we assign some nonsense data to "Checkerboard" then use the result of that calculation to assign some nonsense data to the variable "Value". The purpose here is to illustrate the method of using the double subscripted variables. Next we display the entire matrix of "Checkerboard". The loops cause 8 values to be displayed on one line so that the entire matrix is displayed on only 8 lines. You should study this logic because you will find output sequences like this to be very valuable.
CHANGING A FEW OF THE VALUES
In line 27 and following we change a few of the values at random for illustrative purposes. Since "Value[3,6]" is assigned the value of 3, it can be used as one of the subscripts of the next line and in fact it is. This would be a rather sloppy programming style but it is a good illustration of what can be done. Finally using the same technique as that for "Checkerboard", the "Value" matrix is displayed.
HOW MANY SUBSCRIPTS CAN BE USED?
There is no limit as to how many subscripts can be used in Modula-2 by definition, but there is a practical limit of somewhere in the range of 3 or 4. If you use too many, you will very quickly get confused and lose control of what the program is supposed to be doing. I have never seen more than 3 subscripts used in any programming language, and very few instances of more than two. Let the problem definition be your guide.
This program was pretty straightforward, and it is time for you to compile and run it.
THE TYPE DECLARATION
Load the program named TYPES.MOD for a new topic that you will use often, especially in large programs.
TYPES.MOD
(* Chapter 6 - Program 3 *)
MODULE Types;
TYPE ArrayDef = ARRAY[12..25] OF INTEGER;
CharDef = ARRAY[0..27] OF CHAR;
RealArray = ARRAY[-17..42] OF REAL;
DogFood = ARRAY[1..6] OF BOOLEAN;
Airplane = ARRAY[1..12] OF DogFood;
Boat = ARRAY[1..12],[1..6] OF BOOLEAN;
VAR Index,Counter : CARDINAL;
Stuff : ArrayDef;
Stuff2 : ArrayDef;
Stuff3 : ARRAY[12..25] OF INTEGER;
Puppies : Airplane;
Kitties : Boat;
BEGIN
FOR Index := 1 TO 12 DO
FOR Counter := 1 TO 6 DO
Puppies[Index,Counter] := TRUE;
Kitties[Index,Counter] := NOT Puppies[Index,Counter];
END;
END;
FOR Index := 12 TO 25 DO
Stuff[Index] := Index*4 + 13;
END;
Stuff2 := Stuff; (* all 14 values copied *)
(* Kitties := Puppies; illegal, types are not compatible *)
END Types.
At the top of the listing we have a group of TYPE declarations. The first line defines "ArrayDef" as a new TYPE definition that can be used in the same way you would use INTEGER or any of the other simple type definitions. In line 12, the variable named "Stuff" is defined as a variable of type "ArrayDef", and since "ArrayDef" is a 14 element ARRAY, then "Stuff" is a 14 element array of INTEGER. It seems like we didn't save anything and in fact we added a few keystrokes to the program in order to do this. If you look at line 13 you will see that we have also defined "Stuff2" as the same type of array. We have, in fact, defined them to be "type compatible" which will be very important when we get to the program itself.
Continuing down the list of TYPE declarations, we define a TYPE with 28 characters, then a TYPE with 60 real variables, and another with 6 BOOLEAN variables. The next TYPE consists of 12 variables of TYPE "DogFood" which is itself a TYPE of 6 BOOLEANS, resulting in a TYPE consisting of 6 times 12 = 72 BOOLEANS. It is possible to continue building up TYPE definitions like this indefinitely, and as you build up applications, you will find yourself building up rather complex TYPE declarations and having a clear picture of how they go together because it is your solution to a problem. The last TYPE to be defined is that named "Boat" which has exactly the same size and characteristics as "Airplane". We will see shortly that there is a difference in these two definitions.
HOW DO WE USE ALL OF THIS?
In the VAR part of the definition part of the program, we declare some variables, two simple types and some of the types we defined above. In the program part, we assign some values to the 72 variables making up the "Puppies" matrix and the 72 variables making up the "Kitties" matrix. All of the elements of "Stuff" are then assigned nonsense values. The really interesting statement comes in line 30 where we say "Stuff2 := Stuff;". In this simple statement, all 14 values stored in "Stuff" are copied into the 14 corresponding elements of "Stuff2" without using a loop. This is possible because the two variables are TYPE compatible, they have the same TYPE definition. If you study the definitions above, you will see that "Stuff3" is of the same number and range of elements and is composed of the same type of elements, namely INTEGER, as "Stuff" but they are not TYPE compatible because were not defined with the same TYPE definition statement. In like manner, even though "Puppies" and "Kitties" are identical in type, they are not TYPE compatible.
You have the ability, through careful assignment of variables, to avoid certain kinds of programming errors. If certain variables should never be assigned to each other, a careful selection of types can prevent it. Suppose for example that you have a program working with peaches and books. You would never want to copy a matrix of peaches to one defining books, it just wouldn't make sense. Those two matrices should be defined with different type declarations even though they may be identical in size.
Compile and run this program, even though it will result in no output, then move the comment delimiter in line 31 to a position following the assignment statement and see if it does give you a TYPE incompatibility error.
DEFINING A CONSTANT
Load the program named CONSTANT.MOD for a definition of the constant as used in Modula-2.
CONSTANT.MOD
(* Chapter 6 - Program 4 *)
MODULE Constant;
CONST MaxSize = 12;
IndexStart = 49;
CheckItOut = TRUE;
TYPE BigArray = ARRAY[1..MaxSize] OF CARDINAL;
CharArray = ARRAY[1..MaxSize] OF CHAR;
VAR AirPlane : BigArray;
SeaPlane : BigArray;
Helicopter : BigArray;
Cows : CharArray;
Horses : CharArray;
Index : CARDINAL;
BEGIN (* main program *)
FOR Index := 1 TO MaxSize DO
AirPlane[Index] := Index*2;
SeaPlane[Index] := Index*3 + 12;
Helicopter[MaxSize - Index + 1] := Index + AirPlane[Index];
Horses[Index] := 'X';
Cows[Index] := "R";
END; (* of Index loop *)
END Constant.
We will finally keep the promise made when we studied LOOPDEMO in chapter 4. The new reserved word CONST is used to define a constant for use in the program. The constant "MaxSize" can be used anywhere in the program that it is desired to use the number 12, because they are in fact identical. Two additional CONST values are defined for illustrative purposes only. In the TYPE declaration section we use the constant "MaxSize" to define two types, then use them to define several variables.
In the program there is one FOR loop using the same constant "MaxSize" as the upper limit. It doesn't seem to be too useful yet, but suppose your boss came to you and said to change the program so that it handled 142 cases instead of 12. The way the program is written, you would only have to change the value of the constant, recompile, and you would be done. If you had used the number 12 everywhere, you would have to replace every 12 with the new number, 142, being careful not to change the one in line 21 which is a different kind of 12. Of course even that would not be too difficult in such a simple program, but in a program with 5000 lines of code, one simple change could take a week.
Compile and run this program.
THE OPEN ARRAY IN A PROCEDURE
Load and display the program named ARAYPASS.MOD for an example of a program with arrays being passed to a procedure.
ARAYPASS.MOD
(* Chapter 6 - Program 5 *)
MODULE ArayPass;
FROM InOut IMPORT WriteString, WriteCard, WriteLn;
TYPE OneArray = ARRAY[10..15] OF CARDINAL;
TwoArray = ARRAY[-8..210] OF CARDINAL;
VAR SizeOne : OneArray;
SizeTwo : TwoArray;
Index : INTEGER;
(* **************************************************** AddNumbers *)
PROCEDURE AddNumbers(Donkey : OneArray);
VAR CountUp, Sum : CARDINAL;
BEGIN
Sum := 0;
FOR CountUp := 10 TO 15 DO
Sum := Sum + Donkey[CountUp];
END;
WriteCard(Sum,5);
WriteLn;
END AddNumbers;
(* ************************************************* GenAddNumbers *)
PROCEDURE GenAddNumbers(Donkey : ARRAY OF CARDINAL);
VAR CountUp, Sum : CARDINAL;
BEGIN
Sum := 0;
FOR CountUp := 0 TO HIGH(Donkey) DO
Sum := Sum + Donkey[CountUp];
END;
WriteCard(Sum,5);
WriteLn;
END GenAddNumbers;
BEGIN (* *************************************** main program *)
FOR Index := 10 TO 15 DO
SizeOne[Index] := 10;
END;
FOR Index := 210 TO -8 BY -1 DO
SizeTwo[Index] := 1;
END;
WriteString("The sum of the SizeOne numbers is");
AddNumbers(SizeOne);
WriteString("Gen sum of the SizeOne numbers is");
GenAddNumbers(SizeOne);
WriteString("Gen sum of the SizeTwo numbers is");
GenAddNumbers(SizeTwo);
END ArayPass.
Notice how the procedures are formatted. The rows of asterisks make them really stand out and easy to find. You will develop your own personal style of formatting in a way that is clear and easy to follow for you.
The two procedures in this program are identical except for the way the arrays are passed to them. In the first procedure named "AddNumbers", the variable named "Donkey" is passed the array by using the same type which was used to define one of the arrays. The procedure merely adds the values of the elements of the array passed to it and writes the result out to the monitor. The way it is written, it is only capable of adding arrays that are indexed from 10 to 15. Any other array will cause a "type incompatible" error. This is simply called passing an array to the procedure.
The second procedure named "GenAddNumbers" has its input array defined as an "ARRAY OF CARDINAL" with no limits stated. This procedure can add all of the variables in any CARDINAL array regardless of the range of its subscripts. The lower subscript will always be defined as zero within this type of procedure, and the upper limit of the array can be found with the predefined procedure "HIGH". It is used as shown in the example. The first time this procedure is called in the main program, it is called with the variable "SizeOne". In the procedure, the array subscripts for "Donkey" will be 0 through 5. When the variable named "SizeTwo" is the array sent to the procedure, then "Donkey" will have the limits of 0 and 218. The second procedure definition method is therefore more general. This is called passing an "open array" to the procedure.
WHICH ONE SHOULD I USE?
There will be times when you wish to use the general case for passing a parameter, the "open array". A good example is the procedure named "WriteString" that we have been using in this tutorial. It would be a bit cumbersome if we were only allowed to pass a 10 character string to it each time. Since it can accept a string of any length, it is evidently defined with an "ARRAY OF CHAR" in its header. (We will see in a later chapter that this particular procedure is exactly that, a procedure that someone has thoughtfully programmed for you. You only need to tell the system where it can be found using the IMPORT statement.)
There will likewise be times when you will desire to use the more specific method of definition. If you are using a lot of arrays and have a specific operation that needs to be done to only a few arrays that have a common definition, you would be wise to use this method. The computer could then tell you if you tried to use the procedure on an array that it was not intended for. This is making wise use of the type checking available in the computer.
HANDLING STRINGS IN MODULA-2
Load the last file for this chapter, STRINGEX.MOD for an example of using strings in Modula-2.
STRINGEX.MOD
(* Chapter 6 - Program 6 *)
MODULE StringEx;
(* Note - The "Strings" procedures used here are not standard because
there is no standard. You may need to modify some or all
of the string procedure calls to get them to work. Consult
the documentation for your compiler and library. *)
FROM InOut IMPORT WriteString, WriteInt, WriteLn;
FROM Strings IMPORT Assign, Concat;
TYPE SevenChar = ARRAY[0..6] OF CHAR;
VAR Horse : ARRAY[0..12] OF CHAR;
Cow : ARRAY[0..5] OF CHAR;
S1 : SevenChar;
S2 : SevenChar;
Index : CARDINAL;
(* ******************************************************* Display *)
PROCEDURE Display(Stuff : ARRAY OF CHAR);
BEGIN
WriteString("Array(");
WriteString(Stuff);
WriteString(") - ");
FOR Index := 0 TO HIGH(Stuff) DO
WriteInt(ORD(Stuff[Index]),4);
END;
WriteLn;
END Display;
(* ************************************************** main program *)
BEGIN
Horse := "ABCDEFGHIJKL"; (* Copy constant to variable *)
Display(Horse);
Cow := "12345";
Assign(Cow,Horse); (* Assign variable to variable *)
Display(Horse);
S1 := "Neat";
S2 := "Things";
Concat(S1,S2,Horse); (* Concatenate variables to variable *)
Display(Horse);
S1 := S2; (* Assign variable to variable *)
Concat(Horse,Cow,Horse); (* Concatenate one variable to another *)
Display(Horse);
Concat(Cow,Horse,Horse); (* Concatenate to the beginning *)
Display(Horse);
END StringEx.
This program is the first program to deviate from the standard library as defined by Niklaus Wirth. When he defined the language, he suggested several library procedures that should be available in every Modula-2 compiler and most compiler writers have followed his suggestions quite closely. He failed to define a standard library for the string handling procedures. There is therefore some freedom for each compiler writer to define the string handling routines in any way he pleases. Most however, have followed at least a resemblance to a standard, so the procedure calls are very similar from compiler to compiler. It may be necessary for you to modify this file to suit your particular compiler. The COMPILER.DOC file on your distribution disk has comments for modifications needed for several compilers, but if yours is not listed, it will be up to you to make the required modifications to the source file.
A complete description of the libraries and what they are will be given in chapter 8.
BACK TO THE PROGRAM ON YOUR DISPLAY
The first thing that is different here is the addition of another IMPORT statement in line 10, this one importing procedures from the module named "Strings". This is the module containing the procedures which we will need in this program. A string is an array of type CHAR, each element of the array being capable of storing one character. Thus an array of CHAR type elements is capable of storing a word, a sentence, a paragraph, or even a whole chapter, depending on how big the array is. Using the example on your screen, we will learn how to manipulate text data.
One additional feature of the example program will be found on line 24. In this line the "WriteString" procedure is used in a way we have not used as yet. Instead of having an expression in quotes, it has the name of a variable within its parentheses. It will display whatever characters are stored in the string named "stuff" defined by the "ARRAY OF CHAR". So if we learn how to get a string of characters stored in a variable of type "string", we can display anything on the monitor that we can generate internal to the computer.
According to the definition of Modula-2, a string is an ARRAY OF CHAR with a 0 as a terminator. We will get more familiar with strings as we continue our study.
SOME NEW STRING PROCEDURES
The first line of the program itself, line 34, contains a string assignment. In this case, we are telling the system to copy the constant "ABCDEFGHIJKL" into the variable named "Horse". The array into which you are copying must begin at index 0 in order for this to work because all character constants are, by definition, started at zero. The variable "Horse", which only contains room for 12 characters will only receive the first 12 characters of the constant. The procedure "Display" is called with Horse as the variable and the variable is displayed between parentheses for clarity of understanding, and the 12 characters of the variable are displayed in their ASCII equivalent. When you finally run this program, compare some of the values to the ASCII table that is included with the DOS documentation that came with your computer.
In line 37 of the program, the constant "12345" is assigned to the variable "Cow". In the next line, the variable "Cow" is assigned to the variable "Horse", and the display procedure is called again. This time, the variable "Cow" is shorter than the destination, so the system has to compensate for the difference. After it transfers the 5 characters to "Horse", it will place a 0 (zero) in the next position to indicate the end of the string. The definition of the string still has 12 places, but there are only 5 places of interest, so the system will consider all places past the 5th as undefined. This time the system only prints out 5 characters in the procedure. The list of ASCII equivalents shows that the other values are still there, the output routine simply stopped when it came to the 0 in the sixth position.
Note that the Assign statement may be different for different compilers because it is not a part of the Modula-2 definition by Niclaus Wirth.
CONCATENATION
Concatenation is simply putting two strings together to make up one bigger string. Beginning in line 41, two new string variables are defined, "S1" and "S2", then the two new variables are concatenated together and assigned to our old favorite variable named "Horse". The variable "Horse" should now contain the silly expression "NeatThings", and when you run the program, you will find that it does. It also has a 0 in character position 11 now to indicate the end of the string. Line 47 concatenates "Horse" to "Cow" and stores the result in "Horse", but since the expression is now too long, part of it will get truncated and simply thrown away. Finally, "Cow" is concatenated to "Horse", and the result stored back into "Horse". This has the effect of shifting the prior contents of "Horse" right and adding the characters stored in "Cow" to the beginning. Line 45 is an example of a string assignment. This is only possible because they are of the same TYPE. The variable "Cow" has a different TYPE so can't be assigned to either of these two variables. Note that the TYPE does not have to start at zero for this to work.
Note that even though "Horse" was the only variable used in the calls to "Display", any of the other strings could have been used also. This is the topic of the fourth programming exercise below.
Compile and run the program and see if it really does do all that it should do as described above, keeping in mind that you may have to modify the file to accommodate your particular compiler.
PROGRAMMING EXERCISES
- Write a program to store the CARDINAL values 201 to 212 in an array then display them on the monitor.
- Write a program to store a 10 by 10 array containing the products of the indices, therefore a multiplication table. Display the matrix on the monitor.
- Modify the program in 2 above to include a constant so that by simply changing the constant, the size of the matrix and the range of the table will be changed.
- Modify the program named STRINGEX.MOD to include calls to "Display" with each of the string variables at the end of the program.