The FORTH compiler

published: 19 May 2019 / updated 25 October 2020

Lire cette page en français

 

The FORTH compiler works in a single pass and without managing a reference table. Its role is to increase the functions available in the dictionary.

Compiling a definition

Compiling an ordinary definition begins with the word : (colon) and ends with the word ; (semicolon). The word : is followed by the caption of the word to be defined. Everything between this word and the word ; is part of the definition of the word and will then be executed, either directly from the interpreter, or from another definition of the word FORTH. For example, let's compile a definition which we will name DISPLAY-BINARY and whose role will be to display in binary a number previously placed at the top of the data stack:

: DISPLAY-BINARY  ( n --- ) 
    2 BASE ! . DECIMAL ; 

The word : (colon) opens a compilation sequence and is followed by the label of the word to be defined. The name can consist of all ASCII characters whose code is between 33 and 126, therefore excluding any space or control character.

The length of a word is between 1 and 31 characters maximum.

The name is followed by the definition:

    2 BASE !   \ switch to binary digital base 
    .          \ display of the previously stacked number 
    DECIMAL    \ restitution of the decimal digital base 

The end of the compilation sequence is marked with the word ; (semicolon). Forth returns to interpretation mode. This definition can be typed from the keyboard or be part of an ASCII file.

With gForth, we can compile source code placed in a file by typing:

  INCLUDE <fichier>

where <file> is the name of the file containing the text to be compiled. The files compilable by gForth have the extension you want.

As an exercise, edit a file that you will name BINARY.FTH, type the small definition given as an example, save your file and come back to gForth, then type INCLUDE BINARY.FTH

Now the new definition is part of the FORTH dictionary, as we can find out by typing WORDS. To try out the functioning of the word that has just been created, just type:

14 DISPLAY-BINARY \ display 1110 

If you find that DISPLAY-BINARY is too long, rewrite your definition by calling it DISPBIN or better by defining DISPBIN as follows:

: DISPBIN 
    DISPLAY-BINARY ; 

Any word compiled in the FORTH dictionary can be included in a new definition. In FORTH, a word can be considered as the main program or as a procedure of a more general program.

When compiling a new keyboard typed definition, you can hit <RET> almost anytime:

: DISPBIN 2 BASE ! . DECIMAL ;  <RET>

This definition can be typed from the keyboard in several times as follows:

: DISPBIN <RET>
2 BASE <RET>
! . <RET>
DECIMAL <RET>
; <RET>

The indentation of words, the number of spaces between words, as well as typing in upper or lower case does not matter:

:     dispBin 2 BaSe !         .     DecimaL         ; 

will also be accepted provided that the format of the numbers is consistent and that the words used are defined in the dictionary.

ATTENTION: on some FORTH versions available on microcontrollers, it is necessary to respect the case of the characters constituting the words of the dictionary. This is the case for FlashForth on Arduino.

To date, the FORTH language is the only known language accepting to compile typed source code directly from the keyboard in such an interactive way. This interpreter/compiler duality can also be used when you want to compile the contents of a file.

Compiling and chaining source files

Compiling and chaining source files is possible on FlashForth.

Voir MyFFshell: the FORTH compilation at very high speed

To load the content of a source file, use the word INCLUDE followed by the name of the file treat. The file extension can be FTH, TXT or 4TH or whatever seems appropriate to you.

INCLUDE TEST.TXT 	\ load and compile TEST.TXT 

As has already been briefly explained, gForth can compile definitions from an ASCII file. But gForth can also interpret a series of commands or operations placed in these same files.

The FORTH language does not distinguish between the keyboard and a file source: loading a file with INCLUDE is equivalent to typing on the keyboard each line of this file.

Take the contents of the BINARY.FTH file that you created to test the previous one example and add the following lines after DISPBIN:

CR 1  DISPBIN 
CR 8  DISPBIN 
CR 25 DISPBIN 

Save BINARY.FTH again and recompile its contents from gForth using INCLUDE BINARY.FTH. gForth will compile the contents of the BINARY.FTH file and execute the three lines that have been added.

This interpretation/compilation duality is very useful, because it allows you to modify the context FORTH being compiled.

HEX 
C0 EMIT  D9 EMIT CR 
DECIMAL 

This code will be executed perfectly. The content of an ASCII file containing instructions FORTH is normally compiled or interpreted from the beginning of the file to the physical end of the file.

If you plan to develop a large application, it is desirable first, to split it into several small files. Optionally, a part of a program can be reused by another program and in this case, fragmentation becomes necessary. Example, suppose we have created a program whose source is spread over three files PART1.FTH, PART2.FTH and PART3.FTH:

INCLUDE PART1.FTH 
INCLUDE PART2.FTH 
INCLUDE PART3.FTH 

then compile the whole application by typing INCLUDE GENERAL.FTH.

