Pascal/Assembler: Erste Gehversuche

Pascal erfreut sich weiterhin großer Beliebtheit. Um die Programmiersprache aktuellen Erfordernissen anzupassen, hat sich Tuning durch Assembler bewährt. Wir zeigen, wie's gemacht wird.

Nach dem Grundlagenartikel (3/92) stellen wir nun eine Fallstudie vor. Das Beispielprogramm »Txtprint« besteht aus einem Pascal- und einem Assembler-Teil und erlaubt papiersparend und komfortabel die Ausgabe von ASCII-Dateien (Endlos oder Einzelblatt). Dies wird durch beidseitigen Druck möglich. Die zeitkritischen Probleme übernimmt der Assembler-Teil - die grundlegenden Strukturen formulieren wir im Pascal-Teil.

Nach dem Start verlangt Txtprint zunächst Namen und Pfad des Dokuments. Danach werden - sofern das File überhaupt existiert Dateilänge, Zeilenzahl und Länge der längsten Zeile angezeigt.

Folgende Parameter erwartet Txtprint:

Bei beidseitigem Druck ist nach Abarbeiten der Vorderseiten das Papier umzuordnen. Der Druckvorgang startet dann nach dem Umschichten für die Rückseiten erneut.

Nach Eingabe von Dateinamen und Pfad (»Datei_Auswahl« analysiert die Routine »PreCheck« das Dokument in bezug auf Länge und Zeilenzahl. Anschließend werden die Parameter, wie oben geschildert, eingelesen (»Parameter_Eingabe«). Entsprechend diesen Vorgaben wird der Text im Speicher umformatiert und auf den Druck vorbereitet (»InitMem«).

Eventuell vorzunehmende Zeichenumleitungen und Druckereinstellungen übernimmt »Filter_Initialisierung« bzw. »SetPrinter«. Schließlich bringt die Prozedur »Ausdruck« den Text zu Papier (oder auf den Bildschirm) und gibt den reservierten Speicher anschließend wieder frei.

Zuerst zu den verwendeten Datenstrukturen: Namens_Typ und DTA_Typ, (Zeile 15, 17) sind für den Aufruf einiger GEMDOS-Funktionen zur Bestimmung der Dateilänge und zum Öffnen der Files notwendig. Achtung: GEMDOS kann keine Pascal-Strings verarbeiten, sondern nur C-Zeichenketten, also packed array [1..80] of char, wobei chr(0) das Ende einer Zeichenkette markiert.

Die Umwandlung erledigt die Prozedur »Str_C« (Zeile 118). Zum Ablegen des Textes verwenden wir die Strukturen Zeile (Zeile 32) und Zeilen_Zeiger (Zeile 34). Zeile ist ein Pointer auf eine einzelne Zeile mit einer maximalen Länge von 500 Zeichen und Zeilen_Zeiger weist auf ein Array des Typs Zeile.

Dabei erfolgt die Verwaltung der Arrays dynamisch, d.h. es wird jeweils nur soviel Speicher belegt, wie wirklich nötig. Auf diese Technik gehen wir noch näher ein, vorerst nur soviel: 'Malloc' reserviert den Speicher. Die Adresse auf diesen Bereich wird mit Hilfe des Transfer_Typs (Zeile 39) in einen Pointer des Typs Zeile gewandelt. Im Seiten_Zeiger (Zeile 37) und dem dazugehörigen Seiten_Array wird jeweils die Nummer des Zeigers aus Zeilen_Zeiger auf die erste Zeile einer Seite abgelegt. Der Filter_Typ (Zeile 46) dient zur Umleitung einzelner Zeichen auf ein anderes Druckerzeichen. Damit läßt sich z.B. die Position des 'ß' im ST-Zeichensatz passend für einen Drucker mit IBM-Zeichensatz korrigieren. Initialisiert wird dieser Filter in der Prozedur Filter_Initialisierung (Zeile 132).

Um verschiedene Druckereinstellungen zur Verfügung zu haben, werden Schrift_Typ (Zeile 48) und Schrift_Zeiger (Zeile 53) vereinbart, die auf Steuercodesequenzen zugreifen. Wir definieren diese im Assembler-Teil und rufen sie durch die Funktion GetPrinter (Zeile 112) auf. Das Assembler-Listing folgt in der nächsten Ausgabe.

Assembler definiert diese Sequenzen als Konstanten. Die Funktion GetPrinter liefert einen Zeiger vom Typ Schrift_Zeiger auf die entsprechenden Daten. Durch diese recht einfache Methode sind Vereinbarungen des Assembler-Teils auch in Pascal verfügbar. Nach Systemroutinen (Zeile 67), External-Deklarationen der Assembler-Routinen (Zeile 91) und Hilfsroutinen (Zeile 115) folgen nun die Hauptroutinen (Zeile 164).

Die Prozeduren Datei_Auswahl (Zeile 166) und Parameter_Eingabe (Zeile 179) nehmen die Einstellungen des Benutzers vor. Die Funktion Datei_Laden (Zeile 219) sucht das Dokument (Zeile 233) und reserviert Speicher für den Zeiger auf die Zeilen (Zeile 237) sowie für den Text (Zeile 246). Ist dies erfolgreich, wird das Dokument geöffnet (Zeile 255) und gelesen (Zeile 259). Nach Ablauf der Druckroutinen gibt die Prozedur Speicher-Freigeben (Zeile 278) den reservierten Platz wieder frei.

Die Hauptarbeit im Pascal-Teil übernimmt die Prozedur Ausdruck (Zeile 293), untergliedert in Drucke_Zeile (Zeile 299), Drucke_Seite (Zeile 317) und Prozedurtext (Zeile 336). Ist beidseitiger Druck gewählt, werden alle ungeraden Seiten (Seite = Vorderseite) und nach einer Aufforderung zum Umordnen des Papiers (Zeile 352) - alle geraden Seiten (Seite = Rueckseite) gedruckt. Die Prozedur Drucke_Seite ruft Drucke_Zeile auf und schickt am Seitenende Formfeed (FF) an den Drucker bzw. schreibt eine Meldung auf den Bildschirm. In Drucke_Zeile werden, falls gewünscht, Zeilen- und- Seitennummern ausgegeben. Die Ausgabe der Zeichen erfolgt dabei über den Druckfilter (Zeile 311). Das Hauptprogramm ruft danach die Pascal-und Assembler-Routinen.

PreCheck ist die erste Assembler-Routine, in die der Pascal-Teil verzweigt. Sie ermittelt die Zeilenzahl und die Länge der längsten Zeile. Nachdem die Rücksprungadresse und die übergebenen Parameter (siehe dazu Zeile 18-21) vom Stack geholt wurden (Zeile 33-37), wird zunächst geprüft, ob das File leer ist (Zeile 43f). Die eigentliche Routine (loop_PreCheck, Z. 47-65) analysiert dabei die Datei byteweise. Ist der ASCII-Wert des aktuellen Bytes (D4) größer oder gleich 32 (BLANK), handelt es sich um ein Druckzeichen; der Länge der aktuellen Zeile wird ein Byte zugeschlagen. Andernfalls liegt ein Steuerzeichen vor, die allerdings alle außer 'Carriage Return' (CR) ignoriert werden. Enthält das Register D4 ein CR, wird der Zeilenzähler (D2) um eins erhöht und geprüft, ob die abgeschlossene Zeile die bisher längste war.

Nachdem das gesamte File untersucht wurde, wird ggf. die Zeilenzahl korrigiert. Dies ist immer dann notwendig, wenn auf das letzte CR kein Druckzeichen folgt. Schließlich werden noch die ermittelten Werte an die Adressen der - im PASCAL-Programm vereinbarten Variablen kopiert (Zeile 70f, call by reference) und PreCheck verlassen (Zeile 75).

Die Prozedur InitMem bereitet den Ausdruck vor: Der Text wird unter Berücksichtigung der Parameter 'Max_Zeilenlänge' und 'Max_Seitenlänge' umformatiert. In einer Liste speichert InitMem dann sämtliche Zeiger, die auf Zeilenanfang stehen und in einer anderen Liste jede Zeilennummer eines Seitenanfangs. Auch hier wird das File in einer Schleife (Zeile 131-185) byteweise untersucht, wobei abermals zwischen Druck- und Steuerzeichen unterschieden wird.

Liegt z.B. ein 'a' unter dem Lese_Zeiger (A0), wird geprüft, ob der Platz in der aktuellen Zeile für ein weiteres Zeichen noch ausreicht (Zeile 139) und danach, ob diese schon initialisiert ist (Zeile 142). In Zeile 145 kann das Zeichen über den Schreib_Zeiger (A1) an seinem Bestimmungsort abgelegt werden.

Bei der Behandlung von Steuerzeichen unterscheiden wir vier Fälle:

Folgt auf CR ein LF oder FF, wird CR ignoriert. CR und LF führen zum Anlegen einer neuen Zeile (Zeile 223-232). FF läßt, wie man erwarten darf, eine neue Seite beginnen (Zeile 235-240), nachdem zuvor die aktuelle Zeile abgeschlossen wurde (Zeile 172). Liegt ein Esc vor, wird das folgende Zeichen übersprungen. Nach dem Verlassen der Schleife ist ggf. die Seitenzahl, sofern nämlich die letzte Seite leer ist, und die Zeilenzahl, falls die letzte Zeile kein Zeichen enthält, um eins zu reduzieren. Die Rückgabe dieser beiden Werte erfolgt wieder mittels Zeiger auf die entsprechenden Variablen des Pascal-Teils. Um das Register D7 nutzen zu können, wurde eingangs dessen Wert gesichert (Zeile 117). Vor dem Rücksprung muß nun der alte Inhalt wiederhergestellt werden (Zeile 217).

Die Funktion 'Zeilenlaenge' ermittelt die Anzahl der Bytes in einer Zeile. Das Resultat ist Null, falls der Zeiger den Wert NIL (engl.: Null) hat. Ansonsten ergibt sich der Funktionswert, indem die Adresse der übergebenen Zeile vom nächsten von NIL verschiedenen Zeilenzeiger subtrahiert wird. Daß es immer einen solchen gibt, wird durch das Einrichten der zusätzlichen Zeile (Zeile 211) gewährleistet. Damit der Funktionswert nun auch an den aufrufenden Programmteil zurückgegeben wird, muß das Ergebnis vor dem Rücksprung im Register D0 liegen.

Die Funktion GetPrinter liefert den Zeiger auf die Liste mit Drucksequenzen. In der nächsten Ausgabe folgt der Assembler-Code für die Prozeduren »PreCheck« und »InitMem« sowie für die Funktionen »Zeilenlaenge« und »Get_Printer«.

Martin Erdelmeier und Martin Reichelt/mn txtprint.zip



Aus: ST-Magazin 05 / 1992, Seite 98

Links

Copyright-Bestimmungen: siehe Über diese Seite