[Search for users] [Overall Top Noters] [List of all Conferences] [Download this site]

Conference turris::languages

Title:Languages
Notice:Speaking In Tongues
Moderator:TLE::TOKLAS::FELDMAN
Created:Sat Jan 25 1986
Last Modified:Wed May 21 1997
Last Successful Update:Fri Jun 06 1997
Number of topics:394
Total number of notes:2683

318.0. "Oberon-2" by BIGUN::PHILLIPS (Blair Phillips, SI, Canberra, Oz) Fri Oct 04 1991 05:14

I recently saw reference to Oberon-2 (in the Modula-3 news group).

Does anyone know of any published material on this language?

    thanks,
T.RTitleUserPersonal
Name
DateLines
318.1PrefaceCHEST::WATSONC++ may be the cureThu Oct 17 1991 11:5369
    The following reply is the text of the Oberon-2 report + some misc.
    other stuff. This (Well the postrscript for it) was obtained from ETH
    and they maintain the copyright. It is reproduced here with permission
    providing that the following text follows it :
    
From:	DECPA::NOBODY "ftpmail daemon on mts-gw.pa.dec.com"  6-AUG-1991 08:41:51.33
To:	futurs::watson 
CC:	
Subj:	Oberon/readme part 1 (ascii, last) 

 
Oberon
 
Copyright 1991, ETH Zuerich
 
Permission to use, copy, modify and distribute this software
and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear
in all copies and that both that copyright notice and this
permission notice appear in supporting documentation, and
that the name of ETH not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission.  ETH makes no representations about
the suitability of this software for any purpose.  It is provided
"as is" without express or implied warranty.
 
ETH DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ETH
BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
OUT OF OR IN  CONNECTION WITH THE USE OR PERFORMANCE
OF THIS SOFTWARE.
 
 
Syntax Font
Copyright 1968 Linotype AG
 
Syntax is a registered trademark of Linotype AG.
The Syntax font is owned and used by permission of Linotype AG.
 
RASTER DATA DESCRIBING THE SYNTAX FONT IS SUPPLIED WITH
THIS RELEASE OF OBERON AND MAY BE USED ONLY IN
CONJUNCTION WITH THE OBERON SOFTWARE.  ANY OTHER
USE OF THE SYNTAX FONT REQUIRES A LICENSE FROM LINOTYPE AG.
 
PostScript versions of the fonts in the Syntax family are available
from Linotype, Inc. and its subsidiaries.
 
 
This directory contains the following subdirectories:
  SPARC  contains the Sun-SPARC version.
  MacII  contains the Apple-Macintosh-II version.
  DECstation  contains the DECstation version.
  Docu  contains general Oberon documentation (not related to
        a particular implementation) as compressed postscript files.
 
See the readme files in the appropriate subdirectory for
further important notes.
 
 
% ====== Internet headers and postmarks (see DECWRL::GATEWAY.DOC) ======
Received: by mts-gw.pa.dec.com; id AA16800; Tue, 6 Aug 91 00:42:06 -0700
From: nobody (ftpmail daemon on mts-gw.pa.dec.com)
To: futurs::watson
Subject: Oberon/readme part 1 (ascii, last)
    
318.2Long reply warningCHEST::WATSONC++ may be the cureThu Oct 17 1991 11:541
    The next reply is 1614 lines = 130 blocks DECwindows users beware.
318.3ETH Yellow Book 160 / Full textCHEST::WATSONC++ may be the cureThu Oct 17 1991 11:551614
Differences between Oberon and Oberon-2

H. M�ssenb�ck, N. Wirth

Institut f�r Computersysteme, ETH Z�rich

Oberon-2 is a true extension of Oberon [1]. This paper summarizes the
extensions and tries to shed some light on the motivations behind them. By that
we hope to make it easier for the reader to classify Oberon-2. For details the
reader is referred to the language report.

One important goal for Oberon-2 was to make object-oriented programming easier
without sacrificing the conceptual simplicity of Oberon. After three years of
using Oberon and its experimental offspring Object Oberon [2] we merged our
experiences into a single refined version of Oberon. The new features of
Oberon-2 are type-bound procedures, read-only export of variables and record
fields, open arrays as pointer base types, and a with statement with variants.
The for statement is reintroduced after having been eliminated in the step from
Modula-2 to Oberon.

Oberon-2 is the result of many discussions among all members of the Institute
for Computer Systems at ETH. It is particularly influenced by the ideas of J�rg
Gutknecht and Josef Templ.

Type-bound procedures

