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>

05 Declarations; Types; Text Output

All data which are processed need an internal representation in a computer. From a technical point of view, the computer memory is homogeneous. From this point of view, all data are equivalent. For the user however different data have different interpretations: we have logical values, integer numbers, real numbers, characters, character sequences and many other types which need to be represented. Hence typically three aspects are related by a declaration: a memory location, where the data can be stored; a name, used to refer to this memory location and the data stored there; and a type which specifies an adequate interpretation. Besides variable declarations, Oberon allows type declarations to associate a name with a (possibly abstract) type.

In Oberon, each name has to be introduced by a declaration before it is used. Some names are predefined. These have an implicite definition and need not be declared before they are used. The first group of names denotes basic data types:
    logical values true (TRUE) and false (FALSE)
2.  CHAR
    characters from the extended ASCII character set (0X .. 0FFX)
    integer numbers from MIN(SHORTINT) to MAX(SHORTINT)
    integer numbers from MIN(INTEGER) to MAX(INTEGER)
    integer numbers from MIN(LONGINT) to MAX(LONGINT)
6.  REAL
    real numbers from MIN(REAL) to MAX(REAL)
    real numbers from MIN(LONGREAL) to MAX(LONGREAL)
8.  SET
    sets of integer numbers from 0 to MAX(set)

The types 3 through 5 are integer number types, types 6 and 7 are real types. The constants TRUE and FALSE are pre-declared, as well as the procedures MIN and MAX. For a basic type T, MIN(T) is the minimal value and MAX(T) is the maximal value which can be assigned to a variable of type T.
"Real numbers" is a simplified term only. All numbers in a computer have a finite representation. What is called "real numbers" here is but a very restricted subset of the rational numbers. The specific range of number representations is not defined in Oberon, but is implementation specific.

Variables are introduced by declaring name and type, using this form:Variable declaration:
  name list :type name
For example
  i, j, k: INTEGER  x, y: REAL  p, q: BOOLEAN
  s: set  F: Function

Generally all elements in Oberon need to be declared before they are used. There is an exception for procedures: it is possible to give a reference to a procedure which will be defined later on. After that, the procedure can be used before the proper definition is given. This "forward declaration" has the syntax
Forward declaration:
  PROCEDURE ^ [target] procedure name [formal parameters];

The full syntax of the declaration sequence is:
Declaration sequence:
{     CONST {constant declaration ; }
  |   TYPE {type declaration ; }
  |   VAR {variablen declaration ; }}
{  procedure declaration ;
  | forward declaration; }
Constant declaration:
  name definition = constant expression
Type declaration:
  name definition = type
Variable declaration:
  namen list : type
Name list:
  name definition { , name definition }

  CONST   Pi=3.14159;
  TYPE     AgeType= SHORTINT;
  VAR     Age:AgeType; Len: INTEGER;

Each declaration has a scope of validity, and these scopes build a hierarchy. A validity scope starts at the declaration and ends at the end of the containing segment (module or procedure). Declarations within a procedure are valid upto the end this procedure. The scopes are nested. Each name may be declared only once in each scope. If a name is used in a procedure, which has already been declared on a higher level, the previous declaration becomes invisible until the end of the procedure, and only the local declaration is valid within the procedure scope.

Constant declarations are used in order to define symbolic names for better legibility or for abbreviation. The constant expressions in the declarations are evaluated at compile time. This should be kept in the mind if compiling and execution do not take place on comparable systems.

Variable declarations reserve storage space for variables. At the same time they determine the type conventions according to which the information is to be interpreted. A value which is compatible to a type t can be assigned to a variable of type t by an assignment statement. The syntax of assignments is:
  name := expression

The elementary data types are pre-defined. Beyond this, Oberon offers the possibility of defining more complex data types. We illustrate this using an example from the Oberon system itself: the text output to a text frame.

If there were only one window, only one framework and only one current process, we could append an output text to the most recent output, character by character. The situation becomes more complicated if there are several sources for the data stream. Already in classical systems an error condition might occur while some results are printed out. There may be an urgent message to alert the user while the results still need output. Classical systems use two logical output streams, a standard output, and an error output (diagnostic, stderr...) where an error message may interrupt the standard output. If there are not only error messages, but a multiplicity of sources from which information can arise, this protocol becomes unpractical. It needs to be guaranteed that information from different sources is not mixed, but remains consistent.
The Oberon solution is that each output channel decides for itself when a message is complete and may be output. To allow for this, the information is buffered. It is appended to an output stream only when the information is complete. Besides the actual information, the output characters, some additional information must be noted, for example the character set to be used, the color, vertical shift etc.. All this information needs to be preserved, even if output from other sources is intermittend. In Oberon, these information aspects can be collected in a data type Writer, which is defined exactly to hold this kind of information.

Some of these information constituents, as for instance the vertical shift, can be represented by numbers. Others, for instance the buffer, are more complex. To use a buffer in a controlled way, administrative information, like buffer size and present/immediate allocation of the buffer, must be available. Buffers are not only used as intermediate storage for output, but also at various other places. The Oberon strategy is it to define a general type for the buffer which is re-used for writing in the Writer type.

All together these declarations have the form
  Buffer* = POINTER TO BufDesc;
  BufDesc* = RECORD
    len*: LONGINT;

  Writer* = RECORD
    buf*: Buffer;
    col*: SHORTINT; (* color (index in a color table) *)
    voff*: SHORTINT; (* vertical offset *)

These declarations are contained in the standard module Texts. We return to these definitions later now. For now we note:
  Complex, composed data types can be defined in Oberon.
User defined data types can be used as components in further declarations.

