YAFFA
"Yet Another FIND For All"
"Yet Another FIND For All"

TSB hat einen Befehl, der es ermöglicht, im aktuellen Code nach bestimmten Inhalten zu suchen: FIND. Dabei spielt es seit v2.40329 keine Rolle, ob sich die gesuchte Information innerhalb oder außerhalb eines Strings (Text in Anführungszeichen) befindet.

Im Zusammenhang mit der Entwicklung des Befehls entstand die Frage, ob es nicht sinnvoll wäre, auch in Teilen von Basic-Schlüsselwörtern suchen zu können. Es sollten z.B. bei einer Suche nach dem Text "proc" nicht nur die Prozedurköpfe im Programm gefunden und angezeigt werden, sondern auch die Prozedurabschlussbefehle (END PROC). Da das nicht im bereits tokenisierten Code des Programms im Speicher möglich ist (PROC und END PROC haben zwar gleiche Wortbestandteile, aber natürlich unterschiedliche Token) und es einen erheblichen Aufwand mit sich bringt, on the fly aus dem aktuellen Programm den eigentlichen Quellcode zu generieren, wurde nach einem Weg gesucht, solch eine Funktion dennoch zur Verfügung zu stellen. Es entstand das (Funktionsdemo-) Programm "Yet Another FIND For All", kurz "YAFFA", das mehrere andere Tool-Programme nachlädt, um komfortabel mit den Daten umgehen zu können. Diese sind "DirSelect" von Peter Hagemann (aka Petrus, †) und "Line-Input" von Martin Kumbartzky (aka goloMAK).

Als Funktionsdemo zeigt YAFFA, wie man in TSB bestimmte programmiertechnische Probleme im Griff behält. Diese sind:

 

Vorbereitungen treffen

YAFFA holt sich das zu untersuchende Programm von der Diskette, wo es zudem im Quellcode-Format, also ohne Tokenisierungen, vorliegen muss. Eine solche reine Textdatei erhält man, wenn man die Basic-Datei in den Speicher lädt und von da aus direkt auf die Diskette LISTet. Das macht man folgendermaßen:

Danach steht das aktuelle Programm unter dem Namen "listing" in reiner Textform als SEQ-Datei (also ohne unnötige Startadresse) auf Disk zur weiteren Verarbeitung zur Verfügung.

<nach oben>

 

Nachladeprogramme ohne spätere Absturzgefahr sicher einbinden

Wenn ein Programm Hilfsroutinen in Maschinensprache verwendet wie YAFFA (nämlich DirSelect und Line-Input), dann muss sichergestellt sein, dass sie beim Start des Programms auch korrekt installiert werden, sonst kommt es später bei deren Aufruf unweigerlich zu Abstürzen.

Ein Nachladen per LOAD aus einem laufenden Programm heraus hat immer den Nebeneffekt, dass das Programm nach dem Ladevorgang immer ganz von vorn anfängt, ohne allerdings seine Variableninhalte zu verlieren. Üblicherweise setzt man deshalb die Ladebefehle ganz an den Anfang des Programms, versehen mit einer Abfrage, ob man diese Aktion vielleicht schon einmal durchgeführt hat, damit man nicht in eine Endlosschleife gerät.

Bei zwei nachzuladenden Programmen, die auch noch daraufhin getestet werden sollen, ob sie erfolgreich geladen wurden, ist das etwas aufwändiger. YAFFA macht das so:

In Zeile 100 werden über "init" ein paar Variablen, die für die "file.exists"-Aufrufe in Z. 120 und 150 gebraucht werden, initialisiert. Die Namen der beiden Nachladeprogramme stehen in den Zeilen 110 und 140. Mit den Variablen A und B wird einerseits geprüft, ob der Ladebefehl schon durchgeführt wurde (beide werden dann auf den Wert 1 gesetzt), und andererseits, ob diese Aktion erfolgreich war (wenn ja, werden beide Werte auf 2 angehoben, Z. 130 und 160). Fehlt eins der Nachladeprogramme (oder sogar beide), dann sorgt die Abfrage in Zeile 170 für ein Ende von YAFFA.

