modula-2 home

  Home  
  Tutorial  
  Win32 API  
  Reference  
  Projects  
 

 

Chapter 9 - Scalars, Subranges, and Sets


Prerequisites for this material

In order to understand the material in this chapter, you should have a fairly good understanding of the material in Part I of this tutorial.

A scalar, also called an enumerated type, is a list of values which a variable of that type may assume. Look at the file named ENTYPES.MOD for an example of some scalars. The first TYPE declaration defines "Days" as being a type which can take on any one of seven values. Since, within the VAR declaration, "Day" is assigned the type of "Days", then "Day" is a variable which can assume any one of seven different values. Moreover, "Day" can be assigned the value "mon", or "tue", etc., which makes the program easier to follow and understand. Internally, the Modula-2 system does not actually assign the value "mon" to the variable "Day", but it uses an integer representation for each of the names. This is important to understand because you must realize that you cannot print out "mon", "tue", etc., but can only use them for indexing control statements.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
MODULE Entypes;

FROM Terminal2 IMPORT WriteString, WriteLn, WriteReal;

TYPE Days      = (mon,tue,wed,thu,fri,sat,sun);
     TimeOfDay = (morning,afternoon,evening,night);

VAR Day             : Days;
    Time            : TimeOfDay;
    RegularRate     : REAL;
    EveningPremium  : REAL;
    NightPremium    : REAL;
    WeekendPremium  : REAL;
    TotalPay        : REAL;

BEGIN    (* Main program *)
   WriteString('                         Pay rate table');
   WriteLn;
   WriteLn;
   WriteString('  DAY       Morning    Afternoon');
   WriteString('    Evening      Night');
   WriteLn;

   RegularRate    := 12.00;       (* This is the normal pay rate *)
   EveningPremium := 1.10;  (* 10 percent extra for working late *)
   NightPremium   := 1.33;     (* 33 percent extra for graveyard *)
   WeekendPremium := 1.25;      (* 25 percent extra for weekends *)

   FOR Day := mon TO sun DO

      CASE Day OF
        mon : WriteString('Monday   '); |
        tue : WriteString('Tuesday  '); |
        wed : WriteString('Wednesday'); |
        thu : WriteString('Thursday '); |
        fri : WriteString('Friday   '); |
        sat : WriteString('Saturday '); |
        sun : WriteString('Sunday   ');
      END;    (* of CASE statment *)

      FOR Time := morning TO night DO
         CASE Time OF
           morning   : TotalPay := RegularRate; |
           afternoon : TotalPay := RegularRate; |
           evening   : TotalPay := RegularRate * EveningPremium; |
           night     : TotalPay := RegularRate * NightPremium;
         END;  (* of CASE statement *)

         CASE Day OF
           sat : TotalPay := TotalPay * WeekendPremium; |
           sun : TotalPay := TotalPay * WeekendPremium;
         ELSE (* Do nothing *)
         END;  (* of CASE statement *)

         WriteReal(TotalPay,12);
      END;  (* of Time loop *)
      WriteLn;
   END;  (* of FOR loop *)

END Entypes.

Note that there is an upper limit of 16 enumerated types placed on you by some implementations of Modula-2.

The second line of the type definition defines "TimeOfDay" as another "type". The variable "Time" can only be assigned one of four values since it is defined as the type "TimeOfDay". It should be clear that even though it can be "morning", it cannot be assigned "morningtime" or any other variant spelling of morning, since it is simply another identifier which must have an exact spelling to be understood by the compiler.

Several REAL variables are defined to allow us to demonstrate the use of the scalar variables. After writing a header for our output, the REAL variables are initialized to some values that are probably not real life values, but will serve to illustrate use of the scalar variable.

A big scalar variable loop

The remainder of the program is one large loop being controlled by the variable "Day" as it goes through all of its values, one at a time. Note that the loop could have gone from "tue" to "sat" or whatever portion of the range desired. It does not have to go through all of the values of "Day". Using "Day" as the CASE variable, the name of one of the days of the week is written out each time we go through the loop. Another loop controlled by "Time" is executed four times, once for each value of "Time". The two CASE statements within the inner loop are used to calculate the total pay rate for each time period and each day. The data is formatted carefully to make a nice looking table of pay rates as a function of "Time" and "Day".

Take careful notice of the fact that the scalar variables never entered into the calculations, and they were not printed out. They were only used to control the flow of the logic. It was much neater than trying to remember that "mon" is represented by a 0, "tue" is represented by a 1, etc. In fact, those numbers are used for the internal representation of the scalars, but we can relax and let the Modula-2 system worry about the internal representation of our scalars.

