Site logo

Triceraprog
La programmation depuis le Crétacé

  • Forth sur 6502, épisode 14 ()

    Forth et les nombres

    À sa création, Forth ne manipule que les nombres entiers. C'est suffisant pour son usage. Et un nombre se stocke dans une cellule de données, généralement (et dans le choix d'implémentation de ce Forth sur 6502) une cellule de 16 bits pour un Forth sur processeur 8 bits.

    Par la suite, les nombres « double » seront ajoutés, des nombres stockés sur deux cellules donc. Mais là où la plupart des langages de haut niveau utilisent les mêmes symboles arithmétiques quel que soit le type de donnée numérique, voire même font de la coercition (adapte les types de données pour pouvoir les manipuler ensemble), Forth n'a rien de tout cela : une addition de deux nombres « double » n'utilise pas le même symbole que l'addition de deux nombres simples.

    Par exemple

    4 6 + .      \ fait la somme de deux nombres simples (et l'affiche)
    4. 6. D+ D.  \ fait la somme de deux nombres doubles (et l'affiche)
    

    Le point dans un nombre indique un nombre double. Ce point peut d'ailleurs être n'importe où, ce qui est une bizarrerie pour des habitués de langages plus modernes. Ainsi 40.00, 4.000 ou 4000. représentent tous le nombre double 4000... Troublant.

    De même pour les implémentations de nombres flottants, qui sont d'ailleurs souvent dans les implémentations historiques soit inexistants, soit sous forme d'ajout optionnel.

    Pour cette implémentation sur 6502 pour Famicom, je vais me contenter, au moins dans un premier temps, de traiter les nombres simples. Il sera toujours possible d'ajouter les nombres doubles si besoin, mais... le besoin ne me semble pas évident pour le moment.

    Une question de bases

    Dans le monde des ordinateurs, depuis un bon moment, c'est la base 2 qui fait référence. Un nombre stocké dans une machine est fait de 0 et de 1, si vous êtes en train de lire cet article, je ne dois certainement rien vous apprendre.

    Je ne vous apprends pas grand chose de plus en vous disant que de manière habituelle, les humains contemporains dans leur grande majorité comptent en base 10, avec des chiffres allant de 0 à 9.

    Forth est capable d'analyser un nombre sous forme de chaîne de caractères en n'importe quelle base allant de 0 à 16. Et même d'écrire ces nombres à l'écran. En interne, ces nombres sont bien entendu en binaire et, on l'a vu plus haut, sont stockés naturellement dans une cellule de données, 16 bits pour cette implémentation.

    Il est donc nécessaire d'avoir un mot Forth qui prend une chaîne de caractères représentant un nombre et qui la transforme en ce même nombre en base 2. Ainsi que l'inverse, prendre un nombre interne en base 2 et le transformer en suite de caractères représentant ce même nombre dans une base donnée.

    Le premier mot est NUMBER, le second est . (point). Techniquement, point fait trop de choses : il affiche un nombre sur l'écran, il ne fait pas que la conversion, mais on va le garder en tête pour le moment. Et uniquement en tête car l'objectif courant est toujours le même, interpréter la ligne HEX 7FF FF C! afin de valider l'interpréteur grâce à un point d'arrêt dans le harnais de test. Et . n'est pas nécessaire pour cela.

    HEX est l'équivalent de 16 BASE !, en supposant que la base actuelle est décimale. De manière similaire DECIMAL est l'équivalent de A BASE !, en supposant que la base actuelle est hexadécimale. La raison première de l'existence de ces deux mots est d'ailleurs, à mon avis, de ne pas se soucier de la base actuelle pour alterner entre les deux bases les plus usuelles.

    Il nous faudra donc une variable BASE, ce qui est trivial.

    D'une chaîne vers du binaire

    Reste donc le vrai gros morceau. En Fig-Forth, NUMBER a pour signature ( addr - d ), c'est-à-dire qu'à partir d'un pointeur vers une chaîne précédée de sa longueur, sa représentation en nombre « double » est poussée sur la pile. Cependant, comme cette implémentation n'a pas de nombre double, je choisis pour signature ( addr - n ).

    C'est un choix assez fort : une implémentation des nombres doubles dans le futur nécessitera une adaptation de tous les appels à NUMBER. Mais vraiment, je ne vois pas ce futur dans ce Forth pour Famicom que j'imagine (famous last words ?).

    NUMBER est généralement un mot en Forth qui utilise (NUMBER) mais aussi plein de mots que je n'ai pas encore implémentés, comme DIGIT. C'est un mot qui ne sert qu'à la conversion des nombres dans un code source, donc sans besoin critique de performance. Voilà un source adapté aux seuls nombres simples :

    : NUMBER      \ ( addr - n )
      0           \ accumulateur pour le résultat
      SWAP        \ pointeur de chaîne (addr) au-dessus de la pile
      DUP 1+ C@   \ prend le premier caractère de la chaîne
      2D = DUP >R \ regarde s'il est égal au signe - (moins) et duplique le résultat sur la pile de retour
      +           \ s'il y avait un signe moins, le pointeur de chaîne est avancé d'un (1)
    
      (NUMBER)    \ appel de (NUMBER) avec la chaîne ajustée
      DROP        \ ignore l'adresse de retour de (NUMBER) (2)
    
      R>          \ récupère l'existence d'un signe moins depuis la pile de retour
      IF
        MINUS     \ oppose le signe du nombre s'il y avait un signe moins en début de chaîne
      ENDIF
      ;
    

    (1) Ce recalage peut sembler étrange, car il place le pointeur de la chaîne à analyser non pas sur le premier caractère mais une adresse avant. Puis (NUMBER), juste après, commence avant toute chose à avancer le pointeur d'une position. Pourquoi ne pas l'y positionner tout de suite ?

    Cela permet à (NUMBER) de fonctionner avec des chaînes préfixées par la taille. (NUMBER), contrairement à NUMBER, ne fait pas de traitement de signe négatif, ni, dans le cas où cela est implémenté, de traitement de nombre double... ou presque.

    C'est en fait assez étrange car (NUMBER) requiert la présence d'un accumulateur sur la pile avant son appel, même si ce n'est pas indiqué dans la signature. Il requiert en fait même la présence de deux accumulateurs lorsque les nombres doubles sont implémentés, par une acrobatie entre NUMBER et (NUMBER) pour traiter les deux parties séparées par le ..

    Toute cette partie a été simplifiée ici, mais par curiosité, allez voir une implémentation complète, c'est étonnant.

    (2) Dans l'implémentation avec les nombres doubles, l'adresse permet de vérifier si un . (point) est présent, et si c'est le cas, boucler pour traiter la deuxième partie du nombre. Ici, l'adresse ne nous intéresse pas.

    : (NUMBER)  \ ( addr1 - n addr2 )
      BEGIN
        1+      \ avance de 1 le pointeur de chaîne
        DUP >R  \ copie cette adresse sur la pile de retour (1)
    
        C@      \ met le caractère pointé sur la pile
        BASE @  \ récupère la base courante
        DIGIT   \ transforme ce chiffre alphanumérique en représentation numérique
        \ ici, la pile contient 1 (VRAI) ou 0 (FAUX) suivi 
        \ du le chiffre en représentation machine dans le cas VRAI
        \ DIGIT a consommé le caractère et la BASE
    
      WHILE      \ si DIGIT a renvoyé VRAI (sinon, va au REPEAT)
        SWAP     \ récupère l'accumulateur (2)
        BASE @   \ ainsi que la base
        U* DROP  \ pour les multiplier (3)
        +        \ ajoute le résultat à l'accumulateur
        R>       \ récupération de l'adresse de chaîne avant nouvelle boucle
      REPEAT
      R>         \ remise sur la pile de l'adresse de chaîne
                 \ après son analyse (pointe donc après le nombre)
      ;
    

    (1) la pile de retour sert parfois, dans certains mots, comme pile temporaire supplémentaire à la pile des paramètres. Cela arrive lorsqu'il y a trop de paramètres à gérer sur la pile des paramètres. Il est bien entendu indispensable que le mot remette tout en place avant de se terminer, puisque les informations pour revenir au mot appelant sont présentes dans la pile de retour.

    (2) comme indiqué dans le commentaire sur NUMBER, (NUMBER) part du principe, même si ce n'est pas indiqué par sa signature, qu'il existe un accumulateur initialement positionné à zéro sur la pile. Techniquement, la signature de (NUMBER) devrait plutôt être ( 0 addr1 - n addr2 )

    (3) U* renvoie un nombre double à partir de la multiplication de deux nombres simples ( n1 n2 - d ). Il faut donc oublier la partie haute avec un DROP. U* fait spécifiquement une multiplication non signée, contrairement à *. Je garde la signature et la sortie avec un nombre double même si l'implémentation ne le supporte pas plus, car la routine de multiplication fait de toute façon le calcul, et pour éviter de surprendre avec une signature non usuelle.

    Les mots manquants

    Pour implémenter NUMBER et (NUMBER), je vais avoir besoin des mots suivants :

    Des mots de manipulation simples :

    • SWAP ( n1 n2 - n2 n1 ) : inverse les positions des deux éléments les plus en haut de la pile.
    • DUP ( n - n n ) : duplique le mot au sommet de la pile.
    • C@ ( addr - n ) : met sur le sommet de la pile l'octet présent à l'adresse donnée (pour rappel, @ (fetch) met sur le sommet de la pile la cellule présente à l'adresse donnée, donc deux octets).

    Un peu d'arithmétique et de logique :

    • 1+ ( - ) : ajoute 1 à la valeur présente au sommet de la pile.
    • U* ( n1 n2 - d ) : voir plus haut, effectue une multiplication non signée, avec en résultat un nombre double.
    • MINUS ( n1 - n2 ) : place sur la pile l'opposé du nombre qui y était présent.
    • + ( n1 n2 - n3 ) : effectue l'addition de deux nombres.
    • = ( n1 n2 - b ) : compare deux nombres et place 1 (vrai) sur la pile s'ils sont égaux, 0 sinon.

    De la manipulation de pile de retour :

    • >R ( n - ) : envoie le nombre au sommet de la pile vers la pile des retours.
    • R> ( - n ): récupère depuis la pile des retours la valeur au sommet et la pousse sur la pile des paramètres.

    Pour le besoin des tests, j'ajoute R, qui copie le haut de la pile des retours sur la pile des paramètres. Cela permet de vérifier avec >R R R> que la valeur sur la pile des paramètres a été dupliquée. C'est un DUP en plus complexe. Mais comme >R et R> touchent à la pile des retours, et donc au noyau de l'interpréteur, j'ai trouvé ça moins risqué pour les tests.

    Une variable :

    • BASE : variable qui contient la base numérique actuelle.

    Et enfin DIGIT ( c n1 - n2 b ) : qui convertit le caractère c selon la base n1 en nombre n2 si cela est possible. Dans ce cas, b est égal à 1, dans le cas contraire, b est égal à 0 et il n'y a rien d'autre de mis sur la pile. Et puisque ça ne coûte pas plus cher, la base supportée ira jusqu'à la base 36 (avec toutes les lettres de l'alphabet).

    Tous ces mots seront implémentés en assembleur, puis NUMBER et (NUMBER) seront implémentés en mots Forth.

    Pour passer le temps

    Il n'y a pas beaucoup de choses à montrer dans ces deux derniers articles. C'est beaucoup d'implémentation de fond. Alors en attendant, voici ce à quoi ressemble la sortie du framework de tests après implémentation des tests mais avant l'implémentation des mots du bloc arithmétique et logique :

    Failed tests:
      - 1+ increments TOS: Cannot resolve CFA for word: 1+
      - = returns 0 on inequality: Cannot resolve CFA for word: =
      - + adds two numbers: Cannot resolve CFA for word: +
      - MINUS opposes the TOS: Cannot resolve CFA for word: MINUS
      - U* unsigned-multiplies: Cannot resolve CFA for word: U*
      - = returns 1 on equality: Cannot resolve CFA for word: =
    

    Quelle aventure...

    Tout est implémenté et prêt pour NUMBER, cela sera pour la prochaine fois. Qui sait ? Si tout s'aligne parfaitement, peut-être que INTERPRET suivra dans la foulée !


  • Forth sur 6502, épisode 13 ()

    Les mots nécessaires à l'interprétation

    À partir de ce qui a été établi dans l'article précédent, il est à présent temps d'implémenter les mots nécessaires directement ou indirectement pour INTERPRET.

    HERE

    HERE est un mot qui indique quelle est la prochaine adresse libre dans l'espace de travail de l'interpréteur Forth. Il met en fait sur la pile le contenu d'une variable nommée DP qui pointe vers cette adresse. Cet emplacement se situe après le dictionnaire, et donc va se déplacer au fur et à mesure de l'ajout des mots.

    L'implémentation est simple, cependant, cela signifie aussi de s'intéresser à l'emplacement de travail. Actuellement, on travaille sur la zone de 2ko de RAM disponible sur la Famicom. C'est probablement suffisant pour quelques tests. Le linker permet de savoir quel est le premier octet libre après la zone BSS (variables non initialisées). C'est vers là que je fais pointer DP lors de l'initialisation du système.

    Avec une cartouche contenant de la RAM supplémentaire CPU, il sera possible de déplacer cette zone de travail.

    LATEST

    Trouver un mot dans le dictionnaire a besoin de connaître l'adresse du dernier mot ajouté, afin de remonter toute la chaîne de mots définis. J'ai déjà une variable que j'utilisais jusqu'à maintenant uniquement dans les tests, pour vérifier la présence d'une chaîne valide de dictionnaire. Il suffit donc de rendre publique cette variable.

    WORD

    Un mot un peu moins simple à implémenter... mais surtout comment le tester ? Avec INTERPRET implémenté ça serait facile mais justement, c'est ce que je suis en train d'implémenter. Avec mon harnais de tests en lua, voici l'idée : injecter du code Forth en RAM et y router, grâce à un breakpoint judicieusement placé, le chaînage d'instructions du Forth (assuré par next). La suite d'instructions se termine par une boucle infinie le temps d'effectuer les relevés de tests.

    C'est un peu acrobatique, mais ça fonctionne. Du moins tant que je ne teste rien en rapport avec le PPU.

    Et je me retrouve à écrire, pour faciliter l'écriture des tests, une sorte de mini INTERPRET (en mode compilation) en LUA :

        local cells = {}
        for _, item in ipairs(self.thread) do
            if type(item) == "string" then
                table.insert(cells, resolveCFA(item))
            else
                table.insert(cells, item & 0xFFFF)
            end
        end
    

    Ce qui permet de transformer en thread Forth une table LUA contenant quelque chose de ce type :

    {"LIT", 0x1234, "LIT", 0x5678, "DROP"}
    

    Et par la même occasion, je peux vérifier que LATEST, HERE et DP renvoient les valeurs attendues !

    Et à propos du choix sur la condition d'arrêt de la boucle d'interprétation, j'ai finalement choisi que ce soit lorsque WORD n'a plus rien à parser dans le buffer d'entrée. Dans ce cas là, il laisse dans HERE un mot de longueur nulle, qui sera le signal pour la boucle d'interprétation de s'arrêter.

    COUNT

    Ce mot prend l'adresse d'une chaîne préfixée par sa longueur, tel que renvoyé par WORD par exemple, et met sur la pile l'adresse des données puis la longueur. Cela permet d'être traité par d'autres mots. Il faut bien entendu veiller à ce que l'adresse reste valide. Dans le contexte d'interprétation, ce contexte doit rester valide jusqu'à trouver le mot ou le transformer en nombre. Comme les mots sont traités un par un et ne génèrent pas de nouveau mot dans le dictionnaire, c'est bon.

    Pas de difficulté particulière pour ce mot très simple.

    CAPITAL

    Puis vient CAPITAL, un mot qui met une chaîne décrite sur la pile en majuscules. Le but ici est de rendre la recherche de mots dans le dictionnaire non sensible à la casse. Là encore, pas de difficulté particulière : il suffit de parcourir la chaîne sur la longueur spécifiée et de transformer les lettres trouvées.

    (FIND)

    Ce mot est le mot qui sert à trouver un mot dans le dictionnaire. Il est utilisé par -FIND, qui s'occupe aussi de récupérer le mot et le mettre en forme.

    Encore un mot que j'écris en assembleur. Il est appelé souvent et je n'ai pas encore tous les mots nécessaires pour l'implémenter : pour remonter la chaîne et trouver le LFA, il y a besoin d'un peu d'arithmétique. Je n'ai pas encore ces mots là.

    -FIND

    C'est le mot qui va chercher la prochaine chaîne valide, la met en majuscules puis cherche le mot. Ici, j'ai tous les mots, je peux donc l'écrire en Forth. Cependant, avec mes macros actuelles, je ne peux pas définir un mot Forth qui commence par un caractère spécial, car cela génère automatiquement des labels et ceux-ci seraient invalides.

    Tout comme je l'avais fait pour les mots écrits en assembleur, j'ajoute donc une macro qui permet de donner un nom « interne au compilateur » à ces mots valides en Forth.

    Et voici son code, qui utilise tous les mots implémentés précédemment ! Ça prend forme :

    : -FIND
        BL WORD             \ cherche le mot se terminant par un délimiteur espace dans le TIB
        HERE COUNT CAPITAL  \ passe ce mot en majuscules
        HERE LATEST (FIND)  \ cherche à partir de LATEST et laisse le résultat sur la pile
        ;
    

    On remarque que -FIND a la même sortie sur pile que (FIND), puisque c'est le dernier mot appelé, et que lui-même n'ajoute rien à, ni ne retire rien de la pile.

    À noter : si TIB ne contient plus de mot, WORD laisse une chaîne de taille nulle à HERE, qui ne sera pas trouvée par (FIND). Il faudra donc vérifier, lorsqu'un mot n'est pas trouvé, si par hasard ce n'était pas le mot de taille nulle.

    C'est ici que l'astuce des premiers Forth avait un mot de taille nulle qui provoquait la sortie de la boucle d'interprétation. Cette astuce était mentionnée dans le précédent article, avec ce qu'en pensait la co-autrice de Forth. C'est en suivant son conseil que je n'ai pas suivi cette voie.

    CFA

    Ce mot donne le CFA d'un mot à partir d'un PFA. -FIND donne le PFA et EXECUTE a besoin du CFA, il faut donc transformer l'un en l'autre. Le passage de l'un à l'autre est très simple dans cette implémentation : il suffit de soustraire 2 à l'adresse.

    EXECUTE

    Ce mot prend un CFA et lance l'exécution correspondant à ce CFA. Pas de difficulté particulière, il suffit d'appeler NEXT mais avec le CFA pris depuis la pile.

    Pour tester le mot, il me faut un mot simple avec un effet simple à vérifier. N'importe quel mot simple qui met une valeur sur la pile fera l'affaire. Pourquoi pas LATEST. En prenant le CFA de LATEST via le harnais de test, puis en faisant EXECUTE, je dois retrouver l'adresse de LATEST sur la pile.

    Allez presque !

    Presque tous les mots pour l'implémentation de INTERPRET sont là. La plupart étaient simples ; il reste cependant un gros morceau. Voici mon objectif de code pour INTERPRET pour le moment :

    : INTERPRET
      BEGIN
        -FIND IF ( WORD FOUND )
          CFA EXECUTE
        ELSE
          HERE C@
          IF
            HERE NUMBER
          ELSE
            EXIT
          ENDIF
        ENDIF
      AGAIN ;
    

    Je rappelle que seul le mode d'interprétation est pris en compte, pas le mode de compilation, pas encore.

    BEGIN, IF, ELSE, ENDIF, AGAIN et EXIT sont des mots immédiats qui fonctionneront quand le mode compilation sera actif. Pour le moment, puisque je code ces mots directement avec les CFA, via des macros assembleurs, je n'ai besoin que de 0BRANCH.

    Il reste aussi un C@ qui est le pendant en octet de @, qui sera trivial à implémenter si le code reste le même.

    Mais pour l'objectif qui est toujours de pouvoir exécuter HEX 7FF FF C!, le plus difficile, c'est NUMBER. Transformer des nombres écrits comme des chaînes de caractères en représentation interne, dans n'importe quelle base, ça représente un peu de code.

    Cela sera donc le sujet du prochain épisode.


  • Forth sur 6502, épisode 12 ()

    La reprise

    L'écriture de cet article a commencé le 3 mars 2026... et je le reprends le 9 juillet de la même année. Heureusement que j'avais déjà laissé pas mal de notes car la reprise est difficile !

    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 exécuter la ligne Forth suivante : HEX 7FF FF C!.

    Voilà à quoi ressemble INTERPRET (avec un nommage FIG-Forth) avec uniquement le mode interprétation directe :

    : 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 a 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ées 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 en 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 discussion 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 vocabulaires. 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.


  • Compression de données, la vidéo ()

    Après ma série de quatre articles sur la compression de données sans pertes commencée l'année dernière, j'ai eu envie de faire une petite vidéo illustrative de ces informations. C'est chose faite, avec un angle assez similaire : donner une intuition, une compréhension générale, de comment fonctionnent ces algorithmes. Mais cette fois avec des images animées, ce que je trouve complémentaire dans les articles.

    Pour rappel, les article sont les suivants :

    Après avoir écrit un premier script incluant la partie ZX0, j'ai finalement décidé de retirer cette partie afin de rester dans la compréhension globale. Parler de ZX0, c'est rentrer dans le détail d'implémentation, ça fait intervenir de nombreux concepts. Ça alourdissait beaucoup la vidéo.

    Et voici la vidéo :


  • TextooM, un jeu pour CP/M ()

    Une fois Micreversi terminé, et avant de revenir sur Family Forth, j'ai eu envie de faire un petit détour par une idée qui me trottait dans la tête depuis un bon moment. Depuis le moment du live de la restauration Micral N où on avait affiché « DOOM » en BASIC pour affirmer : Doom tourne sur Micral N. C'était une blague bien entendu, mais pourquoi pas continuer la blague.

    L'idée était donc de faire un Doom façon aventure textuelle, initialement pour Micral N. Mais après réflexion, comme ce n'est pas une machine très courante et puisque mon émulateur n'est pas très pratique à utiliser, je me suis dit qu'une version CP/M serait plus accessible. Et aussi plus facile à produire.

    Et voici ainsi TextooM, un jeu d'aventure textuelle pour CP/M qui s'inspire du premier niveau de Doom, E1M1. C'est plus un jouet qu'un vrai jeu, il ne faut pas en attendre beaucoup. Mais il y a des secrets à trouver, avec un pourcentage à la fin, et il est possible de rusher, ou de tenter le nettoyage complet du niveau (qui n'est pas si simple).

    De quoi amuser 15 minutes...

    TextooM n'est pas le premier jeu de ce type. Au début du développement, j'ai cherché si cela avait été déjà fait et, oui, cela a été fait. En 1996 même ! Ce jeu est Foom et utilise le moteur de fiction interactive TADS. N'ayant pas envie d'entreprendre un portage de TADS, et Foom ayant beaucoup de texte, peut-être beaucoup trop pour une machine CP/M, j'ai décidé de faire mon propre moteur, plus simple, et de faire un jeu plus court. Ah, et en français.

    Le code source du projet est disponible sur GitLab. Et je vous fournis ici l'exécutable pour CP/M.

    Écran de jeu de TextooM


Page 1 / 28 (suivant) »

Tous les tags

3d (15), 6502 (16), 6809 (1), 8bits (1), Affichage (24), AgonLight (2), Altaïr (1), Amstrad CPC (1), Apple (1), Aquarius (2), ASM (33), Atari (1), Atari 800 (1), Atari ST (2), Automatisation (4), BASIC (31), BASIC-80 (4), C (3), Calculs (1), Canon X07 (1), CDC (1), Clion (1), cmake (1), Commodore (1), Commodore PET (1), Compression (5), CP/M (1), CPU (1), Debug (5), Dithering (2), Divers (1), EF9345 (1), Émulation (7), Famicom (12), Forth (17), Game Jam (1), Hector (3), Histoire (1), Hooks (4), Huffman (2), i8008 (1), Image (17), Jeu (17), Jeu Vidéo (4), Livre (1), Logo (2), LZ (2), LZ77 (2), Machine virtuelle (2), Magazine (1), MAME (1), Matra Alice (3), MDLC (7), Micral (2), Motorola (1), MSX (1), Musée (2), Nintendo Switch (1), Nombres (3), Optimisation (1), Outil (1), Outils (3), Pascaline (1), Peertube (1), PHC-25 (2), Photo (2), Programmation (22), Python (1), RLE (2), ROM (15), RPUfOS (7), Salon (1), SC-3000 (1), Schéma (5), Synthèse (15), Tortue (1), Triceraprog (1), VG5000 (62), VIC-20 (1), vidéo (4), Z80 (23), z88dk (1), ZX0 (1)

Les derniers articles

Forth sur 6502, épisode 14
Forth sur 6502, épisode 13
Forth sur 6502, épisode 12
Compression de données, la vidéo
TextooM, un jeu pour CP/M
Micreversi, un micro-jeu pour Canon X07
Comparaisons 8 bits en Z80
z80dezasm, désassembler et commenter le Z80
Adressage 6502, un récapitulatif
Forth sur 6502, épisode 11

Atom Feed

Réseaux