Der Test auf erfolgreiches Laden wird eingeleitet mit einer Überprüfung auf Vorhandensein der Dateien auf der Diskette (file.exists). Nur bei Erfolg (AL=0) führen wir das Laden überhaupt durch. Mit den beiden D!PEEKs (Z. 130 und 160) testen wir, ob sich an den bei D!PEEK genannten Stellen die angegebenen zwei Werte befinden, die Nachladeprogramme sich also dort befinden, wo sie hin sollen. Zwei Werte, damit die Treffersicherheit höher ist. Sind die Werte dort, war das Laden erfolgreich.

Die Zeile 105 dient dazu, dass man nach dem Verlassen von YAFFA und einem möglichen erneuten RUN die Nachladeprogramme nicht noch einmal laden muss, sie sind ja bereits da und hoffentlich unversehrt. Sind sie vorhanden, wird deshalb sofort mit dem Hauptprogramm (ab Zeile 200) fortgefahren.

<nach oben>

 

Schneller Zugriff auf Daten beim Einlesen von Disk

Das Hauptprogramm besteht nur aus dem Aufruf der Prozedur ".findinfile". Dort werden sowohl die Suchparameter gesetzt, die Suche durchgeführt, als auch das Suchergebnis angezeigt (s. Bild ganz oben).

Basic-Programm-Listings können ganz schön umfangreich sein, zumal in Quellcode-Ausführung. Die beiden von Basic aus für das Lesen von Daten von Diskette vorgesehehen Befehle INPUT# und GET# sind einer wie der andere eine schlechte Wahl. Der erste versagt, wenn in den Daten Zeichen wie Komma, Doppelpunkt oder Semikolon vorkommen (die von INPUT# - neben einem <Return> - als Endezeichen interpretiert werden). Der andere liest einzeln Zeichen für Zeichen ein, wobei diese Zeichen noch zu einer verwertbaren Zeichenkette zusammengesetzt werden müssen, und ist daher äußerst langsam in der Ausführung. Außerdem wird der Wert 0 (Null) ignoriert und als Leerstring zurückgeliefert, was ebenfalls behandelt werden muss und zusätzliche Zeit kostet. Einen alternativen Dateneinlesebefehl gibt es nicht, auch nicht in TSB.

Dieser Lücke hat sich @goloMAK angenommen und eine Routine entworfen, die aus einer mit OPEN geöffneten Datei kompakte Datenblöcke von bis zu 255 Zeichen (der Maximallänge von Strings) einlesen kann. Sollen die Datenblöcke kürzer sein, setzt man beim Abspeichern ein <Return> an ihr jeweiliges Ende, denn auch diese Routine versteht <Return> als Endezeichen. Line-Input ist somit sehr schnell und muss sich mit keinen unnötigen Beschränkungen abmühen. Die Version, die YAFFA verwendet, ist für den Kassettenpuffer ($033c) assembliert und öffnet bei $CA00 einen Puffer für die eingelesenen Daten. Von dort aus werden sie in normale Strings umgewandelt, deren Namen beim Aufruf der Routine mit übergeben werden (hier: D$):

F ist die bei OPEN verwendete Dateinummer und in D$ landen die eingelesenen Daten. Sollte der Puffer an $CA00 ungünstig liegen (dort residiert bei anderen Programmen als YAFFA womöglich auch der X!-Befehl), kann man ihn mit zwei POKEs an eine andere Stelle verlegen: POKE $036D,<Hi-Byte> und POKE $0382,<Hi-Byte> (aber nicht mit dem Wert $C0 nach $C000, denn dort liegt DirSelect!)

In YAFFA lädt Line-Input die einzelnen, zu untersuchenden Basic-Zeilen in den String X$ (Z. 1470).

Drei Dinge muss man wissen:

· Wenn der gelesene Datensatz aus nichts anderem besteht als einem <Return>, dann liefert Line-Input einen leeren String zurück (das Ende-<Return> aus den Datensätzen wird von der Routine verworfen). Das wird in Zeile 1470 abgefangen und in ein CHR$(0) umgewandelt, da im Folgenden einige Befehle Strings erwarten, die auch einen Inhalt haben (die Stringlänge wird bei einem Leerstring unzulässig negativ für LEFT$, Zeile 1490).