One other thing should be pointed out in this module. If you observe the CASE statements you will notice that the one that starts in line 32 does not have an ELSE clause. It is really not needed because every possible value that the variable "Day" can have is covered in the various branches. In the CASE statement starting in line 50, there is an ELSE clause because only two of the possible 7 values are acted on in the CASE body itself. Without the ELSE, the program would not know what to do with a value of "mon" through "fri", so the ELSE is required here, but not in the earlier one.

Lets look at some subranges

Examine the program SUBRANGE.MOD for an example of subranges. It may be expedient to define some variables that only cover a part of the full range as defined in a scalar type. Notice that "Days" is declared a scalar type as in the last program, and "Work" is declared a type with an even more restricted range. In the VAR declaration, "Day" is once again defined as the days of the week and can be assigned any of the days by the program. The variable "Workday", however, is assigned the type "Work", and can only be assigned the days "mon" through "fri". If an attempt is made to assign "Workday" the value "sat", a runtime error will be generated. A carefully written program will never attempt that, and it would be an indication that something is wrong with either the program or the data. This is one of the advantages of Modula-2 over other languages.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
MODULE Subrange;

TYPE Days = (mon,tue,wed,thu,fri,sat,sun);
     Work = [mon..fri];
     Rest = [sat..sun];

VAR  Day      : Days;  (* This is any day of the week *)
     Workday  : Work;  (* These are the working days  *)
     Weekend  : Rest;  (* The two weekend days only   *)
     Index    : [1..12];
     Alphabet : ['a'..'z'];
     Start    : ['a'..'e'];

BEGIN    (* Main program *)
(*  The following statements are commented out because they
    contain various errors that will halt compilation.

   Workday := sat;   sat is not part of Workday's subrange.
   Rest := fri;      fri is not part of Rest's subrange.
   Index := 13;      Index is only allowed to go up to 12,
   Index := -1;        and down to 1.
   Alphabet := 'A';  Alphabet, as defined, includes only the
                       lowercase alphabet.
   Start := 'h';     h is not in the first five letters.

   End of the commented out section of program.
*)

   Workday  := tue;
   Weekend  := sat;
   Day      := Workday;
   Day      := Weekend;
   Index    := 3 + 2 * 2;
   Start    := 'd';
   Alphabet := Start;
                            (* Since Alphabet is 'd'     *)
   INC(Alphabet);           (*   and now 'e'             *)
   Start := Alphabet;       (* Start will be 'e'         *)
   DEC(Start,2);            (* Start will be 'c'         *)
   Day := wed;
   INC(Day);                (* Day will now be 'thu'     *)
   INC(Day);                (* Day will now be 'fri'     *)
   Index := ORD(Day);       (* Index will be 4 (fri = 4) *)
END Subrange.

Further examination will reveal that "Index" is declared as being capable of storing only the range of INTEGERS from 1 to 12. During execution of the program, if an attempt is made to assign "Index" any value outside of that range, a runtime error will be generated. Suppose the variable "Index" was intended to refer to your employees, and you have only 12. If an attempt was made to refer to employee number 27, or employee number -8, there is clearly an error somewhere in the data and you would want to stop running the payroll to fix the problem. Modula-2 would have saved you a lot of grief.

Some statements with errors in them

In order to have a program that would compile without errors, and yet show some errors, the first part of the program is not really a part of the program since it is within a comment area. This is a trick to remember when you are debugging a program, a troublesome part can be commented out until you are ready to include it with the rest. The errors are self explanatory.

Going beyond the area commented out, there are seven assignment statements as examples of subrange variable use. Notice that the variable "Day" can always be assigned the value of either "Workday" or "Weekend", but the reverse is not true because "Day" can assume values that would be illegal for the other variables.

Three very useful functions

The last section of the example program demonstrates the use of three very important functions when using scalars. The first is the "INC" function which returns the value of the scalar following that scalar used as the argument. If the argument is the last value in the list, a runtime error is generated. The next function is the "DEC" that returns the value of the prior scalar to that used in the argument. All scalars have an internal representation starting at 0 and increasing by one until the end is reached. The third function is the "ORD" which simply returns the numerical value of the scalar variable.

In our example program, ORD(Day) is 5 if "Day" has been assigned "sat", but ORD(Weekend) is 0 if "Weekend" has been assigned "sat". As you gain experience in programming with scalars and subranges, you will realize the value of these three functions.

A few more thoughts about subranges are in order before we go on to another topic. A subrange is always defined by two predefined constants, and is always defined in an ascending order. A variable defined as a subrange type is actually a variable defined with a restricted range, and should be used as often as possible in order to prevent garbage data. There are actually very few variables ever used that cannot be restricted by some amount. The limits may give a hint at what the program is doing and can help in understanding the program operation. Subrange types can only be constructed using the simple data types.

