Institut für Computersysteme, ETH Zürich

October 1993

Type extension makes Oberon-2 an object-oriented language. An object is a variable of an abstract data type consisting of private data (its state) and procedures that operate on this data. Abstract data types are declared as extensible records. Oberon-2 covers most terms of object-oriented languages by the established vocabulary of imperative languages in order to minimize the number of notions for similar concepts.

This report is not intended as a programmer's tutorial. It is intentionally kept concise. Its function is to serve as a reference for programmers, implementors, and manual writers. What remains unsaid is mostly left so intentionally, either because it can be derived from stated rules of the language, or because it would require to commit the definition when a general commitment appears as unwise.

Appendix A defines some terms that are used to express the type checking rules of
Oberon-2. Where they appear in the text, they are written in italics to indicate
their special meaning (e.g. the *same* type).

1. *Identifiers* are sequences of letters and digits. The first character
must be a letter.

ident = letter {letter | digit}.Examples:

x Scan Oberon2 GetSymbol firstLetter2.

A real number always contains a decimal point. Optionally it may also contain a decimal scale factor. The letter E (or D) means "times ten to the power of". A real number is of type REAL, unless it has a scale factor containing the letter D. In this case it is of type LONGREAL.

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".Examples:

1991 INTEGER 1991 0DH SHORTINT 13 12.3 REAL 12.3 4.567E8 REAL 456700000 0.57712566D-6 LONGREAL 0.000000577125663.

character = digit {hexDigit} "X".4.

string = ' " ' {char} ' " ' | " ' " {char} " ' ".Examples:

"Oberon-2" "Don't worry!" "x"5.

+ := ARRAY IMPORT RETURN - ^ BEGIN IN THEN * = BY IS TO / # CASE LOOP TYPE ~6.DIV MODULE VAR . <= DO NIL WHILE ,>= ELSE OF WITH ; .. ELSIF OR | : END POINTER ( ) EXIT PROCEDURE [ ] FOR RECORD { } IF REPEAT

The *scope* of an object *x* extends textually from the point of its
declaration to the end of the block (module, procedure, or record) to which the
declaration belongs and hence to which the object is *local*. It excludes
the scopes of equally named objects which are declared in nested blocks. The
scope rules are:

- No identifier may denote more than one object within a given scope (i.e. no identifier may be declared twice in a block);
- An object may only be referenced within its scope;
- A type
*T*of the form POINTER TO*T1*(see 6.4) can be declared at a point where*T1*is still unknown. The declaration of*T1*must follow in the same block to which*T*is local; - Identifiers denoting record fields (see 6.3) or type-bound procedures (see 10.2) are valid in record designators only.

Qualident = [ident "."] ident. IdentDef = ident [" * " | " - "].The following identifiers are predeclared; their meaning is defined in the indicated sections:

ABS (10.3) LEN (10.3) ASH (10.3) LONG (10.3) BOOLEAN (6.1) LONGINT (6.1) CAP (10.3) LONGREAL (6.1) CHAR (6.1) MAX (10.3) CHR (10.3) MIN (10.3) COPY (10.3) NEW (10.3) DEC (10.3) ODD (10.3) ENTIER (10.3) ORD (10.3) EXCL (10.3) REAL (6.1) FALSE (6.1) SET (6.1) HALT (10.3) SHORT (10.3) INC (10.3) SHORTINT (6.1) INCL (10.3) SIZE (10.3) INTEGER (6.1) TRUE (6.1)

ConstantDeclaration = IdentDef "=" ConstExpression. ConstExpression = Expression.A constant expression is an expression that can be evaluated by a mere textual scan without actually executing the program. Its operands are constants (Ch.8) or predeclared functions (Ch.10.3) that can be evaluated at compile time. Examples of constant declarations are:

N = 100 limit = 2*N - 1 fullSet = {MIN(SET) .. MAX(SET)}

TypeDeclaration = IdentDef "=" Type. Type = Qualident | ArrayType | RecordType | PointerType | ProcedureType.Examples:

Table = ARRAY N OF REAL Tree = POINTER TO Node Node = RECORD key: INTEGER; left, right: Tree END CenterTree = POINTER TO CenterNode CenterNode = RECORD (Node) width: INTEGER; subnode: Tree END Function = PROCEDURE(x: INTEGER): INTEGER

