Des mots complets
Depuis le début de l'implémentation de ce Forth sur 6502, j'ai parlé de « pseudo mots ». Ces mots ont un CFA (Code Field Address) et un PFA (Parameter Field Address), mais pas d'entête de mot ni de principe de dictionnaire.
Or pour qu'un mot soit complet en Forth, il faut ces deux autres concepts. Dans cette implémentation je vais utiliser deux autres sections : le NFA (Name Field Address) et le LFA (Link Field Address).
Ce qui donne la structure complète suivante :
NFA: entête du mot, dont le premier octet est la longueur du nom du mot, suivi du nom lui-même. Seuls les 5 bits de poids faible sont utilisés pour la longueur (ce qui limite la longueur des noms à 31 caractères). Le bit de poids fort est toujours à 1, les deux autres bits seront vus plus tard. De plus, le dernier octet du nom a son bit de poids fort à 1 pour indiquer la fin du nom.LFA: adresse du mot précédent dans la liste chaînée des mots. Tous les mots disponibles dans ce Forth forment une liste chaînée, chaque mot pointant vers le mot défini avant lui. Le dernier mot défini pointe vers l'adresse 0 pour indiquer la fin de chaîne.CFA: adresse du code machine qui sera exécuté lorsque le mot sera appelé.PFA: paramètres du mot, s'il en a.
Précision : cette implémentation d'un mot complet est celle que l'on trouve dans des Forth historiques 8 bits. Mais elle n'est pas obligatoire, il peut y avoir des variantes. Rien n'empêche d'inverser l'ordre du NFA et du LFA. Ou de terminer le nom par un octet nul plutôt que par un marqueur dans le bit de poids fort.
En regardant des normes plus récentes (section 3.3), on voit qu'il existe un « name space » un « code space » et un « parameter space », et que les mots sont organisés dans un « dictionnaire », cependant, il est laissé à l'implémentation de faire les choix de représentation.
Implémentation
Pour implémenter ces mots complets, j'ajoute des macros pour faciliter l'écriture (et surtout la réécriture en cas de changement d'idée).
Mon mot de test, qui permet de valider que le programme exécute bien la boucle, était comme ceci :
test_word_cfa:
.word test_word_pfa
test_word_pfa:
lda #$42
sta $7FF
jmp next
Il devient maintenant :
DEFINE_CODE_WORD TEST, BRANCH, 0
lda #$42
sta $7FF
END_TO_NEXT
La macro DEFINE_CODE_WORD prend trois paramètres : le nom du mot, le mot précédent (pour le chaînage) et un drapeau pour les mots immédiats, que l'on n'a pas encore abordé. La macro END_TO_NEXT termine le mot en appelant NEXT.
Ainsi, pour le mots Forth définis par du code assembleur, il ne reste apparent que le code lui-même, encadré par les macros.
J'ai une seconde macro qui permet de définir des mots exécutés par « DOCOL », c'est-à-dire des mots écrits comme une série de pointeurs vers d'autres mots.
La boucle principale devient :
DEFINE_FORTH_WORD MAIN_LOOP, TEST, 0
.word READ_JOY_SAFE_word_cfa
.word TEST_word_cfa
.word POST_LOGIC_word_cfa
.word DO_SEMI_word_cfa
Moving Forth, épisode 5
Avançons dans la lecture de la série d'articles Moving Forth avec la cinquième partie.
L'auteur présente le code source d'un ANSI Forth, CamelForth, pour les processeurs Z80, 8051 et 6809. Passons. Ce qui m'intéresse le plus, c'est la notion de « kernel ». À partir de quel ensembles de mots de base écrits peut-on construire un Forth complet ? Et surtout, combien de mots doit-on implémenter en assembleur avant de construire le reste par dessus ?
C'est une question que je me posais dès le premier article dans la conclusion. Je me disais que pour faciliter les choix d'implémentation, il serait intéressant d'avoir un minimum de mots implémentés en assembleur.
Cependant, ce dont nous prévient l'article, c'est que même s'il est théoriquement possible de se limiter à 13 primitives, dans la pratique, faire ce choix entraînera un Forth peu véloce.
L'auteur propose une liste de critères pour décider si un mot doit être implémenté en assembleur ou en Forth :
- la base arithmétique, logique et mémoire le sont. Cela semble évident.
- si le mot est difficile à écrire en Forth (ou de manière trop complexe), alors il devrait être en assembleur.
- si le mot est très souvent utilisé, il devrait être en assembleur.
- si le code assembleur est plus compact (ou vraiment plus efficace) que le code Forth, il devrait être en assembleur.
- si la logique du mot est complexe, il devrait être en Forth.
Avec ces critères, l'auteur arrive à une liste de 70 primitives dans son implémentation. Cela donne une idée d'échelle.
Il décrit enfin une boucle de développement pour l'implémentation d'un Forth. Je suis déjà parti sur la mienne, mais cela est intéressant à regarder.
L'auteur implémente toute sa base en premier, puis assemble et corrige les erreurs. Puis écrit du code qui affiche un premier caractère une fois l'initialisation complète. Puis il écrit un mot entièrement en Forth et vérifie le fonctionnement de NEXT, DOCOL et ;S/EXIT.
Enfin, il implémente les branchements et DODOES globalement en même temps qu'avoir un interpréteur interactif rudimentaire afin de tester ses mots.
Globalement, c'est similaire à ce que je suis en train de faire. J'ai écrit un octet en mémoire plutôt qu'un caractère à l'écran, car c'était plus simple. Mais l'idée est la même.
De plus, je teste de manière automatique, et je compte bien continuer. Même si on va voir que ça va se compliquer un peu.
Conclusion et prochain épisode
J'ai à présent une liste de mots de deux types différents formant un dictionnaire. Ce que je voudrais maintenant est afficher un message à l'écran. J'ai déplacé et changé le message écrit par le squelette de projet pour me donner un modèle, et je veux à présent faire la même chose, mais dans la boucle Forth.
J'ai commencé quelques essais et des questions se posent sur la granularité des mots. Je vais devoir implémenter le mot EMIT. Celui-ci aura besoin de conserver une position à l'écran. Pour cela, je vais avoir besoin de variables.
Pour transformer ces variables en adresse mémoire, je peux soit tout écrire en assembleur (ce que je fais probablement faire dans un premier temps), mais aussi en profiter pour implémenter quelques mots de traitement arithmétique.
Bref, à la prochaine !