ATOS-Magazin April/Mai 1997 Das ATOS-Magazin 2/97

# Forth-Kurs, Teil 4

Von Rainer Saric

Inhalt:
Literatur zu Forth
Die Ausführung von Worten
Vektorisierung von Worten
Erstellung von Standalone-Programmen
Eine Anwendung
Ende des Forth-Kurses

### Literatur zu Forth

Zunächst möchte ich Sie zum letzten und kürzesten Teil unseres kleinen Kurses begrüßen! Bedanken möchte ich mich bei Ihnen, daß Sie den Kurs bis hier verfolgt und kleinere Fehler toleriert haben.

Es konnte natürlich nicht jedes Thema so ausführlich behandelt werden, wie dies vielleicht nötig gewesen wäre. Auch sind viele Programmiertechniken in der Kürze nicht zu diskutieren (z.B.: Object Oriented Forth).

Wenn Sie sich weiter über Forth informieren möchten oder tiefer in die Sprache einsteigen möchten, gebe ich Ihnen hier noch einige Literaturhinweise:

- Leo Brodie: Programmieren in Forth; Hanser-Verlag
- Leo Brodie: In Forth denken; Hanser-Verlag
- Ronald Zech: Forth 83, Franzis-Verlag
- Manfred Mader: Einführung in Forth 83; Heim-Verlag
- Aumiller/Luda: Programmieren in Forth; Markt & Technik (nur Atari)

### Die Ausführung von Worten

Vergegenwärtigen wir uns nochmals den Aufbau eines Forthwortes:

Jedes Forth-Wort beginnt mit dem Zeiger auf die Link Field Adresse (4 Bytes), gefolgt von einem Word Parameter Field oder Kontrollflag (4 Bytes). Das Kontrollflag steuert das Verhalten des Wortes während der Compilezeit (die Flags können selbstverständlich kombiniert werden).

Hier eine Liste von Wörtern, die nur in einer Colon-Definition aufgerufen werden dürfen:

r> >r do loop if else endif begin until while repeat i j

Im Interpretermodus meldet Forth: compile only

Eine wichtige Eigenschaft, die Sie einem Wort geben können, ist das Setzen des immediate-Flags. Worte, bei denen dieses Bit gesetzt ist, werden während der Compilezeit sofort ausgeführt.

Zur Verdeutlichung ein unnützliches Wort:

    : hallo    cr ." hallo" ; immediate
    : test hallo ; /* hallo wird nach <cr> nicht compiliert! */
    test

Berechnungen können so im Compile-Mode durchgeführt werden, die sonst während der Laufzeit Rechenzeit verbrauchen.

    100 constant xWerte
    100 constant yWerte

    : ....  [ xWerte yWerte * ] literal m_alloc ...
    ;

Mit restrict wird ein Wort markiert, das nur in einer Colon-Definition angewendet werden kann.

    : hallo cr ." hallo" ; restrict
    hallo
    : test hallo ;
    test

WORD | Stack | Beschreibung
immediate | -- | markiert letztes Wort als 'immediate'
restrict | -- | markiert letztes Wort als 'restrict'

### Vektorisierung von Worten

Ihnen ist sicherlich schon aufgefallen, daß Forth während des Compilevorgangs jedes Wort bekannt sein muß.

Beispiel:

    : test ( -- )  x @ ( irgendwas ) ;
    variable x

Die Compilierung wird mit der Fehlermeldung x not found abgebrochen. Es ist jedoch mitunter ganz nützlich, ein Wort zu verwenden, dessen Bedeutung sich während der Laufzeit ändern kann. Ich möchte Ihnen jetzt beide Möglichkeiten zur Realisierung zeigen.

Verwendung von Variablen

Da Forth eine typenlose Sprache ist, bleibt es dem Anwender vorbehalten, mit den Daten anzustellen, was er möchte. Nehmen wir an, Sie schreiben ein Wort, das die Ausgabe wahlweise auf den Bildschirm oder den Drucker ausgibt. Die prinzipielle Vorgehensweise:

    variable vOutput  /* hält Ausgaberoutine  */
    ' noop vOutput !  /* Tut erst 'mal nichts */

    : drucker ( .. -- ) ...... ;
    : console ( .. -- ) ...... ;

    ' console vOutput !  /* Default zum Bildschirm */
    : list    ( .. -- ) vOutput perform ;

