The FORTH compiler
published: 19 May 2019 / updated 25 October 2020
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.
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:
- it can be compiled by typing
INCLUDE PART1.FTH INCLUDE PART2.FTH INCLUDE PART3.FTH
from the keyboard, - we can create another file, named GENERAL.FTH for example containing the following lines:
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:
- le mot
(
(parenthèse ouvrante) ouvre une zone commentaire qui s'achève après le premier caractère ) rencontré. - le mot
\
déclare le reste de la ligne courante comme zone commentaire
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: 12Les 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 WORDS affiche les mots du vocabulaire FORTH
- ASSEMBLER WORDS affiche les mots du vocabulaire ASSEMBLER
- FORTH vous ramène dans le vocabulaire FORTH
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