gForth, inmplémentation libre du langage de programmation Forth
FORTH est un langage ancien et assez unique en son genre, utilisant par exemple la notation polonaise inverse. La documentation est par ailleurs plutôt lacunaire, et semble-t-il inexistante en français sur la toile. Deux raisons suffisantes pour y consacrer une page, qui ne sera certainement jamais exhaustive.
En cours d'écriture depuis 2021.05.15 – derniers changements au 2021.06.06
Pour une présentation générale du langage, voir WP
Attention : les exemples, normalement tous fonctionnels, n'ont été testés qu'avec la version 0.7.3 de gForth. Il est cependant possible que des commandes inappropriées ayant rendu votre session gForth instable. Il est parfois bon de sortir de gForth avec bye et de le relancer.
0. Brève présentation1. Entiers et réels
1.1 Entiers simples |
2. Chaînes et fichiers
2.1 Affichage de caractères |
3. Mots, conditions et boucles
3.1 Définition de mots |
4. Mémoire, etc.
4.1 Adressage simple |
0. Brève présentation
Le langage Forth est un langage où le programmeur a accès à une pile, un objet informatique où l'on dépose des éléments, dans l'optique «premier entré, dernier sorti» (FILO : First In, Last Out). Sur cette page, le haut de la pile (souvent la dernière valeur entrée ou calculée) sera appelé P-0, FP-0… selon les cas, celles qui suivent P-1, P-2…
La pile, le plus souvent inaccessible aux programmeurs des langages de haut niveau, a toute son importance pour la Notation Polonaise Inverse (NPI), où l'opérateur suit les opérandes ; on parle de notation postfixée. Par exemple, pour additionner, on pose d'abord deux opérandes, et puis le signe + ; on affiche le résultat avec un point :
5 2 + . dépose d'abord 5 sur la pile, puis 2, tandis que le signe + prélève la quantité 2 de la pile pour l'ajouter à 5. Le point . prélève le résultat l'afficher. Voici comment évolue la pile à chacune des étapes (entre <crochets>, le nombre d'éléments sur la pile) :
état de la pile (visible avec .s) 5 <1> 5 2 <2> 5 2 + <1> 7 . <0> ( affiche 7 )
L'intérêt est plus manifeste si le calcul est plus complexe :
5 2 + 9 6 - * .
5 2 + dépose 5 et 2 sur la pile, + remplace 5 par le résultat 7 et supprime le 2
9 6 - dépose 9 et 6 sur la pile, - remplace 9 par le résultat 3 et supprime le 6
* multiplie les deux résultats 7 et 3 ; 21 remplace 7 et 4 est supprimé de la pile
. sort le résultat de la pile pour l'afficher en console.
Évolution de la pile dans le détail :
état de la pile (visible avec .s) 5 <1> 5 2 <2> 5 2 + <1> 7 9 <2> 7 9 6 <3> 7 9 6 - <2> 7 3 * <1> 21 . <0> (affiche 21)
La notation polonaise inverse est la façon la plus directe de s'adresser à un interpréteur ou à un processeur, beaucoup plus simple que print((5 + 2) * (9 - 6)) qui doit stocker les valeurs et les opérateurs en mémoire avant d'exécuter le code.
Tout ceci peut être réalisé dans l'interpréteur lancé avec gforth dans un terminal. Il est également possible d'éditer un fichier et de le faire exécuter par gforth :
- éditer un fichier essai contenant 5 2 + 9 6 - * . cr bye
- saisir dans la console gforth essai
cr ajoute un retour à la ligne (imprime l'octet 10 de fin de ligne Unix)
bye quitte l'interpréteur pour revenir au terminal
Si le fichier n'a pas prévu la séquence cr bye, il est possible de l'inclure dans la commande en console (-e pour évaluate) :
gforth essai -e cr bye
Commentaires
Un fichier de programmation forth peut contenir des commentaires, qui n'interfèrent pas avec le programme :
\ commentaire à droite de la barre inverse jusqu'à la fin de la ligne
( commentaire finissant avec ) Attention : une ) annule plusieurs (
{ -- commentaire à droite du double trait d'union dans une structure, se terminant avec }
Attention : il faut toujours laisser l'espace entre les signes utilisés \ ( ) { } -- :
- ( à revoir ) est un commentaire.
- (à revoir) sont les deux «mots» (à et revoir) probablement non définis
1. Nombres entiers et réels
Tout nombre saisi se retrouve sur une pile («stack» en anglais informatique). Les opérateurs prélèvent les opérandes sur cette pile et y retourne les résultats.
1.1 Entiers simples
Sur cette page, la pile des entiers est dénommée P. Le «haut» de la pile est P-0, qui peut contenir de la dernière valeur entrée ou du résultat du dernier calcul. L'avant-dernière valeur est P-1, celle d'encore avant P-2, etc.
.s affiche l'état de la pile : le nombre total des valeurs qui s'y trouvent entre < et > suivi de tout au plus neuf valeurs du haut de la pile
. affiche la valeur de P-0 et l'en supprime
n l .r aligne le nombre n à droite sur l rangs
n l u.r aligne le nombre non signé n à droite sur l rangs
Sur un système actuel (64 bits), un entier signé peut aller de -9 223 372 036 854 775 808 à 9 223 372 036 854 775 807, ce qui semble confortable. Un léger dépassement en dessous de cette fourchette donnera un nombre positif, dans l'autre sens un nombre négatif.
Non signé, les valeurs d'un entier simple peut aller de 0 à 18 446 744 073 709 551 615. Tout dépassement de la borne haute équivaut à mod 18 446 744 073 709 551 616 .
nil dépose 0 sur la pile
Les opérateurs suivant opèrent sur les deux dernières valeurs de la pile, écrase l'avant-dernière avec le résultat et supprime la dernière. On peut aussi considérer qu'un opérateur «consomme» deux valeurs en P-1 et P-0, puis dépose le résultat sur P :
+ addition
- soustraction
* multiplication
/ division entière : 19 4 / donne 4 ; -5 4 / donne -2
mod reste de la division entière (modulo) : 19 4 mod donne 3 ; -5 4 mod donne 3
/mod modulo suivi du résultat de la division : 19 4 mod donne 3 4
Attention le langage Forth ne se préoccupe pas des dépassements de capacité : si l'addition ou la multiplication dépasse la limite des nombre entiers, il en résulte un nombre négatif ou tronqué.
min sélectionne la plus basse parmi deux valeurs
max sélectionne la plus haute parmi deux valeurs
umin sélectionne le plus bas de deux entiers non signés
.4 ajoute 4 à la valeur sur la pile
Opérateurs sur trois valeurs
*/ multiplie deux nombres et division entière par un troisième : 3 7 5 */ équivaut à 21 5 / et donc 4
*/mod multiplie deux nombres, avant /mod avec un troisième : 7 3 4 */mod équivaut à 21 /mod 4 et donc 1 5
Opérateurs sur une seule valeur :
negate inverse le signe d'une valeur
abs valeur absolue d'une valeur
sgn renvoie -1 si nombre négatif, 1 si positif, 0 si nul
1+ incrémente une valeur numérique (additionne 1)
1- décrémente une valeur numérique (soustrait 1)
2* double la valeur
2/ divise la valeur par deux (9 2/ . retourne 4 : arrondit par défaut)
: 2/+ 1 - 2/ 1+ ; divise par deux, arrondi par excès : 9 2/ . retourne 5. En enlevant 1 à un nombre pair ou impair, le résultat sera inférieur de 1 au résultat escompté, ce que l'on corrige après la division.
Entiers non signés
Les octets peuvent représenter 256 valeurs (2^8). Les octets non signés (toujours positifs) vont de 0 à 255 et croissent avec les nombres binaires (128 suit 127) ; les octets signés croissent de 0 à 127, mais les nombres binaires dont le bit de poids fort est 1 sont réservés aux négatifs, de -128 à -1 :
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
---|---|---|---|---|---|---|---|---|---|---|
⋮ | ||||||||||
127 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 127 | |
128 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -128 | |
⋮ | ||||||||||
255 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 |
Il en va de même des mots simples, codés en 64 bits, où les non signés codent les entiers de 0 à 18446744073709551615, et les signés de -9223372036854775808 à 9223372036854775807. Pour travailler avec les non-signés (unsigned) :
u. sort la valeur en P-0 et l'affiche comme nombre non signé.
Il n'existe pas de u+, u- u*, etc. parce que les opérations +, -, *, etc. donnent les mêmes résultats, que les nombres soient signés ou non signés
1.2 Travail sur la pile
Une des particularité de Forth est l'utilisation explicite de la pile, mémoire de travail où sont déposées les valeurs tôt ou tard utilisées comme opérande ou paramètres pour des commandes ou fonctions, appelées mots en Forth.
Cette section ne s'occupe que de la pile des entiers simples. Voyez les entiers doubles et les réels pour les mots qui leur sont adaptés.
Entiers simples
. affiche (et consomme) la valeur de la pile
.s affiche le nombre de valeur sur la pile et les neuf dernières valeurs de la pile
depth dépose le nombre de valeurs de la pile sur la pile
Duplication
dup duplique la dernière valeur de la pile :
état de la pile (visible avec .s) 1 2 <2> 1 2 dup <3> 1 2 2
?dup duplique la dernière valeur de la pile si elle est non nulle
1 2 <2> 1 2 ?dup <3> 1 2 2 0 <4> 1 2 2 0 ?dup <4> 1 2 2 0
over duplique l'avant-dernière valeur sur la pile
1 2 3 <3> 1 2 3 over <4> 1 2 3 2
tuck (two back?) duplique la dernière valeur avant l'avant-dernière
1 2 3 <3> 1 2 3 tuck <4> 1 3 2 3
pick duplique sur la pile la valeur du rang désigné par la valeur du haut de la pile : 0 pick = dup, 1 pick = over, n pick duplique P-n…
3 2 1 0 1 2 3 4 5 <3> 1 2 3 4 5 3 <4> 1 2 3 4 5 3 pick <4> 1 2 3 4 5 2
Suppression
drop supprime la dernière valeur de la pile
1 2 3 <3> 1 2 3 drop <2> 1 2
nip supprime l'avant-dernière valeur
1 2 3 <3> 1 2 3 nip <2> 1 3
Rotations
swap intervertit les deux dernières valeurs
0 1 2 <3> 0 1 2 swap <3> 0 2 1
rot déplace l'avant-avant-dernière valeur sur la pile
0 1 2 3 <4> 0 1 2 3 rot <4> 0 2 3 1
-rot déplace la valeur de la pile avant l'avant-dernière valeur
0 1 2 3 <4> 0 1 2 3 rot <4> 0 3 1 2
roll déplace une valeur selon le rang de la pile (swap équivaut à 1 roll, rot à 2 roll)
1 2 3 4 5 <5> 1 2 3 4 5 3 <5> 1 2 3 4 5 3 roll <5> 1 3 4 5 2
1.3 Entiers doubles
Un entier double utilise la même pile que les entiers simples, et utilise de ce fait deux endroits sur la pile, le mot faible en P-1 et le mot fort en P-0. Pour un double renseigné n1 n2 sur la pile, le nombre est de n2 18446744073709551616 * n1 + , ou, en notation infixée P-0 * 18446744073709551616 + P-1
La limite des entiers doubles non signés est de 340.282.366.920.938.463.463.374.607.431.768.211.455, soit 3.4×1038.
d. sort P-0 et P-1 et fabrique le double pour l'afficher
nd l d.r aligne l'entier double nd à droite d'un espace de l caractères
nd l ud.r aligne l'entier double non signé nd à droite d'un espace de l caractères
d.s montre l'état de la pile de façon assez proche de .s
d.s [ 2 ] 00003 00005
ud. affiche le double sous sa forme non signée
Note : il ne suffit pas qu'un nombre dépasse les limites d'un entiers simple pour qu'il soit entré comme un double, il faut le terminer avec un point. Par ailleurs, les expressions 4.21 et 421. n'interprètent pas ces nombres comme étant des réels, mais suppriment le point et inscrivent 0 à l'emplacement suivant, ce qui permet de saisir de petits entiers doubles de façon économique : 4.21 dépose 421 0 sur la pile. IL est néanmoins possible de récupérer la place de ce point :
4.21 dpl @ .
…où dpl contient l'adresse en mémoire de cette place : 0 à la dernière place, 1 à l'avant dernière… et -1 si aucun point ne s'y trouve.
d+ additionne deux doubles (définis par deux fois deux valeurs de P). Cela équivaut à l'addition de P-3 et P-1 sur P-3 et de P-2 et P-0 sur P-2, P-1 et P-0 supprimés.
d- soustraction d'un double par un autre. Cela équivaut à soustrait P-1 à P-3 et P-0 à P-2
d2* multiplie un double par deux, ce qui n'équivaut pas nécessairement à la multiplication des deux dernières valeurs de P
d2/ division entière par deux d'un entier double
dmax retient le plus grand des deux doubles sur la pile
dmin retient le plus petit des deux doubles sur la pile
dnegate inverse un double
dabs rend la valeur absolue d'un double
Précision mixte
d s m+ additionne un entier double (15 0) et un simple (3). Le résultat est un double : 18 0
s1 s2 s3 */ permet à la multiplication de dépasser la limite des entiers simples, même si le résultat final doit être un simple
s1 s2 s3 */mod idem
s1 s2 m* le résultat est un double
u1 u2 um* idem pour les non-signés
d1 s2 u3 m*/ le résultat intermédiaire est un entier triple, le troisième opérande est non signé
ud u1 um/mod reste suivi du quotient, tout en non signé
d1 s1 fm/mod le dividende est double, le diviseur est simple, le reste et le quotient sont simples
d1 s1 sm/rem reste et quotient sont simples, le signe du diviseur doit être le même que celui du reste, ou nul
du s ud/mod division/modulo non signée d'un double par un simple, remet le reste (simple) suivi du quotient (double)
Travail sur la pile pour les entiers doubles
Entiers simples et doubles partagent la même pile, mais il est possible d'y bouger des valeurs par groupes de deux avec 2dup, 2over, 2tuck, 2drop, 2nip, 2swap et 2rot, qui considèrent des blocs de deux valeurs :
état de la pile 1 2 3 <3> 1 2 3 2dup <5> 1 2 3 2 3
2roll n'existe pas, mais on peut le définir (n désigne le rang par paires) :
: 2roll ( n -- x(2n +1), x(2n) --> P-1 P-0 ) 2 * 1+ \ double la longueur du saut et ajuste dup \ préparer deux roll 1+ roll \ premier roll plus long d'une unité swap roll ; \ doit repasser le paramètre du 2nd roll en haut de la pile
1.4 Bases numériques
Les entiers peuvent être exprimés en différentes bases.
hex passe en mode hexadécimal : 65 43 78 hex .s
decimal passe au mode décimal (par défaut)
Attention : en pentadécimal et au-dessus, 12e3 est interprété comme un nombre entier. Pour écrire un nombre réel sous une base numérique supérieure à 14, il faut ajouter un + : +12e3 ou 12e+3 ; c'est inutile si la mantisse ou l'exposant comporte un -
dec. affiche la valeur de la pile en décimal, quelle que soit la base d'affichage.
hex. affiche la valeur de la pile en hexadécimal, non signé
n l dec.r formate l'affichage d'un entier à droite d'un espace de l caractères
& et # préfixent une valeur écrite en décimal, le mode appris à l'école
$ et 0x préfixent une valeur écrite en hexadécimal, ayant les chiffres de0 à 9 et a à f pour les valeurs de 10 à 15
( a à f
% préfixe une valeur binaire, où ne sont tolérés que les 0 et 1
'A entre le rang ASCII du caractère qui suit l'apostrophe.
base est en fait une adresse où est contenue la base numérique en cours. Mais quelle que soit la base actuelle, base @ rendra toujours 10, qui vaut une paire en binaire, une huitaine en octal, la dizaine en décimal et une seizaine en hexadécimal. Pour vraiment connaître la base encours :
base @ dec. \ affiche en décimal le contenu de l'adresse "base"
Pour changer de base quelle que soit celle en cours :
&2 base ! passe en base binaire (inscrit 2 à l'adresse "base"
&8 base ! passe en base octale (chiffres de0 à 7)
&10 base ! passe en base décimale
&16 base ! passe en base hexadécimale
Les bases possibles vont de 2 à 32, où les chiffres vont de 0 à 9 et de a à v.
Note : seuls les entiers sont sensibles aux changements de base.
1.5 Nombres réels
Les nombres réels disposent d'une pile spéciale, nommée ci-après FP.
Toute expression saisie avec la syntaxe -0.0e-0 (n'importe quels nombre entier pour 0, les signes négatifs et le points sont optionnels) est ajoutée à FP. Exemples : 1e0, 2.4e5, 2e-5, -2.1e2 … qui vaudront 1. , 240000. , 0.00002. et -210. Il est illusoire de les y écrire de la façon 3.14 qui inscrit 314 et 0 sur la pile des nombres entiers.
f. sort l'expression du haut de la pile FP-0 pour l'affichage sous la forme 1., 210., -0.00005
fs. fait de même sous la forme scientifique 5.21000000000000E5
fe. fait de même sous la forme de l'ingénieur 521.000000000000E3 (exposant multiple de 3)
D'autre formatages de réels dans cette section
fdepth dépose le nombre de valeurs de FP sur P !
Opérateurs pour les réels
Les opérateurs pour les réels sont en général les mêmes que ceux sur les entiers, mais préfixés par f :
f+, f-, f*, f/, fmod, f** (exposant), fmin, fmax.
f2* double la valeur sur FP
f2/ divise par deux la valeur de FP
1/f inverse la valeur sur FP
fnegate remplace la valeur de FP par sa valeur opposée
fabs remplace la valeur de FP par sa valeur absolue
r1 r1 r3 f~ est vrai si r3 > | r2 - r1 |
r1 r1 r3 f~abs est vrai si r3 > | r2 - r1 | (nuance?)
r1 r1 r3 f~rel est vrai si r3 > |r2 -r1 | / | r2 + r1 | (test de l'erreur relative)
fround écrase FP-0 par son arrondi (.5 vers le nombre pair le plus proche)
floor et ftrunc écrasent FP-0 par son arrondi par défaut
Opérations mixtes
Les opérations qui suivent concernent un réel traité par un ou deux entier·s :
fm* multiplie un réel par un entier
fm/ divise un réel par un entier
fm*/ multiplie un réel par un entier et le divise par un autre
Exponentielles et logarithmes
f**2 met la valeur de FP au carré
fsqrt écrase la valeur de FP par sa racine carrée
fexp retourne l'exponentielle de base e d'un réel
fexpm1 la même réponse moins l'unité
fln retourne le logarithme naturel d'un réel
flnp1 augmente un réel de 1 avant d'en calculer le logarithme naturel
flog écrase la valeur de FP par son logarithme en base 10
falog écrase la valeur de FP par son exponentielle en base 10
Fonctions trigonométriques
Les fonctions trigonométriques n'existent que pour les nombres de FP. Elles écrasent la valeurs en FP-0, les données sont toujours fournies en radians.
pi dépose 3.141592653590E0 sur FP
fsin, fcos, ftan pour sinus, cosinus et tangente
fatan2 est la fonction qui d'après le sinus et le cosinus retrouve l'angle
fsincos écrase sur FP avec le sinus de la valeur et ajoute le cosinus
fasin , facos, fatan pour arcsin, arccos, arctan
fsinh , fcosh, ftanh pour les sinus, cosinus et tangente hyperbolique
fasinh, facosh, fatanh pour arcsinh, arccosh et arctanh
Travail sur la pile pour les réels
Les réels (floating) disposent d'une pile spéciale, qui nécessite de préfixer les mots d'un f : fdup, fdrop, fnip, fover, fpick, frot, fswap, ftuck
froll n'existe pas.
1.7 Conversions de type
Attention : le langage Forth n'avertit pas des dépassements de capacité, comme cela est possible de f > d > s
s>d transforme un entier simple en entier double, en fait ajoute un 0 sur P
d>s transforme un entier double en entier simple, en fait enlève P-0 comme le ferait drop
d>f déplace un entier double (deux valeurs) de P vers FP
f>d déplace un réel de FP en entier double (deux valeurs) vers P
s>f déplace un entier simple de P vers FP
f>s déplace un réel de FP vers P
1.8 Comparaisons
Les deux valeurs à comparer sont remplacées par la réponse sur la pile : -1 si la comparaison est vraie, et 0 si elle est fausse. Ces valeurs booléennes sont évaluée avec les mots conditionnel if ou while.
= teste l'égalité
<> teste l'inégalité des deux valeurs
< teste l'infériorité stricte de la première valeur
<= teste l'infériorité ou l'égalité de la première valeur
> teste la supériorité stricte de la seconde valeur
>= teste la supériorité ou l'égalité de la seconde valeur
within teste si un premier nombre se trouve entre deux autres, plus précisément dans la relation n2 <= n1 < n3
u=, u<>, u<, u<=, u> et u>= considèrent les entiers comme non signés : -3 5 u> . retourne -1
Suivent les comparateurs qui n'ont besoin que d'une valeur&nbs;:
0= teste la nullité d'une valeur
0< teste la négativité stricte d'une valeur
0<= teste la négativité ou nullité d'une valeur
0> teste la positivité stricte d'une valeur
0>= teste la positivité ou nullité d'une valeur
0<> teste la non nullité d'une valeur
Les opérateurs binaires de comparaison des nombres doubles sont d=, d<>, d<, d<=, d> et d>= .
Les tests d'un seul double sont d0= , d0<>, d0<, d0<=, d0>, d0>= .
Les opérateurs binaires de comparaison des nombres doubles non signés sont du=, du<>, du<, du<=, du> et du>=
Pour les nombres réels, ces relations de comparaison sont préfixées de f :
f<, f<=, f<>, f=, f>, f>=, f0<, f0<=, f0<>, f0=, f0> et f0>=
Attention : les réponses -1 (vrai) et 0 (faux) arrivent sur P, la pile des entiers.
1.9 Opérateurs logiques
true dépose -1 sur la pile
false dépose 0 sur la pile
Les opérateurs logiques permettent de combiner plusieurs booléens issus de comparaisons. Par exemple :
cdA | cdB | and | or | xor | |
---|---|---|---|---|---|
-1 | -1 | -1 | -1 | 0 | |
-1 | 0 | 0 | -1 | -1 | |
0 | 1 | 0 | -1 | -1 | |
0 | 0 | 0 | 0 | 0 |
- il faut que deux conditions (cdA et cdB) soit vérifiées (-1) pour que la résultante de cdA and cdB soit vraie ; les autres configurations ont pour résultat 0
- il faut que les deux conditions (cdA et cdB) soient invalidées (0) pour que la résultante de cdA or cdB soit fausse ; les autres configurations ont pour résultat -1 («ou» inclusif)
- il faut que les deux conditions (cdA et cdB) aient des résultats différents pour que la résultante cdA xor cdB soit vraie ; deux conditions en même temps vérifiées ou en même temps invalidées ont pour résultat 0 («ou» exclusif)
Vérifier si un nombre est multiple de 7 ou termine par 7 : 35 dup 7 mod 0= swap 10 mod 0= or
35 <1> 35 dup <> 35 35 7 <> 35 35 7 mod <> 35 0 0= <> 35 -1 swap <> -1 35 10 <> -1 35 10 mod <> -1 -1 0= <> -1 0 or <> -1
and | or | xor | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
12: | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | ||||||
10: | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | ||||||
8: | 1 | 0 | 0 | 0 | 14: | 1 | 1 | 1 | 0 | 6: | 0 | 1 | 1 | 0 |
Les opérateurs logiques permettent également de comparer les entiers bit à bit. :
and seuls survivent les bits communs : 12 10 and donne 8
or un seul bit par rangée suffit : 12 10 or donne 14
xor les bits communs sont positionnés à zéro : 12 10 or donne 6
Pour rendre une lettre majuscule : key 95 and emit
. key <1> 100 ( 11xxxxx rang ASCII d'une minuscule en binaire, p. ex. 100 ) 95 <2> 100 95 ( 1011111 en binaire ) and <1> 68 ( 10xxxxx "and" éteint le bit de rang 5 ) emit <0> ( affiche D, le caractère de rang ASCII 68 )
Note : on aurait pu écrire 32 - , mais cela aurait également affecté les majuscules, ce que ne fait pas 95 and . Voir également key.
3 | 0000000000000000000000000000000000000000000000000000000000000011 |
---|---|
-4 | 1111111111111111111111111111111111111111111111111111111111111100 |
invert inverse les bits : 3 invert donne -4. Les nombres positifs sont inscrits en mémoire de la façon suivante : …0 pour 0, …01 pour 1, …010 pour 2, …011 pour 3, etc. ; les nombres négatifs …111 pour -1, …1110 pour -2, …1101 pour -3, …1100 pour -4, etc.
lshift décale les bits de n rangs vers la gauche (multiplication par 2n)
rshift décale les bits de n rangs vers la droite (division entière par 2n)
11 | … | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | |
4 lshift | … | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
< < < < |
-51 | … | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | |
3 rshift | … | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 |
> > > |
2. Chaînes de caractères et fichiers
Les chaînes de caractères, contrairement aux nombres entiers et réels, prennent une place variable en mémoire, ce qui oblige à les traiter différemment des nombres. Il y a deux sortent de chaînes de caractères : celles qui contiennent leur longeur (leur nombre de caractères) dans leur premier octet, et les autres, où il faut garder leur longueur sous la main.
2.1 Affichage de caractères
emit permet l'affichage d'un seul caractère ASCII (jusqu'à 127) :
- 119 emit (affiche w) en passant le rang ASCII du caractère à afficher
- char w emit en écrivant simplement le caractère (= 'w emit)
- À l'intérieur d'une définition, il faut utiliser [char] (attention : les mots : x et : X sont équivalents) :
: x [char] X emit ;
toupper converti le rang ASCII d'une minuscule en majuscule.
char+ incrémente la valeur en pile de 1
char- idécrémente la valeur en pile de 1
char% dépose 1 1 sur la pile des entiers, soit deux fois la taille en mémoire d'un caractère ASCII
cr affiche un retour à la ligne
space affiche une espace
n spaces affiche n espaces
n zeros affiche n 0
page efface l'écran
’ dépose sur la pile le rang ASCII du caractère qui suit
bl dépose 32 sur la pile
bell devrait faire retentir un 'tink' (caractère de rang ASCII 7) si le système est configuré pour ce faire.
form renseigne le nombre de lignes et de colonne du terminal
colonne ligne at-xy positionne le curseur à la colonne et à la ligne précisées sur P
2.2 Affichage de chaînes
Un point suivi d'un guillemet double et d'une espace entre le guillemet et le début de la chaîne affiche cette chaîne :
." Les sanglots longs Des violons De l'automne"
s" Chanson d'automne" inscrit une chaîne en mémoire, dont l'adresse et la longeur sont retournées sur P.
: poeme s" Chanson d'automne" ; inscrit l'adresse et la longueur de la chaîne dans la constante poeme
poeme dépose dans la pile des entiers l'adresse et la longueur de la chaîne
type affiche la chaîne dont l'adresse et la longueur est en haut de la Pile, et consomme ces deux valeurs.
typewhite affiche le nombre d'espaces équivalent à la longueur de la chaîne.
En augmentant l'adresse de quatre unités et en diminuant la longueur de sept, l'affichage tronque les quatre premières lettres et les trois dernières :
poeme swap 4 + swap 7 - type affiche son d'auto
qqp n c blank écrase n caractères à partir à l'adresse qqp avec le caractère passé par l'octet représentant son rang ASCII :
s" Cos" \ inscrit la chaîne 'Cos' en mémoire ( adresse longueur ) 2dup \ duplique adresse et longueur 88 fill \ remplit la chaîne avec le caractère W (ASCII 88) cr type \ affiche la chaîne transformée sur une nouvelle ligne
qqp n blank écrase n caractères à partir de l'adresse qqp avec l'octet 32 (espace)
qqp n erase écrase n caractères à partir de l'adresse qqp avec l'octet 0
Il existe une autre façon d'inscrire une chaîne en mémoire en définissant un mot :
: wyatt c" Moon in June" ;
wyatt contient alors l'adresse en mémoire où commence la chaîne, avec comme premier octet la longueur de la chaîne, qui ne peut dépasser 255 caractères
wyatt dépose l'adresse de la chaîne sur P
count incrémente (+ 1) l'adresse en et ajoute sur P la longueur de la chaîne
type affiche la chaîne d'après l'adresse en P-1 et sa longueur en P-0
wyatt count type le tout en une fois
wyatt <1> 140315301717192 ( pose sur la pile l'adresse en mémoire de la chaîne count <2> 140315301717193 12 ( augmente l'adresse de la mémoire de 1 et lit la longueur ) type <0> ( Moon in June est affiché )
Note : lorsqu'on n'utilise pas count, il faut tenir compte du premier octet, qui ne fait pas partie de la chaîne.
wyatt 1+ contient le rang ASCII de la première lettre
wyatt 2 + c@ dépose sur la pile le rang ASCII de la seconde lettre
emit affiche la lettre correspondant au rang ASCII en P-0
wyatt 2 + 4 type affiche quatre lettres à partir de la deuxième
expect permet de réécrire une chaîne (limitée à sa longueur de départ) :
wyatt count 2dup type expect affiche puis attend une saisie de chaîne.
2.3 Entrées de caractères
key attend une touche, sans confirmation avec [enter] ; le code ASCII du caractère est transmis à la pile.
key? renvoie -1 sur la pile si un caractère est en attente dans le buffer clavier (une touche a été frappée mais n'a pu être utilisée)
ekey ekey>char renvoie un nombre et un booléen sur P. Si -1, le nombre est le rang ASCII de la touche&nsbp;; si 0, il s'agit le nombre corresond à une autre touche, à comparer avec les nombre que ces mnémoniques déposent sur P :
k-up, k-down, k-left, k-right
k-insert, k-delete, k-home, k-end, k-prior (PgUp), k-next (PgDn)
k1 à k12 et k-f1 à k-f12
accept permet la frappe de plusieurs touches à stocker dans un buffer de types…;
- pad (simple) :
cr ." Nom? " pad 20 accept (attente d'au plus 20 caractères ) ( saisir ) <1> n ( longueur de la chaîne saisie ) pad <2> n 140127294786475 ( adresse du pad ) swap <2> 140127294786475 n ( interversion ) type <2> 0 (les n caractères de la chaîne affichés )
- tib (plus complexe) :
create texte$ 100 allot ( création d'un buffer de 100 caractères ) texte$ 100 accept ( attente d'au plus 100 caractères ) ( saisir ) <1> n ( longueur de la chaîne réellement saisie ) texte$ <2> n 140194786475272 swap <2> 140194786475272 n type <2> 0 (les n caractères de la chaîne affichés )
Note : texte$ swap dump donne un tableau avec les valeurs à gauche et le texte à droite.
Attention : contrairement à une chaîne définie par c" Chanson d'automne", le buffer ne commence pas par l'octet de longueur de chaîne. La longueur du texte, qui peut dépasser 255, est envoyée sur la pile lors de la saisie.
Conversion nombre / chaîne
number et number? tentent de convertir une chaîne en nombre, à partir de son adresse en mémoire. Si la chaîne n'est pas un nombre valide, message d'erreur avec number ; number? renvoie le nombre et -1 (vrai) en cas de nombre valide, ou l'adresse et 0 (faux) sinon.
: nombre$ c" 443" ; nombre$ <1> 140808751927304 number <2> 443 0 2dup <0> nombre$ <1> 140808751927304 number? <1> 443 -1
2.4 Comparaisons de chaînes
s" chaine1" s" chaine2" compare classe deux chaînes : 0 si elles sont identiques, -1 si chaine1 est antérieure à chaine2, 1 dans le cas inverse
s" chaine1" s" chaine2" str< retourne -1 si chaine1 est antérieure, 0 sinon
str> n'est pas implémenté
s" chaine1" s" chaine2" string-prefix? est vrai si chaine2est le début de chaine1
s" chaine1" s" chaine2" search est vrai si chaine2est une partie de chaine1 ; l'adresse de chaine1 à partir de la position de chaine2 + longueur est sur la pile
s" abcdefghi" n /string enlève les n premiers caractères d'une chaîne
s" abcdef " n /-trailing supprime les espaces de fin de chaîne
( à suivre )
2.5 Unicode et iso8859
Les commandes ci-dessous commencent par x parce qu'elles concernent également les jeux de caractères étendus (extended), c'est-à-dire les valeurs de 128 à 255 pour les jeux de caractère iso8859 (latin1, latin15…) ou Windows-1252, ainsi que les caractères codés jusqu'à 4 octets des séquences UTF. Un caractère utilise un emplacement de 8 octets.
xchar-encoding est censé retourner une chaîne représentant le système d'encodage courant (gforth-0.7.0.pdf)
xkey attend un caractère étendu puis l'inscrit sur la pile
xemit interprète et affiche le caractère étendu dont le code est inscrit en haut de la pile
xc!+? xc@ xc@+ xchar+ xchar- xchar-history x-size x@+/string +x/string x\string-
À suivre…
2.5 Formatages de nombres
À ne pas confondre avec r de la pile des retours
Formatage d'entiers
Pour les bases, voir cette section.
Pour une sortie alignée à droite :
n l .r aligne le nombre n à droite sur l rangs
n l u.r aligne le nombre non signé n à droite sur l rangs
nd l d.r aligne l'entier double nd à droite d'un espace de l caractères
nd l ud.r aligne l'entier double non signé nd à droite d'un espace de l caractères
Pour formater un nombre, il faut veiller à ce qu'il soit double, puis garder en tête que l'on tient compte des chiffres en commençant par le rang le plus faible, c'est-à-dire le plus à droite.
- la définition est incluse entre <# et #> (ce n'est pas absolu, il existe également <<# et #>>)
- # représente un seul chiffre
- #s représente ce qu'il reste des chiffres
- char , hold ou 44 hold est une séquence pour insérer un caractère, ici une virgule décimale
- [char] , hold ou 44 hold est la même séquence dans une définition de mot
- bl hold ou 32 hold pour une espace
Par exemple, un nombre entier de grammes peut être converti en kilos de cette façon :
5230 0 <# # # # char g hold char K hold #s #> type 5230 0 <# char g hold char K hold # # # char , hold #s #> type 5230 0 <# char g hold char K hold # # # char , hold # # # #> type
La première ligne donne 5Kg230, la deuxième 5,230Kg et la troisième 005,230Kg, chaque # surnuméraire étant converti en 0 (mais s'il en manque, les chiffres des rangs les plus importants ne seront pas affichés).
Ces conversions gagnent à être incluses dans une définition de mot :
: g2kg s>d <# [char] g hold [char] K hold # # # [char] , hold #s #> ; 5230 g2kg type
sign permet de préciser le signe négatif, car le formatage semble n'utiliser que les entiers non signés
: signe ( n -- -n ) s>d \ transforme l'entier simple en double tuck \ met le mot de poids fort, contenant le signe, avant l'entier double dabs \ valeur absolue du mot double (risque de non-signé sinon) <# #s rot sign \ fait revenir le signe en haut de la pile, '-' si négatif #> ; -45 signe type
Pour forcer l'affichage du + quand le nombre est positif, et le signe en fin d'affichage :
: plex ( f -- formate+ ) s>d tuck dabs \ voir l'exemple ci-dessus <# rot 0< IF \ poids fort négatif [char] - hold \ affichage du 'moins' else [char] + hold \ affichage du 'plus' then #s #> ; cr 43 plex type cr -56 plex type
Note : la nuance entre <# … #> et <<# … #>> reste à préciser.
Formatage de réels
represent renvoie des indications sur la chaîne susceptible de représenter le nombre, en renvoyant trois entiers sur la pile des entiers :
-1.345566e4 s" <# # #s #> " represent cr .s <3> 5 -1 -1
Le premier représente le nombre de chiffres de la partie entière, le second -1 si le réel est négatif, le troisième semble toujours être -1.
f.rdp formate selon trois entiers :
- le nombre total de caractères (supérieur ou égal à 7)
- le nombre de décimales
- le nombre total de chiffres significatifs (inférieur au total -5)
Si la conversion ne peut avoir lieu, le réel est affiché sous sa forme exponentielle.
12 7 4 210e-5 f.rdp 0.0021000
f>str-rdp formate un réel (8 octets) selon
2.12e5 s" <# # #s #>" 9 3 3 f>str-rdp type
L'adresse et le nombre de caractère de la chaîne de formatage restent sur la pile. Si la conversion ne peut avoir lieu, le réel est affiché sous sa forme exponentielle.
f>buf-rdp est proche du précédent, si ce n'est qu'il envoie la chaîne formatée à une adresse, variable chaîne ou pad :
create qqp 16 allot ; 2.151e2 qqp 9 4 3 f>buf-rdp qqp 9 type
ou
2.151e2 pad 9 4 3 f>buf-rdp pad 9 type
…ce qui devrait afficher 215.1000
2.8 Fichiers sources
Pour inclure un fichier source, par exemple des définitions de mots :
s" mesmots.fth" included
Un synonyme est :
include mesmots.fth
Pour être sûr que le fichier ne soit pas inclut une deuxième fois :
s" mesmots.fth" required
Deux synonymes sont :
require mesmots.fth needs mesmots.fth
En résumé, included ou synonyme interprétera à chaque fois le fichier, required ou synonymes ne le fera que si le fichier n'a pas encore été interprété, par un included ou un required.
2.9 Fichiers de données
En désignant les fichiers par leur nom :
s" nom1" s" nom2" rename-file modifie le nom d'un fichier
s" dummy.txt" delete-file supprime définitivement un fichier, sans passer par la corbeille, et dépose 0 sur la pile en cas de succès.
s" nom.fic" slurp-file réserve en mémoire la place qu'il faut pour y écrire les données du fichier (texte ou binaire), en déposant adresse et nombre d'octets sur la pile
Pour les deux commandes suivantes, il est nécessaire de préciser un mode, qui peut être r/o (read-only), r/w (read&write) ou w/o (write-only) pour des fichiers de texte. Pour des fichiers binaires, susceptibles de comporter n'importe lequel des 256 octets possibles, il faut ajouter bin (binary).
r/o, r/w et w/o ne font que déposer 0, 2 ou 4 sur la pile, et l'éventuel bin augmenter de 1 la valeur préalable.
s" fichier.txt" w/o create-file crée un fichier texte disponible en écriture
s" fichier.bin" r/o bin open-file ouvre en lecture un fichier binaire déjà existant
create-file et open-file déposent sur la pile un identifiant de fichier (idf) sous la forme d'un entier non signé et une valeur de retour, toujours 0 si le fichier a bien été créé ou ouvert, ce qui permet, dans une définition de mot, de traiter conditionnellement le fichier ouvert, par exemple :
: lire ( fd boolean -- lit un fichier ou message d'erreur ) 0= if ( code d'acquisition des données ) else ." Le fichier n'a pu être ouvert" then ;
s" fichier" file-status dépose sur la pile 0, 2 ou 4 selon le mode d'ouverture possible, plus un 0 si cela s'est bien passé (fichier présent ; non nul si le fichier manque). Pour les systèmes UNIX, 2 (r/w) correspond à 6 ou 7 (rwx), 4 (w/o) à 2 ou 3 (-wx) et 0 (r/o) à 4 ou 5 (r-x) de la commande chmod (x étant le bit d'exécution, non évalué par file-status)
En désignant les fichiers par leur identifiant idf :
Le descripteur de fichier déposé sur la pile permet la manipulation d'un fichier ouvert. Rappel : il est suivi d'un mot de retour (0 si cela s'est bien passé), à utiliser ou jeter. Par ailleurs, les mots qui suivent consomment l' idf situé sur la pile, il faut le dupliquer pour le réutiliser :
idf flush-file permet, sans fermer le fichier, d'inscrire effectivement les données sur le disque, qui peuvent sinon rester dans une mémoire-tampon (buffer)
idf close-file ferme le fichier ; les modifications sont inscrites dans le fichier si la valeur retour sur la pile est nulle
idf file-size renvoie la longueur du fichier dans un entier double
n1 n2 idf resize-file redimensionne un fichier ouvert en écriture ; la nouvelle longueur doit être transmise avant idf sous la forme d'un entier double ; en cas d'allongement, les nouveaux octets sont nuls, ce qui rend le fichier «binaire».
s" fichier.txt" w/o open-file . \ affiche et supprime l'octet 0 10 0 \ 10 sous forme de mot double rot \ remet l'idf en haut de la pile resize-file . \ affiche et supprime l'octet 0 de réussite
fd slurp-fid réserve la place nécessaire et y copie du contenu d'un fichier (texte ou binaire) désigné selon son idf
fd file-position retourne un double entier contenant la position du curseur dans le fichier (0 0tant que rien n'a été lu)
key?-file dépose -1 sur la pile si au moins un caractère est encore à lire
key-file dépose sur la pile l'octet situé à la position du curseur et avance celui-ci à la position suivante
reposition-file repositionne le curseur à l'endroit désiré. L'offset par rapport au début du fichier est un double entier écrit avant l' idf
buffer idf read-file lit le fichier défini par idf et place les données en mémoire, dont l'adresse est située avant l' idf
s" chaine" idf write-file écrase le fichier avec la chaîne (mais garde ce qui dépasse), à partir du début (écriture effective avec flush-file ou close-file).
s" texte" idf write-line devrait écrire une ligne dans un fichier
car idf emit-file devrait écrire un caractère dans un fichier
( à suivre )
3. Mots, conditions et boucles
Les mots, conditions et boucles sont intimement liés en ce sens que les conditions et les boucles doivent impérativement se trouver dans un mot.
3.1 Définition de mots
On utilise «mot» plutôt que «fonction» en Forth. Une définition commence par : suivi du mot à définir, puis les commandes, le tout se termine par ;
Le mot est composé de tous les caractères voulus, et même + - * / @ # ! ( ) { } ; , . " ' … à condition d'être assemblés de façon originale (aucun mot de ne semble réservé au système, ce qui peut rendre la session gforth inutilisable).
: carre ( n1 -- n1 n1 * ) dup * ( duplication de la valeur en P-1, suivie de leur multiplication ) ; \ termine nécessairement la définition
On peut définir le mot cube à partir du mot carre :
: cube ( n1 -- carre * ) dup carre \ duplique une valeur et l'élève au carré * ; \ multiplie le carré par la première valeur
…ce qui est appelé factorisation: on sépare les problèmes comme on factorise l'expression x²+x-2 en ses deux facteurs x+2 et x-1. C'est plutôt conseillé pour des raisons de débogage et maintenance.
Ces deux définitions peuvent se coder plus brièvement, même si ce n'est pas conseillé :
: carre dup * ; : cube dup carre * ;
see cube permet de visualiser la définition du mot cube
words affiche une liste de tous les mots définis par le langage et par l'utilisateur
Il est possible d'utiliser des variables locales dans une définition de mot, permettant par exemple des manipulations plus audacieuses de la pile :
: rot4 ( n1 n2 n3 n4 -- n2 n3 n4 n1 ) { a b c d } b c d a ;
Les quatre derniers mots de la pile sont prélévés sur la pile avec { a b c d } et ensuite réécrits dans l'ordre b c d a sur la pile. Il est même possible de définir le mot de façon plus courte, ce qui suit -- entre { } étant considéré comme un commentaire.
: rot4 { a b c d -- b c d a } b c d a ;
Il est possible de sortir d'une définition de mot avec exit dans une structureif / then ou case /endcase :
: nombre case 0 of ." c'est nul" exit endof 1 of ." Un" exit endof 2 of ." Deux" endof 3 of ." Trois" exit endof 4 of ." Quatre" endof ." plus grand que quatre" endcase ." - C'est pair" ;
3.2 Condition if else then
Les définitions de mots permettent une structure conditionnelle, de type si condition alors code. Mais en Forth, ce s'écrit condition si code alors
: =43? ( n 43 = -- ) 43 = \ un booléen ( -1 ou 0 ) est déposé sur la pile if ." c'est bien quarante-trois" then \ doit finir la structure ; 42 =43?
Rien ne se passe si le nombre testé n'égale pas 43. Dans l'exemple qui suit, si les deux derniers termes de la pile sont égaux, vrai est affiché, sinon faux. Cette structure est ici écrite dans le mot egal :
: egal ( a b = -- "vrai" ; sinon "faux" ) = if ." vrai" \ affiche 'vrai' si a == b else ." faux" \ affiche 'faux' sinon then ; \ encore "then" ; "endif" est possible mais n'est pas standard
Tester 3 5 egal . , 4 4 egal . … et visualiser le mot avec see egal.
La séquence habituelle des langages comme BASIC ou python est if cdt then si_vrai (endif) ou if cdt then si_vrai else si_faux (endif)
En Forth, cela devient booléen if si_vrai then ou booléen if si_vrai else si_faux then , ce qui est moins intuitif, raison pour laquelle on remplace souvent then par endif , qui n'appartient pas à ANS Forth. En fait, il vaut mieux garder la vision «polonaise inverse» et considérer else comme un séparateur entre le vrai à gauche et l'alternative à droite :
condition SI plan_A | plan_B THEN
Attention :
- l'évaluation consomme les valeurs (une pour les test unaires 0=, 0<, etc. et deux pour les tests binaires = , <, etc.) pour fournir le booléen 0 ou -1 . Pour éviter cela, utiliser dup (évaluation d'une valeur) ou 2dup (de deux valeurs) en début de définition de mot.
- le booléen 0 ou -1 est lui-même consommé par if
Il est possible de d'imbriquer deux ou plusieurs structures conditionnelles :
: casse ( ascii -- casse ) dup 64 > over 91 < and if emit ." est une majuscule" else dup 96 > over 123 < and if emit ." est une minuscule" else dup 47 > over 58 < and if emit ." est un chiffre" else emit ." est une ponctuation ou caractère spécial" then then then ;
Cette écriture a plusieurs défauts : chaque if consomme une valeur sur la pile (d'où le dup) et la structure devient complexe. C'est la raison pour laquelle la structure suivante a été mise en place, mais seulement pour des cas de simples comparaisons.
3.3 Choix case of endof endcase
La structure case / endcase ne fonctionne que dans une définition de mot ; la valeur de la pile est consommée une seule fois en entrée pour tous les cas of / endofmais seulement en sortant dans le cas hors of / endof.
: choix ( ascii -- ) 48 - \ transforme le rang ASCII du chiffre en nombre case 0 of ." Zéro" endof 1 of ." Un" endof 2 of ." Deux" endof 3 of ." Trois" endof 4 of ." Quatre" endof 5 of ." Cinq" endof ." Plus haut que cinq!" 6 of ." Six" endof 7 of ." Sept" endof 8 of ." Huit" endof 9 of ." Neuf" endof ." Ce n'était pas un chiffre!" endcase ; ." Entrer un chiffre" key choix
Note : le cas non défini ne doit pas nécessairement se situer en dernier lieu, il interviendra si aucun des cas précédent n'est rencontré.
: choix ( ascii -- ) 48 - \ transforme le rang ASCII du chiffre en nombre case 0 of ." Zéro" endof 1 of ." Un" endof 2 of ." Deux" endof 3 of ." Trois" endof 4 of ." Quatre" endof 5 of ." Cinq" endof ." Plus haut que cinq!" cr 6 of ." Six" endof 7 of ." Sept" endof 8 of ." Huit" endof 9 of ." Neuf" endof ." Ce n'était pas un chiffre!" cr endcase ; ." Entrer un chiffre" key choix
Cette structure ne permet pas de définir des conditions complexes à chaque nouvel of ce n'est pas un substitut universel aux imbrications de if else then, bien que ce soit possible (réécriture du code de la fin de la section précédente) :
: choix ( ascii -- casse ) dup emit dup 64 > over 91 < and if 1 then \ la pile contient 1 si 64 < pile < 91 dup 96 > over 123 < and if 2 then \ la pile contient 2 si 96 < pile < 123 dup 47 > over 59 < and if 3 then \ la pile contient 3 si 47 < pile < 58 case 1 of ." est une majuscule" endof 2 of ." est une minuscule" endof 3 of ." est un chiffre" endof ." est une ponctuation ou un caractère spécial" endcase ;
3.4 Boucle begin again
Génère et affiche les nombres pairs. [ctrl s] pour suspendre, [ctrl q] pour continuer et [ctrl c] pour arrêter. Les indentations ne sont là que pour une meilleure visibilité.
: pair ( -- ) 0 ( nombre de départ ) begin ( début de boucle ) 2 + dup ( ajoute 2 au nombre et le duplique pour affichage ) . cr ( affiche le nombre et une fin de ligne ) again ; ( retourne à begin )
exit permet d'interrompre cette boucle éternelle, par exemple avec une condition. La boucle qui suit incrémente P et affiche sa valeur, elle teste ensuite son égalité avec 5 auquel cas elle met fin à la récurrence :
: essai 0 begin 1+ dup . dup 5 = if exit then again ;
Note : cette boucle sera avantageusement remplacée par do…loop
3.5 Boucle begin until
Il en existe une autre boucle permettant une condition de sortie :
: vers36 ( -- ) 36 0 ( 36 est le nombre limite, 0 le nombre de départ ) begin 2 + dup . ( ajoute 2 au nombre, le duplique pour l'afficher ) 2dup = ( duplique la paire pour la comparaison, le résultat est mangé par until ) until ; ( résultat positif : ne renvoie pas vers begin )
dup et dup2 ont été utilisés, mais cela aurait pu être over , swap… selon les nécessités.
Il est possible de faire une boucle avec compteurs de type for … next de plusieurs manières. En voici une qui implémente le mot ** (exposant) avec begin … until. L'idée est de travailler alternativement avec la pile pour les multiplications et pour la décrémentation de l'exposant :
: ** ( a b -- a ** b ) 1 swap ( pile : base 1 exposant ) begin -rot over * ( 1 en haut de la pile, recopie la base en haut et multiplication ) rot 1- ( exposant en haut de pile, décrémentation ) dup 0= ( vérification que l'exposant n'est toujours pas nul ) until -rot nip nip ; ( résultat en haut de pile et suppression des opérandes)
La factorielle suit le même principe et est encore plus simple (swap plutôt que rot et -rot) :
: facto ( a -- !a ) 1 swap ( 1 n ) begin swap over * ( 1 en haut de la pile, n copié sur la pile, multiplication swap 1- ( nombre en haut de pile, décrémentation ) dup 0= until ( vérifie la non nullité avant de renvoyer en début de boucle) drop ; ( enlève le reliquat du nombre de départ : 0 )
3.6 Boucle begin while repeat
La boucle begin while repeat est divisée en deux, la première partie sert surtout à préparer un booléen qui va permettre l'exécution du code qui suit jusqu'à repeat (-1) ou d'arrêter la boucle (0) et de poursuivre le code après repeat.
: ascii ( -- ) 128 31 cr \ le code ASCII va de 32 à 127 begin 1+ 2dup <> \ tant que l'incrémentation de 31 n'égale pas 128: -1 (vrai) ou 0 (faux) while dup . space \ duplique 32 et plus pour sortir le rang ASCII dup emit cr \ duplique 32 et plus pour sortir le caractère repeat ;
3.7 Boucle à compteur do … loop
Boucle nécessitant deux nombres sur la pile des entiers :
- un premier nombre pour la valeur limite à laquelle la boucle s'arrêtera
- un second nombre de départ, strictement inférieur au nombre-limite dans ce premiers cas
: zap ( efface la pile ) depth \ depth est le nombre d'entrées sur la pile 0> if depth 0 \ la comparaison a consommé la valeur déposée sur la pile par depth do \ récurrence de 0 à depth -1 drop \ suppression une à une des valeurs sur la pile loop then ;
Un compteur interne i s'incrémente à chaque passage par loop , ne retournant plus à do lorsque la valeur-limite est atteinte. Cela signifie que pour la boucle 10 0 do … loop , i prendra les valeurs de 0 à 9.
: facto 1 \ 1 est l'emplacement cible des multiplications, commence avec la valeur 1 swap \ remettre le nombre à traiter en haut de la pile 1+ \ nombre à traiter + 1 parce que la boucle s'arrête à n-1 2 \ * 1 est inutile : commencer à 2 do i * \ i dépose successivement 2, 3, 4… qui multiplient l'emplacement cible loop ; \ résultat à récupérer sur la pile 5 facto .
Attention : si le second nombre est plus grand ou égal au premier, la condition de sortie ne peut être égalée et la boucle n'arrêtera pas. +do évite ce problème, ne démarrant pas dans ce cas (essayer sans le +) :
: boucle 1 6 \ la valeur de départ (6) est plus grande que la valeur d'arrivée +do \ ne démarrera pas (mais essayez sans le +) i . loop ;
n +loop définit un pas différent de 1 , à préciser juste avant, et qui passe par la pile :
: compter 10 1 do i . 2 +loop ;
n -loop permet de définir un pas négatif :
: compter 0 10 do i . 1 -loop ;
-do est l'équivalent pour -loop de l'expression +do pour loop afin d'éviter les boucles par dépassement de limite. Rien ne se passe si le compteur commence plus haut que la limite (loop) ou plus bas (-loop).
Note :u-do et u+do sont les équivalents pour les entiers non signés.
Attention : évitez 1 10 do i . -1 loop dont les résultats ne sont pas garantis.
leave permet de quitter une boucle avant la fin prévue par do loop :
: essai 100 1 +do \ boucle de 1 à 99 i dup dup \ trois i sont nécessaires 13 mod swap \ 0 (faux) si i divisible par 13 7 mod \ 0 (faux) si i divisible par 7 or if \ si l'un des deux > 0 : . else leave \ affiche i ; sinon quitte do…loop then loop cr . ." est divisible par 13 et 7" ;
unloop exit permet de quitter une boucle et la définition d'un mot :
: essai 100 1 +do \ boucle de 1 à 99 i dup 44 \ un deuxième i est nécessaire < if \ plus petit que 44 cr . \ affiche i else unloop exit \ sinon quitte la définition de mot then ." est plus petit que 44" \ évité avec un loop exit ; ne l'aurait pas été avec leave loop ;
Boucles do loop imbriquées
Si deux boucles do loop doivent être imbriquées, le compteur interne de chacune reste i, mais la boucle interne peut avoir accès au compteur de la boucle externe avec j . Cela marche également avec trois boucles imbriquées et la variable interne k.
: bcl-3 ( -- ) 5 1 do \ 1ere boucle cr i . 4 1 do \ 2eme boucle cr j . i . \ j est le compteur de la 1ere boucle 3 1 do \ 3eme boucle cr k . j . i . \ k est le compteur de la 1ere boucle loop \ j est le compteur de la 2eme boucle loop loop ;
On voit à l'exécution de cette boucle que le compteur de la première boucle est successivement appelé i , j ou k selon qu'il est appelé dans la première, la deuxième ou la troisième boucle.
Rappel :les compteurs i , j et k existent en dehors de la pile, mais leur évocation dépose leur valeur sur la pile, qui en permet l'affichage avec le point .
Note : il n'est pas possible de faire appel à un compteur situé à un niveau supérieur à deux rangs (l n'existe pas), mais il est possible d'imbriquer plus de trois boucles :
: bcl-5 3 1 do cr ." -" i . 3 1 do cr ." --" i . 3 1 do cr ." ---" i . 3 1 do cr ." ----" i . 3 1 do cr ." -----" i . loop loop loop loop loop ;
3.8 Boucle à compteur for … next
Il existe encore la fameuse boucle for … next du BASIC, qui a ici un comportement curieux à double titre : elle est descendante et ne s'arrête pas à n-1. Son compteur interne s'appelle également i.
: fornext 10 for i . next ; fornext
4. Adressage en mémoire
Constantes et valeurs
42 constant tout fixe la constante entière tout
-4266654951368818688 323742 2constant terre fixe la constante entière double (masse de la) terre
314e-2 fconstant pi~ fixe une approximation de pi à la constante réelle pi~.
Il semble qu'on puisse redéfinir une constante, mais ce n'est pas portable.
13 value treize affecte l'entier 13 à treize 31 to treize modifie la valeur de l'entier 13 à treize
Attention : l'utilisation dans un mot d'une constante ou d'une valeur y est figée ; toute redéfinition ultérieure n'y sera pas modifiée.
4.1 Adressage simple
create qqp 16 allot réserve 16 octets ; qqp contient l'adresse du premier des 16 octets
Entiers
65 qqp ! copie un entier à l'adresse indiquée. Attention : le premier octet en mémoire est le moins significatif (petit-boutisme)
qqp @ copie sur la pile l'entier situé à l'adresse indiquée
65 101 qqp 2! copie 101 à l'adresse qqp, puis 65 à l'adresse qqp 8 +
qqp 2@ copie sur la pile l'entier double en qqp 8 + puis l'entier en qqp
Pour ne pas se tromper entre ces deux commandes, il faut se rappeler qu'il est plus dangereux (!) d'écraser la mémoire que d'@jouter une valeur sur la pile.
n qqp +! ajoute un entier à la valeur située à l'adresse précisée
qqp l! copie en mémoire les 32 bits de poids faible d'un entier sur la pile
sl@ copie sur la pile les 32 bits de poids faible de l'entier situé à l'adresse qqp
ul@ copie sur la pile les 32 bits de poids faible (de l'entier signé)
qqp w! inscrit à l'adresse située sur la pile les 16 bits faibles d'un entier
qqp sw@copie sur la pile les 16 bits de poids faible
qqp uw@ copie sur la pile les 16 bits de poids faible (de l'entier signé)
Attention : seuls les octets concernés sont écrasés, les octets de poids forts sont conservés en mémoire.
Octets
65 qqp c! inscrit un octet à l'adresse qqp
qqp c@ ajoute à la pile la valeur de l'octet situé à l'adresse qqp
Réels
sf! inscrit à l'adresse en mémoire précisée sur la pile des entiers la valeur du réel simple précision IEEE (32bits, soit 4 octets) situé sur le pile des réels.
sf@ copie sur la pile des réels la valeur du réel simple précision de l'adresse située en haut de la pile des entiers.
n sfloats l'entier est multiplié par le nombre d'octets nécessaires à un single-float IEEE, à savoir 4 octets
sfloat% dépose 4 4 sur la pile
sfloat+ équivaut à 4 +
1e0 qqp df! inscrit un réel double précision IEEE (64bits, soit 8 octets) à l'adresse en mémoire précisée
qqp df@ copie sur la pile des réels la valeur du réel double de l'adresse en mémoire précisée
dfloats multiplie la valeur du haut de la pile par le nombre d'octets nécessaire à un double-float IEEE, à savoir 8 octets
dfloat% dépose 8 8 sur la pile
dfloat+ équivaut à 8 +
f!, f@, floats, float% et float+ agissent comme sf… ou df… selon que le processeur (ou le système d'exploitation?) fonctionne en 32 bits ou 64 bits.
Chaînes de caractères
: wyatt c" Moon in June" ; inscrit une chaîne de 255 caractères maximum en mémoire. Le premier octet en mémoire (adresse contenu dans la constante wyatt), est sa longueur.
wyatt count inscrit l'adresse sur la pile, l'augmente d'une unité et ajoute sur la pile la longueur de la chaîne, pour la préparer à l'affichage avec type
: verlaine s" Chanson d'Automne" ; inscrit également une chaîne, mais pas limitée à 255 caractères en mémoire. verlaine dépose l'adresse de la chaîne suivie de sa longueur sur la pile
pad est un endroit en mémoire (tampon / buffer) permettant de recevoir une chaîne avec n accept (un espace de 84 caractères est garanti)
unused contient la taille de la mémoire libre?
here contient le pointeur de l'espace des données
chars contient la valeur 1 commune à toutes les machine (taille d'un octet).
Variables
variable spam initialise la variable spam à 0 (réservation de 8 octets). spam contient dès lors l'adresse où peut être stockée, lue… une valeur. L'invoquer suffit pour déposer l'adresse sur la pile.
43 spam ! inscrit le nombre 43 à l'adresse spam
17 spam +! additionne 17 à la valeur contenue à l'adresse spam
spam @ dépose sur la pile le nombre contenu à l'adresse spam
2variable sleepdirt initialise la variable double sleepdirt (réservation de 16 octets)
56 43 sleepdirt 2! inscrit l'entier double 43 à l'adresse sleepdirt
spam d@ dépose sur la pile l'entier double contenu à l'adresse spam
fvariable studiotan initialise la variable réelle studiotan (réservation de 16 octets)
43 spam f! inscrit le réel 43 à l'adresse spam
spam f@ dépose sur la pile le réel contenu à l'adresse spam
r , la pile des retours
À ne pas confondre avec le formatage .r
La pile des retours est une pile utilisée par Forth pour gérer les aller-retours en mémoire notamment lors de l'appel des définitions de : mot ; . Cette pile des retours peut servir de lieu de stockage temporaire pour des manipulations complexes sur la pile des entiers.
Il s'agit de manipulations très délicates : il faut prévoir autant d'entrées que de sorties sur cette pile à l'intérieur d'une définition ou d'une boucle, sinon l'adresse de retour de la définition vers le programme principal ne sera plus valable ; il en va de même lors de l'utilisation des leave et exit au sein des boucles.
>r transfère une valeur de la Pile des entiers sur la Pile des retours
r> transfère une valeur de la Pile des retours sur la pile des entiers
r@ pose sur la pile la valeur à l'adresse courante de la pile des retours
rdrop élimine une cellule sur la pile des retours
2>r, 2r>, 2r@ et 2rdrop sont utilisés pour les entiers doubles
Introspection
s" 5 dup *" evaluate tente d'interpréter une chaîne comme s'il s'agissait d'une saisie au clavier.
literal
literal est une directive de compilation pour réaliser un calcul demandé. Par exemple, pour calculer la limite signée d'un mot long (32 bits) :
: lim32bs [ 2 31 lshift 1 - ] literal ;
see lim32bs devient : lim32bs 4294967295 ;
]l est un raccourci pour les entiers ; 2literal pour les doubles, fliteral pour les réels ; aliteral, cliteral, sliteral…?
postpone
postpone permet la définition d'un alias pour un mot :
: dummy postpone + ; immediate : plus dummy ; see plus
( à suivre )