Zur Ausgabe auf den Drucker müssen Sie jetzt lediglich vOutput neu initialisieren.

    ' drucker vOutput !
    /* In einer Colon-Definition geschieht dies mit: .. ['] drucker vOutput ! ... */

Verwendung von defer

Forth bietet Ihnen das Definitionswort defer an. Defer erzeugt einen Wörterbucheintrag und compiliert darin zunächst das Wort noop.

Probieren Sie folgendes aus:

    defer myVek
    ' dup is myVek
    1 myVek

Für unser Beispiel:

    defer vOutput

    : drucker ( .. -- ) ...... ;
    : console ( .. -- ) ...... ;

    ' console is vOutput /* Default zum Bildschirm */
    : list    ( .. -- ) vOutput ;

Über defer können Worte jetzt innerhalb einer Anwendung neue Funktionen zugewiesen bekommen, auch wenn das Wort erst zu einem späteren Zeitpunkt definiert wurde.

Liste der verwendeten Worte

WORD | Stack | Beschreibung
' | -- <name> | legt PFA von <name> auf ToS
perform | adr -- | Führt Wort aus das adr hält
|  | : perform ( adr - ) @ execute ;
execute | pfa -- | Führt Wort aus, dessen pfa auf dem Stack liegt
defer | -- <name> | erzeugt defered Wort <name>
is | adr -- <name> | setzt PFA von <name> mit adr

### Erstellung von Standalone-Programmen

Ziel jeder Programmiersprache ist die Erstellung eines eigenständigen Programms. Viele Forthsysteme sind allerdings nur in der Lage, ihr gesamtes Programmimage zu sichern. So kann es vorkommen, daß ein einfaches hello, world leicht mehr als 50kb in Anspruch nimmt. Mit mForth dagegen ist es möglich, nur die benötigten Worte aus dem Programm zu einem Programm zu 'linken'. Als Ergebnis erhält man relativ kleine Programme.

Ganz so einfach wie bei einem C-Compiler ist es aber nicht, denn der Forth-Interpreter ist, wie Sie bereits wissen, etwas einfacher aufgebaut. Wir müssen also selbst "Hand" anlegen.

Forth kann nicht wissen, ob eine Adresse im Programm reloziert werden muß. Das Programm würde nur an der gleichen Stelle im Speicher funktionieren. Das ist aber nicht der Sinn der Sache. Stellen Sie sich vor, Sie laden ein zusätzliches ACC. Das erzeugte Programm wäre nun nicht mehr lauffähig, da alle Sprungadressen falsch wären.

