Faqs FORTH CV Liens E-mail me Acceuil info |
Voici une presentation succinte d'un langage un peu particulier: FORTH |
I_ Concepts généraux
Je ne peux m'en délivrer qu'en allant jusqu'au bout de cette histoire. Mais que trouverai-je en fin de parcours ? Vous n'êtes pas de celles qui la laissent ouverte, en vue d'en faire un conte infini. Votre histoire est une suite de portes qui s'ouvrent sur des territoires blancs et des labyrinthes qui tournent …
Tahar Ben Jelloun, La nuit sacrée
Comme tout langage de programmation, FORTH a ses adeptes et ses détracteurs. Toutefois, aux dires de mes sources, il est l'un des langages informatiques les plus performants. Les concepts qu'il manipule allient simplicité, efficacité et complexité ! Contrairement aux langages classiques, FORTH n'est pas limité par une liste finie d'instruction et de structure de données. Le programmeur FORTH a la possibilité de modeler ce dernier à sa convenance et de le spécialiser en fonction de l'application qu'il développe. Pour le forthien, les notions de procédures et de fonctions n'existent pas. De même, la notion de typage est quasi absente. Les instructions du langage s'appellent des mots. A l'instar des langues naturelles, FORTH organise les mots qui le composent en vocabulaire (ensemble de mots ayant un trait commun). Ce vocabulaire peu être étendu, c'est à dire qu'on peut lui apprendre de nouveaux mots, en les définissant à partir de mots existant. C'est d'ailleurs ainsi que l'on programme en FORTH, en enrichissant un vocabulaire existant, ou en en définissant un nouveau.
A_ L'élément centrale de tout système FORTH : la pile
FORTH est un langage structuré (pas de GOTO), basé sur l'utilisation d'une structure de donnée bien connue, la pile LIFO, sa syntaxe utilise la notation polonaise inversée. Il dispose d'un jeu d'instruction sur ses piles, et des noms d'opérations pour en créer de nouveaux.
Le langage est construit autour de 2 piles :
La pile de donnée (data stack)
La pile de retour (return stack)
L'utilisation de la notation polonaise inversée se fait de la façon suivante : les opérandes sont empilés au fur t à mesure de leur rencontre, les opérateurs sont exécutés sur les derniers opérandes empilés, qui sont dépilés et on empile le résultat.
Ex : pour calculer 2+(3*5),
2 3 5 * +
ou, 3 5 * 2 +
C'est là l'une des difficultés du langage : acquérir de nouveaux réflexes. Comme la pile est l'élément central de FORTH, il existe des mots pour modifier son contenu, dont voici quelques exemples :
DUP (x à xx) : duplique l'élément de sommet de pile, et l'empile
Ex : pour calculer le carré d'un nombre on peut procéder ainsi,
2 DUP *
DROP (x à g) : enlève l'élément de sommet de pile.
SWAP (x1x2 à x2x1) : échange les 2 éléments de sommet de pile.
etc…
D'autres mots sont plus " tordus " :
TUCK (x1x2 à x1x2x1) : copie l'élément de sommet de pile sous les 2 premiers.
PICK (x1x2 … xn à x1x2 … xnx1) : empile l'élément de rang de la pile.
B_ Les variables
Notion capitale en informatique, les variables sont également utilisées en FORTH, mais beaucoup moins que dans d'autres langages, du fait de l'utilisation intensive de la pile pour passer des données d'une partie du programme à une autre (d'un mot à l'autre en FORTH). En fait, on les utilise lorsque l'on a une gestion de la pile qui se complique trop à vouloir les éviter. Naturellement, cela se fait au détriment de la lisibilité du code produit.
Les variables doivent être déclarées, ainsi pour créer une variable V, on écrira : VARIABLE V, et V sera créée avec comme valeur par défaut 0. Pour intervenir sur la valeur d'une variable, le programmeur FORTH dispose de mots spécifiques : ! (affectation), @ (affichage).
Ex : VARIABLE note
note @. à afiche 0
18 note !.
note @. à afiche 18
Remarque:
Il est interressant de noter que si on tape, note.
FORTH affichera un nombre (ex: 15666) qui est l'adresse de la variable en
mémoire.
Il existe aussi des pseudo variables appelées constantes, que l'on déclare à l'aide du mot
CONSTANT, et l'utilisateur a également la possibilitée de déclarer des variables
alpha-nulmériques, avec le mot STRING.
Le système FORTH utilise également des variables pour son fonctionnement. Par exemple il
existe une variable prédéfinie BASE à laquelle il suffit d'affecter la valeur de la base
numérique dans laquelle on veu travailler (par défaut le système est en base 10). Naturellement,
il existe des mots qui permettent de changer de base numérique (OCTAL, HEX, …).
C_ Structure de contrôle et récursivitée
En programmation, toute séquence d'instruction peut-être rompue en exécutant un branchement ou une boucle. Comme tout langage informatique qui se respecte, FORTH dispose de toute une panoplie de ces branchements:
Si … alors … sinon
Répéter … jusqu'à
Pour i de 1 à n
Selon
…
Pour exemple prenons la structure IF … ELSE … THEN :
Le mot IF est un mot d'exécution immédiate, et compile un branchement conditionnel. Il n'est
utilisable qu'en compilation.
Si le résultat du test effectué avant IF délivre un flag booléen faux, c'est la partie de code
située entre ESLE et THEN qui sera exécutée, sinon se sera celle entre IF et ELSE. Un flag
booléen peut être le résultat de divers opérations: résultat d'un calcul arithmétique, d'une
poération logique, de la lecture d'une adresse mémoire …
Le mot IF test et consomme la valeur située au sommet de la pile de données, toutes valeur
non nulle sera considérée comme vrai.
Les puristes, dont certaine de mes sources, affirment qu'un langage informatique est incomplet
s'il ne peut traiter la récursivité, pour d'autres on peut s'en passer. FORTH va enthousiasmer
les premiers, car il est équipé pour traiter ce type de situation.
Come il n'est pas possible de faire appel à un mot en cours de définition, on insère le mot
RECURSE partout où on veut faire un appel récurssif.
Attention touefois, la récursivitée à ses limites : la capacité des piles de données et de
retour. Une récursivité mal contrôlée sature les piles et bloque le système.
II_ Vocabulaire
Comme nous l'avons déjà vu, FORTH utilise un vocabulaire que l'on peut étendre en définissant de nouveaux mots. Je dvrais plutôt dire des vocabulaires, car il y en a plusieurs:
Le vocabulaire courrant, créé par l'utilisateur ( il est perdu s'il n'est pas
sauvegarder)
Des vocabulaires créés et sauvegardés par l'utilisateur
Les vocabulaires du système FORTH
Toute nouvelle définition d'un mot se fait à partir des mots déjà présents, il est bien entendu
possible de redéfinir un mot existant;
Lorsque FORTH cherche un mot, il se réfèrera toujours à la définition la plus récente du
vocabulaire courant. Il existe des mots pour manipuler les vocabulaires.
Ex: FORGET: efface les mots défini par l'utilisateur
HERE: renvoie l'adresse du premier emplacement libre du dictionnaire. C'est là que
l'on commence vraiment à jouer avec FORTH car nous pouvons le faire se modifier
lui - même.
FENCE: prmet de protéger une partie du dictionnaire
…
Lorsque que la définition d'un mot fait référence à un mot pré-existant du dictionnaire, l'adresse correspondante est exprimée en adressage absolu au sein de la nouvelle définition. Comme nous l'avons déjà vu, l'activitée de programmation consiste essentiellement à définir de nouveau mots.
Ex:
On souhaite ecrire un programme qui calcul la masse d'un sac de bille. En supposant que
toutes les billes aient la même masse, il suffit de calculer la masse d'une bille, le nombre de
billes et enfin de calculer la masse du sac.
En FORTH, on aurait quelque chose comme cela,
:SAC_BILLE
MASSE_1_BILLE
NOMBRE_BILLE
MASSE_SAC;
Si on soumet se programme au compilateur, il va répondre:
POIDS_BILLE inconnu Pour faire accepter le programme, il faut définir les mots qui le compose. Toutefois, rien n'interdit d'entrer une définition provisoire, et pourquoi pas celle du mot vide?
: MASSE_1_BILLE;
: NOMBRE_BILLE;
: MASSE_SAC;
: SAC_BILLE
MASSE_1_BILLE
NOMBRE_BILLE
MASSE_SAC;
Cette fois, le compilateur accepte le programme et réponds OK, en pur texan! Ce programme ne fait toujours rien, mais il le fait de façon structurée.
Un mot FORTH est constituer de plusieurs parties accolées les unes aux autres, et dont l'organisation est la suivante:
Un VFA(View Field Adress), adresse du champs de vue, qui est utilisée pour l'auto-documentation(commande HELP)
Un LFA(Link Field Adress),adresse du champs de lien, qui pointe vers le définition du mot précédent du dictionnaire
Un NFA(Name Field Adress), adresse du champs de nom, qui contient le nom du mot
Un CFA(Code Field Adress), adresse du champs de code, qui contient le code executable du mot
Un PFA(Parameters Field Adres), adresse du champs de paramètres, contient les adresses des mots qui constituent la définition du mot
III_ Application industrielle
Nous avons vu que pour définir un nouveau mot, FORTH utilise des mots existant. En décomposant de plus en plus loin, on finit par tomber sur les primitives du langage. Toutes les primitives sont rassemblées dans le noyau, qui est le programme de base, de toute petite taille.
Exemples de mots du noyau: @, !, |, 2!, …
Un bon noyau contient seulement quelque dizaines de primitives, qui servent à effectuer des
opérations arithmétiques, aller checher ou stocker des valeurs en mémoire, ouvrir ou fermer des
fichiers, … Bref, tout ce que sait faire un ordinateur sans problème. Un noyau déjà respectable
a une taille de 20 Ko, il existe même des noyau de 1 à 2 Ko, soit le contenu d'une page
imprimée… Cette petite taille du noyau faisait de FORTH un outil idéal pour
la programmation des mini et des micro ordinateurs.
De nos jour, FORTH est toujours utilisé. Par exemple il est utiliser comme boot language sur
les systèmes SPARC, PowerPC et intel x86. Autre application, indirecte cette fois, le langage
postcript est un descendant directe du FORTH. De même, celui des calculatrice HP en est un
dérivé.
Loin de ces mastodontes de l'informatique, un passionné s'est "bricolé" son propre
micro-ordinateur, à partir d'un processeur 68030. JPB a ainsi réussi à crée un système de
gestion de fichier plus performant que windows, inspiré d'UNIX. Pour ce faire, plutôt que
d'utiliser l'assembleur, ce qui peut être fastidieux, il utilise FORTH car bien qu'il soit
puissant, ses faibles besoins en mémoire (compilateur + interprtteur = 16 Ko) l'ont décidé.
Pour lui, FORTH est l'un des langages évolués les plus agréable pour travailler à bas niveau.
Ses instructions de base se révèlent parfaitement adaptées à une implémenntation matérielle
au sein d'un micro-processeur.
Je ne peut me permettre d'aller plus avant sur ce sujet, mais j'incite le lecteur
interressé à visiter le site internet de ce monsieur JPB, dont vous trouverez l'adresse
plus loin, et à lire l'article le concernant dans le dernier Virus mag' (n° 18).
IV_ Interprétation / Compilation
Les langages compilés ont le défaut que l'on ne peut pas les exécuter directement et il faut
recompiler le programme à chaque modification; par contre cela fait ils sont d'une très grande
rapiditée d'exécution.
Les langages interprétés sont eux exécutables directement, mais sont en contre partie beaucoup
plus lents à l'exécution.
Le langage FORTH est, là encore, un langage atypique car il intègre à la fois un interpréteur,
un compilateur et un assembleur. La version FORTH83 dispose même de trois interpréteurs, dont
deux sont écris en FORT§H et le dernier en assembleur.
Le compilateur travaille en une seule passe et sans gérer de table de référence. Son rôle est
d'accroitre les fonctions disponibles dans le dictionnaire, de rajouter des mots à un
vocabulaire existant ou a en créer un nouveau. Tout mot compilé dans le dictionnaire peut-être
intégré à une nouvelle définition.
Il y a deux façon de compiler du code: à partir d'un fichier "classique", ou bien en mode
interprété, mettant le code à compiler entre les mots : et ;.
Le compilateur travaille avec deux dictionnaires, un pour les noms et un autre pour le code.
Le premier est souvent une liste chainée ou une table de hachage, ou tout autre mécanisme
permettant une recherche dans une liste de nom. Le compilateur, ou l'interpréteur cherchent
dans cette liste le nom auquel est associé le code exécutable voulu. Ces deux dictionnaire
peuvent être mélangés ou séparé suivant les systèmes.
Comme nous l'avons déjà vu précédement, certain mots permettent de redéfinir les mots de
base du langage. En fait cela va même encore plus loin, car FORTH dispose d'un outils permettant
de redéfinir un système FORTH complet: son méta-compilateur.
V_ La méta-compilation
Les signes ne sont pas des preuves, puisque n'importe qui peut en produir des faux ou d'ambigus. de là à se rabattre, paradoxalement, sur la toute puissance du langage: puisque rien n'assure le langage, je tiendrai le langage pour la seule et dernière assurance: je ne croirai plus à l'interprétation.
Roland Barthes, ragments d'un discours amoureux
A_ Un autre exemple: CAML
La méta-compilation est sans doute le summum de la complexité, mais aussi d'intelligence en
matière de programmation. Son but est de redéfinir à partir d'un système donné, l'intégralité
d'un nouveau système. Peu de langages évolués sont suffisamment puissants et maniables pour
disposer de ce concept.
Le seul autre exemple que j'ai pu trouver est celui du langage CAML. En effet, dans ce langage
on parle d'auto-génération. Ce mécanisme est même général dans les systèmes CAML : ils sont tous
auto-gènes, c'est à dire produit par eux-mêmes. Le compilateur est entièrement écrit en CAML,
c'est donc un programme CAMLcommo les autres, compilable par le compilateur CAML, c'est à dire
par lui-même.
L'auto-génération, ou bootstrap en anglais, est un mécanisme étrange, puisqu'il s'agit d'une
sorte de " définition récursive du compilateur du langage ". Par quel miracle peut-il en sortir
un système CAML qui tourne ? Et bien la récurrence s'arrête sur un compilateur de niveau 0, le
compilateur initial, écrit dans un autre langage. Progressivement, certaines parties du
compilateur initial sont réécrites dans le langage compilable par le compilateur, jusqu'à
obtenir un compilateur entièrement écrit dans le langage du compilateur.
La comparaison peut paraître osée, toutefois l'idée est la même en FORTH.
B_ Méta-compilation d'un système FORTH
Tout système FORTH, sauf peut-être les versions des toutes premières origines du langage, est
lui-même conçu et écrit en FORTH. L'outils d'écriture d'un système FORTH est son méta
compilateur, et il est d'usage de fournir à l'utilisateur les sources du méta-compilateur,
aussi bien que les sources du langage lui-même méta-compilé. Pour l'utilisateur, disposer du
méta-compilateur et des sources du langage signifie qu'il possède un outil de programmation
extrêmement puissant, l'autorisant à redéfinir, selon ses propres besoins, l'outil même dont
il se sert. Certains méta compilateurs ont étés écrit pour permettre de passer du système FORTH
d'un processeur A à un processeur B.
Historiquement, TURBO-FORTH a été méta compilé en FORTH83 par Marc Petremann1, à l'aide du
méta-compilateur " Phénix " du système FORTH83 (créé par Laxen et Perry). Au fils des
générations, méta-compilateur et source méta-compilés ont été modifiés jusqu'au système actuel
qui assure sa propre auto-génération intégrale, et rien n'empêche le forthien de continuer
cette boucle étrange… Toutefois, toute entreprise de méta-compilation requiert selon moi de
solides connaissances du langage, un QI confortable et beaucoup de rigueur et de patience pour
appréhender le plus complexe de tous les concepts de FORTH.
Nous l'avons vu, l'auto-génération de CAML s'arrête sur un compilateur de niveau 0. Ici, ce
n'est pas le cas, mais on reste assez proche de l'idée car la " récurrence " s'arrête sur un
noyau (CORE) contenant les primitives minimales du langage pour le bon fonctionnement de tout
système FORTH (selon la norme ANSI). Il faut savoir qu'à partir du moment ou un système supporte
ce CORE, il peut compiler du code.
Attention, il ne faut pas confondre compilation et méta-compilation. Là où la compilation ne
fait qu'étendre le dictionnaire d'un système, la méta-compilation créée un système cible
entièrement nouveau, complet et autonome par rapport au système hôte. La cible n'est en aucun
cas un sous-système du système hôte.
Conclusion
FORTH est un langage inoxydable, indémodable, du moins tant qu'il faudra faire passer des
particules dans des portes logiques pour simuler une machine de Turing. Non que FORTH soit le
seul langage qui possède cette aptitude, c'est simplement qu'il est relativement accessible
aux non-informaticiens, s'ils sont disposés à apprendre comment fonctionne un ordinateur.
FORTH est d'une efficacité qui humilie les applications ordinaires, ne coûte pratiquement
rien, fonctionne à la perfection, ne consomme presque rien en mémoire et en disque, tourne sur
à peu prés n'importe quel ordinateur et bénéficie du support quasi bénévole de quelques mordus.
Et le fameux revers de la médaille ?
Il tient principalement dans les quelques dizaines heures à passer pour acquérir de nouveaux
réflexes, qui plus est bien utiles dans d'autres domaine. De plus, l'utilisation de symboles de
ponctuation comme instruction du langage peut rendre les codes sources illisibles, mais des
commentaires et un peu de rigueur rendent le tout relativement digeste, plus en tout cas que
certain script PERL.
Pour ma part, le concept de méta-compilation m'intrigue au plus haut point, et je continu à
travailler sur les documents en anglais que j'ai réussi à trouver sur internet.
Et pour finir, une maxime bien adaptée au FORTH :
small is beautiful ...
Voici un exemple de programme FORTH utilisant au maximum la pile pour le passage des données. Ce programme calculera la somme des éléments impairs d'une liste d'entiers. On supposera que celle-ci, ainsi que le nombre d'élément de la pile se trouve en sommet de pile au moment de l'appel de fonction.
:SOMIMP
0 SWAP 0
DO
SWAP DUP 2 MOD
IF +
ELSE DROP
THEN
LOOP
;
Si on étudie ce programme, on se rend contre qu'il est constituer de trois partie distinct : Soit L la liste sur laquelle on travaille et n son nombre d'élément.
:SOMIMP
/* == Première étape
On empile 0, puis on échange (swap) les deux éléments du sommet de pile et on empile à
nouveau 0.
Si on se souviens que lors de l'appel au programme le sommet de pile doit être : L n ,
on se retrouve après cela avec : L 0 n 0 == */
0 SWAP 0
/* == Deuxième étape
On entre maintenant dans une boucle du type pour i de 1 à n, en effet le mot DO dépiler
les deux éléments de sommet de pile i et j (ici n et 0) et exécute la boucle i-j fois
(ici n-0 fois, c'est à dire autant de fois qu'il y as d'élément dans la boucle.
Invariant de l'itération : l'élément du sommet de pile est la somme S des éléments impaires
de la liste, pour l'instant c'est 0 (le premier 0 que l'on a empilé) == */
DO
/* == Troisième étape
La pile se trouve dans cet état : L' S (L' : liste des élément de L non encore traités,
S : somme des élément impaire de la partie déjà
traitée de la liste)
Si e est le premier élément de L', la première ligne met la pile dans cet état :
L'' S e e 2
Puis, MOD dépile les deux éléments de sommet de pile, calcule leur modulo m et empile le
résultat : L'' S e m
Enfin le mot IF est exécuté : il dépile l'élément de sommet de pile, si celui-ci est
différent de 0 (donc e était impaire),c'est le code entre IF et ELSE qui est exécuté :
L'' S+e
Sinon, e était pair, on exécute le code entre ELSE et THEN : L'' S
Dans tous les cas l'invariant de l'itération est respecté. == */
SWAP DUP 2
MOD
IF +
ELSE DROP
THEN
LOOP
;
Exemple d'appel : 11 12 0 1 23 7 8 SOMIMP. Le résultat sera : 42.