1. BOOLEAN the truth values TRUE and FALSE 2. CHAR the characters of the extended ASCII set (0X .. 0FFX) 3. SHORTINT the integers between MIN(SHORTINT) and MAX(SHORTINT) 4. INTEGER the integers between MIN(INTEGER) and MAX(INTEGER) 5. LONGINT the integers between MIN(LONGINT) and MAX(LONGINT) 6. REAL the real numbers between MIN(REAL) and MAX(REAL) 7. LONGREAL the real numbers between MIN(LONGREAL) and MAX(LONGREAL) 8. SET the sets of integers between 0 and MAX(SET)Types 3 to 5 are

LONGREAL >= REAL >= LONGINT >= INTEGER >= SHORTINT

ArrayType = ARRAY [Length {"," Length}] OF Type. Length = ConstExpression.A type of the form

ARRAY L0, L1, ..., Ln OF T

is understood as an abbreviation of

ARRAY L0 OF

ARRAY L1 OF

...

ARRAY Ln OF T

Arrays declared without length are called *open arrays*. They are restricted
to pointer base types (see 6.4), element types of open
array types, and formal parameter types (see 10.1).
Examples:

ARRAY 10, N OF INTEGER ARRAY OF CHAR

RecordType = RECORD ["("BaseType")"] FieldList {";" FieldList} END. BaseType = Qualident. FieldList = [IdentList ":" Type ].Record types are extensible, i.e. a record type can be declared as an extension of another record type. In the example

T0 = RECORD x: INTEGER END

T1 = RECORD (T0) y: REAL END

*T1* is a (direct) *extension* of *T0* and *T0* is the
(direct) *base type* of *T1* (see App. A). An
extended type *T1* consists of the fields of its base type and of the fields
which are declared in *T1*. All identifiers declared in the extended record
must be different from the identifiers declared in its base type record(s).

Examples of record type declarations:

RECORD day, month, year: INTEGER END

RECORD name, firstname: ARRAY 32 OF CHAR; age: INTEGER; salary: REAL END

PointerType = POINTER TO Type.If

ProcedureType = PROCEDURE [FormalParameters].

VariableDeclaration = IdentList ":" Type.Record and pointer variables have both a

Examples of variable declarations (refer to examples in Ch. 6):

i, j, k: INTEGER x, y: REAL p, q: BOOLEAN s: SET F: Function a: ARRAY 100 OF REAL w: ARRAY 16 OF RECORD name: ARRAY 32 OF CHAR; count: INTEGER END t, c: Tree

Designator = Qualident {"." ident | "[" ExpressionList "]" | "^" | "(" Qualident ")"}. ExpressionList = Expression {"," Expression}.If a designates an array, then

A *type guard v(T)* asserts that the dynamic type of *v* is *T*
(or an extension of *T*), i.e. program execution is aborted, if the dynamic
type of *v* is not *T* (or an extension of *T*). Within the
designator, *v* is then regarded as having the static type *T*. The
guard is applicable, if

*v*is a variable parameter of record type or*v*is a pointer, and if*T*is an extension of the static type of*v*

Examples of designators (refer to examples in Ch. 7):

i (INTEGER) a[i] (REAL) w[3].name[i] (CHAR) t.left.right (Tree) t(CenterTree).subnode (Tree)

Expression = SimpleExpression [Relation SimpleExpression]. SimpleExpression = ["+" | "-"] Term {AddOperator Term}. Term = Factor {MulOperator Factor}. Factor = Designator [ActualParameters] | number | character | string | NIL | Set | "(" Expression ")" | "~" Factor. Set = "{" [Element {"," Element}] "}". Element = Expression [".." Expression]. ActualParameters = "(" [ExpressionList] ")". Relation = "=" | "#" | "<" | "<=" | ">" | ">=" | IN | IS. AddOperator = "+" | "-" | OR. MulOperator = "*" | "/" | DIV | MOD | "&".The available operators are listed in the following tables. Some operators are applicable to operands of various types, denoting different operations. In these cases, the actual operation is identified by the type of the operands. The operands must be