gForth accepts multiple levels of file nesting. The same file can call an infinity of subfiles:

INCLUDE PART1 
INCLUDE PART2 
\ ..etc... 
INCLUDE PART1000 

Any file whose compilation is complete removes the nested files of a level in the nesting hierarchy.

Prenez soin de toujours bien commenter vos programmes source. Il y a deux manière de commenter les programmes:

Exemple:

: .NBR ( n ---) 
    BASE @ >R 
    \ save initail avalue of BASE 
    DUP CR DECIMAL ." Dec: " 5 .R 
    \ display n in decimal 
    DUP CR HEX ." Hex: " 5 .R 
    \ and in hexadecimal 
    CR OCTAL ." Oct: " 5 .R 
    \ and in octal 
    R> BASE ! 
    \ restore inital value of BASE 
; 

Le mot .NBR affiche un nombre entier simple précision signé sur trois lignes séparées en décimal, hexadécimal et octal, ceci quelque soit la base numérique initiale. Exemple:

2 BASE ! 1010 .NBR display Dec: 10 Hex: A Oct: 12

Les mots dans le dictionnaire

Une fois la compilation d'un nouveau mot achevée, il fait partie du dictionnaire au même titre que tous les mots déjà définis. On peut contrôler à tout instant la présence des mots définis en utilisant WORDS. Le dernier mot défini est le premier de la liste affichée par l'exécution de WORDS. Exemple:

: WORD1 ; 
: WORD2 ; 
: WORD3 ; 
WORDS \ display WORD3 WORD2 WORD1 ... 

Lors de l'exécution de WORDS, les mots définis par vous ou ceux déjà pré-définis apparaissent dans l'ordre inverse de leur création, à savoir que le mot le plus récemment défini est affiché en premier.

Seuls les mots appartenant au vocabulaire courant sont affichés:

Forth dispose d'un certain nombre de vocabulaires pré-définis dont vous pouvez lister les noms en exécutant VOCS. La connaissance du contenu de ces divers vocabulaire n'est pas indispensable pour le moment.

Tous les mots FORTH sont chaînés entre eux et l'entrée dans cette chaîne a toujours lieu à partir du dernier mot défini dans le vocabulaire courant.

Lorsqu'une définition d'un mot fait référence à des mots du dictionnaire, chaque adresse correspondante est exprimée en adressage absolu au sein de la nouvelle définition. Ainsi, la définition d'un mot FORTH n'est pas relogeable en mémoire sous sa forme compilée.

Mots définis plusieurs fois

Si deux mots portent le même nom, seul le dernier mot défini sera exécutable en interprétation:

: TEST1 ." Premier test " CR ; 
: TEST1 ." Second test " CR ; 

Ne tenez pas compte du message TEST1 "existe déjà" (ou équivalent):

TEST1 \ affiche SECOND TEST

L'interpréteur accède au contenu du dictionnaire à partir du dernier mot défini et exécute la définition la plus récemment définie parmi deux ou plusieurs définitions de même nom. Si une définition utilise la première version de TEST1, la création d'un nouveau mot portant également le nom TEST1 ne modifie pas le comportement de cette définition:

: TEST1 ." Premier test " CR ; 
: REGARDE TEST1 ; 
: TEST1 ." Second test " CR ; 
REGARDE \ affiche Premier test 

Pour modifier REGARDE et lui faire utiliser la nouvelle version de TEST, il faut également recompiler REGARDE:

: TEST1 ." Premier test " CR ; 
: REGARDE TEST1 ; 
: TEST1 ." Second test " CR ; 
: REGARDE TEST1 ; 
REGARDE \ affiche Second test 

Cette homonymie tolérée par FORTH permet de compiler successivement plusieurs programmes comportant des définitions de même nom sans avoir à rééditer le programme source pour renommer les noms de définitions identiques.

Cependant, dans une phase de mise au point de définition, on peut être gêné par la présence d'homonymes, et il est souhaitable de supprimer les versions de certaines définitions devenues inutiles, soit pour conserver la cohérence du programme, soit pour récupérer de l'espace mémoire ou toute autre raison pratique.

Enfin, l'existence de plusieurs vocabulaires définis par le programme est une façon élégante de lever les ambiguités entre homonymes.

Supprimer un mot du dictionnaire

Pour supprimer un mot du dictionnaire, il faut exécuter le mot FORGET suivi du nom de la définition à supprimer:

FORGET MOT1

supprime du dictionnaire MOT1 et tous les mots définis après lui. Si MOT1 est défini plusieurs fois, seule la dernière version de MOT1 est supprimée. Exemple:

: TEST1 ." Premier test " CR ; 
: REGARDE TEST1 ; 
: TEST1 ." Second test " CR ; 
: REGARDE TEST1 ; 
WORDS       affiche       REGARDE MOT1 REGARDE MOT1 ...etc...
FORGET MOT1 WORDS      affiche             REGARDE MOT1 ...etc...

