home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Global Amiga Experience
/
globalamigaexperience.iso
/
compressed
/
development
/
clusterdemo.dms
/
clusterdemo.adf
/
Sprachreport.lha
/
Cluster_1.4_Sprachreport
next >
Wrap
Text File
|
1993-03-08
|
59KB
|
1,771 lines
Sprachreport für Cluster V1.4
-------------------------------
[redigierte deutsche Version vom 25. 11. 91]
C.1 Grundlegende Sprachelemente
Die Sprache Cluster basiert auf dem ASCII Zeichenstandard. Verwendet werden
die großen und kleinen Buchstaben des Standardalphabets, sowie die üblichen
Sonderzeichen. Cluster ist case-sensitive, das heißt, es wird zwischen
großen und kleinen Buchstaben unterschieden. In der Tradition von Algol,
Pascal, C und Modula ist Cluster formatfrei, d.h. zwischen zwei Symbolen
der Sprache dürfen beliebig viele Leerzeichen und Zeilenvorschübe enthalten
sein.
Cluster ist eine Sprache, die für einen Singlepasscompiler zugeschnitten
ist. Dies hat zur Folge, daß mit Ausnahme von Pointerzielen alle Bezeichner
vor ihrer Verwendung definiert werden müssen.
Die Sprachdefinition wird in EBNF angegeben.
$$ letter ::= a|b|c|...X|Y|Z
$$ digit ::= 0|1|2|3|4|5|6|7|8|9
$$ hexdigit ::= digit|A|B|C|D|E|F
C.1.1 Bezeichner
Bezeichner bestehen in Cluster aus großen und kleinen Buchstaben, Zahlen
und dem Unterstrich "_". Sie müssen immer mit einem Buchstaben beginnen.
$$ ident ::= letter{letter|digit|"_"}
Beispiele für richtige Bezeichner:
Cluster, Text2, Text_2, aus, Ein_Text
Falsch wären:
2Pi, _LVO, Ein Text
Großschreibungen im Wort und der Unterstrich können dazu dienen, die
Bezeichner lesbarer zu gestalten.
Manche Bezeichner müssen noch qualifiziert werden:
$$ qualident ::= {ident.}ident
Folgende Bezeichner sind vorbelegt:
SHORTCARD CARDINAL LONGCARD SHORTINT
INTEGER LONGINT FFP REAL
LONGREAL BOOLEAN CHAR ANYTYPE
ANYPTR STRING SAMEPTR
TRUE FALSE NIL
INC DEC INCL EXCL FLIP CAST ABS CAP
ODD ACOS ASIN ATAN COS COSH EXP LN
LOG SIN SINH SQRT TAN TANH CEIL FLOOR
SETREG REG SHL SHR LMUL LDIV EVEN ROL
ROR UNI SEC HALT HALT2 PUSH POP SUCC
PRED ASSERT ASSERT2 RAISE RAISE2 ALLOC_RESULT
C.1.2 Einfache Konstanten
Ganzzahlkonstanten können binär, dezimal, hexadezimal oder zu einer
beliebigen Basis angegeben werden. Beginnt eine Konstante mit einer Zahl,
wird sie als dezimal interpretiert. Ein Dollarzeichen "$" leitet eine
Hexzahl, ein Prozentzeichen "%" eine Binärzahl ein. Zahlen zu einer
beliebigen Basis haben die Form "Basis:Wert"
$$ intconst ::= (digit{digit})|("$"{hexdigit})|("%"{bindigit})|
(digit{digit}:hexdigit{hexdigit})
Folgende Konstanten haben zum Beispiel den selben Wert:
107, $6B, %1101011, 16:6B 4:1223
Realkonstanten enthalten einen Punkt und bei Bedarf eine Exponentenangabe.
Diese wird durch ein "E" in der Zahl begonnen. Danach kann bei Bedarf ein
"-" folgen.
$$ realconst ::= digit{digit}"."{digit}["E"["-"]digit{digit}]
Folgende Konstanten haben z.B. den selben Wert:
127.64, 1.2764E2, 12764.E-2
Zeichenkonstanten werden durch ein kaufmännisches Und "&", mit
nachfolgendem ASCII-Wert in Dezimalschreibweise angegeben. Alternativ kann
auch das Zeichen in Anführungstrichen gegeben sein.
$$ charconst ::= ("&"digit{digit})|("char")
Beispiele:
"A", "0", &13, &10
C.1.3 Zeichenkettenkonstanten
Zeichenketten werden als Folge von ASCII-Zeichen in Anführungsstrichen
angegeben. Dabei ist eine Konkatenation erlaubt, um im Text Sonderzeichen
verwenden zu können, oder eine Konstante über mehrere Zeilen erstrecken zu
lassen.
$$ strconst ::= ("{char}")|charconst
$$ stringconst ::= strconst{"+"strconst}
Beispiele:
"Haus", "Dies ist ein Text", "X", "Return"+&10+"neue Zeile"+&10
Der Typ einer Zeichenkettenkonstante ist der Typ STRING. Zur kompatibilität
mit Sprachen ohne expliziten Stringtyp, wird einer Zeichenkettenkonstante
ein unsichtbares ASCII-NULL (&0) angehängt.
C.1.4 Geschützte Bezeichner und Symbole
In Cluster sind viele Sprachsymbole durch Bezeichner gegeben. Diese
Bezeichner bestehen nur aus großen Buchstaben, und werden durch den Editor
zusätzlich Fett hervorgehoben.
Folgende Symbole werden in Cluster verwendet:
" # $ % & ( ) (* *) +
+^ , - -^ . .. / : ; <
<= = > >= [ ] ^ { }
AND AND_IF AND_WHILE ARRAY AS BCPLPTR BEGIN BY
CLASSPTR CLOSE CONST DEFINITION DIV DO
ELSE ELSIF END EXCEPT EXIT FOR FORGET FORWARD
FROM GROUP HIDDEN IF IMPLEMENTATION IMPORT IN KEY
LIBRARY LOOP MOD MODULE NOT OF OR OR_IF
OR_WHILE POINTER PROCEDURE RECORD REF REPEAT RETURN
SET SHL SHR STATIC TAGS THEN TO TRACK TRY
TYPE UNTIL VAR WHILE WITH
C.1.5 Kommentare
Kommentare können als (* <Kommentartext> *) angegeben werden. Sie können
geschachtelt werden, und zwischen zwei beliebigen Symbolen liegen. Durch
einen snkrechten Strich "|" kann eine Zeile bzw. der Rest der Zeile nach
dem Strich zum Kommentar erklärt werden.
C.2 Typen
Cluster ist eine streng und statisch getypte Sprache, das heißt, bei allen
Operationen werden die Typen beachtet. Die Typüberprüfung findet soweit
möglich während der Compilierung statt.
Eine Typausdruck hat folgendes Format:
$$ simpletype ::= qualident|("["expression[".."expression]"]")|
("("ident[:=expression]{,ident[:=expression]}")")
$$ varlist ::= ident{,ident}":"typeexpress
$$ elements ::= expression[".."expression]
$$ elemlist ::= elements{","elements}
$$ recordtype ::= varlist|
(IF KEY [ident]":"simpletype
{OF elemlist THEN {varlist}} END)
$$ tagtype ::= ident [:= expression] : typeexpress
$$ paradeclar ::= [VAR|REF] ident [IN expression]
{,ident [IN expression]}:qualident
$$ formalparams ::= ["("[paradeclar{;paradeclar}]")"[":"qualident]
$$ typeexpress ::= ident|simpletype|
(ARRAY [simpletype{,simpletype}] OF typeexpress)|
(RECORD [OF qualident] recordtype{;recordtype})|
(SET OF simpletype)|
(POINTER|CLASSPTR|BCPLPTR TO typeexpress)|
(ident"("expression")")|
(PROCEDURE formalparams)|
(TAG [OF qualident] tagtype{;tagtype})
Eine Typdefiniton hat folgendes Format:
$$ typedef ::= TYPE {ident"="typeexpress}
Dabei wird einem Bezeichner ein Typ zugeordnet, der dann über diesen
Bezeichner weiter verwendet werden kann.
C.2.1 Einfache Typen
C.2.1.1 Zählbare Typen
SHORTINT -128..127
INTEGER -32768..32767
LONGINT -2147483648..2147483648
SHORTCARD 0..255
CARDINAL 0..65535
LONGCARD 0..4294967296
CHAR &0..&255 Zeichen
BOOLEAN TRUE , FALSE
Aufzählungstypen sind Typen, die eine feste Anzahl von Werten
repräsentieren. Diese sind durch Konstanten gegeben, die von Null an aufwärts
numeriert sind. Ein Aufzählungstyp wird durch eine Aufzählung der Elemente
in runden Klammern definiert.
Beispiel:
Obst=(Apfel,Banane,Kirsche) oder Farben=(rot,gruen,blau)
Die Numerierung der Elemente eines Aufzählungstyps kann unterbrochen und
mit einem neuen Wert fortgesetzt werden:
FileAccess=(readWrite=1004,readOnly=1005,newFile=1006);
oder auch:
FileAccess=(readWrite=1004,readOnly,newFile);
Verschiedene Aufzählungstypen dürfen die selben Bezeichner für ihre
Elemente benutzen z.B.:
Hardware:
TYPE IntFlags = (tbe,dskblk,softint,ports,copper ... );
Exec:
TYPE
NodeType = (unknown,task,interrupt,device,msgport,message,
freeMsg,replyMsg,resource,library,memory,
softint,font ... );
TYPE
MsgPortAction = (signal,softint,ignore);
In allen drei Typen ist ein Element namens "softint" enthalten. Der
Compiler versucht so weit ihm dies möglich ist, das richtige Element zu
finden, ist ihm dies nicht eindeutig möglich, meldet er einen Fehler. Das
Element muß dann über seinen Typen qualifiziert werden:
NodeType.softint
Unterbereichstypen repräsentieren eine Untermenge der Elemente eines
einfachen Typs. Die Grenzen werden in eckigen Klammern getrennt durch ".."
gegeben. Wird nur ein Wert angegeben, bedeutet dies einen Unterbereich von
null an mit sovielen Elementen.
Beispiel:
[1..10], [-200..200], ["A".."Z"], [10], [gruen..blau]
C.2.1.2 Überabzählbare Typen
Cluster bietet drei Typen, die die Menge der Reellen Zahlen repräsentieren.
Diese Typen sind unvollständig, da der Speicherplatz eines Rechners
beschränkt ist.
FFP, REAL 7 geltende Stellen, Exponent bis +/- 19
LONGREAL 16 geltende Stellen, Exponent bis +/- 304
REAL-Zahlen sind IEEE-single-precision Zahlen. Da diese erst ab 2.0 bzw.
mit einem mathematischen Coprozessor verfügbar sind, können sie auf einem
normalen Rechner unter 1.3 nicht genutzt werden.
Auf einem Amiga ohne FPU sind FFP-Zahlen schneller als REAL-Zahlen;
besitzt man jedoch eine FPU, sind REAL-Zahlen schneller.
C.2.1.3 Mengentypen
Mengentypen sind Typen von Mengen über einer vorgegebenen Grundmenge. Diese
muß sich aus Elementen eines zählbaren Typs zusammensetzen. Die Maximale
Anzahl der Elemente der Grundmenge ist 32. Ein Mengentyp wird definiert
durch "SET OF".
Beispiel:
BITSET=SET OF [0..15], Farbset=SET OF Farben
C.2.2 Komplexe Typen
Komplexe Typen sind in Cluster Arrays, Records, Tags und Strings.
C.2.2.1 Arrays
Ein Array ist eine Zusammenfassung mehrerer Elemente eines Typs zu einem
neuen, wobei jedes Element eindeutig über einen Index (oder mehrere
Indices) bezeichnet wird. Ein Array besitzt einen Unterbereichstypen als
Indextyp und einen Basistyp.
Beispiel:
Farbwerte = ARRAY Farben OF [0..15];
Mehrdimensionale Arrays sind Arrays von Arrays:
ARRAY T1 OF ARRAY T2 OF ARRAY T3 .. OF T
ist gleichbedeutend mit
ARRAY T1,T2,T3... OF T
Matrix = ARRAY [1..3],[1..3] OF REAL;
Es ist auch möglich Arrays über einen Typen zu definieren, dessen
Obergrenze nicht bekannt ist. Diese offenen Arrays haben immer INTEGER als
Indextyp.
TYPE
Vector = ARRAY OF REAL;
Sie können nur auf vier Arten verwendet werden, als Ziel eines Pointers,
als Parameter einer Prozedur, als Typ einer Konstanten oder bei der
Definition eines geschlossenen Typen. Wird ein offenes Array als
Pointerziel verwendet, sollte dafür ein CLASSPTR verwendet werden, da nur
so eine Bereichsüberprüfung möglich ist. Die Grenzen eines existierenden
offenen Arrays können über Attribute ermittelt werden.
TYPE
Vector3 = Vector(3);
VecPtr = CLASSPTR TO Vector;
CONST
IntArray = ARRAY OF INTEGER:(1,2,3,4);
Will man ein solches offenes Array allozieren, trägt man zuerst den RANGE
des Arrays ein, und ruft dann New auf:
VAR
UserVec : VecPtr;
BEGIN
UserVec'RANGE:=6;
New(UserVec);
C.2.2.2 Records
Ein Record ist eine Zusammenfassung mehrerer Elemente verschiedener Typen
zu einem neuen, wobei jedes Element über einen zusätzlichen Namen
angesprochen wird.
TYPE
Adresse = RECORD
namen,
vornamen : STRING(20);
alter : INTEGER;
END;
Werden in einem Record nicht alle Felder gleichzeitig benötigt, können
diese übereinandergelegt werden. Dies spart Speicherplatz. Man spricht in
diesem Fall von einem varianten Record.
TYPE
Geschlecht = (maennlich,weiblich);
Person = RECORD
namen,
vornamen : STRING(20);
IF KEY geschl : Geschlecht
OF maennlich THEN dienstgrad : STRING(20);
OF weiblich THEN maedchenname : STRING(20);
END;
END;
(Entschuldigen Sie dieses chauvinistische Beispiel, aber es ist nicht von
mir.)
Records lassen sich um weitere Elemente erweitern, dabei bleiben alle
vorherigen Felder erhalten. Ein Record, der sich auf einen bereits
bestehenden gründet, wird durch RECORD OF eingeleitet.
Ein Beispiel aus Exec:
MinNodePtr = POINTER TO MinNode;
MinNode = RECORD
succ,pred : MinNodePtr;
END;
Node = RECORD OF MinNode
type : NodeTypes;
pri : SHORTINT;
name : SysSTringPtr;
END;
Message = RECORD OF Node
replyPort : MsgPortPtr;
length : CARDINAL;
END;
IORequest = RECORD OF Message
device : DevicePtr;
unit : UnitPtr;
command : CARDINAL;
flags : IOFlagSet;
error : SHORTCARD;
END;
So lassen sich Hierarchien von Typen aufbauen, die nach unten
zuweisungskompatibel sind.
C.2.2.3 Strings
Ein String in Cluster hat folgendes Format:
STRING = RECORD
len : INTEGER;
data : ARRAY OF CHAR;
END;
Es gibt wie bei Arrays offene und feste Strings. Für Variablen dürfen nur
Stringtypen mit fester Maximallänge verwendet werden.
String30 = STRING(30);
Offene Strings unterliegen den selben Beschränkungen wie offene Arrays.
Jeder String hat zwei Endkenzeichen: die in len gegebene Länge und ein
Nullbyte nach dem letzten Zeichen. Dieses Nullbyte geht nich in len ein,
wohl aber in die maximale Länge des Strings.
C.2.2.4 Tag-Typen
Ein Tag Element besteht immer aus zwei Elementen. Das erste Element enthält
einen TAG-Wert, der aussagt, welchen Typ das zweite Element hat. Dieses
zweite Feld ist maximal vier Bytes groß. Hierbei ist ein Erben von bereits
bestehenden Tag-Typen möglich.
Beispiel:
ScreenTags = TAGS OF StdTags
width = $80000020 : INTEGER;
height : INTEGER;
depth : INTEGER;
name : POINTER TO STRING;
flags : ScreenFlagSet
END;
Tags werden durch das Amiga-OS 2.0 häufig verwendet und zwar in der Form
von TAG-Listen, das sind (nach Bedarf auch verkettete) Arrays von Tag-
Typen.
Beispiel:
ScreenTagList = ARRAY OF ScreenTags;
Um nun einen Screen zu öffnen, muß eine Tag-Liste angegeben werden, in der
die Elemente aufgeführt sind, die sich von den Werten eines Standardscreens
unterscheiden.
OpenScreenTagList(ScreenTagList:(width : 320,
height : 256,
name : "Name"'PTR));
Die Verwendung von Tags beschränkt sich momentan noch auf derartige
Konstanten.
C.2.3 Zeigertypen
Es gibt in Cluster drei Zeigertypen: normale Zeiger (POINTER), Zeiger für
offene Typen (CLASSPTR) und Zeiger, die zu BCPL kompatibel sind (BCPLPTR).
Beispiel:
TYPE
NodePtr = POINTER TO Node;
Node = RECORD
prev,
next : NodePtr;
key : INTEGER;
END;
Es existiert ein Pointertyp, der zu allen Pointertypen und zu LONGINT
kompatibel ist: ANYPTR. Dieser Pointer hat kein eigentliches Ziel. Zu
diesem Typ gibt es die Konstante NIL, die eine nicht vorhandene Adresse
bedeutet.
Neben diesen allgemeineren Pointertypen exisitiert noch ein Typ, der
speziell zum Gebrauch bei der Vererbung von Records eingeführt wurde,
der SAMEPTR. Ein SAMEPTR kann nur als Element eines Records verwendet
werden, er stellt einen Zeiger auf den Typen dar, den der aktuelle Record
bildet.
Beispiel:
TYPE
Node = RECORD
prev,
next : SAMEPTR;
END;
Node2 = RECORD OF Node
data : INTEGER
END;
Die Felder prev und next haben in Records des Typs Node den Typ POINTER TO
Node, in Records des Typs Node2 aber den Typen POINTER TO Node2. Der Record
bleibt trotzdem nach unten kompatibel, da ja ein POINTER TO Node2 auch ein
Nachfahre von POINTER TO Node darstellt.
Der Vorteil ist aber, daß Ausdrücke der Form Node2^.next^.data möglich
sind, was bei der Verwendung des Typs POINTER TO Node anstatt eines
SAMPETRs nicht möglich wäre.
C.2.4 Opake-Typen
Opake Typen sind Typen, die in einem Definitions-Modul angegeben, aber erst
im Implementations-Modul definiert werden. Dies ermöglicht das
Geheimnissprinzip. Ein opaker Typ darf nur stellvertretend für einen
Pointer stehen.
DEFINITION MODULE <name>
TYPE
List = HIDDEN;
END <name>.
IMPLEMENTATION MODULE <name>
TYPE
List = POINTER TO
RECORD
first,
prev : NodePtr;
END;
END <name>.
C.2.5 Prozedurtypen
Prozeduren haben in Cluster ebenfalls einen Typ. Dieser ist durch die
Übergabeparameter gegeben (siehe Prozedurdeklaration).
C.3 Variablendeklaration
Alle Variablen, die in Cluster benutzt werden, haben einen festen Typ, und
müssen vor ihrer Benutzung deklariert werden.
$$ vardeclar ::= ident [(IN expression)|STATIC]
{,ident [(IN expression)|STATIC]}
(([:typeexpress] := expression)|(:typeexpress))
$$ vardeclaration ::=VAR [vardeclar{;vardeclar}]
Das Schlüsselwort IN gibt an, daß die Variable in ein Register zu legen
ist. Dies gilt nur für den unmittelbaren Sichtbarkeitsbereich der
Variablen, nicht jedoch für weiter innen liegende.
Beispiel:
VAR i,j : INTEGER;
c : CHAR;
k IN D2 : INTEGER;
l : INTEGER := 2;
n := NewScreen:(width=...);
Die Registernamen D0 bis A7 sind im Modul SYSTEM als Aufzählungstyp 'Regs'
definiert.
Durch die Initialisierung ":=" kann der Variable bei ihrer Deklaration ein
Startwert zugewiesen werden. Lokale Variablen werden am Prozeduranfang,
globale Variablen dagegen werden am Programmanfang initialisiert. Alle
globalen Variablen, die nicht mit einer Konstante vorbelegt werden, werden
am Programmanfang mit Nullen initialisiert.
Das Schlüsselwort STATIC kann nur bei Variablendeklarationen innerhalb von
Prozeduren verwendet werden. Diese Variablen überleben ihre Prozedur, und
haben beim nächsten Aufruf noch den selben Wert wie beim Verlassen. Werden
sie vorinitialisiert, so geschieht dies nur einmal am Programmanfang.
C.4 Konstanten
C.4.1 Einfache Konstanten
Einfache Konstanten sind Konstanten der einfachen Typen.
Beispiel:
123.43, "C", 12, $1234
Der Typ einer derartigen Konstante ergibt sich aus ihrer Darstellung, so
ist "C" vom Typ CHAR.
C.4.2 Komplexe Konstanten
In Cluster ist es auch möglich, Konstanten komplexer Typen zu bilden.
Stringkonstanten werden einfach in Hochkomma eingeschlossen:
"Dies ist ein Text"
Andere komplexe Konstanten werden durch einen Typbezeichner mit
nachfolgendem Doppelpunkt eingeleitet:
$$ comconst ::= expression|
("("[comconst{,comconst}]")")|
("("[ident=comconst{,ident=comconst}]")")|
("{"elemlist"}")|
(ident : comconst)
$$ complexconst ::= ((ident|(ARRAY OF))":"comconst)|
("{"elemlist"}")
Beispiel:
ARRAY OF Vector3:((1,0,0),(0,1,0),(0,0,1));
FarbSet:{gruen,blau};
ARRAY OF Adresse:((name ="Sigmund",
vorname="Ulrich",
alter =23),
(name ="Pfrengle",
vorname="Thomas",
alter =21));
Ein Zeiger auf eine derartige komplexe Konstante ist ebenfalls eine
Konstante. Dieser Zeiger kann durch das Attribut PTR gewonnen werden.
Bei Mengen, deren Typ dem Compiler sicher bekannt ist (bei Zuweisungen,
innerhalb komplexer Konstanten etc.) kann auf den Typbezeichner verzichtet
werden.
Bei Recordkonstanten kann auf die Bezeichner verzichtet werden (sollte nur
in Ausnahmefällen geschehen, da die Lesbarkeit des Programms meist
vermindert wird), und die Elemente können in ihrer Reihenfolge durch Kommata
getrennt aufgezählt werden. Diese Technik ist zu empfehlen bei großen
Arrays aus kleinen Records.
Bei Konstanten, die aus offenen Arrays gebildet werden, wird die wirkliche
Größe aus der Anzahl der angegebenen Elemente gebildet.
C.4.2 Konstantendefinition
Konstanten können mit einem Namen belegt werden, und so mehrfach verwendet
werden. Dabei ist für komplexe Konstanten auch eine Vorwärtsdeklaration
möglich, indem nur der Typ der Konstanten angegeben wird. Dies ist
besonders in Defintionsmodulen oder für konstante verkette Listen sinnvoll.
$$ constdef ::= ident "=" (ident|expression)
$$ constdefinition ::= CONST [constdef{;constdef}]
CONST
pi = 3.1415;
Basis = ARRAY OF Vector3:((1,0,0),
(0,1,0),
(0,0,1));
C.4.3 Ausnahmedeklaration
Ausnahmen sind Zustände, die eintreten, wenn nicht planmäßige Ereignisse
auftreten, wie zu wenig Speicher, eine Division durch Null oder der Versuch,
aus einer leeren Datenstruktur ein Element zu lesen.
Es gibt drei Arten von Ausnahmen: solche, die durch den Prozessor
hervorgerufen werden (wie Division durch Null, Bereichsfehler etc.); solche,
die durch die Standard/Schnittstellenmodule ausgelöst werden (wie zu wenig
Speicher) und benutzerdefinierte Ausnahmen.
$$ exceptdefs ::= ident : (stringconst|intconst)
$$ exceptdef ::= EXCEPTION exceptdef{;exceptdef}
Beispiele:
EXCEPTION
NotEnoughMemory : "Nicht genug Systemspeicher verfügbar";
DivisionByZero : 8;
C.4.4 Gruppendeklaration
Objekte und Deklarationen in einem Definitionsmodul können zu einer Gruppe
zusammengefasst werden, durch deren Importierung alle darin enthaltenen
Objekte importiert werden.
$$ groupdef ::= GROUP {ident = ident{,ident};}
In einer solchen Gruppe können auch andere Gruppen enthalten sein. Stammen
diese aus einem anderen Modul, müssen sie qualifiziert angegeben werden.
C.5 Ausdrücke
Ein Ausdruck besteht aus Variablen, Konstanten, Attributen, Funktionen und
Operatoren.
$$ relop ::= "="|"#"|"<="|">="|"<"|">"
$$ operator ::= "+"|"-"|"*"|"/"|"^"|DIV|MOD|SHL|SHR|AND|OR
$$ paralist ::= [expression{,expression}]
$$ term ::= {-}|{NOT}(qualident|complexconst|intconst|realconst|
charconst|stringconst|("("expression")"))|
relop|"+"
$$ expression ::= term{(operator term)|("^"|"+^"|"-^")|
("." ident)|("'" ident)|(IN expression)|
(OF elemlist)|("("paralist")")|
("["expression{,expression}"]")
Die Operatoren haben bei verschiedenen Typen verschiedene Bedeutungen:
Zahlen: Mengen: Zeiger: Boolean:
----------------------------------------------------------------------
+ | Summe Vereinigung -- --
- | Differenz Differenz -- --
* | Produkt Schnitt -- --
/ | Division Sym.Differenz -- --
^ | Potenz -- Dereferenz --
DIV | Ganzzahldiv. -- -- --
MOD | Rest der Div. -- -- --
SHL | Linksshiften -- -- --
SHR | Rechtsshiften -- -- --
+^ | -- -- Postinc.Deref. --
-^ | -- -- Predec.Deref. --
= | gleich gleich gleich Äquivalenz
# | ungleich ungleich ungleich Exklusiv-Oder
< | kleiner -- kleiner --
<= | kleiner/gleich Teilmenge von hleiner/gleich --
> | größer -- größer --
>= | größer/kleich Obermenge von größer/gleich --
AND | -- -- -- Und
OR | -- -- -- Oder
IN | -- Element aus -- --
OF | Element aus -- -- --
Wird ein relationaler Operator als parameterlose Funktion benutzt, liefert
er den Zustand des korrespondierenden Prozessorflags.
Folgende Regeln gelten für die Rechnungskompatibilität der Typen t1 und t2:
t1 ist gleich t2
t1 und t2 sind SHORTINT, INTEGER, LONGINT oder ANYPTR
t1 und t2 sind SHORTCARD, CARDINAL oder LONGCARD
t1 und t2 sind FFP, REAL oder LONGREAL
t1 und t2 sind Unterbereiche zweier rechnungskompatibler Typen
Das Zeichen "." dient dazu, ein Element eines Records zu qualifizieren. Mit
den Klammern [] wird ein Element eines Arrays bestimmt. Dies kann bei
mehrdimensionalen Arrays auf zwei verschiedene Arten geschehen:
A[i1][i2].. oder A[i1,i2,..]
Das Zeichen "^" dient zur Dereferenzierung eines Zeigers, d.h. aus dem
Zeiger wird das Objekt gebildet, auf das der Zeiger zeigt. Bei Zeigern auf
Records und Arrays kann auf die explizite Dereferenzierung verzichtet
werden, wenn diese unmittelbar darauf qualifiziert oder indiziert werden.
Bei der Auswertung eines Ausdrucks gelten folgende Prioritäten:
1. Funktionsauswertung, Dereferenzierung, Indizierung, Qualifizierung,
Attributbildung
2. Unäres Minus, Negation
3. Potenzierung
4. Multiplikation, Division, Shifts, logisches Und
5. Addition, Subtraktion, logisches Oder
6. relationale Operatoren
Attribute liefern Informationen über Objekte und deren Typen. Sie werden
durch ein Hochkomma "'" und einen Attributsbezeichner ermittelt. Folgende
Attribute sind vorgegeben:
PTR : Zeiger auf das Objekt
ADR : Adresse des Objekts
MIN : kleinster Wert, oder untere Indexgrenze
MAX : größter Wert, oder obere Indexgrenze
RANGE : Anzahl der Elemente eines Arrays/Strings
SIZE : Größe in Bytes
Neben selbstdefinierbaren Funktionen existiert auch eine Anzahl
vordefinierter Funktionen. Diese werden meist nicht aufgerufen, sondern
direkt in den erzeugten Code eingebaut.
ODD(x:INTEGER):BOOLEAN liefert TRUE, falls x ungerade ist.
CAST(Typ,x):Typ Wandelt den Typ des Objekts x in Typ
LMUL(x,y):LONG.. Multiplikation von 16x16Bit auf 32Bit
LDIV(x,y):... Division von 32x16Bit auf 16Bit
REG(x):LONGINT Inhalt des Prozessorregisters x
PRED(x):... Liefert den Wert von x um eins vermindert zurück,
auch für Aufzählungstypen
SUCC(x):... Liefert den Wert von x, um eins erhöht zurück.
CEIL(real):... Liefert die nächste ganze Zahl, die größer oder
gleich real ist.
FLOOR(real):... Liefert die nächste ganze Zahl, die kleiner oder
gleich real ist.
Die Standardfunktionen SUCC und PRED liefern bei Integers etc. den Wert
+/-1, bei Zeigern auf Strukturen einen Zeiger des selben Typs, dessen
Zieladresse um die Größe eines Zielelements erhöht ist.
Beispiel:
SUCC(3) = 4
PRED("B") = "A"
TYPE
Elem = RECORD
x,y,z : INTEGER;
c,h : CHAR;
END;
VAR
Array : ARRAY [100] OF Elem;
p : POINTER TO Elem;
i : INTEGER;
BEGIN
p:=Array[0]'PTR;
FOR i:=Array'MAX TO 0 BY -1 DO
p.x:=10;...;p.h:="C";
p:=SUCC(p)
END;
END...
Oder auch extrem:
SUCC(SUCC(p))^.x:=4;
Neben diesen allgemeinen Funktionen verfügt Cluster noch über eine große
Anzahl mathematischer Funktionen für REALs und LONGREALs:
SIN, ASIN, SINH, COS, ACOS, COSH, TAN, ATAN, TANH, LN, LOG, EXP, SQRT, ABS
Der Typ eines Ausdrucks kann sicher in einen anderen überführt werden,
indem der Typbezeichner als Funktion verwendet wird.
C.6 Anweisungen und Strukturen
Eine Anweisungsfolge ist eine Folge von Anweisungen, die durch Semikoli
getrennt sind.
$$ statementsequence ::= statement{;statement}
$$ statement ::= |assignment|repeatstatement|whilestatement|
ifstatement|loopstatement|forstatement|
withstatement|exitstatement|returnstatement|
trystatement|forgetstatement|procedurecall
Auch die leere Anweisung ist eine Anweisung.
C.6.1 Zuweisungen
Die wichtigste Anweisung ist die Zuweisung. Dabei wird einem Ausdruck auf
der linken Seite der Wert des Ausdrucks der rechten Seite zugewiesen. Der
Ausdruck der linken Seite muß eine Variable bzw. eine durch einen Pointer
bezeichnete Speicherstelle sein.
$$ assignment ::= expression ":=" expression
Folgende Regeln gelten für Zuweisungskompatibilität von t1 und t2:
t1 ist gleich t2
t1 und t2 sind Pointer auf den selben Typen
t1 und t2 sind SHORTINT, INTEGER, LONGINT, SHORTCARD, CARDINAL, LONGCARD
oder ANYPTR
t1 und t2 sind REAL oder LONGREAL
t1 und t2 sind Unterbereiche eines zuweisungskompatiblen Typen
t1 und t2 sind Arrays gleichen Basistyps mit gleichviel Elementen
t1 und t2 sind Strings beliebiger Länge
t1 und t2 sind direkte Nachfolger bei offenen Records
C.6.2 REPEAT..UNTIL..
$$ repeatstatement ::= REPEAT statementsequence UNTIL expression
Die Anweisungsfolge zwischen REPEAT und UNTIL wird solange ausgeführt, bis
der Ausdruck an ihrem Ende wahr wird.
i:=0;
REPEAT WriteInt(i,0);INC(i) UNTIL i=10
liefert
0 1 2 3 4 5 6 7 8 9
C.6.3 IF-Struktur
Die IF-Struktur ist extrem mächtig, sie enthält auch die von Modula
bekannte CASE-Anweisung.
$$ ifcase ::= (expression (THEN statementsequence)|
(AND_IF ifcase))|(KEY expression
{OF elemlist (THEN statementsequence END)|
(AND_IF ifcase)})
[OR_IF|ELSIF ifcase]|([ELSE statementsequence] END)
$$ ifstatement ::= IF ifcase
Die Grundstruktur ist:
IF b1 THEN
S1
OR_IF b2 THEN
S2
...
ELSE
SN
END;
Der zu dem ersten Boolausdruck bi, der TRUE ist, gehörende Programmteil Si
wird ausgeführt, und das Programm hinter END fortgesetzt. Ist kein Ausdruck
wahr, wird SN ausgeführt.
Jeder IF-Fall kann durch Einführen einer AND_IF-Klausel eingeschränkt
werden, die selbst wieder OR_IF und/oder ELSE-Klauseln besitzt. Ist keiner
dieser Fälle wahr, und kein ELSE-Teil vorhanden, wird der Vergleich mit dem
nächsten tieferliegenden OR_IF oder ELSE weitergeführt.
...
OR_IF bx
AND_IF bx1 THEN
...
OR_IF bx2 THEN
...
ELSE
...
END
OR_IF by
...
Sollen anhand eines Schlüsselwertes verschiedene Möglichkeiten geprüft
werden, kann "KEY" benutzt werden.
OR_IF KEY a1
OF ... THEN ... END
OF ... AND_IF bx THEN
...
OR_IF KEY a11
OF ... THEN
...
END
OF ... THEN ... END
OR_IF ...
C.6.4 WHILE-Struktur
Die WHILE-Struktur ist völlig symmetrisch zur IF-Struktur. Der Unterschied
ist, daß nachdem ein Programmteil zu einem DO ausgeführt wurde, wieder
zurück zum Anfang der WHILE-Struktur gesprungen wird.
$$ whilecase ::= (expression (DO statementsequence)|
(AND_WHILE whilecase))|(KEY expression
{OF elemlist (DO statementsequence END)|
(AND_WHILE whilecase)})
[OR_WHILE whilecase]|([ELSE statementsequence] END)
$$ whilestatement ::= WHILE whilecase
C.6.5 LOOP..END, EXIT
Die LOOP-Struktur ist eine Schleife ohne dedizierte Terminationsbedingung.
Die Schleife wird durch das Schlüsselwort EXIT terminiert, das beliebig
innerhalb der Schleife stehen darf.
$$ loopstatement ::= LOOP statementsequence END
$$ exitstatement ::= EXIT
C.6.6 FOR..DO..END
Die FOR-Struktur ist eine Schleifenstruktur, deren Termination durch einen
Zähler gegeben ist. Dieser läuft von einem angegebenen Startwert zu einem
Endwert, wobei der Schleifenindex jedesmal um eine vorgegebene konstante
Schrittweite erhöht wird. Als Schleifenzähler sind alle zählbaren Typen
zugelassen.
$$ forstatement ::= FOR assignment TO expression [BY expression] DO
statementsequence END
FOR i:=start TO end BY step DO statement END;
entspricht für positives step:
i:=start;WHILE i<=end DO statement;INC(i,step) END;
bei negativem step:
i:=start;WHILE i>=end DO statement;INC(i,step) END;
Wird kein Schrittwert angegeben, wird eins verwendet.
C.6.7 WITH..DO..END
Die WITH-Struktur erzeugt temporäre Variablen, oder gibt bestehenden einen
neuen Namen.
$$ withstatement ::= WITH expression [AS ident]{,expression AS ident} DO
statementsequence END
Wird als Ausdruck ein Typ angegeben, wird eine neue Variable erzeugt.
VAR source,dest : POINTER TO
ARRAY [0..3] OF
POINTER TO
ARRAY [0..9] OF INTEGER;
i : INTEGER;
FOR i:=0 TO 9 DO
dest^[2]^[i]:=source^[3]^[9-i]
END;
Kann durch WITH vereinfacht werden:
WITH dest^[2]^ AS d,source^[3]^ AS s DO
FOR i:=0 TO 9 DO
d^[i]:=s^[9-i]
END;
END;
Dies ermöglicht dem Compiler auch Optimierungen, da er die Adressausdrücke
nur einmal errechnen muß.
Wird kein neuer Namen durch AS angegeben, wird der Name der Basisvariablen
beibehalten.
C.6.8 RETURN
RETURN beendet die Ausführung einer Prozedur. Handelt es sich dabei um eine
Funktion mit einem einfachen Rückgabetypen, muß dahinter ein Ausdruck
angegeben werden, der den Rückgabewert darstellt. Bei Funktionen mit
komplexem Rückgabewert wird dieser durch die Variable RESULT zurückgegeben.
$$ returnstatement ::= RETURN [expression]
C.6.9 Prozeduraufruf
Eine Prozedure wird aufgerufen, wenn ein Ausdruck einen Prozedurtypen
liefert (eine normale Prozedur ist eine Konstante ihres eigenen
Prozedurtyps).
$$ procedurecall ::= expression "("paralist")"
Cluster verfügt über eine Reihe von Compilerprozeduren, die nicht
aufgerufen werden, sondern direkt in den erzeugten Code eingebaut werden.
INC(x) erhöht x um 1, nur zählbare Typen und Zeiger
INC(x,m) erhöht x um m
DEC(x) erniedrigt x um 1
DEC(x,m) erniedrigt x um m
EVEN(x) rundet x auf die nächste gerade Zahl ab
SHL(x,n) shiftet x um n Bits links
SHR(x,n) shiftet x um n Bits rechts
ROL(x) rotiert x um ein Bit nach links
ROR(x) rotiert x um ein Bit nach rechts
INCL(s,e) fügt das Element e in die Menge s ein
EXCL(s,e) entfernt das Element e aus der Menge s
FLIP(s,e) wechselt die Anwesenheit von e in der Menge s
UNI(d,s) verinigt die Menge d mit der Menge s
SEC(d,s) schneidet die Menge d mit der Menge s
SETREG(r,v) schreibt den Wert von v in Register r
INLINE(x,..) schreibt eine Anzahl von Datenwörter in das erzeugte
Programm
HALT(x) beendet das Programm mit Fehlernummer x
HALT2(x) beendet das Programm mit Fehlernummer x an der Stelle, an
der die aktuelle Prozedur aufgerufen wurde
RAISE(x) löst die Ausnahme x aus
RAISE2(x) löst die Ausnahme x aus, wobei als Ereignisspunkt der Punkt
des Prozeduraufrufs angegeben wird
ASSERT(b,x) stellt sicher, daß die Bedingung b erfüllt ist, andernfalls
wird die Ausnamhe x ausgelöst
ASSERT2(b,x) wie ASSERT, nur wird als Ereignisspunkt der Punkt des
Prozeduraufrufs angegeben.
PUSH(set) Sichert die im set angegebenen Register auf den Stapel, z.B.
für Interrupts etc.
POP(set) Nimmt mit PUSH gesicherte Register wieder vom Stapel
herunter.
C.6.9.1 Der Inline-Assembler
Der Assembler ist als Standardfunktion implementiert: ASSEMBLE(...). Er
kann an einer beliebigen Stelle im Programm benutzt werden, der erzeugte
Code wird an genau dieser Stelle ins Programm eingefügt.
Bsp.:
BEGIN
...
ASSEMBLE(
MOVE.L D0,A0
...
BNE loop
);
...
END ...;
Es ist möglich auf alle lokalen und globalen Variablen über ihren Namen
zuzugreifen, der Assembler wählt automatisch die richtige Adressierungsart.
Auch auf Variablen innerhalb einer Prozedur kann zugegriffen werden, der
Assembler übersetzt den Zugriff in eine SP-relative Adressierung. Dies
wirft allerdings ein Problem auf, wenn der Stackpointer verändert wird, da
dies den Compiler schwer durcheinander bringt.
Bsp.:
VAR i : ARRAY [0..5] OF INTEGER;
PROCEDURE IncI(at,um : INTEGER);
BEGIN
ASSEMBLE(
MOVE um,D0
MOVE at,D1
ASL #1,D1
LEA i,A0
ADD D0,(A0,D1)
)
END IncI;
oder auch:
PROCEDURE IncI(at,um : INTEGER);
BEGIN
ASSEMBLE(USE D0
MOVE um,D0
ADD um,i[at]
)
END IncI;
Der Assembler überführt diesen Code automatisch in die entsprechenden
Anweisungen. Da der Compiler dazu freie Register benötigt, diese dem
Programm aber nicht einfach stehlen kann, ist es sinnvoll, die Register, die
man innerhalb des Programmes benutzen möchte, mit USE anzugeben. Der
Assembler betrachtet die nicht belegten Register als sein Eigentum, und
nutzt diese für erweiterte Adressierungen.
Es können auch Registervariablen verwendet werden:
PROCEDURE IncI(at IN D0,um IN D1 : INTEGER);
BEGIN
ASSEMBLE(USE A0
LEA i,A0
ASL #1,at
ADD um,(A0,at)
)
END IncI;
Der Assembler versucht so gut wie möglich, dem Stack-Pointer zu folgen; so
werden Veränderungen des SP durch ADD #,A7, SUB #,A7, LINK sowie -(A7) und
(A7)+ erkannt, und richtig in die Berechnung der Prozedur-lokalen Variablen
einbezogen. Was der Assembler nicht berücksichtigen kann sind Veränderungen
des SP durch MOVE ...,A7 o.ä, Veränderungen durch variable Werte wie in
ADD D0,A7, und Unterprogramme.
Bsp.:
PROCEDURE ReturnSame(i : INTEGER):INTEGER;
VAR j : INTEGER;
BEGIN
ASSEMBLE(SUB.W #100,A7
MOVE i,j
ADD.W #100,A7
RETURN j;
END ReturnSame;
Dies ist völlig legal und wird auch richtig übersetzt.
Der Compiler verlangt, daß am Ende eines Assemblerteiles der Stackpointer
wieder den selben Wert hat wie beim Eintritt, da er sonst beim Mitzählen
aus dem Ruder geraten würde.
Labels können an einer beliebigen Stelle im Assemblerteil verwendet, oder
definiert werden (dies natürlich nicht mitten in einem Befehl). Bei der
Definition eines Labels muß dieses von einem Doppelpunkt gefolgt werden. Es
gibt keine feste Form, wie Assembleranweisungen im Text formatiert werden
müssen, es muß lediglich zwischen Label, Mnemonic und Operanden mindestens
ein Space stehen, auch dürfen beliebig viele Anweisungen in einer Zeile
stehen.
Bsp.:
PROCEDURE Copy(from IN A0,to IN A1,size IN D2 : LONGINT);
BEGIN
ASSEMBLE(USE D1
MOVE size,D1 | .L wird erkannt, da "size" ein LONGINT
SHR #2,size
SUB #1,size
loop: MOVE.L (from)+,(to)+ DBRA size,loop
BTST #1,D1 BEQ not2 MOVE.W (from)+,(to)+
not2: BTST #0,D1 BEQ not1 MOVE.B (from)+,(to)+
not1: )
END Copy;
Dies steigert zwar nicht die Lesbarkeit, ist aber möglich. Nicht möglich
ist es mit, Labels zu rechnen, da dies den Shortbranch-Optimierer aus dem
Ruder bringen würden (Vorerst sind noch fast alle Optimierungen, die der
Compiler normalerweise ausführt, auch im Assemblerteil vorhanden, also
keinen Code patchen !!!).
Es gibt keine Anweisungen wie ADDA oder ADDI, diese werden durch den
Assembler bei Bedarf erzeugt.
C.6.10 FORGET
Viele Funktionen (gerade solche des Betriebssystems) sind eigentlich keine
Funktionen, sondern Prozeduren, da ihre eigentliche Aufgabe nicht in der
Rückgabe eines Wertes, sondern in der Ausführung einer Tätigkeit besteht
(z.B. WritePixel). Diese Funktionen liefern mehr als Nebeneffekt auch noch
einen Wert zurück, der in den meisten Fällen problemlos ignoriert werden
kann. Da es aber sehr gefährlich ist, eine Verwendung von Funktionen als
Prozeduren zuzulassen, es andererseits aber sehr störend ist, das
Funktionsergebnis jedesmal einer Dummy-Variablen zuzuweisen, kann mit
FORGET ein Ausdruck ausgewertet werden, dessen Ergebniss danach einfach
vergessen wird.
$$ forgetstatement ::= FORGET
Beispiel:
FORGET WritePixel(rast,x,y);
C.6.11 TRY..EXCEPT
Ausnahmesituationen (Exceptions) können durch Programm- bzw. Datenfehler,
systembedingte Probleme wie Speichermangel oder durch beutzerdefinierte
Ausnahmen entstehen.
Ist kein Exceptionhandler installiert, kann das Programm nur durch einen
Abbruch reagieren.
$$ trystatement ::= TRY statementsequence EXCEPT
{OF ident{,ident} THEN statementsequence END}
[ELSE statementsequence] END
Die Anweisungssequenz zwischen TRY und EXCEPT wird ausgeführt; tritt dabei
eine Ausnahme auf, wird das Programm mit dem passenden Exceptionhandler des
EXCEPT Teils fortgesetzt. Ist kein passender Exceptionhandler vorhanden,
wird der eventuell vorhandene ELSE Teil ausgeführt, und die Ausnahme an den
nächsten übergeordneten Exceptionhandler weitergereicht.
Somit werden alle Ausnahmen von dem nächsten passenden Exceptionhandler
bearbeitet. Ist überhaupt kein Exceptionhandler vorhanden, wird das Programm
mit einem Laufzeitfehler abgebrochen. Ein für die Exception angegebener Text
wird als Fehlermeldung verwendet.
Beispiel: Auswertung einer math. Funktion
PROCEDURE F(x : INTEGER):INTEGER;
BEGIN
RETURN 100 DIV x
END F;
Diese Prozedur löst für x=0 eine "DivisionByZero" Exception aus, die durch
den Funktionsplotter abgefangen werden muß:
PROCEDURE WriteF(l,r : INTEGER);
VAR i : INTEGER;
BEGIN
FOR i:=l TO r DO
TRY
WriteInt(F(i),10);
EXCEPT
OF DivisionByZero THEN WriteString("Division durch Null") END
END
WriteLn
END;
END WriteF;
Durch dieses TRY-Statement wird wird nur eine Division durch Null, nicht
aber andere Ausnahmen, wie Ctrl-C o.ä. abgefangen. Diese Ausnahmen werden
ohne Änderung weitergereicht.
Ausnahmen sollten so weit zurückgereicht werden, bis eine sichere
Bearbeitung möglich ist.
Der ELSE-Teil der TRY Anweisung dient als Close-Teil, so daß bereits
allozierte Strukturen noch freigegeben bzw. geschlossen werden können.
C.6.12 TRACK..END
Im Zuge des Resourcetrackings (Verfolgen der Systemresourcen durch das
Laufzeitsystem, siehe auch "Resources.def") wurde TRACK..END eingeführt.
Durch TRACK wird eine neuer Kontext erzeugt und zum ActContext erklärt.
Dieser Kontext wird bei Verlassen der Struktur (Normal, Exception oder
RETURN wieder entfernt).
C.7 Prozedurdeklaration
Eine Prozedurdeklaration besteht aus einem Prozedurkopf, in dem ihre
Schnittstelle definiert wird, und einem Prozedurkörper, in dem der
eigentliche Inhalt der Prozedur definiert wird. Eine Funktionsprozedur
hat zusätzlich zu den normalen Parametern noch einen Rückgabetypen, der
hinter einem Doppelpunkt definiert wird.
Innerhalb einer Prozedur können Typen, Variablen, Konstanten und weitere
Prozeduren deklariert werden. Objekte, die innerhalb einer Prozedur
deklariert sind, existieren nur, solange die Prozedur läuft. Sie sind auch
nur innerhalb dieser sichtbar. Alle außen bekannten Bezeichner sind auch
innerhalb bekannt, solange sie nicht durch neue Deklarationen überdeckt
werden.
$$ paradeclar ::= [VAR|REF] ident [IN expression]
{,ident [IN expression]}:
(qualident[:= expression])|(:=expression)
$$ formalparams ::= ["("[paradeclar{;paradeclar}]")"[":"qualident]
$$ procedureheader ::= PROCEDURE ident formalparams ";"
$$ proceduredeclar ::= ([FORWARD] procedureheader)|(procedureheader
{import|typedef|vardeclaration|constdef|proceduredeclar}
BEGIN statementsequence END ident) ";"
Soll eine Prozedur verwendet werden, bevor ihr Prozedurrumpf implementiert
werden kann, wenn sich also z.B. zwei Prozeduren gegenseitig benötigen, muß
sie mit dem Schlüsselwort FORWARD vorwärts deklariert werden.
Parameter können mit einem Vorgabewert vorbelegt werden. Parameter mit
Vorgabewert brauchen beim Prozeduraufruf nicht mit angegeben werden, statt
dessen wird der Defaultwert vorgegeben:
Beisp:
PROCEDURE WriteInt(val : LONGINT;width : INTEGER := 0);
kann aufgerufen werden mit:
WriteInt(10,4) oder aber auch WriteInt(41)
Hat eine Prozedur viele Parameter, kann nicht immer davon ausgegangen
werden, daß die zu überspringenden Parameter am Ende liegen. In diesem Fall
muß die Zuweisung durch Schlüsselwörter (die Namen der Parameter)
vorgenommen werden:
PROCEDURE New(VAR p : ANYPTR;
chip,
clear : BOOLEAN := FALSE;
context : ContextPtr := NIL);
New(p);
New(p,TRUE,FALSE,MyContext);
New(p,clear:=TRUE);
New(p,clear:=TRUE,context:=MyContext);
...
Die Reihenfolge der Parameter muß eingehalten werden, auch dürfen nach
Parametern mit Bezeichnern keine Positionalen mehr folgen.
Parameter können auf drei Arten übergeben werden, die bestimmen, wie
innerhalb der Prozedur auf dieses zugegriffen werden kann, welche Objekte
übergeben werden können und ob der Parameter nach der Prozedur verändert
sein kann.
C.7.1 Übergabe durch Wert (Call by value)
Als aktueller Parameter kann ein beliebiger Ausdruck angegeben werden. Das
Ergebnis der Auswertung wird als Kopie an die Prozedur übergeben.
Änderungen dieses Parameters innherhalb der Prozedur bleiben außen ohne
Wirkung, da die Prozedur ja lediglich über eine Kopie verfügt.
Nachteilig bei diesem Verfahren ist, daß das Kopieren bei komplexen Typen
relativ viel Zeit und Speicherplatz benötigt.
PROCEDURE WriteInt(i : INTEGER);
C.7.2 Übergabe durch Referenz (Call by reference)
Als aktueller Parameter kann ein adressierbares Objekt angegeben werden
(Variablen, Konstanten, Parameter oder Zeigerziele). Die Prozedur bekommt
die Adresse des Parameters übergeben, es wird im Gegensatz zu oben nicht
kopiert. Der Parameter kann innerhalb der Prozedur nicht verändert werden,
da ja sonst das Original verändert würde. Der aktuelle Parameter wird also
mit Sicherheit nicht verändert.
Diese Übergabe lohnt sich für größere Typen (Arrays oder Records) auf
keinen Fall aber für einfache Typen wie Zeiger oder Zahlen.
PROCEDURE WriteString(REF s : STRING);
C.7.3 Übergabe durch 'wirkliche' Referenz (mit Schreibzugriff)
Als aktueller Parameter kann eine Variable, ein Parameter oder ein
Pointerziel angegeben werden. Die Prozedur erhält die Adresse dieses
Objekts, und darf dieses auch verändern. Die Änderungen bleiben
nach dem Prozeduraufruf erhalten, sind also 'richtige' Änderungen am Objekt.
PROCEDURE ReadString(VAR s : STRING);
C.7.4 Methoden
Hat man mehre Prozeduren, die die gleiche Funktion für verschiedene Typen
erfüllen, ist es unangenehm, jeder Prozedur einen anderen Namen geben zu
müssen. Daher besteht die Möglichkeit, beliebig viele Methoden mit dem
gleichen Namen zu definieren, wenn Sie einen RECORD oder einen Zeiger auf
einen RECORD als ersten Parameter erwarten.
Hier einige Beispiele aus dem Modul DosSupport:
METHOD Get(VAR data : FileData;
REF path : STRING)
METHOD Get(VAR list : DirList;
REF path : STRING;
REF pattern : STRING:="#?";
type := DirSelectType:{selectDirs,selectFiles};
context : ContextPtr := NIL);
Diese Methoden kann man nicht direkt importieren und aufrufen, sondern
werden qualifiziert über eine Variable des Typs des ersten Parameters
aufgerufen z.B.:
VAR
Dir : DirList;
FileInfo : FileData;
BEGIN
FileInfo.Get("s:startup-sequence");
Dir.Get("DH0:");
C7.5 Funktions-Prozeduren
Hat eine Prozedur oder Methode einen Rückgabewert, bezeichnet man sie als
Funktion. Am Ende der Prozedur wird dieser mit RETURN zurückgegeben (siehe
6.8). Handelt es sich bei dem Wert, der zurückgegeben werden soll, um einen
komplexen Typen (Array/Record), so weist man diesen der Variablen RESULT
zu, und verläßt die Funktion durch RETURN, ohne danach den Wert anzugeben.
Handelt es sich bei dem Rückgabetypen um einen offenen Typen
(String/Offenes Array), so muß man vor der Prozedurdeklaration den Schalter
$$OwnHeap:=TRUE setzen (bei Prozeduren, die exportiert werden, genügt es,
dies im Definitionsteil zu machen.). Außerdem muß man dafür sorgen, daß im
Falle, daß das Ergebnis an einen VAR/REF-Parameter übergeben wird,
außreichend Platz auf dem Stack alloziert wird, um das Ergebnis dort
zwischenzulagern. Hierzu dient die Standartprozedur
ALLOC_RESULT(range : LONGINT);
Der Parameter range gibt dabei den Bereich des Arrays/Strings an, für das
Platz alloziert werden soll. Denken Sie dabei daran, daß Sie bei Strings
range = Maximallänge+1 wählen, um Platz für das 0-Byte zu haben.
Die zwei Bytes für die Länge bei einem String alloziert ALLOC_RESULT
selbstständig, wenn es sich bei dem Rückgabetypen um einen String handelt.
C.8 Moduldeklaration
Es gibt drei Arten von Modulen: Programmodule, Definitionsmodule und
Implementationsmodule. Immer ein Definitionsmodul gehöhrt zu einem
Implementationsmodul, in ihm werden die Schnittstellen zu anderen Modulen
definiert.
$$ import ::= (FROM qualident[AS ident] IMPORT ident{","ident}";")|
(IMPORT qualident[AS ident]{","qualident AS ident}";")
$$ modulebody ::= {import|typedef|vardeclaration|constdef|proceduredeclar|
defmodule|implementmod}
[BEGIN statementsequence]
[CLOSE statementsequence]
$$ implementmod ::= IMPLEMENTATION MODULE ident ";"
modulebody END ident
$$ mainmodule ::= MODULE ident ";" modulebody END ident
$$ defmodule ::= DEFINITION MODULE ident
[("("ident : typedef")")|
("=" qualident "(" typedef ")")]
{import|typedef|vardeclaration|constdef|procedureheader|
defmodule} END ident
$$ module ::= mainmodule|implementmod|defmodule "."
Durch die Importklausel, können Elemente anderer Module im eigenen Modul
verwendet werden. Es ist sowohl ein Qualifizierender als auch
Unqualifizerender Import Möglich.
Beispiel:
Durch den Import
FROM InOut IMPORT WriteInt,Read;
werden dem Programm die Bezeichner "WriteInt" und "Read" direkt zur
Verfügung gestellt, aber auch der Bezeichner "InOut", so daß auch über
"InOut.xxx" auf andere Bezeichner des Objektes zugegriffen werden kann.
C.8.1 Generische Module
Viele Datenstrukturen und dazugehörige Algorithmen werden häufig in
verschiedenen Kontexten und in Verbindung mit anderen Datenstrukturen
benötigt (z.B. Listen, AVLBäume, Stacks etc.).
In einer streng und vor allem statisch getypten Sprache tritt das Problem
auf, daß diese Strukturen jedesmal neu definiert und implementiert werden
müssen. Diesem Problem wird durch Generizität abgeholfen.
Ein generisches Modul besitzt einen generischen Parameter. Dieser Parameter
ist ein Zeigertyp, über den das Modul auch bereits Annahmen machen kann.
Ein derartiges Modul muß vor seiner Verwendung durch ein anderes Modul
durch einen aktuellen Parameter ausgeprägt werden. Dieser Typ muß zum
formalen Parametertyp passen.
Beispiel:
DEFINITION MODULE BiList(NodePtr : POINTER TO Node);
TYPE
Node = RECORD
pred,
succ : NodePtr
END;
List = RECORD
first,
last : NodePtr
END;
ApplyProc = PROCEDURE(n : NodePtr);
PROCEDURE Init(VAR l : List);
PROCEDURE InsertTop(VAR l : List;n : NodePtr)
PROCEDURE Apply(VAR l : List;apply : ApplyProc);
END BiList;
IMPLEMENTATION MODULE BiList;
PROCEDURE Init(VAR l : List);
BEGIN
l.first:=NIL;
l.last :=NIL
END Init;
PROCEDURE InsertTop(VAR l : List;n : NodePtr);
BEGIN
n.pred:=NIL;
n.succ:=l.first;
IF l.first=NIL THEN
l.last:=n
ELSE
l.first.pred:=n
END;
l.first:=n
END InsertTop
PROCEDURE Apply(VAR l : List;apply : ApplyProc);
VAR n : NodePtr;
BEGIN
n:=l.first;
WHILE n#NIL DO
apply(n);n:=n.next
END;
END Apply;
END BiList;
Das Implementations-Modul kann über die bekannten Elemente des Knotentyps
(pred,succ) verfügen, da durch die Definition des generischen Parameters
sichergestellt wird, daß nur ein Zeiger auf einen Nachfolger von Node in
Frage kommt.
TYPE
NamePtr = POINTER TO NameNode;
DEFINITION MODULE NameList = BiList(NamePtr);
TYPE
NameNode = RECORD OF NameList.Node
name,
vorname : STRING(100);
alter : INTEGER;
END;
Das Modul NameList verwaltet nun eine Liste derartiger NameNodes. Die
Typsicherheit ist voll gegeben, da keine anderen Knotentypen zugelassen
sind.
VAR
MyNames : NameList.List;
name : NameNode;
Die Prozeduren des Modules NameList können wie üblich importiert und
benutzt werden. Werden jedoch mehrere Ausprägungen des selben generischen
Moduls verwendet, treten Namenskonflikte auf. Diese könnten durch ständiges
qualifizieren umgangen werden, was jedoch sehr aufwendig und fehleranfällig
ist.
NameList.Init(MyNames);
New(name);
NameList.InsertTop(MyNames,name);
Um dies zu umgehen kann eine Prozedur aus einem generischen Modul auch auf
eine andere Art aufgerufen werden:
MyNames.Init;
New(name);
MyNames.InsertTop(name);
Für alle Ausprägungen eines generischen Moduls wird in einem Programm der
selbe Code benutzt, es wird also kein Code vervielfältigt. Aus diesem
Grund können auch nur Zeiger als generische Parameter dienen.
Werden für eine Implementation einer Datenstruktur gewisse Fähigkeiten des
generischen Parameters (wie eine Ordnungsrelation) benötigt, so kann dies
über Prozedurvariablen erreicht werden.
Es ist möglich, lokale Prozeduren als Parameter zu verwenden; es ist
allerdings nicht möglich, einer Prozedurvariablen eine lokale Prozedur
zuzuweisen.
PROCEDURE LasseAltern(VAR l : NameList.List;um : INTEGER);
PROCEDURE altere(n : NodePtr);
BEGIN
INC(n.alter,um)
END altere;
BEGIN
l.Parse(altere);
END LasseAltern;
C.9 Compilerswitches
Compiler-Switches (Schalter) können verwendet werden, um Teile eines Moduls
bedingt zu compilieren. Ein Compilerswitch wird durch $$ eingeleitet:
$$xxx:=yyy
Setzt den Switch xxx auf den Ausdruck yyy
Bsp:
$$RangeChk:=FALSE
Die Zustände der stapelbaren Schalter können durch
$$xxx:=OLD
wieder in ihren vorherigen Zustand zurückgesetzt werden, die maximale
Schachtelungstiefe jedes Schalters ist 16.
Zu jedem Projekt können bis zu 24 Schalter selbstdefiniert werden, dies
gschieht im Project-Requester (nicht das Verzeichnis Cluster:Projects
vergessen). Die Schalter können global, im Info Requester oder im Quelltext
gesetzt/gelöscht werden. Sie können zur bedingten Compilierung eingesetzt
werden.
Bsp: ein benutzerdefinierter Schalter 'Turbo' für eine 020/881 Version:
$$IF Turbo THEN
$$MC68020:=TRUE
$$MC68881:=TRUE
$$END
MODULE ...;
$$IF Turbo THEN
TYPE Float = REAL;
IMPORT VectorReal AS Vectors;
$$ELSE
TYPE Float = FFP
IMPORT VectorFFP AS Vectors;
$$END
...
Je nach dem, ob 'Turbo' gesetzt ist, wird ein anderes Programm compiliert.
Folgende Swicthes sind vordefiniert:
Name: Kurz: Geltung: Init: Bedeutung:
----------------------------------------------------------------------------
OverflowChk V Bereich Info Überlauf bei Rechenoperationen
RangeChk R " " Bereichsprüfung bei Zuweisungen
und Indizierungen
StackChk S " " Stacküberprüfung an
(bei Prozedur/Modulanfang)
StrZeroChk Z " " Test bei Stringzuweisungen, ob
Str.data[Str.len]=&0
NilChk N " " Test bei Dereferenzierungen, ob
der Zeiger NIL enthält
ReturnChk " " Test, ob eine Funktion einen Wert
mit RETURN zurückliefert
LocalChk L " " Test, ob eine Prozedur, die an
eine Variable zugewiesen wird,
eine globale Prozedur ist
ConstChk " TRUE Der Compiler verhindert, daß
Konstanten oder REF-Parameter
verändert werden. Dieser Switch hat
nur Auswirkungen wärend der
Compilierung, er hat keine
Auswirkung auf den erzeugten Code.
RelaxPtr " FALSE Erlaubt bei Konstantendefinitionen
die 'PTR wegzulassen, wenn ein
Zeiger statt der Struktur erwartet
wird.
ChipMem C Modul Info Das Modul wird ins ChipMem gelegt
LongAlign A " " Variablen und Elemente von
Strukturen, die länger als 3 Bytes
sind, werden auf Langwortgrenzen
gelegt
Library Modul " Das Modul soll in einer Library
verwendet werden
Debug " " Das Modul wird mit Debug-Info
compiliert.
WithArea W Struktur TRUE Bei einer WITH Anweisung, lokalen
Variablen oder Parametern einer
Prozedur wird ein Sicherungsbereich
eingerichtet. Dieser ist nötig,
falls innerhalb der Struktur eine
Prozedur aufgerufen wird. Wird
innerhalb der Anweisung keine
Prozedur benützt, sollte (* $W- *)
davor gesetzt werde. Der Compiler
meldet einen Fehler, wenn doch
eine Prozedur aufgerufen wird.
WithModify M " " Nach einer WITH Anweisung wird der
Wert des Registers (falls er
verändert wurde) wieder in die
Variable gesichert, (* $M- *)
unterbindet dies, falls das
Ergebnis nicht mehr benötigt wird.
OwnHeap O Prozedur FALSE Funktionen, die einen offenen
Rückgabetyp haben, müssen sich in
einigen Situationen selbst um die
Allozierung des Rückgabe-Stacks
kümmern (siehe "Strings"). Dieser
Schalter zeigt an, daß die Prozedur
sich darum kümmert, und auch in
diesen Situationen benutzt werden
kann.
PushRegs P " " Sichert alle Register am
Prozeduranfang. Diese Prozedur
kann dann auch innerhalb einer
WITH Struktur benutzt werden, ohne
daß die Registervariablen gesichert
werden müssten.
EntryCode E " TRUE Die Prozedur hat keinen Eingangs-
code. Darf nur verwendet werden,
wenn keine lokalen Parameter auf
dem Stack benötigt werden (hat
dann aber auch wenig Sinn ???)
ModulaII Bereich Info Es gelten die Syntaxregeln von
Modula II
MC68020 " " Es wird Code für den 68020++
erzeugt
MC68881 " " Es wird Code für den 68881++
erzeugt
AssTypeChk " FALSE Der Assembler führt einen Typecheck
durch
ComplexAss " TRUE Der Assembler erlaubt auch
komplexere Adressierungsarten