· TSB kann mit dem Befehl OPTION die eigenen Befehlswörter beim LISTen hervorheben (invers darstellen). Zum Beenden der Hervorhebung gibt es nach jedem Befehlswort den Code $92 (RVS OFF) aus, allerdings immer, auch dann, wenn der Hervorhebungsmodus gar nicht aktiviert ist. Bei der Darstellung in YAFFA stören diese RVS-OFF-Vorkommen bei der Berechnung der YAFFA-eigenen Hervorhebungen der Fundstellen im Modus "Ganze Zeilen". Daher werden sie in den Zeilen 1480 bis 1500 aus dem eingelesenen String herausgefiltert (Schleife, bis alle weg sind: R=0, in I$ befindet sich das RVS-OFF-Zeichen als Suchreferenz).

Modus: Ganze Zeile · Line-Input hängt sich auf, wenn es auf eine Datei zugreifen soll, die nicht geöffnet werden konnte. Also muss auch beim Öffnen der Quellcode-Datei mit "file.exists" abgefragt werden, ob das überhaupt möglich ist (was in den Zeilen vor Z. 1470 natürlich stattgefunden hat).

Im Übrigen geben die Zeilen 1530 bis 1560 je nach Einstellung des Flags GZ (soll für "Ganze Zeile" stehen) entweder formatiert nur die Zeilennummern der Fundstellen aus, ähnlich dem FIND-Befehl (Z. 1540, Ergebnis s. im Bild ganz oben) oder die ganze Zeile mit Markierung der Fundstelle per INV (Z. 1550, in seltenen Fällen klappt das nicht korrekt, außerdem werden nur die ersten Vorkommen des Suchworts in einer Zeile markiert und auch nur in der ersten physischen Ausgabezeile, s. kleines Bild rechts und vergleiche mit Bild ganz oben).

<nach oben>

 

Formatierung von umfangreicheren Zahlenreihen

Genau wie der TSB-Befehl FIND gibt YAFFA die Fundstellen der Suche normalerweise als Liste von Zeilennummern aus. Anders als bei FIND passen hier aber sechs Zeilennummern auf eine Ausgabezeile und zusätzlich sind diese Nummern rechtsbündig formatiert (s. Bild ganz oben). Da formatierte Zahlenreihen sicher öfter mal gebraucht werden, hier eine Programmiervariante dazu:

Über das Flag GZ haben wir bereits gesprochen. Die Variable Z registriert, ob überhaupt ein Suchtreffer gelandet wurde (wenn nicht, wird statt der Zeilennummern eine entsprechende Meldung ausgegeben). Ab hier folgt die Formatierung der Zahlenreihe.

Da alle eingelesenen Zeilen mit einer Zeilennummer beginnen, bietet sich an, diese per VAL(X$) aus dem String herauszulösen. Für die Rechtsbündigkeit wird dieser Wert sofort dem USE-Befehl übergeben, der auf eine fünfstellige Zahl hin formatiert (die größtmögliche Zeilennummer in Basic ist 64999). Am rechten Ende der Zahl folgt ein Leerzeichen als Abstandshalter. Die Ausgabe der Reihe beginnt immer in Spalte 2 und muss bei Spalte 38 enden (davor und dahinter sind die Punkte der Umrahmung), daher passen in eine Zeile 36 durch 6 Zeichen, also sechs Zeilennummern.

Der TSB-Befehl USE gibt seine Ergebnisse hier so ähnlich aus wie ein PRINT mit Tabulatorkomma (dort allerdings alle zehn Zeichen ein TAB-Stopp, was genau mit der Bildschirmbreite von 40 Zeichen aufgeht und einfach ohne weitere Maßnahmen immer weiter fortgesetzt werden kann). Die Ausgabe von YAFFA passt nicht (bei 40 durch 6 bleibt ein Rest), daher müssen wir ein Zeilenendekriterium definieren. Die sechste Zeilennummer beginnt bei Spalte 32 und endet mit Spalte 38. Wenn wir also bei der Ausgabe testen, welche Spalte inzwischen erreicht ist, haben wir dieses Kriterium.

Die Funktion, um die aktuelle Ausgabespalte zu erhalten, lautet POS. Das Argument in der Klammer von POS kann beliebig gewählt werden, ist aber nicht ganz egal. Wenn man eine Zahlenkonstante, z.B. 0 (Null), einsetzt, arbeitet die Funktion am langsamsten. Schneller ist der einfache Punkt, der vom Interpreter als 0 gesehen wird. Ist also die Ausgabespalte in YAFFA jenseits von 34, dann setzt es den Cursor auf die nächste Zeile (LIN+1), beginnend mit Spalte 2, bereit für eine weitere Ausgabezeile.

