Forth est naturellement un langage qui est souvent utilisé dans un environnement interactif qui est à la fois un interpréteur et un compilateur. Il est aussi parfois accompagné d'un éditeur qui permet d'enregistrer des pages de code, afin de les sauvegarder, de les recharger et de les exécuter.
Le Hector HRX a bien tout ça, et cela était probablement très agréable dans les conditions d'époque. Enfin, peut-être pas lorsque l'on n'avait que le lecteur de cassette intégré. En effet, le système puissant des écrans charge et sauve les pages automatiquement au besoin. Pratique lorsque la mémoire de masse est à accès direct, comme une disquette. Mais avec une cassette, il faut souvent avancer, rembobiner avec de bonnes chances d'écraser des données.
Pour le développement de Picthorix, j'ai souvent utilisé le mode interactif pour bien comprendre le fonctionnement de certains mots, pour tester des idées, pour vérifier le fonctionnement de certaines parties de mon code. Mais hors de question d'utiliser en continu le mode natif, j'aurais perdu trop de temps en manipulations et en erreurs.
Donc, comme souvent, j'ai réfléchi à un petit environnement de développement à base d'éditeur et d'émulateur sur PC.
Formatage des écrans
Le Hector HRX peut avoir en mémoire 10 écrans simultanés. Chaque écran contient 798 caractères. Chaque caractère est donc précieux. Mais formatter son code pour le rendre lisible et ajouter des commentaire est très précieux aussi, surtout dans un langage que l'on ne maîtrise pas.
J'ai donc fait le choix d'avoir en source un fichier texte de forme libre et d'avoir une moulinette qui compacte le code pour mettre le maximum de code dans un écran. Il existe aussi le mot Forth -->
qui permet de lancer l'exécution de l'écran suivant numériquement, la moulinette peut donc ajouter ce mot de transition à la fin de chaque écran, sauf le dernier.
En pratique, il y a quelques pièges dans lesquels je suis tomber. Le premier est que la définition d'un mot ne peut pas commencer sur un écran et continuer dans le suivant. Il aurait fallu avoir un peu d'analyse du code Forth pour faire ce découpage correctement. C'est un piège que j'ai contourné de manière tout à fait manuelle : je place les -->
à la main dans le code source pour séparer les screens. Pas le plus agréable, je me suis souvent retrouvé avec des screens trop grands qui ne chargeaient pas correctement, mais pas assez par rapport au temps de développement que cela m'aurait pris.
Second piège, que j'ai mis un moment à comprendre. Parfois, mes affichages de chaînes de caractères étaient décalées, comme si des espaces étaient ajoutées en début de chaîne. J'ai mis un moment à comprendre que c'était parce que la mot de début de chaîne ."
se trouvait en fin de ligne, mais pas en dernière caractère, et que le premier mot affiché se trouvait à la ligne suivante. Comme les écrans ont une largeur fixe, des espaces étaient ajoutés mécaniquement en début de chaîne.
J'ai corrigé ça rapidement en revenant à la ligne lors de la détection d'un mot ."
. Cela fait perdre de l'espace, mais pas trop. Ça passe...
Dernier piège... un développement un peu fait rapidement. La moulinette n'est pas très solide et se plante parfois lorsque la ligne source est « trop » longue. Je n'ai pas analysé pourquoi. J'ai contourné le problème en coupant les lignes trop longues dans le source lorsque le bug apparaissait. Celui-ci provoque un saut au prochain screen pour une raison qui m'échappe, mais qui serait probablement simple à comprendre avec du temps pour le débugger. Je n'ai pas pris ce temps.
L'éditeur
Le code source est édité dans Visual Code, mais techniquement, il n'y a rien de spécial. Je ne l'ai pas spécifiquement customisé, à part une petite colorisation syntaxique.
L'émulateur
Comme d'habitude, j'utilise MAME pour son scripting Lua qui a un accès au debugger. J'ai fais évoluer le script déjà utilisé dans des précédents projets (description initiale ici). Il a fallu bien entendu adapter les points d'arrêt pour l'automatisation et injecter les écrans préalablement converti en format binaire en mémoire.
L'injection des écrans directement en mémoire est un peu fragile. En effet, Forth ajuste des variables lorsque l'on créé et on manipule des écrans. J'ai tenter de forcer ces variables à des valeurs connues en fonction des écrans injectés, mais sans vraiment de succès. Je me retrouvais avec un environnement extrêmement instable. J'ai cependant repéré qu'en évitant d'utiliser le premier écran, le système se comportait bien. Tant pis pour la perte d'un écran.
Une fois les écrans injectés, je lance la compilation en passant l'émulateur en mode rapide, et j'obtiens ainsi un compilateur. La nature du Forth fait qu'à la fin du processus, j'ai une image du jeu compilé en mémoire à un emplacement tout à fait déterminé.
À partir de là, soit je peux lancer le jeu pour les tests, soit tester certains mots. Je peux aussi sauvegarder la mémoire pour la recharger plus tard.
Comme au bout d'un moment, j'ai rempli tous les screens, j'ai eu besoin d'une deuxième série d'écran à compiler à la suite. Pour scripter cela, le dernier écran de la première série termine en écrivant à un endroit précis de la mémoire et très éloigné de l'espace utilisé. Le script Lua détecte cette écriture et injecte la nouvelle série d'écran, puis lance une nouvelle compilation.
Cela aurait pu être étendu à plus de séries d'écrans, il reste encore beaucoup de place en mémoire avec le jeu au complet. Cependant, comme il s'agit de le mettre sur cassette à la fin, il faut aussi veiller à ce que le temps de chargement soit de durée acceptable.
Toujours automatiser !
C'est mon conseil récurrent, et je le répète à nouveau. La dernière étape pour créer un jeu distribuable est donc de dumper la mémoire contenant tous les mots compilés qui forment le jeu. À partir du debuggeur, il faut donc spécifier une plage mémoire à sauvegarder et une adresse du mot de démarrage. Pour cela, en fin de compilation de la dernière série de screen, j'affichais les informations (taille, adresse de début) à l'écran. Restait à utiliser ces informations dans le debugger pour sauvegarder la mémoire.
Le jour où je voulais publier le jeu, impossible d'avoir un dump qui fonctionne. J'ai passé la soirée à analyser un bug. Forcément, un vendredi soir, la fatigue de fin de semaine n'aide pas. Je suis allé me coucher sans trouver. Le lendemain, je relance et je trouve immédiatement (une bonne nuit de sommeil, ça aide). La taille que j'avais recopié à la main pour le dump n'était pas la bonne, de quatre octets...
J'ai donc ajouté au script Lua le dump automatique, en allant chercher l'information sur la taille laissée par la fin de la compilation en mémoire.
Conclusion
Chaque operation manuelle répétée est une occasion de perdre son temps...
À part ça, pas grand chose de neuf. Les outils sont disponibles dans les sources du jeu. Cependant, à la date d'écriture de cet article, avoir un Hector HRX qui fonctionne bien sur MAME nécessite quelques modifications que je n'ai pas encore proposé pour intégration. Mais cela sera fait quand j'aurais nettoyé les changements et vérifié que je n'ai pas cassé d'autres machines.