Procedures can be bound to a record (or a pointer) type. They are equivalent to
methods in object- oriented terminology. The binding is expressed by a separate
parameter (the operand to which the procedure is applicable, or the "receiver"
as it is called in object-oriented terminology).

  TYPE
    Figure = POINTER TO FigureDesc;
    FigureDesc = RECORD
      x, y, w, h: INTEGER
    END;

  PROCEDURE (f: Figure) Draw; BEGIN ... END Draw;
  PROCEDURE (f: Figure) Move (dx, dy: INTEGER); BEGIN ... END Move;

Draw and Move are bound to Figure which means that they are operations
applicable to Figure objects. They are considered local to FigureDesc and can
be referenced like record fields, e.g. f.Move(10, 10) if f is a variable of
type Figure.

Any procedure bound to a type T is implicitly also bound to all extensions of
T. It can be redefined (overridden) by a procedure with the same name and the
same formal parameter list which is explicitly bound to an extension of T, such
as in

TYPE
  Circle = POINTER TO CircleDesc;
  CircleDesc = RECORD (FigureDesc)
    radius: INTEGER
  END;

PROCEDURE (c: Circle) Move (dx, dy: INTEGER);
BEGIN ...
END Move;

Circle is an extension of Figure. A procedure Move is explicitly bound to 
Circle and redefines the Move that is ``inherited'' from Figure. Let f be a
variable of type Figure and a variable of type Circle; then the assignment  f
:= c makes the dynamic type of f (its run time type) be Circle instead of
Figure. In the call

f.Move(10, 10)

the variable f serves two purposes: First it is passed as the receiver
parameter to the procedure Move. Second, its dynamic type determines which
variant of Move is called. Since after the assignment f := c the dynamic type
of f is Circle, the Move that is bound to Circle is called and not the one that
is bound to Figure. This mechanism is called dynamic binding, since the dynamic
type of the receiver is used to bind the procedure name to the actual
procedure.

Within a redefining procedure the redefined procedure can be invoked by calling
it with the suffix ^, e.g. f.Move^(dx, dy).

Motivation. We refrained from introducing the concept of a class but rather
replaced it by the well-known concept of records. Classes are simply record
types with procedures bound to them.

We also refrained from duplicating the headers of bound procedures in the
record as it is done in other object-oriented languages like C++ or Object
Pascal. This keeps record declarations short and avoids unpleasant redundancy
(changes to a header would have to be made at two places in the program and the
compiler would have to check the equality of the headers). If the programmer
wants to see the record together with all procedures bound to it he uses a tool
(a browser) to obtain the information on screen or on paper.

The procedures bound to a type may be declared in any order. They can even be
mixed with procedures bound to a different type. In Object Oberon, where all
methods have to be declared within their class declaration, it turned out that
indirect recursion between methods of different classes make awkward forward
declarations of whole classes necessary.

In languages like Object Pascal or C++, instance variables of the receiver
object self can be accessed with or without qualification (i.e. one can write
either x or self.x). In these languages it is sometimes difficult to see
whether a name is an ordinary variable or an instance variable. It is even more
confusing if the name denotes an instance variable that is inherited from a
base class. We therefore decided that instance variables must always be
qualified in Oberon-2. This avoids having a choice between two semantically
equivalent constructs, which we consider undesirable in programming languages.

In Oberon-2, the receiver is an explicit parameter, so the programmer can
choose a meaningful name for it, which is usually more expressive than the
predeclared name self that is used in other object- oriented languages. The
explicit declaration of the receiver makes clear that the object to which an
operation is applied is passed as a parameter to that operation. This is
usually not expressed in other object-oriented languages. It is in the spirit
of Oberon to avoid hidden mechanisms.

In Object Oberon methods have the same syntax as ordinary procedures. In large
classes where the class header is not visible near the method header it is
impossible to see whether the procedure is an ordinary procedure or a method,
and to which class the method belongs. In Oberon-2, the type of the receiver
parameter of a bound procedure denotes the type to which the procedure is
bound, so no confusion can arise.

Read-only export

While in Oberon all exported variables and record fields can be modified by a
client module, it is possible in Oberon-2 to restrict the use of an exported
variable or record field to read-only access. This is expressed by marking its
declaration with a "-" instead of a "*". The "-" suggests the restricted use of
such a variable.

TYPE
  Rec* = RECORD
    f0* : INTEGER;
    f1- : INTEGER;
    f2  : INTEGER
  END;

VAR
  a* : INTEGER;
  b- : Rec;
  c  : INTEGER;

Client modules can read the variables a and b as well as the fields f0 and 
f1, since these objects are exported. However, they can modify only a and f0;
the value of b and f1 can be read but not modified. Only the module which
exports these objects can modify their values. (Even if clients declare a
private variable of type Rec, its field f1 is read-only.) Since b is
read-only, its components are read-only, too.