- Alle mit ['] compilierten Worte müssen markiert werden.
- Adressen müssen mit a, ins Wörterbuch compiliert und mit mark: <name> markiert werden
- Alle markierten Worte müssen in main stehen.

Das sieht wieder schwieriger aus, als es in Wirklichkeit ist. make aus mForth nimmt Ihnen schon einiges an Arbeit ab.

Damit alle ['] Worte markiert werden, setzen Sie einfach automark on.

Man beachte, das das Wort mark nach return stehen muß, denn mark darf auf keinen Fall aufgerufen werden.

Als Beispiel soll uns ein neues Wort cat dienen, mit dem man zwei Dateien aneinanderhängen kann:

>     /* Cat 2 Files */

>     true constant APP immediate

>     APP #if system automark on toss #endif

>     mforth gemdos
>     #ifndef open   include system\files.4th   #endif

>     mforth gemdos also

>     create $von 64 allot /* Name File 1 */
>     create $an  64 allot /* Name File 2 */
>     FD von     /* Filediscriptor File 1 */
>     FD an      /* "                   2 */
>     create fbff 1026 allot /* Kopierpuffer */

>     : cat ( -- )
>        2 von $von open      /* 1. File öffnen */
>        if    2 an $an open  /* anfüegen an    */
>              if    an fsize an fseek /* Größe bestimmen */
>                    begin  fbff 1024 von fgets dup
>                    while  fbff swap an  fputs
>                    repeat drop
>              endif
>              von close   /* Files schließen */
>              an  close
>        endif ;

>     /* Aus der cmdline Filename extrahieren */
>     : >arg   ( addr dest -- addr' )
>        swap begin dup c@ 0= while 1+ repeat swap /* skip 0bytes */
>        begin over c@ 0<> while over c@ over c! 1 1 pair+ repeat
>        0 swap c! ; /* 0byte hinten dran */

>     : main ( -- )
>        0 $von c! 0 $an  c!  /* = NULLSTRINGS */
>        argv
>        if    cmdline c@ 2 > /* Mehr als 2 Argumente übergeben ? */
>              if    /* Alle Blanks durch Nullbytes ersetzen */
>                    cmdline count bounds do i c@ 32 = if 0 i c! endif loop
>                    /* Filename auslesen */
>                    cmdline 1+ $von >arg $an >arg drop
>                    cat /* Los geht's */
>              endif
>        endif
>        APP #if 0 return [ macro off system ] mark #endif ;

>     mforth
>     APP #if
>        system .ttp output
>        make cat bye
>     #endif

Liste der notwendigen Worte (vocabular system)

WORD | Stack | Beschreibung
return | n -- | Übergibt n und beendet das Programm.
make | -- <name> | Sucht main und erzeugt das Program name
mark: | -- <name> | <name> in das Wort mark compilieren
mark | -- | Enthält Worte, die über ['] compiliert wurden.
automark | -- adr | Bei TRUE, werden alle Worte, die über [']
|  | compilert wurden in das Wort mark compiliert.
prgflags | flags -- | Hält für make die Programmflags.
|  | Mögliche Flags:
|  | SUPER GLOBAL PRIVAT MALLOCALT LOADALT FASTLOAD
setssize | fs rs -- | Setzt Forth - und Returnstacksize.
|  | Defaultwerte sind fs = 2Kb und rs = 3Kb.
output | typ -- | Legt Programmtyp fest: .app .tos .ttp .acc .prg
|  | Beispiel: .app output
pause | -- adr | Unterbricht listing nach jedem gefundenen
|  | neuen Wort und wartet auf einen Tastendruck.
|  | Setzen mit: pause on
listing | -- adr | Ausgabe der Labels während make.
|  | Setzen mit: listing on
writeSYM | -- adr | Schreibt die Symbole eines mForth Programms auf Disk
|  | Setzen mit: writeSYM on
debug | -- adr | Schreibt die Labels in die Symboltabelle des
|  | Programms. (Devpac Format)

### Eine Anwendung

Ein Kursteilnehmer fragte mich, ob es möglich sei, Texte aus Forth heraus auf den Drucker auszugeben. Daraus entstand ein einfaches more

>     /*
>        Einfaches MORE (erweiterbar) für Forth-Kurs
>        -------------------------------------------
>        Use: more <name> p
>        mit p wird die Datei auf den Drucker ausgegeben
>        -----------------------------------------------
>        Ausgabe über GEMDOS
>        Beliebige Taste hält die Ausgabe an, Escape bricht ab
>     */

>     mforth include system\files.4th
>     mforth gemdos also

>     variable ok    /* Bool */
>     defer mEmit    /* Ziel */
>     FD infile      /* Filediscriptor */

>     : printer ( char -- ) c_prnout drop ;
>     : console ( char -- ) c_conout drop ;

>     : _more_ ( -- )
>        begin  infile fgetc EOF over <>
>        while  mEmit key?
>               if   key 255 and 27 =
>                    if    exit
>                    else  key drop
>                    endif
>               endif
>        repeat drop ;

>     : more ( -- <name> <ziel> )
>        ok on ['] console is mEmit
>        name nullstr? not    /* Filename ?? */
>        if    1+ 2 infile rot open /* File öffnen */
>              if    name nullstr? not /* Ziel ??  */
>                    if    1+ c@ ascii p =
>                          if    c_prnos /* Drucker bereit? */
>                                if    ['] printer is mEmit
>                                else  cr ." Drucker nicht bereit"
>                                      ok off
>                                endif
>                          endif
>                    endif
>                    ok @ if cr _more_ endif
>                    infile close
>              endif
>        endif ;

### Ende des Forth-Kurses

So, das wars!

Sollten Sie noch Fragen, Anregungen oder vielleicht einen Fehler entdeckt haben, setzen Sie sich mit mir in Verbindung.

Anrufen (06431-71188 ab 20.00) oder schreiben:

MausNet: Rainer Saric