ATOS - Around The Operating System Das ATOS-Magazin 6/96

ATOS Programmierpraxis ATOS Programmierpraxis

Forth-Kurs, Teil 2

 Bild ue_prog2




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




Ein Beispiel für die Leistungsfähigkeit von FORTH

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.




Fehlerbereinigung

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




Tips zum Start

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.




Einblick in das Innere von Forth

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.

WORDStackBeschreibung
r0( - adr )Variable: Startadr des Returnstacks
rp( - adr )Variable: Adr des Returnstackpointers
query--Holt Zeichen vom Eingabegerät
state-- adrVariable, Inhalt:
0 = Interpretermodus
1 = Compilermodus




Der Returnstack

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

WORDStackReturnstackBeschreibung
>rn ---- nbringt n auf den Returnstack
r>-- nn --holt n vom Returnstack
r@-- nn --Kopie des obersten Zahl vom Returnstack

Besondere Beachtung verdient der Aufruf, von >r und r> in einer do .. (+)loop Schleife!




Ein- und Ausgabe von Zahlen

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

WORDStackBeschreibung
u.n --Zahl ohne Vorzeichen ausgeben
.rn i --Zahl mit i Stellen rechtsbündig ausgeben

Testen Sie: -1 u. <cr> und 100 12 .r <cr>




Änderung der Zahlenbasis

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:

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




Übung 1

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?

  1. Schreiben Sie ein Wort, das Ihnen die aktuelle Zahlenbasis dezimal anzeigt, ohne die gerade aktuelle zu verändern.

  2. Schreiben Sie ein Wort, das Ihnen die oberste Zahl hexadezimal ausgibt. Die aktuelle Zahlenbasis soll nicht verändert werden.




Lösungen 1

  1. : .base ( - ) base @ dup decimal . base ! ;

  2. : .hex ( n - ) base @ swap hex . base ! ;




Zahlen in Strings umwandeln

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:

WORDStackBeschreibung
<#--Leitet Wandlung Zahl->String ein
#+n -- n'generiert ein Zeichen aus n
#s+n -- 0wandelt bis n = 0
#>+n -- adr nwandelt n in String mit Länge n
signflag --fügt gegebenenfalls Minus Zeichen ein
holdchar --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 */




Übung 2

  1. Schreiben Sie ein Wort, daß eine Zahl mit 3 Kommastellen ausgibt.

  2. Schreiben Sie ein Wort, daß nur positive Zahlen ausgibt.




Lösungen 2

  1. : .3places ( n - ) dup >r abs <# # # # 46 hold #s r> sign #> drop type ;

  2. : .p ( n - ) dup 0>= if . else drop ." Error!" endif ;




Eingabe von Zahlen

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:

WORDStackBeschreibung
allotn --setzt Dictionarypointer um n Bytes vor
pad-- adrLiefert die Adresse des Stringzwischenspeichers
expectadr n --Holt n Zeichen von der Tastatur zu adr
typeadr --Gibt eine C-String (0term) auf der Console aus




Strings in Zahlen wandeln

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.

WORDStackBeschreibung
convert0 adr -- n adr'Wandelt String ab adr in eine Zahl
number?^str -- n flagWandelt counted String in eine Zahl
dpl-- adrVariable, enthält Dezimalpunktposition




Schleife do .. loop

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:

bounds
- richtet den Stack für do .. loop ein. (: bounds ( n n' -- n+n' n ) over + swap ;)

do
- erwartet Schleifenindex auf dem Stack

loop
incrementiert Schleifenindex um 1

i
- holt den Index der Schleife auf den Stack

space
- gibt ein Leerzeichen aus (: space ( -- ) 32 emit ;)

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!

WORDStackBedeutung
don2 n1 --Schleife von n1 bis n2
loop--erhöht Schleifenindex um 1
+loopinc --erhöht Schleifenindex um inc
i-- indexKopie des Schleifenindex auf den Stack
j-- index'Kopie des Index der äußeren Schleife
leave--Verlassen einer Schleife




FORTH und Datenstrukturen

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

WORDStackBedeutung
create-- <name>erzeugt einen Eintrag ins Wörterbuch
allotn --setzt Dictionarypointer um n Bytes vor




Strings

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:

WORDStackBeschreibung
s"-- adrHolt String aus dem Inputstream. Ende "
strlenadr -- adr nLiefert die Länge das Strings an adr
strcpyadr1 adr2 --Kopiert String von adr1 nach adr2
strcatadr1 adr2 --Fügt String adr1 an adr2 an
strncpyadr1 adr2 n --Kopiert n Bytes von adr1 nach adr2
strcmpadr1 adr2 -- flagTrue, wenn beide Strings gleich sind
typeadr --gibt C-String auf der Console aus
countadr1 -- adr1+1 nLiefert 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 .




Anwendung

Ausgewählte Worte:

WORDStackBeschreibung
integer-- <name>Erzeugt eine 'veränderliche' Konstante
ton -- <name>Weist name den Wert n zu
callocn -- adr true | falseFordert Speicher an.
f_openname mode -- hdlÖffnet ein File. (handle 0> = OK)
f_readhdl count adr -- readLiest count Bytes aus File nach adr
f_closehdl -- returnSchließ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

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

RS


ATOS Programmierpraxis ATOS Programmierpraxis