EUMEL-Benutzerhandbuch TEIL 6: Erste Hilfe in ELAN Vorwort Dieser Teil des EUMEL-Handbuchs ist keine "Einführung in die Programmierung mit ELAN", sondern ist als Begleitmaterial für einen ELAN-Kurs gedacht. Zudem beschreibt das Skript nicht die vollständige Sprache; dafür ist die Sprachbeschreibung und das Handbuch vorgesehen. Folgende ELAN-Bücher sind z.Zt. erhältlich: Klingen / Liedtke: Programmieren mit ELAN Teubner, Stuttgart, 1982 Jähnichen u.a.: Systematisches Programmieren mit ELAN W. de Gruyter, 1982 Wir haben in dieses Skript auch einige Aufgaben mit aufgenommen, die An- fänger auf jeden Fall lösen sollten. Die Aufgaben dienen aber nur dazu, das erlernte Wissen über ELAN zu überprüfen und sind keine eigentlichen Program- mieraufgaben, die es im begleitenden Kurs geben sollte. Es gibt zwei Arten von Aufgaben: a) HSG (Hätten Sie's gewußt?): Aufgaben, die das bis dahin Gelernte über- prüfen sollen. b) TSW (Trau', Schau', wem!): Aufgaben mit Programmen, die Fehler enthalten können. Alle Programme dieses Skripts sind übrigens von der Art TSW. Es ist auch sinnvoll und notwendig, möglichst viele Programme dieses Skripts direkt auf dem Terminal zu probieren und zu verändern. Auf diese Weise wird ein Anfänger auch mit den Fehlermeldungen des ELAN-Compilers vertraut. Das erste Programm Gleich am Anfang einer Programmierlaufbahn haben Anfänger eine schwierige Hürde zu nehmen: das erste Programm "zum Laufen" zu bringen. Das wird einem Anfänger meist nicht leicht gemacht: schließlich hat er mit dem Betriebs- system eines Computers zu kämpfen. Ein #ib#Betriebssystem#ie# sorgt u.a. für die Steuerung so unterschiedlicher Peripheriegeräte wie Drucker, Lochkarten- leser, Magnetplatten und -bänder usw. Zusätzlich hat es dafür Sorge zu tragen, daß Informationen sicher gespeichert werden und nicht unbeabsichtigt verändert werden können. Letztendlich hat ein Betriebsystem die Aufgabe, die Aufträge von Benutzern ("jobs") - und das können mehrere auf einmal sein - sicher und effizient bearbeiten zu lassen. Um mit einem Betriebsystem "sprechen" zu können, ist meist eine eigene Sprache vorhanden, die Kommando- sprache ("job control language", abgekürzt: JCL). Eine Kommandosprache kann - auf Grund der vielfältigen Aufgaben, die mit ihrer Hilfe formuliert werden müssen - mehr oder weniger kompliziert sein. Zusätzlich sind Kommandosprachen sehr unterschiedlich: aus leicht einsichti- gen Gründen wollen sich Hersteller nicht auf eine Kommandosprache einigen. Deshalb können wir die Anweisungen in einer speziellen Kommandosprache hier nicht angeben; man erfragt diese am besten. Auf jeden Fall muß etwas getan werden, um ein Programm auf einem Rechner "zum Laufen" zu bringen. Wie bereits erwähnt, beschränken wir uns hier auf die eigentlichen Programme. Um den Mechanismus mit den Anweisungen an das Betriebsystem von Anfang an kennen zu lernen, denken wir uns ein sehr einfaches Programm aus, das wir bearbeiten lassen wollen. Programm 1: put ("Hallo: mein erstes Programm") Dieses Programm muß nun dem Rechner zur Bearbeitung übergeben werden. Auch hier treffen wir auf Unterschiede bei den verschiedenen Rechensystemen: bei einigen Rechnern muß ein solches Programm (mit Anweisungen der Kommandosprache) auf Lochkarten übertragen werden, bei anderen dagegen tippt man das Programm direkt an einem Sichtgerät ("Terminal") ein. Die Ausgabe erfolgt dann über einen Schnelldrucker oder auch über das Sichtgerät. Um von Geräten bestimmter Installationen zu abstrahieren, nennen wir im folgenden das Eingabemedium #ib#Eingabegerät#ie# und das Gerät, auf dem die Resultate erscheinen, dementsprechend Ausgabegerät. Aufgabe (TSW): Versuchen Sie, Programm 1 auf dem Rechner Ihrer Installation zu "rechnen". Übungsziel: Umgang mit dem Betriebsystem Das Ergebnis unseres ersten Programms ist nun das Erscheinen des Textes: 'Hallo: mein erstes Programm'. Was ist hier passiert? Da ein Rechner ein ELAN-Programm meist nicht direkt ausführen kann, muß es in eine Form gebracht werden, die der Rechner "versteht". Diese Form ist wiederum eine (sehr andersartige und - für Menschen - nicht leicht verständliche) Sprache, die Maschinensprache. Man muß also ein ELAN-Programm übersetzen. Dies wird von einem Programm (und nicht etwa einer festverdrahteten Schaltung) vorge- nommen, einem Übersetzer. Eine bestimmte Art von Übersetzer heißt Compiler; er übersetzt ein Programm als Ganzes (im Gegensatz zu einem Interpreter, der nur einzelne Anweisungen übersetzt und anschließend ausführt). Darum sind bei ELAN-Programmen, die meist durch Compiler übersetzt ("kompiliert") werden, zwei Phasen zu unterscheiden: a) Übersetzungsphase: In dieser Phase wird ein ELAN-Programm (man spricht von Quellprogramm bzw. "source program") in ein äquivalentes Maschinenprogramm (Objektpro- gramm) transformiert. Dabei überprüft der Übersetzer das Quellprogramm auf eventuelle Fehler (Anweisungen, die nicht der ELAN-Sprachbeschreibung entsprechen). Bei solchen Fehlern, die ein Compiler entdecken kann, spricht man von syntaktischen Fehlern oder von Fehlern zur Übersetzungs- zeit. b) Bearbeitungsphase: In dieser Phase ("run time") wird das übersetzte (Maschinen-) Programm abgearbeitet. Auch hier können Fehler auftreten (z.B. wenn auf einen Wert vom Programm zugegriffen wird, der noch gar nicht berechnet wurde). Solche Fehler nennt man Laufzeitfehler. Haben wir das erste Programm so geschrieben, wie oben angegeben, dürften keine Fehler entdeckt werden und das Programm wird (hoffentlich korrekt, d.h. mit den geforderten Ergebnissen) beendet. Was für ein Programm haben wir nun geschrieben bzw. was haben wir vom Rechner verlangt? Das Wort 'put' bezeichnet eine Prozedur. Eine Prozedur ist ein Algorithmus (hier mit Parametern): eine bestimmte Sammlung von Anweisungen und unter Umständen Daten. Eine solche Prozedur können wir in einem Programm unter einem Namen (nämlich 'put') ansprechen und ausführen lassen. Man spricht dann von dem Aufruf einer Prozedur, wenn ein Prozedurname geschrieben wird. Von einer Prozedur brauchen wir nur zu wissen, was die Prozedur macht, aber gottseidank nicht, wie sie es macht. Eine Prozedur wie 'put' ist vorgefertigt und einfach benutzbar, wobei wir später sehen werden, wie man solche Prozeduren selber schreiben kann. Die Prozedur 'put' hat einen Parameter, nämlich den in Klammern geschriebenen Text, der auf dem Ausgabegerät ausgegeben werden soll. Wir können eine solche Prozedur auch mit anderen Parametern versehen und mehrmals aufrufen: Programm 2: put ("Programm:"); put (2) Mit dem zweiten Programm ist es uns gelungen, ein Programm mit zwei Anwei- sungen zu schreiben (dabei ist der Parameter bei dem ersten Aufruf der 'put'-Prozedur ein Text, beim zweiten Parameter eine ganze Zahl). ELAN ist eine formatfreie Sprache, d.h. Anweisungen können so auf eine Zeile verteilt werden, wie es uns gefällt und zweckmäßig erscheint. Programm 3: put ("mein"); put (3); put (".Programm") Man kann also eine oder mehrere Anweisungen auf eine Zeile schreiben oder eine Anweisung über mehrere Zeilen. Das setzt jedoch voraus, daß die Anwei- sungen voneinander getrennt werden (schließlich muß der Übersetzer erkennen können, wo eine Anweisung anfängt und aufhört). Das ist besonders notwendig, weil man in Namen in ELAN beliebig Leerzeichen zur besseren Lesbarkeit verwenden kann. Programm 4: p u t ( "aha"); put ("aha") Beide Anweisungen bewirken also das Gleiche. Die Trennung von Anweisungen erfolgt in ELAN durch das Trennsymbol Semikolon. Es bedeutet soviel wie: "führe die nächste Anweisung aus". Aus diesem Grund darf hinter der letzten Anweisung eines Programms kein Semiko- lon geschrieben werden (es folgt ja auch keine Anweisung mehr). Der Aufruf einer Prozedur (wie z.B. 'put') verlangt von unserem Rechner im- mer eine Leistung. Wollen wir aber in einem Programm eine Bemerkung schrei- ben (z.B. um uns etwas zu merken), dann können wir einen Kommentar schrei- ben, der vom Übersetzer überlesen und somit keinen Einfluß auf die Aus- führung eines Programms hat. Ein Kommentar in ELAN wird durch die Zeichen (* und *) eingeschlossen und darf über mehrere Zeilen gehen. Kommentare sind in ELAN aber nur in wenigen Fällen notwendig, weil wir Programme durch andere Mittel gut lesbar machen können. Ziel der Programmierung Was wollen wir eigentlich mit dem Programmieren von Computern erreichen? Häufig wiederkehrende und somit oft langweilige Tätigkeiten oder solche, die besonders schnell erledigt werden müssen, sollen von dem "Werkzeug Computer erledigt werden. Gehaltsberechnungen, Unterstützung beim Schreiben von Texten, Katalogsysteme für Bibliotheken, Steuerung von Walzstraßen usw. sind typische Aufgaben für Computer. Bei der Programmierung wird also versucht, Objekte (wie z.B. Geld bei einer Gehaltsberechnung) und Prozesse (wie z.B. die Simulation von Wirtschaftsab- läufen) der realen Welt mit Hilfe von Programmen in einem Computer nachzu- bilden und nach bestimmten Vorstellungen so zu verändern, daß man "brauch- bare" Ergebnisse erlangt. In einem Programm sind Befehle an einen Rechner für eine solche Abbildung enthalten. Die Befehle in einem Programm werden Anweisungen genannt. Ein Programmierer muß also folgendes tun: 1. Abbilden von Objekten und Prozessen der realen Welt in ein Programm. Dabei müssen die Bedingungen der Aufgabe beachtet werden. Was das Pro- gramm Programm leisten soll, wird darum in einer Spezifikation festgelegt. 2. Einbringen des Programms in einen Rechner und Bearbeitung desselben. Die Formulierung von Anweisungen in einem Programm erfolgt in einer bestimmten Sprache, nämlich einer Programmiersprache. 3. Interpretation der Ergebnisse. Das Konzept des Datentyps Befassen wir uns vorerst nur mit Objekten. Sicherlich gibt es sehr viele Ob- jekte in unserer Welt. Einige von ihnen haben aber gleiche Eigenschaften: - Fahrzeuge (Autos, Mofas, Dreiräder) bringen uns von Ort A nach Ort B. - Geld (Münzen, Geldscheine, Murmeln, Franc, DM) erlaubt es, etwas zu kaufen. - Schreibgeräte (Bleistift, Kugelschreiber, Schreibmaschine) sind die Werk- zeuge von Leuten, die etwas zu schreiben haben. - ... Es ist also möglich, einige Objekte der realen Welt in Klassen zusammenzu- fassen. Eine solche Zusammenfassung kann hinsichtlich gleicher Eigenschaften bzw. gleicher Operationen, die für solche Objekte zugelassen sind, erfolgen. Eine Klasse von Objekten mit gleichen Eigenschaften wird in Programmier- sprachen Datentyp genannt. Dabei hat ein Datentyp immer einen Namen, der die Klasse von Objekten sinnvoll kennzeichnet. Als ein Datenobjekt wird ein Exemplar eines Datentyps (also ein spez. Objekt einer Klasse) bezeichnet. Datentypen sind in ELAN ein zentrales Konzept. Jedes der in einem ELAN- Programm verwandten Datenobjekte hat einen Datentyp; somit kann man Daten- typen auch als Eigenschaften von Datenobjekten ansehen. Für jeden Datentyp sind nur spezielle Operationen sinnvoll. Z.B. sind für einen Datentyp "UBoot" die Operationen "erstellen", "tauchen", "auftauchen", "versenken" und "lieber nicht verwenden" sinnvoll, aber nicht die Operation "+" wie bei ganzen Zahlen. Man kann nun Übersetzern die Aufgabe überlassen zu überprüfen, ob stets die richtige Operation auf einen Datentyp angewandt wird. Aufgabe (HSG): Was ist ein Datentyp? Welche Funktion erfüllen Datentypen? Übungsziel: Datentyp-Konzept Einige Datentypen spielen bei der Programmierung eine besondere Rolle, weil sie häufig benötigt werden. In ELAN sind das die Datentypen für - ganze Zahlen. Dieser Datentyp wird INT (für "integer") genannt. - reelle Zahlen (REAL). - Zeichen und Zeichenfolgen (TEXT). - Wahrheitswerte (BOOL). Diese Typen werden in ELAN elementare Datentypen genannt. Für effiziente Rechnungen mit elementaren Datentypen gibt es in den meisten Rechnern spezielle Schaltungen, so daß die Hervorhebung und besondere Rolle, die sie in Programmiersprachen spielen, gerechtfertigt ist. Zudem hat man Werte-Darstellungen innerhalb von Programmen für die elementaren Datentypen vorgesehen, was wir im nächsten Abschnitt erklären wollen. Im weiteren Teil dieses Skripts werden wir uns zunächst auf die Behandlung der elementaren Datentypen beschränken. Das bedeutet für den Programmierer, daß er alle Objekte der realen Welt mit Hilfe der elementaren Datentypen in den Computer abbilden muß. Das kann manchmal sehr schwierig sein (wie bilden wir z.B. Personen oder UBoote mit den elementaren Datentypen ab?). Später werden wir dann Möglichkeiten kennenlernen, neue - problemgerechte - Datentypen in ELAN zu formulieren. Denoter (Werte-Repräsentationen) elementarer Datentypen Wenn wir mit Objekten elementarer Datentypen arbeiten, müssen wir die Möglichkeit haben, Werte in ein Programm zu schreiben. Leider kann man einen Wert "an sich" in einem Programm nicht direkt angeben. Schreiben wir z.B. 4711, dann meinen wir zwar einen INT-Wert, haben aber die Ziffern 4, 7, 1 und 1 geschrieben. Der eigentliche Wert wird in unserem Kopf oder - für unsere Zwecke - in einem Rechner gebildet. Die Werte-Darstellungen oder Werte-Repräsentationen, die in ELAN "Denoter" genannt werden, sind für jeden Datentyp unterschiedlich. Wie bereits erwähnt, haben alle Datenobjekte in ELAN (also auch Denoter) nur einen - vom Über­ setzer feststellbaren - Datentyp. Aus der Form eines Denoters ist also der Datentyp erkennbar: - INT-Denoter: Bestehen aus einer Aneinanderreihung von Ziffern. Beispiele: 17, 007, 32767, 0 Führende Nullen spielen bei der Bildung des Wertes keine Rolle (sie werden vom ELAN-Compiler überlesen). Negative INT-Denoter gibt es nicht (wie negative Werte-Darstellungen in einem Programm geschrieben werden, lernen wir bei den Ausdrücken). - REAL-Denoter: Hier gibt es zwei Formen. Die erste besteht aus zwei INT-Denotern, die durch einen Dezimalpunkt getrennt werden. Beispiele: 0.314159, 17.28 Der Dezimalpunkt wird analog der deutschen Schreibweise als Komma verwendet. Negative REAL-Denoter gibt es wiederum nicht. Eine zweite Form wird kurioserweise als "wissenschaftliche Notation" be- zeichnet. Sie findet dann Verwendung, wenn sehr große oder Zahlen, die nahe bei Null liegen, dargestellt werden müssen. Beispiele: 3.0 e5, 3.0e-5 Der (INT-) Denoter hinter dem Buchstaben e gibt an, wie viele Stellen der Dezimalpunkt nach rechts (positive Werte) oder nach links zu verschieben ist. Dieser Wert wird Exponent, der Teil vor dem Buchstaben e Mantisse genannt. - TEXT-Denoter: TEXT-Denoter werden in Anführungszeichen eingeschlossen. Beispiele: "Das ist ein TEXT-Denoter" "Jetzt ein Text-Denoter ohne ein Zeichen: ein leerer Text" "" Beachte, daß das Leerzeichen ebenfalls ein Zeichen ist. Soll ein An- führungszeichen in einem TEXT erscheinen (also gerade das Zeichen, welches einen TEXT-Denoter beendet), so muß es doppelt geschrieben werden: "Ein TEXT mit dem ""-Zeichen" "Ein TEXT-Denoter nur mit dem ""-Zeichen:" """" Manchmal sollen Zeichen in einem TEXT-Denoter enthalten sein, die auf dem Eingabegerät nicht zur Verfügung stehen. In diesem Fall kann der Code- Wert des Zeichens angegeben werden: ""32"" bedeutet z.B. das (ASCII-) Leerzeichen. Der Code-Wert eines Zeichens er- gibt sich aus einer Code-Tabelle (installationsspezifisch), in der jedem Zeichen eine ganze Zahl zugeordnet ist. - BOOL-Denoter: Es gibt nur zwei BOOL-Denoter: TRUE (für "wahr") und FALSE (für "falsch"). Nun wird auch klar, was für Parameter wir in den obigen Programmen verwandt haben. Es waren natürlich TEXT- bzw. INT-Denoter. Aufgabe (TSW): Welche der folgenden Denotationen ist falsch? a) 1. e) 1 . 0 i) 007 b) -1 f) "" j) "Ein "Getuem" stellt sich vor" c) """ g) """" d) "das ist ein text" h) TRUE k) 1.0 e 37 Übungsziel: Lernen von Denotationen ELAN-Datenobjekte Wie bereits erwähnt, wollen wir mit Hilfe von Programmen Datenobjekte so verändern, daß wir erwünschte Ergebnisse erhalten. Meist wird zu dieser Ver- änderung von Datenobjekten "Rechnen" gesagt, obwohl - wie wir gleich sehen werden - nicht nur numerische Objekte manipuliert werden. Die Veränderung der Datenobjekte findet zur "Laufzeit" (nicht zur Übersetzungszeit) im Rechner statt. Die Darstellung eines Werts in einem Rechner zur Laufzeit eines Programms wird #ib#Repräsentation#ie# genannt. Wenn es eindeutig ist, daß es sich nur um die Repräsentation im Rechner handelt, sprechen wir kurz von Werten. Da also ein Datenobjekt wechselnde Werte annehmen kann, brauchen wir eine Möglichkeit, es in einem Programm anzusprechen, egal welchen Wert das Objekt zu einem Zeitpunkt beinhaltet. Zu diesem Zweck können wir einem Datenobjekt einen Namen geben (wie z.B. einen Personennamen, hinter dem sich eine wirk­ liche Person "verbirgt"). Wenn wir also den Namen des Datenobjekts in ein Programm schreiben, dann meinen wir (meist) den Wert des Datenobjekts, den es zu diesem Zeitpunkt besitzt. Nun sollen die zu behandelnden Datenobjekte ja auch neue Werte erhalten. In diesem Fall müssen wir die Speicherstelle finden, in die der neue Wert ge- bracht werden soll. Für diesen Zweck benutzen wir ebenfalls den Namen, zu- sätzlich zu der Angabe einer Operation, durch die das Objekt einen neuen Wert erhalten soll. Diese Operation (Wert "schreiben") nennen wir Zuweisung. Der Zuweisungs-Befehl wird ':=' geschrieben. Beispiel: a := 5 Bedeutet, daß das Datenobjekt mit dem Namen 'a' den Wert '5' erhält. Von manchen Datenobjekten wissen wir, daß wir ihnen nur einmal einen Wert geben wollen. Sie sollen also nicht verändert werden. Oder wir wissen, daß in einem Programmbereich ein Datenobjekt nicht verändert werden soll. Um ein unbeabsichtigtes Verändern zu verhindern, wird in ELAN dem Namen eines Datenobjekts ein zusätzlicher Schutz mitgegeben: das Zugriffsrecht oder Accessrecht. Es besteht aus der Angabe der Worte VAR (für Lesen und Ver- ändern) oder CONST (für ausschließliches Lesen). Die Deklaration (Vereinbarung) von Datenobjekten Wollen wir ein Datenobjekt in einem Programm verwenden, so müssen wir dem Übersetzer mitteilen, welchen Datentyp und welches Accessrecht das Objekt haben soll. Das dient u.a. dazu, nicht vereinbarte Namen (z.B. verschriebene) vom Übersetzer entdecken zu lassen. Weiterhin ist aus dem bei der Deklaration angegebenen Datentyp zu entnehmen, wieviel Speicherplatz für das Objekt zur Laufzeit zu reservieren ist. Beispiel: INT VAR mein datenobjekt Zuerst wird der Datentyp, dann das Accessrecht und schließlich der Name des Datenobjekts angegeben. Wie werden nun Namen in ELAN formuliert? Das erste Zeichen eines Namens muß immer ein kleiner Buchstabe sein. Danach dürfen beliebig viele kleine Buchstaben, aber auch Ziffern folgen. Zur bes- seren Lesbarkeit können (wie bei den obigen Prozedurnamen) Leerzeichen in einem Namen erscheinen, die aber nicht zum Namen zählen. Beispiele: name1 n a m e 1 x27 gehalts konto das ist ein langer name Verschiedene Datenobjekte mit gleichem Datentyp und Accessrecht dürfen in einer Deklaration angegeben werden (durch Kommata trennen). Mehrere Dekla- rationen werden - genauso wie Anweisungen - durch das Trennsymbol voneinander getrennt. Beispiele: INT VAR mein wert, dein wert, unser wert; BOOL VAR listen ende; TEXT VAR zeile, wort Die Initialisierung von Datenobjekten Um mit den so vereinbarten Datenobjekten arbeiten zu können, muß man ihnen eine Wert geben. Hat ein Datenobjekt noch keinen Wert erhalten, so sagt man, sein Wert sei undefiniert. Das versehentliche Arbeiten mit undefinierten Werten ist eine beliebte Fehlerquelle. Deshalb wird von Programmierern streng darauf geachtet, diese Fehlerquelle zu vermeiden. Eine Wertgebung an ein Datenobjekt kann (muß aber nicht) bereits bei der Deklaration erfolgen, was man in ELAN Initialisierung nennt. Beispiele: INT CONST gewuenschtes gehalt :: 12 000; TEXT VAR zeile :: ""; REAL CONST pi :: 3.14159; BOOL VAR bereits sortiert :: TRUE Allerdings: für mit CONST vereinbarte Datenobjekte ist die Initialisierung die einzige Möglichkeit, ihnen einen Wert zu geben. Die Initialisierung erfolgt mit Hilfe des '::'-Symbols. Anschließend folgt der Wert, den das Datenobjekt erhalten soll. (In den Beispielen haben wir nur Denoter geschrieben. Es sind aber auch allgemeinere Ausdrücke erlaubt.). Es ist nun möglich, mit der oben erwähnten 'put'-Prozedur auch den Wert von Datenobjekten ausgeben zu lassen. Programm 5: INT VAR nummer :: 5; TEXT CONST bemerkung :: ".Programm"; put (nummer); put (bemerkung) Beachte dabei, daß bei der Aufführung eines Namens in diesem Fall immer der Wert des Datenobjekts gemeint ist. Auch die 'put'-Prozedur druckt nicht etwa den Namen des Datenobjekts oder die Adresse der Speicherstelle, sondern ebenfalls den Wert. Aufgabe (HSG): Welche Aufgabe erfüllen Deklarationen? Was heißt: "Eine Variable hat einen undefinierten Wert"? Was ist eine Initialisierung? Was ist ein CONST-Datenobjekt? Warum müssen CONST-Datenobjekte initialisiert werden? Übungsziel: Verständnis von Deklarationen und Accessrecht Schlüsselworte Einige Worte haben in ELAN eine feste Bedeutung und können somit nicht - wie etwa Namen - frei gewählt werden. Solche Worte werden bei den meisten ELAN-Übersetzern mit großen Buchstaben geschrieben, wie z.B. VAR, CONST, INT oder REAL u.a.m. Wie wir später sehen werden, besteht die Möglichkeit, neue Schlüsselworte einzuführen. Halten wir vorläufig fest, daß feste Bestandteile der Sprache (wie z.B. CONST oder VAR) und Datentypen (wie INT oder REAL) Schlüsselworte sind, also mit großen Buchstaben geschrieben werden. Ausdrücke Nun wäre es natürlich schlecht, wenn Programmierer nicht mehr machen könnten, als Werte ausgeben. Als erste Stufe von etwas komplexeren "Rechnungen" dürfen Ausdrücke gebildet werden. Ausdrücke sind eine Zusammenstellung von Datenobjekten (Denoter, VAR- oder CONST-Objekte) und Operatoren. Schauen wir uns dazu erst ein Programm an: Programm 6: INT CONST wert 1 :: 1, wert 2 :: 2, wert 3 :: 3; put (wert1 + wert2); put (wert2 - wert1); put (wert2 * wert3); put (wert3 DIV wert2); put (wert2 ** wert3) In diesem Programm werden drei Datenobjekte initialisiert. Anschließend werden jeweils die Werte von zwei Objekten addiert (Operatorzeichen: '+'), subtrahiert ('-'), multipliziert ('*'), dividiert (ganzzahlige Division ohne Rest: 'DIV') und potenziert ('**'). Dies sind Operatoren, die zwei Operanden haben: man nennt sie dyadische Operatoren. Die monadischen Operatoren da- gegen haben nur einen Operanden. Beispiel: put ( - wert1) Operatoren in ELAN werden - wie wir an den obigen Beispielen sehen - durch ein oder zwei spezielle Zeichen oder durch große Buchstaben (in den Fällen, in denen kein "vernünftiges" Zeichen mehr zur Verfügung steht) als Schlüssel- wort dargestellt. Als Operanden (also die Datenobjekte, auf die ein Operator "wirken" soll) eines Operators darf ein VAR- oder CONST-Datenobjekt, aber auch ein Denoter verwendet werden. Das Resultat eines Operators (also das Ergebnis einer Berechnung) ist bei den obigen Ausdrücken wieder vom Datentyp INT mit dem Accessrecht CONST. Darum ist es erlaubt, solch einen Ausdruck wiederum als Operanden zu verwenden. Praktisch bedeutet dies, daß wir mehrere Operatoren und Datenobjekte zusammen in einem Ausdruck haben dürfen. Programm 7: INT CONST wert 1 :: 1, wert 2 :: 2, wert 3 :: 3; put (wert2 + 3 - wert2 * wert3); put (- wert2 * wert3) Nun haben wir eine Schwierigkeit: Der Ausdruck in der ersten 'put'-Anweisung ist mehrdeutig, d.h. kann - je nach Reihenfolge der Auswertung - unter- schiedliche Ergebnisse als Resultat liefern. Beispiel: a) (wert2 + 3 = 5) - (wert2 * wert3 = 6) = -1 b) ((wert2 + 3 = 5) - wert2 = 3) * 3 = 9 Es kommt also auf die Reihenfolge der Auswertung von Operatoren an. Diese kann man durch die Angabe von Klammern steuern. Beispiel: (a + b) * (a + b) Es wird jeweils erst 'a + b' ausgewertet und dann erst die Multiplikation durchgeführt. In ELAN ist es erlaubt, beliebig viel Klammernpaare zu ver- wenden (Regel: die innerste Klammer wird zuerst ausgeführt). Es ist sogar zulässig, Klammern zu verwenden, wo keine notwendig sind, denn überflüssige Klammernpaare werden überlesen. Beispiel: ((a - b)) * 3 * ((c + d) * (c - d)) Somit können wir beliebig komplizierte Ausdrücke formulieren. (Was man aber vermeiden sollte, weil sie leicht zu Fehlern führen. Stattdessen kann man einen komplizierten Ausdrücke in mehrere (einfachere) zerlegen.) Um solche Ausdrücke einfacher zu behandeln und sie so ähnlich schreiben zu können, wie man es in der Mathematik gewohnt ist, wird in Programmiersprachen die Reihenfolge der Auswertung von Operatoren festgelegt. In ELAN wurden neun Ebenen, Prioritäten genannt, festgelegt: Priorität Operatoren 9 alle monadischen Operatoren 8 ** 7 *, /, DIV, MOD 6 +, - 5 =, <>, <, <=, >, >= 4 AND 3 OR 2 alle übrigen, nicht in dieser Tabelle aufgeführten dyadischen Operatoren 1 := (Die bis jetzt noch nicht erwähnten Operatoren in der Tabelle werden wir in den weiteren Abschnitten besprechen.) Operatoren mit der höchsten Priorität werden zuerst ausgeführt, dann die mit der nächst höheren Priorität usw. Operatoren mit gleicher Priorität werden von links nach rechts ausgeführt. Dadurch ergibt sich die gewohnte Abarbei- tungsfolge wie beim Rechnen. Beispiel: -2 + 3 * 2 ** 3 a) -2 b) 2 ** 3 c) 3 * (2 ** 3) d) ((-2)) + (3 * (2 ** 3)) Wie bereits erwähnt, ist es immer erlaubt, Klammern zu setzen. Ist man sich also über die genaue Abarbeitungsfolge nicht im Klaren, so kann man Klammern verwenden. Aufgabe (HSG): Welche INT-Werte ergeben sich? a) 14 DIV 4 e) -14 DIV -4 b) + 14 DIV 4 f) 2 * 3 DIV 2 ** 2 * 4 c) -14 DIV 4 g) 2 ** 3 ** 4 d) 14 DIV -4 h) 3 + 4 * 2 + 3 Übungsziel: Arithmetische Ausdrücke Aufgabe (HSG): Bilden Sie für folgende mathematische Formeln entsprechende ELAN- Ausdrücke: a b a+b a) - c d) a g) - --- b c a+b b a c b) --- e) -a h) - * - c+d b d a+b -b c c) --- e f) a i) (a*b) c+d Übungsziel: Arithmetische Ausdrücke formulieren Generische Operatoren und Prozeduren Bis jetzt wurden nur Ausdrücke mit INT-Operanden verwendet. Wie sieht es jetzt mit REALs aus? Programm 8: put (1.0 + 2.0); put (2.0 - 1.0); put (2.0 * 3.0); put (3.0 / 2.0); put (2.0 ** 3.0) Man beachte die Unterschiede zum Programm 7: Wir müssen nun REAL-Denoter verwenden (mit INT-Denotern zu arbeiten wäre ein Fehler). Der Divisions- Operator hat sich nun von 'DIV' zu '/' gewandelt. Die Ergebnisse sind nun nicht INT-, sondern REAL-Werte. Für die Reihenfolge der Auswertung der Operatoren sowie die Verwendung von Klammern gilt das für INT-Ausdrücke gesagte. Wir haben den '+'-Operator in zwei verschiedenen Formen gesehen: in Programm 7 mit Operanden vom Datentyp INT, ein INT-Resultat liefernd, und in Programm 8 das gleiche mit REALs. Es liegen also zwei verschiedene Operatoren vor, die aber den gleichen Namen (Zeichen: '+') haben. In ELAN ist es somit möglich, unterschiedlichen Operatoren (aber auch Proze- duren) gleiche Namen zu geben. Solche Operatoren werden generische Opera- toren genannt. Ein ELAN-Compiler wählt den richtigen Operator aufgrund der Datentypen der Operanden aus. Oft werden die verfügbaren Operatoren wie folgt dokumentiert: INT OP + (INT CONST links, rechts) Diese Form nennt man einen "Operator-Kopf". Sie wird in ELAN-Programmen bei der Definition von Operatoren benötigt. Dabei steht OP für "OPERATOR". Die Angabe des Datentyps davor gibt den Datentyp des Resultats des Operators an. Zwischen 'OP' und der öffnenden Klammer steht der Name des Operators (hier: '+'). In den Klammern werden die Datentypen und das Accessrecht der Operanden angegeben. CONST bedeutet hier: der Operand darf vom Operator nicht verändert werden, während bei VAR (was normalerweise ja nicht sein sollte!) ein Operand bei der Abarbeitung eines Operators verändert werden kann. Damit wir solche Definitionen besser beherrschen, geben wir noch weitere Beispiele an: INT OP - (INT CONST operand) REAL OP / (INT CONST l, r) Bei dem ersten Operator handelt es sich um den monadischen Operator '-' für INT-Operanden (z.B.: 'INT VAR a :: 1; put (-a)'), während es sich bei dem zweiten Operator um eine Divisions-Operator handelt, der jedoch ein REAL- Resultat liefert (z.B.: 'put (3 / 2)' liefert 1.5). Der MOD-Operator liefert den Rest einer Division: INT OP MOD (INT CONST l, r) REAL OP MOD (REAL CONST l, r) Die Beschreibung von generischen Prozeduren verläuft analog. Beispiele: PROC put (INT CONST wert) PROC put (REAL CONST wert) Hier wird das Wort 'OP' durch 'PROC' (für 'PROCEDURE') ersetzt. Die Angaben in Klammern bezeichnen nun nicht Operanden, sondern Parameter. Über die verfügbaren Operatoren und Prozeduren für INT- und REAL-Datenob- jekte kann man sich im ELAN-Handbuch oder im EUMEL-Benutzerhandbuch infor- mieren. Einige - aber nicht alle - der Operatoren und Prozeduren (auch für andere Datentypen) werden wir erklären, wenn wir sie in Programmen benötigen. Die Zuweisung Ein spezieller Operator ist die Zuweisung (Zeichen: ':='). Dieser Operator hat immer die geringste Priorität, wird also immer als letzter eines Aus- drucks ausgeführt. Die Zuweisung wird verwendet, um einer Variablen einen neuen Wert zu geben. Beispiel: a := b Hier wird der Wert von 'b' der Variablen 'a' zugewiesen. Der vorher vor- handene Wert von 'a' geht dabei verloren. Man sagt auch, der Wert wird über- schrieben. Auf der rechten Seite (also als rechter Operand) des ':=' Operators darf auch ein Ausdruck stehen. Beispiel: a := b + c Hier wird das Resultat von 'b + c' an die Variable 'a' zugewiesen. Man be- achte dabei die Prioritäten der Operatoren '+' (Priorität 6) und ':=' (Pri- orität 1): die Addition wird vor der Zuweisung ausgeführt. Die Auswertung von Zuweisungen mit Ausdrücken muß immer so verlaufen, da die Zuweisung stets die niedrigste Priorität aller Operatoren hat. Schauen wir uns zum besseren Verständnis die Definitionen des (natürlich auch generischen) Operators ':=' an: OP := (INT VAR ziel, INT CONST quelle) OP := (REAL VAR ziel, REAL CONST quelle) OP := (TEXT VAR ziel, TEXT CONST quelle) OP := (BOOL VAR ziel, BOOL CONST quelle) Der Operator ':=' liefert also kein Resultat (man sagt auch, er liefert keinen Wert) und verlangt als linken Operanden ein VAR-Datenobjekt (an den der Wert der rechten Seite zugewiesen werden soll). Der Wert des linken Operanden wird also verändert. Für den rechten Operanden ist durch CONST sichergestellt, daß er nur gelesen wird. Oft kommt es vor, daß ein Objekt auf der linken und rechten Seite des Zuwei- sungsoperators erscheint, z.B. wenn ein Wert erhöht werden soll. Beispiele: a := a + 1; a := a + 17 Hier wird der "alte", aktuelle Wert von 'a' genommen, um '1' erhöht und dem Objekt 'a' zugewiesen. Man beachte, daß hier in einer Anweisung ein Datenob- jekt unterschiedliche Werte zu unterschiedlichen Zeitpunkten haben kann. In solchen Fällen darf man den Operator INCR verwenden: a INCR 1; a INCR 17 Analoges gilt für den Operator DECR, bei dem ein Wert von einer Variable subtrahiert wird. Also: OP INCR (INT VAR ziel, INT CONST dazu) OP INCR (REAL VAR ziel, REAL CONST dazu) OP DECR (INT VAR ziel, INT CONST abzug) OP DECR (REAL VAR ziel, REAL CONST abzug) Schauen wir uns folgendes Programm an, bei dem zwei Werte vertauscht werden: Programm 9: INT VAR a, b, x; get (a); get (b); x := a; a := b; b := x; put (a); put (b) Wie wir an diesem Beispiel sehen, existieren nicht nur 'put'-Prozeduren, sondern auch 'get'-Prozeduren, die einen Wert vom Eingabemedium einlesen. Es gibt folgende 'get'- Prozeduren (die 'put'-Prozeduren führen wir der Vollständigkeit halber auch mit auf): PROC get (INT VAR wert) PROC get (REAL VAR wert) PROC get (TEXT VAR wert) PROC put (INT CONST wert) PROC put (REAL CONST wert) PROC put (TEXT CONST wert) Aufgabe (HSG): Was versteht man unter Generizität? Übungsziel: Generizitäts-Begriff Refinements Bevor wir die Operationen für TEXTe und BOOLs besprechen, wollen wir eine weitere wichtige Eigenschaft von ELAN diskutieren, nämlich die Namensgebung. Namen für Datenobjekte haben wir bereits kennengelernt. In ELAN ist es eben- falls möglich, Namen für Ausdrücke oder eine bzw. mehrere Anweisungen zu vergeben. Programm 10: INT VAR a, b, x; einlesen von a und b; vertauschen von a und b; vertauschte werte ausgeben. einlesen von a und b: get (a); get (b). vertauschen von a und b: x := a; a := b; b := x. vertauschte werte ausgeben: put (a); put (b). Dies ist das gleiche Programm wie das 9. Beispielprogramm. Für den Namen 'einlesen von a und b' werden die Anweisungen 'get (a); get (b)' vom ELAN-Übersetzer eingesetzt. Man kann also die ersten vier Zeilen des Programms als eigentliches Programm ansehen, wobei die Namen durch die betreffenden Anweisungen ersetzt werden. Eine solche Konstruktion wird in ELAN Refinement genannt. Was wird dadurch erreicht? Durch die sinnvolle Verwendung von Refinements wird ein Programm im Programm und nicht in einer separaten Beschreibung dokumentiert. Weiterhin kann ein Programm "von oben nach unten" ("top down") entwickelt werden: wir haben das obige - zugegeben einfache - Beispielprogramm in drei Teile zerlegt und diese durch Namen beschrieben. Bei der Beschreibung von Aktionen durch Namen sagen wir, was wir machen wollen und nicht wie, denn wir brauchen uns auf dieser Stufe der Programmentwicklung um die Realisierung der Refinements (noch) keine Sorgen zu machen. Das erfolgt erst, wenn wir genauer definieren müssen, wie das Refinement programmiert werden muß. Dabei können wir wiederum Refinements verwenden usw., bis wir auf eine Ebene "herunterge- stiegen" sind, bei dem eine (jetzt: Teil-) Problemlösung sehr einfach ist und wir sie direkt hinschreiben können. Wir beschäftigen uns also an jedem Punkt der Problemlösung nur mit einem Teilaspekt des gesamten Problems. Zudem sieht man - wenn die Refinements einigermaßen vernünftig verwendet werden - dem Programm an, wie die Problemlösung entstanden ist. Die Verwendung von Refinements hat also eine Anzahl von Vorteilen. Schauen wir uns deshalb an, wie die Refinements formal verwandt werden müssen. Das "Hauptprogramm" wird durch einen Punkt abgeschlossen, falls ein Refinement folgt. Ein Refinement besteht aus der Nennung des Refinement-Namens, der von einem Doppelpunkt gefolgt sein muß. In einem Refinement kann eine Anweisung oder mehrere - durch Semikolon getrennt - stehen. Das Refinement wird durch einen Punkt abgeschlossen. Refinements können auch dort verwendet werden, wo ein Wert erwartet wird, z.B. in einem Ausdruck oder einer 'put'-Anweisung. In diesem Fall muß das Refinement natürlich einen Wert liefern. Wie macht man das? Eine Möglichkeit ist, daß im Refinement ein Ausdruck geschrieben wird, der einen Wert als Resultat liefert. Programm 11: INT VAR a :: 1, b :: 2, c :: 3; put (resultat). resultat: (a * b + c) ** 3. Eine Zuweisung liefert - wie bereits erwähnt - kein Resultat. Es ist auch erlaubt, ein Refinement mit mehreren Anweisungen zu schreiben, das einen Wert liefert. Allgemeine Regel: die letzte Anweisung eines Refinements bestimmt, ob ein Refinement einen Wert liefert - und wenn ja, von welchen Datentyp. BOOLesche Operationen Für BOOLesche Datenobjekte gibt es einige Operatoren: BOOL OP AND (BOOL CONST links, rechts) BOOL OP OR (BOOL CONST links, rechts) BOOL OP NOT (BOOL CONST operand) Der Operator AND liefert als Resultat die logische "und"-Verknüpfung (nur wenn beide Operanden den Wert TRUE haben ist das Resultat TRUE, sonst FALSE), OR ist das logische "oder" (nur wenn beide Operanden FALSE liefern, ist das Resultat FALSE, sonst TRUE) und die logische Negation NOT (als Resultat wird das "Gegenteil" geliefert). Ebenfalls wichtig sind die Vergleichs-Operatoren, die zwar keine BOOLeschen Operanden erwarten, aber ein BOOLesches Resultat liefern: BOOL OP = (INT CONST links, rechts) BOOL OP <> (INT CONST links, rechts) BOOL OP < (INT CONST links, rechts) BOOL OP <= (INT CONST links, rechts) BOOL OP > (INT CONST links, rechts) BOOL OP >= (INT CONST links, rechts) Diese Operatoren: = (gleich), <> (ungleich), < (kleiner), <= (kleiner gleich), > (größer), >= (größer gleich) gibt es auch noch für Operanden vom Datentyp REAL und TEXT. Da die Vergleichs-Operatoren ein BOOLesches Resultat liefern, kann man sie in BOOLeschen Ausdrücken verwenden. Zu beachten ist dabei die Priorität der Operatoren: die Vergleiche werden immer vor den Operatoren AND bzw. OR ausgeführt. Programm 12: BOOL CONST kaufen; kaufen := will ich AND NOT zu teuer. will ich: TEXT VAR produktname; get (produktname); produktname = "muesli" OR produktname = "vollkornbrot". zu teuer: INT VAR preis; get (preis); preis > 20. Aufgabe (HSG): Welche BOOL-Werte ergeben sich? a) TRUE AND FALSE e) TRUE AND TRUE OR TRUE b) TRUE OR FALSE f) 10 < 3 AND 17 > 4 c) TRUE AND NOT FALSE g) 17 + 4 = 21 OR TRUE d) NOT TRUE AND FALSE h) TRUE AND FALSE OR TRUE Übungsziel: Boolesche Ausdrücke Abfragen BOOLesche Ausdrücke werden in einer speziellen Anweisung verwandt, der Abfrage: Programm 13: INT VAR a, b; get (a); get (b); IF a > b THEN vertausche a und b END IF; put (a); put (b). vertausche a und b: INT CONST x :: a; a := b; b := x. Das Refinement im THEN-Teil der bedingten Anweisung wird nur durchgeführt, wenn der BOOLesche Ausdruck ('a > b') den Wert TRUE liefert. Liefert er den Wert FALSE, wird die Anweisung, die der bedingten Anweisung folgt (nach END IF), ausgeführt. Programm 13 kann etwas anders geschrieben werden: Programm 14: INT VAR a, b; get (a); get (b); IF a > b THEN put (a); put (b) ELSE put (b); put (a) END IF. Der THEN-Teil wird wiederum ausgeführt, wenn die BOOLesche Bedingung erfüllt ist. Liefert sie dagegen FALSE, wird der ELSE-Teil ausgeführt. Die bedingte Anweisung gibt uns also die Möglichkeit, abhängig von einer Bedingung eine oder mehrere Anweisungen ausführen zu lassen. Dabei können im THEN- bzw. ELSE-Teil wiederum bedingte Anweisungen enthalten sein usw. Solche geschachtelten bedingten Anweisungen sollte man jedoch vermeiden, weil sie leicht zu Fehlern führen können (statt dessen durch Refinements realisieren). Man beachte auch die Einrückungen, die man machen sollte, um die "Zweige" besonders kenntlich zu machen. Aufgabe (HSG): a) In welcher Reihenfolge werden Operatoren ausgewertet? b) Reihenfolge der Auswertung von: a + b + c c) INT VAR a, b, c; ... IF NOT a = 0 AND b = 0 THEN... ergibt einen syntaktischen Fehler. Welchen? d) Wie wird der BOOLesche Ausdruck ausgewertet? INT VAR a :: 0, b :: 4; ... IF a = 0 AND b DIV a > 0 e) Warum ist BOOL VAR ende :: TRUE; ... IF ende = TRUE THEN... Unsinn? Übungsziel: Reihenfolge der Auswertung von Ausdrücken Bei Abfrageketten kann das ELIF-Konstrukt eingesetzt werden. (ELIF ist eine Zusammenziehung der Worte ELSE und IF). Anstatt ... IF bedingung1 THEN aktion1 ELSE IF bedingung2 THEN aktion2 ELSE aktion3 END IF END IF; ... kann man besser ... IF bedingung1 THEN aktion1 ELIF bedingung2 THEN aktion2 ELSE aktion3 END IF; ... schreiben. Die bedingte Anweisung kann auch einen Wert liefern. In diesem Fall muß der ELSE-Teil vorhanden sein und jeder Zweig den gleichen Datentyp liefern (jeweils die letzte Anweisung muß einen Wert liefern). Aufgabe (HSG): Was berechnen folgende (Teil-) Programme? a) INT VAR a; get (a); put (wert). wert: IF a < 0 THEN -a ELSE a END IF. b) INT VAR brutto, netto; get (brutto); berechne gehalt; put ("mein gehalt:"); put (netto). berechne gehalt: IF jahresverdienst > 30 000 (* zu wenig? *) THEN sonderabgabe END IF; netto := brutto - brutto DIV 100 * 20. jahresverdienst: brutto * 12. sonderabgabe: brutto := brutto - brutto DIV 100 * 30 c) INT VAR x; ... put (signum). signum: IF x > 0 THEN 1 ELSE kleiner gleich END IF. kleiner gleich: IF x = 0 THEN 0 ELSE -1 END IF. TEXTe TEXT-Denoter haben wir bereits kennengelernt. Im folgenden Programm stellen wir die Wirkung einiger TEXT-Operationen vor. Programm 15: TEXT VAR a, b, c; a := "ELAN"; b := "-Programm"; c := a + b; put (c) Der Operator TEXT OP + (TEXT CONST links, rechts) liefert als Ergebnis einen TEXT, bei dem an den linken der rechte Operand angefügt wurde (Fachausdruck: "Konkatenation"). Weitere Operatoren: TEXT OP CAT (TEXT VAR ziel, TEXT CONST dazu) TEXT OP * (INT CONST i, TEXT CONST a) TEXT OP SUB (TEXT CONST t, INT CONST pos) Der Operator CAT fügt an einen TEXT einen zweiten an ('a CAT b' wirkt wie 'a := a + b'). Mit dem '*'-Operator kann man einen TEXT vervielfältigen (Beispiel: 17 * "--"), während man mit SUB ein Zeichen aus einem TEXT her- ausholen kann (Beispiel: "ELAN" SUB 3 liefert "A"). Die meisten TEXT-Operationen sind als Prozeduren realisiert, weil mehr als zwei Operanden benötigt werden. Die Wirkung einiger Operationen geben wir in kurzen Kommentaren an: TEXT PROC subtext (TEXT CONST t, INT CONST von) (* rechter Teiltext von 't' von der Position 'von' bis Ende *) TEXT PROC subtext (TEXT CONST t, INT CONST von, bis) (* Teiltext von 't' von der Position 'von' bis 'bis' *) PROC change (TEXT VAR t, TEXT CONST old, new) (* Ersetzung von 'old' in 'new' im TEXT 't' *) INT PROC length (TEXT CONST t) (* Anzahl Zeichen von 't' *) INT PROC pos (TEXT CONST t, muster) (* Die Position des ersten Auftretens von 'muster' in 't' *) Die Vergleichs-Operatoren für TEXTe arbeiten bei dem Vergleich nach der alphabetischen Reihenfolge ('"a" < "b"' liefert TRUE). Dabei definiert ELAN nur die Reihenfolge innerhalb der kleinen und großen Buchstaben und Ziffern. Das Leerzeichen ("#ib#blank#ie#") ist jedoch stets das "kleinste" Zeichen. Wie diese "Zeichenblöcke" und die restlichen Zeichen angeordnet sind, wurde nicht spezifiziert. Ob '"a" < "Z"' TRUE oder FALSE liefert, wurde also nicht festgelegt und ist somit rechnerspezifisch. Anmerkung: Im EUMEL-Betriebs- system wird der ASCII-Zeichencode, DIN 66 003 mit Erweiterungen verwandt. Die folgenden Vergleiche sind alle TRUE: "otto" = "otto" "a" < "z" "Adam" < "Eva" "hallo" < "hallu" "hallo" < "hallo " length ("ha") = 2 subtext ("ELAN-Programmierung", 14) = "ierung" Aufgabe (HSG): Gib die Realisierung von folgenden vorgegebenen Prozeduren und Opera- toren an: a) TEXT PROC subtext (TEXT CONST t, INT CONST von) durch TEXT PROC subtext (TEXT CONST t, INT CONST von, bis) b) OP CAT (TEXT VAR a, TEXT CONST b) durch ':=' und '+' c) TEXT OP SUB (TEXT CONST t, INT CONST p) durch 'subtext' Übungsziel: Lernen einiger vorgegebener TEXT-Operationen Die Wiederholungs-Anweisung Wiederholungs-Anweisungen ermöglichen es uns, Anweisungen wiederholt - meist in Abhängigkeit von einer Bedingung - ausführen zu lassen. Darum wird die Wiederholungs-Anweisung oft auch #ib#Schleife#ie# genannt, die in ihr ent- haltenen Anweisungen #ib#Schleifenrumpf#ie#. Die Schleife von ELAN baut auf einem Basis-Konstrukt auf: REP anweisungen END REP Diese Anweisungsfolge realisiert eine sogenannte "Endlosschleife", weil nicht spezifiziert wird, wann die Schleife beendet werden soll. Bei der abweisenden Schleife wird die Abbruchbedingung an den Anfang der Schleife geschrieben: WHILE boolesche bedingung REP anweisungen END REP Bei jedem erneuten Durchlauf durch die Schleife wird überprüft, ob der BOOLesche Ausdruck den Wert TRUE liefert. Ist das nicht der Fall, wird mit der nächsten, auf die Schleife folgenden Anweisung mit der Bearbeitung fort- gefahren. Die Schleife wird abweisende Schleife genannt, weil der Schleifen- rumpf nicht ausgeführt wird, wenn die Bedingung vor Eintritt in die Schleife bereits FALSE liefert. Anders verhält es bei der nicht abweisenden Schleife: REP anweisungen UNTIL boolesche Bedingung END REP Hier wird der Schleifenrumpf auf jeden Fall einmal bearbeitet. Am Ende des Rumpfes wird die BOOLesche Bedingung abgefragt. Liefert diese den Wert FALSE, wird die Schleife erneut abgearbeitet. Liefert die Bedingung den Wert TRUE, wird die Schleife abgebrochen und mit der ersten Anweisung hinter der Schleife in der Bearbeitung fortgefahren. Bei beiden Arten der Wiederholungs-Anweisung ist es wichtig, daß Elemente der BOOLeschen Bedingung in der Schleife verändert werden, damit das Programm terminieren kann, d.h. die Schleife abgebrochen wird. Eine Endlos-Schleife wird bei der Zählschleife meist nicht vorkommen: FOR i FROM anfangswert UPTO endwert REP anweisungen END REP Zählschleifen werden eingesetzt, wenn die genaue Anzahl der Schleifendurch- läufe bekannt ist. Hier wird eine Laufvariable verwendet (in unserem Bei- spiel 'i': sie muß mit INT VAR deklariert werden), die die INT-Werte von 'anfangswert' bis 'endwert' in Schritten von '1' durchläuft. Diese Schleife zählt "aufwärts". Wird anstatt UPTO das Schlüsselwort DOWNTO verwendet, wird mit Schritten von -1 "abwärts" gezählt. Beispiel: FOR i FROM endwert DOWNTO anfangswert REP ... Für ein Beispielprogramm stellen wir uns die Aufgabe, aus TEXTen das Auf- treten des Buchstabens "e" herauszufinden. Die TEXTe sollen vom Eingabe- medium solange eingelesen werden, bis wir den TEXT "00" eingeben. Programm 16: INT VAR anzahl e :: 0; TEXT VAR wort; REP get (wort); zaehle e im wort UNTIL wort = "00" END REP; put (anzahl e). zaehle e im wort: INT VAR i; FOR i FROM 1 UPTO length (wort) REP IF das i te zeichen ist e THEN anzahl e INCR 1 END IF END REP. das i te zeichen ist e: (wort SUB i) = "e". Aufgabe (HSG): Die Klammern in dem letzten Refinement sind notwendig. Warum? Bevor wir ein Programm einem Rechner zur Bearbeitung übergeben, sollten wir uns davon überzeugen, daß das Programm wirklich das leistet, was es soll. Eine der wichtigsten Bedingungen ist die Terminierung eines Programms, d.h. das Programm darf nicht in eine Endlosschleife geraten. Unser Beispielpro- gramm terminiert, wenn beide Schleifen terminieren: die obere Schleife terminiert durch das Endekriterium, während die zweite Schleife automatisch durch die Zählschleife begrenzt wird. Das Programm wird also auf jeden Fall beendet (kann in keine Endlosschleife geraten), falls das Endekriterium ein- gegeben wird. Interessant sind dabei immer "Grenzfälle", wie z.B. die Eingabe eines "leeren Textes", sehr lange TEXTe usw. Aufgabe (HSG): Welche Fehler befinden sich in den folgenden Programmteilen? a) INT VAR i; FOR i FROM 1 UPTO i REP tue irgendwas END REP b) BOOL CONST noch werte :: TRUE; INT VAR i; WHILE noch werte REP get (i); ... IF i = O THEN noch werte := FALSE END IF END REP c) INT VAR anz berechnungen :: 1; REP lies eingabe wert; berechnung; drucke ausgabewert UNTIL anz berechnungen > 10 END REP. d) INT VAR anz berechnungen; WHILE anz berechnungen <= 10 REP lies eingabewert; berechnung; drucke ausgabewert; anz berechnungen INCR 1 END REP. e) INT VAR n := 1, summe; summe der ersten 100 zahlen. summe der ersten 100 zahlen: WHILE n < 100 REP summe := summe + n; n INCR 1 END REP. (* Achtung: 2 Fehler! (Vorwarnen ist feige) *) f) INT VAR n := 1; REP INT VAR summe := 0; summe := summe + n; n INCR 1 UNTIL n = 100 END REP (* Achtung: 2 Fehler! *) Übungsziel: Arbeiten mit Schleifen Das Programm 16 können wir etwas besser formulieren. Dazu wollen wir uns aber eine etwas andere Aufgabe stellen: wie viele Leerzeichen sind in einem Text? Zur Lösung dieser Aufgabe sollten wir den Text nicht wortweise ein- lesen, sondern zeilenweise. Dazu verwenden wir die Prozedur PROC get (TEXT VAR t, INT CONST max length) die einen TEXT 't' mit maximal 'max length' Zeichen einliest. Auf dem EUMEL-System gibt es dafür auch die Prozedur 'getline'. Programm 17: INT VAR anzahl blanks :: 0; REP lies zeile ein; zaehle blanks UNTIL zeile hat endekriterium END REP. lies zeile ein: TEXT VAR zeile; get (zeile, 80). zaehle blanks: INT VAR von :: 1; WHILE zeile hat ab von ein blank REP anzahl blanks INCR 1; von auf blank position setzen END REP. zeile hat ab von ein blank: pos (zeile, " ", von) > 0. von auf blank position setzen: von := pos (zeile, " ", von). zeile hat endekriterium: pos (zeile, "00") > 0. Aufgabe (TSW): Das Programm 17 enthält (mindestens) zwei Fehler. Finden Sie diese bitte heraus. Übungsziel: Finden von Programmierfehlern. Aufgabe (HSG): a) Welche Werte liefern folgende Ausdrücke für die Textvariable TEXT VAR t :: "Das ist mein Text" a1) pos (t, "ist") a2) pos (t, "ist", 5) a3) length (t) a4) subtext (t, 14) a5) subtext (t, 14, 17) b) Welche Werte liefern folgende Ausdrücke für die Textkonstanten TEXT CONST text :: "ELAN-Programm", alphabet :: "abcde...xyz" b1) 3 * text b2) length ("mein" + text + 3 * "ha") b3) 3 * "ha" < text b4) pos (text, alphabet SUB 1) b5) pos (text, subtext (alphabet, 7, 7)) c) Schreibe in anderer Form: c1) subtext (text, 7, 7) c2) change (text, "alt", "neu") c3) INT VAR laenge :: length (text); IF subtext (text, laenge, laenge) =... c4) IF NOT (text = "aha") THEN aktion 1 ELSE aktion 2 END IF Übungsziel: TEXT-Ausdrücke und Prozeduren Die Repräsentation von Datentypen Wie bereits erwähnt, sind Datentypen Klassen von Objekten der realen Umwelt. Die Objekte eines Datentyps müssen in den Speicher eines Rechners abgebildet werden. Die Darstellung eines Objekts im Rechner wird Repräsentation genannt. Aus organisatorischen Gründen versucht man, immer feste, gleich große Ein- heiten für die Objekte eines Datentyps zu verwenden. Durch die Begrenzung auf feste Speicherplatzeinheiten ist der Wertebereich beschränkt. Diese Grenzen hat man beim Programmieren zu beachten. Beim Datentyp BOOL spielt die Repräsentation nur insoweit eine Rolle, daß man die zwei möglichen Werte mehr oder weniger speicheraufwendig realisieren kann. Eine Einschränkung des Wertebereichs gibt es nicht. Bei INTs ist jedoch eine Einschränkung des Wertebereichs gegeben. Für die Repräsentation von INTs sind Einheiten von 16, 32 Bit u.a.m. gebräuchlich. Es existiert die Möglichkeit, den größten INT-Wert mit Hilfe von maxint zu erfragen. Z.B. ist 'maxint' für EUMEL-Systeme z.Zt. 32 767. Der kleinste INT-Wert ist oft nicht ' - maxint' (im EUMEL-System kann er unter 'minint' angesprochen werden). Übersteigt ein Wert 'maxint', gibt es eine Fehler- meldung 'overflow', im andern Fall 'underflow'. REALs sind noch schwieriger. Durch die endliche Darstellung der Mantisse treten "Lücken" zwischen zwei benachbarten REALs auf. Deshalb ist bei Ver- wendung von REALs immer mit Repräsentationsfehlern zu rechnen. Dieses Thema der "Rundungsfehler" wollen wir hier jedoch nicht weiter vertiefen. Auf jeden Fall gibt es aber auch einen größten REAL-Wert maxreal Bei TEXTen gibt es zwei Repräsentations-Schwierigkeiten. Einerseits werden TEXTe durch "irgendeinen" Code im Rechner repräsentiert, der z.B. bei Ver- gleichen verwendet wird. ELAN-Compiler auf Rechenanlagen mit unterschied- lichen Zeichencodes können daher unterschiedliche Ergebnisse liefern. Andererseits ist in ELAN nicht definiert, wie viele Zeichen maximal in einen TEXT passen, was ebenfalls vom Rechner bzw. von einem ELAN-Compiler abhängt. Auf dem EUMEL-System kann die maximale Anzahl Zeichen eines TEXTs durch 'maxtext length' erfragt werden. Sie ist z.Z. '32 000'. Ein- und Ausgabe Wie Datenobjekte - auf einfache Weise - auf einem Ausgabemedium ausgegeben werden können, haben wir bereits geschildert (Prozedur 'put'). Die Ausgabe erfolgt solange auf einer Zeile, bis ein auszugebender Wert nicht mehr auf eine Zeile paßt. In diesem Fall wird die Ausgabe in die nächste Zeile pla- ziert. Zwischen den einzelnen Werten auf einer Zeile wird jeweils ein Blank Zwischenraum gelassen, um die Ausgaben voneinander zu trennen. Mit folgenden Prozeduren kann man die Ausgabe flexibel gestalten: PROC line (* bewirkt einen Zeilenvorschub *) PROC line (INT CONST anzahl) (* bewirkt 'anzahl' Zeilenvorschübe *) PROC page (* bewirkt einen Seitenvorschub auf einem Drucker oder löscht den Bild- schirm und positioniert in die linke obere Ecke *) PROC putline (TEXT CONST zeile) (* gibt 'zeile' auf dem Bildschirm aus und positioniert auf die nächste neue Zeile *) PROC cursor (INT CONST reihe, spalte) (* Positioniert die Schreibmarke auf dem Bildschirm in die an- gegebene Position *) Die Prozedur 'get' holt Eingaben vom Eingabemedium. Ein Element der Eingabe wird dabei durch ein Blank vom nächsten getrennt. Einige weitere Eingabe- Prozeduren: PROC get (TEXT VAR t, TEXT CONST delimiter) (* die nächste Eingabe wird nicht von einem Blank begrenzt, sondern durch 'delimiter' *) TEXT PROC get (* dient zum Initialisieren *) PROC inchar (TEXT VAR zeichen) (* wartet solange, bis ein Zeichen vom Bildschirm eingegeben wird *) TEXT PROC incharety (* Versucht ein Zeichen vom Bildschirm zu lesen. Ist kein Zeichen vor- handen, wird "" geliefert *) PROC editget (TEXT VAR line) (* Bei der Eingabe kann 'line' editiert werden *) PROC get cursor (INT VAR zeile, spalte) (* Informationsprozedur, wo die Schreibmarke aktuell steht *) Konvertierungen Manchmal ist es notwendig, eine Datentyp-Wandlung für ein Objekt vorzunehmen. Die Wandlungen von einem INT- bzw. einen REAL-Wert in einen TEXT und umge- kehrt sind relativ unkritisch: TEXT PROC text (INT CONST value) TEXT PROC text (REAL CONST value) INT PROC int (TEXT CONST number) REAL PROC real (TEXT CONST number) Aber bei der folgenden Prozedur 'int' gehen im allgemeinen Fall Informationen verloren (es wird abgeschnitten): INT PROC int (REAL CONST value) REAL PROC real (INT CONST value) Zusätzlich steht eine Informationsprozedur 'last conversion ok' zur Ver- fügung, die den Wert TRUE liefert, falls die letzte Konversion fehlerfrei war: BOOL PROC last conversion ok Solche Abfragen sind notwendig, weil die Konversionsroutinen bei falschen Parameterwerten (z.B. 'int (maxreal)') nicht mit einer Fehlermeldung ab- brechen. Als Beispiel zeigen wir ein Programm zum Einlesen von Werten, von denen man nicht weiß, ob sie INT oder REAL sind. Darum kann auch nicht die 'get'-Prozedur für INT oder REAL verwandt werden: Programm 18: TEXT VAR eingabe element; REP get (eingabe element); wert nach intwert oder realwert bringen; berechnung UNTIL ende ENDREP. wert nach intwert oder realwert bringen: IF pos (eingabe element, ".") > 0 THEN REAL VAR realwert :: real (eingabe element) ELSE INT VAR intwert :: int (eingabe element) END IF; IF NOT last conversion ok THEN put ("Fehler bei Konvertierung:" + eingabe element); line END IF. berechnung: ...