Heute möchte ich mit einem kleinen, mehrteiligen Kurs beginnen, der die Grundlagen von FORTH sowie die Systemprogrammierung unter FORTH (GEMDOS, BIOS, GEM, VDI) beleuchtet.
Dieser Ausgabe der ATOS liegt ein komplettes FORTH-System bei, mit dem Sie die Beispiele im Kursus sogleich ausprobieren können. Sie können den Ordner an einen Ihnen genehme Stelle kopieren. Dort sollte sich dann folgende Ordnerstruktur ergeben:
\FORTH forth.ini forth.tos readme \FORTH\BIN bios.bin dsp.bin gemdos.bin mint.bin vt52.bin xbios.bin \FORTH\SOURCE date.4th files.4th heap.4th hello.4th iconify.4th sieve.4th \FORTH\SYSTEM command.4th copy.4th extend.4th util.4th \FORTH\GUIDES forth.hyp
mForth Version 0.95 (c) Rainer Saric (1995,96) (c) Joerg Plewe (1988) Loading: FORTH.INI Loading: system\command.4th Command set loaded; Vocabulary: commands available commands: dir del rd md cd ls pwd p: l: k: j: i: h: g: f: e: d: c: b: a: rename DTA nil 23 words in dictionary. Loading: system\util.4th Loading: system\extend.4th Current Directory: \FORTH\SOURCE Free ST memory: 1550028 bytes NVDI Version: 4.11 Date: 16.03.1996 Geneva cookie is set to: 872218 Transient: command Current: forth Context: command forth gem vdi only & 0 ok>
Betrachten wir zunächst die letzte Zeile.
Das Zeichen & zeigt uns die augenblickliche Zahlenbasis (Decimal) an. 0 gibt die Anzahl der auf dem Stack befindlichen Werte bekannt und ok> bedeutet, daß das System von Ihnen eine Eingabe erwartet.
Hinweis: FORTH-Befehle (Worte) müssen durch mindestens ein Leerzeichen getrennt werden.Tippen Sie words und anschließend drücken Sie Return (ab jetzt: <cr>). Das Fenster (oder der ganze Bildschirm) wird mit vielen Wörtern gefüllt. Aber keine Angst, zu Anfang sind nicht alle Worte nötig, um einfache Programme zu schreiben.
Hinweis: Sie verlassen FORTH, indem Sie bye eingeben und <cr> drücken.Bei FORTH handelt es sich um eine stackorientierte Sprache, die sowohl Compiler als auch einen Interpreter zur Verfügung stellt. Ihre Eingabe von vorhin ("words") wurde interpretiert.
FORTH gehört ebenso zu den 'typenlosen' Sprachen, d.h. der Stackwert kann z.B. als Adresse, als Zahl oder ähnliches interpretiert werden. Letztendlich entscheidet der Programmierer über die Weiterverarbeitung des oder der Werte.
FORTH besitzt einen Datenstack und einen Returnstack, die beide nach dem LIFO-Prinzip arbeiten (LIFO = Last In First Out). Zu Verdeutlichung des Prinzips führen wir nun ein paar einfache Rechnungen durch.
Auf dem Stack sieht es jetzt so aus:
Stack |
1 |
Geben Sie nun noch z.B. eine 4 ein
Stack |
4 |
1 |
und jetzt + <cr>
Stack |
5 |
Ist es Ihnen aufgefallen? FORTH rechnet nach der UPN - oder Postfix-Notation (UPN = Umgekehrte polnische Notation; sie ist durch HP-Taschenrechner sehr bekannt geworden). Im Gegensatz zur Infix-Notation gibt man bei UPN erst die beiden Operanden (Zahlen) und dann erst die gewünschte Operation ein.
1 + 4 . liefert eine Fehlermeldung, da der Operator + zwei Zahlen auf dem Stack erwartet.
Schön, wir wissen was 1 + 4 ist und wir wissen auch, daß es oben auf dem Stack liegt (ToS; Top of Stack), trotzdem wäre es schön, das Ergebnis 'schriftlich' zu haben. Dafür gibt es "dot", genauer . Geben Sie also einen . ein und drücken anschließend <cr>. Voila! Addition ist keine Kunst; doch FORTH kann noch etwas mehr.
Unsere nächste Aufgabe ist schon etwas schwieriger: Die Berechnung von Quadratzahlen!
2 2 * . <cr> Einfach! Oder? Das Problem läßt sich jedoch universeller lösen. Zunächst die Lösung des 'Problems':
: qz dup * . ; <cr>
Aber der Reihe nach.
Das neue Wort im Einsatz:
3 qz <cr>
qz ist jetzt Bestandteil des Forthvokabulars. Geben Sie nochmals words ein. Wenn Sie während der Ausgabe eine Taste drücken, wird die Ausgabe angehalten; bei nochmaligem Drücken wir die Ausgabe fortgesetzt - Escape bricht die Ausgabe ab.
Wort-Typen
Einige mathematische Befehle aus dem FORTH Grundwortschatz:
WORD | Stack | Beschreibung |
+ | n1 n2 - n1+n2 | Addition |
- | n1 n2 - n1-n2 | Subtraktion |
* | n1 n2 - n1*n2 | Multiplikation |
/ | n1 n2 - n1/n2 | Division |
mod | n1 n2 - rest | Rest n1/n2 |
/mod | n1 n2 - rest quo | Division mit Rest |
*/ | n1 n2 n3 - n1*n2/n3 | Mixed Mul/Div |
negate | n1 - -n1 | Vorzeichen ändern |
abs | n1 - abs(n1) | Absoluter Wert |
1+ | n1 - n1+1 | Add 1 zum Top of Stack |
1- | n1 - n1-1 | Sub 1 zum Top of Stack |
2+ | n1 - n1+2 | Add 2 zum Top of Stack |
2- | n1 - n1-2 | Sub 2 zum Top of Stack |
2* | n1 - n1*2 | Mul mit 2 (schnell) |
2/ | n1 - n1/2 | Div mit 2 (schnell) |
: <name> ... Befehlsfolge ... ; |
Dabei ist <name> des neuen Wortes. Der Doppelpunkt leitet die Kompilation des Wortes ein. Das Semicolon beendet sie wieder, schaltet FORTH vom Compilier- in den Interpretermodus zurück.
variable <name> | - | Definition Variable |
"variable" ist ein Definitionswort. Es erzeugt eine Variable, indem <name> ins Wörterbuch eingetragen und Platz für den Wert geschaffen wird (4 Bytes). Die erzeugte Variable ist mit 0 initialisiert.
variable x <cr>
Der Zugriff auf die Variable kann, wie nicht anders zu erwarten war, schreibend oder lesend erfolgen.
Das Lesen der Variable erledigt das Wort @ (fetch): x @ . <cr> 0 Das Schreiben gelingt mit dem Wort ! (store): 123 x ! <cr>
In einer colon-Definition:
: get-x ( -- x ) x @ ;
Speicherbefehle:
WORD | Stack | Beschreibung |
@ | adr - n | Liest 32bit-Wert aus adr aus |
! | n adr - | Schreibt 32bit-Wert in adr |
w@ | adr - w | Liest 16bit-Wert aus adr aus |
w! | w adr - | Schreibt 16bit-Wert in adr |
( | - | Kommentar bis zur nächsten schließenden Klammer ")" |
constant <name> | n - | Definition Konstante |
"constant" bewirkt einen Wörterbucheintrag von <name>, reserviert Platz und weist der Konstante den auf dem Stack befindlichen Wert zu.
100 constant hundert <cr>
Bei einem Aufruf von hundert wird jetzt sofort die Zahl 100 auf dem Stack abgelegt.
Mit einem kleinen Trick läßt sich der Wert von Kostanten jederzeit ändern - sinnvoll ist dies allerdings nicht.
Die wichtigsten Worte zur Stackmanipulation sind swap, dup, over und drop.
FORTH stellt ein Servicewort zur Verfügung mit dem Sie sich den Inhalt des Stacks komplett ansehen können: .s (dot-s). Als Test geben Sie ein paar Zahlen ein und anschließend .s
Beispiel:
Stack vor Aufruf von swap
Stack |
1 |
3 |
Stack nach Aufruf von swap
Stack |
3 |
1 |
WORD | Stack | Beschreibung |
.s | - | Inhalt des Stack ausgeben |
sp0 | - addr | Startadresse des Stack |
sp@ | - addr | Stackpointer-Inhalt vor sp@-Aufruf |
sp! | - | Stackpointer initialisieren |
depth | - n | Stacktiefe vor Aufruf von depth |
over | n1 n2 - n1 n2 n1 | Kopiert 2. Stackwert auf TOS |
rot | n1 n2 n3 - n2 n3 n1 | Macht 3. Eintrag zum TOS |
drop | n - | Löscht TOS |
dup | n - n n | Kopiert TOS |
?dup | n - n n oder n - n | Dupliziert wenn n<>0 |
pick | ... n i - n n.i | Kopiert i.ten Eintrag auf TOS |
Haben sich viele Zahlen auf dem Stack angesammelt, können Sie diese löschen, indem Sie ein unbekanntes Wort eingeben - oder alternativ dot'ten, bis der Stack leer ist.
A B | A and B | A or B | A xor B |
0 0 | 0 | 0 | 0 |
0 1 | 0 | 1 | 1 |
1 0 | 0 | 1 | 1 |
1 1 | 1 | 1 | 0 |
Die FORTH-Befehle and, or, xor und not wirken diesen Regeln entsprechend (bei mForth 32bit breit)
Beispiele:
Vergleichsoperatoren mit 0
WORD | Stack | Beschreibung |
0= | n - flag | -1, wenn n=0 |
0> | n - flag | -1, wenn n>0 |
0< | n - flag | -1, wenn n<0 |
0<> | n - flag | -1, wenn n<>0 |
0>= | n - flag | -1, wenn n>=0 |
0<= | n - flag | -1, wenn n<=0 |
WORD | Stack | Beschreibung |
= | n1 n2 - flag | -1, wenn n1=n2 |
> | n1 n2 - flag | -1, wenn n1>n2 |
< | n1 n2 - flag | -1, wenn n1<n2 |
<> | n1 n2 - flag | -1, wenn n1<>n2 |
>= | n1 n2 - flag | -1, wenn n1>=n2 |
<= | n1 n2 - flag | -1, wenn n1<=n2 |
Beispiele:
1 0= . <cr> 0 |
0 0= . <cr> -1 |
1 3 < . <cr> -1 |
3 1 > . <cr> -1 |
Die bekannteste Entscheidungsstruktur ist sicherlich die if ... else ... endif Struktur.
if <true-Teil> else <false-Teil> endif |
if <true-Teil> endif |
Die if .. endif-Struktur kann natürlich geschachtelt werden. Bei diesen Verschachtelungen muß jedoch auf folgendes geachtet werden:
if if endif else if else endif endif
Ein Wort soll testen, ob die auf dem Stack befindliche Zahl gerade oder ungerade ist:
: | ?gerade ( n - ) |
1 and /* <>0, wenn Zahl ungerade */ | |
if ." Zahl war ungerade!" | |
else ." Zahl war gerade!" | |
endif ; |
WORD | Stack | Beschreibung |
/* | - | Ignoriere Text bis */ |
." | - | Kompiliert String ins Wörterbuch |
Schauen wir uns vorab die zur Verfügung stehenden Worte des VDI an. Geben Sie Sequenz mforth vdi words ein (es sind noch nicht alle VDI-Aufrufe implementiert).
Hier die Erläuterung der einzelnen Befehle:
Zu Beginn müssen wir eine virtuelle Workstation öffnen:
1 1 1 1 1 1 1 1 1 1 2 opnvwk <cr>
Die genaue Bedeutung der Parameter ist im Profibuch nachzulesen (ich möchte an dieser Stelle nicht näher darauf eingehen). opnvwk füllt die Werte in das interne INTIN-array. Die Rückgabewerte befinden sich im INTOUT-array (intin und intout sind im GEM-Vocabular definiert).
Jetzt müssen wir das vdi-handle holen und in der Variablen v_handle sichern:
contrl 12 + w@ v_handle w! <cr>
v_handle sollte > 0 sein. v_handle w@ . <cr>
Wenn Sie FORTH jetzt verlassen, müssen Sie vorher den Befehl clsvwk ausführen! Warum? Sie haben eine virtuelle Workstation geöffnet (opnvwk = open virtual Workstation) und sollten sie jetzt vor dem Verlassen wieder schließen (clsvwk = Close Virtual Workstation). Damit räumt man quasi den Arbeitsplatz, den man vorher eingerichtet hat, wieder auf.
Ein paar Worte zum Ausprobieren:
WORD | Stack | Beschreibung |
line | x y x' y' - | Linie zeichnen von x,y -> x',y' |
bar | x y x' y' - | Gefüllte Box zeichnen von x,y -> x',y' |
clrwk | - | Workstation löschen |
circle | x y radius | Kreis zeichnen |
sl_width | width - | Liniendicke ändern |
sl_color | color - | Linienfarbe ändern |
Um Ihnen Tipparbeit zu ersparen, empfehle ich Ihnen, den Text hier im Hypertext zu markieren und in das Clipboard zu kopieren. Scrap.txt kann dann mit jedem ASCII-Editor ediert werden. Speichern Sie den Text unter \.\forth\source\turtle.4th ab.
Sie können das File in FORTH einladen mit der Befehlssequenz:
include \.\forth\source\turtle.4th
So jetzt aber los:
/* ------------- Turtle-Grafik ------------- Original-Code von R. Zech, angepaßt an mForth */ mforth vocabulary graphic /* Eine eigenes Vokabular für die Befehle */ graphic also definitions /* .. alle Definition ins graphic Voc */ /* x,ycenter sind systemspezifisch, Initialisierung mit ST-Hoch */ integer xcenter 320 to xcenter integer ycenter 200 to ycenter 2variable center 2variable pix_cur /* x,y-Position der Kröte */ variable angle /* Richtung, in der unsere Kröte kriecht */ create crt_bounds 8 allot /* Ausmaße des Bildschirms/Fensters */ /* Wir definieren ein paar Farben für die Linien */ variable colors /* Anzahl der Farben */ : color: ( -- <name> ) create w, does> w@ sl_color ; 0 color: white 1 color: black 2 color: red 3 color: green 4 color: blue 5 color: cyan 6 color: yellow 7 color: magenta 8 color: gray 9 color: darkgray hide color: white /* color: aus dem Wörterbuch "entfernen" */ /* Verschieben des Koordinatensystems */ : shift ( deltay deltay -- ) negate center 4+ +! center +! center 2@ pix_cur 2! ; /* Turtle bewegen ohne irgendwelche Spuren, sprich Linien, zu hinterlassen */ : skip-xy ( deltax deltay -- ) negate pix_cur 4+ +! pix_cur +! ; /* Absolut setzen */ : goto-xy ( x y -- ) center 2@ pix_cur 2! skip-xy ; : home ( -- ) /* Zurück zum Ursprung */ 0 0 goto-xy angle off ; : new ( -- ) xcenter ycenter center 2! home black ; : turn ( winkel -- ) angle +! ; : turnto ( winkel -- ) angle ! ; /* Der Sinus wird aus einer Tabelle ausgelesen, Genauigkeit 5-Stellen skaliert mit 10000. Dies ist die weitaus schnellste Methode. Die Genauigkeit reicht für die meisten Anwendungen aus. So habe ich, mit der um 3D-Funktionen erweiterten Turtlegrafik, Drahtmodelle chem. Verbindungen in hoher Geschwindigkeit drehen können */ : skip ( radius -- ) dup angle @ cos * 10000 / swap angle @ sin * 10000 / swap skip-xy ; : goto ( radius winkel -- ) angle ! center 2@ pix_cur 2! skip ; : dot ( -- ) /* Punkt an Turtleposition zeichnen */ pix_cur 2@ 2dup line ; : draw ( radius -- ) /* Eine Linie mit dem Radius radius zeichnen */ >r pix_cur 2@ r> skip pix_cur 2@ line ; /* Initialisieren, init muß(!) vor dem ersten draw-Befehl aufgerufen werden */ : init ( -- ) 1 1 1 1 1 1 1 1 1 1 2 opnvwk /* Virtuelle Workstation öffnen */ contrl 12 + w@ v_handle w! /* Handle holen und sichern */ intout 26 + w@ colors ! /* Anzahl der Farben */ 0 0 intout 2w@ crt_bounds 4w! /* Ausmaße des Bildschirms */ intout 2w@ 2/ to ycenter /* Center neu */ 2/ to xcenter ; /* Aufrufen, bevor FORTH verlassen wird */ : xinit ( -- ) clsvwk ; mforth bload vt52.bin >voc vt52 vt52 also graphic also /* Eine Box mit der Turtle zeichnen */ : BOX ( laenge breite -- ) over [ graphic ] draw 90 turn dup draw 90 turn swap draw 90 turn draw -270 turn ; /* Turtle wieder zurück */ : wonder ( -- ) new 45 turnto 200 0 do i draw 90 turn i 7 mod sl_color loop black ; : stern ( -- ) 361 0 do 100 draw 0 0 goto-xy i turnto loop ; : demo ( -- ) init new clrwk [ vt52 ] home ." Eine Box" 100 100 BOX key drop clrwk [ vt52 ] home ." Ein Stern" new stern key drop clrwk [ vt52 ] home ." I wonder" new wonder key drop cr ." Vor Verlassen des FORTH-Systems bitte xinit aufrufen!" ;
Ich wünsche Ihnen viel Spaß beim Experimentieren.
Feldname | Länge in Byte |
Link Field | 4 |
Word Parameter Field | 4 |
Code Field Adress | 4 |
Name Field | >= 4 |
Code Len Field | 2 |
Parameter Field | >= 2 |
Anrufen (06431-71188 ab 20:00 Uhr) oder schreiben.
EMail: Maus-Adresse von Rainer Saric