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

Introduction to Oberon

The Oberon Programming Language

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



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