OR logical disjunction p OR q "if p then TRUE, else q" & logical conjunction p & q "if p then q, else FALSE" ~ negation ~ p "not p"These operators apply to BOOLEAN operands and yield a BOOLEAN result.

+ sum - difference * product / real quotient DIV integer quotient MOD modulusThe operators +, -, *, and / apply to operands of numeric types. The type of the result is the type of that operand which includes the type of the other operand, except for division (/), where the result is the smallest real type which includes both operand types. When used as monadic operators, - denotes sign inversion and + denotes the identity operation. The operators DIV and MOD apply to integer operands only. They are related by the following formulas defined for any

x = (x DIV y) * y + (x MOD y)

0 <= (x MOD y) < y

Examples:

x y x DIV y x MOD y 5 3 1 2 -5 3 -2 1

+ union - difference (x - y = x * (-y)) * intersection / symmetric set difference (x / y = (x-y) + (y-x))Set operators apply to operands of type SET and yield a result of type SET. The monadic minus sign denotes the complement of

A set constructor defines the value of a set by listing its elements between
curly brackets. The elements must be integers in the range 0..MAX(SET). A range
*a..b* denotes all integers in the interval [*a*, *b* ].

= equal # unequal < less <= less or equal > greater >= greater or equal IN set membership IS type testRelations yield a BOOLEAN result. The relations =, #, <, <=, >, and >= apply to the numeric types, CHAR, strings, and character arrays containing 0X as a terminator. The relations = and # also apply to BOOLEAN and SET, as well as to pointer and procedure types (including the value NIL).

*v*is a variable parameter of record type or*v*is a pointer, and if*T*is an extension of the static type of*v*

1991 INTEGER i DIV 3 INTEGER ~p OR q BOOLEAN (i+j) * (i-j) INTEGER s - {8, 9, 13} SET i + x REAL a[i+j] * a[i-j] REAL (0<=i) & (i<100) BOOLEAN t.key = 0 BOOLEAN k IN {i..j-1} BOOLEAN w[i].name <= "John" BOOLEAN t IS CenterTree BOOLEAN

Statement =[ Assignment | ProcedureCall | IfStatement | CaseStatement | WhileStatement | RepeatStatement | ForStatement | LoopStatement | WithStatement | EXIT | RETURN [Expression] ].

Assignment = Designator ":=" Expression.If an expression

- if
*Tv*and*Te*are record types, only those fields of*Te*are assigned which also belong to*Tv*(*projection*); the dynamic type of*v*must be the*same*as the static type of*v*and is not changed by the assignment; - if
*Tv*and*Te*are pointer types, the dynamic type of*v*becomes the dynamic type of*e*; - if
*Tv*is ARRAY n OF CHAR and*e*is a string of length*m*<*n*,*v[i]*becomes*ei*for*i*= 0..*m*-1 and*v[m]*becomes 0X.

i := 0 p := i = j x := i + 1 k := log2(i+j) F := log2 (* see 10.1 *) s := {2, 3, 5, 7, 11, 13} a[i] := (x+y) * (x-y) t.key := i w[i+1].name := "John" t := c

If a formal parameter is a variable parameter, the corresponding actual parameter must be a designator denoting a variable. If it denotes an element of a structured variable, the component selectors are evaluated when the formal/actual parameter substitution takes place, i.e. before the execution of the procedure. If a formal parameter is a value parameter, the corresponding actual parameter must be an expression. This expression is evaluated before the procedure activation, and the resulting value is assigned to the formal parameter (see also 10.1).

ProcedureCall = Designator [ActualParameters].Examples:

WriteInt(i*2+1) (* see 10.1 *) INC(w[k].count) t.Insert("John") (* see 11 *)

StatementSequence = Statement {";" Statement}.

IfStatement = IF Expression THEN StatementSequence {ELSIF Expression THEN StatementSequence} [ELSE StatementSequence] END.If statements specify the conditional execution of guarded statement sequences. The Boolean expression preceding a statement sequence is called its

Example:

IF (ch >= "A") & (ch <= "Z") THEN ReadIdentifier ELSIF (ch >= "0") & (ch <= "9") THEN ReadNumber ELSIF (ch = " ' ") OR (ch = ' " ') THEN ReadString ELSE SpecialCharacter END

