Oberon and the Macintosh

The execution environment of the Macintosh poses some special problems. Some of these problems are solved in MPW Oberon by extending the Oberon language and some of them are solved by directives given to the compiler. Compiler directives, or pragmas, are instructions for the compiler embedded into comments. They modify the behavior of MPW Oberon during compilation. See "Compiler options and directives" for a complete list of pragmas supported by MPW Oberon.
  1. Extensions of the Oberon language
  2. Compiler directives for the Macintosh environment
  3. Miscellaneous additions

Extensions of the Oberon language

This section introduces the language extensions provided by MPW Oberon making the development of Macintosh application easier. The first extension helps in declaring and accessing handles since handles can not be declared directly in Oberon. The second extension provides support for inline and external procedures. This give you access to routines written in other languages and to libraries provided by other developers.

Declaration of handles

A handle is a pointer to a pointer referring to a block on the heap. Handles are used by the Memory Manager to reduce the fragmentation of an application's heap. The indirection provided by a handle makes it possible for the Memory Manager to move the block within the heap. Handles are used by virtually all managers in the Macintosh. See the chapter "Memory Manager" in Inside Macintosh: Memory for further details.

In Oberon it is impossible to declare handles in the way as in Pascal, since it is not allowed to define a type to be a pointer to a pointer. Consider the ControlRecord defined by the Control Manager. Such a record is allocated and initialized by the Control Manager for every control created. The application gets a handle as its reference to the new control. If a ControlRecord and the corresponding ControlHandle should be declared in standard Oberon, a "dummy" record type is needed to access the master pointer.

TYPE
  ControlPtr* = POINTER TO ControlRecord;
  ControlHandle* = POINTER TO RECORD mp*: ControlPtr END;
  ControlRecord* = RECORD
  nextControl*: ControlHandle;
    (* more fields *)
  END;
Accessing the field nextControl in a control whose handle is stored in variable x requires to write x.mp.nextControl. The intermediate field mp makes it harder to read the program, since the specifier gets longer. Handles and pointers, both pointing to dynamic variables, should be treated the same way, i.e. is should be possible to access a handle's field without the intermediate record type.

MPW Oberon supports the additional keyword HANDLE which may be used instead of POINTER to define a handle to a dynamic variable. The new keyword HANDLE simplifies both declaring and accessing a handles. Fields within dynamic variables are treated equally and the compiler takes care of dereferencing the handle. The declaration of ControlHandle would be written in MPW Oberon as:

TYPE
  ControlHandle* = HANDLE TO ControlRecord;
The specifier for field nextControl in control x may now be written as x.nextControl simplifying access to handles considerably. Types declared using HANDLE are called handle types.

Note: If you use an anonymous type within a handle declaration no tag field will be added to the dynamic variable. The first field within the record will start at the first byte of the handle. A declaration involving a anonymous type will look like

TYPE
  AHandle = HANDLE TO RECORD
    field1: LONGINT;
    (* more fields *)
  END;
In this example the record containing field1 is anonymous and field1 has an offset of 0 within the handle. MPW Oberon will assign offset 0 to field1 whether or type tags are requested. You should not rely on this feature as it may change with the introduction of garbage collection.

The standard procedure NEW was extended to handle types declared using HANDLE. If NEW is passed a variable whose type is a handle-type, NewHandle will be called to allocate the dynamic variable. The standard procedure DISPOSE used to release dynamic variables handles handle types correctly.

The HANDLE extension of MPW Oberon allows to use handles instead of pointers without any changes requires to the source code. By using handles the usage of memory may become more efficient than by using pointers since the Memory Manager may move handles around.

Warning: There is an incompatibility between pointers and handles namely there behavior when new blocks are allocated. Dynamic variables referred to by pointers never change their position within the heap whereas handles may be moved by the Memory Manager unless they are locked. You should be careful when you pass fields within dynamic variables to a procedure which may move memory. See the chapter "Memory Manager" in Inside Macintosh: Memory for further details.

Inline and external procedures

The routines contained in the Macintosh ROMs are accessed using the A-trap mechanism of the M68000. Activation of a trap is similar to the activation of a regular procedure written in Oberon. Parameters are pushed onto the stack and the trap is called. The compiler inserts the trap word instead of a subroutine call to activate the trap. In Oberon there is no provision for such a scheme. MPW Oberon supports additional keyword INLINE for declaring inline procedures.

Inline procedures are not strictly necessary but you would have to provide a glue routine for every toolbox trap. This glue routine would be called at run time to execute the trap. In standard Oberon the problem of calling these glue routines which are written in assembly language remains.

The syntax for declaring inline procedure is very similar to the one used by MPW Pascal. This convention makes porting existing sources and interface files easier. Consider the procedure HLock provided by the Memory Manager. In MPW Oberon this routine may be declared as:

PROCEDURE HLock*(h: Types.Handle);
  INLINE PASCAL $205F,$A029;
The keyword INLINE indicates that the procedure HLock has no body written in Oberon. For an inline procedure you may specify up to 16 opcodes. These opcodes are inserted instead of a procedure call after the procedure's parameters were pushed onto the stack.

