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.
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.
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.
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.