Interpréter une ligne
Depuis l'article précédent, la boucle principale du système Forth est capable de récupérer une ligne de texte entrée par l'utilisateur. L'étape suivante est bien entendu d'en faire quelque chose : de l'interpréter.
INTERPRET est le mot Forth qui s'occupe de cela. Et ce que fait ce mot est assez simple : prendre le prochain morceau de texte entouré d'espace disponible dans la ligne, essayer de le trouver dans le dictionnaire, et si c'est le cas, l'exécuter. Si le mot n'est pas trouvé, la boucle tente de l'interpréter comme un nombre, et si c'est un nombre valide, elle le pousse sur la pile. Et si ce n'est pas un nombre, alors c'est une erreur.
Du moins, c'est le fonctionnement de INTERPRET en mode d'interprétation directe. INTERPRET peut aussi fonctionner en mode de compilation. Mais nous verrons cela plus tard. Pour le moment, je veux juste une interprétation directe afin de pouvoir executer la ligne Forth suivante : HEX 7FF FF C!.
Voilà à quoi ressemble INTERPRET (avec un nommage FIG-Forth) :
: INTERPRET
BEGIN
-FIND IF
CFA EXECUTE
ELSE
HERE NUMBER
ENDIF
AGAIN
Il y a quelques mots ici que je vais devoir définir. Tout d'abord -FIND, qui à la charge de prendre le prochain groupe de caractères depuis le TIB (Terminal Input Buffer) et de trouver s'il y a un mot correspondant dans le dictionnaire. Si un mot est trouvé, alors le PFA (Parameter Field Address) est laissé sur la pile.
CFA est un mot qui trouve le CFA (Code Field Address) d'un mot à partir de son PFA. Et EXECUTE est un mot qui exécute le code à l'adresse donnée sur la pile. On voit donc comment l'enchaînement de ces trois mots permet d'exécuter un mot trouvé dans le dictionnaire.
-FIND a un effet de bord : lors du parsing du mot, il laisse celui-ci dans la zone de données pointée par HERE. Cette zone de donnée est une zone libre en RAM. Incidemment celle où serait construit un mot si on était en mode de compilation. Mais dans notre cas, c'est une zone libre d'usage. Ainsi, dans le cas où -FIND ne trouve pas de mot correspondant dans le dictionnaire, il laisse les caractères à interpréter dans cette zone de données. NUMBER prend une adresse et tente d'interpréter les caractères à partir de cette adresse comme un nombre. Si c'est un nombre valide, il le pousse sur la pile. Sinon, c'est une erreur qui fera sortir de la boucle d'interprétation.
Sortir d'une boucle infinie
Mais comment est-ce que l'on sort de cette boucle ? En effet, INTERPRET doit redonner la main une fois la ligne complètement interprétée. L'astuce de Fig-Forth est d'ajouter au dictionnaire un mot nommé X, dont le nom est de longueur 1 et de valeur 0. Ainsi, lorsque -FIND arrive en fin de ligne, il trouve (à travers WORD) ce mot X et l'exécute. Or, X est défini de manière à faire sortir de la boucle d'interprétation et enlevant l'adresse de retour qui est au sommet de la pile des retours.
Je ne sais pas encore si je vais utiliser cette astuce. Elle a une sorte d'élégance pratique, mais aussi un côté « hack ». Voici ce qu'en dit la co-autrice de Forth, Elizabeth Rather dans cette discussions sur comp.lang.forth le 5 octobre 2011 :
*Sigh* I remember that trick. It was in very early Forths, probably as
long ago as NRAO. Awful. Excessively "cute" and obscure. Once you're
done enjoying your "eureka!" moment, forget you ever saw that!
Traduction française : *soupir* Je me souviens de cette astuce. Elle était dans les tout premiers Forth, probablement aussi vieille que le NRAO. C'est affreux. Excessivement « mignon » et obscur. Une fois que vous avez fini de profiter de votre moment « eureka ! », oubliez que vous avez jamais vu ça !
Et poursuit en donnant une solution alternative :
A much cleaner solution is to have BEGIN ... WHILE ... REPEAT loops that
compile or interpret depending on STATE, with the loops terminating when
the current input source is exhausted, whereupon the system (or TERMINAL
task) simply waits for more input in the BEGIN ... AGAIN loop in QUIT.
Traduction française : une solution beaucoup plus propre est d'avoir des boucles BEGIN ... WHILE ... REPEAT qui compilent ou interprètent selon l'état, avec les boucles se terminant lorsque la source d'entrée actuelle est épuisée, auquel cas le système (ou la tâche TERMINAL) attend simplement plus d'entrée dans la boucle BEGIN ... AGAIN de QUIT.
Trouver un mot dans le dictionnaire
-FIND est le mot le plus complexe à implémenter pour faire fonctionner INTERPRET. Et lui-même appelle plusieurs autres mots. Voici une implémentation possible de -FIND :
: -FIND ( -- addr len flag )
BL WORD
HERE COUNT CAPITAL
HERE LATEST (FIND)
Le mot agit en trois étapes. Tout d'abord WORD prend le prochain groupe de caractères terminé par le délimiteur spécifié, ici BL, qui représente l'espace. Comme indiqué plus haut, WORD laisse les caractères traités dans la zone de données pointée par HERE, précédés de leur longueur de la chaîne.
Ensuite, CAPITAL convertit les caractères en majuscules. Ce mot prend en entrée l'adresse de la chaîne à convertir ainsi que sa longueur. C'est le rôle de COUNT que de transformer une chaîne de caractères précédée de sa longueur en une adresse de cette chaîne et une longueur séparées sur la pile.
Enfin, l'appel à (FIND) cherche dans le dictionnaire un mot dont le nom correspond à la chaîne de caractères donnée en partant de LATEST, qui est une variable qui pointe vers le dernier mot ajouté au dictionnaire. On se souvient que dans cette implémentation, le dictionnaire est une liste chaînée de mots. (FIND) partira donc du dernier ajouté puis va remonter toute la chaîne jusqu'à trouver un mot... ou pas.
Si le mot n'est pas trouvé, le flag de retour est 0 et il n'y aura ni l'adresse ni la longueur de la chaîne sur la pile.
Pas de vocabulaire pour le moment
En plus de la simplification du mot INTERPRET pour lequel je ne traite pas le mode de compilation, je fais une autre simplification : je n'implémente pas les vocabulaire. Un vocabulaire est un mécanisme de Forth qui permet de regrouper des mots dans des espaces de noms. Cela permet de travailler dans un certain contexte avec des mots ayant une certaine signification, et de changer de contexte au besoin.