(* Beispielloesung zur Ferienaufgabe 4 nh - 03/2005 *) MODULE Punkteliste; IMPORT Conclusions, Read, SD:=StreamDisciplines, Sets, Streams, Strings, UF:=UnixFiles, Write; CONST maxlength = 150; (* Bitte nicht mehr als 150 Studierende ;-) *) pass = 50; (* Punkte fuer die Bestehensgrenze *) separator = ":"; (* Das Feld-Trennzeichen *) filename = "punkte.txt"; (* Die Verwendung eines konstanten Dateinamens ist _schlechter Stil_ !!! Wir machen das hier nur, um das Programm uebersichtlich zu halten! *) TYPE MyString = ARRAY 100 OF CHAR; Result = RECORD (* Eine Variable vom Typ "Result" enthaelt *) matnr: INTEGER; (* genau die Informationen aus genau einer *) name: MyString; (* Zeile unserer Datei "punkte.txt". *) lecture: MyString; points: INTEGER; END; ResultSet = RECORD (* ResultSet verwaltet alle Zeilen der Datei *) numofresults: INTEGER; (* Anzahl der gelesenen Datensaetze *) results: ARRAY maxlength OF Result; (* Array von Result-Variablen *) END; PROCEDURE ReadResults(VAR res: ResultSet; VAR errormsg: MyString) : BOOLEAN; (* Liest die Datei "punkte.txt" ein und speichert die Daten in der Referenzvariable "res". Im Erfolgsfall wird TRUE zurueckgeliefert. Wenn ein Fehler auftritt, lautet der Rueckgabewert FALSE, und in der Referenzvariablen "errormsg" wird die Fehlermeldung gespeichert *) VAR fieldseps: Sets.CharSet; dummy: MyString; str: Streams.Stream; i: INTEGER; PROCEDURE ParseInt(string: ARRAY OF CHAR) : INTEGER; (* Bekommt einen String, der eine Zahl enthaelt, und liefert diese Zahl als INTEGER-Wert zurueck *) VAR s: Streams.Stream; i: INTEGER; BEGIN Strings.Open(s, string); Read.IntS(s, i); Streams.Release(s); RETURN i; END ParseInt; BEGIN (* ReadResults *) (* Schritt 1: Datei oeffen. Im Erfolgsfall ist sie nun durch den Stream str "erreichbar". *) IF ~UF.Open(str, filename, UF.read, Streams.onebuf, NIL) THEN errormsg := "Konnte Datei nicht oeffnen!"; RETURN FALSE; END; (* Schritt 2: Trennzeichen fuer die Datei (also den Stream str) definieren *) Sets.InitSet(fieldseps); (* Initialisieren der Feldtrenner *) Sets.InclChar(fieldseps, separator); SD.SetFieldSepSet(str, fieldseps); (* Schritt 3: Einlesen mit Hilfe einer LOOP-Schleife *) i := 0; LOOP (* Aufpassen, dass wir nicht mehr als die in der Konstanten "maxlength" definierte Anzahl von Datensaetzen einlesen *) IF i >= maxlength THEN errormsg := "Zu viele Datensaetze in der Datei!"; RETURN FALSE; END; (* Nur eine kleine Ausgabe zum "Debuggen", absolut unnoetig hier! soetwas wie ein textbasierter "Fortschrittsbalken"... ;-) *) Write.String("Lese Datensatz Nr. "); Write.Int(i+1, 2); (* +1, weil wir bei 0 zu zaehlen beginnen! *) Write.Ln; (* Matrikelnummer einlesen *) IF ~Read.FieldS(str, dummy) THEN IF str.eof THEN (* Wenn das Dateiende erreicht ist, *) EXIT (* verlassen wir die LOOP-Schleife *) ELSE (* sonstiger Fehler! *) errormsg := "Fehler beim Einlesen einer Matrikelnummer!"; RETURN FALSE; END; END; res.results[i].matnr := ParseInt(dummy); (* Name einlesen *) IF ~Read.FieldS(str, res.results[i].name) THEN errormsg := "Fehler beim Einlesen eines Namens!"; RETURN FALSE; END; (* Fach einlesen *) IF ~Read.FieldS(str, res.results[i].lecture) THEN errormsg := "Fehler beim Einlesen eines Faches !"; RETURN FALSE; END; (* Punktzahl einlesen *) IF ~Read.FieldS(str, dummy) THEN errormsg := "Fehler beim Einlesen einer Punktzahl !"; RETURN FALSE; END; res.results[i].points := ParseInt(dummy); INC(i); IF str.eof THEN EXIT END; Read.LnS(str); IF str.count = 0 THEN EXIT END; END; (* LOOP *) res.numofresults := i; RETURN TRUE; END ReadResults; PROCEDURE PrintResults(res: ResultSet); (* Gibt die Datensaetze auf die in der Aufgabenstellung gewuenschte Art und Weise aus. Die WHILE-Schleife bezieht sich auf die tatsaechliche Anzahl eingelesener Datensaetze (res.numofresults) und _nicht_ auf die Konstante maxlength, damit auch nur vorhandene (d.h. eingelesene) Daten beruecksichtigt werden. Es genuegt hier, die Variable res als Werte-Parameter zu verwenden, da sie nur ausgegeben und nicht veraendert werden soll. *) VAR i: INTEGER; BEGIN i := 0; WHILE i < res.numofresults DO Write.String(res.results[i].name); Write.String(" mit der Matrikelnummer "); Write.Int(res.results[i].matnr, 1); Write.String(" hat in der Klausur"); Write.Ln; Write.String("zur Vorlesung "); Write.String(res.results[i].lecture); Write.String(" << "); Write.Int(res.results[i].points, 1); Write.Line(" >> Punkte erreicht"); Write.String("und sie damit "); IF res.results[i].points < pass THEN Write.String("nicht ") END; Write.Line("bestanden!"); Write.Ln; INC(i); END; (* WHILE *) END PrintResults; PROCEDURE DoIt(); (* Die Steuer-Prozedur unseres Programms *) VAR results: ResultSet; errormesg: MyString; (* fuer die Fehlermeldungen von ReadResult *) BEGIN results.numofresults := 0; (* Initialisierung *); IF ~ReadResults(results, errormesg) THEN Write.StringS(Streams.stderr, "Fehler beim Einlesen: "); Write.LineS(Streams.stderr, errormesg); RETURN END; PrintResults(results); (* Ergebnisse wie gewuenscht ausgeben *) END DoIt; BEGIN (* Das Hauptprogramm... ;-) *) DoIt(); END Punkteliste.