The motivation behind read-only export is to allow a finer grain of information
hiding. Information hiding serves two purposes: First, it helps to keep off
unnecessary details from clients. Second, it allows establishing the assertion
that the values of hidden variables are only modified by access procedures of
the module itself, which is important to guarantee invariants. Read-only export
supports the second goal.

Open arrays

Both in Modula-2 and in Oberon it is possible to have open arrays as
parameters. The length of such an array is given by the length of the actual
parameter. In Oberon-2 open arrays may not only be declared as formal
parameter types but also as pointer base types. In this case, the predeclared
procedure NEW is used to allocate the open array with arbitrary length.

VAR v: POINTER TO ARRAY OF INTEGER;
... NEW (v, 100)

The array v^ is allocated at run time with a length of 100 elements accessed
as v[0] to v[99].

With statements

In Oberon, a with statement is a regional type guard of the form

WITH v: T DO S END;

If the variable v is of dynamic type T, then the statement sequence S
is executed where a type guard v(T) is applied to every occurrence of v, i.e. 
v is regarded as if it had the static type T. If the dynamic type of v is not 
T the program is aborted. In Oberon-2, the with statement can be written with
variants, e.g:

WITH v : T0 DO S0
|   v : T1 DO S1
ELSE S2
END

If the dynamic type of v is T0, then S0 is executed and v is regarded as
if it had the static type T0; if the dynamic type of v is T1, then S1 is
executed and v is regarded as if it had the static type T1; else S2 is
executed. If no variant can be executed and if an else clause is missing the
program is aborted.

For statements

Although for statements can always be expressed by while statements, they are
sometimes more convenient because they are shorter and termination is inherent.
This is the case if the number of iterations is fixed like in many applications
dealing with arrays. The for statement is written as:

FOR i := a TO b BY step DO statements END

The control variable i as well as the expressions a and b and the constant
expression step must be of an integer type. The above for statement is
equivalent to the statement sequence

i := a; temp := b;
IF step > 0 THEN
  WHILE i <= temp DO statements; i := i + step END;
ELSE
  WHILE i >= temp DO statements; i := i + step END;
END;

References

[1] N.Wirth:
  The Programming Language Oberon.
    Software Practice & Experience, 18 (1988)
[2] H.M�ssenb�ck, J. Templ:
  Object Oberon -- A Modest Object-Oriented Language.
    Structured Programming 10/4: 199-207, 1989

The Programming Language Oberon-2

H. M�ssenb�ck, N. Wirth

Institut f�r Computersysteme, ETH Z�rich

1. Introduction

Oberon-2 is a general-purpose language in the tradition of Oberon and Modula-2.
Its most important features are block structure, modularity, separate
compilation, static typing with strong type checking (also across module
boundaries), and type extension with type-bound procedures.

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).

2. Syntax

An extended Backus-Naur Formalism (EBNF) is used to describe the syntax of
Oberon-2: Brackets [ and ] denote optionality of the enclosed expression, and
braces { and } denote its repetition (possibly 0 times). Non-terminal symbols
start with an upper-case letter (e.g. Statement). Terminal symbols either start
with a lower-case letter (e.g. ident), or are written all in upper-case letters
(e.g. BEGIN), or are denoted by strings (e.g. ":=").

3. Vocabulary and Representation

The representation of (terminal) symbols in terms of characters is defined
using the ASCII set. Symbols are identifiers, numbers, strings, operators, and
delimiters. The following lexical rules must be observed: Blanks and line
breaks must not occur within symbols (except in comments, and blanks in
strings). They are ignored unless they are essential to separate two
consecutive symbols. Capital and lower-case letters are considered as distinct.

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

ident = letter {letter | digit}.

Examples:     x     Scan     Oberon2     GetSymbol     firstLetter

2. Numbers are (unsigned) integer or real constants. Integers are sequences of
digits and may be followed by a suffix letter. The type is the minimal type to
which the number belongs (see 6.1). If no suffix is specified, the
representation is decimal. The suffix H indicates hexadecimal representation. 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.		REAL		12.3
4.567E8		REAL		456700000
0.57712566D-6	LONGREAL	0.00000057712566