Pour oublier tous les mots définis depuis le lancement de FORTH, taper EMPTY.

Vous pouvez marquer le début d'une application en exécutant MARK suivi du nom de l'application. Exemple, soit à compiler les fonctions graphiques contenues dans le fichier GRAPHIC.FTH, puis à compiler un programme utilisant ces fonctions graphiques:

INCLUDE GRAPHIC 
MARK DESSIN 

Si le contenu de a été modifié et doit être recompilé, il ne sera pas nécessaire de taper EMPTY puis de tout recompiler; en tapant DESSIN, vous supprimerez du dictionnaire les seules définitions compilées après le mot DESSIN.

Constantes et variables simple précision

Tout programme écrit en FORTH, aussi sophistiqué soit-il, ne peut pas toujours traiter des données provenant de la pile de données. Dans certains cas on fera appel à des constantes et des variables. Les constantes sont définies à l'aide du mot CONSTANT:

2019 CONSTANT ANNEE-COURANTE 

Les variables sont définies à l'aide du mot VARIABLE:

VARIABLE JOURS 

La constante ANNEE-COURANTE et la variable JOURS figurent maintenant dans le dictionnaire FORTH, c'est-à-dire qu'elles sont disponibles au même titre qu'un mot compilé par : (deux-points) ou n'importe quelle autre primitive déjà définie dans FORTH. Seule l'exécution de ces mots diffère de celle d'un mot défini par : (deux-points).

Une constante dépose au sommet de la pile de données la valeur affectée au moment de sa définition:

ANNEE-COURANTE . 2019

Une variable dépose au sommet de la pile de données l'adresse contenant la valeur qui lui a été affectée ou qui devra y être affectée.

JOURS .

affiche une adresse simple précision (ou double précision selon la version Forth).

Le contenu de cette adresse est déposé sur la pile de données par l'exécution du mot @:

JOURS @ .

affiche 0. Zéro est la valeur attribuée par défaut à une variable lors de sa création.

Une valeur numérique simple précision peut être stockée dans une variable par l'exécution du mot !:<:p>

15 JOURS ! 
JOURS @ .  15 

Le mot ? combine l'action de @ et . et s'utilise comme suit:

16 JOURS ! 
JOURS ?   \ affiche   16 

Le mot +! incrémente le contenu d'une variable:

2 JOURS +! 
JOURS ?   \ affiche   18 

Pour décrémenter le contenu d'une variable, il suffit de l'incrémenter avec une valeur négative:

-10 JOURS +! 
JOURS ?   \ affiche   8 

Les versions récentes de FORTH acceptent un nouveau type de variable de type entier définies par VALUE:

30 VALUE TempLimit 

Une variable définie par VALUE doit avoir une valeur prédéfinie lors de sa création. Ici, on dafinit TempLimit en lui affectant la valeur 30.

Une variable de type VALUE déposera sa valeur sur la pile de données. Pour modifier sa valeur, on utilisera le mot TO:

TempLimit .     \ display 30 
35 to TempLimit 
TempLimit .     \ display 35 

Dans certaines situations, on peut considérer le contenu d'une variable simple précision comme un flag booléen et non comme une valeur littérale. Dans ce cas, toute valeur non nulle sera considérée comme vraie et toute valeur nulle comme fausse:

VARIABLE VIVANT 
: .HERITAGE ( ---) 
VIVANT @ 
IF ." Attendez encore pour hériter" 
ELSE ." Maintenant, courrez chez le notaire" 
THEN ; 
0 VIVANT ! \ considère mort 
.HERITAGE   affiche   Maintenant, courrez chez le notaire 

Pour traiter sans équivoque les valeurs booléennes, on restreindra les valeurs affectables à une variable simple *précision, considérée comme flag booléen, les seules valeurs 0 ou -1.

Pour augmenter la lisibilité des programmes FORTH, deux constantes pré-définies, TRUE et FALSE reprennent ces valeurs:

TRUE .   affiche   -1 
FALSE .   affiche     0 
TRUE VIVANT ! 
.HERITAGE   affiche   Attendez encore pour hériter 

Les affectations booléennes ont été simplifiées et peuvent être exécutées par ON et OFF:

VIVANT ON .HERITAGE 
affiche Attendez encore pour hériter 
VIVANT OFF .HERITAGE   affiche   Maintenant, courrez chez le notaire 

Le mot OFF peut aussi servir à remettre à zéro n'importe quelle variable simple précision ou emplacement mémoire simple précision. Exemple:

VARIABLE SCRORE 
10 SCORE +! SCORE ?     \ affiche   10 
SCORE OFF SCORE ?       \ affiche   0