A Description of the Oberon-2 Language

Written by Paul Floyd

Introduction
Usually, Oberon grammar is presented in EBNF (the Extended Backus-Naur Formalism).

The main building block of Oberon applications is the module. Each module has a name. You can import other modules into a module, and can export components of the module so that other modules may import them (i.e., make them public). Variables and functions are tagged as exported by appending a "*" at the end of the name. Read-only export is also possible, this time by appending a "-" at the end of the name. The module can contain the other building blocks described later - type definitions, constants, variables and functions.

Comments are opened by "(*" and closed by "*)".

Here is an example of the module syntax:

Constants
A section of declared constants starts with the keyword CONST. Constants may be built up from other constants. The assignment of a constant uses "=", unlike that of a variable which uses ":=".

Example:

Types
You can define your own types from the base types defined in Oberon, and types you have defined. Type declarations begin with the keyword TYPE.

Oberon has single inheritance. A RECORD may have a base type by putting the name of the base type in parentheses after the RECORD declaration of the derived type.

The built in types are: BOOLEAN, CHAR, SHORTINT, INTEGER, LONGINT, REAL, LONGREAL and SET. Most of these are implementation-specific. A SET is a SET of integers.

Examples of type declarations: You can use ARRAYs and RECORDs (structures) for compound types. You can declare POINTERs, but they must point to structured types only. ARRAYs need not have their dimension specified, but in this case they must be ARRAYS of POINTERs.

Example: ThreeDim is now a three dimensional ARRAY of Coordinate values, where the representation might be Cartesian, Spherical or Cylindrical. This is only useful for parameters/methods.

Which representation you have might be checked by a type guard, which is something completely different than a type cast. It checkes which extension of a base type is the current runtime type.

Variables
Declarations of variables start with the VAR keyword, and use either built in types of types created in the TYPE declarations.

Examples:

Procedures
Procedures basically come in two types, function procedures and proper procedures. Function procedures return a value when called.

Examples: The parameters that the procedures take are either passed by value (default) or passed by reference (indicated by the VAR keyword). Whether or not a procedure is a function procedure depends on whether the PROCEDURE line is followed by the return type. Note however that the return type cannot be compound. You must either return a pointer or a simple type.

Procedure forward declaration
Oberon compilers are generally one-pass (thus blindingly fast), so if a procedure is used before it is defined, it must be declared first. The caret symbol is used to indicate definition of a procedure. The forward declaration and actual declaration must have the same name, type binding and parameters. Example:

Local procedures
Unlike C/C++, Oberon can have procedures within procedures. In the case of local and greater scope procedures having the same name, the local one will take precedence. The local and greater scope procedures with the same name can't be differentiated by parameter lists. If two such functions exist, and you try to call the outer procedure, then either the compiler will perform type conversion and call the local procedure, or it will say that the parameter lists do not match.

Type bound procedures
The concept of type bound procedures is very important in Oberon-2. They are the equivalent of class methods in C++.

Firstly, the syntax. Type bound procedures are indicated by declaring the record to which the procedure in parentheses before the procedure name. A name for the module type is also given, which is used for dereferencing components of the module (as compared to the anonymous this in C++). Type bound procedures are not declared with the record itself, but can be declared at any point.

Simple example: Type bound procedures may be redefined for records that are extensions. For instance, if we define a three dimensional co-ordinate: It is then possible to re-define Add: It is important to note that the redefined procedure must have the same parameters as the base class one.

Built-in function procedures

 * ABS(x):Absolute value (accepts and returns any numeric type)
 * ASH(x, n):Arithmetic shift left (LONGINT)
 * CAP(x):Capitalize (CHAR)
 * CHR(x):Letter from ASCII value (CHAR)
 * ENTIER(x):Integral part of a real number (LONGINT)
 * LEN(v, n):Length of vector n of array v (LONGINT)
 * LEN(v): = LEN(v, 0)
 * LONG(x):Promotes to next greatest numeric type (INTEGER, LOGINT or LONGREAL)
 * MAX(T):If T is a type, returns the maximum value for that type
 * If T is a SET, the maximum value in that set (INTEGER)


 * MIN(T):As MAX but minimum
 * ODD(x):Detects odd integers (BOOLEAN)
 * ORD(x):ASCII value from character (INTEGER)
 * SHORT(x):Demotes to next lowest numeric type (SHORTINT, INTEGER or REAL)
 * SIZE(T):Size of type in bytes (integral type)

System functions
Most Oberon compilers have a module SYSTEM which contains low-level functions. ADR(v)     Address of variable (LONGINT) BIT(a, n)  Test bit n of a (BOOLEAN) CC(n)      Condition n (16 bit) LSH(x, n)  Logical shift n (type of x) ROT(x, n)   Rotate n (type of x) VAL(T, x)   Force x to be expressed as type T (type of T)

Expressions
Expressions consist of operands and operators. Operands consist of procedure calls, string/numeric values, constants or variables. Variables may be simple types, arrays or pointers. A type guard (like a C/C++ cast) may be applied to a variable. Unlike C, where you can do 'useful' things like cast a double to a function call, Oberon type guards only apply to extended records. Using the example from the section on RECORDs above: The operators may be logical (OR & ~) (Why it isn't AND/OR or &/| I'll never know!), arithmetic (+ - * / DIV MOD), set (+ - * /) or relational (= # [not equal] < <= > >= IN [test for set membership] IS [test for type]).

Statements
Statements may be assignment (x := 1), procedure calls or a compound statement. Compound statements are either the contents of BEGIN and END or one of the built in language statements. Statements are always separated by semicolons.

The built in statements are as follows. Items in square brackets are optional.

Case
Here 'label' may be an integral type (or character), a range, indication by two dots (e.g., 0..59), or a CONST. The ELSE clause acts like the default: for a C/C++ switch.

While
WHILE expression DO   statements END

Repeat
REPEAT statements UNTIL expression

Loop
LOOP statements (* must include an EXIT statement *) END

For
The FOR statement does not exist at all in Oberon-1. WHILE, REPEAT or LOOP statements must be used instead.

With
This performs a test on the type of a variable, and executes statements similarly to the CASE statement. If no ELSE clause is supplied and none of the supplied label types match the variable, the program will end. Example: Note that Oberon-1 does not have the option of having more than one guard test in the WITH statement.