Home Up Intro Contents Chapter 1 2 3 4 5 6 7 8 9 10 Design Assert Timing EBNF Report Pas Last Changed: Nov. 19, 1997
This is a conversion from Oberon text to HTML. 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 as ASCII-coded Oberon System 3 documents. 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 in German. Sorry if this is not one of your languages.

Einführung in die Programmiersprache Oberon.

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



05 Deklarationen; Typen; Text-Ausgabe


Alle bearbeiteten Daten müssen im Rechner intern dargestellt werden. Der Speicher ist technisch gesehen homogen, das heisst technisch sind alle Daten gleichartig. Für die Nutzung jedoch haben Daten verschiedene Interpretation: sie können logische Werte, ganze Zahlen, reelle Zahlen, Zeichen, Zeichenketten und vieles andere repräsentieren. In der Regel werden deshalb dreierlei durch eine Deklaration miteinander verknüpft: ein Speicherplatz, an dem Daten hinterlegt werden können; ein Name, unter dem dieser Speicherplatz und damit die Daten angesprochen werden können; und ein Typ, der die angemessene Interpretation spezifiziert. Ausser diesen Variablen-Deklarationen ist es in Oberon auch möglich, mit einer Typ-Deklaration Namen für (abstrakte) Typen einzuführen.

Jeder in Oberon benutzte Name muss zunächst in einer Deklaration eingeführt werden. Eine Reihe von Namen sind vordefiniert: sie haben implizit eine Definition und brauchen nicht ausdrücklich deklariert zu werden. Die erste Gruppe von Namen bezeichnet Basis-Datentypen:
1.  BOOLEAN
    die logischen Werte wahr (TRUE) und falsch (FALSE)
2.  CHAR
    die Zeichen des erweiterten ASCII-Zeichensatzes (0X .. 0FFX)
3.  SHORTINT
    ganze Zahlen von MIN(SHORTINT) bis MAX(SHORTINT)
4.  INTEGER
    ganze Zahlen von MIN(INTEGER) bis MAX(INTEGER)
5.  LONGINT
    ganze Zahlen von MIN(LONGINT) bis MAX(LONGINT)
6.  REAL
    reelle Zahlen von MIN(REAL) bis MAX(REAL)
7.  LONGREAL
    reelle Zahlen von MIN(LONGREAL) bis MAX(LONGREAL)
8.  SET
    Mengen von ganzen Zahlen von 0 bis MAX(SET)

Die Typen 3 bis 5 heissen Ganzzahl-Typen, die Typen 6 und 7 heissen reelle Typen. Die hier benutzten Konstanten TRUE und FALSE sind ebenfalls vordefiniert, ebenso die Funktionen MIN und MAX. Für einen Basis-Typ T ist MIN(T) der minimale Wert und MAX(T) der maximale Wert für T.
"Reelle Zahlen" ist hier nur eine vereinfachte Sprechweise. Alle Zahlen im Rechner heben eine endliche Darstellung, das heisst was hier "reelle Zahlen" genannt wird ist nur eine sehr eingeschränkte Menge von rationalen Zahlen. Der genaue Umfang und die Zahldarstellung sind nicht in Oberon definiert, sondern sind implementationsabhängig.

Variable werden eingeführt, indem Name und Typ in der folgenden Form deklariert werden:
Variablen-Deklaration:
  Namens-Liste :Typ-Name
  zum Beispiel
  i, j, k: INTEGER  x, y: REAL  p, q: BOOLEAN
  s: SET  F: Function

Grundsätzlich müssen alle Elemente in Oberon definiert sein, bevor sie benutzt werden. Bei Prozeduren ist eine Ausnahme geschaffen: es ist möglich, vorab einen Verweis auf eine später folgende Prozedur zu geben. Dann kann die Prozedur benutzt werden, bevor die eigentliche Deklaration erfolgt ist. Die Syntax dafür ist:
Vorwärts-Deklaration:
  PROCEDURE ^ [Ziel] Prozedur-Name [formale Parameter];

