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ésentation

1. Entiers et réels

1.1 Entiers simples
1.2 Travail sur la pile
1.3 Entiers doubles
1.4 Bases numériques
1.5 Nombres réels
1.6 Conversions de types
1.7 Comparaisons
1.8 Opérateurs logiques

2. Chaînes et fichiers

2.1 Affichage de caractères
2.2 Définition de chaînes
2.3 Entrée de chaînes
2.4 Comparaisons de chaînes
2.5 Formatage de nombres

2.8 Fichiers sources
2.9 Fichiers de données

3. Mots, conditions et boucles

3.1 Définition de mots
3.2 Condition if else then
3.3 Choix case endcase
3.4 Boucle begin again
3.5 Boucle begin until
3.6 Boucle begin while repeat
3.7 Boucle à compteur do loop
3.8 Boucle à compteur for next

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 :

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 \ ( ) { } -- :

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 :

0000000000
12701111111127
12810000000-128
25511111111-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 :

cdAcdBandorxor
-1-1-1-10
-100-1-1
010-1-1
00000

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
andorxor
12:110011001100
10:101010101010
8:100014:11106:0110

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.

30000000000000000000000000000000000000000000000000000000000000011
-41111111111111111111111111111111111111111111111111111111111111100

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)

11000000000001011
4 lshift000000010110000
 
 < < < <
-5111111111001101
3 rshift11111111111001
 
 > > >

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) :

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…;

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.

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 :

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 :

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 :

: 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 )