Im ersten Teil des Kurses verschafften wir uns einen groben Überblick über die Möglichkeiten von FORTH:
Dieser zweite Teil beschäftigt sich vornehmlich mit Ausgabe-Funktionen, Stringbehandlung, Schleifen sowie Datenstrukturen. Vorher folgt aber noch ein Beispiel für die Leistungsfähigkeit von FORTH sowie eine leider notwendige Fehlerkorrektur zur FORTH-Version, die der ATOS beigepackt war.
Übersicht:
Ein Beispiel für die Leistungsfähigkeit von FORTH
Fehlerbereinigung
Tips zum Start
Einblick in das Innere von Forth
Der Returnstack
Ein- und Ausgabe von Zahlen
Änderung der Zahlenbasis
Uuml;bung 1
Lösungen 1
Zahlen in Strings umwandeln
Übung 2
Lösungen 2
Eingabe von Zahlen
Strings in Zahlen wandeln
Schleife do .. loop
FORTH und Datenstrukturen
Strings
Anwendung
Beim Flughafenprojekt in Saudi-Arabien Mitte der achtziger Jahre hat die FORTH Inc., California mit 30 Programmierjahren in 18 Monaten Software entwickelt, bei der ein früheres Team (C) in drei Kalenderjahren mit 100 Programmierjahren gescheitert war.
... es kam, wie es kommen mußte!
Bei der Korrektur von mFORTH (Zeitnot) wurde im appl_init und rsrc_gaddr Aufruf (Vocabular gem) ein Register vertauscht (A3 statt A2). Das führt beim Aufruf leider zu einem Absturz mit 2 Bomben). Ein paar andere Kleinigkeiten konnten bei der fälligen Korrektur ebenfalls beseitigt werden.
Alle Anwender, die mehr als das, was im Kurs angeboten wurde, ausprobiert oder ein Demofile mit AES-Aufrufen geladen haben, bitte ich hiermit um Entschuldigung!
Im öffentlichen Programmteil der Maus LB finden Sie eine korrigierte Version von mForth (ForthXXX.zip).
Fehler im Listing der Turtle-Grafik:
statt: bload vt52.bin >voc vt52
muß es heißen: bload bin\vt52.bin >voc vt52
Wird die Default Forth.ini Datei geladen, so kommt es zum Absturz, wenn kein Cookie installiert ist. Entfernen Sie die folgenden Zeilen:
bios also s" NVDI" get_cookie #if cr .( NVDI Version: ) hex @ w@++ <# # # ascii . hold # #> drop type .( Date: ) @ <# # # # # ascii . hold # # ascii . hold # # #> drop type decimal #else cr .( NVDI not installed) #endif s" Gnva" get_cookie #if cr .( Geneva cookie is set to: ) @ . #endif
Ein Fehler in der Routine: pfa>nfa führte dazu, daß ein make auf einem Rechner mit 68000er nicht funktionierte.
Die Version 1.0 wird hoffentlich alle Systemaufrufe dokumentiert haben - und das sind einige hundert...
Wenn Sie Forth.tos als Applikation anmelden (*.4th), so lädt mForth automatisch das ausgewählte File.
Sie dürfen NIE! GEM-Aufrufe tätigen, wenn Sie Forth als TOS-Programm gestartet haben. Geben Sie in diesem Fall forth.tos den Namen forth.prg.
In Multitaskingsystemen ist es nicht notwendig, daß Sie forth.tos umbenennen, da beim ersten appl_init-Aufruf aus Forth heraus die Aufrufe als eigenständige Tasks vom Betriebssystem behandelt werden. Bei VDI-Aufrufen ist dies nicht nötig.
Wenden wir uns den FORTH-Worten zu, die für Forthsysteme typisch sind. Wir unterteilen das System in einen inneren und äußeren Interpreter. QUIT repräsentiert den äußeren Interpreter. Um eine Vorstellung über die Wirkungsweise zu erhalten, hier die vereinfachte Definition von quit (quit ist in mForth unsichtbar)
: quit ( -- ) r0 @ rp ! /* Returnstackpointer setzen */ state off /* Interpretmodus */ begin /* Quit ist eine Endlosschleife */ .status /* Status ausgeben */ query /* Auf eine Eingabe warten .. */ interpret /* .. und diese interpretieren */ repeat ;
In quit kommt interpret natürlich eine besondere Bedeutung zu. Um diese zu würdigen, widmen wir uns diesem im nächsten Teil des Kurses etwas ausführlicher.
Sie sehen, das Forthsystem ist relativ einfach aufgebaut. Einiges muß "von Hand" erledigt werden; das hat allerdings den Vorteil, daß Ihnen der Compiler bei weitem nicht so viele Vorschriften macht wie C oder BASIC. Vektorisiert man z.B. Interpret, so kann man auch eigene Interpret-Routinen installieren.
WORD | Stack | Beschreibung |
r0 | ( - adr ) | Variable: Startadr des Returnstacks |
rp | ( - adr ) | Variable: Adr des Returnstackpointers |
query | -- | Holt Zeichen vom Eingabegerät |
state | -- adr | Variable, Inhalt: |
0 = Interpretermodus | ||
1 = Compilermodus |
Wie Sie bereits aus Teil 1 wissen, hat Forth zwei Stacks, nämlich den Datenstack und den Returnstack. Auf dem Returnstack liegt die Rücksprungadresse des zuletzt aufgerufenen Wortes. Wenn der Returnstack innerhalb eines Wortes nicht verändert wird, kann der Returnstack als Zwischenspeicher "mißbraucht" werden. Der Returnstack verhält sich hier wie if ... endif; zu jedem >r gehört ein r>.
WORD | Stack | Returnstack | Beschreibung |
>r | n -- | -- n | bringt n auf den Returnstack |
r> | -- n | n -- | holt n vom Returnstack |
r@ | -- n | n -- | Kopie des obersten Zahl vom Returnstack |
Besondere Beachtung verdient der Aufruf, von >r und r> in einer do .. (+)loop Schleife!
Zwei Worte zur Zahlenausgabe sind Ihnen bereits bekannt: . und .s Durch die Erweiterbarkeit von FORTH können wir die Ausgabe in weiten Bereichen selbst beeinflussen.
Weitere vordefinierte Worte zur Zahlenausgabe
WORD | Stack | Beschreibung |
u. | n -- | Zahl ohne Vorzeichen ausgeben |
.r | n i -- | Zahl mit i Stellen rechtsbündig ausgeben |
Testen Sie: -1 u. <cr> und 100 12 .r <cr>
Nach dem Programmstart befindet sich FORTH im Dezimal-Modus (angezeigt durch & im Prompt), d.h. das alle Zahlen als Dezimalzahlen interpretiert werden. Auch die Ausgabe erfolgt dezimal.
In FORTH gibt es eine Variable namens base. Der Inhalt von base stellt die gerade gültige Zahlenbasis dar. In mForth sind bereits definiert:
WORD | Stack | Beschreibung |
decimal | -- | Zahlenbasis Dezimal (10 base !) |
hex | -- | Zahlenbasis Hexadezimal (16 base !) |
bin | -- | Zahlenbasis Binär (2 base !) |
Den Inhalt von base bekommt man auf den Stack mit: base @ . <cr> Es kann natürlich auch eine andere Zahlenbasis gewählt werden.
z.B.: decimal 3 base ! <cr>
Es sind jetzt folgende Zahlen gültig: 0 1 und 2
Experimentieren Sie ein wenig mit einer anderen Zahlenbasis.
Wählen Sie ein beliebiges Zahlensystem und schauen sich jeweils den Inhalt von base mit base @ . <cr> an. Wundern Sie sich warum sie immer 10 erhalten?
Forth bietet Ihnen die Zahlenausgabe in fast beliebigen Formaten. Schauen Sie sich zuerst die Tabelle der dafür nötigen bzw. verfügbaren Worte an:
WORD | Stack | Beschreibung |
<# | -- | Leitet Wandlung Zahl->String ein |
# | +n -- n' | generiert ein Zeichen aus n |
#s | +n -- 0 | wandelt bis n = 0 |
#> | +n -- adr n | wandelt n in String mit Länge n |
sign | flag -- | fügt gegebenenfalls Minus Zeichen ein |
hold | char -- | fügt char in String ein |
Beispiel: Zahl Hexadezimal ausgeben (8 Stellen):
: .X ( n -- ) base @ swap hex <# # # # # # # # # 120 hold 48 hold #> drop type base ! ; /* 120 = x ; 48 = 0 */
Um eine Eingabe vorzunehmen, die natürlich nicht unbedingt ein Zahl sein muß, bedienen wir uns des Wortes expect. Expect erwartet eine Adresse und die Anzahl der zu holenden Zeichen auf dem Stack. Die Eingabe wird abgebrochen, wenn entweder die Anzahl der Zeichen erreicht worden ist oder <cr> gedrückt wurde.
Vorher bedarf es noch der kurzen Erläuterung von allot: Allot reserviert (durch Hochsetzen des Dictionary-Pointers) eine gegebene Anzahl Bytes.
create string 100 allot /* darf jetzt max. 100 Zeichen lang sein */
Die erste Eingabe:
string 20 expect <cr>
Sie können jetzt maximal 20 Zeichen eingeben, bevor expect die Eingabe abbricht.
Ansehen können Sie sich das Ganze mit:
string 1+ type <cr>
Strings in Zahlen wandeln:
WORD | Stack | Beschreibung |
allot | n -- | setzt Dictionarypointer um n Bytes vor |
pad | -- adr | Liefert die Adresse des Stringzwischenspeichers |
expect | adr n -- | Holt n Zeichen von der Tastatur zu adr |
type | adr -- | Gibt eine C-String (0term) auf der Console aus |
Manchmal kommt es vor, daß man eine Zahl aus einem String auslesen muß, z.B. aus GEM-Dialogen. Enthält der String einen Dezimalpunkt, wird die Position in der Variablen dpl gespeichert, andernfalls ist dpl = -1.
Zunächst zwei Beispiele:
0 s" 12.3" convert . . <cr> s" 12.3" 1- number? . . <cr> dpl @ . <cr>
Ein bißchen seltsam erscheint das schon, oder?
Probieren Sie:
s" 12.3" number? . . <cr>
Je nach Problem muß zwischen number? und convert gewählt werden.
WORD | Stack | Beschreibung |
convert | 0 adr -- n adr' | Wandelt String ab adr in eine Zahl |
number? | ^str -- n flag | Wandelt counted String in eine Zahl |
dpl | -- adr | Variable, enthält Dezimalpunktposition |
Kommen wir jetzt zu der einfachsten Schleife in Forth, der do .. loop Konstruktion. Sie entspricht etwa dem BASIC-Äquivalent for i=0 to 100 (step x). Zunächst wieder ein Beispiel, das die Anwendung von do .. loop veranschaulicht.
Aufgabe: Ein Wort soll ab einer bestimmten Zahl die nächsten 10 Quadratzahlen ausgeben.
: 10qz ( n -- ) 10 bounds /* ToS: n+10 n */ do i dup * . space loop ;
Neue Worte, aber es ist wieder mal halb so schlimm:
Und jetzt die Quadratzahlen von 0 - 99:
: 100qz ( -- ) 91 0 do cr /* neue Zeile, die 90 wird auch gebraucht */ i 10qz 10 +loop ;
Stellt sich noch das Problem, wie ein do .. loop Schleife vorzeitig verlassen wird, nämlich mit leave. Leave veranlaßt das Programm, hinter dem nächsten loop fortzufahren. Damit das Wort auch seinen Zweck erfüllt, muß es "verpackt" werden.
Beispiel:
: raus_bei_10 ( -- ) 100 0 do i 10 = if leave endif loop ;
Die Bedingung für den Ausstieg kann natürlich beliebig sein.
Wichtig!: Eine do .. loop Schleife NIE mit exit verlasssen!
WORD | Stack | Bedeutung |
do | n2 n1 -- | Schleife von n1 bis n2 |
loop | -- | erhöht Schleifenindex um 1 |
+loop | inc -- | erhöht Schleifenindex um inc |
i | -- index | Kopie des Schleifenindex auf den Stack |
j | -- index' | Kopie des Index der äußeren Schleife |
leave | -- | Verlassen einer Schleife |
Wenn Sie sich das Forth-Wörterbuch näher angeschaut haben, wunderten Sie sich vielleicht über das Fehlen von Strukturen, wie sie Sie aus anderen Sprachen kennen. Forth verfügt jedoch über Möglichkeiten, beliebige Strukturen zu entwerfen. Als Beispiel wähle ich ein eindimensionales Array. Tasten wir uns langsam heran (array ist in mForth bereits definiert).
Aufgabe: Es soll ein Array erzeugt werden, das für 20 Zahlen (LONG = 4bytes) Platz hat.
create myArray 20 4 * allot
So, Platz haben wir, jetzt wird das Array mit Leben, sprich Zahlen, gefüllt.
9 myArray ! /* 1. Element erfordert keine Berechnung */ 8 myArray 4+ ! /* 2. Element */ 7 myArray 8+ ! /* 3. Element */
Das ganze lesend:
myArray @ . /* 1. Element */ myArray 4+ @ . /* 2. ...... */
Der Zugriff läßt sich vereinfachen. Dazu wird die Berechnung in einem Wort zusammengefaßt:
: offset ( addr n -- addr+n*4) 4* + ; 333 myArray 3 offset ! /* 3. Element mit 333 belebt */
So richtig elegant ist das aber immer noch nicht! Hier hilft uns das Wort does> zur Lösung des Problems:
: array ( n -- <name> ) create 4 * allot /* reserviere Speicher */ does> ( n <array> -- addr' ) swap 4* + :
Für unser Beispiel heißt dies:
20 array myArray 9 0 myArray ! 8 1 myArray ! /* usw. */ 0 myArray @ . /* lesen */
Abschließend ein 2-dimensionales Array:
: 2array ( x y -- <name> ) create dup , /* Laenge y wird gebraucht */ * 4* allot does> ( x y <2array> -- addr ) dup >r @ * 4* swap 4* + r> 4+ + ; 10 10 2array xyWerte 123 0 0 xyWerte ! 126 1 0 xyWerte ! /* usw. */
WORD | Stack | Bedeutung |
create | -- <name> | erzeugt einen Eintrag ins Wörterbuch |
allot | n -- | setzt Dictionarypointer um n Bytes vor |
Aufgrund der Tatsache, daß das Betriebssystem des ST in C geschrieben wurde, mußten in mForth einige Kompromisse eingegangen werden. Im Standardforthstring enthält das erste Byte die Anzahl der Zeichen im String (Countbyte). Das hat den Nachteil, das nur Strings mit maximal 255 Zeichen kompiliert werden können. Für "normale" Strings verwendet mForth die gleiche Methode, die für eine Vielzahl von Anwendungen auch ausreicht. In mForth wird ein String ähnlich wie in C behandelt, das heißt, er schließt mit einem NULL-Byte.
Worte zum Stringhandling:
WORD | Stack | Beschreibung |
s" | -- adr | Holt String aus dem Inputstream. Ende " |
strlen | adr -- adr n | Liefert die Länge das Strings an adr |
strcpy | adr1 adr2 -- | Kopiert String von adr1 nach adr2 |
strcat | adr1 adr2 -- | Fügt String adr1 an adr2 an |
strncpy | adr1 adr2 n -- | Kopiert n Bytes von adr1 nach adr2 |
strcmp | adr1 adr2 -- flag | True, wenn beide Strings gleich sind |
type | adr -- | gibt C-String auf der Console aus |
count | adr1 -- adr1+1 n | Liefert Countbyte eines Forth(!)strings |
Die obligatorischen Beispiele:
create str1 32 allot create str2 32 allot create str3 128 allot s" ATOS Around " str1 strcpy s" the Operating System" str2 strcpy str1 str3 strcpy /* kopiere string 1 nach 3 */ str2 str3 strcat /* füge string 2 an */ str3 type str1 strlen str2 swap strncpy str1 str2 strcmp .
Ausgewählte Worte:
WORD | Stack | Beschreibung |
integer | -- <name> | Erzeugt eine 'veränderliche' Konstante |
to | n -- <name> | Weist name den Wert n zu |
calloc | n -- adr true | false | Fordert Speicher an. |
f_open | name mode -- hdl | Öffnet ein File. (handle 0> = OK) |
f_read | hdl count adr -- read | Liest count Bytes aus File nach adr |
f_close | hdl -- return | Schließt File. |
Sie können den Sourcecode über die Kopierfunktion des ST-Guide in das Clipboard kopieren. Dieser Text (scrap.txt) kann in einen ASCII-Editor geladen und als bdays.4th o.ä. gesichert werden.
/* * BDAYS.PRG * Eine kleine Anwendung in mForth * * Das Programm durchsucht ein File, in dem Geburtstage aufgelistet * sind und gibt den Namen aus, wenn Datum im Rechner und in der * Datei übereinstimmen. * Das Programm kann in den Autoordner kopiert oder als TOS-Programm * gestartet werden. */ warning on /* doppelte Definitionen aufzeigen */ mforth /* Suchreihenfolge */ system macro on /* dup, nip etc. als macros */ true constant MAKE immediate /* Programm soll erstellt werden */ include system\date.4th /* Funktionen rund ums Datum */ mforth gemdos also /* s.o. */ create fname ," c:\.\auto\days.dat" /* Name des Datenfiles */ /* --------------------------------------------------------- Format: 17.07 Karl Napp 29.10 Michaela Mai Das File muß mit einer Leerzeile abschließen! Alle Daten im 5-stelligen Format angeben. ------------------------------------------------------------ */ integer fhandle /* Filehandle */ integer filesize /* Größe des DAT-Files */ integer *data /* Startadresse der Daten */ integer *str /* Startadresse der Einträge */ integer year /* Aktuelles Jahr */ integer month /* Aktueller Monat */ integer day /* Aktueller Tag */ variable found /* Bool, True wenn irgendetwas gefunden wurde */ /* Den akt. Monat im Klartext ausgeben */ string-array monate ," Januar" ," Februar" ," März" ," April" ," Mai" ," Juni" ," Juli" ," August" ," September" ," Oktober" ," November" ," Dezember" end-string-array create tage 31 c, 28 c, 31 c, 30 c, 31 c, 30 c, 31 c, 31 c, 30 c, 31 c, 30 c, 31 c, /* String konvertieren, der String s" 12.09" double> läßt auf dem Stack 12 9 zurück */ : double> ( addr -- tag monat ) 1- number? drop 100 /mod ; /* Überspringe das nächste Linefeed in einem String */ code skiplf ( addr -- addr' ) .l a6 )+ a0 move begin .b 10 # a0 )+ cmpi 0<> while repeat .l a0 a6 -) move next end-code : nextday ( monat tag -- monat' tag' ) 2swap over tage + 1- c@ /* max. Anzahl der Tage im Monat */ 2pick 2 = if year 4 mod 0= if 1+ endif endif /* Schaltjahr */ >r 1+ dup r> > if drop 1+ dup 12 > if drop 1 endif 1 /* 1. des Monats */ endif 2swap ; : Today? ( month day m d -- month' day' m d flag ) 2pick over = >r 3pick 2pick = r> and ; /* Die eigentliche Suchroutine */ : ?birthday ( -- ) today to year to month to day ." Heute ist der " day . 8 emit ." . " month 1- monate 1+ type space year . cr cr found off /* Noch nichts gefunden */ *data to *str /* CR durch 0bytes ersetzen */ *data filesize bounds do i c@ 13 = if 0 i c! endif loop begin month day /* Heute */ *str double> Today? /* ?? */ if ." Heute: " *str 6+ type cr found on endif nextday Today? if ." Morgen: " *str 6+ type cr found on endif 6 0 do nextday loop Today? if ." In einer Woche: " *str 6+ type cr found on endif 4drop *str skiplf to *str *str 4+ *data filesize + >= until found ?if key drop endif ; : main ( -- ) fname 1+ 2 f_open dup to fhandle 0> if 32000 calloc /* So groß wird days.dat wohl nicht werden */ if to *data fhandle 32000 *data f_read to filesize ?birthday *data m_free drop endif fhandle f_close drop else cr ." Days.dat nicht gefunden" key drop endif MAKE #if 0 return #endif ; mforth MAKE #if system make \.\forth\bdays.prg bye #endif
Der nächste Teil des FORTH-Kurses wird sich mit folgenden Themen beschäftigen:
Für Anregungen und Fragen bin ich Ihnen dankbar.
Anrufen (06431-71188 ab 20:00 Uhr) oder schreiben.
EMail: Maus-Adresse von Rainer Saric