
 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