The routines of the Macintosh' toolbox use the Pascal calling convention. MPW Oberon supports this convention. For inline procedures the Pascal calling convention is indicated by the word PASCAL following INLINE. If PASCAL is omitted, the calling convention used by MPW Oberon applies (see "Oberon calling convention").

Note: To make porting easier MPW Oberon supports a Motorola notation for hexadecimal numbers. A hexadecimal number is indicated by a leading $-sign which is followed by the digits of the number.

External procedure are treated very similarly. Instead of INLINE the keyword EXTERNAL is used to indicate that the procedure is not implemented in Oberon. For external procedures the name of the procedure is passed to the linker as it is written in Oberon. Names of external procedures written in Pascal are converted to their uppercase equivalence by MPW Oberon. If you write a procedure Great in Pascal, the Pascal compiler will name it GREAT in the object file. MPW Oberon does the same for procedures declared as EXTERNAL PASCAL.

Note: The two additional keywords INLINE and EXTERNAL may be used only when module SYSTEM is imported. Otherwise the compiler will report an error. By enforcing this convention, system dependent modules are easily identified.

Compiler directives for the Macintosh environment

Some requirements of the Macintosh environment can be handled by directives given to the compiler. Directives are embedded into comments and the Oberon language is not changed to support the additional features. Other compilers are able to compile the module since comments are ignored.

This section describes the compiler directives needed in the Macintosh environment. For a complete list of the directives supported by MPW Oberon see "Compiler directives".

Segmentation control

A segment is a part of code that can be separately loaded into memory. Your program can be written without explicit segmentation control or it can contain a number of different segments. Segmentation helps reducing a program's run-time memory requirements. Routines seldom executed are typically put into separate segment that is not loaded at program begin. The segment is loaded when one of the routines in it are executed.

Routines may be placed in different segments in two way. This section describes the standard segmentation scheme provided by the compiler and how to use the $S directive to specify segmentation. The documentation of MPW explains how to use the Link command to modify segmentation.

MPW Oberon puts the routines contained in one modules into three segments. One segment contains the routines in defined in that module. This segment has the same name as the corresponding module. The initialization code of the module is put into a special segment named "%InitModules". All type tags are stored into a separate segment called "%OberonTags".

The $S directive lets you specify several segments within a single source file. To assign source code to a segment, precede the code with a $S directive:

(*$S segment-name*)
Code following this directive is placed in the specified segment until the compiler reads another $S or the end of the file is reached.

Note: Segment names are case sensitive. Leading spaces are ignored, but all characters up to the end of the comment are used.

Code for a given segment does not have to be contiguous within the source file. The program may take the following form:

(*$S SegA*)
function
(*$S SegB*)
function
(*$S SegA*)
The compiler marks each routine with the name of its segment. The linker collects all procedures for a segment from various input files and places them into a single code segment.

Passing parameters in registers

A number of Macintosh routines expect their parameters in registers. Most compilers are not able to pass parameters in registers and provide glue routines for putting the parameters in the registers. This method introduces overhead and increases the size of the program. MPW Oberon supports passing of parameters in registers with the $Parameter directive. The Memory Manager's routine HLock expects its argument in register A0. In MPW Oberon HLock is declared as:
(*$Parameter HLock(A0) *)
PROCEDURE HLock*(h: Types.Handle);
  INLINE PASCAL $A029;
The $Parameter directive must be given directly before the declaration of the corresponding procedure. The procedure names and parameter lists must match. Every parameter required by the procedure must be assigned to a register.

The registers are specified in the standard notation defined by Motorola. You may use the registers D0-D7, A0-A7 and FP0-FP7 for passing parameters. The registers of the MC68881 may be used only if code for the MC68881 is generated.

Note: MPW Oberon currently supports the $Parameter directive only for inline procedures. You may not used it with external procedures or procedure variables.

Passing dynamic structures to the operating system

Every dynamic variable allocated on heap by the Oberon standard procedure NEW has a type tag used to test the type of the variable (see "Type tags in MPW Oberon" for further information). The so-called tag field is invisible to the user and precedes the information stored in the dynamic variable. The Macintosh's operating system expects that data within a dynamic block start at the first byte within that block.

Note: Tag fields used in Oberon to distinguish types of dynamic variables should not be confused with the tag fields used in Pascal. In Pascal tag fields are used to distinguish the variants of a variant record. Variant records are not supported by Oberon. Type extension is used in Oberon instead of variant records.

Note: See "Specifying string constants" for further details about passing literal strings to the operating system.

MPW Oberon supports the $TAGS- directive for declaring data types without type tags. If a dynamic variable is allocated using the standard procedure NEW, no type tag will be added. All data types defined in Inside Macintosh are defined using the $TAGS- directive. For example the type ControlRecord defined by the Control Manager is declared in MPW Oberon as:

TYPE
  (*$TAGS-*)
  ControlPtr* = POINTER TO ControlRecord;
  ControlHandle* = HANDLE TO ControlRecord;
  ControlRecord* = RECORD
    nextHandle*: ControlHandle;
    (* more fields *)
  END;
  (*$TAGS+*)
