4.1. Für Modula-II Programmierer Cluster ist eine mit Modula-II verwandte Programiersprache. Unterschiede ergeben sich bei offenen Typen, sowie WITH, IF, CASE und WHILE Strukturen, sowie bei der Verwendung eines CLOSE-Teils. Cluster verfügt bei den numerischen Typen zusätzlich noch über die Typen SHORTCARD (0..255) und SHORTINT (-128..127). Der Typ ADDRESS ist durch den Typen ANYPTR ersetzt. Der Typ ARRAY OF BYTE ist durch ANYTYPE ersetzt. Neu ist der Typ STRING für Zeichenketten. In Cluster ist eine Zeichenkette nicht durch CHAR(0) abgeschlossen. Strings haben ein Feld, in dem die aktuelle Länge des selben steht. VAR Str : STRING(32); Definiert eine Variable Str, als String mit maximal 32 Elementen. Str.len ist die Länge des Strings und Str.data[i] ist das i. Zeichen des Strings. entspricht in etwa: Str : RECORD len : INTEGER; data : ARRAY [0..31] OF CHAR END; Es können allerdings auch Strings verschiedener Längen und Maximallängen einander zugewiessen werden. Zur Übergabe an Prozeduren gibt es den Typ STRING allein. Dies ist ein offener String. Diese Strings können nur als VAR- Parameter verwendet werden. PROCEDURE First(VAR Str : STRING;ch : CHAR):INTEGER; VAR i : INTEGER; BEGIN FOR i:=0 TO Str.len-1 DO IF ch=Str.data[i] THEN RETURN i END; END; RETURN -1 END First; Funktionen, die in Modula Informationen über einen Typ oder eine Variable liefern, sind in Cluster Attribute. Sie werden nach einen ` an den Namen angehängt. Folgende Attribute sind möglich: SIZE : Größe eines Typs in Bytes RANGE : Anzahl der Element eines Arrays MIN : Kleinstes Element eines einfachen Typs, oder kleinster Index eines Arrays MAX : Größtes Element eines einfachen Typs, oder größter Index eines Arrays PTR : Zeiger auf das Objekt, ist vom Typ POINTER TO ...; ADR : Addresse des Objekts entspricht ADR(...) TYPE Array = ARRAY [7..22] OF INTEGER; VAR a : Array; Dann ist a´MIN = Array´MIN = 7 a´MAX = Array´MAX = 22 a´RANGE = Array´RANGE = 16 OF ist auch als einfache Relation möglich: IF a OF 3,4,10..20,25 THEN... es ist auch möglich, im rechten Teil der OF-Relation Ausdrücke und Variablen anzugeben. IF (x OF links..links+breite) AND (y OF oben..oben+hoehe) THEN... Neue Operatoren für ganze Zahlen sind SHL, linkschieben und SHR, rechtsschieben. Das Schlüsselwort ELSIF heist OR_IF, und es existiert ein Ähnliches Element in der WHILE Strunktur, OR_WHILE. WHILE ... DO ... OR_WHILE ... DO ... END Es wird immer der erste Anweissungsteil ausgeführt, dessen Bedingung wahr ist. Dann wird wieder oben beim ersten WHILE fortgefahren. Die CASE-Struktur existiert nicht mehr, sie ist Element des IFs geworden. Soll eine Fallunterscheidung getroffen werden, geschieht dies mit dem Schlüsselwort KEY. IF KEY Ausdruck OF Bereichsliste THEN .... END OF Bereichsliste THEN .... END OF .. OR_IF .... KEY kann auch hinter OR_IF stehen, oder bei WHILE verwendet werden. Neben den disjunktiven Formen OR_IF, OR_WHILE gibt es auch konjunktive AND_IF, AND_WHILE. Sie können an stelle eines DO oder THEN stehen, und schränken die Aussage weiter ein. Ein AND_.. kann auch über OR_... oder einen ELSE-Teil verfügen. War in einer Bedingungskaskade mit OR_.. kein Fall war, wird der ELSE-Fall ausgeführt, sonst wird der nächste untergeordnete Fall geprüft. FROM Strings IMPORT Compare,equation; TYPE Tree = POINTER TO Node; Node = RECORD left, right : Tree; data : STRING(32); END; PROCEDURE Insert(VAR tree : Tree;elem : Tree):BOOLEAN; VAR p : Tree; BEGIN IF tree=NIL THEN tree:=elem ELSE p:=tree; WHILE KEY Compare(p^.data,elem^.data) OF smaller AND_WHILE p^.left#NIL DO p:=p^.left ELSE p^.left:=Elem END OF greater AND_WHILE p^.right#NIL DO p:=p^.right ELSE p^.right:=Elem END OF equal DO RETURN FALSE END END; END; RETURN TRUE END Insert; Bei Imports ist es möglich, den importierten Modulen einen neuen Namen zu geben. IMPORT InOut AS io; oder FROM InOut AS io IMPORT WriteString; Für REALs und LONGREALs gibt es den Operator ^, Potenzierung, und eine große Zahl Standartfunktionen: SIN(REAL) : REAL : Sinus im Bogenmaß COS(REAL) : REAL : Cosinus TAN(REAL) : REAL : Tangens ASIN(REAL) : REAL : Arcussinus im Bogenmaß ACOS(REAL) : REAL : Arcuscosinus ATAN(REAL) : REAL : Arcustangens SINH(REAL) : REAL : Sinushyperbolicus COSH(REAL) : REAL : Cosinushyperbolicus TANH(REAL) : REAL : Tangenshyperbolicus SQRT(REAL) : REAL : Quadratwurzel LN(REAL) : REAL : natürlicher Logarithmus LOG(REAL) : REAL : dekadischer Lograithmus EXP(REAL) : REAL : e^x CEIL(REAL) : REAL : aufrunden FLOOR(REAL) : REAL : abrunden ABS(REAL) : REAL : Betrag Die WITH-Anweissung hat einen große Änderung erfahren. Sie funktioniert nun auch mit Arrays und einfachen Elementen. Allerdings qualifiziert sie Recordelemente nicht mehr, da dies zu Problemen führt (wenn z.B. zwei Records des selben Typs geWitht werden). Allerdings ist es möglich den geWithen Strukturen einen neuen (kürzeren) Namen zu geben. WITH Variablenausdruck [AS NeuerName], ... Variablenausdruck [AS NeuerName] DO ... END Beispiel, wir haben eine komplexe Zugriffstruktur und müssen mit zwei Elementen daraus Arbeiten: VAR data : ARRAY [0..255] OF POINTER TO ARRAY [0..255] OF RECORD x,y,z : INTEGER END i1,i2, j1,j2 : INTEGER; Wir wollen nun in das Element i1,i2 das inverse des Elements j1,j2 schreiben. ... WITH data[i1]^[i2] AS dest, data[j1]^[j2] AS source DO dest.x:=-source.x; dest.y:=-source.y; dest.z:=-source.z END; ... Bei Funktionen gibt es die Möglichkeit, auch komplexe Typen wie ARRAYs oder RECORDs zurückzugeben. Dies geschieht aber im Gegensatz zu einfachen Typen nicht mit RETURN, sondern mit einer Pseudovariablen RESULT, da ja nicht einfach ein Ausdruck zurückgegeben werden kann, sondern das Ergebniss zusammengesetzt werden muß. PROCEDURE Seg(VAR Str : STRING;first,len : INTEGER):STRING; VAR i : INTEGER; BEGIN FOR i:=0 TO len-1 DO RESULT.data[i]:=Str.data[first+i] END; RESULT.len:=len END Seg; VAR s : STRING(30); ... s:=Seg("Dies ist ein Test",5,3); ... Danach stünde in s dann "ist". 4.2. Für Basic Programmierer Dieses Kapitel richtet sich an Leute, die bereits einigermaßen in Basic programmieren können. Fühlen Sie sich in Basic nicht sicher, sollten Sie vielleicht doch Kapitel 3 lesen. 4.2.1. Der Vorteil eines Compilers und einer strukturierten Programmierung Wenn Sie mit AmigaBasic programmieren, werden Sie sicher relativ bald feststellen, daß ihre Programme einiges langsamer sind, als Programme die Sie kaufen. Dies liegt daran, daß Basicprogramme nicht in der Form vorliegen, wie sie der Amiga braucht, sondern von einem anderen Programm während der Laufzeit erst übersetzt wird. Dies entspricht etwa jemandem, der versucht, einen Text einer fremden Sprache zu lesen, indem er jedes Wort im Wörterbuch nachschlägt. Wesentlich besser ist es doch, den Text von einem Dolmetscher übersetzen zu lassen, um ihn dann zu lesen. Besonders wenn es sich um Texte handelt, die oft gelesen werden müssen. Diese Übersetzungsaufgabe erfüllt ein Compiler. Er beansprucht etwas zeit, bevor ein Programm laufen kann. Das Programm selber läuft dann aber wesentlich schneller, und da das übersetzte Programm auch gespeichert werden kann, benötigt man zu Ausführung auch kein weiteres Programm. Es gibt zwar auch Compiler für Basic, doch ist diese Sprache nur sehr bedingt dazu geeignet, übersetzt zu werden, und der Gewinn ist gering. Ein weiterer Nachteil von Basic ist, daß alle Anweissungen die man verwenden kann zur Sprache gehören. Dies hat zur Folge, daß man keine eigenen neuen Anweissungen beifügen kann, aber eine riesige Menge von Befehlen hat, die man sich kaum merken kann. Cluster geht einen anderen Weg, die Anzahl von Anweissungen und Strukturen in Cluster ist relativ gering, dafür besteht aber die Möglichkeit, beliebig viele eigene und fremde Befehle zu importieren und zu verwenden. 4.2.2. Grundlegende Programmstruktur Cluster ist im Gegensatz zu Basic eine formatfreie Sprache, das heist die äusere Form eines Programms hat keinen Einfluß auf den Inhalt. So ist es im Gegensatz zu Basic möglich, daß sich eine Anweissung über mehrere Zeilen erstreckt. Kommentare werden durch (* eingeleitet und mit *) beendet. Sie können sich auch über mehrere Zeilen erstrecken, und geschachtelt werden. Anweissungen werden voneinander durch ein Strichpunkt getrennt, ein Zeilenende ist kein Anweissungsende. Alle Schlüsselwörter der Sprache werden groß geschrieben. Bei Bezeichnern (Namen) gelten die selben Regeln wie in AmigaBasic, mit der Ausnahme, daß große Buchstaben von kleinen als verschieden betrachtet werden. Ein Programm hat folgenden groben Aufbau: MODULE Namen; (* Deklarationsteil *) BEGIN (* Implementationsteil *) CLOSE END Namen. Alle Variablen müssen im Gegensatz zu Basic vor ihrer Verwendung deklariert werden. Dies verhindert eine fehlerhafte Benutzung. Das eigentliche Programm, beginnt erst hinter dem Schlüsselwort BEGIN, davor werden lediglich Vereinbarungen getroffen. Da Cluster eine Single-Pass-Sprache ist (der Compiler geht bei der Übersetzung nur einmal über den Text), müssen alle Objekt (Variablen etc.) bereits deklariert sein, bevor sie benutzt werden können. 4.2.2. Die Verwendung von Typen Als Basic-Programmierer sind sie schon in Kontakt getreten mit Typen. Sie kennen Ganzzahlen, Realzahlen, Strings und Arrays. Doch dieses Typkonzept ist primitiv im Vergleich zu einer strukturierten Sprache wie Cluster. Es ist in Cluster sogar möglich, eigene Typen zu definieren. Der Typ einer Variable wird nicht durch ein Anhängsel an den Namen wie "$","%","#" o.ä. gegeben, sondern bei der Deklaration einer Variablen angegeben. Typen in Cluster haben Namen, mit denen sie bezeichnet werden. Definiert man eigene Typen, so geschieht dies mit dem Schlüsselwort "TYPE". Danach kommen jeweils Namen und Typdeklaration, getrennt durch ein Gleichzeichen. TYPE Namen = Typdeklaration; ... Namen = Typdeklaration; Man unterscheidet einfache und komplexe Typen. Einfache Typen sind Zahlen, Zeichen oder ähnliches. Zu den komplexen Typen gehören zum Beispiel die Arrays. Es gibt in Cluster eine Reihe von Standarttypen. Die einfachsten sind die ganzen Zahlen, von denen es Vorzeichenbehaftete und Vorzeichenfreie gibt. SHORTINT : -128..127 INTEGER : -32768..32767 LONGINT : -2147483648..2147483647 SHORTCARD : 0..255 CARDINAL : 0..65535 LONGCARD : 0..4294967295 Man kann auch Integertypen definieren, die einen anderen Zahlenbereich umschliessen. Dies ist sinnvoll, da bei einer Zuweissung geprüft wird, ob ein Wert für einen Typen erlaubt ist. Dazu wird als Typdeklaration die obere und untere Grenze angegeben. Diese werden in eckige Klammern eingschlossen, und durch ".." getrennt. TYPE Ziffern = [0..9]; Dieser Typ kann Zahlen im Bereich von 0 bis 9 aufnehmen. Eng verwandt mit diesen Typen ist der Typ CHAR. Er kann ein einzelnes ASCII-Zeichen aufnehmen. Neben ganzen Zahlen verfügt Cluster wie Basic auch noch über gebrochene Zahlen. Dies sind REAL und LONGREAL, wobei LONGREALs eine wesentlich größere Genauigkeit besitzen als REAL Zahlen. In Cluster gibt es im Gegensatz zu Basic den Datentyp BOOLEAN. Eine Variable dieses Typs kann zwei Zustände einnehmen, wahr (TRUE) oder falsch (FALSE). Zum Beispiel ist das Ergebniss eines Vergleichs immer vom Typ BOOLEAN. Neben diesen Standarttypen kann man sich in Cluster auch eigene Typen zusammenbauen. Kann eine Variable eine Anzahl von Zuständen annehmen, die nicht durch Zahlen sodern durch Symbole (Namen) gegeben sein sollen, erzeugt man einen Unterbereichstyp: TYPE Monat = (Januar,Februar,Maerz,April,Mai,Juni, Juli,August,September,Oktober,November,Dezember); Variablen des Typs Monat können nun die Werte Januar,Februar.. annehmen. Auch aus einem Aufzählungstyp kann man wieder einen Unterbereichstypen bilden. TYPE Sommer = [Juli..September]; Von Basic sind ihnen ja Arrays bereits bekannt. In Cluster dürfen Arrays auch bei anderen Werten wie 0 beginnen, und müssen auch nicht unbedingt Zahlen als Index haben, es könnten auch Zeichen oder Elemente eines Aufzählungstyps sein. TYPE Feld = ARRAY [4..20] OF INTEGER; Alphabet = ["A".."Z"]; ZeichenLaenge = ARRAY Alphabet OF INTEGER; Definiert einen Typen mit dem Namen Feld. Er stellt ein Array aus INTEGERs dar, dessen Index, ein Unterbereich, von 4 bis 20 laufen kann. Es ist auch möglich als Indextyp einen anderen Typen als einen Unterbereich, (die Indextypen oben sind Unterbereiche) zu verwenden. Dann ist der Indexbereich der gesamte Wertebereich des Typs also z.B. 0 bis 255 bei SHORTCARD. TYPE Zeichen = ARRAY CHAR OF INTEGER; MonatsLaenge = ARRAY Monat OF SHORTCARD; entspricht : MonatsLaenge2 = ARRAY [Januar..Dezember] OF SHORTCARD; Sei nun mon vom Typ MonatsLaenge, dann wäre mon[Januar] die Länge des Monats Januar. Ein mehrdimensionales Array kann auf zwei Arten definiert werden. TYPE Feld1 = ARRAY [1..10] OF ARRAY [1..10] OF INTEGER; oder Feld2 = ARRAY [1..10],[1..10] OF INTEGER; Man kann auch ein Feld aus anderen Felder definieren: TYPE Feld3 = ARRAY [1..10] OF Feld2; Man kann Felder gleichen Typs direkt einander zuweisen. Um auf ein Feld eines Arrays zu kommen, verfährt man wie in Basic, nur wird der Index in eckigen Klammern angegeben. Sei z.B. feld vom Typ Feld3, so könnte man auf das Element 3,4,2 so kommen: feld[3][4][2] oder feld[3,4,2] In Cluster gibt es auch Strings. Diese sind ähnlich strukturiert wie Arrays. Sie enthalten ein ARRAY .. OF CHAR sowie eine Integerzahl, in der die Länge des Strings steht. Bei der Definition eines Strings muß dessen maximale Länge angegeben werden, damit der Compiler entsprechend Platz vorbelegen kann. TYPE Str32 = STRING(32); Definiert einen Typ Str32, mit maximal 32 Zeichen. Sei s ein String dieses Typs, dann ist s.len die Länge (entspricht in Basic LEN(s)), und s.data[i] das i. Zeichen des Strings. Für Strings gibt es viele Funktionen, ähnlich wie in Basic. Man kann auch Strings verschiedener Länge einander zuweisen. In einem Array werden mehrere Elemente des selben Typs zu einem neuen zusammengefügt, und über eine Nummer angesprochen. Ein anderer Typkonstruktor, der RECORD erlaubt es mehrere Elemente verschiedener Typen zu einem zusammenzufassen, und über ihre Namen anzusprechen. TYPE Adresse = RECORD name, vorname : STRING(32); plz : LONGCARD; END; Sei add eine Variable des Typs Adresse. Dann ist add.name der Namen, add.vorname der Vornamen und add.plz die Postleitzahl. Man kann sowohl einzelnen Elementen eines Records Daten zuweisen, als auch zwei Records des selben Typs sich gegenseitig. Records dienen dazu, Informationsteile zusammenzufassen. Man kann auch wieder Arrays von Records bilden etc. TYPE Adresskartei = ARRAY [0..200] OF Adresse; Sei kartei eine Variable des Typs Adresskartei. Um nun den Namen der 12. Person zu erhalten muß man kartei[12].namen schreiben. Eine weitere interessante Struktur ist die Menge, SET. Ein Mengetyp besteht aus einer Anzahl von Elementen, die in Variablen dieses Typs entweder enthalten oder nicht enthalten sind. Man kann zwei Mengen schneiden, vereinigen, ein Element einfügen etc.. Der Basistyp eines Sets ist ein Aufzählungs- oder Unterberreichstyp. Die maximale Zahl der möglichen Elemente eines Sets ist 32. TYPE MonatSet = SET OF Monate; Angenommen sommer und winter seien Variablen dieses Typs, sommer enthalte die Elemente Juli, August und September, und winter enthalte die Elemente Dezember, Januar und Februar, dann ist die Vereinigung von sommer und winter (sommer+winter) eine Menge mit den Elementen Januar, Februar, Juli, August, September und Dezember. Ein Typ, der absolut nicht in Basic enthalten ist, aber ein wichtiger Bestandteil der Sprache Cluster ist, ist der POINTER. Ein Pointer (Zeiger) zeigt auf eine Variable im Speicher. Er enthält keine Information, er enthält lediglich die Adresse der Datenstruktur. Näheres finden sie in Kapitel 5. Folgende Typen sind zueinander Rechnungskompatibel: - SHORTINT, INTEGER, LONGINT, negative Unterberreiche - SHORTCARD, CARDINAL, LONGCARD, positive Unterberreiche - REAL, LONGREAL - sowie Sets des gleichen Typs Folgende Typen sind zuweisungskompatibel: - SHORTINT, INTEGER, LONGINT, SHORTCARD, CARDINAL, LONGCARD, Unterbereiche - REAL, LONGREAL - STRINGS beliebiger Länge - POINTER mit gleichen Zieltypen - sowie alle Objekte gleichen Typs zueinander 4.2.3. Grundstrukturen 4.2.3.1. Variablen Im Gegensatz zu Basic müssen in Cluster alle Variablen vor ihrer Verwendng deklariert werden. Dies geschieht mit dem Schlüsselwort VAR. Danach kommen die Namen und Typen der Variablen, getrennt durch einen Doppelpunkt. VAR Namen,Namen,.. : Typdeklaration; ... Namen,Namen,.. : Typdeklaration; Um zum Beispiel eine Variable add des Typs Adresse s.o., und Variablen i,j als Integer zu deklarieren: VAR Add : Adresse; i,j : INTEGER; Ab dieser Deklaration können die Variablen benutzt werden. Am Programmbeginn enthalten alle Variablen einen zufälligen Wert, sind also nicht !!! mit 0 vorbelegt. 4.2.3.2. Konstanten Neben Variablen spielen Konstanten eine große Rolle. In Basic gibt es nur einfache Konstante, Zahlen oder Zeichenketten. In Cluster ist es möglich Konstanten beliebiger Typen zu erzeugen. Dies ist entfernt, aber nur sehr entfernt mit READ-DATA vergleichbar. Konstanten können an der Stelle stehen, wo sie benötigt werden, oder im Deklarationsteil mit einem Namen deklariert und später immer wieder verwendet werden. Numerische und Zeichenkettenkonstanten werden gehandhabt, wie in Basic, mit der Ausnahme, daß Gebrochene Zahlen einen Punkt enthalten sollten. Beispiel für die Deklaration von Konstanten mit Namen: CONST pi = 4.*ATAN(1.); wurzel_2 = 1.414; gamma = 6.672E-11; dosname = "dos.library"; zweiHoch16 = 65536; (* dezimal *) c_256 = $100; (* hex *) c_bitmuster = %1001001001001; (* binär *) Konstante Ausdrücke werden schon bei der Übersetzung errechnet, so daß während der Ausführung keine Zeit damit verloren wird. Komplexe Konstanten werden deklariert, indem man zuerst den Typnamen, dann einen Doppelpunkt und dann den Inhalt der Konstanten schreibt. Ist der Typ ein Array, werden die Elemente in Klammern und durch Komma getrennt angegeben. Bei einem Record werden die Elemente angegeben, indem man den Namen, und dann nach einem Gleichzeichen einen konstanten Ausdruck angibt. Beispiel: TYPE Vektor = ARRAY [0..2] OF REAL; Matrix = ARRAY [0..2] OF Vektor; Kammera = RECORD position : Vektor; richtung : Matrix; brennweite : REAL; END; CONST StdKammera = Kammera:(position=(120,240,200), richtung=((1,0,0), (0,1,0), (0,0,1)), brennweite=1.8); Konstanten eines Mengentyps werden ähnlich definiert, allerdings mit Mengenklammern. TYPE Numset = SET OF [0..31]; CONST Leer = Numset:{}; Voll = Numset:{0..31}; Gerade = Numset:{0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30}; Manche = numset:{3,7..11,15,17,23..30}; 4.2.3.3. Ausdrücke und Zuweissung Wie in Basic bilden auch in Cluster Ausdrücke (mathematische o.ä. Terme) und Zuweissungen das Rückrat der Sprache. Cluster verfügt über folgende Operatoren: Für REALs: + : Addition - : Negation oder Subtraktion * : Multiplikation / : Division ^ : Potenzierung Für INTEGERs + : Addition - : Negation oder Subtraktion * : Multiplikation DIV : Ganzzahldivision MOD : Rest der Division SHL : Linksschieben (entspricht mal zwei), siehe Kapitel 8 SHR : Rechtsschieben (entspricht durch zwei) Für SETs + : Vereinigung * : Schnittmenge - : Mengendifferenz / : Symmetrische Differenz Für BOOLEANs AND : logisches Und OR : logisches Oder NOT : logisches Nicht = : Equivalenz # : Exklusiv oder Neben diesen Operatoren, deren Ergebniss immer von dem Typ ist, in dem auch die Operanden sind, gibt es auch noch Relationen. Eine Relation liefert immer einen Booleanwert zurück Für REALs, POINTER, Aufzählungstypen und INTEGERs = : gleich # : ungleich > : größer >= : gröser oder gleich < : kleiner <= : kleiner oder gleich OF : in Bereichsliste enthalten. Links steht ein Ausdruck, rechts eine Bereichsliste. Eine Bereichsliste besteht aus Werten oder Intervallen, die durch Komma getrennt werden. Beispiel: a OF 3,4,20..40,50..60 Die Elemente können auch selbst Ausdrücke sein: x OF pos-links..pos+rechts Für SETs = : gleich # : ungleich <= : Teilmenge von >= : Obermenge von IN : in Menge enthalten. Links steht das Element, rechts die Menge In Cluster wird Punkt vor Strich berücksichtigt. Es ist möglich durch Klammern dies zu umgehen. Folgende Prioritätsstufen existieren. 1.) ^ : Dereferenzierung (für Pointer) 1.) . : Recordelement 1.) [] : Index bei Arrays 1.) ..() : Funktionsauswertung 1.) ´ : Attribut 2.) - : unäres minus 2.) NOT : complement 2.) () : Klammerausdrücke 3.) ^ : Potenzierung 4.) * : Produkt 4.) / : Division 4.) AND : logisches UND 4.) SHL : linkschieben 4.) SHR : rechtsschieben 4.) DIV : Ganzzahldivision 4.) MOD : Rest der Division 5.) + : Summe 5.) - : Differenz 5.) OR : logisches Oder 6.) Relationen Folgende Standartfunktionen sind vorhanden: SIN(REAL) : REAL : Sinus im Bogenmaß COS(REAL) : REAL : Cosinus TAN(REAL) : REAL : Tangens ASIN(REAL) : REAL : Arcussinus im Bogenmaß ACOS(REAL) : REAL : Arcuscosinus ATAN(REAL) : REAL : Arcustangens SINH(REAL) : REAL : Sinushyperbolicus COSH(REAL) : REAL : Cosinushyperbolicus TANH(REAL) : REAL : Tangenshyperbolicus SQRT(REAL) : REAL : Quadratwurzel LN(REAL) : REAL : natürlicher Logarithmus LOG(REAL) : REAL : dekadischer Lograithmus EXP(REAL) : REAL : e^x CEIL(REAL) : REAL : aufrunden FLOOR(REAL) : REAL : abrunden ABS(REAL) : REAL : Betrag ODD(INT) : BOOLEAN : liefert wahr, wenn INT ungerade ist Machmal ist es nötig, den Typ eines Objektes zu wechseln, um z.B. den ASCII-Wert eines Zeichens zu erhalten oder umgekehrt. Ein Typtransfer kann auf zwei Arten geschehen. CAST(Typ,Object) : Object : Wandelt den Typ des Objects in den neuen Typ um. Dies geht nur, wenn beide Typen die selbe Länge haben. Beispiel: TYPE Vector = RECORD x,y,z : REAL END; Array3 = ARRAY [0..2] OF REAL; VAR v : Vector; ... v.y:=CAST(Array3,v)[1] ... NeuTyp(Object) : Wandelt das Object in den Typ NeuTyp um. Dies geht nur bei einfachen Typen. Hierbei wird nicht nur der Typ geändert, sondern das Objekt selbst umgewandelt. Beispiel: VAR i : INTEGER; ... i:=INTEGER(SIN(REAL(i))*256.) ... Jeder Typ und jedes Objekt hat gewisse Attribute. Diese können mit Objekt´XXX ermittelt werden. Folgende Attribute existieren: SIZE : Größe eines Typs in Bytes RANGE : Anzahl der Element eines Arrays MIN : Kleinstes Element eines einfachen Typs, oder kleinster Index eines Arrays entspricht LBOUND in Basic MAX : Größtes Element eines einfachen Typs, oder größter Index eines Arrays entspricht UBOUND in Basic PTR : Zeiger auf das Objekt ADR : Addresse des Objekts, entspricht entspricht VARPTR in Basic TYPE Array = ARRAY [7..22] OF INTEGER; VAR a : Array; Dann ist a´MIN = Array´MIN = 7 a´MAX = Array´MAX = 22 a´RANGE = Array´RANGE = 16 4.2.4. Strukturen statt Sprünge Wenn Sie schon mit AmigaBasic oder einem anderen modernen Basic- Dialekt gearbeitet haben, kennen Sie bereits Strukturen wie WHILE..WEND oder IF..ELSEIF..ELSE..ENDIF. In Cluster und anderen strukturierten Progrmmiersprachen wird zugunsten derartiger Strukturen völlig auf GOTO verzichtet. Dadurch wird ein Programm wesentlich leichter zu lesen, da man nicht immer irgendwelchen Sprüngen folgen muß. Die IF-Struktur sollte ihnen aus Basic bereits bekannt sein. Da Cluster aber eine formatfreie Sprache ist, ist es egal, ob die nächste Anweissung direkt dem THEN folgt, oder erst in der nächsten Zeile erscheint. IF Boolausdruck THEN Anweissungen OR_IF Boolausdruck THEN Anweissungen ... ELSE Anweissungen END; OR_IF entspricht dem ELSEIF aus Basic. Neben dieser einfachen Form der IF-Bedingung gibt es noch eine komplexere Version, die mit einem Schlüsselwert arbeitet. ... IF KEY Schlüsselausdruck OF Bereichsliste THEN Anweissungen END OF Bereichsliste THEN Anweissungen END ... OR_IF ... ELSE END Bei dieser Version wird der Schlüsselausdruck zu Begin errechnet, und dann mit allen Bereichslisten verglichen. Paßt er in eine, werden die zugehörigen Anweisungen ausgeführt, und danach die IF-Struktur verlassen. Mit der IF-Struktur verwandt, und von Basic bekannt, aber mächtiger als dort, ist die WHILE-Struktur. Sie verfügt wie die IF-Anweissung über OR_WHILE Einschübe, und kann auch mit Schlüsselwerten benutzt werden. WHILE BoolAusdruck DO Anweissung OR_WHILE BoolAusdruck DO Anweissung ... ELSE Anweissung END Zuerst wird die oberste Bedingung geprüft. Ist sie wahr, wird der zugehörige Anweissungsblock ausgeführt, und wieder zum WHILE zurückgesprungen. War sie falsch, wir die nächste Bedingung geprüft. War diese richtig, wird ihr Anweissungsteil ausgeführt, und dann wieder zum oberen WHILE zurückgesprungen. War keine OR_WHILE Bedingung wahr, wird der ELSE-Teil ausgeführt, und die Schleife verlassen. Neben dieser Schleife, die ihre Abbruchsbedingung am Anfang prüft, gibt es auch eine, die ihre Bedingung erst am Schluß prüft. Sie bricht ab, wenn diese Bedingung wahr ist. REPEAT Anweissungen UNTIL Boolausdruck; enstpricht etwa in Basic: 100 Anweissungen 110 IF NOT Boolausdruck THEN GOTO 100 Wie in Basic gibt es auch in Cluster eine FOR-Schleife. Sie hat eine leicht veränderte Struktur, auch muß die Schrittweite eine Konstante sein. FOR Intvariable:=Start TO Ende [BY Konstante] DO Anweissungen END; Außer diesen Schleifen mit klarer Abbruchbedingung gibt es noch eine, deren Abbruch durch einen Befehl im Programm geschehen muß. LOOP Anweissungen; IF ... THEN EXIT END; Anweissungen END; Entspricht etwa 100 Anweissungen 110 IF ... THEN GOTO 140 120 Anweissungen 130 GOTO 100 140 ... Da ihre Abbruchsbedingung nicht so einfach zu erkennen ist, sollte auf sie wann immer möglich verzichtet werden. Im gesammten Cluster-Compiler ist keine einzige !!! Neben diesen Strukturen, die den Programmfluß steuern, gibt es auch einen Struktur, die dem Programmierer die Arbeit erleichtern soll. Häufig kommt es vor, daß man Objekte bearbeiten muß, auf die nur über komplexe Indizierungen zugegriffen werden kann. Mit der WITH-Struktur ist es möglich einem derartigen Objekt zeitweilig einen eigenen Namen zu geben, über den darauf zugegriffen werden kann. WITH Varausdruck [AS NeuerNamen], ... Varausdruck [AS NeuerNamen] DO ... END; Beispiel: TYPE Reihe = [1..8]; Spalte = ["a".."z"]; SchachFigur = (Leer,Bauer,Springer,Lauefer,Turm,Koenig,Dame); SchachKoord = RECORD reihe : Reihe; spalte : Spalte; END; SchachFeld = RECORD figur : SchachFigur; weiss : BOOLEAN; nachbarn : ARRAY [-1..1], [-1..1] OF SchachKoord; END; VAR SchachBrett : ARRAY Spalte,Reihe OF SchachFeld; W_Dame : SchachKoord; Um nun die Dame ein Feld vorzurücken, ist ein ziemlicher Aufwand nötig, der durch die WITH-Struktur vereinfacht werden kann. ... WITH SchachBrett[W_Dame.spalte,W_Dame.reihe] AS feld DO feld.figur:=Leer; W_Dame:=feld.nachbarn[0,-1]; END; WITH SchachBrett[W_Dame.spalte,W_Dame.reihe] AS feld DO feld.figur:=Dame; feld.weiss:=TRUE; END; ... 4.2.5. Prozeduren statt GOSUB Im Standartbasic gibt es ein rudimentäres Unterprogrammkonzept, das mit GOSUB-RETURN verwirklicht wird. In AmigaBasic und anderen modernen Dialekten gibt es bereits weitreichendere Unterprogrammtechniken. Unterprogramme in Cluster heißen Prozeduren (PROCEDURE) und sind mächtige Verwandte der eher schwachen SUB..END SUB Strukturen von Basic. Prozeduren müssen deklariert werden, bevor sie benutzt werden können. Dies geschieht mit dem Schlüsselwort PROCEDURE. PROCEDURE Namen[(Parameterliste)]; (* Deklarationsteil *) BEGIN (* Implementationsteil *) END Namen; Im Deklarationsteil stehen alle Deklarationen des Unterprogramms. Dies können Type, Variablen, Konstanten und sogar weitere Prozeduren sein. Diese Deklarationen sind nur im Unterprogramm bekannt, nicht jedoch außerhalb. Alle außen bekannten Deklarationen sind aber innerhalb bekannt, sofern sie nicht durch neue Namen überdeckt werden. In der Parameterliste stehen die Parameter die an das Unterprogramm übergeben werden sollen. Es gibt zwei Arten von Parametern, solche bei denen nur ein Wert übergeben wird, und solche die eine Variable als Parameter erwarten. Die Liste hat folgendes Aussehen, wobei "VAR" bedeutet, daß der Parameter eine Variable sein muß. ([VAR] Namen,Namen.. : Typbezeichner;[VAR] Namen,...); Es können beliebig viele Parameter an eine Prozedur übergeben werden. Es liegt, da Cluster formatfrei ist, auch keine Beschränkung durch die Zeilenlänge vor. Die Parameter wirken in der Prozedur als wären sie initialisierte Variablen. Alle anderen Variablen sind beim Prozeduraufruf als undefiniert zu betrachten, und verlieren ihren Inhalt, wenn die Prozedur wieder verlassen wird. Beispiel, vertauschen zweier INTEGER-Variableninhalte; PROCEDURE Swap(VAR x,y : INTEGER); VAR help : INTEGER; BEGIN help:=x;x:=y;y:=help END Swap; Der Aufruf einer Prozedur erfolgt über den Namen und die zugehörige Parameterliste: VAR i,j : INTEGER; ... Swap(i,j); ... Cluster verfügt über eine Reihe Standartprozeduren: INC(m) : Erhöht eine Ganzzahl, Zeichen oder Zeiger um eins INC(m,n) : Erhöht m um n DEC(m) : Vermindert eine Ganzzahl, Zeichen oder Zeiegr um eins DEC(m,n) : Vermindert m um n INCL(s,e) : Fügt Element e in die Menge s ein EXCL(s,e) : Entfernt das Element e aus der Menge s FLIP(s,e) : Invertiert das Vorhandensein des Elements e in der Menge s UNI(s1,s2) : Vereinigt die Menge s1 mit der Menge s2 nach s1 SEC(s1,s2) : Schneidet die Menge s1 mit der Menge s2 nach s1 HALT(num) : Bricht das Programm ab, mit Fehlermeldung num Neben Prozeduren kann man in Cluster auch Funktionen definieren. Dies geschieht ähnlich wie bei einer Prozedur, nur wird nach der Parameterliste noch der Typ des Rückgabewerts angegeben. PROCEDRURE Namen(params...):Typnamen; ... BEGIN ... RETURN ... ... END Namen; Der Wert, den die Funktion liefert wird mit der Anweissung RETURN Ausdruck angegeben. RETURN beendet allgemein die Ausführung einer Funktion oder Prozedur. PROCEDURE Max(a,b : INTEGER):INTEGER; BEGIN IF a>b THEN RETURN a ELSE RETURN b END END Max; VAR i,j : INTEGER; ... i:=Max(i,j)+Max(i*i,j); ... Funktionen, die einen komplexen Typen zurückgeben tun dies nicht über RETURN. In diesen Funktionen gibt es eine Pseudovariable RESULT, die vom Typ der Rückgabe ist. Diese Variable ist die Rückgabe der Funktion. TYPE Vector = ARRAY [1..3] OF REAL; PROCEDURE VAdd(v1,v2 : Vector):Vector; VAR i : INTEGER; BEGIN FOR i:=Vector´MIN TO Vector´MAX DO RESULT[i]:=v1[i]+v2[i] END; END VAdd; VAR a,b,c : Vector; ... a:=VAdd(VAdd(a,a),VAdd(b,c)); ... Es ist in Cluster möglich, daß sich Prozeduren selbst aufrufen (Rekursion). Dabei verfügt eine Prozedur bei jedem Aufruf über neue lokale Parameter, verändert also nicht die Parameter der aufrufenden Prozedur. 4.2.6. Das Modulkonzept Bei einem großen Projekt verliert man mit der Zeit den Überblick über das Programm. Auch haben lange Programme natürlich auch längere Übersetztungszeiten. Außerdem werden manche Prozeduren und Typen immer wieder benötigt. Was liegt also näher, ein Programm in mehrere Teile (Module) zu zerlegen, und sich auch bei anderen Modulen mit Typen und Prozeduren zu bedienen. Ein Bibliotheksmodul besteht aus zwei Modulen, die getrennt übersetzt werden. Einem Schnittstellen- bzw. Definitionsmodul, in dem die Objekte stehen, die andere Module importieren können, und einem Implementationsmodul, in dem die im Definitionsmodul erklärten Prozeduren implementiert sind. In einem Definitionsmodul stehen von Prozeduren nur die Köpfe, d.h. Der Namen und die Parameter. Die Vollständige Deklaration erscheint erst im Implementationsteil. Definitionsmodul: DEFINITION MODULE Namen; ... END Namen. Implementationsmodul: IMPLEMENTATION MODULE Namen; ... BEGIN ... CLOSE ... END Namen. Variablen, Typen und Konstanten, die bereits im Definitionsmodul erklärt sind, brauchen im Implementationsteil nicht neu deklariert werden. Um ein Objekt aus einem anderen Modul nutzen zu können, muß man es von diesem Importieren. Dies kann auf zwei Arten geschehen, man importiert es aus dem Modul, und kann dann mit seinem Namen darauf zugreifen, oder man importiert das Modul, und kann dann mit dem Modulnamen und dem Objektnamen darauf zugreifen. Beispiel: DEFINITION MODULE Vector; TYPE Vector = ARRAY [1..3] OF REAL; PROCEDURE VAdd(v1,v2 : Vector):Vector; PROCEDURE VMul(v1,v2 : Vector):REAL; END Vector. IMPLEMENTATION MODULE Vector; PROCEDURE VAdd(v1,v2 : Vector):Vector; VAR i : INTEGER; BEGIN FOR i:=Vector´MIN TO Vector´MAX DO RESULT[i]:=v1[i]+v2[i] END; END VAdd; PROCEDURE VMul(v1,v2 : Vector):REAL; VAR help : REAL; i : INTEGER; BEGIN help:=0.; FOR i:=Vector´MIN TO Vector´MAX DO help:=help+v1[i]*v2[i] END; RETURN help END VMul; BEGIN CLOSE END Vector. Nun sollen die Objekte dieses Moduls genutzt werden: MODULE VecUse; FROM Vector IMPORT Vector,VAdd,VMul; IMPORT InOut AS io; (* Standarteinausgabemodul *) VAR a : Vector; BEGIN ... io.WriteLn; (* mit Modulnamen qualifiziert *) io.WriteReal(VMul(a,VAdd(a,Vector:(2,2,2,))),10,5); io.WriteLn; ... CLOSE END VecUse. Ein Definitionsmodul muß immer vor dem Implementationsmodul übersetzt werden. Wird ein Implementationsmodul geändert und neu übersetzt, muß kein weiteres Modul neu übersetzt werden. Wird dagegen ein Definitionsmodul neu übersetzt, muß sowohl das zugehörige Implementationsmodul als auch alle Module die dieses Implementieren neu übersetzt werden. Um diese Arbeit zu vereinfachen verfügt Cluster über ein Make, das diese Abhängigkeiten prüft, und die nötigen Module in der richtigen Reihenfolge übersetzt. Eine Sammlung mit Standartmodulen finden Sie in Kapitel 6. 4.3. Für "C" Programmierer Cluster ist eine Strukturierte Programmiersprache der Pascal Schule. Das heist, es wird mehr Wert auf Sicherheit und Lesbarkeit gelegt, als auf maschienennähe und kurzen Programmtexten. In C kommt es häufig zu einer Symbolhäufung, da viele Elemente der Sprache nicht als Wort sondern als Zeichen dargestellt werden. Dies kann in Cluster nicht entstehen, da die meisten Sprachelemente Wörter sind. 4.3.1. Grundlegende Programmkonstruktion Wie C ist auch Cluster eine Formatfreie Sprache, d.h. es gibt kein festes Format in dem eine Anweissung in eine Zeile geschrieben werden muß. Das Zeichen, das zwei Anweissungen trennt ist ebenfalls das Semikolon. Kommentare werden durch (* eigeleitet und mit *) beendet. Sie sind stapelbar. Schlüsselwörter sind im gegensatz zu C immer groß geschrieben. Zwischen Groß und Kleinschreibung wird auch bei Bezeichnern unterschieden. Ein Bezeichner in Cluster ist aufgebaut wie einer in C, mit der Ausnahme, daß der Unterstrich nicht als erstes Zeichen erlaubt ist. Ein Cluster-Programm hat folgenden Grundlegenden Aufbau: MODULE Namen; (* Deklarationsteil *) BEGIN (* Implementationsteil *) CLOSE (* Abschlussteil *) END Namen. Der Begin- und der Closeteil sind optional, und können weggelassen werden. Da Cluster ein Single-Pass-Compiler ist, müssen alle Bezeichner, Typen, Variablen etc. deklariert sein, bevor sie verwendet werden können. 4.3.2. Ein strenges Typkonzept Wie in C gibt es auch in Cluster den Begriff des Typs. Nur ist hier das Typkonzept wesentlich strenger und sicherer als in C. Zuweissungs und Rechnungskompatibilität zweier Typen ist genau festgelegt. Es ist üblich neue Typen mit eigenem Namen zu bilden, statt für jede Variablen einen neuen Typ zu konstruieren. Dies geschieht mit dem Schlüsselwort TYPE. Danach kommen beliebig viele Typdeklarationen. TYPE Name1 = Typkonstruktion1; Name2 = Typkonstruktion2; ... NameN = TypkonstruktionN; entspricht in C: typedef Typkonstruktion1 Name1; typedef Typkonstruktion2 Name2; ... typedef TypkonstruktionN NameN; Folgende einfachen Typen sind in Cluster vorhanden: SHORTCARD = UBYTE CARDINAL = UWORD LONGCARD = ULONG SHORTINT = BYTE INTEGER = WORD LONGINT = LONG REAL = float LONGREAL = double CHAR = char BOOLEAN = hat keine eigentliche entsprechung in C. Ist als Flag zu sehen, das die Werte Wahr (TRUE) oder Falsch (FALSE) annehmen kann. ANYPTR = APTR; Es ist möglich von einem Numerischen Typen nur ein Intervall als zulässige Werte eines Typs zu erklären. Bei Zuweissungen wird überprüft, ob das Ergebniss auch in diesem Intervall ist. TYPE Bis100 = [0..99]; Definiert einen Unterbereichstypen Bis100. Variablen dieses Typs können nur Zahlen von 0 bis 99 zugewiessen werden. Alle anderen Zuweissungen führen zu Fehlern. Wird nur ein Wert in den Klammern angegeben gilt der Unterbereich von 0 bis zu diesem Wert weniger 1. TYPE Bis100 = [100]; TYPE Capital = ["A".."Z"]; Definiert einen Unterbereich der Zeichen. In Cluster gibt es ebenfalls die Möglichkeit, einen Aufzählungstypen zu erzeugen. Elemente dieses Typs sind dann immer nur zu Elementen dieses Typs kompatibel. Sie erhalten aufsteigende Nummern, von null an. TYPE Monate = (Januar,Februar,Maerz,April,Mai,Juni,Juli,August, September,Oktober,November,Dezember); entspricht in C: enum Monate {Januar = 0, Februar = 1, Maerz = 2, April = 3, Mai = 4, Juni = 5, Juli = 6, August = 7, September = 8, Oktober = 9, November = 10, Dezember = 11}; Arrays werden in Cluster fast genauso gehandhabt wie in C. Der Indexbereich muß jedoch nicht bei 0 beginnen. Auch können andere Typen als Indextyp verwendet werden, im allgemeinen Unterbereichstypen. TYPE AType = ARRAY [10] OF INTEGER; entspricht in C: int AType[10]; TYPE BType = ARRAY [10..20] OF INTEGER; CType = ARRAY ["a".."b"] OF INTEGER; DType = ARRAY CHAR OF LONGINT; EType = ARRAY Monate OF INTEGER; Mehrdimensionale Arrays können auf zwei Arten definiert werden: TYPE FType = ARRAY [1..10] OF ARRAY ["a".."b"] OF LONGINT; GType = ARRAY [1..10],["a".."b"] OF LONGINT; Der Zugriff auf Arrays erfolgt wie in C, nur bei mehrdimensionalen Arrays, ist eine Vereinfachung möglich. Sei GVar vom Typ GType: GVar[2]["d"] entspricht GVar[2,"d"] Anderst als in C gibt es in Cluster einen Typ für Strings. Dieser enthält neben einem Array, in dem die Zeichen stehen noch einen Wert, der die aktuelle Länge des Strings angibt. TYPE String32 = STRING(32); Definiert einen String mit maximal 32 Zeichen. Sei Str32 von diesem Typ, dann ist das i-te Zeichen Str32.data[i] und die aktuelle Länge Str32.len. Für die Verarbeitung von Strings gibt es eine große Zahl von Prozeduren und Funktionen. Werden zwei Strings einander zugewiessen, wird der Inhalt und nicht nur der Zeiger darauf übergeben. Analog zu den structs gibt es in Cluster die Records. TYPE Datum = RECORD day, minute, tick : LONGINT END; entspricht struct Datum {long day; long minute; long tick}; Der Zugriff erfolgt wie in C mit dem Punkt. Pointer werden in Cluster anderst deklariert wie in C: TYPE DatePtr = POINTER TO Datum; entspricht typedef Datum *DatePtr; Da ein Typnamen nicht bekannt ist, bevor seine Deklaration vollständig ist, ist es erlaubt einen Zeiger auf einen Typen zu Deklarieren, bevor dieser Bekannt ist. TYPE NodePtr = POINTER TO Node; Node = RECORD prev, next : NodePtr; data : INTEGER; END; wäre in C struct Node{Node *prev; Node *next; int data}; Die Dereferenzierung eines Pointers geschieht in Cluster nicht durch ein vorgestelltes "*" sondern durch ein nachgestelltes "^". Auch gibt es keinen speziellen Operator "->" sondern es muß "^." geschrieben werden. Der Mengentyp SET dient meist dazu verschiedene Flags zusammenzufassen. Er entspricht einer Menge der Mengenlehre, wobei angegeben werden muß, welche Elemente theoretisch enthalten sein könnten. Dies dürfen maximal 32 sei, da jedes Element ein Bit belegt. TYPE MonatSet = SET OF Monat; entspricht sehr grob in C etwa: f_Januar = 1 << Januar; f_Februar = 1 << Februar; ... f_Dezember = 1 << Dezember; Folgende Typen sind zueinander Rechnungskompatibel: - SHORTINT, INTEGER, LONGINT, negative Unterberreiche - SHORTCARD, CARDINAL, LONGCARD, positive Unterberreiche - REAL, LONGREAL - sowie Sets des gleichen Typs Folgende Typen sind zuweisungskompatibel: - SHORTINT, INTEGER, LONGINT, SHORTCARD, CARDINAL, LONGCARD, Unterbereiche - REAL, LONGREAL - STRINGS beliebiger Länge - POINTER mit gleichen Zieltypen - sowie alle Objekte gleichen Typs zueinander 4.3.3. Grundstrukturen 4.3.3.1. Variablen Variablen müssen wie auch in C vor ihrer Benutzung deklariert werden. Dies geschieht durch das Schlüsselwort VAR. Danach folgen Namen und Typen der Variablen: VAR Namen1,Namen2,.. : TypDefinition1; ... NamenX,NamenY... : TypDefinitionX; Beispiel: Deklaration einer Integervariablen i, und eines Zeigers p: VAR i : INTEGER; p : POINTER TO INTEGER; etspricht in C int i; int *p; 4.3.3.2. Konstanten Es gibt zwei Arten Konstanten in Cluster, solche deren Typ Implizit bekannt ist, und solche deren Typ explizit angegeben werden muß. Ganzzahlige numerische Konstanten können dezimal 2000 hexadezimal $7D0 oder binär %11111010000 angegeben werden. Zeichenkettenkonstanten werden in " eingekleidet "Maier" etc.. Flieskommakonstanten können ein "." sowie ein "E" enthalten, sie müssen aber auf jeden Fall mit einer Ziffer beginnen. Komplexere Konstanten müssen mit ihrem Typ angegeben werden. TYPE MonLenType = ARRAY Monate OF INTEGER; Die Länge des i-ten Monats wäre dann: MonLenType:(31,28,31,30,31,30,31,31,30,31,30,31)[i]; TYPE Adresse = RECORD name, vorname : STRING(30); plz : INTEGER; END; ... Adresse:(name="Müller",vorname="Klaus",plz=5300); Es ist möglich, einer Konstanten einen Namen zu geben, um dann mit diesem darauf zuzugreifen. CONST pi = 3.14159; MonLen = MonLenType:(31,28,31,30,31,30,31,31,30,31,30,31); Es ist möglich, eine Konstante zu deklarieren, bevor ihr Inhalt deklariert wird. Dies geht nur bei Komplexen Konstanten. CONST Maier = Adresse; ... Maier = Adresse:(name="Maier",vorname="Klaus",plz=2000); Der Vorname von Herrn Maier wäre dann Maier.name, und könnte bereits benutzt werden, bevor die Deklaration vollständig ist. Konstanten eines Mengentyps werden ähnlich definiert, allerdings mit Mengenklammern. TYPE Numset = SET OF [0..31]; CONST Leer = Numset:{}; Voll = Numset:{0..31}; Gerade = Numset:{0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30}; Manche = numset:{3,7..11,15,17,23..30}; 4.3.3.3. Ausdrücke und Zuweissung Wie in C bilden auch in Cluster Ausdrücke (mathematische o.ä. Terme) und Zuweissungen das Rückrat der Sprache. Zuweissungen werden in Cluster durch ":=" vorgenommen. Cluster verfügt über folgende Operatoren: Für REALs: + : Addition - : Negation oder Subtraktion * : Multiplikation / : Division ^ : Potenzierung Für INTEGERs + : Addition - : Negation oder Subtraktion * : Multiplikation DIV : Ganzzahldivision MOD : Rest der Division SHL : Linksschieben (entspricht <<) SHR : Rechtsschieben (entspricht >>) Für SETs + : Vereinigung * : Schnittmenge - : Mengendifferenz / : Symmetrische Differenz Für BOOLEANs AND : logisches Und OR : logisches Oder NOT : logisches Nicht = : Equivalenz # : Exklusiv oder Neben diesen Operatoren, deren Ergebniss immer von dem Typ ist, in dem auch die Operanden sind, gibt es auch noch Relationen. Eine Relation liefert immer einen Booleanwert zurück Für REALs, POINTER, Aufzählungstypen und INTEGERs = : gleich # : ungleich > : größer >= : größer oder gleich < : kleiner <= : kleiner oder gleich OF : in Bereichsliste enthalten. Links steht ein Ausdruck, rechts eine Bereichsliste. Eine Bereichsliste besteht aus Werten oder Intervallen, die durch Komma getrennt werden. Beispiel: a OF 3,4,20..40,50..60 Die Elemente können auch selbst Ausdrücke sein: x OF pos-links..pos+rechts Für SETs = : gleich # : ungleich <= : Teilmenge von >= : Obermenge von IN : in Menge enthalten. Links steht das Element, rechts die Menge In Cluster wird Punkt vor Strich berücksichtigt. Es ist möglich durch Klammern dies zu umgehen. Folgende Prioritätsstufen existieren. 1.) ^ : Dereferenzierung 1.) . : Recordelement 1.) [] : Index bei Arrays 1.) ..() : Funktionsauswertung 1.) ´ : Attribut 2.) - : unäres minus 2.) NOT : complement 2.) () : Klammerausdrücke 3.) ^ : Potenzierung 4.) * : Produkt 4.) / : Division 4.) AND : logisches UND 4.) SHL : linkschieben 4.) SHR : rechtsschieben 4.) DIV : Ganzzahldivision 4.) MOD : Rest der Division 5.) + : Summe 5.) - : Differenz 5.) OR : logisches Oder 6.) Relationen Folgende Standartfunktionen sind vorhanden: SIN(REAL) : REAL : Sinus im Bogenmaß COS(REAL) : REAL : Cosinus TAN(REAL) : REAL : Tangens ASIN(REAL) : REAL : Arcussinus im Bogenmaß ACOS(REAL) : REAL : Arcuscosinus ATAN(REAL) : REAL : Arcustangens SINH(REAL) : REAL : Sinushyperbolicus COSH(REAL) : REAL : Cosinushyperbolicus TANH(REAL) : REAL : Tangenshyperbolicus SQRT(REAL) : REAL : Quadratwurzel LN(REAL) : REAL : natürlicher Logarithmus LOG(REAL) : REAL : dekadischer Lograithmus EXP(REAL) : REAL : e^x CEIL(REAL) : REAL : aufrunden FLOOR(REAL) : REAL : abrunden ABS(REAL) : REAL : Betrag ODD(INT) : BOOLEAN : liefert wahr, wenn INT ungerade ist Machmal ist es nötig, den Typ eines Objektes zu wechseln, um z.B. den ASCII-Wert eines Zeichens zu erhalten oder umgekehrt. Ein Typtransfer kann auf zwei Arten geschehen. CAST(Typ,Object) : Object : Wandelt den Typ des Objects in den neuen Typ um. Dies geht nur, wenn beide Typen die selbe Länge haben. Beispiel: TYPE Vector = RECORD x,y,z : REAL END; Array3 = ARRAY [0..2] OF REAL; VAR v : Vector; ... v.y:=CAST(Array3,v)[1] ... NeuTyp(Object) : Wandelt das Object in den Typ NeuTyp um. Dies geht nur bei einfachen Typen. Hierbei wird nicht nur der Typ geändert, sondern das Objekt selbst umgewandelt. Beispiel: VAR i : INTEGER; ... i:=INTEGER(SIN(REAL(i))*256.) ... Jeder Typ und jedes Objekt hat gewisse Attribute. Diese können mit Objekt´XXX ermittelt werden. Folgende Attribute existieren: SIZE : Größe eines Typs in Bytes RANGE : Anzahl der Element eines Arrays MIN : Kleinstes Element eines einfachen Typs, oder kleinster Index eines Arrays MAX : Größtes Element eines einfachen Typs, oder größter Index eines Arrays PTR : Zeiger auf das Objekt entspricht dem & ADR : Addresse des Objekts TYPE Array = ARRAY [7..22] OF INTEGER; VAR a : Array; Dann ist a´MIN = Array´MIN = 7 a´MAX = Array´MAX = 22 a´RANGE = Array´RANGE = 16 4.3.4. Programmkosntruktionen In Cluster steht nicht das Statement (Anweissung) im Vordergrund der Sprachdefinition, sondern die Statementsequenz (Anweissungsfolge). Folgende Strukturen existieren in Cluster: Bedingung: IF Bedingung1 THEN Anweissungsfolge1 OR_IF Bedingung2 THEN Anweissungsfolge2 ... ELSE AnweissungfolgeN END; entspricht: if (Bedingung1){Anweissungfolge1} else if (Bedingung2){Anweissungsfolge2}... else {AnweissungfolgeN} Zählschleife FOR Variable:=Startwert TO Endwert [BY Schrittwert] DO Anweissungsfolge END; entspricht: for (Variable=Startwert;Variable=Endwert;Variable++) {Anweissungsfolge} Anfangsschleife WHILE Bedingung DO Anweissungsfolge END; entspricht: while (Bedingung) {Anweissungsfolge} Endeschleife REPEAT Anweissungsfolge UNTIL Bedingung; entspricht: do {Anweissungsfolge} while (Begingung) Endlosschleife LOOP ... EXIT; ... END entspricht: for (;;) {... break; ...} Fallunterscheidung IF KEY Ausdruck OF Bereich1 THEN Anweissungsfolge1 END OF Bereich2 THEN Anweissungsfolge2 END ... ELSE AnweissungsfolgeN END; entspricht: switch (x) {case Fall1 : Anweissungsfolge1 case Fall2 : Anweissungsfolge2 ... default : AnweissungsfolgeN} Neben diesen Strukturen, die den Programmfluß steuern, gibt es auch einen Struktur, die dem Programmierer die Arbeit erleichtern soll. Häufig kommt es vor, daß man Objekte bearbeiten muß, auf die nur über komplexe Indizierungen zugegriffen werden kann. Mit der WITH-Struktur ist es möglich einem derartigen Objekt zeitweilig einen eigenen Namen zu geben, über den darauf zugegriffen werden kann. WITH Varausdruck [AS NeuerNamen], ... Varausdruck [AS NeuerNamen] DO ... END; Beispiel: TYPE Reihe = [1..8]; Spalte = ["a".."z"]; SchachFigur = (Leer,Bauer,Springer,Lauefer,Turm,Koenig,Dame); SchachKoord = RECORD reihe : Reihe; spalte : Spalte; END; SchachFeld = RECORD figur : SchachFigur; weiss : BOOLEAN; nachbarn : ARRAY [-1..1], [-1..1] OF SchachKoord; END; VAR SchachBrett : ARRAY Spalte,Reihe OF SchachFeld; W_Dame : SchachKoord; Um nun die Dame ein Feld vorzurücken, ist ein ziemlicher Aufwand nötig, der durch die WITH-Struktur vereinfacht werden kann. ... WITH SchachBrett[W_Dame.spalte,W_Dame.reihe] AS feld DO feld.figur:=Leer; W_Dame:=feld.nachbarn[0,-1]; END; WITH SchachBrett[W_Dame.spalte,W_Dame.reihe] AS feld DO feld.figur:=Dame; feld.weiss:=TRUE; END; ... 4.3.5. Prozeduren und Funktionen In Cluster wird im gegensatz zu C streng zwischen Prozeduren und Funktionen unterschieden, wobei Prozeduren keinen Rückgabewert haben. Unterschiede ergeben sich vor allem bei der Prozedurdeklaration. In Cluster werden die Typen der Parameter in der Deklarationszeile angegeben, nicht hinterher. PROCEDURE DoNothing; BEGIN END DoNothing; DoNothing ist eine Parameterlose Prozedur, die nichts tut. In C sähe dies so aus: VOID DoNothing() {} Eine Prozedur mit Parameter: PROCEDURE WithParam(i,j : INTEGER;c : CHAR); ... und in C VOID WithParam(i,j,c) int i,j; char c; {... In Cluster gibt es auch Parameter die durch Referenz übergeben werden. Dies entspricht in etwa der Übergabe mit Pointern. PROCEDURE VarParam(i,j : INTEGER;VAR erg : INTEGER); BEGIN erg:=i+j END VarParam; und der Aufruf VAR x,y,z : INTEGER; ... VarParam(x,y,z) ... nun das ganze noch in C VOID VarParam(i,j,erg) int i,j,*erg; {*erg=i+j}; int x,y,z; ... VarParam(x,y,&z) ... Es ist in Prozeduren möglich Variablen, Typen, Konstanten und sogar neue Prozeduren definierbar. Diese lokalen Objekte sind immer vom Typ auto, also nur innerhalb der Prozedur existent. TYPE array = ARRAY [0..9] OF INTEGER; PROCEDURE Sort(VAR field : array); PROCEDURE Swap(VAR x,y : INTEGER); VAR h : INTEGER; BEGIN h:=x;x:=y;y:=h END Swap; VAR i,j : INTEGER; BEGIN FOR i:=0 TO 9 DO FOR j:=0 TO 8 DO IF field[i]>field[i+1] THEN Swap(field[i],field[j]) END END END END Sort; In der inneren Prozedur sind ebenfalls alle Variablen der äusseren Bekannt. Sie kann auch selbst wieder über lokale Prozeduren verfügen. Funktionen unterscheiden sich in der Deklaration dadurch von Prozeduren, daß ihnen ein Typ angehängt wird. Die Rückgabe des Wertes ergfolgt bei einfachen Typen über RETURN ausdruck. RETURN kann auch Prozeduren beenden. PROCEDURE Max(x,y : INTEGER):INTEGER; BEGIN IF x>y THEN RETURN x ELSE RETURN y END END Max; und der Aufruf VAR i : INTEGER; ... i:=Max(i,10); ... und nun in C int Max(x,y) int x,y; {return (x+y)}; int i; ... i=Max(i,10) ... Komplexe Typen werden mit einer Pseudovariablen (RESULT) zurückgegeben. Sie ist vom Typ des Rückgabewerts. PROCEDURE Flip(field : array):array; VAR i : INTEGER; BEGIN FOR i:=0 TO 9 DO RESULT[i]:=field[9-i] END; END Flip; Da es manchmal nötig ist, daß sich zwei Prozeduren gegenseitig aufrufen, kann man Prozeduren deklarieren, bevor man sie Implemenetiert. Dies geschieht mit dem Schlüsselwort FORWARD. FORWARD PROCEDURE A; PROCEDURE B; BEGIN A END B; PROCEDURE A; BEGIN B END CallA; Bei der Implementierung der Prozedur muß der Prozedurkopf noch einmal vollständig aufgeführt sein. 4.3.6. Modulkonzept Häufig ist es nötig ein Programm in kleine Module zu zerlegen, oder Objekte aus Bibliotheken zu benutzen. Dies ist in C nur unvollständig über INCLUDE möglich, da eine sichere Überprüfung bei externen Objekten nicht stattfindet, bzw. der miteinbezogene Text selbst noch einmal compiliert werden muß. Cluster verfügt über ein ausgereiftes Modulkonzept. Dazu wird jedes Modul, das einem anderen Objekte zur Verfügung stellen soll, in zwei gesplittet, einem Definitions- und einem Implementationsmodul. Diese werden getrennt compiliert. Das Definitionsmodul muß immer vor dem Implementationsmodul compiliert werden, eine Neucompilierung des Implementationsmodul zieht aber keine Neucompilierung des Definitionsmodul nach sich. Im Definitionsmodul stehen Typ-, Variablen-, Konstanten- und Prozedurdeklarationen, wobei bei Prozeduren nur der Kopf mit den Parametern aufgeführt wird. Die volle Implementierung befindet sich im Implementationsmodul. Beispiel eines Schnittstellenmoduls: DEFINITION MODULE Listen; TYPE List = POINTER TO Node; Node = RECORD next : List; data : INTEGER; END; PROCEDURE Insert(VAR l : List;d : INTEGER); PROCEDURE First(l : List):INTEGER; END Listen. Das zugehörige Implementationsmodul könnte etwa so aussehen: IMPLEMENTATION MODULE Listen; FROM Heap IMPORT New; PROCEDURE Insert(VAR l : List;d : INTEGER); VAR p : List; BEGIN New(p); p^.next:=l; p^.data:=d; END Insert; PROCEDURE First(l : List):INTEGER; BEGIN RETURN l^.data END First; BEGIN CLOSE END Listen. Funktionen, Prozeduren, Typen, Variablen und Konstanten können aus derartigen Modulen importiert werden. Dies kann auf zwei Arten geschehen. FROM Modul [AS NeuerName] IMPORT Objekt,Objekt,...; Derartig importierte Objekte können direkt mit ihrem Namen bezeichnet werden. IMPORT Modul [AS NeuerName]; Objekte die nicht direkt importiert werden, sondern mit ihrem ganzen Modul importiert werden, müssen über den Modulnamen Qualifiziert werden. Es ist möglich, beim Import ein Modul umzubennen, um nicht immer den ganzen Namen schreiben zu müssen. MODULE ListTest; FROM Listen AS li IMPORT List; FROM InOut IMPORT WriteInt; VAR l : List; BEGIN l:=NIL; li.Insert(l,12);li.Insert(l,332); WriteInt(li.First(l)); CLOSE END ListTest. Der BEGIN-Teil eines importierten Moduls wird immer ausgeführt, bevor auf eine Prozedur dieses Moduls zugegriffen wird. Der CLOSE-Teil wird ausgeführt, wenn ein Programm wieder beendet wird. Mit dieser Technik werden auch die Librarys geöffnet und wieder geschlossen, so daß sich der Programierer nicht mehr darum kümmern muß, er muß lediglich das Modul importieren, das zur Library gehört. Eine Übersicht über alle Standartbibliotheken finden sie in Kapitel 6. Ein ImplementationsModul muß immer nach seinem Schnittstellenmodul übersetzt werden. Wird nur die Implementierung eines Moduls, aber nicht dessen Defintion geändert, braucht weder das Definitionsmodul noch Module, die dieses Importieren neu übersetzt zu werden. Wird jedoch das Definitionsmodul neu compiliert, müssen alle Module die dieses Importieren, sowie das Implementationsmodul neu übersetzt werden. Damit dies problemlos geschieht, verfügt Cluster über ein MAKE, das dies automatisiert. 4.4. Für Assembler Programmierer Dieses Kapitel richtet sich an einigermaßen versierte Assemblerfreaks, die schon einige Erfahrung im Programmieren haben. Fühlen Sie sich auch in Assembler nicht sicher, sollten Sie besser das Kapitel 3 für Programmieranfänger lesen. 4.4.1. Wozu eine höhere Programmiersprache Assembler ist die Möglichkeit, die schnellsten und kürzesten Programme für einen Computer zu schreiben, erscheint also optimal zu sein. Doch hat Assembler einen entscheidenten Haken. Diese Sprache ist dem Rechner angepaßt, nicht dem Programmierer. Hieraus rührt das Problem, daß Assemblerprogramme ab einer bestimmten Größe fast nicht mehr gewartet werden können. Man kann sagen, daß die Zeit, die nötig ist ein Assemblerprogramm zu schreiben exponentiell zu seiner Länge anwächst. Auch bietet Assembler kaum Unterstützung um Programmierfehler zu vermeiden. Es ist einem Assembler völlig egal, ob ein Wert in einem Register nun Byte oder Wortlänge hat, und meldet folglich auch keinen Fehler wenn mit der falschen Länge auf eine Instruktion zugegriffen wird. Es ist ebenfalls fast unmöglich dem Programmfluß in einem Assemblerprogramm zu folgen, da notwendigerweisse häufige Sprünge ein Verfolgen schier unmöglich machen. Die interessantesten Fehler sind aber sicher die, wo ein Wert auf dem Stack vergessen wurde, und am Ende des Unterprogramms, sich der Rechner nach Indien wendet. Ein Assembler bietet auch keinen oder nur sehr wenig Komfort, wenn es darum geht, Unterprogrammbibliotheken anzulegen und zu verwenden. Es gibt zwar meist die Möglichkeit mit einem Include oder XRef fremde Programmteile zu benutzen, doch ist man bei der Verwendung der richtigen Parameterübergabe auf sein eigenes Gedächtniss verwießen. Eine strukturierte Programmiersprache führt wärend der Übersetzung eine Reihe Kontrollen durch, die viele Fehler unterbinden. So ist es nicht möglich (ohne vorherige Umwandlung) ein Zeichen zu einer Realzahl zu addieren, was eine Assembler gnadenlos übersetzten würde. Durch den Verzicht auf Sprünge, wird der Programmfluß deutlich und einfach zu überschauen. Es ist somit wesentlich einfacher, eine fertiges Programm zu verstehen oder warten. Da auch Unterprogramme mit ihren Parametern ein Teil der Sprache ist, kann auch keines mit fehlerhaften Parametern angesprungen werden. Durch die konsequente Verwendung von Bibliotheksmodulen, ist es ein leichtes Prozedursammlungen zu erstellen und in vielen Projekten zu verwenden. Da auch hierbei alle Kontrollen bestehen bleiben, bleibt die Konsistenz des Programmes gewahrt. Ein weiterer Vorteil ist die gute Laufzeitumgebung sowie viele Standartmodule, die es ermöglichen ohne große Ahnung des Betriebssystems doch gute und schnelle Programme zu schreiben. Zusammenfassend läßt sich sagen, daß eine höhere Programmiersprache zwar kaum schnelleren Code erzeugt, dafür aber sicheren und die Programmierung und Fehlersuche wesentlich vereinfacht und beschleunigt. Und gerade auf den heutigen schnellen Rechnern wie dem Amiga, ist die Laufzeit nicht mehr das allein entscheidende. 4.4.2. Grundlegende Programmstruktur Cluster ist anderst als Assembler eine Formatfreiesprache, das heist, es ist unwichtig, wie eine Zeile aussieht. Es gibt also keine Spalteneinteilung wie in Assembler. Dadurch lassen sich Programme so schreiben, wie sie am deutlichsten ihre Struktur zeigen. Kommentare sind ebenfalls formatfrei. Sie werden durch (* eingeleitet, und durch *) geschlossen. Komentare können auch geschachtelt werden. Schlüsselwörter leiten eine Struktur ein, und beenden sie auch wieder. Diese Wörter bestehen aus großen Buchstaben. Ein Programm beginnt immer mit dem Bezeichner "MODULE", und endet mit "END". Einzelne Teile wie Deklarations- und Implementationsteil werden durch Schlüsselwörter eingeleitet. MODULE Test; (* Deklarationsteil *) BEGIN (* Implementationsteil *) CLOSE END Test. Zwischen zwei Anweissungen steht immer ein Semikolon als Trennzeichen. Da es auch die leere Anweissung gibt, können mehrere Strichpunkte hintereinander kommen. Eigene Bezeichner beginnen immer mit einem Buchstaben, und können danach noch weitere Buchstaben und Zahlen, sowie den Unterstrich enthalten. Große und kleine Buchtaben sind verschieden, also ist der Bezeichner "Screen" anderst als der Bezeichner "screen". Um einzelne Worte eines Bezeichners hervorzuheben, hat es sich bewährt, deren ersten Buchtaben groß zu schreiben "ScreenFlags". Da Cluster eine Singlepass Sprache ist, der Compiler also nur einmal über den Text geht, müssen alle Dinge deklariert werden, bevor sie benutzt werden können. Dies stellt zwar eine kleine Einschränkung dar, sorgt aber für kurze Compilierzeiten, und kann umgangen werden. 4.4.3. Die Verwendung von Typen Ein grundlegendes Konzept strukturierter Programmiersprachen ist der Begriff des Typs. Jedes datentragende Objekt also Variablen und Konstanten hat einen bestimmten Typen. In Assembler gibt es auch eine Art Typ, die Operandenlänge. Ein Typ hat (fast) immer einen Namen, anhand dessen er identifiziert wird. Erzeugt der Programmierer einen neuen Typen, gibt er ihm ebenfalls einen Namen. Eigene Typdefinitionen werden durch das Schlüsselwort TYPE eingeleitet. Diese Definition muß im Deklarationsteil durchgeführt werden. Danach folgen die Namen und Deklarationen der Typen getrennt durch ein Gleichheitszeichen. Formal: TYPE Namen = Typdeklaration; .. Namen = Typdeklaration; Man unterscheidet einfache und zusammengesetzte Typen. Einfache Typen entsprechen meist Byte, Wort und Langwort. Die einfachsten Typen sind die ganzen Zahlen. Man unterscheidet im Gegensatz zu Assembler vorzeichenbehaftete und vorzeichenfreie Zahlen. Diese unterscheidung ist sinnvoll, da nur so über- bzw. Unterläufe richtig beurteilt werden können. Folgende Ganzzahltypen sind bereits vorgegeben: SHORTINT : -128..127 INTEGER : -32768..32767 LONGINT : -2147483648..2147483647 SHORTCARD : 0..255 CARDINAL : 0..65535 LONGCARD : 0..4294967295 Weitere Typen, die einen eingeschränkten Zahlenbereich enthalten können, Unterberreichstypen, können selbst definiert werden. Diese Typen haben den Vorteil, daß, wenn einer Variablen diesen Typs ein Wert zugewiessen wird, der nicht im Bereich liegt, ein Fehler gemeldet wird. Dazu wird der Bereich in zwei eckigen Klammern angegeben. TYPE Monat = [1..12]; Definiert einen Unterbereich mit erlaubten Werten von 1 bis 12. Wird in der Klammer nur ein Wert angegeben, bedeutet dies einen Bereich mit sovielen Elementen, der bei 0 beginnt. Nibble = [16]; ist das selbe wie Nibble = [0..15]; Die Länge eines Unterbereiches hängt von der Zahl der Elemente ab, sie werden immer möglichst klein gehalten. Zu den zählbaren Typen gehören auch die Zeichen, CHAR. Sie belegen immer ein Byte. Desweiteren gibt es noch die Aufzählungstypen. Diese Typen können eine bestimmte Anzahl von Elementen annehmen, die durch ihren Namen gekennzeichnet sind. TYPE Monat = (Januar,Februar,Maerz,April,Mai,Juni, Juli,August,September,Oktober,November,Dezember); Variablen des Typs Monat können die Elemente Januar, Februar u.s.w. annehmen. Eigentlich handelt es sich hierbei um die Werte von 0 bis 11, doch da Zahlen nicht sehr aussagekräftig sind, werden die Namen verwendet. Um die Konsistenz des Typs zu halten, können ihm keine Zahlen, sondern nur Elemente seines Typs zugewiessen werden. Auch von einem Aufzählungstypen kann wieder ein Unterbereich definiert werden. TYPE Sommer = [Juli..September]; Neben den ganzen Zahlen gibt es noch die gebrochenen. Davon hat Cluster zwei Arten, REAL entspricht FFP und LONGREAL, das den IEEE-Zahlen doppelter Genauigkeit entspricht. Nicht zählbar, aber dennoch einfach ist der Typ BOOLEAN. Dieser Typ enthält die Werte wahr (TRUE) und falsch (FALSE). Er enthält also ein Flag. Auf maschinenebene ist er ein Byte, das 0=FALSE oder 1=TRUE enthalten kann. Gesetzt wird er meist mit "Scc". Der letzte einfache Typ ist die Menge. Sie kann als Elemente die eines Unterbereiches oder eines Aufzählungstyps enthalten. Die Elemente der Menge entsprechen den Bits in einem Byte, Wort oder Langwort. Eine Menge kann maximal ein Langwort sein, und bis zu 32 Elemente enthalten. TYPE MonatSet = SET OF Monat; So ein Monatset kann nun eine beliebige Menge an Monaten enthalten. Ist ein Element vorhanden, ist das korrespodierende Bit gesetzt, sonst gelöscht. Die IDCMPFlags in Intuition sind z.B. eine derartige Menge. Daneben gibt es noch zusammengesetzte Typen, die aus einfachen Typen aufgebaut sind. Der einfachste ist die Reihung, das ARRAY. Dabei werden mehrere Elemente des selben Typs hintereinander angeordnet, und können über einen Index angesprochen werden. Die Allgemeine Form der Arraydeffinition sieht so aus: ARRAY Indextyp OF Basistyp; wobei Indextyp ein Unterberreichs oder Aufzählungstyp sein muß, und Basistyp ein beliebiger Typ sein kann. Beispiel: TYPE Vector = ARRAY [0..3] OF INTEGER; Eine Variable diesen Typs belegt z.B. 8 Bytes. Das erste Element hat zum Anfang das Displacement 0, das nächste 2, dann 4 und das letzte 8. Als Index kann auch ein komplexer Ausdruck verwendung finden. Angenommen wir haben eine Variable a dieses Typs, und Variablen i und j vom Typ INTEGER, dann sähe ein Zugriff folgendermaßen aus. a[3]:=2; MOVE.W #2,6+a(A4) a[i]:=a[j+1]; MOVE.W i(A4),D7 ADD.W D7,D7 MOVE.W j(A4),D6 ADD.W #1,D6 ADD.W D6,D6 MOVE.W a(A4,D6),a(A4,D7) Man kann auch, anstatt den einzelnen Elementen eines Arrays etwas zuzuweissen, das ganze Array einem anderen Array dieses Typs zuweissen. Es ist auch möglich, ein ARRAY aus ARRAYs zu erzeugen. TYPE Matrix = ARRAY [0..2] OF ARRAY [0..2] OF REAL; einfacher kann man auch schreiben: Matrix = ARRAY [0..2],[0..2] OF REAL; Die Berechnung des Index gestaltet sich dann etwas komplexer; a[i,j]:=2; MOVE.W i(A4),D7 MULU.W #12,D7 MOVE.W j(A4),D6 ADD.W D6,D6 ADD.W D6,D7 MOVE.W #2,a(A4,D7) Im Gegensatz zu Assembler braucht man sich aber nicht um die eigentliche Berechnung kümmern, sondern kann sich auf den Compiler verlassen. Wird z.B. die Arraystruktur geändert, braucht nicht an jeder Stelle die Indexberechnung angepasst werden, da sich der Compiler darum kümmert. Ein STRING ist ein dem Array verwandter Typ. Er dient dazu Zeichenketten aufzunehmen. Dazu verfügt er zusätlich noch über ein Feld, in dem die wirkliche, also mit Zeichen gefüllte Länge des Strings enthalten ist. TYPE Str80 = STRING(80); Definiert einen String mit maximal 80 Zeichen. Man kann auf einzelne Elemente und die Länge folgendermassen zugreifen. Sei s eine Variable vom Typ Str80. Länge : s.len n-tes Element : s.data[n] Für Strings gibt es eine reichhaltige Bibliothek mit häufig benötigten Funktionen. In einem ARRAY werden mehrere Elemente des gleichen Typs gruppiert, und mit einem Index adressiert. In einem RECORD, einer anderen Struktur, werden dagegen mehrere Objekt verschiedener Typen zusammengefasst, und mit einem Namen angesprochen. TYPE Adresse = RECORD namen : STRING(20); vornamen : STRING(20); strasse : STRING(100); plz : LONGCARD; END; Um nun ein Element dieses Verbundes anzusprechen, muß man es über seinen Namen qualifizieren. Dies geschieht, indem man ihn durch einen Punkt getrennt angibt. Der Zugriff auf ein Recordelement entspricht der Adressierungsart Indirekt mit Displacement. Sei a vom Typ Adresse: a.plz:=7800 MOVE.L #7800,a+plz(A4) Es ist auch möglich, Arrays von Records zu bilden, oder Records als Recordelemente zu verwenden. Als letzten Typkonstruktur gibt es noch den Zeiger. Ein Zeiger enthält die Adresse eines Objektes. Im Gegensatz zu einer reinen Adresse weis ein Zeiger aber welchen Typ das Objekt hat, auf das er Zeigt. TYPE AdPtr = POINTER TO Adresse; Um von der Adresse des Elements auf das Element selber zu kommen, muß man einen Zeiger dereferenzieren. Dazu stellt man ihm einen Hochpfeil "^" nach. Sei p ein AdPtr: p^.plz:=7800; MOVE.L p(A4),A3 MOVE.L #7800,plz(A3) Die Adresse $000000 hat den Namen NIL. NIL ist eine Konstante, die zu jedem Zeigertypen kompatibel ist. Es sollte NIE !!! ein Pointer mit $000000 verglichen, oder belegt werden, immer mit NIL. 4.4.4. Grundstrukturen 4.4.4.1. Variablen Wird in Assembler Speicherplatz benötigt, um Werte zwischenzuspeichern, oder für Tabellen und ähnliches, wird dieser mit ds.x o.ä. belegt. Die Adresse dieses freien Stücks wird dann einem Label zugewiesen. Der Assembler achtet später aber nicht darauf, ob nur der reservierte Platz und nicht mehr beschrieben wird, was aber mit Sicherheit zu unterhaltsamen Meditationen führt. In höheren Programmiersprachen heißen diese Speicherbereiche, in denen wärend der Laufzeit Daten gelagert werden Variablen. Jede Variable hat einen festen Typ, so daß eine fehlerhafte Benutzung ausgeschlossen ist. Es ist auch nicht nötig, die Länge dieses Speicherstückes anzugeben, da sich diese aus dem Typ ergibt. Die Deklaration von Variablen beginnt mit dem Schlüsselwort "VAR". Danach folgen die Namen, die durch einen Doppelpunkt von ihrem Typ getrennt werden. Formal: VAR Name1,Name2,.. : Typdeklaration; .. Name4,... : Typdeklaration; Beispiel: VAR i : INTEGER; ptr : POINTER TO Adresse; add : Adresse; Variablen sind erst bekannt, nachdem sie deffiniert wurden. Zu Programmbeginn, sind alle Variablen als zufällig belegt zu betrachten. 4.4.4.2. Konstanten Neben den Variablen, die ihren Wert erst während der Laufzeit erhalten, gibt es noch Objekte, deren Wert schon zur Compilierzeit festgelegt wird. Dies Konstanten werden in Assembler mit dc.x deffiniert. In Cluster haben auch Konstanten einen Typ. Dieser kann entweder aus der Definition selbst folgen (123.45 ist immer eine gebrochene Zahl) oder über einen Typ gegeben werden. Konstanten können entweder an den Stellen stehen, wo sie im Programm vorkommen, oder mit einem Namen versehen, und zu beginn definiert, immer wieder verwendet werden. Die Definition der letzteren Art wird mit dem Schlüsselwort "CONST" eingeleitet. Danach folgen die Namen, getrennt durch ein Gleichzeichen, folgt danach der konstante Ausdruck, dessen Wert sie repräsentieren sollen. Formal: CONST Namen = Konstanter_Ausdruck; .. Namen = Konstanter_Ausdruck; Die Konstanten erhalten den Typ, den der Ausdruck rechts enthält. Dieser muß konstant sein. Beispiel: CONST pi = 4.*ATAN(1.); wurzel_2 = 1.414; gamma = 6.672E-11; dosname = "dos.library"; zweiHoch16 = 65536; (* dezimal *) c_256 = $100; (* hex *) c_bitmuster = %1001001001001; (* binär *) Komplexere Konstanten werden durch einen Typbezeichner gefolgt von einem Doppelpunkt eingeleitet. Danach folgen die Werte, die die konstante haben soll. Ist die Konstante ein Array, werden die Werte in Klammern und durch ein Komma getrennt angegeben. Bei einem Record wird erst der Namen, dann ein Gleichzeichen und zum Schluß der Wert den dieses Element erhalten soll geschrieben. Beispiel: TYPE Vektor = ARRAY [0..2] OF REAL; Matrix = ARRAY [0..2] OF Vektor; Kammera = RECORD position : Vektor; richtung : Matrix; brennweite : REAL; END; CONST StdKammera = Kammera:(position=(120,240,200), richtung=((1,0,0), (0,1,0), (0,0,1)), brennweite=1.8); Elemente, die bei einem Record nicht angegeben werden, werden mit 0 bzw. NIL belegt. SET-Konstanten werden ebenfalls durch ihren Typnamen, und dann einem Doppelpunkt eingeleitet. Danach folgen die Elemente, die in der Menge enthalten sein sollen in Mengenklammern, und durch Komma getrennt. Es ist auch möglich einen ganzen Bereich anzugeben. Dazu muß zuerst die untere Grenze, dann ".." und dann die obere Grenze angegeben werden. Beispiel: TYPE Numset = SET OF [0..31]; CONST leer = Numset:{}; voll = Numset:{0..31}; manche = Numset:{2,4..7,11,13,27..31}; 4.4.4.3. Ausruck und Zuweissung In Assembler sieht eine Anweissung immer folgendermassen aus, erst kommt ein Befehl, dann die dazugehörigen Operanden. Diese Zweiadressen Form ist unübersichtlich und verschleiert den Inhalt des Programms. Eine der grundlegensten Strukturen in höheren Programmiersprachen sind Ausdrücke. Dies sind folgen von Operanden, Funktionen und Operatoren, wie sie aus der mathematik bekannt sind. Beispiel: a+3*(b+c) MOVE.W b(A4),D7 ADD.W c(A4),D7 MULU.W #3,D7 ADD.W a(A4),D7 Man sieht deutlich, daß die Struktur des Ausdrucks wesentlich einfacher zu verstehen ist, als die der Zweiadressen Form. Neben den bekannten Operatoren für numerische Ausdrücke +,-,*,/,^ gibt es noch welche für Boolsche ausdrücke. NOT : liefert das Gegenteil der Aussage, die danach folgt AND : liefert nur wahr, wenn der rechte und linke Teil der Aussage wahr ist OR : liefert dann wahr, wenn mindestens der linke oder rechte Teil der Aussage wahr ist. Beispiel: Es soll geprüft werden, ob a zwischen -10 und 10 liegt, aber nicht 3 oder -3 ist. (a>-10) AND (a<10) AND NOT ((a=3) OR (a=-3)) Folgende Operatoren existieren für ganze Zahlen: + : Summe - : Differenz oder Negation * : Produkt DIV : Ganzzahldivision MOD : Rest der Division SHL : Linksschieben SHR : Rechtsschieben Folgende Operatoren existieren für gebrochene Zahlen: + : Summe - : Differenz oder Negation * : Produkt / : Quotient ^ : Potenz Folgende Operatoren existieren für Mengen: + : Vereinigung * : Schnittmenge - : Differenz / : symmetrische Differenz Folgende Relationen existieren für Mengen: = : gleich # : ungleich <= : Teilmenge >= : Obermenge Folgende Relationen existieren für numerische Type, Zeichen und Zeiger: > : größer < : kleiner = : gleich # : ungleich <= : kleiner oder gleich >= : größer oder gleich OF : Intervall bzw. Aufzählungstest Die Relation OF erlaubt es zu prüfen, ob ein Wert in einem Intervall oder einer Aufzählung von Elementen oder Intervallen vorhanden ist: Beispiel: a OF 3..7,11,22,4..7 Liefert wahr, wenn gilt: (a>=3) AND (a<=7) OR (a=11) OR (a=22) OR (a>=4) AND (a<=7) Es dürfen auch Variablen rechts stehen. Um zum Beispiel zu prüfen, ob ein Punkt in einem Rechteck liegt: TYPE Point = ARRAY [0..1] OF INTEGER; Rect = RECORD topLeft : Point; bottomRight : Point; END; VAR p : Point; rect : Rect; ... (p[0] OF rect.topLeft[0]..rect.bottomRight[0]) AND (p[1] OF rect.topLeft[1]..rect.bottomRight[1]) ... Für Mengen gibt es noch eine Relation, die angibt, ob ein Element in ihr enthalten ist : "IN". VAR s : SET OF [0..31]; i : INTEGER; i IN s MOVE.L s(A4),D7 MOVE.W i(A4),D6 BTST D6,D7 Ausdrücke werden in Cluster nach den üblichen Regeln ausgewertet, also Punkt vor Strich, Klammern zuerst und sonst von links nach rechts. Folgende Prioritäten sind vorgegeben: 1.) ^ : Dereferenzierung 1.) . : Recordelement 1.) [] : Index bei Arrays 1.) ..() : Funktionsauswertung 1.) ´ : Attribut 2.) - : unäres minus 2.) NOT : complement 2.) () : Klammerausdrücke 3.) ^ : Potenzierung 4.) * : Produkt 4.) / : Division 4.) AND : logisches UND 4.) SHL : linkschieben 4.) SHR : rechtsschieben 4.) DIV : Ganzzahldivision 4.) MOD : Rest der Division 5.) + : Summe 5.) - : Differenz 5.) OR : logisches Oder 6.) Relationen Folgende Standartfunktionen sind vorhanden: SIN(REAL) : REAL : Sinus im Bogenmaß COS(REAL) : REAL : Cosinus TAN(REAL) : REAL : Tangens ASIN(REAL) : REAL : Arcussinus im Bogenmaß ACOS(REAL) : REAL : Arcuscosinus ATAN(REAL) : REAL : Arcustangens SINH(REAL) : REAL : Sinushyperbolicus COSH(REAL) : REAL : Cosinushyperbolicus TANH(REAL) : REAL : Tangenshyperbolicus SQRT(REAL) : REAL : Quadratwurzel LN(REAL) : REAL : natürlicher Logarithmus LOG(REAL) : REAL : dekadischer Lograithmus EXP(REAL) : REAL : e^x CEIL(REAL) : REAL : aufrunden FLOOR(REAL) : REAL : abrunden ABS(REAL) : REAL : Betrag ODD(INT) : BOOLEAN : liefert wahr, wenn INT ungerade ist LMUL(INT,INT) : LONG : 16Bit auf 32bit Multiplikation LDIV(LONG,INT) : INT : 32Bit durch 16Bit auf 16Bit Division REG(INT) : LONG : liefert den Inhalt des Registers INT Machmal ist es nötig, den Typ eines Objektes zu wechseln, um z.B. den ASCII-Wert eines Zeichens zu erhalten oder umgekehrt. Ein Typtransfer kann auf zwei Arten geschehen. CAST(Typ,Object) : Object : Wandelt den Typ des Objects in den neuen Typ um. Dies geht nur, wenn beide Typen die selbe Länge haben. Beispiel: TYPE Vector = RECORD x,y,z : REAL END; Array3 = ARRAY [0..2] OF REAL; VAR v : Vector; ... v.y:=CAST(Array3,v)[1] ... Typ(Object) : Wandelt den das Object in den Typ Typ um. Dies geht nur bei einfachen Typen. Hierbei wird nicht nur der Typ geändert, sondern das Objekt selbst umgewandelt. Beispiel: VAR i : INTEGER; ... i:=INTEGER(SIN(REAL(i))*256.) ... Jeder Typ und jedes Objekt hat gewisse Attribute. Diese können mit Objekt´XXX ermittelt werden. Folgende Attribute existieren: SIZE : Größe eines Typs in Bytes RANGE : Anzahl der Element eines Arrays MIN : Kleinstes Element eines einfachen Typs, oder kleinster Index eines Arrays MAX : Größtes Element eines einfachen Typs, oder größter Index eines Arrays PTR : Zeiger auf das Objekt ADR : Addresse des Objekts Beispiel: VAR add : Adresse; p : POINTER TO Adresse; ... p:=add´PTR ... Weist p die Adresse von add als Pointerziel zu. Da diese Attribute meist konstant sind, können sie auch in konstanten Ausdrücken erscheinen. TYPE List = POINTER TO Node; Node = RECORD data : INTEGER; next : List; END; CONST list = Node:(data=4 ,next= Node:(data=5 ,next= Node:(data=11,next= Node:(data=22,next=NIL)´PTR)´PTR)´PTR)´PTR; Erzeugt eine konstante Liste mit den Elementen 4,5,11 und 22. Da es manchmal nötig ist, daß zwei Konstanten gegenseitig ihre Adressen benötigen (z.B. doppelt verkettete List) ist es möglich, eine Konstante zu deklarieren, bevor sie mit Werten gefüllt wird. Dazu wird sie zuerst ohne Doppelpunkt und Inhalt deffiniert, um dann Später mit einer vollen Deklaration belegt zu werden. TYPE List = POINTER TO Node; Node = RECORD data : INTEGER; prev, next : List; END; CONST Node2 = Node; Node3 = Node; Node1 = Node:(data=4,prev=NIL,next=Node2´PTR); Node2 = Node:(data=6,prev=Node1´PTR,next=Node2´PTR); Node3 = Node:(data=8,prev=Node2´PTR,next=NIL); list = Node1´PTR; Die wichtigste Anweissung in höheren Programmiersprachen ist die Zuweissung. Hierbei wird einer Variable ein Wert zugewiessen. Sie entspricht in etwa dem MOVE Befehl. Eine Zuweissung hat folgendes Format: Zielvariable := Ausdruck; Hierbei muß die Zielvariable zuweissungskompatibel zum Ausdruck sein. Dies ist dann der Fall, wenn wenn sie den selben oder einen Verwandten Typ wie der Ausdruck hat. 4.4.5. Strukturen statt Sprünge In Assembler wird der Programmfluß durch bedingte und feste Sprünge festgelegt. Da diese es allerdings erschweren, den Sinn eines Programmteiles zu erkennen, wird in modernen höheren Programmiersprachen auf Sprünge völlig verzichtet. An ihre Stelle treten Strukturen. Die einfachste Struktur dieser Art ist die Bedingung : IF. IF Boolausdruck_1 THEN Anweissungen_1 OR_IF Boolausdruck_n THEN Anweissungen_n ELSE Anweissungen END; Zuerst wird der Boolausdruck_1 ausgewertet. Ist er wahr, werden die Anweissungen_1 ausgeführt, und danach das Programm hinter END fortgeführt. War der Ausdruck falsch, wird bei OR_IF mit der Überprüfung fortgefahren. War keiner der Ausdrücke wahr, so werden die Anweissungen nach ELSE ausgeführt. Ein ELSE Teil ist nicht nötig, und kann weggelassen werden. Beispiel: Maximum zweier Zahlen a und b nach m: IF a>b THEN MOVE.W a(A4),D7 CMP.W b(A4),D7 BLE else m:=a MOVE.W a(A4),m(A4) ELSE BRA end else: m:=b MOVE.W b(A4),m(A4) END end: Es ist auch möglich einen Schlüsselwert zu verwenden, um eine Auswahl zu treffen. Dies wird durch das Schlüsselwort "KEY" angezeigt. IF KEY Ausdruck OF Ausdruckliste_1 THEN Anweissungen_1 END OF Ausdruckliste_2 THEN Anweissungen_2 END ... OF Ausdruckliste_n THEN Anweissungen_n END OR_IF ... Hierbei wird der Ausdruck von oben nach unten mit den Ausdrucklisten (siehe oben bei "OF") verglichen. Passt einer, wird die zugehörige Anweissungsfolge durchlaufen, und die IF- Struktur verlassen. Trifft kein Fall zu, wird der nächste OR_IF- Fall geprüft. Neben der Bedingungsstruktur gibt es noch eine Anzahl Schleifenstrukturen. Die einfachste ist die REPEAT.. UNTIL Schleife. REPEAT Anweissungen UNTIL Boolausdruck; Zuerst werden die Anweissungen durchgeführt. Dann wird der Boolausdruck geprüft. Ist er falsch, wird wieder bei den Anweissungen fortgefahren. Dies wird wiederholt, bis der Boolausdruck wahr wird. Beispiel: In einem Array mit 1000 BOOLEAN-Elementen sollen all jene auf TRUE gesetzt werden, die Potenzen von 2 sind: VAR i : INTEGER; a : ARRAY [0..999] OF BOOLEAN; ... i:=1; MOVE.W #1,i(A4) REPEAT repeat: a[i]:=TRUE; MOVE.W i(A4),D7 ST a(A4,D7) i:=i*2 MOVE.W i(A4),D7 ADD.W D7,D7 MOVE.W D7,i(A4) UNTIL i>=1000; CMPI.W #1000,i(A4) BLT.S repeat ... Bei einer WHILE-Schleife steht die Bedingung am Anfang, vor dem Schleifenkörper. Eine WHILE-Schleife wird solange ausgeführt, wie mindestens eine der Schleifenbedingungen wahr ist. WHILE Boolausdruck_1 DO Anweissungen_1 OR_WHILE Boolausdruck_n DO Anweissungen_n ELSE Anweissungen END Die Struktur ähnelt der IF-Anweissung, mit dem Unterschied, daß wenn eine der Anweissungsfolen 1 bis n ausgeführt wurde, wieder an den Schleifenanfang gesprungen wird. Es ist auch hier möglich mit KEY und einem Schlüsselwert zu arbeiten. Beispiel: Suchen nach einem Element in einem Array: VAR i : INTEGER; a : ARRAY [0..999] OF INTEGER; ... i:=a´MIN; CLR.W i(A4) WHILE (i<=a´MAX) AND (a[i]#346) DO while: CMPI.W #999,i(A4) BGT end MOVE.W i(A4),D7 ADD.W D7,D7 CMPI.W #346,a(A4,D7) BEQ end i:=i+1 ADDQ.W #1,i END; BRA.S while end: ... Neben diesen Schleifen mit Bedingungen gibt es noch solche, die von einem Wert zu einem Anderen Zählen, FOR-Schleifen. FOR Intergervariable := Ausdruck_1 TO Ausdruck_2 [BY Konstante] DO Anweissungen END; Zuerst wird der Integervariablen der Ausdruck_1 zugewiessen. Danach werden solange die Anweissungen ausgeführt, bis die Variable den Wert von Ausdruck_2 übersteigt. Dazu wird sie nach jedem Schleifendurchlauf um die Konstante erhöht, ist keine angegeben um 1. Ist der Startwert schon größer als der Endwert, wird der Schleifenkörper nicht ausgeführt. Beispiel, ein Array mit nullen füllen. VAR i : INTEGER; a : ARRAY [0..999] OF INTEGER; ... FOR i:=a´MIN TO a´MAX DO CLR.W i(A4) for: a[i]:=0 MOVE.W i(A4),D7 ADD.W D7,D7 CLR.W a(A4,D7) END ADDQ.W #1,i(A4) CMP.W #999,i(A4) BLE.S for: ... Als letzte Schleifenstruktur gibt es noch die LOOP-Schleife. Sie hat keine dedizierte Endbedingung. Sie wird solange ausgeführt, bis sie durch einen EXIT-Anweissung verlassen wird. LOOP Anweissungen; EXIT; Anweissungen; END; Da bei einer LOOP-Schleife nur schwer zu erkennen ist, wann sie endet, sollte sie wann immer möglich vermieden werden (im gesamten Cluster-Compiler gibt es kein einziges LOOP !!!). Beispiel, suchen nach einem Element in einem Array. VAR i : INTEGER; a : ARRAY [0..999] OF CHAR; ... i:=a´MIN; CLR.W i(A4) LOOP loop: IF a[i]="e" THEN MOVE.W i(A4),D7 CMPI.B #"e",a(A4,D7) BNE end1 EXIT BRA end END; end1: INC(i); ADDQ.W #1,i(A4) IF i>a´MAX THEN CMPI.W #999,i(A4) BLE end2 EXIT BRA end END; end2: END; BRA loop end: ... Neben diesen Strukturen, die sich auf den Programmfluß auswirken, gibt es noch eine, die die Arbeit vereinfacht. Häufig kommt es vor, daß man an die Daten mit denen man arbeitet nur über verschlungene Wege herankommt. Dann kann man diesen Objekten mit der WITH-Struktur für einen gewissen Bereich einen neuen Namen geben. WITH Variablenausdruck [AS NeuerName], ... Variablenausdruck [AS NeuerName] DO ... END Beispiel, wir haben eine komplexe Zugriffstruktur und müssen mit zwei Elementen daraus Arbeiten: VAR data : ARRAY [0..255] OF POINTER TO ARRAY [0..255] OF RECORD x,y,z : INTEGER END i1,i2, j1,j2 : INTEGER; Wir wollen nun in das Element i1,i2 das inverse des Elements j1,j2 schreiben. ... WITH data[i1]^[i2] AS dest, MOVE.W i1(A4),D7 ADD.W D7,D7 ADD.W D7,D7 MOVE.L data(A4,D7),A3 MOVE.W i2(A4),D7 MOVE.W D7,D6 ADD.W D7,D7 ADD.W D6,D7 ADD.W D7,D7 LEA (A3,D7),A3 data[j1]^[j2] AS source DO MOVE.W j1(A4),D7 ADD.W D7,D7 ADD.W D7,D7 MOVE.L data(A4,D7),A2 MOVE.W j2(A4),D7 MOVE.W D7,D6 ADD.W D7,D7 ADD.W D6,D7 ADD.W D7,D7 LEA (A2,D7),A2 dest.x:=-source.x; MOVE.W (A2),D7 NEG.W D7 MOVE.W D7,(A3) dest.y:=-source.y; MOVE.W 2(A2),D7 NEG.W D7 MOVE.W D7,2(A3) dest.z:=-source.z MOVE.W 4(A2),D7 NEG.W D7 MOVE.W D7,4(A3) END; ... 4.4.6. Prozeduren Die Entsprechungen zu den Unterprogrammen in Assembler sind in Cluster Prozeduren. Da ein Assembler aber nicht überwacht, ob die Parameterübergabe an Unterprogramme korrekt ist, kann es leicht zu schweren Fehlern kommen. In Cluster gehören die Parameter zur Prozedurdefinition dazu und können so vom Compiler überwacht werden. Die einfachste Prozedur ist die Parameterlose. Ihre Definition hat folgendes Aussehen: PROCEDURE Namen; (* Deklarationsteil *) BEGIN (* Implementationsteil *) END Namen; Im Deklarationsteil einer Prozedur können auch wieder Type, Variablen, Konstanten und sogar neue Prozeduren definiert werden. Alle Dinge, die in einer Prozedur definiert sind, sind nur in ihr sichtbar, sie sind lokal. Dennoch sind alle Dinge die außerhalb bekannt sind auch innerhalb bekannt, es sei denn sie werden durch Definitionen im innern überdeckt. Variablen, die in einer Prozedur deklariert sind, leben nur so lange, wie die Prozedur abgearbeitet wird, da sie auf dem Stack angelegt werden. Ist die Prozedur beendet, werden sie wieder dealloziert. Eine Prozedur wird aufgerufen, indem einfach ihr Namen als Anweissung ins Programm geschrieben wird. Es gibt zwei Verschienden Arten, wie Daten an eine Prozedur übergeben werden können, als Werte- oder Variablenparameter. Bei einem Werteparameter wird der Wert eines Ausdrucks übergeben, bei einem Variablenparameter dagagen die Adresse einer Variablen. Parameter erscheinen für eine Prozedur wie vorbelegte Variablen. Bei der Definition werden sie in Klammern hinter dem Prozedurnamen mit Namen und Typen angegeben. PROCEDURE Namen([VAR] Namen,Namen,... : TypNamen;...;[VAR] ...); ... Eine Prozedur wird aufgerufen, indem ihr Namen mit den Parameter in Klammern ins Programm geschrieben wird. Beispiel, Tauschen zweier Variableninhalte : PROCEDURE Swap(VAR x,y : INTEGER); VAR z : INTEGER; BEGIN z:=x;x:=y;y:=z END Swap; VAR i,j : INTEGER; ... Swap(i,j) ... Alle Libraryfunktionen sind in Cluster als Prozeduren verfügbar. Beispiel, Zeichen einer Geraden: VAR r : RastPortPtr; ... Move(r,20,20); MOVE.L r(A4),A1 MOVEQ #20,D0 MOVEQ #20,D1 MOVE.L GfxBase,A6 JSR -240(A6) Draw(r,100,100); MOVE.L r(A4),A1 MOVEQ #100,D0 MOVEQ #100,D1 JSR -246 ... Es kann einem also nie mehr passieren, daß man einen Parameter ins falsche Register lädt, oder gar die Librarybasis vergisst nach A6 zu schreiben. Es gibt auch eine Reihe von Standartprozeduren, die bereits vorgegeben sind. INC(m) : Erhöht eine Ganzzahl, Zeichen oder Zeiger um eins INC(m,n) : Erhöht m um n DEC(m) : Vermindert eine Ganzzahl, Zeichen oder Zeiegr um eins DEC(m,n) : Vermintert m um n INCL(s,e) : Fügt Element e in die Menge s ein (BSET) EXCL(s,e) : Entfernt das Element e aus der Menge s (BCLR) FLIP(s,e) : Invertiert das Vorhandenseins des Elements e in der Menge s (BCHG) UNI(s1,s2) : Vereinigt die Menge s1 mit der Menge s2 nach s1 SEC(s1,s2) : Schneidet die Menge s1 mit der Menge s2 nach s1 SETREG(reg,val) : Lädt Register reg mit dem Wert vaL INLINE(val,val,val) : Schreibt direkt Opcodes ins Compilat HALT(num) : Bricht das Programm ab, mit Fehlermeldung num Neben diesen Prozeduren gibt es auch noch Funktionen. Diese liefern einen Wert direkt zurück (im allgemeinen in D0), ähnlich wie die Standartfunktionen SIN,COS etc. Bei ihrer Definition wird zusätzlich noch nach einem Doppelpunkt der Typ des Rückgabewerts an. Der Rückgabewert selbst wird mit der RETURN-Anweissung übergeben. PROCEDURE Max(a,b : INTEGER):INTEGER; BEGIN IF a>b THEN RETURN a ELSE RETURN b END END Max; VAR i,j,k : INTEGER; ... i:=Max(j,k) DIV 2; ... RETURN beendet die Ausführung einer Prozedur oder Funktion an der angegebenen Stelle. Ist es unumgehbar, daß eine Prozedur vor ihrer Definition bekannt sein muß, so kann sie ähnlich wie Konstanten Vorwärts deklariert werden. Dazu benutzt man das Schlüsselwort "FORWARD". Danach kommt nur der Prozedurkopf mit Namen und Parametern. Die Prozedur selbst muß dann weiter hinten vollständig definiert werden. FORWARD PROCEDURE a(x : INTEGER):INTEGER; (* ab hier ist a bekannt *) PROCEDURE a(x : INTEGER):INTEGER; ... BEGIN ... END a; (* erst hier wird a definiert *) 4.4.7. Das Modulkonzept Bei größeren Projekten verliert man leicht den überblick, wenn man nur mit einem großen Text arbeitet (ein Freund von mir hat es geschafft für einen bekannten Amgia-Hardwarezusatz einen Assemblertext von mehreren 100KBytes zu schreiben). Es ist sinnvoll größere Projekte in mehrere Compilationseinheiten zu zergliedern. In Assembler ist dies auf zwei Arten möglich, einmal als Include und als externe Referenz. Die erste hat den Nachteil, daß jedesmal, wenn ein Teil übersetzt wird, alle Teile übersetzt werden müssen. Die andere führt zu Inkonsistenzen, da nur Adressen eingfügt werden, und dies vor allem erst beim linken. Cluster bietet die Möglichkeit, ein Programm aus mehreren Modulen zusammen zu setzten, wobei auch eine Typüberprüfung über Modulgrenzen hinweg durchgeführt wird, es aber vermieden wird, importierte Module neu übersetzten zu müssen. Diese Module eignen sich auch als Bibliotheken, aus denen häufig benötigte Prozeduren und Typen importiert werden können. Da Cluster einen selektiven Linker besitzt, führt die Benutzung von Bibliotheken nicht unbedingt zu riesigen Blähprogrammen. Ein Modul gliedert sich in zwei Teile, dem Defitions- oder Schnittstellen- und dem Implementationsmodul, die als getrennte Texte vorliegen müssen, und auch getrennt übersetzt werden. Im Schnittstellenmodul liegen die Definitionen, die von anderen Programmen importiert werden können, im Implementationsteil schliesslich die Implementationen der Prozeduren selbst. Prozeduren erscheinen in einem Definitionsmodul nur mit ihrem Namen und ihren Parametern. Beispiel eines Schnittstellenmoduls: DEFINITION MODULE Listen; TYPE List = POINTER TO Node; Node = RECORD next : List; data : INTEGER; END; PROCEDURE Insert(VAR l : List;d : INTEGER); PROCEDURE First(l : List):INTEGER; END Listen. Das zugehörige Implementationsmodul könnte etwa so aussehen: IMPLEMENTATION MODULE Listen; FROM Heap IMPORT New; PROCEDURE Insert(VAR l : List;d : INTEGER); VAR p : List; BEGIN New(p); p^.next:=l; p^.data:=d; END Insert; PROCEDURE First(l : List):INTEGER; BEGIN RETURN l^.data END First; BEGIN CLOSE END Listen. Funktionen, Prozeduren, Typen, Variablen und Konstanten können aus derartigen Modulen importiert werden. Dies kann auf zwei Arten geschehen. FROM Modul [AS NeuerName] IMPORT Objekt,Objekt,...; Derartig importierte Objekte können direkt mit ihrem Namen bezeichnet werden. IMPORT Modul [AS NeuerName]; Objekte die nicht direkt importiert werden, sondern mit ihrem ganzen Modul importiert werden, müssen über den Modulnamen Qualifiziert werden. Es ist möglich, beim Import ein Modul umzubennen, um nicht immer den ganzen Namen schreiben zu müssen. MODULE ListTest; FROM Listen AS li IMPORT List; FROM InOut IMPORT WriteInt; VAR l : List; BEGIN l:=NIL; li.Insert(l,12);li.Insert(l,332); WriteInt(li.First(l)); CLOSE END ListTest. Der BEGIN-Teil eines importierten Moduls wird immer ausgeführt, bevor auf eine Prozedur dieses Moduls zugegriffen wird. Der CLOSE-Teil wird ausgeführt, wenn ein Programm wieder beendet wird. Mit dieser Technik werden auch die Librarys geöffnet und wieder geschlossen, so daß sich der Programierer nicht mehr darum kümmern muß, er muß lediglich das Modul importieren, das zur Library gehört. Eine Übersicht über alle Standartbibliotheken finden sie in Kapitel 6. Ein ImplementationsModul muß immer nach seinem Schnittstellenmodul übersetzt werden. Wird nur die Implementierung eines Moduls, aber nicht dessen Defintion geändert, braucht weder das Definitionsmodul noch Module, die dieses Importieren neu übersetzt zu werden. Wird jedoch das Definitionsmodul neu compiliert, müssen alle Module die dieses Importieren, sowie das Implementationsmodul neu übersetzt werden. Damit dies problemlos geschieht, verfügt Cluster über ein MAKE, das dies automatisiert. Folgende Personen haben an diesem Projekt mitgearbeitet bzw. unterstützend oder hindernt mitgearbeitet. Ulrich Sigmund Autor von Compiler Editor etc, sowie einiger Module, sowie einige Handbuchkapitel Thomas Pfrengle Koautor beim Compiler, Autor der meisten Module und Kapitel. Jean-Claude Aparisi Manager, und Vertrieb. Setzte das Handbuch und lieferte Spagetthi und Pizza. Träger unserer Krawatte. Norbert Scherer Autor zweier Handbuchkapitel und einiger Demos Tom Richter Korrigierte das Handbuch auf Rechtschreibung und Verständlichkeit Michael Schwarz Stellte das DTP-System für das Handbuch, und beriet in geschäftlichen Dingen. Liegt uns dauernd in den Ohren mit "Warum schreibt ihr den Compiler nicht für Mamas /36 um, dann könnt ihr 50000.- pro Stück nehmen." Steffen Herr Schrieb bzw. schreibt gerade ein Modul für Sounds. Andreas Schmidt Sagte wie immer "Alles Scheiße", wollte dann aber doch einen Compiler. (Dank des integrierten Tetris (Shift+F10). Dieter Lenau und Paul Rottler Für ihre Nachsicht Roland Schauffler "Schreib selle´ Kompailer doch für de´ Arschi um, damit kansch irre Kohle scheffle !!!" Commodore Amiga Durch Produktion dieses fantastischen Rechners Guru Half uns beim Meditieren über mannigfaltige Fehlleistungen von uns und des Kickstarts. Meinem Prof Hat bis heute keine Anhnung von Compilern. Die Autoren von "C" Gaben ein schlechtes Beispiel !!! Modula2, Ada und "C" Wichtige Anregungen