Home Up Intro Contents Chapter 1 2 3 4 5 6 7 8 9 10 Design Assert Timing EBNF Report Pas Last Changed: July 12th, 1997
This is a conversion from Oberon text to HTML, and from German to English. The converter software is still under development, and some features or information may be missing in this converted version. HTML hypertext facilities are not yet active in this document. To exploit the interactive facilities, use Oberon System 3 and the source of this text, available for download using binary ftp as Oberon System 3 archive. The converter from German to English is still under development as well. A previous version is also available for Oberon V4. To access this and other additional material use ftp.
For the convenience of our students, most of this information and the related material is available in German as well.

Introduction to Oberon

The Oberon Programming Language

G. Sawitzki <gs@statlab.uni-heidelberg.de>

&P: Oberon for Pascal Programmers

This chapter is a quick introduction to Oberon for Pascal or Modula programmers. Only some short explanations are given here. Pascal or Modula programmers surely find their own way through the Oberon course
<"http://statlab.uni heidelberg.de/projects/oberon/intro/">.
This section is to point out the special aspects, which else must be looked for in the bulk of the material.

Oberon belongs to a family of programming languages, with older members of the family being ALGOL, Pascal and Modula. We concentrate here on the current version, Oberon-2. The most important characteristics of Oberon-2 are block structure, modularity, separate compilability, static data types with strong type checking (even across module boundaries), and extensible data types with type-bound procedures. Type-bound procedures are a special feature of Oberon-2. The other features are available beginning with the original Oberon version.

Oberon is also the name of an operating system (written in the language Oberon). In this section we concentrate on the Oberon language.

An article of N. Wirth describing the differences between Modula 2 and Oberon is in <ftp://statlab.uni-heidelberg.de/pub/mirrors/inf.ethz.ch/oberon/docu/>

A Modula->Oberon converter by N. Wirth is available as well.
Pascal programs can be converted to a large extend automatically using
  Mess.Style * \i=P \o=O ~
if both Mess und MessP are installed in an Oberon system. Mess is in

We take a rather informal point of view regarding Pascal. Pascal is formally defined in the Pascal report, complemented by the Object Pascal report, which extends the language by constructs for object-oriented programming. In practice there is however a series of deviating implementations, up to peculiarnesses of individual compilers such as Turbo Pascal, so that a rather pragmatic approach seems advisable.

Small differences
Compared with Modula and Pascal Oberon is cleaned up. Rarely used language elements or elements which can be implemented only with difficulty are removed. Language elements, which were a frequent source of error, have been reduced. Language elements which made the compiling process lengthy without bringing a recognizable advantage, have been deleted.

Some of the clean up results are:

- Names are case sensitive. All reserved words use capital letters only.   Example:
    BEGIN    reserved
    begin    different from BEGIN. user defined name

- compound statements always have a statement sequence. The alternative "statement/statement sequence" has been removed. The opening "BEGIN" has been removed now: the begin of the statement sequence is already marked by the compound statement.
    IF i>0 THEN
    IF i>0 THEN
An ELSIF is introduce to support IF cascades.
    IF i>0 THEN
    ELSIF i=0 THEN

- All arrays are 0 based. For the declaration the length is indicated (not the highest index value).
  contains a[0]...a[9].

- logical operations are processed from left to the right. Evaluation is stopped as soon as the result is established (short cut evaluation).
    IF NoDiskError & EraseOk THEN ... END;
  calls EraseOk only if NoDiskError returns the result TRUE.

- Procedure blocks are different from compound statements. Procedure blocks end with an identifier which permits a synchronization with the heading.
    PROCEDURE xyz;
    END xyz;

- GOTO has been removed. To exit from a loop, EXIT can be used. To leave a program, HALT is available.