3. Character constants are either denoted by a single character enclosed in
single (') or double (") quote marks or by the ordinal number of the character
in hexadecimal notation followed by the letter X.

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

4. Strings are sequences of characters enclosed in single (') or double (")
quote marks. The opening quote must be the same as the closing quote and must
not occur within the string. The number of characters in a string is called its 
length.

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

Examples:     "Oberon-2"     "Don't worry!"

5. Operators and delimiters are the special characters, character pairs, or
reserved words listed below. The reserved words consist exclusively of capital
letters and cannot be used as identifiers.

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

6. Comments may be inserted between any two symbols in a program. They are
arbitrary character sequences opened by the bracket (* and closed by *).
Comments may be nested. They do not affect the meaning of a program.

4. Declarations and scope rules

Every identifier occurring in a program must be introduced by a declaration,
unless it is a predeclared identifier. Declarations also specify certain
permanent properties of an object, such as whether it is a constant, a type, a
variable, or a procedure. The identifier is then used to refer to the
associated object. 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:

1. No identifier may denote more than one object within a given scope (i.e. no
identifier may be declared twice in a block);

2. An object may only be referenced within its scope;

3. A type T of the form POINTER TO T1 (see 6.4) can be declared before the
scope of T1. In this case, the declaration of T1 must follow in the same block
to which T is local;

4. Identifiers denoting record fields (see 6.3) or type-bound procedures (see
10.2) are valid in record designators only.

An identifier declared in a module block may be followed by an export mark ("*"
or "-") in its declaration to indicate that it is exported. An identifier x
exported by a module M may be used in other modules, if they import M (see
Ch.11). The identifier is then denoted as M.x in these modules and is called a
qualified identifier. Identifiers marked with "-" in their declaration are 
read-only in importing modules.

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)

5. Constant declarations

A constant declaration associates an identifier with a constant value.

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)}

6. Type declarations

A data type determines the set of values which variables of that type may
assume, and the operators that are applicable. A type declaration associates an
identifier with a type. In the case of structured types (arrays and records) it
also defines the structure of variables of this type.

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

6.1 Basic types

The basic types are denoted by predeclared identifiers. The associated
operators are defined in 8.2 and the predeclared function procedures in 10.3.
The values of the given basic types are the following:

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 integer types, types 6 and 7 are real types, and together
they are called numeric types. They form a hierarchy; the larger type 
includes (the values of) the smaller type:

LONGREAL \312  REAL  \312  LONGINT  \312  INTEGER  \312  SHORTINT

6.2 Array types

An array is a structure consisting of a number of elements which are all of the
same type, called the element type. The number of elements of an array is
called its length. The elements of the array are designated by indices, which
are integers between 0 and the length minus 1.

ArrayType = ARRAY [Length {"," Length}] OF Type.
Length = ConstExpression.

A declaration of the form

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

is understood as an abbreviation of the declaration

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) and formal parameter types (see 10.1).

Examples:
ARRAY 10, N OF INTEGER
ARRAY OF CHAR

6.3 Record types

A record type is a structure consisting of a fixed number of elements, called 
fields with possibly different types. The record type declaration specifies
the name and type of each field. The scope of the field identifiers extends
from the point of their declaration to the end of the record type, but they are
also visible within designators referring to elements of record variables (see
8.1). If a record type is exported, field identifiers that are to be visible
outside the declaring module must be marked. They are called public fields;
unmarked elements are called private fields.

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 (see Ch. 6). Examples of record type
declarations:

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

6.4 Pointer types

Variables of a pointer type P assume as values pointers to variables of some
type T. T is called the pointer base type of P and must be a record or array
type. Pointer types inherit the extension relation of their pointer base types:
if a type T1 is an extension of T, and P1 is of type POINTER TO T1, then P1 is
also an extension of P.

PointerType = POINTER TO Type.

If p is a variable of type P = POINTER TO T, a call of the predeclared
procedure NEW (see 10.3) allocates a variable of type T in free storage. If T
is a record type or an array type with fixed length, the allocation has to be
done with NEW(p); if T is an n-dimensional open array type the allocation has
to be done with NEW(p, e0, ..., en-1) where T is allocated with lengths given
by the expressions e0, ..., en-1. In either case a pointer to the allocated
variable is assigned to p. p is of type P. The referenced variable p^ is of
type T. Any pointer variable may assume the value NIL, which points to no
variable at all. All pointer variables are initialized to NIL.

6.5 Procedure types

Variables of a procedure type T have a procedure (or NIL) as value. If a
procedure P is assigned to a variable of type T, the formal parameter lists of
P and T must match (see App. A). P must not be a predeclared or type-bound
procedure nor may it be local to another procedure.

ProcedureType = PROCEDURE [FormalParameters].

7. Variable declarationse

Variable declarations introduce variables by defining an identifier and a data
type for them.

VariableDeclaration = IdentList ":" Type.

Record and pointer variables have both a static type (the type with which they
are declared -- simply called their type) and a dynamic type (the type they
assume at run time). For pointers and variable parameters of record type the
dynamic type may be an extension of their static type. The static type
determines which fields of a record are accessible. The dynamic type is used to
call type-bound procedures (see 10.2). 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

8. Expressions

Expressions are constructs denoting rules of computation whereby constants and
current values of variables are combined to compute other values by the
application of operators and function procedures. Expressions consist of
operands and operators. Parentheses may be used to express specific
associations of operators and operands.