Insgesamt hat die Deklarations-Folge die Syntax:
Deklarations-Folge:
{     CONST {Konstanten-Deklaration ; }
  |   TYPE {Typen-Deklaration ; }
  |   VAR {Variablen-Deklaration ; }}
{  Prozedur-Deklaration ;
  | Vorwärts-Deklaration; }
mit
Konstanten-Deklaration:
  Namens-Definition = konstanter Ausdruck
Typen-Deklaration:
  Namens-Definition = Typ
Variablen-Deklaration:
  Namens-Liste : Typ
wobei
Namens-Liste:
  Namens-Definition { , Namens-Definition }

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

Jede Deklaration hat einen Gültigkeitsbereich, und diese Bereiche sind geschachtelt. Grundsätzlich beginnt ein Gültigkeitsbereich bei der Deklaration und endet beim Ende der jeweiligen Struktur (Modul oder Procedure). Deklarationen innerhalb einer Prozedur gelten bis zum Ende dieser Prozedur - die Gültigkeitsbereiche von Unterprozeduren sind geschachtelt. Jeder Name darf in einer Stufe nur einmal deklariert werden. Wird ein Name in einer Prozedur benutzt, der auf einer höheren Stufe schon deklariert ist, so wird die vorherige Deklaration bis zum Ende der Prozedur unsichtbar - es gilt jeweils die lokale Deklaration.

Konstanten-Deklarationen werden benutzt, um Konstanten der besseren Lesbarkeit halber oder zur Abkürzung symbolisch zu bezeichnen. Die konstanten Ausdrücke werden dabei zur Compilierungszeit ausgewertet. Dies sollte im Gedächtnis behalten werden, wenn Compilierung und Ausführung nicht auf vergleichbaren Systemen stattfindet.

Variablen-Deklarationen reservieren Speicherplatz für Variable, und legen gleichzeitig fest, nach welchen Typen-Konventionen die Information zu interpretieren ist. Einer Variablen x vom Typ t kann ein Wert, der mit dem Typen t verträglich ist, durch eine Zuweisungsanweisung zugewiesen werden. Zuweisungen haben die Syntax
Zuweisung:
  Name := Ausdruck

Die elementaren Datentypen sind vordefiniert. Oberon bietet darüber hinaus die Möglichkeit, komplexere Datentypen zu definieren. Wir illustrieren dies an einem Beispiel aus dem Oberon-System selbst: der Text-Ausgabe in einen Text-Rahmen.

Gäbe es nur ein Fenster, nur einen Rahmen und nur einen laufenden Prozess, so könnte man den Ausgabetext Zeichen für Zeichen an den zuletzt ausgegebenen Text anhängen. Die Situation wird komplizierter, wenn es mehrere Quellen für den Datenstrom gibt. Dies kennt man schon aus klassischen Systemen: während ein Resultat ausgegeben wird, kann es zu einer Fehlersituation kommen, vor der der Benutzer dringend gewarnt werden muss. Klassische Systeme benutzen dazu zwei logische Ausgabeströme, eine Standard-Ausgabe, und eine Fehlerausgabe (diagnostic, stderr,...), die so gesteuert werden, dass im Notfall die Fehlermeldung die laufende Ausgabe unterbricht. Gibt es nicht nur Fehlermeldungen, sondern eine Vielzahl von Quellen, aus denen Information kommen kann, so wird dieses Verfahren unpraktikabel. Es muss sichergestellt werden, dass Information aus verschiedenen Quellen nicht durchmischt wird, sondern konsistent bleibt.
Die Oberon-Lösung ist, dass jeder Ausgabekanal für sich bestimmt, wann eine Botschaft vollständig ist und als ganzes ausgegeben werden kann. Dazu wird die Information zwischengespeichert, gepuffert. Erst wenn die Information vollständig ist, wird sie an eine Ausgabe angehängt. Ausser der eigentlichen Information, den ausgegebenen Zeichen, muss noch zusätzliche Information notiert werden, etwa über den jeweils aktuellen Zeichensatz, Farbe, vertikale Verschiebung u.s.w. All dies soll ja erhalten bleiben, auch wenn zwischendurch eine Ausgabe von anderen Quellen erfolgt. In Oberon können diese Informationsbestandteile zusammengefasst in einem speziell dafür definierten Datentyp Writer repräsentiert werden.

