home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 10 Tools
/
10-Tools.zip
/
mod201j.zip
/
modula2.exe
/
os2doc
/
mod-us.inf
(
.txt
)
< prev
next >
Wrap
OS/2 Help File
|
1996-03-29
|
177KB
|
5,825 lines
ΓòÉΓòÉΓòÉ 1. Release notes ΓòÉΓòÉΓòÉ
Welcome to beta version 2.01J. This compiler is a beta-ware. It is not
shareware nor is it freeware!
Please read the following sections before using this compiler:
- Licence agreement
- How to contact the author
- To-do-list
ΓòÉΓòÉΓòÉ 1.1. Licence agreement ΓòÉΓòÉΓòÉ
IF YOU DOWNLOAD OR USE THIS PROGRAM YOU AGREE TO THESE TERMS.
The author grants you a licence to use the program "Canterbury-MODULA-2 BETA
2.01J for OS/2" as a Beta tester. This package contains programs, source files,
and .INF documents. They are called "the programs" in the following sentences.
The programs are demo- or beta-test versions. Hence their functionality is
still limited. The GA version may be different from this demo version.
The programs are copyrighted. You obtain no rights on the programs other than
those granted you under this licence. If you do not agree to the terms listed
below you are not allowed to use the programs.
Under this licence, you may:
1. use the programs on one or more machines at a time
2. make copies of the programs for use or backup purposes.
3. make copies of the original file you downloaded and distribute it,
provided that you transfer a copy of this licence to the other party.
The other party agrees to these terms by its first use of the program.
You must reproduce the original copyright notice on each copy or partial
copy of the programs.
You may NOT:
1. sublicence, rent, lease, or assign the programs
2. reverse assemble, reverse compile, or otherwise translate the
programs.
3. use the programs for military purposes.
Under no circumstances is the author liable for any of the following:
1. third-party claims against you for losses or damages
2. loss of, or damage to, your records or data or
3. any economic damages even if the author is informed of their
possibility.
Some jurisdictions do not allow these limitations or exclusions, so they
may not apply to you.
The author does not warrant uninterrupted or error free operation of the
programs.
IF YOU DOWNLOAD OR USE THIS PROGRAM YOU AGREE TO THESE TERMS.
THERE ARE NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
You may terminate this licence at any time. The author may terminate this
licence if you fail to comply with any of its terms or if the GA version of
these programs is available. In either event, you must destroy all your
copies of the programs.
You may not sell, transfer, assign, or subcontract any of your rights or
obligations under this licence. Any attempt to do so is void.
This licence is governed by the laws of the country in which you acquired
the programs.
ΓòÉΓòÉΓòÉ 1.2. How to contact the author ΓòÉΓòÉΓòÉ
Please feel free to send any bug reports, comments, or proposals to the author
Juergen Neuhoff. You can do this via CompuServe e-mail (the prefered way):
CompuServe 76721,303
This compiler will be marketed soon by:
Mill Hill & Canterbury Group, Ltd.
P.O. BOX 1277
Peralta NM 87042
U.S.A.
CompuServe 73261,1622
If you downloaded this software from a CompuServe forum library, you can also
send public messages to that forum. This way other beta users may benefit from
your information, too.
ΓòÉΓòÉΓòÉ 1.3. To-do-list ΓòÉΓòÉΓòÉ
This compiler is still an early beta release. There are still many things to be
done. Future versions may include:
- Symbolic debugger support for IPMD or SD386:
This compiler genererates full HLL3-compatible debug-infos.
These should be supported by both IPMD and SD386, according
to IBM's statements. Unfortunately, both IBM debuggers are
still too buggy. This compiler version supports source-line
level debugging as well as some limited symbol support for SD386.
- Bug fixes
- "Direct-to-SOM" feature
- Modula-2 to IDL generation
- Class libraries
- IDE and/or closer integration into WPS
An IDE is currently being developed.
- Modula-style resource editors
A visual interface designer will be adapted to M2.
- COMPLEX type
- Range checking during runtime
- 32-to-16 bit thunking to foreign libraries
with different names/linkage
- Assembler-listing
ΓòÉΓòÉΓòÉ 2. Introduction ΓòÉΓòÉΓòÉ
Modula-2 is a general-purpose programming language that evolved from Pascal. As
the name 'Modula' suggests, the module concept is an important feature of this
programming language. From Pascal it inherited a revised and systematic syntax.
This includes in particular the data structures such as arrays, records,
variant records, sets and pointers. Structured statements include the familiar
if, case, repeat, while, for, and with statements.
This compiler is an OS/2 2.x or 3.0 implementation of Modula-2. It is based
upon the descriptions of the book 'Programming in Modula-2' by N.Wirth, fourth
edition. This documentation is not intended as a programmer's tutorial. It is
intentionally kept concise because its function is to serve as a reference for
programmers who wish to write efficient software running under the OS/2 2.x or
3.0 operating system.
ΓòÉΓòÉΓòÉ 2.1. A first example ΓòÉΓòÉΓòÉ
This compiler is an OS/2 text-mode application. It is started from a command
prompt of an OS/2 full screen or window screen. It can neither run under DOS
nor as a Presentation Manager application. The compiler is called by typing in
the command 'MOD'.
Let us take as an example a simple program doing nothing else than simply
displaying a 'Hello World' message on the screen. The OS/2 2.x or 3.0
System-Editor can be used to create such a Modula-2 source file. The contents
of the source file might look like this:
MODULE HELLO;
IMPORT InOut;
BEGIN
InOut.WriteString( "Hello World" );
InOut.WriteLn();
END HELLO.
Type in the following commands to create, compile, link and run this sample
program from a OS/2 fullscreen or windowed screen:
E HELLO.MOD <enter>
MOD HELLO -o -m -p <enter>
LINK386 @HELLO.RSP <enter>
HELLO <enter>
The output from the sample program looks like this:
Hello World
The necessary linker response file HELLO.RSP is created automatically by the
Modula-2 compiler because of the '-m' switch. The compiler creates object files
in a format suitable for the OS/2 2.x or 3.0 linker program LINK386.EXE, which
produces the final HELLO.EXE executable file. The command switch '-p' causes a
verbose screen output of all the sources including possible error prompts. The
optimization switch '-o' tells the compiler to produce an all-optimized code.
ΓòÉΓòÉΓòÉ 2.2. Features of compiler version 2.01 ΓòÉΓòÉΓòÉ
This compiler provides the following major features:
- target operating system:
- 32-bit OS/2 2.x or 3.0
- 16-bit OS/2 1.x
- 16-bit MS-Windows 3.x
- 16-bit DOS
Note: Modula-libraries are currently only available
for 32-bit OS/2 2.x or 3.0. Future releases will support the other
target systems.
- compiler output:
- 16 or 32-bit object files suitable for
LINK.EXE or LINK386.EXE.
- linker response file(s)
- linker definition file(s)
- integrated Make or Build facility:
- 'Make' recompiles dependent updated modules.
- 'Build' recompiles all dependent modules.
- Baisc standard types and functions with different sizes:
- 1-, 2- and 4-bytes cardinal and integer types.
- 4- and 8-bytes real types
- Set types with up to 256 elements:
- e.g. TYPE CharSet = SET OF CHAR;
- Powerful language extensions:
- Allow underscore '_' for identifier names
(as in OS/2 APIs)
- Bitwise operators 'AND' 'OR' 'XOR' 'SHL' 'SHR' 'NOT'
- New logical operator 'XOR'
- Object orientation similar to Oberon language
- System Object Model support under OS/2
- Initialized variables expressed as typed constants
- Multidimensional open arrays
- Symbolic INLINE assembler
- NEAR or FAR keywords for INTEL 80x86 segmentation
- Extended imports
- Dynamic link library development for OS/2 2.x or 3.0
- Definition modules for external .OBJ .DLL .LIB files
- OS/2 2.x or 3.0 Control Program interface
- OS/2 2.x or 3.0 Graphics Program interface
- OS/2 2.x or 3.0 Presentation Manager interface
- OS/2 2.x or 3.0 Workplace Shell interface
- OS/2 2.x or 3.0 Keyboard interface
- OS/2 2.x or 3.0 Video interface
- OS/2 2.x or 3.0 Mouse interface
ΓòÉΓòÉΓòÉ 3. Syntax ΓòÉΓòÉΓòÉ
A language is an infinite set of sentences, namely the sentences well formed
according to its syntax.In Modula-2, these sentences are called compilation
units. Each compilation unit is a program or definition module stored in a
source file. Each unit is a finite sequence of symbols from a finite
vocabulary. The vocabulary of Modula-2 consists of identifiers, numbers,
strings, operators, delimiters and comments. They are called lexical symbols
and are composed of character sequences.
To describe the syntax, a formalism similar to that of Backus-Naur is used.
Angular brackets '[' and ']' denote an optional syntactic entity, and braces
'{' and '}' denote its repetition (zero or more times). A '|' character means a
choice between two entities. Syntactic entities are formed from the Modula-2
set of lexical symbols. They are called non-terminal symbols and are denoted by
English words expressing their intuitive meaning. Symbols of the language
vocabulary, the terminal symbols, are denoted by strings enclosed in quote
marks or words written in capital letters, so-called reserved words.
Non-terminals are formed from terminals and non-terminals using productions
with fixed syntactic rules. Productions are denoted by the '=' sign.
Example 1:
Decl = VAR { VarDecl ";" }
A declaration is started by the reserved keyword VAR followed by an possibly
empty list of variable declarations seperated by semicolons.
Example 2:
Stmt = RETURN [ Expr ] | EXIT
A statement can be a RETURN statement possibly followed by an expression. Or it
can be an EXIT.
ΓòÉΓòÉΓòÉ 4. Vocabulary and representation ΓòÉΓòÉΓòÉ
The representation of symbols in term of characters is defined using the ASCII
set. Symbols are identifiers, numbers, strings, character constants, operators
and delimiters, and comments. Blanks and line breaks must not occur within
symbols ( except in comments, and, in the case of blanks, in strings ). They
are ignored unless they are needed to separate two consecutive symbols. Capital
and lower letters are considered as being distinct.
ΓòÉΓòÉΓòÉ 4.1. Identifiers ΓòÉΓòÉΓòÉ
ΓûÉ Ident = FirstLetter { "_" | Letter | Digit }
ΓûÉ FirstLetter = "_" | Letter
Identifiers are sequences of letters and digits. The first character however
must not be a digit. If the language-extensions are enabled, then the
underscore character '_' is also allowed for identifiers. This is needed for
identifiers from OS/2 2.x or 3.0 APIs. Otherwise the programmer should refrain
from freely using underscores for his own identifiers.
Examples include :
x scan Modula GetSymbol
Examples with underscore '_' enabled :
FILE_READONLY OPEN_SHARE_DENYWRITE vector_graphics
ΓòÉΓòÉΓòÉ 4.2. Numbers ΓòÉΓòÉΓòÉ
ΓûÉ Number = Integer | Real
ΓûÉ Integer = Digit { Digit } | OctalDigit { OctalDigit } "B" |
ΓûÉ Digit { HexDigit } "H"
ΓûÉ Real = Digit { Digit } "." { Digit } [ ScaleFactor ]
ΓûÉ ScaleFactor = "E" [ "+" | "-" ] Digit { Digit }
ΓûÉ HexDigit = Digit | "A" | "B" | "C" | "D" | "E" | "F"
ΓûÉ Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
ΓûÉ OctalDigit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7"
Numbers are unsigned integers or real numbers. Integers are sequences of
digits. If the number is followed by the letter 'B', it is taken as an octal
number; if it is followed by the letter 'H', it is taken as a hexadecimal
number.
The default type for an integer number i is assigned according to the following
value ranges:
SHORTINT 0 ............ 127
INTEGER 128 ......... 32 767
LONGINT 32 768 .. 2 147 483 647
SHORTCARD 0 ............ 255
CARDINAL 255 ......... 65 535
LONGCARD 65 536 .. 4 294 967 296
This means, that a given value can have 1 or 2 types. The value 128 for
instance is both of type INTEGER and SHORTCARD. This looks more complicated
than it actually is, since SHORTINT and INTEGER and LONGINT are compatible with
each other, and SHORTCARD and CARDINAL and LONGCARD are compatible with each
other as well.
A real number is a sequence of decimal digits with a decimal point. It is
optinally followed by a decimal scale factor. The scale factor is introduced by
the letter 'E' and an optional '+' or '-' sign, to be followed by the digits of
the scale factor itself. The introducing letter 'E' is understood to be a 'ten
to the power of'. A real number is internally stored as a LONGREAL. The decimal
range and precision are guarantied for up to 15 significand digits and a power
of 10 range from -307 to +308. If there only up to 6 siginificand digits and a
power of 10 range from -37 to 38 than a real value can also be treated as a
SHORTREAL.
Integer number examples:
1980 3764B 7BCH 0FFFFFFFFH
Real number examples:
123456789012.345E-307 12345. 12.3
ΓòÉΓòÉΓòÉ 4.3. Strings ΓòÉΓòÉΓòÉ
ΓûÉ String = "'" { Character } "'" | """ { Character } """
Strings are sequences of characters enclosed in quote marks. Both double quotes
and single quotes (apostrophes) may be used as quote marks. However, the
opening and closing marks must match each other, and they cannot appear within
the string. A string must not extend over the end of a line. This compiler
always appends a terminating zero to the internal storage of a string, though
this not explicitly stated in Wirth's standard. A string s can be assigned to
an array a of characters if the length of s is strictly less to the number of
elements of a. This ensures that the internal terminating zero can always be
appended to the resulting a. The purpose is to simplify possible tests for the
end of a string.
String examples:
"MODULA" "Don't worry" 'codeword "Barbarossa"'
ΓòÉΓòÉΓòÉ 4.4. Characters ΓòÉΓòÉΓòÉ
ΓûÉ CharConst = "'" Character "'" |
ΓûÉ OctalDigit { OctalDigit } "C" |
ΓûÉ Digit { HexDigit } "X"
A character constant represents an ASCII character. They are either denoted by
a single character enclosed in single quote marks or by the ordinal number of
the character in octal notation followed by the letter 'C'. This compiler, with
language-extensions enabled, also allows a hexadecimal notation followed by the
letter 'X' for the ordinal value of the character. The actual appearance and
meaning of the characters depends on the active OS/2 2.x or 3.0 code page and
font.
ΓòÉΓòÉΓòÉ 4.5. Operators and delimiters ΓòÉΓòÉΓòÉ
Operators and delimiters are the special characters, character-pairs and
reserved words. The reserved words consist exclusively of capital letters and
must not be used in the role of identifiers. The symbols '#' and '<>' are
synonyms, so are '&' and 'AND', as well as '~' and 'NOT'.
ΓòÉΓòÉΓòÉ 4.5.1. Special characters ΓòÉΓòÉΓòÉ
+ - * / & | ~
. , ; ^ .. : :=
= # < > <> <= >=
( ) [ ] { }
" '
ΓòÉΓòÉΓòÉ 4.5.2. Reserved words ΓòÉΓòÉΓòÉ
The following identifiers are treated as reserved words. They cannot be taken
for user names.
AND DO IF OF SET
ARRAY ELSE IMPLEMENTATION OR THEN
BEGIN ELSIF IMPORT POINTER TO
BY END IN PROCEDURE TYPE
CASE EXIT LOOP QUALIFIED UNTIL
CONST EXPORT MOD RECORD VAR
DEFINITION FOR MODULE REPEAT WHILE
DIV FROM NOT RETURN WITH
This compiler also regards few more identifiers as extended reserved words for
an extended Modula-2 langauge.
ΓòÉΓòÉΓòÉ 4.5.3. Extended reserved words ΓòÉΓòÉΓòÉ
If the language-extensions are enabled, then the following identifiers are
treated as reserved words as well. They cannot be taken then for user
identifiers.
FAR
IS
NEAR
SHL
SHR
XOR
ΓòÉΓòÉΓòÉ 4.6. Comments ΓòÉΓòÉΓòÉ
Comments may be inserted between any two symbols in a program. A comment is any
sequence of characters enclosed in the brackets "(*" and "*)". Comments may be
nested, and they do not affect the meaning of a program. Comments may also
contain compiler-directives.
Comments are skipped by the compiler and serve as additional information to the
human reader. The INLINE-assembler also treats a ';' as the start of a comment
up to the end of a line.
ΓòÉΓòÉΓòÉ 5. Scope rules for declarations ΓòÉΓòÉΓòÉ
Every identifier occuring in a program must be introduced by a declaration,
unless it is a standard-identifier. The latter is considered to be predeclared.
This compiler keeps the predeclarations in a pseudo definition module called
SYSTEM. These standard identifiers are valid in all parts of a program. They
are somewhat pervasive.
Declarations also serve to specify certain permanent properties of an item,
such as whether it is a constant, a type, a variable, a procedure or a module.
The identifier is then used to refer to the associated item. Such a reference
is possible in those parts of a program only which are within the so-called
scope of the declaration. No identifier may denote more than one item within a
given scope. The scope extends textually from the point of the declaration to
the end of the block of the procedure or module to which the declaration
belongs and hence to which the item is local. The scope rule has the following
amendments:
(1) If a type P is defined as a pointer-type, e.g. P = POINTER TO T,
then the identifier T can be declared textually following the
declaration of P, but it must lie within the same scope.
(2) Field identifiers of a record-type are valid only in field
designators and in with-statements refering to a variable of that
record type.
(3) If an identifier defined in module M1 is exported, the scope
expands to the end of the block which contains M1. If M1 is a
compilation-unit it extends to all those units which import M1.
ΓòÉΓòÉΓòÉ 5.1. Qualification of identifiers ΓòÉΓòÉΓòÉ
ΓûÉ Qualident = { Qualifier "." } Ident
ΓûÉ Qualifier = Ident
An identifier may be qualified. In this case it is prefixed by another
identifier which designates the module of a local module-declaration or of a
global compilation-unit in which the qualified identifier is defined and
exported. The prefix and the identifier are separated by a period.
Standard-identifiers are all rooted in the SYSTEM module for this compiler
implementation and are somewhat pervasive, hence there is no need for
qualifying them.
ΓòÉΓòÉΓòÉ 5.2. Standard identifiers ΓòÉΓòÉΓòÉ
Some of the standard identifiers from this compiler might not be present with
other implementations. They are marked with an asterisk. They include NEW,
DISPOSE, LONG... and SHORT... and are treated like standard identifiers in this
compiler because of different basic type sizes and dynamic memory support.
ABS FLOAT LONGREAL *SHORTCARD
BITSET HALT *LONGTRUNC *SHORTFLOAT
BOOLEAN HIGH MAX *SHORTINT
CAP INC MIN *SHORTNIL
CARDINAL INCL *NEW *SHORTREAL
CHAR INTEGER NIL *SHORTTRUNC
CHR *LONG ODD SIZE
DEC *LONGCARD ORD TRUE
*DISPOSE *LONGFLOAT PROC TRUNC
EXCL LONGINT REAL VAL
FALSE *LONGNIL *SHORT
ΓòÉΓòÉΓòÉ 6. Declarations ΓòÉΓòÉΓòÉ
ΓûÉ Decl = CONST { ConstDecl ";" } | TYPE { TypeDecl ";" } |
ΓûÉ VAR { VarDecl ";" } | ProcedureDecl | ModuleDecl
Every item occuring in a program such as types, variables or procedures must be
introduced by a declaration. A declaration serves to specify certain permanent
properties of an object. The following kinds of objects can be declared for
user identifiers:
- constant
- types
- variables
- procedures
- modules
ΓòÉΓòÉΓòÉ 6.1. Constant declarations ΓòÉΓòÉΓòÉ
ΓûÉ ConstDecl = ConstDef | ConstVarDecl "=" TypedConst
ΓûÉ ConstVarDecl = Ident ":" FormalType
ΓûÉ ConstDef = Ident "=" ConstExpr
ΓûÉ ConstExpr = Expr
A constant declaration associates an identifier with a constant value. A
constant expression is an expression which can be evaluated by a mere textual
scan without actually executing the program. Its operands are constants.
Examples of constant declarations:
CONST
N = 100;
limit = 2*N - 1;
all = {0..WordSize-1};
bound = MAX(INTEGER) - N;
This compiler also supports the declaration of initialized variables using
typed-constants. This feature is only available if the language-extensions are
enabled.
ΓòÉΓòÉΓòÉ 6.2. Type declarations ΓòÉΓòÉΓòÉ
ΓûÉ TypeDecl = Ident = Type
ΓûÉ Type = SimpleType | ArrayType | RecordType | SetType |
ΓûÉ PointerType | ProcedureType
ΓûÉ SimpleType = Qualident | Enumeration | SubrangeType
A data type determines the set of values that variables of that type may
assume, and the operators that are applicable. A type declaration is used to
associate an identifier with the type. Such association may be with
unstructured types, such as:
- basic types
- enumerations
- subranges
- pointer-types
- procedure-types
Or the association may be with structured types, in which case it defines the
structures of variables of this type and, by implication, the operators that
are applicable to the components. There are three different structures, namely:
- records
- arrays
- sets
Examples of type declarations:
TYPE
Color = (red, green, blue);
Index = [1..80];
Card = ARRAY Index OF CHAR;
Node = RECORD
key : CARDINAL;
left,right : TreePtr;
END;
Tint = SET OF Color;
TrrePtr = POINTER TO Node;
Function = PROCEDURE( CARDINAL ) : CARDINAL;
ΓòÉΓòÉΓòÉ 6.2.1. Basic types ΓòÉΓòÉΓòÉ
The following basic types are denoted by predeclared standard identifiers. The
values of a given basic type and the bytes occupied in memory are as follows:
SHORTINT -128 ... 127 1 byte
INTEGER -32 768 ... 32 767 2 bytes
LONGINT -2 147 483 648 ... 2 147 483 647 4 bytes
SHORTCARD 0 ... 255 1 byte
CARDINAL 0 ... 65 535 2 bytes
LONGCARD 0 ... 4 294 967 296 4 bytes
BOOLEAN FALSE and TRUE 1 byte
CHAR the characters of the extended 1 byte
ASCII set (0X ... 0FFX).
SHORTREAL [+|-] 1.17E-38 ... 3.37E+38 4 bytes
LONGREAL [+|-] 2.23E-308 ... 1.67E+308 8 bytes
REAL same as LONGREAL by default, or if 8 bytes
compiler switch '-R8' or
compiler directive (*$R8*).
same as SHORTREAL if 4 bytes
compiler switch '-R4' or
compiler directive (*$R4*).
ΓòÉΓòÉΓòÉ 6.2.2. Enumerations ΓòÉΓòÉΓòÉ
ΓûÉ Enumeration = "(" IdentList ")"
ΓûÉ IdentList = Ident { "," Ident }
An enumeration is a list of identifiers that denote the values which constitute
a data type. These identifiers are used as constants in the program. They, and
no other values, belong to this type. The values are ordered, and the ordering
relation is defined by their sequence in the enumeration. The ordinal number of
the first value is 0. An enumeration with up to 256 values occupies only 1 byte
in memory, else 2 bytes.
Examples of enumerations:
(red, green, blue)
(club, diamond, heart, spade)
(Monday, Tueday, Wednesday, Thursday, Friday, Saturday, Sunday)
ΓòÉΓòÉΓòÉ 6.2.3. Subrange types ΓòÉΓòÉΓòÉ
ΓûÉ SubrangeType = [ BaseType ] "[" ConstExpr ".." ConstExpr "]"
ΓûÉ BaseType = Qualident
A type T may be defined as a subrange of another, basic or enumeration type T1
(except reals) by specification of the least and the highest value in the
subrange. The first constant specifies the lower bound, and must not be greater
than the upper bound. The type T1 of the bounds is called the base type of T,
and all operators applicable to operands of type T1 are also applicable to
operands of type T. However, a value to be assigned to a variable of a subrange
type must lie within the specified interval. The base type can be specified by
an identifier (possibly qualified in this compiler) preceding the bounds. If it
is omitted, and if the lower bound is a non-negative number, the base type is
taken to be a cardinal (in this compiler LONGCARD for 32-bit models like OS/2
2.x or 3.0, or CARDINAL for 16-bit models); if it is a negative number, it is
an integer (in this compiler LONGINT for 32-bit models like OS/2 2.x or 3.0, or
INTEGER for 16-bit models).
A type T1 is said to be compatible with a type T0, if it is declared either as
T1 = T0 or as a subrange of T0, or if T0 is a subrange of T1, or if T0 and T1
are both subranges of the same base type.
Examples of subrange types:
[ 0 .. N-1 ]
Color [red .. green]
[ 'A' .. 'Z' ]
[ Monday .. Friday ]
ΓòÉΓòÉΓòÉ 6.2.4. Array types ΓòÉΓòÉΓòÉ
ΓûÉ ArrayType = ARRAY IndexType { "," IndexType } OF Type
ΓûÉ IndexType = SimpleType
An array is a structure consisting of a fixed number of elements that are all
of the same type, called the component type. The elements of the array are
designated by indices, values belonging to the index type. The array type
declaration specifies the component type as well as the index type. The latter
must be an enumeration, a subrange type, or one of the basic types BOOLEAN or
CHAR.
A declaration of the form
ARRAY T1, T2, ... , Tn OF T
with index types T1 ... Tn must be understood as an abbreviation for the
declaration
ARRAY T1 OF
ARRAY T2 OF
...
ARRAY Tn OF T
Examples of array types:
ARRAY [0..N-1] OF CARDINAL
ARRAY [1..10], [1..20] OF [0..99]
ARRAY [-10..+10] OF BOOLEAN
ARRAY WeedDay OF Color
ARRAY Color OF WeekDay
ΓòÉΓòÉΓòÉ 6.2.5. Record types ΓòÉΓòÉΓòÉ
ΓûÉ RecordType = RECORD [ "(" RecordBase ")" ] FieldListSeq END
ΓûÉ RecordBase = Qualident
ΓûÉ FieldListSeq = FieldList { ";" FieldList }
ΓûÉ FieldList = [ IdentList ":" Type | Variants ]
ΓûÉ Variants = CASE [ Ident ":" ] Qualident
ΓûÉ OF VariantList ElseVariant END
ΓûÉ VariantList = Variant { "|" Variant }
ΓûÉ ElseVariant = ELSE FieldListSeq
ΓûÉ Variant = CaseLabelList ":" FieldListSeq
ΓûÉ CaseLabelList = CaseLabels { "," CaseLabels }
ΓûÉ CaseLabels = ConstExpr [ ".." ConstExpr ]
A record type is a structure consisting of a fixed number of elements of
possibly different types. The record type declaration specifies for each
element, called a field or member, its type and an identifier that denotes the
field. The scope of these fields is the record definition itself, but they are
also visible within field designators refering to elements of record variables,
and within with statements.
A record type may have several variant sections each of them occupying the same
memory. A value of a tag type indicates which variant is assumed by the
section. There may also be a tag field, which, if declared with an tag
identifier, stores the tag value during runtime. Individual variant structures
are identified by case labels. These labels are constants of the tag type,
possibly indicated by the tag field.
This compiler also provides object oriented features in connection with record
types, similar to the new OBERON language. For further details, refer to the
documentation sections:
- object oriented features
- extending record types
- dynamic type tests for records
- type guard selectors
- regional type guards
- Type bound procedures
- System Object Model
These object oriented features are available only if the language-extensions is
enabled.
ΓòÉΓòÉΓòÉ 6.2.6. Set types ΓòÉΓòÉΓòÉ
ΓûÉ SetType = SET OF SimpleType
ΓûÉ SimpleType = Qualident | Enumeration | SubrangeType
A set type is defined as a SET OF T. It comprises all sets of values of its
base type T. The base type must be a a subrange of the integers between 0 and
N-1, or a subrange of an enumeration type with at most N values, where N is the
maximum number of elements. This compiler accepts sets with up to 256 elements,
e.g. the common SET OF CHAR is allowed.
The predeclared type BITSET = SET OF [0..15] is valid in all parts of the
program, because it is one of the standard-identifiers and as such is
automatically exported from the SYSTEM-module.
ΓòÉΓòÉΓòÉ 6.2.7. Pointer types ΓòÉΓòÉΓòÉ
ΓûÉ PointerType = [ NEAR | FAR ] POINTER TO Type
Variables of pointer type P assume as values pointers to variables of some type
T. The pointer type P is said to be bound to T, and T is the pointer base type
of P. If p is a variable of type P = POINTER TO T then its value is generated
by a call to an allocation procedure in a storage management module. The
standard procedure call NEW(p) is translated into an ALLOCATE(p,SIZE(T)) and is
the prefered way to generate a pointer value for allocating dynamic memory. The
standard procedure call DISPOSE(p) is translated into a DEALLOCATE(p) and is
the prefered way to release a pointer value by freeing up allocated dynamic
memory.
If the language-extensions are enabled the default size of a pointer type
declaration can be overwritten by the NEAR or FAR keywords. This is useful for
accessing far memory segments outside the near DGROUP memory segment. The
default pointer size (NEAR or FAR) depends on the memory-model and is NEAR for
the 32-bit OS/2 2.x or 3.0 flat memory model.
Besides above mentioned pointer values, a pointer variable may assume the value
NIL, which can be thought as pointing to no variable at all. If the
language-extensions are enabled, then LONGNIL or SHORTNIL can be used instead
of NIL for pointers with explicit FAR or NEAR specifications.
ΓòÉΓòÉΓòÉ 6.2.8. Procedure types ΓòÉΓòÉΓòÉ
ΓûÉ ProcedureType = [ NEAR | FAR ] PROCEDURE [ FormalTypeList ]
ΓûÉ FormalTypeList = "(" FTSectionList ")" [ ":" Qualident ]
ΓûÉ FTSectionList = [ FTSection { "," FTSection } ]
ΓûÉ FTSection = [ [ NEAR | FAR ] VAR ] FormalType
ΓûÉ FormalType = { ARRAY OF } Qualident
Variables of a procedure type T may assume as their value a pointer to a
procedure P. The types of the formal parameters of P must be the same as those
indicated in the formal type list list of T. The same holds for the result type
in the case of a function procedure. P must not be declared local to another
procedure, and neither can it be a generic standard procedure.
If the language-extensions are enabled, the NEAR or FAR attributes can be used.
FAR indicates that the procedure pointer may assume procedures declared in
another code segment, or that a formal parameter is passed as a reference to a
variable of another data segment, e.g. outside DGROUP. This is useful for
memory-models with many code or data segments.
ΓòÉΓòÉΓòÉ 6.3. Variable declarations ΓòÉΓòÉΓòÉ
ΓûÉ VarDecl = VarIdent { "," VarIdent } ":" Type
ΓûÉ VarIdent = Ident [ FarPointerConst ]
ΓûÉ FarPointerConst = "[" ConstExpr ":" ConstExpr "]"
Variable declarations serve to introduce variables and associate them with
identifiers that must be unique within the given scope. They also serve to
associate fixed data-types with the variables. Variables whose identifiers
appear in the same list are all of the same type.
Examples of variable declarations (refer to the examples in the section about
type-declarations):
i,j,k : INTEGER;
x,y : REAL;
p,q : BOOLEAN;
s : BITSET;
FUNC : PROCEDURE( VAR INTEGER, LONGINT ) : BOOLEAN;
f : FUNC;
a : ARRAY Index OF CARDINAL;
w : ARRAY [0..7] OF RECORD
ch : CHAR;
count : CARDINAL;
END;
t : TreePtr;
If the language-extensions are enabled then variables of a a pointer-type to
T0 and VAR-parameters of a record-type T0 may assume values whose type T1 is an
record-extension of their declared type T0.
ΓòÉΓòÉΓòÉ 6.4. Procedure declarations ΓòÉΓòÉΓòÉ
ΓûÉ ProcedureDecl = ProcedureHeading ";" Block Ident |
ΓûÉ ProcedureHeading ";" FORWARD
ΓûÉ ProcedureHeading = [ NEAR | FAR ] PROCEDURE
ΓûÉ [ Receiver ] Ident [ FormalParameters ]
ΓûÉ Receiver = "(" [ [ NEAR | FAR ] VAR ] Ident : Ident ")"
ΓûÉ Block = { Decl } [ BEGIN StmtSeq ] END
ΓûÉ Decl = CONST { ConstDecl ";" } | TYPE { TypeDecl ";" } |
ΓûÉ VAR { VarDecl ";" } | ProcedureDecl | ModuleDecl
Procedure declarations consist of a procedure heading and a block which is said
to be the procedure body. The heading specifies the procedure identifier, the
formal-parameters and the result type (if any). The body contains declarations
and statements. The procedure identifier is repeated at the end of the
procedure declaration. If the language-extensions are enabled, then an optional
receiver may be specified. The receiver is a leading formal parameter for the
target object on which to apply the procedure. It is needed for type-bound
procedures only. It closely resembles the concepts of the new Oberon-2 language
for classes and methods.
There are two kinds of procedures, namely proper procedures and function
procedures. The latter are activated by a function designator in an expression,
yielding a result that serves as an operand in the expression. Proper
procedures are activated by a procedure-call. The function procedure is
distinguished in the declaration by indication of the return type of its result
following the parameter list. Its body must contain a RETURN-statement which
defines the result of the function procedure.
The procedure body can also be declared FORWARD. This makes it possible for
procedures to mutually call each other recursively. A forward declared
procedure must be fully declared later within the same lexical scope, fully
repeating the procedure heading, with a full procedure block.
If the language-extensions are enabled, then the FAR or NEAR modifiers can be
used to indicate, whether the procedure resides in a far or near code segment.
This may be needed for 16-bit APIs like the one of MS-Windows 3.x. Under OS/2
2.x or 3.0 however there is no need for explicitly overriding the default NEAR
attribute because of the 32-bit flat memory-model.
All the constants, variables, types, modules and procedures declared within the
block that constitutes the procedure body are lexically local to the procedure.
The values of local variables, including those of local modules, are undefined
upon entry to the procedure, because local variables reside on the stack. Each
time a procedure is called a new local stack frame is activated for the local
vriables and remains alive for the runtime of the procedure. Since procedures
may be declared as local items too, procedure declarations may be nested. Every
item is said to be declared at a certain level of nesting. If e.g. it is
declared local to a procedure at level k, it itself has level k+1. Items
declared in the module that constitutes a compilation unit are defined to be at
level 0.
In addition to its formal parameters and locally declared items, the items
declared in the environment of the procedure so far (at scope levels outside
the procedure) are also visible to the procedure, unless some of those items
have the same name as an item declared locally.
The use of the procedure identifier in a call within its declaration implies
recursive activation of the procedure including the creation of another local
stack frame for the local variables.
ΓòÉΓòÉΓòÉ 6.4.1. Formal parameters ΓòÉΓòÉΓòÉ
ΓûÉ FormalParameters = "(" FPSectionList ")" [ ":" Qualident ]
ΓûÉ FPSectionList = [ FPSection { ";" FPSection } ]
ΓûÉ FPSection = [ [ NEAR | FAR ] VAR ] IdentList ":" FormalType
ΓûÉ FormalType = { ARRAY OF } Qualident
Formal parameters are identifiers which denote actual parameters specified in
the procedure-call. The correspondence between formal and actual parameters is
established when the procedure is called. There are two kind s of parameters,
namely value and variable parameters. The kind is indicated in the formal
parameter list. Value parameters stand for local variables to which the result
of the evaluation of the corresponding actual parameter is assigned as initial
value. Variables parameters correspond to actual parameters that are variables,
and they stand for these variables. They are passed by reference, not by value,
using the addresses. While variable parameters are indicated by the symbol VAR,
value parameters are introduced without that symbol.
If the language-extensions are enabled, the variable parameters can be declared
as NEAR or FAR. This serves as a hint for the procedure to reference the actual
corresponding variables in a near DGROUP data segment or far segment.
Formal parameters are local to the procedure; that is, their scope is the
program text that constitutes the procedure declaration.
The type of each formal parameter is specified in the parameter list. For
variable parameters, it must be identical to the corresponding actual
parameter's type, except in the case of a record-type, with language-extensions
enabled, where it must be a base type of the corresponding actual parameter's
type. For value parameters, the rules of assignment hold.
If the formal parameter's type is specified as ARRAY OF T, the parameter is
said to be an open array parameter, and the corresponding actual parameter may
be any array with element type T. If the language-extensions are enabled, a
formal parameter's type can even be a multidimensional open array. An open
array parameter can only be accessed elementwise, with an index range from 0 to
N-1, where N is the actual number of elements. The formal array may also wholy
occur as an actual parameter whose formal parameter is an open array itself.
A function procedure without parameters has an empty parameter list. It must be
called by a function designator whose actual parameter list is emtpy, too, but
with enclosing brackets. If a formal parameter specifies a procedure type then
the corresponding actual parameter must be either a procedure declared globally
at zero scope level, or it must be a variable or parameter of that procedure
type. It cannot be a generic standard-procedure.
This compiler also permits structured return types.
ΓòÉΓòÉΓòÉ 6.4.2. Standard procedures ΓòÉΓòÉΓòÉ
Standard procedures are predefined (in this compiler, they reside in the SYSTEM
module). Some are generic procedures that cannot be explicitly declared, i.e.
they apply to classes of operand types or have several possible parameter list
forms.
The following standard function procedures are available with this compiler:
Procedure name Description
ABS( x ) absolute value; result type = argument type
CAP( x ) if ch is a lower case letter, the corresponding
capital letter, else the the same letter
CHR( x ) the character with ordinal number x;
CHR(x)=VAL(CHAR,x)
FLOAT( x ) x of type SHORTINT, INTEGER, LONGINT, SHORTCARD,
CARDINAL, or LONGCARD, represented as a value of type
REAL
LONGFLOAT( x ) x of type SHORTINT, INTEGER, LONGINT, SHORTCARD,
CARDINAL, or LONGCARD, represented as a value of type
LONGREAL
SHORTFLOAT( x ) x of type SHORTINT, INTEGER, LONGINT, SHORTCARD,
CARDINAL, or LONGCARD, represented as a value of type
SHORTREAL
HIGH( a ) high index bound of array a
MAX( T ) the maximum value of type T, where T is a basic type
(except reals), an enumeration or a subrange type.
MIN( T ) the minimum value of type T, where T is a basic type
(except reals), an enumeration or a subrange type.
ODD( x ) x MOD 2 <> 0
ORD( x ) ordinal number (of a cardinal type with same size
than that of x) in the set of values defined by type
T of x. T is any enumeration type, CHAR, SHORTINT,
INTEGER, LONGINT, SHORTCARD, CARDINAL, or LONGCARD.
SIZE( T ) the number of bytes of a specified type T.
SIZE( x ) the number of bytes required by a variable for its
type.
TRUNC( x ) real number x (of type REAL, SHORTREAL or LONGREAL)
truncated to its integral part of type INTEGER.
LONGTRUNC( x ) real number x (of type REAL, SHORTREAL or LONGREAL)
truncated to its integral part of type LONGINT.
SHORTTRUNC( x ) real number x (of type REAL, SHORTREAL or LONGREAL)
truncated to its integral part of type SHORTINT.
LONG( x ) size extended value of x, where x is an integer,
cardinal or real expression, according to the
follwing rules:
SHORTINT->INTEGER,
INTEGER->LONGINT,
LONGINT->LONGINT,
SHORTCARD->CARDINAL,
CARDINAL->LONGCARD,
LONGCARD->LONGCARD,
SHORTREAL->LONGREAL,
LONGREAL->LONGREAL,
REAL->LONGREAL
SHORT( x ) size truncated value of x, where x is an integer,
cardinal or real expression, according to the
following rules:
LONGINT->INTEGER,
INTEGER->SHORTINT,
SHORTINT->SHORTINT,
LONGCARD->CARDINAL,
CARDINAL->SHORTCARD,
SHORTCARD->SHORTCARD,
LONGREAL->SHORTREAL,
SHORTREAL->SHORTREAL,
REAL->SHORTREAL
VAL( T, x ) the value with ordinal number x and with type T. T is
any enumeration type, CHAR, SHORTINT, INTEGER,
LONGINT, SHORTCARD, CARDINAL or LONGCARD.
VAL(T,ORD(x))=x, if x of type T.
The following standard proper procedures are available with this compiler:
Procedure name Description
DEC( x ) x := x - 1, where x is a variable with type SHORTINT,
INTEGER, LONGINT, SHORTCARD, CARDINAL, LONGCARD,
CHAR, enumeration or subrange.
DEC( x, n ) x := x - n, where x is a variable with type SHORTINT,
INTEGER, LONGINT, SHORTCARD, CARDINAL, LONGCARD,
CHAR, enumeration or subrange. n is any cardinal
expression.
EXCL( s, i ) s := s - { i }
HALT terminate program execution
INC( x ) x := x + 1, where x is a variable with type SHORTINT,
INTEGER, LONGINT, SHORTCARD, CARDINAL, LONGCARD,
CHAR, enumeration or subrange.
INC( x, n ) x := x + n, where x is a variable with type SHORTINT,
INTEGER, LONGINT, SHORTCARD, CARDINAL, LONGCARD,
CHAR, enumeration or subrange. n is any cardinal
expression.
INCL( s, i ) s := s - { i }
NEW( p ) ALLOCATE(p,SIZE(T)); Allocate dynamic memory and
assign its address to a pointer p.
DISPOSE( p ) DEALLOCATE(p,SIZE(T)); Deallocate dynamic memory and
set p to NIL.
ΓòÉΓòÉΓòÉ 6.5. Modules ΓòÉΓòÉΓòÉ
ΓûÉ ModuleDecl = MODULE Ident [ Priority ] { Import } [ Export ]
ΓûÉ Block Ident
ΓûÉ Priority = [ "[" ConstExpr "]" ]
ΓûÉ Import = [ FROM Ident ] IMPORT IdentList ";"
ΓûÉ Export = EXPORT [ QUALIFIED ] IdentList ";"
ΓûÉ IdentList = Ident { "," Ident }
A module is a collection of declarations of constants, types, variables,
modules and procedures, and a sequence of statements. They are enclosed in
brackets MODULE and END. The module heading contains the module identfier, and
possibly a number of import lists and an export list. An import list specifies
all identifiers of items that are declared outside but used within the module
and therefore have to be imported. The export list specifies all identifiers of
items declared within the module and used outside. Hence, a module constitutes
a visibility wall around its local items whose transparancy is strictly under
control of the programmer.
Items local to a module are said to be at the same scope level as the module.
They can be considered as being local to the procedure enclosing the module but
residing within a more restricted scope. The module identifier is repeated at
the end of the declaration.
ΓòÉΓòÉΓòÉ 6.5.1. Purpose of local modules ΓòÉΓòÉΓòÉ
The statement sequence which constitutes the module body is executed when the
procedure or compilation unit to which the module is local is called. If
several modules are declared, then these bodies are executed in the sequence in
which the modules occur. These bodies serve to intialize local variables and
must be considered as prefixes to the enclosing procedure's or compilation
unit's statement part.
If an indentifier occurs in the import (export) list, then the denoted item may
be used inside (outside) the module as if the module brackets did not exist.
If, however, the symbol EXPORT is followed by the symbol QUALIFIED, then the
listed identifiers must be prefixed with the module's identifier when used
outside the module. Such a qualified export serves to avoid clashes of
identical identifiers exported from different modules and presumably denoting
different items.
A module may feature several import lists which may be prefixed with the symbol
FROM and a module identifier. The FROM clause has the effect of unqualifying
the imported identifiers. Hence they may be used within the module as if they
had been exported in a normal, non-qualified mode.
If a record-type is exported, all its field identifiers are exported too. The
same is true for the constant identifiers in the case of an enumeration-type.
ΓòÉΓòÉΓòÉ 6.5.2. Module priority ΓòÉΓòÉΓòÉ
A classical problem of multitasking is that of exchanging data among various
processes, e.g. as part of a comunication between them. Common variables are
used to transfer data among processes, and raise the problem of harmonious
cooperation. No process should meddle with common variables while another is
performing a critical action upon them. A reasonable solution to this problem
is to encapsulate shared variables in a module which guarantees mutual
exclusion of processes. Such a module is also called a monitor. Access to its
local data is, because they are hidden, restricted to statements of the
monitor's procedures. These procedures might be exported. A module is
designated to be a monitor by specifiying a priority in its heading. The
priority value is a cardinal number.
Under OS/2 2.x or 3.0 there up to 32 different priority levels from 0 to 31.
Each priority value has its own OS/2 mutex semaphore. This way the procedures
inside the priority module are protected against other threads. The compiler
implements Modula's processes as OS/2 threads, the basic time-sliced task
units.
ΓòÉΓòÉΓòÉ 7. Expressions ΓòÉΓòÉΓòÉ
ΓûÉ Expr = SimpleExpr [ Relation SimpleExpr ]
ΓûÉ SimpleExpr = [ "+" | "-" ] Term { AddOperator Term }
ΓûÉ Term = Factor { MulOperator Factor }
ΓûÉ Factor = CharConst | Number | String | Set |
ΓûÉ Designator [ ActualParams ] | "(" Expr ")" |
ΓûÉ Not Factor
ΓûÉ Set = Qualident "{" [ ElemList ] "}"
ΓûÉ ElemList = Elem { "," Elem }
ΓûÉ Elem = Expr { ".." Elem }
ΓûÉ ActualParams = "(" [ Expr { "," Expr } ] ")"
Expressions are constructs denoting rules of computation whereby constants and
current values of variables are combined to derive other values by the
application of operators and function procedures. Expressions consist of
operands and operators. Parentheses may be used to express specific
associations of operators and operands.
Examples of expressions (refer to the examples in the section about
variable-declarations):
expression: denoted type:
1980 CARDINAL
k DIV 3 INTEGER
NOT p OR q BOOLEAN
(i+j) * (i-j) CARDINAL
s - {8,9,13} BITSET
a[i] + a[j] CARDINAL
a[i+j] * a[i-j] CARDINAL
(0<=k) & (k<100) BOOLEAN
t^.key = 0 BOOLEAN
{13..15} <= s BOOLEAN
i IN {0,5..8,15} BOOLEAN
ΓòÉΓòÉΓòÉ 7.1. Operands ΓòÉΓòÉΓòÉ
ΓûÉ Designator = Qualident { Selector }
ΓûÉ Selector = "." Ident | "[" IndexList "]" | "^" | TypeGuard
ΓûÉ IndexList = Expr { "," Expr }
ΓûÉ TypeGuard = "(" Qualident ")"
With the exception of literal constants, that is numbers, strings, characters
and sets, operands are denoted by designators. A designator consists of an
identifier referring to the constant, variable, or procedure to be designated.
Such an identifier may possibly be qualified by module identifiers, and it may
be followed by selectors, if the designated item is an element of a structure.
If the structure is an array A, then the designator A[E] denotes that component
of A whose index is the current value of the expression E. The index type of A
must be assignment-compatible with the type of E. A designator of the form
A[E1,E2,...,En] stands for A[E1][E2]...[En].
If P designates a pointer, P^ denotes the variable, that is referenced by P.
If R is a structured variable of a record-type then the designator R.f denotes
the record field f of R. If the language-extensions are enabled and a procedure
m has been declared with a formal receiver type for record R, then R.m denotes
the type-bound procedure. Again, if the language-extensions are enabled, the
typeguard v(T0) asserts that v is of type T0; that is, program execution is
aborted (in this compiler with a return value 3), if it is not of type T0. The
guard is applicable if
(1) T is an extension of the declared type T0 of v, and
(2) v is a variable parameter of record type or v is a record pointer.
If the designated item is a variable, then the designator refers to the
variable's current value. If the item is a function procedure, a designator
without parameter list refers to that procedure. If it is followed by a
(possibly empty) parameter list, the designator implies an activation of the
procedure and stands for the value resulting from its execution, that is the
returned value. The types of these actual parameters must correspond to the
formal-parameters as specified in the procedure-declaration.
Examples of designators (see examples in the section about
variable-declarations):
Designator: Denoted type:
k INTEGER
a[i] CARDINAL
w[3].ch CHAR
t^.key CARDINAL
t^.left^.right TreePtr
ΓòÉΓòÉΓòÉ 7.2. Operators ΓòÉΓòÉΓòÉ
ΓûÉ Relation = "=" | "<>" | "#" | "<" | "<=" | ">" | ">=" | IN | IS
ΓûÉ AddOperator = "+" | "-" | OR | XOR
ΓûÉ MulOperator = "*" | "/" | DIV | MOD | AND | "&" | SHL | SHR
ΓûÉ Not = NOT | "~"
The syntax of expressions distinguishes between four classes of operators with
different precedences or binding strengths. The operators '~' and NOT have the
highest precedence, followed by multiplication operators, addition operators
and relations. Operators of the same precedence associate from left to right.
For example, x-y-z stands for (x-y)-z.
ΓòÉΓòÉΓòÉ 7.2.1. Arithmetic operators ΓòÉΓòÉΓòÉ
The following operators are defined in standard Modula-2 language:
+ binary addition or unary identity
- binary subtraction or unary sign inversion
* multiplication
/ real division
DIV integer division
MOD modulus
The arithmetic operators (except /) apply to operands of type SHORTINT,
INTEGER, LONGINT, SHORTCARD, CARDINAL, or LONGCARD, or subranges thereof. Both
operands must be either of a cardinal, or they must be both of an integer
type. The resulting type is LONGINT, INTEGER or SHORTINT if both operands are
of integer types, depending on the longest operand type. The resulting type is
LONGCARD, CARDINAL or SHORTCARD if both operands are of cardinal types.
The operators '+', '-', and '*' also apply to real operands. In this case,
both operands must be of a real type, and the result is a LONGREAL or
SHORTREAL depending on the longest operand type. The division operator '/'
applies to real operands only. The '+' and '-' operators can also be used as
unary operators, with a single operand only. In such a cases '-' denotes sign
inversion and '+' denotes the identitiy operation. Sign inversion can be
applied to integer or real operands. The operations DIV and MOD are defined by
the following rules:
x DIV y is equal to the truncated quotient of x / y
x MOD y is equal to the remainder of x DIV y
x = (x DIV y) * y + (x MOD y), 0 <= (x MOD y) < y
If the language-extensions are enabled, then the following bitwise operators
can be used for cardinal or integer operands:
OR bitwise or
XOR bitwise exclusive or
AND bitwise and
& bitwise and, same as AND
SHL bitwise shift left
SHR bitwise shift right
NOT unary bitwise negation
~ unary bitwise negation
ΓòÉΓòÉΓòÉ 7.2.2. Logical operators ΓòÉΓòÉΓòÉ
The following logical operators are available with standard Modula-2. They
apply to boolean operands and yield a boolean result.
OR logical conjunction
p OR q means "if p then TRUE, otherwise q"
AND logical disjunction
p AND q means "if p then q, otherwise FALSE"
& logical disjunction, same as AND
p & q means "if p then q, otherwise FALSE"
NOT unary logical negation
If the language-extensions are enabled, then the following, additional logical
operator is available for boolean operands:
XOR logical exclusive conjunction
p XOR q means "if p then NOT q, otherwise q"
ΓòÉΓòÉΓòÉ 7.2.3. Set operators ΓòÉΓòÉΓòÉ
The following operators apply to operands of any set type and yield a result in
the same type.
+ set union
x IN (s1 + s2) means "(x IN s1) OR (x IN s2)"
- set difference
x IN (s1 - s2) means "(x IN s1) AND NOT (x IN s2)"
* set intersection
x IN (s1 * s2) means "(x IN s1) AND (x IN s2)"
/ symmetric set difference
x IN (s1 / s2) means "(x IN s1) XOR (x IN s2)"
ΓòÉΓòÉΓòÉ 7.2.4. Relations ΓòÉΓòÉΓòÉ
Relations yield a boolean result. The ordering relations apply to the
basic-types SHORTINT, INTEGER, LONGINT, SHORTCARD, CARDINAL, LONGCARD, BOOLEAN,
CHAR, SHORTREAL, LONGREAL, REAL, to enumerations and to subrange-types.
= equal
# unequal
<> unequal, same as #
< less than
<= less than or equal
set inclusion
> greater than
>= greater than or equal
set inclusion
IN element contained in a set
IS type test
The relations '=' and '#' also apply to sets and pointers. If the relational
operators '<=' and '>=' are applied to sets, they denote (improper) inclusion.
The relation IN denotes set membership. In an expression of the form x IN s,
the expression s must be of type SET OF T, where T is the type of x, or where
T is compatible with the type of x.
The IS relation is only available when the language-extensions are enabled. v
IS T stands for 'v is of type T' and is called a type-test. It is applicable
if
(1) T is an extension of the declared type T0 of v, and
(2) v is a variable parameter of record type or v is a record pointer.
Assuming, for instance, that T is an extension of T0 and that v is a
designator declared of type T0, then the test 'v IS T' determines whether the
actually designated variable is (not only a T0, but also) a T. The value of
'NIL IS T' is undefined.
ΓòÉΓòÉΓòÉ 8. Statements ΓòÉΓòÉΓòÉ
ΓûÉ Stmt = [ Assignment | ProcedureCall | IfStmt | CaseStmt |
ΓûÉ WhileStmt | RepeatStmt | LoopStmt | ForStmt |
ΓûÉ WithStmt | EXIT | ReturnStmt ]
Statements denote execution of the program. There are elementary and structured
statements. Elementary statements are not composed of any parts that are
themselves statements, as is the case with structured statements. Structured
statements are used to express sequencing, and conditional, selective, and
repetitive execution.
The elementary statements are the assignment, the procedure-call, the
return-statement and the exit-statement. A structured statement can be an
if-statement, a case-statement, a while-statement, a repeat-statement, a
loop-statement, a for-statement or a with-statement. Except for the latter one,
they control the flow of program execution.
A statement may also be empty, in which case it denotes no action. The empty
statement is included included in order to relax punctuation rules in
statement-sequences.
ΓòÉΓòÉΓòÉ 8.1. Assignments ΓòÉΓòÉΓòÉ
ΓûÉ Assignment = Designator ":=" Expr
An assignment serves to replace the current value of a variable by a new value
specified by an expression. The assignment operator is written as ':=' and
pronounced as 'becomes'. The designator to the left of the assignment operator
denotes a variable. After an assignment is executed, the variable has the value
obtained by evaluating the expression. The old value is lost. It is
overwritten.
The type of the result variable must be assignment-compatible with the type of
the expression to the right of the operator. Operand types are said to be
assignment-compatible, if either they are compatible or both are integers
(SHORTINT, INTEGER, LONGINT) or cardinals (SHORTCARD, CARDINAL, LONGCARD) or
subranges with integer or cardinal base types. If, however, truncation occurs
as a result of an assignment then the operands are not regarded as
assignment-compatible. Cardinal and integer operands (and their subranges) are
also assignment-compatible. though they are not compatible in expressions.
A string of length n1 can be assigned to an array variable with n2 > n1
elements of type CHAR. In this case, the string value is extended with a null
character 0C. A string of length 1 is compatible with the type CHAR.
Examples of assignments:
i := k;
p := i = j;
j := log2( i+j );
s := { 2, 3, 5, 7, 11, 13 };
a[ i ] := (i + j) * (i - j);
t^.key := i;
w[ i+1 ].ch := "A";
If the language-extensions are enabled then an expression with an extended-type
may assigned to the left variable, too.
ΓòÉΓòÉΓòÉ 8.2. Procedure calls ΓòÉΓòÉΓòÉ
ΓûÉ ProcedureCall = Designator [ ActualParams ]
ΓûÉ ActualParams = "(" [ Expr { "," Expr } ] ")"
A procedure call serves to activate a procedure. The program flow is transfered
to the activated procedure for execution, and after its completion execution
continues with the statement after the procedure call. The procedure call may
contain a list of actual parameters that are substituted in place of their
corresponding formal-parameters defined in the procedure-declaration. The
positions of the actual and formal parameters establishes the correspondence
between them. There are two kinds of parameters: variable and value parameters.
In the case of variable parameters, the actual parameter can only be a
designator denoting a variable. If it designates an element of a structured
variable, all selectors are evaluated before the formal-to-actual parameter
substitution. All of this takes place before the execution of the called
procedure. If the parameter is a value parameter, the corresponding actual
parameter must be an expression. This expression is evaluated prior to the
procedure activation, and the resulting value is assigned to the formal
parameter, which now constitutes a local variable.
The types of corresponding actual and formal parameters must be equal in the
case of variable parameters and assignment-compatible in the case of value
parameters. If the language-extensions are enabled then if the formal parameter
is a variable of a record type or a value parameter of a record-pointer-type,
then the actual parameter can be of an extended type similar to the assignment
rules.
Examples of procedure calls:
Read( i );
Write( j*2+1, 6 );
INC( a[i] );
ΓòÉΓòÉΓòÉ 8.3. Statement sequences ΓòÉΓòÉΓòÉ
ΓûÉ StmtSeq = Stmt { ";" Stmt }
A computation is a sequence of actions that transforms an intial state into a
final one that, it is hoped, satisfies the stated result condition. In
Modula-2, the statement is the basic unit of execution. Thus the execution
sequence of actions is expressed in a statement sequence
Stmt1; Stmt2; Stmt3; ... Stmti; ... Stmtn;
The semicolon is a statement separator that indicates that the action specified
by a given statement is to be succeeded by the one textually following the
separator. Only flow-of-control statements such as a return-statement or an
exit-statement or loop constructs can cause the execution to continue somewhere
else.
ΓòÉΓòÉΓòÉ 8.4. If statements ΓòÉΓòÉΓòÉ
ΓûÉ IfStmt = IF BoolExpr THEN StmtSeq
ΓûÉ { ELSIF BoolExpr THEN StmtSeq }
ΓûÉ [ ELSE StmtSeq ] END
ΓûÉ BoolExpr = Expr
If-Statements specify the conditional execution of guarded statements. The
boolean expression preceding a statement sequence serves as a guard. These
expressions are evaluated in sequence of occurance, until one evaluates to
TRUE, whence its associated statement sequence is executed. If no boolean
expression is evaluated to TRUE, the statement sequence following the symbol
ELSE is executed, if there.
Example:
IF (ch >= "A") & (ch <= "Z") THEN
ReadIdentifier
ELSIF (ch >= '0') & (ch <= '9') THEN
ReadNumber
ELSIF ch = '"' THEN
ReadString( '"' );
ELSIF ch = "'" THEN
ReadString( "'" );
ELSE
SpecialCharacter
END;
ΓòÉΓòÉΓòÉ 8.5. Case statements ΓòÉΓòÉΓòÉ
ΓûÉ CaseStmt = CASE Expr OF Case { "|" Case } [ ELSE StmtSeq ] END
ΓûÉ Case = CaseLabelList ":" StmtSeq
ΓûÉ CaseLabelList = CaseLabels { "," CaseLabels }
ΓûÉ CaseLabels = ConstExpr [ ".." ConstExpr ]
Case statements specify the selection and execution of a statement-sequence
according to the value of an expression. First the case expression is
evaluated; then the statement sequence is executed whose case label list
contains the obtained value. The type of the case expression can only be a
basic-type (except reals), an enumeration-type, or a subrange-type. All labels
must be strictly-compatible with that type. Case labels are constants, and no
value must occur more than once. If the value of the expression does not occur
as a label of any case, the statement sequence following the symbol ELSE is
selected, if there is one.
Example:
CASE ch OF
"A".."Z" : ReadIdentifier;
| "0".."9" : ReadNumber;
| "'" , '"' : ReadString( ch );
ELSE SpecialCharacter;
END;
ΓòÉΓòÉΓòÉ 8.6. While statements ΓòÉΓòÉΓòÉ
ΓûÉ WhileStmt = WHILE BoolExpr DO StmtSeq END
ΓûÉ BoolExpr = Expr
While-statements specify repetition. If the boolean expression yields TRUE, the
statement-sequence is executed. The expression evaluation and the statement
execution are repeated as long as the boolean expression yields TRUE.
Examples include:
WHILE j > 0 DO
j := j DIV 2;
i := i + 1
END;
WHILE i # j DO
IF i > j THEN
i := i - j;
ELSE
j := j - i;
END;
END;
WHILE (t # NIL) & (t^.key # i) DO
t := t^.left
END;
ΓòÉΓòÉΓòÉ 8.7. Repeat statements ΓòÉΓòÉΓòÉ
ΓûÉ RepeatStmt = REPEAT StmtSeq UNTIL BoolExpr
ΓûÉ BoolExpr = Expr
Repeat-statements specify the repeated execution of a statement-sequence
depending on the value of a boolean expression. The expression is evaluated
after each execution of the statement sequence, and the repetition stops as
soon as it yields the value TRUE. The statement sequence is executed at least
once.
Example:
REPEAT
k := i MOD j;
i := j;
j := k;
UNTIL j = 0;
ΓòÉΓòÉΓòÉ 8.8. For statements ΓòÉΓòÉΓòÉ
ΓûÉ ForStmt = FOR ControlVar ":=" Expr TO Expr [ BY ConstExpr ]
ΓûÉ DO StmtSeq END
ΓûÉ ControlVar = Ident
ΓûÉ ConstExpr = Expr
A for-statement specifies the repeated execution of a statement-sequence for a
fixed number of times while a progression of values is assigned to an integer-
or cardinal variable called the control variable of the for-statement. The
control variable cannot be a component of a structured variable, it cannot be
imported, nor can it be a parameter, unless the language-extensions are
enabled. Its value should not be changed by the statement sequence.
The statement
FOR v := low TO high BY step DO
statements
END;
is equivalent to
v := low;
temp := high;
IF step > 0 THEN
WHILE v <= temp DO
statements;
v := v + step;
END;
ELSE
WHILE v >= temp DO
statements;
v := v + step;
END;
END;
'low' must be assignment-compatible with 'v', 'high' must be compatible (that
is comparable) with 'v', and 'step' must be a nonzero constant expression of an
integer or cardinal type. If 'step' is not specified, it is assumed to be 1.
Examples:
FOR i := 1 TO 80 DO
j := j + a[ i ];
END;
FOR i := 80 TO 2 BY -1 DO
a[ i ] := a[ i-1 ];
END;
ΓòÉΓòÉΓòÉ 8.9. Loop/Exit statements ΓòÉΓòÉΓòÉ
ΓûÉ Stmt = LoopStmt | EXIT
ΓûÉ LoopStmt = LOOP StmtSeq END
A loop-statement specifies the repeted execution of a statement-sequence. It is
terminated upon execution of an EXIT statement within that sequence.
An exit statement is denoted by the symbol EXIT. It specifies termination of
the enclosing loop statement and continuation with the statement following that
loop statement. Exit statements are contextually, although not syntactically,
associated with the loop statement that contains them. This compiler, however,
issues a warning, if an EXIT is specified without an enclosing loop statement.
Loop-Exit statements are useful to express repetitions with several exit points
or cases where the exit condition is in the middle of the repeated statement
sequence.
Example:
LOOP
ReadInt( i );
IF i < 0 THEN
EXIT;
END;
WriteInt( i );
END;
ΓòÉΓòÉΓòÉ 8.10. With statements ΓòÉΓòÉΓòÉ
ΓûÉ WithStmt = WITH RecDesignator DO StmtSeq END
ΓûÉ RecDesignator = Designator | Guard
ΓûÉ Guard = Qualident ":" Qualident
The with statement specifies a record variable and a statement-sequence. In
these statements the qualification of field identifiers may be omitted, if they
are to refer to the variable specified in the with clause. If the designator
denotes a component of a structured variable, the selector is evaluated once
before the statement sequence. The with statement opens a new scope
Example (refer to examples in the section about variable-declarations):
WITH t^ DO
key := 0;
left := NIL;
right := NIL
END;
If the language-extensions are enabled, then a with statement can execute a
statement sequence depending on the result of a type-test and apply a
type-guard to every occurance of the tested variable within this statement
sequence.
If 'v' is a variable parameter of record type or a pointer to record variable,
and if it is of a static type 'T0', the statement
WITH v : T1 DO ... END
has the following meaning: if the dynamic-type of 'v' is 'T1', then the
statement sequence is executed, where 'v' is regarded as if it had the static
type 'T1'; else the program execution is halted (in this compiler with result
code 3).
Example:
TYPE
(* basic employee structure *)
Employee = POINTER TO EmployeeDesc;
EmployeeDesc = RECORD
Name : ARRAY [0..30] OF CHAR;
Salary : REAL;
END;
(* extended employee record for secretaries *)
Secretary = POINTER TO SecretaryDesc;
SecretaryDesc = RECORD( Employee )
Bonus : REAL;
END;
VAR
EmployeeVar : Employee;
(* .............................. *)
WITH EmployeeVar : Secretary DO
Employee^.Salary := 1234.00;
Employee^.Bonus := 77.00;
END;
The regional-type-guard does not open a new scope, but a local-type-guard in a
with clause does.
Same example with local-type-guard:
WITH EmployeeVar( Secretary )^ DO
Salary := 1234.00;
Bonus := 77.00;
END;
ΓòÉΓòÉΓòÉ 8.11. Return statements ΓòÉΓòÉΓòÉ
ΓûÉ ReturnStmt = RETURN [ Expr ]
A return statement causes the termination of a procedure. It is denoted by the
symbol RETURN, followed by an expression if the procedure is a function
procedure. The type of the expression must be assignment-compatible with the
result type specified in the procedure heading.
Function procedures require the presence of a return statement indicating the
result value. In proper procedures, a return statement is implied by the
procedure block. Any explicit return statement therefore appears as an
additional (usually an exceptional) termination point.
ΓòÉΓòÉΓòÉ 9. Type compability rules ΓòÉΓòÉΓòÉ
Throughout this documentation terms are used to refer to various levels of type
compatibilities. This section provides the complete definitions for the
following terms amd compatibility rules:
- Same types
- Equal types
- Type extensions
- Assignment compatibility
- Expression compatibility
- Extended expression compatibility
- Strict expression compatibility
- Array compatibility
- Matching formal parameter lists
- Type transfer functions
The rules are somewhat extended when compared with Standard Modula-2 because of
different basic type sizes and because of this compiler's language extensions.
Nevertheless, downward compatibility is given for programs restricting
themsevles to types as described in Wirth's 'Programming in Modula-2, 4th
edition'. Wirth's standard mentions the basic types INTEGER, LONGINT and
CARDINAL, but neither SHORTINT nor SHORTCARD nor LONGCARD is described in his
book, and the LONGINT is only briefly mentioned, without further discussion as
to the implications in compatibility rules.
This compiler tries to stick as closely as possible to Wirth's standard while
also providing SHORT and LONG versions for integers and cardinals, and for
standard TRUNC and FLOAT functions.
ΓòÉΓòÉΓòÉ 9.1. Same types ΓòÉΓòÉΓòÉ
Two variables 'a' and 'b' with types 'Ta' and 'Tb' are of the same type if
1. 'Ta' and 'Tb' are both denoted by the same type identifier, or
2. 'Ta' is declared to equal 'Tb' in a type declaration of the form 'Ta
= Tb', or
3. 'a' and 'b' appear in the same identifier list in a variable, record
field, or formal parameter declaration and are not open arrays.
ΓòÉΓòÉΓòÉ 9.2. Equal types ΓòÉΓòÉΓòÉ
Two types 'Ta' and 'Tb' are equal if
1. 'Ta' and 'Tb' are the same type
2. 'Ta' and 'Tb' are open array-types with equal element types, or
3. 'Ta' and 'Tb' are procedure-types whose formal-parameter lists match.
ΓòÉΓòÉΓòÉ 9.3. Type extension (base type) ΓòÉΓòÉΓòÉ
This rule only applies if the language-extensions are enabled.
Given a type declaration 'Tb=RECORD(Ta)...END', 'Tb' is a direct extension of
'Ta', and 'Ta' is a direct base type of 'Tb'.
A type 'Tb' is an extension of type 'Ta', that is, 'Ta' is a base type of 'Tb',
if
1. 'Ta' and 'Tb' are the same types, or
2. 'Tb' is a direct extension of an extension of 'Ta'
If 'Pa = POINTER TO Ta' and 'Pb = POINTER TO Tb', 'Pb' is an extension of 'Pa'
('Pa' is a base type of 'Pb') if 'Tb' is an extension of 'Ta'.
ΓòÉΓòÉΓòÉ 9.4. Assignment compatibility ΓòÉΓòÉΓòÉ
An expression 'e' of type 'Te' is assignment compatible with a variable 'v' of
type 'Tv' if one of the following conditions hold:
1. 'Te' and 'Tv' are the same type.
2. 'Te' and 'Tv' are cardinal or integer types and
TSIZE(Tv)>=TSIZE(Te).
3. 'Te' and 'Tv' are real types and TSIZE(Tv)>=TSIZE(Te).
4. 'Te' and 'Tv' are record-types and 'Te' is an extension of 'Tv' and
the dynamic type of 'v' is 'Tv'.
5. 'Te' and 'Tv' are pointer-types and 'Te' is an extension of 'Tv'.
6. 'Tv' is a pointer-type and 'e' is NIL.
7. 'Tv' is a character array-type with n elements, and 'e' is a
string-constant with m characters, and m < n.
8. 'Tv' is a procedure-type and 'e' is the name of a procedure whose
formal-parameters match those of 'Tv'.
9. 'Tv' or 'Te' is one of the SYSTEM-types BYTE, WORD, DWORD or
LONGWORD, FWORD, QWORD, TBYTE and 'TSIZE(Tv)=TSIZE(Te)'.
10. 'Tv' is the SYSTEM-type SHORTADDRESS (or near ADDRESS for
memory-models Tiny, Small, Medium, or Flat32) and 'Te' is a
cardinal or integer or near pointer type and 'TSIZE(Tv)>=TSIZE(Te)'
11. 'Tv' is a cardinal or integer or near pointer type and 'Te' is the
SYSTEM-type SHORTADDRESS (or near ADDRESS for memory-models Tiny,
Small, Medium, or Flat32) and 'TSIZE(Tv)>=TSIZE(Te)'
12. Either 'Tv' or 'Ta' is the SYSTEM-type LONGADDRESS (or far ADDRESS
for memory-model Compact or Large) and the other 'Ta' or 'Tv' is
any far pointer-type.
ΓòÉΓòÉΓòÉ 9.5. Expression compatibility ΓòÉΓòÉΓòÉ
For a given operator, the types of its operands are expression compatible if
they conform to the following table, which also shows the result type of the
expression. Pointer type 'P1' must be an extension of type 'P0'. If the
language-extensions are enabled, then some additional expression
compatibilities are available.
The SYSTEM-types SHORTADDRESS, ADDRESS and LONGADDRESS are treated like
cardinals. And if the operator is neither the '=' nor '#' nor '<>' relation and
if the address type is a far pointer then only the offset part is taken for the
evaluation. If there are far address operands in a relational expression then
only the '=' or '#' or '<>' are permitted. If there are address operands in a
non-relational expression then the result type is the type of the address
operand.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéOperator ΓöéOperand1 ΓöéOperand2 ΓöéResult Type Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé+ - * DIV ΓöéSHORTINT ΓöéSHORTINT Γöésmallest integer Γöé
ΓöéMOD ΓöéINTEGER ΓöéINTEGER Γöéincluding both Γöé
Γöé ΓöéLONGINT ΓöéLONGINT Γöéoperand types Γöé
Γöé Γöéint-subrange Γöéint-subrange Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé+ - ΓöéSHORTINT Γöé Γöésame integer type Γöé
Γöé ΓöéINTEGER Γöé Γöé Γöé
Γöé ΓöéLONGINT Γöé Γöé Γöé
Γöé Γöéint-subrange Γöé Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé+ - * ΓöéSHORTREAL REALΓöéSHORTREAL REALΓöésmallest real Γöé
Γöé ΓöéLONGREAL ΓöéLONGREAL Γöéincluding both Γöé
Γöé Γöé Γöé Γöéoperand types Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé+ - ΓöéSHORTREAL REALΓöé Γöésame real type Γöé
Γöé ΓöéLONGREAL Γöé Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé/ ΓöéSHORTREAL REALΓöéSHORTREAL REALΓöésmallest real Γöé
Γöé ΓöéLONGREAL ΓöéLONGREAL Γöéincluding both Γöé
Γöé Γöé Γöé Γöéoperand types Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé+ - * / Γöéset type Γöésame set type Γöésame set type Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéOR AND & ΓöéBOOLEAN ΓöéBOOLEAN ΓöéBOOLEAN Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéNOT ~ ΓöéBOOLEAN Γöé ΓöéBOOLEAN Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé= <> < <= ΓöéSHORTINT ΓöéSHORTINT ΓöéBOOLEAN Γöé
Γöé> >= # ΓöéINTEGER ΓöéINTEGER Γöé Γöé
Γöé ΓöéLONGINT ΓöéLONGINT Γöé Γöé
Γöé Γöéint-subrange Γöéint-subrange Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé= <> < <= ΓöéSHORTCARD ΓöéSHORTCARD ΓöéBOOLEAN Γöé
Γöé> >= # ΓöéCARDINAL ΓöéCARDINAL Γöé Γöé
Γöé ΓöéLONGCARD ΓöéLONGCARD Γöé Γöé
Γöé Γöécard-subrange Γöécard-subrange Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé= <> < <= ΓöéSHORTREAL REALΓöéSHORTREAL REALΓöéBOOLEAN Γöé
Γöé> >= # ΓöéLONGREAL ΓöéLONGREAL Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé= <> < <= ΓöéCHAR ΓöéCHAR ΓöéBOOLEAN Γöé
Γöé> >= # Γöéchar-subrange Γöéchar-subrange Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé= <> < <= ΓöéBOOLEAN ΓöéBOOLEAN ΓöéBOOLEAN Γöé
Γöé> >= # Γöé Γöé Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé= <> <= >=Γöéset type Γöésame set type ΓöéBOOLEAN Γöé
Γöé# Γöé Γöé Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé= <> # Γöépointer type Γöépointer type ΓöéBOOLEAN Γöé
Γöé ΓöéP0 or P1 ΓöéP0 or P1 Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé= <> < <= Γöéenum Γöésame enum ΓöéBOOLEAN Γöé
Γöé> >= # Γöé Γöé Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéIN ΓöéSHORTCARD ΓöéSET OF 1st ΓöéBOOLEAN Γöé
Γöé ΓöéSHORTINT CHAR Γöétype Γöé Γöé
Γöé ΓöéBOOLEAN Γöé Γöé Γöé
Γöé Γöébyte-enum Γöé Γöé Γöé
Γöé Γöébyte-subrange Γöé Γöé Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
ΓòÉΓòÉΓòÉ 9.6. Extented expression compatibility ΓòÉΓòÉΓòÉ
This Modula-2 compiler implementation provides some additional expression
compatibilies. They are only available if the language-extensions are enabled
and include further logical, relational and also bitwise operators. Type T1
must be an extension of type T0 for the relation 'T0 IS T1'.
The following table shows the additional compatibilities:
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéOperator ΓöéOperand1 ΓöéOperand2 ΓöéResult Type Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéXOR ΓöéBOOLEAN ΓöéBOOLEAN ΓöéBOOLEAN Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéOR XOR ANDΓöéSHORTINT ΓöéSHORTINT Γöésmallest integer Γöé
ΓöéSHL SHR & ΓöéINTEGER ΓöéINTEGER Γöéincluding both Γöé
Γöé ΓöéLONGINT ΓöéLONGINT Γöéoperand types Γöé
Γöé Γöésubrange Γöésubrange Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéOR XOR ANDΓöéSHORTCARD ΓöéSHORTCARD Γöésmallest cardinal Γöé
ΓöéSHL SHR & ΓöéCARDINAL ΓöéCARDINAL Γöéincluding both Γöé
Γöé ΓöéLONGCARD ΓöéLONGCARD Γöéoperand types Γöé
Γöé Γöésubrange Γöésubrange Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéNOT ~ ΓöéSHORTINT Γöé Γöésame integer Γöé
Γöé ΓöéINTEGER Γöé Γöé Γöé
Γöé ΓöéLONGINT Γöé Γöé Γöé
Γöé Γöésubrange Γöé Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéNOT ~ ΓöéSHORTCARD Γöé Γöésame cardinal Γöé
Γöé ΓöéCARDINAL Γöé Γöé Γöé
Γöé ΓöéLONGCARD Γöé Γöé Γöé
Γöé Γöésubrange Γöé Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé= <> # Γöéprocedure Γöésame Γöésame procedure type Γöé
Γöé Γöétype T Γöéprocedure ΓöéT Γöé
Γöé Γöé Γöétype T Γöé Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéIS Γöétype T0 Γöéextended ΓöéBOOLEAN Γöé
Γöé Γöé Γöétype T1 Γöé Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
ΓòÉΓòÉΓòÉ 9.7. Strict expression compatibility ΓòÉΓòÉΓòÉ
Two operands are strictly compatible if they are expression-compatible and if
their types are of equal sizes. Strict expression compatibility is required for
operands in the following contexts:
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéContext ΓöéOperand1 ΓöéOperand2 Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöécase-statement Γöécase label Γöécase expr Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöéprocedure-call Γöéactual Γöéformal VAR Γöé
Γöé Γöéparameter Γöéparameter Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
ΓòÉΓòÉΓòÉ 9.8. Array compatibility ΓòÉΓòÉΓòÉ
An actual parameter 'a' of type 'Ta' is array conpatible with a
formal-parameter 'f' of type 'Tf' if
1. 'Tf' and 'Ta' are the same type, or
2. 'Tf' is an open array, 'Ta' is any array, and their element types
are array compatible, or
3. 'Tf' is 'ARRAY OF CHAR' and 'a' is a string.
ΓòÉΓòÉΓòÉ 9.9. Matching formal parameter lists ΓòÉΓòÉΓòÉ
Two formal-parameter lists match if
1. they have the same number of parameters
2. they have either the same function result type or none, and
3. parameters at corresponding positions have equal types, and
4. parameters at corresponding positions are both either value or
variable parameters.
ΓòÉΓòÉΓòÉ 9.10. Type transfer functions ΓòÉΓòÉΓòÉ
Besides the items exported from the pseudo-module SYSTEM, there is another
system-dependent facilitiy. It is the possibility to use a type-identifier 'T'
as name denoting the type transfer function from the type of the operand to the
type 'T'. Such functions are of course highly implementation dependent since
they do not involve any explicit conversion instructions. They are simply
reinterpreting an operand as a value of another type. The programmer should be
aware of the fact that type transfers are highly non-portable. Though Modula-2
has strict type checkings, it provides some portable type checking relaxations
through standard type transfer functions, e.g. VAL(T,x), CHR(x) or ORD(x), as
well as standard type resizing functions, e.g. SHORT(x) or LONG(x).
Examples for type transfer include:
VAR i : SHORTINT;
VAR ch : CHAR;
VAR b : BOOLEAN;
VAR v : SHORTREAL;
VAR c : LONGCARD;
....
BEGIN
(* BOOLEAN b interpreted as SHORTINT *)
i := SHORTINT( b );
(* BOOLEAN b interpreted as CHAR *)
ch := CHAR( b );
(* LONGCARD c interpreted as SHORTREAL *)
v := SHORTREAL( c );
END ....
ΓòÉΓòÉΓòÉ 10. SYSTEM module ΓòÉΓòÉΓòÉ
The module SYSTEM contains certain constants, types, function-procedures, and
proper-procedures that are necessary to implement low-level operations
particular to a given computer and/or operating system. These include, for
example, facilities for accessing devices that are controlled by the computer,
and facilities to break the type compatibility rules otherwise imposed by the
language definition.
It is strongly recommended that the use of the module SYSTEM be restricted to
specific modules (called low-level modules). Such modules are inherently
nonportable and unsafe, but easily recognized due to the identifier SYSTEM
appearing in their import list.
Note: Because the items imported from SYSTEM obey special rules, this module
must be known to the compiler. It is therefore called a pseudo-module and need
not be supplied as a separate definition module.
This compiler also implements all the standard-identifiers in the SYSTEM
module, from where they are automatically exported to all modules.
ΓòÉΓòÉΓòÉ 10.1. SYSTEM constants ΓòÉΓòÉΓòÉ
The following constants denote the active memory model, the target
operating-system and the target processors. They are only qualified exported.
Their values are set up during compiler intialization and are influenced by
corresponding command line switches. The following table shows the possible
constants and their values:
SYSTEM.TinyModel TRUE if compiling for 16-bit tiny memory model
FALSE otherwise
SYSTEM.SmallModel TRUE if compiling for 16-bit small memory model
FALSE otherwise
SYSTEM.MediumModel TRUE if compiling for 16-bit medium memory model
FALSE otherwise
SYSTEM.CompactModel TRUE if compiling for 16-bit compact memory model
FALSE otherwise
SYSTEM.LargeModel TRUE if compiling for 16-bit large memory model
FALSE otherwise
SYSTEM.Flat32Model TRUE if compiling for flat 32-bit memory model
FALSE otherwise
SYSTEM.DOS TRUE if compiling for DOS,
FALSE otherwise.
SYSTEM.WIN TRUE if compiling for MS-Windows 3.x,
FALSE otherwise.
SYSTEM.OS2 TRUE if compiling for OS/2,
FALSE otherwise.
SYSTEM.Processor 8086 if compiling for 8086/8088 CPU,
80286 if compiling for 80286 CPU,
80386 if compiling for 80386 CPU,
80486 if compiling for 80486 CPU,
80586 if compiling for 80586 (PENTIUM) or higher CPU
SYSTEM.NumericProcessor TRUE if compiling for 80x87 numeric coprocessor,
FALSE if no numeric coprocessor specified.
Note:OS/2 2.x or 3.0 provides a software emulation if there is no
numeric coprocessor installed in the computer system.
ΓòÉΓòÉΓòÉ 10.2. SYSTEM types ΓòÉΓòÉΓòÉ
The Modula-2 language usually enforces a strict type checking during
compilation. This can be relaxed when using one of the SYSTEM types BYTE, WORD,
DWORD, FWORD, QWORD or TBYTE. No operation except assignment is defined for
them. However, if a formal-parameter of a procedure is of one of above
mentioned types, the corresponding actual type may be of any type that uses the
same amount of storage units in the given implementation. If the formal
parameter is an 'ARRAY OF T', where 'T' is one of above mentioned types, its
corresponding actual parameter 'A' may be of any type as long as
SIZE(A)=TSIZE(T)*n holds true, that is, the storage size must be a whole
multiple of the type size of 'T'. The following table shows the type size of
above mentioned types:
Type Size
SYSTEM.BYTE 1
SYSTEM.WORD 2
SYSTEM.DWORD 4
SYSTEM.LONGWORD same as SYSTEM.DWORD
SYSTEM.FWORD 6
SYSTEM.QWORD 8
The following pointer types are expression-compatible and
assignment-compatible with all pointer-types with same (possibly implicit)
NEAR or FAR attribute and same type size. If they denote (a possibly implicit)
NEAR pointer, they may also be assignment-compatible with cardinal and integer
types. They are defined as follows:
SYSTEM.SHORTADDRESS NEAR POINTER TO BYTE
SYSTEM.ADDRESS if compiling for compact or large memory-models:
FAR POINTER TO BYTE
otherwise:
NEAR POINTER TO BYTE
SYSTEM.LONGADDRESS FAR POINTER TO BYTE
SYSTEM.PROCESS SYSTEM.ADDRESS
ΓòÉΓòÉΓòÉ 10.3. SYSTEM function procedures ΓòÉΓòÉΓòÉ
The SYSTEM function procedures provide basic runtime services and low-level
facilities. Some are generic and are expanded like macros during compilation.
Others are implemented as genuine procedures in the SYSTEM.MOD module. In this
compiler all standard-functions have their origin in the SYSTEM module as well.
The following non-standard function procedures are available and described in
the next sections:
- generic LEN( a, n )
- generic LEN( a )
- generic TSIZE( T )
- generic DSIZE( a, n )
- generic DSIZE( a )
- generic ADR( x )
- generic LONGADR( x )
- generic SHORTADR( x )
- generic OFS( x )
- generic SEG( x )
- generic currentFile( )
- generic currentLine( )
- PROCEDURE GetExitProc( ) : PROC
The following runtime functions are implemented in the SYSTEM module:
- PROCEDURE TestBit
( Set:ADDRESS; BitPos:SHORTCARD ):BOOLEAN;
- PROCEDURE LongMul
( i,j:LONGCARD ):LONGCARD;
- PROCEDURE LongIMul
( i,j:LONGINT ):LONGINT;
- PROCEDURE LongDiv
( i,j:LONGCARD ):LONGCARD;
- PROCEDURE LongIDiv
( i,j:LONGINT ):LONGINT;
- PROCEDURE LongMod
( i,j:LONGCARD ):LONGCARD;
- PROCEDURE LongIMod
( i,j:LONGINT ):LONGINT;
- PROCEDURE LongShl
( i,j:LONGCARD ):LONGCARD;
- PROCEDURE LongShr
( i,j:LONGCARD ):LONGCARD;
- PROCEDURE LongSar
( i,j:LONGINT ):LONGINT;
- PROCEDURE somResolve
( Object : LONGWORD; MethodToken : LONGWORD ) : LONGWORD;
- PROCEDURE somFindSMethodOk
( classObject : LONGWORD; idMethod : LONGWORD ) : LONGWORD;
- PROCEDURE somGetClass
( Object : LONGWORD ) : LONGWORD;
ΓòÉΓòÉΓòÉ 10.3.1. SYSTEM function LEN ΓòÉΓòÉΓòÉ
The function LEN(a,n) returns for an array operand 'a' in dimension 'n' the
number of elements as a cardinal number. The function LEN(a) is equivalent to
LEN(a,0). The following example illustrates its usage:
..........
FROM SYSTEM IMPORT LEN;
..........
CONST
Len0 = 15;
Len1 = 20;
Len2 = 25;
..........
VAR
A : ARRAY [1..Len0], [1..Len1], [1..Len2] OF INTEGER;
i : CARDINAL;
j : CARDINAL;
k : CARDINAL;
..........
BEGIN
i := LEN( A ); (* i := 15 *)
j := LEN( A, 1 ); (* j := 20 *)
k := LEN( A, 2 ); (* k := 25 *)
END
..........
ΓòÉΓòÉΓòÉ 10.3.2. SYSTEM function TSIZE ΓòÉΓòÉΓòÉ
The function TSIZE(T) returns for a given type 'T' its byte size as a cardinal
or integer number. This function is usually needed when allocating dynamic
memory objects.
The following example illustrates its usage:
IMPORT Storage;
IMPORT SYSTEM;
...........
TYPE
A : ARRAY [0..10] OF LONGINT;
...........
VAR
pa : POINTER TO A;
...........
BEGIN
(* Allocate 11 * TSIZE( LONGINT ) = 11 * 4 = 44 bytes *)
Storage.ALLOCATE( pa, SYSTEM.TSIZE( A ) );
...........
END
...........
ΓòÉΓòÉΓòÉ 10.3.3. SYSTEM function DSIZE ΓòÉΓòÉΓòÉ
The function DSIZE(a,n) returns for an array operand 'a' in dimension 'n' the
operand size as a cardinal number. The function DSIZE(a) is equivalent to
DSIZE(a,0). The following example illustrates its usage:
..........
FROM SYSTEM IMPORT DSIZE;
..........
CONST
Len0 = 15;
Len1 = 20;
Len2 = 25;
..........
VAR
A : ARRAY [1..Len0], [1..Len1], [1..Len2] OF INTEGER;
i : CARDINAL;
j : CARDINAL;
k : CARDINAL;
..........
BEGIN
i := DSIZE( A ); (* i := 15*20*25*TSIZE(INTEGER) *)
j := DSIZE( A, 1 ); (* j := 20*25*TSIZE(INTEGER) *)
k := DSIZE( A, 2 ); (* k := 25*TSIZE(INTEGER) *)
END
..........
ΓòÉΓòÉΓòÉ 10.3.4. SYSTEM function ADR ΓòÉΓòÉΓòÉ
The function ADR(x) returns the address of a given operand 'x'. The result type
is ADDRESS. The function ADR(x) is like a SHORTADR(x) if compiling for one of
the non-segmented small data models Tiny, Small, Medium or Flat32. Otherwise it
is like a LONGADR(x) if compiling for one of the segmented large data models
Compact or Large.
ΓòÉΓòÉΓòÉ 10.3.5. SYSTEM function LONGADR ΓòÉΓòÉΓòÉ
The function LONGADR(x) returns the far address of a given operand 'x'. The
result type is the segmented far pointer LONGADDRESS which is
expression-compatible with any far pointer or cardinal expression, and it is
assignment-compatible with any far pointer.
ΓòÉΓòÉΓòÉ 10.3.6. SYSTEM function SHORTADR ΓòÉΓòÉΓòÉ
The function SHORTADR(x) returns the near address of a given operand 'x'. The
result type is the non-segmented near pointer SHORTADDRESS which is
expression-compatible with any near pointer or cardinal expression, and it is
assignment-compatible with any near pointer or cardinal or integer operand.
ΓòÉΓòÉΓòÉ 10.3.7. SYSTEM function OFS ΓòÉΓòÉΓòÉ
The function OFS(x) returns the offset part of the address of operand 'x'. If
the address of x is a far address, then it is stripped off its segment part.
The result type is LONGCARD for 32-bit memory-models and CARDINAL for 16-bit
memory-models.
ΓòÉΓòÉΓòÉ 10.3.8. SYSTEM function SEG ΓòÉΓòÉΓòÉ
The function SEG(x) returns the segment part of the address of operand 'x',
stripping off its offset part. The result type is CARDINAL.
ΓòÉΓòÉΓòÉ 10.3.9. SYSTEM function currentFile ΓòÉΓòÉΓòÉ
The function currentFile() returns a zero-terminated string of the current
source module's file name. This function is mainly used for debugging purposes
or for error routines.
ΓòÉΓòÉΓòÉ 10.3.10. SYSTEM function currentLine ΓòÉΓòÉΓòÉ
The function currentLine() returns a zero-terminated string of the currently
compiled line number of the source module. This function is mainly used for
debugging purposes or for error routines.
ΓòÉΓòÉΓòÉ 10.3.11. SYSTEM function GetExitProc ΓòÉΓòÉΓòÉ
The procedure
PROCEDURE GetExitProc() : PROC;
is declared in module SYSTEM. It returns the latest procedure from a chain of
exit procedures. For further information on how to implement a module-specific
exit procedure, see documentation section SetExitProc.
ΓòÉΓòÉΓòÉ 10.4. SYSTEM proper procedures ΓòÉΓòÉΓòÉ
The SYSTEM proper procedures provide basic runtime services and low-level
facilities. Some are generic and are expanded like macros during compilation.
Others are implemented as genuine procedures in the SYSTEM.MOD module. In this
compiler all standard-procedures have their origin in the SYSTEM module as
well.
The following non-standard procedures are available and described in the next
sections:
- INLINE
( ... )
- PROCEDURE IOTRANSFER
( VAR p1,p2:ADDRESS; va:CARDINAL );
- PROCEDURE LISTEN
( );
- PROCEDURE NEWPROCESS
( p:PROC; a:ADDRESS; n:CARDINAL; VAR p1:ADDRESS );
- PROCEDURE TRANSFER
( VAR p1,p2:ADDRESS );
- PROCEDURE ExitProgram
( ExitCode:SHORTCARD );
- generic NEW( p )
- generic DISPOSE( p )
- PROCEDURE SetExitProc
( UserProc : PROC );
The following runtime procedures are implemented in the SYSTEM module:
- PROCEDURE OrBytes
( Dest,Source1,Source2:ADDRESS; Size:LONGCARD );
- PROCEDURE XorBytes
( Dest,Source1,Source2:ADDRESS; Size:LONGCARD );
- PROCEDURE AndBytes
( Dest,Source1,Source2:ADDRESS; Size:LONGCARD );
- PROCEDURE MemSet
( Dest:ADDRESS; Val:WORD; Size:LONGCARD );
- PROCEDURE MemCmp
( Dest,Source: ADDRESS; Size:LONGCARD );
- PROCEDURE MemCpy
( Dest,Source: ADDRESS; Size:LONGCARD );
- PROCEDURE SetBitRange
( Dest:ADDRESS; FromBit:SHORTCARD; ToBit:SHORTCARD );
- PROCEDURE DelBitRange
( Dest:ADDRESS; FromBit:SHORTCARD; ToBit:SHORTCARD );
- PROCEDURE TestBit
( Set:ADDRESS; BitPos:SHORTCARD ):BOOLEAN;
- PROCEDURE Push
( Param:ADDRESS; Size:LONGCARD );
- PROCEDURE PushString
( Str:ADDRESS; StrSize:LONGCARD; Size:LONGCARD );
- PROCEDURE LocalCopy
( VAR Source:ADDRESS; Size:LONGCARD );
- PROCEDURE LocalFree
( VAR Source:ADDRESS );
- PROCEDURE StartUp
( );
- PROCEDURE TypeGuard
( Wanted : TypeDescADDRESS; Actual : TypeDescADDRESS );
- PROCEDURE EnterPriority
( Level : SHORTCARD );
- PROCEDURE ExitPriority
( );
- PROCEDURE LinkVMT
( TypeInfo : TypeDescADDRESS; VMT : TypeDescADDRESS );
- PROCEDURE CopyVMT
( Dest, Origin : TypeDescADDRESS; Size : LONGCARD );
- PROCEDURE InitVMT
( Dest : TypeDescADDRESS; Size : LONGCARD );
- PROCEDURE InitSOM
( );
ΓòÉΓòÉΓòÉ 10.4.1. Standard procedure NEW ΓòÉΓòÉΓòÉ
The standard procedure NEW is rooted in the SYSTEM module for this compiler.
The procedure-call
NEW( p )
is expanded into the procedure-call
ALLOCATE( p, TSIZE( p^ ) )
during compilation. This means a dynamic memory allocation from a global heap
to the pointer variable 'p'. The size of the allocated memory is that of the
type being pointed at by 'p'.
If the language-extensions are enabled and if 'p' is a pointer to a record-type
not referring to an instance of an OS/2 System Object Model (SOM), then the
procedure-call
NEW( p )
is translated into the sequence
ALLOCATE
( p,
TSIZE( <type descriptor address> ) +
TSIZE( <record type> )
);
t := p;
IF t # NIL THEN
IF <extended type> THEN
t^ := ADR( <type desciptor> );
ELSE
t^ := NIL;
END;
p := ADDRESS( t ) + TSIZE( <type descriptor address> );
END;
Before allocating a memory block for a record, few more bytes are to be
allocated for a pointer to an internally maintained type descriptor. This
additional information is needed e.g. for dynamic type-tests for records, for
type-guard selectors, or for rergional type-guards.
The standard procedure NEW can also be used for creating instances of the OS/2
System Object Model. If 'p' has been declared as such a SOM-pointer then the
procedure-call
NEW( p )
is translated into a sequence where a static SOM method resolution yields a
pointer to the OS/2 procedure 'somNew', which is called for creating a new
instance of the desired object class.
This compiler uses various optimizations during code generation. For this
purpose it keeps track of the contents of all pointers used. In particular the
compiler assumes that any pointer 'p' whose value has been set by a NEW(p) can
only point to inside the heap, but not to other variables, thus enabling the
code optimizer to continue keeping values from non-heap variables in
CPU-registers.
ΓòÉΓòÉΓòÉ 10.4.2. Standard procedure DISPOSE ΓòÉΓòÉΓòÉ
The standard procedure DISPOSE is rooted in the SYSTEM module for this
compiler. The procedure-call
DISPOSE( p )
is expanded into the procedure-call
DEALLOCATE( p, TSIZE( p^ ) )
during compilation. This means that a previously allocated dynamic memory block
is released and 'p' is set to NIL. The size of the deallocated memory is that
of the type being pointed at by 'p'.
If the language-extensions are enabled then if 'p' is a pointer to a
record-type then a procedure-call
DISPOSE( p )
is translated into the sequence
p := ADDRESS( p ) - TSIZE( <type descriptor address> );
DEALLOCATE
( p,
TSIZE( record type ) +
TSIZE( <type descriptor address> )
);
This means, that 'p' is first decremented by the size of the address of an
internally maintained type descriptor before deallocation, because each
allocated record memory block keeps a pointer to a type descriptor in the bytes
preceding the record area, and these bytes must be freed up, too.
The standard procedure DISPOSE can also be used for releasing instances of the
OS/2 System Object Model. If 'p' has been declared as such a SOM-pointer then
the procedure-call
DISPOSE( p )
is translated into a sequence where a static SOM method resolution yields a
pointer to the OS/2 procedure 'somFree', which is called for releasing the
SOM-Object.
ΓòÉΓòÉΓòÉ 10.4.3. SYSTEM procedure ExitProgram ΓòÉΓòÉΓòÉ
The procedure
PROCEDURE ExitProgram( ExitCode : SHORTCARD );
is declared in module SYSTEM. It accepts one value parameter. This procedure is
automatically called at the end of a main program-module. The standard
procedure-call
HALT();
is translated into a
SYSTEM.ExitProgram( 0 );
by this compiler. ExitProgram receives one cardinal value parameter for the
exit code. Under OS/2 2.x or 3.0 such an exit code is returned to the caller,
e.g. after a DosExecProgram(...) API function call in one of the parameters
passed by reference.
If the language-extensions are enabled then any type-test failure causes the
call SYSTEM.ExitProgram(3) to be executed.
ΓòÉΓòÉΓòÉ 10.4.4. SYSTEM procedure SetExitProc ΓòÉΓòÉΓòÉ
The procedure
PROCEDURE SetExitProc( UserProc : PROC );
is declared in module SYSTEM. It accepts one value parameter 'UserProc'.
Procedure SetExitProc is typically used to add a new procedure or to restore an
old procedure to the top of a chain of exit procedures which are executed
during program termination.
The following example illustrates how to implement a module-specific exit
procedure:
IMPLEMENTATION MODULE <module-id>;
...
VAR
PreviousExitProc : PROC;
...
PROCEDURE <exit-id>();
BEGIN
<module specific exit code>
SYSTEM.SetExitProc( PreviousExitProc );
END <exit-id>;
...
BEGIN
<module initialization>
PreviousExitProc := SYSTEM.GetExitProc();
SYSTEM.SetExitProc( <exit-id> );
END <module-id>.
ΓòÉΓòÉΓòÉ 10.5. Coroutines ΓòÉΓòÉΓòÉ
The standard book 'Programming in Modula-2', 4th edition, by N.Wirth, describes
a Modula-2 interface for the implementation of quasi-concurrent processes on a
conventional single-processor computer. The word process is here used with the
meaning of a coroutine. Coroutines are processes that are executed by a single
processor. A coroutine may also be invoked by a device interrupt.
This compiler provides no special implementation for coroutines, except for
some primitive cooperative multitasking within an OS/2-process. If compiling
for OS/2 2.x or 3.0, module 'Processes' should be used instead, where
coroutines are implemented as OS/2 threads. OS/2 exception handlers can be
easily installed using the OS/2 2.x or 3.0 exception management API
'DOSEXCEPTIONS'.
Nevertheless, module SYSTEM provides an interface for implementing an own
coroutine system using the following procedure declarations:
PROCEDURE NEWPROCESS
( p:PROC; a:ADDRESS; n:CARDINAL; VAR p1:ADDRESS );
PROCEDURE TRANSFER
( VAR p1,p2:ADDRESS );
PROCEDURE IOTRANSFER
( VAR p1,p2:ADDRESS; va:CARDINAL );
PROCEDURE LISTEN
( );
NEWPROCESS serves to create a new process (coroutine), where the parameters
have the following meanings:
P denotes the procedure which constitutes the process,
A is the base address of the process' workspace
n is the size of this workspace,
p1 as a result parameter is the address of a newly created process
descriptor.
A new process with 'P' as program and 'A' as workspace of size 'n' is assigned
to 'p1'. This process is allocated, but not activated. 'P' must be a
parameterless procedure declared at level 0.
A transfer of control between two processes is specified by a call to
PROCEDURE TRANSFER( VAR p1,p2:ADDRESS )
This call suspends the current process, assigns it to 'p1', and resumes the
process designated by 'p2'. Evidently, 'p2' must have been assigned a process
by an earlier call to either NEWPROCESS or TRANSFER. Both procedures must be
imported from module SYSTEM. A program is to terminate whenever control
reaches the end of a procedure which is the body of a process. Because of this
requirement, this compiler automatically generates a HALT() at the end of
procedures containing TRANSFER(...) statements. Since the assignment to 'p1'
is to occur after identification of the new process 'p2', the actual
parameters may be identical.
The procedure IOTRANSFER is simular to TRANSFER except that it accepts an
additional interrupt vector number 'va' which is also stored in the process
descriptor pointed at by 'p1'. After execution of an IOTRANSFER any new CPU
interrupt causes the routine to be resumed at the point as specified in the
process descriptor 'p1'.
The parameterless procedure LISTEN serves to temporarily lower a current
module's priority, allowing any pending interrupt requests (IOTRANSFERs) to
occur.
ΓòÉΓòÉΓòÉ 10.6. SYSTEM INLINE assembly ΓòÉΓòÉΓòÉ
Modula-2 is mainly a high-level language. Low-level support is generally not
directly programmable under a high-level language unless using the features
provided by the preudo-module SYSTEM. Sometimes there is a need to implement
functions on a CPU-instructions level. While other compilers leave this problem
to an external assembler program, this Modula-2 compiler has chosen a different
approach. A SYSTEM.INLINE(...) statement invokes a complete symbolic nested
assembler for the INTEL 80x86 and 80x87 processor families. It is beyond the
scope of this compiler reference to provide a detailed assembler description.
The INLINE statement accepts a variable number of a actual parameters, which
are treated as assembler -instructions and -directives. For further information
on how to program in assembler the following literature is recommended:
- 'The 80386/387 Architecture' by Stephen P. Morse,
1987, John Wiley & Sons, New York
- '386 DX Microprocessor Programmer's Reference Manual'
1990, Intel Corporation, Literature Sales
A complete language-grammar for the INLINE assembler has been included in a
following documention section for a quick reference.
An INLINE-statement must preserve all registers, except for eAX.
ΓòÉΓòÉΓòÉ 10.6.1. Parameter passing conventions ΓòÉΓòÉΓòÉ
This compiler basically passes its procedure parameters on the CPU-stack. Under
OS/2 this is almost identical with IBM's _System linkage convention.
A procedure heading may originally be declared in a C-style or Pascal-style
manner, using either (*$CDECL+*) or (*$CDECL-*) directives. The default is
Pascal-style procedures. If a procedure is Pascal-like, then the parameters are
to be passed from left to right, and the callee has to clear the stack before
returning to the caller. If a procedure is C-style, parameters are passed in
reversed order, that is, from right to left, and it is up to the caller to
clear the stack immediately after the procedure-call.
Modula-2 also requires additional parameters to be passed on the stack for open
array HIGH bounds. These are to be pushed on the stack before any of the
regular parameters. The called Pascal-style procedure is expected to clear the
stack space of the regular parameters only, while the caller has to clear the
additional stack space for the invisible HIGH bounds.
The following INLINE assembly sequence demonstrates a common 32-bit flat memory
model procedure call:
SYSTEM.INLINE
(
;--------------------------------------
; sample calling Pascal-style procedure
;--------------------------------------
PUSH <HIGH outer dimension for n-th open array parameter>
:
PUSH <HIGH inner dimension for n-th open array parameter>
:
PUSH <HIGH outer dimension for 1-st open array parameter>
:
PUSH <HIGH inner dimension for 1-st open array parameter>
:
PUSH <1-st parameter>
PUSH <2-nd parameter>
:
PUSH <last parameter>
CALL <target-procedure>
ADD ESP, <total size of HIGH bound parameters, if any>
:
);
C-style procedures are not expected to process any HIGH bounds for open arrays.
Hence the same example, this time C-style, looks like this:
SYSTEM.INLINE
(
;---------------------------------
; sample calling C-style procedure
;---------------------------------
PUSH <last parameter>
:
PUSH <2-nd parameter>
PUSH <1-st parameter>
CALL <target procedure>
ADD ESP, <total size of regular parameters>
:
);
Once a called procedure receives control, it has executed an ENTER instruction,
which among others saves caller's stack frame pointers and sets up a new local
stack frame for local variables. Local variables as well as parameters are to
be accessed via base pointers, as in the following example:
SYSTEM.INLINE
(
;-------------------------------------
; sample accessing procedure parameter
;-------------------------------------
MOV EAX, <parameter-id>[ EBP ]
:
;---------------------------------
; sample accessing local variable
;---------------------------------
MOV <local-var-id>[ EBP ], EAX
:
);
If a Pascal-style procedure is originally declared with language extensions
enabled then the compiler also pushes one DWORD for each record VAR-parameter
on the stack because of the dynamic type informations. This is even done before
pushing any HIGH bounds and the regular parameters. The dynamic type
informations are only needed when using object oriented language constructs
such as the IS-relation. The current Modula-2 compiler release 2.01 does not
support OOP-extensions on an INLINE-assembly level. The called procedure's
RET-instruction only has to clear the stack space of the regular parameters.
The remainder (that is, HIGH bounds and type-infos) are to be cleared by the
caller. Hence it follows that, as long as the called procedure does not use the
additional invisible parameters, it can be fully implemented in
INLINE-assembler or even in another programming language.
If a called procedure returns a structured value, the caller has to push a
hidden address of the structure on the stack. This way the callee will know
where to place its return value. This hidden parameter is treated as if it had
been declared like a regular first VAR-parameter.
ΓòÉΓòÉΓòÉ 10.6.2. Function return registers ΓòÉΓòÉΓòÉ
Any function procedure has to return a value in CPU-registers. Depending on the
type size of the returning formal parameter, the following registers take the
return value:
AL 1-byte return type
AX 2-bytes return type
EAX 4-bytes return type (32-bit code)
DX:AX 4-bytes return type (16-bit code)
ST(0) REAL,SHORTREAL,LONGREAL return type
ΓòÉΓòÉΓòÉ 11. Processes ΓòÉΓòÉΓòÉ
Because Modula-2 was originally designed to write among other things operating
systems, it includes coroutines for the expression of concurrent activities.
This compiler has chosen another way for implementing concurrent processes
under OS/2 2.x or 3.0. Rather than reinventing the wheel this compiler
restricts itself to using already available preemptive multitasking features,
namely those from the API module 'DOSPROCESS'. This API provides multitasking
features and is used by a module called 'Processes'. Module 'Processes' follows
Wirth's suggestions in an appendix of his book 'Programming in Modula-2', 4th
edition. Modula-2 processes are mapped to OS/2 threads. Hence, using module
'Processes' enables the program to run several preemptive tasks within an OS/2
2.x or 3.0 process, using OS/2 2.x or 3.0 threads.
Module 'Processes' provides the following interface:
DEFINITION MODULE Processes;
TYPE SIGNAL;
PROCEDURE StartProcess( P : PROC; n : CARDINAL );
(*
start a concurrent process with program P
and workspace of size n.
PROC is a standard type defined as PROC = PROCEDURE().
*)
PROCEDURE SEND( VAR s : SIGNAL );
(*
one process waiting for s is resumed
*)
PROCEDURE WAIT( VAR s : SIGNAL );
(*
wait for some other process to send s
*)
PROCEDURE Awaited( s : SIGNAL ) : BOOLEAN;
(*
Awaited( s ) = "at least one process is waiting for s"
*)
PROCEDURE Init( VAR s : SIGNAL );
(*
compulsory initialization
*)
END Processes.
ΓòÉΓòÉΓòÉ 12. Compilation units ΓòÉΓòÉΓòÉ
ΓûÉ CompilationUnit = DefModule | [ IMPLEMENTATION ] ProgramModule
ΓûÉ
ΓûÉ ProgramModule = MODULE Ident Priority ";" { Import } Block Ident "."
ΓûÉ Priority = [ "[" ConstExpr "]" ]
ΓûÉ Import = [ FROM Ident ] IMPORT IdentList ";"
ΓûÉ IdentList = Ident { "," Ident }
ΓûÉ
ΓûÉ DefModule = DEFINITION MODULE Ident ";" { Import } { Def } END Ident "."
ΓûÉ Def = CONST { ConstDef ";" } | TYPE { TypeDef ";" } |
ΓûÉ VAR { VarDecl ";" } | ProcedureHeading ";"
ΓûÉ ConstDef = Ident "=" ConstExpr
ΓûÉ TypeDef = TypeDecl | Ident
A text source file is accepted by the compiler as a unit and is called a
compilation unit. There a three kinds of compilation units:
- main modules
- definition modules
- implementation modules
A main module constitutes a main program and consists of a so-called program
module. In particular, it has no export list. Imported items are defined in
other, separately compilable program parts which themselves are subdivided into
two units, called definition and implementation module.
A definition module specifies the names and properties of items that are
relevant to clients, i.e. other modules which import from it. These items are
automatically qualified exported and might consist of constants, types,
variables and specifications of procedure headings. The corresponding
implementation module contains local items and statements that need not be
known to a client. It also contains the complete procedure declarations, and
possibly further declarations of items not exported. Definition and
implementation modules exist in pairs. In this compiler, an implementation
module need not be written, if the definition module refers to a non-Modula-2
interface such as operating system APIs or if the definition modules only
contains constants and type definitions and no runtime items such as variables
or procedures. Both definition and implementation modules may contain import
lists, and all items defined in the definition module are available in the
corresponding implementation module without explicit import.
The definition module evidently represents the interface between the
implementation module on one side and its clients on the other side. The
definition module contains those declarations which are relevant to the client
modules, and presumably no other ones. In this compiler the definition module
can also represent an operating system's API. In such cases the implementation
module is usually supplied by third parties and often implemented and
precompiled in other languages. Definition modules in this compiler can refer
to external .OBJ, .LIB, or .DLL files containing other compiler output objects,
static and dynamic link libraries.
Definition modules imply the use of qualified export. Type definitions may
consist of the full specification of the (fully transparent) type, or they may
consist of the type identifier only. In this case the full specification must
appear in the corresponding implementation module, and its export is said to be
opaque. The type is known in the importing client modules by its name only, and
all its properties are hidden. Therefore, procedures operating on operands of
this type, and in particular operating on its components, must be defined in
the same implementation module which hides the type's properties. Opaque export
in this compiler is restricted to pointers and to non-real basic-types only.
Assignment and test for equality are applicable to all opaque types.
As in local modules, the body of an implementation module acts as an
initialization facility for its local objects. Before its execution, the
imported modules are intialized in the order in which they are listed. If
circular references occur among modules, their order of initialization is not
defined.
ΓòÉΓòÉΓòÉ 13. Compiler usage ΓòÉΓòÉΓòÉ
This compiler is invoked from an OS/2 2.x or 3.0 command line processor. It
uses the following command line syntax:
MOD source-file {switches}
The command is started with a 'MOD' or 'MOD.EXE', to be followed by the
Modula-2 source file name and optional switches. A source file contains a
complete compilation-unit and has the file name extension '.MOD' for program
modules or '.DEF' for definition modules. The default extension is '.MOD'.
Module names may be up to 255 characters long and are case sensitive. This
Modula-2 compiler takes up to 8 first characters in uppercase notation for the
corresponding file names and appends the file name with above mentioned file
extensions. This 8.3 file name convention is compatible with the FAT file
systems. It can also be used with the new installable file system called OS/2
2.x or 3.0 High Performance File System (or HPFS in short). HPFS supports long
file names and treats them as case sensitive. This Modula-2 compiler, however,
only recognizes files, whose names are up to 8 characters long and treats them
as case insensitive. The output file name for the object file is the same as
the input source file name, but in all capital letters and with an '.OBJ'
extension instead of '.MOD' or '.DEF'.
Note: Linker definition files under OS/2 2.x or 3.0 or MS-Windows 3.x use the
same '.DEF' file extension as Modula-2 sources. So it is strongly recommended
to keep the linker definition files and Modula-2 definition files in separate
file directories.
If the Make or Build switch is specified for a compilation on the command line,
then the Modula-2 compiler also produces a complete linker response file (file
extension '.RSP') and a possible linker definition file (file extension '.LDF')
in addition to the object files (file extension '.OBJ'). These output files are
suitable for the OS/2 2.x or 3.0 LINK386.EXE or LINK.EXE linker programs, which
create the final executable file (file extension .EXE).
The following example shows, how to compile and link a flat memory-model 32-bit
main program module from file 'TEST.MOD' producing a TEST.EXE file with the
final executable program:
MOD TEST.MOD -m -o -mf
LINK386 @TEST.RSP
The example produces a TEST.EXE file ready to be run.
In order to correctly run the compiler must consult some environment variables
to find the drives letters and file directories for the source and object
files. The command line switches inform the Modula-2 compiler how to perform
its compilation.
The necessary environment settings and the command lines switches are described
in the following documentation sections.
ΓòÉΓòÉΓòÉ 13.1. Command line switches ΓòÉΓòÉΓòÉ
Some of this Modula-2 compiler's features are controlled through command line
switches. These are serving as global compiler directives. Some of them can
also be specified as special comments inside the source files in which case
they said to be local (effective only during compilation of the module where
they are specified). Command line switches are effective for all the modules
being compiled, and not only for one module. The command line switches are
specified after the source file name. They are separated by blanks. Every
switch is introduced by a '-' character, to be immediately followed by the
switch string itself. The switch strings are case insensitive, e.g. -B and -b
are both the same.
The following command line switches are available with this compiler:
-M Make for linker: Recompile updated dependent
modules
-B Build for linker: Recommpile all dependent
modules
-P Verbose compilation with error prompts
-XL Extended Modula-2 language
-XI Undocumented CPU instructions
-XF relaxed function designators
-8086 Target CPU 8086
-x86 Target CPU 286, 386, 486 or 586
-F Numeric coprocessor
-E=directory Target directory for executable file
-SS=stacksize Stack size for executable file in K bytes
-C=number Cancel after number semantic errors
-R4 or -R8 4-bytes- or 8-bytes-REAL
-D Debugger support
-L Line numbers into object files
-Mx Memory model
(Tiny,Small,Medium,Compact,Large,Flat32)
-OB Expression optimization
-OI Pointer and index optimization
-OG Global data flow
-O Optimize all: -OG -OB -OI
-DOS Target operating system DOS
-OS2 Target operating system OS/2 1.3 or 2.x or 3.0
-WIN Target operating system MS-Windows 3.x
-SSFAR Assume far stack segment
-A -A1 -A2 -A4 Record field alignment
-V Assume volatile global variables
-PM:type OS/2 application type PM, VIO, or NOVIO
-$=char Redefine introducing character for compiler
directives
-CDECL Assume C-style procedures
-H2 or -H4 2-bytes- or 4-bytes-HIGH
-FSAVE Save all FPU registers at the beginning of a
procedure
The following default switches are used for the OS/2 2.x or 3.0 operating
system:
-386 -OS2 -MF -E=.\ -SS=8 -C=15 -R8 -A -PM:VIO -H4
ΓòÉΓòÉΓòÉ 13.1.1. Memory models ΓòÉΓòÉΓòÉ
This compiler supports up to six different memory models. Memory models are
needed to tell the compiler, where and how to place code and data segments for
the final executable file. This information is required because of the
segmented memory access mechanism of the Intel 80x86 processor family. A modern
computer usually provides many megabytes of direct random access memory (DRAM)
for its programs to run in. But if the processor is running under a 16-bit
mode, all the CPU registers are only 16 bits wide, covering only a 64 KB range
for 16-bit pointers. This means that the RAM can only be accessed in small
portions not more than 64 KB at a time. This kind of memory access is called
segmented memory access. Every 16-bit pointer can only point to inside a
segment less than 64 KB. Segment descriptors are needed at runtime to let the
system know the beginning of actual code or data segments inside the physical
memory. Segment registers are refering to such desciptors. Hence a complete
linear memory address is made up of two components:
16-bit-segment : 16-or-32-bit-offset
A pointer with both segment and offset components is called a far pointer while
a pointer which contains only the offset portion of a memory address is called
a near pointer. A near pointer refers to a default memory segment stored in one
of the segment registers DS or CS (data or code segment). If compiling for
32-bit programs, the offset portion is 32 bits wide, covering an address range
of up to 4 Giga Bytes. This is more than enough for modern programs for the
foreseeable future. Hence 32-bit programs only need a linear flat memory
access. All the necessary segment registers in such a flat model are set once
by the operating system to the same physical base address. A common problem
with 16-bit programs in particular is the one how to distribute the memory
segments among the modules and their data. Depending on the memory model chosen
the following approach has been implemented for this Modula-2 compiler:
Model Name Align Combine Class Group Remarks
S _TEXT WORD PUBLIC 'CODE' -
_DATA WORD PUBLIC 'DATA' DGROUP
_BSS WORD PUBLIC 'BSS' DGROUP
STACK PARA STACK 'STACK' DGROUP (1)
M x_TEXT WORD PUBLIC 'CODE' - (3)
_DATA WORD PUBLIC 'DATA' DGROUP
_BSS WORD PUBLIC 'BSS' DGROUP
STACK PARA STACK 'STACK' DGROUP (1)
C _TEXT WORD PUBLIC 'CODE' -
x_DATA WORD private 'FAR_DATA' -
x_BSS WORD private 'FAR_BSS' -
STACK PARA STACK 'STACK' DGROUP (1)
L x_TEXT WORD PUBLIC 'CODE' - (3)
x_DATA WORD private 'FAR_DATA' -
x_BSS WORD private 'FAR_BSS' -
STACK PARA STACK 'STACK' DGROUP (1)
F _TEXT DWORD PUBLIC 'CODE' - (2)
_DATA DWORD PUBLIC 'DATA' DGROUP (2)
_BSS DWORD PUBLIC 'BSS' DGROUP (2)
STACK DWORD STACK 'STACK' DGROUP (1) (2)
Remarks:
(1) STACK only defined in main module.
(2) 32-bit segment
(3) x_TEXT:
one logical code segment per module x.
x_BSS:
one logical uninitialized data segment per module x.
x_DATA:
one logical initialized data segment per module x.
When compiling for a 16-bit code, select the appropriate memory model with a
command line switch according to the following total program code and data
sizes:
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéSwitch ΓöéCode Size ΓöéData Size ΓöéMode Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé-MS Γöéless than 64 KBΓöéless than 64 KBΓöé16-bit code Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé-MM Γöémore than 64 KBΓöéless than 64 KBΓöé16-bit code Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé-MC Γöéless than 64 KBΓöémore than 64 KBΓöé16-bit code Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé-ML Γöémore than 64 KBΓöémore than 64 KBΓöé16-bit code Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
Γöé-MF Γöé0..4 GB Γöé0..4 GB Γöé32-bit code Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
ΓòÉΓòÉΓòÉ 13.1.2. Target operating system ΓòÉΓòÉΓòÉ
This Modula-2 implementation accepts the following target operating system
switches:
Compiler
switch Description
-OS2 Produce 80x86 protected mode object code for IBM OS/2 2.x
or 3.0 or 1.3
-WIN Produce 80x86 protected mode object code for MS Windows 3.x
-DOS Produce 80x86 object code for DOS
However, the current compiler version 2.01 is only distributed with standard
modules and APIs for OS/2 2.x or 3.0. Future versions will also include the
full standard libraries for DOS and MS-Windows.
ΓòÉΓòÉΓòÉ 13.1.3. Target processors ΓòÉΓòÉΓòÉ
This Modula-2 compiler produces object code for the Intel 80x86 family
processor. A coprocessor switch is to be specified if the INLINE assembler is
to accept 80x87 numeric coprocessor instructions. The main code generator
always assumes the existence of the 80x87 numeric coprocessor or a software
emulation, regardless of the coprocessor switch. Under OS/2 2.x or 3.0 a
coprocessor emulation is supplied with the operating system. The current
compiler version 2.01 has no coprocessor emulator itself. Under OS/2 2.x or 3.0
the default target processor is 80386 for the 32-bit flat memory-model.
Summary of target processor switches:
-8086
-80286 or -286
-80386 or -386
-80486 or -486
-80586 or -586
-F
ΓòÉΓòÉΓòÉ 13.1.4. Make and Build ΓòÉΓòÉΓòÉ
A Modula-2 program typically consists of many modules which are related with
each other in a hierarchical order. The top level module is usually a main
program module or a dynamic link implementation module and it may import lower
level modules. The lower level modules may in turn import other low level
modules etc. If a low level module has been changed and is to be recompiled,
all modules importing the changed module are affected. If the low level module
has been updated in its definition module then all importing modules must be
recompiled as well.
This process of recompiling dependent importing modules is called a 'make' or
'build'. A 'make' function only recompiles updated dependent modules, while a
'build' function compiles all the dependent modules (updated or not). Other
compilers often need the services of a separate make-utility using a special
make-file with a description of the module's interdependences in order to
invoke the recompilation of dependent modules. This Modula-2 version, however,
has already integrated the make- or build- functions in its compiler program.
It is activated with one of the simple command line switches:
-M Also recompile updated dependent modules
-B Also recompile all dependent modules
If one the above mentioned switches is specified on the command line the
compiler also produces a complete linker response and linker definition files
which can be used for the 32-bit LINK386.EXE or 16-bit LINK.EXE programs for
producing the final executable program file.
ΓòÉΓòÉΓòÉ 13.1.5. Language and CPU extensions ΓòÉΓòÉΓòÉ
This Modula-2 compiler provides an extended language grammar if the following
command line switch has been specified:
-XL
The Intel 80x86 CPU family provides some undocumented code instructions which
can make machine code shorter and quicker when used. The code generator of this
Modula-2 compiler can make use of them if the extended instructions switch is
specified on the command line:
-XI
The undocumented instructions should work with any INTEL 80386 or higher CPU,
but they are not necessarily available with CPUs from other manufacturers,
which are otherwise compatible.
It is often desirable to ignore return values from function designators,
especially when using OS/2 APIs. This does not agree with the stricter Modula-2
language definition. This compiler, however, accepts the relaxed function
designator switch
-XF
from the command line. It enables the programmer to use function procedure
identifiers like normal procedure calls, just ignoring the return value.
ΓòÉΓòÉΓòÉ 13.1.6. Stack segment ΓòÉΓòÉΓòÉ
There are two command line switches affecting the runtime stack:
-SSFAR
-SS=stacksize
The '-SSFAR' tells the compiler that the stack segment is not supposed to
reside in the default data segment group DGROUP. This switch is needed for
16-bit memory-model dynamic link libraries whose functions are typically called
from another program using the caller's CPU stack for local variables and
actual parameters. This switch is not needed for 32-bit flat memory-model
dynamic link libraries because the flat linear address scheme doesn't care
about memory segmentation.
The 'SS=stacksize' tells the compiler to pass on the stack size in number of
KBytes to the linker response file. Under 16-bit memory-models the value range
for the stacksize must not exceed 64 KBytes because of the 16-bit memory
segmentation. For 32-bit programs the maximum value currently is 65536 KBytes.
The default stack size is 8 KBytes.
Command line examples:
REM
REM build an optimized 16-bit small memory model
REM dynamic link library
REM
MOD MYLIB.MOD -OS2 -MS -O -SSFAR -B
LINK @MYLIB.RSP
REM
REM make an optimized 32-bit flat memory model program
REM with a stacksize of 128 KBytes
REM
MOD MYPROG.MOD -OS2 -MF -O -SS=128 -M
LINK386 @MYPROG.RSP
ΓòÉΓòÉΓòÉ 13.1.7. Debugger support ΓòÉΓòÉΓòÉ
The current compiler version 2.01 provides no debugger. Future releases may
include special Modula-2 debuggers. Nevertheless, some basic debugger
information can be stored into the output object files. The linker places these
informations into the final executable file. The following debug switches are
supported:
-L
-D
The '-L' switch tells the compiler to store the line numbers of the input
source files into the output object files, from where they are passed on by the
linker program into the map file and executable file. If compiling for the
32-bit OS/2 target platform, the internal source line number format is readable
for any HLL-compatible debugger, such as IBM's SD386 from the Developer
Connection for OS/2, Volume 6 or later, (Development Tools, Disc 1). Otherwise
the source line numbers are generated in a format which should be readable for
CodeView-compatible debuggers.
The '-D' switch causes the compiler's code generator to produce code which
spills any register held values into the memory variables for each of the
Modula-2 source expressions. This ensures proper updated memory values when
stepping through the source lines with a debugger. For this purpose the '-D'
switch disables the '-OB' and '-OI' optimizer switches.
The '-D' switch also causes the compiler to generate HLL-compatible symbolic
debug infos. These are written into the output OBJ-file and passed on by the
linker into the final executable file.
Command line example for making and debugging a 32-bit OS/2 program:
MOD MYPROG.MOD -B -O -L
LINK386 @MYPROG.RSP
SD386 MYPROG.EXE
ΓòÉΓòÉΓòÉ 13.1.8. Optimizations ΓòÉΓòÉΓòÉ
Modula-2 is a high-level programming language providing a fair degree of
hardware independence. Because of this the compiler parser does not immediately
translate source statements into a machine code. It rather produces an
intermediate form of statements which in turn are transformed into the target
CPU machine code. In order to make use of the special CPU capabilities for
shorter and quicker programs some optimizations can take place before producing
the machine code. This compiler provides the following optimizer switches:
-OG
-OB
-OI
-O
The '-OG' switch causes the compiler to perform a global data flow analysis
accross basic blocks. A statement-sequence for a procedure or module body is
subdivided into basic blocks because of various branchings taking place at
runtime by flow-of-control statements. The global dataflow information serves
as the basis for further live variable analysis which tries to find out for how
long any variable might be needed by succeeding statements. This information in
turn is used for the allocation of stack memory during runtime for local
temporary variables and for loop optimizations. Temporary variables are created
during runtime to hold intermediate expressions during program execution. The
goal is to use as little stack memory as possible for temporaries keeping them
rather in CPU registers whenever possible. The goal of the loop optimization is
to keep inner loop variables in CPU registers, too, if possible, because inner
loop variables are usually more often accessed.
The global dataflow analysis also serves as a basis for an available register
analysis which tries to determine for how long any register byte might keep its
value even accross basic blocks without changing the intended program
semantics. This helps to avoid unecessary reloads of CPU registers during
program execution.
The '-OB' switch causes the compiler to perform an expression optimization. If
the source program repeatedly specifies the same complex expression then it is
often possible to compute such an expression only once during program execution
and to store it in a temporary variable. Such a common expression could then be
used by simply refering to that temporary variable instead of completely
recomputing the expression each time it is needed. Common expressions are
recognized within basic blocks and to some degree even globally outside basic
blocks by this compiler.
The '-OB' switch also tells the compiler to reduce in strength intermediate
statements created after the parsing. Multiplication and division operations
for example can often be reduced to quicker shift instructions.
The '-OI' switch causes the compiler to produce more efficient machine code for
indexed and/or dereferenced pointer expressions. An Intel 80x86 CPU operand can
have index and/or base register(s) and a scale factor in addition to the memory
offset. The goal of this optimization is to keep as many pointers or indices as
possible in base or index registers for machine code operands during program
execution.
The '-O' switch causes the compiler to perform all of above mentioned
optimizations. It is just a short notation for '-OG -OB -OI'.
ΓòÉΓòÉΓòÉ 13.1.9. Record field alignments ΓòÉΓòÉΓòÉ
This compiler provides various schemes for record field alignments. A record
field can be aligned on a byte, word or double word boundary. Proper alignments
can reduce the memory access time for record fields. If, for example, a double
word sized record field has to be accessed in a 32-bit program, more than one
step would be needed if it were not double word aligned in the memory.
Alignment is done by this compiler through implicit insertion of unnamed filler
bytes.
The following alignment switches are recognized by the compiler:
-A default alignment
-A1 byte alignment
-A2 word alignment
-A4 double word alignment
The '-A1' switch tells the compiler to align every record field on a byte
boundary. This means no filler bytes are inserted whatsoever between record
fields.
The '-A2' switch tells the compiler to align any record field with a type size
greater than 1 byte or with a record type on a 2-bytes word boundary by
inserting filler bytes between record fields as needed.
The '-A4' switch tells the compiler to align any record field with a type size
greater than 1 byte or with a record type on a 4-bytes double word boundary by
inserting filler bytes between record fields as needed.
If no alignment or '-A' switch is specified on the command line then a default
alignment scheme applies for record fields as follows:
If the field is of record type then align it on a near pointer size (2 bytes
for 16-bit memory-models, 4 bytes for 32-bit memory-models).
Otherwise if the field size is 1 byte, perform byte alignment.
Otherwise if the field size is 2 bytes, perform word alignment.
Otherwise perform word alignment for 16-bit memory-models and double word
alignment for 32-bit memory-models.
If the type of the record field is a (possibly multidimensional) array then
alignment for any of above mentioned alignment schemes is based upon the array
element type.
ΓòÉΓòÉΓòÉ 13.1.10. OS/2 application types ΓòÉΓòÉΓòÉ
Under OS/2 there are 3 different application types:
-PM:NOVIO full screen text mode applications
-PM:VIO window compatible text mode applications
-PM:PM Presentation Manager applications
A full screen application is not compatible with the OS/2 Presentation
Manager. It is run in a separate text-mode screen session. Full screen
applications may use low-level facilities for screen display and mouse or
keyboard inputs.
A window compatible text mode application only uses a certain subset of the
OS/2 API for screen, mouse and keyboard. They can be run as text mode full
screen or windowed applications. All Modula-2 standard libraries for this
compiler have been written for a window compatible mode. The compiler itself
is window compatible, too.
A Presentation Manager application runs under the graphical user interface
using windows. It does not run as a text mode application for a separate
session. The Presentation Manager provides a CUA-compatible user interface for
programs using windows and graphic objects, with full mouse support.
Only one of above mentioned switches can be specified on the command line. The
default application type is window compatible: '-PM:VIO'.
ΓòÉΓòÉΓòÉ 13.1.11. C-style procedures ΓòÉΓòÉΓòÉ
The following switches tell the compiler to assume C-style parameter passing
order or a C-style procedure return code. The default is a Pascal-like
parameter passing convention and a Pascal-like return code.
-CPARAM C-style order of parameter passing
-CRET C-style procedure return: Caller clears local stack
-CDECL -CPARAM and -CRET combined.
These parameters are needed for compatibility with functions written in the
C-language, e.g. OS/2 2.x or 3.0 APIs or C-libraries.
Note: These parameters are active for all modules being compiled, including
possible imported modules. Modula-2 procedures usually conform to a
Pascal-like manner for parameter passing in order to produce shorter and safer
machine code. C-style parameters and procedure returns should be the
execption. It is recommended not to use these switches on the command line,
but only locally as compiler directives before individual procedure
declarations inside the source files.
ΓòÉΓòÉΓòÉ 13.1.12. Miscellaneous switches ΓòÉΓòÉΓòÉ
The following miscellaneous switches are recognized by this compiler:
-P verbose compilation with error prompts
-W0 warning level 0
-W1 warning level 1
-E=directory executable file directory
-C=number cancel of <number> compiler error
-R4 REAL = SHORTREAL
-R8 REAL = LONGREAL
-H2 HIGH returns 2-bytes CARDINAL
-H4 HIGH returns 4-bytes LONGCARD
-V assume volatile global variables
-FSAVE Save all FPU registers are the beginning of a
procedure.
-$=char redefine the introducing character for compiler
directives
The '-P' switch causes a source file display during compilation. It also
activates error prompts for semantic errors, asking the user to hit any key
for the continuation of the compilation. If this switch is not specified then
only error messages without prompts are displayed.
The '-W0' or '-W1' switches will set the warning level for the compilation. If
the warning level is 1 then the compiler also displays warning messages. If
the warning level is 0 then no warnings are displayed. Error messages are
always displayed.
The '-E=directory' switch specifies the directory in which the linker response
and linker definition files and the final executable files are to be written.
The default executable directory is the current one (-E=.\).
Command line example:
REM
REM make an optimized executable program for
REM the directory C:\MOD32\OS2BIN
REM
MOD MYPROG.MOD -M -O -E=C:\MOD32\OS2BIN
LINK386 @C:\MOD32\OS2BIN\MYPROG.RSP
The '-R4' or '-R8' switches tell the compiler, what type size to be used for
the REAL type. A REAL can be either a 4-bytes SHORTREAL or a 8-bytes LONGREAL.
The default is REAL=LONGREAL. This switch has been introduced to ensure
compatibility with other Modula-2 compilers.
The '-H2' or '-H4' switches tell the compiler, what type size to be returned
for standard procedure HIGH. The default is LONGCARD for 32-bit flat
memory-model, and CARDINAL for any 16-bit memory-model.
The '-V' switch tells the compiler to treat non-stack (that is, non-local)
variables as volatile. Volatiles variables are only kept in registers for the
duration of a single machine code instruction, if at all. Volatile variables
might be accessed concurrently by several asynchronous processes. That's why
they cannot be kept in registers accros multiple instructions. An example of
volatile variables is that of memory mapped devices.
The switch '-FSAVE' tells the compiler to generate code for saving all FPU
register at the beginning of a procedure. And it also causes the compiler to
generate FPU restoration code before leaving the procedure. This is useful for
Pascal-style behavior: Callee saves FPU registers. If this switch is not
specified then the compiler assumes that the caller has to save any FPU
register before calling the procedure.
The switch '-$=char' tells the compiler to use another character for starting
comment embedded compiler-directives. The default compiler-directive
introducing character is the dollar sign '$'. Compiler-directives serve as
local compiler switches for the duration of the compilation of a source
module. They are embedded in special source file comments and started with
this special character.
Example:
REM
REM compile with a redefined compiler directive
REM introducing character
REM
MOD MYPROG.MOD -O -$=?
If the source file MYPROG.MOD for above mentioned example contains
compiler-directives, they are now only recognized with the new '?' introducing
special character. Example:
MODULE MyProg;
(*$R8 no more recognized as compiler directive R8 *)
(*?A4 now recognized as compiler directive A4 *)
.........
END MyProg.
Defining a new introducing character for comment embedded compiler-directives
helps to avoid clashes with other Modula-2 compilers using a similar syntax
for comment embedded directives.
ΓòÉΓòÉΓòÉ 13.2. Compiler directives ΓòÉΓòÉΓòÉ
Many of the command line switches are also available locally inside source
modules. They are embedded in special comments and serve as local compiler
directives. They overwrite the command line or default switches for the
duration of the compilation of a source module. The following summary lists the
frequently used compiler directives recognized by this compiler:
(*$XL+*) or (*$XL-*) enable or disable language-extensions
(*$XI+*) or (*$XI-*) enable or disable usage of undocumented CPU
instructions
(*$XF+*) or (*$XF-*) enable or disable relaxed function designators
(*$8086*)
(*$80286*) or (*$286*)
(*$80386*) or (*$386*)
(*$80486*) or (*$486*)
(*$80586*) or (*$586*) Compile for specified target-CPU
(*$F+*) or (*$F-*) Assume existence of a numeric coprocessor for
INLINE-assembler
(*$R4*) or (*$R8*) Assume REAL=SHORTREAL or REAL=LONGREAL
(*$D+*) or (*$D-*) Enable or disable debugger support
(*$L+*) or (*$L-*) Enable or disable the passing on of source line-numbers
to output object files.
(*$OB+*) or (*$OB-*) Enable or disable expression optimization
(*$OG+*) or (*$OG-*) Enable or disable global dataflow optimization for
register assignments and loop optimizations.
(*$OI+*) or (*$OI-*) Enable or disable pointer and index optimizations for
CPU base/index register usage.
(*$O+*) or (*$O-*) Enable or disable all compiler optimizations
(*$DLL*) Only recognized at the beginning of a definition module. The
corresponding implementation module is to become a dynamic link
library.
(*$PREFIX*) Only recognized at the beginning of a definition module, whose
corresponding implementation module is to become a dynamic link
library. All public symbols of the compiled dynamic link library will
be qualified with the module name. Otherwise OS/2 by default uses
public DLL symbols in an unqualified manner.
(*$SSFAR*) Only recognized at the beginning of a program module. A far
stack-segment is assumed for the compilation of the module.
(*$A*)
(*$A1*)
(*$A2*)
(*$A4*) Record field alignment (A = default alignment scheme, An =
n-boundary alignment scheme)
(*$V+*) or (*$V-*) Enable or disable the assumption that global variables
are volatile
(*$CPARAM+*) or (*$CPARAM-*) C-style or Pascal-style parameter passing
order for procedures
(*$CRET+*) or (*$CRET-*) C-style or Pascal-style procedure return code
(*$CDECL+*) or (*$CDECL-*) C-style or Pascal-style procedures. Combines
(*$CPARAMё*) and (*$CRETё*).
(*$SOM+*) or (*$SOM-*) The following record-type declarations are treated
as OS/2 SOM classes or as Modula-2 records.
(*$API16+*) or (*$API16-*) Only accepted in a definition module for an old
OS/2 16-bit API. If set on, the following APIs (procedure headings in
definition module) are to be treated as 16-bit APIs. Even though the
procedure headings are declared like any flat memory model API, they
are thunked to a 16-bit code whenever called, using 'SYSTEM.Thunk',
from where control is then transfered to the 16-bit library. Thunking
from 0:32 to 16:16 addressing is needed for flat 32-bit OS/2, because
there are still some older 16-bit kernel APIs left from previous
versions of OS/2, without corresponding new 32-bit APIs. Otherwise
there is no need for using this compiler directive.
(*$H2*) or (*$H4*) Standard procedure HIGH returns CARDINAL or LONGCARD
value.
(*$W1*) or (*$W0*) Enable or disable Warnings during the compilation.
Any comment embedded compiler directive must be started with a special
character, usually a dollar sign '$'. Another special character can be used as
specified with command line switch '-$=char' to avoid clashes with other
Modula-2 compilers using a similar comment embedded directive syntax.
ΓòÉΓòÉΓòÉ 13.3. Conditional compilation ΓòÉΓòÉΓòÉ
This compiler also enables conditional compilation using the following comment
embedded compiler directives:
(*$IF BoolExpr *) or
(*$IF NOT BoolExpr *) or
(*$IFDEF Ident *) or
(*$IFNDEF Ident *)
........
(*$ELSE *)
........
(*$ENDIF *)
where 'Ident' is any existing identifier and 'BoolExpr' is any existing boolean
constant identifier.
Example:
PROCEDURE CAP( ch:CHAR ):CHAR;
CONST
UseCountryCode = SYSTEM.OS2 AND SYSTEM.Flat32Model;
VAR
(*$IF UseCountryCode *)
CountryCode : COUNTRYCODE;
rc : APIRET;
(*$ENDIF *)
BEGIN
(*$IF UseCountryCode *)
(* 32-bit OS/2 uses own case mapping *)
CountryCode.Country := 0;
CountryCode.CodePage := 0;
rc := DosMapCase( 1, CountryCode, ch );
(*$ELSE*)
(* use Modula's case mapping *)
CASE ch OF
| 'Д': ch := 'О';
| 'Ф': ch := 'Щ';
| 'Б': ch := 'Ъ';
| 'a'..'z': ch := CHR( ORD(ch) - (ORD('a') - ORD('A')) );
END;
(*$ENDIF*)
RETURN ch;
END CAP;
ΓòÉΓòÉΓòÉ 13.4. Linker directives ΓòÉΓòÉΓòÉ
There is a special linkage directive available with this compiler. It is
specified as a special comment embedded directive and is recognized anywhere
inside a program module source text. It is started with a $LINK keyword, to be
followed by a series of linkage definition statements. The linkage definition
statements are directly passed on to the linker response file, along with other
default linkage statements.
Example:
(*$LINK
LIBRARY ANIMAL
INITINSTANCE
DESCRIPTION
'Animal class DLL, compiled with Modula-2.'
PROTMODE
DATA
MULTIPLE NONSHARED LOADONCALL
*)
If a linker directive specifies an IMPORTS or EXPORTS list, the corresponding
default list is no more longer generated from the public symbols of the
definition module. Hence an IMPORTS or EXPORTS linker directive must be
followed by a complete symbol list. A partial list won't do it. The linkage
program (usually LINK386.EXE or LINK.EXE) must be able to understand the linker
directives. There is no need for explicit linker directives for a normal
program or dynamic link library. The $LINK directive has been introduced mainly
because of the deviating specifications for OS/2 SOM libraries.
It is beyond the scope of this compiler documentation to provide a complete
list of all the possible linker definition statements. The user is referred to
the original program manuals for LINK386 or LINK.
ΓòÉΓòÉΓòÉ 13.5. Compiler environment ΓòÉΓòÉΓòÉ
Every program runs in a process which has its own environment. The process
environment consists of a series of zero-terminated strings. Every string has
the form <identifier>=<contents>. An environment string starts with an
identifier which is called an enironment variable. It is followed by an equal
sign '=' and a string value. Environment variables usually store information
about the operating environment for programs, e.g. the directories to be
searched for specific data or programs on the hard disks.
This Modula-2 compiler consults the following environment variables:
MOD_SRC Directories for Modula-2 source files (.MOD or .DEF)
MOD_TMP Directory for temporary files (.TMP or .WRK)
MOD_LIB Directories for library files (.LIB or .DLL)
MOD_OBJ_SMALL
MOD_OBJ_MEDIUM
MOD_OBJ_COMPACT
MOD_OBJ_LARGE
MOD_OBJ_FLAT32 Directories for the corresponding memory-model object files
(.OBJ)
DPATH Default directory for object files (.OBJ)
APPEND Default directory for object files (.OBJ)
LIB Default directory for library files (.LIB or .DLL)
If the MOD_LIB environment variable does not exist then the LIB environment
variable is taken.
If the MOD_OBJ_... environment variable for the corresponding memory-model
does not exist then the DPATH environment variable is taken. If the DPATH
environment variable does not exist then the APPEND environment variable is
taken. The compiler may search multiple paths for objects during possible
recompilation of dependent modules. But it will only use the first path from
the object environment variable for storing newly compiled object files.
If no environment variable exists for object file directories then the current
directory '.\' is used for object files. The same is true for library files.
The LINK.EXE or LINK386.EXE programs do not need any special environment
variables if the corresponding linker response files as made or build by the
compiler are used, except for the TMP variable, which contains the temporary
files directory name.
ΓòÉΓòÉΓòÉ 13.6. Dynamic link library creation ΓòÉΓòÉΓòÉ
A dynamic link library contains executable code for common procedures, just as
an ordinary library does. Yet code for procedures in dynamic link libraries is
not copied into the executable (.EXE) file. Instead, the library itself is
loaded into memory at run time, along with the executable file.
Dynamic link libraries serve much the same purpose that standard libraries do,
but they have the following advantages:
- Applications link more quickly. With dynamic linking, the executable
code for a dynamic link library is not copied into the .EXE file of
the application. Instead, only an import definition is copied.
- Applications require less disk space. With dynamic linking, several
different program applications can access the same dynamic link
procedure stored in one place. Without dynamic linking, the code for
the procedure would be repeated in every .EXE file.
- Libraries and applications are independent. Dynamic link libraries
can be updated any number of times without relinking the applications
that use them. This may be particularly convenient for a user of
third part-party libraries. In case of updates, only a copy a the new
library needs to be installed on the disk. At run time, the unchanged
applications then automatically call the updated library procedures.
- Code and data segments can be shared. Without dynamic linking, such
sharing is not possible because each file has its own copy of all the
code and data it uses. By sharing segments with dynamic linking,
memory can be used more efficiently.
Under OS/2 2.x or 3.0 it is also possible to declare commonly used variables
in addtion to the procedures as part of the public interface of a dynamic link
library. As in the case with public procedure, these common variables are only
allocated once and then shared by all the applications.
This compiler supports the creation of dynamic link libraries. A definition
module serves to describe the public interface of a dynamic link library,
while the implementation module declares all private data and the procedures.
From a programmer's point of view there is hardly any difference between an
ordinary module and a dynamic link library. In order to make an implementation
module a dynamic link library from code compiled by this compiler, the
following compiler directive must be specified at the very beginning of the
definition module:
(*$DLL*)
If compiling for a segmented 16-bit memory-model then the stack segment for
the dynamic link library has to treated as a far segment, since it is the
caller's stack, which will be used during run time. The following compiler
directive at the very beginning of the definition module informs the compiler
about the far location of the stack segment:
(*$SSFAR*)
The following example illustrates the creation of a dynmaic library for the
standard module 'Terminal' under OS/2 2.x or 3.0. The source files for the
definition and implementation modules might contain this:
(*$DLL Terminal to be implemented as dynamic link library *)
DEFINITION MODULE Terminal;
PROCEDURE Read( VAR ch: CHAR );
PROCEDURE BusyRead( VAR ch: CHAR );
PROCEDURE ReadAgain();
PROCEDURE ScanCode():SHORTCARD;
PROCEDURE SetEcho( Flag: BOOLEAN );
PROCEDURE GetEcho( VAR Flag: BOOLEAN );
PROCEDURE Write( ch: CHAR );
PROCEDURE WriteLn();
PROCEDURE WriteString( s: ARRAY OF CHAR );
END Terminal.
IMPLEMENTATION MODULE Terminal;
(* private code and data declarations ... *)
END Terminal.
The steps for making the dynamic link library are much the same that the
creation process for an ordinary execution file would require:
REM
REM Make the dynamic link library
REM TERMINAL.DLL
REM
MOD TERMINAL.MOD -m -o
LINK386 @TERMINAL.RSP
Indeed, the internal format of executable files and those of dynamic link
libraries are almost the same. Only the file name extensions are different:
.DLL for dynamic link libraries
.EXE for executable applications
ΓòÉΓòÉΓòÉ 14. Language extensions ΓòÉΓòÉΓòÉ
Modula-2 is a powerful high-level programming language. The language-grammar is
based upon the book 'Programming in Modula-2', 4th edition, by N.Wirth.
Nevertheless, the Modula-2 language is not perfect. Modern software engeneering
requires language tools for object oriented programming and to some degree a
cooperation with modules written in other programming languages such as
C-language. A programming language also has to provide access to operating
system APIs. For these reasons this compiler provides some extensions to the
standard Modula-2 language. The extensions are enabled globally via the command
line switch
-XL
or locally via the comment embedded compiler directive
(*$XL+*)
and they provide the following additional features:
- Extended identifiers
- New reserved words
- Object oriented features
- Extending record types
- Dynamic type tests for records
- Type guard selectors
- Regional type guards
- Type bound procedures
- System Object Model
- Bitwise operators
- New logical operators
- Typed constants
- Multidimensional open array parameters
- Segmentation
- Extended imports
Using any of above mentioned features makes the program less portable for other
Modula-2 compilers. But they can make software implementation a lot more easier
for many projects. The following documentation sections provide further
information on each of above mentioned extended language feature.
ΓòÉΓòÉΓòÉ 14.1. Extended Identifiers ΓòÉΓòÉΓòÉ
Modula-2 identifiers consist of case sensitive letters and digits. They must
start with a digit. For the majority of application programs this is
sufficient. Only in cases of mixed language programming or of using operating
system APIs the identifier rules do not always satisfy the programmer's needs.
OS/2 2.x or 3.0 APIs and C-language modules allow the underscore character '_'
in their identifier strings. That's why this Modula-2 compiler will also
accecpt the underscore '_' in addition to normal letters and digits for its
identifiers if the command line language-extensions switch or compiler
directive for the language-extensions has been set on.
ΓòÉΓòÉΓòÉ 14.2. New reserved words ΓòÉΓòÉΓòÉ
Some of the extended language features such as the object-oriented programming
or the new bitwise and logical operators require the following new reserved
words:
FAR
IS
NEAR
SHL
SHR
XOR
The above mentioned identifiers will be treated as keywords only if the command
line switch or the comment embedded compiler directive for the
language-extensions has been set on. They are then not available as user
defined identifiers.
ΓòÉΓòÉΓòÉ 14.3. Object oriented features ΓòÉΓòÉΓòÉ
This Modula-2 compiler has been extended with object oriented language
constructs. They are closely based on the new Oberon language, which has been
developed in the late 1980s as a successor of the Modula-2 language by Niklaus
Wirth at the ETH ZБrich. The Oberon language has been fully described in the
books:
- 'Programming in Oberon', by Martin Reiser, 1992, Addison Wesley, New
York.
- 'Object-Oriented Programming in Oberon-2', by Hanspeter MФssenbeck,
1993, Springer-Verlag, New York.
Almost since the beginnings of programming, software development has been
dominated by a procedure-oriented way of thinking. Programs are decomposed
into procedures that transform input data into output data.
The object-oriented way of thinking focuses on the data rather than on the
procedures. The data and the operations applicable to them constitute objects
that can be asked to handle certain requests and to return data as their
result.
The point here is that one does not have to bother about the type of the
object to which a request is sent. Every type of object has its own handlers
called methods for processing any requests.
Object oriented languages usually provide the following features:
- Information hiding
- Data abstraction
- Inheritance
- Dynamic bindings
Information hiding means that the implementation of complex data is
encapsulated in objects and that clients have access only to an abstract view
of it. Clients cannot directly access the encapsulated data, but must use
procedures that are part of the respective object. Thus clients are not
troubled with implementation details and are not affected by later changes in
the implementation of the object. Information hiding has also been available
with non-object-oriented programming languages for many years. Modula-2, for
example, can achieve this through modules with exported handler procedures.
However, there is more than that to object orientation.
Data abstraction is the next step after information hiding. The objects
described above exist only once, yet sometimes multiple copies of them are
needed. Just as the programmer can declare any number of variables of a
specific type, object oriented programming makes it prossible to declare
multiple objects of a common abstract data type, including specific operations
for them. An abstract data type is thus a unit consisting of data and the
operations applicable to them. Multiple variables of such a type can be
declared. Under Modula-2 this is mainly done with variables of a record-type,
which might also contain fields of procedure-types for the handlers and
operations. Handler procedures could also be separately declared, along with
the record types, within the same module block.
Inheritance is a concept not found in any conventional programming language.
It means that an existing abstract data type, that is a record-type, can be
extended to a new one that inherits all the data and operations of the
existing type. The new type can include additional data and operations and can
even modify inherited operations. This makes it possible to design a type as a
semifinished product, store it in a library, and later extend it to produce
various final products. An important consequence of inheritance is that the
extended type is compatible with the original one. All procedures that work
with objects of the original type can also work with objects of the new type.
This considerably promotes the reusability of existing algorithms.
The fourth characteristic of object-oriented programming languages is dynamic
bindings of messages or requests to procedures. Procedures are bound during
run time to the dynamic-type of objects. Dynamic binding has also been known
for a long time in the form of procedure variables. The activation of a
procedure variable causes the invocation of the procedure contained in it at
run time. Working with procedure variables, however, is troublesome and
error-prone, while dynamic binding in object-oriented languages represents a
more elegant and reliable solution.
The following table translates the most important terms of object-oriented
languages into conventional terminology. These terms do not necessarily
represent radically new concepts, but have their corresponding terms in
conventional programming.
OOP term Conventional term
Class Extensible record-type with procedure variable
field(s), defining an abstract data type
Object Variable pointing to an instance of a (possibly
extended ) record-type
Message A call of a procedure associated with the class
Method Procedure of a class, either declared in same module
block, or dynamically bound through a record field
with a compatible procedure-type, or type-bound to the
class.
To sum it up: Object oriented programming means programming with abstract data
types (classes) using inheritance and dynamic binding. This Modula-2 compiler
provides the following features for object orientation:
- Type extensions
- Dynamic type tests for records
- Type guard selectors
- Regional type guards
Object orientation is achieved through the use of above mentioned features and
through the use of procedure variables for the dynamic bindings of methods to
records. Object orientation can be also achieved even more elegantly through
type-bound procedures in connection with above listed features. Examples are
given in the documentation sections about type-tests and about the type-bound
procedures.
ΓòÉΓòÉΓòÉ 14.3.1. Type extensions ΓòÉΓòÉΓòÉ
The extensibility of record-types is the new aspect of object-oriented
programming (OOP) and the reason that OOP proves superior to conventional
programming in many situations.
In this Modula-2 compiler a record-type can be extended to a new type that
contains new fields, yet maintains its compatibility with the original type. In
the declarations
TYPE
T0 = RECORD ... END;
T1 = RECORD( T0 ) ... END;
T1 is a (direct) extension of T0, and T0 is the (direct) base type of T1. Or,
to express it in OOP terms: T0 is the base class or superclass of T1, while the
extension can be regarded as a subclass of T0.
Specifying the name of the base type in parentheses after the symbol RECORD
means that the new type is an extension of the base type and thus contains, in
addition to its own fields, all fields of the base type, as though they had
been explicitly declared here. We say that the extended type inherits the
fields of the base type and thus also refer to type extension as inheritance.
Type extension also works for pointer types. Example:
TYPE
P0 = POINTER TO T0;
P1 = POINTER TO T1; (* P1 extends P0 *)
T0 = RECORD ... END;
T1 = RECORD( T0 ) ... END; (* T1 extends T0 *)
ΓòÉΓòÉΓòÉ 14.3.2. Dynamic type tests for records ΓòÉΓòÉΓòÉ
A common problem for object orient programming is that of method resolution.
Messages or requests can be sent to any variable pointing to a dynamically
allocated record (object variable) and the object has to find the proper
procedures to handle the requests depending on the actual dynamic type of the
object. Since an object variable could also point to any of its base typed
records, a sort of dynamic type test is needed before calling a handler for the
appropriate type. Such a test must reveal the dynamic type of a record pointer
variable. The new relational operator 'IS' performs this type test:
v IS T
which is satisfied if the actual (or dynamic) type of 'v' is an extension of
'T'; that is equal to 'T' or a proper extension of 'T'. 'T' must extend the
declared (or static) type of a variable 'v'; and variable 'v' is a record
pointer or a formal VAR parameter of record-type.
The following example shows an object oriented source skeleton for a simple
graphics editor:
TYPE (* Message types for graphic objects *)
Msg = RECORD
END;
DrawMsg = RECORD( Msg )
END;
ClearMsg = RECORD( Msg )
END;
MarkMsg = RECORD( Msg )
END;
MoveMsg = RECORD( Msg )
dX,dY : INTEGER;
END;
...
TYPE (* common graphics figure type *)
Figure = POINTER TO FigureRec;
TYPE (* Handler procedure for graphics figure *)
Handler = PROCEDURE( Self : Figure; VAR Message : Msg );
TYPE (* common graphics figure structure *)
FigureRec = RECORD
Handle : Handler;
selected : BOOLEAN;
END;
...
TYPE (* Rectangle = extended Figure *)
Rectangle = POINTER TO RectangleRec;
RectangleRec = RECORD( FigureRec )
x,y,w,h : INTEGER;
END;
...
TYPE (* TextBox = extended Rectangle *)
TextBox = POINTER TO TextBoxRec;
TextBoxRec = RECORD( RectangleRec )
text : ARRAY [0..31] OF CHAR;
END;
...
(* Message handler for Rectangle *)
PROCEDURE HandleRectangle( Self : Figure; Message : Msg );
BEGIN
WITH Self : Rectangle DO
(* 'Self' treated as if it were of type 'Rectangle' *)
IF Message IS DrawMsg THEN
WITH Message : DrawMsg DO
(* draw a rectangle *)
...
END;
ELSIF Message IS ClearMsg THEN
WITH Message : ClearMsg DO
(* clear a rectangle *)
...
END;
ELSIF Message IS MarkMsg THEN
WITH Message : MarkMsg DO
(* mark a rectangle *)
Self^.selected := TRUE;
...
END;
ELSIF Message IS MoveMsg THEN
WITH Message : MoveMsg DO
(* move a rectangle *)
Self^.x := Self^.x + Message.dX;
Self^.y := Self^.y + Message.dY;
...
END;
ELSE
(* Message not understood, typically no action *)
END;
END;
END HandleRectangle;
...
(* Message handler for TextBox *)
PROCEDURE HandleTextBox( Self : Figure; Message : Msg );
BEGIN
WITH Self : TextBox DO
...
END;
END HandleRectangle;
...
VAR
MyFigure : Figure;
MyRectangle : Rectangle;
MyTextBox : TextBox;
...
BEGIN
(* Create new Rectangle object *)
NEW( MyRectangle );
MyFigure = MyRectangle;
...
(* dynamic binding of a Rectangle specific handler *)
MyFigure^.Handle := HandleRectangle;
...
IF MyFigure IS Figure THEN (* TRUE *)
(* perform Figure specific operations *)
...
END;
...
IF MyFigure IS Rectangle THEN (* TRUE *)
...
(* perform Rectangle specific operations,
e.g. drawing and marking
*)
MyFigure^.Handle( MyFigure, DrawMsg );
MyFigure^.Handle( MyFigure, MarkMsg );
...
END;
...
IF MyFigure IS TextBox THEN (* FALSE *)
...
(* this would perform TextBox specific operations,
if Figure were of type TextBox
*)
...
END;
...
END ...
ΓòÉΓòÉΓòÉ 14.3.3. Type guard selectors ΓòÉΓòÉΓòÉ
ΓûÉ Designator = Qualident { Selector }
ΓûÉ Selector = TypeGuard
ΓûÉ TypeGuard = "(" Qualident ")"
Any object (in the sense of OOP) in this compiler is a variable pointing to an
instance of a (possibly extended) record-type. Because of the assignment rules
for extended records, an object operand must not always refer to the originally
declared record-type to be pointed at, but also to any of its extensions. For
this reason a new designator selector has been introduced called a type guard.
The typeguard 'v(T0)' asserts that the variable 'v' is of dynamic type 'T0';
that is, it aborts program execution if it is not of type T0. The guard is
applicable if
(1) T0 is an extension of the declared type 'T' of 'v', and
(2) 'v' is a formal variable parameter of record-type or 'v' is a
pointer to a record
Specifying a type guard selector for an operand enables the programmer to
treat the operand as if it had been declared with the extended type from the
beginning, without giving up a run time check for proper record type usage.
Example:
TYPE
(* common graphics base type *)
Figure = POINTER TO FigureRec;
FigureRec = RECORD
selected : BOOLEAN;
END;
...
(* extended Figure *)
Rectangle = POINTER TO RectangleRec;
RectangleRec = RECORD( FigureRec )
x,y,w,h : INTEGER;
END;
...
(* extended Rectangle *)
TextBox = POINTER TO TextBoxRec;
TextBoxRec = RECORD( RectangleRec )
text : ARRAY [0..31] OF CHAR;
END;
...
VAR
MyFigure : Figure;
...
BEGIN
...
IF MyFigure IS TextBox THEN
(* MyFigure has extended type
POINTER TO TextBoxRec during run time
*)
MyFigure( TextBox )^.text := "default string";
END;
...
END
...
ΓòÉΓòÉΓòÉ 14.3.4. Regional type guards ΓòÉΓòÉΓòÉ
ΓûÉ WithStmt = WITH Guard DO StmtSeq END
ΓûÉ Guard = Qualident ":" Qualident
It is quite common that in a statement-sequence the same type-guard appears a
number of times. Therefore a type guard with a wider textual scope is desirable
to improve clarity and avoid unnecessary clerical work. The extended form of
the with-statement provides such a regional type guard. Unlike the ordinary
Modula-2 with-statement this extended form does not automatically make
available the record fields for a separate local lexical scope. Record fields
still have to be qualified with a record designator, even inside the scope of
this extended with-statement.
Example:
TYPE
(* common graphics base type *)
Figure = POINTER TO FigureRec;
FigureRec = RECORD
selected : BOOLEAN;
END;
...
(* extended Figure *)
Rectangle = POINTER TO RectangleRec;
RectangleRec = RECORD( FigureRec )
x,y,w,h : INTEGER;
END;
...
(* extended Rectangle *)
TextBox = POINTER TO TextBoxRec;
TextBoxRec = RECORD( RectangleRec )
text : ARRAY [0..31] OF CHAR;
END;
...
VAR
MyFigure : Figure;
...
BEGIN
...
IF MyFigure IS Rectangle THEN
WITH MyFigure : Rectangle DO
(* Designator 'MyFigure' treated as if it were of type
POINTER TO RectangleRec inside this scope
*)
MyFigure^.x := 1;
MyFigure^.y := 2;
MyFigure^.w := 10;
MyFigure^.h := 15;
END;
END;
...
END
...
ΓòÉΓòÉΓòÉ 14.3.5. Type-bound procedures ΓòÉΓòÉΓòÉ
ΓûÉ ProcedureHeading = [ NEAR | FAR ] PROCEDURE
ΓûÉ [ Receiver ] Ident [ FormalParameters ]
ΓûÉ Receiver = "(" [ [ NEAR | FAR ] VAR ] Ident : Ident ")"
This Modula-2 compiler supports the concept of type-bound procedures. It is
available when the language-extensions are enabled. Type-bound procedures were
first introduced with the new Oberon-2 language. Oberon-2 is the
object-oriented successor of Modula-2. That's why this Modula-2 compiler has
borrowed many of Oberon's object oriented features into a language extension
for Modula-2.
Globally declared procedures may be associated with a record-type declared in
the same module. The procedures are said to be bound to the record type. Such a
record-type can be regarded as a class, while its type bound procedures serve
as its methods. The methods are responsible for accessing and updating
(possibly multiple) instances of a class.
The binding is expressed by the type of the receiver in the heading of a
procedure-declaration. The receiver may be either expressed as a variable
parameter of record-type T or a value parameter of type POINTER TO record-type
T. The procedure is bound to the type T and is considered local to it.
Binding a procedure P to a type T0 also implies the binding to any type T1 that
is an extension of T0. However, a procedure P' (with same name as P) may be
explicitly bound to T1, thus overriding the binding of P. P' is considered a
redefinition of P for T1. The formal parameters of P and P' must match. Both P'
and T1 must be declared in the same module. The same is true for the original P
and T. If T1 is exported P' may be exported, too. Unlike Oberon-2, this
Modula-2 compiler also accepts the declarations of private type bound
procedures, even when overriding an inherited method, which is exported from
another definition module. A private type bound procedure is only declared in
the implementation or program module, while its type may be declared in the
corresponding definition module.
If a designator v points to a record and P is a type-bound procedure, the v^.P
denotes that procedure P that is bound to the dynamic type of v. Note that this
may be a different procedure than the one bound to the static type of v.
Variable v is passed to P's receiver according to the parameter passing rules
of the procedure-declaration.
It is also possible to denote a parent procedure. If r is a receiver
VAR-parameter declared with record-type T, r.P^ denotes the redefined procedure
P bound to the base type of T. Also, if p is a receiver VAL-parameter declared
with the formal pointer-type P pointing to record-type T, p^.P^ denotes the
redefined procedure P bound to the base type of T. Denoting a parent procedure
always results in a static procedure reference, without taking care about
possible dynamic bindings.
In a forward declaration of a type-bound procedure, the receiver parameter must
be of the same type as in the actual procedure declaration. The same is true
for any type-bound procedure heading of a definition module, when it is fully
declared in the implementation module. The formal parameter lists of both
declarations must match.
Example:
TYPE
TREE = POINTER TO NODE;
NODE = RECORD
key : INTEGER;
left : TREE;
right : TREE;
END;
TYPE
CENTERTREE = POINTER TO CENTERNODE;
CENTERNODE = RECORD( NODE )
width : INTEGER;
subnode : TREE;
END;
(* declaring method Insert for TREE *)
PROCEDURE( self : TREE ) Insert( node : TREE );
VAR p, father : TREE;
BEGIN
p := self;
REPEAT
father := p;
IF node^.key = p^.key THEN
RETURN
ELSIF node^.key < p^.key THEN
p := p^.left
ELSE
p := p^.right;
END;
UNTIL p = NIL;
IF node^.key < father^.key THEN
father^.left := node;
ELSE
father^.right := node;
END;
node^.left := NIL;
node^.right := NIL;
END Insert;
(* overriding method Insert for extended CENTERTREE *)
PROCEDURE( self : CENTERTREE ) Insert( node : TREE );
BEGIN
WriteInt( node( CENTERTREE )^.width );
(* now calling parent method bound to TREE *)
self^.Insert^( node );
END Insert:
ΓòÉΓòÉΓòÉ 14.4. System Object Model ΓòÉΓòÉΓòÉ
The OS/2 System Object Model (SOM) is an object-oriented interface for
building, exposing, and manipulating software objects. The software objects are
packaged into binary class libraries. Unlike object models found in formal
object-oriented programming languages, such as C++ or Oberon-2, the design goal
for SOM is language neutrality. This means, that the user of an object and the
implementer of an object do not need to do their programming in the same
language. This independence of implementation and client language extends even
to the concept of subclassing (or, to put it into Modula-2 or Oberon-2 terms:
extending records ). A user of a SOM class can create a subclass that may or
may not be written in the same language as its superclass.
A principal benefit of using SOM is that SOM accomodates changes in
implementation details and even in certain parts of a class interface, without
breaking the binary class interface to a class library and without requiring
recompilation of client programs. As a matter of fact, if changes to a SOM
class do not require source code changes in client programs, then those client
programs will not need to be recompiled. This is not true of many
object-oriented languages, and it is therefore one of the chief benefits of
using SOM. For instance, SOM classes can undergo structural changes such as the
following, yet retain full backward, binary compatibility:
- Adding new methods
- Changing the size of an object by adding or deleting instance variables
- Inserting new classes above your class in the inheritance hierarchy
- Relocating methods upward in the class hierarchy
In short, typical kinds of changes can be made to an implementation that
evolving software systems experience over the time.
SOM is supposed to be language neutral for three reasons:
1. All SOM interactions consist of standard procedure calls. On systems that
have standard linkage convention for system calls, SOM interactions
conform to those conventions. Thus, most programming languages that can
make external procedure calls can use SOM. Under Modula-2 for OS/2 this
means calling external procedures, which are to be declared in definition
modules for dynamic link libraries. The OS/2 dynamic link libraries can
contain public SOM classes.
2. The form of the SOM Application Programming Interface, or API, can vary
from language to language, as a benefit of the SOM bindings. The way
that programmers invoke methods, create objects, and so on, can be
readily mapped into the semantic rules of a wide range of different
commercial object-oriented programming languages like C++, or even of
procedural languages like C or Modula-2. SOM bindings are the set of
macros and procedure calls that make implementing and using SOM classes
more convenient by tailoring the interface to a particular programming
language.
3. SOM supports several mechanisms for method resolution that can be readily
mapped into the semantics of a wide range of object-oriented programming
languages. Thus, SOM class libraries can be shared across object-oriented
languages that have different object models. That's why a SOM object can
potentially be accessed with three different forms of method resolution.
The SOM method resolution mechanisms are as follows:
Offset resolution This is roughly equivalent to the C++ virtual function
concept, or to the concept of type-bound procedures in Oberon-2 or this
extended Modula-2. Offset resolution implies a static scheme for typing
objects, with polymorphic behaviour based strictly on class inheritance.
It offers the best performance characteristics for SOM method resolution.
Methods accessible through offset resolution can be regarded as static
methods as they are considered a fixed aspect of an object's interface.
Name lookup resolution This is similar to that employed by Objective-C and
Smalltalk. Name resolution supports untyped (sometimes called dynamically
typed) access to objects, with polymorphism based on the actual protocols
that objects honor. Name resolution offers the opportunity to write code
to manipulate objects with little or no awareness of the type or shape of
the object when the code is compiled.
Dispatch function resolution This is a unique feature of SOM that permits
method resolution based on arbitrary rules known only in the domain of
the receiving object. Languages that require special entry or exit
sequences or local objects that represent distributed object domains are
good candidates for using dispatch function resolution. This technique
offers the highest degree of encapsulation for the implementation of an
object, with some cost in performance.
The original SOM design philosophy requires the usage of a special SOM
compiler, whose input is a language neutral class description, and whose
output are language-specific SOM-bindings. This Modula-2 compiler takes a more
direct approach. No special language independent SOM compiler is needed.
Existing or new SOM class libraries are simply described in terms of Modula-2
definition modules. And any new SOM class can be specified for Modula-2 in an
implementation module. Of course, this approach does not generate language
bindings for other languages, but the final binary SOM class libraries are
fully compatible with the OS/2 System Object Model. And if the implementor
wishes to offer the language-neutral form of a SOM class description, he can
still obtain the OS/2 SOM-compiler with all its reference manuals, and create
the language independent 'interface description language' file (IDL-file) for
the SOM-compiler. The SOM-compiler, in turn, could then produce the other
language bindings.
There is only one predefined root-class for all SOM-classes. Hence all
SOM-classes are finally derived from that root class. This root class is
called 'SOMObject'. It offers basic methods, whose facilities are useful for
any SOM-class. SOM also uses the concept of metaclasses. A metaclass is a
class whose instances are all classes (also known as class objects or class
descriptors). The metaclass provides factory methods for a regular class,
while a regular class provides instance methods. Factory methods provide
methods for creating and constructing instances of regular classes. The root
class for all SOM metaclasses is called 'SOMClass', which has been itself
derived from 'SOMObject'. A new SOM-class is usually bundled with an
associated metaclass in a single module.
ΓòÉΓòÉΓòÉ 14.4.1. SOM class definition ΓòÉΓòÉΓòÉ
Every SOM-class under OS/2 will be finally stored as a binary file in a dynamic
link library. If a programmer wishes to access such a library he will need a
language-specific application programming interface, called API. Such an API
consists of a class description suitable for an inclusion in the compilation
process. The C-language, for instance, uses special header files for that
purpose. This Modula-2 compiler uses definition modules for the dynamic link
libraries containing the SOM-classes. The absence of any corresponding
implementation module will not necessarily cause an error message from this
compiler. It rather tries to find a foreign precompiled dynamic link library or
a linker import library with the same name. The definition module is then
associated with that matching library. Or to express it in terms of OS/2 SOM:
The definition module containing the object oriented class description is the
required language binding for Modula-2.
The public interface of a SOM-class can be expressed for this compiler in a
Modula-2 definition module. The definition module describes the new class in
terms of a language-extended object-oriented syntax, using extended records and
type bound procedures. The following example demonstrates all the features of a
new SOM class interface description:
(*$DLL to be implemented as a dynamic link library *)
(*$XL+ Modula-2 language extensions: '_' allowed for symbol names *)
(*$CDECL+ C-style procedures *)
(*$A default alignment for record fields *)
DEFINITION MODULE Dog;
(************************************************************************
Public interface for a SOM class 'Dog'.
*************************************************************************)
IMPORT SOM; (* needed for basic SOM features *)
(************************************************************************
Additional IMPORT-, TYPE-, and CONST-declarations supporting Dog
*************************************************************************)
IMPORT ANIMAL; (* this module contains the parent class interface *)
IMPORT OS2DEF;
TYPE PSZ = OS2DEF.PSZ;
(*************************************************************************
SOM class API for Dog, including type-bound procedures
**************************************************************************)
(* These types are almost always needed for a SOM class description *)
TYPE PSOMClass = SOM.PSOMClass;
TYPE INTEGER4 = SOM.INTEGER4;
TYPE somMToken = SOM.somMToken;
TYPE somDToken = SOM.somDToken;
TYPE somMethodProc = SOM.somMethodProc;
TYPE PDog = POINTER TO Dog;
TYPE PM_Dog = POINTER TO M_Dog;
(* major and minor version numbers for new SOM class 'Dog' *)
CONST
Dog_MajorVersion = 0;
Dog_MinorVersion = 0;
(* Every SOM class has a public <class>ClassData structure,
whose fields are tokens needed for method resolution.
In this example there are 4 token fields for the 4 methods
of the SOM class 'Dog'.
*)
TYPE
DogClassDataStructure = RECORD
classObject : PSOMClass;
setBreed : somMToken;
setColor : somMToken;
getBreed : somMToken;
getColor : somMToken;
END;
VAR
DogClassData : DogClassDataStructure;
(* Class 'Dog' is expressed as an extension of record type 'Animal'
inheriting all record fields and type bound procedures.
The directive '$SOM+' tells the compiler to treat the new record
type 'Dog' as a SOM class, and not as an extended Modula-2 record.
*)
TYPE
(*$SOM+ *)
Dog = RECORD( ANIMAL.Animal ) END;
(*$SOM- *)
(* Every SOM class must have <class>NewClass procedure *)
PROCEDURE DogNewClass
(
majorVersion : INTEGER4;
minorVersion : INTEGER4
) : PSOMClass;
(* A SOM class may have type bound procedures (also known as methods).
For every of the following type bound procedures a corresponding
token field must have been specified in a previous <class>ClassData
record variable.
*)
PROCEDURE( Self : PDog ) setBreed
(
myBreed : OS2DEF.PSZ
);
PROCEDURE( Self : PDog ) setColor
(
myColor : OS2DEF.PSZ
);
PROCEDURE( Self : PDog ) getBreed() : OS2DEF.PSZ;
PROCEDURE( Self : PDog ) getColor() : OS2DEF.PSZ;
(*************************************************************************
SOM class API for M_Dog, including type-bound procedures.
This is the meta-class.
The meta-class is optional and should be used for factory methods
such as constructors or destructors. The meta class syntax is
similar to the one of the regular class.
**************************************************************************)
CONST
M_Dog_MajorVersion = 0;
M_Dog_MinorVersion = 0;
TYPE
M_DogClassDataStructure = RECORD
classObject : PSOMClass;
newDog : somMToken;
END;
VAR
M_DogClassData : M_DogClassDataStructure;
TYPE
(*$SOM+ *)
M_Dog = RECORD( ANIMAL.M_Animal ) END;
(*$SOM- *)
PROCEDURE M_DogNewClass
(
majorVersion : INTEGER4;
minorVersion : INTEGER4
) : PSOMClass;
PROCEDURE( Self : PM_Dog ) newDog
(
sound : OS2DEF.PSZ;
breed : OS2DEF.PSZ;
color : OS2DEF.PSZ
) : PDog;
END Dog.
ΓòÉΓòÉΓòÉ 14.4.2. SOM class usage ΓòÉΓòÉΓòÉ
An application programmer can fully use a predefined SOM-class. The definition
of a SOM-class is made available for a Modula-2 program through an IMPORT
statement. Commonly the program will create one or more instances of the
SOM-class. In this compiler this can simply be achieved using standard
procedure NEW, which makes an internal call to the SOM procedure 'somNew'.
Almost all of this compiler's object-oriented features can be applied to
instances of a SOM-class, too. This includes dynamic type-tests, designated
type-guards, regional type-guards, and calling type-bound procedures. So
working with instances of a SOM-class is as easy as working with ordinary
extended records. However, the data fields of a SOM-class cannot be accessed
directly. They are only indirectly accessable by the implementation's
type-bound procedures (that is, the public methods of the SOM-class). That's
why in a record definition for a SOM-class the field-components are never
specified as record fields.
This compiler will translate the following language constructs into appropriate
internal SOM procedure calls, if applied to instances of SOM-classes:
Dynamic type test The dynamic type of a SOM object can be tested using the
IS-relation. The compiler produces code for calling the SOM function
procedure 'somIsA'. The method 'somIsA' is inherited from the basic
SOM-class 'SOMObject'.
Type guard Every designated type guard selector and every regional type guard
for SOM-objects is translated by this compiler into a call to the
SOM-procedure 'somIsA'. Its boolean return value is tested during runtime
and 'SYSTEM.ExitProgram' is called for abnormal program termination in
case of a type guard failure.
Calling type-bound procedures The calling of procedures bound to a SOM-class
type involves the internal invocation of runtime procedure
'SYSTEM.somResolve', which in turn calls SOM-procedure 'somResolve'.
Calling a parent method Calling a parent method means a transfer of control to
a procedure bound to the base type of the current extended record. The
procedure of that base record type has the same name. This compiler
translates such a call into a static method resolution, using
'SYSTEM.somFindSMethodOk', which in turn calls SOM-procedure
'somFindSMethodOk'.
Creating a SOM-object using NEW A procedure call NEW( p ) is translated by the
compiler into a code sequence using among others a call to SOM-procedure
'somNew'.
Deleting a SOM-object using DISPOSE A procedure call DISPOSE( p ) is
translated by the compiler into a code sequence using among others a call
to SOM-procedure 'somFree'.
ΓòÉΓòÉΓòÉ 14.4.3. SOM class implementation ΓòÉΓòÉΓòÉ
This Modula-2 compiler supports the creation of new SOM-classes, which are to
be derived from existing classes. The public interface of the new SOM-class is
specified in a definition module, while the class itself with all the new or
overridden methods is to be written in an implementation module. The
implementation details are not visible to other client programs. The compiled
machine code of the class methods are usually stored in a dynamic link library.
So in case of implementing a new SOM-class in Modula-2, the definition module
must contain a comment embedded $DLL+ compiler directive at its very beginning.
The following sections will illustrate the implementation of a SOM-class.
Implementing a new SOM-class is slightly more complicated than the
implementation of a new extended Modula-2 (OBERON-style) class. The latter
needs only the declaration of all the type-bound procedures that were specified
as corresponding procedure headings in the definition module. SOM under OS/2
uses a more complicated method resolution mechanism before gaining access to a
public type-bound procedure for the SOM-class. The final dynamic link library
will offer a public record variable with token fields for all the methods of
the SOM-class. During runtime such a token can be transformed to the requested
procedure pointer, which can be called as a type bound procedure. Finding a
method this way is also known as offset resolution, because SOM treats the
public method tokens from the dynamic link library as a kind of index to a
SOM-internal virtual method table. The latter is maintained outside Modula's
runtime libraries. Apart from this offset resolution SOM also offers name
resolution and function dispatch resolution for gaining access to a method.
This is needed because some programming languages are not able to support the
basic offset resolution, while SOM is to be language-neutral. For above
mentioned reasons the implementation of a SOM-class in Modula-2 (and for any
other object-oriented language) is more complicated than implementing a pure
language-defined class.
The implementation module should contain the components listed below:
(1) SOM-specific comment-embedded compiler directives.
(2) Common IMPORT statements and individual declarations for constants,
types and variables.
(3) Implementation header with common declarations for constants, types
and variables.
(4) Apply and redispatch stubs for all the new methods.
(5) FORWARD declarations of all private (or privately overridden)
methods.
(6) Procedures for the creation of the new SOM-class.
(7) Method procedures for the new SOM-class.
(8) Module initialization.
If there is an explicit metaclass, it should be implemented in the same module
by specifying above mentioned components (3) to (7) again. A metaclass
provides class methods such as instance creation procedures or contructor
methods. If there is no explicit metaclass, then the one of the parent class
will be taken.
The following documentation sections provide a detailed description of all the
implementation components. The program examples contain placeholders, which
are embraced by '<' and '>', such as <class> or <identifier>. They are to be
replaced by actual name symbols when writing an implementation module.
Placeholder <class> always has to be replaced by the actual name of the new
SOM class.
ΓòÉΓòÉΓòÉ 14.4.3.1. SOM class implementation (Compiler directives) ΓòÉΓòÉΓòÉ
An implementation module for a new SOM-class must be always introduced with
some comment embedded compiler directives as follows:
IMPLEMENTATION MODULE <module-identifier>;
(*$XL+ Modula-2 extenmsions: '_' in symbol names, OOP facilities *)
(*$CDECL+ C-style procedures *)
(*$A default alignment for record fields *)
(*$LINK
LIBRARY <module-identifier> INITINSTANCE
PROTMODE
DATA MULTIPLE NONSHARED LOADONCALL
*)
The $LINK switch tells the Modula-2 compiler, that the final SOM-class is to
become a dynamic link library, which, contrary to default conventions, will
create a new data segment for every client application during run time.
ΓòÉΓòÉΓòÉ 14.4.3.2. SOM class implementation (IMPORTs and declarations) ΓòÉΓòÉΓòÉ
The implementation module begins with a series of common IMPORT statements
which are always needed for a new SOM-class. It is followed by Modula's
equivalent of the SOM compiler's passthru lines, containing further individual
IMPORTs and declarations for constants, types, and variables.
(*************************************************************************
Common IMPORTs for a SOM-class implementation.
**************************************************************************)
IMPORT SOM; (* basic SOM module, always needed *)
IMPORT <parent-module>; (* module with parent class, always needed *)
IMPORT Conversions; (* data conversion support *)
FROM SOMMISC IMPORT somDebug; (* debugging aid *)
FROM SOMMISC IMPORT somWriteString; (* debugging aid *)
FROM SOMMISC IMPORT somWriteLn; (* debugging aid *)
FROM SYSTEM IMPORT currentFile; (* debugging aid *)
FROM SYSTEM IMPORT currentLine; (* debugging aid *)
FROM SYSTEM IMPORT ADR;
(*************************************************************************
This is Modula's equivalent for the language-neutral SOM-emitter's
'passthru lines before'.
It consists of further individual IMPORTs for the implementation.
**************************************************************************)
IMPORT ..... ;
(*************************************************************************
This is Modula's equivalent for the language-neutral SOM-emitter's
'passthru lines after'.
It consists of private types, constants, variables and/or procedures
for the class implementation.
**************************************************************************)
CONST
.....
TYPE
.....
VAR
.....
ΓòÉΓòÉΓòÉ 14.4.3.3. SOM class implementation (implementation header) ΓòÉΓòÉΓòÉ
The implementation header for a new SOM-class, which is usually created by the
SOM-compiler from a language-neutral form of the class definition, must be
specified manually for this Modula-2 compiler, because current SOM versions do
not generate Modula-2 bindings. The basic layout for the implementation header
will be always the same, regardless of the new class. It may vary only with the
number of instance fields or the number of methods.
(*************************************************************************
Implementation header for the new SOM class.
(constants, types and variables)
**************************************************************************)
CONST
<class>_MaxNoMethods = <number>; (* number of new methods *)
<class>Debug = FALSE; (* enable/disable method debugging *)
(*
* Declare the C specific class data structure;
* Place it either here or in the definition module.
*)
TYPE
<class>CClassDataStructure = RECORD
parentMtab : SOM.somMethodTabs;
instanceDataToken : SOM.somDToken;
END;
VAR
<class>CClassData : <class>CClassDataStructure;
(*
* Temporary class data structure used only in class creation
*)
VAR
<class>tempClassData : SOM.somClassDataStructure;
(*
* Internal instance data fields
*)
TYPE
<class>Data = RECORD
<1st field> : <1st type>;
..........................
<nth field> : <nth type>;
END;
P<class>Data = POINTER TO <class>Data;
(*
* <class>GetData function, gives access to the instance data, if any
*)
PROCEDURE <class>GetData( Self : P<class> ) : P<class>Data;
BEGIN
RETURN SOM.somDataResolve( Self, <class>CClassData.instanceDataToken );
END <class>GetData;
(*
* SOM specific identifiers for all
* the new and also the overridden methods
*)
VAR
somId_<method-1> : SOM.somId;
................................
somId_<method-n> : SOM.somId;
ΓòÉΓòÉΓòÉ 14.4.3.4. SOM class implementation (apply/redispatch stubs) ΓòÉΓòÉΓòÉ
The implementation header also has to specify for every new method an apply
stub and a redispatch stub in addition to the regular method. These are needed
because of SOM's method resolution techniques, which might take place during
runtime when a client application cannot master the normal offset method
resolution.
Every method may have an unknown number of formal parameters, which are usually
passed as DWORDs on the stack. The apply stub has a formal parameter pointing
to the open-ended method parameter list, usually expressed as ARRAY OF DWORD.
Each DWORD corresponds to an actual numeric value parameter. Structures and
arrays are usually passed by reference, so as to only pass a DWORD-sized
address on the stack. A LONGREAL consists of 2 DWORDs. Passing long structures,
whose size is not exactly a multiple of DWORDs, should be the rare exception.
For such cases the method parameter list could also be declared as an ARRAY OF
BYTE parameter in the apply stub.
The apply stub has to extract and to pass on the parameters for the target
method, before transfering control to the target method, which belongs to the
'Self'-object.
The apply and redispatch stubs for a proper procedure method should be
implemented as follows for Modula-2:
(*
* apply stub for a new proper procedure method
*)
PROCEDURE somAP_<method>
(
somSelf : P<class>;
id : SOM.somId;
desc : SOM.somId;
VAR args : ARRAY OF SOM.DWORD
);
VAR
<param-0> : <basic-type>;
....................
<param-n> : <basic-type>;
BEGIN
<param-0> := args[0];
.................
<param-n> := args[n];
somSelf^.<method>( <param-0>, ... <param-n> );
END somAP_<method>;
(*
* redispatch stub for a new proper procedure method
*)
PROCEDURE somRD_<method>
(
somSelf : P<class>;
<param-0> : <basic-type>;
.......................
<param-n> : <basic-type>
);
VAR
retBuffer : SOM.somToken;
retValue : SOM.somToken;
args : SOM.ADDRESS;
dispatched : BOOLEAN;
BEGIN
retValue := ADR( retBuffer );
args := ADR( somSelf ) + SIZE( somSelf );
dispatched := somSelf^.somDispatch( retValue, somId_<method>, args^ );
END somRD_<method>;
If the method is a value returning function procedure, the apply and redispatch
stubs look slightly different:
(*
* apply stub for a new function procedure method
*)
PROCEDURE somAP_<method>
(
somSelf : P<class>;
id : SOM.somId;
desc : SOM.somId;
VAR args : ARRAY OF SOM.DWORD
) : <returning-type>;
VAR
<param-0> : <basic-type>;
....................
<param-n> : <basic-type>;
BEGIN
<param-0> := args[0];
.................
<param-n> := args[n];
RETURN somSelf^.<method>( <param-0>, ... <param-n> );
END somAP_<method>;
(*
* redispatch stub for a new function procedure method
*)
PROCEDURE somRD_<method>
(
somSelf : P<class>;
<param-0> : <basic-type>;
.......................
<param-n> : <basic-type>
) : <returning-type>;
VAR
retBuffer : <returning-type>;
retValue : SOM.somToken;
args : SOM.ADDRESS;
dispatched : BOOLEAN;
BEGIN
retValue := ADR( retBuffer );
args := ADR( somSelf ) + SIZE( somSelf );
dispatched := somSelf^.somDispatch( retValue, somId_<method>, args^ );
RETURN retBuffer;
END somRD_<method>;
ΓòÉΓòÉΓòÉ 14.4.3.5. SOM class implementation (FORWARD declarations) ΓòÉΓòÉΓòÉ
During the class creation some static forward references are made to private
(or privately overridden) methods, whose headings are not specified in the
definition module for the new SOM-class. For every such method a
forward-declaration has to be written. Such a declaration only contains the
procedure heading, to be followed by the keyword FORWARD.
(*
* sample forward declared private proper method
*)
PROCEDURE( Self : P<class> ) <private-method-id>
( <parameters> );
FORWARD;
(*
* sample forward declared private function method
*)
PROCEDURE( Self : P<class> ) <private-method-id>
( <parameters> ) : <returning-type>;
FORWARD;
ΓòÉΓòÉΓòÉ 14.4.3.6. SOM class implementation (class creation procedures) ΓòÉΓòÉΓòÉ
The OS/2 SOM system has to maintain runtime information for every class which
might come into existence at some stage of the information processing. A
SOM-class may stay alive even accross succeeding processes. But every SOM-class
has to be created before usage. Instances of a SOM-class can only be created
and accessed after class creation. The class creation is done by calling
special SOM procedures from its dynamic link library. The basic layout for
class creation procedures remains the same regardless of the class. SOM
maintains created classes through internal class descriptors. Such an internal
class descriptor is regarded by SOM as an instance of the metaclass and is
called a class object, whose pointer is made available after class creation.
(*************************************************************************
SOM-class creation procedures.
Only the <class>NewClass() procedure is publicly
available for client programs.
**************************************************************************)
(*
* class initialization
*)
PROCEDURE <class>somInitializeClass;
VAR
m : <class>; (* needed for static method references *)
c : SOM.PSOMClass;
md : SOM.somId;
BEGIN
c := <class>tempClassData.classObject;
md := SOM.somIdFromString( "----" );
(* Add the new methods, including apply and redispatch stubs,
to the new SOM class
*)
<class>ClassData.<method-1> := c^.somAddStaticMethod
( somId_<method-1>, md, m.<method-1>, somRD_<method-1>, somAP_<method-1> );
......................................................................
<class>ClassData.<method-n> := c^.somAddStaticMethod
( somId_<method-n>, md, m.<method-n>, somRD_<method-n>, somAP_<method-n> );
(* Override inherited methods, if any *)
c^.somOverrideSMethod( somId_<override-method-1>, m.<override-method-1> );
.....................................................................
c^.somOverrideSMethod( somId_<override-method-n>, m.<override-method-n> );
END <class>somInitializeClass;
(*
* class creation procedure
*)
PROCEDURE <class>somCreateClass
(
pClsObj : SOM.PSOMClass;
mClsObj : SOM.PSOMClass
);
VAR
classObject : SOM.PSOMClass;
BEGIN
classObject := mClsObj^.somNew();
<class>tempClassData.classObject := classObject;
classObject^.somInitClass
(
"<class>",
pClsObj,
SIZE( <class>Data ),
<class>_MaxNoMethods,
<class>_MajorVersion,
<class>_MinorVersion
);
<class>CClassData.instanceDataToken := classObject^.somGetInstanceToken();
<class>somInitializeClass();
<class>CClassData.parentMtab := classObject^.somGetPClsMtab();
classObject^.somSetClassData( SYSTEM.ADR( <class>ClassData ) );
classObject^.somClassReady();
(* make newly created class object visible *)
<class>ClassData.classObject := classObject;
END <class>somCreateClass;
(*
* public NewClass-procedure
*)
PROCEDURE <class>NewClass
(
majorVersion : SOM.INTEGER4;
minorVersion : SOM.INTEGER4
) : SOM.PSOMClass;
VAR
pClsObj : SOM.PSOMClass;
mClsObj : SOM.PSOMClass;
line : LONGCARD;
b : BOOLEAN;
BEGIN
(* Check the version numbers *)
IF ((majorVersion <> 0) AND (majorVersion <> <class>_MajorVersion)) OR
((minorVersion <> 0) AND (minorVersion > <class>_MinorVersion)) THEN
somWriteString( "<class>NewClass: Error, bad version numbers." );
somWriteLn();
b := Conversions.StrToLongCard( currentLine(), line );
SOM.SOMError( SOM.SOMERROR_BadVersion, currentFile(), line );
END;
(* Don't do anything if class object is already created. *)
IF <class>ClassData.classObject <> NIL THEN
RETURN <class>ClassData.classObject;
END;
(* Make sure the environment is initialized. *)
IF SOM.SOMClassMgrObject = NIL THEN
SOM.SOMClassMgrObject := SOM.somEnvironmentNew();
IF SOM.SOMClassMgrObject = NIL THEN
b := Conversions.StrToLongCard( currentLine(), line );
SOM.SOMError( SOM.SOMERROR_CouldNotStartup, currentFile(), line );
END;
(* SOMClassMgrObject initialized... *)
END;
(* Get the parent class object. *)
pClsObj := <parent-module>.<parent-class>NewClass( 0, 0 ); (* static *)
pClsObj := SOM.SOMClassMgrObject^.somFindClass
( SOM.somIdFromString( "<parent-class>" ), 0, 0 );
IF pClsObj = NIL THEN
b := Conversions.StrToLongCard( currentLine(), line );
SOM.SOMError( SOM.SOMERROR_NoParentClass, currentFile(), line );
END;
(* Get the metaclass object. *)
(* If explicit metaclass, get it from there *)
mClsObj := <metaclass>NewClass( 0, 0 ); (* static*)
mClsObj := SOM.SOMClassMgrObject^.somFindClass
( SOM.somIdFromString( "<metaclass>" ), 0, 0 );
(* else use parent's metaclass:
mClsObj := pClsObj^.mtab^.classObject;
*)
IF mClsObj = NIL THEN
b := Conversions.StrToLongCard( currentLine(), line );
SOM.SOMError( SOM.SOMERROR_NoMetaClass, currentFile(), line );
END;
SOM.somConstructClass
( <class>somCreateClass, pClsObj, mClsObj, SYSTEM.ADR( <class>tempClassData ) );
RETURN <class>ClassData.classObject;
END <class>NewClass;
ΓòÉΓòÉΓòÉ 14.4.3.7. SOM class implementation (method procedures) ΓòÉΓòÉΓòÉ
The final part of a new SOM-class implementation consists of the declarations
for all new and overridden methods. Every method must be fully declared as a
type-bound procedure. The following examples show the basic layouts for
implementing proper methods and function methods:
(*
* sample proper method procedure, type-bound to new SOM-class
*)
PROCEDURE( Self : P<class> ) <method-id> ( <parameters> );
VAR
somThis : P<class>Data;
BEGIN
somThis := <class>GetData( Self );
IF <class>Debug THEN
somDebug( "<class>", "<method-id>", currentFile(), currentLine() );
END;
.........
END <method-id>;
(*
* sample function method procedure, type-bound to new SOM-class
*)
PROCEDURE( Self : P<class> ) <method-id> ( <parameters> ) : <return-type>;
VAR
somThis : P<class>Data;
BEGIN
somThis := <class>GetData( Self );
IF <class>Debug THEN
somDebug( "<class>", "<method-id>", currentFile(), currentLine() );
END;
.........
RETURN <expression>;
END <method-id>;
ΓòÉΓòÉΓòÉ 14.4.3.8. SOM class implementation (module initialization) ΓòÉΓòÉΓòÉ
A SOM-class is usually implemented in a module, which can be compiled and
linked into a dynamic library. Such a library must have a per-instance
initialization routine, which looks like this:
(*
* sample module initialization for a new SOM-class
*)
BEGIN (* of class module *)
(* intialize SOM's environment, if not yet active *)
IF SOM.SOMClassMgrObject = NIL THEN
SOM.SOMClassMgrObject := SOM.somEnvironmentNew();
END;
(* initialize some record fields for class-supporting structures *)
<class>CClassData.parentMtab := NIL;
<class>ClassData.classObject := NIL;
(* find the identifier tokens for all the new or overridden methods *)
somId_<method_0> := SOM.somIdFromString( "<method_0>" );
............................................................
somId_<method_n> := SOM.somIdFromString( "<method_n>" );
(* if an explicit metaclass has been declared in the same module then
* initialize some record fields for metaclass-supporting structures
*)
M_<class>CClassData.parentMtab := NIL;
M_<class>ClassData.classObject := NIL;
(* if an explicit metaclass has been declared in the same module then
* find the identifier tokens for all the new or overridden methods
* belonging to the metaclass.
*)
somId_<method_0> := SOM.somIdFromString( "<method_0>" );
............................................................
somId_<method_n> := SOM.somIdFromString( "<method_n>" );
END <module-identifier>.
ΓòÉΓòÉΓòÉ 14.5. Bitwise operators ΓòÉΓòÉΓòÉ
This Modula-2 compiler provides extended arithmetic bitwise operators. They are
applicable for cardinal or integer operands and work bit by bit. Bitwise
operators are available only if the command line switch or the compiler
directive for the language extensions is enabled. Their purpose is to simplify
access to bitfields from the operating system interfaces such as the OS/2 2.x
or 3.0 APIs. And they also simplify the porting of low-level code written in
other programming languages. Most of the bitwise operators could also be
implemented using set-operators for variables with set-types. Using set-typed
variables with the corresponding set-operators acting upon them is the prefered
way to implement bitwise operations when source code portability is an issue.
Using the new bitwise operators should be restricted to low-level modules only.
The following table gives a summary of the new arithmetic bitwise operators:
OR bitwise or
XOR bitwise exclusive or
AND bitwise and
& bitwise and, same as AND
SHL bitwise shift left
SHR bitwise shift right
NOT unary bitwise negation
~ unary bitwise negation
ΓòÉΓòÉΓòÉ 14.6. New logical operators ΓòÉΓòÉΓòÉ
Modula-2 provides a set of logical operators for boolean expressions. There is,
however, no direct implementation for the logical exclusive 'OR'. An exclusive
'OR' yields a TRUE only if either a first or a second boolean expression is
TRUE, but not both of them at the same time. Example:
IF (Contition1 AND NOT Condition2) OR
(NOT Condition1 AND Condition2) THEN
(* execute if either, but not both conditions are met *)
END
If the command line switch or the compiler directive for the language
extensions is enabled then the above mentioned example could be easier
expressed with the new 'XOR' operator:
IF Condition1 XOR Condition2 THEN
(* execute if either, but not both conditions are met *)
END
ΓòÉΓòÉΓòÉ 14.7. Typed constants ΓòÉΓòÉΓòÉ
ΓûÉ ConstDecl = ConstDef | ConstVarDecl "=" TypedConst
ΓûÉ ConstVarDecl = Ident ":" FormalType
ΓûÉ TypedConst = ConstExpr | PointerConst | ArrayConst | RecordConst
ΓûÉ PointerConst = POINTER TO Qualident |
ΓûÉ PROCEDURE Qualident |
ΓûÉ "^" Qualident
ΓûÉ RecordConst = "(" FieldConstList ")"
ΓûÉ FieldConstList = FieldConst { ";" FieldConst }
ΓûÉ FieldConst = [ FieldId ":" TypedConst ]
ΓûÉ FieldId = Ident
ΓûÉ ArrayConst = "[" TypedConstList "]"
ΓûÉ TypedConstList = TypedConst { "," TypedConst }
The original Modula-2 language offers no facilities for initialized variables
with the exception of modules, which can contain statement-sequences for
assigning the starting values to variables. Though modules are executed
automatically at the beginning of the program execution, they are somehow
wasteful because of the assignments.
This compiler provides an additional way for initializing variables. If the
command line switch or the compiler directive for the language extensions is
enabled then variables can be declared with initial values with no need for any
explicit assignments. Initialized variables are called typed constants. Typed
constants are not restricted to basic types. They can also be used for
structured types such as arrays or records. Typed constants are like variable
declarations with additional specifications of their initial values. The linker
program places all typed constants in a special intialized segment along with
any internally produced strings and other constants. When the data segments are
loaded into memory by the operating system for the later program execution, all
the intial values are loaded with them. Typed constants are particularly useful
for long initialized tables. Constant variable declarations with typed
constants can only be specified within a 'CONST' section. The constant value
must be assignment compatible with the type of the corresponding constant
variable declaration.
A typed constant for an array-type consists of a list of typed constants for
the array elements. The list elements are separated by commas, and the list
itself is enclosed by squared brackets. If the number of typed constants in
that list is less than the number of declared array elements then the remaining
array elements are automatically initialized with binary zeroes. If the
declared variable has a character array type then a string can also be
specified as the corresponding typed constant.
A typed constant for a record-type consists of a list of field constants. The
field constants are separated by semicolons. Every field constant is introduced
by the field name, to be followed by a colon and the typed constant. The field
constant list itself is enclosed by the bracket pair '(' and ')'. The order of
the field constants must be the same as the one in the corresponding
record-type. Any field without a corresponding field constant is initialized
with binary zeroes (unless overlapped by case variants with specified field
constants).
A pointer constant can be specified for data pointers or for procedure
variables. They are finally fixed by the linker, because they are to represent
the addresses of variables or procedures. A data pointer is introduced by a
'POINTER TO' sequence, while a procedure constant has to be preceded by the
'PROCEDURE' keyword. An alternative short notation is also available for
pointer constants, using only a '^' sign, in which case the declared variable
type is consulted by the compiler for the correct choice between data or
procedure pointer. The pointer constant is always concluded by a (possibly
qualified) name of the variable or procedure, whose address is to be taken as
the constant value.
Examples:
TYPE
ArrayType = ARRAY [0..10] OF CHAR;
RecType = RECORD
Str : ArrayType;
Len : INTEGER;
Handler : PROC;
Next : POINTER TO RecType;
END;
...
PROCEDURE MyProc();
BEGIN
...
END MyProc;
...
VAR
YourRecVar : RecType;
...
CONST
MyRecVar : RecType =
(
Str : "MyString";
Len : 8;
Handler : ^MyProc;
Next : ^YourRecVar;
);
...
The constant variable declaration may also specify a one-dimensional open array
as its type. In this case the number of reserved array elements is determined
by the number of typed constant elements. Example:
CONST
(* zero terminated initialized text string variables *)
Msg1 : ARRAY OF CHAR = "This is message 1";
Msg2 : ARRAY OF CHAR = "This is another text string";
(* initialized integer array variable with 6 elements *)
Token : ARRAY OF INTEGER = [ 1, 2, 3, 4, 5, 6 ];
ΓòÉΓòÉΓòÉ 14.8. Multidimensional open array parameters ΓòÉΓòÉΓòÉ
ΓûÉ FormalType = { ARRAY OF } Qualident
Standard Modula-2 offers open arrays for formal-parameters for
procedure-declarations or for procedure-types. In this compiler, if the command
line switch or the compiler directive for the language extensions is enabled,
multidimensional arrays are permitted. The corresponding actual parameter must
be an assignment-compatible array with the same number of dimensions. The high
bounds for all dimensions are automatically pushed on the stack before any of
the actual parameters. Each dimension size can be determined by HIGH(x) or a
SYSTEM.LEN(x) expressions. Example:
PROCEDURE MyProc( x : ARRAY OF ARRAY OF ARRAY OF INTEGER );
VAR
High0 : LONGCARD;
High1 : LONGCARD;
High2 : LONGCARD;
Len0 : LONGCARD;
Len1 : LONGCARD;
Len2 : LONGCARD;
BEGIN
High0 := HIGH( x ); (* HIGH outer dimension *)
High1 := HIGH( x[0] ); (* HIGH middle dimension *)
High2 := HIGH( x[0][0] ); (* HIGH inner dimension *)
Len0 := SYSTEM.LEN( x, 0 ); (* outer dimension length *)
Len1 := SYSTEM.LEN( x, 1 ); (* middle dimension length *)
Len2 := SYSTEM.LEN( x, 2 ); (* inner dimension length *)
...
END MyProc;
...
ΓòÉΓòÉΓòÉ 14.9. Segmentation ΓòÉΓòÉΓòÉ
As mentioned in the documentation section about the memory-models the Intel
80x86 CPU family provides only a segmented memory access. In the 16-bit
memory-models this means that a pointer range cannot exceed 64 KB. 16-bit
segment selectors are needed for accessing various segments. System maintained
segment descriptors contain information about segment sizes and their linear
starting addresses. Because of this, two different pointer kinds are available.
A near pointer contains the offset relative to memory default segment, whose
selector is stored in the default data segment register DS for data pointers,
or in the default code segment register CS for code pointers. A far pointer
contains in addition to an offset portion a 16-bit segment selector.
Depending on the memory-model chosen for the compilation, the default pointer
is a far or near one. The default pointer can be overwritten by this compiler,
however, if the command line switch or the compiler directive for the language
extensions is enabled, using the new 'FAR' or 'NEAR' keywords. These keywords
are available in the following contexts:
ΓûÉ ProcedureHeading = [ NEAR | FAR ] PROCEDURE
ΓûÉ [ Receiver ] Ident [ FormalParameters ]
ΓûÉ Receiver = "(" [ [ NEAR | FAR ] VAR ] Ident : Ident ")"
ΓûÉ FormalParameters = "(" FPSectionList ")" [ ":" Qualident ]
ΓûÉ FPSectionList = [ FPSection { ";" FPSection } ]
ΓûÉ FPSection = [ [ NEAR | FAR ] VAR ] IdentList ":" FormalType
ΓûÉ PointerType = [ NEAR | FAR ] POINTER TO Type
ΓûÉ ProcedureType = [ NEAR | FAR ] PROCEDURE [ FormalTypeList ]
ΓûÉ FormalTypeList = "(" FTSectionList ")" [ ":" Qualident ]
ΓûÉ FTSectionList = [ FTSection { "," FTSection } ]
ΓûÉ FTSection = [ [ NEAR | FAR ] VAR ] FormalType
A procedure or procedure-type can be declared as FAR or NEAR. A far procedure
is used for far code entry and exit, with segmented call and return
instructions produced by the code generator. A near procedure is used for code
which is to reside in the same code segment, using only the code offset
portions for the call and return instructions as produced by the code
generator.
A pointer type can be optionally declared as FAR or NEAR, overwriting the
default attribute, which is NEAR for near data memory-models Tiny, Small,
Compact, or Flat32, and which is FAR otherwise. A far pointer consists of
16-bit segment : 16-or-32-bit offset
while a near pointer only contains the
16-or-32-bit offset
portion.
A formal VAR parameter may be preceded by a FAR or NEAR keyword, thus
overwriting the default attribute, which is FAR for far data memory-models
Compact or Large, and which is NEAR otherwise. A FAR VAR parameter points to a
variable passed by reference, which resides in a different data segment. A NEAR
VAR parameter, also passed by reference, is supposed to reside in the same
default data segment.
ΓòÉΓòÉΓòÉ 14.10. Extended imports ΓòÉΓòÉΓòÉ
ΓûÉ Import = FROM Ident IMPORT IdentList ";" |
ΓûÉ IMPORT [ FROM ] Ident { "," [ FROM ] Ident }
Standard-Modula offers no way to import all the identifiers from another
definition module in an unqualified manner without explicitly specifying all
the needed identifiers, e.g.
FROM WINFRAME IMPORT
WinCreateStdWindow,
WinFlashWindow,
:
BEGIN
(* unqualified reference to imported WinCreateStdWindow *)
WinCreateStdWindow( ... );
(* qualified reference available, too *)
WINFRAME.WinCreateStdWindow( ... );
END ...;
However, when dealing with a large number of items to be imported in an
unqualified manner, it will mean writing long import lists. This will often be
the case when writing programs for the OS/2 Presentation Manager or the
Workplace Shell. That's why this compiler offers a special extension to the
import statement, namely the unqualified import of a whole definition module.
This extension causes the parser to look for identifiers not only within the
current module being compiled, but also in the imported definition module. The
visibility scope extends into the unqualified imported definition modules. Even
if two unqualified imported definition modules contain the same identifier, the
first one is shadowed by the second one. To pick up above mentioned example,
this time using the new import construct:
IMPORT FROM WINFRAME;
:
BEGIN
(* unqualified reference to imported WinCreateStdWindow *)
WinCreateStdWindow( ... );
(* qualified reference still available, too *)
WINFRAME.WinCreateStdWindow( ... );
END ...;
ΓòÉΓòÉΓòÉ 15. Language grammar ΓòÉΓòÉΓòÉ
The Modula-2 and INLINE assembler languages are formed according to a set of
grammar rules. An extended Backus-Naur formalism is used to describe the
syntax.
This compiler's Modula-2-grammar does not only support Wirth's standard syntax
but also some powerful language-extensions.
The INLINE-assembler-grammar has been designed with ease of use in mind. Some
of the reserved words such as 32-bit register names are only available for
32-bit programs.
ΓòÉΓòÉΓòÉ 15.1. Modula-2 grammar ΓòÉΓòÉΓòÉ
CompilationUnit = DefModule | [ IMPLEMENTATION ] ProgramModule
ProgramModule = MODULE Ident Priority ";" { Import } Block Ident "."
Priority = [ "[" ConstExpr "]" ]
Import = FROM Ident IMPORT IdentList ";" |
IMPORT [ FROM ] Ident { "," [ FROM ] Ident }
IdentList = Ident { "," Ident }
DefModule = DEFINITION MODULE Ident ";" { Import } { Def } END Ident "."
Def = CONST { ConstDef ";" } | TYPE { TypeDef ";" } |
VAR { VarDecl ";" } | ProcedureHeading ";"
ConstDef = Ident "=" ConstExpr
TypeDef = TypeDecl | Ident
ProcedureHeading = [ NEAR | FAR ] PROCEDURE
[ Receiver ] Ident [ FormalParameters ]
Receiver = "(" [ [ NEAR | FAR ] VAR ] Ident : Ident ")"
FormalParameters = "(" FPSectionList ")" [ ":" Qualident ]
FPSectionList = [ FPSection { ";" FPSection } ]
FPSection = [ [ NEAR | FAR ] VAR ] IdentList ":" FormalType
FormalType = { ARRAY OF } Qualident
Qualident = { Qualifier "." } Ident
Qualifier = Ident
Block = { Decl } [ BEGIN StmtSeq ] END
Decl = CONST { ConstDecl ";" } | TYPE { TypeDecl ";" } |
VAR { VarDecl ";" } | ProcedureDecl | ModuleDecl
ConstDecl = ConstDef | ConstVarDecl "=" TypedConst
ConstVarDecl = Ident ":" FormalType
TypeDecl = Ident = Type
VarDecl = VarIdent { "," VarIdent } ":" Type
VarIdent = Ident [ FarPointerConst ]
FarPointerConst = "[" ConstExpr ":" ConstExpr "]"
ProcedureDecl = ProcedureHeading ";" Block Ident |
ProcedureHeading ";" FORWARD
ModuleDecl = MODULE Ident [ Priority ] { Import } [ Export ]
Block Ident
Export = EXPORT [ QUALIFIED ] IdentList ";"
TypedConst = ConstExpr | PointerConst | ArrayConst | RecordConst
PointerConst = POINTER TO Qualident |
PROCEDURE Qualident |
"^" Qualident
RecordConst = "(" FieldConstList ")"
FieldConstList = FieldConst { ";" FieldConst }
FieldConst = [ FieldId ":" TypedConst ]
FieldId = Ident
ArrayConst = "[" TypedConstList "]"
TypedConstList = TypedConst { "," TypedConst }
Type = SimpleType | ArrayType | RecordType | SetType |
PointerType | ProcedureType
SimpleType = Qualident | Enumeration | SubrangeType
Enumeration = "(" IdentList ")"
SubrangeType = [ BaseType ] "[" ConstExpr ".." ConstExpr "]"
BaseType = Qualident
ArrayType = ARRAY IndexType { "," IndexType } OF Type
IndexType = SimpleType
RecordType = RECORD [ "(" RecordBase ")" ] FieldListSeq END
RecordBase = Qualident
FieldListSeq = FieldList { ";" FieldList }
FieldList = [ IdentList ":" Type | Variants ]
Variants = CASE [ Ident ":" ] Qualident
OF VariantList ElseVariant END
VariantList = Variant { "|" Variant }
ElseVariant = ELSE FieldListSeq
Variant = CaseLabelList ":" FieldListSeq
SetType = SET OF SimpleType
PointerType = [ NEAR | FAR ] POINTER TO Type
ProcedureType = [ NEAR | FAR ] PROCEDURE [ FormalTypeList ]
FormalTypeList = "(" FTSectionList ")" [ ":" Qualident ]
FTSectionList = [ FTSection { "," FTSection } ]
FTSection = [ [ NEAR | FAR ] VAR ] FormalType
StmtSeq = Stmt { ";" Stmt }
Stmt = [ Assignment | ProcedureCall | IfStmt | CaseStmt |
WhileStmt | RepeatStmt | LoopStmt | ForStmt |
WithStmt | EXIT | ReturnStmt ]
Assignment = Designator ":=" Expr
ProcedureCall = Designator [ ActualParams ]
ActualParams = "(" [ Expr { "," Expr } ] ")"
IfStmt = IF BoolExpr THEN StmtSeq
{ ELSIF BoolExpr THEN StmtSeq }
[ ELSE StmtSeq ] END
BoolExpr = Expr
CaseStmt = CASE Expr OF Case { "|" Case } [ ELSE StmtSeq ] END
Case = CaseLabelList ":" StmtSeq
CaseLabelList = CaseLabels { "," CaseLabels }
CaseLabels = ConstExpr [ ".." ConstExpr ]
WhileStmt = WHILE BoolExpr DO StmtSeq END
RepeatStmt = REPEAT StmtSeq UNTIL BoolExpr
LoopStmt = LOOP StmtSeq END
ForStmt = FOR ControlVar ":=" Expr TO Expr [ BY ConstExpr ]
DO StmtSeq END
ControlVar = Ident
WithStmt = WITH RecDesignator DO StmtSeq END
RecDesignator = Designator | Guard
Guard = Qualident ":" Qualident
ReturnStmt = RETURN [ Expr ]
ConstExpr = Expr
Expr = SimpleExpr [ Relation SimpleExpr ]
SimpleExpr = [ "+" | "-" ] Term { AddOperator Term }
Term = Factor { MulOperator Factor }
Factor = CharConst | Number | String | Set |
Designator [ ActualParams ] | "(" Expr ")" |
Not Factor
Set = Qualident "{" [ ElemList ] "}"
ElemList = Elem { "," Elem }
Elem = Expr { ".." Elem }
Relation = "=" | "<>" | "#" | "<" | "<=" | ">" | ">=" | IN | IS
AddOperator = "+" | "-" | OR | XOR
MulOperator = "*" | "/" | DIV | MOD | AND | "&" | SHL | SHR
Not = NOT | "~"
Designator = Qualident { Selector }
Selector = "." Ident | "[" IndexList "]" | "^" | TypeGuard
IndexList = Expr { "," Expr }
TypeGuard = "(" Qualident ")"
CharConst = "'" Character "'" | Digit { HexDigit } "X" |
OctalDigit { OctalDigit } "C"
String = "'" { Character } "'" | """ { Character } """
Number = Integer | Real
Integer = Digit { Digit } | OctalDigit { OctalDigit } "B" |
Digit { HexDigit } "H"
Real = Digit { Digit } "." { Digit } [ ScaleFactor ]
ScaleFactor = "E" [ "+" | "-" ] Digit { Digit }
HexDigit = Digit | "A" | "B" | "C" | "D" | "E" | "F"
Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
OctalDigit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7"
Ident = FirstLetter { "_" | Letter | Digit }
FirstLetter = "_" | Letter
ΓòÉΓòÉΓòÉ 15.2. INLINE assembler grammar ΓòÉΓòÉΓòÉ
Any identifier from the token stream is assigned to either token 'Ident',
'ConstId', 'Qualifier' or 'Keyword'. From a very strict lexical point of view
they look much the same. However, the lexical analyzer of this INLINE assembler
has been expanded so that it can find proper token assignments through detailed
semantic checks. The lexical rules for numbers and character constants are
equal to those of Modula-2.
Inline = "(" InlineList ")"
InlineList = CodeList { InstrList }
InstrList = CSEG eol CodeList | DSEG eol DataList |
SSEG eol DataList | ISEG eol InitList
CodeList = Code { eol Code }
Code = CodeInstr | CondCode
DataList = Data { eol Data }
Data = DataInstr | CondData
InitList = Init
Init = InitInstr | CondInit
CondCode = IF CondExpr [ THEN | eol ] CodeList [ ELSE CodeList ] ENDIF
CondData = IF CondExpr [ THEN | eol ] DataList [ ELSE DataList ] ENDIF
CondInit = IF CondExpr [ THEN | eol ] InitList [ ELSE InitList ] ENDIF
CondExpr = ImmedExpr
CodeInstr = [ Label ] [ CodeStmt ]
CodeStmt = InstrId [ Operand [ Operand [ Operand ] ] ] |
CodeDirective CodeConstLst
Label = AnyId ":"
InstrId = [ RepPrefix ] AnyId | AND | OR | XOR | NOT | DIV | SHL | SHR
RepPrefix = REP | REPE | REPZ | REPNE | REPNZ
AnyId = Ident | ConstId
Operand = UserReg | SegReg | MemDesignator | ImmedExpr | SegExpr |
OfsExpr | FloatStack | ControlReg | DebugReg | TestReg
UserReg = AX | BX | CX | DX | SI | DI | BP | SP |
AL | AH | BL | BH | CL | CH | DL | DH | Reg32
Reg32 = EAX | EBX | ECX | EDX | ESI | EDI | EBP | ESP
SegReg = CS | DS | ES | SS | FS | GS
FloatStack = ST [ "(" ImmedExpr ")" ]
ControlReg = CR0 | CR2 | CR3
DebugReg = DR0 | DR1 | DR2 | DR3 | DR6 | DR7
TestReg = TR6 | TR7
MemDesignator = [ Attr ] Mem
Mem = MemId [ DispExpr | "[" IndexExpr "]" ] | "[" MemExpr "]"
DispExpr = Unary DispTerm { Unary DispTerm }
DispTerm = ImmedTerm
IndexExpr = IndexStart { Unary DispTerm | RegTerm }
IndexStart = [ "-" ] DispTerm | RegTerm
RegTerm = BX | BP | SI | DI | reg32 [ "*" ScaleFactor ]
ScaleFactor = ImmedFactor
MemExpr = MemStart { Unary DispTerm | "+" RegTerm }
MemStart = [ "-" ] DispTerm | RegTerm | MemId
MemId = Designator
Attr = FAR | NEAR | SHORT |
SegReg ":" | SizeAttr | SizeAttr SegReg ":"
SizeAttr = SizeId [ PTR ] | LOW | HIGH
SizeId = BYTE | WORD | DWORD | FWORD | QWORD | TBYTE
SegExpr = SEG MemId
OfsExpr = OFFSET MemId
Designator = Qualident { "." MemberId }
Qualident = { QualifierList "." } UserId
QualifiedConst = { QualifierList "." } ConstId
QualifierList = Qualifier { "." Qualifier }
UserId = Ident | Keyword
MemberId = AnyId | Keyword
CodeDirective = DataDirective
CodeConstLst = CodeConst { "," CodeConst }
CodeConst = ImmedExpr | SegExpr | OfsExpr | FarPointerConst |
Designator | DupSize DUP "(" CodeConstLst ")"
FarPointerConst = "[" ImmedExpr ":" ImmedExpr "]"
DataInstr = [ DataName DataDirective DataAttrLst ]
DataName = [ AnyId ]
DataDirective = DB | DW | DD | DF | DQ | DT
DataAttrLst = DataAttr { ",' DataAttr }
DataAttr = "?" | DupSize DUP "(" DataAttrLst ")"
DupSize = ImmedExpr
InitInstr = [ InitName InitDecl ]
InitName = [ AnyId ]
InitDecl = CodeDirective CodeConstLst
ImmedExpr = SimpleImmedExpr { Relation SimpleImmedExpr }
SimpleImmedExpr = [ Unary ] ImmedTerm { AddOperator ImmedTerm }
ImmedTerm = ImmedFactor { MulOperator ImmedFactor }
ImmedFactor = QualifiedConst | Number | String | CharConst |
"(" ImmedExpr ")" | NOT ImmedFactor |
TYPE VarId | SIZE VarId | LENGTH VarId
VarId = Designator
AddOperator = "+" | "-" | OR | XOR
MulOperator = "*" | "/" | DIV | MOD | "&" | AND | SHL | SHR
Unary = "+" | "-"