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