Einzelne dieser Informationsbestandteile, wie etwa die vertikale Verschiebung, können als Zahlen repräsentiert werden. Andere, etwa der Puffer, sind selbst wieder komplexer. Damit dieser Puffer in geordneter Weise genutzt werden kann, muss auch Verwaltungsinformation, wie Puffergrösse und augenblickliche Belegung des Puffers, verfügbar sein. Puffer zur Zwischenspeicherung werden nicht nur zur Ausgabe, sondern auch an verschiedenen Stellen des Systems gebraucht. Die Oberon-Strategie ist es, einen allgemeinen Typ für den Puffer (Buffer) zu definieren, der zum Schreiben im Writer wiederbenutzt wird.

Im Zusammenhang haben die Deklarationen die Form
TYPE
  Buffer* = POINTER TO BufDesc;
  BufDesc* = RECORD
    len*: LONGINT;
    ...
  END;

  Writer* = RECORD
    ...
    buf*: Buffer;
    ...
    col*: SHORTINT; (* Farbe (Index in einer Farbtabelle) *)
    voff*: SHORTINT; (* vertikale Verschiebung *)
  END;

Diese Deklarationen sind in dem Standard-Modul Texts enthalten. Wir kommen auf diese Definition noch später zurück. Für den Augenblick notieren wir:
Komplexere, zusammengesetzte Datentypen können in Oberon deklariert werden.
Neu definierte Datentypen können als Komponenten in weiteren Deklarationen benutzt werden.

Ein anderes Beispiel für einen komplexen Datentyp ist der Typ Text. Dieser repräsentiert einen ganzen Text, mitsamt dem Inhalt und allen für einen Text wichtigen Attributen. Wie Buffer und Writer ist auch der Datentyp Text schon im Modul mit dem Modulnamen Texts definiert.

Es gibt eine Variable vom Typ Text, die nach Konvention immer für die Ausgabe bereit gehalten wird. Diese ist im Modul Oberon definiert. Da der Datentyp Text nicht im Modul Oberon selbst definiert ist, sondern aus dem Modul Texts importiert wird, müssen wir auf ihn in der qualifizierten Form als Texts.Text bezug nehmen; der "Standard-Ausgabetext" in im Modul Oberon definiert in der Form
VAR
  Log*  : Texts.Text;
Der Inhalt eines Puffers buf wird an diesen Text mit dem Befehl
  Texts.Append(Oberon.Log,buf);
angehängt.

Wie kann Information im Puffer eines Writers bereitgestellt werden? Zunächst muss ein Writer deklariert werden. Dies geschieht mit einer Deklaration der Form
VAR
  W  : Texts.Writer;
Dann muss sichergestellt werden, dass der Puffer vom Anfang beschrieben wird. Diese Initialisierung wird erreicht durch die Anweisung:
  Texts.OpenWriter(W);
Danach ist der Writer bereitgestellt und alles ist vorbereitet, dort Information zu speichern. Für eine Ausgabe wollen wir die Information auch in einem Format berietstellen, dass wir kontrollieren können. Zahlen und Texte können in einer Vielzahl von Formaten ausgegeben werden. Um diese Formate zu kontrollieren, gibt es eine ganze Reihe von Funktionen. Zwei Aufrufe sind zum Beispiel:
  Texts.WriteString(W,"Hallo");    ergänzt "Hallo"
  Texts.WriteLn(W);      wechselt zu neuer Zeile.
