167 25 24MB
German Pages 446 [460] Year 1989
de Gruyter Lehrbuch Gehring/Röscher • Einfuhrung in Modula-2
Hermann Gehring • Peter Röscher
Einführung in Modula-2 Programmierung und Systementwicklung
w
Walter de Gruyter G Berlin • New York DE
Dr. rer. pol. Hermann Gehring Univ.-Professor für Wirtschaftsinformatik am Fachbereich Wirtschaftswissenschaft der Freien Universität Berlin Dipl.-Informatiker Peter Röscher Wissenschaftlicher Mitarbeiter am Fachbereich Wirtschaftswissenschaft der Freien Universität Berlin Das Buch enthält 116 Abbildungen, 154 Tabellen und 2 0 Programmbeispiele
CIP-Titelaufnahme der Deutschen Bibliothek Gehring, Hermann: Einführung in Modula-2 : Programmierung und Systementwicklung / Hermann Gehring ; Peter Röscher. Berlin ; New York : de Gruyter, 1989 (De-Gruyter-Lehrbuch) ISBN 3-11-011931-5 NE: Röscher, Peter:
© Gedruckt auf säurefreiem Papier © Copyright 1989 by Walter de Gruyter & Co., D-1000 Berlin 30. Dieses Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Jede Verwertung außerhalb der engen Grenzen des Urheberrechtsgesetzes ist ohne Zustimmung des Verlages unzulässig und strafbar. Das gilt insbesondere für Vervielfältigungen, Übersetzungen, Mikroverfilmungen und die Einspeicherung und Verarbeitung in elektronischen Systemen. Der Verlag hat für die Wiedergabe aller in diesem Buch enthaltenen Informationen (Programme, Verfahren, Mengen, Dosierungen, Applikationen etc.) mit Autoren bzw. Herausgebern große Mühe darauf verwandt, diese Angaben genau entsprechend dem Wissensstand bei Fertigstellung des Werkes abzudrucken. Trotz sorgfältiger Manuskripterstellung und Korrektur des Satzes können Fehler nicht ganz ausgeschlossen werden. Autoren bzw. Herausgeber und Verlag übernehmen infolgedessen keine Verantwortung und keine daraus folgende oder sonstige Haftung, die auf irgendeine Art aus der Benutzung der in dem Werk enthaltenen Informationen oder Teilen davon entsteht. Printed in Germany. Druck: WB-Druck, Rieden / Buchbinderische Verarbeitung: Dieter Mikolai, Berlin.
Vorwort Dieses Buch ist im Rahmen einer Veranstaltungsreihe zur Wirtschaftsinformatik am Fachbereich Wirtschaftswissenschaft der Freien Universität Berlin entstanden. Die Ausbildung im Fach Wirtschaftsinformatik der FU Berlin entspricht weitgehend der inzwischen an vielen deutschen Hochschulen eingeführten Studienrichtung Wirtschaftsinformatik. Im einzelnen umfaßt die Ausbildung Lehrveranstaltungen zu den Bereichen Programmierung und Systementwicklung, Algorithmen und Datenstrukturen, Datenbank- und Informationssysteme sowie ergänzende Spezialveranstaltungen wie Datenschutz, Expertensysteme, Computergrafik usw. Als Ausbildungssprache wurde der Sprache Modula-2 gegenüber der im wirtschaftlichen Bereich gängigsten Sprache COBOL der Vorzug gegeben, da Modula-2 anerkannte softwaretechnische Prinzipien und Konzepte weitgehend unterstützt. Modula-2 wurde Anfang der achtziger Jahre von Nikiaus Wirth als ein Programmiersystem zur Bewältigung komplexer Programmieraufgaben vorgestellt. Als Weiterentwicklung von Pascal enthält Modula-2 alle wesentlichen Sprachelemente von Pascal, darüberhinaus ein weiter verfeinertes Typenkonzept und ein Konzept zur modularen Programmierung. Außerdem gestattet Modula-2 die Programmierung nichtsequentieller Algorithmen und ist somit gleichermaßen zur Bearbeitung von Aufgabenstellungen der Systemprogrammierung und der Anwendungsprogrammierung geeignet. Ziel dieses Buches ist es, zu zeigen, wie bausteinartig aus Modulen zusammengesetzte Softwaresysteme in Modula-2 implementiert werden können. Jedoch richtet sich das Buch nicht nur an Softwareentwickler, die ein Werkzeug kennenlernen möchten, mit dem modular entworfene und spezifizierte Systembausteine unmittelbar in eine Programmiersprache umgesetzt werden können. Aufgrund der ausführlichen und systematischen Sprachbeschreibung ist das Buch insbesondere auch für Programmieranfänger geeignet. Das vorliegende Buch ist in vier Kapitel gegliedert: Kapitel 1 enthält eine allgemeine Charakterisierung der Sprache Modula-2. Gezeigt wird, worin sich Modula-2 von ihrem Vorgänger Pascal unterscheidet und was die Besonderheit der modularen Systementwicklung mit Modula-2 ausmacht. Um den letztgenannten Aspekt zu verdeutlichen, werden der Modulbegriff, Eigenschaften von Modulen, Beziehungen zwischen Modulen und und schließlich auch Arten von Modulen, über die die Programmiersprache Modula-2 verfügt, behandelt.
VI
Den Hauptbestandteil des Buches stellt das zweite Kapitel dar. Es behandelt die vollständige Syntax von Modula-2. Da eine internationale Normierung der Sprache noch aussteht, liegt dem zweiten Kapitel der Sprachstandard zugrunde, wie er sich nach der vierten Revision von Wirths „Programming in Modula-2" definiert. Umfangreiche Beispiele zu den in diesem Kapitel vermittelten Sprachbestandteilen runden den Text ab. Im dritten Kapitel wird ein spezieller Modula-2-Compiler beschrieben. Hierbei handelt es sich um den im Herbst 1988 vorgestellten TopSpeed-Modula-2-Compiler der Firma Jensen und Partner. Gezeigt wird ausführlich, worin sich TopSpeedModula-2 von dem im zweiten Kapitel behandelten Sprachstandard unterscheidet. Dabei stehen nicht nur Änderungen des Standards, sondern auch eine ganze Reihe nützlicher Systemerweiterungen im Vordergrund. Gegenstand des vierten und letzten Kapitels ist die Systementwicklung mit Modula-2. Am Beispiel eines Fahrschulsystems wird demonstriert, daß sich diese Sprache in ganz besonderer Weise für die Programmierung größerer Systeme eignet. Die Ausführungen in diesem Kapitel erstrecken sich auf die Entwicklungsphasen Anforderungsdefinition, Entwurf und Implementierung. Allerdings erlaubt die Komplexität des verwendeten Beispiels nur eine auszugweise Darstellung der Entwicklungsunterlagen. Der Anhang enthält einige Zusammenstellungen wichtiger Sprachelemente sowie eine Übersicht über die verwendete und zitierte Literatur. Danken möchten wir denen, die mit der Erstellung dieses Buches befaßt waren. Unser besonderer Dank gilt Frau Maas und Frau Graf für die computergestützte Erfassung des Manuskripts sowie Herrn Uhlemann für die Erstellung zahlreicher Abbildungen und die Formatierung des gesamten Textes. Schließlich möchten wir dem Verlag de Gruyter für die Übernahme des Textes und für die gute Zusammenarbeit danken. Berlin, im August 1989 Hermann Gehring und Peter Röscher
Inhaltsverzeichnis Seite Modulare Sprachen 1.1 Modula-2 und Pascal 1.2 Module in Modula-2 1.3 Residenter Sprachteil 1.4 Nichtresidenter Sprachteil 1.5 Struktur von Modula-2-Programmen
1 2 3 8 8 11
Sprachstandard 2.1 Bestandteile der Sprache 2.1.1 Bezeichner 2.1.2 Schlüsselwörter 2.1.3 Zahlen 2.1.4 Zeichenketten 2.1.5 Operatoren 2.1.6 Begrenzer 2.1.7 Kommentare 2.2 Programmaufbau 2.2.1 Deklarationen 2.2.1.1 Deklaration von Konstanten 2.2.1.2 Deklaration von Typen 2.2.1.3 Deklaration von Variablen 2.2.1.4 Deklaration von Prozeduren 2.2.1.5 Deklaration von Modulen 2.2.2 Anweisungen 2.2.3 Ausdrücke 2.3 Datentypen 2.3.1 Einfache Datentypen 2.3.1.1 Datentyp INTEGER 2.3.1.2 Datentyp CARDINAL 2.3.1.3 Datentyp REAL 2.3.1.4 Datentyp LONGINT 2.3.1.5 Datentyp CHAR 2.3.1.6 Datentyp BOOLEAN 2.3.1.7 Aufzählungstypen 2.3.1.8 Unterbereichstypen
17 18 19 21 21 26 29 30 31 33 37 39 45 48 49 51 54 57 63 64 65 70 72 76 77 82 85 89
VIII
Inhaltsverzeichnis
Seite
2.3.2
Strukturierte Datentypen 2.3.2.1 Feldtypen 2.3.2.2 Zeichenketten 2.3.2.3 Mengentypen 2.3.2.4 Verbundtypen 2.3.2.5 Dateien 2.3.2.6 Prozedurtypen 2.3.3 Systemdatentypen 2.3.3.1 Systemdatentyp WORD 2.3.3.2 Systemdatentyp ADDRESS 2.3.4 Übersicht über Datentypen und Operationen Ein- und Ausgabe 2.4.1 Bibliotheks-Modul Terminal 2.4.2 Bibliotheks-Modul InOut 2.4.3 Bibliotheks-Modul ReallnOut Anweisungen 2.5.1 Wertzuweisungen 2.5.2 Verzweigungen 2.5.2.1 IF- Anweisung 2.5.2.2 CASE-Anweisung 2.5.3 Schleifen 2.5.3.1 WHILE-Anweisung 2.5.3.2 REPEAT-Anweisung 2.5.3.3 FOR-Anweisung 2.5.3.4 LOOP-Anweisung und EXIT-Anweisung 2.5.4 WITH-Anweisung 2.5.5 RETURN-Anweisung 2.5.6 Prozeduren 2.5.6.1 Prozedur-Deklaration 2.5.6.2 Prozedur-Aufruf 2.5.6.3 Funktionsprozeduren 2.5.6.4 Sichtbarkeitsbereiche von Objekten Module 2.6.1 Lokale Module 2.6.1.1 Deklaration lokaler Module 2.6.1.2 Benutzung lokaler Module
94 94 102 107 116 128 143 156 156 161 162 164 165 166 170 175 175 177 178 183 189 189 196 206 211 214 216 217 218 225 235 239 244 244 245 250
IX
Inhaltsverzeichnis
Seite
2.6.2
2.7
2.8
2.9 3.
Module Deklaration globaler Module Benutzung globaler Module Benutzung und Lebensdauer von Objekten 2.6.2.4 Verbergen von Informationen 2.6.2.5 Getrennte Übersetzung und Versionskonflikte Dynamische Datenstrukturen 2.7.1 Datentyp P O I N T E R 2.7.2 Operationen auf dynamischen Objekte 2.7.3 Listenverarbeitung 2.7.4 Weitere dynamische Datenstrukturen Rekursionen 2.8.1 Rekursive Prozeduren 2.8.2 Inkarnationen 2.8.3 Ablauf rekursiver Prozeduren 2.8.4 Beispiele rekursiver Prozeduren 2.8.4.1 Fibonacci-Reihe 2.8.4.2 Sortieralgorithmus Quicksort Parallele Prozesse
258 259 262 262 264 266 280 281 283 286 298 302 302 304 305 307 307 310 313
Sprachdialekte
317
3.1 3.2
317 318 323
Entwicklung von Sprachdialekten TopSpeed-Modula-2 3.2.1 Reservierte Wörter 3.2.2 3.2.3 3.2.4
4.
Globale 2.6.2.1 2.6.2.2 2.6.2.3
Standarddatentypen Standardfunktionen und -prozeduren Standardbibliotheken
323 326 327
Systementwicklung mit Modula-2 - ein Beispiel
347
4.1 4.2 4.3 4.4 4.5
347 359 367 372 405 407
Systemfunktionen und Benutzerschnittstelle Modularisierung Definitionsmodule Implementationsmodule Erzeugen eines ausführbaren Modula-2-Programms 4.5.1 Übersetzen der Definitionsmodule 4.5.2 Übersetzen des Programm- und der Implementationsmodule 4.5.3 Binden der Objektdateien 4.5.4 Starten des ausführbaren Programms
409 409 411
Inhaltsverzeichnis
Seite
ANHANG A A A A A A A B B B B C
1. ASCII-Tabelle 2. Schlüsselwörter in Modula-2 3. vordeklarierte Konstanten 4. vordeklarierte Datentypen 5. Standardprozeduren 6. Standardfunktionen 7. Standardmodule 1. Abbildungsverzeichnis 2. Tabellenverzeichnis 3. Programmverzeichnis 4. Literaturverzeichnis Index
414 418 418 418 419 419 419 420 423 426 427 429
1. Modulare Sprachen Modularisierung und Modultechnik als Gestaltungsprinzipien komplexer Systeme haben sich in den Ingenieurwissenschaften bereits seit längerem durchgesetzt. Auch in der Datenverarbeitung gilt die modulare Systemgestaltung schon seit langem als ein allgemein anerkanntes Entwicklungsprinzip. Mit ihm verbindet man Erwartungen, wie leichtere Wartbarkeit von Systemen, Wiederverwendbarkeit von Systemteilen usw. Doch Programmiersprachen, die die Entwicklung modularer Systeme konsequent unterstützen, stehen erst seit relativ kurzer Zeit zur Verfügung. Zu ihnen zählt die von Wirth entwickelte und in den frühen achtziger Jahren vorgestellte Sprache Modula-2. In der Zwischenzeit ist eine ganze Reihe von Modula-2-Compilern unter verschiedenen Betriebssystemen erschienen. Der erste Modula-2-Compiler wurde von Wirth und seiner Forschungsgruppe für das Betriebssystem RT-11 geschrieben. Später erschien ein unter UNIX lauffähiger Compiler für den Computer PDP-11, entwickelt an der University of New South Wales. Weitere Modula-2Compiler entstanden unter Berkeley UNIX auf einem VAX-Rechner, implementiert an der University of Pittsburgh, und eine ebenfalls auf einem VAX-Rechner lauffähige verbesserte Version, erstellt von der Digital Equipment Corporation. Eine größere Verbreitung erreichte die Sprache Modula-2 mit der Verfügbarkeit von unter dem stark verbreiteten Mikrocomputer-Betriebssystem MS-DOS lauffähigen Compilern. 1983 erschien der erste Modula-2-Compiler für das MS-DOS Betriebssystem, entwickelt in der Schweiz von der Firma LOGITECH. Zwei Jahre später wurde ein in Houston (USA) von der Firma SDS entwickeltes und unter dem gleichen Betriebssystem lauffähiges Modula-2-System, genannt SDS-Modula, vorgestellt. In den Jahren 1987 und 1988 folgten das Taylor-Modula-2, von der Firma Tayloris Software AG, und das TopSpeed-Modula-2, von der Firma Jensen und Partner, London. Die Systeme SDS-Modula-2 und TopSpeed-Modula-2 zeichnen sich durch komfortable Benutzerschnittstellen aus. Sie eignen sich daher gut für Lehrzwecke. Nachdem die Entwicklung von Modula-2-Systemen kurz skizziert wurde, steht die Sprache Modula-2 selbst im Vordergrund der nun folgenden Abschnitte des Kapitels 1. Kapitel 1.1 vergleicht die beiden Programmiersprachen Pascal und Modula-2, ohne jedoch auf die elementaren Unterschiede der Sprachen einzugehen. Kapitel 1.2 setzt sich mit dem Begriff des Moduls auseinander und definiert Eigenschaften von Modulen. Die Kapitel 1.3 und 1.4 schließlich behandeln den residenten und den nichtresidenten Sprachteil von Modula-2.
2
1. Modulare Sprachen
1.1 Modula-2 und Pascal Der Sprachstandard von Pascal wurde in den siebziger Jahren von Wirth und Jensen [WI1] festgelegt. Ihr Ziel war es, eine elegante, leicht erlernbare und dennoch mächtige Sprache zu entwickeln, die im Gegensatz zu den meisten kommerziellen Programmiersprachen über ein ausgereiftes Typenkonzept verfügt und die Prinzipien der Strukturierten Programmierung in konsequenter Weise umsetzt. Ursprünglich als Ausbildungssprache gedacht, sollte Pascal seinen Weg primär im akademischen Anwendungsbereich nehmen. Mittlerweile ist Pascal jedoch auch im kommerziellen Bereich verbreitet, obgleich die Versuche einiger Anwender, Pascal durch Hinzufügen von weiteren Sprachelementen für den kommerziellen Bereich attraktiver zu machen, durchaus kontrovers diskutiert werden. Modula-2 (MODUlar programming LAnguage) stellt eine Weiterentwicklung von Pascal dar. Es enthält alle wesentlichen Sprachelemente von Pascal, ist im Sprachumfang nur wenig mächtiger als Pascal und wesentlich einfacher als zum Beispiel die Programmiersprache ADA. Typenkonzept und Unterstützung der Strukturierten Programmierung wurden von Pascal in Modula-2 übernommen. Die gegenüber Pascal wesentlichste Neuerung in Modula-2 ist die Unterstützung des Entwickeins modularer Systeme durch ein fundiertes und sprachlichexplizites Modularisierungskonzept. Wie erwähnt, unterstützen Pascal und Modula-2 gleichermaßen die Strukturierte Programmierung. Sie basiert auf der Erkenntnis, daß sich sämtliche Algorithmen durch einige wenige Grundkonstrukte ausdrücken lassen. Für diese Konstrukte, nämlich die Reihung (Sequenz), die Auswahl (Alternative) und die Wiederholung (Schleife), bieten beide Sprachen geeignete Ausdrucksmittel an. Mit diesen wenigen Konstrukten lassen sich beliebig komplexe Algorithmen formulieren und in überschaubarer Weise darstellen. Bewußt verzichtet wurde in der Sprache Modula-2 auf die GOTO-Anweisung, die ja zum Sprachumfang der meisten Programmiersprachen gehört. Die undisziplinierte Verwendung der GOTO-Anweisung begünstigt das Entstehen wenig durchschaubarer „Spaghetti-Programme" und entspricht nicht den Grundsätzen der Strukturierten Programmierung. Der Verzicht auf das GOTO ist konsequent und vor allem auch aus der Sicht der Programmierausbildung begrüßenswert. Beiden Sprachen gemeinsam ist auch ein differenzierendes Typenkonzept. Je nach Problemstellung erlaubt es die Abbildung realer Phänomene in elementar-einfache und komplexe, zusammengesetzte Datenobjekte. Darüberhinaus wird dem Benutzer die Möglichkeit geboten, Datentypen selbst zu definieren und unter Nutzung des in den Sprachen verwendeten Zeigerkonzepts beliebig komplexe Datenstrukturen zu definieren und zu manipulieren. Die dem Benutzer eröffneten Alternativen der Datenmodellierung führen bei beiden Sprachen zu einem ausgewogenen Verhältnis der algorithmischen und datenbezogenen Sprachkomponenten. Bei einer
1.2 Module in Modula-2
3
ganzen Reihe von kommerziellen Programmiersprachen, die man als typarme und mitunter auch als nahezu typlose Sprachen bezeichnen kann, ist dieses Verhältnis weniger ausgewogen. Das in Modula-2 verwendete Modulkonzept erlaubt es, Programme in überschaubare und voneinander weitgehend unabhängige Teile (Module) zu gliedern. Die Module entsprechen abgegrenzten Teilproblemen der gegebenen Problemstellung. Sie enthalten die Prozeduren bzw. Funktionen und die Datenobjekte, die zur Bearbeitung der jeweiligen Teilprobleme erforderlich sind. Beim Programmablauf interagieren die Module über ihre Schnittstellen, die in Modula-2 sprachlich explizit zu definieren sind. Modula-2 sieht das separate Übersetzen von Modulen vor. Übersetzte Module können somit als wiederverwendbare Problemlösungen von Teilproblemen in einer Programmbibliothek abgelegt werden. Auf das Modularisieren mit Modula-2 wird im nächsten Kapitel näher eingegangen.
1.2 Module in Modula-2 Als eines der wichtigsten Prinzipien des Entwurfs von Softwaresystemen gilt die Modularisierung. Modularisieren ist die Tätigkeit, einzelne Teile einer Gesamtlösung (Algorithmen bzw. Funktionen und Datenobjekte) zu sinnvollen Einheiten zusammenzufassen. Grundsätzlich kann man dabei datenorientiert, problemorientiert oder funktionsorientiert vorgehen. Werden Algorithmen datenorientiert zu einer Einheit zusammengefügt, dann operieren die zusammengefaßten Funktionen auf gemeinsamen Daten. Bei der problemorientierten Vorgehensweise werden dagegen alle Funktionen, die zur Lösung eines abgeschlossenen Teilproblems beitragen, als zusammengehörig betrachtet und zu einer Einheit zusammengefaßt. Die funktionsorientierte Vorgehensweise schließlich besteht darin, Algorithmen, die verwandte Funktionen erfüllen, zu vereinigen. Welche der angegebenen Zusammenfassungsmöglichkeiten man in einem konkreten Fall wählt, hängt von der Art des Problems und vom Zusammenspiel der Teillösungen ab. Das Ergebnis der Zusammenfassung mehrerer Algorithmen zu einer Einheit nennt man einen „Modul". Goos [GO] definiert einen Modul wie folgt: „Unter einem Modul wird eine Sammlung von Objekten und Algorithmen verstanden mit der Eigenschaft, daß ihre Kommunikation mit der Außenwelt nur über eine klar definierte Schnittstelle erfolgt. Das Zusammensetzen mehrerer Module zu einer Gesamtlösung darf keine Kenntnis ihres inneren Aufbaus voraussetzen, und die Korrektheit eines Moduls muß ohne Kenntnis seiner Einbettung in die Gesamtlösung nachprüfbar sein." Nach dieser Definition besteht ein Modul aus einem inneren Bereich, der eine Sammlung von Datenobjekten und Funktionen darstellt, und einer Schnittstelle, die die Verbindung zu der Umgebung des Moduls herstellt. Logisch gesehen besteht die Schnittstelle aus zwei Teilen, der Importschnittstelle und der Exportschnittstelle:
1. Modulare Sprachen
4
-
Die Importschnittstelle beschreibt, welche Datenobjekte und Funktionen der Modul von seiner Umgebung benötigt bzw. importiert.
-
Die Exportschnittstelle beschreibt, welche Datenobjekte und Funktionen der Modul seiner Umgebung zur Verfügung stellt bzw. exportiert.
Die Beschreibung des Inneren und der Schnittstelle eines Moduls kann z.B. in Pseudocode gemäß folgendem Beschreibungsschema erfolgen: MODUL modulname; IMPORT
Liste der importierten Datenobjekte und Funktionen;
EXPORT
Liste der exportierten Datenobjekte und Funktionen; Beschreibung der exportierten Datenobjekte; Beschreibung der exportierten Funktionen;
LOCAL
Beschreibung der lokalen Datenobjekte und Funktionen;
END modulname.
Die Liste der importierten Datenobjekte und Funktionen enthält die Namen der Datenobjekte und Funktionen anderer Module, die der vorliegende Modul benutzt. Dagegen beschreibt die Liste der exportierten Datenobjekte und Funktionen die moduleigenen Datenobjekte und Funktionen, die der vorliegende Modul seiner Außenwelt zur Verfügung stellt. Ein Modul kann außerdem noch über lokale Datenobjekte und Funktionen verfügen. Lokale Objekte und Funktionen dienen nur modulinternen Zwecken und werden daher nicht exportiert. Die Beschreibung exportierter und lokaler Datenobjekte eines Moduls hat den Zweck, die Datentypen der Objekte festzulegen. Die Beschreibung exportierter und lokaler Funktionen eines Moduls dient der inhaltlichen Präzisierung der Funktionen. Zur präzisierenden Beschreibung von Datenobjekten und Funktionen läßt sich vorteilhaft ein Pseudocode verwenden, der an die später verwendete Implementierungssprache angelehnt ist. Die Frage, wie Module als Teile einer Gesamtlösung abzugrenzen sind, umreißt ein Problem, für dessen Lösungen unterschiedlichste Empfehlungen gegeben werden. Sie reichen von Empfehlungen allgemeiner Art über spezielle Merkmale, die Module aufweisen sollen, bis hin zu konkreten Verfahren der Modularisierung. Den Charakter einer allgemeinen Empfehlung besitzt z.B. die Vorschrift, Module so abzugrenzen, daß sie in sich möglichst homogen und im Vergleich zueinander möglichst heterogen sind. Im Gegensatz dazu ist die von Myers [MY] für bestimmte Anwendungsfälle vorgeschlagene „Transactional Decomposition" ein konkretes Verfahren zum Ableiten von Modulen aus einer gegebenen Problemstellung. In den
1.2 Module in Modula-2
5
weiten Bereich zwischen allgemeinen Empfehlungen und konkreten Modularisierungsverfahren ist u.a. die Vielzahl der Merkmale einzuordnen, die Module nach Auffassung verschiedener Autoren erfüllen sollen. Beispiele für solche Merkmale sind: -
Einfachheit: Jeder Modul soll eine überschaubare, für sich allein verständliche Einheit bilden. Aus dieser Forderung läßt sich aber nicht eine bestimmte maximale Größe für einen Modul (in Zeilen oder Seiten) ableiten. Auch ein Modul, der sich über mehrere Textseiten erstreckt, kann durchaus eine überschaubare und verständliche Einheit bilden, wenn er gut gegliedert ist.
-
Minimalität und Überschaubarkeit der Schnittstellen: Die Schnittstellen zwischen Modulen sollen so einfach wie möglich gehalten werden. Eine Modulschnittstelle ist umso einfacher, je weniger importierte und exportierte Objekte und Funktionen sie aufweist und je kürzer die Parameterlisten der Funktionen sind. Wenn ein Modul zu viele Informationen mit seiner Umgebung austauscht, besteht die Gefahr, daß Einfachheit und Überschaubarkeit der Schnittstellen verloren gehen.
-
Unabhängigkeit: Module sollen voneinander so unabhängig sein, daß Änderungen der Interna eines Moduls keine Änderungen in anderen Modulen nach sich ziehen. Das heißt, die Menge aller Annahmen, die Module über andere Module enthalten, beschränkt sich auf die Objekte ihrer Schnittstellen. Wenn die Schnittstelle eines Moduls spezifiziert ist, kann der Entwurf der inneren Struktur daher ohne Kenntnis der Umgebung des Moduls bzw. des Gesamtsystems durchgeführt werden.
-
Abgeschlossenheit: Sämtliche Funktionen eines Moduls sollen auf den selben Datenstrukturen operieren, sowie notwendig und hinreichend sein, um eine in sich abgeschlossene Aufgabe zu lösen. Umgekehrt soll ein Modul keine Sammlung von beliebigen Funktionen darstellen, zwischen denen keine logische Beziehung besteht.
-
Testbarkeit: Jeder Modul soll so beschaffen sein, daß seine Korrektheit ohne Kenntnis seiner Einbettung in ein Gesamtsystem und nur unter Betrachtung seiner Schnittstelle überprüft werden kann.
Aus diesen Kriterien wird deutlich, daß der Zweck der Modularisierung darin besteht, eine Gesamtlösung aus sinnvoll abgegrenzten, bausteinartigen Teillösungen zusammenzusetzen. Diese Vorgehensweise weist mehrere Vorteile auf. Bausteinartige Teillösungen sind überschaubar, leichter beherrschbar und wiederverwendbar. Außerdem lassen sie sich in flexibler Weise zu Gesamtlösungen zusammensetzen, die leichter änderbar und einfacher an geänderte Anforderungen anpaßbar sind.
6
1. Modulare Sprachen
Dem Aspekt der Änderbarkeit kommt eine besondere Bedeutung zu, da der Anteil der Wartungskosten an den gesamten Software-Entwicklungskosten in der Vergangenheit beständig gestiegen ist und vermutlich noch weiter steigen wird. Das Modularisierungskonzept der Programmiersprache Modula-2 sieht die Möglichkeit der Bildung problemspezifischer und nichtproblemspezifischer Module vor. Problemspezifische Module leiten sich unmittelbar aus einer gegebenen Problemstellung ab. Man unterscheidet drei Typen problemspezifischer Module: ProgrammModule, lokale Module und globale Module. Die Unterschiede zwischen diesen drei Modultypen ergeben sich aus den mit ihnen verbundenen unterschiedlichen Zwecke. Ein Programm-Modul stellt ein Hauptprogramm im klassischen Sinne dar. Programm-Module unterscheiden sich in ihrem Aufbau nur in geringfügigen Sprachdetails gegenüber Hauptprogrammen, die in anderen Sprachen und insbesondere in der Sprache Pascal abgefaßt wurden. Lokale Module sind Module, die im Innern anderer Module verborgen sind. Bei diesen anderen Modulen kann es sich um Programm-Module, aber auch um die noch zu erläuternden globalen Module handeln. Lokale Module sind ein Mittel zur Strukturierung des Innern anderer Module. Vor allem bei größeren Programm-Modulen oder globalen Modulen kann es sinnvoll sein, Gruppen von Funktionen zu lokalen Modulen zusammenzufassen und so das Innere des umgebenden Moduls klarer zu strukturieren. In einem Modul auf diese Weise eingekapselte lokale Module können nur von diesem Modul selbst genutzt werden. Für die Außenwelt des sie einschließenden Moduls sind sie nicht nutzbar. Globale Module stellen als zentrales Strukturierungsmittel die wesentlichste Neuerung der Programmiersprache Modula-2 dar. Mittels globaler Module lassen sich Programme in überschaubare und voneinander weitgehend unabhängige Teile (Module) gliedern. Die in Modula-2 praktizierte Aufspaltung globaler Module in Definitions- und Implementationsmodule ermöglicht das separate Übersetzen von Modulen. Programmteile können daher auch als wiederverwendbare Module in einer Programmbibliothek abgelegt werden. In einem Definitionsmodul wird von den Details der Realisierung der Modulfunktionen abstrahiert. Beschrieben wird lediglich, was der jeweilige Modul tut. Wie die Modulfunktionen konkret erfüllt werden, geht aus dem zugehörigen Implementationsmodul hervor. Für die Zusammenarbeit innerhalb eines Programmierteams genügt es deshalb, nur die Definitionsmodule des zu entwickelnden Systems gemeinsam festzulegen. Das Ausformulieren und das Austesten der Implementationsmodule kann dann gänzlich dem jeweils zuständigen Teammitglied überlassen bleiben. Modularisierung ist somit ein Weg, die arbeitsorganisatorischen Probleme, die die Entwicklung umfangreicher Programme mit sich bringt, in den Griff zu bekommen.
1.2 Module in Modula-2
7
Möchte man das Konzept globaler Module plastisch verdeutlichen, so könnte man einen globalen Modul mit einem „Spezialgeschäft" wie beispielsweise einer Bäckerei, einem Bekleidungsgeschäft oder einem Schlüsseldienst vergleichen. Jedes dieser Geschäfte ist auf eine bestimmte Aufgabe spezialisiert: eine Bäckerei verkauft Backwaren, ein Bekleidungsgeschäft Bekleidung und ein Schlüsseldienst Schlüssel. Völlig analoge Verhältnisse ergeben sich bei globalen Modulen. Auch jeder globale Modul ist auf eine bestimmte Aufgabe spezialisiert; er liefert eine spezielle Dienstleistung in Form bestimmter Funktionen. Die Sprache Pascal hingegen verfügt nicht über ein Modularisierungskonzept. Ein Pascal-Programm kann im gegebenen Beispiel mit einem „Warenhaus" verglichen werden. Jedes Spezialgeschäft besteht aus zwei Bereichen: Verkauf und Fabrikation. Im Bereich Verkauf werden die jeweiligen Erzeugnisse angeboten, während im Bereich Fabrikation die Vorschriften und Geheimnisse verborgen sind, die die Herstellung der Erzeugnisse betreffen. In Analogie dazu treten globale Module in zwei Modularten auf, als Definitionsmodule und Implementationsmodule. In einem Definitionsmodul werden die Leistungen eines globalen Moduls in Form der Modulschnittstellen beschrieben. Realisiert werden die Leistungen hingegen im zugehörigen Implementationsmodul. Anders ausgedrückt: der Definitionsteil vereinbart die sichtbaren Dienstleistungen, während der Implementationsteil den Rest verbirgt. Im Gegensatz zu den bisher behandelten Modularten erfüllen nicht problemspezifische Module Funktionen, die nicht speziell auf einzelne Problemstellungen zugeschnitten sind. Die nicht problemspezifischen Module lassen sich weiter unterteilen in Basis-Module und Bibliotheks-Module. Basis-Module (low-level-modules) entstehen durch das Einkapseln maschinen- bzw. compilerabhängiger Programmteile in Modulen. Sie stellen gewissermaßen eine auf das jeweilige individuelle Computersystem zugeschnittene Spracherweiterung dar und erleichtern das Übertragen von Programmen auf unterschiedliche Rechenanlagen. Welche maschinenabhängigen Funktionen Basis-Module im einzelnen erfüllen, wird in den folgenden Kapiteln behandelt. Bibliotheks-Module enthalten Funktionen und Dienste, deren Implementierung sinnvollerweise der Benutzergemeinschaft bzw. den Compiler-Herstellern überlassen bleibt. So gibt es in Modula-2 z.B. keine Standardroutinen für die Ein- und Ausgabe, keinen Standard-Datentyp Datei (file), ebensowenig das Konzept eines Prozesses (task), einer Bildschirmroutine (monitor) oder einer Ausschlußroutine (semaphore). Programmierelemente dieser Art werden vielmehr in Bibliotheks-Modulen bereitgestellt. Eine Übersicht über Bibliotheks-Module gibt das Kapitel 1.4.
8
1. Modulare Sprachen
1.3 Residenter Sprachteil Der residente Sprachteil der Programmiersprache Modula-2 umfaßt die im Compiler verankerten Sprachkomponenten. Hierzu gehören die Elementardatentypen INTEGER, LONGINT, CARDINAL, REAL, CHAR, BOOLEAN, BITSET, der Unterbereichs- und der Aufzählungsdatentyp, der Mengen-, der Feld- und der Verbundtyp sowie die dynamischen Daten. Des weiteren zählt man die Kontrollstrukturen (Reihung, Auswahl und Wiederholung), Prozeduren und Funktionen zum residenten Sprachteil von Modula-2. Mit Hilfe des residenten Sprachteils von Modula-2 lassen sich Konstanten, Typen und Variablen vereinbaren sowie Anweisungen formulieren, die der problembezogenen Manipulation vereinbarter Datenobjekte dienen. Der residente Sprachteil repräsentiert die eigentliche Programmiersprache Modula-2. Er wird ausführlich in Kapitel 2 behandelt. Im Gegensatz dazu stellt der nichtresidente Sprachteil eine Menge von Spracherweiterungen in Form von Funktionen dar, die in speziellen Modulen gekapselt sind. Auf den nichtresidenten Sprachteil wird im nächsten Kapitel eingegangen.
1.4 Nichtresidenter Sprachteil Den nichtresidenten Sprachteil von Modula-2 bilden die bereits im Kapitel 1.2 erwähnten Basis- und Bibliotheks-Module. Basis- und Bibliotheks-Module stellen globale Module dar, die in der Regel von Compiler-Herstellern mitgeliefert werden. Darüberhinaus kann der einzelne Benutzer die Modulbibliothek durch BibliotheksModule seiner Wahl ergänzen. In Modula-2 gibt es nur einen Basis-Modul und zwar den Modul SYSTEM: -
SYSTEM: Jede Implementierung von Modula-2 enthält einen Modul SYSTEM, der einige systemabhängige Datentypen und Prozeduren bereitstellt. Der Modul SYSTEM kann in Modula-2-Programmen wie ein Implementationsmodul benutzt werden. Er besteht jedoch nicht aus einem Definitions- und einem Implementationsmodul, sondern ist ein fiktiver, zum Compiler gehörender Modul. Die exportierten Datentypen und Prozeduren des Moduls SYSTEM sind nicht in Modula-2 ausgedrückt; sie stellen gewissermaßen eine Spracherweiterung dar.
Die von einem Hersteller gelieferten Bibliotheks-Module werden hier unter dem Begriff Grundbibliotheks-Module zusammengefaßt. Sie beinhalten zum einen in der Sprachdefinition von Modula-2 festgelegte Standard-Module (nach Wirth) und zum anderen Module, die weitere Spracherweiterungen in Form von Konstanten, Typen, Prozeduren oder Funktionsprozeduren anbieten. Zu den GrundbibliotheksModulen gehören folgende Module:
1.4 Nichtresidenter Sprachteil
-
ASCII: Der Modul ASCII enthält symbolische Konstanten für nicht druckbare ASCII-Zeichen.
-
Break: Der Modul Break stellt einen Interrupt-Händler für den Kontroll-Break-Interrupt (Ctrl-Break) der Betriebssysteme MS-DOS bzw. PC-DOS zur Verfügung.
-
CardinallO: Der Modul CardinallO bietet Lese-/Schreiboperationen für dezimale oder hexadezimale Cardinal-Zahlen (Datentyp CARDINAL) an.
-
Clock: Der Modul Clock stellt das im Betriebssystem MS-DOS geführte Datum oder die dort geführte Uhrzeit zur Verfügung.
-
Conversi: Der Modul Conversi konvertiert Oktalzahlen, Hexazahlen, ganze Zahlen (Datentyp INTEGER) und natürliche Zahlen (Datentyp CARDINAL) in Zeichenketten (Datentyp ARRAY OF CHAR).
-
Devices: Der Modul Devices ermöglicht weitere Zugriffe auf den Interrupt-Controller, die nicht direkt durch die Systemprogrammierung in Modula-2 erfolgen.
-
DiskDirectory: Der Modul DiskDirectory stellt Operationen zur Manipulation von MS-DOS-Inhaltsverzeichnissen zur Verfügung.
-
DiskFile: Der Modul DiskFile stellt die Verbindung zwischen dem Datei-System (Bibliotheks-Modul FileSystem) und dem operierenden Betriebssystem dar.
-
FileMessage: Der Modul FileMessage gibt den Datei-Fehlerstatus auf dem Bildschirm aus.
-
FileSystem: Der Standard-Modul FileSystem bietet Operationen zur Datei-Verarbeitung an.
-
InOut: Der Standard-Modul InOut stellt Ein-/Ausgabeoperationen für Dateien, das Benutzer-Terminal und andere Einheiten zur Verfügung.
-
MathLibO: Der Standard-Modul MathLibO bietet mathematische Funktionen auf reellen Zahlen (Datentyp REAL) an.
-
NumberConversions: Der Modul NumberConversions konvertiert ganze Zahlen (Datentyp INTEGER) und natürliche Zahlen (Datentyp CARDINAL) in Zeichenketten und umgekehrt.
9
10
1. Modulare Sprachen
-
Processe: Der Standard-Modul Processe stellt Synchronisationsoperationen zur nichtsequentiellen Programmierung zur Verfügung.
-
Program: Der Modul Program erlaubt die Ausführung von eigenständigen Programmen aus einem Modula-2-Programm, d.h. Modula-2Programme lassen sich aus Modula-2-Programmen laden und aufrufen.
-
RealConv: Der Modul RealConv konvertiert reelle Zahlen (Datentyp REAL) in Zeichenketten (Datentyp ARRAY OF CHAR) und umgekehrt.
-
ReallnOut: Der Standard-Modul ReallnOut stellt Ein-/Ausgabeoperationen für reelle Zahlen (Datentyp REAL) zur Verfügung.
-
ScreenlO: Der Modul ScreenlO stellt Bildschirmfunktionen, wie Cursorpositionierung, Löschen des Bildschirms usw., bereit.
-
Storage: Der Modul Storage enthält die zur Manipulation des Arbeitsspeichers notwendigen Zugriffsoperationen.
-
String: Der Standard-Modul String stellt Operationen auf Zeichenketten (Datentyp ARRAY OF CHAR) zur Verfügung.
-
Terminal: Der Standard-Modul Terminal bietet Ein-/Ausgabeoperationen auf Zeichen (Datentyp CHAR) und Zeichenketten (Datentyp ARRAY OF CHAR) an.
Wie bereits angedeutet, kann jeder Anwender von Modula-2 den nichtresidenten Sprachteil erweitern, indem er immer wieder benutzte Module in seiner BenutzerBibliothek ablegt. Das von einem Hersteller gelieferte nichtresidente Grundsystem, bestehend aus Basis- und Bibliotheks-Modulen, läßt sich auf diese Weise entsprechend den Bedürfnissen des einzelnen Benutzers erweitern. Beispiele für einige der gebräuchlichsten Module in Benutzer-Bibliotheken sind: -
PrintIO: Der Modul definiert Operationen auf dem Drucker, wie etwa Ansteuerungsprozeduren für Schriftbreiten, Schrifttypen, Tabulatoren usw. Des weiteren enthält der Modul Operationen, die der Ausgabe ganzer Zahlen (Datentyp INTEGER), natürlicher Zahlen (Datentyp CARDINAL), Zeichen (Datentyp CHAR) und Zeichenketten (Datentyp ARRAY OF CHAR) auf dem Drucker dienen.
-
MaskMan: Der Modul verwaltet vom Benutzer definierte Menues und Masken und ruft diese im Programmablauf auf.
-
IdxSeq: Der Modul stellt Funktionen zur Verwaltung index-sequentiell organisierter Dateien auf externen Speichern zur Verfügung.
11
1.5 Struktur von Modula-2-Prograramen
Eine Übersicht über die Module des residenten und des nichtresidenten Sprachteils der Programmiersprache Modula-2 gibt die Abbildung 1.1.
ProgrammModule
Grundbiblio — theks — Module. Benutzer — bibliotheks — Module
Module
des
nichtresidenten
Sprachteils
Abb. 1.1: Übersicht über Module in Modula-2.
1.5 Struktur von Modula-2-Programmen Wie aus den bisherigen Ausführungen folgt, stellt ein komplexeres Modula-2-Programm ein bausteinartig aus Modulen zusammengesetztes Softwaresystem dar. Das System ist hierarchisch in mehreren Schichten aufgebaut. An der Spitze der Hierarchie residiert ein Programm-Modul. Dieser „Top-Modul" repräsentiert das gesamte System. Beim Programmstart erhält er als erster Modul die Kontrolle, die er dann an einen anderen Modul durch Aufruf einer Funktion dieses Modul weitergibt. Durch den Aufruf einer Modulfunktion ist eine hierarchische Beziehung zwischen rufendem und aufgerufenem Modul definiert. Der aufgerufene Modul ist dem rufenden Modul untergeordnet. Da mit einem Aufruf das Benutzen einer Funktion des untergeordneten Moduls durch den übergeordneten Modul verbunden ist, spricht man in diesem Zusammenhang auch von Benutzt-Beziehung. Die Benutzt-Bezie-
12
1. Modulare Sprachen
hung läßt sich grafisch durch eine Verbindungslinie zwischen zwei als Rechteck dargestellten Modulen repräsentieren. In Abbildung 1.2 benutzt der übergeordnete Modul A den untergeordneten Modul B.
Export-Schnittstelle
von A
Import-Schnittstelle
von A
Benutzt-Beziehung Export-Schnittstelle Modul
von B
B
Abb. 1.2: Benutzt-Beziehung zwischen zwei Modulen.
Die Benutzt-Beziehung kann man auch als einen Transfer von Diensten über definierte Modulschnittstellen ansehen. Wie Abbildung 1.2 zeigt, kann ein Modul zwei Schnittstellen besitzen, eine Exportschnittstelle und eine Importschnittstelle. Über seine Exportschnittstelle stellt ein Modul Dienste an übergeordnete Module in Form von Funktionen zur Verfügung. Er „exportiert" in ihm eingekapselte Funktionen. Umgekehrt bezieht ein Modul über seine Importschnittstelle Dienste untergeordneter Module durch den Aufruf von Funktionen dieser Module. Er „importiert" benötigte Funktionen. Erwähnt sei noch, daß sich der Transfer von Diensten im Rahmen der Benutzt-Beziehung nicht auf Funktionen beschränken muß. Er kann auch den Import/Export von Datentypen und Datenobjekten einschließen. Das Wesen der Benutzt-Beziehung läßt sich grafisch deutlicher ausdrücken, wenn man das Innere von Modulen einbezieht. Eine entsprechende Darstellung zeigt Abbildung 1.3.
1.5 Struktur von Modula-2-Programmen
13
Abb. 13: Verfeinerte Darstellung der Benutzt-Beziehung.
Laut Abb. 1.3 umfaßt Modul A drei Funktionen. Zwei davon, AI und A2, können von übergeordneten Modulen benutzt werden. Dies gilt nicht für A3, die als interne Funktion über die Export-Schnittstelle von Modul A nach außen nicht „sichtbar" bzw. von außen nicht benutzbar ist. A3 dient internen Zwecken und kann von den Funktionen AI und A2 aufgerufen werden. Solche internen Funktionen lassen sich auch zu internen Modulen zusammenfassen. Module dieser Art heißen in Modula-2 lokale Module. Modul B umfaßt auch drei zusammengehörige Funktionen, die auf die gemeinsame Datenstuktur DB1 zugreifen. Zwei dieser Funktionen, B1 und B2, werden von der Funktion AI benutzt und die dritte, B3, von der Funktion A2. Man beachte, daß
14
1. Modulare Sprachen
Modul A nicht direkt auf die Datenstruktur DB1 zugreifen kann. Vielmehr müssen die Dienste der Funktionen Bl, B2 und B3 in Anspruch genommen werden. Nur diese Funktionen können Objekte dieser Struktur nach außen zur Verfügung stellen. Ein komplexeres, aus einem Programm-Modul, lokalen und globalen Modulen bestehendes Softwaresystem ist schematisch in Abbildung 1.4 dargestellt. Man bezeichnet diese Darstellung auch als Moduldiagramm. Modul 1 stellt den ProgrammModul dar, die Module 6 und 7 sind lokale Module des globalen Moduls 3, und die übrigen Module sind globale Module.
Abb. 1.4: Schematische Darstellung eines allgemeinen Moduldiagramms.
Für die Arbeitsweise eines derartigen, modular-hierarchisch strukturierten Softwaresystems gilt: -
Es existiert ein ausgezeichneter Modul an der Spitze der Hierarchie, der Programm-Modul, der die Kontrolle zuerst erhält und hierarchisch tiefer liegende Module aufruft.
-
Die gesamte Verarbeitung findet in Funktionen innerhalb von Modulen statt, und die Steuerung des Arbeitsablaufes erfolgt ausschließlich über Funktionsaufrufe.
1.5 Struktur von Modula-2-Programmen
-
Es gibt keine globalen Datenobjekte. Funktionen haben nur Zugang zu den Datenobjekten des Moduls, in dem sie definiert sind. Datenobjekte anderer Module sind nur über den Aufruf von Zugriffsfunktionen dieser Module erreichbar.
-
Die Datenobjekte eines Moduls repräsentieren gleichsam ein Modulgedächtnis, indem sie Werte dieser Objekte auf unbestimmte Zeit speichern. Eine Änderung der mit diesem Gedächtnis umschlossenen Werte erfolgt durch den Aufruf der Zugriffsprozeduren durch andere Module.
15
Im einfachsten Fall besteht ein Moduldiagramm aus nur einem Modul, dem Programm-Modul. Vor allem die einfachen Beispiele in den ersten Teilen des Kapitels 2 sind derart beschaffen. Bei aus mehreren Modulen bestehenden Systemen ist von Interesse, welche der globalen Module spezielle problembezogene Module darstellen und welche Module der Modulbibliothek des nichtresidenten Sprachteils angehören. Entsprechende Auskunft gibt der Modulname, da die Namen der BibliotheksModule „reserviert" sind. Bei größeren Moduldiagrammen kann es aus Gründen der Übersichtlichkeit ratsam sein, nur die problembezogenen Module in das Diagramm einzubeziehen. Zur Dokumentation sämtlicher Modulbeziehungen eignet sich dann eine Beziehungsmatrix. In ihr stellt man die Gesamtmenge der Module einmal als Menge von Zeilen und einmal als Menge von Spalten dar. Ein Kreuz an der Überschneidungsstelle einer Spalte und einer Zeile markiert die Existenz einer BenutztBeziehung zwischen den entsprechenden Modulen.
2. Sprachstandard Ziel des vorliegenden zweiten Kapitels ist es, den Modula-2-Sprachstandard darzustellen, wie er von Wirth [WI] festgelegt wurde. Mit Ausnahme der Dateiverarbeitung (siehe Kapitel 2.3.2.5) beziehen sich sämtliche Ausführungen des zweiten Kapitels auf diesen Sprachstandard. Modula-2 kennt keinen Dateityp. Die Dateiverarbeitung ist daher in einem nicht zum Sprachstandard gehörenden globalen Modul organisiert. Modula-2 ist eine höhere Programmiersprache, die die Formulierung von Programmen in einer dem Englischen ähnlichen Sprache gestattet. Die zulässigen sprachlichen Formulierungen sind gegenüber der Umgangssprache stark eingeschränkt. Vorgegeben werden diese Einschränkungen durch verschiedene sprachliche Regeln, deren Gesamtmenge die Syntax der Programmiersprache Modula-2 bildet. Die Syntax der Sprache Modula-2 läßt sich auf verschiedene Arten beschreiben. So beispielsweise mit Worten, mit der BNF-Notation (Backus-Naur-Form), einer formelartigen Beschreibungsweise oder mit Syntaxdiagrammen. Bei verbal formulierten Regeln sind Mißverständnisse nicht immer auszuschließen. Deshalb zieht man es meist vor, die Syntax einer Programmiersprache in einer formalisierten Sprache anzugeben. Diese Formalisierung besitzt jedoch den Nachteil, daß sie für Programmieranfänger schwer verständlich ist. Im folgenden wird daher eine grafische Darstellungsweise der Sprachsyntax von Modula-2 gewählt. Als grafische Darstellungstechnik besonders geeignet sind Eisenbahndiagramme. Sie verknüpfen einzelne Sprachsymbole, gewissermaßen wie auf Schienen, zu Syntaxdiagrammen. Syntaxdiagramme beschreiben, wie die zu verwendenden Symbole einer Programmiersprache anzuordnen sind. Alle syntaktisch erlaubten Zusammenhänge sind in ihnen verankert. Sie sagen jedoch nichts über die Bedeutung (Semantik) einer Sprache aus. Eisenbahndiagramme bestehen aus Linien („Schienen"), elementaren Ereignissen („End-Bahnhöfen"), nicht elementaren Ereignissen („Umsteige-Bahnhöfen") und Flußrichtungen („Streckenrichtungen"). Jedes Diagramm und jedes Ereignis trägt einen Namen. Abbildung 2.1 gibt einen Überblick über die Symbole von Eisenbahndiagrammen.
2. Sprachstandard
18
Linie
("Schiene")
Flußrichtung (
)
("Streckenrichtung
"j
elementares Ereignis ("Endbahnhof nichtelementares
")
Ereignis
("Umsteigebahnhof
")
Abb. 2.1: Symbole von Eisenbahndiagrammen.
Das nun folgende Kapitel 2.1 analysiert die Bestandteile der Sprache Modula-2. Im Kapitel 2.2 kann dann der Programmaufbau, bestehend aus Deklarationen, Anweisungen und Ausdrücken, behandelt werden. Die von der Programmiersprache angebotenen Standarddatentypen werden im Kapitel 2.3 vorgestellt. Das Kapitel 2.4 stellt Ein-/Ausgabe-Operationen vor, während die Aufgabe des Kapitels 2.5 in der Behandlung von Modula-2-Anweisungen liegt. Das Kapitel 2.6 ist den Modulen der Sprache Modula-2 gewidmet. Es werden zum einen lokale Module vorgestellt und zum anderen die die Sprache auszeichnenden globalen Module behandelt. Gegenstand des Kapitels 2.7 sind dynamische Datenstrukturen, während Kapitel 2.8 eine Einführung in die rekursive Programmierung gibt. Schließlich gibt Kapitel 2.9 eine kurze Einführung in die Programmierung nichtsequentieller Algorithmen.
2.1 Bestandteile der Sprache Wie alle Programmiersprachen verfügt auch Modula-2 über einen bestimmten Vorrat an Symbolen, die aus verschiedenen, aufeinander folgenden Zeichen aufgebaut sind. Modula-2 kennt sieben Symboltypen: Bezeichner (Identifier), Schlüsselwörter (reserved words), Zahlen (numbers), Zeichenketten (strings), Operatoren (operators), Begrenzer (delimiters) und Kommentare (comments). Im folgenden wird nun beschrieben, nach welchen Bildungsgesetzen diese Symboltypen aufgebaut sind.
2.1.1 Bezeichner
19
2.1.1 Bezeichner Bezeichner dienen der Benennung von in Programmen auftretenden Objekten. Im einzelnen kann es sich dabei um Datenobjekte, Module, Prozeduren usw. handeln. Zur eindeutigen Identifizierung können Objekten Namen zugewiesen werden. Im deutschen Sprachraum hat sich für diese Namen das Wort Bezeichner (vom englischen identifier) eingebürgert. Im folgenden werden die Begriffe „Name" und „Bezeichner" synonym verwendet. In Modula-2 müssen Bezeichner nach der in Abbildung 2.2 definierten Regel vereinbart werden. Eine in mehrere Punkte gegliederte verbale Beschreibung dieses Diagramms ist in der Tabelle 2.1 angegeben.
Ident
Abb. 2.2: Syntaxdiagramm für „Ident" (Bezeichner).
Tab. 2.1: Beschreibung des Syntaxdiagramms für „Ident". (1) Bezeichner können aus den Buchstaben A...Z und a...z (letter) sowie den Ziffern 0...9 (digit) zusammengesetzt sein und aus einem oder mehreren Zeichen bestehen. (2) Umlaute (ä, Ä, ö, Ö, ü, Ü), das ß-Zeichen, das Leerzeichen (" ") und Sonderzeichen ( ! , " , # , $ , % , & , ( , ) , *, + , „ - , . , / , : , ; , , ? , [ , \ , ] , |,}, usw.) sowie die Zeichen des erweiterten Zeichensatzes sind in einem Bezeichner nicht zulässig. (3) Groß- und Kleinschreibung haben unterschiedliche Bedeutung.
2. Sprachstandard
20
(4) Das erste Zeichen eines Bezeichners muß ein Buchstabe sein. (5) Es gibt keine Längenbeschränkung für Bezeichner. (6) Ein Bezeichner, der ein einzelnes Objekt eindeutig qualifiziert, heißt qualifizierter Bezeichner. (7) Reicht die einfache Qualifizierung zur eindeutigen Bezeichnung eines Objekts nicht aus, so kann die Qualifikation unter Abtrennung mit einem Punkt wiederholt werden (z.B. x.y).
Die Syntax für „Letter" und „Digit" ist in den Abbildungen 2.3 und 2.4 definiert und in den Tabellen 2.2 und 2.3 verbal beschrieben.
Letter
Abb. 2.3: Syntaxdiagramm für „Letter" (Buchstabe).
Tab. 2.2: Beschreibung des Syntaxdiagramms für „Letter". Ein Buchstabe kann ein Element aus der Menge der Großbuchstaben (A...Z) oder aus der Menge der Kleinbuchstaben (a...z) sein.
21
2.1.2 Schlüsselwörter
Digit
Abb. 2.4: Syntaxdiagramm für „Digit" (Ziffer).
Tab. 2.3: Beschreibung des Syntaxdiagramms für „Digit". Eine Ziffer ist ein Element aus der Menge der Ziffern 0..9.
2.1.2 Schlüsselwörter Jede Programmiersprache verfügt über Wörter mit festgelegter Bedeutung. Diese werden als Schlüsselwörter (engl, key words) oder als reservierte Wörter bezeichnet. Schlüsselwörter sind in Modula-2 genauso aufgebaut wie Bezeichner, jedoch werden sie ausschließlich aus Großbuchstaben ( A . . . Z ) gebildet. Reservierte Wörter dürfen nicht als Namen für Objekte, sondern nur für ganz bestimmte Zwecke verwendet werden. Auf diese Zwecke wird erst in den folgenden Kapiteln eingegangen. Anders als in anderen Programmiersprachen gibt es in Modula-2 nur 40 Schlüsselwörter, die man sich leicht merken kann. Eine Aufstellung sämtlicher Schlüsselwörter enthält die Tabelle 2.4.
2.1.3 Zahlen Modula-2 unterscheidet - wie die meisten anderen Programmiersprachen auch ganze Zahlen (engl, integer numbers), natürliche Zahlen (engl, cardinal numbers) und gebrochene Zahlen (reelle Zahlen, engl, real numbers). Abbildung 2.5 zeigt den Aufbau einer Zahl. Die zugehörige Beschreibung findet sich in Tabelle 2.5.
2. Sprachstandard
22
Tab. 2.4: Schlüsselwörter (reservierte Wörter) in Modula-2. AND ARRAY BEGIN BY CASE CONST DEFINITION DIV DO ELSE ELSIF END EXIT EXPORT
FOR FROM IF IMPLEMENTATION IMPORT IN LOOP MOD MODULE NOT OF OR POINTER PROCEDURE
QUALIFIED RECORD REPEAT RETURN SET THEN TO TYPE UNTIL VAR WHILE WITH
Number
Abb. 2.5: Syntaxdiagramm für „Number" (Zahl).
Tab. 2.5: Beschreibung des Syntaxdiagramms für „Number". Zahlen bestehen in Modula-2 aus ganzen Zahlen (INTEGER), aus natürlichen Zahlen (CARDINAL) oder aus reellen Zahlen (REAL).
23
2.1.3 Zahlen
Ganze und natürliche Zahlen werden im allgemeinen zum Zählen oder als Laufindex verwendet. Im Gegensatz zu gebrochenen Zahlen haben sie jedoch einen stärker begrenzten Darstellungsbereich; Integer-Zahlen haben einen Darstellungsbereich von -32768...32767 und Cardinal-Zahlen von 0...65535. Die nun folgende Abbildung 2.6 definiert die Syntax von Integer-Zahlen. Die zugehörige verbale Beschreibung geht aus der Tabelle 2.6 hervor.
Integer
Tab. 2.6: Beschreibung des Syntaxdiagramms für „Integer". (1) Eine Integer-Zahl besteht aus einer Ziffer oder mehreren aufeinanderfolgenden Ziffern. (2) Integer-Zahlen dürfen aus maximal zehn Ziffern bestehen, wobei der Zahlenwert den Wertebereich von Integer-Zahlen nicht verlassen darf. (3) Eine negative Integer-Zahl wird durch ein vorangestelltes Minuszeichen gekennzeichnet.
In Modula-2 können natürliche Zahlen in verschiedenen Zahlenbasen (Dezimal, Oktal- und Hexadezimalsystem) dargestellt werden (siehe hierzu Abbildung 2.7 bis Abbildung 2.10 und die Erläuterungen in den Tabellen 2.7 bis 2.10).
2. Sprachstandard
24
Cardinal / w,
J
OecCardinal \
OcKardinal
w
• HexCardinai Abb. 2.7: Syntaxdiagramm für „Cardinal" (natürliche Zahl).
Tab. 2.7: Beschreibung des Syntaxdiagramms für „Cardinal". Eine Cardinal-Zahl besteht entweder aus einer Dezimal-, einer Oktaloder einer Hexadezimalzahl.
OecCardinal /
+
V
Digit
\
j
Abb. 2.8: Syntaxdiagramm für „DecCardinal" (Dezimalzahl).
Tab. 2.8: Beschreibung des Syntaxdiagramms für „DecCardinal". (1) Eine Dezimalzahl besteht aus einer oder mehreren aufeinander folgenden Ziffern. (2) Dezimalzahlen dürfen aus maximal fünf Ziffern bestehen, wobei der Zahlenwert den Wertebereich von Dezimalzahlen nicht verlassen darf. (3) Dezimalzahlen besitzen kein Vorzeichen und sind folglich stets positiv.
25
2.1.3 Zahlen
OctCardinal
Abb. 2.9: Syntaxdiagramm für „OctCardinal" (Oktalzahl).
Tab. 2.9: Beschreibung des Syntaxdiagramms für „OctCardinal". Oktalzahlen bestehen aus den Ziffern 0...7 und werden am Ende durch den Buchstaben B gekennzeichnet.
Hex Cardinal
, #
-
Ungleichheit
—
größer
< =
—
kleiner gleich
> =
—
größer gleich
SONSTIGE OPERATOREN :=
—
Wertzuweisung
^
—
Auswertung von Zeigervariablen
KLAMMERN ()
—
runde Klammern (Klammern im algebraischen Sinne)
[]
—
Indexklammern
{ }
—
Mengenklammern
2.1.6 Begrenzer Bisher haben wir Objekte und Operatoren kennengelernt. Um Objekte voneinander trennen zu können, müssen sogenannte Begrenzer (Delimiter) eingeführt werden. In älteren Programmiersprachen kommt man fast ohne Begrenzer aus, weil bestimmte Symbole nur in bestimmten Bereichen von Code-Zeilen auftreten dürfen. In Modula-2 gibt es keine derartigen Vorschriften für die äußere Programmform. Es muß lediglich gewährleistet sein, daß die in einem Programm enthaltenen Symbole vom Compiler als solche erkannt werden können, das heißt, der Compiler muß er-
31
2.1.7 Kommentare
kennen können, wo ein Symbol endet und das nächste beginnt. Bevor nun in der Tabelle 2.17 einige Regeln zur eindeutigen Begrenzung von Symbolen angegeben werden, seien in der Tabelle 2.16 alle in Modula-2 verwendeten Begrenzer aufgeführt.
Tab. 2.16: Begrenzer in Modula-2. Komma Punkt Strichpunkt (Semikolon) Doppelpunkt Bereichssymbol Trennstrich
2.1.7 Kommentare Kommentare spielen in Programmen eine wesentliche Rolle. Man benötigt sie, um Programmen zusätzliche Informationen, meist als Erläuterungen einzelner Anweisungen oder Anweisungsfolgen, hinzuzufügen. Da der Compiler Kommentare ignoriert, haben sie keinerlei Auswirkung auf die Programmausführung. Auch beim Testen von Programmen werden Kommentare vorteilhaft eingesetzt, indem Programmanweisungen zu Kommentaren umfunktioniert werden, ohne sie dem Quelltext zu entnehmen. Dann spricht man vom „Auskommentieren" von Programmteilen. Die in der Tabelle 2.18 angegebenen Regeln sind bei der Anwendung von Kommentaren zu beachten.
Tab. 2.17: Begrenzungsregeln für Symbole. (1) Symbole dürfen nicht getrennt werden. (2) Symbole dürfen keine Leerzeichen enthalten (eine Ausnahme stellen die Zeichenketten dar).
32
2. Sprachstandard
(3) Symbole können unmittelbar hintereinander stehen, wenn Mißverständnisse ausgeschlossen sind.
Tab. 2.18: Regeln für die Kommentarprogrammierung. (1) Kommentare dürfen wie Leerzeichen überall zwischen zwei aufeinanderfolgenden Symbolen stehen. (2) Kommentare beginnen bzw. enden mit der Zeichenkombination (* und *) oder mit dem Zeichen { und } . (3) Kommentare dürfen sich über mehrere Zeilen erstrecken und können wiederum Kommentare enthalten. (4) Kommentare dürfen - im Gegensatz zu Bezeichnern und Schlüsselwörtern auch Leerzeichen, Sonderzeichen, Umlaute sowie das ß-Zeichen, Grafikzeichen und länderspezifische Zeichen enthalten.
Beispiele Bezeichnen Wert, G60, EinGanzLangerBezeichner, ... Schlüsselwörter: AND, DIV, END, FOR, REPEAT, TYPE, ... Zahlen: - 1 0 0 0 0 , 0, 50000, 3.14, 0., 22B, 01AH, ... Zeichenketten: 'Dieses ist eine Zeichenkette!',"", ... Operatoren: - , + , *, ..., AND, OR
=,
?
2.3.1.5 Datentyp CHAR
79
Tab. 2.64b: ASCII-Code-Tabelle für Einzelzeichen der Ordnungszahlen 64 bis 127. Dezimal
Oktal
Hexa
Zeichen
Dezimai
Oktal
Hexa
Zeichen
64 65 66 67
100B 101B 102B 103B
40 H 41H 42H 43H
A B C
@
96 97 98 99
MOB 141B 142B 143B
60H 61H 62H 63H
a b c
68 69 70 71
104B 105B 106B 107B
44H 45H 46H 47H
D E F G
100 101 102 103
144B 145B 146B 147B
64H 65H 66H 67H
d e f g
72 73 74 75
110B 111B 112B 113B
48H 49H 4AH 4BH
H I J K
104 105 106 107
150B 151B 152B 153B
68H 69H 6AH 6BH
h i i k
76 77 78 79
114B 115B 116B 117B
4CH 4DH 4EH 4FH
L M N 0
108 109 110 111
154B 155B 156B 157B
6CH 6DH 6EH 6FH
I m n 0
80 81 82 83
120B 121B 122B 123B
50H 51H 52H 53H
P Q R S
112 113 114 115
160B 161B 162B 163B
70H 71H 72H 73H
P q
84 85 86 87
124B 125B 126B 127B
54H 55H 56H 57H
T U V w
116 117 118 119
164B 165B 166B 167B
74H 75H 76H 77H
88 89 90 91
130B 131B 132B 133B
58H 59H 5AH 5BH
X Y z
120 121 122 123
170B 171B 172B 173B
78H 79H 7AH 7BH
92 93 94 95
134B 135B 136B 137B
5CH 5DH 5EH 5FH
\
124 125 126 127
174B 175B 176B 177B
7CH 7DH 7EH 7FH
[
•
r
s t U V
w X
y z
{ I t
DEL
80
2. Sprachstandard
Zur rechnerinternen Darstellung eines Zeichens bzw. einer Variablen vom Typ CHAR werden somit 8 Bits (1 Byte) benötigt. Der rechnerinternen Darstellung eines Zeichens kommt eine besondere Bedeutung zu. Sie dient als Ordnungszahl, die die Stelle eines Zeichens innerhalb des Zeichensatzes festlegt. Die die Zeichen intern identifizierenden Ordnungszahlen ermöglichen einen Vergleich von Einzelzeichen. Die Ziffern 0 bis 9, die Großbuchstaben A bis Z und die Kleinbuchstaben a bis z werden jeweils durch aufeinanderfolgende Ordnungszahlen identifiziert. Die Ordnungszahlen steigen in der angegebenen Reihenfolge an. Die Ordnungszahlen der Kleinbuchstaben sind also größer als die der Großbuchstaben. Speziellen Aufgaben dient eine bestimmte Gruppe von Zeichen. Es handelt sich um die Steuerzeichen, die im ASCII-Code im Bereich der Ordnungszahlen von 0 bis 31 liegen. Steuerzeichen lösen bestimmte Funktionen des Betriebssystems aus, so beispielsweise beim Editieren, beim Ansteuern von Bildschirm und Drucker, aber auch bei der Kommunikation in Netzwerken (LAN) und bei der Datenfernverarbeitung. Eine Besonderheit weist auch das Ansprechen von Steuerzeichen auf. Steuerzeichen können nicht direkt mit nur einer Taste der Tastatur angesprochen werden, vielmehr sind jeweils bestimmte Tastenkombinationen zu verwenden. Zeichenkonstanten des Datentyps CHAR stellen einzelne in Hochkommata oder Anführungszeichen eingeschlossene Zeichen dar. Nicht druckbare Zeichen, wie beispielsweise die Steuerzeichen, können durch ihre oktale Ordnungszahl, gefolgt von dem Suffix C angesprochen werden. Ein Hochkomma oder Anführungszeichen muß mit dem jeweils anderen Anschlußzeichen eingeschlossen werden (also: "dieses ist ein Text" oder 'auch dieses ist einer'). Mit Variablen oder Werten vom Typ CHAR sind, abgesehen vom Vergleich, keine Rechenoperationen möglich. Jedoch lassen sich die in der Tabelle 2.65 angegebenen Relationen und die in der Tabelle 2.66 genannten, im Compiler verankerten Standardfunktionen auf CHAR-Objekte anwenden.
Tab. 2.65: Relationen auf Datenobjekte vom Typ CHAR. Relationen zwischen Datenobjekten des Typs CHAR können durch die Relationaloperatoren = , < > , # , < , < = , > und > = ausgedrückt werden. Als Ergebnis einer Relation wird ein boole'scher Wert (TRUE, FALSE) geliefert, der sich aus dem Vergleich der Ordnungszahlen der beteiligten Zeichen ergibt.
Beispiele
81
Tab. 2.66: Standardfunktionen auf Datenobjekte vom Typ CHAR. (1) Standardfunktionen sind: CAP, CHR, MAX, MIN, ORD und VAL. (2) Argumente der Funktionen sind Datenobjekte vom Typ CHAR oder deren Ordnungszahlen. (3) Die Funktionen MAX, MIN und VAL wurden bereits bei der Behandlung des Datentyps INTEGER vorgestellt (siehe hierzu Tabelle 2.52). (4) Die Funktion CAP liefert als Ergebnis einen Großbuchstaben, falls ein Kleinbuchstabe als Argument übergeben wurde. (5) Die Funktion CHR liefert für eine als Argument übergebene Ordnungszahl das zugehörige Zeichen der ASCII-Code-Tabelle (nach der vierten Überarbeitung des Modula-2-Standards durch Wirth kann das Argument eine INTEGER-Zahl sein). (6) Die Funktion ORD liefert für ein als Argument übergebenes Zeichen die zugehörige Ordnungszahl der ASCII-Code-Tabelle (nach der vierten Überarbeitung des Modula-2-Standards durch Wirth kann das Ergebnis eine INTEGER-Zahl sein).
Beispiele MODULE CharDemo; VAR zehn CHAR; ordzahl : CARDINAL; BEGIN zehn zehn zehn ordzahl
= a; = CAP(zchn); (* Umwandlung In Großbuchstabe A *) = CHR(48); (* ergibt das Zeichen 0 (Ordnungszahl = 48) • : = ORD('a');
END CharDemo.
(* ergibt die Ordnungszahl = 97 '
2. Sprachstandard
82
2.3.1.6 Datentyp BOOLEAN Der Datentyp BOOLEAN repräsentiert Wahrheitswerte, auch logische oder boole'sche Werte genannt. Sein Wertebereich ist auf die beiden Werte „wahr" und „falsch" beschränkt. Diese beiden Wahrheitswerte werden in Modula-2 durch die vordeklarierten Konstanten TRUE und FALSE dargestellt. Rechnerintern beansprucht ein Datenobjekt vom Typ BOOLEAN einen Speicherplatz von 8 Bits (1 Byte). Boole'sche Werte können nicht nur bei der Verarbeitung boole'scher Datenobjekte, sondern auch beim Vergleich nicht boole'scher Datenobjekte entstehen. In boole'schen Ausdrücken können daher - Vergleichsoperationen auf nichtboole'sche Datenobjekte und -
logische Operationen auf boole'sche Werte oder Datenobjekte vom Typ BOOLEAN
gemeinsam auftreten. Mit Ausnahme des Vergleichs sind Rechenoperationen auf Datenobjekte vom Typ BOOLEAN nicht zulässig. Als Operanden für Vergleiche in boole'schen Ausdrücken kommen Datenobjekte einfacher Datentypen (INTEGER, CARDINAL, REAL, LONGINT, CHAR, BOOLEAN, Aufzählungs- und Unterbereichstyp), aber auch strukturierte Datentypen (RECORD, Menge, Array, usw.) in Frage. Vergleichsoperationen enthalten stets relationale Operatoren. Die in boole'schen Ausdrücken verwendbaren Relationaloperatoren sind in der Tabelle 2.67 zusammengestellt. Beim Einbeziehen von Vergleichen in boole'sche Ausdrücke ist darauf zu achten, daß die logische Konstante T R U E größer ist als als die logische Konstante FALSE. Vergleiche lassen sich völlig bedeutungsgleich auch als logische Operationen darstellen. In der Tabelle 2.67 ist angegeben, welchen logischen Ausdrücken einzelne Relationen entsprechen. Tab. 2.67: Relationen auf Datenobjekte vom Typ BOOLEAN. (1) Relationen zwischen Datenobjekten vom Typ BOOLEAN können durch die Relationaloperatoren = , < > , < , < = , > und > = ausgedrückt werden. Ergebnis einer Relation ist stets ein boole'scher Wert (TRUE, FALSE), (2) Die Gleichheitsrelation x = y entspricht dem logischen Ausdruck x AND y OR NOT x AND NOTy. (3) Die Ungleichheitsrelation x < > y entspricht dem logischen Ausdruck x AND NOT y OR NOT x AND y.
2.3.1.6 Datentyp BOOLEAN
83
(4) Die Kleiner-Relation x < y entspricht dem logischen Ausdruck NOT x AND y. (5) Die Kleiner-Gleich-Relation x< =y entspricht dem logischen Ausdruck: NOT xORy, (6) Die Größer-Relation x > y entspricht dem logischen Ausdruck x AND NOT y. (7) Die Größer-Gleich-Relation x > =y entspricht dem logischen Ausdruck x OR NOTy.
Logische Operationen verknüpfen Werte oder Objekte vom Typ BOOLEAN mit Hilfe der logischen Operatoren AND (logisches Und), OR (logisches Oder) oder NOT (logische Negation). Bedeutung und Wirkung dieser Operatoren sind in der Tabelle 2.68 beschrieben. Boole'sche Ausdrücke können beliebig lang sein. Komplexeren booleschen Ausdrücken ist auf Anhieb oft nicht anzusehen, in welcher Weise die Auswertung erfolgt. Die Auswertungsfolge hängt von den verwendeten logischen Operatoren und eventuell gesetzten runden Klammern ab. Sind keine runden Klammern gesetzt, so orientiert sich die Auswertung ausschließlich am Vorrang der Operatoren. Man spricht dann auch von impliziter Auswertung. Die in Modula-2 gültigen Vorrangregeln für die implizite Auswertung von Operatoren gehen aus Tabelle 2.69 hervor. Diese Tabelle enthält auch Angaben zur expliziten Auswertung, die durch das Setzen runder Klammern bewirkt wird. Das Setzen runder Klammern beeinflußt einerseits die Auswertungsfolge und ist andererseits ein Mittel zur transparenteren Strukturierung logischer Ausdrücke.
Tab. 2.68: Operationen auf Datenobjekte vom Typ BOOLEAN. (1) Logische Operationen können mit den logischen Operatoren AND (Konjunktion), OR (Disjunktion) und NOT (Negation) ausgedrückt werden. (2) Die Operatoren AND und OR verknüpfen zwei logische Werte zu einem neuen logischen Wert. Ein Operator muß stets zwischen den beiden zu verknüpfenden Operanden stehen. (3) Das Ergebnis der Operation AND ist dann und nur dann wahr (TRUE), wenn beide Operanden wahr (TRUE) sind. Andernfalls ist das Ergebnis falsch (FALSE).
84
2. Sprachstandard
(4) Das Ergebnis der Operation OR ist dann und nur dann wahr (TRUE), wenn einer der Operanden oder beide Operanden wahr (TRUE) sind. Andernfalls ist das Ergebnis falsch (FALSE). (5) Der Operator NOT negiert den Wahrheitswert eines gegebenen Arguments. Das Ergebnis der Negation ist wahr (TRUE), wenn der Wahrheitswert des Arguments falsch (FALSE) ist und umgekehrt.
Tab. 2.69: Vorrangregeln für Operatoren. (1) Vier Klassen von Operatoren sind zu unterscheiden: relationale, additive, multiplikative und logische Operatoren (siehe hierzu auch Kap. 2.2.3). (2) Jede Operatorklasse besitzt eine implizit gegebene „Bindung" zu den jeweiligen Operanden. Die Stärke der Bindung steigt in der Reihenfolge, in der die Klassen in der Regel 1 genannt sind. Relationale Operatoren weisen also die schwächste und logische Operatoren die stärkste Bindung auf.
(3) Die Stärke der Bindung drückt die Reihenfolge der Auswertung der Operanden aus. Demnach werden zuerst logische Operationen ausgewertet, dann multiplikative, dann additive und zuletzt relationale. (4) Die Reihenfolge der Auswertung der Operationen kann durch Setzen von runden Klammern beeinflußt werden. Durch runde Klammern eingeschlossene Teilausdrücke werden stets zuerst ausgewertet. Sind durch runde Klammern eingeschlossene Teilausdrücke mehrfach ineinander geschachtelt, so erfolgt die Auswertung der Klammern grundsätzlich von innen nach außen. Die innerste Klammer wird also zuerst und die äußerste zuletzt ausgewertet.
85
Beispiele
Beispiele MODULE BoolDemo; VAR BoolWertl, BoolWert2, BoolWert3 : BEGIN BoolWertl BoolWert2 BoolWert3 BoolWert3 BoolWert3 BoolWert3 BoolWert3 BoolWert3 BoolWert3 BoolWert3 BoolWert3 BoolWert3
BOOLEAN;
: = TRUE; : = FALSE; : = BoolWertl AND BoolWert2; (* ergibt FALSE *) : = BoolWertl OR BoolWert2; (* ergibt TRUE *) : = NOT(BoolWert1 AND BoolWert2); (* ergibt TRUE *) : = NOT (BoolWertl OR BoolWert2); (* ergibt FALSE *) .: = NOT BoolWertl AND BoolWert2; (* ergibt FALSE *) : = NOT BoolWertl OR BoolWert2; (* ergibt FALSE *) : = BoolWertl AND NOT BoolWert2; (* ergibt T R U E * ) : = BoolWertl OR NOT BoolWert2; (* ergibt TRUE *) : = NOT BoolWertl AND NOT BoolWert2; (* ergibt FALSE *) : = NOT BoolWertl OR NOT BoolWert2; (* ergibt TRUE *)
END BoolDemo.
2.3.1.7 Aufzählungstypen Ein Aufzählungstyp (engl, enumeration type) dient dazu, zusammengehörige nichtnumerische Werte mit sinnvollen Namen zu belegen und zu einem Typ zusammenzufassen. Solche Werte können beispielsweise Elemente von Objektmengen (z.B. Apfel, Birne, Kirsche, Pflaume usw. als Elemente von Obst) oder Ausprägungen von Objektmerkmalen (z.B. rot, grün, blau, gelb usw. als Elemente von Farbe) sein. Die explizite Verwendung solcher Werte im Rahmen von Aufzählungstypen führt zu wesentlich leichter verständlichen Programmen, als die früher allgemein übliche Verschlüsselung (z.B. Verschlüsselung der Farben rot, grün, blau, gelb usw. mit den Zahlen 1,2,3,4 usw.). Verschlüsselungen besitzen den Nachteil, wesensverschiedene Phänomene durch nur einen (numerischen) Datentyp auszudrücken. Auf-
86
2. Sprachstandard
Zählungstypen ermöglichen es dagegen, unterschiedliche Phänomene auch durch unterschiedliche, das Wesen von Objekten erfassende Typen in Programmen abzubilden. Die Definition eines Aufzählungstyps erfolgt in der Weise, daß man sämtliche Werte, die er annehmen darf, in runden Klammern und getrennt durch Kommata aufzählt. Mit der Aufzählung der Werte ist zugleich die Festlegung der sogenannten Enumerationskonstanten verbunden. Die Enumerationskonstanten stellen Ordnungszahlen dar, mit denen die Werte in der Reihenfolge der Aufzählung und beginnend mit Null durchgezählt werden. Die Enumerationskonstanten dienen der rechnerinternen Identifikation der Werte von Aufzählungstypen. Zur internen Darstellung von Datenobjekten des Aufzählungstyps stehen jeweils 8 Bits (1 Byte) zur Verfügung. Zu einem Aufzählungstyp können daher bis zu 256 (2 hoch 8) Werte zusammengefaßt werden. Für die Definition von Aufzählungstypen gilt das Syntaxdiagramm i n Abbildung 2.45. Es wird in Tabelle 2.70 erläutert.
Abb. 2.45: Syntaxdiagramm für „Enumeration" (Aufzählung).
Tab. 2.70: Beschreibung des Syntaxdiagramms für „Enumeration". (1) Ein Aufzählungsdatentyp besteht aus einer in runden Klammern eingeschlossenen Liste von durch Kommata getrennten Bezeichnern. Die Liste darf keine numerischen Werte, keine bereits in anderen Typen verwendeten Werte, keine bereits verwendeten Bezeichnernamen und keine doppelten Werte enthalten. (2) Durch die in der Liste angegebene Reihenfolge der Bezeichner wird eine Ordnung definiert.
2.3.1.7 Aufzählungstypen
87
(3) Die auftretenden Werte des Aufzählungsdatentyps dürfen nur in Verwendung mit diesem Datentyp benutzt werden.
Mit Datenobjekten eines Aufzählungsdatentyps sind keine Rechenoperationen möglich. Jedoch läßt die interne Darstellung von Aufzählungstyp-Objekten mittels Ordnungszahlen die Anwendung bestimmter Relationen und Operationen zu. Als Beispiel sei der Aufzählungsdatentyp FARBE mit den Werten rot, gruen, blau und gelb betrachtet. Den Werten entsprechen die Ordnungszahlen 0, 1, 2 und 3 (in der Aufzählungsfolge). Demnach ist die zulässige Relation rot = blau nicht erfüllt. Aufzählungsdatentypen erlauben die Anwendung der in Tabelle 2.71 genannten Relationen. Darüberhinaus enthalten Modula-2-Compiler Standardfunktionen und -Prozeduren, die die Verarbeitung von Aufzählungsdatentypen gestatten. Eine entsprechende Übersicht enthält die Tabelle 2.72.
Tab. 2.71: Relationen auf Datenobjekte eines Aufzählungsdatentyps. Relationen zwischen Datenobjekten eines Aufzählungsdatentyps können durch die Relationaloperatoren = , < > , # , < , < = , > und > = ausgedrückt werden. Als Ergebnis einer Relation wird ein boole'scher Wert (TRUE, FALSE) geliefert, der sich aus dem Vergleich der Ordnungszahlen der jeweiligen Enumerationskonstanten ergibt.
Tab. 2.72: Standardfunktionen und -prozeduren auf Datenobjekte von Aufzählungsdatentypen. (1) Zur Verfügung steht eine Standardfunktion: VAL. (2) Standardprozeduren sind: DEC und INC. (3) Argumente der Funktion und der Prozeduren sind Datenobjekte von einem Aufzählungsdatentyp.
2. Sprachstandard
88
(4) Die Funktion VAL wurde bereits bei der Behandlung des Datentyps INTEGER vorgestellt (siehe hierzu Tabelle 2.52). Sie kann im gegebenen Fall nur die Ordnungszahl eines Datenobjekts eines gegebenen Aufzählungsdatentyps in einen numerischen Wert eines Zieldatentyps transformieren und als Ergebnis bereitstellen. (5) Die Prozeduren DEC bzw. INC liefern den vorangehenden bzw. nachfolgenden Wert eines Aufzählungsdatentyps in Bezug auf den als Argument übergebenen Wert. Für einen Aufzählungsdatentyp sind der dem ersten Wert vorangehende Wert und der dem letzten Wert nachfolgende Wert nicht definiert.
Beispiele MODULE EnumerationType; VAR PCHersteller (IBM, Compaq, Olivetti, Commodore, Schneider); IBMpcTyp : (XT, AT, PS2); OliTyp : (M40, M70); DiskTyp (fuenfeinviertelZoll, dreieinhalbZoll); Coprozessor (achtzigsiebenundachtzig, achtzigzweisiebenundachtzig, achtzigdreisiebenundachtzig); Maus (ja, nein); Screen (NECmultisync2, IBM8513); PrinterTyp
(EpsonLQ2550, EpsonLQ850, IBMgraphikdrucker2B);
BEGIN PCHersteller : = IBM; IBMpcTyp : = PS2; DiskTyp : = dreieinhalbZoll; Coprozessor : = achtzigdreisiebenundachtzig; Maus : = ja; Screen :=IBM8513; PrinterTyp : = IBMgraphikdrucker2B; INC(PCHersteller, 2); OliTyp : = M40; INC(DiskTyp); DEC(CoProzessor) ; INC(Maus); DEC(Screen); DEC(PrinterTyp, 2);
END EnumerationDemo.
89
2.3.1.8 Unterbereichstypen
2.3.1.8 Unterbereichstypen Unterbereichstypen (engl, subrange types) schränken den Wertebereich von Typen ein, deren Elemente mit Ordinalzahlen identifiziert werden oder Ordinalzahlen darstellen. Solche Basisdatentypen sind alle Aufzählungsdatentypen sowie die vordeklarierten Datentypen INTEGER, CARDINAL, LONGINT, CHAR und BOOLEAN. Der zur rechnerinternen Darstellung von Datenobjekten eines Unterbereichstyps benötigte Speicherplatz hängt von dem zugrundeliegenden Basisdatentyp ab. Er variiert also zwischen 8 Bits (z.B. für den Basisdatentyp CHAR) und 32 Bits (z.B. für den Basisdatentyp LONGINT). Unterbereichstypen lassen sich dann sinnvoll anwenden, wenn man in einem Programm oder Programmabschnitt nur eine Teilmenge von Werten des Basisdatentyps, die innerhalb fest vorgegebener Grenzen liegt, verarbeiten möchte. Bei der Abarbeitung des Programms wird die Einhaltung der Grenzen überprüft. Eine Verarbeitung von Werten außerhalb dieser Grenzen, wie sie ohne Anwendung eines einschränkenden Unterbereichstyps möglich wäre, ist damit ausgeschlossen. In Modula-2 definiert man einen Unterbereichstyp durch einen mittels eckiger Klammern eingeschlossenen Wertebereich, der aus dem kleinsten Wert, dem größten Wert und dem zwischen beiden Werten stehenden Bereichssymbol „ .. " besteht. Die vollständige und korrekte Definitionsvorschrift für Unterbereichstypen enthält das in Abbildung 2.46 gezeigte Syntaxdiagramm. Die zugehörigen verbalen Erläuterungen enthält die Tabelle 2.73.
Subrange Qualldent ~j(Ty~
ConstExpr -Q-
ConsfExpr
-(?)-•
Abb. 2.46: Syntaxdiagramm für „Subrange" (Unterbereich).
Tab. 2.73: Beschreibung des Syntaxdiagramms für „Subrange". (1) Ein Unterbereichstyp wird durch ein in eckige Klammern eingeschlossenes Intervall beschrieben.
90
2. Sprachstandard
(2) Das Intervall muß aufsteigend notiert sein, d.h. der linke Wert des Intervalls muß kleiner als der rechte Wert sein. Beide Werte müssen vom gleichen Datentyp sein. (3) Die beiden Grenzwerte des Intervalls werden durch das Bereichssymbol voneinander getrennt. (4) Durch einen vorangestellten qualifizierten Bezeichner kann der Basistyp des Unterbereichstyps festgelegt werden. Als Basistyp sind die Datentypen INTEGER, CARDINAL, LONGINT, CHAR, BOOLEAN und Aufzählungsdatentypen zulässig.
Auf Datenobjekte von Unterbereichsdatentypen sind die auf den jeweiligen Basistypen definierten Rechenoperationen, Relationen, Funktionen und Prozeduren anwendbar.
Beispiele MODULE SubrangeDemo; TYPE PCHersteller = (IBM, Compaq, Olivetti, Commodore, Schneider); VAR Index CARDI NAL[1. .256] ; Celsius INTEGER [-100.. 100] ; Adresse LONGINTJO..100000]; CHAR['a'..'z']; Kleinbuchstaben PCHersteller[IBM..Olivetti]; Marktfuehrer BEGIN
END SubrangeDemo.
Programmbeispiel (1) MODULE Scheine; (*
(* Dieses Programm ermittelt Art und Anzahl der Geldeinheiten, die bei der Auszahlung eines ganzzahligen Geldbetrages erforderlich sind. Das Programm geht davon aus, daß der Geldbetrag eingelesen wird. Folgende Geldeinheiten stehen zur Auszahlung zur Verfügung: 1000 DM-Scheine, 500 DM-Scheine, 100 DM-Scheine, 50 DM-Scheine, 20 DM-Scheine, 10 DM-Scheine, 5 DM-Stücke, 2 DM-Stücke und 1 DM-Stücke. Zum Verständnis dieses Beispiels ist die Kenntnis der Syntax der bedingten Anweisung
Programmbeispiel (1)
(*
91
(IF-Anweisung) und der PROCEDURE-Anweisung (parameterlose Prozeduren) notwendig. In diesem Programmbeispiel wird der Grundbibliotheks-Modul InOut und der BibliotheksModul Screen (enthält Routinen zur Bildschirmansteuerung) verwendet.
C (* Import-Deklarationen (* FROM InOut IMPORT WriteString, WriteLn, WriteCard, ReadCard, Read; FROM Screen IMPORT ClearScreen;
*) *) *) *) *)
(*
(* Variablenvereinbarung (* VAR Tausender, Fuenfhunderter, Hunderter Fuenfziger, Zwanziger, Zehner Fuenfer, Zweier, Einer Betrag space ZahlOk
*)
CARDINAL CARDINAL CARDINAL CARDINAL CHAR; BOOLEAN
(* (* Unterprogramm zum Einblenden von Programminformationen
*) *)
(*
*)
PROCEDURE TitelO; BEGIN (*
*)
(* Löscht den Bildschirminhalt C ClearScreen(); WriteStringC Dieses Programm ermittelt Art und Anzahl der Geldstücke, die bei Aus-'); WriteLnO; WriteStringC Zahlung eines ganzzahligen Geldbetrages erforderlich sind.'); WriteLn(); Read (space) END Titel;
*) •)
(*
*)
(* Unterprogramm zur Eingabe des Betrages
*) *)
PROCEDURE EingabeO; BEGIN WriteStringC Geben Sie einen Geldbetrag bis max. 65535 DM ein! — = >'); ReadCard (Betrag); WriteLnO; WriteLnO; IF (Betrag > =0) AND (Betrag < = 65535) THEN ZahlOk: =TRUE ELSE WriteLnO; WriteStringC Dieser Betrag ist unzulässig!');
2. Sprachstandard
92
WriteLn(); ZahlOk: = FALSE END END Eingabe; (*
*)
(* Unterprogramm zur Berechnung der Anzahl der Geldscheine
*)
(*
PROCEDURE BerechnungO; BEGIN IF ZahlOkTHEN = Betrag Tausender = Betrag Betrag Fuenfhunderter = Betrag = Betrag Betrag = Betrag Hunderter Betrag = Betrag = Betrag Fuenfziger = Betrag Betrag Zwanziger = Betrag = Betrag Betrag Zehner = Betrag = Betrag Betrag Fuenfer = Betrag = Betrag Betrag Zweier = Betrag = Betrag Betrag = Betrag Einer END END Berechnung; (
*
*)
DIV 1000; MOD 1000; DIV 500; MOD 500; DIV 100; MOD 100; DIV 50; MOD 50; DIV 20; MOD 20; DIV 10; MOD 10; DIV 5; MOD 5; DIV 2; MOD 2; DIV 1
*
(* Unterprogramm zur Ausgabe der berechneten Daten (*
PROCEDURE AusgabeO; BEGIN IF ZahlOkTHEN WriteString('Tausend-Mark-Scheine: ') ; WriteCard (Tausender, 5); WriteLnO; WriteStringC Fünfhundert-Mark-Scheine: '); WriteCard(Fuenfhunderter, 5); WriteLnO; WriteStringC Einhundert-Mark-Scheine: '); WriteCard(Hunderter, 5); WriteLnO; WriteStringC Fünfzig-Mark-Scheine: '); WriteCard(Fuenfziger, 5); WriteLnO; WriteStringC Zwanzig-Mark-Scheine: '); WriteCard (Zwanziger, 5); WriteLnO; WriteString('Zehn-Mark-Scheine: ') ; WriteCard (Zehner, 5); WriteLnO; WriteString('Fünf-Mark-Stücke: ') ; WriteCard(Fuenfer, 5); WriteLnO; WriteString ('Zwei-Mark-Stücke: ') ; WriteCard (Zweier, 5); WriteLnQ;
)
*)
*)
Testergebnisse Programmbeispiel (1)
93
WriteString('Ein-Mark-Stücke: WriteCard(Einer, 5); WriteLnO END END Ausgabe;
');
(*
*)
(* Hauptprogramm (*
*) *)
BEGIN Titel 0; Eingabe(); BerechnungO; AusgabeO END Scheine.
Testergebnisse Programmbeispiel (1) Dieses Programm ermittelt Art und Anzahl der Geldstücke, die bei Auszahlung eines ganzzahligen Geldbetrages erforderlich sind.
Eingaben des Programms: Geben Sie einen Geldbetrag bis max. 65535 DM ein! - = > 12345
Ausgaben des Programms: Tausend-Mark-Scheine:
12
Fünfhundert-Mark-Scheine:
0
Einhundert-Mark-Scheine:
3
Fünfzig-Mark-Scheine:
0
Zwanzig-Mark-Scheine:
2
Zehn-Mark-Scheine:
0
Fünf-Mark-Stücke:
1
Zwei-Mark-Stücke:
0
Ein-Mark-Stücke:
0
94
2. Sprachstandard
2.3.2 Strukturierte Datentypen Im Gegensatz zu einfachen Datentypen bestehen strukturierte Datentypen nicht aus einem einzigen Datentyp. Vielmehr definieren sie Datenstrukturen, die sich in der Regel aus mehreren Datentypen zusammensetzen. Neben einfachen Datentypen können Datenstrukturen selbst wieder Bausteine in noch komplexer strukturierten Datentypen sein. Strukturierte Datentypen können also eine mehrfach gestufte Struktur aufweisen. Die in Modula-2 verwendbaren strukturierten Datentypen sind der Feldtyp, Zeichenketten (eine spezielle Form des Feldtyps), der Mengentyp, der Verbundtyp und der einige Besonderheiten aufweisende Prozedurtyp. Diese Datentypen werden in den nachfolgenden Kapiteln behandelt. In die Kapitelabfolge eingeschoben werden Ausführungen zur Dateiverarbeitung. Modula-2 kennt bekanntlich keinen Dateityp und organisiert statt dessen die Dateiverarbeitung in einem globalen Modul.
2.3.2.1 Feldtypen Ein Feld (engl, array) ist eine statische Datenstruktur, die eine feste Anzahl von Datenobjekten gleichen Typs umfaßt. Den Typ der in einem Feld enthaltenen Elemente bezeichnet man auch mit Basisdatentyp. Zur rechnerinternen Identifikation der Feldelemente dient ein Indexdatentyp. Jedem Feldelement ist ein das Element eindeutig identifizierender Index zugeordnet. In der Regel stellt ein Index eine ganze Zahl dar; mit Hilfe des Indexdatentyps werden die Feldelemente gewissermaßen durchnumeriert. Neben der Einfachindizierung von Feldelementen ist auch eine Mehrfachindizierung möglich. Beispiele für einfach indizierte oder eindimensionale Felder sind Zeichenketten (siehe Kapitel 2.3.2.2) und Vektoren. In beiden Fällen liegt eine eindimensionale, lineare Anordnung von Feldelementen vor. Zweifach indizierte oder zweidimensionale Felder stellen Matrizen dar. Jedes Element einer Matrix wird eindeutig durch zwei Indexwerte identifiziert, die sich als Zeilen- und Spaltenindex des Elements interpretieren lassen. Da der Basistyp von Feldern nicht eingeschränkt ist, lassen sich natürlich nicht nur Zahlen, sondern beliebige andere Datentypen in Vektor- bzw. Matrixform organisieren und verarbeiten. Für die Definition von Feldtypen (ARRAY-Typen) gilt das in Abbildung 2.47 dargestellte Syntaxdiagramm. Eine Beschreibung des Diagramms enthält die Tabelle 2.74.
2.3.2.1 Feldtypen
95
Array Type
(¿RRA0-^-\simpleTyp^—©—|
Type
Abb. 2.47: Syntaxdiagramm für „ArrayType" (Feldtyp).
Tab. 2.74: Beschreibung des Syntaxdiagramms für „ArrayType". (1) Die Definition eines Feldtyps beginnt mit dem Schlüsselwort ARRAY, dem ein einfacher Datentyp oder mehrere, durch Kommata getrennte einfache Datentypen, das Schlüsselwort OF und schließlich der Basisdatentyp folgen. (2) Die dem Schlüsselwort ARRAY folgenden einfachen Datentypen beschreiben die Indexdatentypen, die zur Identifizierung der Feldelemente dienen. Als Indexdatentypen zulässig sind die Typen INTEGER, CARDINAL, BOOLEAN, CHAR, Aufzählungs- und Unterbereichsdatentypen. (3) Der nach dem Schlüsselwort OF anzugebende Typ gibt den Datentyp der Feldelemente (Basisdatentyp) an. Der Basisdatentyp kann beliebig sein.
Ein Feld läßt sich als Ganzes, nämlich über die das ganze Feld identifizierende Feldvariable, oder elementweise ansprechen. Das elementweise Bearbeiten von Feldern geschieht über die Qualifizierung von Bezeichnern mit Hilfe von Indexklammern (siehe Abbildung 2.41). Dabei ist jedoch zu berücksichtigen, daß die Indices innerhalb der in den Feldtypen deklarierten Indexgrenzen bleiben. In Modula-2 kann der Basisdatentyp eines Feldes wiederum ein Feldtyp sein. In diesem Fall liegt ein zweidimensionales Feld (Matrix) vor. Bekanntlich wird dann jedes Element dieses Feldes über zwei Indices angesprochen. Da die oben genannte Regel rekursiv ist, sind auch beliebig dimensionierte Felder denkbar. Der Definition mehrdimensionaler Felder ist jedoch durch die maximale Größe eines Feldes eine Grenze gesetzt. Eine Variable vom Typ ARRAY darf in der Regel nur Speicherplatz im Umfang von bis zu einem Speichersegment (64 KByte) belegen.
2. Sprachstandard
96
Felder werden häufig auch als Parameter in Prozeduren benutzt. Um eine allgemeinere Anwendung von Prozeduren mit Feldern als Parametern zu ermöglichen, bietet Modula-2 „offene Felder" an. Bei offenen Feldern enthalten die formalen Feld-Parameter keine starren Feldgrenzen. Jedoch ist der Indexdatentyp stets vom Typ CARDINAL, und die Indizierung beginnt grundsätzlich mit dem Indexwert 0. Auf diese Weise lassen sich mit einer Prozedur Felder mit variabler Feldgröße verarbeiten. Den größten auftretenden Index eines offenen Feldes kann man mit der nur auf offenen Feldern definierten Funktion HIGH erfragen. HIGH erwartet als Argument den formalen Parameter des Feldes. Zur Verarbeitung von Feldern stehen weiterhin die Standardprozeduren und -funktionen sowie die in BibliotheksModulen vereinbarten Prozeduren und Funktionen zur Verfügung, die sich auch auf den jeweiligen Basisdatentyp anwenden lassen.
Beispiel Offene Felder MODULE OffenesFeld; (* *) (* Dieses Programm liest reelle Werte zweier Felder unterschiedlicher Länge über Tastatur ein, bestimmt pro Feld den Mittelwert der eingegebenen Werte und gibt die Mittelwerte aus. Implementiert wurde dieses Beispiel unter Verwendung von Offenen Feldern. Zum Verständnis dieses Beispiels ist die Kenntnis der Syntax der Wiederholanweisung (FORAnweisung) und der PROCEDURE-Anweisung notwendig. In diesem Programmbeispiel werden die Grundbibliotheks-Module InOut und ReallnOut sowie der Bibliotheks-Modul Screen (enthält Routinen zur Bildschirmansteuerung) verwendet. *) (* *) (*
*)
(* Import-Deklarationen (* FROM InOut IMPORT WriteString, WriteLn, WriteCard, Read; FROM ReallnOut IMPORT WriteReal, ReadReal; FROM Screen IMPORT ClearScreen; (*
*) *)
(* Konstantendeklarationen (*
CONST Maxindex
*> *)
*)
= 19;
(*
*)
(* Typendeklarationen (* TYPE Indexl = [0..Maxindex]; Index2 = [10..Maxlndex + 1];
*)
Beispiel „Offene Felder"
97
(* (* Variablendeklarationen
*) *)
(*
VAR Feldl Feld2 space
: :
*)
ARRAY Indexl OF REAL; ARRAY Index2 OF REAL; CHAR;
(* *) (* Unterprogramm zum Einblenden von Programminformationen *) ( * * ) P R O C E D U R E TitelO; BEGIN ClearScreenO; WriteStringC Dieses Programm liest reelle Werte zweier Felder unterschiedlicher Länge'); WriteLnO; WriteStringC über Tastatur ein, bestimmt pro Feld den Mittelwert der eingegebenen Werte '); WriteLnO; WriteStringC und gibt die die Mittelwerte aus. Implementiert wurde dieses Beispiel unter'); WriteLnO; WriteStringC Verwendung von Offenen Feldern.'); WriteLnO; Read (space) E N D Titel;
( * * ) (* Unterprogramm zur Berechnung des Mittelwertes aus den Werten des jeweiligen Feldes (*
*) *)
P R O C E D U R E M ittel wert (Feld : ARRAY OF REAL):REAL; VAR I : CARDINAL; Summe : REAL; BEGIN Summe: =0.; FOR i: = 0 TO HIGH(Feld) DO Summe; = Summe + Feld[i] END; RETURN(Summe/FLOAT(HIGH(Feld) + 1)) E N D Mittelwert; (*
*)
(* Unterprogramm zur Eingabe der reellen Werte in das jeweilige Feld ( * M P R O C E D U R E Eingabe(VAR Feld : ARRAY OF REAL); VAR i : CARDINAL; BEGIN WriteString('Geben Sie die Werte für das Feld an!'); WriteLnO; FOR i: = 0 TO HIGH(Feld) DO WriteString('reeller Wert: '); WriteCard(i + 1, 2);
*)
2. Sprachstandard
98
WriteString(': '); ReadReal(Feld[i]); WriteLnO; END END Eingabe; f (** Unterprogramm zur Ausgabe des Mittelwertes (
*) *) *)
PROCEDURE Ausgabe(DurchWert : REAL); BEGIN WriteReal(DurchWert, 8); WriteLnO END Ausgabe;
(* (* Hauptprogramm
*) *)
C BEGIN Titel 0; Eingabe(Feldl); Ausgabe(Mittelwert(Feld1)); Eingabe(Feld2); Ausgabe(Mittelwert(Feld2)) END OffenesFeld.
*)
Programmbeispiel (2) MODULE Sortieren;
*)
(* Dieses Programm vereinigt zwei aufsteigend sortierte Zahlenfolgen zu einer wiederum aufaufsteigend sortierten Zahlenfolge. Das Programm geht davon aus, daß die Elementenzahl und die Elemente selbst per Tastatur eingegeben werden. Die Zahlenfolgen dürfen aus maximal 100 Elementen bestehen und die Zahlen selbst dürfen nur ganze Zahlen im Bereich von -32768 und +32767 sein. Werden für beide Zahlenfolgen O-Elemente eingegeben, so wird das Programm beendet. Zum Verständnis dieses Beispiels ist die Kenntnis der Syntax der bedingten Anweisung IFAnweisung), der Wiederholanweisung (WHILE-, REPEAT- und FOR-Anweisung) und der PROCEDURE-Anweisung notwendig. In diesem Programmbeispiel wird der Grundbibliotheks-Modul InOut und das BibliotheksModul Screen (enthält Routinen zur Bildschirmansteuerung) verwendet. *) *) (* (*
(* Import-Deklarationen FROM InOut FROM Screen
IMPORT WriteString, WriteLn, Writelnt, WriteCard, Read, Readlnt, ReadCard; IMPORT ClearScreen;
*) *)
Programmbeispiel (2)
99
(* Konstantenvereinbarung CONST Maxindex (.
= 100;
(* (* Typendeklarationen TYPE IntegerFeld
= ARRAY [1..Maxindex] OF INTEGER;
r(*
Variablendeklarationen (* VAR Feldl, Feld2, Feld3 Maxlndexl, Maxlndex2 space
IntegerFeld; CARDINAL; CHAR;
(* (* Unterprogramm zum Einblenden von Programminformationen ( * * ) P R O C E D U R E TitelO; BEGIN ClearScreenO; WriteString('Dieses Programm vereinigt zwei aufsteigend sortierte Zahlenfolgen') WriteLnO; WriteStringfzu einer wiederum aufsteigend sortierten Zahlenfolge.'); WriteLnO; Read(space) E N D Titel;
*) *)
(* (* Unterprogramm zum Einlesen eines Feldes (* P R O C E D U R E Eingabe(VAR x : IntegerFeld; VAR i : CARDINAL); VAR j : CARDINAL; BEGIN WriteString('Feldlänge: '); ReadCard(i); WriteLnO; FOR j: = 1 T O i D O WriteString('Element '); WriteCard(j, 3); WriteString(': '); Readlnt(x[j]); WriteLnO END END Eingabe;
*) *) *)
(* (* Unterprogramm zum Sortieren zweier Felder (*
*) *) *)
P R O C E D U R E Vereinige(a, b : IntegerFeld; VAR c: IntegerFeld; VAR laengeA, laengeB : CARDINAL);
VAR i, j, k : CARDINAL; BEGIN (* Ende des Feldes a markieren *) IFIaengeA < MaxIndexTHEN a[laengeA + 1 ] : = 32767 END; (* Ende des Feldes b markieren *) IFIaengeB < MaxIndexTHEN b[laengeB + 1]: =32767 END; i: = 1;j: = 1;k: = 1; WHILE (k < = laengeA + laengeB) AND (k < = Maxlndex) DO IF a[i] < b[j] THEN c[k]: =a[i]; (* aus a-Feld einsortieren *) INC(i) ELSE c[k]: = b[j]; (* aus b-Feld einsortieren *) INC© END; INC(k); (* Ergebnisfeld enthält weniger Elemente als laengeA + laengeB *) I F k - 1 = Maxlndex THEN laengeB: = Maxlndex-laengeA END END END Vereinige;
(* Unterprogramm zur Ausgabe eines Feldes (* PROCEDURE Ausgabe(I : CARDINAL; x : IntegerFeld); VAR I : CARDINAL; BEGIN FOR i: = 1 TO I DO IF i = 1 THEN WriteStrlng('Die sortierten Elemente lauten: '); WriteLnO; END; WrlteStringfElement '); WriteCard(i, 3); WriteString(': '); Writelnt(x[i], 5); WriteLnO; END END Ausgabe; (* (* Hauptprogramm BEGIN Titel 0; REPEAT WriteString('Elngabe erstes Feld:'); WriteLn; Eingabe(Feld1, Maxlndexl); WriteString('Elngabe zweites Feld: ');WriteLn;
Testergebnisse Programmbeispiel (2) Eingabe(Feld2, Maxlndex2); Vereinige(Feld1, Feld2, Feld3, Maxlndexl, Maxlndex2); Ausgabe(Maxlndex1 +Maxlndex2, Feld3) UNTIL Maxlndexl + Maxlndex2 = 0 END Sortieren.
Testergebnisse Programmbeispiel (2) Dieses Programm vereinigt zwei aufsteigend sortierte Zahlenfolgen zu einer wiederum aufsteigend sortierten Zahlenfolge.
Eingaben des Programms: Eingabe erstes Feld: Feldlänge:
2
Element 1: 1 Element 2: 10
Eingabe zweites Feld: Feldlänge:
3
Element 1: 12 Element 2: 13 Element 3: 18
Ausgaben des Programms: Die sortierten Elemente lauten: Element Element Element Element Element
1 2 3 4 5
1 10 12 13 18
Eingabe erstes Feld: Feldlänge:
0
Eingabe zweites Feld: Feldlänge:
0
101
102
2. Sprachstandard
2.3.2.2 Zeichenketten Zeichenketten sind bereits aus Kapitel 2.1.4 bekannt. Die Verarbeitung von Zeichen mit Feldern kann recht aufwendig sein. Man denke etwa an das Erzeugen längerer Zeichenketten (Zuweisen einzelner Zeichen zu Feldelementen), an das Hinzufügen von Zeichen am Ende einer Zeichenkette (Zuweisen einzelner Zeichen zu Elementen am Feldende) oder an das Einfügen einzelner Zeichen in eine Zeichenkette (Verschieben von Feldelementen zwecks Einfügen neuer Elemente). Der einfacheren Handhabung von Zeichenketten dient in Modula-2 ein ausgezeichneter Feld-Typ, der Typ Zeichenketten (ARRAY [0..255] OF CHAR). Datenobjekte vom Typ Zeichenketten stellen eindimensionale Felder mit CHAR-Werten als Feldelementen dar. Daraus folgt, daß die für allgemeine Felder gültige Syntax nur mit Einschränkungen auf Zeichenketten zu übertragen ist. Insbesondere darf der Indexdatentyp nur ein Unterbereichstyp aus den natürlichen Zahlen (CARDINAL) sein. Der Indexbereich ist auf die Werte von 0 bis 255 beschränkt, wobei der kleinste Index immer 0 sein muß. Schließlich ist als Basisdatentyp nur der Datentyp CHAR zulässig. Hersteller von Modula-2-Compilern bieten zur Bearbeitung von Zeichenketten spezielle Funktionen und Prozeduren an, die aus dem globalen Modul Strings zu importieren sind. Eine Übersicht über diese Funktionen und Prozeduren gibt die Tabelle 2.75. Tab. 2.75: Funktionen und Prozeduren auf Datenobjekte vom Typ Zeichenketten des globalen Moduls Strings. (1) Funktionen sind Length und Pos. (2) Prozeduren sind Concat, Copy, Delete und Insert. (3) „Length" gibt die aktuelle Länge einer Zeichenkette als CARDINAL-Zahl zurück. Length besitzt ein Argument: den Namen der Zeichenkette, für die die Länge ermittelt werden soll. (4) „Pos" ermittelt den Index, ab dem eine Zeichenkette in einer anderen Zeichenkette zum ersten Mal auftritt und gibt diesen als CARDINAL-Zahl zurück. Wird die gesuchte Zeichenkette nicht gefunden, so wird der Wert 0 zurückgegeben. Pos besitzt zwei Argumente: die zu suchende Zeichenkette und die zu durchsuchende Zeichenkette.
Programmbeispiel (3)
103
(5) „Concat" verbindet zwei Zeichenketten zu einer Ergebnis-Zeichenkette. Concat besitzt drei Argumente: die zwei zu verbindenden Zeichenketten und die Ergebnis-Zeichenkette. (6) „Copy" kopiert eine Teilzeichenkette aus einer Zeichenkette in eine ErgebnisZeichenkette. Copy besitzt vier Argumente: die Teilzeichenkette, den Kopierindex (Position, ab der zu kopieren ist), die Anzahl der zu kopierenden Zeichen und die Ergebnis-Zeichenkette. (7) „Delete" entfernt eine bestimmte Anzahl von Zeichen aus einer Zeichenkette. Delete besitzt drei Argumente: die zu bearbeitende Zeichenkettenvariable, die Löschposition und die Anzahl der zu löschenden Zeichen. (8) „Insert" fügt eine Teilzeichenkette in eine Ergebnis-Zeichenkette ein. Insert besitzt drei Argumente: die Teilzeichenkette, die Ergebnis-Zeichenkette und die Einfügeposition innerhalb der Ergebnis-Zeichenkette.
Programmbeispiel (3) MODULE BetraglnWorten; (*
*)
(* Dieses Programm gibt für einen ganzzahligen DM-Geldbetrag den Wert in Worten aus. Geldbeträge zwischen 1 und 65535 sind zulässig. Die Eingabe des Wertes 0 führt zur Beendigung des Programms. Zum Verständnis dieses Beispiels ist die Kenntnis der Syntax der bedingten Anweisung (IFAnweisung), der Auswahlanweisung (CASE-Anweisung), der Wiederholanweisung (REPEATAnweisung) und der PROCEDURE-Anweisung (parameterlose Prozeduren) notwendig. In diesem Programmbeispiel werden die Grundbibliotheks-Module InOut und Strings sowie der Bibliotheks-Modul Screen (enthält Routinen zur Bildschirmansteuerung) verwendet. *) *> (* (*
(* Import-Deklarationen (* FROM InOut IMPORT WriteLn, WriteStrlng, ReadCard, Read; FROM Screen IMPORT ClearScreen; FROM Strings IMPORT Concat; (* (* Variablendeklarationen (*
VAR Betrag, ZehnTausender, Tausender, Hunderter, Auswahl, Zehner, Einer: CARDINAL; space: CHAR; Text : ARRAY [0..80] OF CHAR;
*> *) *)
*)
*)
104
2. Sprachstandard
(* (* Unterprogramm zum Einblenden von Programminformationen
*) *)
(*
*)
PROCEDURE Titel0; BEGIN (* (* Löscht den Bildschirminhalt
*) *)
C
ClearScreenO; WriteStringC WriteLnO; Read (space) END Titel;
Dieses Programm gibt einen Betrag in Worten aus.')
(* Unterprogramm zur Ermittlung der Quotienten (* PROCEDURE ErmlttleQuotientenO; BEGIN DIV 10000; ZehnTausender MOD 10000; Betrag Tausender = Betrag DIV 1000; Betrag = Betrag MOD 1000; = Betrag DIV 100; Hunderter Betrag = Betrag MOD 100; = Betrag DIV 10; Zehner Betrag = Betrag MOD 10; = Betrag DIV 1 ; Einer Betrag = 65535 END ErmittleQuotienten;
*)
C (* Unterprogramm zur Ermittlung der Zahltexte (* PROCEDURE GebeTextAusO; PROCEDURE GebeEinerAusO; BEGIN CASE Auswahl OF 1 : ConcatfText, 'ein', Text); | 2; Concat(Text,'zwei', Text); I 3: ConcatfText, 'drei', Text); I 4: ConcatfText, 'vier', Text); I 5: ConcatfText, 'fuenf', Text); | 6: ConcatfText, 'sechs', Text); I 7: ConcatfText, 'sieben', Text); I 8: ConcatfText, 'acht', Text); I 9: ConcatfText, 'neun', Text); ELSE END END GebeEinerAus;
*) *) *)
Programmbeispiel (3)
PROCEDURE GebeZehnerAusO; BEGIN CASE Auswahl OF 1 : Concat(Text, 'zehn', Text); I 2: Concat(Text, 'zwanzig', Text); I 3: ConcatfText, 'dreißig', Text); I 4: Concat(Text, 'vierzig', Text); I 5: ConcatfText, 'fuenfzig', Text); I 6: ConcatfText, 'sechzig', Text); I 7: ConcatfText, 'siebzig', Text); I 8: ConcatfText, 'achtzig', Text); I 9: ConcatfText, 'neunzig', Text); ELSE END; END GebeZehnerAus;
PROCEDURE SonderbehandlungZehnerO ; BEGIN CASE Auswahl OF 10: ConcatfText,'zehn', Text); I 11 : ConcatfText, 'elf, Text); I 12: ConcatfText,'zwölf, Text); I 13: ConcatfText,'dreizehn', Text); I 14: ConcatfText,'vierzehn', Text); I 15: ConcatfText,'fuenfzehn', Text); I 16: ConcatfText,'sechzehn', Text); I 17: ConcatfText,'siebzehn', Text); I 18: ConcatfText,'achtzehn', Text); I 19: ConcatfText,'neunzehn', Text); ELSE END; END SonderbehandlungZehner;
BEGIN Concat(",",Text); (* Sonderfallbehandlung "Zehner" *) IF (ZehnTausender=1) AND fTausender> =0) THEN Auswahl: =ZehnTausender * 10 + Tausender; SonderbehandlungZehnerO ELSIF Tausender > 0 THEN Auswahl: = Tausender; GebeElnerAusO; IF ZehnTausender > OTHEN ConcatfText, 'und', Text) END; END; (* Normalfall *) IF ZehnTausender > 1 THEN Auswahl: = ZehnTausender; GebeZehnerAusO
105
2. Sprachstandard
106 END; IF (ZehnTausender > 0) OR (Tausender > 0) THEN ConcatfText, 'tausend', Text) END; IF Hunderter > 0 THEN Auswahl: - Hunderter; GebeEinerAusO; Concat(Text, 'hundert', Text) END; (* Sonderfallbehandlung "Zehner" *) IF (Zehner=1) AND (Einer > =0) THEN Auswahl: = Zehner* 10 + Einer; SonderbehandlungZehnerO ELSIF Einer > 0 THEN Auswahl: = Einer; GebeEinerAusO; IF Zehner > 0 THEN Concat(Text, 'und', Text) END; END; (* Normalfall *) IF Zehner > 1 THEN Auswahl: = Zehner; GebeZehnerAusO END; Concat(Text,' DM.', Text); WrlteString('Der Betrag in Worten lautet:'); WriteString(Text); WrlteLnO END GebeTextAus; (*
*)
(* Hauptprogramm
*)
(*
*)
BEGIN TitelO;
REPEAT WriteString('Geben Sie den Betrag in DM an:'); ReadCard (Betrag); WriteLnO; IF Betrag > 0 THEN ErmittleQuotientenO; GebeTextAüsO ELSE WriteString('Betrag ist negativ oder Null!'); WrlteLnO END UNTIL Betrag=0 END BetraglnWorten.
Testergebnisse Programmbeispiel (3)
107
Testergebnisse Programmbeispiel (3) Dieses Programm gibt einen Betrag in Worten aus.
Eingaben des Programms: Geben Sie den Betrag in DM an: 65432
Ausgaben des Programms: Der Betrag in Worten lautet: fuenfundsechzigtausendvierhundertzweiunddreißig DM.
2.3.2.3 Mengentypen Eine Menge (engl, set) besteht aus Elementen des gleichen Datentyps, dem Basisdatentyp. In Modula-2 sind bei der Vereinbarung eines Mengentyps alle Elemente der Menge zu benennen. Die Anzahl der deklarierbaren Elemente einer Menge hängt vom verwendeten Compiler ab. Während sie bei älteren Modula-2-Compilern auf 16 begrenzt ist, erlauben neuere Compiler die Definition von bis zu 65536 Elementen pro Mengentyp. Rechnerintern werden die Mengenelemente durch Ordinalzahlen identifiziert. Die Wahl des Basisdatentyps unterliegt in Modula-2 keinen Beschränkungen. Für die Deklaration von Mengentypen gilt das Syntaxdiagramm in Abbildung 2.48. Das Diagramm wird in der Tabelle 2.76 verbal beschrieben.
SetType
Abb. 2.48: Syntaxdiagramm für „SetType" (Mengentyp).
Tab. 2.76: Beschreibung des Syntaxdiagramms für „SetType". (1) Ein Mengentyp wird durch die Schlüsselwörterfolge SET OF und einen nachfolgenden einfachen Datentyp (INTEGER, CARDINAL, CHAR, BOOLEAN, Aufzählungs- oder Unterbereichstyp), dem Basisdatentyp, definiert.
108
2. Sprachstandard
(2) Die den Mengenelementen zugeordneten Ordinalzahlen erstrecken sich von 0 bis zu einer compiler-abhängigen Obergrenze. Diese Obergrenze legt zugleich die Mächtigkeit der Menge (Anzahl der Mengenelemente) fest.
Mengentypen in Modula-2 entsprechen dem Mengenbegriff in der Mathematik. Es lag daher nahe, mathematische Mengenoperationen in die Sprache Modula-2 zu übernehmen. Eine Übersicht über Operationen, die auf Datenobjekte von SETTypen definiert sind, enthält die Tabelle 2.77. Man beachte, daß die in Modula-2 verwendeten Symbole für Operatoren von den in der Mathematik üblichen Symbolen abweichen.
Tab. 2.77: Operationen auf Datenobjekte vom Typ SET. (1) Zur Definition von Mengenoperationen stehen die Mengenoperatoren Vereinigung ( + ), Differenz (-), Durchschnitt (*), Symmetrische Mengendifferenz (/) und das Enthaltensein eines Elementes in einer Menge (IN) zur Verfügung. (2) Die Operatoren + , - , * und / müssen stets zwischen den Operanden stehen, die verknüpft werden sollen. (3) Die Vereinigung x+y zweier Mengen x und y enthält genau die Elemente , die in mindestens einer der beiden Mengen enthalten sind. (4) Die Differenz x-y zweier Mengen x und y enthält genau die Elemente der Menge x, die nicht zugleich auch Elemente der Menge y sind. (5) Der Durchschnitt x*y zweier Mengen x und x enthält genau die Elemente, die in jeder der beiden Mengen enthalten sind. (6) Die symmetrische Differenz x/y zweier Mengen x und y enthält genau die Elemente, die entweder in x oder in y, nicht aber zugleich in beiden Mengen enthalten sind. (7) Der Relationaloperator IN kann benutzt werden, um zu prüfen, ob ein bestimmtes Element in einer Menge enthalten ist. Der Ausdruck z IN x ist genau dann wahr, wenn die Menge x das Element z enthält.
Programmbeispiel (4)
109
Programmbeispiel (4) MODULE Kalender; (* *) (* Dieses Programm gibt einen Kalender aus. Es kann zwischen dem Modus "Ganzes Jahr ausgeben" oder "Einzelner Monat ausgeben" gewählt werden. Der Kalender läßt sich nur für die Jahre 1988 .. 1999 erstellen. Zum Verständnis dieses Beispiels ist die Kenntnis der Syntax der bedingten Anweisung (IFAnweisung), der Auswahlanweisung (CASE-Anweisung), der Wiederholanweisung (REPEAT-, WHILE- und FOR-Anweisung) und der PROCEDURE-Anwelsung (parameterlose Prozeduren) notwendig. In diesem Programmbeisplel wird der Grundbibliotheksmodul InOut und der Bibliotheksmodul Screen (enthält Routinen zur Bildschirmansteuerung) verwendet. *) (* *) ( * * ) (* Import-Deklarationen *) *) (* FROM InOut IMPORT Read, ReadCard, Write, WriteCard, WriteString, WriteLn; FROM Screen IMPORT ClearScreen, Gotoxy; (* *) (* Konstantendeklarationen (* CONST Tage Zuordnung Ja M1 M2 M3 M4 M5 M6 M7 M8 M9 M10 M11 M12 J188 J189 J190 J191 J192 J193 J194 J195 J196 J197 J198 J199
= = = = = = = = = = = = = = = = = = = = = = = = = = =
'WOCHE MO DI MI DO FR SA SO'; ' 01 02 03 04 05 06 07'; '19'; 'JANUAR'; 'FEBRUAR'; 'MÄRZ!; 'APRIL'; 'MAI'; 'JUNI'; 'JULI'; 'AUGUST'; 'SEPTEMBER'; 'OKTOBER'; 'NOVEMBER'; 'DEZEMBER'; 5; 7; 1; 2; 3; 5; 6; 7; 1; 3; 4; 5;
*) *)
110
2. Sprachstandard
(*
*)
(* (* Typendeklarationen
*) *)
TYPE Monat = [1 ..12]; Monate = SET OF Monat; (*
*)
(* Variablendeklarationen
*)
(*
*)
VAR monat, jähr : CARDINAL; dreissigTageMonate Monate; einunddreissigTageMonate : Monate; achtundzwanzigTageMonate: Monate; space CHAR; (*
*)
(* Unterprogramm zum Einblenden von Programminformationen
*)
(*
*)
PROCEDURE TitelO; BEGIN (*
(* Löscht den Bildschirminhalt (*
*)
*) *)
ClearScreenO; WriteStringC Dieses Programm gibt einen Kalender aus. Es können'); WriteLnO; WriteString('einzelne Monate oder ein komplettes Jahr angezeigt werden.'); WriteLnO; Read (space) END Titel;
(*
(* Unterprogramm zum Aufbau des Bildschirmmenüs PROCEDURE BildschirmO; VAR wähl monate, jähre, abbruch BEGIN REPEAT ClearScreen; * (
*)
*) *>
: CHAR; BOOLEAN;
(* Setzt die Schreibmarke (in diesem Fall auf 8.Zeile und 20.Spalte), wobei der (* Bildschirm als Koordinatensystem verstanden wird und der Punkt in erster (* Spalte der Punkt(1,1) ist. X stellt also immer die Spaltenkoordinate und Y (* die Zeilenkoordinate dar. (* Gotoxy(20,8); WriteString('A U S G A B E D E S K A L E N D E R S ' ) ;
*) *) *) *) *)
Programmbeispiel (4)
Gotoxy(28,11); WriteString('Einzelne Monate - = > M'); Gotoxy(28,13); WriteString('Ein ganzes Jahr - = > J'); Gotoxy(28,15); WriteString('Ende des Programms — = > E'); Gotoxy(33,17); WriteString('Auswahl - = > '); monate : = FALSE; : = FALSE; jähre abbruch : = FALSE; Gotoxy(51,17); Read(wahl); IF (wähl = 'M') OR (wähl = 'm') THEN REPEAT Gotoxy(20,19); WriteString('Geben Sie den Monat an (1 ..12)! - = >'); ReadCard(monat); IF (monat> =1) AND (monat< =12) THEN monate: =TRUE; REPEAT Gotoxy(20,20); WrlteString('Geben Sie das Jahr an (88..99)! - = >'); ReadCard(jahr); IF (jahr> =88) AND (jahr< =99) THEN jähre: = TRUE; BerechneEinenMonatO ELSE Write(CHR(7)) END UNTIL jähre; ELSE Write(CHR(7)) END UNTIL monate ELSIF (wähl = 'J') OR (wähl = 'j') THEN REPEAT Gotoxy(20,20); WriteString('Geben Sie das Jahr an (88..99)! - = >'); ReadCard(jahr); IF (jahr> =88) AND Qahr< =99) THEN jähre: =TRUE; BerechneAlleMonateO ELSE Write(CHR(7)) END UNTIL jähre ELSIF (wähl = 'E') OR (wähl = 'e') THEN abbruch: = TRUE ELSE Write(CHR(7)); Gotoxy(51,17); Writef') END UNTIL abbruch END Bildschirm;
111
112
2. Sprachstandard
(*
*)
(* Unterprogramm zum Hochzählen einer natürlichen Zahl, falls diese kleiner 52 ist C PROCEDURE lnc(VAR i : CARDINAL); BEGIN IF i = 52THEN i: = 1 ELSE INC(i) END END Inc;
*) *)
(*
(* Unterprogramm zur Berechnung eines Monats 1 THEN Startwoche: = Startwoche + starttag-zuvielTage-ersteTage; Startwoche: = (startwoche DIV 7) + 1 END; (* ergibt Rest 1 ,..7, für die Wochentage 1 ..7 (MO..SO) *) starttag: = ((starttag-1) MOD 7) +1; (* Wochennummer ausgeben, letzte Woche des vorherigen Jahres = 53 *) Gotoxy(20,10); IF startwoche=0 THEN WriteCard(53, 4) ELSE WriteCard (startwoche, 4) END; Writef'); (* Vorschub für angebrochene Woche ausgeben *) FOR i: = 1 TO starttag-1 DO WriteStringC ') END; j : = 0 ; nextline: = 0; REPEAT i : = starttag; lastday : = FALSE; (* jeweils eine Woche ausgeben *) WHILE (i< >8) AND NOT lastday DO INC(i); WriteCard(j, 4); INC(I); (* Ende des Monats prüfen *) IF j = 28 THEN lastday: = (monat IN achtundzwanzigTageMonate) AND (jähr MOD 4 < >0) ELSIF j = 2 9 T H E N IF (monat IN achtundzwanzigZageMonate) AND (jähr MOD 4 = 0) THEN lastday: =TRUE END ELSIF j =30 THEN lastday: = (monat IN dreissigTageMonate) ELSIF j = 31 THEN lastday: = (monat IN einunddreissigTageMonate) END END; (* nächste Zeile auf dem Bildschirm *) INC(nextline); starttag: = 1; Inc(startwoche); IF NOT lastday THEN Gotoxy(20,10 + nextline); WriteCard (startwoche, 4); Write('') END UNTIL lastday; Gotoxy(20, 24); WriteString('Bitte eine Taste drücken!'); Read(c) END BerechneEinenMonat;
Testergebnisse Programmbeispiel (4)
115
(* (* (* Unterprogramm zur Berechnung aller Monate
*) *) *)
PROCEDURE BerechneAlleMonateO; VAR i : CARDINAL; BEGIN FOR i: = 1 TO 12 DO monat: = i; BerechneEinenMonatO END END BerechneAlleMonate; (*
*)
(* Hauptprogramm
*)
(*
*)
BEGIN einunddreissigTageMonate : = Monate{1,3,5,7,8,10,12}; dreissigTageMonate : = Monate{4,6,9,11}; achtundzwanzigTageMonate : = Monate{2}; Titel 0; BildschirmO END Kalender.
Testergebnisse Programmbeispiel (4) Dieses Programm gibt einen Kalender aus. Es können einzelne Monate oder ein komplettes Jahr angezeigt werden.
Anzeigen des Bildschirmmenüs: AUSGABE
DES
KALENDERS
Einzelne Monat
- = > M
Ein ganzes Jahr
- = > J
Ende des Programms — = > E Auswahl
- = > M
Eingabe von weiteren Informationen bezüglich der Auswahl „M": Geben Sie den Monat an (1 ..12) I Geben Sie das Jahr an (88..99) !
- = > 3 - = >89
2. Sprachstandard
116
Ausgabe des berechneten Monats gemäß obiger Eingabe: MÄRZ WOCHE 9 10 11 12 13
1989 MO DI 6 13 20 27
7 14 21 28
MI 1 8 15 22 29
DO 2 9 16 23 30
FR 3 10 17 24 31
SA 4 11 18 25
SO 5 12 19 26
Bitte eine Taste drücken!
2.3.2.4 Verbundtypen Ähnlich wie ein Feldtyp gestattet der Datentyp Verbund (engl, record) die Zusammenfassung mehrerer Elemente zu einer Datenstruktur. Während jedoch die Elemente eines Felds vom gleichen Datentyp sein müssen, können die Elemente eines Verbunds verschiedenen Datentypen angehören. Die Elemente eines RecordTyps heißen Felder (fields, strikt zu unterscheiden von Felder im Sinne von arrays). In einer RECORD-Deklaration werden die Namen und Datentypen der RecordFelder festgelegt. Ein Datenobjekt eines RECORD-Typs kann als Gesamtheit mittels einer RECORD-Variablen oder komponentenweise angesprochen und manipuliert werden. Mit Hilfe der mehrfachen Qualifizierung von Bezeichnern (Folge von je durch einen Punkt getrennten Bezeichnern) läßt sich die gewünschte Komponente auch bei komplexen RECORD-Strukturen eindeutig angeben. Bei der rechnerinternen Darstellung von Datenobjekten eines RECORD-Typs wird für jedes RECORD-Feld der durch den Datentyp des Feldes beanspruchte Speicherplatz reserviert. Ein Datenobjekt von einem RECORD-Typ ist beispielsweise ein Artikelsatz, der aus den Komponenten Artikelnummer, Artikelgruppe, Artikelbezeichnung und Verpackungsart besteht. Folgende Definition sei gegeben: RECORD artnr gruppe bezeichnung Verpackung END
[1 ..9999]; [A,B,C,D]; A R R A Y [0..7] O F CHAR; [Flasche, Fass, Kiste, Karton]
Diese Definition bietet mehrere Vorteile. Die Unterbereichs-Definition von artnr stellt sicher, daß nur Artikelnummern im angegebenen Bereich verarbeitet werden. Ebenso gestatten die Aufzählungstypen für gruppe und Verpackung nur die Verarbeitung der angegebenen Werte.
117
Komponenten von RECORD-Objekten können selbst wieder strukturierte Datenobjekte sein. Ein RECORD-Typ kann daher eine mehrfach gestufte, komplexe Struktur aufweisen. Diese mögliche Komplexität schlägt sich in der für RECORDTypen gültigen Syntax wieder, die eine ganze Folge aufeinander aufbauender Syntaxdiagramme umfaßt. In Abbildung 2.49 ist das erste Syntaxdiagramm dieser Folge dargestellt Es beschreibt die Definition von RECORD-Typen aus globaler Sicht. Erläutert wird das Diagramm in der Tabelle 2.78.
RecordType
Abb. 2.49: Syntaxdiagramm für „RecordType" (Verbundtyp).
Tab. 2.78: Beschreibung des Syntaxdiagramms für „RecordType". Die Beschreibung eines Verbunds wird durch das Schlüsselwort RECORD eingeleitet, dem eine RECORD-Feldliste oder mehrere, je durch einen Strichpunkt voneinander getrennte RECORD-Feldlisten sowie das Schlüsselwort END folgen.
Die oben erwähnte Feldliste dient insbesondere auch dem Einführen von RECORD-Komponenten, die dem gleichen Datentyp angehören. Für die Definition einer Feldliste gilt das Syntaxdiagramm in Abbildung 2.50. Es wird in der Tabelle 2.79 erläutert.
2. Sprachstandard
118
Field Li st
Abb. 2.50: Syntaxdiagramm für „FieldList" (Feldliste).
Tab. 2.79: Beschreibung des Syntaxdiagramms für „FieldList". Eine Feldliste besteht aus einer Liste von durch Kommata getrennten Bezeichnern, gefolgt von einem Doppelpunkt und einem beliebigen Datentyp, oder sie besteht aus einer Varianten Feldliste. Um eine eindeutige Identifikation der Komponenten eines RECORD-Typs zu gewährleisten, müssen alle Bezeichnernamen innerhalb der Verbundvereinbarung verschieden sein. Außerhalb des Verbundes können die Namen der RECORD-Felder mit einer anderen Bedeutung verwendet werden. Die Vereinigung von Elementen verschiedener Datentypen kann zu speicherplatzineffizienten RECORD-Strukturen führen. Dies ist dann der Fall, wenn ein RECORD-Typ Komponenten aufweist, die nur für Teilmengen von Datenobjekten gültige Werte annehmen können. Solche RECORD-Typen enthalten Strukturvarianten, die von der Art der Datenobjekte abhängen. Als Beispiel seien verschiedene Lebensmittel mit folgenden Eigenschaften betrachtet:
RECORD artnr bezeichnung preis artgruppe fettgehalt alkoholgehalt gesalzen END
CARDINAL; ARRAY [0..7] OF CHAR; CARDINAL; Gruppe; REAL; REAL; BOOLEAN
119
Hierbei ist Gruppe ein Aufzählungstyp, der beispielsweise folgende Werte zusammenfaßt: Gruppe: (streichfett, bier, fisch).
Während allen betrachteten Artikeln eine Nummer, eine Bezeichnung und ein Preis zugeordnet ist, weisen nur Streichfette einen üblicherweise zu berücksichtigenden Fettgehalt auf. Der Alkoholgehalt ist nur im Zusammenhang mit Bier von Interesse. Die Eigenschaft „gesalzen" können sowohl Streichfette, als auch Fische aufweisen. Modula-2 berücksichtigt solche Strukturvarianten mit RECORD-Feldern, die unterschiedliche (Variante) Strukturen aufweisen können. Welche Struktur im aktuellen Fall gilt, wird durch ein zusätzliches, spezielles RECORD-Feld, das sogenannte Diskriminatorfeld (engl, tag-field), entschieden. In Abhängigkeit vom Inhalt dieses Feldes gilt alternativ eine der Feldvarianten. Da Variante RECORD-Felder im Speicher überlagert werden, ergibt sich eine Reduktion des Speicherplatzbedarfs gegenüber der Darstellungsart ohne Variante RECORD-Felder. Die Reservierung von Speicherplatz orientiert sich an der Varianten mit dem größten Speicherplatzbedarf. Für die Definition varianter RECORD-Felder gilt das in Abbildung 2.51 dargestellte Syntaxdiagramm. Das Diagramm wird in der Tabelle 2.80 erläutert.
VariantFieldList
Tab. 2.80: Beschreibung des Syntaxdiagramms für „VariantFieldList". (1) Den Anfang einer Varianten Feldliste bildet das Schlüsselwort CASE, gefolgt von dem Bezeichner des Diskriminatorfeldes (Ident), das der Fallunterscheidung dient, und dem Datentyp des Diskriminatorfeldes.
120
2. Sprachstandard
(2) Der Bezeichner des Diskriminatorfeldes kann weggelassen werden. In diesem Fall wird für dieses Feld kein Speicherplatz bereitgestellt. (3) Der Typangabe des Diskriminatorfeldes folgen das Schlüsselwort O F und eine Liste von durch den Begrenzer „ |" getrennten Varianten. Diese Liste weist für jeden gültigen Wert des Diskriminatorfeldes die dazugehörige Variante aus. (4) Eine Variante Feldliste kann außerdem eine Feldliste enthalten, die stets dann zur Anwendung kommt, wenn keine der angegebenen Varianten für einen Wert des Diskriminatorfeldes zutrifft. Das Schlüsselwort ELSE leitet diese Feldliste ein. (5) Den Abschluß einer Varianten Feldliste bildet das Schlüsselwort END.
Die Definition von Varianten beschreibt das Syntaxdiagramm in Abbildung 2.52. Die dazugehörigen Erläuterungen enthält die Tabelle 2.81.
Abb. 2.52: Syntaxdiagramm für „Variant" (Variante).
Tab. 2.81: Beschreibung des Syntaxdiagramms für „Variant". (1) Den Anfang einer Varianten bildet eine Liste (CaseLabelList), die alle Werte oder Wertebereiche der Schalterkomponenten benennt, für die diese Variante zutrifft. Die Werte müssen von dem Datentyp sein, dem die Schalterkomponente angehört. (2) Auf die CaseLabelList folgen ein Doppelpunkt und eine Feldliste oder mehrere durch je ein Semikolon getrennte Feldlisten. Folgt dem Doppelpunkt keine leere Feldliste, kann eine mehrfach gestufte Variantenstruktur definiert werden.
121
(3) Die leere Variante ermöglicht das Setzen von Begrenzern ( | ) am Anfang und am Ende einer Varianten Feldliste.
Wie die Werte oder Wertebereiche von Schalterkomponenten zu definieren sind, zeigt das Syntaxdiagramm in Abbildung 2.53. Erläutert wird das Diagramm in Tabelle 2.82.
CaseLabelList **
O
ConstExpr —y~Q Vto.
ConstExpr 1
1 T
Abb. 2.53: Syntaxdiagramm für „CaseLabelList".
Tab. 2.82: Beschreibung des Syntaxdiagramms für „CaseLabelList". (1) Eine CaseLabelList kann ein einzelner Wert, eine Liste von durch Kommata getrennten Werten, ein Wertebereich oder eine Liste von durch Kommata getrennten Wertebereichen sein. Ein Wertebereich wird durch einen unteren Wert, einen oberen Wert und das zwischen beiden Werten stehende Bereichssymbol definiert. (2) Die angegebenen Werte müssen Konstanten sein. Werte dürfen nicht mehrfach auftreten.
Bezieht man Variante RECORD-Felder in das obige Beispiel ein, so führt dies zu folgender RECORD-Struktur: RECORD artnr bezeichriung preis
: : :
CARDINAL; ARRAY [0..7] OF CHAR; CARDINAL;
2. Sprachstandard
122
C A S E artgruppe streichfett : | bier | fisch ELSE E N D (* C A S E *) E N D (* R E C O R D *)
Gruppe OF fettgehalt gesalzen alkoholgehalt gesalzen
REAL; BOOLEAN; REAL; BOOLEAN
Programmbeispiel (5) MODULE Kartei; (* *) (* Dieses Programm verwaltet eine Kartei. E s können bis zu 100 Karteikarten eingelesen und und sortiert nach dem Nachnamen auf dem Bildschirm ausgegeben werden. Zum Verständnis dieses Beispiels ist die Kenntnis der Syntax der bedingten Anweisung (IFAnweisung), der Wiederholanweisung (REPEAT- und FOR-Anweisung) und der P R O C E D U R E Anweisung (parameterlose Prozeduren) notwendig. In diesem Programmbeispiel werden die Grundbibliotheksmodule InOut, ReallnOut und Strings und der Bibliotheksmodul Screen (enthält Routinen zur Bildschirmsteuerung) verwendet. *) (*
*)
(*
*)
(* Import-Deklarationen
*)
(*
FROM FROM FROM FROM (*
InOut ReallnOut Screen Strings
IMPORT IMPORT IMPORT IMPORT
*) Write, WriteString, WriteLn, WriteCard, Read, ReadString, ReadCard; ReadReal, WriteReal; ClearScreen, Gotoxy; Compare; *)
(* Konstantenvereinbarungen (* CONST MaxAngestellte = 100;
*) *)
(* (* Typenvereinbarungen (* TYPE Karten = [1 ..MaxAngestellte]; Name = ARRAY [0..30] OF CHAR; Ort = ARRAY [0..30] OF CHAR; Datum = ARRAY [0..10] OF CHAR; Person = RECORD Nachname Name; Vorname Name; GebDatum Datum;
*) *) *)
Programmbeispiel (5)
123
GebOrt : Ort; Geschlecht (maennlich, weiblich); FamStand (ledig, verheiratet, geschieden, verwitwet); Gehalt : REAL; END; KarteiKasten = ARRAY Karten OF Person; (* (** Variablenvereinbarungen (
VAR KartelKarten : MaxKarte : space
KarteiKasten; CARDINAL; CHAR;
(* (* Unterprogramm zum Einblenden von Programminformationen (
*
PROCEDURE TitelO; BEGIN (* (* Löscht den Blldschlrmlnhalt (
*) *) *>
*
*) *)
*) *) *)
*)
ClearScreenO; WrlteStrlng('Dleses Programm liest eine Personalkartei mit maximal 100 Karten ein,'); WriteLnO; WriteStringC sortiert diese nach Nachnamen und gibt die Inhalte der einzelnen Karten'); WriteLnO; WriteStringC Wiederaus.') WriteLnO; Read(space) END Titel;
(* (* Unterprogramm zum Einlesen von Kartelkarten (
*
PROCEDURE KarteiKartenErstellenO; VAR KartenCheck : BOOLEAN; i : CARDINAL; PROCEDURE Geschlecht(i : CARDINAL); VAR help : ARRAY [0..20] OF CHAR; found : BOOLEAN; BEGIN found: = FALSE; REPEAT Gotoxy(10,11); WriteString('GESCHLECHT: - = >'); ReadString(help); IF (Compare(help, 'maennlich') = 0) OR (Compare(help, 'MAENNLICH') = 0) OR (Compare(help, 'Maennlich') = 0) THEN KarteiKarten[i].Geschlecht: = maennlich; found: =TRUE ELSIF (Compare(help, 'weiblich') = 0) OR (Compare(help, 'WEIBLICH') = 0) OR (Compare(help, 'Weiblich') = 0) THEN KarteiKarten[i]. Geschlecht: = weiblich; found: =TRUE
*) *)
*)
124
2. Sprachstandard
ELSE Write(CHR(7)) END UNTIL found E N D Geschlecht; P R O C E D U R E Stand(i: CARDINAL); VAR help : ARRAY [0..20] OF CHAR; found : BOOLEAN; BEGIN found: = FALSE; REPEAT Gotoxy(10,12); WrlteString('FAMSTAND: - = >'); ReadString(help); IF (Compare(help, 'ledig') = 0) OR (Compare(help, 'LEDIG') = 0) OR (Compare(help, 'Ledig') = 0) THEN KarteiKarten[i].FamStand: =ledig; found: = T R U E ELSIF (Compare(help, 'verheiratet') = 0) OR (Compare(help, 'VERHEIRATET') = 0) OR (Compare(help, 'Verheiratet') = 0) THEN KarteiKarten[i].FamStand: =verheiratet; found: = T R U E ELSIF (Compare(help, 'geschieden') = 0) OR (Compare(help, 'GESCHIEDEN') = 0) OR (Compare(help, 'Geschieden') = 0) THEN KarteiKarten[i].FamStand: = geschieden; found: = T R U E ELSIF (Compare(help, 'verwitwet') = 0) OR (Compare(help, 'VERWITWET') = 0) OR (Compare(help, 'Verwitwet') = 0) THEN KarteiKarten[i].FamStand: =verwitwet; found: = T R U E ELSE Write(CHR(7)) END UNTIL found END Stand; BEGIN KartenCheck: = FALSE; REPEAT Gotoxy(10,12); WriteString('Geben Sie die Anzahl der einzulesenden Karten an 1..100)! - = >'); ReadCard (MaxKarte); IF (MaxKarte> =0) AND (MaxKarte< =100) THEN KartenCheck: = TRUE; FOR I: = 1 TO MaxKarte DO ClearScreenO; Gotoxy(10,6); WriteString('KARTE '); WriteCard(i, 3); Gotoxy(10,7); WriteString('NACHNAME: - = >'); ReadString(KarteiKarten[i] .Nachname); Gotoxy(10,8); WriteString('VORNAME: - = >'); ReadString(KarteiKarten[i] .Vorname); Gotoxy(10,9); WriteString('GEBDATUM: - = >'); ReadString(KarteiKarten[i].GebDatum); Gotoxy(10,10); WriteString('GEBORT: - = >'); ReadString(KarteiKarten[i].GebOrt); Geschlecht (I);
Programmbeispiel (S)
125
Stand (i); Gotoxy(10,13); WriteString('GEHALT: - = >'); ReadReal (KarteiKarten[i] .Gehalt) ; Read (space) END ELSE Write(CHR(7)) END UNTIL KartenCheck END KarteiKartenErsteilen;
(* *) (* Unterprogramm zum Sortieren von Karteikarten. Als Sortieralgorithmus wurde "Bubblesort" verwendet. *) PROCEDURE KarteiSortierenO; VAR i, j : CARDINAL; x : Person; BEGIN FOR i : = 2 T O MaxKarte DO FOR j: = MaxKarte TO i BY - 1 DO IF Compare(KarteiKarten[j-1].Nachname, KarteiKarten[j],Nachname) > OTHEN x: = KarteiKarten [j-1 ] ; KarteiKarten[j-1 ] : = KarteiKarten[j] ; KarteiKarten[j]:=x END END END END KarteiSortieren;
(* (* Unterprogramm zur Ausgabe von Karteikarten (* PROCEDURE KarteiDurchBlaetternO; VAR i : CARDINAL; BEGIN FOR i: = 1 TO MaxKarte DO ClearScreenO; Gotoxy(10,6); WriteString('KARTE '); WriteCard(i, 3); Gotoxy(10,7); WriteString ('NACHNAME: - = >'); WriteString(KarteiKarten[i]. Nachname) ; Gotoxy(10,8); WriteString('VORNAME: - = >'); WriteString(KarteiKarten[l].Vorname); Gotoxy(10,9); WriteString('GEBDATUM: - = >'); WriteString(KarteiKarten[i].GebDatum); Gotoxy(10,10); WriteString('GEBORT: - = >'); WriteString(KarteiKarten[i].GebOrt); Gotoxy(10,11); WriteString('GESCHLECHT: - = >');
•) *) *)
2. Sprachstandard
126
IF KarteiKarten[i].Geschlecht = maennlich THEN WriteString('maennlich') ELSiF KarteiKarten[i].Geschlecht=weiblich THEN WriteString('weiblich') END; Gotoxy(10,12); WriteString('FAMSTAND: - = >'); IF KarteiKarten[i].FamStand = ledig THEN WriteString('ledig') ELSIF KarteiKarten[i].FamStand=verheiratet THEN WriteString('verheiratet') ELSIF KarteiKarten[i].FamStand = geschieden THEN WriteString('geschieden') ELSIF KarteiKarten[i].FamStand=verwitwet THEN WriteString('verwitwet') END; Gotoxy(10,13); WriteString('GEHALT: - = >); WriteReal(KarteiKarten[i].Gehalt, 13); Read(space) END END KarteiDurchBlaettern;
*) *)
(*
(* (* Hauptprogramm BEGIN TitelO; KarteiKartenErstellenO; KarteiSortierenO; KarteiDurchBlaetternQ END Kartei.
Testergebnisse Programmbeispiel (5) Dieses Programm liest eine Personalkartei mit maximal 100 Karten ein, sortiert diese nach Nachnamen und gibt die Inhalte der einzelnen Karten wieder aus. Geben Sie die Anzahl der einzulesenden Karten an (1 ..100)! - = > 3
Unsortierte Eingabe: KARTE 1 NACHNAME: VORNAME: GEBDATUM: GEBORT: GESCHLECHT: FAMSTAND: GEHALT:
>SCHULZ >KARL >01.01.1950 > BRAUNSCHWEIG >MAENNLICH > LEDIG >2800.00
Testergebnisse Programmbeispiel (5)
KARTE 2 NACHNAME: VORNAME: GEBDATUM: GEBORT: GESCHLECHT: FAMSTAND: GEHALT:
— — — — -
= = = = = = =
> MEIER > PETER >31.12.1940 > BREMEN >MAENNLICH > VERHEIRATET >3500.00
KARTE 3 NACHNAME: VORNAME: GEBDATUM: GEBORT: GESCHLECHT: FAMSTAND: GEHALT:
— — — -
= = = = = = =
> MÜLLER > PETRA >29.02.1940 >HAMBURG > WEIBLICH > GESCHIEDEN >3200.00
KARTE 2 NACHNAME: VORNAME: GEBDATUM: GEBORT: GESCHLECHT: FAMSTAND: GEHALT:
— — — — -
= = = = = = =
> MEIER >PETER >31.12.1940 >BREMEN > maennlich > verheiratet >3.50000E + 003
KARTE 1 NACHNAME: VORNAME: GEBDATUM: GEBORT: GESCHLECHT: FAMSTAND: GEHALT:
— — — —
= = = = = = =
> MÜLLER > PETRA >29.02.1940 >HAMBURG > weiblich > geschieden > 3.20000E + 003
KARTE 3 NACHNAME: VORNAME: GEBDATUM: GEBORT: GESCHLECHT: FAMSTAND: GEHALT:
— — — —
= = = = = = =
>SCHULZ > KARL >01.01.1950 > BRAUNSCHWEIG > maennlich > ledig >2.80000E + 003
Sortierte Ausgabe:
127
128
2. Sprachstandard
2.3.2.5 Dateien Eine Datei (engl, file) ist eine Informationsmenge, die vom Betriebssystem eines Rechners als Gesamtheit verwaltet wird. Der Begriff Verwaltung bezeichnet das Schreiben und Lesen von Dateien sowie weitere Operationen auf Dateien. Die Organisation von Dateioperationen hängt auch vom Dateityp ab. Nach der Art der in einer Datei gespeicherten Informationen unterscheidet man die Dateiarten Textdatei, Codedatei und Datendatei. Textdateien werden mit Textverarbeitungssystemen bzw. Texteditoren erstellt. Beispiele für Textdateien sind editierte Programme (Programmtexte) und editierte umgangssprachliche Texte wie Briefe, Protokolle, Aufsätze usw. Codedateien resultieren aus der Verarbeitung von Programmtexten durch bestimmte Komponenten von Rechner-Betriebssystemen. So stellt das Ergebnis der Übersetzung eines Programmtextes mit einem Compiler, also der sogenannte Objekt-Code, eine Codedatei dar. Ebenso ist ein mit einem Binder (Linker) bearbeiteter Programmcode eine Codedatei. Datendateien beinhalten Nutzdaten, die mit Anwendungsprogrammen verarbeitet werden sollen oder die das Ergebnis von Verarbeitungsprozessen darstellen. Sollen Nutzdaten permanent, also auch über die Laufzeit von Programmen hinaus, zur Verfügung stehen, organisiert man sie in Datendateien und legt diese auf externen Speichern ab. Die externe Speicherung von Datendateien ermöglicht es verschiedenen Programmen, auf gleiche Nutzdaten zuzugreifen. Benötigt man Nutzdaten nur temporär (z.B. Zwischenergebnisse), so bietet sich ihre Organisation in Form temporärer, im Arbeitsspeicher (RAM) gehaltener Datendateien an. Betriebssysteme unterstützen üblicherweise beide Formen von Datendateien. Vom verwendeten Betriebssystem hängt die Art und Weise der Organisation, Speicherung und Manipulation von Dateien ab. Betriebssystemspezifisch sind auch die Regeln der Vergabe von Benutzungs- und Änderungsrechten für Dateien. Eine betriebssystemübergreifende Regelung dateibezogener Vorgaben, Rechte und Operationen ist im Rahmen einer Programmiersprache nicht möglich. Aus diesem Grunde sieht Modula-2 auch keinen vom Compiler unterstützten Standarddatentyp FILE vor. Dateiverarbeitungsfunktionen werden vielmehr in einem speziellen Modul des nichtresidenten Sprachteils bereitgestellt. Die Auslagerung der Dateiverarbeitung aus dem residenten Sprachteil erleichtert das Portieren von Modula-2Compilern.
2.3.2.5 Dateien
129
Die in einem Dateiverarbeitungs-Modul angebotenen Dateioperationen sind teils auf Dateien definiert, teils dienen sie der Manipulation von Dateielementen. Abhängig vom Dateityp sind diese Elemente bestimmte Einzelzeichen, Zeichenfolgen oder (strukturierte) Datenobjekte. So bestehen Codedateien ausschließlich aus binären Zeichen, während in Textdateien nur aus ASCII-Zeichen bestehende Zeichenketten auftreten. Datendateien stellen in der Regel bestimmte Anordnungen von Datenobjekten des gleichen Datentyps, seltener auch verschiedener Datentypen, dar. Eine häufig verwendete Organisationsform von Datendateien ist die Aneinanderreihung von RECORD-Objekten (Datensätzen). Diese sequentielle Speicherungsform steht im folgenden im Vordergrund. Da Codedateien hier nicht weiter interessieren, sind die folgenden Ausführungen auf Text- und Datendateien beschränkt. Treffen Aussagen auf beide Dateiarten zu, so wird nur von Datei gesprochen. Andernfalls wird die Dateiart benannt. Dateioperationen, die einzelne Elemente einer sequentiell organisierten Datei verarbeiten, setzen einen elementweisen Dateizugriff voraus. Elementweiser Zugriff bedeutet Lesen oder Schreiben einzelner Informationseinheiten. In Modula-2 kann eine Informationseinheit bestehen aus: -
einem einzelnen Zeichen (Datentyp CHAR),
-
einer Folge von Zeichen (Bytes),
-
einem Speicherwort (Datentyp WORD).
Der Zugriff zu derartigen Informationseinheiten erfolgt über ein sogenanntes Dateifenster. Das Dateifenster wird durch folgende Angaben beschrieben: - den Dateizeiger, der auf das erste Zeichen zeigt, das gelesen werden kann (Anfangsmarke) und -
die Anzahl der Zeichen, die ab dem ersten Zeichen gelesen werden soll.
Das Dateifenster kann mit speziellen Dateioperationen gesetzt und verschoben werden. Bei einer bestimmten Position des Dateifensters kann nur der im Fenster befindliche Dateiausschnitt manipuliert werden. Ist ein Zugriff zu Informationen außerhalb des Dateifensters erwünscht, so muß zuvor das Dateifenster verschoben werden. Das Verschieben des Dateifensters über das Ende einer Datei hinaus kann durch die Abfrage einer Dateiendemarke (engl, end of file, EOF) verhindert werden. Wie eben dargestellt, ist die Dateiverarbeitung in Modula-2 auf Zeichenebene organisiert. Den Benutzer von Datendateien interessieren jedoch eher RECORDObjekte (Datensätze). Der Übergang von der Satz- zur Zeichenebene wird vollzogen, indem man das Lesen und Schreiben von Sätzen auf das Lesen und Schreiben einzelner Zeichen zurückführt. Hierbei ist gegebenenfalls auf variierende Satzlän-
2. Sprachstandard
130
gen zu achten, denn Modula-2 schreibt keine einheitliche Länge der Sätze einer Datei vor. Abbildung 2.54 veranschaulicht in schematischer Weise den Dateizugriff mittels Dateifenster.
xxxxx
xxxxxxxxxxx
xxxxxx
XXXXXX
t
xxxxxxxx
XX xxxxxxxxxxxx
Dateifenster
Dateizeiger
EOF
Legende: SOF EOF x X
Start of File (Dateianfangsmarke), End of File (Dateiendemarke), Informationen der Datei und Informationen, die im Zugriff durch das Dateifenster stehen.
Abb. 2.54: Das Dateifenster und Dateimarken in einer Datei.
Um den bisherigen Ausführungen mehr Inhalt zu verleihen, sei exemplarisch ein konkretes Modula-2-Dateisystem betrachtet. Es handelt sich um die mit dem Bibliotheks-Modul FileSystem bereitgestellte Dateiverwaltung. Der Modul FileSystem ist Bestandteil des „Lilith Modula-2 Systems" [LI], das für das Betriebssystem MS-DOS entwickelt wurde. FileSystem stellt den Datentyp File und Dateioperationen zur Verfügung. Jede Datei, die mit FileSystem-Operationen bearbeitet werden soll, muß vom Datentyp File sein. Die von FileSystem exportierten Dateioperationen lassen sich in drei Gruppen einteilen: (1) Dateioperationen, die auf Text- und Datendateien anwendbar sind, (2) Dateioperationen, die nur auf Textdateien definiert sind, und (3) Dateioperationen, die nur auf Datendateien definiert sind. Eine nach diesen Gruppen gegliederte Übersicht über die Dateioperationen des Moduls FileSystem enthält die Tabelle 2.83.
2.3.2.5 Dateien
131
Tab. 2.83: Übersicht über die auf Text- und Datendateien definierten Dateioperationen im „Lilith Modula-2 System". (1) Auf Text- und Datendateien sind folgende Dateioperationen anwendbar: Create, Lookup, Close, Reset, Rename, Delete, SetRead, SetWrite, SetModify, SetOpen, SetPos, GetPos und Length. (2) Zum Lesen und Schreiben von Texten sind folgende nur auf Textdateien definierte Operationen zulässig: ReadChar und WriteChar. (3) Das Lesen und Schreiben in Datendateien gestatten folgende Operationen: ReadWord, ReadByte, ReadNBytes, Write Word, WriteByte und WriteNBytes.
Die Gruppe der auf Text- und Datendateien definierten Dateioperationen dient nicht dem Lesen und Schreiben, sondern dateiorganisatorischen Zwecken wie Erzeugen, Löschen, Öffnen, Schließen usw. von Dateien. Teils sind die Operationen nur auf temporäre, teils nur auf permanente und teils auf temporäre und permanente Dateien anwendbar. Entsprechende Hinweise sind der Tabelle 2.84 zu entnehmen, die Kurzbeschreibungen der auf Text- und Datendateien definierten Operationen enthält.
Tab. 2.84: Operationen auf Text- und Datendateien im „Lilith Modula-2 System". (1) Create: Erstellt eine temporäre Datei und positioniert den Dateizeiger auf den Dateianfang. (2) Lookup: Öffnet oder erstellt eine permanente Datei. Wird eine neue Datei erstellt, so wird ein Eintrag in das Dateiverzeichnis vorgenommen. Gleichzeitig wird der Dateizeiger auf den Anfang der Datei gesetzt und die Datei im Betriebssystem als OFFEN gekennzeichnet. (3) Close: Schließt eine Datei. Wird eine temporäre Datei geschlossen, so wird sie im internen Speicher gelöscht. Permanente Dateien werden geschlossen, indem sie im Betriebssystem als GESCHLOSSEN gekennzeichnet werden. (4) Reset: Setzt den Dateizeiger einer Datei auf den Dateianfang.
2. Sprachstandard
132
(5) Rename: Weist einer Datei einen neuen Namen zu ohne die Struktur der Datei zu verändern. (6) Delete: Löscht eine permanente Datei, indem der für diese Datei gültige Eintrag im Dateiverzeichnis gelöscht wird. (7) SetRead: Setzt den Dateistatus einer Datei auf LESEN, d.h. die Datei kann nur lesend und ohne Veränderung des Dateizeigers bearbeitet werden. (8) SetWrite: Setzt den Dateistatus einer Datei auf SCHREIBEN, d.h. die Datei kann nur schreibend und ohne Veränderung des Dateizeigers bearbeitet werden. (9) SetModify: Setzt den Dateistatus einer Datei auf LESEN-SCHREIBEN, d.h. die Datei kann bei unverändertem Dateizeiger lesend oder schreibend bearbeitet werden. (10)SetOpen: Setzt den Dateistatus einer Datei auf OFFEN, veranlaßt, daß die zu diesem Zeitpunkt im Dateipuffer des Arbeitsspeichers befindlichen Daten in die Datei geschrieben werden, und verbietet den lesenden oder schreibenden Zugriff. (11) SetPos: Setzt den Dateizeiger einer Datei auf die angegebene Position. (12)GetPos: Ermittelt die momentane Position des Dateizeigers einer Datei. (13)Length: Ermittelt die Größe einer Datei in Byte. Das Lesen und Schreiben in Textdateien erfolgt zeichenweise. Zur Verfügung steht je eine Lese- und eine Schreiboperation. Beide Operationen werden in der Tabelle 2.85 beschrieben. Tab. 2.85: Operationen auf Text-Dateien im „Lilith Modula-2 System". (1) ReadChar: Liest ein Zeichen aus der Datei von der momentanen Position des Dateizeigers und setzt diesen um ein Byte weiter. (2) WriteChar: Schreibt ein Zeichen in die Datei an die Position des Dateizeigers und setzt diesen um ein Byte weiter.
2.3.2.5 Dateien
133
Im Gegensatz zu Textdateien sind für Datendateien auch Operationen verfügbar, die mehr als ein Zeichen lesen oder schreiben können. Manipulierbare Informationseinheiten sind Zeichen, Zeichenfolgen und Speicherworte. Kurzbeschreibungen der auf diese Informationseinheiten anwendbaren Lese- und Schreiboperationen enthält die Tabelle 2.86.
Tab. 2.86: Operationen auf Datendateien im „Lilith Modula-2 System". (1) ReadWord: Liest ein Speicherwort bei gegebener Position des Dateizeigers und setzt diesen um die Länge des gelesenen Worts weiter. (2) ReadByte: Liest ein Byte bei gegebener Position des Dateizeigers und setzt diesen um ein Byte weiter. (3) ReadNBytes: Liest n Bytes bei gegebener Position des Dateizeigers und setzt diesen um n Bytes weiter. (4) WriteWord: Schreibt ein Speicherwort bei gegebener Position des Dateizeigers und setzt diesen um die Länge des geschriebenen Worts weiter. (5) WriteByte: Schreibt ein Byte bei gegebener Position des Dateizeigers und setzt diesen um ein Byte weiter. (6) WriteNBytes: Schreibt n Bytes bei gegebener Position des Dateizeigers und setzt diesem um n Bytes weiter.
Neben den eigentlichen Text- oder Nutzdaten werden in einer Datei auch Informationen abgelegt, die Aspekte wie Inhalt, Bearbeitungsstand usw. der Datei betreffen. Üblicherweise faßt man diese Dateiinformationen in einem RECORD-Objekt (Kennsatz) zusammen. Der Kennsatz einer Datei steht in der Regel am Dateianfang. Im „Lilith Modula-2 System" umfaßt der Kennsatz folgende Informationen: -
den Inhalt des Dateipuffers im Arbeitsspeicher,
-
den Status der Datei (d.h den Zustand der Datei),
-
die Einheit der Datei (d.h. das Laufwerk der Datei),
-
den Erfolg der letzten Dateioperation und den Bearbeitungszustand, der angibt, ob das Ende der Datei bereits erreicht ist.
2. Sprachstandard
134
Einige der Dateiinformationen werden ausschließlich von den Dateioperationen benutzt. Für den Anwender zugänglich und bedeutsamer ist die Verbund-Komponente, die Auskunft über den Erfolg der letzten Dateioperation gibt. Diese Komponente stellt einen Aufzählungsdatentyp dar, dessen Werte die erfolgreiche Durchführung einer Dateioperation oder, im Fall eines Mißerfolgs, die möglichen Fehlersituationen charakterisieren. Eine Zusammenstellung dieser Werte einschließlich einer kurzen Beschreibung ihrer Bedeutung enthält die Tabelle 2.87.
Tab. 2.87: Fehlercodes von Ein-/Ausgabeoperationen auf „Files". (1) Der Fehlercode kann folgende Werte annehmen: done, notdone, notsupported, callerror, unknownmedium, unknownfile, paramerror, toomanyfiles, eom und userdeverror. (2) Die erfolgreiche Bearbeitung der letzten Dateioperation wird durch den Wert „done" angezeigt. (3) Der Wert „notdone" signalisiert den Abbruch der letzten eingeleiteten Dateioperation aufgrund eines Fehlers. (4) Wurde die letzte Dateioperation nicht unterstützt, so entsteht der Fehler „notsupported". (5) Der Wert „callerror" zeigt einen unzulässigen Dateizugriff an. (6) Der Zugriff auf ein unbekanntes Laufwerk wird durch den Wert „unknownmedium" angezeigt. (7) Das Resultat einer physikalisch nicht gefundenen Datei wird durch den Wert „unknownfile" angezeigt. (8) Der Wert „paramerror" zeigt eine fehlerhafte Parameterübergabe an. (9) Der Wert „toomanyfiles" zeigt an, daß die Anzahl geöffneter Dateien die maximal zulässige Anzahl überschritten hat. (10) Der Wert „eom" zeigt an, daß auf dem Speichermedium kein freier Platz mehr zur Verfügung steht.
Programmbeispiel (6)
135
(11) Der Wert „userdeverror" zeigt an, daß eine Geräteeinheit angesprochen wurde, die nicht definiert ist.
Programmbeispiel (6) MODULE Personal; (* *) (* Dieses Programm verwaltet eine Personaldatei. Es können interaktiv Personaldatensätze eingefügt, geändert oder ausgegeben werden. Zum Verständnis dieses Beispiels ist die Kenntnis der Syntax der bedingten Anweisung (IFAnweisung), der Auswahlanweisung (CASE-Anweisung), der Wiederholungsanweisung (REPEAT-, WHILE- und FOR-Anweisung) und der PROCEDURE-Anweisung notwendig. In diesem Programmbeispiel werden die Grundbibliotheks-Module FileSystem, InOut, ReallnOut und Strings verwendet. Weiterhin kommen der Basis-Modul SYSTEM und der BibliotheksModul Screen zur Anwendung. *) (*
( * (* Import-Deklarationen (*
*)
*
)
*)
*)
FROM FileSystem IMPORT File, Lookup, Close, Length, WriteNBytes, ReadNBytes, SetPos, SetWrite, SetModify, SetRead; FROM InOut IMPORT Write, WriteString, WriteLn, WriteCard, Read, ReadString, ReadCard; FROM ReallnOut IMPORT ReadReal, WriteReal; FROM Screen IMPORT ClearScreen, Gotoxy; FROM Strings IMPORT Compare, Concat; FROM SYSTEM IMPORT ADR, SIZE; (* *) (* Konstantendeklarationen (* CONST RecordLaenge = 128; MaxRecordperOffset = 512; (*
*) *)
(* Typendeklarationen
*)
TYPE Name = ARRAY [0..30] OF CHAR Ort = ARRAY [0..30] OF CHAR Datum = ARRAY [0..10] OF CHAR Person = RECORD PersonalNummer ; CARDINAL; Nachname Name; Vorname Name; GebDatum Datum;
')
2. Sprachstandard
136
(*
GebOrt Geschlecht FamStand FirmenEintritt Gehalt Filler END;
:
: :
Ort; (maennlich, weiblich); (ledig, verheiratet, geschieden, verwitwet); Datum; REAL; CHAR;
*)
(* Variablendeklarationen (* VAR PersKartei KarteiKarte PersNummer space
*) *)
File; Person; CARDINAL; CHAR;
(* (* Unterprogramm zum Einblenden von Programminformationen (*
*) *) *)
P R O C E D U R E TitelO; BEGIN
(
*
(* Löscht den Bildschirminhalt
*
)
(*
*)
*)
ClearScreenQ; WriteString('Dieses Programm liest Karteikarten einer Personalkartei ein, speichert'); WriteLnO; WriteString('sie, verändert bestehende Karteikarten oder gibt bestimmte oder alle'); WriteLnO; WriteStringC Karteikarten wieder auf dem Bildschirm aus.'); WriteLnO; Read (space) E N D Titel;
(* (* Unterprogramm zum Aufbau des Bildschirmmenüs (* P R O C E D U R E BlldschlrmO; VAR Wahl : CHAR; Abbruch : BOOLEAN; BEGIN REPEAT ClearScreen; Gotoxy(10,6); WriteString('Z U G R I F F A U F D I E PERSONALKARTEI'); Gotoxy(22,9); WriteString('Karteikarte einlesen - = >L'); Gotoxy(22,11); WriteString('Karteikarte ändern - = >U'); Gotoxy(22,13); WriteString('Bestimmte Karteikarte lesen - = > B ' ) ; Gotoxy(22,15); WriteString('Alle Karteikarten lesen - = >A');
*) *) *)
Programmbeispiel (6)
137
Gotoxy(22,17); WriteString('Ende des Programms - = > X ' ) ; Gotoxy(22,19); WriteString('Auswahl - = > ' ) ; Abbruch: = FALSE; Gotoxy(56,19); Read (Wahl); IF (Wahl = 'L') OR (Wahl = T ) T H E N KarteiKarteEinLesenO ELSIF (Wahl = 'U') OR (Wahl = 'u') T H E N KarteiKarteAendernO ELSIF (Wahl = 'B') OR (Wahl = 'b') T H E N KarteiKarteAusLesenO ELSIF (Wahl = 'A') OR (Wahl = 'a') T H E N SetRead(PersKartel); SetPos(PersKartei, 0, RecordLaenge); W H I L E NOT(PersKartel.eof) D O KarteiDurchBlaetternO END ELSIF (Wahl = 'X') OR (Wahl = 'x') T H E N Abbruch: = TRUE ELSE Write(CHR(7)); Gotoxy(56,19); Write('') END UNTIL Abbruch E N D Bildschirm; (*
( * Unterprogramm zur Abfrage der einzelnen einzulesenden Daten (*
P R O C E D U R E StandardAbfrageO; VAR i : CARDINAL; P R O C E D U R E Geschlecht(I : CARDINAL); VAR help : ARRAY [0..20] O F CHAR; found : BOOLEAN; BEGIN found : = FALSE; REPEAT Gotoxy(10,11); WriteString('GESCHLECHT: - = > ' ) ; ReadString(help); IF (Compare(help, 'maennlich') = 0) OR (Compare(help, 'MAENNLICH') = 0) OR (Compare(help, 'Maennlich') = 0) T H E N KarteiKarte.Geschlecht: = maennlich; found: = T R U E ELSIF (Compare(help, 'weiblich') = 0) OR (Compare(help, 'WEIBLICH') = 0) OR (Compare(help, 'Weiblich') = 0) T H E N KarteiKarte.Geschlecht: =weiblich; found: = T R U E ELSE Write(CHR(7)) END UNTIL found E N D Geschlecht;
*) *)
*)
2. Sprachstandard
138
P R O C E D U R E Stand(i: CARDINAL); VAR help : ARRAY [0..20] OF CHAR; found : BOOLEAN; BEGIN found: = FALSE; REPEAT Gotoxy(10,12); WriteString('FAMSTAND: - = >'); ReadString(help); IF (Compare(help, 'ledig') = 0) OR (Compare(help, 'LEDIG') = 0) OR (Compare(help, 'Ledig') = 0) THEN KarteiKarte.FamStand: = ledig; found: = TRUE ELSIF (Compare(help, 'verheiratet') = 0) OR (Compare(help, 'VERHEIRATET') = 0) OR (Compare(help, 'Verheiratet') = 0) THEN KarteiKarte.FamStand: =verheiratet; found: = T R U E ELSIF (Compare(help, 'geschieden') = 0) OR (Compare(help, 'GESCHIEDEN') = 0) OR (Compare(help, 'Geschieden') = 0) THEN KarteiKarte.FamStand: = geschieden; found: = T R U E ELSIF (Compare(help, 'verwitwet') = 0) OR (Compare(help, 'VERWITWET') = 0) OR (Compare(help, 'Verwitwet') = 0) THEN KarteiKarte.FamStand: =verwitwet; found: = T R U E ELSE Write(CHR(7)) END UNTIL found E N D Stand; BEGIN Gotoxy(10,7); WriteString('NACHNAME: - = >'); ReadString(KarteiKarte.Nachname); Gotoxy(10,8); WriteString('VORNAME: - = >'); ReadString(KarteiKarte. Vorname); Gotoxy(10,9); WrlteString('GEBDATUM: - = >'); ReadString(KarteiKarte.GebDatum); Gotoxy(10,10); WriteString('GEBORT: - = >'); ReadString(KarteiKarte.GebOrt); Geschlecht(i); Stand(i); Gotoxy(10,13); WriteString('GEHALT: - = >'); ReadReal(KarteiKarte.Gehalt); Read (space) E N D StandardAbfrage;
r
(* Unterprogramm zum Einlesen eines Datensatzes (* P R O C E D U R E KarteiKarteEinLesenO; VAR highPos, lowPos : CARDINAL;
*) *) *)
Programmbeispiel (6)
139
written : CARDINAL; BEGIN INC(PersNummer); KarteiKarte.PersonalNummer: = PersNummer; ClearScreenO; Gotoxy(10,6); WriteStrlng('KARTE '); WriteCard(PersNummer, 5); StandardAbf rageO ; SetModify(PersKartel); Length(PersKartei, highPos, lowPos); SetPos(PersKartei, highPos, lowPos); WriteNBytes(PersKartei, ADR(KartelKarte), SIZE(KartelKarte), written) END KarteiKarteEinLesen; (* Unterprogramm zum Ändern eines Datensatzes C PROCEDURE KarteiKarteAendernO; VAR ok : BOOLEAN; highPos, lowPos : CARDINAL; written, PersNr : CARDINAL; BEGIN ok: = FALSE; REPEAT ClearScreenO; Gotoxy(10,6); WriteString('PERSNUMMER: - = >'); ReadCard (PersNr); IF (PersNr >0) AND (PersNr< = PersNummer) THEN ok: =TRUE ELSE Write(CHR(7)) END UNTIL ok; KarteiKarte.PersonalNummer: = PersNr; StandardAbf rageO; highPos : = PersNr DIV MaxRecordperOffset; lowPos : = (PersNr MOD MaxRecordperOffset) * RecordLaenge; SetPos(PersKartei, highPos, lowPos); SetWrite(PersKartei); WriteNBytes(PersKartei, ADR(KarteiKarte), SIZE(KarteiKarte), written) END KarteiKarteAendern;
*j *)
(* Unterprogramm zur Ausgabe eines einzelnen Datensatzes
*)
PROCEDURE StandardAusgabeO; BEGIN IF NOT(PersKartei.eof) THEN ClearScreenO; Gotoxy(10,6); WriteString('KARTE '); WriteCard(KarteiKarte.PersonalNummer, 5); Gotoxy(10,7); WriteString('NACHNAME: - = >'); WriteString(KarteiKarte. Nachname) ;
2. Sprachstandard
140
Gotoxy(10,8); WriteString('VORNAME: - = >'); WriteString(KarteiKarte.Vorname); Gotoxy(10,9); WriteString('GEBDATUM: - = >'); WriteString(KarteiKarte.GebDatum); Gotoxy(10,10); WriteString('GEBORT: - = >'); WriteString(KarteiKarte.GebOrt); Gotoxy(10,11); WriteStringCGESCHLECHT: - = >'); IF KarteiKarte.Geschlecht = maennlich THEN WriteString('maennlich') ELSIF KarteiKarte.Geschlecht=weiblich THEN WriteString('weiblich') END; Gotoxy(10,12); WriteString('FAMSTAND: - = >'); IF KarteiKarte.FamStand = ledig THEN WriteString('ledig') ELSIF KarteiKarte.FamStand =verheiratet THEN WriteString('verheiratet') ELSIF KarteiKarte.FamStand = geschieden THEN WriteString('geschieden') ELSIF KarteiKarte.FamStand=verwitwet THEN WriteString('verwitwet') END; Gotoxy(10,13); WriteString('GEHALT: - = >'); WriteReal (KarteiKarte.Gehalt, 13); Read (space) END END StandardAusgabe;
(* Unterprogramm zur Ausgabe von Datensätzen (
*
P R O C E D U R E KarteiKarteAusLesenO; VAR ok : BOOLEAN; highPos, lowPos : CARDINAL; read Bytes, PersNr : CARDINAL; BEGIN ok: = FALSE; REPEAT ClearScreenO; Gotoxy(10,6); WriteStringCPERSNUMMER: - = >'); ReadCard (PersNr); IF (PersNr > 0) AND (PersNr < = PersNummer) THEN ok: = TRUE ELSE Write(CHR(7)) END UNTIL ok; highPos : = PersNr DIV MaxRecordperOffset; lowPos : = (PersNr M O D MaxRecordperOffset) * RecordLaenge; SetPos(PersKartei, highPos, lowPos); SetRead(PersKartei); ReadNBytes(PersKartei, ADR(KarteiKarte), SIZE(KarteiKarte), readBytes);
*)
*)
Programmbeispiel (6)
141
StandardAusgabeO END KarteiKarteAusLesen;
(* Unterprogramm zum Blättern von Datensätzen (*P R O C E D U R E KarteiDurchBlaetternO; VAR read Byte : CARDINAL; BEGIN ReadNBytes(PersKartei, ADR(KarteiKarte), SIZE(KarteiKarte), readByte); StandardAusgabeO E N D KarteiDurchBlaettern;
(* Unterprogramm zur Prüfung, ob Datei leer ist
*)
(*
*)
P R O C E D U R E FileEmptyOiBOOLEAN; VAR hlghPos, lowPos CARDINAL; BEGIN Length(PersKartei, highPos, lowPos); RETURN(highPos = 0) A N D (lowPos=0) END FileEmpty;
(*
*)
(* Unterprogramm, um Anzahl der gespeicherten Datensätze in erstem Datensatz zu speichern*) *) (* P R O C E D U R E WritePersonalNummer(PersNr : CARDINAL); VAR written : CARDINAL; BEGIN WITH KarteiKarte DO PersonalNummer : = PersNr; Geschlecht : = maennllch; FamStand : = ledig; Gehalt : = 0.0; Concat(", 'Mustermann', Nachname); Concatf', 'Erika', Vorname); Concat(", '01.01.1980', GebDatum); Concat(", '9999 Musterstadt', GebOrt); Concat(", '01.01.1980', FlrmenEintritt); END; SetWrite(PersKartel); SetPos(PersKartei, 0, 0); WriteNBytes(PersKartei, ADR(KartelKarte), SIZE(KarteiKarte), written) E N D WritePersonalNummer;
2. Sprachstandard
142
(* Unterprogramm, um Anzahl der gespeicherten Datensätze aus erstem Datensatz zu lesen PROCEDURE ReadPersonalNummerO; VAR read Byte : CARDINAL; BEGIN SetRead(PersKartei); ReadNBytes(PersKartei, ADR(KarteiKarte), SIZE(KarteiKarte), readByte); PersNummer: = KarteiKarte.PersonalNummer END ReadPersonalNummer;
*)
(*
*)
(* Hauptprogramm C BEGIN Titel 0; Lookup(PersKartei, 'PERSONAL.DAT', TRUE); IF FileEmptyO THEN PersNummer: =0; WritePersonalNummer(PersNummer) ELSE ReadPersonalNummerO END; BlldschirmO; WritePersonalNummer(PersNummer); Close(PersKartei) END Personal.
*) *)
Testergebnisse Programmbeispiel (6) Dieses Programm liest Karteikarten einer Personaldatei ein, speichert sie, verändert bestehende Karteikarten oder gibt bestimmte oder alle Karteikarten wieder auf dem Bildschirm aus.
Anzeigen des Bildschirmmenüs: ZUGRIFF
AUF
DIE
PERSO N A LKA R T EI
Karteikarte einlesen
— = > L
Karteikarte ändern
— = > U
Bestimmte Karteikarte lesen — = > B Alle Karteikarten lesen
— = > A
Ende des Programms
- = > X
Auswahl
- = > L
2.3.2.6 Prozedurtypen
143
Eingabe nach Auswahl „L": KARTE 1 NACHNAME: VORNAME: GEBDATUM : GEBORT: GESCHLECHT: FAMSTAND: GEHALT:
— — -
= = = = = = =
> MÜLLER >UTE >01.01.1950 > BERLIN > WEIBLICH > LEDIG >2800.00
Eingabe nach Auswahl „L": KARTE 2 NACHNAME: VORNAME : GEBDATUM: GEBORT: GESCHLECHT: FAMSTAND: GEHALT:
-
= = = = = = =
> BERGER >OTTO >31.12.1950 > MÜNCHEN > MAENNLICH > VERWITWET >3000.00
Ausgabe nach Auswahl „B": KARTE 2 NACHNAME: VORNAME: GEBDATUM: GEBORT: GESCHLECHT: FAMSTAND: GEHALT:
-
= = = = = = =
> BERGER >OTTO >31.12.1950 > MÜNCHEN >maennlich >veiwitwet >3.00000E + 003
2.3.2.6 Prozedurtypen Im Rahmen von Datenverarbeitungsaufgaben können nicht nur Strukturvariante Datenobjekte auftreten, sondern auch ablaufvariante Verarbeitungsprozesse. Ein ablaufvarianter Verarbeitungsprozeß enthält ein Verarbeitungs-Grundmuster, das abhängig von bestimmten Merkmalen der zu verarbeitenden Datenobjekte geringfügig variiert wird. Als Beispiel sei die Abrechnung von Leistungen genannt, die sich aus mehreren, für alle Abrechnungsfälle zutreffenden Leistungskomponenten zusammensetzen. Für alle Abrechnungsfälle gilt somit das gleiche Abrechnungsgrundeine bestimmte Leistungskomponente, je nach Art des Abrechnungsfalls, verschiedene Abrechnungstarife anzuwenden sind.
2. Sprachstandard
144
Zur Behandlung von ablaufvarianten Verarbeitungsproblemen bietet Modula-2 Prozedurtypen und Prozedurvariablen an. Einer Prozedurvariablen können keine Datenobjekte, sondern nur Prozeduren zugewiesen werden. Insbesondere können einer Prozedurvariablen Prozeduren zugewiesen werden, die Variante Verarbeitungsabschnitte eines Verarbeitungsprozesses darstellen. Der Aufruf einer solchen Prozedurvariablen hat zur Folge, daß an der Aufrufstelle die Prozedur gestartet wird, die der Prozedurvariablen als Wert zugewiesen wurde. Je nach dem Wert der Prozedurvariablen können an der Aufrufstelle folglich verschiedene, Varianten darstellende Prozeduren zum Ablauf gebracht werden. Obwohl Prozedurvariablen keine Datenobjekte im eigentlichen Sinne darstellen, werden sie in Modula-2 syntaktisch als solche behandelt. Die syntaktische Gleichstellung mit allen anderen Variablen hat zwei Konsequenzen: Prozedurvariablen müssen deklariert werden, und sie müssen einem Prozedurtyp angehören. Modula-2 erlaubt die Verwendung von Prozedurvariablen, die: -
dem vordeklarierten Datentyp PROC oder
-
einem PROCEDURE-Typ
angehören. Der vordeklarierte Datentyp PROC ist für parameterlose Prozeduren vorgesehen. Er ist wie folgt definiert: TYPE PROC = PROCEDURE.
Verwendung findet der PROC-Typ primär im Zusammenhang mit der Programmierung paralleler Prozesse. Daher wird er hier nicht weiter betrachtet. PROCEDURE-Typen beschreiben Objekte, die Prozeduren und Funktionsprozeduren darstellen können. Für die Definition eines PROCEDURE-Typs gilt das in Abbildung 2.55 dargestellte Syntaxdiagramm. Eine Beschreibung des Diagramms enthält die Tabelle 2.88.
ProcedureType PROCEDURE
Abb. 2.55: Syntaxdiagramm für „ProcedureType" (Prozedurtyp).
145
2.3.2.6 Prozedurtypen
Tab. 2.88: Beschreibung des Syntaxdiagramms für „ProcedureType". (1) Die Beschreibung eines Prozedurtyps besteht aus dem Schlüsselwort PROCEDURE und einer auf das Schlüsselwort folgenden formalen Typliste. Die formale Typliste benennt die Datentypen der formalen Prozedurparameter. (2) Fehlt die Angabe einer formalen Typliste, so beschreibt das Schlüsselwort PROCEDURE den Typ der parameterlosen Prozedur. Die formale Typliste gibt die Anzahl und die Typen der Parameter eines Prozedurtyps an. Die Definition einer formalen Typliste beschreibt das Syntaxdiagramm in Abbildung 2.56. Die dazugehörigen Erläuterungen enthält die Tabelle 2.89.
FormalTypeList
Quallder '
Abb. 2.56: Syntaxdiagramm für „FormalTypeList" (formale Typliste).
Tab. 2.89: Beschreibung des Syntaxdiagramms für „FormalTypeList". (1) Den Anfang einer formalen Typliste bildet eine in runden Klammern eingeschlossene Liste, die Typen formaler Parameter enthält, oder eine in runden Klammern eingeschlossene Leerliste.
2. Sprachstandard
146
(2) Die Typliste kann einen Typ oder mehrere durch Kommata getrennte Typen enthalten. Beschreibt ein Typ einen Übergabeparameter, so ist ihm das Schlüsselwort VAR voranzustellen. (3) Als Typen können in der Typliste alle vordefinierten und selbstdefinierten Typen angegeben werden. (4) Ist dem Klammerausdruck ein durch einen Doppelpunkt eingeleiteter Ergebnistyp nachgestellt, so beschreibt der Prozedurtyp eine Funktionsprozedur. Der Ergebnistyp muß ein bereits bekannter Typ sein.
Die Anwendung einer Prozedurvariablen erfordert im einzelnen folgende Vorkehrungen: - Definition des PROCEDURE-Typs, dem die einzuführende Prozedurvariable angehören soll, -
Deklaration der (in einem Verarbeitungsprozeß anzuwendenden) Prozedurvariablen,
-
Schreiben der Prozeduren, die der Prozedurvariablen als Werte zugewiesen werden können,
-
Zuweisung einer Prozedur zu der Prozedurvariablen und Aufruf der Prozedurvariablen.
Prozeduren, die einer Prozedurvariablen zugewiesen werden sollen, müssen mit dieser kompatibel sein. Dies ist der Fall, wenn identische Definitionsformate (nach Anzahl und Typ der formalen Parameter und nach Art der Prozedur) vorliegen. Zur Verdeutlichung obiger Ausführungen sei ein Beispiel betrachtet, auf dessen Inhalte nicht näher eingegangen wird. Definiert werde der Typ einer Funktionsprozedur: TYPE IntProc = PROCEDURE (CARDINAL, INTEGER) :INTEGER;
Nun werde eine Prozedurvariable eingeführt, die diesem Prozedurtyp angehört: VAR berechne : INTPROC;
Weiter seien zwei nicht näher spezifizierte Prozeduren gegeben:
147
2.3.2.6 Prozedurtypen
PROCEDURE versioni (n1 :CARDINAL; n2:INTEGER):INTEGER;
END versioni ; PROCEDURE version2(m1 : CARDINAL; m2:INTEGER):INTEGER;
END version2;
Möchte man in einem Verarbeitungsfall die Prozedur „version2" anwenden, so ist „version2" der Prozedurvariablen „berechne" als Wert zuzuweisen und „berechne" aufzurufen:
berechne : = version2;
(* Wertzuweisung *)
ergebnis : = berechne(x,y); (»Prozeduraufruf*)
Hierbei stellen „ergebnis" und „y" INTEGER-Variablen und „x" eine CARDINAL-Variable dar. Bei der Anwendung von Prozedurvariablen sind verschiedene Restriktionen zu berücksichtigen. Eine Zusammenstellung dieser Restriktionen enthält die Tabelle 2.90. Tab. 2.90: Restriktionen bei Anwendung von Prozedurvariablen. (1) In anderen Prozeduren lokal definierte Prozeduren dürfen Prozedurvariablen nicht zugewiesen werden. (2) Standardprozeduren dürfen Prozedurvariablen nicht zugewiesen werden. (3) Eine Prozedur muß mit der Prozedurvariablen, der sie zugewiesen werden soll, kompatibel sein. (4) Wird eine Prozedur einer Prozedurvariablen zugewiesen, so darf in der Zuweisung nur der Name ohne nachfolgende Parameterliste angegeben werden.
2. Sprachstandard
148
In Modula-2 wird das Anwendungsspektrum von Prozedurtypen dadurch erheblich erweitert, daß Prozeduren auch als aktuelle Parameter in Prozeduraufrufen verwendet werden dürfen. Allerdings muß der dem aktuellen Parameter entsprechende formale Parameter einem Prozedurtyp angehören.
Programmbeispiel (7) M O D U L E Lager; (*
*)
(* Dieses Programm verwaltet Artikel eines Lagersystems. Die Artikel können interaktiv eingeeingelesen und nach verschiedenen Kriterien (Bezeichnung, Nummer, Bestand) sortiert auf dem Bildschirm ausgegeben werden. Z u m Verständnis dieses Beispiels ist die Kenntnis der Syntax der bedingten Anweisung (IFAnweisung), der Auswahlanweisung (CASE-Anweisung), der Wiederholungsanweisung (REPEAT-, WH1LE- und FOR-Anweisung) und der P R O C E D U R E - A n w e i s u n g notwendig. In diesem Programmbeispiel werden die Grundbibliotheks-Module InOut, ReallnOut und Strings verwendet. Weiterhin kommt der Bibliotheks-Modul Screen (zur Ansteuerung des Bildschirms) zur Anwendung. *) (* *) (* (* Import-Deklarationen (* F R O M InOut IMPORT F R O M ReallnOut I M P O R T F R O M Screen IMPORT F R O M Strings IMPORT (*
*) *) *) Write, WriteStrlng, WriteLn, WriteCard, Read, ReadString, ReadCard; ReadReal, WriteReal; ClearScreen, Gotoxy; Compare; *)
(* Konstantendeklarationen
*)
(*
*>
(*
*)
CONST MaxLagerArtikel = 100;
(* Typendeklarationen
*)
(•
*)
TYPE LagerArtikel = [1 ..MaxLagerArtikel]; Text = A R R A Y [0..30] O F C H A R ; Artikel = RECORD Bezeichnung Text; Nummer : CARDINAL; Gewicht ; REAL; BestandsMenge CARDINAL; Beschaffung (gas, fluessig, fest); Verpackung (kiste, karton, sack, fass); END;
149
Programmbeispiel (7)
LagerBestand SortierKriterium
= ARRAY LagerArtikel OF Artikel; = PROCEDURE (Artikel, Artikel):BOOLEAN;
(*
(* Variablendeklarationen VAR LagerBestaende MaxArtikel space
*)
LagerBestand; CARDINAL; CHAR;
C (* Unterprogramm zum Einblenden von Programminformationen C PROCEDURE Titel(); BEGIN
*) *) *)
*)
(* Löscht den Bildschirminhalt *) p *) ClearScreenQ; WriteStringC Dieses Programm liest Artikelsätze mit maximal 100 Artikeln ein,'); WriteLnO; WriteStringC sortiert diese wie gewünscht nach Artikelnummer, Artikelbezeichnung'); WriteLnO; WriteString('oder nach Artikelbestand und gibt diese dann wieder auf dem Bildschirm aus.'); WriteLnO; Read(space) END Titel;
(* Unterprogramm zum Aufbau des Bildschirmmenüs PROCEDURE BildschlrmO; VAR Wahl : CHAR; Abbruch : BOOLEAN; BEGIN REPEAT ClearScreen; Gotoxy(24,6); WriteString('A R T I K E L V E R W A L T U N G'); Gotoxy(23,9); WriteStrlng('Artikel einlesen - = >L'); Gotoxy(23,11); WriteString('Nach Artikelbezeichnungen - = > B') ; Gotoxy(23,13); WriteString('Nach Artikelnummern - = >N'); Gotoxy(23,15); WriteString('Nach Artikel bestand - = >D'); Gotoxy(23,17); WriteString('Ausgeben der Artikel - = >A'); Gotoxy(23,19); WriteString('Ende des Programms - = >E'); Gotoxy(23,21); WriteString('Auswahl - = >'); Abbruch: = FALSE; Gotoxy(57,21); Read(Wahl); IF (Wahl = 'L') OR (Wahl = T) THEN
*)
2. Sprachstandard
150
Artikel KartenErstellenO; ELSIF (Wahl = 'B') OR (Wahl = 'b') THEN IF MaxArtikel > 0 THEN ArtikelSortieren(SortBezeichnung) ELSE FehlerO END ELSIF (Wahl = 'N') OR (Wahl = 'n') THEN IF MaxArtikel > 0 THEN ArtikelSortieren(SortNummern) ELSE FehlerO END ELSIF (Wahl = 'D') OR (Wahl = 'd') THEN IF MaxArtikel > 0 THEN ArtikelSortleren(SortBestandsMenge) ELSE FehlerO END ELSIF (Wahl = 'A') OR (Wahl = 'a') THEN IF MaxArtikel > 0 THEN ArtikelDurchBlaetternO; ELSE FehlerO END ELSIF (Wahl = 'E') OR (Wahl = 'e') THEN Abbruch: = T R U E ELSE Write(CHR(7)); Gotoxy(57,21); Write('') END UNTIL Abbruch; E N D Bildschirm; (*
*)
(* Unterprogramm zum Einlesen von Artikelkarteikarten (* P R O C E D U R E ArtikelKartenErstellenO; VAR ArtikelCheck : BOOLEAN; i : CARDINAL; P R O C E D U R E Beschaff(i : CARDINAL); VAR help : ARRAY [0 .10] OF CHAR; found : BOOLEAN; BEGIN found: = FALSE; REPEAT Gotoxy(10,11); WriteString('BESCHAFFUNG: - = >'); ReadString(help); IF (Compare(help, 'gas') = 0) OR (Compare(help, 'GAS') = 0) OR (Compare(help, 'Gas') = 0) THEN LagerBestaende[i].Beschaffung: = gas; found: = T R U E
*) *)
ELSIF (Compare(help, 'fluessig') = 0) OR (Compare(help, 'FLUESSIG') = 0) OR (Compare(help, 'Fluessig') = 0) THEN LagerBestaende[i].Beschaffung: =fluessig; found: =TRUE ELSIF (Compare(help, 'fest') = 0) OR (Compare(help, 'FEST') = 0) OR (Compare(help, 'Fest') = 0) THEN LagerBestaende[i]. Beschaffung: =fest; found: =TRUE ELSE Wrlte(CHR(7)) END UNTIL found END Beschaff;
(* (* Innere Prozedur von ArtikelKartenErstellen (*
PROCEDURE Verpack(i: CARDINAL); VAR help : ARRAY [0..10] OF CHAR; found : BOOLEAN; BEGIN found: = FALSE; REPEAT Gotoxy(10,12); WriteString('VERPACKUNG: - = >'); ReadStrlng(help); IF (Compare(help, 'kiste') = 0) OR (Compare(help, 'KISTE') = 0) OR (Compare(help, 'Kiste') = 0) THEN LagerBestaende[i].Verpackung: = kiste; found: =TRUE ELSIF (Compare(help, 'karton') = 0) OR (Compare(help, 'KARTON') = 0) OR (Compare(help, 'Karton') = 0) THEN LagerBestaende[i].Verpackung: = karton; found: =TRUE ELSIF (Compare(help, 'sack') = 0) OR (Compare(help, 'SACK') = 0) OR (Compare(help, 'Sack') = 0) THEN LagerBestaende[i] .Verpackung: = sack; found: =TRUE ELSIF (Compare(he)p, 'fass') = 0) OR (Compare(help, 'FASS') = 0) OR (Compare(help, 'Fass') = 0) THEN LagerBestaende[i].Verpackung: =fass; found: =TRUE ELSE Write(CHR(7)) END UNTIL found END Verpack; c BEGIN ArtikelCheck: = FALSE; ClearScreenO; REPEAT Gotoxy(10,12); WriteString('Geben Sie die Anzahl der einzulesenden Artikel an (1 ..100)1 — = >'); ReadCard (MaxArtikel); IF (MaxArtikel > = 0) AND (MaxArtikel < = 100) THEN ArtikelCheck: =TRUE; FOR i: = 1 TO MaxArtikel DO ClearScreenO; Gotoxy(10,6); WriteString('ARTIKEL'); WriteCard(i, 3);
Gotoxy(10,7); WriteString('BEZEICHNUNG: - = >'); ReadString(LagerBestaende[i] .Bezeichnung); Gotoxy(10,8); WrlteStringfNUMMER: - = >'); ReadCard(LagerBestaende[i],Nummer); Gotoxy(10,9); WriteStringfGEWICHT: - = >'); Read Real (LagerBestaende [i] .Gewicht); Gotoxy(10,10); WriteString('BESTANDSMENGE: - = >'); ReadCard(LagerBestaende[i].BestandsMenge); Beschaff (i); Verpack®; Read (space; END ELSE Write(CHR(7)) END UNTIL ArtikelCheck; END ArtikelKartenErstellen;
(* Unterprogramm zum Sortieren der Artikel nach Bezeichnung PROCEDURE SortBezeichnung(Artikel1, Artikel2 : Artlkel):BOOLEAN; BEGIN RETURN(Compare(Artikei1.Bezeichnung, Artlkel2.Bezeichnung) > 0) END SortBezeichnung; (* (* Unterprogramm zum Sortieren der Artikel nach Nummern (* PROCEDURE SortNummern(Artikei1, Artikel : Artikel):BOOLEAN; BEGIN RETURN(Artikel1.Nummer > Artikei2.Nummer) END SortNummern; (* (* Unterprogramm zum Sortieren der Artikel nach Bestand (* PROCEDURE SortBestandsMenge(Artikel1, Artikei2 : Artikel):BOOLEAN; BEGIN RETURN(Artikel1.BestandsMenge > Artikel2.BestandsMenge) END SortBestandsMenge;
(* (* Unterprogramm zum Sortleren der Artikel (* PROCEDURE ArtikelSortieren(Sortiere : SortierKriterium); VAR i, j : CARDINAL; x : Artikel; BEGIN
FOR i: = 2 TO MaxArtikel DO FOR j: = MaxArtikel TO I BY - 1 DO IF Sortiere(LagerBestaende[j-l], LagerBestaende[j]) THEN x: = LagerBestaende[)-1 ] ; LagerBestaende[j-1 ] : = LagerBestaende[j] ; LagerBestaende[j]: = x END END END END ArtikelSortieren;
C (* Unterprogramm zum Blättern aller Artikel (* PROCEDURE ArtikelDurchBlaetternO; VAR i : CARDINAL; BEGIN FOR i: = 1 TO MaxArtikel DO ClearScreenO; Gotoxy(10,6); WriteString('ARTIKEL '); WriteCard(i, 3); Gotoxy(10,7); WriteString('BEZEICHNUNG: - = >'); WriteString(LagerBestaende[i].Bezeichnung); Gotoxy(10,8); WriteStrlngfNUMMER: - = >'); WriteCard (LagerBestaende[l] .Nummer,5) ; Gotoxy(10,9); WriteString('GEWICHT: - = >'); WriteReal(LagerBestaende[i] .Gewicht, 13) ; Gotoxy(10,10);WriteString('BESTANDSMENGE: - = >'); WriteCard (LagerBestaende[i].BestandsMenge, 5); Gotoxy(10,11); WriteString('BESCHAFFUNG: - = >'); IF LagerBestaende[i].Beschaffung = gas THEN WriteString('gas') ELSIF LagerBestaende[i]. Beschaffung=fluessig THEN WriteString('fluessig') ELSIF LagerBestaende[i].Beschaffung =fest THEN WriteString('fest') END; Gotoxy(10,12); WriteString('VERPACKUNG: - = >'); IF LagerBestaende[i].Verpackung = kiste THEN WrlteString('kiste') ELSIF LagerBestaende[i] .Verpackung = karton THEN WriteString('karton') ELSIF LagerBestaende[i].Verpackung = sack THEN WriteString('sack') ELSIF LagerBestaende[i].Verpackung=fass THEN WriteString('fass') END; Read(space) END END ArtikelDurchBlaettern;
154
2. Sprachstandard
(* (* Unterprogramm zum Ausgeben von Fehlermeldungen
*) *)
PROCEDURE FehlerO; BEGIN
Gotoxy(10, 22);
WriteString('Keine Artikel zum Sortieren vorhanden!'); Write(CHR(7)); Read(c); Gotoxy(10, 22); WriteStringC ') END Fehler;
(* (* Hauptprogramm (.
*) *) *)
BEGIN MaxArtikel: = 0 ; TitelO; BildschirmO; END Lager.
Testergebnisse Programmbeispiel (7) Dieses Programm liest Artikelsätze mit maximal 100 Artikeln ein, sortiert diese wie gewünscht nach Artikelnummer, Artikelbezeichnung oder nach Artikelbestand und gibt diese dann wieder auf dem Bildschirm aus.
Anzeigen des Bildschirmmenüs: ARTIKELVERWALTUNG
Artikel einlesen
—= > L
Nach Artikelbezeichnungen — = > B Nach Artikelnummern
—= > N
Nach Artikelbestand
—= > D
Ausgeben der Artikel
—= > A
Ende des Programms
- = > E
Auswahl
— = >
L
Eingabe nach Auswahl „L": Geben Sie die Anzahl der einzulesenden Karten an (1..100)1 - = > 3
155
Testergebnisse Programmbeispiel (7)
ARTIKEL 1 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
— = > BIRNEN -
=
> 1
- = >0.1 - = >1000 — = > FEST — = > KISTE
ARTIKEL 2 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
- = >ÄPFEL - = >2 - = >0.2
— = >3000 — = > FEST — = > KARTON
ARTIKEL 3 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
- = > ERDBEEREN - = >3 - = >0.05 - = >2000 — = >FLUESSIG — = > KISTE
Ausgabe nach Auswahl „B",
Ausgabe nach Auswahl „D", „A":
ARTIKEL 2 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
-
= = = = = =
> ÄPFEL >2 >2.00000-001 >3000 >fest > karton
ARTIKEL 1 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
ARTIKEL 1 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
-
= = = = = =
> BIRNEN >1 > 1.00000-001 >1000 >fest >kiste
ARTIKEL 2 BEZEICHNUNG: - = > ERDBEEREN NUMMER: - = > 3 GEWICHT: - = >5.00000E-002 BESTANDSMENGE: - = >2000 BESCHAFFUNG: — = >fluessig VERPACKUNG: - = >kiste
ARTIKEL 3 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
-
= = = = = =
> ERDBEEREN >3 >5.00000E-002 >2000 >fluessig >kiste
ARTIKEL 3 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
-
-
= = = = = =
= = = = = =
> BIRNEN >1 > 1.00000E-001 >1000 >fest > kiste
> ÄPFEL >2 >2.00000E-001 >3000 >fest >karton
156
2. Sprachstandard
2.3.3 Systemdatentypen Alle maschinennahen und implementationsabhängigen Sprachelemente, also Sprachelemente, die auf einer Abstraktionsebene dicht an der verwendeten Maschine angesiedelt sind, werden in Modula-2 in einem Modul SYSTEM zusammengefaßt. Da dieser Modul weder zum residenten, noch zum nichtresidenten Sprachteil von Modula-2 gehört, wird er auch als „fiktiver Modul" bezeichnet. Die Verwendung implementationsabhängiger oder maschinenspezifischer Sprachelemente ist somit stets an ihren Import aus dem Modul SYSTEM gebunden. Der Modul SYSTEM umfaßt systemabhängige Datentypen und Prozeduren. Der Modul SYSTEM kennt zwei maschinennahe Datentypen: WORD und ADDRESS. Einige Modula-2-Implementierungen verfügen über einen weiteren Datentyp, den Datentyp BYTE, der aber keinesfalls zum Standard von Modula-2 gehört und somit hier nur erwähnt und nicht behandelt werden soll.
2.3.3.1 Systemdatentyp WORD Der Systemdatentyp WORD bezeichnet eine individuell adressierbare Speichereinheit mit implementationsabhängiger, fester Bitzahl. Für einen Modula-2-Compiler bilden Speichereinheiten vom Typ WORD die Grundeinheit, in der Daten und Code manipuliert werden können. Die meisten Compiler verwenden eine Länge von 16 Bits pro Wort. Variablen vom Typ WORD können ihre Werte über die Wertzuweisung erhalten. Sie sind zuweisungskompatibel mit allen Typen, die im Speicher die gleiche implementierungsabhängige Bitzahl benutzen. Bei einer Bitzahl von 16 und einer Wortlänge von 2 Byte sind die Standarddatentypen INTEGER, CARDINAL und BITSET zulässig. Weiterhin läßt sich der Systemdatentyp WORD als formaler Parameter in Prozeduren verwenden. Beim Aufruf solcher Prozeduren findet daher kein expliziter Typtransfer statt. Ein formaler Parameter vom Typ ARRAY OF WORD (offenes ARRAY) ist mit jedem aktuellen Parameter (also auch ARRAY- und RECORD-Typen) zuweisungskompatibel, der sich aus Wort-Größen zusammensetzen. Die interne Struktur der Daten, wie sie der Compiler anlegt, muß der Programmierer natürlich kennen, um Parameter vom Typ ARRAY OF WORD sinnvoll nutzen zu können.
Programmbeispiel (8)
157
Programmbeispiel (8) MODULE Keller;
*)
(* Dieses Programm verwaltet eine in der Informatik viel benutzte Datenstruktur den Keller (engl. Stack). Die Kellerverwaltung ist hier unter Verwendung des Systemdatentyps W O R D implementiert worden. WORD ist, wie im Text beschrieben, kompatibel mit allen 2-Byte-Datentypen, insbesondere mit den Standarddatentypen INTEGER und CARDINAL. Der Keller ist daher so implementiert, daß er einerseits als "INTEGER-Keller" verwendet werden kann und andererseits mit der gleichen Verwaltung als "CARDINAL-Keller" genutzt werden kann. Zum Verständnis dieses Beispiels ist die Kenntnis der Syntax der bedingten Anweisung (IFAnweisung), der Auswahlanweisung (CASE-Anweisung), der Wiederholungsanweisung (REPEAT-Anweisung und der PROCEDURE-Anweisung notwendig. In diesem Programmbeispiel wird der Grundbibliotheksmodul InOut, der Basismodul SYSTEM und der Bibliotheksmodul Screen (zur Ansteuerung des Bildschirms) verwendet. *) (* *) (*
*)
(* Import-Deklarationen
*)
*)
FROM InOut
IMPORT WriteString, WriteCard, Writelnt, Write, WriteLn, ReadCard, Readlnt, Read; FROM Screen IMPORT ClearScreen, Gotoxy; FROM SYSTEM IMPORT WORD;
f (* Konstantendeklarationen
*) *) -)
CONST MaxLaengeStack = 100; (*
*)
(* Typendeklarationen (*
*) *)
TYPE stackarray = ARRAY [1..MaxLaengeStack] OF WORD; (*
*)
(* Variablendeklarationen
*)
(*
VAR leer, voll laenge, Elementi selector Element2 space stack
: : : :
*)
BOOLEAN CARDINAL CARDINAL INTEGER; CHAR; stackarray;
158
2. Sprachstandard
(* (* Unterprogramm zum Einblenden von Programminformationen f
*) *) *)
PROCEDURE TitelO; BEGIN (* (** Löscht den Bildschirminhalt
*) *) *)
(
ClearScreenQ; WriteString('Dieses Programm verwaltet einen Keller (STACK). Es kann ein Keller für'); WriteLnO; WriteStringfnatürliche Zahlen (CARDINAL) oder ganze Zahlen (INTEGER) gewählt'); WriteString(' werden!'); WriteLnO; WriteString('Das Einfügen und Entnehmen der Kellerelemente geschieht interaktiv.'); WriteLnO; Read(space) END Titel;
(*
(* Unterprogramm zur Auswahl des gewünschten Kellertyps (*
*) *)
*)
PROCEDURE KellerwahlO; VAR wähl : CHAR; BEGIN REPEAT ClearScreenO; Gotoxy(15,12); WriteString('Keller für natürliche oder ganze Zahlen (N/G)? - = >'); Read(wahl); CASE wähl OF 'n','N' selector: = 1 ; | 'g'.'G' : selector: =2; ELSE Gotoxy(10,22); WriteString('Keine korrekte Kellerwahl!'); Write(CHR(7)); Read(space) END; UNTIL (selector =1) OR (selector=2) END Kellerwahl;
(* (* Unterprogramm zum Einlesen von ganzen oder natürl. Zahlen in die Datenstruktur Keller (* PROCEDURE ElementEinlesenO; BEGIN ClearScreenO; IF voll THEN Gotoxy(10,22); WriteString('Stack ist voll!'); Wrlte(CHR(7)); Read(space) ELSE Gotoxy(10,12); WriteString('Geben Sie die Zahl an, die in den Keller gelegt werden soll! - = >');
*) *) *>
Programmbeispiel (8)
159
CASE selector O F 1 : ReadCard(Elementl); Push(Elementl) | 2 : Readlnt(Element2); Push(Element2) ELSE Gotoxy(10,22); WriteString('Kein gültiger Kellerspeicher!'); Write(CHR(7)); Read(space) END END E N D ElementEinlesen;
(* Unterprogramm zur Ausgabe von ganzen oder natürl. Zahlen aus der Datenstruktur Keller (*
*) *) *)
PROCEDURE ElementEntnehmenO; BEGIN ClearScreenO; IF leer T H E N Gotoxy(10,22); WriteString('Stack Ist leer!'); Wrlte(CHR(7)); ELSE Gotoxy(10,12); WriteString('Die letzte Zahl des Kellers lautet - = > ' ) ; CASE selector OF 1 : Pop(Elementl); WriteCard(Element1,5); | 2 : Pop(Element2); Wrltelnt(Element2,5); ELSE Gotoxy(10,22); WriteString('Kein gültiger Kellerspeicher!'); Write(CHR(7)) END END; Read (space) END ElementEntnehmen; (*
(* Unterprogramm zum Aufbau des Bildschirmmenüs (*
PROCEDURE BildschirmO; VAR Wahl : CHAR; Abbruch : BOOLEAN; BEGIN REPEAT ClearScreen; Gotoxy(17,8);WriteString('VERWALTEN E I N E S K E L L E R S ' ) ; Gotoxy(18,11); WriteString('Element in den Kellerspeicher legen — = >L'); Gotoxy(18,13); WriteString('Element dem Kellerspeicher entnehmen - = > N ' ) ; Gotoxy(18,15); WriteString('Endedes Programms - = > E ' ) ; Gotoxy(18,17); WriteString('Auswahl - = > ' ) ; Abbruch: = FALSE;
*) *)
*)
2. Sprachstandard
160 Gotoxy(60,17); Read(Wahl); IF (Wahl = 'L') OR (Wahl = T) THEN ElementEinlesenO ELSIF (Wahl = 'N') OR (Wahl = 'n') THEN ElementEntnehmen 0 ELSIF (Wahl = 'E') OR (Wahl = 'e') THEN Abbruch: =TRUE ELSE Write(CHR(7)); Gotoxy(60,17); Write(''); END; UNTIL Abbruch; END Bildschirm; (* (* Unterprogramm zum Abspeichern von Elementen in den Keller (* PROCEDURE Push(x : WORD); BEGIN IF NOT voll THEN INC(laenge); stack[laenge]: =x; leer: = FALSE; voll: = laenge = MaxLaengeStack END END Push;
*) *) *)
(* (* Unterprogramm zum Entnehmen von Elementen in den Keller (* PROCEDURE Pop(VAR x : WORD); BEGIN IF NOT leer THEN x: = stack[laenge]; DEC(laenge); voll: = FALSE; leer: = laenge=0 END END Pop;
*) *) *)
(* (* Hauptprogramm (*
*) *) *)
BEGIN laenge: =0; leer: = TRUE; voll: = FALSE; TitelO;
KellerwahIO; BildschirmO END Keller.
161
Testergebnisse Programmbeispiel (8)
Testergebnisse Programmbeispiel (8) Dieses Programm verwaltet einen Keller (STACK). Es kann ein Keller für natürliche Zahlen (CARDINAL) oder ganze Zahlen (INTEGER) gewählt werden. Das Einfügen und Entnehmen der Kellerelemente geschieht interaktiv.
Auswahl der Kellertyps: Keller für natürliche oder ganze Zahlen (N/G)? — = > N Anzeigen des Bildschirmmenüs: VERWALTEN EINES
KELLERS
Element in den Kellerspeicher legen
—= >L
Element dem Kellerspeicher entnehmen — = > N Ende des Programms
—=>E
Auswahl
- = >L
Eingabe eines Kellerelementes: Geben Sie die Zahl an, die in den Keller gelegt werden soll! — = > 10 Nach Auswahl im Menü „L" erneute Eingabe eines Kellerelementes: Geben Sie die Zahl an, die in den Keller gelegt werden soll! - = > 20 Nach Auswahl im Menü „N" für Ausgabe des letzten Kellerelementes: Die letzte Zahl des Kellers lautet - = >20
2.3.3.2 Systemdatentyp ADDRESS Die Werte des Systemdatentyps ADDRESS bezeichnen Adressen von SpeicherWorten. In der Sprachdefinition von Wirth [WI] ist dieser Typ als ADDRESS = POINTER TO WORD definiert. Der Systemdatentyp ADDRESS ist
2. Sprachstandard
162
mit den Typen POINTER und CARDINAL zuweisungskompatibel. Es können also die in den Tabellen 2.91 und 2.92 angegebenen Zeigeroperationen und Rechenoperationen verwendet werden.
Tab. 2.91: Operationen auf Objekte vom Typ ADDRESS. Addition ( + ), Subtraktion (-) bzw. Vorzeichenumkehr, Multiplikation (*), Division (DIV) und Divisionsrest (MOD) sind als Rechenoperationen zulässig.
Tab. 2.92: Relationen auf Objekte vom Typ ADDRESS. Die Relationen =, < > , # , < , < = , > und > = sind auf Werte oder Variablen vom Typ ADDRESS zulässig.
2.3.4 Übersicht über Datentypen und Operationen Bis auf den Datentyp POINTER wurden in den vorangehenden Kapiteln sämtliche Datentypen angesprochen, deren Behandlung hier vorgesehen ist. Aus bereits genannten Gründen wird auf den POINTER-Typ erst in Kapitel 2.7 eingegangen. Die Darstellung der einzelnen Datentypen umschloß auch die Benennung und Kommentierung der auf sie definierten Operationen. Betrachtet man die Gesamtmenge der Operationen, so stellt man fest, daß - keine der Operationen für den Datentyp ARRAY zugelassen ist, -
nur die Wertzuweisung auf die restlichen Datentypen definiert ist und
-
die übrigen Operationen jeweils nur auf einen Teil der restlichen Datentypen
definiert sind. Einen Überblick über die Operationen, die auf die einzelnen Datentypen definiert sind, gibt die Matrix in Tabelle 2.93. In dieser Darstellung werden die Operationen durch die entsprechenden Operator-Symbole repräsentiert.
2.3.4 Übersicht über Datentypen und Operationen
163
Tab. 2.93: Datentypen und anwendbare Operatoren. \Operatoren WZ
:= + Typen
VERGLEICH
ARITHMETIK
*
/ DIV MOD
=
#
IN NOT AND OR
\
INTEGER
X
X
X
X
X
X
X
X
X
X
X
X
CARDINAL
X
X
X
X
X
X
X
X
X
X
X
X
REAL
X
X
X
X
X
X
X
X
X
X
LONGINT
X
X
X
X
X
X
X
X
X
X
CHAR
X
X
X
X
X
X
X
BOOLEAN
X
X
X
X
X
X
X
Enumeration
X
X
X
X
X
X
X
Subrange
X
0
0
0
X
X
X
X
X
X
SET
X
X
X
X
X
X
X
X
X
X
BITSET
X
X
X
X
X
X
X
X
X
X
RECORD
X
PROCEDURE
X
PROC
X
WORD
X
ADDRESS
X
X
X
X
X
X
X
POINTER
X
X
X
X X
0
X
0
X
ARRAY
X
X
X
X
X
Legende: WZ
—
Wertzuweisung
x
—
zulässige Kombination von Operator und Datentyp
o
-
auf CARDINAL und INTEGER eingeschränkt
X
X
164
2. Sprachstandard
2.4 Ein- und Ausgabe In Zusammenhang mit der Behandlung von Datentypen wurden bislang nur Operationen betrachtet, die der rechnerinternen Verarbeitung von Datenobjekten dienen. Die mit einem Programm zu verarbeitenden Daten müssen jedoch, wie auch das Programm selbst, erst in den Rechner eingegeben werden. Umgekehrt sind die Ergebnisse der Verarbeitung für den Benutzer erst nach ihrer Ausgabe verfügbar. In Modula-2 unterliegt auch die Organisation der Ein- und Ausgabe von Daten dem Modulkonzept. Auf bestimmte externe Geräte bezogene Ein- und Ausgabeoperationen werden in sogenannten Basis-Modulen als Prozeduren bereitgestellt. Welche dieser Prozeduren ein Programm importiert, hängt von der Art der im Programm durchzuführenden Ein- und Ausgaben ab. Zur konkreten Durchführung der Ein- und Ausgaben sind an den jeweiligen Programmstellen geeignete Prozeduraufrufe vorzusehen. In diesem Kapitel werden ausschließlich die Module und Prozeduren behandelt, die die Nutzung der üblichen externen Gerätekomponenten von Personal-Computern ermöglichen. Zu diesen Komponenten gehören Tastatur, Bildschirm, Drucker und externer Speicher (Disketten- oder Plattenlaufwerk). Ein- und Ausgaben mit diesen Geräten werden in Modula-2 durch drei Basis-Module unterstützt, die Bibliotheks-Module Terminal, InOut und ReallnOut. Die Module dienen der Ein- und Ausgabe mit folgenden Geräten: -
Terminal: Eingabe von Zeichen mit der Tastatur und Ausgabe von Zeichen(ketten) auf dem Bildschirm.
-
InOut: Eingabe von Zeichen(ketten) und ganzen Zahlen mit der Tastatur und Ausgabe von Zeichen(ketten) und ganzen Zahlen auf dem Bildschirm. Nach Umschaltung sind die gleichen Operationen auf externe Dateien(z.B. auf den Plattenspeicher) anwendbar.
-
ReallnOut: Eingabe von reellen Zahlen mit der Tastatur und Ausgabe von reellen Zahlen auf dem Bildschirm. Nach Umschaltung sind die gleichen Operationen auf externe Dateien anwendbar.
Welche Prozeduren im einzelnen zur Verfügung stehen, ist den folgenden Kapiteln zu entnehmen.
2.4.1 Bibliotheks-Modul Terminal
165
2.4.1 Bibliotheks-Modul Terminal Dieser Modul Terminal enthält je drei Prozeduren zum Lesen von Zeichen von der Tastatur und zum Schreiben von Zeichen auf den Bildschirm. Das Lesen erfolgt ausschließlich auf Zeichenebene. Es können also nur Datenobjekte vom Typ CHAR eingelesen werden. Verfügbar sind die Prozeduren: -
Read:
Lesen eines Zeichens,
-
BusyRead:
Lesen mit Tastaturabfrage,
- ReadAgain: wiederholtes Lesen. Allen drei Prozeduren ist gemeinsam, daß ein gelesenes Zeichen nicht auf dem Bildschirm angezeigt wird. Die Prozeduren erfüllen die in der Tabelle 2.94 beschriebenen Zwecke.
Tab. 2.94: Lese-Prozeduren des Basis-Moduls Terminal. Read(VAR ch: CHAR): Die Prozedur Read weist das (erste) Zeichen, das nach ihrem Aufruf durch Betätigen einer Taste ausgewählt wird, dem Variablenparameter ch zu. BusyRead(VAR ch: CHAR): Die Prozedur BusyRead weist das Zeichen, das nach dem letzten Aufruf von Read oder BusyRead per Tastendruck ausgewählt wurde dem Variablenparameter ch zu. Wurde jedoch kein Zeichen per Tastendruck eingegeben, so gibt BusyRead den Wert OC (Nullzeichen) zurück. ReadAgain: Die parameterlose Prozedur ReadAgain hat zur Folge, daß das zuletzt gelesene Zeichen beim nächsten Aufruf von Read oder BusyRead erneut zurückgegeben wird.
Das Schreiben kann sich auf Zeichen und Zeichenketten, also auf Datenobjekte der Typen CHAR und ARRAY OF CHAR, beziehen. Zur Verfügung stehen die Prozeduren: -
Write:
Schreiben eines Zeichens,
-
WriteString:
Schreiben einer Zeichenkette,
-
WriteLn:
Beenden einer Schreibzeile.
Die Zwecke dieser Prozeduren werden in der Tabelle 2.95 beschrieben.
2. Sprachstandard
166
Tab. 2.95: Schreib-Prozeduren des Basis-Moduls Terminal. Write(ch: CHAR): Die Prozedur Write gibt das Zeichen ch auf dem Bildschirm aus. WriteString(s: ARRAY OF CHAR): Die Prozedur WriteString gibt die Zeichenkette s auf dem Bildschirm aus, falls s kein Nullzeichen (OC) enthält. Andernfalls wird nur der vor dem ersten Auftreten des Zeichens OC gelegene Teil der Zeichenkette ausgegeben (OC bewirkt also das Ende der Ausgabe). WriteLn: Die parameterlose Prozedur WriteLn schließt die aktuelle Bildschirmzeile ab und setzt die Schreibmarke (Cursor) auf die erste Position der nächsten Bildschirmzeile.
2.4.2 Bibliotheks-Modul InOut Der Modul InOut erweitert den Funktionsumfang des Moduls Terminal in zweifacher Weise. Einerseits enthält InOut Prozeduren zur Ein- und Ausgabe von Zeichen, Zeichenketten und ganzen Zahlen. Andererseits können sich Ein- und Ausgaben nicht nur auf Tastatur und Bildschirm, sondern auch auf extern gespeicherte Dateien (z.B. Plattendateien) beziehen. Zur Anwendung kommen jeweils die gleichen Prozeduren. Ob die Prozeduren Tastatur/Bildschirm oder externe Dateien betreffen, hängt davon ab, ob externe Dateien geöffnet sind. Sind keine externen Dateien geöffnet, so dienen die Schreibprozeduren von InOut zur Bildschirmausgabe und die Leseprozeduren von InOut zur Tastatureingabe. Andernfalls werden Daten auf externe Dateien geschrieben bzw. von externen Dateien gelesen. Beim Arbeiten mit externen Dateien ist zwischen Eingabe- und Ausgabedateien zu unterscheiden. Der Modul InOut stellt daher die folgenden vier Prozeduren zum Öffnen und Schließen externer Dateien zur Verfügung: -
Openlnput:
Öffnen einer Eingabedatei (Lesen von Daten),
-
OpenOutput:
Öffnen einer Ausgabedatei (Schreiben von Daten),
-
Closelnput:
Schließen einer Eingabedatei,
-
CloseOutput:
Schließen einer Ausgabedatei.
2.4.2 Bibliotheks-Modul InOut
167
Tab. 2.96: Prozeduren des Basis-Moduls InOut zum Öffnen und Schließen von Dateien. OpenInput(defext:ARRAY OF CHAR): Die Prozedur verlangt die Eingabe eines Dateinamens und öffnet die angegebene Datei als Eingabedatei. Falls der angegebene Dateiname mit einem Punkt endet, wird die Erweiterung defext angehängt. Mit der exportierten Variablen Done wird die korrekte Ausführung des Öffnens angezeigt. Bei korrektem Öffnen wird Done auf TRUE gesetzt (andernfalls auf FALSE) und die Eingabe auf Dateieingabe umgeschaltet, d.h. alle folgenden Aufrufe von Leseprozeduren beziehen sich auf die geöffnete Datei. OpenOutput(defext:ARRAY OF CHAR): Die Prozedur verlangt die Eingabe eines Dateinamens und öffnet die angegebene Datei als Ausgabedatei. Falls der angegebene Dateiname mit einem Punkt endet, wird die Erweiterung defext angehängt. Mit der exportierten Variablen Done wird die korrekte Ausführung des Öffnens angezeigt. Bei korrektem Öffnen wird Done auf TRUE gesetzt (andernfalls auf FALSE) und die Ausgabe auf Dateiausgabe umgeschaltet, d.h. alle folgenden Aufrufe von Schreibprozeduren beziehen sich auf die geöffnete Datei. Closelnput: Diese parameterlose Prozedur schließt die aktuell geöffnete Eingabedatei und schaltet die Eingabe auf Tastatureingabe um. Alle folgenden Aufrufe von Leseprozeduren beziehen sich folglich auf die Tastatur. CloseOutput: Diese parameterlose Prozedur schließt die aktuell geöffnete Ausgabedatei und schaltet die Ausgabe auf den Bildschirm um. Alle folgenden Aufrufe von Schreibprozeduren beziehen sich folglich auf den Bildschirm.
Zum Lesen von Daten exportiert der Modul InOut folgende Prozeduren: - Read: Lesen eines Zeichens, -
ReadString:
Lesen einer Zeichenkette,
-
Readlnt:
Lesen einer INTEGER-Zahl,
-
ReadCard:
Lesen einer CARDINAL-Zahl.
Nähere Angaben zu diesen Prozeduren enthält die Tabelle 2.97.
2. Sprachstandard
168
Tab. 2.97: Lese-Prozeduren des Basis-Moduls InOut. Read(VAR ch : CHAR): Die Prozedur Read weist das von der Tastatur oder einer Eingabedatei gelesene Zeichen dem Variablenparameter ch zu. Erfolgreiches Lesen wird mit dem Wert TRUE der exportierten Variablen Done angezeigt. Konnte im Fall einer Eingabedatei wegen Erreichen des Dateiendes kein Zeichen gelesen werden, so erhält die Variable Done den Wert FALSE. ReadString(VAR s : ARRAY OF CHAR): Die Prozedur ReadString weist die von der Tastatur oder einer Eingabedatei gelesene Zeichenfolge dem Variablenparameter s zu. Führende Leerzeichen werden nicht beachtet und der Lesevorgang endet, sobald ein Leer- oder Steuerzeichen auftritt. Der exportierten Variablen termCH wird das Zeichen zugewiesen, das zum Beenden des Lesens geführt hat. ReadInt(VAR x: INTEGER): Die Prozedur Readlnt liest eine Zeichenfolge von der Tastatur oder einer Eingabedatei ein, wandelt die Zeichenfolge in eine INTEGER-Zahl (mit Vorzeichen) um und weist die Zahl dem Variablenparameter x zu. Die exportierte Variable Done zeigt mit dem Wert TRUE an, daß eine INTEGER-Zahl gelesen wurde. Andernfalls erhält Done den Wert FALSE. ReadCard(VAR x : CARDINAL): Die Prozedur ReadCard liest eine Zeichenfolge von der Tastatur oder einer Eingabedatei ein, wandelt die Zeichenfolge in eine CARDINAL-Zahl um und weist die Zahl dem Variablenparameter x zu. Die exportierte Variable Done zeigt mit dem Wert TRUE an, daß eine CARDINAL-Zahl gelesen wurde. Andernfalls erhält Done den Wert FALSE.
Zum Schreiben von Daten stellt der Modul InOut folgende Prozeduren zur Verfügung (Beschreibungen dieser Prozeduren befinden sich in der Tabelle 2.98): Write: Schreiben eines Zeichens, WriteString:
Schreiben einer Zeichenkette,
Writelnt:
Schreiben einer dezimalen INTEGER-Zahl,
WriteCard:
Schreiben einer dezimalen CARDINAL-Zahl,
WriteOct:
Schreiben einer oktalen CARDINAL-Zahl,
WriteHex:
Schreiben einer hexadezimalen CARDINAL-Zahl,
WriteLn:
Beenden einer Schreibzeile.
2.4.2 Bibliotheks-Modul InOut
169
Tab. 2.98: Schreib-Prozeduren des Basis-Moduls InOut. Write(ch: CHAR): Die Prozedur Write gibt das mit dem Parameter ch übergebene Zeichen auf dem Bildschirm aus oder schreibt das Zeichen in eine Ausgabedatei. WriteString(s : ARRAY O F CHAR): Die Prozedur WriteString gibt die mit dem Parameter s übergebene Zeichenkette auf dem Bildschirm aus oder schreibt die Zeichenkette in eine Ausgabedatei. Der Schreibvorgang wird durch das erste in der Zeichenkette auftretende Nullzeichen OC beendet. Writelnt(x: INTEGER; n : CARDINAL): Die dezimal dargestellte INTEGERZahl x wird mit mindestens n-stelliger Länge auf dem Bildschirm ausgegeben oder in eine Ausgabedatei geschrieben. Je nach Stellenzahl von x umfaßt die Ausgabe führende Leerzeichen (für x < n), oder es werden mehr als n Stellen ausgegeben (für x > n). WriteCard(x, n : CARDINAL): Die dezimal dargestellte CARDINAL- Zahl x wird mit mindestens n-stelliger Länge auf dem Bildschirm ausgegeben oder in eine Ausgabedatei geschrieben. Je nach Stellenanzahl von x umfaßt die Ausgabe führende Leerzeichen (für x < n), oder es werden mehr als n Stellen ausgegeben (für x > n). WriteOct(x, n : CARDINAL): Die oktal dargestellte CARDINAL-Zahl * wird mit mindestens n-stelliger Länge auf dem Bildschirm ausgegeben oder in eine Ausgabedatei geschrieben. Je nach Stellenanzahl von x umfaßt die Ausgabe führende Leerzeichen (für x < n), oder es werden mehr als n Stellen ausgegeben (für x > n). WriteHex(x, n : CARDINAL): Die hexadezimal dargestellte CARDINAL-Zahl x wird mit mindestens n-stelliger Länge auf dem Bildschirm ausgegeben oder in eine Ausgabedatei geschrieben. Je nach Stellenanzahl von x umfaßt die Ausgabe führende Leerzeichen (für x < n), oder es werden mehr als n Stellen ausgegeben (für x > n). WriteLn: Die parameterlose Prozedur WriteLn schließt eine Bildschirmzeile durch Ausgabe des Endezeichens EOL ab oder schreibt dieses Zeichen in eine Ausgabedatei. Bei Bildschirmausgabe wird außerdem die Schreibmarke (Cursor) auf die erste Position der nächsten Bildschirmzeile gesetzt.
2. Sprachstaadard
170
2.4.3 Bibliotheks-Modul ReallnOut Der Modul InOut enthält keine Prozeduren zur Ein- und Ausgabe von reellen Zahlen. Dieses Manko behebt der Modul ReallnOut, der zwei Prozeduren zur Einund Ausgabe von REAL-Zahlen exportiert: - ReadReal: Lesen einer REAL-Zahl, -
WriteReal:
Schreiben einer REAL-Zahl.
Ebenso wie bei den Prozeduren des Moduls InOut kann sich das Lesen und Schreiben auf Tastatur und Bildschirm oder auf Ein- und Ausgabedateien beziehen. Das Umschalten auf das jeweilige Ein- und Ausgabemedium erfolgt mittels der bereits behandelten Prozeduren zum Öffnen und Schließen von externen Dateien (siehe Tabelle 2.96). Nähere Angaben zu den Prozeduren ReadReal und WriteReal enthält die Tabelle 2.99. Tab. 2.99: Lese- und Schreibprozeduren des Basis-Moduls ReallnOut. ReadReal(VAR x : REAL): Die Prozedur ReadReal liest eine Zeichenfolge von der Tastatur oder einer Eingabedatei ein, wandelt die Zeichenfolge in eine REAL-Zahl (Gleitkommazahl) um und weist die Zahl dem Variablenparameter x zu. Die exportierte Variable Done zeigt mit dem Wert T R U E an, daß eine REAL-Zahl gelesen wurde. Andernfalls erhält Done den Wert FALSE. WriteReal(x : REAL; n : CARDINAL): Die Gleitkommazahlx, bestehend aus Mantisse und Exponent, wird mit mindestens n-stelliger Länge auf dem Bildschirm ausgegeben oder in eine Ausgabedatei geschrieben. Je nach Stellenanzahl von x umfaßt die Ausgabe führende Leerzeichen (für x < n), oder es werden mehr als n Stellen ausgegeben (für x > n).
Programmbeispiel (9) MODULE Tabelle; (* (* Dieses Programm gibt eine Tabelle auf dem Bildschirm aus, In der einander entsprechende Temperaturwerte in Grad Fahrenheit und Grad Celsius gegenübergestellt werden. Es kann interaktiv eine Standardtabelle ausgewählt und ausgegeben werden (Schrittweite 9 Grad Fahrenheit und 5 Grad Celsius) oder eine Individualtabelle, bei der der Startwert entsprechend vorzugeben ist. Zum Verständnis dieses Beispiels ist die Kenntnis der Syntax der bedingten Anweisung (IFAnwelsung), der Wiederholungsanweisung (REPEAT- und FOR-Anweisung) und der PROCEDURE-Anweisung notwendig.
171
Programmbeispiel (9)
In diesem Programmbeispiel werden die Grundbibliotheks-Module InOut, MathLibO und RealConversions sowie der Bibliotheksmodul Screen (zur Ansteuerung des Bildschirms) verwendet. *) C *)
(* Import-Deklarationen C FROM InOut FROM MathLibO FROM RealConversions FROM Screen (*
*) *) *) IMPORT IMPORT IMPORT IMPORT
WriteString, Write, Writelnt, WriteLn, Read, Readlnt; real; RealToString; ClearScreen, Gotoxy;
(.
*)*)
(* Variablendeklarationen VAR space :
*>
CHAR;
(*
*)
(* Unterprogramm zum Einblenden von Programminformationen
*) *)
PROCEDURE TitelO; BEGIN (* Löscht den Bildschirminhalt *) ClearScreenQ; WriteStringC Dieses Programm rechnet in Fahrenheit gegebene Temperaturwerte'); WriteStringC in Grad Celsius um.'); WriteLnO; WriteStringC Es kann eine Standardtabelle mit Startwert-40 Grad Fahrenheit ausgegeben'); WriteLnO; WriteStringC oder ein anderer Startwert gewählt werden.'); WriteLnO; Read (space) END Titel;
(*
*)
(* Unterprogramm zum Einblenden des Kopfes der Tabelle p PROCEDURE KopfO; BEGIN Gotoxy(30, 3); WriteStringC + + + '); Gotoxy(30, 4); WriteStringC FAHRENHEIT | CELSIUS '); Gotoxy(30, 5) ; WriteStringC+ + + ') END Kopf; (* (* Unterprogramm zum Einblenden der Abschlußzeile der Tabelle
*) «)
PROCEDURE EndeO; BEGIN Gotoxy(30, 23) ; WriteStringC + END Ende;
+
+ ')
*) *) «)
172
2. Sprachstandard
(*
*)
(** Unterprogramm zur Berechnung und Ausgabe einer Standard-Tabelle (
*) *)
PROCEDURE StandardberechnungO; VAR Fahrenheit, Celsius : INTEGER; i : CARDINAL; PROCEDURE BerechnungO; BEGIN Celsius: = (Fahrenheit-32) * 5 DIV 9 END Berechnung; PROCEDURE AusgabeO; BEGIN Gotoxy(30, 5 + i); WriteStringC | '); Writelnt(Fahrenheit, 3); WriteStrlngC | '); Writelnt(Celsius, 3); WriteStringC |") END Ausgabe; BEGIN ClearScreenO; KopfO; Fahrenheit : = -49; FOR i : = 1 TO 17 DO INC(Fahrenheit, 9); BerechnungO; AusgabeO END; EndeO; Read(space) END Standardberechnung; (*
*)
(* Unterprogramm zur Berechnung und Ausgabe einer Individual-Tabelle
(*
PROCEDURE IndividualberechnungO; VAR Fahrenheit : INTEGER; Celsius REAL; i : CARDINAL; PROCEDURE BerechnungO; BEGIN Celsius : = real (Fahrenheit-32) * 5.0 / 9.0 END Berechnung; PROCEDURE AusgabeO; VAR ok : BOOLEAN; s : ARRAY [0..8] OF CHAR; BEGIN RealToString(Celsius, 1, 8, s, ok); Gotoxy(30, 5 + i); WriteStringC | '); Writelnt(Fahrenheit, 6); WriteStringC |'); WriteString(s); WriteStringC |') END Ausgabe; BEGIN ClearScreenO; Gotoxy(25,12); WriteString('Startwert für Fahrenheit angebenl — = >'); Readlnt(Fahrenheit); ClearScreenO; KopfO; FOR i: = 1 TO 17 DO BerechnungO; AusgabeO; INC(Fahrenheit)
*) .)
Programmbeispiel (9)
(*
END; EndeO; Read(space) END Individualberechnung;
(* Unterprogramm zum Aufbau des Bildschirmmenüs
173
*) *)
PROCEDURE BildschirmO; VAR Wahl : CHAR; Abbruch : BOOLEAN; BEGIN REPEAT ClearScreen; Gotoxy(15,7); WriteStringfA U S G A B E E I N E R T A B E L L E ZUR'); Gotoxy(10, 9); WriteString('U M R E C H N U N G F A H R E N H E I T - C E L S I U S ' ) ; Gotoxy(8,12); WriteString('Standard (-40 .. +104 Fahrenheit),'); Gotoxy(8,14); WriteStrlngf Schrittwerte 9 Grad - = > S'); Gotoxy(8,16); WriteString('Auswahl (Schrittweite 1 Grad, aufwärts) - = > W'); Gotoxy(8,18); WriteString('Ende des Programms - = > E'); Gotoxy(8, 20); WriteString('Auswahl — = >'); Abbruch: = FALSE; Gotoxy(72,20); Read(Wahl); IF (Wahl = 'S') OR (Wahl = 's') THEN StandardberechnungO ELSIF (Wahl = 'W') OR (Wahl = 'w') THEN Individ ualberechnungO ELSIF (Wahl = 'E') OR (Wahl = 'e') THEN Abbruch: =TRUE ELSE Write(CHR(7)); Gotoxy(72,20); Write('') END UNTIL Abbruch; END Bildschirm; (*
(* Hauptprogramm (* BEGIN TitelO; BildschirmO END Tabelle.
*)
*) *)
2. Sprachstandard
174
Testergebnisse Programmbeispiel (9) Dieses Programm rechnet in Grad Fahrenheit gegebene Temperaturwerte in Grad Celsius um. Es kann eine Standardtabelle mit Startwert -40 Grad Fahrenheit oder ein anderer Startwert gewählt werden.
Anzeigen des Bildschirmmenüs: A U S G A B E EINER TABELLE ZUR U M R E C H N U N G F A H R E N H E IT - C E L S I U S
Standard (-40 .. +104 Fahrenheit), Schrittweite 9 Grad
- = > S
Auswahl (Schrittweite 1 Grad, aufwärts)
—= > W
Ende des Programms
—= > E
Auswahl
- = > S
Anzeigen der Standardtabelle: FAHRENHEIT
CELSIUS
-40 -31 -22 -13 -4 5 14 23 32 41 50 59
-40 -35 -30 -25 -20 -15 -10 -5 0 5 10 15
104
40
2.5 Anweisungen
175
2.5 Anweisungen In den vorhergehenden Kapiteln wurde gezeigt, auf welche Weise Datenobjekte verschiedener Typen definiert, in einen Rechner eingelesen und ausgegeben werden können. Auf die rechnerinterne Verarbeitung von Datenobjekten geht das vorliegende Kapitel ein. Bereits in Kapitel 2.2 wurden Anweisungen als die verarbeitenden Bestandteile von Programmen charakterisiert. Dort wurden auch die Anweisungsarten benannt, die dem Modula-2-Programinierer zur Verfügung stehen. Die Anweisungsarten lassen sich wie folgt gliedern: - Wertzuweisung. -
Verzweigungen: IF-Anweisung und CASE-Anweisung.
-
Schleifen: WHILE-Anweisung, REPEAT-Anweisung, FOR-Anweisung und LOOP-Anweisung.
-
Schleifen-Beendigung: EXIT-Anweisung.
-
RECORD-Verarbeitung: WTTH-Anweisung.
-
Ausführung beenden: RETURN-Anweisung.
-
Prozeduraufruf.
-
Leeranweisung.
Die folgenden Kapitel behandeln diese Anweisungen in der Reihenfolge ihrer Nennung. Dies gilt nicht für die Leeranweisung, zu der an dieser Stelle nur folgendes bemerkt sei: Schließen zwei Semikola keine weiteren Zeichen ein, so liegt eine Leeranweisung vor. Eine Leeranweisung darf jeder anderen Anweisung folgen. Sie kann beispielsweise dazu verwendet werden, eine andere Anweisung mit einem Semikolon abzuschließen.
2.5.1 Wertzuweisungen In Programmen deklarierte Variable besitzen zunächst keine Werte. Werte müssen ihnen erst explizit zugeordnet werden. Dies kann in direkter oder indirekter Form geschehen. Die indirekte Form ist beim Prozeduraufruf gegeben. In diesem Fall wird einer Variablen der Wert eines Variablenparameters übergeben. Auf die Wertübergabe mittels Variablenparameter wird in Kapitel 2.5.6 näher eingegangen.
2. Sprachstandard
176
Gegenstand des vorliegenden Kapitels ist die direkte Form der Wertzuweisung (engl, assignment Statement). Bei der Wertzuweisung wird einer Variablen der Wert eines Ausdrucks zugewiesen. Der Ausdruck kann insbesondere auch eine Konstante oder eine andere Variable darstellen. Für die Wertzuweisung gilt das in Abbildung 2.57 dargestellte Syntaxdiagramm. Das Diagramm wird in der Tabelle 2.100 erläutert.
Assignment Designator——
Expression
Abb. 2.57: Syntaxdiagramm für „Assignment" (Zuweisung).
Tab. 2.100: Beschreibung des Syntaxdiagramms für „Assignment". (1) Eine Wertzuweisung beginnt mit einem Designator, dem das Zuweisungssymbol „: = ", ein Ausdruck und ein Strichpunkt folgen. (2) Der Designator muß stets eine Variable sein. Bezeichner, die Prozeduren oder Module qualifizieren, sind nicht zulässig. (3) Der Datentyp des Ausdrucks auf der rechten Seite darf beliebig sein. Allerdings muß er mit dem Typ des Designators verträglich sein.
Beispiel MODULE AssignOemo; VAR Wertl, Wert3 : CARDINAL; Wert2 : REAL; zchnl : CHAR; ok : BOOLEAN;
2.5.2 Verzweigungen
177
BEGIN
W e r t l : = 1; Wert2:=2.; Wert3: = W e r t l ;
zchnl
ok
:=
:=
'A';
TRUE;
E N D AssignDemo.
2.5.2 Verzweigungen Datenverarbeitungsprozesse beinhalten sehr häufig Verarbeitungsabschnitte, die eine alternative Behandlung von sonst gleich behandelten Datenobjekten vorsehen. Maßgeblich dafür sind in der Regel bestimmte Objektmerkmale. Dies sei an zwei Beispielen verdeutlicht: Repräsentiert ein Datenobjekt einen Stammkunden, so wird ihm bei der Abrechnung gelieferter W a r e n ein höherer Rabatt gewährt, als einem Laufkunden. Überschreiten die von einem Versicherungsvertreter in einem Monat getätigten Vertragsabschlüsse einen gegebene Umsatzgrenze, so wird ihm eine Zusatzprämie gewährt und andernfalls nicht.
Diese beiden Beispiele vereinfachen reale Gegebenheiten. In der Realität stellen sich solche Verzweigungssituationen meist nicht nur als einstufige „Ja/Nein-Entscheidungen", sondern als vielstufige Verzweigungen dar. Abrechnungsvorschriften, Prämiensysteme usw. weisen in der Praxis häufig mehr als zwei Berechnungsvarianten auf. Der Berücksichtung von Verarbeitungsvarianten im Programmablauf dienen Verzweigungen. Verzweigungen sind Anweisungen, die die weitere Verarbeitung von Datenobjekten von bestimmten Bedingungen abhängig machen. Man nennt sie daher auch bedingte Anweisungen. Die Ausführung einer bedingten Anweisung erfolgt in zwei Schritten. Der erste Schritt besteht in der Auswertung der Bedingung. Abhängig vom Ergebnis der Auswertung wird dann im zweiten Schritt die Anweisungsfolge eingeleitet, die für das Auswertungsergebnis vorgesehen ist.
2. Sprachstandard
178
Zur Programmierung von Verarbeitungsvarianten stehen in Modula-2 zwei Arten von bedingten Anweisungen zur Verfügung, die IF-Anweisung und die CASE-Anweisung. In den beiden folgenden Kapiteln wird zuerst die IF-Anweisung und danach die CASE-Anweisung behandelt.
2.5.2.1 IF-Anweisung Die IF-Anweisung erlaubt die Formulierung von Verzweigungen in Abhängigkeit von einer oder mehreren logischen Bedingungen. Enthält sie nur eine Bedingung, so wird in zwei Anweisungsfolgen verzweigt. Andernfalls liegt eine mehrfach gestufte Anweisungsstruktur vor. Für die IF-Anweisung gilt das in Abbildung 2.58 dargestellte Syntaxdiagramm. Das Diagramm wird in der Tabelle 2.101 erläutert.
IF-Statement IF j - Expressior —( THEÜ)- StatementSequence
Expression -(THEff)-
.
StatementSequence ^
StatementSequence -
*(ENDy Abb. 2.58: Syntaxdiagramm für das „IF-Statement" (IF-Anweisung). Tab. 2.101: Beschreibung des Syntaxdiagramms „IF-Statement". (1) Eine IF-Anweisung beginnt mit dem Schlüsselwort IF, dem ein logischer Ausdruck, das Schlüsselwort THEN und ein mit dem Schlüsselwort END abgeschlossenes Anweisungskonstrukt folgen. (2) Das Anweisungskonstrukt besteht aus einer Anweisungsfolge, der entweder — keine weitere Anweisung folgt (einseitige Auswahl), oder — eine mit dem Schlüsselwort ELSE eingeleitete Anweisungsfolge folgt (zweiseitige Auswahl) oder — ein mit dem Schlüsselwort ELSIF eingeleitetes, weiteres Anweisungskonstrukt folgt (mehrstufige Verzweigung).
Programmbeispiel (10)
179
(3) Bei der einseitigen Auswahl wird die auf THEN folgende Anweisungsfolge, auch THEN-Zweig genannt, ausgeführt, falls der logische Ausdruck erfüllt ist. Andernfalls erfolgt keine Aktion. (4) Ist bei der zweiseitigen Auswahl der logische Ausdruck erfüllt, so wird der THEN-Zweig ausgeführt und andernfalls der ELSE-Zweig, d.h. die auf ELSE folgende Anweisungsfolge. (5) Im Fall der mehrstufigen Verzweigung kann dem THEN-Zweig beliebig oft ein ELSIF-THEN-Konstrukt folgen. Jedes dieser Konstrukte besteht aus dem Schlüsselwort ELSE, dem ein logischer Ausdruck und eine mit THEN eingeleitete Anweisungsfolge folgen. Nur dem letzten dieser Konstrukte kann der ELSE-Zweig folgen. (6) Anweisungsfolgen dürfen beliebige Anweisungen enthalten und beliebig lang sein. Die in einer IF-Anweisung enthaltenen logischen Ausdrücke werden in der Reihenfolge ihres Auftretens, also von links nach rechts in der Pfeilrichtung des Syntaxdiagramms ausgewertet. Für die Verzweigung ist der erste logische Ausdruck, dessen Auswertung den Wert TRUE ergibt, maßgeblich. Es wird nämlich die diesem Ausdruck, unter Zwischenschaltung des Schlüsselwortes THEN, unmittelbar nachgelagerte Anweisungsfolge ausgeführt. Nach Ausführung dieser Anweisungsfolge ist die IF-Anweisung beendet. Ergibt die Auswertung der logischen Ausdrücke in keinem Fall den Wert TRUE, so wird, falls vorhanden, der ELSE-Zweig ausgeführt. Nach Ausführung des ELSEZweiges ist die IF-Anweisung beendet. Fehlt der ELSE-Zweig, so ist sie bereits dann beendet, wenn die Auswertung aller logischen Ausdrücke den Wert FALSE ergeben hat.
Programmbeispiel (10) M O D U L E Briefgebuehren; (*
')
(* Dieses Programm berechnet die Briefsendungsgebühren der Post (Stand: 01.04.1989) in Abhängigkeit v o m Gewicht und von der Art des Briefes (Standardbrief, Normalbrief, Brief innerhalb - Berlins). Ausgegeben wird der errechnete Betrag in DM. In diesem Programmbeispiel wird das Grundbibliotheks-Modul InOut und RealConversions sowie das Bibliotheks-Modul Screen (zur Ansteuerung des Bildschirms) verwendet. *) (*
*)
2. Sprachstandard
180 (* (** Import-Deklarationen (
*) *) *)
FROM InOut IMPORT WriteString, WriteLn, Read, ReadCard; FROM RealConversions IMPORT RealToString; FROM Screen IMPORT ClearScreen, Gotoxy; (*
*)
(** Variablendeklarationen (
*) *)
VAR Gewicht Gebuehr Antwort StandardBrief, BriefBerlin (*
: : : :
CARDINAL; REAL; CHAR; BOOLEAN;
*)
(* Unterprogramm zum Einblenden von Programminformationen
*)
PROCEDURE Titel; BEGIN (* *) (* Löscht den Bildschirminhalt *) ( * * ) ClearScreenO; WriteString(' Dieses Programm berechnet die Briefsendungsgebühren der Post in') WriteStrlng('Abhängigkeit') ; WriteLn 0; WriteStringC vom Gewicht und von der Art des Briefes (Standardbrief, Normalbrief, Brief); WriteLnO; WriteStringC innerhalb Berlins) und gibt diese auf dem Bildschirm aus.'); WriteLnO; Read (Antwort) END Titel; (* (* Unterprogramm zur Eingabe von Daten (Standardbrief (J/N) und Berlinbrief (J/N) (* PROCEDURE EingabeO; BEGIN ClearScreenO; Gotoxy(10,10); WriteString('Wollen Sie einen Standardbrief verschicken (J/N) ? Read (Antwort); Antwort: = CAP(Antwort); IF Antwort = 'J' THEN StandardBrief: =TRUE ELSE StandardBrief: = FALSE END; Gotoxy(10,12); WriteString('Wollen Sie einen Brief innerhalb Berlins verschicken (J/N) Read (Antwort); Antwort: = CAP(Antwort);
— = >');
? - = >');
*) *)
Programmbeispiel (10)
181
IF Antwort = 'J' THEN BriefBerlin:=TRUE ELSE BriefBerlin: = FALSE END; IF NOT StandardBrief THEN Gotoxy(10,14); WriteString('Geben Sie das Gewicht ihrer Briefsendung in g an (1 ..1000)! — = >'); ReadCard(Gewicht) END END Eingabe;
p (* Unterprogramm zur Berechnung der Portogebühren (* PROCEDURE BerechnungO; BEGIN IF BriefBerlin THEN IF StandardBrief THEN Gebuehr:=0.60 ELSIF Gewicht > 1000 THEN Gebuehr:=0.00 ELSIF (Gewicht > 500) AND (Gewicht < = 1000) THEN Gebuehr: = 2.60 ELSIF (Gewicht > 250) AND (Gewicht < = 500) THEN Gebuehr:=2.20 ELSIF (Gewicht > 100) AND (Gewicht < = 250) THEN Gebuehr: = 1.80 ELSIF (Gewicht > 50) AND (Gewicht < = 100) THEN Gebuehr: = 1.40 ELSIF Gewicht < = 50 THEN Gebuehr: = 1.00 END ELSIF StandardBrief THEN Gebuehr: = 1.00 ELSIF Gewicht > 1000 THEN Gebuehr: =0.00 ELSIF (Gewicht > 500) AND (Gewicht < = 1000) THEN Gebuehr: = 4.80 ELSIF (Gewicht > 250) AND (Gewicht < = 500) THEN Gebuehr: = 4.00 ELSIF (Gewicht > 100) AND (Gewicht < = 250) THEN Gebuehr: = 3.20 ELSIF (Gewicht > 50) AND (Gewicht < = 100) THEN Gebuehr: =2.40 ELSIF Gewicht < = 50 THEN Gebuehr: = 1.70 ELSE Gebuehr: =0.00 END END Berechnung;
*) *) *)
182
2. Spracfastandard
( * * (* Unterprogramm zur Ausgabe der Portogebühren
)
*)
PROCEDURE AusgabeO; VAR ok : BOOLEAN; S : ARRAY [0..5] OF CHAR; BEGIN ClearScreenO; Gotoxy(6,12); IF Gebuehr=0.0 THEN WriteStrlng('Der Brief ist zu schwer. Machen Sie davon ein Päckchen!') ELSE WriteString('Das Porto für Ihren '); IF StandardBrief THEN WriteString('Standard') END; WrlteString('Brief '); IF BriefBerlin THEN WrlteStringfinnerhalb West-Berlins ') END; WriteStrlng('beträgt '); RealToString(Gebuehr, 2, 5, s, ok); (* (* da die Ausgabe von reelen Zahlen mittels der Prozedur WriteReal nur die Ausgabe In Potenzschreibweise zuläßt, wurde die Variable "Gebuehr" in eine Zeichenkette konvertiert *) (*
*)
WriteString(s); WriteString(' DM.') END END Ausgabe; (*
*)
(* Hauptprogramm (* BEGIN Titel 0; EingabeO; BerechnungO; AusgabeO END Briefgebuehren.
*) *)
Testergebnisse Programmbeispiel (10) Dieses Programm berechnet die Briefsendungsgebühren der Post in Abhängigkeit vom Gewicht und von der Art des Briefes (Standardbrief, Normalbrief, Brief innerhalb Berlins) und gibt diese auf dem Bildschirm aus.
183
2.5.2.2 CASE-Anweisung
Eingaben des Programms: Wollen Sie einen Standardbrief verschicken (J/N) ? Wollen Sie einen Brief innerhalb Berlins verschicken (J/N) ?
—= >J —= >J
Ausgaben des Programms: Das Porto für Ihren Standardbrief innerhalb West-Berlins beträgt 0.60 DM.
2.5.2.2 CASE-Anweisung Treten in einer mehrstufigen Verzweigungsstruktur alternative Verarbeitungsvarianten auf, die abhängig vom Wert eines Ausdrucks auszuführen sind, so läßt sich vorteilhaft die CASE-Anweisung anwenden. Sie erlaubt es, alternative Anweisungsfolgen zu kennzeichnen, und je nach dem Ergebnis der Auswertung eines Ausdrucks, anzuspringen. Darüberhinaus kann die CASE-Anweisung eine bestimmte Verarbeitungsfolge für den Fall vorsehen, daß keine der gekennzeichneten Varianten zutrifft. Es liegt nahe, die CASE-Anweisung zur Verarbeitung von RECORD-Objekten mit varianter Struktur heranzuziehen. Eine CASE-Anweisung besitzt nämlich einen formalen Aufbau, der der Deklaration eines Varianten RECORD-Typs sehr ähnlich ist. Die nur formale Ähnlichkeit geht in inhaltliche Beziehungen über, wenn man zur Bearbeitung der Varianten Komponenten eines RECORD-Typs entsprechende Verarbeitungsvarianten in einer CASE-Anweisung vorsieht. Das Syntaxdiagramm in Abbildung 2.59 gibt an, wie CASE-Anweisungen zu formulieren sind. Die dazugehörige Beschreibung enthält die Tabelle 2.102.
CASE-Statement (CASE)—|
Expression
G CaseLabelLisfy^
E
|—(Of
)
.• y-\statementsequence
Ü
(ELSty- Sta temen tSequenci
(~£M7)H•
Abb. 2.59: Syntaxdiagramm für das „CASE-Statement" (CASE-Anweisung).
184
2. Sprachstandard
Tab. 2.102: Beschreibung des Syntaxdiagramms für „CASE-Statement". (1) Eine CASE-Anweisung beginnt mit dem Schlüsselwort CASE, dem ein Ausdruck (in dem jedoch die Datentypen POINTER, ARRAY O F CHAR und SET OF nicht auftreten dürfen), das Schlüsselwort OF und eine Aufzählung der Verarbeitungsvarianten folgen. Die Varianten werden durch senkrechte Striche voneinander getrennt. (2) Zulässige Datentypen des Ausdrucks sind die einfachen Datentypen INTEGER, LONGINT, CARDINAL, BOOLEAN und CHAR, der Aufzählungstyp und der Unterbereichstyp. Nicht zulässig sind die Datentypen REAL und ARRAY OF CHAR. (3) Eine Verarbeitungsvariante besteht aus einer Anweisungsfolge, der eine Kennzeichnung (CaseLabelList) und ein Doppelpunkt vorangehen. Bei der Ausführung einer CASE-Anweisung wird die Variante gewählt, deren Kennzeichnung mit dem Ergebnis der Auswertung des Ausdrucks übereinstimmt. Die für eine CaseLabelList gültige Syntax wurde bereits in Kapitel 2.3.2.4 behandelt. (4) Auf die Aufzählung der Verarbeitungsvarianten kann ein ELSE-Zweig folgen. Die Anweisungsfolge des ELSE-Zweiges wird stets dann zur Ausführung gebracht, wenn das Ergebnis der Auswertung des Ausdrucks keine der Varianten kennzeichnet. (5) Den Abschluß einer CASE-Anweisung bildet das Schlüsselwort END.
Die Ausführung einer CASE-Anweisung umfaßt zwei Schritte. Der erste Schritt besteht in der Auswertung des dem Schlüsselwort CASE folgenden Ausdrucks. Abhängig vom Ergebnis der Auswertung wird im zweiten Schritt die Variante ausgeführt, deren CaseLabelList-Wert dem Auswertungsergebnis entspricht. Nach der Ausführung der so gewählten Anweisungsfolge ist das Ende der CASE-Anweisung erreicht. Eine Komplikation kann dann auftreten, wenn das Auswertungsergebnis keine der Verarbeitungsvarianten bezeichnet. Ist ein derartiger Fall nicht auszuschließen, so sollte unbedingt ein ELSE-Zweig vorgesehen werden. Der ELSE-Zweig verhindert das Auftreten eines Undefinierten Zustands, der in der Regel einen Abbruch des Programms zur Folge haben wird.
Programmbeispiel (11)
185
Programmbeispiel (11) MODULE Paketgebuehren; f (* Dieses Programm berechnet die Paketsendungsgebühren der Post (Stand: 01.09.1989) in Abhängigkeit vom Gewicht und von der Kilometerzone (Zone 1 bis 150 km, Zone 2 über 150 km bis 300 km, Zone 3 über 300 km) und gibt den errechneten Betrag auf dem Bildschirm aus. In diesem Programmbeispiel werden die Grundbibllotheks-Module InOut und RealConversions sowie der Bibliotheks-Modul Screen (zur Ansteuerung des Bildschirms) verwendet. (* (*
*) *) *)
(* Import-Deklarationen (* FROM InOut FROM RealConversions FROM Screen (*
*) *)
IMPORT WriteString, WriteLn, Read, ReadCard; IMPORT RealToString; IMPORT ClearScreen, Gotoxy;
(* Variablendeklarationen
(*
*) *)
VAR Gewicht : Zone : Gebuehr : space
CARDINAL; CARDINAL; REAL; CHAR;
(*
*)
(* Unterprogramm zum Einblenden von Programminformationen (*
*) *)
PROCEDURE TitelO; BEGIN (* (* Löscht den Bildschirminhalt (*
*) *) *)
ClearScreenQ; WriteStringC Dieses Programm berechnet die Paketsendungsgebühren der Post in') WriteString(' Abhängigkeit'); WriteLnO; WriteStringC vom Gewicht und von der Kilometerzone und gibt diese auf dem') WriteString('Bildschirm aus.'); WriteLnO; Read (space) END Titel; (* (* Unterprogramm zur Eingabe von Daten (Kilometerzone, Gewicht) (* PROCEDURE EingabeO; BEGIN ClearScreenO; Gotoxy(4,8);
*) *) *)
2. Sprachstandard
186
WriteString('ln welcher Kilometerzone möchten Sie Ihr Paket verschicken (1..3) ?'); Gotoxy(28,10); WriteString('Zone 1 bis 150 KM'); Gotoxy(28,12); WriteString('2one 2 über 150 KM bis 300 KM'); Gotoxy(28,14); WriteString('Zone 3 über 300 KM - = >'); ReadCard(Zone); IF (Zone=0) OR (Zone >3) THEN Zone: =3 END; Gotoxy(4,16); WriteString('Geben Sie das Gewicht Ihrer Paketsendung In g an (1 ..20000)! — = >'); ReadCard(Gewicht) END Eingabe;
(*
*)
(* Unterprogramm zur Berechnung der Paketgebühren
*)
(*
PROCEDURE BerechnungO; BEGIN CASE Zone OF 1:
IF Gewicht > 20000 THEN Gebuehr: = 0.00 ELSIF (Gewicht >18000) AND (Gewicht< = 20000) THEN Gebuehr: = 15.40 ELSIF (Gewicht > 16000) AND (Gewicht < = 18000) THEN Gebuehr: = 13.90 ELSIF (Gewicht > 14000) AND (Gewicht < = 16000) THEN Gebuehr: = 12.40 ELSIF (Gewicht > 12000) AND (Gewicht < = 14000) THEN Gebuehr: = 10.90 ELSIF (Gewicht > 10000) AND (Gewicht < = 12000) THEN Gebuehr: =9.40 ELSIF (Gewicht > 9000) AND (Gewicht < = 10000) THEN Gebuehr: = 8.70 ELSIF (Gewicht > 8000) AND (Gewicht < = 9000) THEN Gebuehr: = 8.00 ELSIF (Gewicht > 7000) AND (Gewicht < = 8000) THEN Gebuehr: = 7.30 ELSIF (Gewicht > 6000) AND (Gewicht < = 7000) THEN Gebuehr: =6.60 ELSIF (Gewicht > 5000) AND (Gewicht < = 6000) THEN Gebuehr: =5.90 ELSE Gebuehr: =5.20 END I 2:
IF Gewicht > 20000 THEN Gebuehr: =0.00 ELSIF (Gewicht > 18000) AND (Gewicht < = 20000) THEN
*)
Programmbeispiel (11)
Gebuehr: = 16.70 ELSIF (Gewicht > 16000) A N D (Gewicht < = 18000) THEN Gebuehr: = 15.10 ELSIF (Gewicht > 14000) AND (Gewicht < = 16000) THEN Gebuehr: = 13.50 ELSIF (Gewicht > 12000) AND (Gewicht < = 14000) T H E N Gebuehr: = 11.90 ELSIF (Gewicht > 10000) AND (Gewicht < = 12000) THEN Gebuehr: = 10.30 ELSIF (Gewicht > 9000) AND (Gewicht < = 10000) THEN Gebuehr: =9.50 ELSIF (Gewicht > 8000) AND (Gewicht < = 9000) THEN Gebuehr: = 8.70 ELSIF (Gewicht > 7000) A N D (Gewicht < = 8000) THEN Gebuehr: =7.90 ELSIF (Gewicht > 6000) A N D (Gewicht < = 7000) THEN Gebuehr: = 7.10 ELSIF (Gewicht > 5000) AND (Gewicht < = 6000) THEN Gebuehr: =6.30 ELSE Gebuehr: =5.50 END |3: IF Gewicht > 20000 THEN Gebuehr: = 0.00 ELSIF (Gewicht > 18000) AND (Gewicht < = 20000) THEN Gebuehr: = 18.00 ELSIF (Gewicht > 16000) AND (Gewicht < = 18000) THEN Gebuehr: = 16.30 ELSIF (Gewicht > 14000) AND (Gewicht < = 16000) THEN Gebuehr: = 14.60 ELSIF (Gewicht > 12000) AND (Gewicht < = 14000) THEN Gebuehr: = 12.90 ELSIF (Gewicht > 10000) AND (Gewicht < = 12000) THEN Gebuehr: = 11.20 ELSIF (Gewicht > 9000) AND (Gewicht < = 10000) THEN Gebuehr: = 10.30 ELSIF (Gewicht > 8000) AND (Gewicht < = 9000) THEN Gebuehr: =9.40 ELSIF (Gewicht > 7000) AND (Gewicht < = 8000) THEN Gebuehr: = 8.50 ELSIF (Gewicht > 6000) A N D (Gewicht < = 7000) THEN Gebuehr: = 7.60 ELSIF (Gewicht > 5000) A N D (Gewicht < = 6000) THEN Gebuehr: =6.70 ELSE Gebuehr: = 5.80 END ELSE END E N D Berechnung;
187
188
2. Sprachstandard
(* (* Unterprogramm zur Ausgabe der Paketgebühren (*
*) *) *)
PROCEDURE AusgabeO; VAR ok : BOOLEAN; s : ARRAY [0..6] OF CHAR; BEGIN ClearScreenO; IFGebuehr = 0.0 THEN Gotoxy(12,12); WriteString('Das Paket ist zu schwer. Machen Sie davon mehrere Pakete!') ELSE Gotoxy(18,12); WriteString('Die Gebühren für Ihr Paket betragen '); RealToString(Gebuehr, 2,6, s, ok); (*
»)
(* da die Ausgabe von reellen Zahlen mittels der Prozedur WriteReal nur In Potenzschreibwelse möglich ist wurde die Variable "Gebuehr" in eine Zeichenkette konvertiert (*
*) *)
WriteString(s); WriteStringC DM.') END END Ausgabe; (* (* Hauptprogramm
*) *) *)
BEGIN TitelO; EingabeO; BerechnungO; AusgabeO END Paketgebuehren.
Testergebnisse Programmbeispiel (11) Dieses Programm berechnet die Paketsendungsgebühren der Post in Abhängigkeit vom Gewicht und von der Kilometerzone und gibt diese auf dem Bildschirm aus.
Eingaben des Programms: In welcher Kilometerzone möchten Sie Ihr Pakte verschicken (1 ..3) ? Zone 1 bis 150 KM Zone 2 über 150 KM bis 300 KM Zone 3 über 300 KM Geben Sie das Gewicht Ihrer Paketsendung in g an (1 ..20000)!
- = >3 - = > 10000
189
2.5.3 Schleifen
Ausgaben des Programms: Die Gebühren für Ihr Paket betragen 10.30 DM.
2.5.3 Schleifen Im vorangehenden Kapitel wurden Anweisungsarten zur Formulierung von Verarbeitungsvarianten vorgestellt. Soll jedoch eine Menge von Datenobjekten einer identischen Folge von Verarbeitungsschritten unterworfen werden, so bietet sich die Verwendung von Schleifen an. Als Beispiele für solche Verarbeitungsfälle seien die sukzessive Addition von Einzelbeträgen zu Zwischensummen, das wiederholte Eingeben von Zeichen mit der Tastatur und das wiederholte Drucken von Zeilen einer Artikelstatistik genannt. Wie oft bestimmte Verarbeitungsschritte zu wiederholen sind, hängt von der bearbeiteten Problemstellung ab. So kann die Anzahl der Wiederholungen fest vorgegeben oder zunächst noch Undefiniert sein. Im zweiten Fall ist es der Eintritt einer bestimmten Bedingung, der über die Fortsetzung oder den Abbruch der wiederholten Verarbeitung entscheidet. Die eben vorgenommene Unterscheidung findet sich bei den in Modula-2 verfügbaren Schleifenarten wieder. Sie lassen sich wie folgt gliedern: - Schleifen mit bedingungsabhängiger Wiederholung: WHILE-Anweisung, REPEAT-Anweisung und LOOP-Anweisung. -
Schleifen mit fester Anzahl von Wiederholungen: FOR-Anweisung.
Diese Schleifenarten werden in den folgenden Kapiteln behandelt. Abweichend von dieser Gruppierung wird die LOOP-Anweisung zuletzt behandelt, denn die Kenntnis der übrigen Schleifenarten erleichtert ihr Verständnis. Die LOOP-Anweisung ist zwar die allgemeinste Schleifenart, zu ihrer Formulierung ist jedoch eine weitere Anweisungsart erforderlich, die EXIT-Anweisung. Diese darf nur innerhalb von LOOP-Anweisungen auftreten und wird daher zusammen mit der LOOP-Anweisung behandelt.
2.5.3.1 WHILE-Anweisung Die WHILE-Anweisung ist eine Schleife mit Schleifeneingangsbedingung. Solange die Bedingung am Schleifeneingang erfüllt ist, wird eine gegebene Anweisungsfolge wiederholt.
2. Sprachstandard
190
Den Aufbau der WHILE-Anweisung beschreibt das in Abbildung 2.60 dargestellte Syntaxdiagramm. Erläutert wird das Diagramm in der Tabelle 2.103.
WHILE-Statement
Abb. 2.60: Syntaxdiagramm für das „WHILE-Statement" (WHILE-Anweisung).
Tab. 2.103: Beschreibung des Syntaxdiagramms für „WHILE-Statement". (1) Eine WHILE-Anweisung beginnt mit dem Schlüsselwort WHILE, dem ein logischer Ausdruck, das Schlüsselwort DO und eine mit END abgeschlossene Anweisungsfolge nachgestellt sind. (2) Die Anweisungsfolge, auch Schleifenrumpf genannt, darf aus beliebig vielen Anweisungen bestehen.
Die Schleifeneingangsbedingung ist eine logische Bedingung. Das Ergebnis der Auswertung der Bedingung muß also vom Datentyp BOOLEAN (TRUE oder FALSE) sein. Bei der Ausführung einer WHILE-Anweisung wird zuerst die Schleifeneingangsbedingung geprüft. Ergibt die Prüfung den Wert TRUE, so wird die Anweisungsfolge des Schleifenrumpfes ausgeführt und zum Schleifenanfang übergegangen. Es schließt sich also eine erneute Ausführung der WHILE-Anweisung an. Ergibt die Prüfung dagegen den Wert FALSE, so wird die Schleife abgebrochen. Dies bedeutet, daß die Ausführung des Schleifenrumpfes unterbleibt und die WHILE-Anweisung beendet ist. Ergibt die Prüfung der Schleifenbedingung bereits beim ersten Eintritt in die Schleife den Wert FALSE, so wird der Schleifenrumpf kein einziges Mal ausgeführt.
191
Programmbeispiel (12)
Programmbeispiel (12) MODULE Roemisch; (*
*)
(* Dieses Programm wandelt eine Dezimalzahl in eine römische Zahl um. In diesem Programmbeispiel werden die Grundbibliotheks-Module InOut und Strings sowie der Bibliotheks-Modul Screen (zur Ansteuerung des Bildschirms) verwendet. (*
*) *)
(*
*)
(* Import-Deklarationen (*
*) *)
FROM InOut FROM Screen FROM Strings (*
*)
IMPORT WriteString, WriteLn, Read, ReadCard, WriteCard; IMPORT ClearScreen, Gotoxy; IMPORT Concat;
(* Variablendeklarationen (* VAR Zahl, MerkeZahl: CARDINAL; space CHAR; Text : ARRAY [0..80] OF CHAR;
(
*
*) .)
*
(* Unterprogramm zum Einblenden von Programminformationen
)
*)
(*
*)
P R O C E D U R E TitelO; BEGIN (
*
(* Löscht den Bildschirminhalt *) (*
*
)
*)
ClearScreenO; WrlteStringC Dieses Programm gibt eine Dezimalzahl im römischen Zahlensystem aus.'); WriteLnO; Read(space) E N D Titel;
(*
*)
(* Unterprogramm zur Eingabe der Dezimalzahl
*)
(*
*)
P R O C E D U R E EingabeO; BEGIN ClearScreenO; Gotoxy(10,12); WriteString('Geben Sie eine Dezimalzahl an (0..65535)! ReadCard (Zahl); MerkeZahl: = Zahl E N D Eingabe;
- = >');
192
2. Sprachstandard
(* (* Unterprogramm zur Berechnung der römischen Zahl (*
*) *) *)
PROCEDURE BerechnungO; VAR CARDINAL EinerZiffer, ZehnerZiffer, HunderterZiffer CARDINAL ZahlHelp CARDINAL gesperrt BOOLEAN PROCEDURE FindEndzifferO; BEGIN (* (* Ermittlung der einzelnen Ziffern, aus denen sich die Dezimalzahl zusammensetzt (*
*) *) *)
ZahlHelp: = Zahl; ZahlHelp: = ZahlHelp - (ZahlHelp DIV 1000) * 1000; HunderterZiffer: = ZahlHelp DIV 100; ZahlHelp: = ZahlHelp - (ZahlHelp DIV 100) * 100; ZehnerZiffer: = ZahlHelp DIV 10; ZahlHelp: =ZahlHelp - (ZahlHelp DIV 10) * 10; EinerZiffer: = ZahlHelp END FindEndziffer; BEGIN FindEndzifferO; Concat(",",Text); gesperrt: = FALSE; (* (* Tausender ermitteln (
*
*) *)
*)
WHILE Zahl > = 1000 DO Concat(Text, 'M', Text); DEC(Zahl,1000) END; (* (* Sonderfallbehandlung, um das Zeichen für 100 und 200 vorwegzuziehen, falls Ziffern abgezogen werden müssen, z.B. 199 - = >CIC, 289 - = > CCIXC *) (
*
*)
IF (HunderterZiffer > = 1) AND (HunderterZiffer < =2) THEN WHILE Zahl > = 100 DO ConcatfText, 'C', Text); DEC(Zahl,100) END END; IF EinerZiffer=9 THEN (* (* Ziffern ermitteln, die abgezogen werden müssen (* IF ZehnerZiffer=8 THEN (* (* Sonderfallbehandlung: für 89 (* ConcatfText, 'IX', Text); gesperrt: =TRUE;
*) *)
Programmbeispiel (12)
INC(Zahl,11) ELSIF ZehnerZiffer=9 T H E N C (* Sonderfallbehandlung: für 99
(.
Concat(Text, T, Text); gesperrt: = T R U E ; INC(Zahl) END END; IF Zahl DIV 1000 = 1 T H E N (* Tausender ermitteln, falls die Dezimalzahl aufgewertet wurde, z.B. bei 1 9 9 9 - = > MIM (. Concat(Text, 'M', Text); DEC(Zahl,1000) ELSIF Zahl DIV 900 = 1 T H E N C (* Neunhunderter ermitteln (*
ConcatfText, 'CM', Text); DEC(Zahl,900) ELSIF Zahl DIV 500 = 1 T H E N
(.
(* Fünfhunderter ermitteln
193
*) *)
*)
*) *)
*) *)
*)
*)
*) *)
ConcatfText, 'D', Text); DEC(Zahl,500) ELSIF Zahl DIV 400 = 1 T H E N (* (* Vierhunderter ermitteln
*) *)
Concat(Text, 'CD', Text); DEC(Zahl,400) END; (* (* Hunderter ermitteln
*) *)
(*
*)
(*
*)
WHILE Zahl = 100 DO Concat(Text, 'C', Text); DEC(Zahl,100) END; (* (* Sonderfallbehandlung: für 1 9 , 2 9 und 39 ; Behandlung der Normalfälle 9, 49,59, 69, 79, falls Sonderbehandlung als solche noch nicht erkannt wurde
*)
(*
IF (EinerZiffer=9) AND NOT gesperrt T H E N IF (ZehnerZiffer = 1) T H E N ConcatfText, 'XIX', Text); Zahl: = 0 ELSIF (ZehnerZiffer=2) T H E N
»)
*)
194
2. Sprachstandard
ConcatfText, 'XXIX', Text); Zahl: = 0 ELSIF ZehnerZiffer=3 THEN ConcatfText, 'IX', Text); INC(Zahl,11) ELSE ConcatfText, 'I', Text); INC(Zahl) END END; IF Zahl DIV 100 = 1 THEN C (** Hunderter ermitteln
*) *) *)
(
ConcatfText, 'C', Text); DEC(Zahl,100) ELSIF Zahl DIV 90 = 1 THEN (*
*)
(* Neunziger ermitteln (*
*) *)
ConcatfText, 'XC', Text); DEC(Zahl,90) ELSIF Zahl DIV 50 = 1 THEN (*
*)
(.
*)
(* Fünfziger ermitteln
*)
ConcatfText, 'L', Text); DEC(Zahl,50) ELSIF (* Zahl DIV 40 = 1 THEN
*)
(* Vierziger ermitteln (
*) *
ConcatfText, 'XL', Text); DEC(Zahl,40) END;
*
)
(*
*)
(* Zehner ermitteln
*)
(*
*)
WHILE Zahl = 10 DO ConcatfText, 'X', Text); DEC(Zahl,10) END; IF Zahl DIV 5 = 1 THEN (*
(* Fünfer ermitteln (* ConcatfText, 'V', Text); DEC(Zahl,5) ELSIF Zahl DIV 4 = 1 THEN
*)
*) *)
(*
')
(* Vierer ermitteln
*)
(*
ConcatfText, 'IV, Text);
*)
Testergebnisse Programmbeispiel (12)
195
DEC(Zahl,4) END; (* (* Einer ermitteln (* WHILE Zahl 0 DO Concat(Text, T, Text); DEC(Zahl) END; END Berechnung;
*) *) .)
(* (* Unterprogramm zur Ausgabe der römischen Zahl (* PROCEDURE AusgabeO; BEGIN ClearScreenO; Gotoxy(10,12); IF MerkeZahl > 0 THEN WrìteString('Die Dezimalzahl '); WriteCard(MerkeZahl, 5); WriteStringC lautet im römischen Zahlensystem '); WriteString(Text); WriteString('. ') ELSE WriteString('Für die Dezimalzahl 0 kenne ich die römische Zahl nicht!') END END Ausgabe;
(*
(* Hauptprogramm (* BEGIN Titel 0; EingabeO; BerechnungO; AusgabeO END Roemisch.
Testergebnisse Programmbeispiel (12) Dieses Programm gibt eine Dezimalzahl im römischen Zahlensystem aus.
Eingaben des Programms: Geben Sie eine Dezimalzahl an (0..65535)!
- = > 139
2. Sprachstandard
196
Ausgaben des Programms: Die Dezimalzahl
139 lautet im römischen Zahlensystem CIXL.
2.5.3.2 REPEAT-Anweisung Als Schleife mit Schleifenausgangsbedingung besitzt die REPEAT-Anweisung einen gegenüber der WHILE-Anweisung konträren Aufbau. Sie dient der Wiederholung einer Anweisungsfolge bis die Bedingung am Schleifenausgang erfüllt ist. Den Aufbau der REPEAT-Anweisung beschreibt das in Abbildung 2.6 dargestellte Syntaxdiagramm. Erläutert wird das Diagramm in der Tabelle 2.104.
REPEAT-Statement (REPEAT)- StatementSequence
J
Expression
Abb. 2.61: Syntaxdiagramm für das „REPEAT-Statement" (REPEAT-Anweisung).
Tab. 2.104: Beschreibung des Syntaxdiagramms für „REPEAT-Statement". (1) Eine REPEAT-Anweisung beginnt mit dem Schlüsselwort REPEAT, dem eine Anweisungsfolge, das Schlüsselwort UNTIL und ein logischer Ausdruck folgen. (2) Die Anweisungsfolge, auch Schleifenrumpf genannt, darf aus beliebig vielen Anweisungen bestehen. (3) Die REPEAT-Anweisung wird nicht mit dem Schlüsselwort END beendet. Das Ende der REPEAT-Anweisung bildet die Schleifenausgangsbedingung. Diese muß einen Ausdruck vom Datentyp BOOLEAN darstellen. Bei der Abarbeitung der Schleife zeigt der Ausdruck an, ob die REPEAT-Anweisung beendet ist. Eine formale Kennzeichnung des Schleifenendes mit END erübrigt sich daher.
197
Programmbeispiel (13)
Beim Eintritt in eine REPEAT-Anweisung wird zuerst die auf REPEAT folgende Anweisungsfolge ausgeführt. Danach wird die Schleifenausgangsbedingung überprüft. Ergibt die Auswertung den Wert FALSE, so wird zum Schleifenanfang übergegangen und die REPEAT-Anweisung erneut ausgeführt. Ergibt die Auswertung dagegen den Wert TRUE, so wird die Schleife abgebrochen. Dies bedeutet, daß die REPEAT-Anweisung beendet ist. Da die Prüfung der Schleifenbedingung stets nach Ausführung der Anweisungsfolge stattfindet, wird die Anweisungsfolge mindestens einmal ausgeführt, nämlich beim ersten Eintritt in die REPEAT-Anweisung.
Programmbeispiel (13) MODULE Potenzen; (* *) (* Dieses Programm gibt die Potenzen zur Basis 2 aus, die unterhalb einer einzulesenden Ganzzahl liegen. In diesem Programmbeispiel wird der Grundbibliotheks-Modul InOut sowie der BibliotheksModul Screen (zur Ansteuerung des Bildschirms) verwendet. *) *) (* (*
(* Import-Deklarationen (* FROM InOut IMPORT WriteString, WriteLn, Write, WriteCard, Read, ReadCard; FROM Screen IMPORT ClearScreen, Gotoxy; (*
*) *) *) *)
(* Konstantendeklarationen (*
*) *)
CONST PotenzWert = 2; (* (* Variablendeklarationen (* VAR Potenz, Exponent, MaxWert :
*) *) *)
space
CARDINAL; CHAR;
(* (* Unterprogramm zum Einblenden von Programminformationen ( * * PROCEDURE TitelO; BEGIN ( * * (* Löscht den Bildschirminhalt (* ClearScreenQ;
*) *) )
)
*) *)
2. Sprachstandard
198
WriteString('Dieses Programm gibt die unterhalb einer einzulesenden Ganzzahl'); WriteLnO; WriteStringC liegenden 2-Potenzen aus.'); WriteLnO; Read(space) END Titel;
(* Unterprogramm zur Eingabe der Zahl, unterhalb der die zu berechnenden Potenzen zur Basis 2 liegen (*
*) *)
PROCEDURE EingabeO; VAR WertOk : BOOLEAN; BEGIN REPEAT ClearScreenO; Gotoxy(19,12); WriteString('Geben Sie die Zahl ein, unterhalb der die auszugebenden 2-Potenzen'); Gotoxy(19,13); WriteStringfliegen sollen (1 ..32767) ! - = >'); ReadCard (MaxWert); IF (MaxWert> =0) AND (MaxWert< =32767) THEN WertOk: = TRUE ELSE Write(CHR(7)); WertOk: = FALSE END UNTIL WertOk END Eingabe;
(*
*)
(* Unterprogramm zur Ausgabe der Potenzen zur Basis 2 in Form einer Tabelle
*)
(*
*)
PROCEDURE AusgabeO; PROCEDURE KopfTabelleO; BEGIN Gotoxy(8, 1); WriteStringC... '); Gotoxy(8, 2); WriteStringC EX | 2-POTENZ | EX | 2-POTENZ | EX | 2-POTENZ | EX | 2-POTENZ '); Gotoxy(8, 3); WriteStringC... ') END KopfTabelle;
*)
(* die Spalten werden hier ohne Venwendung von Parametern ausgegeben
*)
(*
*)
PROCEDURE SpaltelO; BEGIN Gotoxy(8, 4 + Exponent DIV 4);
Programmbeispiel (13)
WriteStringC I '); WriteCard (Exponent, 3); WriteStringC I '); WriteCard (Potenz, 5); WriteStringC |') END Spaltel; PROCEDURE Spalte2(); BEGIN Gotoxy(24, 4 +Exponent DIV 4); WriteStringC I '); WriteCard(Exponent, 3); WriteStringC | '); WriteCard (Potenz, 5); WriteStringC I ') END Spalte2; PROCEDURE Spalte30; BEGIN Gotoxy(40, 4 + Exponent DIV 4); WriteStringC I '); WriteCard (Exponent, 3); WriteStringC | '); WriteCard (Potenz, 5); WriteStringC | ') END Spalte3; PROCEDURE Spalte40; BEGIN Gotoxy(56, 4 + Exponent DIV 4); WriteStringC | '); WriteCard (Exponent, 3); WriteStringC | '); WriteCard (Potenz, 5); WriteStringC | ') END Spalte4; PROCEDURE LeerSpalteO; BEGIN WriteStringC I | |') END LeerSpalte; PROCEDURE AusgabeSpalteO; BEGIN CASE Exponent MOD 4 OF 0 : Spaitelo; I 1 : Spalte20; I 2 : Spalte30; I 3 : Spalte40; ELSE END; END AusgabeSpalte; PROCEDURE EndeTabelleO; BEGIN CASE Exponent MOD 4 OF 0: I 1: Gotoxy(24, 4 + (Exponent DIV 4));
199
2. Sprachstandard
200
LeerSpalte; Gotoxy(40, 4 + (Exponent DIV 4)); LeerSpalte; Gotoxy(56, 4 + (Exponent DIV 4)); LeerSpalte; |2: Gotoxy(40, 4 + (Exponent DIV 4)); LeerSpalte; Gotoxy(56, 4 + (Exponent DIV 4)); LeerSpalte; 13: Gotoxy(56, 4 + (Exponent DIV 4)); LeerSpalte; ELSE END; Gotoxy(8, 5 + (Exponent-1) DIV 4); WriteStrlng('.... '); Read (space) END EndeTabelle; BEGIN ClearScreenO; KopfTabelleO; Potenz: = 1; Exponent: =0; REPEAT AusgabeSpalteO; Potenz: = Potenz * PotenzWert; INC(Exponent) UNTIL Potenz MaxWert; EndeTabelleO END Ausgabe; (*
*)
(* Hauptprogramm
*)
(* BEGIN Titel 0; EingabeO; AusgabeO END Potenzen.
Testergebnisse Programmbeispiel (13) Dieses Programm gibt die unterhalb einer einzulesenden Ganzzahl liegenden 2-Potenzen aus.
Eingaben des Programms: Geben Sie die Zahl ein, unterhalb der die anzugebenden 2-Potenzen liegen sollen (1 ..32767)! - = >1025
*)
201
Testergebnisse Programmbeispiel (13)
Ausgaben des Programms: EX
2-Potenz
EX
2-Potenz
EX
2-Potenz
EX
2-Potenz
0
1
1
2
3
4
3
8
4
16
5
32
6
64
7
128
8
256
9
512
10
1024
Trotz der unterschiedlichen Abbruchsteuerung weisen die WHILE- und die REPEAT-Anweisung einige Gemeinsamkeiten auf. Sie dienen beide der Wiederholung (Iteration) von Anweisungsfolgen. Man bezeichnet sie daher auch als iterative Schleifen. Außerdem setzen sie sich aus den gleichen inhaltlichen Bestandteilen zusammen: einem logischen Ausdruck und einer Anweisungsfolge. Die Gemeinsamkeiten erlauben es, eine WHILE-Anweisung in eine REPEATAnweisung zu überführen und umgekehrt. Um dies zu zeigen, seien die beiden Schleifen WHILE bedingungl DO rumpfl END
und REPEAT UNTIL
rumpf2 bedingung2
betrachtet. Völlig bedeutungsgleiche Formulierungen für diese beiden Schleifen sind: IF bedingungl THEN REPEAT rumpfl UNTIL NOT bedingungl END
und rumpf2; WHILE NOT bedingung2 DO rumpf2 END
2. Sprachstandard
202
Im ersten Fall muß eine IF-Anweisung und im zweiten Fall die sogenannte Codeverdoppelung angewendet werden. Man beachte, daß in beiden Fällen die ursprünglich gegebene Bedingung negiert wird. Im zweiten Fall kann man anstelle der Codeverdoppelung auch eine boole'sche Hilfsvariable (Schaltervariable) wie folgt verwenden: Schalter : = TRUE; WHILE Schalter DO rumpf2; Schalter: = NOT bedingung2 END
Die Initialisierung der Variablen „Schalter" mit dem Wert TRUE bewirkt, daß „rumpf2" mindestens einmal ausgeführt wird.
Programmbeispiel (14) MODULE Potenz; C *) (* Dieses Programm gibt die Potenzen zur Basis 2 aus, die unterhalb einer einzulesenden Ganzzahl liegen. Dieses Beispiel wurde im Gegensatz zum Programmbeispiel 14 mit einer WHILE-Schleife und unter Anwendung der Codeverdoppelung programmiert. In diesem Programmbeispiel wird der Grundbibliotheks-Modul InOut sowie der BibliotheksModul Screen (zur Ansteuerung des Bildschirms) verwendet. *) *) (* (*
(* Import-Deklarationen
*) *)
FROM InOut FROM Screen (*
*)
(*
*)
IMPORT WriteString, WriteLn, Write, WriteCard, Read, ReadCard; IMPORT ClearScreen, Gotoxy;
(* Konstantendeklarationen ( * CONST PotenzWert = 2;
*) *
)
(*
*)
(* Variablendeklarationen (* VAR Potenz, Exponent, MaxWert : space :
*) *) CARDINAL; CHAR;
(*
*>
(*
*)
(* Unterprogramm zum Einblenden von Programminformationen PROCEDURE TitelQ;
*)
Programmbeispiel (14)
203
BEGIN (* (* Löscht den Bildschirminhalt (* ClearScreenO; WriteStringfDieses Programm gibt die unterhalb einer einzulesenden Ganzzahl'); WriteLnO; WriteStringC liegenden 2-Potenzen aus.'); WriteLnO; Read (space) E N D Titel;
(* (* Unterprogramm zur Eingabe der Zahl, unterhalb der die zu berechnenden Potenzen zur Basis 2 liegen
*)
*
*>
(
*)
P R O C E D U R E EingabeO; VAR WertOk : BOOLEAN; BEGIN REPEAT ClearScreenO; Gotoxy(19,12); WriteString('Geben Sie die Zahl ein, unterhalb der die auszugebenden 2-Potenzen'); Gotoxy(19,13); WriteString('liegen sollen (1 ..32767) I - = >'); ReadCard(MaxWert); IF (MaxWert > =0) AND (MaxWert < =32767) THEN WertOk: = TRUE ELSE Write(CHR(7)); WertOk: = FALSE END UNTIL WertOk E N D Eingabe;
(
*
*)
(* Unterprogramm zur Ausgabe der Potenzen zur Basis 2 in Form einer Tabelle *) (* *) P R O C E D U R E AusgabeO; P R O C E D U R E KopfTabelleO; BEGIN Gotoxy(8,1); WriteStringC... '); Gotoxy(8, 2); WriteStringC EX | 2-POTENZ | EX | 2-POTENZ | EX | 2-POTENZ | EX | 2-POTENZ '); Gotoxy(8, 3); WriteStringC... '); E N D KopfTabelle;
204
2. Sprachstandard
(*
*)
(* (* die Spalten werden hier ohne Verwendung von Parametern ausgegeben
*) *)
P R O C E D U R E SpaltelO; BEGIN Gotoxy(8, 4 + Exponent DIV 4); WriteStringf I '); WriteCard(Exponent, 3); WriteStringC | '); WriteCard(Potenz, 5); WriteStringf |'); E N D Spaltel ; P R O C E D U R E Spalte20; BEGIN Gotoxy(24, 4 + Exponent DIV 4); WriteStringf I '); WriteCard (Exponent, 3); WriteStringC I '); WriteCard (Potenz, 5); WriteStringC |'); E N D Spalte2; P R O C E D U R E Spalte30; BEGIN Gotoxy(40, 4 + Exponent DIV 4); WriteStringC | '); WriteCard(Exponent, 3); WriteStringC | '); WriteCard (Potenz, 5); WriteStringC |'); E N D Spalte3; P R O C E D U R E Spalte40; BEGIN Gotoxy(56, 4 + Exponent DIV 4); WriteStringC I '); WriteCard(Exponent, 3); WriteStringC | '); WriteCard(Potenz, 5); WriteStringC |'); E N D Spalte4; P R O C E D U R E LeerSpalteO; BEGIN WriteStringC I | |'); E N D LeerSpalte; P R O C E D U R E AusgabeSpalteO; BEGIN C A S E Exponent M O D 4 OF 0 : SpaltelO; I 1 : Spalte20; I 2 : Spalte30; I 3 : Spalte40; ELSE END; E N D AusgabeSpalte;
Programmbeispiel (14)
205
PROCEDURE EndeTabelleO; BEGIN CASE Exponent MOD 4 OF 0: I 1:
Gotoxy(24, 4 + (Exponent DIV 4)); LeerSpalte; Gotoxy(40, 4 + (Exponent DIV 4)); LeerSpalte; Gotoxy(56, 4 + (Exponent DIV 4)); LeerSpalte; | 2:
Gotoxy(40, 4 + (Exponent DIV 4)); LeerSpalte; Gotoxy(56, 4 + (Exponent DIV 4)); LeerSpalte; | 3: Gotoxy(56, 4+(Exponent DIV 4)); LeerSpalte; ELSE END; Gotoxy(8, 5 + (Exponent-1 ) DIV 4); WriteStringC... '); Read (space) END EndeTabelle; BEGIN ClearScreenO; KopfTabelleO; Potenz: = 1 ; Exponent: =0; AusgabeSpalteO; (* (* Codeverdoppelung von "AusgabeSpalteO" (*
*> *)
*)
WHILE Potenz MaxWert DO Potenz: = Potenz * PotenzWert; INC(Exponent); AusgabeSpalteO END; EndeTabelleO END Ausgabe; (*
(* Hauptprogramm BEGIN TitelO; ElngabeO; AusgabeO END Potenz.
*) *)
2. Sprachstandard
206
Testergebnisse Programmbeispiel (14) Dieses Programm gibt die unterhalb einer einzulesenden Ganzzahl liegenden 2-Potenzen aus.
Eingaben des Programms: Geben Sie die Zahl ein, unterhalb der auszugebenden 2-Potenzen liegen sollen (1 ..32767)!
- = >3000
Ausgaben des Programms: EX
2-POTENZ
EX
2-POTENZ
EX
2-POTENZ
EX
2-POTENZ
0
1
1
2
3
4
3
8
4
16
5
32
6
64
7
128
8
256
9
512
10
1024
8
2048
2.5.3.3 FOR-Anweisung Wie bereits erwähnt wurde, erlaubt die FOR-Anweisung die Formulierung von Schleifen mit einer festen Anzahl von Wiederholungen. Zur Schleifensteuerung ist daher die bekannte Form der Bedingungsabfrage nicht geeignet. Verwendet wird vielmehr eine sogenannte Laufvariable, die gewissermaßen zum Abzählen der Schleifendurchläufe dient. Mit der Vorgabe einer Unter- und einer Obergrenze für die Werte der Laufvariablen sowie einer Schrittweite für die Veränderung der Laufvariablen ist die Anzahl der Schleifendurchläufe festgelegt. Für die FOR-Anweisung gilt das in Abbildung 2.62 dargestellte Syntaxdiagramm. Eine Beschreibung des Diagramms enthält die Tabelle 2.105.
Tab. 2.105: Beschreibung des Syntaxdiagramms für „FOR-Statement". (1) Eine FOR-Anweisung beginnt mit dem Schlüsselwort FOR, dem drei Angaben zur Schleifensteuerung, das Schlüsselwort DO, eine Anweisungsfolge und das Schlüsselwort END folgen.
2.5.3.3 FOR-Anweisung
207
(2) Die erste Angabe zur Schleifensteuerung benennt die Laufvariable und ordnet ihr über ein Zuweisungssymbol und einen arithmetischen Ausdruck einen Startwert zu. Der Datentyp des Ausdrucks muß mit dem Datentyp der Laufvariablen kompatibel sein. (3) Die zweite Angabe, ein dem Schlüsselwort TO folgender arithmetischer Ausdruck, gibt den Endwert der Laufvariablen an. Dieser Ausdruck muß ebenfalls ein zulässiger Wert des Datentyps der Laufvariablen sein. (4) Die dritte Angabe, eine dem Schlüsselwort BY folgende Konstante, benennt die Schrittweite der Veränderung der Laufvariablen. Wird sie weggelassen, so beträgt die Schrittweite 1. (5) Die Laufvariable kann nur von einem der Datentypen INTEGER, LONGINT, CARDINAL und Unterbereichstyp sein. (6) Start- und Endwert der Laufvariablen sowie die Schrittweite müssen ganze Zahlen sein. Die Schrittweite muß von Null verschieden sein.
FOR-Statement Expression
¡dent
D
CriF>
Expression
^»(ßO
Statement-Sequence -(END)
)
hf®-
i— ConstExpr D
Abb. 2.62: Syntaxdiagramm für das „FOR-Statement" (FOR-Anweisung).
Beim Eintritt in eine FOR-Anweisung werden zuerst die Angaben zur Schleifensteuerung ausgewertet und geprüft. Ermittelt werden Anfangs- und Endwert der Laufvariablen und die Schrittweite. Ist bei positiver Schrittweite der Endwert kleiner als der Anfangswert, so wird die Schleife abgebrochen. Ein Abbruch erfolgt auch, wenn bei negativer Schrittweite der Endwert größer als der Anfangswert ist. In beiden Fällen wird die FOR-Anweisung beendet, ohne daß die Anweisungsfolge auch nur ein einziges Mal ausgeführt wurde.
2. Sprachstandard
208
Führt die Prüfung zu Anfang nicht unmittelbar zum Abbruch, so wird der Schleifenrumpf (die Anweisungsfolge) mindestens einmal ausgeführt. Danach wird der Wert der Laufvariablen um die Schrittweite verändert. Falls nun der Wert der Laufvariablen noch innerhalb des durch Anfangs- und Endwert definierten Wertebereichs liegt, wird der Rumpf erneut ausgeführt. Der skizzierte Ablauf (Wertveränderung der Laufvariablen, Prüfung auf Wertbereich und Ausführung des Rumpfs) wird solange wiederholt, bis der Wert der Laufvariablen außerhalb des Wertebereichs liegt. Tritt dieser Fall ein, so wird die Schleife abgebrochen.
Programmbeispiel (15) MODULE MathFunc; (* *) (* Dieses Programm gibt Werte mathematischer Funktionen (Quadrat-, Kubik-, Wurzel-, Exponential- und Logarithmusfunktion) in einer Tabelle aus. Die Argumente der Funktionen sind natürliche Zahlen n, 1 n 40. Es können in der Tabelle für maximal 20 verschiedene Argumente Funktionsberechnungen durchgeführt werden. Ein Startwert für n ist entsprechend per Tastatur einzugeben. Die Schrittweite für n ist Eins. In diesem Programmbeispiel werden die Grundbibliotheks-Module InOut, MathLibO und RealConversions sowie der Bibliotheks-Modul Screen (zur Ansteuerung des Bildschirms) verwendet. *) * *) ( (* (* Import-Deklarationen (* IMPORT WriteString, WriteLn, Write, WriteCard, Read, ReadCard, Readlnt; FROM MathLibO IMPORT exp, In, sqrt; FROM RealConversions IMPORT RealToString; FROM Screen IMPORT ClearScreen, Gotoxy; (* (* Variablendeklarationen (* VAR StartWert: CARDINAL; space : CHAR;
*) *) *)
FROM InOut
(*
(* Unterprogramm zum Einblenden von Programminformationen (* PROCEDURE Titel 0; BEGIN (* (* Löscht den Bildschirminhalt (*
*) *) *)
*) *) *)
*) *) *)
ClearScreenQ; WriteStringC Dieses Programm gibt Werte mathematischer Funktionen (Quadrat-, Kubik-,');
209
Programmbeispiel (15)
WriteLnO; WriteStringf WriteLnO; Read(space) END Titel;
Wurzel-, Exponential- und Logarithmus-Funktion) in einer Tabelle aus.');
(*
*)
(* Unterprogramm zur Eingabe eines Startwertes für die Ausgabe der Tabelle
*)
PROCEDURE EingabeO; VAR WertOk : BOOLEAN; BEGIN REPEAT ClearScreenO; Gotoxy(17,12); WriteString('Geben Sie einen Startwert von (1 ..21) an I ReadCard(StartWert); IF (StartWert>0) AND (StartWert< =21) THEN WertOk: = TRUE ELSE WertOk: = FALSE; Write(CHR(7)) END UNTIL WertOk; END Eingabe;
— = >');
(* (* Unterprogramm zur Ausgabe und Berechnung aller Funktionswerte
*) *)
*)
PROCEDURE BerechnungUndAusgabeO; VAR i,j : CARDINAL; (* (* Unterprogramm zur Ausgabe der Kopfzeilen der Tabelle (* PROCEDURE KopfTabelleO; BEGIN Gotoxy(3,1); WriteStringC... '); Gotoxy(3, 2); WriteStringC NR | QUADRAT | KUBIK Gotoxy(3, 3); WriteStringC... '); END KopfTabelle;
| WURZEL
*) *) *)
| e-Funktion
| Logarith ');
2. Sprachstandard
210 (* (* Unterprogramm zur Brechnung der Funktionswerte (* PROCEDURE BerechnungO; VAR Quadrat, Kubik CARDINAL; Wurzel, eFunc, InFunc REAL; done BOOLEAN; s ARRAY [0..22] OF CHAR; BEGIN Gotoxy(3, 3+j); WriteStringC I '); WrlteCardO, 2); Quadrat: = i*i; WriteStringC | '); WriteCard(Quadrat, 5); WriteStringC | '); Kubik: = i*l*i; WriteCard (Kubik, 5); WriteStringC | '); Wurzel: =sqrt(FLOAT(i)); RealToString(Wurzel, 2, 5, s, done); WriteString(s); WriteStringC | '); eFunc: = exp(FLOAT(i)) ; RealToString(eFunc, 2, 22, s, done); WriteString(s); WriteStringC | '); InFunc: = ln(FLOAT(i)); RealToString(lnFunc, 2, 6, s, done); WriteString(s); WriteStringC | '); END Berechnung; (*
(* Unterprogramm zur Ausgabe der Endezeile der Tabelle (* PROCEDURE EndeTabelleO; BEGIN Gotoxy(3, 24); WriteStringC...'); END EndeTabelle; BEGIN ClearScreenO; KopfTabelleO; j:=0; FOR i: = StartWert TO StartWert +19 DO INC(j); BerechnungO END; EndeTabelleO END BerechnungUndAusgabe;
211
Testergebnisse Programmbeispiel (15) (* (* Hauptprogramm
*) *)
(*
*)
BEQIN TitelO;
EingabeO; BerechnungUndAusgabeO E N D MathFunc.
Testergebnisse Programmbeispiel (15) Dieses Programm gibt Werte mathematischer Funktionen (Quadrat-, Kubik-, Wurzel-, Exponential- und Logarithmus-Funktion) in einer Tabelle aus.
Eingaben des Programms: Geben Sie einen Startwert von (1 ..21) an !
— = > 10
Ausgaben des Programms: NR | Q U A D R A T | KUBIK | WURZEL 10 100 1000 3.16 11 121 1331 3.32 12 144 1728 3.46 2197 13 169 3.61 14 2744 196 3.74 15 225 3375 3.87 16 256 4096 4.00 17 4.12 289 4913 324 5832 4.24 18 19 361 6859 4.36 20 400 8000 4.47 21 441 9261 4.58 22 484 10648 4.69 529 23 12167 4.80 24 13824 576 4.90 25 625 15625 5.00 26 676 17576 5.10 27 729 19683 5.20 28 784 21952 5.29 841 29 24389 5.39
|
e-Funktion 22026.47 59874.14 162754.79 442413.39 1202604.28 3269017.37 8886110.52 24154952.75 65659969.14 178482300.96 485165195.41 1318815734.48 3584912846.13 9744803446.25 26489122129.84 72004899337.39 195729609428.84 532048240601.80 1446257064291.48 3931334297144.04
| Logarith 2.30 2.40 2.48 2.56 2.64 2.71 2.77 2.83 2.89 2.94 3.00 3.04 3.09 3.14 3.18 3.22 3.26 3.30 3.33 3.37
2.5.3.4 LOOP-Anweisung und EXIT-Anweisung Die bisher behandelten Schleifenarten weisen einige Gemeinsamkeiten auf, die die Formulierung allgemeinerer Schleifen einschränken. Angesprochen sind die Schleifensteuerung und der Schleifenrumpf:
2. Sprachstandard
212
-
Die Steuerung von Schleifendurchläufen erfolgt über Bedingungen, die entweder am Anfang oder am Ende der Schleifen geprüft werden.
-
Nach einem (erneuten) Eintritt in eine Schleife wird der Rumpf entweder vollständig oder gar nicht ausgeführt.
In Verbindung mit der EXTT-Anweisung erlaubt die LOOP-Anweisung die Programmierung allgemeinerer Schleifen. Darunter seien Schleifen verstanden, die den genannten Einschränkungen nicht unterliegen. Solche Schleifen weisen mehr Flexibilität in der Schleifensteuerung und im Ausführungsteil auf: - Abbruchbedingungen können auch im Inneren von Schleifen angegeben werden. -
Anweisungsfolgen müssen nicht grundsätzlich vollständig ausgeführt werden.
Den Aufbau einer LOOP-Anweisung beschreibt das in Abbildung 2.63 angegebene Syntaxdiagramm. Die dazugehörigen Erläuterungen gehen aus der Tabelle 2.106 hervor.
LOOPStatement
*
(JÖÖP)—[StaiementSeqüenc^lNDy
Abb. 2.63: Syntaxdiagramm für ein „LOOP-Statement" (LOOP-Anweisung).
Tab. 2.106: Beschreibung des Syntaxdiagramms für „LOOP-Statement". (1) Eine LOOP-Anweisung beginnt mit dem Schlüsselwort LOOP, dem eine Anweisungsfolge und das Schlüsselwort END folgen. (2) Die Anweisungsfolge kann beliebig viele Anweisungen umfassen. Zugelassen sind alle Anweisungsarten, insbesondere auch die LOOP-Anweisung selbst und die EXIT-Anweisung. Die EXIT-Anweisung hat den sofortigen Abbruch einer LOOP-Anweisung zur Folge. Das Syntaxdiagramm für die EXIT-Anweisung ist in Abbildung 2.64 dargestellt. Die dazugehörigen Erläuterungen enthält die Tabelle 2.107.
2.5.3.4 LOOP-Anweisung und EXIT-Anweisung
213
EXIT-Statement •
(
EX IT
)
•
Abb. 2.64: Syntaxdiagramm für ein „EXIT-Statement" (EXTT-Anweisung).
Tab. 2.107: Beschreibung des Syntaxdiagramms für „EXIT-Statement". (1) Eine EXIT-Anweisung besteht aus dem Schlüsselwort EXIT. (2) Die Anwendung der EXIT-Anweisung ist strikt auf die LOOP-Anweisung beschränkt. Außerhalb von LOOP-Anweisungen dürfen EXIT-Anweisungen nicht verwendet werden. Bei der Ausführung einer LOOP-Anweisung wird die Anweisungsfolge zwischen LOOP und END ständig wiederholt. Das Abbrechen der Wiederholungen kann nur eine EXIT-Anweisung bewirken. Eine LOOP-Anweisung, die keine EXIT-Anweisung enthielte, wäre in der Tat eine unendlich oft wiederholte „Endlos-Schleife". Das Verlassen einer LOOP-Anweisung wird man in der Regel über eine oder mehrere Bedingungen steuern. Zur Steuerung bieten sich beispielsweise IF-Anweisungen an, in die EXIT-Anweisungen in geeigneter Weise einzubeziehen sind. Betrachtet sei ein Beispiel mit mehreren Austrittsstellen: LOOP anweisungsfolgel ; IF bedingungl THEN EXIT END; anweisungsfolge2; IF bedingung2 THEN EXIT END; anweisungsfolge3 END
Diese LOOP-Anweisung enthält zwei Austrittsstellen. Nach Ausführung von „anweisungsfolgel" wird sie beendet, falls „bedingungl" erfüllt ist. Andernfalls wird „anweisungsfolge2" ausgeführt und „bedingung2" geprüft. Ist „bedingung2" erfüllt, so wird die Schleife abgebrochen. LOOP-Anweisungen können selbst wieder LOOP-Anweisungen enthalten. Betrachtet sei folgendes Beispiel zweier geschachtelter LOOP-Anweisungen:
2. Sprachstandard
214
LOOP anweisungsfolgel; LOOP anweisungsfolge2; IF bedingungl THEN EXIT END; anweisungsfolge3 END; anweisungsfolge4; IF bedingung2 THEN EXIT END; anwelsungsfolge5 END
Die äußere und die innere Schleife enthalten je eine Austrittsstelle. Über die innere Austrittsstelle wird allerdings nur die innere Schleife verlassen. Ist also nach Ausführung von „anweisungsfolge2" „bedingungl" erfüllt, so wird die Verarbeitung in der äußeren Schleife mit „anweisungsfolge4" fortgesetzt.
2.5.4 WITH-Anweisung Bei der Behandlung des Datentyps RECORD in Kapitel 2 wurde auch der Zugriff zu Komponenten eines RECORD angesprochen. Er ist mit Hilfe der „Punktqualifizierung" vorzunehmen. Stellt „artikel" einen RECORD und „artnr" eine Komponente des RECORD dar, so spezifiziert man mit der Schreibweise „artikel.artnr" einen Zugriff auf die Komponente „artnr". Diese Schreibweise ist vor allem dann mühselig, wenn in einem Programmabschnitt Komponenten eines RECORD oft angesprochen werden. Abhilfe bietet in solchen Fällen die WITH-Anweisung. Sie ermöglicht eine verkürzte Schreibweise. Den Aufbau der WITH-Anweisung beschreibt das Syntaxdiagramm in Abbildung 2.65. Die dazugehörigen Erläuterungen enthält die Tabelle 2.108.
With-Statement
Abb. 2.65: Syntaxdiagramm für das „WITH-Statement" (WITH-Anweisung).
2.5.4 WITH-Anweisung
215
Tab. 2.108: Beschreibung des Syntaxdiagramms für „WITH-Statement". (1) Eine WITH-Anweisung beginnt mit dem Schlüsselwort Wl i H, dem ein Designator, das Schlüsselwort DO und eine durch END abgeschlossene Anweisungsfolge nachgestellt sind. (2) Der Designator bezeichnet eine Variable, die vom Datentyp RECORD sein muß. (3) In der Anweisungsfolge zwischen DO und END können RECORD-Komponenten ohne Voranstellen des RECORD-Namens und des qualifizierenden Punkts angesprochen werden. (4) Verschiedene Designatoren können durch Kommata voneinander getrennt werden. (5) Sollten bei Verwendung der WITH-Anweisung Komponenten den gleichen Namen wie im umschließenden Block vereinbarte Variablen oder Parameter tragen, so werden im DO-END-Block der WITH-Anweisung stets bei Auftreten solcher Namen die Komponenten des RECORD angesprochen.
Die WITH-Anweisung dient ausschließlich der Reduzierung des Schreibaufwands beim Programmieren. Insbesondere löst sie im Rechner keine Verarbeitungsoperationen aus. Ihre Wirkungsweise sei an einem kleinen Beispiel erläutert. Gegeben seien eine RECORD-Variable und der Datentyp der Variablen: TYPE Artikel = RECORD artnr : [1..999]; artbez : ARRAY [0..7] OF CHAR; bestand : CARDINAL; preis : CARDINAL END; VAR artikelsatz : ARTIKEL;
Die Manipulation einiger RECORD-Komponenten stellt sich in der üblichen Schreibweise z.B. wie folgt dar: artikelsatz.artnr : = 216; Concatf, 'SCHRAUBE', artikelsatz.artbez); artikelsatz.bestand : = 5000;
2. Sprachstandard
216
Bei Nutzung der WITH-Anweisung enthält man folgende, völlig bedeutungsgleiche Formulierung: WITH artikelsatz DO artnr : = 216; Concatf', 'SCHRAUBE', artbez); bestand : = 5000 END
Wll H-Anweisungen können die Übersichtlichkeit von Programmen beeinträchtigen. Vor allem dann, wenn man sie ineinander verschachtelt. Zu achten ist auch darauf, daß die Namen von innerhalb der Wl l H-Anweisung benutzten Variablen nicht mit benutzten Komponenten-Namen identisch sind.
2.5.5 RETURN-Anweisung Ein Modula-2-Programm besteht bekanntlich aus einem Programm-Modul und, falls es nicht trivial ist, aus weiteren Modulen. Sämtliche Module eines Programms können Verarbeitungs-Bausteine in Form sogenannter Prozeduren und Funktionsprozeduren enthalten. Prozeduren werden im nächsten Kapitel eingehend behandelt. An dieser Stelle genügt es, sie als in sich abgeschlossene Verarbeitungsabschnitte zu charakterisieren, die an verschiedenen Stellen eines Programms „aufgerufen" werden können. Ein Prozeduraufruf bewirkt die Ausführung einer Prozedur. Nach der Ausführung wird die Verarbeitung unmittelbar hinter der Aufrufstelle fortgesetzt. Nun können bei der Ausführung von Modulen (genauer: Modulrümpfen) und Prozeduren Situationen eintreten, die eine weitere Ausführung eines Moduls oder einer Prozedur erübrigen. Eine sinnvolle Konsequenz ist im Falle eines ProgrammModuls das sofortige Beenden des Programms und im Falle einer Prozedur das Abbrechen der Prozedur und das Fortsetzen der Verarbeitung unmittelbar nach der Aufrufstelle. Modula-2 bietet für solche Fälle die RETURN-Anweisung an. Für die RETURN-Anweisung gilt das in Abbildung 2.66 dargestellte Syntaxdiagramm. Erläuterungen zu dem Diagramm enthält die Tabelle 2.109. Tab. 2.109: Beschreibung des Syntaxdiagramms für „RETURN-Statement". (1) Eine RETURN-Anweisung besteht entweder nur aus dem Schlüsselwort RETURN oder aus dem Schlüsselwort RETURN und einem nachfolgenden Ausdruck.
217
2.5.6 Prozeduren
(2) Die nur aus dem Schlüsselwort RETURN bestehende RETURN-Anweisung darf nicht in Funktionsprozeduren verwendet werden. (3) Aus dem Schlüsselwort RETURN mit nachfolgendem Ausdruck bestehende RETURN-Anweisungen dürfen nur in Funktions-prozeduren verwendet werden.
RETURN-Statement •
QRETURN)-
T
Expression
J
Abb. 2.66: Syntaxdiagramm für das „RETURN-Statement" (RETURN-Anweisung). Angewandt in (Programm-)Modulen und Prozeduren bewirkt die RETURN-Anweisung den Abbruch der Verarbeitung in der bereits dargelegten Weise. In einer Funktionsprozedur kommt noch eine weitere Wirkung hinzu, die Ermittlung des Funktionswertes der Prozedur. Da eine RETURN-Anweisung stets die Rückkehr an die Aufrufstelle einer Prozedur auslöst, ist der ermittelte Wert an der Aufrufstelle verfügbar.
2.5.6 Prozeduren Prozeduren als Bestandteile von Modula-2-Programmen wurden bereits in den Kapiteln 1.2 „Module in Modula-2" und 1.5 „Struktur von Modula-2-Programmen" angesprochen, allerdings unter Verwendung des nicht näher spezifizierten Begriffs „Funktion". In den genannten Kapiteln wurde ausgeführt, daß der Entwurf (komplexerer) Modula-2-Programme auch folgende Tätigkeiten umfaßt: -
Zusammensetzen einer Gesamtlösung (Programm) aus Bausteinen (Modulen), die als Teillösungen der Bearbeitung abgegrenzter Teilprobleme dienen.
-
Definition der (zusammengehörigen) Funktionen, die in einem Modul zwecks Bearbeitung eines Teilproblems zusammenzufassen sind.
218
2.5.6.1 Prozedur-Deklaration
Erfüllen solche Funktionen gewisse sprachliche Anforderungen, so stellen sie Prozeduren im Sinne der Sprache Modula-2 dar. Prozeduren dienen also der Bearbeitung abgegrenzter Teilprobleme eines Gesamtproblems. Sie stellen Teilalgorithmen des ein Problem lösenden (Gesamt-)Algorithmus „Programm" dar. Prozeduren können selbst wieder Prozeduren enthalten. Innere Prozeduren stellen ihrerseits Teilalgorithmen der sie jeweils umschließenden Prozeduren dar. Mit der sukzessiven Definition ineinander geschachtelter Prozeduren geht die Aufspaltung eines Problems in immer feinere Teilprobleme einher. Man spricht bei dieser Vorgehensweise deshalb auch von „schrittweiser Verfeinerung". Prozeduren sind ein Mittel zur programmiertechnischen Realisierung der schrittweise verfeinernden Entwicklungsweise. Sie weisen aber noch weitere Vorteile auf. Unter Beachtung bestimmter Regeln können Prozeduren an verschiedenen Stellen eines Programms verwendet werden. Sie sind also mehrfach verwendbar. Und schließlich fördern Prozeduren die Übersichtlichkeit von Programmen. Das in Modula-2 verwendete Prozedurkonzept wird in den folgenden Kapiteln behandelt. Im einzelnen wird gezeigt, - wie Prozeduren zu deklarieren sind (Kapitel 2.5.6.1), -
wie Prozeduren aufzurufen sind und welcher Verarbeitungsablauf sich bei geschachtelten Prozeduren ergibt (Kapitel 2.5.6.2),
-
welche Besonderheiten Funktionsprozeduren aufweisen (Kapitel 2.5.6.3) und
-
in welcher Weise in einer Prozedur definierte Datenobjekte in anderen Prozeduren „sichtbar" (verwendbar) sind (Kapitel 2.5.6.4).
2.5.6.1 Prozedur-Deklaration Prozeduren können als Teilalgorithmen in Modulen oder in anderen (übergeordneten) Prozeduren auftreten. Entsprechend sind sie im Vereinbarungsteil von Modulen oder übergeordneten Prozeduren zu deklarieren. Eine Prozedur-Deklaration besteht aus zwei Teilen, dem Prozedurkopf und dem Prozedurrumpf. Der Prozedurkopf umfaßt: - den Namen der Prozedur und -
die Beschreibung der Parameter der Prozedur.
Prozedurparameter sind Datenobjekte, die der Übergabe von Werten an die Prozedur und der Rückgabe von (Ergebnis-) Werten an die aufrufende Einheit (Modul oder Prozedur) dienen.
219
2. Sprachstandard
Der auf den Prozedurkopf folgende Prozedurrumpf enthält: - die Beschreibung der lokalen Objekte der Prozedur und -
die Anweisungen, die beim Aufruf der Prozedur ausgeführt werden.
Lokale Objekte einer Prozedur sind Typen, Konstanten, Variablen und Prozeduren, die nur im Innern der Prozedur verwendet werden und daher nach außen nicht „sichtbar" sind. Die für die Deklaration von Prozeduren gültige Syntax ist etwas umfänglicher. Daher wird sie in mehreren Definitionsschritten präsentiert. Das erste, in Abbildung 2.67 angegebene Syntaxdiagramm beschreibt den Grundaufbau einer Prozedur-Deklaration. Erläuterungen des Diagramms enthält die Tabelle 2.110.
Procedure
Declaration
Procedure Heading—\Block\-(ËNd)-
¡dent
Abb. 2.67: Syntaxdiagramm für „ProcedureDeclaration".
Tab. 2.110: Beschreibung des Syntaxdiagramms für „ProcedureDeclaration". (1) Eine Prozedurdeklaration wird mit einem Prozedurkopf eingeleitet, dem ein Block, das Schlüsselwort END, der Prozedurname und ein Strichpunkt folgen. (2) Der Bestandteil Block ist bereits aus dem Syntaxdiagramm „Module" bekannt (siehe Abbildung 2.18). Eine Prozedur hat also den gleichen inneren Aufbau wie ein Modul. Im obigen Syntaxdiagramm ist lediglich der Bestandteil „Prozedurkopf' noch unbekannt. Den Aufbau eines Prozedurkopfes beschreibt das Syntaxdiagramm in Abbildung 2.68. Die dazugehörigen Erläuterungen enthält die Tabelle 2.111.
2.5.6.1 Prozedur-Deklaration
220
Procedure
He a ding
PROCEDURE)—\ldent
X
Formal Parame fers
Abb. 2.68: Syntaxdiagramm für „ProcedureHeading" (Prozedurkopf).
Tab. 2.111: Beschreibung des Syntaxdiagramms für „ProcedureHeading". (1) Ein Prozedurkopf beginnt mit dem Schlüsselwort PROCEDURE, dem entweder der Prozedurname und ein Strichpunkt, oder der Prozedurname, formale Parameter und ein Strichpunkt folgen. (2) Formale Parameter stellen Beschreibungen von Werte- und Variablenparametern dar.
Enthält der Prozedurkopf keine Parameterangaben, so stellt die deklarierte Prozedur keine Funktionsprozedur dar. Um welche Prozedurart es sich handelt, legen die Parameterangaben fest. Die formalen Parameter werden in Form einer Liste spezifiziert. Den Aufbau des Parameterteils beschreibt das Syntaxdiagramm in Abbildung 2.69. Erläuterungen zu dem Diagramm enthält die Tabelle 2.112.
Abb. 2.69: Syntaxdiagramm für „Formalparameters" (formale Parameter).
2. Sprachstandard
221
Tab. 2.112: Beschreibung des Syntaxdiagramms für „Formalparameters". (1) Der formale Parameterteil besteht aus einer Liste formaler Parameter, der im Falle einer Funktionsprozedur ein Doppelpunkt und der Name eines bereits bekannten Datentyps folgen müssen. Fehlen die beiden letztgenannten Angaben, so liegt keine Funktionsprozedur vor. (2) Die formale Parameterliste ist eine in runde Klammern eingeschlossene Liste einzelner durch Strichpunkt getrennter Parameterabschnitte. (3) Wird durch die Angabe eines Doppelpunkts und eines Datentyps eine Funktionsprozedur deklariert, so sind die mit der Prozedur ermittelten Funktionswerte von dem angegebenen Datentyp.
Ein Parameterabschnitt kann Angaben zu einem oder mehreren Parametern des gleichen Typs enthalten. Außerdem ist in einem Parameterabschnitt die Parameterart (Werte- oder Variablenparameter) festzulegen. Für einen Parameterabschnitt gilt das in Abbildung 2.70 dargestellte Syntaxdiagramm. Die dazugehörigen Erläuterungen enthält die Tabelle 1.113.
ParamSection
Abb. 2.70: Syntaxdiagramm für „ParamSection" (Parameterabschnitt).
Tab. 2.113: Beschreibung des Syntaxdiagramms für „ParamSection". (1) Ein Parameterabschnitt besteht aus einer Liste von Bezeichnern, der ein Doppelpunkt und die Angabe eines Datentyps folgen. Die Bezeichnerliste enthält den Namen eines formalen Parameters oder durch Kommata getrennte Namen mehrerer formaler Parameter. Der oder die Parameter sind von dem angegebenen Datentyp.
222
2.5.6.1 Prozedur-Deklaration
(2) Wird der Bezeichnerliste das Schlüsselwort VAR vorangestellt, so stellen die in der Liste benannten Parameter Variablenparameter dar. Andernfalls handelt es sich um Werteparameter. (3) Ein Werteparameter (ohne vorangestelltes VAR) dient lediglich der Übergabe eines Wertes an eine Prozedur beim Aufruf der Prozedur. Übergebene Werte können insbesondere auch Konstanten und Ausdrücke sein. (4) Ein Variablenparameter (mit vorangestelltem VAR) dient sowohl der Übergabe des Wertes der Variablen an eine Prozedur, als auch der Rückgabe des durch die Ausführung der Prozedur veränderten Variablenwertes an die aufrufende Einheit. Nur Variablen können als Variablenparameter deklariert werden.
Das Syntaxdiagramm in Abbildung 2.71 gibt an, in welcher Weise der Typ der formalen Parameter eines Parameterabschnitts zu deklarieren ist. Eine Beschreibung des Diagramms enthält die Tabelle 2.114.
FormalType
Abb. 2.71: Syntaxdiagramm für „FormalType".
Tab. 2.114: Beschreibung des Syntaxdiagramms für „FormalType". (1) Der Datentyp der formalen Parameter eines Parameterabschnitts wird durch einen qualifizierten Bezeichner festgelegt. (2) Werden dem qualifizierten Bezeichner die Schlüsselwörter ARRAY OF vorangestellt, so stellen die formalen Parameter Felder mit beliebiger Anzahl von Elementen („offene Felder") dar. In diesem Fall beginnt die Indizierung der Elemente eines Feldes mit dem Indexwert Null (siehe hierzu Kapitel 2.3.2.1 „Feldtypen").
2. Sprachstandard
223
Beispiel M O D U L E Scheine; (* *) (* In diesem Programm sollen exemplarisch Prozedurdeklarationen gezeigt werden. Die zu den Prozedurdeklarationen gehörenden Prozedur aufrufen werden im nächsten Kapitel behandelt. Inhaltlich geht es bei diesem Programm um die Ermittlung der Geldstücke, die bei der Auszahlung eines ganzzahligen Geldbetrages erforderlich sind. Das vollständige Programm ist am Ende von Kapitel 2.3.1 abgedruckt. In diesem Programmbeispiel wird der Grundblbliotheks-Modul InOut sowie der BibliotheksModul Screen (zur Ansteuerung des Bildschirms) verwendet. *) (*
*>
(*
*)
(* Deklarationen (* Import-Deklarationen
*) *)
(* F R O M InOut F R O M Screen
*>
IMPORT WriteString, WriteLn, WriteCard, ReadCard, Read; IMPORT ClearScreen;
(*
*)
(* Variablenvereinbarung c VAR Tausender, Fuenfhunderter, Hunderter: Fuenfziger, Zwanziger, Zehner Fuenfer, Zweier, Einer Betrag : space ZahlOk :
*)
CARDINAL CARDINAL CARDINAL CARDINAL CHAR; BOOLEAN;
(*
*)
(* Prozedurdeklaration 1 (* Unterprogramm zum Einblenden von Programminformationen p P R O C E D U R E TitelO; BEGIN (*
*) *) *)
*)
(* Löscht den Bildschirminhalt *) *) (* ClearScreenQ; WriteStringC Dieses Programm ermittelt die Anzahl und Geldstücke, die bei Auszah-'); WriteLn (); WriteStringC lung eines ganzzahligen Geldbetrages erforderlich sind.'); WriteLn(); Read (space) E N D Titel;
224 (* (* Prozedurdeklaration 2 (* Unterprogramm zur Eingabe des Betrages (*
Beispiel *) *) *) .)
PROCEDURE EingabeO; BEGIN WriteStringC Geben Sie einen Geldbetrag bis max. 65535 DM ein! - = >'); ReadCard(Betrag); WriteLnO; WriteLnO; IF (Betrag > =0) AND (Betrag < = 65535) THEN ZahlOk: =TRUE ELSE WriteLnO; WriteStringC Dieser Betrag ist zu groß!'); WriteLnO; ZahlOk: = FALSE END END Eingabe;
(*
*)
(* Prozedurdeklaration 3 (* Unterprogramm zur Berechnung der Anzahl der Geldscheine (*
*) *) *)
PROCEDURE BerechnungO; BEGIN IF ZahlOk THEN Tausender = Betrag DIV100G ;Betrag: = Betrag MOD 1000 Fuenfhunderter = Betrag DIV 500; Betrag: = Betrag MOD 500; Hunderter = Betrag DIV 100; Betrag: = Betrag MOD 100; Fuenfziger = Betrag DIV 50; Betrag: = Betrag MOD 50; Zwanziger = Betrag DIV 20; Betrag: = Betrag MOD 20; Zehner = Betrag DIV 10; Betrag: = Betrag MOD 10; Fuenfer = Betrag DIV 5; Betrag: = Betrag MOD 5; Zweier = Betrag DIV 2; Betrag: = Betrag MOD 2; Einer = Betrag DIV 1 END END Berechnung;
(*
(* Prozedurdeklaration 4 (* Unterprogramm zur Ausgabe der berechneten Daten (* PROCEDURE AusgabeO; BEGIN IF ZahlOk THEN WriteStringC Tausend-Mark-Scheine: '); WriteCard(Tausender, 5); WriteLnO; WriteStringC Fünfhundert-Mark-Scheine: ');
*)
*) *) *)
2. Sprachstandard
225
WriteCard(Fuenfhunderter, 5); WriteLnO; WriteStringC Einhundert-Mark-Scheine: '); WriteCard(Hunderter, 5); WriteLnO; WriteStringC Fünfzig-Mark-Scheine: '); WriteCard(Fuenfziger, 5); WriteLnO; WriteStringC Zwanzig-Mark-Scheine: '); WriteCard (Zwanziger, 5); WriteLnO; WriteStringC Zehn-Mark-Scheine: '); WriteCard (Zehner, 5); WriteLnO; WriteStringC Fünf-Mark-Stücke: '); WriteCard (Fuenfer, 5); WriteLnO; WriteStringC Zwei-Mark-Stücke: '); WriteCard (Zweier, 5); WriteLnO; WriteStringC Ein-Mark-Stücke: '); WriteCard (Einer, 5); WriteLnO END E N D Ausgabe;
(* Programmanweisungen BEGIN
-*) *) -*)
E N D Scheine.
2.5.6.2 Prozedur-Aufruf Ein Prozedur-Aufruf (engl, procedure call) stellt eine Anweisung dar und kann als solche an jeder Stelle einer beliebigen Anweisungsfolge auftreten. Der Aufruf einer Prozedur bewirkt die Ausführung der in der Prozedur deklarierten Anweisungsfolge. Nach der Ausführung wird die Verarbeitung mit der auf den Prozeduraufruf folgenden Anweisung fortgesetzt. Eine Prozedur kann in einem Programm beliebig oft, auch mehrfach nacheinander, aufgerufen werden.
226
2.5.6.2 Prozedur-Aufruf
Der Programmteil, in dem eine Prozedur aufgerufen wird, sei als Instanz bezeichnet. Instanzen können Programm-Module, globale Module, lokale Module und andere Prozeduren sein. Prozeduren können innerhalb der aufrufenden Instanz definiert sein. Ist eine Prozedur nicht innerhalb der aufrufenden Instanz deklariert, so liegt der Fall des Imports einer Prozedur aus einem globalen Modul vor. Der Aufruf einer Prozedur kann mit dem Transfer von Datenobjekten zwischen aufrufender Instanz und Prozedur verbunden sein. Bei parameterlosen Prozeduren findet eine solche Parameterübergabe nicht statt. Bei Prozeduren mit formalen Parametern hängt der Mechanismus der Parameterübergabe davon ab, ob Werte- oder Variablenparameter übergeben werden. In beiden Fällen sind die formalen, bei der Deklaration der Prozedur angegebenen Parameter von den aktuellen Parametern zu unterscheiden. Aktuelle Parameter sind Datenobjekte (Ausdrücke bzw. Werte, Konstanten, Variablen) der aufrufenden Instanz, mit denen eine Prozedur aufgerufen wird. Bei der Niederschrift eines Prozeduraufrufs sind also aktuelle Parameter anstelle entsprechender formaler Parameter in die Parameterliste einzusetzen. Nach diesen Vorbemerkungen kann nun angegeben werden, wie eine Prozedur aufzurufen ist. Den Aufbau eines Prozeduraufrufs beschreibt das Syntaxdiagramm in Abbildung 2.72. Die dazugehörigen Erläuterungen finden sich in der Tabelle 2.115.
Abb. 2.72: Syntaxdiagramm für „PROCEDURE-Call" (Prozeduraufruf). Tab. 2.115: Beschreibung des Syntaxdiagramms für „PROCEDURE-Call". (1) Eine parameterlose Prozedur wird durch die Nennung des Prozedurnamens aufgerufen. Dem Prozedurnamen kann eine leere Parameterliste, bestehend aus einem Paar runder Klammern, nachgestellt werden. (2) Der Aufruf einer Parameterprozedur (Prozedur mit nicht leerer Parameterliste) beginnt mit dem Prozedurnamen, dem eine öffnende runde Klammer, eine Liste von durch Kommata getrennten, aktuellen Parametern und eine schliessende runde Klammer folgen.
2. Sprachstandard
227
(3) Beim Aufruf einer Parameterprozedur in der Parameterliste aufgeführte aktuelle Werteparameter (ohne vorangestelltes VAR) müssen mit den entsprechenden formalen Parametern verträglich sein. Ein aktueller und ein formaler Werteparameter sind verträglich, wenn sie Datentypen angehören, die die Zuweisung des Wertes des aktuellen Parameters zu dem formalen Parameter gestatten. (4) Beim Aufruf einer Parameterprozedur in der Parameterliste aufgeführte aktuelle Variablenparameter (mit vorangestelltem VAR) müssen vom gleichen Datentyp sein wie die entsprechenden formalen Parameter. Als aktuelle Variablenparameter sind nur Variablen zulässig.
Erwähnt wurde bereits, daß der Mechanismus der Parameterübergabe durch die Parameterart (Werte- oder Variablenparameter) bestimmt wird. Die Übergabe eines Werteparameters ist in Modula-2 wie folgt organisiert: Stellt der beim Prozeduraufruf eingesetzte aktuelle Parameter einen Ausdruck dar, so wird der Ausdruck erst ausgewertet. Der ermittelte Wert wird nun dem formalen Parameter zugewiesen. Ein aktueller Parameter, der eine als Wert gegebene Konstante oder eine Variable darstellt, wird dem formalen Parameter unmittelbar zugewiesen. Das Zuweisen geschieht in Form eines Kopiervorgangs, d.h. es wird eine Kopie des aktuellen Parameters erzeugt, auf die bei der Ausführung der Prozedur zugegriffen wird. Ein Zugriff auf die Kopie erfolgt stets dann, wenn im Anweisungsteil der Prozedur der formale Parameter verwendet wird. Mit dem einmaligen Kopiervorgang ist die Wertübertragung abgeschlossen. Wird bei der Ausführung von Anweisungen der Wert des formalen Parameters (d.h. die Kopie) verändert, so hat das keine Rückwirkung auf den aktuellen Parameter - sein Wert wird nicht verändert. Ein Werttransfer findet also nur in einer Richtung statt: von der aufrufenden Instanz hin zur aufgerufenen Prozedur. Anders stellt sich der Werttransfer bei der Übergabe eines Variablenparameters dar: Der Prozeduraufruf bewirkt die Übergabe der Adresse des aktuellen Parameters an die Prozedur. Eine Kopie des aktuellen Parameters wird also nicht erzeugt. Mittels der Adresse kann bei der Ausführung der Prozedur auf den aktuellen Parameter zugegriffen werden. Ein Zugriff auf den aktuellen Parameter erfolgt stets dann, wenn der formale Parameter in der Prozedur benutzt wird. Führt die Nutzung zu einer Änderung des formalen Parameters, so wird auch der Wert des aktuellen Parameters in der aufrufenden Instanz verändert. Ein Werttransfer findet also in beiden Richtungen statt:
2.5.6.2 Prozedur-Aufruf
228
-
Bei der erstmaligen Nutzung des formalen Parameters nach dem Prozeduraufruf wird der Wert des aktuellen Parameters übernommen (Übergabe von der aufrufenden Instanz an die gerufene Prozedur).
-
Nach Ausführung der Prozedur besitzt der aktuelle Parameter den (veränderten) Wert des formalen Parameters (Übergabe von der gerufenen Prozedur an die rufende Instanz).
Ausdrücklich sei darauf hingewiesen, daß dieser Übergabemechanismus auf Parameter beschränkt ist, die Variablen darstellen. Eine Erweiterung auf Konstanten und Ausdrücke wäre wegen der Rückwirkung auf den aktuellen Parameter (Änderung) gar nicht möglich. Beim Anlegen einer Kopie des aktuellen Parameters tritt diese Rückwirkung nicht auf. Als Werteparameter dürfen daher neben Variablen auch Konstanten und Ausdrücke übergeben werden. Beim Aufruf einer Prozedur werden alle lokalen Datenobjekte der Prozedur in einem speziellen Arbeitsspeicherbereich, dem Stack, angelegt, und es werden die Prozeduranweisungen in der Reihenfolge ihres Auftretens ausgeführt. Beachtung sollte finden, daß die Werte lokaler Variablen beim Anlegen im Speicher Undefiniert sind. Bei Ausführung der Prozedur müssen ihnen also erst Werte zugewiesen werden, bevor sie lesend benutzt werden können. Solange die Prozedur aktiv ist, werden die lokalen Datenobjekte im Speicher gehalten. Insbesondere gilt eine Prozedur auch dann als aktiv, wenn während ihrer Ausführung eine andere - von ihr gerufene Prozedur abgearbeitet wird. Es können also mehrere Prozeduren gleichzeitig aktiv sein. Mit Abarbeitung der letzten Prozeduranweisung wird die Kontrolle an die rufende Instanz zurückgegeben. Gleichzeitig werden alle lokalen Datenobjekte der Prozedur gelöscht und der durch diese Datenobjekte belegte Speicherbereich (im Stack) wird freigegeben. Für den Programmieranfänger ist der Kontrollfluß, d.h. die Folge der ausgeführten Anweisungen, vor allem dann nicht sofort transparent, wenn Prozeduren ineinander geschachtelt sind. Dies liegt auch an dem Unterschied zwischen der statischen Niederschrift eines Programmteils und dem dadurch bewirkten dynamischen Ablauf (Kontrollfluß). Betrachtet werde das in Abbildung 2.73 dargestellte Beispiel. Auf der linken Seite ist die statische Niederschrift und auf der rechten Seite der Kontrollfluß zu sehen. Die Verarbeitung beginnt mit der Ausführung von „anweisungsfolge-ml" im Rumpf des Moduls „berechnung". Es folgt der Aufruf der Prozedur „tarifl". Der Kontrollfluß verzweigt zu Prozedur „tarifl". Nach Ausführung von „anweisungsfol-
2. Sprachstandard
229
ge-tl" wird die Kontrolle an den Modul zurückgegeben und die Verarbeitung mit „anweisungsfolge-m2" fortgesetzt. Den weiteren Kontrollfluß entnehme man der grafischen Darstellung.
MODULE
berechnung;
PROCEDURE t a r i f 1; tarif!
BEGIN anweisungsfolge-t1 ; END t a r i f 1;
PROCEDURE t a r i f 2 ;
PROCEDURE g r u p p e ; gruppe
BEGIN anweisungsfolge-g END g r u p p e ; Jarif2 BEGIN anweisungsfolge-t21 ; gruppe; anweisungsfolge-t22; END t a r i f 2 ;
BEGIN anweisungsfolge-m 1 ; tariti; anweisungsfolge-m2; t a r i f 2; anweisungsfolge-m3;
START
END b e r e c h n u n g . ENDE
Abb. 2.73: Statische Niederschrift und Kontrollfluß.
230
Beispiel
Beispiel MODULE Scheine; (* (* In diesem Programm sollen exemplarisch Prozeduraktivierungen gezeigt werden. Es wird das gleiche Beispiel verwendet, wie am Ende des Kapitels 2.5.6.1. Inhaltlich geht es bei diesem Programm um die Ermittlung der Geldstücke, die bei der Auszahlung eines ganzzahligen Geldbetrages erforderlich sind. Das vollständige Programm Ist am Ende von Kapitel 2.3.1 abgedruckt. In diesem Programmbeispiel wird der Grundbibliotheks-Modul InOut sowie der BibliotheksModul Screen (zur Ansteuerung des Bildschirms) verwendet. (*
*> *)
(*
(* Deklarationen (* Import-Deklarationen (* FROM InOut IMPORT WriteString, WriteLn, WriteCard, ReadCard, Read; FROM Screen IMPORT ClearScreen; (* (* Variablenvereinbarung (* VAR Tausender, Fuenfhunderter, Hunderter Fuenfziger, Zwanziger, Zehner Fuenfer, Zweier, Einer space ZahlOk
*) *) *)
CARDINAL; CARDINAL; CARDINAL; CARDINAL; CHAR; BOOLEAN;
(* (* Prozedurdeklaration 1 (* Unterprogramm zum Einblenden von Programminformationen (* PROCEDURE TitelO; BEGIN
END Titel;
(* (* Prozedurdeklaration 2 (* Unterprogramm zur Eingabe des Betrages (* PROCEDURE EingabeQ;
*) *) *) *)
2. Sprachstandard
231
BEGIN
END Eingabe;
(*
*)
(* Prozedurdeklaration 3 (* Unterprogramm zur Berechnung der Anzahl der Geldscheine (*
*) *) *)
PROCEDURE BerechnungO; BEGIN
END Berechnung;
(* (* Prozedurdeklaration 4 (* Unterprogramm zur Ausgabe der berechneten Daten (*
*) *) *)
*)
PROCEDURE AusgabeO; BEGIN
END Ausgabe;
(*
*)
(* Programmanweisungen
*)
(* BEGIN (*
*) *)
(* Aktivierung Prozedur Titel (*
*) *)
Titel (); (* (* Aktivierung Prozedur Eingabe (*
*) *) *)
Eingabe(); (* (* Aktivierung Prozedur Berechnung (*
*) *) *)
232
Programmbeispiel (16)
(.
*)
BerechnungO; (* Aktivierung Prozedur Ausgabe (*
*) *)
AusgabeO END Scheine.
Programmbeispiel (16) MODULE Scheine; (* (* Dieses Programm wurde gegenüber der ursprünglichen Implementierung in Kapitel 2.3.1 geändert, indem statt parameterlosen Prozeduren nun Prozeduren mit Parametern verwendet werden. Dadurch wird der Quelltext übersichtlicher und kompakter. In diesem Programmbeispiel wird der Grundbibliotheks-Modul InOut sowie der BibliotheksModul Screen (zur Ansteuerung des Bildschirms) verwendet. (* (* Import-Deklarationen (* FROM InOut IMPORT WriteString, WriteLn, WriteCard, ReadCard, Read; FROM Screen IMPORT ClearScreen; (* (* Variablenvereinbarung (* VAR Tausender, Fuenfhunderter, Hunderter Fuenfziger, Zwanziger, Zehner Fuenfer, Zweier, Einer Betrag space ZahlOk
CARDINAL CARDINAL CARDINAL CARDINAL CHAR; BOOLEAN
(* (* Unterprogramm zum Einblenden von Programminformationen PROCEDURE TitelO; BEGIN ClearScreenO; WriteStringC Dieses Programm ermittelt Art und Anzahl der Geldstücke, die bei'); WriteLn(); WriteStringC Auszahlung eines ganzzahligen Geldbetrages erforderlich sind. ') ; WriteLn (); WriteStringC '); Read (space); WriteLn () END Titel;
2. Sprachstandard
233
C (* Unterprogramm zur Eingabe des Betrages
*) *)
(*
.)
PROCEDURE Eingäbet); BEGIN WriteStringC Geben Sie einen Geldbetrag bis max. 65535 DM ein! - = >'); ReadCard(Betrag); WriteLnO; WriteLnO; IF (Betrag = >0) AND (Betrag < = 65535) THEN ZahlOk: =TRUE ELSE WriteLnO; WriteStringC Dieser Betrag ist zu groß!'); WriteLnO; ZahlOk: = FALSE END END Eingabe;
(* (* Unterprogramm zur Berechnung der Anzahl der Geldscheine
*) *) *)
PROCEDURE Berechnung(VAR Betrag, Zahl : CARDINAL; Divisor : CARDINAL); BEGIN Zahl : = Betrag DIV Divisor; Betrag : = Betrag MOD Divisor END Berechnung;
(*
(* Unterprogramm zur Ausgabe der berechneten Daten ( * * PROCEDURE Ausgabe(); BEGIN IF ZahlOk THEN WriteStringC Tausend-Mark-Scheine: '); WriteCard(Tausender, 5); WriteLnO; WriteStringC Fünfhundert-Mark-Scheine: '); WriteCard(Fuenfhunderter, 5); WriteLnO; WriteStringC Einhundert-Mark-Scheine: '); WriteCard(Hunderter, 5); WriteLn(); WriteStringC Fünfzig-Mark-Scheine: '); WriteCard(Fuenfziger, 5); WriteLnO; WriteStringC Zwanzig-Mark-Scheine: '); WriteCard (Zwanziger, 5); WriteLnO;
*> )
*)
234
Testergebnisse Programmbeispiel (16)
WriteStringC Zehn-Mark-Scheine: WriteCard (Zehner, 5); WriteLnO; WriteString(' Fünf-Mark-Stücke: WriteCard (Fuenfer, 5); WriteLnO; WriteStringC Zwei-Mark-Stücke: WriteCard (Zweier, 5); WriteLnO; WriteStringC Ein-Mark-Stücke: WriteCard (Einer, 5); WriteLnO END END Ausgabe;
');
');
');
');
(• (* Hauptprogramm
*) *)
(.
BEGIN TitelO; EingabeO IF ZahlOkTHEN Berechnung(Betrag, Berechnung(Betrag, Berechnung(Betrag, Berechnung(Betrag, Berechnung(Betrag, Berechnung(Betrag, Berechnung(Betrag, Berechnung(Betrag, Berechnung(Betrag, END; AusgabeO END Scheine.
Tausender, 1000); Fuenfhunderter, 500); Hunderter, 100); Fuenfziger, 50); Zwanziger, 20); Zehner, 10); Fuenfer, 5); Zweier, 2); Einer, 1)
Testergebnisse Programmbeispiel (16) Dieses Programm ermittelt Art und Anzahl der Geldstücke, die bei Auszahlung eines ganzzahligen Geldbetrages erforderlich sind.
Eingaben des Programms: Geben Sie einen Geldbetrag bis max. 65535 DM ein! - = > 671
235
2. Sprachstandard
Ausgaben des Programms: Tausend-Mark-Scheine: Fünfhundert-Mark-Scheine: Einhundert-Mark-Scheine: Fünfzig-Mark-Scheine: Zwanzig-Mark-Scheine: Zehn-Mark-Scheine: Fünf-Mark-Stücke: Zwei-Mark-Stücke: Ein-Mark-Stücke:
0 1 1 1 1 0 0 0 1
2.5.6.3 Funktionsprozeduren Implizit wurde der Zweck einer Funktionsprozedur bereits bei der Behandlung der RETURN-Anweisung angesprochen (siehe Kapitel 2.5.5). Eine Funktionsprozedur dient - wie eine mathematische Funktion - der Berechnung eines Funktionswertes. Ergebnis der Anwendung einer Funktionsprozedur ist also genau ein Wert. Für die Deklaration von Funktionsprozeduren gelten die in Kapitel 2.5.6.1 „Prozedur-Deklaration" gemachten Ausführungen. Funktionsprozeduren können mit und ohne formale Parameter deklariert werden. Im ersten Fall stellen die Parameter Argumente dar, die bei der Berechnung von Funktionswerten benötigt werden. Bei parameterlosen Funktionsprozeduren gehen keine übergebenen Argumente in die Berechnungsvorschrift ein. Für den Aufruf einer Funktionsprozedur gilt das in Kap. 2.5.6.2 „Prozedur-Aufruf' Gesagte. Zu beachten sind jedoch einige Besonderheiten: -
Der Aufruf einer Funktionsprozedur stellt im Gegensatz zum Prozeduraufruf keine eigentliche Modula-2-Anweisung dar. Da eine Funktionsprozedur stets explizit einen Funktionswert an die rufende Instanz zurückgibt, kann der Funktionsaufruf nur im Sinne eines Ausdrucks verwendet werden. Ein Funktionsaufruf muß deshalb auf der rechten Seite einer Wertzuweisung stehen, oder an einer Stelle, an der ein einzelner Wert erwartet wird (z.B. als aktueller Werteparameter in einem Prozeduraufruf).
-
Beim Aufruf einer parameterlosen Funktionsprozedur muß eine leere Parameterliste zwischen runden Klammern angegeben werden. Das Klammerpaar erlaubt es, den Aufruf einer parameterlosen Prozedur von einer Variablen zu unterscheiden.
236
Programmbeispiel (17)
-
Die Parameterübergabe beim Aufruf einer Funktionsprozedur spielt sich in der dargestellten Weise ab (siehe Kapitel 2.5.6.2). Jedoch muß im Fall einer Funktionsprozedur der Funktionswert als Ergebnis an die rufende Instanz übergeben werden. Dies geschieht mit Hilfe der RETURN-Anweisung. Um die Ergebnisübergabe sicherzustellen, muß eine Funktionsprozedur mindestens eine RETURN-Anweisung enthalten.
Die bei der Anwendung von Funktionsprozeduren zu beachtenden Besonderheiten sind in der Tabelle 2.116 nochmals als Regeln zusammengefaßt.
Tab. 2.116:
Bei der Benutzung von Funktionsprozeduren zu beachtende Regeln.
(1) Bei der Deklaration einer Funktionsprozedur müssen der formalen Parameterliste im Prozedurkopf ein Doppelpunkt und der Datentyp der Funktionsprozedur folgen. (2) Der Datentyp der Funktionsprozedur darf kein ARRAY-, SET- oder RECORD-Datentyp sein. (3) Jede Funktionsprozedur muß mindestens eine RETURN-Anweisung enthalten. (4) Eine RETURN-Anweisung weist dem Namen einer Funktionsprozedur einen Funktionswert zu. (5) Beim Aufruf einer parameterlosen Funktionsprozedur muß dem Prozedurnamen eine zwischen runden Klammern stehende leere Parameterliste folgen.
Programmbeispiel (17) MODULE MaxMin; (* *) (* Dieses Programm liest zwei ganze Zahlen ein und ermittelt, ob die Zahlen gleich sind. Ist dies nicht der Fall, so werden das Maximum und das Minimum berechnet und ausgegeben. Bei der Implementierung wurden Funktionsprozeduren vorteilhaft eingesetzt. In diesem Programmbeispiel wird der Grundbibliotheks-Modul InOut sowie der Bibliotheks*) (* Modul Screen (zur Ansteuerung des Bildschirms) verwendet. *)
2. Sprachstandard
237
e (* Import-Deklarationen
*) *)
(*
FROM InOut FROM Screen
*)
IMPORT WriteString, WriteLn, Read, Readlnt, Writelnt; IMPORT ClearScreen, Gotoxy;
(*
*)
(* Variablendeklarationen
*)
(*
VAR ZahU, Zahl2 : space
*)
INTEGER; CHAR;
( * * (* Unterprogramm zum Einblenden von Programminformationen
)
(*
PROCEDURETitelO; BEGIN
*)
*)
(*
*)
(* Löscht den Bildschirminhalt
*)
(*
*)
ClearScreenO; WriteStringC WriteLnO; WriteStringC WriteLnO; Read(space) END Titel;
Dieses Programm ermittelt aus zwei ganzen Zahlen das Maximum'); und das Minimum und gibt die beiden Extrema aus.');
(*
(* Unterprogramm zur Eingabe der ganzen Zahlen (*
*)
*)
*)
PROCEDURE EingabeO; BEGIN ClearScreenO; Gotoxy(10,12); WriteString('Geben Sie eine ganze Zahl an (-32768.-32767) ! - = >'); Readlnt (ZahU); Gotoxy(10,14); WriteString('Geben Sie noch eine ganze Zahl an (-32768.-32767) ! - = >'); Readlnt (Zahl2) END Eingabe;
(*
(* Funktionsprozedur zur Ermittlung der Gleichheit beider Zahlen (* PROCEDURE Samelnt(x, y : INTEGER) : BOOLEAN;
*) *) *)
BEGIN RETURN(x = y) END Samel nt;
(* (* Funktionsprozedur zur Ermittlung des Maximums beider Zahlen (* PROCEDURE Maxlnt(x, y : INTEGER) : INTEGER; BEGIN IFx > y T H E N RETURN(x) ELSE RETURN(y) END END Maxlnt;
(* (* Funktionsprozedur zur Ermittlung des Minimums beider Zahlen (* PROCEDURE Mlnlnt(x, y : INTEGER) : INTEGER; BEGIN IFx < y T H E N RETURN(x) ELSE RETURN(y) END END Minlnt;
(* (* Unterprogramm zur Ausgabe der Extrema der beiden (* (» eingelesenen Zahlen PROCEDURE BerechnungUndAusgabeO; BEGIN ClearScreenO; IF Samelnt(Zahl1, Zahl2) THEN Gotoxy(10,12); WriteString('Die beiden Zahlen sind identisch!') ELSE Gotoxy(10,12); WriteString('Das Maximum der Zahlen lautet'); Writelnt(Maxlnt(Zahl1, Zahl2), 5); WriteStringf .'); Gotoxy(10,14); WriteString('Das Minimum der Zahlen lautet'); Writelnt(Minlnt(Zahl1, Zahl2), 5); WriteStrlngC.') END END BerechnungUndAusgabe;
239
2. Sprachstandard
e
*)
(* (* Hauptprogramm
*) *)
BEGIN TitelO; EingabeO; BerechnungUndAusgabeO END MaxMin.
Testergebnisse Programmbeispiel (17) Dieses Programm ermittelt aus zwei ganzen Zahlen das Maximum und das Minimum und gibt die beiden Extrema aus.
Eingaben des Programms: Geben Sie eine ganze Zahl an (-32768-32767) ! Geben Sie noch eine ganze Zahl an (-32768..32767)!
- = >-100 - = > 4711
Ausgaben des Programms: Das Maximum der Zahlen lautet 4711 . Das Minimum der Zahlen lautet -100 .
2.5.6.4 Sichtbarkeitsbereiche von Objekten An einigen Stellen des bisher behandelten Stoffs war von lokalen Datenobjekten die Rede. So ist beispielsweise eine im Deklarationsteil einer Prozedur definierte Variable lokal vereinbart in Bezug auf die Prozedur. Die Variable kann in allen Anweisungen der Prozedur verwendet werden. Nun stellt sich die Frage, in welchen sonstigen Programmteilen diese Variable noch benutzt werden kann. Angesprochen ist damit der Sichtbarkeitsbereich oder Gültigkeitsbereich (engl, scope) der Variablen. Unter Sichtbarkeitsbereich sei hier der Bereich eines Programmes verstanden, in dem ein Objekt benutzt werden kann. Unter Objekt sind sowohl Datenobjekte (Werte bzw. Ausdrücke, Typen, Konstanten, Variablen), als auch Prozeduren zu verstehen. Datenobjekte können mit allen Anweisungen verarbeitet werden, die in ihrem Sichtbarkeitsbereich liegen. Prozeduren können an allen Stellen ihres Sichtbarkeitsbereichs aufgerufen werden. Wie weit der Sichtbarkeitsbereich für ein Objekt gezogen ist, hängt ab von:
2.5.6.4 Sichtbarkeitsbereiche von Objekten
240
-
der Objektart (Datenobjekt oder Prozedur) und
-
der Instanz in der das Objekt deklariert wurde.
Als Instanzen sind zu unterscheiden: Module, Prozeduren und innere Prozeduren. Eine im Deklarationsteil einer Prozedur A definierte Prozedur B ist eine innere Prozedur in Bezug auf A. Für den Sichtbarkeitsbereich von Prozeduren gilt nun: -
Sei B eine innere Prozedur von A, dann ist B nur innerhalb von A sichtbar. B kann also an allen Stellen des Anweisungsblocks von A aufgerufen werden. Außerhalb von A ist B nicht sichtbar und daher auch nicht aufrufbar.
-
Sei A eine Prozedur, die in einem Modul M definiert wurde, dann ist A innerhalb von M sichtbar. A kann folglich im gesamten Block von M benutzt werden. Inwieweit A auch außerhalb von M benutzt werden kann (Export der Prozedur A), wird in Kapitel 2.6 „Module" behandelt.
Bei der Behandlung des Sichtbarkeitsbereichs von Datenobjekten ist zwischen Datenobjekten ohne und mit gleichen Bezeichnern zu unterscheiden. Im Falle von Datenobjekten ohne gleiche Bezeichner gilt: -
Der Sichtbarkeitsbereich eines in einer Programmeinheit (Modul oder Prozedur) deklarierten Datenobjekts ist auf den Block der Programmeinheit beschränkt. Außerhalb der Einheit kann das Datenobjekt nicht benutzt werden (im Fall eines Moduls ist jedoch ein Export von Datenobjekten möglich, siehe hierzu Kapitel 2.6 „Module").
-
Ein in einer Programmeinheit deklariertes Datenobjekt ist im ganzen Innenbereich der Einheit sichtbar, d.h. im Falle eines Moduls also in allen Prozeduren des Moduls und im Falle einer Prozedur in allen inneren Prozeduren. Dies gilt auch bei mehrfach geschachtelten Prozeduren. Eine innere Prozedur kann somit Datenobjekte äußerer Prozeduren und des umgebenden Moduls benutzen.
Der Verdeutlichung der Sichtbarkeitsbereiche von Datenobjekten ohne gleiche Bezeichner dienen zwei Abbildungen. Abbildung 2.74 zeigt Sichtbarkeitsbereiche nicht geschachtelter Prozeduren, während Abbildung 2.75 den Fall geschachtelter Prozeduren illustriert. Eine separate Behandlung erfordern Datenobjekte mit gleichen Bezeichnern. In geschachtelten Prozeduren oder Funktionsprozeduren dürfen gleiche Bezeichner für verschiedene Objekte vergeben werden. Die mehrfache Vergabe von Bezeichnern hat jedoch eine Einschränkung des Sichtbarkeitsbereichs der betroffenen
2. Sprachstandard
241
MODULE A; VAR k : INTEGER;
PROCEDURE A I ; VAR i : INTEGER; BEGIN
Sichtbarkeitsbereich der Variablen i
END A 1 ;
Sichtbarkeitsbereich der Variablen k PROCEDURE A 2 ; VAR j : INTEGER; BEGIN
Sichtbarkeitsbereich der Variablen i
END A 2 ;
BEGIN
END A.
Abb. 2.74: Sichtbarkeitsbereiche von Datenobjekten bei gleichgeordneten Prozeduren.
242
2.5.6.4 Sichtbarkeitsbereiche von Objekten
MODULE A; VAR k : INTEGER;
PROCEDURE A 1 ; VAR i : INTEGER;
PROCEDURE
A11;
VAR j : INTEGER; BEGIN
END A 1 1 ;
Sichtbarkeitsbereich der Variablen i
Sichtbarkeitsbereich der Variablen i
Sichtbarkeitsbereich der Variablen k
BEGIN
END A 1 ;
BEGIN
END A.
Abb. 2.75: Sichtbarkeitsbereiche von Datenobjekten bei geschachtelten Prozeduren.
2. Sprachstandard
243
Objekte zur Folge. Wurde beispielsweise ein Objekt i sowohl in der Prozedur AI als auch in A l l , einer inneren Prozedur von AI, deklariert, so ist in A l l nur das in A l l selbst deklarierte Objekt i bekannt; das in AI definierte Objekt i kann dagegen nur innerhalb von AI, nicht aber in A l l verwendet werden. Die Deklaration des Objekts i in der inneren Prozedur A l l „verdeckt" die Deklaration des Objekts i in der äußeren Prozedur AI. Dieser Zusammenhang ist nochmals in der Abbildung 2.76 dargestellt.
MODULE A; VAR k : INTEGER;
PROCEDURE A 1 ; VAR i : INTEGER;
PROCEDURE A1 1; VAR i ; INTEGER; BEGIN
END A 1 1 ;
BEGIN
END A 1 ;
Sichtbarkeitsb e r e i c h der Variablen i der P r o z e d u r A1
Sichtbarkeitsbereich der Variablen i der P r o z e dur A1 1
Sichtbarkeitsbereich der Variablen k
Sichtbarkeitsbereich der Variablen i der P r o z e d u r A1
BEGIN
E N D A.
Abb. 2.76: Sichtbarkeitsbereiche von Objekten mit gleichen Bezeichnern innerhalb geschachtelter Prozeduren.
244
2. Sprachstandard
2.6 Module In Kapitel 1 „Modulare Sprachen" wurde bereits ein Überblick über die in der Programmiersprache Modula-2 verwendbaren Modularten (Programm-Module, lokale Module und globale Module) gegeben. Auch wurde grob skizziert, welchen Zwecken die einzelnen Modularten dienen und in welcher Weise Module miteinander kommunizieren. Um die Kommunikation zwischen Modulen erläutern zu können, wurde der grundsätzliche Aufbau eines Moduls beschrieben, und es wurde der Begriff der Benutzt-Beziehung eingeführt. Auf Programm-Module wurde in Kapitel 2.2 näher eingegangen. Gezeigt wurde, welchen Aufbau Programm-Module besitzen und wie sie syntaktisch korrekt zu definieren sind. Die Beispiele der auf Kapitel 2.2 folgenden Kapitel waren in erster Linie auch auf Programm-Module zugeschnitten. Im vorliegenden Kapitel stehen nun lokale und globale Module im Vordergrund. Gegenstand der Ausführungen sind: - die Deklaration lokaler und globaler Module, -
die Benutzung lokaler und globaler Module und
-
spezielle Aspekte und Probleme bei der Benutzung globaler Module.
Lokale Module betreffende Zusammenhänge werden in Kapitel 2.6.1 und globale Module in Kapitel 2.6.2 behandelt.
2.6.1 Lokale Module Ein lokaler Modul (auch innerer Modul genannt) ist ein vollständig in eine andere Programmeinheit eingebetteter Modul. Einzubetten ist ein lokaler Modul stets in den Block einer anderen Programmeinheit. Lokale Module können in allen Programmeinheiten, die einen Block aufweisen, auftreten, also in: - Programm-Modulen, -
globalen Modulen (jedoch nur in Implementationsmodulen, siehe Kapitel 2.6.2.1) und
-
Prozeduren und Funktionsprozeduren.
Ein lokaler Modul bildet in dem Block, in dem er deklariert wurde, eine abgekapselte Programmeinheit. Eine Kommunikation mit der den Modul umschließenden Programmeinheit ist nur über die Modul-Schnittstelle möglich. Einen lokalen Modul wird man dann in einen Block einbetten, wenn man bestimmte, eine Einheit bildende Implementierungsteile vom Rest des Blocks trennen möchte. Eine solche
245
2.6.1.1 Deklaration lokaler Module
Einheit kann insbesondere aus zusammengehörigen Datenobjekten und darauf operierenden Teilalgorithmen bestehen. Diese Objekte und Algorithmen sind in dem lokalen Modul gewissermaßen verborgen.
2.6.1.1 Deklaration lokaler Module Die Deklaration eines lokalen Moduls orientiert sich am Aufbau eines Moduls. Sie definiert folglich den Kopf, die Schnittstelle und den Rumpf des Moduls. Einzelheiten gehen aus dem Syntaxdiagramm in Abbildung 2.77 hervor. Erläuterungen des Syntaxdiagramms befinden sich in der Tabelle 2.117. Hinweis: Dieses Syntaxdiagramm wurde bereits in Abbildung 2.34 des Kapitels 2.2.1.5 dargestellt und dort auch erläutert (Tabelle 2.38). Um ein Hin- und Herblättern zu vermeiden, wird das Diagramm im vorliegenden Kapitel nochmals wiedergegeben und erläutert.
ModuleDeclara tion -—^MODULE)—I
Import
ldenl\-^—\
Ì
Priority
hy-Q»^
Export
Abb. 2.77: Syntaxdiagramm für „ModuleDeclaration" (Moduldeklaration).
Tab. 2.117: Beschreibung des Syntaxdiagramms für „ModuleDeclaration". (1) Die Deklaration eines lokalen Moduls beginnt mit einem Modulkopf, dem Import- und Exportdeklarationen, ein Block und eine Abschlußzeile folgen. (2) Ein Modulkopf wird eingeleitet mit dem Schlüsselwort MODULE, dem der Name des Moduls, wahlweise eine Prioritätsangabe und ein Strichpunkt folgen. Die Prioritätsangabe ist nur im Zusammenhang mit der Programmierung von Prozessen von Interesse.
2. Sprachstandard
246
(3) Ein Modul kann beliebig viele Import-Anweisungen enthalten. Import-Anweisungen beziehen sich stets auf die den Modul umschließende Programmeinheit; in Import-Anweisungen verwendete Bezeichner müssen in dieser Einheit bekannt sein. (4) Ein Modul kann beliebig viele Export-Anweisungen enthalten. Sinnvollerweise enthält er mindestens eine Exportanweisung. Diese Anweisung stellt sicher, daß der Modul von der ihn umschließenden Programmeinheit benutzt werden kann. (5) Der Block stellt den eigentlichen Modulinhalt (lokale Objekte und Anweisungen) dar. Das Syntaxdiagramm für Block wurde bereits früher angegeben (siehe Kapitel 2.2). (6) Die Abschlußzeile besteht aus dem Schlüsselwort END, dem Modulnamen und einem abschließenden Strichpunkt. Die Kommunikation eines lokalen Moduls mit der ihn umschließenden Programmeinheit wird über eine Schnittstelle abgewickelt. Definiert wird die Schnittstelle durch Import- und Export-Anweisungen. Die Import-Anweisungen geben an, welche Objekte (Datenobjekte, Prozeduren, Module) der Modul aus seiner Umgebung importiert. Dagegen beschreiben die Export-Anweisungen die Objekte, die der Modul an seine Umgebung abgibt. Das Syntaxdiagramm für Import-Anweisungen wurde bereits in Kapitel 2.2 angegeben und erläutert (siehe Abbildung 2.17 und Tabelle 2.21). Unterschieden wurden dort zwei Arten von Import-Deklarationen: - qualifizierter Import (Import-Anweisung ohne FROM) und -
nichtqualifizierter Import (Import-Anweisung mit FROM).
An dieser Stelle sei nochmals auf beide Arten von Import-Anweisungen eingegangen, um den Unterschied zwischen qualifiziertem und nichtqualifiziertem Import noch mehr zu verdeutlichen. Angaben zu einem Import ohne FROM enthält die Tabelle 2.118 und zu einem Import mit FROM die Tabelle 2.119.
Tab. 2.118: Syntaxregeln bei der Verwendung von Import ohne „FROM". (1) Eine IMPORT-Anweisung ohne FROM beginnt mit dem Schlüsselwort IMPORT, dem ein Bezeichner und ein Strichpunkt folgen.
2.6.1.1 Deklaration lokaler Module
247
(2) Der Bezeichner benennt das Modul, aus dem Objekte (Konstanten, Typen, Variablen, Prozeduren) benutzt werden können. (3) Wird ein Modul M l importiert, so können im importierenden Modul M2 alle Objekte benutzt werden, die der importierte Modul exportiert. Jedoch müssen die Objekte mit dem Namen des importierten Moduls qualifiziert werden. Sei B der Bezeichner eines von M l exportierten Objekts, dann kann in M2 das Objekt nur mittels der Qualifizierung Ml.B angesprochen werden.
Tab. 2.119: Syntaxregeln bei der Verwendung von Import mit „FROM". (1) Eine IMPORT-Anweisung mit FROM beginnt mit dem Schlüsselwort FROM, dem ein Modulname, eine Liste von durch Kommata getrennten Bezeichnern und ein Strichpunkt folgen. (2) Der Modulname bezeichnet den Modul, aus dem die in der Bezeichnerliste benannten Objekte (Konstanten, Typen, Variablen, Prozeduren) importiert werden. (3) Da der nach dem Schlüsselwort FROM angegebene Modulname die exportierende Programmeinheit benennt, ist eine Qualifikation der exportierten Objekte nicht erforderlich. Seien M l ein exportierender und M2 ein importierender Modul und B ein von M2 aus M l importiertes Objekt, dann kann mit Hilfe der Import-Anweisung „FROM M l IMPORT B;" in M2 der Bezeichner B ohne Qualifizierung (ohne vorangestellten Modulnamen Ml) benutzt werden. Die Export-Anweisung wurde in Kapitel 2.2 nicht behandelt, da sie in ProgrammModulen nicht auftritt. Den Aufbau einer Export-Anweisung beschreibt das Syntaxdiagramm in Abbildung 2.78. Erläuterungen des Diagramms enthält die Tabelle 2.120.
248
2. Sprachstandard
Tab. 2.120: Beschreibung des Syntaxdiagramms für „Export". (1) Eine EXPORT-Anweisung beginnt mit dem Schlüsselwort EXPORT, dem das Schlüsselwort QUALIFIED (wahlweise), eine Liste von durch Kommata getrennten Bezeichnern und ein Strichpunkt folgen. (2) Die Bezeichner benennen die Objekte (Konstanten, Typen, Variablen, Prozeduren), die in der den Modul umschließenden Einheit benutzt werden können. Die Bezeichner müssen in dem exportierenden Modul definiert sein; mit der EXPORT-Anweisung werden sie der umschließenden Einheit bekannt gemacht. (3) Enthält eine EXPORT-Anweisung das Schlüsselwort QUALIFIED, so liegt ein qualifizierter Export vor. In diesem Fall können die exportierten Bezeichner in der den Modul umschließenden Einheit mit dem Namen des exportierenden Moduls qualifiziert werden. Sei M l ein Modul, der den exportierenden Modul M2 umschließt, und sei B der Bezeichner eines von M2 exportierten Objekts, so bewirkt die Export-Anweisung „EXPORT QUALIFIED B;" in M2, daß das Objekt in M l mittels der Qualifizierung M2.B benutzt werden kann. (4) Fehlt in einer EXPORT-Anweisung das Schlüsselwort QUALIFIED, so liegt ein nicht qualifizierter Export vor. In diesem Fall können die exportierten Bezeichner in der importierenden Einheit ohne Qualifizierung angesprochen werden.
Der qualifizierte Export trägt einerseits zur Transparenz von Programmen bei (einem qualifizierten Bezeichner sieht man seine Herkunft an) und ermöglicht andererseits das Vermeiden von Bezeichnerkonflikten. So tritt ein Bezeichnerkonflikt beispielsweise nicht auf, wenn ein (qualifiziert) exportierter Bezeichner bereits in der umschließenden Einheit (für ein anderes Datenobjekt) verwendet wird. Die bisherigen Ausführungen zum qualifizierten und unqualifizierten Export bezogen sich auf Konstellationen, die aus einem lokalen Modul und einem den Modul umschließenden Block bestehen. Nun ist es aber denkbar, daß ein Block mehrere lokale Module umschließt. In solchen Fällen gelten folgende Besonderheiten:
Programmbeispiel (18)
-
Import- und Exportbeziehungen sind zwischen je zwei lokalen Modulen in beiden Richtungen möglich. Sei M ein Modul, der die beiden lokalen Module Ml und M2 umschließt, dann kann also M l Prozeduren von M2 importieren und umgekehrt.
-
Exportiert ein lokaler Modul seine Bezeichner qualifiziert, so kann ein im gleichen Block definierter weiterer lokaler Modul die Bezeichner sowohl qualifiziert als auch unqualifiziert importieren.
-
Exportiert ein lokaler Modul seine Bezeichner unqualifiziert, so kann ein im gleichen Block definierter weiterer lokaler Modul die Bezeichner sowohl unqualifiziert als auch qualifiziert importieren.
249
Programmbeispiel (18) MODULE LokModDemol; (. (* Dieses Programm zeigt, in welcher Reihenfolge lokale Module und der umschließende globale Modul initialisiert werden. Im vorliegenden Programmbeispiel wird erst Modul A, dann Modul B, Modul C und anschließend der Programm-Modul initialisiert. In diesem Programmbeispiel wird der Grundbibliotheks-Modul InOut verwendet. (*
*)
*) *)
*)
(* Import-Deklarationen des Programm-Moduls (*
FROM InOut
*)
*)
IMPORT WriteLn, WriteString;
(*
*>
(* lokaler Modul A (* MODULE A; (* (* Import-Deklarationen des lokalen Moduls A in Bezug auf den umliegenden Programm-Modul
*) *) *) *)
(*
*)
IMPORT WriteString, WriteLn; (*
*)
(* Anweisungen des lokalen Moduls A*) (*
*)
BEGIN WriteString('Module A wird Initialisiert!'); WriteLn () END A;
250
2. Sprachstandard
(* (* lokaler Modul B C MODULE B; (* (* Import-Deklarationen des lokalen Moduls B in Bezug auf den umliegenden Programm-Modul (*
*) *) *) *) *) *)
IMPORT WriteString, WriteLn; (.
*)
(* Anweisungen des lokalen Moduls B
*) *)
BEGIN WriteStrlngfModule B wird initialisiert!'); WriteLn 0 END B;
(* (* lokaler Modul C (* MODULE C; (* (* Import-Deklarationen des lokalen Moduls C aus dem umliegenden Programm-Modul (* IMPORT WriteString, WriteLn; (* (* Anweisungen des lokalen Moduls C (*
*) *) *) *) *) *) *) *) *)
BEGIN WriteStringfModule C wird initialisiert!'); WriteLnO END C;
(* (* Hauptprogramm (*
*) *) *>
BEGIN WriteString('Hauptprogramm wird initialisiert!'); WriteLnO END LokModDemol.
2.6.1.2 Benutzung lokaler Module Ein lokaler Modul kann in der ihn umschließenden Programmeinheit nicht - wie eine Prozedur - als Gesamtheit aufgerufen werden. Jedoch können die einzelnen, von ihm exportierten Objekte benutzt werden. Die Benutzung besteht in:
2.6.1.2 Benutzung lokaler Module
-
der Verwendung exportierter Konstanten, Typen und Variablen und
-
im Aufruf exportierter Prozeduren.
251
Die exportierten Objekte müssen bekanntlich im Deklarationsteil eines lokalen Moduls definiert sein. Dieser Deklarationsteil bildet zusammen mit einem Anweisungsteil (auch Modulrumpf genannt) den Block eines Moduls. Der Deklarationsteil kann weitere, nicht exportierte Objekte (lokale Objekte) enthalten, die durch Anweisungen des Rumpfs manipuliert werden. Der Modulrumpf kann jedoch auch leer sein. Nun stellt sich die Frage, wann die Anweisungen eines nicht leeren Modulrumpfes ausgeführt werden. Generell gilt: Die Anweisungen des Modulrumpfes werden vor den Anweisungen desjenigen Blocks ausgeführt, in dem der Modul definiert ist. In diesem Block werden die von dem Modul exportierten Objekte benutzt. Damit folgt auch: Die Anweisungen des Modulrumpfes werden ausgeführt, bevor die von dem Modul exportierten Objekte in der umschließenden Einheit benutzt werden. Mittels der Anweisungen des Modulrumpfes können exportierten Variablen somit Anfangswerte vor ihrer erstmaligen Nutzung zugewiesen werden. Die Definition von Anfangswerten in einem Modul nennt man auch Initialisierung des Moduls. Enthält der Rumpf eines Moduls keine Anweisungen, so entfällt eine Initialisierung. Ein solcher Modul kann durchaus ein sinnvoller Baustein sein. So kann beispielsweise der Deklarationsteil seines Blocks Prozeduren enthalten, die außerhalb des Moduls benötigt werden. Betrachtet wurde bislang nur die Benutzung und Initialisierung eines Moduls. Kompliziertere Verhältnisse ergeben sich, wenn: -
in einem Programm-Modul oder im Implementationmodul eines globalen Moduls mehrere lokale Module auftreten und wenn
-
ein lokaler Modul in einer Prozedur auftritt.
Im ersten Fall gilt: Die lokalen Module werden in der Reihenfolge initialisiert, in der sie im Block des sie umschließenden Moduls deklariert wurden. Außerdem werden die Module nur ein einziges Mal initialisiert. Andere Verhältnisse liegen im zweiten Fall vor: Ein von einer Prozedur umschlossener lokaler Modul wird bei jedem Aufruf der Prozedur (erneut) initialisiert. Zur Verdeutlichung obiger Aussagen sei das in Abbildung 2.79 dargestellte Schema eines Programm-Moduls P betrachtet.
2. Sprachstandard
252
MODULE M;
P R O C E D U R E P;
MODULE M1; BEGIN anweisungsfolgel ; END M1.
BEGIN anweisungsfolge2; END P;
MODULE M2;
MODULE M3; BEGIN anweisungsfolge3; END M3.
BEGIN anweisungsfolge4; END M2. BEGIN anweisungsfolgeô; END M.
Abb. 2.79: Schema eines Programm-Moduls.
2.6.1.2 Benutzung lokaler Module
253
Der Modul M repräsentiert ein Programm, dessen Ausführung wie folgt abläuft: Zu Beginn wird der Modul „M3" initialisiert und danach erst Modul „M2". „anweisungsfolge3" wird also vor „anweisungsfolge4" ausgeführt; beide Anweisungsfolgen werden nur einmal ausgeführt. Jetzt erst wird mit der Verarbeitung von „anweisungsfolge5" im Rumpf des Programm-Moduls „M" begonnen. Sobald im Rumpf von „M" die Prozedur „P" aufgerufen wird, erfolgt die Initialisierung des Moduls „Ml" (Ausführung von „anweisungsfolgel"). Erst nach dieser Initialisierung werden die Anweisungen der Prozedur („anweisungsfolge2") ausgeführt. Wird „P" im Rumpf von „M" erneut aufgerufen, so wird „Ml" erneut initialisiert und dann erst „anweisungsfolge2" ausgeführt. Zum Abschluß dieses Kapitels sei noch auf einige Besonderheiten hingewiesen, die bei der Benutzung lokaler Module zu beachten sind. Sie betreffen die Kommunikation lokaler Module mit ihrer Umgebung, die Sichtbarkeit von Objekten und die Übersetzung lokaler Module: -
Import- und Exportbeziehungen sind zwischen lokalen Modulen in beiden Richtungen möglich. Der Import von Daten und Prozeduren anderer lokaler Module sollte auf die Benutzung in exportierten oder inneren Prozeduren beschränkt sein und sich nicht auf die Benutzung im Initialisierungsteil des lokalen Moduls beziehen, da die Reihenfolge der Initialisierung lokaler Module streng vorgegeben ist.
-
Objekte, die in der einen lokalen Modul umschließenden Programmeinheit definiert wurden, sind in dem lokalen Modul nicht sichtbar.
-
Lokale Objekte eines lokalen Moduls sind in der den Modul umschließenden Programmeinheit nicht sichtbar. Lokale Module können nicht wie globale Module separat übersetzt werden.
-
Diese Besonderheiten unterscheiden lokale Module von globalen Modulen. Für globale Module gelten keine derart eingeschränkten Import- und Export-Beziehungen mit ihrer Umgebung, und globale Module sind separat übersetzbar. Außerdem ist auch ein Unterschied gegenüber Prozeduren feststellbar, der die Sichtbarkeit von Datenobjekten betrifft. Globale Datenobjekte eines Moduls sind in allen Prozeduren des Moduls sichtbar, und lokale Datenobjekte einer Prozedur sind in allen inneren Prozeduren dieser Prozedur sichtbar.
2. Sprachstandard
254
Programmbeispiel (19) MODULE LokModDemo2; (* (* Dieses Programm zeigt wie lokale Module innerhalb eines Programm- oder Implementationsmoduls Objekte und Dienstleistungen untereinander verwenden können. In diesem Programmbeispiel wird der Grundbibliotheks-Modul InOut verwendet. Modul A führt eine Prozedur A1 und eine Variable s unqualifiziert aus. Modul B verwendet die von Modul A exportierte Prozedur A1 und die Variable s, wobei von dem Modul selbst keine Bezeichner zur Verfügung gestellt werden. Modul C benutzt die von Modul A unqualifiziert exportierte Variable s und stellt eine unqualifiziert exportierte Prozedur C1 bereit. Modul D exportiert eine Prozedur D1 und eine Variable qualifiziert. Das Hauptprogramm kann nun die von Modul A bereitgestellte Prozedur A1 und die Variable s sowie die von Modul C zur Verfügung gestellte Prozedur C1 unqualifiziert importieren. Weiterhin sind die Prozedur D1 und die Variable w des Moduls D qualifiziert ansprechbar. Auffallend sind die Importanweisungen "WriteCard, WriteString, WriteLn" aus dem Programm-Modul, die in jedem lokalen Modul vor Benutzung der Bezeichner stehen müssen, da im Programm-Modul vereinbarte Bezeichner nicht automatisch im lokalen Modul bekannt sind. (*
*) *>
(*
*)
(* Import-Deklarationen des Programm-Moduls
*)
(*
*)
FROM InOut IMPORT WriteCard, WriteLn, WriteString; (*
*)
(* Variablendeklarationen des Programm-Moduls (* VAR z : CARDINAL;
*) *)
(*
*)
(* lokaler Modul A
*)
*>
MODULE A; (
*
*
(* Import-Deklarationen des lokalen Moduls A (* in Bezug auf den umliegenden Programm-Modul
)
(*
*) *) *)
IMPORT WriteCard, WriteString, WriteLn; (
*
'
(* Export-Deklarationen des lokalen Moduls A (*
)
*)
*)
EXPORT s, A1; (*
*)
(* Variablendeklarationen des lokalen Moduls A
*)
(*
*)
VAR s
:
CARDINAL;
(*
*)
(* Exportprozedur des lokalen Moduls A
*)
(*
*)
PROCEDURE A1();
255
Programmbeispiel (19) (* (** Variablendeklarationen der Prozedur A1
*) *) *)
(
VAR u : CARDINAL; (* (* Prozeduranweisungen (* BEGIN u: =8; WriteString('u:'); WriteCard(u, 5);WriteLnO END A1;
(* (* Anweisungen des lokalen Moduls A ( * * BEGIN s: = 10; WriteString('s:'); WriteCard (s, 5); WriteLnO ENDA;
*) *) *)
*) *) )
(* (* lokaler Modul B ( * * ) MODULE B; (* (* Import-Deklarationen des lokalen Moduls B in Bezug*) auf den umliegenden Programm-Modul und den lokalen Modul A (*
IMPORTS, A1; IMPORT WriteCard, WriteString, WriteLn; (* (* Variablendeklarationen des lokalen Moduls A (* VAR t : CARDINAL; (* (* Anweisungen des lokalen Moduls B (*
BEGIN t:=s-1; WriteString('t:'); A10 END B;
*) *)
*) *)
*) *)
*) *) *) *)
*)
WriteCard(t, 5); WriteLnO;
(* (* lokaler Modul C (* MODULE C; (* (* Import-Deklarationen des lokalen Moduls C in Bezug
*) *) *)
*)
2. Sprachstandard
256
(*
auf den umliegenden Programm-Modul und den lokalen Modul A
I M P O R T s; I M P O R T WriteCard, WrlteString, WriteLn; (*
*)
(* Export-Deklarationen des lokalen Moduls C ( * * E X P O R T C1; (* (* Variablendeklarationen des lokalen Moduls A (* VAR v (*
:
*) *)
*) )
CARDINAL;
*) *) *)
*)
(* Exportprozedur des lokalen Moduls C
*)
(* P R O C E D U R E C10; (* (* Variablendeklarationen der Prozedur C1
*>
(*
VAR x (*
:
CARDINAL;
(*
(*
*)
*)
WriteCard(x, 5); WriteLnO
(* Anweisungen des lokalen Moduls C (*
BEGIN v: = s-3; WriteString('v:'); E N D C;
-) *)
(* Prozeduranweisungen BEGIN x: = 5 ; WriteString('x:'); E N D C1;
*) *)
*) *)
.)
WriteCard(v, 5); WriteLnO
(* (* lokaler Modul D (* M O D U L E D; (* (* Import-Deklarationen des lokalen Moduls C in Bezug auf den umliegenden Programm-Modul und den lokalen Modul A (* I M P O R T WriteCard, WriteString, WriteLn; (*
*) *) *) *) *) *) *)
(* qualifizierte Exportdeklaration des lokalen Moduls D
*)
(* E X P O R T Q U A L I F I E D w, D1;
*)
Programmbeispiel (19)
257
(*
*)
(* Variablendeklarationen des lokalen Moduls D
*)
(*
VAR w : (*
*)
CARDINAL;
*)
(* Exportprozedur des lokalen Moduls D
*)
(*
*)
PROCEDURED10; (
*
(* Variablendeklarationen der Prozedur D1
*
(*
VAR y (*
:
CARDINAL;
*)
(*
(*
*)
*) *)
(* Prozeduranweisungen BEGIN y: =4; WriteStringCy: '); END D1;
)
*)
WriteCard(y, 5); WrlteLnO
*)
(* Anweisungen des lokalen Moduls D
*)
(*
*)
BEGIN w: =6; WriteString('w: '); END D;
WriteCard(w, 5);
WriteLnO
(*
*)
(* Anweisungen des Programm-Moduls (* BEGIN
*) ,)
(*
*)
(* unqualifizierte Benutzung
*)
(*
*)
C1(); (* (* qualifizierte Benutzung (* D.D1(); (* (* unqualifizierte Benutzung und qualifizierte Benutzung (*
z: = s-D.w-1; WriteString('z: '); WriteCard(z, 5); WriteLnO END LokModDemo2.
*) *) *) *) *)
2. Sprachstandard
258
2.6.2 Globale Module Auf die besondere Rolle globaler Module bei der Entwicklung komplexerer Softwaresysteme wurde bereits in den Kapiteln 1.2 „Module in Modula-2" und 1.5 „Struktur von Modula-2-Programmen" hingewiesen. Sie zeigt sich vor allem in folgenden Eigenschaften globaler Module: -
Wiederverwendbarkeit,
-
getrennte Übersetzbarkeit und
- Aufteilung in Definitions- und Implementationsmodule. Im Gegensatz zu Programm-Modulen sind globale Module keine selbständig lauffähigen Programme. Sie können nur in Verbindung mit Programm-Modulen benutzt werden. Andererseits sind sie nicht, wie lokale Module, starr in eine umschließende Programmeinheit eingebettet und nur in dieser Umgebung benutzbar. Globale Module stellen vielmehr mehrfach zugängliche Bausteine dar, deren Dienste Programm-Module und andere globale Module in Anspruch nehmen können. Globale Module lassen sich mehrfach, vor allem auch in verschiedenen Programmen, verwenden. Bei der Entwicklung größerer Programme erweist sich die getrennte Übersetzbarkeit von globalen Modulen als Vorteil. Jeder globale Modul, der als Baustein eines Modula-2-Programms konzipiert ist, kann separat und losgelöst von den anderen Programmbausteinen implementiert und compiliert (übersetzt) werden. Es ist also nicht erforderlich, jeweils das ganze Programm zu übersetzen und entsprechend hohe Übersetzungszeiten in Kauf zu nehmen. Weniger Aufwand verursachen auch Fehlerbehebungen und Programmänderungen. Die einen einzelnen Modul betreffenden Änderungen können separat durchgeführt und getestet werden, ohne daß bei jedem einzelnen Eingriff oder Test gleich das ganze Programm ausgeführt werden muß. Ermöglicht wird die getrennte Übersetzbarkeit durch die Aufteilung globaler Module in Definitions- und Implementationsmodule. Ein Definitionsmodul definiert die Schnittstelle eines globalen Moduls. Die Definition bezieht sich auf die Import- und Exportschnittstelle und spezifiziert: - die aus anderen Modulen importierten Datenobjekte und Prozeduren und -
die für andere Module bereitgestellten (exportierten) Datenobjekte und Prozeduren.
Ausführbare Programmteile enthält ein Definitionsmodul nicht. Er soll lediglich die Import-/Export-Beziehungen eines globalen Moduls dokumentieren. Aus diesen Beziehungen läßt sich ablesen, was der Modul leistet, aber nicht, wie diese Leistungen realisiert (implementiert) sind. Die Details der Realisierung, den eigentlichen
259
2.6.2.1 Deklaration globaler Module
Programmcode im Sinne ausführbarer Anweisungen, enthält der Implementationsmodul eines globalen Moduls. Im Implementationsmodul ist quasi das „wie" der Realisierung verborgen.
2.6.2.1 Deklaration globaler Module In diesem Kapitel wird zuerst die Vereinbarung von Definitionsmodulen und danach die Vereinbarung von Implementationsmodulen behandelt. Da ein Definitionsmodul nur die Import-/Exportschnittstelle eines globalen Moduls festlegt, enthält er keinen Block. Den Aufbau eines Definitionsmoduls beschreibt das Syntaxdiagramm in Abbildung 2.80. Die dazugehörigen Erläuterungen sind der Tabelle 2.121 zu entnehmen.
DefinitionModule •-( ç-
DEFINITION—( Import
MODULE )—| Ident Export
ldent\
Definition
O"
Abb. 2.80: Syntaxdiagramm für ,,DefinitionModule"(Definitionsmodul).
Tab. 2.121: Beschreibung des Syntaxdiagramms für „DefinitionModule". (1) Die Deklaration eines Definitionsmoduls beginnt mit einem Modulkopf, dem Importdeklarationen (wahlweise), eine Exportdeklaration und eine Abschlußzeile folgen. (2) Ein Modulkopf beginnt mit der Schlüsselwortfolge DEFINITION MODULE, der der Modulname und ein Strichpunkt folgen.
260
2. Sprachstandard
(3) Ein Definitionsmodul kann beliebig viele Import-Anweisungen enthalten. Import-Anweisungen bezeichnen die Objekte (Datenobjekte, Prozeduren), die im Definitionsteil zwar verwendet, aber im Definitionsmodul nicht definiert werden. Es sind dies die von dem globalen Modul importierten Objekte. (4) Ältere Modula-2-Compiler enthalten eine EXPORT-Anweisung, die aus dem Schlüsselwort EXPORT und einer Definitionsliste besteht. Die Definitionsliste gibt die Objekte (Konstanten, Typen, Variablen und Prozedurköpfe) an, die der globale Modul seiner Umgebung zur Verfügung stellt (exportiert). Bei neueren Modula-2-Compilern werden alle definierten Konstanten, Typen, Variablen, Prozeduren und Funktionsprozeduren automatisch exportiert. (5) Die Abschlußzeile besteht aus dem Schlüsselwort END, dem Modulnamen und einem abschließenden Punkt.
Für den Aufbau der Definitionsliste gilt das in Abbildung 2.81 dargestellte Syntaxdiagramm. Erläuterungen des Diagramms enthält die Tabelle 2.122.
Definition ConstDeclaration /—Type
Definition
\—*(TYPEj
d
Variable Declaration
Procedure Headin g Abb. 2.81: Syntaxdiagramm für „Definition".
Tab. 2.122: Beschreibung des Syntaxdiagramms für „Definition". (1) Mit einer Definition des Definitionsteils kann alternativ eine Liste von Konstanten, eine Liste von Typen, eine Liste von Variablen oder ein Prozedurkopf definiert werden.
261
2.6.2.1 Deklaration globaler Module
(2) Eine Liste von Datenobjekten beginnt mit einem Schlüsselwort (CONST, TYPE oder VAR), dem keine, eine oder mehrere Deklarationen bzw. Definitionen von Objekten (Konstanten, Typen oder Variablen) folgen können. (3) Die Definition einer Prozedur in einem Definitionsmodul besteht nur aus dem Prozedurkopf. Diese Angabe genügt, da sie sämtliche für die Benutzung der Prozedur benötigten Informationen enthält. Ein Implementationsmodul enthält die Realisierung (Implementierung) der von einem globalen Modul exportierten Prozeduren. Darüberhinaus kann er beliebig viele nicht exportierte (lokale) Prozeduren enthalten. Lokale Prozeduren werden in der Regel auch Datenobjekte manipulieren, die im zugehörigen Definitionsmodul nicht definiert wurden. Diese Datenobjekte müssen im Implementationsmodul definiert werden. Objekte, die im Definitionsmodul bereits (vollständig) deklariert wurden, dürfen im Implementationsmodul dagegen nicht mehr deklariert werden. Der Aufbau eines Implementationsmoduls entspricht dem eines ProgrammModuls. Ein Implementationsmodul ist jedoch, wie das Syntaxdiagramm in Abbildung 2.82 zeigt, durch ein zusätzliches Schlüsselwort zu kennzeichnen. Kommentare zu dem Syntaxdiagramm enthält die Tabelle 2.123.
ImplementationModule -(
IMPLEMENTATION
)—¡ProgramModule
Abb. 2.82: Syntaxdiagramm für „ImplementationModule".
Tab. 2.123: Beschreibung des Syntaxdiagramms für „ImplementationModule". Ein Implementationsmodul besteht aus einem Programm-Modul, dem das Schlüsselwort IMPLEMENTATION vorangestellt wird.
Definitions- und Implementationsmodule sind getrennt voneinander zu erstellen und zu übersetzen, bevor sie von anderen globalen Modulen oder ProgrammModulen benutzt werden können. Ein Definitionsmodul ist stets vor dem zugehörigen Implementationsmodul zu übersetzen. Beim Übersetzen eines Definitionsmoduls wird ein Zwischencode erzeugt, der zur Compilation des dazugehörigen Impie-
262
2. Sprachstandard
mentationsmoduls benötigt wird. Diesen Zwischencode können andere Module (globale Module oder Programm-Module) bereits vor der Implementierung und Übersetzung des zu dem Definitionsmodul gehörigen Implementationsmoduls verwenden. Beim Übersetzen eines Implementationsmoduls wird ebenfalls ein Zwischencode erzeugt. Dieser Zwischencode ist zum Binden eines Programm-Moduls erforderlich. Zur Compilation eines Programm-Moduls reichen die übersetzten Definitionsmodule aus.
2.6.2.2 Benutzung globaler Module Im Unterschied zu lokalen Modulen kann ein globaler Modul von beliebig vielen anderen (globalen) Modulen benutzt werden. Für die Art der Benutzung trifft das zu, was bereits zur Benutzung lokaler Module ausgeführt wurde. Auch ein globaler Modul kann nicht als Gesamtheit aufgerufen werden. Er kann nur durch die Verwendung exportierter Datenobjekte und durch den Aufruf exportierter Prozeduren benutzt werden. Der Implementationsmodul eines globalen Moduls enthält einen Block, der aus einem Deklarationsteil und einem Anweisungsteil (Modulrumpf) besteht. Dieser Anweisungsteil läßt sich, wie bei einem lokalen Modul, zur Initialisierung modulinterner (lokaler) Variablen verwenden. Die Initialisierung wird durchgeführt, wenn ein globaler Modul importiert wird. Importiert ein Programm-Modul M einen globalen Modul Ml, so wird der Rumpf von Ml vor dem Rumpf von M ausgeführt. Ein globaler Modul wird nur ein einziges Mal initialisiert. Importieren neben dem Programm-Modul M auch die globalen Module M2, M3 und M4 den Modul Ml, so wird Ml vor der Initialisierung der Module M, M2, M3 und M4 initialisiert. Unterstellt sei weiter, daß auch der Programm-Modul M die Module M2, M3 und M4 importiert. Die Reihenfolge der Initialisierung hängt nun auch von der in M angegebenen Importreihenfolge ab. Beispielsweise hat die Importanweisung „IMPORT M2;", „IMPORT M3;", „IMPORT M4;" und „IMPORT Ml;" die Initialisierungssequenz Ml, M2, M3, M4, M zur Folge. Ml muß zuerst initialisiert werden, da alle übrigen Module den Modul Ml importieren. M2, M3 und M4 werden in der Reihenfolge ihrer Nennung in der Importanweisung des Moduls M initialisiert.
2.6.2.3 Benutzung und Lebensdauer von Objekten Die Lebensdauer von Objekten ist nur für Variablen definiert. Sie gibt an, wie lange für Variablen während der Programmausführung Speicherplatz zur Verfügung steht. Nur solange können Variablen auch benutzt werden. Die Lebensdauer von
2.6.2.3 Benutzung und Lebensdauer von Objekten
263
Variablen hängt davon ab, in welchem Teil eines Modula-2-Programms die Variablen definiert wurden. In einem Programmteil definierte Variablen heißen bezüglich dieses Programmteils lokal. Zu unterscheiden sind drei Fälle: - lokale Variablen von Prozeduren, -
lokale Variablen von globalen Modulen und
-
lokale Variablen von lokalen Modulen.
Lokale Variablen einer Prozedur können nur in der Prozedur einschließlich der in dieser Prozedur vereinbarten inneren Prozeduren und inneren bzw. lokalen Module benutzt werden. Wird eine Prozedur aufgerufen, so wird ihren lokalen Variablen Speicherplatz in einem speziellen Bereich des Arbeitsspeichers, dem Stack, zugewiesen. Bei Beendigung der Prozedur wird den lokalen Variablen der Speicherplatz wieder entzogen. Die Lebensdauer von lokalen Variablen einer Prozedur erstreckt sich also jeweils vom Aufruf der Prozedur bis zum Ende der Prozedurausführung. Da eine Prozedur mehrfach aufgerufen werden kann, können die lokalen Variablen der Prozedur mehrere Lebensphasen - jeweils solange die Prozedur aktiv i s t - aufweisen. Lokale Variablen eines Programm-Moduls können nur im Programm-Modul selbst benutzt werden. Die Lebensdauer der Variablen erstreckt sich über die gesamte Aktivitätszeit des Programm-Moduls bzw. Modula-2-Programms. Die Lebensdauer beginnt also mit dem Programmstart und endet mit dem Programmende. Im Hinblick auf die Benutzung lokaler Variablen von Implementationsmodulen sind zwei Fälle zu unterscheiden: -
Lokale Variablen eines Implementationsmoduls, die im zugehörigen Definitionsmodul nicht als exportierte Variablen definiert wurden, können nur im Implementationsmodul benutzt werden.
-
Lokale Variablen, die im zugehörigen Definitionsmodul dagegen als exportierte Variablen definiert wurden, können darüberhinaus auch in allen Modulen benutzt werden, die diese Variablen importieren.
Lokale Variablen eines Implementationsmoduls leben während der gesamten Aktivitätszeit des Programm-Moduls, der diesen Modul direkt oder indirekt benutzt. Eine indirekte Benutzung liegt vor, wenn dieser Modul direkt von einem anderen Implementationsmodul benutzt wird, den der Programm-Modul verwendet. Die Lebensdauer ist also unabhängig davon, ob lokale Variablen eines Implementationsmoduls exportiert werden oder nicht.
264
2. Sprachstandard
Die Benutzbarkeit lokaler Variablen von lokalen Modulen hängt wie bei Implementationsmodulen davon ab, ob die Variablen exportiert werden: -
Nicht exportierte, lokale Variablen eines lokalen Moduls können nur in dem Modul benutzt werden.
-
Exportierte lokale Variablen eines lokalen Moduls können darüber hinaus auch in dem Block benutzt werden, der den Modul umschließt.
Für die Lebensdauer lokaler Variablen eines lokalen Moduls gilt generell: Die Lebensdauer endet, sobald der den lokalen Modul umschließende Block ausgeführt ist. Im einzelnen folgt daher: -
Die lokalen Variablen eines lokalen Moduls, der von einer Prozedur umschlossen wird, haben die gleiche Lebensdauer wie die lokalen Variablen der Prozedur.
-
Die lokalen Variablen eines lokalen Moduls, der von einem Programm-Modul umschlossen wird, haben eine Lebensdauer, die mit der Laufzeit des Programms identisch ist.
-
Die lokalen Variablen eines lokalen Moduls, der von einem Implementationsmodul umschlossen wird, sind nur dann lebensfähig, wenn der Implementationsmodul von einem Programm-Modul direkt oder indirekt benutzt wird. Ihre Lebensdauer ist dann mit der Laufzeit des Programms identisch.
2.6.2.4 Verbergen von Informationen Eine der wesentlichen Eigenschaften von Modulen ist es, über die Modulschnittstelle nur die Informationen bekannt zu geben, die zur Benutzung des Moduls erforderlich sind. Realisierungsdetails bleiben dagegen im Modulinneren verborgen. Dieses Konzept fördert die Wartbarkeit von Programmsystemen. Änderungen in einem Modul, die sich nicht in seiner Schnittstelle auswirken, haben nämlich keinerlei Konsequenzen für andere, diesen Modul benutzende Module. Im Sinne dieses Konzepts wird man bestrebt sein, die Schnittstellen von Modulen so schmal wie möglich zu halten und so wenig Modulinterna wie möglich nach außen bekannt zu geben.
2.6.2.4 Verbergen von Informationen
265
Stellt man einem Benutzer eines globalen Moduls neben dem Objektcode nur den Quellencode des Definitionsmoduls zur Verfügung und enthält man ihm den Quellencode des Implementationsmoduls vor, so kann er sämtliche exportierten Leistungen des Moduls nutzen. Er kennt jedoch nur den Aufbau der Datenobjekte und die Namen und Parameterlisten der Prozeduren, die im Definitionsmodul deklariert wurden (also Bestandteil der Modulschnittstelle sind). Verborgen bleiben ihm sämtliche im Implementationsmodul deklarierten Datenobjekte und die Realisierung (der Code) der Prozeduren. Denkbar ist nun noch eine weitergehende Form des Verbergens, die bei der Realisierung abstrakter Datentypen verwendet wird und dem „information hiding" im strengeren Sinne entspricht. Diese Form besteht im Export eines „undurchsichtigen" (engl, opaque) Datentyps und von auf diesen Datentyp anwendbaren Prozeduren. Ein undurchsichtiger Datentyp liegt dann vor, wenn lediglich der Typname, nicht aber die Struktur des Datentyps bekannt gegeben wird. In Modulen, die einen undurchsichtigen Datentyp importieren, können Variablen von diesem Datentyp kreiert und mit den auf diesen Datentyp (im exportierenden Modul) definierten Prozeduren manipuliert werden. Modula-2 unterstützt das Konzept undurchsichtiger Datentypen. Ein undurchsichtiger Datentyp kann mittels der Typdefinition im Definitionsmodul eines globalen Moduls definiert werden. Das Syntaxdiagramm für die Typdefinition, die auch zur Vereinbarung anderer (also nicht undurchsichtiger) Datentypen dient, ist in Abbildung 2.83 dargestellt. Erläuterungen zu dem Diagramm enthält die Tabelle 2.124.
TypeDefinition
Abb. 2.83: Syntaxdiagramm für „TypeDefinition" (Typdefinition).
Tab. 2.124: Beschreibung des Syntaxdiagramms für „TypeDefinition". (1) Eine Typdefinition beginnt mit einem Namen, dem ein Gleichheitszeichen, die Beschreibung des Typs und ein Strichpunkt folgen.
266
2. Sprachstandard
(2) Werden Gleichheitszeichen und Beschreibung des Typs nicht angegeben, so liegt ein undurchsichtig exportierter (opaquer) Datentyp vor. Im Definitionsmodul eines globalen Moduls wird also nur der Name eines undurchsichtigen Datentyps bekannt gegeben. Die vollständige Deklaration des Datentyps ist im dazugehörigen Implementationsmodul vorzunehmen.
In globalen Modulen, die einen undurchsichtigen Datentyp importieren, können Variablen dieses Typs definiert und manipuliert werden. Die Manipulation dieser Variablen unterliegt allerdings folgenden Beschränkungen: -
Die Variablen können nur mit Prozeduren verarbeitet werden, die auf den undurchsichtigen Datentyp definiert und zusammen mit ihm zu importieren sind.
-
Darüberhinaus können die Variablen nur in Wertzuweisungen und in Vergleichsoperationen (Prüfung auf Gleichheit und Ungleichheit) verwendet werden.
Realisiert werden undurchsichtige Datentypen in Modula-2 mit Hilfe des Zeigerkonzepts (siehe Kapitel 2.7 „Dynamische Datenstrukturen"). Ein im Definitionsmodul eines globalen Moduls angegebener undurchsichtiger Datentyp ist grundsätzlich vom Datentyp POINTER (also ein Zeigertyp). Wie in Kapitel 2.7 noch dargelegt wird, ist einem Datentyp POINTER stets ein sogenannter Elementardatentyp zugeordnet. Als Elementardatentyp kommen alle bisher behandelten Datentypen in Frage. Der Datentyp POINTER repräsentiert eine Verweisadresse, die auf ein Objekt des zugeordneten Elementardatentyps verweist. Eine Variable, die vom Typ eines importierten undurchsichtigen Datentyps ist, stellt also eine Verweisadresse (einen Zeiger) auf ein Datenobjekt des zugeordneten Elementardatentyps dar. Mittels dieser Verweisadresse kann nur auf das Datenobjekt insgesamt, nicht aber auf seine Komponenten zugegriffen werden. Die Struktur des Datenobjekts bleibt in dem Implementationsmodul verborgen, in dem der Elementardatentyp deklariert wird.
2.6.2.5 Getrennte Übersetzung und Versionskonflikte Auf die getrennte Übersetzbarkeit globaler Module in Modula-2 wurde bereits in Kapitel 2.2 kurz eingegangen. Hingewiesen wurde dort, wie auch an anderen Stellen, auf einige Vorteile dieses Konzepts. Das vorliegende Kapitel geht etwas näher auf die getrennte Übersetzung ein. Dabei stehen vor allem Abhängigkeiten zwischen Modulen bei der Übersetzung und sogenannte Versionskonflikte im Vordergrund.
2.6.2.5 Getrennte Übersetzung und Versionskonflikte
267
Bei der getrennten Übersetzung globaler Module ist nach Definitions- und Implementationsmodulen zu unterscheiden. Der Definitions- und der Implementationsmodul eines globalen Moduls werden nicht gemeinsam, sondern jeweils für sich übersetzt. Bei der Übersetzung eines Implementationsmoduls überprüft der Compiler insbesondere auch, ob die Schnittstelle des Moduls mit den Schnittstellen der importierten Module verträglich ist. Damit eine Konsistenzprüfung der Schnittstellen möglich ist, müssen bei der Übersetzung eines Implementationsmoduls folgende übersetzten Definitionsmodule vorliegen: -
der zu dem Implementationsmodul gehörige übersetzte Definitionsmodul und
-
sämtliche übersetzten Definitionsmodule der globalen Module, die der Implementationsmodul importiert.
Die Übersetzung von Modulen eines Softwaresystems unterliegt also gewissen Reihenfolgebeschränkungen. Diese lassen sich dem zugrundeliegenden Moduldiagramm entnehmen. Betrachtet werde das in Abbildung 2.84 dargestellte Beispiel eines Moduldiagramms.
Abb. 2.84: Beispiel eines Moduldiagramms.
2. Sprachstandard
268
Neben dem Programm-Modul M enthält das Diagramm die vier globalen Module M l , M2, M3 und M4. Der Definitionsmodul eines globalen Moduls Mi sei mit DMi und der Implementationsmodul mit IMi bezeichnet. Für die Übersetzung des Programm-Moduls und der Implementationsmodule gilt dann: IM4
kann übersetzt werden, wenn D M 4 übersetzt ist.
IM3
kann übersetzt werden, w e n n D M 3 übersetzt ist.
IM2
kann übersetzt werden, wenn DM4, D M 3 und D M 2 übersetzt sind.
IM1
kann übersetzt werden, wenn D M 3 und D M 1 übersetzt sind.
M
kann übersetzt werden, wenn alle Definitionsmodule übersetzt sind.
In einer geeigneten Reihenfolge übersetzte Module (globale Module und Programm-Module) stellen noch kein lauffähiges Programm dar. Sie müssen erst vom Binder (engl, linker) zu einem lauffähigen Programmsystem gebunden werden. Beim Prozeß des Bindens können Versionskonflikte auftreten. Ursache von Versionskonflikten sind „unverträgliche Versionen" übersetzter Module, die allerdings nicht bei allen Modula-2-Compilern in gleicher Weise auftreten. Manche Modula-2-Compiler weisen jedem übersetzten Definitionsmodul einen speziellen Schlüssel zu. Der Schlüssel wird im erzeugten Symbolfile, dem Ergebnis der Übersetzung eines Definitionsmoduls, gespeichert. Er besteht beispielsweise aus Datum und Uhrzeit und identifiziert eindeutig einen übersetzten Definitionsmodul. Wird ein Definitionsmodul erneut übersetzt, so erhält der identifizierende Schlüssel einen neuen Wert (da sich zumindest die Uhrzeit geändert haben wird) und identifiziert eine neue Version des übersetzten Definitionsmoduls. Dies gilt insbesondere auch dann, wenn der Definitionsmodul seit der letzten Übersetzung nicht geändert wurde. Modula-2-Compiler der angesprochenen Art benutzen identifizierende Schlüssel bei der Übersetzung von Implementationsmodulen als Referenzen. Ergebnis der Übersetzung eines Implementationsmoduls ist ein sogenannter Linkfile. Im Linkfile legt der Compiler die Schlüssel der (übersetzten) Definitionsmodule ab, die zur Übersetzung des Implementationsmoduls benötigt werden. Der Übersetzung eines Implementationsmoduls liegt folglich für jeden der importierten Module eine eindeutig referenzierte Version des übersetzten Definitionsmoduls zugrunde. Auf eben diese Versionen greift der Binder zurück. Beim Binden werden die im Linkfile eines jeden Implementationsmoduls eingetragenen Schlüssel mit den entsprechenden, in den Symbolfiles der importierten Module gespeicherten Schlüsseln verglichen. Stimmen die Schlüssel nicht überein, so liegt ein Versionskonflikt vor, und der Bindeprozeß wird unterbrochen.
2.6.2.5 Getrennte Übersetzung und Versionskonflikte
269
Meist wird man einen Definitionsmodul nur nach der Vornahme von Änderungen erneut übersetzen. Ein Versionskonflikt droht jedoch bereits dann, wenn ein Definitionsmodul bei unverändertem Text nur erneut übersetzt wird. Um dies zu vermeiden, müssen sämtliche Module, die diesen Definitionsmodul benutzen, neu übersetzt werden. Auf diese Weise wird die Konsistenz der Schnittstellen der interagierenden Module vor allem auch bei komplexen, aus sehr vielen Modulen bestehenden Programmen völlig zweifelsfrei sichergestellt. Zur Unterstützung aufwendiger Übersetzungsarbeiten bieten einige Hersteller von Modula-2-Compilern ein spezielles Werkzeug, meist MAKE genannt, an. Dieses Werkzeug dient der automatischen Übersetzung der Definitions- und Implementationsmodule, die beispielsweise von der Änderung eines Definitionsmoduls im Sinne eines Versionskonflikts betroffen sind. Abgeschlossen werden soll dieses Kapitel mit einem Beispiel, in dem in größerem Umfang als bei den bisherigen Beispielen die Dienste von GrundbibliotheksModulen genutzt werden. Darüberhinaus werden auch einige BenutzerbibliotheksModule verwendet. Wie sich hier zeigt, stellen Benutzerbibliotheks-Module sinnvolle Erweiterungen von Grundbibliotheks-Modulen dar. Eine Übersicht über die in dem Programmbeispiel benutzten globalen Module beider Bibliotheken gibt das in Abbildung 2.85 dargestellte Moduldiagramm.
Abb. 2.85: Moduldiagramm des Programmbeispiels 20.
270
2. Sprachstandard
Verwendet werden demnach die Grundbibliotheks-Module InOut, ReallnOut und Strings sowie der aus den bisher behandelten Programmbeispielen bereits bekannte Benutzerbibliotheks-Modul Screen. Screen stellt Routinen zur Ansteuerung des Bildschirms, wie Löschen des Bildschirminhaltes (ClearScreen), Setzen der Schreibmarke (Gotoxy) usw. zur Verfügung. Folgende weitere BenutzerbibliotheksModule werden in diesem Beispiel verwendet (in Abbildung 2.85 wurden jeweils nur die ersten 8 Stellen der Modulnamen angegeben): -
Keys: exportiert Konstanten zur Ansteuerungen der Tastatur, wie z.B. Funktionstasten, Cursortasten, ESCAPE-Taste, INSERT-Taste, DELETE-Taste usw.
-
StringUtility: enthält erweiterte Operationen auf Zeichenketten.
-
WMen: definiert Datentypen zur Verarbeitung von Window-Menüs.
-
WMemory: stellt Dateioperationen zum Lesen und Schreiben von auf externen Speichern abgelegten Window-Menüs zur Verfügung.
-
WMenMan: Manager zur Verwaltung (Aufruf, Einblenden und Dialogverarbeitung) von Window-Menüs. Die Window-Menüs werden in der PopUp-Technik dargestellt und auf dem Bildschirm überlagert.
-
WMenMemo: Manager zur Verwaltung aller im Programmsystem verwendeten Window-Menüs.
Programmbeispiel (20) MODULE Lager; (*
*)
(* Dieses Programm verwaltet Artikel eines Lagersystems. Die Artikel können interaktiv eingelesen und nach verschiedenen Suchkriterien (Bezeichnung, Nummer, Bestand) sortiert auf dem Bildschirm ausgegeben werden. Implementiert wurde dieses Beispiel unter Verwendung von globalen Modulen. So werden wie im Beispiel 6 (Kap. 2.3.2.6) die globalen Grundbibliotheks-Module InOut, ReallnOut und Strings verwendet. Darüberhinaus werden weitere Bibliotheks-Module benötigt. Hierbei handelt es sich um die Module Keys, StringUtility, WMen, WMemory, WMenMan und WMenMemo. *) *) (* (*
*)
(* Import-Deklarationen des Programm-Moduls
*)
( FROM InOut
*
* ) IMPORT Write, WriteString, WriteLn, WriteCard, Read, ReadString,
Programmbeispiel (20)
FROM FROM FROM FROM FROM FROM FROM FROM FROM
InOut Keys ReallnOut Screen Strings Stringlltility WMen WMemory WMenMan
FROM WMenMemo (*
271
IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT
ReadCard; ESC; ReadReal, WrlteReal; ClearScreen, Gotoxy; Compare; AsslgnString, MakeString; Menues, Selected, Selections; GetWMenue; ShowMenueWindow, RefreshMenueWindow, InteractiveMenueWindow; IMPORT Menue;
*)
(* Konstantendeklarationen (*
*) *)
CONST MaxLagerArtikel (*
.)
= 100;
(* Typendeklarationen
*)
(*
*)
TYPE LagerArtikel = [1.. MaxLagerArtikel]; Text = ARRAY [0 .30] OF CHAR; Artikel = RECORD Bezeichnung Text; Nummer : CARDINAL; Gewicht : REAL; BestandsMenge : CARDINAL; Beschaffung (gas, fluessig, fest); Verpackung (kiste, karton, sack, fass); END; LagerBestand = ARRAY LagerArtikel OF Artikel; SortierKriterium = PROCEDURE (Artikel, Artikel):BOOLEAN; (* (* Variablendeklarationen (* VAR LagerBestaende MaxArtikel space
(*
LagerBestand; CARDINAL; CHAR;
(* Unterprogramm zum Einblenden von Programminformationen (* P R O C E D U R E TitelO; BEGIN (* (* Löscht den Bildschirminhalt (* WriteStringC Dieses Programm liest Artikelsätze mit maximal 100 Artikeln ein,');
*)
*) *)
*) *) *)
2. Sprachstandard
272
WriteLnO; WriteStringC sortiert diese wie gewünscht nach Artikelnummern, Artikelbezeichnungen'); WriteLn(); WriteStringC oder nach Artikelbeständen und gibt diese dann wieder'); WriteStringC auf dem Bildschirm aus.'); WriteLn(); Read(space) E N D Titel;
(*
*)
(* Unterprogramm zum Aufbau des Bildschirmmenüs. Dieses Bildschirmmenü ist als PopUp-Menü Implementiert.
*)
P R O C E D U R E BildschirmO; VAR resulti 0 Selected; Abbruchl0 : CHAR; BEGIN (* (* Blendet Menü ein (*
*) *) *)
(*
ShowMenueWindow(1 ) ; REPEAT (* (* Lichtbalken springt in das Menü (* lnteractiveMenueWindow(1, resultIO, AbbruchIO); IF Abbruch10< > C H R ( E S C ) T H E N C A S E resultl 0 O F first: (* AuswahM *) ArtlkelKartenErstellen(); | second : (* Auswahl2 *) IF MaxArtikel > 0 T H E N ArtikelSortieren(SortBezeichnung) ELSE Fehler() END | third : (* Auswahl3 *) IF MaxArtikel > 0 T H E N ArtikelSortieren(SortNummern) ELSE Fehler() END | fourth : (* Auswahl4 *) IF MaxArtikel > O T H E N ArtikelSortieren(SortBestandsMege) ELSE FehlerO END | fifth : (* Auswahl5 *) IF MaxArtikel > O T H E N ArtikelDurchBlaettern();
*)
*) *) *)
Programmbeispiel (20)
273
ELSE Fehler() END ELSE END; ClearScreen(); (* (* erneutes Aktivieren des Menüs (* END (*
(* E S C = Menüabbruch (* UNTIL AbbruchlO = CHR(ESC) E N D Bildschirm;
*) *) *)
*) *)
(*
*)
(* Unterprogramm zum Lesen und Abspeichern des Menüs (* P R O C E D U R E SpeicherMenueAufbau(Datei : STRINGPathNameSize; Number ; CARDINAL; VAR ok : BOOLEAN); VAR Window : Selections; Status: INTEGER; BEGIN ok: = FALSE; Status: =0; GetWMenue(Datei, Window, Status); IF Status = 0 THEN INC(Menue.CountOfActualMenues); Menue.AIIMenues[Menue.CountOfActualMenues].Data: = Window; Menue.AIIMenues[Menue.CountOfActualMenues].ActualSelection: = 1 ELSE WriteString('Menu Artikelverwaltung nicht vorhanden!') END; ok: = Status = 0 E N D SpeicherMenueAufbau;
*) *)
(* (* Unterprogramm zur Prüfung, ob Menü auf Speichermedium vorhanden ist
*) *)
P R O C E D U R E SucheMenue():BOOLEAN; VAR ok1 : BOOLEAN; Datei : ARRAY [0..63] OF CHAR; BEGIN MakeStringC ', 63, Datei); AssignString('Art1.WME', Datei); SpeicherMenueAufbau(Datei, 1, ok1); RETURN(okl) END SucheMenue;
(* Unterprogramm zum Aufruf des Bildschirmmenüs, falls vorhanden PROCEDURE StartO; VAR ok : BOOLEAN; BEGIN ok: = SucheMenueO ; IF ok THEN BildschirmO END END Start; (* (* Unterprogramm zum Einlesen von Artikelkarteikarten (* PROCEDURE ArtikelKartenErstellenO; VAR ArtikelCheck: BOOLEAN; i : CARDINAL; (* PROCEDURE Beschaff(i: CARDINAL); VAR help : ARRAY [0..10] OF CHAR; found : BOOLEAN; BEGIN found: = FALSE; REPEAT Gotoxy(10,11); WriteString('BESCHAFFUNG: - = >'); ReadString(help); IF (Compare(help, 'gas') = 0) OR (Compare(help, 'GAS') = 0) OR (Compare(help, 'Gas') = 0) THEN LagerBestaende[i]. Beschaffung: = gas; found: =TRUE ELSIF (Compare(help, 'fluessig') = 0) OR (Compare(help, 'FLUESSIG') = 0) OR (Compare(help, 'Fluessig') = 0) THEN LagerBestaende[i] .Beschaffung: - fluessig; found: = TRUE ELSIF (Compare(help, 'fest') = 0) OR (Compare(help, 'FEST') = 0) OR (Compare(help, 'Fest') = 0) THEN LagerBestaende [i]. Beschaffung: - fest; found: = TRUE ELSE Write(CHR(7)) END UNTIL found END Beschaff;
Programmbeispiel (20)
P R O C E D U R E Verpack(i: CARDINAL); VAR help : ARRAY [0..10] OF CHAR; found : BOOLEAN; BEGIN found: = FALSE; REPEAT Gotoxy(10,12); WriteString('VERPACKUNG: - = >'); ReadString(help); IF (Compare(help, 'kiste') = 0) OR (Compare(help, 'KISTE') = 0) OR (Comparejhelp, 'Kiste') = 0) THEN LagerBestaende[i].Verpackung: = kiste; found: = TRUE ELSIF (Compare(help, 'karton') = 0) OR (Compare(help, 'KARTON') = 0) OR (Compare(help, 'Karton') = 0) THEN LagerBestaende[i].Verpackung: = karton; found: = TRUE ELSIF (Compare(help, 'sack') = 0) OR (Compare(help 'SACK') = 0) OR (Compare(help, 'Sack') = 0) THEN LagerBestaende[i].Verpackung: = sack; found: = T R U E ELSIF (Compare(help, 'fass') = 0) OR (Compare(help, 'FASS') = 0) OR (Compare(help, 'Fass') = 0) THEN LagerBestaende[i].Verpackung:=fass; found: = T R U E ELSE Write(CHR(7)) END UNTIL found E N D Verpack;
(* BEGIN ArtikelCheck: = FALSE; ClearScreen(); REPEAT Gotoxy(10,12); WriteString('Geben Sie die Anzahl der einzulesenden Artikel an (1 ..100)! — = >'); ReadCard (MaxArtikel); IF (MaxArtikel> =0) AND (MaxArtikel< =100) THEN ArtikelCheck: = TRUE; FOR i: = 1 TO MaxArtikel DO ClearScreenO; Gotoxy(10,6); WriteString('ARTI KEL'); WriteCard(i, 3); Gotoxy(10,7); WriteString('BEZEICHNUNG: - = >'); ReadString(LagerBestaende[i]. Bezeichnung); Gotoxy(10,8);
275
276
2. Sprachstandard
WriteString('NUMMER: - = >'); ReadCard(LagerBestaende[i].Nummer); Gotoxy(10,9); WriteStringCGEWICHT: - = >'); ReadReal (LagerBestaende[i] .Gewicht) ; Gotoxy(10,10); WriteString('BESTANDSMENGE: - = >'); ReadCard(LagerBestaende[i]. BestandsMenge) ; Beschaff (i); Verpack(i); Read(space) END ELSE Write(CHR(7)) END UNTIL ArtikelCheck END ArtikelKartenErstellen; (*
*)
(* Unterprogramm zum Sortieren der Artikel nach Bezeichnung
*)
(*
*)
PROCEDURE SortBezeichnung(Artikel1, Artikel2 : Artikel):BOOLEAN; BEGIN RETURN(Compare(Artikel1.Bezeichnung,Artikel2.Bezeichnung) > 0) END SortBezeichnung;
(
*
*
(* Unterprogramm zum Sortieren der Artikel nach Nummer (*
)
*)
*)
PROCEDURE SortNummern(Artikel1, Artikel2 : Artikel) : BOOLEAN; BEGIN RETURN(Artikel1.Nummer > Artlkel2.Nummer) END SortNummern;
(*
*)
(* Unterprogramm zum Sortieren der Artikel nach Bestand
*)
(*
*)
PROCEDURE SortBestandsMenge(Artikel1, Artikel2 : Artikel):BOOLEAN; BEGIN RETURN(Artikel1.BestandsMenge > Artikel2.BestandsMenge) END SortBestandsMenge; (*
*)
(* Unterprogramm zum Sortieren der Artikel
*)
(*
PROCEDURE ArtikelSortieren(Sortiere : SortierKriterium); VAR i, j ; CARDINAL; x Artikel;
*)
Programmbeispiel (20)
277
BEGIN F O R i: = 2 T O MaxArtikel D O F O R j: = MaxArtikel T O i B Y - 1 D O IF Sortiere(LagerBestaende[j-1], LagerBestaende[j]) T H E N x: = LagerBestaende[j-1]; LagerBestaende[j-1]: = LagerBestaende[j]; LagerBestaende[j]: = x END END END E N D ArtikelSortieren;
(
*
*
(* Unterprogramm zum Durchblättern der Artikel (* P R O C E D U R E ArtikelDurchBlaetternO; VAR i : CARDINAL; BEGIN F O R i: = 1 T O MaxArtikel D O ClearScreen(); Gotoxy(10,6); WriteString('ARTIKEL'); WriteCard(i, 3); Gotoxy(10,7); WriteString('BEZEICHNUNG: - = > ' ) ; WriteString(LagerBestaende[i].Bezeichnung); Gotoxy(10,8); WriteString('NUMMER: - = >'); WriteCard(LagerBestaende[i].Nummer,5); Gotoxy(10,9); WriteStringCGEWICHT: - = >'); WriteReal (LagerBestaende[i] .Gewicht, 13) ; Gotoxy(10,10); WriteString('BESTANDSMENGE: - = > ' ) ; WriteCard(LagerBestaende[i].BestandsMenge,5); Gotoxy(10,11); WriteString('BESCHAFFUNG: - = > ' ) ; IF LagerBestaende[i].Beschaffung = gas T H E N WriteString('gas') E L S I F LagerBestaende[i].Beschaffung=fluessig T H E N WriteString('fluessig') E L S I F LagerBestaende[i].Beschaffung=fest T H E N WriteString('fest') END; Gotoxy(10,12); WriteString('VERPACKUNG: - = >'); IF LagerBestaende[i].Verpackung = kiste T H E N WriteString('kiste') E L S I F LagerBestaende[i].Verpackung = karton T H E N WriteString('karton') E L S I F LagerBestaende[i].Verpackung = sack T H E N
) *) *)
278
2. Sprachstandard
WriteString('sack') ELSIF LagerBestaende[i].Verpackung=fass THEN WriteString('fass') END; Read (space) END END ArtikelDurchBlaettern; (* (* Unterprogramm zum Ausgeben von Fehlermeldungen (* PROCEDURE FehlerO; BEGIN Gotoxy(10, 22); WriteString('Keine Artikel zum Sortieren vorhanden!'); Write(CHR(7)); Read (space); Gotoxy(10, 22) END Fehler; (* (* Hauptprogramm ( BEGIN MaxArtikel: =0; Titel 0; StartO END Lager.
*) *) *
*
)
Testergebnisse Programmbeispiel (20) Dieses Programm liest Artikelsätze mit maximal 100 Artikeln ein, sortiert diese wie gewünscht nach Artikelnummern, Artikelbezeichnungen oder nach Artikel beständen und gibt diese dann wieder auf dem Bildschirm aus.
Anzeigen des Bildschirmmenüs: ARTIKELVERWALTUNG Einlesen Bezeichnung-Sort Nummer-Sort Bestand-Sort Ausgeben Eingabe nach der Auswahl „E" oder durch Cursorauswahl und ENTER:
Eingaben des Programms: Geben Sie die Anzahl der einzulesenden Karten an (1.. 100)! - = > 3
Testergebnisse Programmbeispiel (20)
279
ARTIKEL 1 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
— = > BIRNEN - = >1 - = >0.1 - = >1000 — = > FEST — = > KISTE
ARTIKEL 2 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
- = >ÄPFEL - = >2 - = >0.2 - = >3000 — = > FEST — = > KARTON
ARTIKEL 3 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
— = >ERDBEEREN - = >3 - = >0.05 - = >2000 — = >FLUESSIG — = > KISTE
Ausgabe nach Auswahl „B", „A"oder durch Cursorauswahl und ENTER:
Ausgabe nach der Auswahl „D", „A" oder durch Cursorauswahl und ENTER:
ARTIKEL 2 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
- = >ÄPFEL - = >2 - = > 2.00000-001 - = > 3000 - = >fest — = > karton
ARTIKEL 1 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
— = > BIRNEN - = >1 - = > 1.00000E-001 - = > 1000 - = >fest — = > kiste
ARTIKEL 1 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
— = > BIRNEN - => 1 - = > 1.00000-001 - = > 1000 — = >fest — = > kiste
ARTIKEL 2 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
— = >ERDBEEREN - => 3 - = > 5.00000E-002 - = > 2000 - = >fluessig - = > kiste
ARTIKEL 3 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
— = >ERDBEEREN - = >3 - = > 5.00000-002 - = > 3000 - = >fluessig — = > kiste
ARTIKEL 3 BEZEICHNUNG: NUMMER: GEWICHT: BESTANDSMENGE: BESCHAFFUNG: VERPACKUNG:
- = >ÄPFEL - = >2 - = > 2.00000E-001 - = > 3000 — = >fest — = > karton
2. Sprachstandard
280
2.7 Dynamische Datenstrukturen Sämtliche in Kapitel 2.3 behandelten einfachen und strukturierten Datentypen weisen eine gemeinsame Eigenschaft auf: sie sind statischer Natur. Einem Datenobjekt, das einem dieser Datentypen angehört, wird vom Compiler zur Übersetzungszeit der benötigte Speicherplatz fest zugewiesen. Diese Zuweisung ändert sich während der Ausführung des Programms, in dem dieses Objekt deklariert wurde, nicht. Statische Datenstrukturen besitzen folgende Nachteile: -
Ihr Aufbau (Art und Anzahl von Komponenten) muß bereits zur Übersetzungszeit fest vorgegeben sein; er ist folglich zur Programmausführungszeit nicht mehr veränderbar.
-
Ihr Umfang (z.B. Anzahl von Komponenten eines ARRAY) hat sich an Maximalanforderungen (z.B. maximal benötigte Anzahl von ARRAY-Komponenten) zu orientieren; die Folge ist eine schlechte Speicherplatzausnutzung.
Allerdings weisen statische Datenstrukturen auch bestimmte Vorteile auf: So kann beispielsweise mittels des Feldindex gezielt auf beliebige Komponenten einer ARRAY-Struktur zugegriffen werden. In bestimmten Anwendungsfällen ist es jedoch wünschenswert, daß die Anzahl der Elemente einer (zusammengesetzten) Datenstruktur zur Ausführungszeit laufend verändert werden kann. In solchen Fällen wendet man vorteilhaft dynamische Datenstrukturen an. Dynamisch vereinbarten Datenobjekten wird der benötigte Speicherplatz erst zur Programmlaufzeit zugewiesen und - sobald die Objekte nicht mehr benötigt werden - auch zur Programmlaufzeit wieder entzogen. Für dynamische Datenobjekte ist ein bestimmter Bereich des Arbeitsspeichers, genannt Halde (eng. heap), reserviert. Die Verwaltung dynamischer Objekte (Zuweisung und Entzug von Speicherplatz) obliegt dem Laufzeitsystem, einer speziellen Komponente des Betriebssystems. Dynamische Datenstrukturen weisen also folgende Vorteile auf: -
Ihre Struktur kann sich nach dem Umfang der beteiligten Elemente zur Programmausführungszeit ändern; nicht veränderbar ist jedoch der Datentyp der Elemente.
-
Den nicht mehr benötigten Elementen kann zur Programmausführungszeit der Speicherplatz entzogen werden.
Außerdem lassen sich bestimmte Operationen auf dynamische Datenstrukturen sehr effizient ausführen. Als Beispiel seien lineare Listen betrachtet. Eine lineare Liste ist eine lineare Anordnung von Datenobjekten des meist gleichen Datentyps.
2.7.1 Datentyp POINTER
281
Jedes Element (Datenobjekt) der Liste, bis auf das letzte Element, enthält einen Zeiger, der auf das folgende Element verweist. Möchte man nun beispielsweise ein Element aus der Liste entfernen, so kann dies ganz einfach durch die Änderung nur eines Zeigers geschehen: Der auf das zu löschende Element verweisende Zeiger wird so verändert, daß er auf das nachfolgende Element verweist. Wesentlich umständlicher ist das Löschen eines Elementes, falls man eine lineare Anordnung von Elementen statisch realisiert. Als Datenstruktur bietet sich dann das ARRAY an. Löscht man ein Element in einem ARRAY, so entsteht eine Lücke. Das Schließen der Lücke erfordert ein aufwendiges Umspeichern der Elemente. Ähnlich ungünstige Verhältnisse liegen beim (sortierten) Einfügen eines Elements in ein ARRAY vor. Auch hier ist ein Umspeichern unumgänglich (es sei denn, das Element ist zufällig am Ende einzufügen). Diesen Nachteilen stehen jedoch bereits erwähnte Vorteile statischer Datenstrukturen gegenüber. Die obigen Ausführungen sollten verdeutlicht haben, daß sowohl statische, als auch dynamische Datenstrukturen spezifische Vor- und Nachteile aufweisen. Im konkreten Einzelfall ist zu entscheiden, welche Strukturart günstiger ist. Die Anwendung dynamischer Datenstrukturen empfiehlt sich dann, wenn: - eine stark variierende Anzahl von Datenobjekten zu verarbeiten ist, -
Datenobjekte häufig in eine Struktur einzufügen und aus der Struktur zu entfernen sind und
-
Datenobjekte in einer Struktur selten aufzusuchen sind.
Sind diese Bedingungen nicht erfüllt, so sollte man eher statische Datenstrukturen verwenden. In den folgenden Kapiteln werden die Definition und das Arbeiten mit dynamischen Datenstrukturen behandelt. Als zentrales konstruktives Element bei der Bildung dynamischer Datenstrukturen fungiert in Modula-2 der Datentyp POINTER (Kapitel 2.7.1). Zur Manipulation von POINTER-Objekten stellt die Sprache Modula-2 spezielle Operationen zur Verfügung (Kapitel 2.7.2). Mit POINTER-Objekten lassen sich nicht nur lineare Listen erzeugen und verarbeiten (Kapitel 2.7.3), vielmehr sind auch generellere Strukturen wie Stapel, Schlangen und Bäume (Kapitel 2.7.4) darstellbar.
2.7.1 Datentyp POINTER In der Sprache Modula-2 repräsentiert der Datentyp POINTER den zur Realisierung dynamischer Strukturen benötigten Zeigertyp. Ein POIN l ER-Objekt enthält keine Nutzdaten. Vielmehr stellt es eine Speicheradresse dar, unter der ein Daten-
2. Sprachstandard
282
objekt gespeichert ist. Der Datentyp dieses Datenobjekts muß bei der Deklaration des auf diesen Datentyp verweisenden POINTER-Typs festgelegt werden. Man nennt diesen mit einem POINTER-Typ gekoppelten Datentyp auch Basis- oder Elementardatentyp. Für die Definition eines POINTER-Typs gilt das in Abbildung 2.86 dargestellte Syntaxdiagramm. Erläuterungen zu diesem Diagramm enthält die Tabelle 2.125.
PointerType •
( POINTER)—(
TO )
1 Type
Abb. 2.86: Syntaxdiagramm für „PointerType" (Zeigertyp).
Tab. 2.125: Beschreibung des Syntaxdiagramms für „PointerType". (1) Die Deklaration eines POINTER-Typs wird mit den Schlüsselwörtern POINTER TO eingeleitet. Ihnen ist ein Elementardatentyp nachgestellt. (2) Als Elementardatentyp sind alle bereits vorgestellten einfachen und zusammengesetzten Datentypen zulässig. (3) Der deklarierte POINTER-Typ ist an den angegebenen Elementardatentyp gebunden. Von einem POINTER-Typ können mehrere (Zeiger-)Variablen definiert werden. Diese Zeigervariablen dürfen grundsätzlich nur auf Datenobjekte verweisen, die dem Elementardatentyp angehören, an den der POINTER-Typ gebunden ist. Auf POIN1 ER-Objekte sind nur bestimmte Operationen anwendbar. Sie dienen dem Erzeugen, Manipulieren und Löschen dynamischer Datenobjekte. Behandelt werden diese Operationen im nächsten Kapitel. Ist der einem POINTER-Typ zugeordnete Elementardatentyp ein RECORDTyp, dann kann eine RECORD-Komponente selbst wieder von diesem POINTERTyp sein. Ein derart strukturiertes RECORD-Objekt enthält somit einen Zeiger, der auf ein weiteres RECORD-Objekt der gleichen Struktur verweist. Zur Verdeutlichung dieses Zusammenhangs sei folgendes einfache Beispiel betrachtet:
2.7.2 Operationen auf dynamische Objekte
283
Artzeiger = POINTER T O Artikelsatz; Artikelsatz = RECORD artnr : [1..9999]; artbez: ARRAY [0..19] O F CHAR; preis : CARDINAL; next : Artzeiger END
In diesem Beispiel ist „Artzeiger" ein POINTER-Typ, der an den Elementardatentyp „Artikelsatz", einen RECORD-Typ, gebunden ist. Die Komponente „next" des RECORD-Typs ist vom POINTER-Typ „Artzeiger" und verweist somit auf ein weiteres RECORD-Objekt. Die obige Deklaration definiert eine lineare Liste, die sich in der in Abbildung 2.87 gezeigten Weise veranschaulichen läßt.
erstes Listenelement next
nutzdaten
Legende: O NIL
^
letztes W nutzdaten
next
W
Listenelement
W nutzdaten
next
NIL
Zeigerobjekt Listenende
Abb. 2.87: Schematische Darstellung einer linearen Liste.
Zu betonen ist, daß die Elemente einer linearen Liste keineswegs in aufeinanderfolgenden Speicherplätzen abgelegt werden müssen. Für jedes Zeigerobjekt wird der zulässige Wertebereich durch den Adreßraum des vom Laufzeitsystem verwalteten Heap definiert. Die Elemente einer linearen Liste werden daher die Speicherplätze des Heap belegen, die zum Zeitpunkt der Erzeugung der Liste gerade frei sind.
2.7.2 Operationen auf dynamische Objekte Zum Erzeugen und Manipulieren dynamischer Datenstrukturen bietet die Sprache Modula-2 spezielle Prozeduren an, die auf POINTER-Typen definiert sind. Abgesehen von Zuweisungen und dem Prüfen auf Gleichheit und Ungleichheit sind auf POIN l ER-Objekte keine weiteren Operationen, vor allem auch keine arithmetischen Operationen, ausführbar. Im einzelnen sind folgende Operationen anwendbar:
284
2. Sprachstandard
-
Erzeugen eines Datenobjekts, auf das eine Zeigervariable verweist (Standardprozedur ALLOCATE).
-
Freigeben des Speicherplatzes, den ein mit ALLOCATE erzeugtes Datenobjekt belegt (Standardprozedur DEALLOCATE).
-
Zugreifen auf ein Datenobjekt, auf das eine Zeigervariable verweist.
-
Zuweisung einer Zeigervariablen zu einer anderen Zeigervariablen.
-
Prüfen von Zeigervariablen auf Gleicheit und Ungleichheit.
Die Prozeduren ALLOCATE und DEALLOCATE sind aus dem Bibliotheksmodul Storage zu importieren. Die Parameterlisten dieser Prozeduren gehen aus der Tabelle 2.126 hervor. Beide Parameterlisten enthalten eine mit SIZE bezeichnete Prozedur, die den für den jeweiligen Elementardatentyp T benötigten Speicherplatz ermittelt. SIZE ist aus dem Modul SYSTEM zu importieren. Tab. 2.126: Standardprozeduren ALLOCATE und DEALLOCATE. ALLOCATE (p, SIZE(T)): Die Prozedur ALLOCATE reserviert Speicherplatz für ein Datenobjekt vom Elementardatentyp T, auf das die Zeigervariable p verweist. Der zu reservierende Speicherplatz wird mit SIZE(T) ermittelt. Beim Aufruf von ALLOCATE müssen p und T definiert sein. DEALLOCATE (p, SIZE(T)): Die Prozedur DEALLOCATE gibt den Speicherplatz frei, auf den die Zeigervariable p verweist. Dieser Speicherplatz wurde bislang von einem Datenobjekt des Elementardatentyps T belegt; die Größe des belegten Speicherplatzes wird mit SIZE(T) ermittelt. Das Zugreifen auf ein Datenobjekt, auf das eine Zeigervariable „p" verweist, geschieht mittels der Schreibweise „p ~ ". Diese Schreibweise ermöglicht es, dem Datenobjekt einen Wert zuzuweisen oder den Wert des Datenobjekts zu ermitteln. Sind „pl" und „p2" zwei Zeigervariablen, so ist die Zuweisung pl: =p2 zulässig. Eine solche Zuweisung kann beispielsweise dazu dienen, einen bestimmten Wert einer Zeigervariablen mittels einer anderen Zeigervariablen zwischenzuspeichern. Wie bereits erwähnt, sind auch Gleichheits- und Ungleichheitsrelationen auf Zeigervariablen zulässig (z.B. pl = p2 und pl < > p2). Anhand eines sehr einfachen Beispiels sollen nun die Wirkungen der genannten Operationen verdeutlicht werden. Gegeben seien folgende Vereinbarungen:
2.7.2 Operationen auf dynamische Objekte
TYPE Zahlzeiger Zahl
= POINTER TO Zahl; = CARDINAL;
VAR p1, p2 x, y
Zahlzeiger; Zahl;
285
Einige Operationen auf die vereinbarten Variablen sind in Abbildung 2.88 zusammengestellt und grafisch veranschaulicht. I
Operationen
grafische Darstellung
ALLOCATE(p1, SIZE(Zahl)) ALLOCATE(p2, SIZE(Zahl))
p 1 ~ : = 72 p2~:=820
x: = p1~ y: = p2~
DEALLOCATE(p1, SIZE(Zahl))
p1: = p2
p1 o
•
?
p2 o
•
p1 o
•
p2 o
p1 o
?
72
Für zwei Datenobjekte vom Typ Zahl vom Typ Zahl, auf die p1 und p2 verweisen, wird Speicherplatz reserviert.
Den bislang noch unbestimmten Datenobjekten werden die Werte 72 und 820 zugewiesen.
820
•
72
p2 o
820
p1 o
72
P2 o
820
p1 72 P2 o
Erläuterung
L •
Die Werte der Datenobjekte werden in zwei Variablen x und y gespeichert.
Der Speicherplatz des Datenobjekts, auf das p1 verweist, wird freigegeben.
Auf das Datenobjekt mit dem Wert 820 verweisen nun zwei Zeiger.
820
Abb. 2.88: Beispiele für Operationen auf dynamische Datenobjekte.
2. Sprachstandard
286
Nach der Freigabe des Speicherplatzes des Datenobjekts, auf das der Zeiger p l verweist, ist das Datenobjekt nicht mehr zugänglich. Wie im obigen Beispiel kann jedoch der Wert eines solchen Datenobjekts vor der Speicherplatzfreigabe einer statischen Variablen zugewiesen und damit über die Lebensdauer des Datenobjekts hinaus verfügbar gemacht werden.
2.7.3 Listenverarbeitung Zu Beginn des Kapitels 2.7 wurde der Begriff der linearen Liste kurz erläutert, und in Kapitel 2.7.1 wurde ein einfaches Beispiel für eine lineare Liste angegeben. Im vorliegenden Kapitel wird der Begriff der linearen Liste präzisiert, und es werden häufig auf Listen auszuführende Operationen behandelt. Unter der dynamischen Datenstruktur „lineare Liste" wird hier eine lineare Anordnung von Datenobjekten verstanden, die folgende Merkmale aufweist: -
Jedes Element der Liste, bis auf das letzte Element, besitzt genau einen Nachfolger, und jedes Element der Liste, bis auf das erste Element, besitzt genau einen Vorgänger.
-
Auf das erste Element der Liste verweist ein Zeiger, der Anfangszeiger oder Anker genannt wird.
-
Jedes Element der Liste enthält einen Zeiger, der auf das nachfolgende Element verweist. Eine Ausnahme bildet der Zeiger des letzten Elements: er verweist auf kein Element.
-
Die Elemente der Liste müssen nicht notwendigerweise vom gleichen Datentyp sein.
Ein Zeiger, der auf kein Element verweist, erhält den Wert NIL. In Modula-2 ist NIL eine vordeklarierte Konstante, die nur auf POINTER-Objekten definiert ist und gewissermaßen ins „Nichts" zeigt. Das bereits in Kapitel 2.7.1 angegebene Beispiel einer linearen Liste sei nun um einige weitere Angaben ergänzt und in Abbildung 2.89 nochmals grafisch veranschaulicht. Das ergänzende Beispiel lautet: TYPE Artzeiger Artikelsatz
= POINTER TO Artikelsatz = RECORD artnr : [1..9999]; artbez: ARRAY [0..19] OF CHAR; preis : CARDINAL; next : Artzeiger END
2.7.3 Listenverarbeitung
VAR first, last :
287
Artzeiger;
Die Zeigervariable „first" verweist auf das erste Listenelement. Sie stellt also den Anker dar, der den Einstieg in die Liste auf der Seite des Listenanfangs ermöglicht. Die Zeigervariable „last" verweist auf das Listenende (Endezeiger).
Endezeiger
Abb. 2.89: Lineare Liste mit Anfangs- und Endezeiger.
Bei der Verarbeitung von linearen Listen werden immer wieder einige Operationen benötigt, die der Manipulation von Listenelementen dienen. Die wichtigsten dieser Operationen sind: -
Einfügen eines Elements in eine lineare Liste.
-
Entfernen eines Elements aus einer linearen Liste.
-
Suche eines Elements in einer linearen Liste.
Je nach Organisation der Liste kann das Einfügen von Elementen stets am Listenanfang, stets am Listenende oder sortiert vorgesehen sein. Die Operationen können auch kombiniert auftreten. Ein Beispiel ist das Einfügen eines Elements vor oder nach einem Bezugselement, das noch aufzusuchen ist. Analoge Operationen benötigt man zur Verarbeitung spezieller linearer und nichtlinearer Listen.
288
2. Sprachstandard
Spezielle lineare Listen sind: -
der Stapel (engl. Stack), bei dem das Einfügen und Entnehmen von Elementen nur am Listenanfang erlaubt ist, und
-
die Schlange (engl, queue), bei der das Einfügen von Elementen nur am Listenanfang und das Entnehmen nur am Listenende zulässig ist.
Eine Ringstruktur entsteht, wenn der Zeiger des letzten Listenelements auf das erste Element zurückverweist. Besitzen einige Listenelemente mehr als einen Vorgänger oder mehr als einen Nachfolger, so liegt eine nichtlineare Liste vor. Spezielle lineare Listen und nichtlineare Listen werden im nächsten Kapitel angesprochen. Der restliche Teil dieses Kapitels ist der detaillierten Darstellung der Listenverarbeitung gewidmet. An einem Beispiel werden die mit dem Generieren von Listen sowie die mit dem Einfügen und Entfernen von Elementen verbundenen Deklarationen und Anweisungen demonstriert. Gegenstand des Beispiels ist ein vereinfachtes rechnergestütztes Ausleihsystem einer Bibliothek.
Beispiel Im Rahmen eines rechnergestützten Ausleihsystems einer Bibliothek sollen die Ausleihe und die Rückgabe von Büchern, das Erstellen von Leserausweisen, das Einziehen von Leserausweisen und das Verbuchen von Neuzugängen und Verlusten von Büchern mit einer in Listenform organisierten Datenbasis abgewickelt werden. Die Datenbasis soll auf der Grundlage der in Abbildung 2.90 dargestellten Listen-Repräsentation aufgebaut werden. Die Repräsentation besteht aus einer Vielzahl von linearen Listen, die miteinander verkettet sein können. Im einzelnen sind das lineare Listen für Bücher und lineare Listen für Leser. Jeder Buchtitel wird geordnet nach den beiden Anfangsbuchstaben des Titels in eine lineare Liste eingetragen. Bücher mit den gleichen Anfangsbuchstaben stehen in der gleichen linearen Liste. Es gibt somit 26*26 = 676 lineare Buch-Listen. Die Anfangszeiger der Buch-Listen werden in einem Feld abgespeichert, das in Abbildung 2.90 nicht dargestellt ist. Jeder Leser wird geordnet nach dem Anfangsbuchstaben seines Namens in eine lineare Liste eingetragen. Leser mit gleichen Anfangsbuchstaben stehen in der gleichen linearen Liste. Die Anfangszeiger der 26 Leser-Listen werden ebenfalls in einem nicht in Abbildung 2.90 dargestellten Feld abgelegt. Ein Objekt vom Typ Buch enthält drei Zeiger. Zeigerl wird zur Verkettung mit Büchern verwendet, deren Titel die gleichen beiden Anfangsbuchstaben aufweisen. Zeiger2 verkettet die Bücher, die vom gleichen Leser ausgeliehen wurden, und Zeiger3 stellt die Verbindung mit diesem Leser her.
Beispiel
289
Buch-Listen
Leser-Listen
Legende: Objekt v o m Typ Buch Zeiger!
Buchdaten
Zeiger2
Objekt v o m Typ Leser Zeiger3
Zeiger2
Leserdaten
Zeiger!
Abb. 2.90: Listen-Repräsentation der Datenbasis eines Ausleihsystems.
2. Sprachstandard
290
Ein Objekt vom Typ Leser beinhaltet dagegen nur zwei Zeiger. Zeigerl stellt die Verbindung mit weiteren Lesern her, deren Namen mit dem gleichen Anfangsbuchstaben beginnen. Zeiger2 verweist auf das erste von diesem Leser ausgeliehene Buch. Abbildung 2.91 zeigt beispielhaft, wie Leser und Bücher miteinander verkettet sein können. Aus Gründen der Übersichtlichkeit sind hier, wie auch in Abbildung 2.90, nur einige Zeiger eingetragen.
Buch-Listen
Leser-Listen
Abb. 2.91: Datenbasis eines Ausleihsystems mit beispielhafter Verkettungsstruktur.
a) Generieren und Einfügen Die Erzeugung dynamischer Datenobjekte besteht aus zwei Schritten: Zum einen aus der Deklaration der zur Beschreibung dynamischer Objekte benötigten Datentypen und der Deklaration der Datenobjekte selbst, und zum anderen aus der eigentli-
Beispiel
291
chen Generierung dynamischer Objekte zur Programmlaufzeit. Für das oben beschriebene Ausleihsystem haben die Deklarationen das nachfolgend angegebene Aussehen: Fortsetzung Beispiel TYPE STRING20 STRING25 STRING40
= ARRAY [0..20] O F CHAR = ARRAY [0..25] O F CHAR = ARRAY [0..40] O F CHAR
BuchZeiger = POINTER T O Buch; LeserZeiger = POINTER TO Leser;
Buch = RECORD Titel Autor Verlag naechstesBuch ausgeliehenesBuch Ausleiher END;
Leser = RECORD Name Strasse Ort naechsterLeser ausgeliehenesBuch END; AlleBuchZeiger AlleLeserZeiger
STRING40; STRING25; STRING25; BuchZeiger; BuchZeiger; LeserZeiger;
STRING20; STRING20; STRING25; LeserZeiger; BuchZeiger;
= ARRAY [1..676] OF BuchZeiger; = ARRAY [1..26] O F LeserZeiger;
Mit diesen Deklarationen sind die dynamischen Strukturen (Typen) formal vereinbart, jedoch existieren damit noch keine Datenobjekte. Zur Generierung von Datenobjekten sind Zeigervariablen erforderlich. Im gegebenen Fall werden zwei Tabellen benötigt, in denen die auf die Buch- und Leser-Listen verweisenden Zeiger abgelegt sind. Einzuführen sind außerdem zwei zusätzliche Zeiger vom Typ „BuchZeiger" und zwei zusätzliche Zeiger vom Typ „LeserZeiger", die nur für die ListenGenerierung und für das Suchen von Einfügepositionen benötigt werden.
2. Sprachstandard
292
Fortsetzung Beispiel VAR Buecher AlleLeser neuesBuch SuchBuch neuerLeser SuchLeser alterSuchLeser alterLeser Buchindex Leserindex Name
AlleBuchZeiger; (* Tabelle aller Zeiger auf Bücher *) AlleLeserZeiger; (* Tabelle aller Zeiger auf Leser *) BuchZeiger; (* Zeiger auf ein neues Buch *) BuchZeiger; (* Zeiger zum Durchsuchen der BuchListe *) LeserZeiger; (* Zeiger auf einen neuen Leser *) LeserZeiger; (* Zeiger zum Durchsuchen der LeserListe *) : LeserZeiger; (* alten Zeiger zum Durchsuchen eines Lesers *] LeserZeiger; (* Zeiger auf einen zu löschenden Leser *) [1.676]; [1- 26]; STRING20;
Das Erzeugen eines Datenobjektes, auf das eine Zeigervariable verweist, geschieht mit der nur auf Zeiger definierten Standardprozedur ALLOCATE. Im gegebenen Fall führt der Aufruf ALLOCATE (neuerLeser, SIZE (Leser)) zum Generieren eines Datenobjekts vom Datentyp „Leser"; die Zeigervariable „neuerLeser" zeigt auf das neu angelegte Objekt. Die Komponenten des so erzeugten Datenobjekts sind zu diesem Zeitpunkt noch Undefiniert. Ihnen müssen Werte noch explizit zugewiesen werden. Den erforderlichen Zugriff zu den einzelnen Komponenten ermöglicht die Punktqualifizierung. Die Qualifizierung „neuerLeser ^ .Name" realisiert beispielsweise den Zugriff auf die Komponente „Name".
Beispiel ALLOCATE(neuerLeser, SIZE(Leser)); (* Erzeugung eines Objektes vom Typ Leser *) Concatf, 'MAIERBAUM, ERIKA', neuerLeser".Name); Concat(", 'Chipweg 1', neuerLeser".Strasse); Concat(", '9000 Musterstadt', neuerLeser ".Ort); neuerLeser "naechst erLeser := NIL; neuerLeser" ausgeliehenesBuch : = NIL;
Das mit obigen Anweisungen erzeugte Datenobjekt „neuerLeser" muß nun in die zutreffende lineare Liste eingefügt werden (siehe hierzu die nicht weiter kommentierten Abbildungen 2.92 und 2.93 ). Dazu ist zunächst die Einfügeposition in der
Beispiel
293
Liste zu ermitteln. Dies kann mit der nachfolgend angegebenen Suchschleife geschehen (Voraussetzung: einzufügendes Element ist nicht erstes Element!). Fortsetzung Beispiel GetLeserlndex(neuerLeser~ .Name, Leserindex); (* Ermitteln Index für Anfangszeiger *) gefunden: = FALSE; SuchLeser: = AlleLeser[Leserlndex]; (* Suchzeiger mit Anfangwert belegen *) alterSuchLeser: = SuchLeser; (* altenSuchzeiger mit Anfangswert belegen *) WHILE (SuchLeser < > NIL) AND NOT gefunden DO gefunden: = Compare(SuchLeser^.Name, neuerLeser ~ .Name) > 0; (»Vergleichen Einfügenamen mit Namen in Leserliste*) IF NOT gefunden THEN alterSuchLeser: = SuchLeser; (*alten Suchzeiger merken*) SuchLeser: = SuchLeser ~. naechsterLeser (*Suchzeiger weitersetzen*) END END; IF gefunden THEN neuerLeser ~ .naechsterLeser: = SuchLeser; (* Verbinden des neuen Lesers in nächstgrößerem Namen Leserliste *) alterSuchLeser ~ .naechsterLeser: = neuerLeser (•Verbinden des kleineren Namens in Leselüste mit neuem Leser*) END;
b) Löschen und Zurückgewinnen Die Auflösung dynamischer Objekte erfolgt in zwei Schritten: Der erste besteht in der Isolierung des aufzulösenden Objektes und der zweite aus dem Vorgang des Löschens selbst. Ein Listenelement isoliert man, indem man es von seinem Vorgänger und seinem Nachfolger trennt. Dies geschieht durch das Ändern der beiden Zeiger, die das Element in die Listenstruktur einbinden. Beim Isolieren des Elements ist darauf zu achten, daß es mit einem Hilfszeiger verbunden bleibt. Der Hilfszeiger wird für das anschließende Löschen benötigt. Das Löschen von Datenobjekten geschieht mit der Standardprozedur DEALLOCATE. Der Prozedur ist als Parameter ein Hilfszeiger zu übergeben, der auf das zu löschende Datenobjekt verweist. Im gegebenen Beispiel wird die Zeigervariable „neuerLeser" als Hilfszeiger verwendet. Der Aufruf DEALLOCATE(neuerLe-
294
2. Sprachstandard
ser,SIZE(Leser)) gibt den Speicherplatz des Datenobjekts frei, auf den der Hilfszeiger „neuerLeser" verweist. Der Wert dieser Zeigervariablen ist nach dem Aufruf von DEALLOCATE Undefiniert! Die Anweisungen zum Ermitteln der Position, zum Isolieren und zum Löschen eines Listenelementes sind nachfolgend angegeben. Auf die Abfrage, ob der zu löschende Leser noch Bücher ausgeliehen hat und somit nicht gelöscht werden dürfte, wurde hier verzichtet. Zur Veranschaulichung des Löschens sei auf die Abbildung 2.93 zurückgegriffen. Diese Abbildung kennzeichne den Zustand vor dem Löschen eines Lesers. Die Listenstruktur nach dem Löschen eines Lesers ist in Abbildung 2.94 dargestellt (Voraussetzung: zu löschendes Element ist nicht erstes Element!).
Fortsetzung Beispiel Concat(", 'MAIERAMSEL, PETER', Name); (* Name des zu löschenden Lesers *) GetLeserlndex(Name, Leserindex); (* Ermitteln Index für Anfangszeiger *) gefunden: = FALSE; SuchLeser: = AlleLeser[Leserl ndex]; (* Lokalen Suchzeiger mit Anfangswert belegen *) alters uch Leser: = SuchLeser; (* altenSuchzeiger mit Anfangswert belegen *) WHILE (SuchLeser < > NIL) AND NOT gefunden DO gefunden: =Compare(SuchLeser/v.Name, Name) = 0; (* Vergleichen Einfügenamen mit Namen in Leserliste *) IF NOT gefunden THEN alterSuchLeser: = SuchLeser; (* alten Suchzeiger merken *) SuchLeser: = SuchLeser ~ .naechsterLeser (* Suchzeiger weitersetzen *) END END; IF gefunden THEN alterLeser: = SuchLeser; (* Zeiger kopieren *) alterSuchLeser ~ .naechsterLeser: = SuchLeser ~ .naechsterLeser; (* altenLeser aus der Liste isolieren *) alterLeser ~ .naechsterLeser: = NIL; (* letzte Verbindung mit Liste lösen *) DEALLOCATE(alterLeser, SIZE(Leser)) (* Speicher zurückgewinnen*) END;
295
Beispiel
1
AlleLeser[lndex]
MAIER, OTTO naechsterLeser ausgeliehenesBuch
NIL
MAIERAMSEL, PETER
• alterSuchLeser
naechsterLeser ausgeliehenesBuch
NIL
MAIERBAUM, ERIKA
MÜLLER, KARL HEINZ
•neuerLeser
naechsterLeser
NIL
ausgeliehenesBuch
NIL
-SuchLeser
naechsterLeser
- • NIL
ausgeliehenesBuch
->NIL
Abb. 2.92: Beispiel einer Leser-Liste vor dem Einfügen eines Lesers.
296
2. Sprachstandard
1
AlieLeser[lndex]
MAIER. OTTO
noechsterLeser ausgeliehenesBuch
NIL
MAIERAMSEL, PETER
noechsterLeser ausgeliehenesBuch
NIL
MAIERBAUM, ERIKA
noechsterLeser ausgeliehenesBuch
NIL
f
MÜLLER, KARL HEINZ
noechsterLeser
NIL
ausgeliehenesBuch
NIL
Abb. 2.93: Beispiel einer Leser-Liste nach dem Einfügen eines Lesers.
297
Beispiel
AlleLeser[lndex]
alterSuchleser
M A I E R , OTTO noechsterLeser ausgeliehenesBuch
NIL
MAIERAMSEL,
PETER
SuchLeser alterLeser
noechsterLeser
NIL
ausgeliehenesBuch
NIL
MAIERBAUM,
ERIKA
noechsterLeser ausgeliehenesBuch
NIL
MÜLLER, KARL HEINZ noechsterLeser ausgeliehenesBuch
- • N I L NIL
Abb. 2.94: Beispiel einer Leser-Liste nach dem Löschen eines Lesers.
2. Sprachstandard
298
2.7.4 Weitere dynamische Datenstrukturen Im vorliegenden Kapitel werden einige spezielle lineare Datenstrukturen und eine nichtlineare Datenstruktur vorgestellt. Abbildung 2.95 zeigt einen als lineare Liste organisierten Stapel. Das Einfügen (ein) und Entnehmen (aus) eines Elements geschieht grundsätzlich nur am oberen Listenende (TOP). Da das zuletzt eingefügte Element als erstes entnommen wird, bezeichnet man den Stapel auch als LIFO-Speicher (last in first out). Zur Manipulation des Stapels dient der auf das TOP-Element verweisende Anfangszeiger „first."
first O
^
daten
TOP
>
next
next
£ daten next
daten next
i NIL
Abb. 2.95: Der Stapel (Stack) als lineare Liste.
299
2.7.4 Weitere dynamische Datenstrukturen
Eine als lineare Liste organisierte Schlange ist in Abbildung 2.96 zu sehen. Bei der Schlange werden Elemente am oberen Listenende (TOP) eingefügt (ein) und am unteren Listenende (BOTTOM) entnommen (aus). Da das zuerst eingefügte Element als erstes entnommen wird, bezeichnet man die Schlange auch als FIFOSpeicher (first in first out). Zur Manipulation der Schlange dienen zwei Zeiger: der auf das TOP-Element verweisende Anfangszeiger „first" und der auf das BOTTOMElement verweisende Endezeiger „last". aus.
•
first i
daten
TOP
O
next
daten next
daten next
last O
^
daten next i
i BOTTOM NIL
i
ein
Abb. 2.96: Die Schlange (Queue) als lineare Liste.
300
2. Sprachstandard
Möchte man auf die Elemente einer linearen Liste sowohl ausgehend vom Listenanfang, als auch in entgegengesetzter Richtung zugreifen, so bietet sich eine Ringstruktur mit doppelter Verkettung an. Eine solche Struktur ist in Abbildung 2.97 dargestellt. Dem Einstieg in die Liste dienen der Anfangszeiger „first" und der Endezeiger „last". Jedes Listenelement enthält zwei Zeiger. Der Zeiger „next" eines Listenelements verweist auf das nachfolgende Element und der Zeiger „pred" auf das vorhergehende (pred steht als Abkürzung für Vorgänger, engl, predecessor).
first O
^
daten next pred
J
1 daten next pred
t
Í
daten next
i
pred
^
O last
Abb. 2.97: Doppelt verkettete Liste in einer zirkulären Struktur.
301
2.7.4 Weitere dynamische Datenstrukturen
Listen heißen nichtlinear, wenn ein oder mehrere Listenelemente mehr als einen Vorgänger oder mehr als einen Nachfolger besitzen. Besitzt jedes Element einer Liste genau einen Vorgänger und einen oder mehrere Nachfolger, so liegt eine Baumstruktur vor. Der Sonderfall des Binärbaums ist in Abbildung 2.98 dargestellt. In einem Binärbaum besitzt jedes Element maximal zwei Nachfolger, einen rechten und einen linken. Auf den rechten Nachfolger verweist in Abbildung 2.98 der Zeiger „right" und auf den linken der Zeiger „left". Baumstrukturen verwendet man beispielsweise im Bereich der Datenorganisation zur effizienten Gestaltung von Suchprozessen (Suchbäume).
Q
• daten right left
daten
daten NIL
right
right
left
left
daten NIL
right NIL
daten
left
NIL
right NIL
daten
left
NIL
right NIL
Abb. 2.98: Ein „Binärbaum" als Listenstruktur.
left
2. Sprachstandard
302
2.8 Rekursionen Unter Rekursionen versteht man Formulierungen, die auf sich selbst oder auf Teile von sich selbst zurückgreifen. Rekursionen sind nicht nur in der Informatik, sondern auch in der Mathematik, in der Optik und in Erzählungen oder Liedern bekannt. „Der Mops kam in die Küche..." ist ein Beispiel für ein rekursives Liedchen. In der Informatik hat die Rekursion deshalb eine so große Bedeutung, weil viele der in diesem Gebiet untersuchten Probleme rekursiver Natur sind. Es lag daher nahe, Programmiersprachen mit einem Rekursionskonzept auszustatten, um auf diese Weise eine elegante Bearbeitung rekursiver Probleme zu ermöglichen. Nicht alle Programmiersprachen verfügen, wie die Sprache Modula-2, über ein Rekursionskonzept. Rekursive Probleme sind bei der Verwendung solcher Sprachen in der meist umständlicheren, iterativen Weise zu bearbeiten. Der iterative Weg ist grundsätzlich auch gangbar, da sich jedes rekursive Problem auch iterativ formulieren und lösen läßt. Für den rekursiven Lösungsansatz spricht jedoch, daß er meist kürzer, übersichtlicher, änderungsfreundlicher und leichter auf Korrektheit überprüfbar ist. Im vorliegenden Kapitel werden Rekursionen nur einführend behandelt. An einem Beispiel wird gezeigt, wie sich rekursive Probleme mittels rekursiver Prozeduren lösen lassen (Kapitel 2.8.1). Danach werden sogenannte Inkarnationen (Kapitel 2.8.2) und der Ablauf (Kapitel 2.8.3) rekursiver Prozeduren behandelt. Den Abschluß bilden zwei Beispiele für rekursiv gelöste Probleme, die Fibonacci-Reihe (Kapitel 2.8.4.1) und der Quicksort-Algorithmus (Kapitel 2.8.4.2).
2.8.1 Rekursive Prozeduren Zur Bearbeitung rekursiver Probleme bietet die Sprache Modula-2 ein Rekursionskonzept an, das sich auf Prozeduren und Funktionsprozeduren erstreckt. Mit beiden Arten von Prozeduren lassen sich direkte und indirekte Rekursionen formulieren: -
Eine direkte Rekursion liegt vor, wenn sich eine Prozedur oder eine Funktionsprozedur selbst aufruft.
-
Dagegen spricht man von indirekter Rekursion, wenn eine (Funktions-) Prozedur eine andere Prozedur aufruft und von dieser wieder aufgerufen wird; zwischen dem ersten Aufruf einer Prozedur und ihrem rekursiven Wideraufruf kann natürlich auch eine ganze Aufruffolge anderer Prozeduren liegen.
303
2.8.1 Rekursive Prozeduren
Ein zur Erläuterung des Rekursionskonzepts gerne verwendetes Beispiel ist die Berechnung der Fakultät ganzer positiver Zahlen. Die Fakultät FAC(n) einer ganzen Zahl n, n > = 0, ist definiert als: FAC(n)
= 1*2*3*..*ri
fürn>0,
FAC(0)
=1
für n = 0.
Die rekursive Berechnungsvorschrift für FAC(n), n > 0, lautet: FAC(n)
= FAC(n-1)*n.
Die Berechnung von FAC(n) wird also auf die Berechnung von FAC(n-l) zurückgeführt. Die Rückführung auf die gleiche Funktion, die mit anderem Argument auszuwerten ist, stellt nichts anderes als eine direkte Rekursion dar. Um im gegebenen Beispiel das Berechnungsgesetz zu verdeutlichen, sei die Auswertung von FAC(n) für wachsende n betrachtet: FAC(0)
= 1
FAC(1)
= FAC(0)*1 = 1 * 1
FAC(2)
= FAC(1)*2 = FAC(0)*1*2 = 1*1*2
FAC(3)
= F A C ( 2 ) * 3 = FAC(1)*2*3 =
=1,
= FAC(0)*1*2*3 = 1 * 1 * 2 * 3 = 6.
Allgemein gilt also: FAC(n)
= FAC(r>-1)*n, = FAC(n-2)*(n-1)*n, =
FAC(n-n)*2*3*...*(n-2)*(n-1)*n,
= 1 * 2 * 3 * . ,.*(n-2)*(n-1)*n.
Nachfolgend ist eine Funktionsprozedur zur Berechnung der Fakultät ganzer positiver Zahlen mittels direkter Rekursion angegeben.
304
2. Sprachstandard
Beispiel PROCEDURE FAC(n : CARDINAL):CARDINAL; (*
*)
(* Diese Prozedur dient der rekursiven Berechnung der Fakultät einer ganzen Zahl n, 0 < = n < = 8. Für n > 8 reicht der Darstellungsbereich von CARDINAL-Zahlen zur Darstellung des Ergebnisses nicht aus! *) (*-
BEGIN IF n = 0 T H E N RETURNO) ELSE RETURN(FAC(n-1)*n) END END FAC;
2.8.2 Inkarnationen Jeder rekursive Aufruf einer (Funktions-) Prozedur erzeugt eine Kopie oder Inkarnation der Prozedur mit allen ihren formalen und lokalen Daten. Bezogen auf das im vorliegenden Kapitel behandelte Beispiel der Fakultät bedeutet dies, daß jeweils pro rekursivem Aufruf zwei Variablen vom Typ CARDINAL, nämlich FAC(n) und n, dupliziert und im Speicher abgelegt werden. Die rekursive Vorgehensweise bei der Problemlösung ist mit einem erheblichen Speicherplatzbedarf verbunden, wenn viele Inkarnationen mit nicht eben wenigen zu duplizierenden Datenobjekten zu generieren sind. Ist der Speicherplatz sehr begrenzt, so kann eventuell die iterative Vorgehensweise angebracht sein. Um den Unterschied zwischen iterativer und rekursiver Vorgehensweise zu verdeutlichen, wird der rekursiven Fakultätsberechnung die iterative gegenübergestellt. Das folgende Beispiel beschreibt eine Funktionsprozedur zur iterativen Fakultätsberechnung.
Beispiel PROCEDURE FAC(n : CARDINAL):CARDINAL; (*
')
(* Diese Prozedur dient der iterativen Berechnung der Fakultät einer ganzen Zahl n, 0 < = n < = 8. Für n > 8 reicht der Darstellungsbereich von CARDINAL-Zahlen zur Darstellung des Ergebnisses nicht aus! *) (* *) VAR Sum, i : CARDINAL;
2.8.3 Ablauf rekursiver Prozeduren
305
BEGIN Sum: = 1; FOR i: = n T O 0 BY - 1 D O Sum: = S u m * n END; R E T U R N (Sum) E N D FAC;
Die iterative Berechnung der Fakultät erfordert zwar mehr, nämlich hier vier Variablen (FAC, n, Sum, i), nicht aber das Erzeugen von Inkarnationen bzw. das Duplizieren von Datenobjekten in den einzelnen Iterationsschritten. Im gegebenen Fall ergibt sich ab dem dritten Schritt ein Speicherplatzvorteil gegenüber dem rekursiven Lösungsansatz. Rekursive Algorithmen benötigen in der Regel mehr Speicherplatz als bedeutungsgleiche iterative Algorithmen. Der Zusatzbedarf an Speicherplatz ist beim Einsatz rekursiver Techniken zu berücksichtigen. Ein besonderes Problem bei der rekursiven Programmierung stellen nichtendende Rekursionen dar. Es werden also ständig neue Inkarnationen erzeugt, ohne daß das Programm terminiert. Um solche Situationen zu vermeiden, sind - wie auch bei der iterativen Problemlösung - geeignete Abbruchkriterien zu definieren. Im Falle der rekursiven Fakultätsberechnung stellt die Bedingung „IF n = 0 THEN..." das Abbruchkriterium dar. Das gleiche Abbruchkriterium wird bei der iterativen Fakultätsberechnung verwendet.
2.8.3 Ablauf rekursiver Prozeduren In diesem Kapitel wird der Ablauf einer rekursiven Prozedur am Beispiel der Fakultätsberechnung betrachtet. Die Berechnungsvorschrift besteht aus einer „Schleife", die eine Terminationsbedingung und zwei Zweige umfaßt: IF(n=0) THEN R E T U R N (1) ELSE R E T U R N ( F A C ( n - 1 )*n) END
(* Terminationsbedingung *) (* Zweig ohne rekursiven Aufruf *) (* Zweig mit rekursivem Aufruf *)
Solange die Terminationsbedingung n — 0 nicht erfüllt ist, generiert der ELSEZweig ständig neue Inkarnationen durch rekursive Aufrufe. Ist die Terminationsbedingung nach einer bestimmten Anzahl rekursiver Aufrufe schließlich erfüllt, so erhält der THEN-Zweig zum ersten Mal die Kontrolle. Dieser Zweig wird unmittelbar im Anschluß an die letzte erzeugte Inkarnation aktiv; zugleich steht er am Beginn einer Folge von Auswertungen.
2. Sprachstandard
306
Durch die rekursiven Aufrufe wurden bislang nur Inkarnationen erzeugt, jedoch nicht ausgewertet. Erst nach dem Erreichen der Terminationsbedingung beginnt die Auswertung der Inkarnationen. Die Auswertungsfolge ist zur Erzeugungsfolge gegenläufig. Zuerst wird also die zuletzt erzeugte Inkarnation ausgewertet, und zuletzt die zuerst erzeugte. Abbildung 2.99 veranschaulicht die Erzeugung und Auswertung von Inkarnationen für das Beispiel der rekursiven Fakultätsberechnung. Man beachte besonders die Gegenläufigkeit der Erzeugungs- und der Auswertungsfolge.
auswerten von Inkarnationen FAC(4) = FAC(3)*4
FAC(4) = 6 * 4 = 24
I FAC(3) = FAC(2)*3
FAC(3) = 2 * 3 = 6
FAC(2) = FAC(1 ) * 2
FAC(2) = 1 * 2 = 2
I FAC(1) = FAC(0)*1
FAC(1) = 1*1 = 1
erzeugen von Inkarnationen FAC(0) = 1, Terminationsbedingung erfüllt
Abb. 2.99: Erzeugung und Auswertung von Inkarnationen bei der rekursiven Fakultätsberechnung (n = 4).
2.8.4 Beispiele rekursiver Prozeduren
307
Im Fall der Fakultätsberechnung wird eine Inkarnation der rekursiven Prozedur durch jeweils nur einen rekursiven Aufruf erzeugt. Dies muß nicht generell so sein. Im nächsten Kapitel werden zwei Probleme vorgestellt, bei deren rekursiver Lösung pro Inkarnation mehrere rekursive Aufrufe erforderlich sind.
2.8.4 Beispiele rekursiver Prozeduren In diesem Kapitel werden zwei Beispiele rekursiver Prozeduren behandelt. Es sind dies die bekannte Fibonacci-Reihe und der Sortieralgorithmus Quicksort.
2.8.4.1 Fibonacci-Reihe Der italienische Mathematiker Fibonacci stellte eine nach ihm benannte Zahlenfolge gemäß folgendem Bildungsgesetz auf: Jede Zahl der Folge ergibt sich aus der Summe der beiden unmittelbar vorhergehenden Zahlen; das nullte und das erste Glied der Folge sind durch die Zahlen 0 und 1 gegeben. Der Anfang der FibonacciReihe lautet: 0, 1,1,2, 3, 5, 8, 13, 21, 34, ...
Man erkennt sofort das Bildungsgesetz dieser Reihe: 2. Element: 0 + 1
= 1,
3. Element: 1+1
=2,
4. Element: 1 + 2
=3,
5. Element: 2 + 3
= 5 . usw.
Demnach läßt sich die n-te Fibonacci-Zahl FIB(n) mit Hilfe einer rekursiven Formel berechnen; sie lautet: FIB(n)
=
FIB(n-1) + FIB(n-2).
FIB(1)
=
1,
FIB(0)
=
0
wobei:
und
n>=2.
So wird z.B. FIB(4) in folgenden Schritten berechnet: FIB(4)
=
FIB(3) + FIB(2)
2. Sprachstandard
308
= (FIB(2) + FIB(1)) + (FIB(1) + FIB(0)) = (FIB(1) + FIB(0)) +
1 + 1 + 0
= 1 + 0 + 1 + 1 + 0 = 3.
Das vierte Element der Fibonacci-Reihe lautet also 3. Zur Ermittlung dieses Ergebnisses waren acht rekursive Aufrufe erforderlich. Ein rekursiv formuliertes Programm zur Berechnung der Fibonacci-Reihe sieht in Modula-2 beispielsweise wie folgt aus:
Beispiel M O D U L E Fibonacci; (* *) (* Dieses Programm berechnet eine Fibonacci-Zahl aus ihrer Platznummer n, n = 0,1,2,...,24, in der Fibonacci-Reihe. Für n > 24 reicht der Darstellungsbereich von CARDINAL-Zahlen zur Darstellung des Ergebnisses nicht aus! Das erste Element hat die Platznummer 0. In diesem Programmbeispiel wird der Grundbibliotheks-Modul InOut verwendet. *) *) (* (* Import-Deklarationen ( * * FROM InOut IMPORT WriteString, WriteLn, ReadCard, WriteCard; ( * (* Variablendeklarationen (* VAR FibNum :
*
*) *) )
)
*) *)
CARDINAL;
( * * (* Unterprogramm zum Ermittlung der Fibonacci-Zahl (* P R O C E D U R E FIB(n : CARDINAL):CARDINAL; BEGIN IF n > 1 THEN RETURN(FIB(n-1) + FIB(n-2)) ELSIF n = 1 THEN RETURN(1) ELSE RETURN(0) END E N D FIB;
)
*) *)
309
(* (* Hauptprogramm (* BEGIN REPEAT WriteString('Geben Sie die Platznummer an: '); ReadCard (FibNum) ; WriteLnO UNTIL FibNumO; WriteString('Die Fibonacci-Zahl lautet: '); WriteCard(FIB(FibNum), 5); WriteLnO END Fibonacci.
Die Tabelle 2.127 gibt die Anzahl der Rekursionsaufrufe in Abhängigkeit von der Platznummer (FibNummer) der Fibonacci-Reihe an. Der starke Anstieg der Anzahl der Rekursionsaufrufe ist dadurch zu erklären, daß jeweils zwei rekursive Aufrufe pro Inkarnation der rekursiven Fibonacci-Formel erforderlich sind.
Tab 2.127: Anzahl der Rekursionen in Abhängigkeit von der Platznummer.
FibNummer
Rekursionsaufrufe
0
-
1
-
2
2
3
4
4
8
5
14
2. Sprachstandard
310
2.8.4.2 Sortieralgorithmus Quicksort Die Aufgabe eines Sortieralgorithmus besteht darin, die Elemente in einer linearen Datenstruktur (z.B. eindimensionales Feld oder lineare Liste) so anzuordnen, daß in Bezug auf ein (Sortier-) Kriterium eine auf- oder absteigende Elementfolge vorliegt. Häufig verwendete Sortierkriterien sind beispielsweise Nummern (ArtikelNr., Personal-Nr. usw.), Namen und das Datum. Zum Bearbeiten von Sortierproblemen werden in der Literatur verschiedene Sortieralgorithmen vorgeschlagen. Einer der bekanntesten und effizientesten Sortieralgorithmen ist der rekursiv formulierte Algorithmus Quicksort. Quicksort basiert auf der Idee, in einer Datenstruktur durch Austauschschritte von Elementen eine sortierte Reihenfolge zu erzeugen. Die Austauschschritte werden so gewählt, daß das Austauschen von Elementen vorzugsweise über größere Distanzen ausgeführt wird und dann am effizientesten ist. Sind z.B. n Elemente in umgekehrter Reihenfolge ihrer Schlüssel gegeben, so kann man sie mit nur n/2 Austauschvorgängen sortieren, indem man, links und rechts außen beginnend, schrittweise von beiden Seiten nach innen vorgeht [WI86]. Dieses Vorgehen sei an einem Beispiel illustriert: Sei a ein Feld mit n-Eiementen, das durch den Quicksort-Aigorithmus zu sortieren ist. Man wähle ein beliebiges Element des Feldes a und nenne es x (am effizientesten wäre, wenn x das mittlere Element des Feldes a wäre). Man durchsuche das Feld von links, bis ein Element a[i] > x gefunden wird, und von rechts, bis ein Element a[j] < x gefunden wird. Nun sind diese beiden Elemente zu vertauschen, und dieser Prozeß des „Durchsuchens und Vertauschens" ist so lange fortzusetzen, bis man sich beim Durchsuchen aus beiden Richtungen irgendwo trifft. Als Resultat ist jetzt das Feld zerlegt in einen linken Teil mit Schlüsseln kleiner x und in einen rechten Teil mit Schlüsseln größer x. Ergebnis der Sortierung soll jedoch keine Zerlegung in zwei in sich unsortierte Teilfelder sein. Deshalb wird diese gerade beschriebene Technik auf jede entstandene Zerlegung erneut angewendet (entspricht einem rekursiven Aufruf), bis jeder Teil der Zerlegung nur noch aus einem Element besteht.
Programm M O D U L E Quick; (*
*)
(* Dieses Programm liest ein Feld mit 100 Werten (Nachname, Vorname, Kundennummer und Information) ein, sortiert die Werte aufsteigend nach Nachnamen und gibt sie anschließend sortiert aus. In diesem Programmbeispiel werden die Grundbibliotheks-Module InOut und Strings verwendet. *)
(*
*)
Programm
311
(*
*)
(* Import-Deklarationen
*)
(*
FROM InOut FROM Strings (*
IMPORT WriteString, WriteLn, WriteCard, ReadCard, ReadString; IMPORT Compare;
(* Konstantendeklarationen (* CONST MaxRec = 100; (*
(* Typendeklarationen
*) *) *) *)
*)
*)
(*
*)
TYPE STRING40 : ARRAY [0..40] OF CHAR; STRING80 : ARRAY [0..80] OF CHAR; Data = RECORD Vorname STRING40; Nachname STRING40; Nummer CARDINAL; Info STRING80; END; SortierFeld = ARRAY [1..MaxRec] OF Data; (* (* Variablendeklarationen
M *)
(*
*)
VAR Kunden i
:
SortierFeld; [1..MaxRec];
(* Unterprogramm Quicksort
(*
*) *)
P R O C E D U R E Quicksort); (*
(»Unterprogramm Sortieren p P R O C E D U R E Sort(l, r ; CARDINAL); VAR i, j : CARDINAL; Satzl, Satz2 : Data; BEGIN i:=l; j: = r; Satzl : = Kunden[(l + r) DIV 2]; REPEAT WHILE Compare(Kunden[i].Nachname, Satzl.Nachname)
2. Sprachstandard
316 FOR j: = 1 TO 80 DO Write (CHR(i)) END; WriteLn(); TRANSFER (Pro2, Pro1) END; TRANSFER(Pro2, main) END P2;
(* (* * Hauptprogramm (
*) *) *)
BEGIN NEWPROCESS(P1, ADR(workspace1), SIZE(workspace1), Pro1); NEWPROCESS(P2, ADR(workspace2), SIZE(workspace2), Pro2); TRANSFER (main, Pro1) END CoroutineDemo.
Bemerkungen zum Beispiel Neben ADDRESS, NEWPROCESS und TRANSFER werden auch die Prozeduren ADR und SIZE aus dem Basismodul SYSTEM importiert. ADR dient der Festlegung von Anfangsadressen der Speicherbereiche „workspacel" und „workspace2", während SIZE die Größe der Speicherbereiche ermittelt. Im Rumpf des Programm-Moduls werden die beiden Coroutinen PI und P2 durch je einen NEWPROCESS-Aufruf erzeugt. Mit dem TRANSFER-Aufruf im Rumpf des Programm-Moduls wird vom Modulrumpf, der unter „main" geführt wird, auf den Prozeß „PI" umgeschaltet. Danach sind zunächst nur noch die beiden Prozesse „PI" und „P2" aktiv und zwar wechselweise. Sind beide Prozesse abgearbeitet, so geht die Kontrolle zurück an den Rumpf des Programm-Moduls. Da dort auf den TRANSFER-Aufruf keine weitere Anweisung folgt, ist das Verarbeitungsende erreicht.
3. Sprachdialekte Die detaillierte Darstellung sämtlicher inzwischen entwickelter Modula-2-Dialekte würde den Rahmen dieses Buches sprengen. Aus diesem Grunde wird in Kapitel 3.1 lediglich die Entwicklung von Sprachdialekten begründet und in Kapitel 3.2 exemplarisch in ein Modula-2-System, das TopSpeed-Modula-2, eingeführt.
3.1 Entwicklung von Sprachdialekten Im zweiten Kapitel wurde der Sprachstandard von Modula-2, so wie ihn der Urheber der Sprache festgelegt hat, vorgestellt. Das vorliegende Kapitel setzt sich mit der Entwicklung von Modula-2-Sprachdialekten auseinander. Ein Sprachdialekt besteht charakteristischerweise aus einem dem Sprachstandard entsprechenden Kern und einer über den Standard hinausgehenden Spracherweiterung. Spracherweiterungen nehmen die jeweiligen Compiler-Hersteller nach eigenem Ermessen vor. Sie erschweren daher die Übertragung von Programmen in andere Modula-2-Programmierumgebungen. Ähnlich wie bei der Programmiersprache Pascal entstand inzwischen eine ganze Reihe von Modula-2-Sprachdialekten. Begründet wurde diese Entwicklung zum einen durch das Fehlen einer international anerkannten Norm und zum anderen durch die dreimalige Revision des Modula-2-Standards (publiziert in der zweiten, dritten und vierten Auflage von Wirths „Programming in Modula-2"). Die Folge ist, daß für alle vier Sprachversionen Modula-2-Compiler angeboten werden. Ein weiteres Problem besteht darin, daß die Sprachdefinition bezüglich des Typtransfers, der Typkonversionen und der maschinenabhängigen Konzepte nicht genügend klar ist. Die herstellerabhängige Ausgestaltung solcher sprachlichen Freiheitsgrade bewirkt zusätzliche Anpassungsprobleme zwischen verschiedenen Modula-2Compilern. Schließlich ist noch die besondere Rolle zu berücksichtigen, die Standard-Bibliotheken neben dem Sprachstandard in Modula-2 einnehmen. Im Gegensatz zur Sprache Pascal besitzt Modula-2 für Standardzwecke wie Ein-/Ausgabe, mathematische Funktionen usw. keine in die Sprache integrierten Standardprozeduren und -funktionen. Vielmehr werden solche Prozeduren und Funktionen, wie bei der Sprache Ada, in Modulen (in Ada Pakete oder packages) bereitgestellt. Wirth hat zwar einige solcher Module in seinem Buch „Programming in Modula-2" vorgestellt, doch ist das
3. Sprachdialekte
318
mit diesen Modulen abgedeckte Funktionsangebot noch unzureichend. Auch hat Wirth diese Module bislang noch nicht explizit als verbindlichen Standard vorgegeben. So ist es nicht verwunderlich, daß fast jede Modula-2-Implementierung eine von anderen Implementierungen mehr oder weniger stark abweichende Bibliothek aufweist. Dem eigentlichen Sinn von Standard-Bibliotheken wird dieser Zustand leider nicht gerecht. Da es hier nicht beabsichtigt ist, in alle Modula-2-Dialekte einzuführen, soll zumindest eine Übersicht über derzeit verfügbaren Modula-2-Programmiersysteme gegeben werden. Eine entsprechende Zusammenstellung zeigt die Tabelle 3.1.
Tab. 3.1: Übersicht über Modula-2-Compiler und verwendete Betriebssysteme. Betriebssystem
MS-DOS PC-DOS
TOS Amiga-DOS
Bezeichnung und Hersteller
Logitech M2SDS Taylor TopSpeed
TDI-M2 Amiga-M2
UNIX
MOCKA (ModulaKarlsruhe)
VMS
MAC-UNIX/
VM/CMS
MAC-DOS
VMS/Logitech M2/CMS (TU Berlin)
MAC-M2 MAC-Meth TML-M2
Auf die unter dem Betriebssytem DOS lauffähigen Systeme LOGITECH-, M2SDS-, Taylor- und TopSpeed-Modula-2 wurde bereits im Kapitel 1 kurz eingegangen. Im folgenden Kapitel soll nun einer dieser Dialekte vertiefend behandelt werden. Aus Aktualitätsgründen ist dies das System TopSpeed-Modula-2. Im Vordergrund stehen dabei die Sprachteile, die über den in Kapitel 2 definierten Sprachstandard hinausgehen sowie die Sprachteile, die abweichend vom Wirth-Standard implementiert wurden.
3.2 TopSpeed-Modula-2 TopSpeed-Modula-2 basiert auf dem 1985 von Wirth vorgegebenen Sprachstandard [WI85]. Jedoch enthält es auch einige nützliche Spracherweiterungen, erweiterte Bibliotheken mit ca. 200 Prozeduren und Funktionsprozeduren und schließlich einen Kern von Systembibliotheken, der die Kompatibilität mit anderen Compilern gewährleisten soll [JP88].
3.2 TopSpeed-Modula-2
319
In die Sprache aufgenommen wurden weitere Reservierte Wörter, weitere Standardbezeichner und Symbole, neue und umdefinierte Standardprozeduren, Standardfunktionen und Datentypen. Es fällt auf, daß die von Wirth zu Recht vermiedene GOTO-Anweisung in die Sprache einbezogen wurde. Erhebliche Verbesserungen sind im Umgang mit den Datentypen ARRAY, A R RAY O F CHAR, R E C O R D und SET festzustellen. Nach dem bisher gültigen Standard konnte man einer Variablen vom Datentyp A R R A Y nur komponentenweise Werte zuweisen. TopSpeed-Modula-2 gestattet es dagegen, sämtlichen Komponenten Werte mit nur einer Wertzuweisung zuzuordnen. Dies geschieht durch Benennung sämtlicher zuzuweisender Werte, für den kleinsten Index des A R R A Y bis hin zum größten, auf der rechten Seite der Wertzuweisung. In analoger Weise können die Komponenten eines R E C O R D mit einer Wertzuweisung initialisiert werden. Möglich ist es auch, ein zwischen Hochkommata stehendes Textliteral einer Variablen vom Typ A R R A Y O F C H A R mit einer Zuweisung zuzuordnen. Schließlich soll nicht unerwähnt bleiben, daß der Mengendatentyp SET endlich mehr als 16 Elemente (nämlich 2 1 6 = 65536) aufnehmen kann. Die vom Hersteller von TopSpeed-Modula-2 angebotenen erweiterten Bibliotheks-Module umfassen System-Module, Assembler-Module, Utility-Module, Input-/Output-Module und Window-Module. Im einzelnen sind das die in der Tabelle 3.2 aufgeführten Module. Die von diesen Modulen exportierten Leistungen werden nebst Schnittstellen in Kapitel 3.2.4 auszugsweise beschrieben. Die Module dienen den folgenden Zwecken: -
System ist ein fiktiver Modul, der das Modula-2-System um compilerabhängige Prozeduren und Funktionen ergänzt.
-
Asm Li b enthält einige Assembler-Prozeduren von BibliotheksModulen, die nicht direkt in Modula-2 implementiert wurden.
-
MATHLIB exportiert trigonometrische, hyperbolische und logarithmische Funktionen in einem Umfang, der das Angebot im Modul MathLibO erheblich übersteigt. Außerdem stehen Typkonvertierungsprozeduren und -funktionen zur Verfügung sowie Prozeduren, die speziell die Fähigkeiten des Intel-Prozessors 80x87 ausnutzen.
-
Str stellt Prozeduren zur Manipulation von Objekten des Datentyps A R R A Y O F CHAR zur Verfügung.
-
Lib umfaßt eine ganze Reihe von nützlichen Routinen, wie Sortierprozeduren (Quicksort, Heapsort), Zufallszahlengeneratoren, Prozeduren zum Aufrufen einer DOS-Umgebung, erweiterte Speicherverwaltungsroutinen, nicht im Modul Storage enthaltene Arithmetik-
3. Sprachdialekte
320
Tab. 3.2: Erweiterte Bibliotheken in TopSpeed-Modula-2. Bibliothek System
Assembler
Utility
lnput-/Output
Windows
Modul System
X
AsmLib
X
MathLib
X
Str
X
Lib
X
Storage
X
Process
X
Graph
X
FIO
X
IO
X
Window
X
FloatExc
X
ProcTrace
X
3.2 TopSpeed-Modula-2
321
Prozeduren, DOS-Prozeduren zum Ansprechen von Registern, Absetzen von Interrupts und Aufrufen von Programmen, erweiterte Fehlerbehandlungsroutinen sowie Prozeduren zur Ansteuerung des PC-Lautsprechers. -
Storage enthält Prozeduren zur dynamischen Speicherverwaltung bzw. zur Verwaltung des Heap.
-
Process implementiert einen Multi-Prozessor-Manager, der die parallele Programmierung mit Routinen zur Prozeßsynchronisation unterstützt.
-
Graph ist ein Modul, der verschiedene PC-Grafikkarten (CGA, EGA, VGA, Hercules und AT&T) unterstützt.
-
FIO enthält Zugriffsroutinen für MS-DOS-Files. Die ein- und auszugebenden Informationen können sowohl als unformatierter Binärcode vorliegen, als auch aus formatierten Daten oder Text bestehen. In der Regel stellen Files Plattendateien dar, sie können aber auch auf anderen Einheiten abgelegt werden.
-
IO verfügt über Prozeduren und Funktionsprozeduren für die formatierte Ein- und Ausgabe mittels Tastatur und Bildschirm. Es werden Ein-/Ausgabeoperationen für alle Standarddatentypen (CHAR, BOOLEAN, INTEGER, CARDINAL, REAL, ARRAY OF CHAR), für erweiterte Standarddatentypen in TopSpeed-Modula-2 (SHORTINT, LONGINT, SHORTCARD, LONGCARD, LONGREAL) und für hexadezimale Zahlen angeboten.
-
Window gestattet es, Bereiche des Bildschirms als Ausgabebereiche (sogenannte Windows) zu definieren. Diese Windows können simultan und auch überlappend in Bildschirmausgaben eingeblendet werden.
-
FloatExc unterstützt die Fehlerbehandlung für den Intel-Prozessor 80x87.
-
ProcTrace erlaubt es, Prozeduraufrufe in Programmen zu suchen. Auch können Werte von Variablen geprüft werden.
TopSpeed-Modula-2 enthält zusätzlich einen Kern an Systembibliotheken, die obwohl nicht standardisiert - von den meisten Compilerherstellern als Standardbibliotheken angeboten werden. In der Tabelle 3.3 sind diese Bibliotheksmodule aufgeführt. Auf eine ausführliche Beschreibung wird hier jedoch verzichtet, da einige dieser Bibliotheksmodule bereits in Kapitel 2 behandelt wurden.
3. Sprachdialekte
322
Tab. 3.3: Bibliotheken des Modula-2-Kernsystems in TopSpeed-Modula-2. Biblio^xthek System Assembler Utility Modul ASCII
lnput-/Output
Windows
X
InOut
X
MathLibO
X
ReallnOut Strings
X
X
Terminal
X
Die Module haben im einzelnen folgende Aufgaben: -
ASCII enthält symbolische Konstanten für nicht druckbare ASCIIZeichen.
-
InOut stellt Ein-/Ausgabeoperationen für Dateien, das BenutzerTerminal und andere Einheiten zur Verfügung.
-
MathLibO bietet trigonometrische und logarithmische Funktionen auf reelle Zahlen (Datentyp REAL) an.
-
ReallnOut stellt Ein-/Ausgabeoperationen für reelle Zahlen (Datentyp REAL) zur Verfügung.
-
String enthält Operationen zur Manipulation von Zeichenketten (Datentyp ARRAY O F CHAR).
-
Terminal bietet Ein-/Ausgabeoperationen für Zeichen (Datentyp CHAR) und Zeichenketten (Datentyp ARRAY O F CHAR) an.
3.2.1 Reservierte Wörter
323
Nachdem dieses Kapitel einen Überblick über die in TopSpeed-Modula-2 vorgenommenen Sprach- und Systemerweiterungen gegeben hat, sollen nun in den nachfolgenden Kapiteln einige Details behandelt werden. Sie betreffen neue Reservierte Wörter (Kapitel 3.2.1), neue oder geänderte Standarddatentypen (Kapitel 3.2.2), neue und erweiterte Standardfunktionen (Kapitel 3.2.3) und erweiterte Standardbibliotheken (Kapitel 3.2.4).
3.2.1 Reservierte Wörter TopSpeed-Modula-2 verfügt gegenüber dem in Kapitel 2 vorgestellten Modula-2Standard über drei weitere Reservierte Wörter: FORWARD, GOTO und LABEL. Diese Reservierten Wörter dienen folgenden Zwecken: -
FORWARD wird bei der Deklaration von Prozeduren verwendet. FORWARD gestattet es, die Schnittstellen von später im Quelltext definierten Prozeduren oder Funktionsprozeduren im Voraus bekanntzugeben. (Diese Regel ist bereits von Pascal bekannt und kann eigentlich als Rückschritt angesehen werden, da in Modula-2 nach Wirth-Standard keine Reihenfolgevorschrift bezüglich deklarierter Prozeduren und Funktionsprozeduren existiert.)
-
GOTO ist eine Sprunganweisung, auf die eine aus einer Ziffernfolge bestehende Marke folgt. Die Ausführung einer Sprunganweisung bewirkt eine Programmfortsetzung an einer anderen, mit der Marke gekennzeichneten Programmstelle.
-
LABEL wird zur Deklaration von Sprungmarken benötigt. LABELDeklarationen stellen somit neben Konstanten-, Typen-, Variablen-, (inneren) Modul- und Prozedurdeklarationen eine weitere Deklarationsart dar.
3.2.2 Standarddatentypen Die in TopSpeed-Modula-2 verfügbaren neuen oder geänderten Standarddatentypen erinnern an die Programmiersprache C. Für das Rechnen mit ganzen oder natürlichen Zahlen stehen mit den Datentypen LONGINT und LONGCARD erheblich erweiterte Wertebereiche zur Verfügung. Auf ersatzweise praktizierte Berechnungen in gebrochenen Zahlen (der Datentyp REAL überdeckt einen wesentlich größeren Wertebereich) wird man häufig verzichten können. Dies spart einerseits Speicherplatz, und andererseits können Rundungsfehler durch Ganzzahlberechnungen im Datentyp REAL nicht mehr auftreten. Ebenso kann Speicherplatz mit den „kurzen" Datentypen SHORTINT und SHORTCARD gespart werden, die sich dar-
324
3. Sprachdialekte
überhinaus ganz vorzüglich als Laufindices verwenden lassen. Das Rechnen mit gebrochenen Zahlen geringerer Genauigkeit gestattet in TopSpeed-Modula-2 der Datentyp REAL; dagegen entspricht der Datentyp LONGREAL in TopSpeed-Modula-2 dem bisherigen Datentyp REAL. Die Tabelle 3.4 gibt eine Übersicht über die numerischen Standarddatentypen des Systems TopSpeed-Modula-2. In der zweiten Spalte wird die Länge in Bytes und in der dritten Spalte der Wertebereich des jeweiligen Datentyps angegeben.
Tab. 3.4: Numerische Standarddatentypen in TopSpeed-Modula-2. Numerischer Standarddatentyp
Länge in Bytes
Wertebereich max
min
Natürliche Zahlen SHORTCARD CARDINAL LONGCARD
1 2 4
0 0 0
255 65535 4294967295
Ganze Zahlen SHORTINT INTEGER LONGINT
1 2 4
-128 -32768 -2147483648
... ... ...
127 32767 2147483647
Gebrochen rationale Zahlen REAL
4
LONGREAL
8
1.2 E - 3 8 ... 3.4 E + 3 8 6 digits precision 2.3 E - 3 0 8 ... 1.7 E + 3 0 8 15 digits precision
3.2.2 Standarddatentypen
325
Auch einige Systemdatentypen wurden in TopSpeed-Modula-2 erweitert; sie sind in der Tabelle 3.5 angegeben. Zusätzlich zu den Systemdatentypen WORD und ADDRESS verfügt TopSpeed-Modula-2 über die Datentypen LONGWORD, SHORTADDR und BYTE. Diese Systemdatentypen sind mit den in TopSpeed-Modula-2 neu eingeführten Standarddatentypen kompatibel. Der Systemdatentyp SHORTADDR entspricht dem bisherigen Systemdatentyp ADDRESS. Der Systemdatentyp ADDRESS ist somit kompatibel mit allen 4-Byte-Datentypen (LONGCARD, LONGINT, REAL).
Tab. 3.5: SYSTEM-Standarddatentypen des TopSpeed-Modula-2-Systems. Systemdatentypen
Länge in Bytes
kompatibel mit
WORD
2
CARDINAL, INTEGER
LONGWORD
4
LONGCARD, LONGINT, REAL
SHORTADDR
2
CARDINAL, INTEGER
ADDRESS
4
LONGCARD, LONGINT, REAL
BYTE
1
SHORTCARD, SHORTINT, CHAR, BOOLEAN
In TopSpeed-Modul-2 wurde auch der Gebrauch von Offenen Feldern als Parameter in Prozeduren erweitert. Als Parameter können nun auch die Systemdatentypen „ARRAY OF BYTE" und „ARRAY OF LONGWORD" verwendet werden. Wie die Tabelle 3.6 zeigt, sind diese Typen mit allen anderen einfachen und strukturierten Datentypen kompatibel. Mit Hilfe dieser erweiterten Systemdatentypen lassen sich typunabhängige Prozeduren und Funktionsprozeduren programmieren.
326
3. Sprachdialekte
Tab. 3.6: Offene Felder in TopSpeed-Modula-2 als formale Parameter. Offene Felder als formale Parameter
kompatibel mit
ARRAY OF BYTE
einem beliebigen Datentyp
ARRAY OF LONGWORD
einem beliebigen Datentyp
3.2.3 Standardfunktionen und -prozeduren Die Einführung neuer Datentypen und die Erweiterung vorhandener Datentypen zogen in TopSpeed-Modula-2 Änderungen an einigen Standardfunktionen nach sich. Betroffen sind die Funktionen FLOAT, MAX, MIN, T R U N C und VSIZE: -
FLOAT erwartet zwar als Parameter wie bisher eine Variable oder einen Wert vom Datentyp CARDINAL, das Ergebnis der Funktion ist jedoch ein Wert vom Datentyp R E A L neuer Art. Dieser Datentyp weicht vom bisherigen Datentyp R E A L ab. Angaben zu dem Datentyp R E A L in TopSpeed-Modula-2 enthält die Tabelle 3.4.
-
MAX läßt sich konsequenterweise auf alle numerischen Standarddatentypen in TopSpeed-Modula-2 anwenden, also auf die Datentypen SHORTCARD, CARDINAL, LONGCARD, SHORTINT, INTEGER, LONGINT, R E A L und L O N G R E A L sowie auch auf Unterbereichs- und Enumerationstypen. MAX liefert den größten Wert, der mit einem als Parameter zu übergebenden Datentyp dargestellt werden kann.
-
MIN liefert den kleinsten Wert, der mit einem als Parameter zu übergebenden Datentyp dargestellt werden kann; als Parameter sind die gleichen Datentypen wie bei MAX zulässig.
-
TRUNC konvertiert einen Wert oder eine Variable vom Datentyp CARDINAL in den Datentyp R E A L (neuer Art); für das Ergebnis gelten die bereits zur Funktion FLOAT angebrachten Bemerkungen.
-
VSIZE ermittelt den für eine Komponente eines R E C O R D benötigten Platz im Arbeitsspeicher. Als Parameter sind an VSIZE der Datentyp des R E C O R D und der Name der Komponente zu übergeben.
3.2.4 Standardbibliotheken
327
TopSpeed-Modula-2 weist einige neue Standardprozeduren auf, und zwar die Prozeduren HALT, NEW und DISPOSE. „HALT" ist eine parameterlose Prozedur, die den regulären Abbruch eines Programms bewirkt. Ein regulärer Abbruch liegt vor, wenn ein Programm nicht (an einer beliebigen Stelle) durch das Betriebssystem oder durch das Laufzeitsystem unterbrochen, sondern an einer definierten Stelle beendet wird. Die Prozeduren NEW und DISPOSE dienen der Verwaltung dynamischer Datenstrukturen. Sie waren bereits Bestandteil eines früheren Modula-2-Standards, wurden aber in der zweiten Revision des Modula-2-Standards im Jahre 1985 nicht mehr berücksichtigt. NEW und DISPOSE entsprechen den Prozeduren ALLOCATE und DEALLOCATE, weisen jedoch einfachere Parameter auf. NEW reserviert Speicherplatz für ein Datenobjekt, auf das ein als Parameter zu übergebender Zeiger verweist, und DISPOSE gibt den Speicherplatz frei, auf den ein als Parameter zu übergebender Zeiger verweist.
3.2.4 Standardbibliotheken Die vom Hersteller von TopSpeed-Modula-2 angebotenen, erweiterten Bibliotheken umfassen die in der Tabelle 3.2 aufgeführten Module. Beispielhaft sollen nun für einige dieser Module die dem Benutzer angebotenen Modulleistungen in Form von exportierten -
Prozeduren,
-
Funktionsprozeduren,
-
Konstanten,
-
Typen und
-
Variablen
benannt und kurz beschrieben werden. Dies geschieht in tabellarischer Form für die Module: -
MATHLIB (Tabellen 3.7a bis 3.7c),
-
Str (Tabellen 3.8a bis 3.8e),
-
IO (Tabellen 3.9a bis 3.9f),
-
FIO (Tabellen 3.10a bis 3. lOd).
Der Bibliotheksmodul FIO umfaßt noch eine ganze Reihe von Schreib- und Leseoperationen für Plattendateien, die sowohl formatiertes als auch unformatiertes Schreiben und Lesen gestatten. Diese Operationen entsprechen im wesentlichen den dem gleichen Zweck dienenden Operationen des Bibliotheksmoduls IO. Es genügt daher, wenn diese Operationen im folgenden nur aufgezählt und nicht im ein-
328
3. Sprachdialekte
zelnen beschrieben werden. Für die formatierte Ein- und Ausgabe stehen folgende Prozeduren und Funktionsprozeduren zur Verfügung (vgl. [JP88, S.182ff]): PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE
RdChar (F : File) : CHAR; RdBool (F : File) : BOOLEAN; RdShtlnt (F : File) : SHORTINT; Rdlnt (F : File) : INTEGER; RdLnglnt (F : File) : LONGINT; RdShtCard (F : File) : SHORTCARD; RdCard (F : File) : CARDINAL; RdLngCard (F : File) : LONGCARD; RdShtHex (F : File) : SHORTCARD; RdHex (F : File) : CARDINAL; RdLngHex (F : File) : LONGCARD; RdReal (F : File) : REAL; RdLngReal (F : File) : LONGREAL; WrChar (F : File; V : CHAR); WrBool (F : File; V : BOOLEAN; Length : INTEGER); WrShtlnt (F : File; V : SHORTINT; Length : INTEGER); Wrlnt (F : File; V : INTEGER; Length : INTEGER); WrLnglnt (F : File; V : LONGINT; Length : INTEGER); WrShtCard (F : File; V : SHORTCARD; Length : INTEGER); WrCard (F : File; V : CARDINAL; Length : INTEGER); WrLngCard (F : File; V : LONGCARD; Length : INTEGER); WrShtHex (F : File; V : SHORTCARD; Length : INTEGER); WrHex (F : File; V : CARDINAL; Length : INTEGER); WrLngHex (F : File; V ; LONGCARD; Length : INTEGER); WrReal (F : File; V : REAL; Precision : CARDINAL; Length : INTEGER); WrLngReal (F : File; V : LONGREAL; Precision : CARDINAL; Length : INTEGER);
Für die unformatierte Ein- und Ausgabe sind folgende Prozeduren und Funktionsprozeduren verfügbar (vgl. [JP88, S.182ff]): PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE
RdStr (F : File; VAR V : ARRAY OF CHAR); Rdltem (F : File; VAR V : ARRAY OF CHAR); RdBin (F : File; VAR Buf : ARRAY OF BYTE; Count : CARDINAL) : CARDINAL; WrStr (F : File; V : ARRAY OF CHAR); WrStrAdj (F : File; V : ARRAY OF CHAR; Length : INTEGER); WrCharRep (F : File; V : CHAR; Count : CARDINAL); WrLn (F : File); WrBin (F : File; Buf : ARRAY OF BYTE; Count : CARDINAL);
Außerdem enthält der Bibliotheksmodul FIO folgende Prozeduren zur Manipulation von Unterverzeichnissen (vgl. [JP88, S.182ff]): PROCEDURE PROCEDURE PROCEDURE PROCEDURE PROCEDURE
ChDir (Name : ARRAY OF CHAR); MkDir (Name : ARRAY OF CHAR); RmDir (Name : ARRAY OF CHAR); GetDir (Drive : SHORTCARD; VAR Name : ARRAY OF CHAR); ReadFirstEntry (DirName : ARRAY OF CHAR; Attr : FileAttr; VAR D : DirEntry) : BOOLEAN PROCEDURE ReadNextEntry (VAR D : DirEntry) : BOOLEAN;
3.2.4 Standardbibliotheken
329
Tab. 3.7a: Schnittstelle des Moduls MATHLIB. Modul
MATHLIB
Leistung
PROC
Schnittstelle
FU NC CONST TYPE VAR
Beschreibung
Sin
X
PROCEDURE Sin(A:LONGREAL): LONGREAL; Liefert für den im Bogenmaß gegebenen Winkel der Variablen A den Sinuswert.
Cos
X
PROCEDURE Cos(AiLONGREAL): LONGREAL; Liefert für den im Bogenmaß gegebenen Winkel der Variablen A den Cosinuswert.
Tan
X
PROCEDURE Tan(A:LONGREAL): LONGREAL; Liefert für den im Bogenmaß gegebenen Winkel der Variablen A den Tangenswert.
ASin
X
PROCEDURE ASin(A:LONGREAL) : LONGREAL; Liefert für den Sinuswert der Variablen A den zugehörigen Winkel im Bogenmaß (Arcussinus).
ACos
X
PROCEDURE ACos(ArLONGREAL): LONGREAL; Liefert für den Cosinuswert der Variablen A den zugehörigen Winkel im Bogenmaß (Arcuscosinus).
ATan
X
PROCEDURE ATan(A:LONGREAL): LONGREAL; Liefert für den Tangentswert der Variablen A den zugehörigen Winkel im Bogenmaß (Arcustangens).
SinH
X
PROCEDURE SinH(A:LONGREAL): LONGREAL; Liefert für den im Bogenmaß gegebenen Winkel der Variablen A den Sinus-Hyperbolicus.
CosH
X
PROCEDURE CosH(A:LONGREAL) : LONGREAL; Liefert für den im Bogenmaß gegebenen Winkel der Variablen A den Cosinus-Hyperbolicus.
330
3. Sprachdialekte
Tab. 3.7b: Schnittstelle des Moduls MATHLIB. Modul
MATHLIB
Schnittstelle
Leistung
PROC FUNC CONST TYPE VAR
Beschreibung
TanH
X
Log
X
LoglO
X
Pow
X
Exp
X
PROCEDURE Exp(A:LONGREAL): LONGREAL; Liefert für den Wert der Variablen A den Wert der Exponentialfunktion e x .
Mod
X
PROCEDURE Mod(X,Y:LONGREAL): LONGREAL; Liefert den Divisionsrest des Quotienten X/Y.
Rexp
X
Sqrt
X
LongToBcd
X
PROCEDURE TanH(A:LONGREAL): LONGREAL; Liefert für den im Bogenmaß gegebenen Winkel der Variablen A den TangensHyperbolicus. PROCEDURE Log(A:LONGREAL) : LONGREAL; Liefert den natürlichen Logarithmus für den Wert der Variablen A. PROCEDURE LoglO(A:LONGREAL): LONGREAL; Liefert den Zehner-Logarithmus für den Wert der Variablen A. PROCEDURE Pow(X,Y:LONGREAL): LONGREAL; Liefert das Ergebnis der Potenz von xy.
PROCEDURE Rexp(VAR LINTEGER; A:LONGREAL):LONGREAL; Liefert für den Wert der Variablen A den Exponent in der Variablen I und die Mantisse im Funktionsergebnis zur Basis 2. PROCEDURE Sqrt(A:LONGREAL): LONGREAL; Liefert für den Wert der Variablen A die Quadratwurzel. PROCEDURE LongToBcd(A:LONGREAL) :PackedBcd; Liefert für den Wert der Variablen A die gepackte BCD-Darstellung.
3.2.4 Standardbibliotheken
331
Tab. 3.7c: Schnittstelle des Moduls MATHLIB. Modul
MATHLIB
Leistung
PROC
BcdTo Long
LoadCon trolWord
FUNC CONST T Y P E VAR
PROCEDURE
LoadControlWord (C:BITSET); Lädt das mit C übergebene Kontrollwort in den Intel-Prozessor 8087.
X
PROCEDURE StoreControIWord(): BITSET; Liefert das Kontrollwort des Intel-Prozessors 8087 zurück.
X
PROCEDURE ClearExceptions(); Setzt das Ausnahme-, Interrupt- und BusyFlag im Statuswort des Intel-Prozessors 8087 zurück.
X
Environment
StoreEn-
Beschreibung PROCEDURE BcdToLong (A:PackedBcd):LONGREAL; Transformiert die gepackte BCD-Darstellung der Variablen A in die LONGREALDarstellung.
X
StoreControlWord
ClearExceptions
Schnittstelle
X
X
Environment = RECORD ControlWord:BITSET; StatusWordrBITSET; TagWord:BITSET; IPrCARDINAL; OpCode:CARDINAL; DataPointer:CARDINAL; R80287: CARDINAL END; PROCEDURE StoreEnvironment(): Environment; Liefert die momentane Umgebung des Intel-Prozessors 8087 in Environment zurück.
332
3. Sprachdialekte
Tab. 3.8a: Schnittstelle des Moduls Str. Modul
Str
Leistung
PROC
Append
X
PROCEDURE Append(VAR R: A R R A Y OF CHAR; S:ARRAY OF CHAR); Fügt die Zeichenkette S an die Zeichenkette R an. Ist die Länge von R + S größer als die ursprüngliche Länge von R, so werden insgesamt soviele Zeichen an R angehängt, wie R aufnehmen kann.
Caps
X
PROCEDURE Caps(VAR S l : A R R A Y OF CHAR); Konvertiert Kleinbuchstaben in die entsprechenden Großbuchstaben.
Compare
Schnittstelle FUNC CONST T Y P E VAR
X
Beschreibung
PROCEDURE
Compare(Sl,S2:ARRAY OF CHAR):INTEGER; Überprüft die Zeichenketten S l und S2 Zeichen für Zeichen auf Identität. Ist S l kleiner als S2, so erhält die Funktion den Rückgabewert -1, während bei Gleichheit der Wert 0 und für den Fall, daß S l größer als S2 ist, der Wert 1 zurückgeliefert wird.
Copy
X
PROCEDURE Copy(VAR R:ARRAY OF CHAR; S:ARRAY OF CHAR); Kopiert die Zeichenkette S nach R. Ist die Länge von R größer als die von S, so wird S vollständig nach R kopiert; andernfalls kann von S nur die Anzahl von Zeichen nach R kopiert werden, die R aufnehme kann.
Length
X
PROCEDURE
Pos
Length(S:ARRAY OF CHAR):CARDINAL; Ermittelt die Länge der Zeichenkette S, ohne den Zeichenkettenterminator mitzuzuzählen X
PROCEDURE
Pos(S,P:ARRAY OF CHAR):CARDINAL; Ermittelt die Position des ersten Auftretens der Zeichenkette P in der Zeichenkette S und liefert diese als natürliche Zahl zurück.
3.2.4 Standardbibliotheken
333
Tab. 3.8b: Schnittstelle des Moduls Str. Modul
Str
Leistung
PROC
Schnittstelle FUNC CONST T Y P E VAR
Beschreibung
Slice
X
PROCEDURE Slice(VAR R:ARRAY OF CHAR; S:ARRAY OF CHAR; P,L:CARDINAL); Kopiert L Zeichen ab der Position P aus der Zeichenkette S in die Zeichenkette R. Ist P größer als die Länge von S, so wird ein Leerstring kopiert. Falls P + L - l größer als die Länge der Zeichenkette S ist, so werden ab P alle Zeichen bis zum Ende von S kopiert.
Item
X
PROCEDURE Item(VAR R:ARRAY OF CHAR; S:ARRAY OF CHAR;T:CHARSET; N:CARDINAL); Analysiert die Zeichenkette S und teilt sie auf in Teilzeichenketten, die durch Zeichen der in T beschriebenen Zeichenmenge voneinander getrennt werden. Item liefert die N-te Zeichenkette (beginnend bei 0) in der Zeichenkette R ab.
ItemS
X
PROCEDURE ItemS(VAR R:ARRAY OF CHAR; S,T:ARRAY OF CHAR; N:CARDINAL); Dient dem gleichen Zweck wie Item. Die Trennzeichen werden jedoch als Feld und nicht als Menge angegeben.
Insert
X
PROCEDURE Insert(VAR R:ARRAY OF CHAR; S:ARRAY OF CHAR; P:CARDINAL); Fügt die Zeichenkette S an der Position P in die Zeichenkette R ein. Ist P größer als die Länge der Zeichenkette R, dann wird S am Ende der Zeichenkette R eingefügt.
Delete
X
PROCEDURE Delete(VAR R:ARRAY OF CHAR; P,L:CARDINAL); Löscht in der Zeichenkette R ab der Position P die Anzahl von L Zeichen, sofern P kleiner als die Länge von R ist. Falls
334
3. Sprachdialekte
Tab. 3.8c: Schnittstelle des Moduls Str. Modul
Str
Leistung
PROC
Schnittstelle FUNC CONST T Y P E VAR
Beschreibung P + 1 ^ 1 größer als die Länge der Zeichenkette R ist, so werden ab P alle Zeichen bis zum Ende von R gelöscht. PROCEDURE Match(Source,Pattern: A R R A Y OF CHAR): BOOLEAN; Prüft, ob die Zeichenkette Source die Zeichenkette Pattern enthält und liefert das Prüfergebnis als BOOLEAN zurück. Die Zeichenkette Pattern kann dabei „Wildcards" enthalten. Das Wildcard „*" steht stellvertretend für eine Sequenz (auch leere Sequenz) von Zeichen, wohingegen das Wildcard „?" für ein einzelnes beliebiges Zeichen steht.
Match
X
IntToStr
X
PROCEDURE
CardToStr
X
PROCEDURE
IntToStr(V:LONGINT; VAR S:ARRAY OF CHAR; Base:CARDINAL; VAR ok:BOOLEAN); Konvertiert ein Datenobjekt V vom Datentyp LONGINT zur Basis Base in die Zeichenkette S. Das Ergebnis der Transformation wird in der booleschen Variablen ok bereitgestellt. Ok ist FALSE, wenn beispielsweise die angegebene Zeichenkette für die Transformation zu kurz ist. CardToStr(V:LONGCARD; VAR S:ARRAY OF CHAR; Base:CARDINAL; VAR okrBOOLEAN) ; Konvertiert ein Datenobjekt V vom Datentyp LONGCARD zur Basis Base in die Zeichenkette S. Das Ergebnis der Transformation wird in der boole'schen Variablen ok bereitgestellt. Ok ist FALSE, wenn beispielsweise die angegebene Zeichenkette für die Transformation zu kurz ist.
3.2.4 Standardbibliotheken
335
Tab. 3.8d: Schnittstelle des Moduls Str. Modul
Str
Leistung
PROC FUNC CONST TYPE VAR
RealToStr
FixRealToStr
StrToInt
Schnittstelle Beschreibung PROCEDURE RealToStr(V:LONGREAL; Precision: CARDINAL; Eng: BOOLEAN; VAR S:ARRAY OF CHAR; VAR ok:BOOLEAN); Konvertiert ein Datenobjekt V vom Datentyp LONGREAL mit der Genauigkeit Precision (Stellenzahl der Mantisse) in die Zeichenkette S und liefert in der boole'schen Variablen ok das Ergebnis der Transformation. Ok ist beispielsweise FALSE, wenn die angegebene Zeichenkette für die Transformation zu kurz ist. Falls Eng TRUE ist, wird die ingenieurmäßige Darstellung zur Ausgabe der Zahlen (Exponent als Vielfaches von drei) gewählt.
X
PROCEDURE FixRealToStr(V:LONGREAL; Precision:CARDINAL; VAR S:ARRAY OF CHAR; VAR ok:BOOLEAN); Konvertiert ein Datenobjekt V vom Datentyp LONGREAL, dargestellt in Fixpunktschreibweise mit der Genauigkeit Precision (Anzahl der Nachkommastellen), in die Zeichenkette S und liefert in der boole'schen Variablen ok das Ergebnis der Transformation. Ok ist beispielsweise FALSE, wenn die angegebene Zeichenkette für die Transformation zu kurz ist oder wenn der Absolutbetrag von V größer als 1.0E18 ist.
X
X
PROCEDURE StrToInt(S:ARRAY OF CHAR; Base:CARDINAL; VAR ok: BOOLEAN):LONGINT; Konvertiert die Zeichenkette S zur Basis Base in eine LONGINT-Zahl. Das Ergebnis der Transformation wird in der boole'schen Variablen ok zurück geliefert. Als Basis ist ein Wert zwischen 2 und 16 zulässig. ok ist FALSE, wenn sich die Zei-
336
3. Sprachdialekte
Tab. 3.8e: Schnittstelle des Moduls Str. Modul
Str
Leistung
PROC FUNC CONST TYPE VAR
Schnittstelle Beschreibung chenkette S nicht als LONGINT-Zahl darstellen läßt.
StrToCard
X
StrToReal
X
PROCEDURE StrToCard(S:ARRAY OF CHAR; Base: CARDINAL; VAR ok: BOOLEAN): LONGCARD; Konvertiert die Zeichenkette S zur Basis Base in eine LONGCARD-Zahl. Das Ergebnis der Transformation wird in der booleschen Variablen ok zurückgeliefert, als Basis ist ein Wert zwischen 2 und 16 zulässig. Ok ist FALSE, wenn sich die Zeichenkette S nicht als LONGCARDZahl darstellen läßt. PROCEDURE StrToReal(S:ARRAY OF CHAR; VAR ok:BOOLEAN): LONGREAL; Konvertiert die Zeichenkette S in eine LONGREAL-Zahl. Das Ergebnis der Transformation wird in der booleschen Variablen ok zurückgeliefert. Ok ist FALSE, wenn sich die Zeichenkette S nicht als LONGREAL-Zahl darstellen läßt.
3.2.4 Standardbibliotheken
337
Tab. 3.9a: Schnittstelle des Moduls IO. Modul
IO
Schnittstelle
Leistung
PROC
MaxRdLength
FUNC CONST T Y P E VAR
Anzahl der maximal zu lesenden Zeichen ( = 256)
X
WrStrType
Prozedurdatentyp zum Schreiben von Zeichen: PROCEDURE (ARRAY OF CHAR);
X
RdStrType
Prozedurdatentyp zum Lesen von Zeichen: PROCEDURE (VAR A R R A Y OF CHAR);
X
CHARSET
Mengendatentyp von Zeichen: SET OF CHAR;
X
RdLnOnWr Prompt WrStrRedirect
X
löscht Eingabepuffer nach Schreibvorgang
X
Prompt „?" nach einem erfolglosen Lesevorgang, wenn Prompt T R U E ist
X
RdStrRedirect
X
Separators
Beschreibung
Variable vom Prozedurdatentyp: WrStrTyp Variable vom Prozedurdatentyp: RdStrType
X
Zeichenmenge als Separator, wie CHR(9) CHR(IO), CHR(13),...
OK
X
boole'sche Prüfung, ob formatierter Input-/Output
ChopOff
X
Eng
X
WrChar
X
boole'sche Prüfung, ob Speicherplatz für formatierte Daten vorhanden ist ingenieurgemäße Darstellung ja/nein PROCEDURE WrChar(V:CHAR); Schreibt das Zeichen V auf die Standardausgabeeinheit.
3. Sprachdialekte
338
Tab. 3.9b: Schnittstelle des Moduls IO. Modul
IO
Schnittstelle
Leistung
PROC FUNC CONST TYPE VAR
Beschreibung
WrBool
X
PROCEDURE WrBool(V:BOOLEAN; Length:INTEGER); Schreibt den Wahrheitswert V mit der Länge Length auf die Standardausgabeeinheit.
WrShtInt
X
PROCEDURE WrShtInt(V:SHORTINT; Length:INTEGER) ; Schreibt die kurze Ganzzahl V mit der Länge Length auf die Standardausgabeeinheit.
Wrlnt
WrLngInt
WrShtCard
WrCard
WrLngCard
WrShtHex
X
X
X
X
X
X
PROCEDURE WrInt(V:INTEGER; Length:INTEGER); Schreibt die Ganzzahl V mit der Länge Length auf die Standardausgebeeinheit. PROCEDURE WrLngInt(V:LONGINT; Length:INTEGER) ; Schreibt die lange Ganzzahl V mit der Länge Length auf die Standardausgabeeinheit.. PROCEDURE WrShtCard(V:SHORTCARD; Length: INTEGER); Schreibt die kurze natürliche Zahl V mit der Länge Length auf die Standardausgabeeinheit. PROCEDURE WrCard(V:CARDINAL; Length:INTEGER); Schreibt die natürliche Zahl V mit der Länge Length auf die Standardausgabeeinheit. PROCEDURE WrLngCard(V:LONGCARD; Length: INTEGER); Schreibt die lange natürliche Zahl V mit der Länge Length auf die Standardausgabeeinheit. PROCEDURE WrShtHex(V:SHORTCARD; Length: INTEGER); Schreibt die kurze hexadezimale Zahl V mit der Länge Length auf die Standardausgabeeinheit.
339
3.2.4 Standardbibliotheken
Tab. 3.9c: Schnittstelle des Moduls IO. Modul
IO
Schnittstelle
Leistung
PROC
FUNC CONST T Y P E VAR
Beschreibung
WrHex
X
PROCEDURE WrHex(V:CARDINAL; Length:INTEGER); Schreibt die hexadezimale Zahl V mit der Länge Length auf die Standardausgabeeinheit.
WrLngHex
X
PROCEDURE WrLngHex(V:LONGCARD; Length: INTEGER); Schreibt die lange hexadezimale Zahl V mit der Länge Length auf die Standardausgabeeinheit.
WrReal
X
PROCEDURE
WrLngReal
X
PROCEDURE WrLngReal(V:LONGREAL; Precision:CARDINAL; Length: INTEGER); Schreibt die lange reelle Zahl V mit der Genauigkeit Precision und der Länge Length auf die Standardausgabeeinheit.
WrStr
X
PROCEDURE WrStr(S:ARRAY OF CHAR); Schreibt die Zeichenkette S auf die Standar dausgabeeinheit.
X
PROCEDURE WrStrAdj (S : A R R A Y OF CHAR; Length: INTEGER); Schreibt die Zeichenkette S mit der Länge Length auf die Standardausgabeeinheit.
X
PROCEDURE
WrStrAdj
WrCharRep
WrReal(V:REAL; Precision:CARDINAL; Length:INTEGER); Schreibt die reelle Zahl V mit der Genauigkeit Precision und der Länge Length auf die Standardausgabeeinheit.
WrCharRep(V:CHAR; Count:CARDINAL) ; Schreibt das Zeichen V so oft wie in Count angegeben, auf die Standardausgabeeinheit.
340
3. Sprachdialekte
Tab. 3.9d: Schnittstelle des Moduls IO. Modul
IO
Leistung
PROC FUNC CONST TYPE VAR
WrLn
Schnittstelle Beschreibung PROCEDURE WrLn(); Führt einen Zeilenwechsel auf der Standardausgabeeinheit aus.
X
RdChar
X
RdBool
X
RdShtInt
X
Rdlnt
X
PROCEDURE RdInt():INTEGER; Liest eine Ganzzahl von der Standardeingabeeinheit.
X
PROCEDURE RdLngInt() :LONGINT; Liest ein lange Ganzzahl von der Standardeingabeeinheit.
X
PROCEDURE RdShtCard():SHORTCARD; Liest eine kurze natürliche Zahl von der Standardeingabeeinheit.
RdLngInt RdShtInt
RdCard
RdLngCard
RdShtHex
RdHex
X
PROCEDURE RdChar():CHAR; Liest ein Zeichen von der Standardeingabeeinheit. PROCEDURE RdBool():BOOLEAN; Liest einen Wahrheitswert von der Standardeingabeeinheit. PROCEDURE RdShtInt():SHORTINT; Liest eine kurze Ganzzahl von der Standardeingabeeinheit.
PROCEDURE RdCard():CARDINAL; Liest eine natürliche Zahl von der Standardeingabeeinheit.
X
PROCEDURE RdLngCard():LONGCARD; Liest eine lange natürliche Zahl von der Standardeingabeeinheit.
X
PROCEDURE RdShtHex():SHORTCARD; Liest eine kurze hexadezimale Zahl von der Standardeingabeeinheit.
X
PROCEDURE RdHex():CARDINAL; Liest eine hexadezimale Zahl von der Standardeingabeeinheit.
341
3.2.4 Standardbibliotheken
Tab. 3.9e: Schnittstelle des Moduls IO. Modul
IO
Schnittstelle
Leistung
PROC FUNC CONST TYPE VAR
RdLngHex
X
RdReal
X
RdLngReal
X
RdStr
X
Rdltem
X
RdLn
X
Beschreibung PROCEDURE RdLngHex(): LONGCARD; Liest eine lange hexadezimale Zahl von der Standardeingabeeinheit. PROCEDURE RdReal():REAL; Liest eine reelle Zahl von der Standardeingabeeinheit. PROCEDURE RdLngReal():LONGREAL; Liest ein lange reelle Zahl von der Standardeingabeeinheit. PROCEDURE RdStr(VAR V:ARRAY OFCHAR); Liest eine Zeichenkette von der Standardeingabeeinheit und liefert sie in V ab. PROCEDURE RdItem(F:File;VAR V: ARRAY OFCHAR); Liest eine Zeichenkette von der Standardeingabeeinheit bis zum ersten Mal ein Zeichen der globalen Variablen „Separator" gelesen wird. PROCEDURE RdLn(); Liest einen Zeilenwechsel von der Standardeingabeeinheit.
EndOfRead
X
PROCEDURE EndOfRead(Skip:BOOLEAN):BOOLEAN; Gilt Skip = TRUE, dann wird eine Sequenz von Zeichen (Separatoren) überlesen. EndOfRead liefert den boole'schen Wert TRUE, wenn alle Zeichen von der Standareingabeeinheit gelesen wurden.
KeyPressed
X
PROCEDURE KeyPressed():BOOLEAN; Liefert den boole'schen Wert TRUE, wenn ein Zeichen über die Standardeingabeeinheit eingegeben wurde.
342
3. Sprachdialekte
Tab. 3.9f: Schnittstelle des Moduls IO. Modul
IO
Schnittstelle
Leistung
PROC
RdChar
FUNC CONST T Y P E VAR X
Beschreibung PROCEDURE RdChar():CHAR; Liest ein Zeichen von der Standardeingabeeinheit, ohne es auf der Standardausgabeeinheit darzustellen.
Redirect Input
X
PROCEDURE RedirectInput(FileName: A R R A Y O F CHAR); Schließt den aktuellen Eingabestrom und öffnet einen neuen Eingabestrom.
RedirectOutput
X
PROCEDURE RedirectOutput(FileName:ARRAY O F CHAR); Schließt den aktuellen Ausgabestrom und öffnet einen neuen Ausgabestrom.
343
3.2.4 Standardbibliotheken
Tab. 3.10a: Schnittstelle des Moduls FIO. Modul
HO
Leistung
PROC
Schnittstelle FUNC CONST T Y P E VAR
Beschreibung
MaxOpenFÜes
X
Anzahl der Dateien, die maximal geöffnet werden können ( = 15)
DiskFull
X
Fehler, wenn Plattendatei voll ist ( = 0F0H)
StandardInput
X
Standardeingabeeinheit Tastatur ( = 0)
StandardOutput
X
Standardausgabeeinheit Bildschirm ( = 1)
ErrorOutput
X
Fehlerausgabeeinheit ( = 2)
AuxDevice
X
Hilfsausgabeeinheit ( = 3)
PrinterDevice
X
DruckeTeinheit ( = 4)
BufferOverhead
X
Größe des Puffers ( = SIZE(BufRec)-l)
File
X
Datei-Datentyp: File = CARDINAL
BufRec
X
Puffer-Datentyp: BufRec = RECORD RWPos, EOB, BufSize: CARDINAL; Buffer: ARRAY[0..0) OF SHORTCARD END;
PathS tr
X
MS-DOS-Pfad-Datentyp: PathStr = A R R A Y [0..64] OF CHAR;
PathTail
X
MS-DOS-File-Extension-Datentyp: PathStr = ARRAY[0..12] OF CHAR;
FileAttr
X
Datei-Attribut-Datentyp: FileAttr = SET OF (readonly,hidden, system,volume, directory,archive);
344
3. Sprachdialekte
Tab. 3.10b: Schnittstelle des Moduls FIO. Modul
FIO
Leistung
PROC
Schnittstelle FUNC CONST T Y P E VAR
DirEntry
Beschreibung Inhaltsverzeichniseintrags-Datentyp: DirEntry = RECORD rsvd: ARRAY[0..20] OF SHORTCARD; attr: FileAttr; time, date: CARDINAL; size: LONGCARD; Name: PathTail; END;
X
EOF
X
boole'sche Prüfung auf EndOfFile
IOcheck
X
boole'sche Prüfung, ob Fehler terminieren und ob Report gegeben wird
Separators
X
Zeichenmenge als Separator, wie CHR(9), CHR(IO), C H R ( 1 3 ) , . . . .
OK
X
boole'sche Prüfung, ob formatierter Input-/Output
ChopOff
X
boole'sche Prüfung, ob Speicherplatz für formatierte Daten vorhanden ist
Eng
X
ingenieurgemäße Darstellung ja/nein
IOresult
X
PROCEDURE IOresult():CARDINAL; Fehlercode der letzten IO-Operation. Falls dieser Null ist, liegt kein Fehler vor. Die Fehlercodes sind dem DOS-TechnicalReferenz-Manual zu entnehmen.
Open
X
PROCEDURE
Open(Name:ARRAY OF CHAR):File; Offnet eine Datei Name zum Lesen und Schreiben. Das Dateifenster wird auf den Anfang der Datei gesetzt. Open ermöglicht die Anwendund einiger IO-Operationen. Das Ergebnis einer IO-Operation läßt sich über IOresult abfragen.
3.2.4 Standardbibliotheken
345
Tab. 3.10c: Schnittstelle des Moduls FIO. Modul
no
Schnittstelle
Leistung
PROC FUNC CONST TYPE VAR
Create
X
Append
X
Close
X
Exists
X
AssignBuffer
Erase
X
X
Beschreibung PROCEDURE Create(Name:ARRAY OF CHAR):File; Erzeugt eine Datei Name. Falls die Datei bereits vorhanden ist, wird sie überschrieben. Andernfalls wird der Dateiname in das Dateiverzeichnis eingetragen. Das Dateifenster wird auf den Anfang gesetzt. Create ermöglicht die Anwendung einiger IO-Operationen. Das Ergebnis einer IOOperation läßt sich über IOresult abfragen PROCEDURE Append(Name:ARRAY OF CHAR):File; Öffnet eine Datei Name zum Lesen und Schreiben. Das Dateifenster wird auf das Ende der Datei gesetzt. Append ermöglicht die Anwendung einiger IO-Operationen. Das Ergebnis einer IO-Operation läßt sich über IOresult abfragen. PROCEDURE Close(F:File); Schließt interne Puffer der Datei F und die Datei F selbst. Das Ergebnis dieser IOOperation läßt sich über IOresult abfragen. PROCEDURE Append(Name:ARRAY OF CHAR):BOOLEAN; Prüft, ob die durch Name spezifizierte Datei im Platteninhaltsverzeichnis vermerkt ist. PROCEDURE AssignBuffer(F:File; VAR Buf:ARRAY OFBYTE); Ordnet der Datei F einen größeren internen Speicherbereich zu. Buf sollte ein Vielfaches von 512 und nicht kleiner als 1024 sein. PROCEDURE Erase(Name:ARRAY OF CHAR); Löscht eine Datei Name im Plattenverzeichnis.
346
3. Sprachdialekte
Tab. 3.10d: Schnittstelle des Moduls FIO. Modul
FIO
Leistung
PROC FTJNC CONST TYPE VAR
Rename
X
Truncate
X
GetPos
Seek
Size
Schnittstelle Beschreibung PROCEDURE Rename(Name, NewName:ARRAY OF CHAR); Ändert den Dateinamen Name in NewName. PROCEDURE Truncate(F:File); Schneidet eine Datei F an der aktuellen Position des Dateifensters ab. X
PROCEDURE GetPos(F:File):LONGCARD; Liefert die aktuelle Position des Dateifensters der Datei F in Byte. PROCEDURE Seek(F:File;Pos:LONGCARD); Setzt das Dateifenster der Datei F entsprechend der in Pos angegebenen Bytezahl.
X
X
PROCEDURE Size(F:File):LONGCARD; Ermittelt die Länge der Datei F in Bytes.
4. Systementwicklung mit Modula-2 - ein Beispiel Auf die Modularisierung als Prinzip zur Gestaltung komplexer Softwaresysteme und ihre Vorteile wurde in Kapitel 1 hingewiesen. Dort wurde auch der grundsätzliche Aufbau modular strukturierter Softwaresysteme behandelt, und es wurde betont, daß die Sprache Modula-2 die modulare Systementwicklung in besonderer Weise zu unterstützen vermag. Bei der Vorstellung des Modula-2-Sprachstandards in Kapitel 2 und des Dialekts TopSpeed-Modula-2 in Kapitel 3 konnte diese Eigenschaft in Modula-2 kaum demonstriert werden. Die notwendigerweise im Umfang zu begrenzenden Beispiele ließen das nicht zu. Dieses Defizit zumindest teilweise zu beheben, ist das Anliegen des vorliegenden Kapitels. Am Beispiel eines Fahrschulsystems, das einen wesentlichen Teil der in einem Fahrschulbetrieb anfallenden Informationsverarbeitungsaufgaben unterstützt, soll gezeigt werden, in welcher Weise Modula-2 die Entwicklung modularer Softwaresysteme unterstützt. Die Ausführungen erstrecken sich auf die Entwicklungsphasen Anforderungsdefinition, Entwurf und Implementierung. Aus Platzgründen können allerdings keine vollständigen Entwicklungsunterlagen, sondern nur Auszüge präsentiert werden. Auf Komponenten einer Anforderungsdefinition des Fahrschulsystems geht das Kapitel 4.1 ein. Es enthält eine kurze Beschreibung der Systemfunktionen und der Benutzerschnittstelle des Systems. Die Umsetzung dieser Systemanforderungen in einen Systementwurf ist Gegenstand von Kapitel 4.2. Im Vordergrund steht dabei weniger die Vorgehensweise bei der Modularisierung, sondern vielmehr das Moduldiagramm als ein zentrales Ergebnis dieser Tätigkeit. Die beiden darauf folgenden Kapitel beschäftigen sich mit der Implementierung der Module in Modula-2. Einige ausgewählte Definitionsmodule werden in Kapitel 4.3 und die dazugehörigen Implementationsmodule in Kapitel 4.4 angegeben. Schließlich setzt sich das Kapitel 4.5 mit der Erzeugung eines lauffähigen Modula-2-Programms aus den implementierten Modulen auseinander.
4.1 Systemfunktionen und Benutzerschnittstelle Das Fahrschulsystem dient der Abwicklung von Informationsverarbeitungsaufgaben, wie sie üblicherweise in einem Fahrschulbetrieb auftreten. Zu diesen Aufgaben gehören beispielsweise die Pflege der Schülerstammdatei, die tägliche Erfassung
348
4. Systementwicklung mit Modula-2 - ein Beispiel
und Prüfung von schüler- und fahrlehrer-bezogenen Bewegungsdaten, die Abrechnung von Ausbildungsleistungen und die Archivierung abgeschlossener Fahrausbildungen. Für die Systemstrukturierung wurde ein funktionaler Ansatz gewählt, der sich an den Ablauf der Informationsverarbeitung in einem Fahrschulbetrieb anlehnt. Zentrale Funktionen des Systems sind: - Erfassen von Daten, -
Drucken von Daten,
-
Prüfen von Daten,
-
Verwalten von Daten,
-
Archivieren von Daten und
-
Konfigurieren eines Fahrschulsystems.
Zur weiteren Untergliederung der zentralen Funktionen bieten sich die bearbeiteten Objekte oder spezielle Abläufe in einem Fahrschulbetrieb an. Die Funktion „Erfassen von Daten" umfaßt folgende Teilfunktionen: -
Schüleranmeldung, d.h. Erfassen der Daten, die für die Ausfertigung eines Ausbildungsvertrags erforderlich sind.
-
Ausbildungsdaten komplettieren, d.h. Nacherfassen der Daten, die erst während der Ausbildung bekannt werden.
-
Tagesnachweise erfassen, d.h. tägliches Erfassen der Tätigkeiten der Fahrlehrer (Praxisfahrstunden, theoretischer Unterricht, Bürotätigkeiten, Fahrzeugpflege) und Verbuchen der Tätigkeitsdaten nach Fahrlehrern.
-
Theorieunterricht erfassen, d.h. Erfassen von Thema und Datum einer durchgeführten theoretischen Unterrichtseinheit und Verbuchen dieser Leistung nach Fahrschülern.
-
Gutschriften erfassen, d.h. Erfassen von Gutschriftsdaten (Datum, Kreditinstitut, Betrag usw.) und Verbuchen der Gutschrift auf dem zutreffenden Schülerkonto.
-
Prüfungen erfassen, d.h. Erfassen von Prüfungsdaten (Datum, Art der Prüfung (Theorie oder Praxis), Prüfer, Ergebnis) und Verbuchen der Daten nach Fahrschülern.
Mit Ausnahme des Druckprozesses „Ausbildungsvertrag drucken", der unmittelbar im Anschluß an die Schüleranmeldung gestartet wird, faßt die Funktion „Drucken von Daten" sämtliche Druckroutinen zusammen. Im einzelnen sind das die folgenden Routinen:
4.1 Systemfunktionen und Benutzerschnittstelle
-
Tagesnachweis drucken, d.h. Auflisten sämtlicher von einem Fahrlehrer an einem Tage geleisteter Arbeiten auf einem Tagesnachweisformular. Tätigkeitsnachweise müssen aus rechtlichen Gründen täglich erstellt und über mehrere Jahre archiviert werden.
-
Rechnung drucken, d.h. Erstellen einer Rechnung über die von einem Fahrschüler in Anspruch genommenen Leistungen und zu entrichtenden Gebühren (Grundgebühr, Prüfungsgebühr, Lehrmaterial, Fahrstunden usw.), soweit deren Begleichung noch offen steht.
-
Mahnung drucken, d.h. Erstellen einer Zahlungserinnerung für einen bereits überfälligen Rechnungsbetrag.
-
Endabrechnung drucken, d.h. Erstellen einer Endabrechnung für ein abgeschlossenes Ausbildungsverhältnis, in der für einen Schüler sämtliche erbrachten Leistungen und zu entrichtenden Gebühren, bereits geleistete Zahlungen sowie der noch offene Restbetrag ausgewiesen sind.
-
Nachweis drucken, d.h. Erstellen eines Nachweises über die Theoriestunden, Fahrstunden, Prüfungstermine und Prüfungsfahrten eines Fahrschülers.
349
Die übrigen Funktionen seien nur grob skizziert. Das Prüfen von Daten betrifft eine sogenannte Schülerübersicht, in der verschiedene Informationen für einen Schüler in komprimierter Form zusammengestellt sind, und den Ausbildungsstand pro Schüler. Die Funktion „Verwalten von Daten" ermöglicht das Betrachten, Korrigieren und Löschen von Schülerstammdaten, Kontendaten, Schülerstunden, Fahrlehrerstunden, Rechnungsdaten und Gutschriftsdaten. Die Funktion „Archivieren von Daten" umfaßt das Extrahieren von bestimmten Schülerstammdaten, von Kontendaten und von Fahrlehrerstunden aus der Schülerstammdatei und verschiedenen Bewegungsdateien sowie das Ablegen der extrahierten Daten in einer Form, die jederzeit einen Datenzugriff (Betrachten von Daten) ermöglicht. Mit Hilfe der Funktion „Konfigurieren eines Fahrschulsystems" können verschiedene System- und Betriebsparameter des Programmsystems an die speziellen Gegebenheiten in einem Fahrschulbetrieb angepaßt werden. Die skizzierte funktionale Strukturierung schlägt sich in der Benutzerschnittstelle des durchgängig interaktiv konzipierten Fahrschulsystems nieder. Wie die Abbildung 4.1 zeigt, erstreckt sich die Benutzerführung über drei Menue-Ebenen: -
Die erste Menue-Ebene besteht aus dem Hauptmenue mit den genannten zentralen Funktionen.
-
Die zweite Menue-Ebene vollzieht die beschriebene Untergliederung der zentralen Funktionen nach.
4. Systementwicklung mit Modula-2 - ein Beispiel
350
-
Die dritte Menue-Ebene enthält weitergehende Untergliederungen für einige Bereiche der zweiten Ebene.
Jeder eingerahmte Bereich in Abbildung 4.1 stellt ein Menue dar. Die Menues sind als sogenannte Pop-Up-Menues mit weitgehend identischen Bedienungskommandos gestaltet. Auf dem Bildschirm werden aufeinander folgende Menues überlagernd dargestellt. Die vom Benutzer gewählte Funktionsfolge ist damit jederzeit erkennbar. Zur Verdeutlichung der Benutzerschnittstelle sind auf den folgenden Seiten einige Menues, einige Erfassungsmasken und eine Ausgabeliste dargestellt. Bei den Menues handelt es sich um: -
das Hauptmenue des Fahrschulsystems (Abbildung 4.2),
-
das Menue für die Funktion „Erfassen von Daten" in der zweiten Menue-Ebene (Abbildung 4.3),
-
das Menue für die Funktion „Verwalten von Daten" in der zweiten Menue-Ebene (Abbildung 4.4),
-
das Menue für die Funktion „Verwalten von Stammdaten" in der dritten Menue-Ebene (Abbildung 4.5) und
-
das Menue für die Funktion „Fahrschulparameter-Kostensätze" der in Abbildung 4.1 nicht dargestellten vierten Menue-Ebene (Abbildung 4.6).
Welche Daten beispielsweise mit der Funktion „Erfassen von Daten" manipuliert werden, geht aus der in Abbildung 4.7 gezeigten Maske zum Erfassen von Schülerdaten hervor. Als ein weiteres Beispiel ist in Abbildung 4.8 die Maske zum Erfassen der in einen Tagesnachweis eingehenden Daten zu sehen und in Abbildung 4.9 die Maske zum Verändern von Kostensätzen. Ein Beispiel für eine mit der Funktion „Drukken von Daten" erzeugten Ausgabeliste ist die in Abbildung 4.10 wiedergegebene Rechnung.
.CHULDATEN ssen ken >n alten gurieren i vieren
ARCHIVIEREN
PRÜFEN
KONFIGURIEREN
Schülerübersicht
Automatik
Systemparameter
Ausbildungsstand
Verwalten
Fahrschulparameter
¡HNUNGEN
GUTSCHRIFTEN
SYSTEMPARAMETER
FAHRSCHUL-
nschauen
Anschauen
Schülernummer
¡sehen
Löschen
Fehlerstamm
Kostensätze
Infostamm
Tätigkeiten
Druckersteuerung
Lehrmaterialien
Rechnungsnummer
Lehrinhalte
PARAMETER Betrieb
Fahrlehrer Mahnungstext Informationen Umsatzsteuer Ausbildungsfahrzeuge Rechnungsinter walle
4.1 Systemfunktionen und Benutzerschnittstelle
Abb.4.1: Menue-Ebenen des Fahrschulsystems; s. nebenstehendes Faltblatt.
351
352
4. Systementwicklung mit Modula-2 - ein Beispiel
19.03.1989
FAHRSCHULVERWALTUNG
19:48:02
r— FAHRSCHULDATEN— Erfassen Drucken Prüfen Verwalten Konfigurieren Archivieren
^
= vorher. Wahl
= n ä c h s t e Wahl
= abschicken
ESC =
beenden
Abb. 4.2: Hauptmenue des Fahrschulsystems.
19.03.1989
FAHRSCHULVERWALTUNG
19:48:20 • FAHRSCHULDATENErfassen
-ERFASSEN Anmeldung Tagesnachweise Theorieunterricht Gutschriften Prüfungen
= vorher. Wahl
n ä c h s t e Wahl
abschicken
ESC =
beenden
Abb. 4.3: Menue „Erfassen von Daten" in der ersten Menue-Ebene.
4.1 Systemfunktionen und Benutzerschnittstelle
19.03.1989
353
19:49:46
FAHRSCHULVERWALTUNG I—
FAHRSCHULDATENErfassen Drucken Prüfen Verwalten
-VERWALTEN Stammdaten Kontodaten Schülerstunden Fohrlehrerstunden Rechnungen Gutschriften vorher.
Wahl
nächste
abschicken
Wahl
ESC =
beenden
Abb. 4.4: Menue „Verwalten von Daten" in der zweiten Menue-Ebene.
19.03.1989
FAHRSCHULVERWALTUNG ,—
19:49:47 FAHRSCHULDATEN— Erfassen Drucken Prüfen Verwalten
|
VERWALTEN Sta m m d a t e n
-STAMMDATEN
—
Ändern Löschen Gutschriften =
vorher,
Wahl
i ==
nächste
Wahl
Eintragungen mit J / N vornehmen I «eMAT3: MAT4: MAT 5: MAT 6: MAT9: MAT10: MAT 11 : MAT12:
ABLEGEN
AB-ENDE
AB-DRU
ENDE
ESC = beenden TAB = weiter POS = erste Eingabe = abschicken t = vorher. Eingabe END = letzte Eingabe DEL = lösche Zeichen BS = lösche zurück — n ä c h s t e s Zeichen - vorheriges Zeichen INS = einfügen
Abb. 4.7: Maske zum Erfassen von Daten bei der Anmeldung eines Fahrschülers.
4. Systementwicklung mit Modula-2 - ein Beispiel
356
19.03.1989
FAHRSCHULVERWALTUNG 19:51:08 FAHRSCHULDATEN \ ERFASSEN \ TAGESNACHWEISE Über diese Maske w e r d e n die T a g e s n a c h w e i s e des F a h r l e h r e r s e r f a ß t Termin:
Fahrlehrer: Zeit: VON BIS
DATEN:
Tätigkeit: TEXT
EINGABE
ABLEGEN
Kunde: NAME
AB-ENDE
WEITER
ESC = b e e n d e n TAB = weiter POS = e r s t e Eingabe f = v o r h e r . Eingabe END = letzte Eingabe DEL = l ö s c h e Z e i c h e n = nächstes Zeichen = vorheriges Zeichen
Fahrzeug: TEXT
ENDE = abschicken BS = l ö s c h e z u r ü c k INS = e i n f ü g e n
Abb. 4.8: Maske zum Erfassen von Daten für Tagesnachweise.
357
4.1 Systemfunktionen und Benutzerschnittstelle
19.03.1989
FAHRSCHULVERWALTUNG
20:05:24
F A H R S C H U L D A T E N \ K O N F I G U R I E R E N \ F A H R S C H U L P A R A M E T E R \ KOSTENSÄTZE \ GEB + STD Über diese Maske werden Gebühren der einzelnen Führerscheinklassen
Führerscheinklasse
erfaßt.
Grundgebühren Grundgebühr : StVA : TÜV :
Fahrst undengebuhren Stadtverkehr : Landstraße : Autobahn : Beleuchtung : Nicht erschienen :
DATEN:
ESC POS
EINGABE
beenden e r s t e Wahl
Prüfungsgebührentheoretisch : praktisch :
AB-ENDE
DRUCKEN
n ä c h s t e Wahl
ENDE
= v o r h e r i g e Wahl
= abschicken END = l e t z t e W a h l
Abb. 4.9: Maske zum Verändern von Kostensätzen.
358
4. Systementwicklung mit Modula-2 - ein Beispiel
FAHRSCHULE STRASSENSCHRECK - OBERER HOLZWEG 7 - 9001 LEHRPFAD 1
Pascal, Blaise Wirthgasse 2
Seite 1
9000 Oberondorf
R E C H N U N G
Rechnungs-Nr. 4711
Kunden-Nr. 0007
GRUNDGEBÜHR KLASSE 3
KLASSE 3 FAHRTEN STADTVERKEHR
Rechnungs-Datum 19.03.89
280.00 2 FST 0 MIN 2 FST0 MIN
80.00
ZWISCHENSUMME (In diesem Betrag sind 14% MwSt (DM 50.40) enthalten. ]
80.00
360.00
LEHRMATERIAL LEHRMATERIAL 10 LEHRMATERIAL 11 LEHRMATERIAL 12
100.00 110.00 120.00 330.00
(In diesem Betrag sind 7% MwSt (DM 23.10) enthalten.) SUMME
690.00
Wir bitten Sie, den offenen Rechnungsbetrag zu überweisen.
Bankverbindung Computer Bank - BANKLEITZAHL 12345678 - KONTONUMMER 999888-777 Der Schüler bestätigt hiermit die Kenntnisnahme der in den Fahrschulräumen ausgehängten Geschäftsbedingungen.
Abb. 4.10: Rechnung als Beispiel für eine Ausgabeliste.
4.2 Modularisierung
359
4.2 Modularisierung Bereits in Kapitel 1 wurde die Modularisierung als eines der wichtigsten Prinzipien der Software-Entwicklung charakterisiert. Dort wurden auch mit der Modularisierang zusammenhängende Sachverhalte und Begriffe erörtert. An einige der Begriffe sei hier erinnert: -
Import- und Exportschnittstellen, über die Module Dienste (Typen, Datenobjekte, Funktionen) austauschen.
-
Die Benutzt-Beziehung als Begriff für das Austauschverhältnis zwischen einem übergeordneten, importierenden und einem untergeordneten, exportierenden Modul.
-
Das Moduldiagramm als grafische Repräsentation der Module und der zwischen den Modulen definierten Benutzt-Beziehungen eines Softwaresystems.
Im vorliegenden Kapitel steht die exemplarische Betrachtung des Moduldiagramms für das in Kapitel 4.1 skizzierte Fahrschulsystem im Vordergrund. Bevor das Moduldiagramm vorgestellt wird, sollen noch zwei für die Behandlung des Diagramms wesentliche Gesichtspunkte vorweggenommen werden: die „Problemnähe" einzelner Module und die Zugehörigkeit von Modulen zu Schichten des Moduldiagramms. Die Module eines anwendungsorientierten Softwaresystems lassen sich in Bezug auf das Kriterium „Problemnähe" in zwei grobe Kategorien einteilen, und zwar in: - unmittelbar problembezogene Module und - nicht speziell problembezogene Hilfs- und Bibliotheks-Module. Module der ersten Art bieten Dienste an, die Bestandteile der Bearbeitung oder Lösung eines Problems darstellen. Solche Dienste können beispielsweise in der Erfassung von Problemdaten, in der Bearbeitung von Problemdaten und in der Aufbereitung von Ergebnissen der Bearbeitung von Problemdaten bestehen. Module der zweiten Art bieten dagegen problemübergreifende Dienste an. Darunter sind Dienste zu verstehen, die unabhängig von dem jeweils konkret bearbeiteten Problem in verschiedenen Anwendungssystemen sinnvoll genutzt werden können. Als Beispiele lassen sich Module zur Fehlerverwaltung, zur Unterstützung der Bildschirmprogrammierung, zur Ein- und Ausgabe, zur Verwaltung von Daten und zur Datenkonversion nennen. Die meisten dieser Module werden der Grundbibliothek oder der Benutzerbibliothek angehören (siehe hierzu Abbildung 1.2 in Kapitel 1.4). Mit der Schichtenstruktur von (modularen) Softwaresystemen haben sich verschiedene Autoren bereits seit längerem beschäftigt (so auch Schaede[SC], der speziell interaktive Anwendungssysteme betrachtet). Nach seiner Auffassung kann man
4. Systementwicklung mit Modula-2 - ein Beispiel
360
die Module solcher Systeme in vier Gruppen einteilen, die je eine Schicht des Moduldiagramms bilden. Diese Schichten sind: -
die Steuerschicht als oberste Schicht des Moduldiagramms,
-
die darunterliegende problemorientierte Schicht,
-
die dann folgende Verwaltungsschicht und
-
die Zugriffsschicht als unterste Schicht des Moduldiagramms.
Die Module der Steuerschicht dienen der Steuerung des Arbeitsablaufs bei der Systemanwendung, während die Module der problemorientierten Schicht die verschiedenen zur Problembearbeitung und Problemlösung erforderlichen Algorithmen beinhalten. Die Module der Verwaltungsschicht umschließen die zur Verwaltung der Problemdaten erforderlichen Funktionen, jedoch keine Funktionen zum Lesen und Schreiben auf externen Speichern. Diese Zugriffsfunktionen werden von den Modulen der untersten Schicht bereitgestellt. Die für das betrachtete Fahrschulsystem vorgenommene Modularisierung führte zu insgesamt 90 Modulen. 60 Module stellen problembezogene Module dar, und die restlichen 30 sind Hilfs- und Bibliotheks-Module. Die grafische Präsentation aller 90 Module in einem Moduldiagramm ist aus Gründen der fehlenden Übersichtlichkeit nicht sinnvoll. Für die hier verfolgten Zwecke genügt die Darstellung bestimmter Teilzusammenhänge. Abbildung 4.11 zeigt ein unvollständiges Moduldiagramm des Fahrschulsystems. Bis auf die Module ISAM und Files enthält es nur problembezogene Module. Nur für den Funktionsbereich ERFASSEN sind alle problembezogenen Module dargestellt und vollständig miteinander verbunden. Die übrigen Funktionsbereiche sind unvollständig dargestellt. Man beachte die Schichtenzuordnung der problembezogenen Module: -
Die Module der Steuerschicht ermöglichen dem Benutzer das Verzweigen in die einzelnen Problembearbeitungsfunktionen. Jeder der Module baut zu diesem Zweck ein Menue auf; er bedient sich dabei einiger, in Abbildung 4.11 nicht dargestellter Hilfsmodule.
-
Die Module der problemorientierten Schicht enthalten die verschiedenen Problembearbeitungsfunktionen. Diese Funktionen werden durch die Module der Steuerschicht aktiviert. Darüberhinaus binden einige der problemorientierten Module den Benutzer in die Problembearbeitung ein. Beispielsweise durch das Aufbauen von Erfassungsmasken, in die der Benutzer Problemdaten einzutragen hat. Auch beim Aufbau von Erfassungsmasken werden Dienste nicht dargestellter Hilfsmodule beansprucht.
4.2 Modularisierung
-
361
D i e Module der Verwaltungsschicht führen Verwaltungsfunktionen auf Problemdatenbeständen aus. Solche Funktionen sind beispielsweise das Prüfen von Daten, das Fortschreiben von Daten, das Ändern von Daten und das Löschen von Daten. D i e bei der Ausführung dieser Funktionen erforderlichen Dateizugriffe stellt der Hilfsmodul ISAM bereit.
U m die Zusammenhänge in dem Funktionsbereich E R F A S S E N etwas zu verdeutlichen, sind in Tabelle 4.1 sämtliche Module dieses Bereichs zusammengestellt und kurz erläutert. D i e Bezeichnungen der übrigen Module in Abbildung 4.11 haben folgende Bedeutungen: TopMenue: Fktb_Erfassen: Fktb_Drucken: Fktb_Pruefen: Fktb_Verwalten: FktbKonflg: Fktb Archiv: Pruef Erf: GutErf: TagesnVerw: Theorie_Erf: RechnDru: Mahn_Dru: NachwDru: Endab_Dru: Pruef_Verw: Schue_Verw: SchuestdVerw: Rechn_Verw: Konto_Verw: Lehrerstd_Verw: Gut_Verw: BetriebsVerw: Kostensaetze_Verw: Rechlnt_Verw: Auto_Arch: ArchVerw: SchueKonto: SchueStamm: Schuestd: Lehrerstd: Rechnungen: Gutschriften: Archiv_Schueler: Archiv_Konto: ArchivLehrerstd: Betriebsstamm: ISAM: Files:
Programm-Modul, Funktionsbereich Erfassen, Funktionsbereich Drucken, Funktionsbereich Prüfen Funktionsbereich Verwalten Funktionsbereich Konfigurieren, Funktionsbereich Archivieren, Prüfungen erfassen Gutschriften erfassen Tagesnachweise erfassen und drucken, Theorieunterricht erfassen Rechnung drucken Mahnung drucken, Nachweis drucken Endabrechnung drucken, Prüfungen verwalten, Schüler verwalten, Schülerstunden verwalten, Rechnungen verwalten, Konto verwalten Lehrerstunden verwalten, Gutschriften verwalten, Betriebsstamm verwalten, Kostensätze verwalten, Rechnungsintervalle verwalten, automatisches Archivieren, Archiv verwalten, Schülerkontodatei, Schülerstammdatei Schülerstundendatei, Lehrerstundendatei, Rechnungsdatei, Gutschriftsdatei, Schülerarchivstammdatei, Schülerarchivkontodatei, Lehrerarchivstundendatei, Betriebsstammdatei, indexsequentielle Speicherverwaltung und Dateiverarbeitung in Modula-2.
362
4. Systementwicklung mit Modula-2 - ein Beispiel
Abb.4.11: Unvollständiges Moduldiagramm des Fahrschulsystems (es fehlen Hilfs- und Bibliotheks-Module); s. nebenstehendes Faltblatt.
STEUERSCHICHT
PROBLEMORIENTIERTE SCHICHT
DATENVERWALTUNGSSCHICHT
DATENZUGRIFFSSCHICHT
Schue-
Schue-
Schue-
Lehrer-
konto
stamm
std
std
4.2 Modularisierung
Tab. 4.1: Problembezogene Module des Funktionsbereichs ERFASSEN. Top_Menue: Prüfen der Zugangsberechtigung, Aufbau des Hauptmenues und Verzweigen in den gewählten Funktionsbereich. Fktb_Erfassen: Aufbau des Menues für den Funktionsbereich Erfassen und Verzweigen in die gewählte Erfassungsfunktion. Pruef_Erfassen: Aufbau der Maske zum Erfassen von Prüfungsdaten, Durchführen der Erfassung und Aufruf von Verwaltungsfunktionen zum Ablegen der erfaßten Daten in der Schülerkontodatei. Theorie_Erfassen: Aufbau der Maske zum Erfassen von Daten über theoretische Unterrichtseinheiten, Durchführung der Erfassung und Aufruf von Verwaltungsfunktionen zum Ablegen von erfaßten Daten in der Schülerstundendatei. Gut_Erfassen: Aufbau der Maske zum Erfassen von Gutschriftsdaten, Durchführen der Erfassung und Aufruf von Verwaltungsfunktionen zum Ablegen erfaßter Daten in verschiedenen Dateien. Schue_Verwalten: Aufbau der Maske zum Erfassen der Daten eines Ausbildungsvertrags, Durchführen der Erfassung und Aufruf von Verwaltungsfunktionen zum Ablegen erfaßter Daten in der Schülerkonto- und in der Schülerstammdatei. Tagesn_VerwaIten: Aufbau der Maske zum Erfassen von Fahrlehrertätigkeiten, Durchführen der Erfassung und Aufruf von Verwaltungsfunktionen zum Ablegen erfaßter Daten in verschiedenen Dateien. Schuekonto: Ablegen, Ändern, Löschen und Bereitstellen von Datensätzen der Schülerkontodatei unter Nutzung der Dateizugriffsfunktionen des Moduls ISAM. Schuestamm, Schuestd, Lehrerstd, Rechnungen, Gutschriften, usw.: Funktionen analog Schuekonto, jedoch bezogen auf die jeweilige Datei. ISAM: Bereitstellen von Funktionen zur Manipulation der Dateien des Fahrschulsystems, u.a. Öffnen und Schließen von Dateien, Lesen und Schreiben von Datensätzen.
363
364
4. Systementwicklung mit Modula-2 - ein Beispiel
Jeder problembezogene Modul importiert Leistungen aus Hilfs und BibliotheksModulen. Exemplarisch seien für einige Module des Funktionsbereichs ERFASSEN und für den Programm-Modul Top_Menue die Austauschbeziehungen mit Hilfsund Bibliotheks-Modulen in Abbildung 4.12 grafisch veranschaulicht. Diese Abbildung verdeutlicht die erhebliche Komplexität selbst solcher Anwendungssysteme, die vergleichsweise einfache Informationsverarbeitungsaufgaben unterstützen. Die Komplexität drückt sich hierbei in dem Beziehungsreichtum zwischen problembezogenen Modulen und Hilfs- und Bibliotheks-Modulen aus. Die Aufgaben der Bibliotheks-Module in Abbildung 4.12 sind aus den Kapiteln 2 und 3 bekannt. Für die Hilfsmodule in Abbildung 4.12 sind kurze Erläuterungen in Tabelle 4.2 zusammengestellt.
Tab. 4.2: Hilfsmodule im Funktionsbereich ERFASSEN. SYSTEM: Bereitstellen systemnaher Datentypen, Prozeduren und Funktionsprozeduren. MaskDef: Datendefinitionen zur Beschreibung von Bildschirmmasken. MasklO: Lesen und Schreiben von Maskendefinitionen von oder auf eine Plattendatei. MaskMan: Einblenden und Verwalten von Masken auf dem Bildschirm. MSDOSCALL: Bereitstellen von DOS-Funktionen (siehe Technisches Handbuch DOS). Sys: Datendefinitionen zur Beschreibung von Zeichenketten unterschiedlicher Länge. Printer: Bereitstellen von Funktionen zur Ausgabe von Daten unterschiedlichen Datentyps (CARDINAL, INTEGER, REAL, CHAR, ARRAY OF CHAR) auf dem Drucker. InfoIO: Lesen und Schreiben von Hilfe-Informationen von oder auf eine Plattendatei sowie auf den Bildschirm. Keyboard: Datendefinitionen zur Ansteuerung von Tasten.
4.2 Modularisierung
Password: Prüfen von Schutzwörtern für verschiedene Funktionsbereiche (Erfassen, Drucken,...). Screen: Datendefinitionen zur Ansteuerung von Bildschirmmanipulierungsoperationen. StringUtil: Erweiterte Funktionen auf Zeichenketten. WScreen: Datendefinitionen zur Beschreibung verschiedener Standardbildschirme. WMen: Datendefinitionen zur Beschreibung von Window-Menüs. WMemory: Lesen und Schreiben von Window-Menüs von oder auf eine Plattendatei. WMenMemo: Verwalten von Window-Menüs auf dem Bildschirm. WMenMan: Einblenden und Verwalten von Window-Menüs auf dem Bildschirm.
365
366
4. Systementwicklung mit Modula-2 - ein Beispiel
Abb.4.12: Unvollständiges Moduldiagramm des Fahrschulsystems mit allen Hilfsund Bibliotheks-Modulen für den Funktionsbereich ERFASSEN; s. nebenstehendes Faltblatt.
STEUERSCHICHT
PROBLEMORIENTIERTE
SCHICHT
DATENVERWAL TUNGSSCHICHT
Pruef. Erf
Tages_ Verw
Schue. Verw
Schue.
Schue.
konto
stamm
VERWALTUNGS SCHICHT NumberCo
DATEIZUGRIFFS SCHICHT
ISAM
SYS
Printer
InOut
TopMenue
Fktb_
Fktb_
Fktb_
Fktb.
Erf
Dru
Prue
Archiv
FILES
4.3 Definitionsmodule
367
4.3 Definitionsmodule Im vorangegangenen Kapitel wurden ausgewählte Module des Fahrschulsystems vorgestellt und die Aufgaben der Module erläutert. Ausgeklammert war dabei die Betrachtung der Modulschnittstellen, die ja die Beziehungen zwischen Modulen im Detail beschreiben. In Modula-2 repräsentieren die Definitionsmodule globaler Module vollständige Schnittstellenbeschreibungen. Sie enthalten Angaben über die exportierten - Datentypen, -
Datenobjekte (Konstanten und Variablen),
-
Funktionsprozeduren und
-
Prozeduren.
Auf den nachfolgenden Seiten sind für einige ausgewählte Module des Funktionsbereichs ERFASSEN die Definitionsmodule angegeben. Die Auswahl wurde so getroffen, daß die Module auf einem Aufrufpfad liegen, der sich über alle Schichten des Moduldiagramms erstreckt. Abbildung 4.13 zeigt den entsprechenden Ausschnitt aus dem Moduldiagramm.
Abb. 4.13: Ausschnitt aus dem Moduldiagramm des Fahrschulsystems.
368
4. Systementwicklung mit Modiüa-2 - ein Beispiel
Abgesehen von einer Ausnahme enthalten die Definitionsmodule keine Angaben über die von anderen Modulen importierten Objekte und Dienste. Eine Überprüfung der Konsistenz der Schnittstellen zweier interagierender Module ist daher erst möglich, wenn die Quelltexte der zugehörigen Implementationsmodule vorliegen. Die erwähnte Ausnahme ist im Fall des Moduls SchueStamm gegeben (Im folgenden werden aus Implementierungsgründen die Modulnamen ohne den verwendeten Tiefstrich „_" verwendet!). Im Definitionsmodul SchueStamm werden die importierten Typen ausgewiesen, die zur Definition des exportierten RECORD-Typs „PupilRec" erforderlich sind oder in den Parameterlisten einiger der exportierten Prozeduren auftreten. Der aus dem Modul ISAM importierte Typ Longlnt und sämtliche aus dem Modul Sys importierten STRING-Typen gehen in die Definition von PupilRec ein. Der aus ISAM importierte Typ ISAMKeyStr wird beispielsweise in Parameterlisten der Prozeduren GetPupilRec, SearchPupilRec und NextPupilRec benötigt. D E F I N I T I O N M O D U L E FktbErfassen; f *) (* Ruft das Menue Erfassen des Fahrschulsystems auf und verzweigt entsprechend der getätigten Auswahl in eine Erfassungsfunktion. *) C *) (* (* Exportdeklarationen (* (* EXPORT QUALIFIED ...nur bei bestimmten Compilern notwendig! EXPORT QUALIFIED MenueErfassen; C (* Exportierte Prozeduren (* (* (* Funktionsbereich ERFASSEN (* P R O C E D U R E MenueErfassen;
*) *) *) *) *) •) *) *) *)
E N D FktbErfassen.
D E F I N I T I O N M O D U L E SchueVerwalten; (*
*)
(* Mit Hilfe dieses Moduls läßt sich ein Schülerstammsatz erfassen, ändern und löschen sowie ein Schülerausbildungsauftrag drucken. *) (* *) *) (* (* Exportdeklarationen *) n (* EXPORT QUALIFIED ...nur bei bestimmten Compilern notwendig! EXPORT QUALIFIED Maske39, Maske40, Maske41, Maske42; *)
4.3 Definitionsmodule (* (* Exportierte Prozeduren (* (* (* Schülerstammsatz erfassen (* P R O C E D U R E Maske39Q; (* (* Schülerstammsatz ändern (* P R O C E D U R E Maske4O0; (* (* Schülerstammsatz löschen (*
P R O C E D U R E Maske41(); (* (* Schülerausbildungsvertrag drucken (*
369 *) *) *) *) *) *) *) *) *) *) *) *)
*) *) ')
P R O C E D U R E Maske420; E N D SchueVerwalten. DEFINITION MODULE SchueStamm; (* *) (* Mit Hilfe dieses Moduls läßt sich die Schülerstammdatei indexsequentiell verwalten. Es werden zwei Schlüssel pro Schüler verwendet: ein Hauptschlüssel und ein Nebenschlüssel. Der Hauptschlüssel muß eindeutig sein. Er besteht aus dem Nachnamen und der nachgestellten Ausbildungsnummer. Der Nebenschlüssel beinhaltet lediglich den Nachnamen. *) *) (*
(*
(* Importdeklarationen (* FROM ISAM IMPORT ISAMKeyStr, Longlnt; FROM Sys IMPORT STRINGI, STRING4, STRING5, STRING8, STRINGI2, STRING15, STRING20, STRING25;
(* (* Exportdeklarationen
*) *) *)
*)
*) *)
(* EXPORT QUALIFIED ...nur bei bestimmten Compilern notwendig! EXPORT QUALIFIED PupilRec, PupilPageMemoryOpen, PupHPageMemoryClose, OpenPupilFileAndlDX, PutPupilRec, GetPupilRec, UpdatePupilRec, SearchPupilRec, NextPupilRec, Next2PupilRec, PrevPupilRec, DeletePupilRec, PupllClearKeys;
*)
4. Systementwicklung mit Modula-2 - ein Beispiel
370
(*
*)
(* Typdeklarationen *
*) *)
TYPE (* (* (* Datenbeschreibungen des Schülerstamm-RECORDS
*) *) *)
PupilRec = RECORD null name firstname street city telefon address cityofbirth birthdate birthname national drivingclass educationstart educationend bank code account employer cityofemployer educationnumber turnovertax mat1 mat2 mat3 mat4 mats mat6 mat7 mat8 mat9 mat10 mat11 mat12 END;
Longlnt; STRING20; STRING20; STRING25; STRING25; STRING15; STRING5; STRING25; STRING8; STRING20; STRING5 STRING5 STRING8 STRING8 STRING20; STRING8; STRINGI 2; STRING20; STRING25; STRING4 STRINGI STRINGI STRINGI STRINGI STRINGI STRINGI STRINGI STRINGI STRINGI STRINGI STRINGI STRINGI STRINGI
(* (* Exportierte Prozeduren (* Zugriffsprozeduren auf den Schülerstamm-RECORD (* (* Indexsequentiell organisierte Schülerstammdatei Speicher bereitstellen (* PROCEDURE PupilPageMemoryOpen0:BOOLEAN;
*)
*) *) *) *) *) *)
371
4.3 Definitionsmodule
(* (* Indexsequentiell organisierte Schülerstammdatei schließen ( * *
*) )
P R O C E D U R E PupilPageMemoryCloseO; (* (* Indexsequentiell organisierte Schülerstammdatei öffnen (*
*)
*) *) *)
P R O C E D U R E OpenPupilFileAndlDXO; (* (* Neuen Schülerstammsatz ablegen und Schlüssel verwalten
*) *)
(«
*)
P R O C E D U R E PutPupilRec(Pupil : PupilRec); (*
*)
(* Schülerstammsatz verändern (*
*) *)
P R O C E D U R E UpdatePupilRec(key : Longlnt; Pupil : PupilRec); (*
*)
(* Schülerstammsatz lesen (*
*) •>
P R O C E D U R E GetPupilRec(VAR Pupil : PupilRec; key2 : ISAMKeyStr; key : INTEGER); (*
*)
(* Schülerstammsatz löschen (*
*) *)
P R O C E D U R E DeletePupilRec(key : Longlnt; Pupil : PupilRec); (* (* Schülerstammsatz löschen (*
*) *) *)
P R O C E D U R E PupilClearKeysO; (* (* Ersten Schülerstammsatz zum Schlüssel "key2" lesen (* P R O C E D U R E SearchPupilRec(VAR Pupil : PupilRec; VAR key : Longlnt; VAR key2 : ISAMKeyStr; keynr : INTEGER; VAR Error : BOOLEAN); (*
*) *) *)
(* Nächsten Schülerstammsatz zum Schlüssel "key2" lesen ohne Fehlerausgabe (* P R O C E D U R E NextPupiiRec(VAR Pupil : PupilRec; VAR key : Longlnt; VAR key2 : ISAMKeyStr; keynr : INTEGER); (* (* Nächsten Schülerstammsatz zum Schlüssel "key2" lesen mit Fehlerausgabe
*) *)
(* P R O C E D U R E Next2PupiiRec(VAR Pupil : PupilRec; VAR key : Longlnt; VAR key2 : ISAMKeyStr; keynr : INTEGER); (* (* Vorherigen Schülerstammsatz zum Schlüssel "key2" lesen mit Fehlerausgabe (* P R O C E D U R E PrevPupilRec(VAR Pupil : PupilRec; VAR key : Longlnt; VAR key2 : ISAMKeyStr; keynr : INTEGER); END SchueStamm.
*)
*) *) *)
*) *) *)
4. Systementwicklung mit Modula-2 - ein Beispiel
372
4.4 Implementationsmodule Auf den folgenden Seiten sind der Programm-Modul, der Implementationsmodul „FktbErfassen" und Auszüge aus den Implementationsmodulen „SchueVerwalten" und „SchueStamm" dargestellt. Mit Ausnahme des Moduls „ISAM" liegen damit für alle Module des in Abbildung 4.11 gezeigten Ausschnitts aus dem Moduldiagramm zumindest auszugsweise Beschreibungen vor. Insbesondere enthalten der Programm-Modul und die dargestellten Implementationsmodule vollständige Beschreibungen ihrer Importschnittstellen. Die Import-/Exportbeziehungen zwischen den in Abbildung 4.11 ausgewiesenen Modulen können nunmehr im Detail nachvollzogen werden. Es würde den hier gegebenen Rahmen sprengen, wollte man sämtliche Einzelbeziehungen besprechen. Auf einige Anmerkungen soll jedoch nicht verzichtet werden. Die Beziehungen des Programm-Moduls zu anderen Modulen gehen aus der Beschreibung seiner Importschnittstelle hervor. Dort heißt es u.a.: FROM FktbErfassen IMPORT MenueErfassen;
Der Programm-Modul importiert also die Funktion MenueErfassen aus dem Modul FktbErfassen. Durch den Aufruf dieser Funktion in der Prozedur „Interactive" des Programm-Moduls wird aus dem Programm-Modul in den Modul FktbErfassen verzweigt. Hingewiesen sei noch darauf, daß im Block des Programm-Moduls die Funktionsprozedur „CheckPassword" aufgerufen wird. CheckPassword wird aus dem Modul „PassWord" importiert. CheckPassword übergibt den Wert TRUE, wenn das vom Benutzer eingegebene Paßwort korrekt ist. Nur in diesem Fall führt der Programm-Modul weitere Aktionen aus. Der Modul FktbErfassen wird durch den Aufruf der Prozedur MenueErfassen aktiviert. In der Importschnittstelle von FktbErfassen heißt es u.a.: FROM SchueVerwalten IMPORT Maske39, Maske40;
Durch den Aufruf einer der Prozeduren „Maske39" oder „Maske40" in der Prozedur Interactive aktiviert der Modul FktbErfassen den Modul SchueVerwalten. Bemerkt sei noch, daß der Block des Moduls FktbErfassen keine Anweisungen enthält. Eine Initialisierung dieses Moduls findet also nicht statt. Etwas umfangreicher ist der Implementationsmodul SchueVerwalten. Er enthält eine ganze Reihe von Prozeduren zur Zeichenkettenverarbeitung. Diese Prozeduren werden zum Drucken der einzelnen Abschnitte des Ausbildungsvertrags benötigt. Aktiviert wird SchueVerwalten durch den Aufruf der Prozeduren Maske39 oder Maske40. Maske39 dient dem Aufbau der Maske zum Erfassen von Schülerdaten und dem Durchführen der Erfassung, während Maske 40 für das Ändern von Schü-
4.4 Implementationsmodule
373
lerdaten vorgesehen ist. Die Schnittstelle von SchueVerwalten enthält die Importanweisung: FROM SchueStamm IMPORT PupilRec, PupilPageMemoryOpen, ...
Durch den Aufruf einer der in der Importliste angegebenen Prozeduren wird der Modul SchueStamm aktiviert. Er enthält eine ganze Reihe von Prozeduren zur indexsequentiellen Speicherung von Schülerstammsätzen. Dabei handelt es sich zum einen um Prozeduren zum Schreiben, zum Überschreiben, zum Löschen, zum Suchen und zum Blättern von Schülerstammsätzen sowie zum anderen um Prozeduren zum Öffnen und Schließen der Schülerstammdatei und um eine Prozedur, die der Einlagerung der extern gespeicherten Indextabelle der Schülerstammdatei in einen speziellen Arbeitsspeicherbereich, dem Heap, dient. Der in der Importanweisung von SchueStamm verwendete Bezeichner PupilRec stellt keinen Prozeduraufruf dar. PupilRec ist vielmehr ein im Definitionsmodul von SchueStamm vereinbarter Datentyp, der somit wie ein vordeklarierter Datentyp als Typ verwendbar ist. Insbesondere wird der Datentyp PupilRec in der Schnittstelle verschiedener durch den Modul SchueStamm importierter Prozeduren, wie etwa PutPupilRec und GetPupilRec, benötigt. PupilPageMemoryOpen stellt eine Prozedur dar, die der oben erwähnten Einlagerung der Indextabelle der Schülerstammdatei in den Arbeitsspeicher dient. Die Schnittstelle von SchueStamm enthält die Importanweisung: FROM ISAM IMPORT AddKey, AddRec, ...
Durch den Aufruf einer der in der Importliste angegebenen Prozeduren wird der Modul ISAM aktiviert. Dieser Modul ist sehr maschinennah implementiert. Er enthält viele compilerspezifische Details und muß somit an jeden Compiler angepaßt werden. In der Regel wird dieser Modul vom Compilerhersteller als Bibliotheks-Modul angeboten.
MODULE TopMenue;
*)
(* Ruft das TopMenue des Fahrschulsystems auf und verzweigt entsprechend der getätigten Auswahl in einen Funktionsbereich.
*)
(*
*)
(*
*)
(* Import-Deklarationen
*)
(*
FROM FROM
ErrorlO Files
*) IMPORT ShowErrorText; IMPORT STRINGPathNameSize;
374
FROM FROM FROM FROM FROM FROM FROM FROM FROM FROM FROM FROM FROM FROM FROM FROM
4. Systementwicklung mit Modula-2 - ein Beispiel FktbErfassen FktbDrucken FktbPruefen FktbVerwalten FktbKonfigurieren FktbArchivieren Keyboard Password Screen String StringUtility WScreen WMen WMemory WMenMemo WMenMan
IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT
MenueErfassen; MenueDrucken; MenuePruefen; MenueVerwalten; MenueKonfigurleren; MenueArchivleren; ESC; CheckPassword; ClearScreen; Concat, Compare; AssignString, MakeString; ScreenOne; Menues, Selected, Selections; GetWMenue; Menue; ShowMenueWindow, RefreshMenueWindow, I nteractiveMenueWindow;
(*
*)
(* Unterprogramm zum Einblenden des TopMenüs und zum Einleiten der getroffenen Auswahl *) (* *> PROCEDURE InteractiveO; VAR resultlO Selected; AbbruchlO : CHAR; BEGIN ShowMenueWindow(l); REPEAT lnteractiveMenueWindow(l, resultlO, AbbruchlO); IF AbbruchlO< >CHR(ESC) THEN CASE resultlO OF first: MenueErfassenO; | second: MenueDruckenO; | third: MenuePruefenO; | fourth : MenueVerwaltenO; | fifth: MenueKonfigurierenO; | sixth: MenueArchivierenO; ELSE END; RefreshMenueWindow(1) END UNTIL AbbruchlO = CHR(ESC) END Interactive;
375
4.4 Implementationsmodule
(* *) (* Lädt das WindowMenü, falls vorhanden, von der Platte und speichert es für den OverlayWindowManger zwischen. Ist das WindowMenü nicht vorhanden, wird eine entsprechende Fehlermeldung ausgegeben. *) (*
*)
P R O C E D U R E CheckMenueFrame(Datei : STRINGPathNameSize; Number : CARDINAL; VAR ok : BOOLEAN); VAR Window : Selections; Status : INTEGER; BEGIN ok : = FALSE; Status: = 0; GetWMenue(Datei, Window, Status); IF Status = 0 THEN INC(Menue.CountOfActualMenues); Menue.AIIMenues[Menue.CountOfActualMenues],Data: = Window; Menue.AIIMenues[Menue.CountOfActualMenuesj. ActualSelection: = 1 ELSE ShowErrorText(150 + Number) END; ok: = Status = 0 E N D CheckMenueFrame;
(* (* Prüft, ob das zu ladende WindowMenü auf dem angegebenen DOS-Inhaltsverzeichnis vorhanden (* P R O C E D U R E CheckMenues():BOOLEAN; VAR ok1 : BOOLEAN; Datei : STRINGPathNameSize; BEGIN MakeStringC ', 63, Datei); AssignString('Fahr1 .WME', Datei); CheckMenueFrame(Datei, 1, ok1); RETURN(okl) E N D CheckMenues; ( * * (* Steuert den Prüf- und Aufrufmechanismus für WindowMenüs (* P R O C E D U R E goO; VAR Ok : BOOLEAN; BEGIN ok: = CheckMenuesO; IF ok THEN InteractiveO END END go;
*) *) *,
) *)
4. Systementwicklung mit Modula-2 - ein Beispiel
376
(* (** Hauptprogramm
*) *) *)
(
BEGIN IF CheckPasswordO THEN goO;
ClearScreenO; ScreenOneO END ENDTopMenue.
IMPLEMENTATION MODULE FktbErfassen; (*
*)
(* Ruft das TopMenue des Fahrschulsystems auf und verzweigt entsprechend der getätigten Auswahl in eine Erfassungsfunktion. (* (* Import-Deklarationen 1. FROM ErrorlO FROM Files FROM GutErfassen FROM Keyboard FROM Password FROM PruefErfassen SchueVerwalten FROM FROM Screen String FROM FROM StringUtility FROM TagesnVerwalten FROM TheorieErfassen FROM WScreen FROM WMen FROM WMemory FROM WMenMemo FROM WMenMan
*) *i *)
IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT IMPORT
ShowErrorText; STRINGPathNameSize; Maske59; ESC; CheckPassword; Maske54; Maske39, Maske40; ClearScreen; Concat, Compare; AssignString, MakeString; Maske43; Maske55; ScreenOne; Menues, Selected, Selections; GetWMenue; Menue; ShowMenueWindow, RefreshMenueWindow, 1 nteractlveMenueWindow;
( * * ) (* Unterprogramm zum Einblenden des WindowMenü E R F A S S E N und zum Einleiten der getroffenen Auswahl P R O C E D U R E InteractiveO; VAR Selected; resulti 0 AbbruchlO : CHAR; BEGIN ShowMenueWindow(1 );
*)
4.4 Implementationsmodule
377
REPEAT lnteractiveMenueWindow(1, resulti0, AbbruchIO); IF Abbruch10< > C H R ( E S C ) THEN C A S E resulti 0 OF first Maske39(); second Maske40(); third : Maske43(); fourth : Maske550; fifth : Maske590; sixth : Maske540; ELSE END; ScreenOneO; MenllO; RefreshMenueWindow(1 ) END UNTIL Abbruchl 0 = CHR(ESC) E N D Interactive;
(* Lädt das WindowMenü, falls vorhanden, von der Platte und speichert es für den OverlayWindowManger zwischen. Ist das WindowMenü nicht vorhanden, wird eine entsprechende Fehlermeldung ausgegeben. *)
(.
P R O C E D U R E CheckMenueFrame(Datei : STRINGPathNameSize; Number : CARDINAL; VAR ok : BOOLEAN); VAR Window : Selections; Status : INTEGER; BEGIN ok : = FALSE; Status: = 0; GetWMenue(Datei, Window, Status); IF Status=0 THEN INC(Menue.CountOfActualMenues); Menue.AIIMenues[Menue.CountOfActualMenues].Data: = Window; Menue.AIIMenues[Menue.CountOfActualMenuesj.ActualSelection: = 1 ELSE ShowErrorText(150 + Number) END; ok: = Status = 0 E N D CheckMenueFrame;
*>
378
4. Systementwicklung mit Modula-2 - ein Beispiel
(*
(* Prüft, ob das zu ladende WindowMenü auf dem angegebenen DOS-Inhaltsverzeichnis vorhanden ist. (* P R O C E D U R E CheckMenuesO:BOOLEAN; VAR ok1 : BOOLEAN; Datei : STRINGPathNameSIze; BEGIN MakeStringC ', 63, Datei); AssignString('Fahr2.WME', Datei); CheckMenueFrame(Datei, 2, ok1); RETURN(okl) E N D CheckMenues;
')
*) *)
*) (* Steuert den Prüf- und Aufrufmechanismus für WindowMenüs (* P R O C E D U R E goO; VAR ok : BOOLEAN; BEGIN ok: =CheckMenues(); IF ok THEN InteractiveO END E N D go;
*) *)
(* Unterprogramm zur Organisation des WindowMenüs E R F A S S E N (* P R O C E D U R E MenueErfassenO; BEGIN IF CheckPasswordO THEN goO; ClearScreenO; ScreenOneO END E N D MenueErfassen;
*) *)
(* (* Hauptprogramm (* BEGIN E N D FktbErfassen.
*) *) *)
4.4 Implementationsmodule
379
IMPLEMENTATION MODULE SchueVerwalten; (*(* Mit Hilfe dieses Moduls läßt sich ein Schülerstammsatz erfassen, ändern und löschen sowie ein Schülerausbildungsvertrag drucken. (*
(* (* Import-Deklarationen (* FROM Concerni O FROM CostIO FROM Files FROM FROM FROM FROM FROM FROM FROM FROM FROM FROM FROM FROM FROM
ErrorlO InfoDrlO InfolO InOut ISAM Keyboard MaskDef MaskenMan MasklO MSDOSCALL NumberConversion Printer PrintOperations
FROM
SchueKonto
FROM
SchueStamm
FROM FROM IMPORT FROM FROM FROM
Screen String Strings; StringUtility StudIO Sys
FROM
SYSTEM
-*)
IMPORT ConcernRec, GetConcern; IMPORT licenceclass, CostRec, GetCost; IMPORT Close, File, Mode, Open, ReadNBytes, Response, STRINGPathNameSize; IMPORT ShowErrorText; IMPORT InfoDrRec, GetlnfoDr; IMPORT GetlnfoText; IMPORT WriteString; IMPORT ISAMKeyStr, Longlnt; IMPORT ESC; IMPORT inout, select, selecttag, show; IMPORT loMaske, loRefreshMaske; IMPORT ReadMask; IMPORT DosGetDate, DosGetTime; IMPORT StringToCard, CardToString; IMPORT PrintChar, PrintString, PrintLines, PrintLn, PrintFF; IMPORT CondensedModeOn, EliteModeOn, PicaModeOn, OneLineEnlargedModeOn, OneLineEnlargedModeOff; IMPORT AccountRec, AccountPageMemoryOpen, AccountPageMemoryClose, OpenAccountFlleAndlDX, PutAccountRec, SearchAccountRec, DeleteAccountRec, AccountClearKeys; IMPORT PupilRec, PupilPageMemoryOpen, Pupil PageMemoryClose, OpenPupilFileAndlDX, SearchPupilRec, NextPupilRec, PrevPupllRec, PutPupilRec, DeletePupilRec, UpdatePupilRec, PupilClearKeys; IMPORT Gotoxy; IMPORT Compare, Concat, Delete, Length, Insert, Substr; IMPORT AssignString; IMPORT StudRec, GetStud, PutStud; IMPORT STRING2, STRING4, STRING40, STRING72, STRING76, STRING80; IMPORT ADR, SIZE;
380
4. Systementwicklung mit Modula-2 - ein Beispiel
(*
*)
(* Konstantendeklarationen (*
*) *)
CONST FileName = 'TITEL.MAS'; r (* Typdeklarationen
*) *)
I rrt material = ARRAY [1 ..12] OF BOOLEAN; (*
*)
(* Löscht in einer Zeichenkette beginnend am Anfang Leerzeichen, bis ein Zeichen ungleich Space gefunden wurde oder bis die Zeichenkette keine Zeichen mehr beinhaltet («
*) .)
*)
PROCEDURE DelBlanks(VAR s : ARRAY OF CHAR); VAR i : CARDINAL; BtbiN i: = Strings.Length(s)-l ; LUOP IF s[i] = ' ' (* Blank *) THEN Strings. Delete(s, i, 1) ELSE EXIT END; IF i < > OTHEN DEC(i) ELSE EXIT END END END DelBlanks;
(• (* Zentriert eine Zeichenkette der Maximallänge 40 (* PROCEDURE CenterTrim(s : ARRAY OF CHAR; VAR s1 : ARRAY OF CHAR); CONST Leerzeichen = ' '; VAR Filler : STRING40; BEGIN IF Strings.Length(s) > OTHEN AssignString(", Filler); Substr(Leerzeichen, 1, (40-Strings. Length (s)) DIV2, Filler); IF Strings.Length(s) MOD 2 = 0 THEN Strings.Concat(Filler, s, s1); Strings.Concat(s1, Filler, s1) ELSE Strings.Concat(Filler, s, s1);
*) *) *)
4.4 Implementationsmodule
381
Strings.Concat(Filler,' Filler); Strings.Concat(s1, Filler, s1) END END END CenterTrim;
(* (* Druckt eine zentrierte Zeichenkette aus
*) *)
(*
*)
PROCEDURE PrintOut(s : ARRAY OF CHAR); VAR si : STRING40; BEGIN PrintStringC '); (* linken Rand drucken *) DelBlanks(s); CenterTrim (s, s1); PrintString(sl) END Printout; (*
*)
(* Druckt eine 2-stellige natürliche Zahl mit führender Null
*)
(*
*)
PROCEDURE PrintCard2(n : CARDINAL); VAR s : STRING2; BEGIN AssignStringC, s); I F n < 10 THEN PrintChar('O'); CardToString(n, s, 1) ELSE CardToString(n, s, 2) END; PrintString(s) END PrintCard2;
( * (* Druckt Leerzeichenkette der Länge n (*
PROCEDURE PrintSpaces(n : CARDINAL); VAR i : CARDINAL; BEGIN FOR i: = 1 TO n DO PrlntCharC ') END END PrintSpaces;
*
) *)
')
4. Systementwicklung mit Modula-2 - ein Beispiel
382
(* (* Druckt die Personalien eines Fahrschülers auf dem Ausbildungsauftrag aus (* P R O C E D U R E FormularPersonalien(Pupil : PupllRec); VAR i : CARDINAL; (* (* Druckt eine Strichzeile aus Grafikzeichen mit zentriertem Text: PERSONALIEN (* P R O C E D U R E DataZeilelO; BEGIN EliteModeOnO; PrintStringC PrintChar(CHR(201)); FOR i: = 1 TO 31 DO PrintChar(CHR(205)) END; PrintString('PERSONALIEN') FOR i: = 1 TO 32 DO PrintChar(CHR(205)) END; PrintChar(CHR (187)) ; PrintLnO; PicaModeOnO END DataZeilel ;
*) *) *)
(* Schrift auf Elite setzen *) '); (* linken Rand drucken *) (* linke obere Ecke *) (* Strichzeile *) (* zentrierter Text *) (* Strichzeile *) (* rechte obere Ecke *) (* Schrift auf Pica setzen *)
(* (* Druckt Schülername und -nummer (* P R O C E D U R E DataZeile20; BEGIN (* Schrift auf Elite setzen *) EliteModeOnO; PrintStringC '); (* linken Rand drucken *); (* senkrechter Strich *) PrintChar(CHR(186)); PrintStringC NAME: '); PrintString(Pupil.name); PrintSpaces(7); (* sieben Leerzeichen drucken *) PrintStringC KDNR: '); PrintString(Pupil.educationnumber); (* achtzehn Leerzeichen drucken *) PrintSpaces(18); (* senkrechter Strich *) PrintChar(CHR(186)); PrintLnO; PicaModeOnO (* Schrift auf Pica setzen *) END DataZeile2;
(
*
(* Druckt Vorname und Geburtsdatum (* P R O C E D U R E DataZeile3(); BEGIN
*) *) *)
383
4.4 Implementationsmodule
EliteModeOnO; PrintStringC PrintChar(CHR (186)) ; PrintStringC V O R N A M E N : '); PrintString(Pupll.firstname); PrintSpaces(7); PrintStringC G E B O R E N : '); PrintString(Pupll.birthdate); PrintSpaces(14); PrintChar(CHR(186)); PrintLnO; PicaModeOnO E N D DataZeile3;
(* Schrift auf Elite setzen *) '); (* linken Rand drucken *) (* senkrechter Strich *)
(* sieben Leerzeichen drucken *)
(* vierzehn Leerzeichen drucken *) (* senkrechter Strich *) (* Schrift auf Pica setzen *)
(* Druckt Straße und Geburtsort P R O C E D U R E DataZeile40; BEGIN EliteModeOnO; PrintStringC PrintChar(CHR (186)) ; PrintStringC S T R A S S E : '); PrintString(Pupil.street); PrintSpaces(2); PrintStringC GEBURTSORT: '); Delete(Pupil.cityofbirth, 21, 5); PrintString(Pupil.cityofbirth) ; PrintSpaces(2); PrintChar(CHR(186)); PrintLnO; PicaModeOnO E N D DataZeile4;
(* Schrift auf Elite setzen * (* linken Rand drucken *) (* senkrechter Strich *)
(* zwei Leerzeichen 1
(* zwei Leerzeichen *) (* senkrechter Strich *) (* Schrift auf Plca setzen *)
(* Druckt Ort und Staatsangehörigkeit P R O C E D U R E DataZeile50; BEGIN EliteModeOnO; PrintStringC PrintChar(CHR(186)); PrintStringC ORT: '); PrintStrlng(Pupil.clty); PrintSpaces(2); PrintStringC STAATSANGEH: '); PrintString(Pupil.national); PrintSpaces(17); PrintChar(CHR(186)); PrintLnO;
(* Schrift auf Elite setzen *) (* linken Rand drucken *) (* senkrechter Strich *)
(* zwei Leerzeichen *)
(* siebzehn Leerzeichen ' (* senkrechter Strich *)
4. Systementwicklung mit Modula-2 - ein Beispiel
384
PrintLnO; PicaModeOnO END DataZeile5;
(* Schrift auf Pica setzen *)
(* Druckt Telefonnummer und Ausbildungsklasse aus (* PROCEDURE DataZeile60; BEGIN (* Schrift auf Elite setzen *) EliteModeOnO; (* linken Rand drucken *) PrintStringC (* senkrechter Strich *) PrintChar(CHR(186)); PrintStringC TELEFON: '); PrintString(Pupil.telefon); PrintSpaces(12); (* zwölf Leerzeichen *) PrintStringC KLASSE: '); PrintString(Pupil.drivingclass); PrintSpaces(18); (* achtzehn Leerzeichen *) PrintChar(CHR(186)); (* senkrechter Strich *) PrintLnO; PicaModeOnO (* Schrift auf Pica setzen *) END DataZeile6;
(* Druckt Abschlußzeile des Rahmens (* PROCEDURE DataZeile70; BEGIN (* Schrift auf Elite setzen *) EliteModeOnO; (* linken Rand drucken *) PrintStringC (* linkes Kreuz *) PrintChar(CHR(204)); FOR i: = 1 TO 24 DO (* Strichzeile *) PrintChar(CHR(205)) END; PrintStringCENTGELT FÜR DIE AUSBILDUNG'); (* zentrierter Text *) FOR i: = 1 TO 24 DO (* Strichzeile *) PrintChar(CHR(205)) END; PrintChar(CHR(185)) ; PrintLnO; PicaModeOnO END DataZeile7;
(* rechtes Kreuz *) (* Schrift auf Pica setzen *)
(* Anweisungen der Prozedur "Druckt die Personalien ... auf dem Ausbildungsauftrag aus" *) *) (* BEGIN DataZeile1(); LeerZeile(); DataZeile2(); LeerZeileQ;
385
4.4 Implementationsmodule
DataZeile30; LeerZeileO; DataZeile40; LeerZeileO; DataZeile50; LeerZeileO; DataZeile60; LeerZeileO; DataZeile70; LeerZeileO E N D FormularPersonalien;
(*
*)
(* Druckt die aktuelle Preisliste auf dem Ausbildungsauftrag aus (*
*) *)
P R O C E D U R E FormularPreislisteO; VAR InfoDr: InfoDrRec; P R O C E D U R E Data(s : STRING72); BEGIN EliteModeOnO; PrintStringC PrintStringC | '); PrintString(s); PrintStringC | '); PrintLnO; PicaModeOnO E N D Data; P R O C E D U R E DataZeileO; VAR i : CARDINAL; BEGIN EliteModeOnO; PrintStringC PrintChar(CHR(200)); FOR i: = 1 TO 74 D O PrintChar(CHR(205)) END; PrintChar(CHR(188)); PrintLnO; PicaModeOnO E N D DataZeile;
(* '); (* (* (* (*
Schrift auf Elite setzen *) linken Rand drucken *) Begrenzer links *) Zeichenkette drucken *) Begrenzer rechts *)
(* Schrift auf Pica setzen *)
(* Schrift auf Elite setzen *) '); (* linken Rand drucken *) (* linke untere Ecke *) (* Strichzeile *) (* rechte untere Ecke *) (* Schrift auf Pica setzen *)
BEGIN (* (* Gebührentabelle aus der Infodatei lesen. E s werden 21 Info-Zeilen Text der Datei im Ausbildungsauftrag ausgedruckt. (* GetlnfoDr(lnfoDr); Data(lnfoDr.text1 ) ; Data(lnfoDr.text2); Data(l nfoDr.text3) ; Data(l nfoDr.text4) ; Data(l nfoDr.textö) ; Data(l nfoDr.text6) ; Data(lnfoDr.text7); Data(lnfoDr.text8); Data(lnfoDr.text9); Data(lnfoDr.textlO); Data(lnfoDr.text11 );Data(lnfoDr.text12);
*) *) *)
4. Systementwicklung mit Modula-2 - ein Beispiel
386
Data(lnfoDr.text13);Data(lnfoDr.text14) Data(lnfoDr.text15);Data(lnfoDr.text16) Data(lnfoDr.text17);Data(lnfoDr.text18) Data(l nfoDr.textl 9) ; Data(lnfoDr.text20) Data(l nfoDr.text21 ) ; DataZeileQ ; PrintLines(3) E N D FormularPreisliste;
C (* Druckt die gesetzlichen Vertreter auf dem Ausbildungsvertrag aus (* P R O C E D U R E FormularVertreterO; P R O C E D U R E DataZeilel 0; BEGIN EliteModeOnO; PrintStringC PrintStringC '); PrintSpaces(15); PrintStringCPrlntSpaces(13); PrintStringC PrintLnO; PicaModeOnO E N D DataZeilel ;
(* (* (* (* (* (* (*
Schrift auf Elite setzen *) linken Rand drucken *) unterstrichenes Feld für die Fahrlehrerunterschrift *) fünfzehn Leerzeichen *) unterstrichenes Feld für Ort und Datum *) dreizehn Leerzeichen *) unterstrichenes Feld für die Schülerunterschrift *)
(* Schrift auf Pica setzen *)
P R O C E D U R E DataZelle2(); BEGIN EHteModeOn(); (* Schrift auf Elite setzen *) PrintStringC '); (* linken Rand drucken *) PrintString('FAHRLEHRER'); PrintSpaces(15); (* fünfzehn Leerzeichen *) PrintStringC ORT DATUM PrintSpaces(13); (* dreizehn Leerzeichen *) PrintStringCFAHRSCHÜLER ODER'); PrintLnO; PicaModeOnO (* Schrift auf Pica setzen *) E N D DataZelle2; P R O C E D U R E DataZeile3(); BEGIN EliteModeOnO; (* Schrift auf Elite setzen *) PrintStringC PrintSpaces(57); (* siebenundfünfzig Leerzeichen *) PrlntStringCGESETZL. VERTRETER'); PrintLnO; PicaModeOnO (* Schrift auf Pica setzen *) E N D DataZeile3; BEGIN DataZeilel (); DataZelle20; DataZeile3(); PrlntLlnes(2) E N D FormularVertreter;
4.4 Implementationsmodule
(*
387
*)
(* Druckt Bankverbindung, Bankleitzahl und Kontonummer der Fahrschule auf dem Ausbildungsauftrag aus r
*) *)
PROCEDURE FormularBankverbindung(Concern : ConcernRec); PROCEDURE DataZeilelO; BEGIN END DataZeilel ; PROCEDURE DataZelle2(); BEGIN END DataZeile2; BEGIN DataZeilelO; DataZeile20; PrintFFO END FormularBankverbindung;
(*
*)
(* Druckt einen Zeilenvorschub mit Umrahmung aus. Die Umrahmung besteht aus Grafikzeichen (* PROCEDURE LeerZeileO; BEGIN
*) *)
END LeerZeile;
(*
*)
(* Druckt den Briefkopf der Fahrschule aus
*)
(*
*)
PROCEDURE KopfFahrschuleO; BEGIN END KopfFahrschule;
(*
*)
(* Druckt Betriebsname, -Straße und -ort der Fahrschule aus (* PROCEDURE KopfBetrieb(Concern : ConcernRec); BEGIN
*) *)
END KopfBetrieb;
4. Systementwicklung mit Modula-2 - ein Beispiel
388
(* (* Druckt Formulartitel der Maximallänge 40 zentriert aus (
*) *)
*
*)
PROCEDURE KopfTitle(Title : STRING40); BEGIN END KopfTitle;
(
*
-
*
)
(* Druckt aktuelles Datum, das vom Betriebssystem verwaltet wird (* PROCEDURE PrlntDate(Column : CARDINAL); BEGIN
*) *)
END PrintDate;
( * * (* Druckt aktuelle Zelt, die vom Betriebssystem verwaltet wird (* PROCEDURE PrlntTime(Column : CARDINAL); BEGIN
)
*) *)
END PrintTime; (* (* Druckt aktuelles Datum und aktuelle Zeit auf dem Ausbildungsauftrag im Kopf aus (
*
PROCEDURE KopfDateAndTimeO; BEGIN
*) *)
*)
END KopfDateAndTime; (* (* Druckt den Formularkopf des Ausbildungsauftrages aus (
*
PROCEDURE FormularKopf(Title : STRING40; VAR Concern : ConcernRec); BEGIN KopfFahrschuleO; GetConcem(Concern) ; KopfBetrieb(Concern) ; KopfTitle(Tltle); KopfDateAndTimeO END FormularKopf;
*> *)
*)
4.4 Implementationsmodule
389
(* Druckt den Ausbildungsauftrag mit Briefkopf, Betriebsdaten, Datum, Uhrzeit, Name des Formulars, Personalien des Fahrschülers, aktueller Preisliste, allen Fahrstunden, sonstigen Gebühren, Unterschriftsfeldern, Bankverbindung und einem Auszug aus den allgemeinen Geschäftsbedingungen auf 12 Zoll Papierlänge aus. *)
*> P R O C E D U R E Druck(Pupil : PupilRec); VAR str : STRING40; Concern : ConcernRec; BEGIN AssìgnString('A U S B I L D U N G S A U F T R A G', str); FormularKopf(str, Concern); FormularPersonalien(Pupil) ; FormularPreislisteO; FormularVertreter() ; FormularBankverbindung(Concern) E N D Druck;
(* Konvertiert Daten des R E C O R D PupilRec in einen R E C O R D vom Typ inout und umgekehrt. Die Datenkonvertierung ist notwendig, da die Eingabe von Daten über eine Maske oder das Anzeigen von Daten in einer Maske über den Bibliotheks-Modul MaskMan organisiert ist. MaskMan erwartet Daten des Datentyps inout (Zeichenketten) und überführt die Komponenten des Ausgangsdatentyps PupilRec zwecks Anzeige auf dem Bildschirm (Zeichenketten). Diese Prozedur ist nur anwendbar für das Erfassen von Daten. *) *) (* P R O C E D U R E Convert(selections ; CARDINAL; VAR Stud : StudRec; VAR InOutputs : inout; VAR Pupil: PupilRec; VAR class : licenceclass; VAR mat: material; VAR ok : BOOLEAN);
(* (* Konvertiert Daten des R E C O R D PupilRec in einen R E C O R D vom Typ inout (
*
*) *)
*)
P R O C E D U R E pupilTOinoutputs(Pupil : PupilRec; VAR Stud : StudRec; VAR InOutputs : inout); BEGIN END pupilTOinoutputs;
(* (* Konvertiert Daten des R E C O R D inout in einen R E C O R D vom Typ PupilRec ( * * ) P R O C E D U R E InoutputsTOpupil(InOutputs : inout; Stud : StudRec; VAR Pupil : PupilRec; VAR class : licenceclass; VAR mat : material; VAR ok : BOOLEAN)
*> *)
4. Systementwicklung mit Modula-2 - ein Beispiel
390
(* (* Prüft die Eingabe daraufhin, ob die angegebene Fahrschulklasse gelehrt wird. (*
*) *)
*)
PROCEDURE CheckClass(s : STRING76; VAR class : licenceclass):BOOLEAN; BEGIN END CheckClass; (*
*)
(* Prüft die Eingabe daraufhin, ob das angegebene Datum ein zulässiges Tagesdatum ist (* PROCEDURE CheckDate(s : STRING76):BOOLEAN; BEGIN
*) *)
END CheckDate; (* (* Prüft die Eingabe daraufhin, ob Lehrmaterialien ausgewählt wurden
(.
*) *)
*)
PROCEDURE CheckLehrmaterial(s : STRING76):BOOLEAN; BEGIN (.
END CheckLehrmaterial;
(* Anweisungen der Prozedur "inoutputsTOpupil" (* BEGIN END inoutputsTOpupil; (* (* Anweisungen der Prozedur "Convert" (*
*) *) *)
*) *)
*)
BEGIN IF selections = 0 THEN pupilTOinoutputs(Pupil, Stud, InOutputs) ELSIF selections = 1 THEN inoutputsTOpupil (InOutputs, Stud, Pupil, class, mat, ok) END END Convert;
(* --*) (* Konvertiert Daten des RECORD PupilRec in einen RECORD vom Typ inout und umgekehrt. Die Datenkonvertierung ist notwendig, da die Eingabe von Daten über Maske oder das Anzeigen von Daten in einer Maske über den Bibliotheksmodul MaskMan organisiert ist, der stets die Daten in dem Datentyp inout erwartet, wobei alle Komponenten des Ausgangsdatentyps in Zeichenketten (zur Anzeige auf dem Bildschirm) überführt werden. Diese Prozedur ist nur anwendbar für das Ändern von Daten. *) p *) PROCEDURE Convertl (selections : CARDINAL; VAR Stud : StudRec; VAR InOutputs : inout; VAR Pupil: PupilRec; VAR class : licenceclass; VAR mat: material; VAR ok: BOOLEAN);
4.4 Implementationsmodule
391
BEGIN END Converti ;
(* (* Initialisiert einen RECORD des Schülerkontos (*
*) *) — * )
PROCEDURE lnitAccount(class : licenceclass; mat : material; Pupil : PupilRec; VAR Account : AccountRec); BEGIN END InitAccount;
(*
*)
(* Erfaßt Schüler über die auf dem Bildschirm angezeigte Maske "Schüler erfassen" (* PROCEDURE Maske390; VAR inout; InOutputs, oldlnOut select; Selections Texte show; Result selecttag; Status INTEGER; FeldNumber, eduNumber CARDINAL; Interrupt, done, ok BOOLEAN; Infol, lnfo2 STRING80; Abbruch CHAR; Pupil PupilRec; Stud StudRec; Account AccountRec; year, month, day CARDINAL; class licenceclass; mat material;
*) *)
(* (* Füllt führende Blanks mit Nullen auf (* PROCEDURE FuellO; VAR i : CARDINAL; BEGIN i: = 1; LOOP IF Stud.number[i] = " (* Blank *) THEN Stud.number[i]: = '0' END; IF i = 4 THEN EXIT ELSE
*) *) *)
4. Systementwicklung mit Modula-2 - ein Beispiel
392
INC(i) END END END Fuell;
(* (* Liest letzte Ausbildungsnummer, erhöht sie und zeigt sie auf der Bildschirmmaske an (*
*) *) *)
P R O C E D U R E GetNextStudO; BEGIN GetStud(Stud); StringToCard(Stud.number, eduNumber, done); IF eduNumber = 9999 THEN eduNumber: = 1 ELSE INC(eduNumber) END; CardToString(eduNumber, Stud.number, 4); Fuell 0; Delete(Texte.frames[1].text, 16, 4); lnsert(Stud.number, Texte.frames[1].text, 16); Gotoxy(Texte.frames[1].column, Texte.frames[1].row); WriteString(Texte.frames[1 ] .text) E N D GetNextStud;
(
*
(* Füllt Zeichenkette der Länge zwei mit Nullen auf (
*
-*) *)
•)
P R O C E D U R E FuellNullen2(n : CARDINAL; VAR s : STRING2); BEGIN IF n < 10 THEN CardToStrlng(n, s, 1); Concat('0\ s, s) ELSE CardToString(n, s, 2) END END FuellNullen2;
(* (* Formatiert das Datum (
*
P R O C E D U R E SetDate(VAR s:STRING76); VAR s1, s2, s3 : STRING2; BEGIN FuellNullen2(day, s1); FuellNullen2(month, s2); year: =year - ((year DIV 100) * 100); FuellNullen2(year, s3);
*) *)
*)
393
4.4 Implementationsmodule
Concat(s1, '.', s); Concat(s, s2, s); Concat(s, '.', s); Concat(s, S3, s) END SetDate;
(*_
')
(* Öffnet die indexsequentiell verwaltete Schülerdatei, schreibt einen Datensatz und schließt die Datei wieder *) (*
*)
PROCEDURE MemoryPupil; BEGIN IF PupilPageMemoryOpenO THEN OpenPupilFileAndlDXO; PutPupilRec(Pupil); PupilPageMemoryCloseO END END MemoryPupil;
(* Öffnet die indexsequentiell verwaltete Kontodatei, schreibt einen Datensatz und schließt die Datei wieder *) (*
*)
PROCEDURE MemoryAccount; BEGIN IF AccountPageMemoryOpenO THEN OpenAccountFileAndlDXO; PutAccountRec(Account); AccountPageMemoryCloseO END END MemoryAccount;
(
*
*
)
(* Anweisungen der Prozedur Maske39
*)
(*
')
BEGIN (* Maskenbeschreibung aus der Datei "Fahr39.MAS" lesen *) ReadMask('Fahr39.MAS', Texte, InOutputs, Selections, Status); (* alte Ein-/Ausgabefelder Zwischenspeichern *) oldlnOut: = InOutputs; AssignString('ENDE',Result); (* Status = 0, falls beim Lesen der Maske kein I/O-Fehler aufgetreten ist *) IF Status = 0 THEN (* Hilfe-Texte für die Maske39 aus der Hilfedatei lesen *) GetlnfoText(39, Infol, lnfo2); (* Datum vom Betriebssystem lesen *) DosGetDate(year, month, day); ok: =TRUE;
394
4. Systementwicklung mit Modula-2 - ein Beispiel
REPEAT (* Initialisierungen für die erste Bearbeitung *) IF ok THEN InOutputs: = oldlnOut; GetNextStudO; SetDate(lnOutputs.frames[11 ].text); SetDate(lnOutputs.frames[13].text); END; (* Initialisierungen für das Editieren der Maske *) Interrupt: = FALSE; FeldNumber: = 1; (* Editieren der Maske39 am Bildschirm*) loMaske(lnfo1, lnfo2, Texte, Selections, InOutputs, Result, Interrupt, FeldNumber, Abbruch); (* Auswerten der Abbruchkommandos der Maske39 *) (* Fall 1: ABLEGEN im Speicher oder ABLEGEN und ENDE *) IF (Compare(Result,'AB-ENDE') =0) OR (Compare(Result,'ABLEGEN') =0) THEN (* Editierte Daten von Zeichenketten in den "PupilRec"-Datensatz konvertieren *) Convert(1, Stud, InOutputs, Pupil, class, mat, ok); (* Konvertierte Daten nach Plausibilitätsüberprüfung abspeichern *) IF ok THEN MemoryPupilO; InitAccountfclass, mat, Pupil, Account); MemoryAccountO; PutStud(Stud) END (* Fall2: ABLEGEN im Speicher und DRUCKEN *) ELSIF (Compare(Result,'AB-DRU') =0) THEN (* Editierte Daten von Zeichenketten in den "PupilRec"-Datensatz konvertieren *) Convert(1, Stud, InOutputs, Pupil, class, mat, ok); (* Konvertierte Daten nach Plausibilitätsüberprüfung abspeichern *) IF ok THEN MemoryPupilO; lnitAccount(class, mat, Pupil, Account); MemoryAccountO; PutStud(Stud); Druck(Pupil) END END UNTIL ((Compare(Result,'AB-ENDE') =0) AND ok) OR (Compare(Result,'ENDE')=0) OR (Abbruch = CHR(ESC)) ELSE ShowErrorText(61) END END Maske39;
4.4 Implementationsmodule
395
(._
*)
(* Ändert Schüler über die auf dem Bildschirm angezeigte Maske "Schüler ändern" (*
PROCEDURE Maske4O0; VAR InOutputs, oldlnOut Selections Texte Result Status FeldNumber, eduNumber Interrupt, Error, ok Infol, lnfo2 Abbruch Pupil Stud key keyl class mat
*) *)
inout; select; show; selecttag; INTEGER; CARDINAL; BOOLEAN; STRING80; CHAR; PupilRec; StudRec; LONGINT; ISAMKeyStr; licenceclass; material;
(* (* Zeigt die Ausbildungsnummer in der Maske40 an*) C PROCEDURE ShowEducationNumberO; BEGIN Delete(Texte.frames[1].text, 16, 4); lnsert(Stud.number, Texte.frames[1].text, 16); Gotoxy(Texte.frames[1]. column, Texte.frames [1], row); WriteString (Texte.frames [ 1 ] .text) END ShowEducationNumber;
(* Kopiert den über Maske eingegebenen Schlüssel in das Schlüsselfeld von ISAM (* PROCEDURE MakeKeyO; VAR i : CARDINAL; BEGIN FOR i : = 0 T O 80 DO keyl [i]: = " (* Blank *) END; FOR i: = 1 TO Length(lnOutputs.frames[1].text) DO keyl [i]: = lnOutputs.frames[1].text[i] END END MakeKey;
*) - *)
*) *)
(* Anweisungen der Prozedur Maske40 (* BEGIN (* Maskenbeschreibung aus der Datei "Fahr40.MAS" lesen *) ReadMask('Fahr40.MAS\ Texte, InOutputs, Selections, Status); (* alte Ein-/Ausgabefelder Zwischenspeichern *) oldlnOut: = InOutputs; AssignString('ENDE',Result); (* Status = 0, falls beim Lesen der Maske kein I/O-Fehler aufgetreten ist *) IF Status = 0 THEN (* Hilfe-Texte für die Maske40 aus der Hilfedatei lesen *) GetlnfoText(40, Infol, lnfo2); ok:=TRUE; (* indexsequentiell verwaltete Schülerdatei öffnen *) IF PupilPageMemoryOpenO THEN OpenPupilFileAndlDXO; REPEAT Error: = FALSE; IF ok THEN InOutputs: = oldlnOut END; (* Initialisierungen für das Editieren der Maske *) Interrupt := FALSE; FeldNumber: = 1; (* Auswerten der Abbruchkommandos der Maske40 *) (* Fall 1: VORWÄRTS BLÄTTERN im Speicher *) IF Compare(Result,'NÄCHSTE') = 0 THEN NextPupilRec(Pupil, key, keyl, 2); Interrupt: =TRUE (* Fall 2: RÜCKWÄRTS BLÄTTERN im Speicher *) ELSIF Compare(Result,'LETZTEN') = 0 THEN PrevPupilRec(Pupil, key, keyl, 2); Interrupt: =TRUE (* Fall 3: SUCHEN im Speicher *) ELSE REPEAT (* Schlüsselfeld ausfüllen und Interrupt geben *) loMaske(lnfo1, lnfo2, Texte, Selections, InOutputs, Result, Interrupt, FeldNumber, Abbruch); (* Interrupt auswerten *) IF Interrupt THEN (* Schlüsselfeld nach ISAM kopieren *) MakeKeyO; SearchPupllRec(Pupil, key, keyl, 2, Error) ELSIF NOT (Abbruch = CHR(ESC)) THEN ShowErrorText(23) END UNTIL Interrupt OR (Abbruch = CHR(ESC)) OR (Compare(Result,'ENDE') =0) END;
4.4 Implementationsmodule
397
(* Falls Interrupt, dann Datensatz zum Schlüssel suchen *) IF Interrupt AND NOT(Error) THEN Convert1(0, Stud, InOutputs, Pupil, class, mat, ok); ShowEducationNumberO; REPEAT Interrupt: = FALSE; FeldNumber: = 1; (* Datensatz zum Ändern anzeigen *) loRefreshMaske(Texte, Selections, InOutputs, Result, Interrupt, FeldNumber, Abbruch); IF Interrupt AND NOT (Abbruch = CHR(ESC)) THEN ShowErrorText(21) END UNTIL NOT(lnterrupt) OR (Abbruch = CHR(ESC)) OR (Compare(Result,'ENDE') =0) OR (Compare(Result,'NÄCHSTE') = 0) OR (Compare(Result,'LETZTEN') = 0); IF Compare(Result,'ENDE') = 0 THEN AssignString('EINGABE', Result) ELSIF (Compare(Result,'ABLEGEN') =0) OR (Compare(Result,'AB-ENDE') =0) THEN (* geänderten Datensatz konvertieren *) Convert1(1, Stud, InOutputs, Pupil, class, mat, ok); (* Konvertierte Daten nach Plausibilitätsüberprüfung zurückspeichern *) IF ok THEN UpdatePupilRec(key, Pupil) END ELSIF (Compare(Result,'DRUCKEN') = 0) THEN Druck(Pupil) END END UNTIL ((Compare(Result,'AB-ENDE') =0) AND ok) OR (Compare(Result,'ENDE') = 0) OR (Abbruch = CHR(ESC)); (* Indexsequentiell verwaltete Schülerdatei schließen *) PupHPageMemoryCloseO END ELSE ShowErrorText(62) END E N D Maske40;
(* (* Löscht Schüler über die auf dem Bildschirm angezeigte Maske "Schüler löschen1'
(.
P R O C E D U R E Maske41(); BEGIN END Maske41 ;
*) *)
*)
4. Systementwicklung mit Modula-2 — ein Beispiel
398
(* *) (* Druckt für angegebenen Schüler den Ausbildungsauftrag aus. Vor dem Ausdrucken werden die Schülerdaten zwecks Kontrolle auf dem Bildschirm angezeigt. *) *) (* P R O C E D U R E Maske420; BEGIN END Maske42; (* (* Anweisungen des Implementationsmoduls.
*) *)
C BEGIN E N D SchueVerwalten.
*)
IMPLEMENTATION MODULE SchueStamm; (*
*)
(* Mit Hilfe dieses Moduls läßt sich die Schülerstammdatei indexsequentiell verwalten. E s werden zwei Schlüssel pro Schüler verwendet: ein Hauptschlüssel und ein Nebenschlüssel. Der Hauptschlüssel muß eindeutig sein. Er besteht aus dem Nachnamen und der nachgestellten Ausbildungsnummer. Der Nebenschlüssel beinhaltet lediglich den Nachnamen. *) (* *) (
*
*)
(* Importdeklarationen (* FROM ErrorlO FROM ISAM
FROM FROM (
*) *) IMPORT ShowErrorText; IMPORT GetPageStack, ReleasePageStack, MakeFileBlock, OpenFileBlock, CloseFileBlock, AddRec, DeleteRec, UsedRecs, FreeRecs, PutRec, GetRec, ClearKey, NextKey, PrevKey, SearchKey, FindKey, AddKey, DeleteKey, ISAMOK, ISAMFehler, ISAMFileBlock, ISAMIndBeschr, ISAMKeyStr; IMPORT Concat; IMPORT AssignString;
Strings StringUtility *
(* Konstantendeklarationen (* CONST Pupil = 'BED1'; (* (* Variablendeklarationen (' VAR PupillDX : ISAMFileBlock; nrOfPages : INTEGER;
*
)
*) *)
*) *) *)
4.4 Implementationsmodule
399
(.
*)
(* Für indexsequentiell organisierte Schülerstammdatei zusätzlichen Speicherplatz bereitstellen Für Indextabelle der Schülerstammdatei Speicherplatz Im Arbeitsspeicher (Heap) bereitstellen *) (* *) PROCEDURE PupilPageMemoryOpenO:BOOLEAN; VAR pages: Longlnt; BEGIN pages := LONGINT(128000); nrOfPages : = GetPageStack(pages); checklOO; RETURN ISAMOK END PupilPageMemoryOpen;
(*
*)
(* Indexsequentiell organisierte Schülerstammdatei schließen C PROCEDURE PupllPageMemoryCloseO; BEGIN CloseFileBlock(PupillDX); ReleasePageStackO
*) *)
END PupilPageMemoryClose; (*
*)
(* Prüft, ob ein Fehler vorliegt. Falls ja, wird eine Fehlermeldung ausgegeben
*)
(*
*>
PROCEDURE checklOO; BEGIN IF NOT ISAMOK THEN CASE ISAMFehler OF 1..9 : ShowErrorText(ISAMFehler); | 1000 : ShowErrorText(300) ; | 1010 : ShowErrorText(301); | 1020 : ShowErrorText(302) ; j 1030 : ShowErrorText(303) ; | 1040 : ShowErrorText(304); | 1050 : ShowErrorText(305) ; | 1070 : ShowErrorText(307) ; | 1080 : ShowErrorText(308); | 1090 : ShowErrorText(309) ; | 1100 : ShowErrorText(310) ELSE ShowErrorText(306) END; CloseFileBlock(PupillDX); ReleasePageStackO END END checklO;
400
4. Systementwicklung mit Modula-2 - ein Beispiel
(* (* Indexsequentiell organisierte Schülerstammdatei öffnen
*) *)
C P R O C E D U R E OpenPupilFileAndlDXO; VAR indexDescr : ISAMIndBeschr; size Longlnt; BEGIN OpenFileBlock(PupillDX, Pupil); IF NOT ISAMOK THEN IF ISAMFehler=1 THEN WITH indexDescr[1] DO KeyL : = 12; AllowDupK : = FALSE END; WITH indexDescr[2] DO KeyL : = 20; AllowDupK : = TRUE END; size: = LONGINT (295); MakeFileBlock(PupillDX, Pupil, size, 2, indexDescr); checklOO ELSE checklOO END END E N D OpenPupilFileAndlDX;
*)
(* Neuen Schülerstammsatz ablegen und Schlüssel verwalten (*
P R O C E D U R E PutPupilRec(Pupil : PupilRec); VAR handle Longlnt; keyl : ISAMKeyStr; key2 : ISAMKeyStr; P R O C E D U R E MakeKeylO; BEGIN ConcatC ', Pupil.educationstart, keyl); Concat(key1, Pupil.educationnumber, keyl) E N D MakeKeyl ; P R O C E D U R E MakeKey20; VAR i : CARDINAL; BEGIN FOR i: = 0 TO 80 D O key2[i]: = ' ' (* Blank *) END; i: = 1 ; LOOP IF Pupil.namefi] < > " (* Blank *) THEN key2[i]: = Pupil.name[i];
*)
*)
4.4 Implementationsmodule
401
INC(i) ELSE EXIT END END END MakeKey2; BEGIN AddRec(PupillDX, handle, Pupil); checklOO; MakeKeylO; AddKey(PupillDX, 1, handle, keyl); IF NOT ISAMOK AND (ISAMFehler=0) THEN ShowErrorText(311); DeleteRec(PupillDX, handle); checklOO ELSE checklOO; MakeKey2(); AddKey(PupillDX, 2, handle, key2); checklOO END END PutPupilRec;
(*
*)
(* Schülerstammsatz verändern
*)
(*
*)
PROCEDURE UpdatePupilRec(key : LONGINT; Pupil : PupilRec); BEGIN PutRec(PupillDX, key, Pupil); checklOO END UpdatePupilRec; (* (* Schülerstammsatz lesen PROCEDURE GetPupilRec(VAR Pupil : PupilRec; key2 : ISAMKeyStr; key : INTEGER); VAR handle Longlnt; BEGIN FindKey(PupillDX, key, handle, key2); IF (NOT ISAMOK) AND (ISAMFehler = 0) THEN ShowErrorText(312); DummyRec(Pupil) ELSE GetRec(PupillDX, handle, Pupil); checklOO END END GetPupilRec;
*) *)
402
4. Systementwicklung mit Modula-2 - ein Beispiel
(*
')
(* Ersten Schülerstammsatz zum Schlüssel "key2" suchen
*) *)
PROCEDURE SearchPupilRec(VAR Pupil: PupilRec; VAR key : Longlnt; VAR key2 :1 SAM Key St r; keynr: INTEGER; VAR Error: BOOLEAN); BEGIN Error: = FALSE; SearchKey(PupillDX, keynr, key, key2); IF (NOT ISAMOK) AND (ISAMFehler=0) THEN ShowErrorText(313); DummyRec(Pupil); Error: = TRUE ELSE GetRec(PupillDX, key, Pupil); checklOO END END SearchPupilRec;
(*
(* Nächsten Schülerstammsatz zum Schlüssel lesen (*PROCEDURE NextPupilRec(VAR Pupil : PupilRec; VAR key : Longlnt; VAR key2 : ISAMKeyStr; keynr : INTEGER); VAR handle Longlnt; BEGIN NextKey(PupillDX, keynr, key, key2); IF (NOT ISAMOK) AND (ISAMFehler = 0) THEN ShowErrorText(314); DummyRec(Pupil) ELSE GetRec(PupillDX, key, Pupil); checklOO END END NextPupilRec;
(*
*)
(* Nächsten Schülerstammsatz zum Schlüssel lesen mit Fehlerausgabe (*
*) *)
PROCEDURE Next2PupilRec(VAR Pupil: PupilRec; VAR key : Longlnt; VAR key2 : ISAMKeyStr; keynr: INTEGER); VAR handle Longlnt; BEGIN NextKey(PupillDX, keynr, key, key2); IF (NOT ISAMOK) AND (ISAMFehler=0) THEN DummyRec(Pupil) ELSE GetRec(PupillDX, key, Pupil); checklOO END END Next2PupilRec;
4.4 Implementationsmodule
403
(* Vorherigen Schülerstammsatz zum Schlüssel lesen (* P R O C E D U R E PrevPupilRec(VAR Pupil : PupilRec; VAR key : Longlnt; VAR key2 : ISAMKeyStr; keynr : INTEGER); VAR handle Longlnt; BEGIN PrevKey(PupillDX, keynr, key, key2); IF (NOT ISAMOK) AND (ISAMFehler=0) THEN ShowErrorText(315); DummyRec(Pupll) ELSE GetRec(PupillDX, key, Pupil); checklOO END END PrevPupilRec;
(* Schülerstammsatz löschen P R O C E D U R E DeletePupilRec(key : Longlnt; Pupil : PupilRec); VAR keyl : ISAMKeyStr; key2 : ISAMKeyStr; P R O C E D U R E MakeKey1(); BEGIN Concat(' ', Pupil.educationstart, keyl); Concat(key1, Pupil.educatlonnumber, keyl) E N D MakeKeyl ; P R O C E D U R E MakeKey20; VAR i : CARDINAL; BEGIN FOR i: = 0 TO 80 DO key2[i]: = " ( * Blank *) END; i: = 1 ; LOOP IF Pupil.name[i] < > " (* Blank *) THEN key2[i]: = Pupil.name[i]; INC(i) ELSE EXIT END END E N D MakeKey2; BEGIN MakeKeyl (); DeleteKey(PupillDX, 1, key, keyl); checklO(); MakeKey2();
*)
404
4. Systementwicklung mit Modula-2 - ein Beispiel
DeleteKey(PupillDX, 2, key, key2); checklOO; DeleteRec(PupillDX, key); checklOO END DeletePupilRec; (*
*)
(* Schlüssel zurücksetzen
*)
(*
*)
PROCEDURE PupilClearKeysO; BEGIN ClearKey(PuplllDX, 1); ClearKey(PupillDX, 2) END PupilClearKeys; (*
*)
(* Initialisiert einen Dummy-Satz
*)
(*
*}
PROCEDURE DummyRec(VAR Pupil : PupilRec); BEGIN WITH Pupil DO null: = LONGINT(0); AssignString('0000', educationnumber); AssignStringC name); AssignStringC firstname); AssignStringC street); AssignStringC city); AssignStringC address); telefon); AssignStringC national); AssignStringC birthname); AssignStringC AssignStringC birthdate); AssignStringC ', cityofbirth); AssignStringC educationstart); AssignStringC drivingclass); AssignStringC educationend); AssignStringC ', bank); AssignStringC code); AssignStringC account); AssignStringC employer); AssignStringC ', cityofemployer); turnovertax); AssignStringC ', AssignStringC ', mat1) AssignStringC ', mat2) AssignStringC ', mat3) AssignStringC ', mat4) AssignStringC ', mat5) AssignStringC ', mat6) mat7) AssignStringC ', mats) AssignStringC ',
4.5 Erzeugen eines ausführbaren Modula-2-Programms
AssignStringC', AssignStringC', AssignStringC', AssignStringC', END E N D DummyRec;
405
mat9); mat10); mat11); mat12)
C (* Hauptprogramm (* BEGIN E N D SchueStamm.
-*) *) *)
4.5 Erzeugen eines ausführbaren Modula-2-Programms Ein aus einem Programm-Modul und aus globalen Modulen bestehendes Modula-2-Programm ist unmittelbar nach der Implementierung der einzelnen Module noch nicht ausführbar. Vielmehr müssen der Programm-Modul und die übrigen globalen Module zunächst übersetzt und danach zu einem ausführbaren Programm gebunden werden. Nun erst kann das ausführbare Programm aufgerufen und ausgeführt werden. Im vorliegenden Kapitel wird dargelegt, in welchen Schritten das Übersetzen und Binden vorzunehmen ist. Insbesondere werden auch die hierzu erforderlichen Kommandos für ein konkretes Modula-2-Programmiersystem, das bereits im Kapitel 3 angesprochene TopSpeed-Modula-2-System, behandelt. Die gesamte Vorgehensweise nach der Implementierung der einzelnen Module umfaßt folgende, nacheinander auszuführende Schritte: (1) Übersetzen aller Definitionsmodule eines Programmsystems in einer bestimmten Reihenfolge. (2) Übersetzen des Programm-Moduls und der Implementationsmodule in beliebiger Reihenfolge. (3) Binden des Programm-Moduls und der Implementationsmodule zu einem ausführbaren Programm. (4) Starten des ausführbaren Programms.
4. Systementwicklung mit Modula-2 - ein Beispiel
406
Zu präzisieren ist noch, in welcher Reihenfolge die Definitionsmodule eines Programmsystems zu übersetzen sind. Zu diesem Zweck sei auf den in Abbildung 4.11 gezeigten Ausschnitt aus dem Moduldiagramm eines Fahrschulsystems zurückgegriffen. Hier und auch im folgenden sei vereinfachend unterstellt, daß -
der Programm-Modul Top Menue nur den Modul Fktb Erfassen,
-
der Modul Fktb_Erfassen nur den Modul Schue Verwalten,
-
der Modul Schue_Verwalten nur den Modul Schue_Stamm und
-
der Modul Schue Stamm nur den Modul ISAM
benutzt. Da ein wesentlicher Zweck der Übersetzung von Definitionsmodulen auch in der Überprüfung der Verträglichkeit von Modulschnittstellen besteht, übersetzt man die Module von „unten nach oben". Zuerst werden also die Definitionsmodule an der Basis des Moduldiagramms übersetzt. Danach können die Definitionsmodule übersetzt werden, die ausschließlich Importbeziehungen zu bereits übersetzten Definitionsmodulen (zunächst nur Module an der Basis des Moduldiagramms) aufweisen usw. Im genannten Beispiel ergibt sich folgende Übersetzungsreihenfolge: -
zuerst Übersetzung des Definitionsmoduls ISAM,
-
dann Übersetzung des Definitionsmoduls Schue_Stamm,
-
dann Übersetzung des Definitionsmoduls Schue_Verwalten
-
und zuletzt Übersetzung des Definitionsmoduls Fktb_Erfassen.
Sämtliche der genannten Schritte (1) bis (4) werden von dem TopSpeed-Modula-2-System unterstützt. Dieses System stellt ein vollständiges Programmentwicklungssystem dar. Es enthält folgende Komponenten: -
Einen Editor zum Bearbeiten von (Programm-)Texten; es können gleichzeitig bis zu 4 Textdateien geladen und verwaltet werden.
-
Einen Compiler zur Übersetzung von Definitions-, und Implementations- und Programm-Modulen.
-
Einen Linker zum Binden von Programm-Modulen mit den jeweils zu einem Programm gehörenden Implementationsmodulen.
-
Eine Make-Routine, die zur automatischen Recompilation von Modulen beim Auftreten von Versionskonflikten dient.
-
Eine Run-Routine zum Starten von ausführbaren Modula-2-Programmen.
-
Utilities für verschiedene Zwecke (z.B. Wechseln von Dateiverzeichnissen, Aufrufen der DOS-Shell usw.).
4.5.1 Übersetzen der Definitionsmodule
407
Das System weist eine recht komfortable Benutzerschnittstelle auf. Verwendet werden u.a. folgende benutzerfreundliche Konzepte: -
Pull-down-Menüs,
-
Fenstertechnik und
- Shortkeys (Kurzkommandos). Alle Kommandos können über Menüs ausgewählt werden. Die wichtigsten Kommandos (compile, link, make und run) können als Shortkeys per Tastatur eingegeben werden. Die Eingabe von Daten bzw. Texten erfolgt über Fenster, in die die jeweils bearbeiteten Dateien zu laden sind. Nachdem nun das zugrunde gelegte Programmiersystem kurz skizziert wurde, können die in jedem der Schritte (1) bis (4) einzugebenden Kommandos dargestellt und kurz erläutert werden.
4.5.1 Übersetzen der Definitionsmodule Jede Compilation beginnt mit dem Laden der zu übersetzenden Datei (jeder Definitions-, Implementations- und Programm-Modul kann als eine ProgrammtextDatei aufgefaßt werden) in eines der vier Editorfenster. Hierzu sind zwei Kommandos erforderlich: 1. Files 2. Load
Beim Aufruf des TopSpeed-Modula-2-Systems erscheint zunächst das Fenster Top-Menue auf dem Bildschirm. Die Auswahl von Files bewirkt einen Übergang zum Files-Menue, das dem Top-Menue auf dem Bildschirm überlagert wird. Die Auswahl von Load bewirkt das Öffnen eines Fensters, über das der Name der zu ladenden Datei einzugeben ist. Für den Definitionsmodul ISAM lautet die Eingabe: 3. ISAM.DEF
Ist die Datei im DOS-Inhaltsverzeichnis verzeichnet; so wird sie in eines der Editorfenster geladen. Die Übersetzung des Definitionsmoduls ISAM bzw. der Datei ISAM.DEF kann nun auf zwei Arten erfolgen: - ausgehend vom Editor-Modus durch Eingabe eines Shortkeys oder -
ausgehend vom Top-Menue, in das man zuvor zurückkehren muß.
4. Systementwicklung mit Modula-2 - ein Beispiel
408
Der im ersten Fall verwendete Shortkey geht von der Grundeinstellung „dateiname.MOD" aus, d.h. es wird angenommen, daß der zu übersetzende Modul ein Programm- oder ein Implementationsmodul ist. Da hier jedoch ein Definitionsmodul übersetzt werden soll, ist der Dateiname erneut über ein Fenster einzugeben: 4. ISAM.DEF
Nun kann mit einem über zwei Tasten einzugebenden Shortkey die Übersetzung gestartet werden: 5. ALT und C
Während der Übersetzung von ISAM.DEF werden in einem Compilationsfenster die Anzahl der bereits übersetzten Zeilen und die Anzahl der bereits gefundenen Syntaxfehler angezeigt. Nach Beendigung des Übersetzungsvorgangs gibt das Compilationsfenster Auskunft über die Anzahl der insgesamt übersetzten Zeilen und die Anzahl der gefundenen Syntaxfehler. Das Drücken irgendeiner Taste bewirkt nun das Verschwinden des Compilationsfensters und die Rückkehr in den Editor-Modus. Sind Syntaxfehler aufgetreten, so können die entsprechenden Programmtextstellen angezeigt (vorwärts bzw. rückwärts blättern mittels der Funktionstasten F7 bzw. F8) und korrigiert werden. Im zweiten Fall kehrt man durch Betätigen der Funktionstaste F10 in das TopMenue zurück, also: 4a. F10
Nun wählt man im Top-Menue das Kommando „Compilieren" aus. Dies kann durch Positionieren des Menue-Lichtbalkens mittels der Cursortasten auf „Compilieren" und „Enter" oder durch die Eingabe des Buchstabens C (für Compilieren) geschehen: 5a. „Compilieren" oder C
In beiden Fällen erscheint auf dem Bildschirm ein Fenster, in das der Name der zu übersetzenden Datei einzugeben ist. Die Eingabe lautet also: 6. ISAM.DEF
Der Übersetzungsvorgang wird nun gestartet und läuft aus der Sicht des Benutzers in der oben skizzierten Weise ab. In völlig analoger Weise können nun die übrigen Definitionsmodule in der Reihenfolge SchueStamm.DEF, SchueVerwalten.DEF und FktbErfassen.DEF übersetzt werden.
4.5.2 Übersetzen des Programm- und der Implementationsmodule
409
Ausdrücklich sei darauf hingewiesen, daß bei Benutzung des Systems TopSpeedModula-2 eine Übersetzung der Definitionsmodule durch den Benutzer nicht zwingend erforderlich ist. Beim Übersetzen eines Programm-Moduls oder eines Implementationsmoduls werden nämlich ohne Zutun des Benutzers stets auch die Definitionsmodule übersetzt, die zu den vom übersetzten Modul benutzten Implementationsmodulen gehören. Die vorab vorgenommene Übersetzung der Definitionsmodule eines Programmsystems ist jedoch empfehlenswert, da sie bereits vor der Compilation der Implementationsmodule zur Aufdeckung von Fehlern in den Modulschnittstellen führt.
4.5.2 Übersetzen des Programm- und der Implementationsmodule Die Übersetzung des Programm-Moduls oder eines Implementationsmoduls kann in der gleichen Weise erfolgen, wie die eines Definitionsmoduls. Daher erübrigt sich hier die Angabe der anzuwendenden Kommandofolge. Wird ein Definitionsmodul, beispielsweise die Datei ISAM.MOD, übersetzt, so wird der erzeugte Objektcode (das Ergebnis der Übersetzung) mit der DOS-extension (.OBJ) abgelegt. Der Objekt-File ISAM.OBJ wird also im DOS-Inhaltsverzeichnis ausgewiesen. Die Übersetzung des Programm-Moduls TopMenue.MOD und der restlichen Implementationsmodule FktbErfassen.MOD, SchueVerwalten.MOD und SchueStamm.MOD ergibt entsprechend die Objekt-Files TopMenue.OBJ, FktbErfassen.OBJ, SchueVerwalten.OBJ und SchueStamm.OBJ. Die Objekt-Files können nun im nächsten Schritt zu einem ausführbaren Programm gebunden werden.
4.5.3 Binden der Objekt-Dateien Ebenso wie das Compilieren kann das Binden auf zwei Arten erfolgen: - ausgehend vom Editor-Modus durch Eingabe eines Shortkeys oder -
ausgehend vom Top-Menue, in das man zuvor zurückkehren muß.
Im ersten Fall wird der Binder durch Drücken folgender Tasten (Shortkey) aufgerufen: 7. ALT und L
Auf dem Bildschirm erscheint nun ein Fenster, über das der Name der zu bindenden Programmdatei einzugeben ist. Die Eingabe von 8. TOP-Menue
leitet nun das Binden des Objekt-Files TopMenue.OBJ mit den Objekt-Files aller
4. Systementwicklung mit Modula-2 - ein Beispiel
410
Implementationsmodule des Programmsystems ein. Im betrachteten Beispiel werden also TopMenue.OBJ, FktbErfassen.OBJ, SchueVerwalten.OBJ, SchueStamm.OBJ und ISAM.OBJ
zu einem ausführbaren Programm gebunden. Während des Bindens wird in einem Linkfenster auf dem Bildschirm angezeigt, ob der Bindeprozeß noch aktiv ist und ob Bindefehler (Versionskonflikte) aufgetreten sind. Bindefehler können mit Hilfe der Make-Routine des TopSpeed-Module-2Systems ausgeräumt werden. Das durch das Binden erzeugte ausführbare Programm wird als Code-File mit der DOS-extension (.EXE) abgelegt und somit im DOS-Inhaltsverzeichnis in der Form dateiname.EXE ausgewiesen. Im gegebenen Beispiel erzeugt der Binder die ausführbare Datei TopMenue.EXE. Im zweiten Fall kehrt man durch Betätigen der Funktionstaste F10 in das TopMenue zurück, also: 6a. F10
Nun wählt man imTop-Menue das Kommando „Linken" durch Positionieren des Menue-Lichtbalkens und Drücken der Taste „Enter" oder durch die Eingabe des Buchstabens L (für Linken) aus: 7a. „Linken" oder L
Auf dem Bildschirm erscheint ein Fenster, über das der Name der zu bindenden Datei einzugeben ist. Die Eingabe: 8. Top-Menue
startet den Bindeprozeß. Dieser läuft aus Benutzersicht in der beschriebenen Weise ab.
4.5.4 Starten des ausführbaren Programms
411
4.5.4 Starten des ausführbaren Programms Ein übersetztes und gebundenes Programm kann mittels der Run-Routine ausgeführt werden, ohne daß man das TopSpeed-Modula-2-System verlassen muß. Durch Eingabe des Shortkeys: 9. ALT und R
wird die Run-Routine aufgerufen. Gibt man nun über das auf dem Bildschirm erscheinende Fenster den Namen der auszuführenden Datei ein, also: 10. TOP-Menue
so wird das Programm gestartet. Natürlich kann jede im DOS-Inhaltsverzeichnis geführte ausführbare Datei auch direkt auf Betriebssystemebene gestartet werden. Im gegebenen Fall müßte man zu diesem Zweck das System TopSpeed-Modula-2 verlassen und danach die auszuführende Datei angeben, also: 11. TOP-Menue
Die Angabe der extension „exe" ist hier nicht erforderlich.
Anhang
414
Anhang A
la: ASCII-Code Tabelle für Einzelzeichen der Ordnungszahlen 0 bis 63. zimal
Oktal
Hexa
Zeichen
Dezimal
Oktal
Hexa
0 1 2 3
OB 1B 2B 3B
00H 01H 02H 03H
NUL SOH STX ETX
32 33 34 35
40 B 41B 42B 43B
20 H 21H 22H 23H
SPACE i
4 5 6 7
4B 5B 6B 7B
04H 05H 06H 07H
EOT ENQ ACK BEL
36 37 38 39
44B 45B 46B 47B
24H 25 H 26H 27H
$
8 9 10 11
10B 11B 12B 13B
08H 09H OAH OBH
BS HT LF VT
40 41 42 43
50B 51B 52B 53B
28H 29H 2AH 2BH
( )
12 13 14 15
14B 15B 16B 17B
OCH ODH OEH OFH
FF CR SO Sl
44 45 46 47
54B 55B 56B 57B
2CH 2DH 2EH 2FH
16 17 18 19
20B 21B 22B 23 B
10H 11H 12H 13H
DLE DC1 DC2 DC3
48 49 50 51
60B 61B 62B 63B
30H 31H 32H 33H
0 1 2 3
20 21 22 23
24B 25B 26B 27B
14H 15H 16H 17H
DC4 NAK SYN ETB
52 53 54 55
64B 65B 66B 67B
34H 35H 36H 37H
4 5 6 7
24 25 26 27
30B 31B 32B 33B
18H 19H 1AH 1BH
CAN EM SUB ESC
56 57 58 59
70B 71B 72B 73 B
38H 39H 3AH 3BH
8 9
28 29 30 31
34B 35B 36B 37B
1CH 1DH 1EH 1FH
FS GS RS US
60 61 62 63
74B 75 B 76B 77B
3CH 3DH 3EH 3FH
9
415
Anhang A
lb: ASCII-Code-Tabelle für Einzelzeichen der Ordnungszahlen 64 bis 127. Dezimal
Oktal
Hexa
Zeichen
Dezimal
Oktal
Hexa
Zeichen
64 65 66 67
100B 101B 102B 103B
40H 41H 42H 43 H
A B C
@
96 97 98 99
140B 141B 142B 143B
60H 61H 62H 63H
a b c
68 69 70 71
104B 105B 106B 107B
44H 45H 46H 47H
D E F G
100 101 102 103
144B 145B 146B 147B
64H 65H 66H 67H
d e f g
72 73 74 75
110B 111B 112B 113B
48H 49H 4AH 4BH
H I J K
104 105 106 107
150B 151B 152B 153B
68H 69H 6AH 6BH
h i i k
76 77 78 79
114B 115B 116B 117B
4CH 4DH 4EH 4FH
L M N 0
108 109 110 111
154B 155B 156B 157B
6CH 6DH 6EH 6FH
I m n 0
80 81 82 83
120B 121B 122B 123B
50H 51H 52H 53 H
P Q R S
112 113 114 115
160B 161B 162B 163B
70H 71H 72H 73 H
P q r s
84 85 86 87
124B 125B 126B 127B
54H 55 H 56H 57H
T U V w
116 117 118 119
164B 165B 166B 167B
74H 75 H 76H 77H
t u
88 89 90 91
130B 131B 132B 133B
58H 59H 5AH 5BH
X Y 2
120 121 122 123
170B 171B 172B 173B
78H 79H 7AH 7BH
y z
92 93 94 95
134B 135B 136B 137B
5CH 5DH 5EH 5FH
\
124 125 126 127
174B 175B 176B 177B
7CH 7DH 7EH 7FH
t
[
]
•
V
w X
{ I
DEL
Anhang A
416
lc: ASCII-Code-Tabelle für Einzelzeichen der Ordnungszahlen 128 bis 191. Dezimal
Oktal
Hexa
Zeichen
Dezimal
Oktal
Hexa
Zeichen
128 129 130 131
200B 201B 202B 203B
80H 81H 82H 83H
S ü e ä
160 161 162 163
240B 241B 242B 243B
60H A1H A2H A3H
ä i ö ü
132 133 134 135
204B 205B 206B 207B
84H 85H 86H 87H
ä ä ä
244B 245B 246B 247B
A4H A5H A6H A7H
n N a