Rein und Raus

Ein wesentlicher Aspekt der Ein- und Ausgabe bei Forth bezog sich bei älteren Systemen auf die Verwendung von sogenannten Blöcken. Das interessante daran war, dass es für einen Benutzer von Forth praktisch keinen Unterschied zwischen dem Speicher im Rechner, dem sogenannten RAM und dem Speicher auf einer Festplatte oder Diskette gab. Ich denke, dass man Forth als das erste System mit virtuellem Speicher bezeichnen kann. Trotzdem werde ich die Aspekte der Aus- und Eingabe von diesen Blöcken nicht beschreiben, da heutzutage praktische alle Forth-Systeme auf Host-Rechnern laufen, also auf Rechnern bei denen Forth nicht auch gleichzeitig das Betriebssystem ist. Bei diesen Systemen, also zum Beispiel Linux, FreeBSD, MacOS, Android (es gibt da noch mehr, sogar eins, das mit 'W' anfängt, aber mir fällt der Name gerade nicht ein) wird der Festplattenspeicher in der Regel nicht in Blöcken sondern in Dateien oder Files organisiert. Die meisten nordernen Forth-Systeme bieten Möglichkeiten auf diese Dateien zugreifen zu können. Da das aber bei nahezu allen Forth-Systemen ein bisschen anders geregelt ist, verweise ich hier auf das Handbuch von ihrem Forth.

Anders sieht das mit der Ein- und Ausgabe von Zeichen von der Tastatur und auf dem Bildschirm, genauer dem Terminal aus. Die Worte, die sich damit befassen, sollen hier besprochen werden. Einige kennen sie schon, aber sie sollen hier in größerem Zusammenhang dargestellt werden.

Die einfachen Ausgabeworte

Viele einfache Worte zur Ausgabe kennen sie schon. Das Wort . gibt die oberste Zahl auf dem Stack auf dem Bildschirm aus. Auch die Worte, die zur formatierten Ausgabe von doppelten Zahlen dienen, sind schon bekannt. Auch für die Ausgabe von Zeichen kennen sie schon EMIT und .". Um die Sache etwas einfacher zu machen kann man sich noch der Worte SPACE, das ein einfaches Leerzeichen auf dem Bildschirm ausgibt, CR, das den Cursor an den Anfang der nächsten Zeile poisiotniert und SPACES, das eine bestimmte Anzahl von Leerzeichen ausgibt, bedienen.

Ein bisschen besser

Gerade wenn es um die Ausgabe von Zeichenketten, sogenannten Strings geht, ist Forth allerdings ein bisschen arm (es steht ihnen frei das zu ändern). Ein wichtiges Wort ist hier TYPE. Es erwartet die Anfangsadresse einer Zeichenkette und deren Länge auf dem Stack und gibt die Zeichenkette dann auf dem Bildschirm aus.

Der Sinn eines solchen Wortes leuchtet nicht sofort ein. Betrachten sie die folgende Definition:

  : KLEINERTEXT ." Hier ist Text" ;

In der Kompilation von KLEINERTEXT steht zum einen der Laufzeitkode der Ausgabe, die von ." veranlasst wird, aber es steht auch die Zeichenkette "Hier ist Text" zusammen mit ihrer Länge in der Kompilation des Wortes. Das kann man nun ausnützen. Mit einem kleinen Dump erkennt man schnell, dass von der PHA des Eintrags die Zeichenkette noch 8 Bytes entfernt ist. Daher ist nun folgendes möglich:

  ' KLEINERTEXT >PHA 8 + 13 TYPE
  Hier ist Text  ok

Nun, das ist wirklich nicht sonderlich interessant, insbesondere, weil die gleiche Ausgabe ja sofort mit dem Wort KLEINERTEXT selber auf den Bildschirm gebraucht werden könnte. Aber nun stellen sie sich mal vor, sie wollten eine von drei Zeichenketten ausgeben. Der Einfachheit halber wollen wir uns mal vorstellen, sie wollten 'rot', 'blau' oder 'gelb' ausgeben -- was dann? Man kann natürlich drei Wörter definieren und diese dann bei Bedarf aufrufen. Es geht aber noch anders.