Das obige Beispiel muss nun modifiziert werden. Wir wollen ja nicht irgendeinen Puffer ausgeben, sondern den Puffer von W. Die korrekte Anweisung lautet:
  Texts.Append(Oberon.Log,W.buf);
Die Datenstruktur Texts.Writer ist eine aus mehreren Komponenten zusammengesetzte Datenstruktur. W.buf ist die Puffer-Komponente dieser Datenstruktur und hat den Typ Texts.Buffer.

Der jeweilig aktuelle Inhalt des Texts Oberon.Log kann mithilfe des Moduls System angezeigt werden. System.OpenLog öffnet ein Fenster mit dem Namen System.Log, und zeigt darin den Text Oberon.Log. Mit System.ClearLog wird der Inhalt gelöscht.

Ein vollständiges Programm sehen Sie mit
  Desktops.OpenDoc Kurs/Prog06.Mod
Sie compilieren es mit
  Builder.Compile Kurs/Prog06.Mod \s ~
Mit
  Prog06.Hallo
können Sie dieses Programm austesten.


Eine vollständige Liste der in Texts exportierten Typen, Variablen und Prozeduren erhalten Sie mit
  Browser.ShowDef Texts ~
bzw. mit
  Watson.ShowDef Texts ~
Die Ausgabe-Routinen haben Namen, die mit Write... beginnen. Die meisten Routinen enthalten neben Ausgabeziel und auszugebendem Wert weitere Parameter, die das Format bestimmen (üblich: n: Anzahl der auszugebenden Stellen, ggf. k Anzahl der Nachkommastellen). Eine Beschreibung der Routinen finden Sie in Wirth/Gutknecht: Projekt Oberon.



Übungen:

Legen Sie mit
  System.CopyFiles Kurs/Prog06.Mod
      =>  Kurs/Test06.Mod ~

eine Kopie des Beispielprogramms an. Öffnen Sie diese Kopie. Verändern Sie den Namen von Prog06 in Test06; compilieren Sie das Programm und überzeugen Sie sich mit
  Test06.Hallo
davon, dass das Programm noch lauffähig ist. Fügen Sie eine Prozedur
PROCEDURE Range*;
BEGIN
  Texts.WriteString(W," Integer MIN: ");
  Texts.WriteInt(W,MIN(INTEGER),20);
  Texts.WriteString(W," Integer MAX: ");
  Texts.WriteInt(W,MAX(INTEGER),20);
  Texts.WriteLn(W);
  Texts.Append(Oberon.Log,W.buf);
END Range;
an; compilieren Sie und testen Sie mit
  Test06.Range

Ergänzen Sie die Prozedur Range so, dass eine in Spalten aufgeteilte Tabelle mit allen elementaren Zahlentypen entsteht:

Typ  MIN  MAX
SHORTINT  ......  ......
INTEGER  ......  ......
LONGINT  ......  ......
REAL  ......  ......
LONGREAL  ......  ......
SET  ......  ......

Anmerkung: Bei Ausgabe mit Proportionalfonts (z.B. Syntax) ist die Gestaltung von Tabellen schwierig. Die obige Tabelle ist mit einem Lineal und Tabulator-Stops gestaltet. Mit Texts.Write(W,09X) können Sie das ASCII-Zeichen 9 = Tab in ihrer Ausgabe einfügen. Eine andere Möglichkeit ist es, für den Ausgabetext einen Font mit fester Schrittweite (z.B. Math oder Courier) zu benutzen.