Sets

Now for a new topic, sets. Examining the example program SETS.MOD will reveal use of some sets. A scalar type is defined first, in this case the scalar type named "Goodies". A set is then defined with the reserved words SET OF followed by a predefined scalar type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
MODULE Sets;

FROM Terminal2 IMPORT WriteString, WriteLn;

TYPE Goodies = (IceCream,WhippedCream,Banana,Nuts,Cherry,
                ChocSyrup,StrawBerries,Caramel,SodaWater,
                Salt,Pepper,Cone,Straw,Spoon,Stick);

     Treat = SET OF Goodies;

VAR  Sundae       : Treat;
     BananaSplit  : Treat;
     Soda         : Treat;
     IceCreamCone : Treat;
     NuttyBuddy   : Treat;
     Mixed        : Treat;

BEGIN
        (* Define all ingredients used in each treat *)
IceCreamCone := Treat{IceCream,Cone};
Soda         := Treat{Straw,SodaWater,IceCream,Cherry};
BananaSplit  := Treat{IceCream..Caramel};
BananaSplit  := BananaSplit + Treat{Spoon};
NuttyBuddy   := Treat{Cone,IceCream,ChocSyrup,Nuts};
Sundae       := Treat{IceCream,WhippedCream,Nuts,Cherry,ChocSyrup,
                      Spoon};

                (* Combine for a list of all ingredients used *)
Mixed := IceCreamCone + Soda + BananaSplit + NuttyBuddy +
         Sundae;

                (* Now find what ingredients are not used *)

Mixed := Treat{IceCream..Stick} - Mixed;

   IF IceCream     IN Mixed THEN
                  WriteString('Ice cream not used');
                  WriteLn; END;
   IF WhippedCream IN Mixed THEN
                  WriteString('Whipped Cream not used');
                  WriteLn; END;
   IF Banana       IN Mixed THEN
                  WriteString('Bananas not used');
                  WriteLn; END;
   IF Nuts         IN Mixed THEN
                  WriteString('Nuts not used');
                  WriteLn; END;
   IF Cherry       IN Mixed THEN
                  WriteString('Cherries not used');
                  WriteLn; END;
   IF ChocSyrup    IN Mixed THEN
                  WriteString('Chocolate Syrup not used');
                  WriteLn; END;
   IF StrawBerries IN Mixed THEN
                  WriteString('Strawberries not used');
                  WriteLn; END;
   IF Caramel      IN Mixed THEN
                  WriteString('Caramel not used');
                  WriteLn; END;
   IF SodaWater    IN Mixed THEN
                  WriteString('SodaWater not used');
                  WriteLn; END;
   IF Salt         IN Mixed THEN
                  WriteString('Salt not used');
                  WriteLn; END;
   IF Pepper       IN Mixed THEN
                  WriteString('Pepper not used');
                  WriteLn; END;
   IF Cone         IN Mixed THEN
                  WriteString('Cone not used');
                  WriteLn; END;
   IF Straw        IN Mixed THEN
                  WriteString('Straw not used');
                  WriteLn; END;
   IF Spoon        IN Mixed THEN
                  WriteString('Spoon not used');
                  WriteLn; END;
   IF Stick        IN Mixed THEN
                  WriteString('Stick not used');
                  WriteLn; END;
END Sets.

Several variables are defined as sets of "Treat", after which they can individually be assigned portions of the entire set. Consider the variable "IceCreamCone" which has been defined as a set of type "Treat". This variable is composed of as many elements of "Goodies" as we care to assign to it. In the program, we define it as being composed of "IceCream", and "Cone". The set "IceCreamCone" is therefore composed of two elements, and it has no numerical or alphabetic value as most other variables have. Continuing in the program, you will see 4 more delicious deserts defined as sets of their components. Notice that the banana split is first defined as a range of terms, then another term is added to the group. All five are combined in the set named "Mixed", then "Mixed" is subtracted from the entire set of values to form the set of ingredients that are not used in any of the deserts. Each ingredient is then checked to see if it is IN the set of unused ingredients, and printed out if it is. Running the program will reveal a list of the unused elements.

In this example, better programming practice would have dictated defining a new variable, possibly called "Remaining" for the ingredients that were unused. It was desirable to illustrate that "Mixed" could be assigned a value based on subtracting itself from the entire set, so the poor variable name was used.

This example resulted in some nonsense results but hopefully it led your thinking toward the fact that sets can be used for inventory control, possibly a parts allocation scheme, or some other useful system.


 

Next: Chapter 10. Records