XDS allows you to mix Modula-2, Oberon-2, C, and Assembler modules, libraries, and object files in one project.
It is not necessary to notify the compiler of using Modula-2 objects in Oberon-2 module and vice versa. The compiler will detect the language automatically when processing symbol files on IMPORT clause.
In Oberon-2 the basic types have the same length on all platforms. In Modula-2 the size of types INTEGER, CARDINAL and BITSET may be different and depends on the value of the M2BASE16 option. The following table summarizes the correspondence between the basic types.
The system types INT and CARD correspond to Modula-2 INTEGER and CARDINAL types respectively. We recommend to use INT and CARD in Oberon-2 when importing Modula-2 modules. For example, if the procedure Foo is defined in the Modula-2 definition module M as
DEFINITION MODULE M; PROCEDURE Foo(VAR x: INTEGER); END M.
its portable usage in Oberon-2 is as follows:
VAR x: SYSTEM.INT; . . . M.Foo(x);
XDS allows any Modula-2 data structures to be used in Oberon-2 modules, even those that can not be defined in Oberon-2 (e.g. variant records, range types, set types, enumerations, etc).
However, usage of Modula-2 types in Oberon-2 and vice versa is restricted. Whenever possible XDS tries to produce the correct code. If a correct translation is impossible, an error is reported:
Standard procedures NEW and DISPOSE are always applied according to the language of a parameter’s type. For example, for the following declarations in an Oberon-2 module:
TYPE Rec = RECORD END; MP = POINTER ["Modula"] TO Rec; (* Modula pointer *) OP = POINTER TO Rec; (* Oberon pointer *) VAR m: MP; o: OP;
the call NEW(m) will be treated as a call to the Modula-2 default ALLOCATE, while NEW(o) will be treated as a call of the standard Oberon-2 run-time routine. See also Direct language specification.
Implicit memory deallocation (garbage collection) is applied to Oberon-2 objects only. If a variable of a Modula-2 pointer type is declared in an Oberon-2 module, it shall be deallocated explicitly.
(* Modula-2*) DEFINITION MODULE m2; TYPE Rec = RECORD (* a record with variant parts *) CASE tag: BOOLEAN OF |TRUE: i: INTEGER; |FALSE: r: REAL; END; END; Ptr = POINTER TO Rec; VAR r: Rec; p: Ptr; PROCEDURE Foo(VAR r: Rec); END m2. (* Oberon-2 *) MODULE o2; IMPORT m2; (* import of a Modula-2 module *) VAR r: m2.Rec; (* using the Modula-2 record type *) p: m2.Ptr; (* using the Modula-2 pointer type *) x: POINTER TO m2.Rec; BEGIN NEW(p); (* Modula-2 default ALLOCATE *) NEW(x); (* Oberon-2 NEW *) m2.Foo(r); m2.Foo(p^); m2.Foo(x^); END o2.
It is important to remember that Modula-2 and Oberon-2 have different approaches to memory utilization. When a program contains both Modula-2 and Oberon-2 modules, garbage collection is used. See Memory management for more information.
The compiler must know the implementation language of a module to take into account different semantics of different languages and to produce correct code.
In some cases, it is necessary for a procedure or data type to be implemented according to the rules of a language other than that of the whole module. In XDS, it is possible to explicitly specify the language of a type or object. Direct language specification (DLS) is allowed either if language extensions are enabled or if the module SYSTEM is imported.
In a record, pointer, or procedure type declaration, or in a procedure declaration, the desired language (or, more precisely, the way in which that declaration is treated by the compiler) can be specified as "[" language "]" immediately following the keyword RECORD, POINTER, or PROCEDURE. language can be a string or integer constant expression /We recommend to use strings, integer values are preserved for backward compatibility./ :
UntracedPtr = POINTER ["Modula"] TO Rec;
Here UntracedPtr is defined as a Modula-2 pointer, hence all variables of that type will not be traced by garbage collector.
PROCEDURE ["C"] sig_handler (id : SYSTEM.int);
. . .
Here sig_handler has C calling and naming conventions, so it can be installed as a signal handler into C run-time support.
A direct language specification clause placed after a name of a field, constant, type, or variable points out that the name of the object will be treated according to the rules of the specified language.
TYPE Rec ["C"] = RECORD name ["C"]: INTEGER; END; CONST pi ["C"] = 3.14159; VAR buffer["C"]: POINTER TO INTEGER;
Note: In ISO Modula-2, an absolute address may be specified for a variable after its name in square brackets, so the empty brackets are required in the last line.
A procedure name is treated according to the language of its declaration, so in the following declaration:
PROCEDURE ["C"] Foo;
both the procedure type and the procedure name are treated according to the C language rules. Note: If you are using a C++ compiler, the Foo function should be declared with C name mangling style. Consult your C++ manuals for further information.
Special efforts were made in XDS to provide convenient interface to other languages, primarily to the C language. The main goal is to allow direct usage of existing C libraries and APIs in Modula-2/Oberon-2 programs.
A direct language specification clause may appear immediately after keywords DEFINITION MODULE. The effect is that all objects defined in that module are translated according to the specified language rules, thus making unnecessary direct language specifications for each object.
Several options are often used in foreign definition modules. See Foreign language interface for the description of options used to create a foreign definition module.
<*+ M2EXTENSIONS *> <*+ CSTDLIB *> (* C standard library *) <*+ NOHEADER *> (* we already have header file *) DEFINITION MODULE ["C"] string; IMPORT SYSTEM; PROCEDURE strlen(s: ARRAY OF CHAR): SYSTEM.size_t; PROCEDURE strcmp(s1: ARRAY OF CHAR; s2: ARRAY OF CHAR): SYSTEM.int; END string.
Take the following considerations into account when designing your own foreign definition module:
In some cases, it may be desirable not to write a foreign definition module but to use some C or API functions directly. XDS compilers allow a function to be declared as external.
The declaration of an external procedure consists of a procedure header only. The procedure name in the header is prefixed by the symbol "/".
PROCEDURE ["C"] / putchar(ch: SYSTEM.int): SYSTEM.int;
The compiler performs all semantic checks for an object or type according to its language specification. Any object declared as that of Modula-2 or Oberon-2 is subject to Modula-2 or Oberon-2 compatibility rules respectively. The compiler uses relaxed compatibility rules for objects and types declared as "C", "Pascal", "StdCall", and "SysCall".
Two pointer type objects are considered assignment compatible, if
VAR x: POINTER TO T; y: POINTER TO T; z: POINTER ["C"] TO T; BEGIN x := y; -- error y := z; -- ok z := y; -- ok
For procedures declared as "C", "Pascal", "StdCall", or "SysCall", the type compatibility rules for parameters are significantly relaxed:
If a formal value parameter is of the type declared as POINTER TO T, the actual parameter can be of any of the following types:
If a formal parameter is an open array of type T, the actual parameter can be of any of the following types:
This relaxation, in conjunction with the SYSTEM.REF function procedure, simplifies Modula-2/Oberon-2 calls to C libraries and the target operating system API, preserving the advantages of the type checking mechanism provided by that languages.
TYPE Str = POINTER TO CHAR; Rec = RECORD ... END; Ptr = POINTER TO Rec; PROCEDURE ["C"] Foo(s: Str); ... END Foo; PROCEDURE ["C"] Bar(p: Ptr); ... END Bar; PROCEDURE ["C"] FooBar(a: ARRAY OF CHAR); ... END FooBar; VAR s: Str; a: ARRAY [0..5] OF CHAR; p: POINTER TO ARRAY OF CHAR; R: Rec; A: ARRAY [0..20] OF REC; P: POINTER TO REC; Foo(s); (* allowed - the same type *) Foo(a); (* allowed for the "C" procedure *) Foo(p^); (* allowed for the "C" procedure *) Bar(R); (* the same as Bar(SYSTEM.REF(R)); *) Bar(A); (* allowed for the "C" procedure *) Bar(P); (* allowed for the "C" procedure *) FooBar(s); (* allowed for the "C" procedure *)
It is a standard practice in C programming to ignore the result of a function call. Some standard library functions are designed taking that practice into account. E.g. the string copy function accepts the destination string as a variable parameter (in terms of Modula-2) and returns a pointer to it:
extern char *strcpy(char *, const char *);
In many cases, the result of the strcpy function call is ignored.
In XDS, it is possible to ignore results of functions defined as "C", "Pascal", "StdCall", or "SysCall". Thus, the function strcpy defined in the string.def foreign definition module as
PROCEDURE ["C"] strcpy(VAR d: ARRAY OF CHAR;
s: ARRAY OF CHAR): ADDRESS;
can be used as a proper procedure or as function procedure: