Converting to ISO Standard Modula-2
This topic assumes you are fluent with the older non standard dialect of Modula-2 defined by Niklaus Wirth.
Alternate tokens
The following are alternate source tokens for some common Modula-2 token types. These are to support machines without extended ASCII character sets.
Preferred token | Required alternate token |
[ | (! |
] | !) |
{ | (: |
} | :) |
| | ! |
^ | @ |
Embedded directives
ISO Modula-2 defines a standard way to define compiler embedded directives. The content of the directives is not defined. Stony Brook directives use the command line switch syntax.
<* ... *>
Examples:
<*/INLINE:N*>
<*/NOOPTIMIZE/INLINE:N*>
REAL and LONGREAL
ISO Modula-2 defines the REAL and LONGREAL data types. These types have not changed.
The following operators are defined for real types.
+, -, /, *, =, <>, #, <, <=, >, >=
COMPLEX and LONGCOMPLEX
ISO Modula-2 defines the COMPLEX and LONGCOMPLEX data types. These are completely new data types. They represent complex numbers.
The following operators are defined for complex types.
+, -, /, *, =, <>, #
INTEGER and CARDINAL
ISO defines INTEGER, CARDINAL. These types have not changed.
The following operators are defined.
+, -, *, /, REM, DIV, MOD, =, <>, #, <, <=, >, >=
The division operators have changed from Wirth Modula-2. ISO supports the following integer division operations.
What is the difference? MOD is defined as the modulus, not the remainder. The modulus is always positive.
This is how the ISO division operators equate to the Wirth operators
Wirth M2 DIV => ISO M2 /
Wirth M2 MOD => ISO M2 REM
op1 operator op2
op2 cannot be negative for the DIV and MOD operators. This raises an exception.
If both op1 and op2 are positive then (/ = DIV) and (REM = MOD). Thus for type CARDINAL there is no difference between the operators.
/ and REM satisfy the equation. left = right * quotient + remainder
DIV and MOD satisfy the equation. left = right * quotient + modulus.
Here is a table taken from the ISO draft. It should clear things up a bit about the differences.
op | 31 op 10 | 31 op -10 | -31 op 10 | -31 op -10 |
/ | 3 | -3 | -3 | 3 |
REM | 1 | 1 | -1 | -1 |
DIV | 3 | exception | -4 | exception |
MOD | 1 | exception | 9 | exception |
SET and PACKED SET
ISO defines two different set types. SET and PACKEDSET. ISO guidelines state that PACKEDSET implementations should map the ordinal values directly to the bits in memory. No guidelines exist for the SET type. In all Stony Brook implementations SET types are implemented as PACKEDSET types.
The SET type is unchanged. The PACKEDSET type is new type.
The following operators are defined for set types.
+, -, /, *, =, <>, #, <=, >=, IN.
The INCL and EXCL procedures are defined.
Also, for PACKEDSET the SHIFT and ROTATE functions from the SYSTEM module are defined.
Set literals, including BITSET are entered as follows.
set constructor = set type identifier, set constructed value
set type identifier = type identifier
set constructed value = left brace, [member, {comma, member}], right brace
member = interval | singleton
interval = ordinal expression, ellipsis, ordinal expression
singleton = ordinal expression
Examples:
BITSET{};
BITSET{3..6, 7};
FlagSet{Pure, UltraPure};
ARRAY and RECORD constants
ISO Modula-2 allows constants of all types. New syntax is added to allow for constants for RECORD and ARRAY types. This syntax is the same as the set constant syntax you are familiar with.
TypeName{... }
The fields of RECORDs and the elements of ARRAYs are separated with commas. Now for a few examples.
TYPE
Rect =
RECORD
x, y, x1, y1 : CARDINAL;
END;
TwoRect =
RECORD
a, b : Rect;
END;
foo =
RECORD
size : CARDINAL;
a : ARRAY [0..0] OF CHAR;
END;
aType = ARRAY [0..31] OF CHAR;
CONST
Origin = Rect{0,0,0,0};
TR1 = TwoRect{{0,0,0,0}, {0,0,0,0}};
TR2 = TwoRect{Rect{0,0,0,0}, Rect{0,0,0,0}};
cFoo = foo{0, {0C}}
The typename is optional on structures nested within structures. This is to allow fields with anonymous types as in the last example. The type name is really not needed as we know what the type is from the field of the structure. If the type name is there, the compiler will double check that they are the same type.
Constant constructors can be used in CONST declarations or they can be used in source statements. For example:
myVal := Rect{0,0,0,0};
When constant constructors are used in statements they can have variable values for any of the fields of the constant.
myVal := Rect{0,0,x1,y1};
For ARRAY constants you can use the BY keyword to repeat the array element to the left of the BY keyword a compile time constant number of times. You can repeat a variable value if the constructor is used in a statement.
myVal := aType{' ' BY SIZE(aType)}
myVal := aType{ch BY SIZE(aType)}
Multi dimensional open array parameters
PROCEDURE MatrixAdd(VAR c : ARRAY OF ARRAY OF REAL;
a, b : ARRAY OF ARRAY OF REAL);
VAR
i, j : CARDINAL;
BEGIN
IF (HIGH(c) <> HIGH(a)) OR
(HIGH(c[0]) <> HIGH(a[0])) OR
(HIGH(b) <> HIGH(a)) OR
(HIGH(b[0]) <> HIGH(a[0]))
THEN
Bug('Bozo');
END;
FOR i := 0 TO HIGH(a) DO
FOR j := 0 TO HIGH(a[0]) DO
c[i,j] := a[i,j] + b[i,j];
END;
END;
END MatrixAdd;
There is no limit on the number of dimensions. Multi dimension open arrays are not as efficient as other array types because the array element size is not known and must be computed by multiplying the HIGH bounds and the terminal element size together, at each dimension of the array. These computations are common subexpressions and the optimizer will minimize the overhead.
To access the HIGH bound of any dimension of any array, the HIGH function accepts the following syntax.
HIGH(a) <= access the first dimension of the array
HIGH(a[0]) <= access the second dimension of the array
HIGH(a[0,0]) <= access the third dimension, etc...
The values used in the index do not matter and are ignored.
Only open array parameters are accepted by the HIGH function.
Function procedure return types
ISO Modula-2 allows function procedures of any type including structured types.
Note: Structured function return values are placed on the stack, thus you should make sure you have enough stack space to store the value.
Note: Where maximum performance is necessary, it is generally better to use VAR parameters to return results. This does sacrifice some source code readability.
You use the RETURN statement as usual to return a function value.
Hint: Declare a local variable to use as your structured return value. Use a single RETURN statement of this local in the function. The compiler will replace all occurrences of the usage of the local with the structured result variable, thus eliminating the assignment of the local to the result.
Standard functions
The following relationships are true.
This | is equivalent to |
INT(...) | VAL(INTEGER, ...) |
ORD(...) | VAL(CARDINAL, ...) |
FLOAT(...) | VAL(REAL, ...) |
LFLOAT(...) | VAL(LONGREAL, ...) |
TRUNC(...) | VAL(CARDINAL, ...) |
You cannot convert a CHAR, BOOLEAN or Enumeration type to a real type and visa versa.
Standard procedures
INCL | same as Wirth M2 |
EXCL | same as Wirth M2 |
NEW | same as Wirth M2 |
DISPOSE | same as Wirth M2 |
HALT | same as Wirth M2 |
INC | same as Wirth M2 except does not accept ADDRESS type. Use SYSTEM.ADDADR |
DEC | same as Wirth M2 except does not accept ADDRESS type. Use SYSTEM.SUBADR |
Type casting, or type transfers
Type transfers the old way are no longer supported.
myVal := SomeType(...);
See the SYSTEM module for the CAST function, which provides the ISO type transfer feature. Other alternatives to use could be. ORD, INT and VAL.
CASE statements
ISO defines that when a CASE statement is evaluated and the CASE expression does not evaluate to one of the selectors and there is no ELSE for the CASE then an exception is to be generated. We generate a compiler warning in this case, to alert you of this situation before you run your program.
If you want to evaluate to the specified selectors and IGNORE all others you should place an empty ELSE statement in the CASE. This mimics the behavior of our previous compilers. Example
CASE integertype OF
-1:
|
0:
|
1:
ELSE
END;
When the CASE statement is used in a variant RECORD, all possible selector values of the CASE type must be accounted for the in the selectors if there is no ELSE statement for the CASE.
FOR loops
FOR loop control variables. eg. FOR i := (*i is the control variable*).
All of this is designed to ensure the control variable is read-only within the bounds of the FOR loop.
EXCEPTIONS
ISO has an exception mechanism defined. See Modula-2 Exceptions.
MODULE termination code
FINALLY.
Finally what?
FINALLY we have standardized termination code.
As you know Modula-2 modules can have initialization code. Well what about termination code. FINALLY provides this.
IMPLEMENTATION MODULE foo;
BEGIN
(* this is the module initialization code *)
FINALLY
(* this is the module termination code *)
END foo.
Each part is a unique block of code, just as two procedures are unique blocks of code from each other. You cannot have a FINALLY part without an initialization part. The initialization part could of course be empty. Both the initialization and FINALLY parts of a module block can have their own EXCEPT part. Only module blocks can have FINALLY parts. Procedure blocks cannot.
When is FINALLY code executed. A simple way to look at it. Think of modules as two distinct types when it comes to termination code.
Static: IMPLEMENTATION and program modules and modules that are local to these modules
Dynamic: modules declared within a procedure declaration
Static modules.
Are initialized at program startup.
Are terminated at program termination.
Dynamic modules.
Are initialized on entry of the enclosing procedure block.
Are terminated upon exit of the enclosing procedure block.
Modules are initialized in the order their initialization body occurs in the source file. They are terminated in the reverse of this order.
Here is a wacky module with all types of modules. The code does not actually accomplish anything.
IMPLEMENTATION MODULE modu;
FROM Storage IMPORT
ALLOCATE, DEALLOCATE;
VAR
globalStatic : CARDINAL;
PROCEDURE it() : CARDINAL;
MODULE dynamic;
MPORT ALLOCATE, DEALLOCATE, localVar;
BEGIN
NEW(localVar);
FINALLY
DISPOSE(localVar);
END dynamic;
VAR
localVar : POINTER TO CHAR;
BEGIN
globalStatic := globalStatic + expProc();
RETURN globalStatic;
EXCEPT
RETRY;
END it;
MODULE local1;
EXPORT expProc;
VAR
localStatic : CARDINAL;
PROCEDURE expProc() : CARDINAL;
BEGIN
localStatic := localStatic + 1;
RETURN localStatic;
END expProc;
BEGIN
localStatic := 1;
FINALLY
localStatic := MAX(CARDINAL)-1;
END local1;
BEGIN
globalStatic := 0;
FINALLY
globalStatic := MAX(CARDINAL);
END modu.
System pseudo modules
ISO defines a number of system modules. These are pseudo modules that are implemented within the compiler and do not require source code or object code. The SYSTEM module you should be familiar with.