Zuerst bestimmt man die Länge der längsten Ausgabe, die gemacht werden soll. Das ist hier einfach, es sind vier Zeichen. Dann bringt man alle möglichen Ausgaben auf diese Länge, indem man sie mit Leerzeichen auffüllt. Das Ganze schreibt man dann in eine Definition, die der obigen entspricht.

  : "FARBEN" ." rot blaugelb" ;

Nun sind alle möglichen Ausgaben in einer Defintion und damit an einer Stelle im Speicher gesammelt. Außerdem weiß man, wo sie liegen, wie man dran kommt und wie man ein Offset berechnet, wenn man nicht an die erste Meldung heran will. Die Definition ist wieder einfach:

  : FARBE ['] "FARBEN" >PHA 8 + SWAP 4 * + 4 TYPE SPACE ;

Man kann nun >code>FARBE mit den Werten 0, 1 oder 2 aufrufen und bekommt die entsprechende Meldung:

  0 FARBE
  rot  ok
  1 FARBE
  blau ok
  2 FARBE
  gelb ok

Bei dieser Lösung gibt es noch zwei kleine Probleme. Zum einen wird nicht abgefangen, ob die Zahl auf dem Stack wirklich im Bereich 0..2 liegt. Das kann man durch ein entsprechendes Einfügen von MIN und MAX leicht ändern. Das nächste Problem ist ein bisschen kniffliger, aber auch lösbar. Wie man oben sieht, wird bei der Ausgabe von 'rot' ein Leerzeichen mehr geschrieben, als das bei den anderen Ausgaben der Fall ist. Das ist auch logisch, denn wir haben 'rot' ja zu 'rot ' aufgefüllt. Oft ist es aber sinnvoll die Anzahl von Leerzeichen kontrollieren zu können. Da hilft dann das Wort -TRAILING. Es bekommt die gleichen Werte auf dem Stack übergeben, wie TYPE entfernt dann aber alle angehängten Leerzeichen der Zeichenkette und gibt die Werte dann in korrigierter Form weiter. Die endgültige Version von FARBE sollte daher so aussehen:

  : FARBE 0 MAX 2 MIN ['] "FARBEN" >PHA 8 + SWAP 4 * + 4 -TRAILING TYPE ;

Und schon sieht die Sache anders aus:

  0 FARBE
  rot OK
  1 FARBE
  blau OK
  2 FARBE
  gelb OK
  3 FARBE
  gelb OK
  -1 FARBE
  rot OK

Eine solche Vorgehensweise ist besonders bei Fehlermeldungen und anderen Meldungen eines Programms sinnvoll. Es gibt aber noch zwei Befehle, die im Zusammenhang mit Zeichenketten interessant sind; die Wörter CMOVE und MOVE. Beide bekommen eine Quell- und eine Zieladresse und eine Anzahl von Bytes, die kopiert, beziehungsweise verschoben werden sollen, auf dem Stack übergeben. Beide verschieben dann den Inhalt des Speichers von der Quelladresse zur Zieladresse und zwar beginnend mit dem Speicherinhalt der Quelladresse. Der Unterschied zwischen diesen beiden Wörtern liegt darin, dass MOVE sich darum kümmert, ob die beiden Bereiche sich überlappen. Ist das der Fall, dann sorgt MOVE autoamtisch dafür, dass dennoch der ursprüngliche Inhalt ab Quelladresse nach Zieladresse verschoben wird. Da die Wörter in den unterschiedlichen Forth-Varianten unterschiedlich heißen, sollte man vor ihrem Gebrauch allerdings noch mal das Handbuch lesen.

Eingabe

Nun zur Eingabe von Zeichen. Das einfachste Wort, das es dazu gibt ist KEY. Dieses Wort wartet, bis am Terminal eine Taste gedrückt wurde und legt deren ASCII-Kode auf den Stack. Ein Wort, das mit diesem in Verbindung steht ist KEY?. Dieses Wort testet, ob eine Taste am Terminal gedrückt wurde. Ist das der Fall, dann holt der nächste Aufruf von KEY den entsprechenden Tastenkode sofort auf den Stack, es wird nicht mehr gewartet. Das Wort KEY? ist kein Standard, ist aber in vielen Fällen äußerst nützlich. Man sollte allerdings vorsichtig damit umgehen und es nur dann verwenden, wenn man zwischen dem Test und einer eventuellen Reaktion auf einen Tastendruck wirklich was zu berechnen hat. In den allermeisten Fällen dürfte KEY absolut ausreichend sein.

Wenn es um mehr als ein Zeichen geht, dann ist EXPECT das Wort der Wahl. In manchen Variationen von Forth heißt dieses Wort auch ACCEPT, aber die Arbeitsweise ist immer gleich. EXPECT erwartet eine Adresse und eine Zahl auf dem Stack. Nach Aufruf dieses Wortes wird jede weitere bearbeitung von Wörtern angehalten und EXPECT nimmt Tastendrücke von der Tastatur entgegen und zwar entweder bis die Anzahl, die durch die übergebene Nummer angegeben wird, erreicht ist, oder ein Return eingegeben wird. Die Zeichenkodes werden ab der Adresse aufsteigend abgelegt. Meistens wird die Zeichenkette, die so entsteht mit einem Nullbyte abgeschlossen, was aber nicht unbedingt sein muss.

QUIT verwendet genau dieses Wort, um die Eingabe von der Tastatur zu lesen, die dann von INTERPRET ausgewertet wird. Wie geschieht nun das? Dazu dient ein weiteres wichtiges Wort: WORD. WORD wird mit einem Zeichenkode aufgerufen. Das entsprechende Zeichen dient dann als Trenner. Word überliest zunächst alle Zeichen der Eingabe, die dem Trennzeichen entsprechen (da sie am Anfang stehen sind sie nicht weiter von Interesse). Danach werden alle Zeichen bis zum nächsten Auftreten des Trennzeichens an eine andere Stelle im Speicher geschrieben. Dabei wird der so separierte Text mit einem führenden Längenbyte abgespeichert, so dass er von vielen anderen Worten, die mit Zeichenketten arbeiten direkt weiter verwendet werden kann. Außerdem wird noch ein Zeiger erhöht, der angibt, an welcher Stelle in der Eingabe WORD angelangt ist. Damit wird sicher gestellt, dass der nächste Aufruf von WORD auch tatsächlich das nächste 'Wort' findet.

Ich denke, dass dieses Konzept hinreichend erklärt ist. Es ist eigentlich auch ziemlich einfach. Es gibt aber noch eine andere Art von Eingabe, die Eingabe von Zahlen. Wann immer ein Wort von INTERPRET nicht erkannt werden kann, wird versucht es als Zahl zu interpretieren. Das macht das Wort NUMBER. Dieses Wort gibt es in 'modernen' Varianten von Forth in dieser Form nicht mehr. Die Umwandlung von Ziffernfolgen in Zahlen funktioniert dort anders, aber es soll dennoch erklärt werden, wie dieses Wort funktioniert.

Das 'alte' Wort NUMBER erwartete auf dem Stack eine Adresse. Dort sollte ein String liegen, der eine Ziffernfolge darstellt, wobei das erste Byte das Längenbyte war. Ausgehend von der um Eins höhreren Adresse wandelte NUMBER diese Ziffernfolge in eine Zahl um und zwar eine einfache Zahl, wenn kein Punkt, Komma oder ähnliches Zeichen auf eine doppelte Zahl hinwies, sonst eben in eine doppelte Zahl.

Moderen Forth-Varianten erwarten die Ziffernfolge immer im Eingabestrom. Wie es sich genau bei ihrem Forth verhält, entnehmen sie bitte ihrem Handbuch.