8.1 Operands

With the exception of set constructors and literal constants (numbers,
character constants, or strings), operands are denoted by designators. A
designator consists of an identifier referring to a constant, variable, or
procedure. This identifier may possibly be qualified by a module identifier
(see Ch. 4 and 11) and may be followed by selectors if the designated object is
an element of a structure.

Designator = Qualident {"." ident | "[" ExpressionList "]" | "^"
  | "(" Qualident ")"}.
ExpressionList = Expression {"," Expression}.

If a designates an array, then a[e] denotes that element of  a whose index is
the current value of the expression e. The type of e must be an integer type.
A designator of the form a[e0, e1, ...,  en] stands for a[e0][e1]...[en]. If
r designates a record, then r.f denotes the field f of r or the procedure f
bound to the dynamic type of r (Ch. 10.2). If p designates a pointer, p^
denotes the variable which is referenced by p. The designators p^.f and p^[e]
may be abbreviated as p.f and p[e], i.e. record and array selectors imply
dereferencing. If a or r are read-only, then also a[e] and r.f are read-only.

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

1. v is a variable parameter of record type or v is a pointer, and if

2. T is an extension of the static type of v

If the designated object is a variable, then the designator refers to the
variable's current value. If it is a procedure, the designator refers to that
procedure unless it is followed by a (possibly empty) parameter list in which
case it implies an activation of that procedure and stands for the value
resulting from its execution. The actual parameters must correspond to the
formal parameters as in proper procedure calls (see 10.1). Examples of
designators (refer to examples in Ch.7):

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

8.2 Operators

Four classes of operators with different precedences (binding strengths) are
syntactically distinguished in expressions. The operator ~ has the highest
precedence, followed by multiplication operators, addition operators, and
relations. Operators of the same precedence associate from left to right. For
example, x-y-z stands for (x-y)-z.

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 expression compatible with respect to the operator (see
App.A).

8.2.1 Logical operators

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.

8.2.2 Arithmetic operators

+	sum
-	difference
*	product
/	real quotient
DIV	integer quotient
MOD	modulus

The 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 and positive divisors y:

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

8.2.3 Set Operators

+	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 x, i.e. -x denotes the set of
integers between 0 and MAX(SET) which are not elements of x.

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].

8.2.4 Relations

=	equal
#	unequal
<	less
<=	less or equal
>	greater
>=	greater or equal
IN	set membership
IS	type test

Relations yield a BOOLEAN result. The ordering relations <, <=, >, and >= apply
to the numeric types, CHAR, (open) character arrays, and strings. The relations
= and # also apply to BOOLEAN and SET, as well as to pointer and procedure
types (including the value NIL). x IN s stands for " x is an element of s". 
x must be of an integer type, and s of type SET. v IS T stands for "the dynamic
type of v is T (or an extension of T)" and is called a type test. It is
applicable if

1. v is a variable parameter of record type or v is a pointer, and if

2. T is an extension of the static type of v

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

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 CenterNode		BOOLEAN

9. Statements

Statements denote actions. There are elementary and structured statements.
Elementary statements are not composed of any parts that are themselves
statements. They are the assignment, the procedure call, the return, and the
exit statement. Structured statements are composed of parts that are themselves
statements. They are used to express sequencing and conditional, selective, and
repetitive execution. A statement may also be empty, in which case it denotes
no action. The empty statement is included in order to relax punctuation rules
in statement sequences.

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

9.1 Assignments

Assignments replace the current value of a variable by a new value specified by
an expression. The expression must be assignment compatible with the variable
(see App. A). The assignment operator is written as ":=" and pronounced as 
becomes.

Assignment = Designator ":=" Expression.

If an expression e of type Te is assigned to a variable v of type Tv,
the following happens:

1. 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;

2. if Tv and Te are pointer types, the dynamic type of v becomes the
dynamic type of e ;

3. if Tv is ARRAY n OF CHAR and e is a string of length m<n, v[i]
becomes e i for i = 0.. m-1 and v [m] becomes 0X.

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

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

9.2 Procedure calls

A procedure call activates a procedure. It may contain a list of actual
parameters which replace the corresponding formal parameters defined in the
procedure declaration (see Ch. 10). The correspondence is established by the
positions of the parameters in the actual and formal parameter lists. There are
two kinds of parameters: variable and value parameters.

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 *)

9.3 Statement sequences

Statement sequences denote the sequence of actions specified by the component
statements which are separated by semicolons.

StatementSequence = Statement {";" Statement}.

9.4 If statements

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 guard.
The guards are evaluated in sequence of occurrence, until one evaluates to
TRUE, whereafter its associated statement sequence is executed. If no guard is
satisfied, the statement sequence following the symbol ELSE is executed, if
there is one.