CaseStatement = CASE Expression OF Case {"|" Case} [ELSE StatementSequence] END. Case = [CaseLabelList ":" StatementSequence]. CaseLabelList = CaseLabels {"," CaseLabels}. CaseLabels = ConstExpression [".." ConstExpression].Example:

CASE ch OF "A" .. "Z": ReadIdentifier | "0" .. "9": ReadNumber | "'", '"': ReadString ELSE SpecialCharacter END

WhileStatement = WHILE Expression DO StatementSequence END.Examples:

WHILE i > 0 DO i := i DIV 2; k := k + 1 END WHILE (t # NIL) & (t.key # i) DO t := t.left END

RepeatStatement = REPEAT StatementSequence UNTIL Expression.

ForStatement = FOR ident ":=" Expression TO Expression [BY ConstExpression] DO StatementSequence END.The statement

FOR v := beg TO end BY step DO statements END

is equivalent to

temp := end; v := beg;

IF step > 0 THEN

WHILE v <= temp DO statements; v := v + step END

ELSE

WHILE v >= temp DO statements; v := v + step END

END

*temp* has the *same* type as *v*. *step* must be a nonzero
constant expression. If *step* is not specified, it is assumed to be 1.

Examples:

FOR i := 0 TO 79 DO k := k + a[i] END FOR i := 79 TO 1 BY -1 DO a[i] := a[i-1] END

LoopStatement = LOOP StatementSequence END.Example:

LOOP ReadInt(i); IF i < 0 THEN EXIT END; WriteInt(i) ENDLoop 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.

Function procedures require the presence of a return statement indicating the result value. In proper procedures, a return statement is implied by the end of the procedure body. Any explicit return statement therefore appears as an additional (probably exceptional) termination point.

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 which contains them.

WithStatement = WITH Guard DO StatementSequence {"|" Guard DO StatementSequence} [ELSE StatementSequence] END. Guard = Qualident ":" Qualident.If

WITH v: T1 DO S1 | v: T2 DO S2 ELSE S3 END

has the following meaning: if the dynamic type of *v* is *T1*, then the
statement sequence *S1* is executed where *v* is regarded as if it had
the static type *T1*; else if the dynamic type of *v* is *T2*,
then *S2* is executed where *v* is regarded as if it had the static
type *T2*; else *S3* is executed. *T1* and *T2* must be
extensions of *T0*. If no type test is satisfied and if an else clause is
missing the program is aborted.

Example:

WITH t: CenterTree DO i := t.width; c := t.subnode END

There are two kinds of procedures: *proper procedures* and *function
procedures*. The latter are activated by a function designator as a
constituent of an expression and yield a result that is an operand of the
expression. Proper procedures are activated by a procedure call. A procedure is a
function procedure if its formal parameters specify a result type. The body of a
function procedure must contain a return statement which defines its result.

All constants, variables, types, and procedures declared within a procedure body
are *local* to the procedure. Since procedures may be declared as local
objects too, procedure declarations may be nested. The call of a procedure within
its declaration implies recursive activation.

Objects declared in the environment of the procedure are also visible in those parts of the procedure in which they are not concealed by a locally declared object with the same name.

ProcedureDeclaration = ProcedureHeading ";" ProcedureBody ident. ProcedureHeading = PROCEDURE [Receiver] IdentDef [FormalParameters]. ProcedureBody = DeclarationSequence [BEGIN StatementSequence] END. DeclarationSequence = {CONST {ConstantDeclaration ";"} | TYPE {TypeDeclaration ";"} | VAR {VariableDeclaration ";"} } {ProcedureDeclaration ";" | ForwardDeclaration ";"}. ForwardDeclaration = PROCEDURE " ^ " [Receiver] IdentDef [FormalParameters].If a procedure declaration specifies a

FormalParameters = "(" [FPSection {";" FPSection}] ")" [":" Qualident]. FPSection = [VAR] ident {"," ident} ":" Type.Let

If *Tf* is an open array, then *a* must be *array compatible* with
*f* (see App. A). The lengths of *f* are
taken from *a*.

Examples of procedure declarations:

PROCEDURE ReadInt(VAR x: INTEGER); VAR i: INTEGER; ch: CHAR; BEGIN i := 0; Read(ch); WHILE ("0" <= ch) & (ch <= "9") DO i := 10*i + (ORD(ch)-ORD("0")); Read(ch) END; x := i END ReadInt

PROCEDURE WriteInt(x: INTEGER); (*0 <= x <100000*) VAR i: INTEGER; buf: ARRAY 5 OF INTEGER; BEGIN i := 0; REPEAT buf[i] := x MOD 10; x := x DIV 10; INC(i) UNTIL x = 0; REPEAT DEC(i); Write(CHR(buf[i] + ORD("0"))) UNTIL i = 0 END WriteInt

PROCEDURE WriteString(s: ARRAY OF CHAR); VAR i: INTEGER; BEGIN i := 0; WHILE (i < LEN(s)) & (s[i] # 0X) DO Write(s[i]); INC(i) END END WriteString;

PROCEDURE log2(x: INTEGER): INTEGER; VAR y: INTEGER; (*assume x>0*) BEGIN y := 0; WHILE x > 1 DO x := x DIV 2; INC(y) END; RETURN y END log2

ProcedureHeading = PROCEDURE [Receiver] IdentDef [FormalParameters]. Receiver = "(" [VAR] ident ":" ident ")".If a procedure

If *v* is a designator and *P* is a type-bound procedure, then
*v.P* denotes that procedure *P* which 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*. *v* is passed to *P* 's receiver according to
the parameter passing rules specified in Chapter 10.1.

If *r* is a receiver parameter declared with type *T*, *r.P^*
denotes the (redefined) procedure *P* bound to the base type of *T*. 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 formal parameter
lists of both declarations must *match* (App. A).

Examples:

PROCEDURE (t: Tree) Insert (node: Tree); VAR p, father: Tree; BEGIN p := t; REPEAT father := p; IF node.key = p.key THEN RETURN END; IF 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;

PROCEDURE (t: CenterTree) Insert (node: Tree); (*redefinition*) BEGIN WriteInt(node(CenterTree).width); t.Insert^ (node) (* calls the Insert procedure bound to Tree *) END Insert;

Function Procedures Name Argument type Result type Function ABS(x) numeric type type of x absolute value ASH(x, n) x, n: integer type LONGINT arithmetic shift (x * 2^n) CAP(x) CHAR CHAR x is letter: corresponding capital letter CHR(x) integer type CHAR character with ordinal number x ENTIER(x) real type LONGINT largest integer not greater than x LEN(v, n) v: array; LONGINT length of v in dimension n n: integer const. (first dimension = 0) LEN(v) v: array LONGINT equivalent to LEN(v, 0) LONG(x) SHORTINT INTEGER identity INTEGER LONGINT REAL LONGREAL MAX(T) T = basic type T maximum value of type T T = SET INTEGER maximum element of a set MIN(T) T = basic type T minimum value of type T T = SET INTEGER 0 ODD(x) integer type BOOLEAN x MOD 2 = 1 ORD(x) CHAR INTEGER ordinal number of x SHORT(x) LONGINT INTEGER identity INTEGER SHORTINT identity LONGREAL REAL identity (truncation possible) SIZE(T) any type integer type number of bytes required by T

Proper procedures Name Argument types Function ASSERT(x) x: Boolean expression terminate program execution if not x ASSERT(x, n) x: Boolean expression; terminate program execution n: integer constant if not x COPY(x, v) x: character array, string; v := x v: character array DEC(v) integer type v := v - 1 DEC(v, n) v, n: integer type v := v - n EXCL(v, x) v: SET; x: integer type v := v - {x} HALT(n) integer constant terminate program execution INC(v) integer type v := v + 1 INC(v, n) v, n: integer type v := v + n INCL(v, x) v: SET; x: integer type v := v + {x} NEW(v) pointer to record or allocate v^ fixed array NEW(v,x0,...,xn) v: pointer to open array; allocate v^ with lengths xi: integer type x0..xnCOPY allows the assignment of a string or a character array containing a terminating 0X to another character array. If necessary, the assigned value is truncated to the target length minus one. The target will always contain 0X as a terminator. In ASSERT(x, n) and HALT(n), the interpretation of

Module = MODULE ident ";" [ImportList] DeclarationSequence [BEGIN StatementSequence] END ident ".". ImportList = IMPORT Import {"," Import} ";". Import = [ident ":="] ident.The import list specifies the names of the imported modules. If a module

The statement sequence following the symbol BEGIN is executed when the module is
added to a system (loaded), which is done after the imported modules have been
loaded. It follows that cyclic import of modules is illegal. Individual
(parameterless and exported) procedures can be activated from the system, and
these procedures serve as *commands* (see Appendix
D1).

MODULE Trees; (* exports: Tree, Node, Insert, Search, Write, Init *) IMPORT Texts, Oberon; (* exports read-only: Node.name *) TYPE Tree* = POINTER TO Node; Node* = RECORD name-: POINTER TO ARRAY OF CHAR; left, right: Tree END; VAR w: Texts.Writer; PROCEDURE (t: Tree) Insert* (name: ARRAY OF CHAR); VAR p, father: Tree; BEGIN p := t; REPEAT father := p; IF name = p.name^ THEN RETURN END; IF name < p.name^ THEN p := p.left ELSE p := p.right END UNTIL p = NIL; NEW(p); p.left := NIL; p.right := NIL; NEW(p.name, LEN(name)+1); COPY(name, p.name^); IF name < father.name^ THEN father.left := p ELSE father.right := p END END Insert; PROCEDURE (t: Tree) Search* (name: ARRAY OF CHAR): Tree; VAR p: Tree; BEGIN p := t; WHILE (p # NIL) & (name # p.name^) DO IF name < p.name^ THEN p := p.left ELSE p := p.right END END; RETURN p END Search; PROCEDURE (t: Tree) Write*; BEGIN IF t.left # NIL THEN t.left.Write END; Texts.WriteString(w, t.name^); Texts.WriteLn(w); Texts.Append(Oberon.Log, w.buf); IF t.right # NIL THEN t.right.Write END END Write; PROCEDURE Init* (t: Tree); BEGIN NEW(t.name, 1); t.name[0] := 0X; t.left := NIL; t.right := NIL END Init; BEGIN Texts.OpenWriter(w) END Trees.

**Integer types**- SHORTINT, INTEGER, LONGINT
**Real types**- REAL, LONGREAL
**Numeric types**- integer types, real types
**Same types**- Two variables
*a*and*b*with types*Ta*and*Tb*are of the*same*type if*Ta*and*Tb*are both denoted by the same type identifier, or*Ta*is declared to equal*Tb*in a type declaration of the form*Ta = Tb*, or*a*and*b*appear in the same identifier list in a variable, record field, or formal parameter declaration and are not open arrays.

**Equal types**- Two types
*Ta*and*Tb*are*equal*if*Ta*and*Tb*are the*same*type, or*Ta*and*Tb*are open array types with*equal*element types, or*Ta*and*Tb*are procedure types whose formal parameter lists*match*.

**Type inclusion**- Numeric types
*include*(the values of) smaller numeric types according to the following hierarchy:LONGREAL >= REAL >= LONGINT >= INTEGER >= SHORTINT

**Type extension**(base type)- 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 a type*Ta*(*Ta*is a*base type*of*Tb*) if*Ta*and*Tb*are the*same*types, or*Tb*is a*direct extension*of an*extension*of*Ta*

*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*. **Assignment compatible**- An expression
*e*of type*Te*is*assignment compatible*with a variable*v*of type*Tv*if one of the following conditions hold:*Te*and*Tv*are the*same*type;*Te*and*Tv*are numeric types and*Tv**includes**Te*;*Te*and*Tv*are record types and*Te*is an*extension*of*Tv*and the dynamic type of*v*is*Tv*;*Te*and*Tv*are pointer types and*Te*is an*extension*of*Tv*;*Tv*is a pointer or a procedure type and*e*is NIL;*Tv*is ARRAY n OF CHAR,*e*is a string constant with*m*characters, and*m*<*n*;*Tv*is a procedure type and*e*is the name of a procedure whose formal parameters*match*those of*Tv*.

**Array compatible**- An actual parameter
*a*of type*Ta*is*array compatible*with a formal parameter*f*of type*Tf*if*Tf*and*Ta*are the*same*type, or*Tf*is an open array,*Ta*is any array, and their element types are*array compatible*, or*Tf*is ARRAY OF CHAR and*a*is a string.

**Expression compatible**- For a given operator, the types of its operands are
*expression compatible*if they conform to the following table (which shows also the result type of the expression). Character arrays that are to be compared must contain 0X as a terminator. Type T1 must be an extension of type T0:operator first operand second operand result type + - *

*numeric**numeric*smallest*numeric*type including both operands /*numeric**numeric*smallest*real*type type including both operands + - * / SET SET SET DIV MOD*integer**integer*smallest*integer*type type including both operands OR & ~ BOOLEAN BOOLEAN BOOLEAN = # <*numeric**numeric*BOOLEAN <= > >= CHAR CHAR BOOLEAN character array, character array, BOOLEAN string string = # BOOLEAN BOOLEAN BOOLEAN SET SET BOOLEAN NIL, pointer type NIL, pointer type BOOLEAN T0 or T1 T0 or T1 procedure type T, procedure type T, BOOLEAN NIL NIL IN*integer*SET BOOLEAN IS type T0 type T1 BOOLEAN **Matching formal parameter lists**- Two formal parameter lists
*match*if- they have the same number of parameters, and
- they have either the
*same*function result type or none, and - parameters at corresponding positions have
*equal*types, and - parameters at corresponding positions are both either value or variable parameters.

Module = MODULE ident ";" [ImportList] DeclSeq [BEGIN StatementSeq] END ident ".". ImportList = IMPORT [ident ":="] ident {"," [ident ":="] ident} ";". 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. ForwardDecl = PROCEDURE "^" [Receiver] IdentDef [FormalPars]. FormalPars = "(" [FPSection {";" FPSection}] ")" [":" Qualident]. FPSection = [VAR] ident {"," ident} ":" Type. Receiver = "(" [VAR] ident ":" ident ")". Type = Qualident | ARRAY [ConstExpr {"," ConstExpr}] OF Type | RECORD ["("Qualident")"] FieldList {";" FieldList} END | POINTER TO Type | PROCEDURE [FormalPars]. 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 StatementSeq {"|" Guard DO StatementSeq} [ELSE StatementSeq] END | EXIT | RETURN [Expr] ]. 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. Set = "{" [Element {"," Element}] "}". Element = Expr [".." Expr]. Relation = "=" | "#" | "<" | "<=" | ">" | ">=" | IN | IS. AddOp = "+" | "-" | OR. MulOp = " * " | "/" | DIV | MOD | "&". Designator = Qualident {"." ident | "[" ExprList "]" | " ^ " | "(" Qualident ")"}. ExprList = Expr {"," Expr}. IdentList = IdentDef {"," IdentDef}. Qualident = [ident "."] ident. IdentDef = ident [" * " | "-"].

Module SYSTEM exports a type BYTE with the following characteristics: Variables of type CHAR or SHORTINT can be assigned to variables of type BYTE. If a formal variable parameter is of type ARRAY OF BYTE then the corresponding actual parameter may be of any type.

Another type exported by module SYSTEM is the type PTR. Variables of any pointer type may be assigned to variables of type PTR. If a formal variable parameter is of type PTR, the actual parameter may be of any pointer type.

The procedures contained in module SYSTEM are listed in the following tables.
Most of them correspond to single instructions compiled as in-line code. For
details, the reader is referred to the processor manual. *v* stands for a
variable, *x*, *y*, *a*, and *n* for expressions, and
*T* for a type.

Function proceduresName Argument types Result type Function ADR(v) any LONGINT address of variable v BIT(a, n) a: LONGINT BOOLEAN bit n of Mem[a] n:integerCC(n)integerconstant BOOLEAN condition n (0 <= n <= 15) LSH(x, n) x:integer, CHAR, BYTE type of x logical shift n:integerROT(x, n) x:integer, CHAR, BYTE type of x rotation n:integerVAL(T, x) T, x: any type T x interpreted as of type T

Proper proceduresName Argument types Function GET(a, v) a: LONGINT; v: any basic type, v := Mem[a] pointer, procedure type PUT(a, x) a: LONGINT; x: any basic type, Mem[a] := x pointer, procedure type GETREG(n, v) n:integerconstant; v := Register n v: any basic type, pointer, procedure type PUTREG(n, x) n:integerconstant; Register n := x x: any basic type, pointer, procedure type MOVE(a0,a1,n) a0, a1: LONGINT; n:integerMem[a1..a1+n-1] := Mem[a0.. a0+n-1]) NEW(v, n) v: any pointer; n:integerallocate storage block of n bytes; assign its address to v

The following module demonstrates the use of commands. It implements an abstract
data structure *Counter* that encapsulates a counter variable and provides
commands to increment and print its value.

MODULE Counter; IMPORT Texts, Oberon; VAR counter: LONGINT; w: Texts.Writer; PROCEDURE Add*; (* takes a numeric argument from the command line *) VAR s: Texts.Scanner; BEGIN Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(s); IF s.class = Texts.Int THEN INC(counter, s.i) END END Add; PROCEDURE Write*; BEGIN Texts.WriteInt(w, counter, 5); Texts.WriteLn(w); Texts.Append(Oberon.Log, w.buf) END Write; BEGIN counter := 0; Texts.OpenWriter(w) END Counter.The user may execute the following two commands:

`Counter.Add`*n*- adds the value
*n*to the variable*counter* `Counter.Write`- writes the current value of
*counter*to the screen

When *Counter.Add* is invoked for the first time, the module *Counter*
is loaded and its body is executed. Every call of *Counter.Add n* increments
the variable *counter* by *n*. Every call of *Counter.Write*
writes the current value of *counter* to the screen.

Since a module remains loaded after the execution of its commands, there must be an explicit way to unload it (e.g. when the user wants to substitute the loaded version by a recompiled version.) The Oberon system provides a command to do that.

A module *M0* may cause the dynamic loading of a module *M1* without
importing it. *M1* may of course import and use *M0*, but *M0*
need not know about the existence of *M1*. *M1* can be a module that is
designed and implemented long after *M0*.

A garbage collector frees a programmer from the non-trivial task of deallocating data structures correctly and thus helps to avoid errors. However, it requires information about dynamic data at run time (see D5).

DEFINITION Trees; TYPE Tree = POINTER TO Node; Node = RECORD name: POINTER TO ARRAY OF CHAR; PROCEDURE (t: Tree) Insert (name: ARRAY OF CHAR); PROCEDURE (t: Tree) Search (name: ARRAY OF CHAR): Tree; PROCEDURE (t: Tree) Write; END; PROCEDURE Init (VAR t: Tree); END Trees.For a record type, the browser also collects all procedures bound to this type and shows their declaration in the record type declaration.

The dynamic type of a record corresponds to the address of its type descriptor.
For dynamically allocated records this address is stored in a so-called *type
tag* which precedes the actual record data and which is invisible for the
programmer. If t is a variable of type *CenterTree* (see
example in Ch. 6) Figure D5.1 shows one possible implementation of the run
time data structures.

**Fig. D5.1** A variable *t* of type *CenterTree*, the record
*t^* it points to, and its type descriptor

Since both the table of procedure addresses and the table of pointer offsets must have a fixed offset from the type descriptor address, and since both may grow when the type is extended and further procedures and pointers are added, the tables are located at the opposite ends of the type descriptor and grow in different directions.

A type-bound procedure *t.P* is called as *t^.tag^.ProcTab[IndexP]*.
The procedure table index of every type-bound procedure is known at compile time.
A type test *v IS T* is translated into
*v^.tag^.BaseTypes[ExtensionLevelT] = TypeDescrAdrT*. Both the extension
level of a record type and the address of its type descriptor are known at
compile time. For example, the extension level of *Node* is 0 (it has no
base type), and the extension level of *CenterNode* is 1.

- N.Wirth, J.Gutknecht: The Oberon System. Software Practice and Experience 19, 9, Sept. 1989
- M.Reiser: The Oberon System. User Guide and Programming Manual. Addison-Wesley, 1991
- C.Pfister, B.Heeb, J.Templ: Oberon Technical Notes. Report 156, ETH Zürich, March 1991

Adapted to HTML by Jürgen Geßwein; 9. Juni 1995