All data types declared after a $TAGS- directive are marked by the compiler as untagged and dynamic variables allocated for those types will not carry a type tag. The $TAGS- directive remains in effect until the end of the source file or until a $TAGS+ directive is encountered. You may use any number of $TAGS- directives in a module.

If you extend an untagged type, its extension is untagged too. There is no way to add a type tag to an untagged type. Since dynamic variables of untagged types contain no type tags, type test or type guards are not possible with those types. You may use the $TYPECHECK directive to override the behavior of MPW Oberon in this respect.

Warning: The $TYPECHECK directive lets you to use the WITH statement as a type cast. Consider a procedure updating either a window or a dialog in response to an update event. The following code fragment show the WITH statement used as a type cast.

(*$TYPECHECK- ; no type checks please*)
IF window.windowKind=Windows.dialogKind THEN
  (* window refers to a dialog *)
  WITH window: Dialogs: DialogPtr DO
    (* do something useful *)
  END
ELSE
END
(*$TYPECHECK+; turn type checks on*)
This fragment is correct because the windowKind field maintained by the Window Manager indicates the type of window and hence the size of the corresponding data structure (see the chapter "Window Manager" in Inside Macintosh: Macintosh Toolbox Essentials for further information about the windowKind field). Nevertheless this technique is rather dangerous and should be used with caution.

Miscellaneous additions

In order make to writing of code easier, MPW Oberon incorporate a few small additions. All these additions are not mandatory to write an Oberon program and the Compiler will indicate their occurrence if the -cp option is specified on the command line.

Additional notations for integers

In Oberon hexadecimal number are denoted by the number's digits followed by the letter "H". MPW Pascal uses the standard Motorola notation for hexadecimal numbers, i.e. a $-sign followed by the digits of the number. A hexadecimal number must not contain more than eight digits. This notation is especially useful when you want to convert Pascal files.

Integers may also be specified using a binary notation which is also the standard Motorola notation. A binary number is denoted by a %-sign followed by the number's digits. A binary number must not contain more than 32 digits. This notation is especially useful when you want to assemble opcodes.

Additional notation for operators

Oberon is defined with ASCII as its alphabet. The extended character set of the Macintosh contains a few characters that may be used to enhance the readability of source code. The following table indicates the alternate notation supported by MPW Oberon for some Oberon operators.
<=
>=
#
~¬
&AND

Handling OSTypes

The data type OSType is defined as an array containing four characters. If OSType were declared this way, it would not be possible to assign a constant with four characters like "TEXT" since Oberon wants to move five characters.

In the Oberon interface files OSType is defined as a 32-bit integer. For the assignment of constants to variables of this type the standard procedure LONG was extended. Besides integers a string with four visible characters may be passed to LONG. For example

x:=LONG("TEXT")
assigns $54455854 to the variable x. You may notice that LONG("TEXT") is much more readable than $54455854.

Additional notation for SYSTEM.VAL

The standard function SYSTEM.VAL is used to cast the type of an expression. To change the type of expression e to T you write SYSTEM.VAL(T,e). This notation generates long expressions especially when types are used which are imported form other modules (e.g. Windows.WindowPtr).

MPW Oberon supports the Modula-2's notation for casting types. Using this notation expression e is casted to type T by writing T(e). The alternate notation may by used only when the module SYSTEM is imported.

Specifying string constants

Strings in standard Oberon may contain only the visible characters of the international alphabet No. 5 (ASCII) since this is the alphabet of the Oberon programming language. The Macintosh provides an extended character set with many useful characters like umlauts, German double-s and accented characters. Furthermore a string should contain invisible character line carriage returns or line feeds. Since these characters are use for terminating a line and literal strings must not span lines, these characters are not allowed in a string constant.

MPW Oberon supports an extended string notation similar to the one supported by the C language. You may use the backslash character "\" to include characters into a string by specifying their numerical values. Character values must be specified as decimal values with three digits. The following string contains "hello world" followed by a carriage return and a line feed:

"hello world\013\$0A"
The Macintosh toolbox uses strings in the Pascal format. This format is different from the one used by Oberon. The Pascal format consists of a length byte followed by the string's characters. In Oberon strings are terminated with a null characters just like in C. MPW Oberon supports the same mechanism for defining literal strings in the Pascal format as MPW C. To defines a literal string in Pascal format write "\P" after the string's opening quote. The following string is the same string as above but this time as a Pascal string:
"\Phello world\013\$0A"
However MPW Oberon tries to be clever about strings. If you call a procedure that uses the Pascal convention for passing parameters, MPW Oberon converts a literal strings passed to that procedure to the Pascal format. If you want to pass the string "Item 1;Item 2" to the Menu Manager's function AddMenu, write:
Menus.AddMenu(menu, "Item 1;Item 2")
This conversion takes place at compile time for every parameter that is declared as an array of characters.

Note: The standard procedures and functions supported by MPW Oberon for strings expect their strings to be in the Oberon format. You should be careful about mixing strings of the two formats.


Previous Section, Next Section, Contents
Jürgen Geßwein; 2. Juni 1995