Example:

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

9.5 Case statements

Case statements specify the selection and execution of a statement sequence
according to the value of an expression. First the case expression is
evaluated, then that statement sequence is executed whose case label list
contains the obtained value. The case expression and all labels must be of the 
same type, which must be an integer type or CHAR. Case labels are constants,
and no value must occur more than once. If the value of the expression does not
occur as a label of any case, the statement sequence following the symbol ELSE
is selected, if there is one, otherwise the program is aborted.

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

9.6 While statements

While statements specify the repeated execution of a statement sequence while
the Boolean expression (its guard) yields TRUE. The guard is checked before
every execution of the statement sequence.

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

9.7 Repeat statements

A repeat statement specifies the repeated execution of a statement sequence
until a condition specified by a Boolean expression is satisfied. The statement
sequence is executed at least once.

RepeatStatement = REPEAT StatementSequence UNTIL Expression.

9.8 For statements

A for statement specifies the repeated execution of a statement sequence for a
fixed number of times while a progression of values is assigned to an integer
variable called the control variable of the for statement.

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

The statement FOR v := low TO high BY step DO statements END is equivalent to

v := low; temp := high;
IF step > 0 THEN
WHILE v <= temp DO statements; v := v + step END
ELSE
WHILE v >= temp DO statements; v := v + step END
END

low must be assignment compatible with v (see App. A), high must be  expression
compatible (i.e. comparable) with v, and step must be a nonzero constant
expression of an integer type. 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

9.9 Loop statements

A loop statement specifies the repeated execution of a statement sequence. It
is terminated upon execution of an exit statement within that sequence (see
9.10).

LoopStatement = LOOP StatementSequence END.

Example:

LOOP
  ReadInt(i);
  IF i < 0 THEN EXIT END;
  WriteInt(i)
END

Loop 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.

9.10 Return and exit statements

A return statement indicates the termination of a procedure. It is denoted by
the symbol RETURN, followed by an expression if the procedure is a function
procedure. The type of the expression must be assignment compatible (see App.
A) with the result type specified in the procedure heading (see Ch.10).
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.

9.11 With statements

With statements execute a statement sequence depending on the result of a type
test and apply a type guard to every occurrence of the tested variable within
this statement sequence.

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

If v is a variable parameter of record type or a pointer variable, and if it is
of a static type T0, the statement

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

10. Procedure declarations

A procedure declaration consists of a procedure heading and a procedure body.
The heading specifies the procedure identifier and the formal parameters. For
type-bound procedures it also specifies the receiver parameter. The body
contains declarations and statements. The procedure identifier is repeated at
the end of the procedure declaration. 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. In addition to its formal
parameters and locally declared objects, the objects declared in the
environment of the procedure are also visible in the procedure (with the
exception of those objects that have the same name as an object declared
locally).

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 receiver parameter, the procedure is
considered to be bound to a type (see 10.2). A forward declaration serves to
allow forward references to a procedure whose actual declaration appears later
in the text. The formal parameter lists of the forward declaration and the
actual declaration must match (see App. A).

10.1 Formal parameters

Formal parameters are identifiers declared in the formal parameter list of a
procedure. They correspond to actual parameters specified in the procedure
call. The correspondence between formal and actual parameters is established
when the procedure is called. There are two kinds of parameters, value and
variable parameters, indicated in the formal parameter list by the absence or
presence of the keyword VAR. Value parameters are local variables to which the
value of the corresponding actual parameter is assigned as an initial value.
Variable parameters correspond to actual parameters that are variables, and
they stand for these variables. The scope of a formal parameter extends from
its declaration to the end of the procedure block in which it is declared. A
function procedure without parameters must have an empty parameter list. It
must be called by a function designator whose actual parameter list is empty
too.

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

Let T f be the type of a formal parameter f and T a the type of the
corresponding actual parameter a. For variable parameters, T a must be the 
same as T f, or T f must be a record type and T a an extension of T f.
For value parameters, a must be assignment compatible with f (see App. A).
If T f is an open array, then a must be array compatible with f (see App.
A). The lengths of f are taken from a. The result type of a procedure can be
neither a record nor an array.

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 <10^5*)
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;

10.2 Type-bound procedures

An abstract data type consists of a record type and a set of associated
procedures which are said to be bound to it. The binding is expressed by the
type of the receiver in the heading of a procedure declaration. The receiver
may be either a variable parameter of record type or a value parameter of type
pointer to record. The procedure is bound to the type of the receiver. If it is
bound to a pointer type it is also bound to its pointer base type. A procedure
bound to a record type is considered local to it.

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