Another example of a complex data type is the type Text. It represents a complete text, with contents and all layout attributes important for a text. The data type Text is defined in the module named Texts, together with the data types Buffer and Writer.

There is one variable of type text which is always available for output by convention. It is defined in the module Oberon. Since the the Text data is not defined in the module Oberon, but is imported from the the Texts module it must be referenced in the qualified form as Texts.Text. In module Oberon, the definition of the "standard output text" is
  Log*  : Texts.Text;
Contents of a buffer buf is appended to this text using

How do we prepare information in the buffer of a Writer? First the Writer needs to be declared. This is done by a declaration of this form:
  W  : Texts.Writer;
Next it has to be made sure that the buffer is written properly, starting at its first position. The necessary initialization is achieved by a statement:
After this, the Writer is set up and prepared to receive information. For output, we want to control the format as well as the information contents. Numbers and texts can be output in a multiplicity of formats. In order to control these formats, a whole collection of functions is available. Two of these are for example:
  Texts.WriteString(W,"Hello");    appends "Hello"
  Texts.WriteLn(W);      moves to a new line.
The example given above needs some modification now. We do not want to send output to an arbitrary buffer, but to the buffer of our writer W. The correct statement is:
The data structure Texts.Writer is a complex data structure built from several components. W.buf is the buffer component of this data structure and has the type Texts.Buffer.

The current contents of the text Oberon.Log can be displayed using module System. System.OpenLog opens a window with the name System.Log, and displays the text Oberon.Log. System.ClearLog deletes the contents.

A complete module is opened with
  Desktops.OpenDoc ItO/ItOProg06.Mod
To compile it, use
  Builder.Compile ItO/ItOProg06.Mod \s ~
Test it using

A complete list of the the types, variables and procedures exported by Texts is generated by
  Browser.ShowDef Texts ~
  Watson.ShowDef Texts ~
The output routines have names, which begin with Write.... Apart from output device and the value to be sent to output, most routines contain further parameters, which determine the format a (usually: n: Number of places which can be output, if necessary k: number of post-decimal positions). You find a description of the routines in Wirth/Gutknecht: Project Oberon.


Get a copy of the sample module using
  System.CopyFiles ItO/ItOProg06.Mod
      =>  ItO/ItOTest06.Mod ~

Open this copy. Change the name of ItOProg06 to ItOTest06; compile the module and make sure the module still works by executing
Add a procedure
Procedure Range*;
  Text.WriteString(w," Integer MIN: ");
  Text.WriteString(w," Integer MAX: ");
END Range;
Compile and test again, using

Complete the procedure Range to give a table with all elementary number types, separated into columns:

Type  MIN  MAX
SHORTINT  ......  ......
INTEGER  ......  ......
LONGINT  ......  ......
REAL  ......  ......
LONGREAL  ......  ......
Set  ......  ......

Note: With proportional fonts (e.g. Syntax) the organization of tables is difficult. The above table is arranged using a ruler and a tabulator stop. With Texts.Write(W,09X) you can insert the ASCII character 9 = tab. Another possibility is it to use a font with fixed character width (e.g. Math or Courier) for the output text .

Apart from the output field width, for real number types REAL and LONGREAL the accuracy the output is of importance. Depending upon the implementation the accuracy of the internal number representation does not have to be homogeneous. A finer accuracy is usual used with small numbers than with large. In Oberon there is no pre-defined possibility to query the accuracy. It must be determined dynamically, and this is not trivial. A widely used solution is a program which you can compile using
  Builder.Compile ItO/ItOmachar.Mod ~
To start the program, use
After starting the program, use
   System.State ItOmachar.Init ~
For the long real version, use
  Builder.Compile ItO/ItOmacharL.Mod ~
  System.State ItOmacharL.Init ~

to get the values determined by the program. The exact meaning of these values is described by comments in the heading of these modules.

The type of a variable determines which values it can take. It also determines which operators are applicable to a variable, and how the operators are to be interpreted.
For real number variables there are the usual operators +, -, *, /. The slash / always means a real division.
For the integer division there is the special operator DIV, which is linked with the modulus MOD: for all integer values x and positive divisors y
  x = (x DIV y) * y + (x MOD y)
  0 <= (x MOD y) < y

For sets, the operators are sed accordingly:
  +  union
  -  difference (x - y = x * ( - y))
  *  intersection
  /  symmetric difference (x / y = (x-y) + (y-x))
The unitary operator - denotes the complement, i.e.
  - M = { 0..MAX(SET)}-M.

The logical operators are OR, & , ~ corresponding to or, and, not.
  p OR q  means    if p then TRUE else q end;
  p & q  means    if p then to q else FALSE end;
In more detail: If p is true, then q is not even analysed and (p OR q) is true . If p is not true, then q is analysed and the output has the respective value of q. The same applies to &. OR and & thus are not not commutative. If p and q are elementary expression, this is of no relevance. But p and q can represent more complex operations, which have a side effects during the analysis of their logical value. For example imagine q is a procedure, which deletes and formats a hard disk, returning some error code, and p may be the result of a user confirmation as to whether the deletion is desired. The analysis
of p&q probably yields the required result, i.e. tells whether everything ran as required. Every other sequence of analysis presumably is not the desired result.

For relations, the operators
  =  equal
  #  unequal
  <  less
  <=  less or equal
  >  larger
  >=  larger or equal
  IN  element of
are available.

Type definitions of complex types can be extended. The special operator
  IS  is of the type
serves to check the type affiliation of a variable dynamically.
  Name IS Type
So for example
  x IS y
for a variable x and a type y tells whether x belongs to type y.

Introduction to the Oberon programming language. ItO/Ch05.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