<nach oben>

 

Kritik an der Benutzerführung und Lösungsvorschläge dazu

Abgesehen davon, dass YAFFA nur ein ausdrückliches Demonstrationsprogramm dafür ist, wie man im SEQ-Format gespeicherte Basic-Quellcode-Dateien auslesen kann, haben sich Kritikpunkte an der Benutzerführung von YAFFA ergeben, die nicht unerwähnt bleiben sollen.

  1. Wenn es darum geht, nur SEQ-Dateien einzulesen, warum werden dann nicht ausschließlich SEQ-Dateien im Directory angezeigt?

    • Vorschlag: In Zeile 1310 findet sich der Aufruf von DirSelect, das hier zur Anzeige des Disk-Inhaltsverzeichnisses verwendet wird. Eine kleine Änderung bringt die gewünschte Abhilfe:
      SYS $C000,"$:*=S",P$,6.

  2. YAFFA startet bereits mit einem Suchwort-Vorschlag statt erst einmal mit einer Suchwort-Abfrage.

    • Vorschlag: Um beim ersten Start von YAFFA den Vorgabesuchstring zu übergehen (die Zeilen 1110 bis 1150), müsste dafür in Prozedur INIT ein zusätzliches Flag eingeführt werden, das dafür sorgt, diese Zeilen beim ersten Aufruf zu überspringen. Bitte auch daran denken, dass dann in die Abfrage dieses Flags die Cursorpositionierung auf Zeile 2/Spalte 2 (von Zeile 1110) eingebaut wird, damit der Bildschirmaufbau nicht durcheinandergerät. Wer will, kann dann auch die Reihenfolge der Abfragen (zuerst das Suchwort, dann die Suchdatei, nicht umgekehrt wie jetzt, Zeilen 1160 bis 1235) ändern.

  3. Beim Ausgabemodus wird grundsätzlich nach der Ausgabe zurückgeschaltet auf "Nur Zeilennummern". Hatte man die Einstellung "Ganze Zeilen" gewählt, wird diese also immer wieder verworfen.

    • Vorschlag: Für die Umschaltung der Ergebnisanzeige ist das Flag GZ (für "Ganze Zeilen") zuständig. Es wird nach jeder Anzeige zurückgesetzt auf "Nur Zeilennummern" (Zeile 1590). Wenn man in Zeile 1590 die Rücksetzung löscht, hat das aber Auswirkungen auf die Ausgabe der Bildschirmmaske in Prozedur HEADER, denn in der GZ-Einstellung für "Ganze Zeilen" wird im Moment der gepunktete Rahmen gelöscht (in Zeile 1075). Ist die GZ-Einstellung permanent, ist auch der Bildschirm permanent ohne Rahmung. Eine Abhilfe wäre, die Zeile 1065 nach Zeile 1465 zu verlegen und etwas umzudefinieren (den Befehl RCOMP und die Zeile 1065 löschen, FT in GZ umändern).

  4. Bei umfangreichen Ergebnisausgaben, die nicht vollständig auf einen Bildschirm passen, rollt die Anzeige unwiederbringlich oben aus dem Bild. Man kann sie nicht anhalten.

    • Vorschlag: Dies ist etwas knifflig. Die Ergebnisanzeige findet statt in den Zeilen 1530 bis 1560, speziell in 1540 und 1550. Das Hochrollen bei gefülltem Bildschirm erledigt die Zeile 1535 mit dem einfachen PRINT-Statement. Wenn man hier eingreift und am Ende der Zeile ein Wartekommando einfügt (z.B. KEYGET A$), dann muss man bei allen noch folgenden Ausgaben manuell weiterschalten, Zeile für Zeile. Ein neu einzuführender Zähler für die bereits ausgegebenen Bildschirmzeilen könnte das umgehen und eine bildschirmlange Ausgabe ermöglichen, auch nach dem Wartebefehl.

Ich möchte jeden, der einmal mit TSB experimentieren möchte, ermutigen, nach diesen Vorschlägen Änderungen im Programm YAFFA vorzunehmen. Bei Fragen stehe ich gern als Tutor zur Verfügung.

<nach oben>

 

Download YAFFA (D64 mit TSB und mehreren Beispiel-Listings von der TSB-Diskette)