If a procedure P is bound to a type T0, it is implicitly also bound to any
type T1 which is an extension of T0. However, a procedure P' (with the same
name as P) may be explicitly bound to T1 in which case it overrides the binding
of P. P' is considered as a redefinition of P for T1. The formal parameters of
P and P' must match (see App. A). 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 (dynamic binding). 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;

10.3 Predeclared procedures

The following table lists the predeclared procedures. Some are generic
procedures, i.e. they apply to several types of operands. v stands for a
variable, x and n for expressions, and T for a type.

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;  n : integer const. LONGINT length of v in dimension n
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

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(x)		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 fixed array	allocate v^
NEW(v, x0,	v : pointer to open array;		allocate v^
  ..., xn)	xi : integer type			with lengths  x0 .. xn

COPY allows the assignment between (open) character arrays with different
types. If necessary, the source is shortened to the target length minus one.
The target is always terminated by the character 0X. In

HALT(x), the interpretation of x is left to the underlying system
implementation.

11. Modules

A module is a collection of declarations of constants, types, variables, and
procedures, together with a sequence of statements for the purpose of assigning
initial values to the variables. A module constitutes a text that is compilable
as a unit.

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 A is
imported by a module M and A exports an identifier x, then x is referred to
as A.x within M. If A is imported as B := A, the object x is referenced as
B.x. This allows short alias names in qualified identifiers. Identifiers that
are to be exported (i.e. that are to be visible in client modules) must be
marked by an export mark in their declaration (see Chapter 4).

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, NewTree *)
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 NewTree* (): Tree;
VAR t: Tree;
BEGIN
  NEW(t); NEW(t.name, 1); t.name[0] := 0X; t.left := NIL; t.right := NIL;
  RETURN t
END NewTree;

BEGIN Texts.OpenWriter(w)
END Trees.

Appendix A: Definition of terms

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

1. Ta and Tb are both denoted by the same type identifier, or
2. Ta is declared to equal T b in a type declaration of the form Ta = Tb, or
3. a and b appear in the same identifier list in a variable, record field, or
   formal parameter declaration. Equal types Two types Ta and Tb are equal if

  1. Ta and Tb are the same type, or
  2. Ta and Tb are open array types with equal element types.

Type inclusion

Numeric types include (the values of) smaller numeric types according to the
following hierarchy:

LONGREAL \312 REAL \312 LONGINT \312 INTEGER \312 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

1. Ta and Tb are the same types, or

2. Tb is a direct extension of an extension of Ta If 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:

1. Te and Tv are the same type but are not open arrays;
2. Te and Tv are numeric types and Tv includes Te;
3. Te and Tv are record types and Te is an extension of Tv and the
   dynamic type of v is Tv;
4. Te and Tv are pointer types and Te is an extension of Tv;
5. Tv is a pointer or a procedure type and e is NIL;
6. Tv is ARRAY n OF CHAR, e is a string constant with m characters, and m < n;
7. Tv is a procedure type and e is the name of a procedure whose formal
   parameters match those ofTv.

Array compatible

An actual parameter a of type Ta is array compatible with a formal parameter f
of type Tf if

1. Tf and Ta are the same type, or
2. Tf is an open array, Ta is any array, and their element types are array
   compatible.

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):

operator 	valid operand types 	result type

+ - *		numeric			largest numeric type of the operands
/		numeric			smallest real type incl. both operands
+ - * /		SET			SET
DIV MOD		integer			largest integer type of the operands
OR & ~		BOOLEAN			BOOLEAN
= # < <= > >=	numeric, CHAR,		BOOLEAN
		character arrays,
		strings
= #		BOOLEAN, SET,		BOOLEAN
		pointers (incl. NIL),
		procedure types (incl. NIL)
IN		1st: integer; 2nd: SET	BOOLEAN
IS		1st: pointer or		BOOLEAN
		 record variable
		2nd: pointer or record type

Matching formal parameter lists

Two formal parameter lists match if

1. they have the same number of parameters, and
2. they have either the same function result type or none, and
3. parameters at corresponding positions have equal types, and
4. parameters at corresponding positions are both either value or
   variable parameters.

Appendix B: Syntax of Oberon-2

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 ["*" | "-"].

Appendix C: The module SYSTEM

The module SYSTEM contains certain types and procedures that are necessary to
implement low-level operations particular to a given computer and/or
implementation. These include for example facilities for accessing devices that
are controlled by the computer, and facilities to break the type compatibility
rules otherwise imposed by the language definition. It is strongly recommended
to restrict their use to specific modules (called low-level modules). Such
modules are inherently non-portable, but easily recognized due to the
identifier SYSTEM appearing in their import list. The following specifications
hold for the implementation of Oberon-2 on the Ceres computer. 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 procedures	Name	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 : integer type
CC(n)		integer constant BOOLEAN condition  n (0 <= n <= 16)
LSH(x, n)	x, n : integer type type of x logical shift
ROT(x, n)	x, n: integer type type of x rotation
VAL(T, x)	T, x: any typeTx interpreted as of type T

