Structures de données pour eFORTH
publication: 3 mars 2023 / mis à jour 3 mars 2023
Préambule
eFORTH est une version 32 bits du langage FORTH. Ceux qui ont pratiqué FORTH depuis ses débuts ont programmé avec
des versions 16 bits. Cette taille de données est déterminée par la taille des éléments déposés sur la pile de données.
Pour connaître la taille en octets des éléments, il faut exécuter le mot cell. Exécution de ce mot pour eFORTH:
cell . \ display 4
La valeur 4 signifie que la taille des éléments déposés sur la pile de donnéees est de 4 octets, soit 4x8 bits = 32 bits.
Avec une version FORTH 16 bits, cell empilera la valeur 2. De même, si vous utilisez une version 64 bits, cell
empilera la valeur 8.
Les tableaux en FORTH
Nous allons commencer par des structures assez simples: les tableaux. Nous n'aborderons que les tableaux à une ou deux dimensions.
Tableau de données à une dimension
C'est le type de tableau le plus simple. Pour créer un tableau de ce type, on utilise le mot create suivi
du nom du tableau à créer:
create temperatures
34 , 37 , 42 , 36 , 25 , 12 ,
Dans ce tableau, on stocke 6 valeurs: 34, 37....12. Pour récupérer une valeur, il suffit d'utiliser le mot @ en incrémentant
l'adresse empilée par temperatures avec le décalage souhaité:
temperatures \ push addr on stack 0 cell * \ calculate offset 0 + \ add offset to addr @ . \ display 34 temperatures \ push addr on stack 1 cell * \ calculate offset 0 + \ add offset to addr @ . \ display 37
On peut factoriser le code d'accès à la valeur souhaitée en définissant un mot qui va calculer cette adresse:
: temp@ ( index -- value ) cell * temperatures + @ ; 0 temp@ . \ display 34 2 temp@ . \ display 42
Vous noterez que pour n valeurs stockées dans ce tableau, ici 6 valeurs, l'index d'accès doit toujours être dans l'intervalle [0..n-1].
Mots de définition de tableaux
Voici comment créer un mot de définition de tableaux d'entiers à une dimension:
: array ( comp: --| exec: index create does> swap cell * + ; array myTemps 21 , 32 , 45 , 44 , 28 , 12 , 0 myTemps @ . \ display 21 5 myTemps @ . \ display 12-- addr )
Dans notre exemple, nous stockons 6 valeurs comprises entre 0 et 255. Il est aisé de créer une variante de array pour
gérer nos données de manière plus compacte:
: arrayC ( comp: --| exec: index create does> + ; arrayC myCTemps 21 c, 32 c, 45 c, 44 c, 28 c, 12 c, 0 myCTemps c@ . \ display 21 5 myCTemps c@ . \ display 12-- addr )
Avec cette variante, on stocke les mêmes valeurs dans quatre fois moins d'espace mémoire.
Lire et écrire dans un tableau
Il est tout à fait possible de créer un tableau vide de n éléments et d'écrire et lire des valeurs dans ce tableau:
arrayC myCTemps
6 allot \ allocate 6 bytes
0 myCTemps 6 0 fill \ fill this 6 bytes with value 0
32 0 myCTemps c! \ store 32 in myCTemps[0]
25 5 myCTemps c! \ store 25 in myCTemps[5]
0 myCTemps c@ . \ display 32
Dans notre exemple, le tableau contient 6 éléments. Avec eFORTH, il y a assez d'espace mémoire pour traiter des tableaux bien plus grands, avec 1.000 ou 10.000 éléments par exemple. Il est facile de créer des tableaux à plusieurs dimensions. Exemple de tableau à deux dimensions:
63 constant SCR_WIDTH
16 constant SCR_HEIGHT
create mySCREEN
SCR_WIDTH SCR_HEIGHT * allot \ allocate 63 * 16 bytes
mySCREEN SCR_WIDTH SCR_HEIGHT * bl fill \ fill this memory with 'space'
Ici, on définit un tableau à deux dimensions nommé mySCREEN qui sera un écran virtuel de 16 lignes et 63 colonnes.
Il suffit de réserver un espace mémoire qui soit le produit des dimensions X et Y du tableau à utiliser. Voyons maintenant comment gérer ce tableau à deux dimensions:
: xySCRaddr { x y -- addr }
SCR_WIDTH y *
x + mySCREEN +
;
: SCR@ ( x y -- c )
xySCRaddr c@
;
: SCR! ( c x y -- )
xySCRaddr c!
;
char X 15 5 SCR! \ store char X at col 15 line 5
15 5 SCR@ emit \ display X
Exemple pratique de gestion d'écran
Voici comment afficher la table des caractères disponibles:
: tableChars ( -- )
base @ >r hex
128 32 do
16 0 do
j i + dup . space emit space space
loop
cr
16 +loop
256 160 do
16 0 do
j i + dup . space emit space space
loop
cr
16 +loop
cr
r> base !
;
tableChars
Voici le résultat de l'exécution de tableChars:

Ces caractères sont ceux du jeu ASCII MS-DOS. Certains de ces caractères sont semi-graphiques. Voici une insertion très simple d'un de ces caractères dans notre écran virtuel:
$db dup 5 2 SCR! 6 2 SCR! $b2 dup 7 3 SCR! 8 3 SCR! $b1 dup 9 4 SCR! 10 4 SCR!
Voyons maintenant comment afficher le contenu de notre écran virtuel. Si on considère chaque ligne de l'acran virtuel comme chaîne alphanumérique, il suffit de définir ce mot pour afficher une des lignes de notre écran virtuel:
: dispLine { numLine -- }
SCR_WIDTH numLine *
mySCREEN + SCR_WIDTH type
;
Au passage, on va créer une définition permettant d'afficher n fois un même caractère:
: nEmit ( c n -- )
for
aft dup emit then
next
drop
;
Et maintenant, on définit le mot permettant d'afficher le contenu de notre écran virtuel. Pour bien voir le contenu de cet écran virtuel, on l'encadre avec des caractères spéciaux:
: dispScreen
0 0 at-xy
\ display upper border
$da emit $c4 SCR_WIDTH nEmit $bf emit cr
\ display content virtual screen
SCR_HEIGHT 0 do
$b3 emit i dispLine $b3 emit cr
loop
\ display bottom border
$c0 emit $c4 SCR_WIDTH nEmit $d9 emit cr
;
L'exécution de notre mot dispScreen affiche ceci:

Dans notre exemple d'écran virtuel, nous montrons que la gestion d'un tableau à deux dimensions a une application concrète. Notre écran virtuel est accessible en écriture et en lecture. Ici, nous affichons notre écran virtuel dans le fenêtre du terminal. Cet affichage est loin d'être performant. Mais il peut être bien plus rapide sur un vrai écran OLED.
Gestion de structures complexes
eFORTH dispose du vocabulaire structures. Le contenu de ce vocabulaire permet de définir des structures de données complexes.
Voici un exemple trivial de structure:
structures
struct YMDHMS
ptr field >year
ptr field >month
ptr field >day
ptr field >hour
ptr field >min
ptr field >sec
Ici, on définit la structure YMDHMS. Cette structure gère les pointeurs >year
>month >day >hour >min et >sec.
Le mot YMDHMS a comme seule utilité d'initailiser et regrouper
les pointeurs dans la structure complexe. Voici comment sont utilisés ces pointeurs:
create DateTime
YMDHMS allot
2022 DateTime >year !
03 DateTime >month !
21 DateTime >day !
22 DateTime >hour !
36 DateTime >min !
15 DateTime >sec !
: .date ( date -- )
>r
." YEAR: " r@ >year @ . cr
." MONTH: " r@ >month @ . cr
." DAY: " r@ >day @ . cr
." HH: " r@ >hour @ . cr
." MM: " r@ >min @ . cr
." SS: " r@ >sec @ . cr
r> drop
;
DateTime .date
On a défini le mot DateTime qui est un tableau simple de 6 cellules 32 bits consécutives. L'accès à chacune
des cellules est réalisée par l'intermédiaire du pointeur correspondant. On peut redéfinir l'espace alloué de notre structure
YMDHMS en utilisant le mot i8 pour pointer des octets:
structures
struct cYMDHMS
ptr field >year
i8 field >month
i8 field >day
i8 field >hour
i8 field >min
i8 field >sec
create cDateTime
cYMDHMS allot
2022 cDateTime >year !
03 cDateTime >month c!
21 cDateTime >day c!
22 cDateTime >hour c!
36 cDateTime >min c!
15 cDateTime >sec c!
: .cDate ( date -- )
>r
." YEAR: " r@ >year @ . cr
." MONTH: " r@ >month c@ . cr
." DAY: " r@ >day c@ . cr
." HH: " r@ >hour c@ . cr
." MM: " r@ >min c@ . cr
." SS: " r@ >sec c@ . cr
r> drop
;
cDateTime .cDate \ display:
\ YEAR: 2022
\ MONTH: 3
\ DAY: 21
\ HH: 22
\ MM: 36
\ SS: 15
Dans cette structure cYMDHMS, on a gardé l'année au format 32 bits et réduit toutes les autres valeurs à
des entiers 8 bits. On constate, dans le code de .cDate, que l'utilisation des pointeurs permet un accès
aisé à chaque élément de notre structure complexe....
Définition de sprites
On avait précédemment définit un écran virtuel comme tableau à deux dimensions. Les dimensions de ce tableau sont définies par deux constantes. Rappel de la définition de cet écran virtuel:
63 constant SCR_WIDTH
16 constant SCR_HEIGHT
create mySCREEN
SCR_WIDTH SCR_HEIGHT * allot \ allocate 63 * 16 bytes
mySCREEN SCR_WIDTH SCR_HEIGHT * bl fill \ fill this memory with 'space'
L'inconvénient, avec cette méthode de programmation, c'est que les dimensions sont définies dans des constantes, donc en dehors du tableau. Il serait plus intéressant d'embarquer les dimensions du tableau dans le tableau. Pour ce faire, on va définir une structure adaptée à ce cas:
structures
struct cARRAY
i8 field >width
i8 field >height
i8 field >content
create myVscreen \ define a screen 8x32 bytes
32 c, \ compile width
08 c, \ compile height
myVscreen >width c@
myVscreen >height c@ * allot
Pour définir un sprite logiciel, on va mutualiser très simplement cette définition:
: sprite: ( width height -- ) create swap c, c, \ compile width and height does> ; 2 1 sprite: blackChars $db c, $db c, 2 1 sprite: greyChars $b2 c, $b2 c, blackChars >content 2 type \ display content of sprite blackChars
Voici comment définir un sprite 5 x 7 octets:
5 7 sprite: char3
$20 c, $db c, $db c, $db c, $20 c,
$db c, $20 c, $20 c, $20 c, $db c,
$20 c, $20 c, $20 c, $20 c, $db c,
$20 c, $db c, $db c, $db c, $20 c,
$20 c, $20 c, $20 c, $20 c, $db c,
$db c, $20 c, $20 c, $20 c, $db c,
$20 c, $db c, $db c, $db c, $20 c,
Pour l'affichage du sprite, à partir d'une position x y dans la fenêtre du terminal, une simple boucle suffit:
: .sprite { xpos ypos sprAddr -- }
sprAddr >height c@ 0 do
xpos ypos at-xy
sprAddr >width c@ i * \ calculate offset in sprite datas
sprAddr >content + \ calculate real address for line n in sprite datas
sprAddr >width c@ type \ display line
1 +to ypos \ increment y position
loop
;
0 constant blackColor
1 constant redColor
4 constant blueColor
10 02 char3 .sprite
redColor fg
16 02 char3 .sprite
blueColor fg
22 02 char3 .sprite
blackColor fg
cr cr
Résultat de l'affichage de notre sprite:

Voilà. C'est tout. J'espère que le contenu de cet article vous aura donné quelques idées intéressantes que vous aimeriez partager...
Legal: site web personnel sans commerce / personal site without seling