Bei den reellen Zahlentypen REAL und LONGREAL ist neben dem möglichen Wertebereich noch von Bedeutung, mit welcher Genauigkeit die Zahlen repräsentiert werden. Je nach Implementierung muss die Genauigkeit nicht homogen sein. Üblich ist eine feinere Genauigkeit bei kleinen Zahlen als bei grossen. In Oberon gibt es keine vordefinierte Möglichkeit, die Genauigkeit zu erfragen. Sie muss dynamisch festgestellt werden, und dies ist eine nicht triviale Aufgabe. Eine weit verbreitete Lösung ist ein Programm, dass Sie mit
  Builder.Compile Kurs/machar.Mod ~
bzw. mit
  Builder.Compile Kurs/macharL.Mod ~
compilieren und mit
  machar.Init bzw. mit macharL.Init
starten können. Danach können sie mit
   System.State machar.Init ~
bzw. mit
   System.State macharL.Init ~
die Werte abfragen. Die genauere Bedeutung dieser Wert ist als Kommentar im Kopfteil der Programme beschrieben.

Durch die Typenzugehörigkeit ist festgelegt, welche Werte eine Variable annehmen kann. Durch die Typenzugehörigkeit ist auch festgelegt, welche Operatoren auf Variable angewandt werden, und wie diese interpretiert werden.
Für reelle numerische Variable gibt es die üblichen Operatoren +, -, *, /. Das Divsionszeichen / steht immer für die reelle Division.
Für die ganzzahlige Division gibt es den speziellen Operator DIV, der mit dem Modulus MOD verknüpft ist: für alle ganzzahligen Werte x und positive Divisoren y ist
  x = (x DIV y) * y + (x MOD y)
  0 <= (x MOD y) < y

Für Mengen (Sets) werden die Operatoren entsprechend benutzt:
  +  Vereinigung
  -  Differenz (x - y = x * (-y))
  *  Durchschnitt
  /  symmetrische Differenz (x / y = (x-y) + (y-x))
Der unitäre Operator - liefert das Komplement, d.h. -M= {0..MAX(SET)}-M.

Die logischen Operatoren sind OR, & , ~ entsprechend oder, und, nicht.
Dabei ist die Bedeutung
  p OR q   entspricht  if p then TRUE else q end;
  p & q  entspricht  if p then q else FALSE end;
Ausführlicher: Wenn p wahr ist, so wird q gar nicht erst ausgewertet und es ist (p OR q) wahr. Ist p nicht wahr, so wird q ausgewertet und der Ausdruck hat den jeweiligen Wert von q. Entsprechendes gilt für &. OR und & sind also nicht kommutativ. Wenn p und q elementare Ausdrücke sind, spielt dies kein Rolle. Hinter p und q können aber komplexere Operationen stehen, die bei der Auswertung des logischen Wertes einen Seiteneffekt haben. Man stelle sich unter q eine Prozedur vor, die eine Festplatte löscht und mitteilt, ob dies fehlerfrei erfolgt ist, und unter p die Benutzerbestätigung, ob das Löschen erwünscht ist. Die Auswertung
  if p then q else FALSE
von p&q bringt das vermutlich gewünschte Resultat, das heisst die Information, ob alles nach Wunsch verlaufen ist. Jede andere Auswertung ist vermutlich weniger erwünscht.

Für die Relationen stehen die Operatoren
  =  gleich
  #  ungleich
  <  kleiner
  <=  kleiner oder gleich
  >  grösser
  >=  grösser oder gleich
  IN  Element von
zur Verfügung.

Typendefinitionen komplexer Typen können erweitert werden. Der speziellen Operator
  IS  ist vom Typ
dient dazu, die Typ-Zugehörigkeit einer Variablen dynamisch zu überprüfen.
  Name IS Typ
So zum Beispiel
  x IS y
für eine Variable x und einen Typ y.


Einführung in die Programmiersprache Oberon. Kurs/Kap05.Text
gs (c) G. Sawitzki, StatLab Heidelberg
<http://statlab.uni-heidelberg.de/projects/oberon/kurs/>

Home Up Intro Contents Chapter 1 2 3 4 5 6 7 8 9 10 Design Assert Timing EBNF Report Pas