Proper procedures Name Argument types Function

GET(a, v)	a: LONGINT; v: any basic type,v := Mem[a]
pointer type, procedure type
PUT(a, x)	a: LONGINT; x: any basic type, Mem[a] :=  x pointer type, procedure
type
GETREG(n, v)	n: integer constant; v: any basic type,v := Register n pointer
type, procedure type
PUTREG(n, x)	n: integer constant; x: any basic type, Register n :=  v pointer
type, procedure type
MOVE(a0, a1, n)	a0, a1: LONGINT; n: integer type	Mem[a1.. a1+n-1] := Mem[a0.. a0+n-1]
NEW(v, n)	v: any pointer type; n: integer type allocate storage block of
n bytes assign its address to  v

Appendix D: The Oberon Environment

Oberon-2 programs usually run in an environment that provides command
activation, garbage collection, dynamic loading of modules, and certain run
time data structures. Although not part of the language, this environment
contributes to the power of Oberon-2 and is to some degree implied by the
language definition. Appendix D describes the essential features of a typical
Oberon environment and provides implementation hints. More details can be found
in [1], [2], and [3].

D1. Commands

A command is any parameterless procedure P that is exported from a module M.
It is denoted by M.P and can be activated under this name from the shell of
the operating system. In Oberon, a user invokes commands instead of programs or
modules. This gives him a finer grain of control and allows modules with
multiple entry points. When a command M.P is invoked, the module M is
dynamically loaded unless it is already in memory (see D2) and the procedure P
is executed. When P terminates, M remains loaded. All global variables and
data structures that can be reached from global pointer variables in M retain
their values. When P (or another command of M) is invoked again, it may
continue to use these values. 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

Since commands are parameterless they have to get their arguments from the
operating system. In general, commands are free to take arguments from
everywhere (e.g. from the text following the command, from the most recent
selection, or from a marked viewer). The command Add uses a scanner (a data
type provided by the Oberon system) to read the value that follows it on the
command line. 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.

D2. Dynamic Loading of Modules

A loaded module may invoke a command of a still unloaded module by specifying
its name as a string. The specified module is then dynamically loaded and the
designated command is executed. Dynamic loading allows the user to start a
program as a small set of basic modules and to extend it by adding further
modules at run time as the need becomes evident. 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.

D3. Garbage Collection

In Oberon-2, the predeclared procedure NEW is used to allocate data blocks in
free memory. There is, however, no way to explicitly dispose an allocated
block. Rather, the Oberon environment uses a garbage collector to find the
blocks that are not used any more and to make them available for allocation
again. A block is in use as long as it can be reached from a global pointer
variable via a pointer chain. Cutting this chain (e.g., setting a pointer to
NIL) makes the block collectable. 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).

D4. Browser

The interface of a module (the declaration of the exported objects) is
extracted from the module by a so- called browser which is a separate tool of
the Oberon environment. For example, the browser produces the following
interface of the module

Trees from Ch. 11.

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 NewTree (): 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. 

D5. Run Time Data Structures

Certain information about records has to be available at run time: The dynamic
type of records is needed for type tests and type guards. A table with the
addresses of the procedures bound to a record is needed for calling them using
dynamic binding. Finally, the garbage collector needs information about the
location of pointers in dynamically allocated records. All that information is
stored in so-called type descriptors of which there is one for every record
type at run time. The following paragraphs show a possible implementation of
type descriptors. 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.

t t^ type descriptor of CenterNode ProcTab BaseTypes offsets of pointers in t^
(for garbage collector) tag key left right width subnode 4 0 4 8 12 16 Node
CenterNode 8 16 NIL NIL

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.

[1] N.Wirth, J.Gutknecht:
  The Oberon System.
    Software Practice and Experience 19, 9, Sept. 1989
[2] M.Reiser:
  The Oberon System. User Guide and Programming Manual.
    Addison-Wesley, 1991
[3] C.Pfister, B.Heeb, J.Templ:
  Oberon Technical Notes.
    Report 156, ETH Z�rich, March 1991
318.4CHEST::WATSONC++ may be the cureThu Oct 17 1991 12:0412
    There have been a number of (very) slight changes the the syntax of
    Oberon-2 to make the totally upwards compatable with Oberon/revised
    since this report was written. (Mainly WITH statement and NEW of ARRAY
    OF)
    
    I am in active contact with Peter M�ssenb�ck re changes to Oberon-2 as
    I am developing a Oberon-2 -> ANSI-C compiler(?) in my spare time. If
    you have any further question on Oberon and the way its going let me
    know.
    
    	Rik