Home
Up
Intro
Contents
Chapter
1
2
3
4
5
6
7
8
9
10
Design
Assert
Timing
EBNF
Report
Pas
Last Changed: July 12th, 1997
This is a conversion from Oberon text to HTML, and from German to English. The converter software is still under development,
and some features or information may be missing in this converted version.
HTML hypertext facilities are not yet active in this document.
To exploit the interactive facilities, use Oberon System 3 and the source of this text,
available for download using binary ftp as Oberon System 3 archive.
The converter from German to English is still under development as well.
A previous version is also available for Oberon V4.
To access this and other additional material use
ftp.
For the convenience of our students, most of this information and the related material is available
in German as well.
Introduction to Oberon
The Oberon Programming Language
08 Records, Arrays, Pointer, Objects
Each variable in Oberon has a type. The type specifies the possible scope
for the variable, determines, which operators are applicable and determines
indirectly how the information of the variables is stored and how stored
information is to be interpreted. In chapter 5 we have already met the elementary
data types
BOOLEAN CHAR
SHORTINT INTEGER LONGINT
REAL LONGREAL
SET
These types are pre-defined. Oberon permits combining types to extend type
definitions and define new types.
Example 0: A " universal " plot procedure should produce a plot for drawing
a function (scaled automatically) on the basis of scope and the function
definition. Suitable declarations are:
TYPE
Function=
PROCEDURE (x:LONGREAL):LONGREAL;
...
PROCEDURE Plot(from,to:LONGREAL;
f: Function);
BEGIN
...
END Plot;
Upon calling Plot any procedure can be
passed for f as long as the parameter pattern,
or signature, corresponds to the type of Function.
Instead of introducing a new type for Function,
in this simple example it would be possible to define the type implicitely
by writing; PROCEDURE
Plot(from,to:LONGREAL;
f:
PROCEDURE (x:LONGREAL):LONGREAL);
BEGIN
...
END Plot;
Example 1: We implemented three random number generators RandLGM,
RandPRB und RandUNIX in the previous chapter. All three have the same
calling structure:
PROCEDURE xxxxx(VAR seed:LONGINT):
LONGINT;
If we want to experiment with different generators in a simulation, without
rewriting our program each time, we can store the current generator in a
variable, like we would handle any initial values or parameters values.
In addition we define
TYPE
GeneratorProc =
PROCEDURE (VAR seed:LONGINT):
LONGINT;
...
VAR
NextRandom: GeneratorProc;
GlobalSeed:LONGINT;
With these declarations a new type GeneratorProc
is introduced, which is of equal standing with all pre-defined types. This
type is then used e.g. with the declaration of the variable NextRandom. After
assigning
NextRandom:=RandLGM;
a procedure call NextRandom(GlobalSeed) returns
the next random number, calculated with the LGM generator. After
NextRandom:=RandPRB;
the subsequent calls of NextRandom(GlobalSeed)
returns the next random numbers, calculated using the PRB generator.
Example 2: We can go on and bundle the current initial value of a random-number
generator with the procedure used for calculation. This gives a simple possibility
to control different random-number generators in a reproducible way. For
this we declare:
TYPE
GeneratorProc
= PROCEDURE (VAR
seed:LONGINT): LONGINT;
GeneratorType = RECORD
seed:LONGINT;
NextRandom: GeneratorProc
END
...
VAR
Generator,Generator1,Generator2,...:
GeneratorType;
Now we can initialize the generators, for example as
Generator1.NextRandom:=RandPRB;
Generator1.seed:=Oberon.Time();
Generator2.NextRandom:=RandLGM;
Generator2.seed:=Oberon.Time();
and using Generator:=Generator1 resp. Generator:=Generator2
we can switch to the first or second generator. The current calculation procedure
is always called as
Generator.NextRandom(Generator.seed).
Example 3: In order to simulate spatial processes, we want to access vectors
and matrices. We can implement these as done in example 2, TYPE
VectorType = RECORD
x,y,z:REAL
END;
but then already simple linear algebra is cumbersome. Alternatively
we can declare:
TYPE
VectorType = ARRAY
3 OF REAL;
MatrixType = ARRAY
3,3 OF REAL;
VAR
v:VectorType; m:MatrixType;
To access component i of v use v[i] resp. for i,j, of m use m[i,j].
Components are alway numerated starting at zero. For example v has components
v[0], v[1], v[2].
Example 4: For each type we can define a corresponding pointer type. This
can hold a reference, similar to a "forward" or "next" pointer in a hypertext.
In a pointer variable only a reference to the data is stored, not the data
themselves. This reference is an indicator, which can be used to access the
data themselves. A typical application is to include an appropriate "next"
pointer in a data structure. As an exception, in pointer declarations it
is possible to use a type before it is declared.
TYPE
InfoPointer=
POINTER TO InfoPage;
InfoPage=
RECORD
..
lots of information...;
NextPage:
InfoPointer
END;
The formal syntax definitions are
Type declaration:
TYPE
{name
=
Type;}
Type:
Type
name|array type|record type|pointer type|procedure type
Arrays (vectors, matrices, tensors...) consist of a (fixed) number
of elements of a uniform type. The Length of an
array is the number of its elements. An array without length specification
is called an open array. Open arrays are allowed as parameters in procedures.
In Oberon-2 it is also possible to define open array types.
Array type:
ARRAY
[length{, length} ] OF type
Length :
constant
expression
The individual elements are identified by their index, beginning
with index 0 and ending with (Length-1).
Array element:
Array
name [index]{[index]}
Array elements are variables. They can be used like any other variable.
The only difference to usual variables is that array elements do not have
individual names: they are collected in the array and are identified by array
names and index within the array.
The elements of arrays can have any type, in particular - like here - they
can be arrays again. This allows constructions like:
TYPE
VectorType = ARRAY
maxdim OF REAL;
MatrixType = ARRAY
maxdim OF VectorType;
TensorType = ARRAY
maxdim OF MatrixType;
Thus arrays with higher dimensions can be defined. The elements may be of
anonymous type as well. The notation
ARRAY L0, L1, ..., Ln OF T
is allowed as an abbreviation for
ARRAY L0 OF
ARRAY L1 OF
...
ARRAY
Ln OF T
To access elements in a multidimensional array an index list can be used,
for example A[i,j,k] as abbreviation for A[i][j][k].
Example:
TYPE tMATRIX= ARRAY 2,2 OF REAL;
Exercises:
What are the types of these terms
x[i] A[i] T[i] T[i,j]
if the variables are declared as follows:
VAR x,y:
VectorType;
A: MatrixType;
T:TensorType;
i,j:INTEGER;
Rewrite Example ItO/PIO/IFS.Mod using a 2*2 matrix und
a 2d shift vector, operating on 2d vectors. The source file is also available
in slightly modified form in ItO/ItOIFS.Mod.
An array has a fixed (possibly open) number of elements of a uniform
type, which are identified by their index. In contrast to this a record type
has a fixed number of elements (fields), possibly of different type, which
are identified by their name.
Record Type:
RECORD
[
(
base type )
]
[
field list {;
field list}]
END
Base Type:
record name
Field List:
Namens-Liste : Typ
The individual elements are identified by their qualified name.
Record Field:
variable name.field
name
A record type can extend a previously defined record type. The
new record type "inherits" all fields of the base type and adds additional
fields.
Example:
Time= RECORD
Hour, Min, Sec :
SHORTINT
END;
TimeAndDate = RECORD (Time)
Month,Day:SHORTINT;
Year:INTEGER
END;
In Oberon there is always at the most one type of base for each type. We
will return to type extension in later chapter (Chapter 9).
Individual fields are addressed as
record var
name.field
name
For example for a variable t of the type Time the fields are addressed as
t.Hour, t.Min, t.Sec.
If a record type is exported, the fields are not exported automatically.
Each field which should be visible to the outside must be individually marked
as exported.
Variables of pointer type take only references to other variables as values.
Hence a pointer type is always bound to some referenced type, the base type
of the pointer.
Pointer Type:
POINTER
TO type
The type can be an array type or a record type. Pointers to elementary
types are not supported. Pointers can have the special value NIL. NIL is
a reserved value, which is used in the meaning of nowhere. NIL is frequently
used to mark a variable as having no defined value.
The Oberon standard specifies all variable values initially as undefined
(random). POINTERS are the exception. POINTER variable are always preinitialized
to a NIL value.
Variables of a procedure type always have a procedure or NIL as their value.
If a procedure is assigned as value, the formal parameter list must fit with
the list given in the type declaration.
Procedure Type:
PROCEDURE
[ formal
parameter list ]
If a type is introduced by a type declaration, the type gets a name, which
identifies it. A type can also be introduced ad-hoc in a variable declaration
or a parameter list. These types remain anonymous.
Example:
VAR W:
ARRAY 3 OF REAL;
...
W[0]:=0; W[1]:=0; W[2]:=2.5;
The types determine which compatibilities may exist between variables. The
requirements may be different for assignment, use in operations and for use
as parameter.
Assignment Compatibility
For assignment compatibility, there is a rule of thumb: an expression
is assignment compatible with a variable, if the assignment
variable:=expression
trivially has a safe interpretation. Allocations between two variables with
the same type are always possible if....
1) if variable and expression have the same type
2) if variable and expression are numeric, and the type of the variables
includes that of the expression. (an integer expression can be assigned to
a real variable. The opposite assignment is not compatible: a real value
must be rounded or cut off only, before it is assigned to an integer variable).
3) if expression and variable have a record type, and the type of the expression
is an extension of the type of the variable. (The opposite assignment is
not compatible: an extension can have fields, which are not defined in the
original type. The base type expression cannot be assigned to a variable
which is a genuine extension.
4) if expression and variable are pointers to a record type and 3) applies
accordingly.
5) if the expression is NIL and the variable is a pointer or a procedure
type.
6) if the variable is of type ARRAY OF CHAR and the expression is a string
constant, which has at most as many characters as the variable can take.
7) if the variable is of procedure type and the expression refers to a procedure
with suitable formal parameter list.
The assignment between two variables usually is not possible if both variables
do not have the same declared type, even if the types have the same structure.
Example:
TYPE
Location =
ARRAY [3] OF REAL;
Speed =ARRAY
[3] OF REAL;
NewLocation = Location;
VAR
PosA,PosB :
Location;
VA,VB :
Speed;
NPos :
NewLocation;
...
:::
PosA[0]:=0; PosA[1]:=0;
PosA[2]:=2.5;
...
PosB := PosA;
(* permitted.
Types are assignment compatible *)
(* VA:=PosA; not
permitted. The same structure,
but
not assignment compatible *)
NPos := PosB;
(* permitted.
Types are assignment compatible *)
VA[0]:=PosA[0];
(* permitted.
Types of the elements are
assignment
compatible *)
More on Base Types
A base type T2 is assignment compatible to base type T1, if T2 is
"contained" in T1, according to the following order:
SHORTINT <= INTEGER <=
LONGINT <=REAL <= LONGREAL
For arithmetic expressions an automatic transformation takes place, if its
choice is unique. In this case the types are called expression compatible.
The result is the smallest base type, which can represent the result.
With the pre defined function SHORT a conversion to a smaller type of number
is forced if possible. With LONG, an expansion to a larger type of number
is forced. ENTIER gives the largest integer number < = x to a real number
x.
Example: ENTIER(17/4) gives 4
More on Arrays
In Oberon the size of an array can be queried at run time unsing LEN. The
pre defined function
LEN(a,n)
returns the length of a in dimension n where n=0,...dim(a)-1.
LEN(a) is a short form of LEN(a,
0).
A typical loop over all elements of a vector, written using LEN, would be:
i:=0;
WHILE i< LEN(a) DO
a[i]:=a[i]*2;
INC(i)
END;
This loop is equivalent to:
FOR i:=0 TO LEN(a)-1 DO a[i]:=a[i]*2
END;
The use of the WHILE construct is recommended in Oberon to iterate through
elements of an array.
Array types can be declared without specification of the length. Arrays without
explicit length specifications are called open arrays. In Oberon open arrays
can be used as parameters for functions. Oberon-2 permits also the declaration
of variables as open arrays. Open arrays can be used in connection with n
LEN to implement flexible procedures for linear algebra.
Example:
PROCEDURE MaxDiag(A: ARRAY OF ARRAY OF REAL;
VAR
Val:REAL);
VAR i:INTEGER;
BEGIN
Val:=0;
i:=0;
WHILE i< LEN(A)
DO
IF ABS(A[i,i])>Val
THEN Val:=ABS(A[i,i]) END;
INC(i)
END
END MaxDiag;
If an array is passed as value parameter like in this example, the entire
information is copied to a local variable when the procedure is called. This
holds for all value parameters. In contrast to this, for VAR parameters only
a reference is transferred. In particular, arrays often contain large amounts
of information. Copying becomes time and space consuming. In these cases,
it is usual to pass variables as VAR parameters, although the contents is
not changed and passing by value would be sufficient.
Example:
PROCEDURE MaxDiag(VAR A: ARRAY
OF ARRAY OF REAL; VAR Val:REAL);
Exercises:
Write a procedure, which calculates the dot product
of two vectors A,B and returns the result as a variable C:
PROCEDURE SKALPROD(VAR A,B: ARRAY
OF REAL;
VAR C: REAL);
Use this procedure to calculate the dot product of the following vectors:
A[i]:= sin(i* (2*pi)/n) B[i]:=
cos(i* (2*pi)/n)
for n=1, ..., 10 where i=0,...,n-1.
Note: Sinus and Cosinus can be imported from module Math.
Apart from the representation of geometrical objects such as vectors, a typical
use of arrays is the representation of (rectangular) tables with information.
In contrast to the previous examples, often a procedure need not process
the complete information, but only an initial part until a certain element
is found
Example: Look the index up j of an entry with the value x in a table t
TYPE Table= ARRAY n OF INTEGER;
VAR t:Table; j,x:INTEGER;
..
j:=0;
WHILE (j<n) & (t[j]#x)
DO INC(j) END;
In this example, it would not be correct to use
j:=0;
WHILE (t[j]#x) & (j<n)
DO INC(j) END;
Why?
More on Records
Allocations of a record type T1 to a type T0 are possible if T1 extends the
type T0. The assignment transfers then only the fields, which are contained
in T0 as well. T1 is "projected onto" T0.
VAR
t: Time;
ta,tb: TimeAndDate;
...
t:=ta; (*
permitted. Transfers Hour, min, seconds *)
...
tb:=ta; (*
permitted. Transfers all fields *)
...
(* ta:=t is
not permitted *)
ta.Hour:=t.Hour; (*
permitted.
Allocation
of variables of the
same
base type *)
The rules for type extension apply similarly to pointers as well: if Type1
is an extension of Type0, then a pointer to Type1 is an extension of a pointer
to Type0.
More on Pointer Variables
If a variable P is declared to be of a pointer type, only the memory space
for a reference is allocated, but not the for a variable of the base type.
The space for the variable referenced by the pointer is only alloated by
a call of the pre defined procedure NEW, for example NEW(P).
NEW tries to supply the memory space for the variable. If the space could
be supplied, then a reference to this new variable is stored in P. The variable
thus defined can be accessed as P^. If no space could be supplied, then the
further behavior is implementation dependent. If the space could not be supplied,
then P has the value NIL.
The simple call of NEW supplies a variable of fixed length. If the type of
base is an open array, then NEW(P, e0, ..., en-1) allocates a matrix of the
lengths e0, ..., en-1.
The value NIL can be assigned to each variable of pointer type; NIL refers
to no variable.
If the base type is a RECORD type, the components can be accessed with a
short way of writing, for example with
P: POINTER TO Time; (* Time as declared above *)
you can use
P.Hour as abbreviation for P^.Hour.
(similar a short way of accessing array elements is, for example, p[i, j,
k] in place of p^[i, j, k]).
Introduction to the Oberon programming language. ItO/Ch08.Text
gs (c) G. Sawitzki, StatLab Heidelberg
<http://statlab.uni-heidelberg.de/projects/oberon/intro/>
Home
Up
Intro
Contents
Chapter
1
2
3
4
5
6
7
8
9
10
Design
Assert
Timing
EBNF
Report
Pas