- GOTO does not give it any longer. Over of a loop to break out there is EXIT; over of a program to break out there is STOP.
Types are extensible. The type system is defined in such a way that its consistency is to be checked easily and an effective implementation is possible.
  - enumerated types have been removed.
  - subset types for integer numbers have been removed.
  - PACKED has been removed. Instead the standard types
    CHAR or SHORTINT can be used for economic storage.
  - SET is always a subset of the natural numbers;
    the max. scope is implementation specific.
   Typical ranges are 0..31 or 0..63.
  - the rules for type compatibility have been cleaned up.
  - RECORD variants have been removed. Instead of this
    all RECORD types are now extensible.

The syntax of procedures is unified. There are no special cases for in- or output on language level. In- or output procedures are services, which are furnished by "library procedures". However this implies that in- or output procedures are implementation dependent and not uniform.

Large differences

Separate compilability is an outstanding special feature of Oberon. The program organization is basically different from Pascal. In Pascal the "main program" lost already its meaning to a large extent and is often reduced to a trunk. Cleared up Pascal programs frequently have the structure
  PROGRAM xxx;
    VAR abc: xxxType;



In Oberon is this structure reduced to
  MODULE xxx;
    VAR abc*: xxxType;
    END Init;

    PROCEDURE DoTheJob*;
    END DoTheJob;

    PROCEDURE Exit*;
    END Exit;
  END xxx.

Oberon modules can be given exactly the same structure as Pascal programs. The separate compilation gives additional possibilities. These become clear in relationship with the Oberon system. In conventional systems programs are volatile, i.e. after end of the program all program information is lost, if it is not as printed out, stored or saved otherwise. In Oberon a module takes the place of a program. The information of a module is preserved, until the module will explicitly unload.
As consequence several modules can be present at the same time in Oberon. Modules can access the available information mutually. In order to differentiate between information from different modules, the name system extended. Oberon names can be simple designators (as in Pascal), or qualified names. Qualified names have the form
  < module name>.<variable name >.

Access control is on module level. A * mark following a name on global level marks the information as public. A - mark indicates readability, but no public right to change.

The variable ABC for example can be addressed in the module xxx as ABC as in Pascal. In Oberon the variable ABC can be addressed however also by every other at the same time active module, if it is exported. Here the qualified name is used, i.e. other modules use ABC in the qualified form as xxx.abc.
  abc:=kiki    within xxx
  xxx.abc:=kiki    outside of xxx
The consequences for the programmer are:
-  it is not necessary to write data to an intermediate in order to exchange it with different "programs".
-   there is no overhead when dividing programs into components which restrict to special functions.

Apart from this fundamental difference, the parting from the global program in favor of module components, there are two further modifications which make Oberon an object-oriented programming language: RECORDs are extensible and procedures are first class members of the type family.

RECORDs are extensible: RECORD data types form a heritability hierarchy. Derived data types inherit all fields of their ancestors, and can have additional data fields. Declarations for derived data types have the form
  NewRecord=RECORD (oldrecord)
    NewField: NewFieldTyp

Procedure types are on one level with other types. In particular procedures can be transferred not only as parameters, but also be stored in variables. It is also possible to encapsulate procedures as methods with the appropriate data in a RECORD type i.e. object-oriented programming is supported.
  Handler*=PROCEDURE(obj: Object; VAR M: ObjMsg);
  Object*=POINTER TO ObjDesc;
    handle*: Handler


On procedure level the block structure of Oberon, like with all languages of the Oberon family, ensures that the storage space is managed correctly. However If storage space is requested explicitly under program control, for example with NEW, a reference to the storage space can be passed on as parameters, value of variables or in other ways, without the control of the operating system. The usual way was to leave the memory management to the programmer. The programmer would allocate memory with procedures like NEW, and hopefully would release it again with procedures such as DISPOSE. Even with classical programs this could be a source of problems. The programmer could try to use memory space without allocating it first. This situation is usually recognizable, and it could be left to compiler builders to find ways to avoid this error. The other possible errors are releasing memory although it is still used, or not releasing memory although it is not used anymore. The first problem leads to a problem well-known as "dangling pointers" , the second problem to "memory leakage".
In Oberon these problems are avoided. "garbage collection" is done by the Oberon system. There is a procedure "NEW" like in Pascal, but there is no "DISPOSE". The system is responsible for releasing memory. The system has to prove that memory to be released cannot be accessed any more.
The programmer can ease the task of finding unaccessible memory by setting dynamic variables to NIL as soon as they are not needed any more.
There is a weak point in some older Oberon implementations. Oberon allows to release a modul explicitly. But a module can contain procedures, which may be referenced to by variables. While immediate references to procedures are handled correctly in nearly all Oberon implementations, some may have difficulties if a a procedure reference is stored in a variable. Oberon allows to mark procedures which are passed in variables using a * after the reserved word PROCEDURE. This possibility should be used as a hint for compilers to avoid possible difficulties.

