Les variables locales avec eFORTH
publication: 18 mars 2023 / mis à jour 18 mars 2023
Introduction
Le langage FORTH traite les données essentiellement par la pile de données. Ce mécanisme très simple offre une performance inégalée. A contrario, suivre le cheminement des données peut rapidement devenir complexe. Les variables locales offrent une alternative intéressante.
Le faux commentaire de pile
Si vous suivez les différents articles et exemples FORTH, vous avez noté les commentaires
de pile encadrés par (
et )
. Exemple:
\ add two unsigned values, leave sum and carry on stack : um+ ( u1 u2 -- sum carry ) \ here the definition ;
Ici, le commentaire ( u1 u2 -- sum carry )
n'a absolument aucune
action sur le reste du code FORTH. C'est un pur commentaire.
Quand on prépare une définition complexe, la solution est d'utiliser des variables
locales encadrées par {
et }
. Exemple:
: 2OVER { a b c d } a b c d a b ;
On définit quatre variables locales a, b, c et d.
Les mots {
et }
ressemblent aux mots (
et )
mais n'ont pas du tout le même effet. Les codes placés entre {
et }
sont des variables locales. Seule contrainte: ne pas utiliser de noms de variables qui pourraient être
des mots FORTH du dictionnaire FORTH. On aurait aussi bien pu écrire notre exemple comme ceci:
: 2OVER { varA varB varC varD } varA varB varC varD varA varB ;
Chaque variable va prendre la valeur de la donnée de pile dans l'ordre de leur dépôt sur la pile de données. ici, 1 va dans varA, 2 dans varB, etc..:
--> 1 2 3 4 ok 1 2 3 4 --> 2over ok 1 2 3 4 1 2 -->
Notre faux commentaire de pile peut être complété comme ceci:
: 2OVER { varA varB varC varD -- varA varB } ......
Les caractères qui suivent --
n'ont pas d'effet. Le seul intérêt est
de rendre notre faux commentaire semblable à un vrai commentaire de pile.
Action sur les variables locales
Les variables locales agissent exactement comme des pseudo-variables définies
par value
. Exemple:
: 3x+1 { var -- sum } var 3 * 1 + ;
A le même effet que ceci:
0 value var
: 3x+1 ( var -- sum )
to var
var 3 * 1 +
;
Dans cet exemple, var
est défini explicitement par value
.
On affecte une valeur à une variable locale avec le mot to
ou +to
pour incrémenter le contenu d'une variable locale. Dans cet exemple, on rajoute une variable
locale result
initialisée à zéro dans le code de notre mot:
: a+bEXP2 { varA varB -- (a+b)EXP2 } 0 { result } varA varA * to result varB varB * +to result varA varB * 2 * +to result result ;
Est-ce que ce n'est pas plus lisible que ceci?
: a+bEXP2 ( varA varB -- result )
2dup
* 2 * >r
dup *
swap dup * +
r> +
;
Voici un dernier exemple, la définition du mot um+
qui additionne
deux entiers non signés et laisse sur la pile de données la somme et la valeur
de débordement de cette somme:
\ add two unsigned values, leave sum and carry on stack
: um+ { u1 u2 -- sum carry }
0 { sum }
cell for
aft
u1 $100 /mod to u1
u2 $100 /mod to u2
+
cell 1- i - 8 * lshift +to sum
then
next
sum
u1 u2 + abs
;
Voici un exemple plus complexe, la réécriture DUMP
en exploitant des variables locales:
\ locals variables in DUMP: \ START_ADDR \ first address to dump \ END_ADDR \ latest address to dump \ 0START_ADDR \ first address for dump loop \ LINES \ number of lines for dump loop \ myBASE \ current numeric base internals : dump ( start len -- ) cr cr ." --addr--- " ." 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ------chars-----" 2dup + { END_ADDR } \ store latest address to dump swap { START_ADDR } \ store START address to dump START_ADDR 16 / 16 * { 0START_ADDR } \ calc. addr for loop start 16 / 1+ { LINES } base @ { myBASE } \ save current base hex \ outer loop LINES 0 do 0START_ADDR i 16 * + \ calc start address for current line cr <# # # # # [char] - hold # # # # #> type space space \ and display address \ first inner loop, display bytes 16 0 do \ calculate real address 0START_ADDR j 16 * i + + c@ <# # # #> type space \ display byte in format: NN loop space \ second inner loop, display chars 16 0 do \ calculate real address 0START_ADDR j 16 * i + + \ display char if code in interval 32-127 c@ dup 32 < over 127 > or if drop [char] . emit else emit then loop loop myBASE base ! \ restore current base cr cr ; forth
L'emploi des variables locales simplifie considérablement la manipulation de
données sur les piles. Le code est plus lisible. On remarquera qu'il n'est
pas nécessaire de pré-déclarer ces variables locales, il suffit de les désigner
au moment de les utiliser, par exemple: base @ { myBASE }
.
ATTENTION: si vous utilisez des variables locales dans une
définition, n'utilisez plus les mots >r
et r>
, sinon vous
risquez de perturber la gestion des variables locales. Il suffit de regarder la décompilation
de cette version de DUMP
pour comprendre la raison de cet avertissement:
: dump cr cr s" --addr--- " type s" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ------chars-----" type 2dup + >R SWAP >R -4 local@ 16 / 16 * >R 16 / 1+ >R base @ >R hex -8 local@ 0 (do) -20 local@ R@ 16 * + cr <# # # # # 45 hold # # # # #> type space space 16 0 (do) -28 local@ j 16 * R@ + + CA@ <# # # #> type space 1 (+loop) 0BRANCH rdrop rdrop space 16 0 (do) -28 local@ j 16 * R@ + + CA@ DUP 32 < OVER 127 > OR 0BRANCH DROP 46 emit BRANCH emit 1 (+loop) 0BRANCH rdrop rdrop 1 (+loop) 0BRANCH rdrop rdrop -4 local@ base ! cr cr rdrop rdrop rdrop rdrop rdrop ;
Legal: site web personnel sans commerce / personal site without seling