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.