Pre-defined procedures

A small group of procedures plays a special role. These procedures are defined in the Oberon report (section 10.3) and implemented in each Oberon system. Partially the type compatibility conditions for these procedures are relaxed. In comparison with PASCAL there are some replacements:
- PRED and SUCC are replaced by the more general procedures DEC and INC.
- ROUND has been removed. ENTIER is used to convert from real numbers to integers.

Name    Function
ABS(x)  absolute value
ASH(x, n)  arithmetic shift (x * 2n)
CAP(x)  x is letter: corresponding capital letter
CHR(x)  character with ordinal number x
ENTIER(x)  largest integer not greater than x
LEN(v, n)  length of v in dimension n (first dimension = 0)
LEN(v)  equivalent to LEN(v, 0)
LONG(x)  identity, or extension
MAX(T)  maximum value of type T
MIN(T)  minimum value of type T
ODD(x)  x MOD 2 = 1
ORD(x)  ordinal number of x
SHORT(x)  identity, or restriction
SIZE(T)  number of bytes required by T

Proper procedures

Name  Function
ASSERT(x)  terminate program execution if not x
ASSERT(x, n) terminate program execution if not x
COPY(x, v)  v := x
DEC(v)  v := v - 1
DEC(v, n)  v := v - n
EXCL(v, x)  v := v - {x}
HALT(n)  terminate program execution
INC(v)  v := v + 1
INC(v, n)  v := v + n
INCL(v, x)  v := v + {x}
NEW(v)  allocate v ^
NEW(v, x0, ..., allocate v ^ with lengths x0.. xn

Oberon systems and libraries

In Oberon there is no fundamental difference between system, libraries and user programs. Each of these is implemented in modules, and each module can access services of any other modules. Access rights are checked on module level. Each module specifies which information is exported. Only information (constant, type definitions, variable, procedures) on the highest (global) level can be exported. The export conditions are specified using a mark in the declaration after the name. Names marked "*" are generally exported; names marked "-" are exported only for reading.

An importing module specifies all modules to be imported in an import declaration. An import declaration has the form
  IMPORT <name>[ , <name>] ;
"Alias" names can be introduced. For an alias, < name > has the form
  <alias-name>:=<orig. name>

The available modules are implementation specific. The most important implementations are (as of July 10th, 1998)
  Oberon V4  the current implementation of the
    original Oberon system.
    Available on practically all architectures.
  Oberon System 3  an earlier variant of Oberon.
    A major part of the Oberon development
    has concentrated on System 3, so that
    most important new developments
    take place in System 3.
    Available for MS-DOS, LINUX,
    Windows, Mac.
    Various local differences to Oberon V4,
    numerous extensions.
  Oberon/F  an Oberon based framework
    with numerous extensions.
    Available on Windows and Mac only.
    Largely incompatible with Oberon V4
    or System 3. Now replaced by BlackBox.
  BlackBox  a commercial framework using
    Component Pascal, a proprietary dialect
    based on Oberon
    Available on Windows and Mac only.
    Largely incompatible with Oberon V4
    or System 3.
  Each implementation supplies its own libraries, which unfortunately do not always coincide even for the basic procedures. Therefore the user depends on which special implementation is installed.

For input and output there are simple models, which are presented in the book by Reiser&Wirth. Practically each implementation offers acces to these models. They can be used, if the following modules are imported:
  In    Input
  Out    Output
  XYplane  graphical output.
Depending on the implementation, an initialization is needed before using these modules.
  In.Open  (resp. Out.Open, XYPlot.Open).
Usual services are:

    Done*: BOOLEAN;



  PROCEDURE Char*(ch: CHAR);

END Out.


    Objects, Files;


    H*: INTEGER;
    W*: INTEGER;
    X*: INTEGER;
    Y*: INTEGER;

  PROCEDURE Dot*(x, y, mode: INTEGER);
  PROCEDURE XYhandle*(F: Objects.Object; VAR M: Objects.ObjMsg);

END XYplane.

The advanced programmer will avoid these modules. They are educational examples, which are replaced by more flexible and more efficient models available for professional work. Oberon V4 and S3 supply basic elements in the modules Texts and Display (or Display3) for text and graphical in/output.


We give here a commentated formal definition of Oberon in extended Backus-Naur form. The original definition is an appendix of the Oberon report.

The elementary items are:

ident = letter {letter | digit}.
number   = integer | real.
integer   = digit {digit} | digit {hexDigit} "H".
real   = digit {digit} "." {digit} [ScaleFactor].
ScaleFactor   = ("E" | "D") ["+" | "-"] digit {digit}.

hexDigit   = digit | "A" | "B" | "C" | "D" | "E" | "F".
digit   = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9".
Numerical types are
1.4E3 denotes a REAL constant, 1.4D3 a LONGREAL constant. The conversion from a lower numerical type to a higher one is done automatically. Conversion from a higher (longer) data type to a lower one is done by the SHORT procedure To go from a real type to an integer type, an explicit conversion using the procedure ENTIER is needed.
Warning: The higher data types cover the scope of the lower. However it is not guaranteed that the accuracy covers those of the lower data type. In particular generally the accuracy is lower with REAL numbers than with LONGINT.

Hexadecimal numbers can be written in H-notation, for example 0FFH.

character const = digit {hexDigit} "X" | '"' character '"'..

string = ' " ' {char} ' " ' | " ' " {char} " ' ".
Character constants can be given in hexadecimals (for example 0X, 0DX) or as characters (for example "A"). String constants can contain " or ', but not both.

By convention, string variables are stored as ARRAY OF CHAR with a terminating character 0X.

Comments are delimited by (*... *) and can be nested.

Module  =   MODULE ident ";" [ImportList] DeclSeq [BEGIN StatementSeq] END ident ".".
The MODULE takes the place of the program. Modules are resident and can make information mutually accessible.

ImportList   =   IMPORT [ident ":="] ident {"," [ident ":="] ident} ";".
Module can import other modules, as long as no "import cycles" are introduced. Imported modules must be declared in an import declaration. Upon import, alias names can be introduced for modules, for example Tx:=Text;
DeclSeq   =   { CONST {ConstDecl ";" } | TYPE {TypeDecl ";"} | VAR {VarDecl ";"}} {ProcDecl ";" | ForwardDecl ";"}.
ConstDecl  =   IdentDef "=" ConstExpr.
TypeDecl  =   IdentDef "=" Type.
VarDecl  =   IdentList ":" Type.
ProcDecl   =   PROCEDURE [Receiver] IdentDef
  [FormalPars] ";" DeclSeq
  [BEGIN StatementSeq] END ident.
Procedure declarations end with an END, followed by the procedure name. There is no special type for function procedures. Function procedures are special cases of procedures, having a result type.

Procedures can be bound to a type. Type-bound procedures are automatically associated with each new instance of this type.
ForwardDecl  =   PROCEDURE "^" [Receiver] IdentDef [FormalPars].
Example:  PROCEDURE ^ Later;
FormalPars   =   "(" [FPSection {";" FPSection}] ")" [":" Qualident].
FPSection   =   [VAR] ident {"," ident} ":" Type.Procedure declarations begin by PROCEDURE. Function procedures are marked by a result type.
The result of a function procedure is returned explicitly, using a RETURN statement.
Example:  (a bad example - the intermediate calculation can lead to an overflow)
    RETURN Math.Sqrt(x*x+y*y)
  END Norm;
By convention function procedures always have a (possibly empty) parameter list.

Receiver  =   "(" [VAR] ident ":" ident ")".
Type   =   Qualident
  |   ARRAY [ConstExpr {"," ConstExpr}] OF Type
  |   RECORD ["("Qualident")"]
      FieldList {";" FieldList}
  |   POINTER TO Type
  |   PROCEDURE [FormalPars].
In Oberon ARRAY parameters can be of indefined length (open arrays). Oberon-2 allows open arrays for types and variables as well In this case no expression for the length is given. Upon allocation with NEW the length needs to specified. Using with LEN, the allocated length can be queried.

  IF LEN(vec)< 100 THEN... END;

Pointer types use POINTER TO.

Records are extensible. An extended RECORD type inherits all fields of its base type, and possibly defines additional fields. For variables membership to a RECORD type can be checked using IS.
  FieldList   =   [IdentList ":" Type].
StatementSeq  =   Statement {";" Statement}.
Statement   =  [ Designator ":=" Expr
  |   Designator ["(" [ExprList] ")"]
  |   IF Expr THEN StatementSeq {ELSIF Expr THEN StatementSeq} [ELSE StatementSeq] END
  |   CASE Expr OF Case {"|" Case} [ELSE StatementSeq] END
  |   WHILE Expr DO StatementSeq END
  |   REPEAT StatementSeq UNTIL Expr
  |   FOR ident ":=" Expr TO Expr [BY ConstExpr] DO StatementSeq END
  |   LOOP StatementSeq END
  |   WITH Guard DO
      {"|" Guard DO StatementSeq}
      [ELSE StatementSeq] END
  |   EXIT
  |   RETURN [Expr]
WITH is a variant of CASE, which selects according to type (not by value). WITH converts implicitly: if a guard condition is fulfilled, then the variable is treated as variable ot that type for the corresponding statement sequence.

CASE entries are separated by |. CASE can have an ELSE branch.

FOR can have a step size. There is no special DOWNTO - this is implemented by a for statement with step size -1.

  FOR i=top TO bottom BY -1 DO

Case   =   [CaseLabels {"," CaseLabels} ":" StatementSeq].
CaseLabels   =   ConstExpr [".." ConstExpr].
Guard  =   Qualident ":" Qualident.
ConstExpr  =   Expr.
Expr   =   SimpleExpr [Relation SimpleExpr].
SimpleExpr  =   ["+" | "-"] Term {AddOp Term}.
Term   =   Factor {MulOp Factor}.
Factor   =   Designator ["(" [ExprList] ")"] | number | character | string | NIL | Set | "(" Expr ")" | " ~ " Factor.The negation operator is ~.

Set  =   "{" [Element {"," Element}] "}".Set braces are {, }.
Element   =   Expr [".." Expr].
Relation   =   "=" | "#" | "<" | "<=" | ">" | ">=" | IN | IS.
The not equal operator is #.
The comparison operators apply also to variables of the type ARRAY ... OF CHAR.
IS check type membership, for example IF x IS vector THEN..END;
  AddOp   =   "+" | "-" | OR.
  Division for sets is the symmetric difference.

MulOp   =   " * " | "/" | DIV | MOD | "&".
& means "and".

Designator   =   Qualident {"." ident | "[" ExprList "]" | " ^ " | "(" Qualident ")"}.
Dereferencing is option if it is well-defined by the context.
for example.
  A[i,j]    instead of A^[i,j]
  xyz.abc    instead of xyz^.abc
References to array ARRAYs are resolved step b step, i.e.. A[i,j] is equivalent to A[i][j].
ExprList   =   Expr {"," Expr}.
IdentList   =   IdentDef {"," IdentDef}.
Qualident   =   [ident "."] ident.
IdentDef   =   ident [" * " | "-"].

Introduction to the Oberon programming language. ItO/ChPas.Text
gs (c) G. Sawitzki, StatLab Heidelberg

Home Up Intro Contents Chapter 1 2 3 4 5 6 7 8 9 10 Design Assert Timing EBNF Report Pas