Voilà un petit texte d'introduction à Forth. 1. Introduction --------------- Forth est un nom qui recouvre plusieurs choses : -- un langage de programmation ; -- un environnement de programmation ; -- un système d'exploitation ; -- une philosophie. À la base, Forth a été inventé en 1970 par Chuck Moore, astronome de son état. Ça a ensuite évolué de façon un peu foisonnante. Un groupe non gouvernemental, le "Forth Interest Group" (FIG), a publié en 1983 un premier effort de standardisation du Forth, dit "Forth-83". Puis ANSI s'y est collé et a publié, en 1994, le standard "ANS Forth" qui est aussi (supposément) un standard ISO (je ne sais pas lequel). Forth est traditionnellement quelque chose de très proche de la machine et Forth-83 décrit beaucoup de choses qui sont du domaine du détail d'implémentation. ANS Forth est nettement plus conceptuel, et laisse libre les implémentations de faire des choses intelligentes telles que générer du code natif ou gérer automatiquement la tail-recursion. ANS Forth est essentiellement ce dont je parle ici. Il existe pas mal de textes d'introduction à Forth, en général accompagnés d'un pathos considérable sur le thème de "ouin Forth il est mieux que C mais personne ne s'en sert, Dieu que les gens sont bêtes et bourrés de préjugés". Pour de la bonne doc, je conseille celle de Gforth : http://www.complang.tuwien.ac.at/forth/gforth/Docs-html/ (Malgré le "G", cette documentation explicite très correctement ce qui est du domaine du standard et ce qui est de l'extension locale.) Le présent document ne remplace pas un bon didacticiel ; disons plutôt qu'il le complète. J'ai essayé d'expliciter les quelques points qui m'ont semblé les plus techniques quand j'ai appris le Forth. En gros, ce document est celui que j'aurais aimé avoir en complément de la doc de Gforth. Il existe des implémentations pour plein de systèmes. Les unixiens choisiront Gforth (ftp://ftp.gnu.org/gnu/gforth/) ou PFE (Portable Forth Environment: http://pfe.sourceforge.net/). Ces deux-là sont packagées pour tous les bons OS. Pour Windows, on dit du bien de Win32Forth (http://win32forth.sourceforge.net/). Dans le domaine payant (mais pas cher), il y a iForth (Windows et Linux, binaire seulement : http://home.iae.nl/users/mhx/i4faq.html) dont on dit qu'il génère du code natif très efficace. 2. Modèle --------- Forth utilise le modèle de machine suivant : -- Il y a un type de données natif, qui fait souvent 16 ou 32 bits, voire 64 bits. Un tel type entier est aussi appelé "cell". En Forth-83, les nombres signés sont représentés en complément à deux. ANS Forth permet les représentations en complément à un et en "bit de signe et mantisse" mais c'est très rare. -- L'espace d'adressage est linéaire. Chaque adresse est une valeur entière non signée. L'unité est l'"address unit" dont on ne sait pas grand-chose au fond. Tous les accès mémoire se font soit par "cell", soit par "char". Un "char" est un caractère et peut prendre plusieurs "address units". Par exemple, sur HP-48 (processeur Saturn), chaque address unit est un quartet et un "char" occupe deux "address units". On a aussi des Forth où le "char" fait 16 bits et où l'"address unit" est un octet. -- La machine exécute des procédures (appelées "words", donc "mots") qui sont, essentiellement, des suites d'appels d'autres mots. Il y a une "return stack" qui s'occupe de gérer les appels et retours. Il y a une autre pile, la "data stack", qui sert pour passer des arguments et pour globalement gérer un peu tout ce qui passe. La gestion de la "return stack" est semi-automatique (on peut déposer et retirer des données) mais les appels et retours de mots utilisent cette pile et mieux vaut ne pas trop les déranger. Pour ceux qui ont fait de l'assembleur 6809, la "return stack" est celle du registre S et la "data stack" utilise le registre U (nul doute que Motorola pensait au Forth en définissant ce processeur). Les deux piles stockent des "cells". -- Il y a une zone de mémoire, dite "dictionnaire", qui a une allocation LIFO par adresses croissantes: on ajoute des données au bout et le pointeur de fin de données augmente. C'est le modèle du brk() Unix. Le dictionnaire sert à stocker des données et, potentiellement, la forme compilée des mots. -- Forth travaille avec un interpréteur qui permet de définir / compiler de nouveaux mots, et de les appeler. Forth-83 définissait précisément ce que définir un nouveau mot voulait dire : un entête était inscrit dans le dictionnaire (avec le nom du mot, entre autres), suivi du code du mot, qui est la suite des adresses des sous-mots à appeler. Quelques mots spéciaux font les opérations spéciales (sauts, constantes, sorties du mot...). ANS Forth rend tout ça plus flou (pour permettre des implémentations avec génération de code natif, par exemple) mais cette structure est la plus courante et c'est ce qui permet d'avoir un code interprété efficace : il suffit d'avoir une routine rapide pour aller chercher l'instruction suivante. Cette routine est souvent appelée "NEXT" dans la litérature Forth, mais ce n'est pas un mot Forth (je ne la cite que parce que pas mal d'articles se mettent à en parler sans préciser ce que c'est, à savoir un détail d'implémentation). Un leitmotiv de Forth, c'est le minimalisme. Un Forth de base n'a pas d'allocation mémoire à la malloc(), parce que malloc() cache des structures compliquées (des listes, des arbres, etc...) dont l'efficacité varie suivant les contextes d'utilisation. Ce genre d'allocation est fourni comme extension standard (ALLOCATE and co) mais n'est pas forcément présent sur un système Forth donné, et a priori rien (ou pas grand-chose) ne s'en sert dans le système. 3. Interprétation et compilation -------------------------------- Un système Forth a une entrée standard, en général un "terminal" où l'utilisateur tape du code. Ça peut être aussi un fichier où il y a du code source, ou des "blocs" (les blocs sont une API minimaliste d'accès à un disque considéré comme une tableau de blocs de 1 Ko). Ça peut enfin être une chaîne de caractères en mémoire (Forth possède un EVALUATE). L'entrée est "empilable" à la manière d'un #include du C (passage à une autre entrée, puis retour à la précédente). Plus intéressant, l'entrée est manipulable depuis le code lui-même. Forth accepte l'entrée ligne à ligne ; dans chaque ligne, il y a un pointeur qui dit où on en est. Un programme peut aller regarder la suite de la ligne en avance, et il peut déplacer le pointeur comme il veut. Si l'entrée est une chaîne reconstituée en mémoire, le programme peut aussi modifier ce buffer. À tout moment, le système Forth peut être en mode "interprétation" ou en mode "compilation". Cela se voit comme une boucle : 1. Trouver le prochain mot dans le buffer d'entrée. Un mot est une suite de caractères ASCII >= 33 (l'espace et les caractères de contrôle sont des séparateurs). 2. Chercher s'il existe un mot défini sous ce nom. Si ce n'est pas le cas, essayer de considérer que le mot est un nombre. Si ça ne marche pas, râler. 3. Si le mode courant est "interprétation", alors le mot / nombre est interprété : exécuté si c'est un mot, poussé sur la "data stack" si c'est un nombre. Puis retour en 1. 4. Si le mode courant est "compilation", alors le mot / nombre est compilé : son appel (ou sa valeur) est rajouté à la définition courante (le mot dont on construit le code dans le dictionnaire). Cette boucle, je l'appelle le "toplevel". Elle est invoquable en Forth sous le nom de QUIT. En Forth-83, il existe des mots dits "immédiats" qui sont executés même si on est en mode "compilation". En ANS Forth, le concept est un peu étendu : chaque mot possède des "interpretation semantics" et des "compilation semantics", qui correspondent à ce que le mot fait quand on le rencontre en mode, respectivement, "interprétation" et "compilation". La plupart des mots ont les "default compilation semantics" qui consistent à ajouter au mot en train d'être défini un appel au mot rencontré (c'est-à-dire que les "interpretation semantics" sont ajoutées à la définition courante). Cette distinction entre les deux modes est le point crucial de Forth. Si on comprend ça, on comprend Forth. Voilà des exemples commentés. Supposons que l'utilisateur soit face à un système Forth lancé. Le système l'accueille avec un prompt (en général "ok"). Si l'utilisateur tape ceci : 1 2 + . CR alors le système part à bouffer tout ça. Forth est au départ en mode interprétation. Il commence par considérer le "1". Il ne trouve aucun mot de ce nom-là, alors il suppose que c'est un nombre, et c'est effectivement le cas. Comme Forth est en mode interprétation, il pousse la valeur 1 immédiatement sur la pile. Ensuite, il passe au "2", qui subit le même traitement. Après, il arrive sur le "+" : Forth trouve un mot déjà défini avec ce nom ; comme il est en mode interprétation, il l'exécute. L'exécution de "+" retire deux valeurs de la pile (ici le "1" et le "2"), en fait la somme et pousse le résultat ("3") sur la pile. La main est redonnée à l'interpréteur Forth. L'interpréteur Forth continue son boulot et trouve le ".", qui est un mot standard. En s'exécutant, ce mot enlève la valeur en sommet de pile, la considère comme un nombre signé, et l'affiche dans la base courante (10, au démarrage). Enfin, le mot "CR" est un mot standard qui affiche un retour à la ligne (note : tous les mots standard sont définis en majuscules ; l'immense majorité des systèmes Forth ne sont pas sensibles à la casse). Une fois tout cela fait, le système se retrouve au bout de son buffer d'entrée. Il affiche son prompt et attend. Maintenant, tapons ceci : : FOO DUP * + . CR S" Zoinx RuLeZ !!!" TYPE CR ; ceci ne donne aucun résultat tangible immédiat (Forth se contente de bouffer tout ça et de réafficher son prompt) mais ça définit le mot "FOO". Analysons un peu : : ce mot est exécuté. Il passe en mode compilation, regarde le mot suivant dans le buffer d'entrée (ici "FOO"), le prend comme nom du nouveau mot à définir, crée un entête de mot avec ce nom dans le dictionnaire, et pousse sur la pile une ou plusieurs valeurs qui seront utilisées plus loin pour clôturer la définition. Une fois que la main est rendue au toplevel, le pointeur d'entrée désigne le "D" de "DUP" ("FOO" a été consommé). DUP c'est un mot standard dont les "interpretation semantics" sont de dupliquer la valeur en sommet de pile. Comme Forth est en mode compilation, il se contente de rajouter l'appel à DUP à la définition courante (celle du mot "FOO"). * mot standard pour multiplier les deux valeurs en sommet de pile et les remplacer par leur produit. Compilé dans la définition de "FOO". + mot standard pour ajouter les deux valeurs en sommet de pile et les remplacer par leur somme. Compilé dans la définition de "FOO". . mot standard pour afficher le sommet de pile (en l'enlevant). Compilé. CR mot standard pour afficher un retour à la ligne. Compilé. S" ce mot est immédiat : bien qu'on soit en mode compilation, il est exécuté. En s'exécutant, il extrait les caractères suivant dans le buffer d'entrée, à commencer par le 'Z' et jusqu'au '"' suivant (non inclus). Il copie ces caractères dans une zone mémoire statique (conceptuellement, le dictionnaire) et rajoute à la définition courante le code qui, lors de l'exécution de FOO, poussera sur la pile l'adresse puis la longueur de cette chaîne. Le pointeur d'entrée désigne maintenant le "T" de "TYPE" et la main est rendue au toplevel. TYPE mot standard qui prend deux valeurs sur la pile : l'adresse et la longueur d'une chaîne ; la chaîne est affichée. Compilé. CR comme précédemment. Compilé. ; mot immédiat : il termine la définition du mot en cours, et repasse en mode interprétation. Il enlève de la pile les valeurs qui ont été placée par ":". On est donc revenu en mode interprétation. Si maintenant on tape : 4 7 FOO alors 4 et 7 sont poussés sur la pile, et le mot "FOO" est executé. En s'exécutant : -- DUP agit et duplique le 7. La pile contient alors : 4 7 7 (le sommet de pile est à droite). -- * agit ; la pile devient : 4 49 -- + agit, ce qui donne : 53 -- . agit, et affiche 53. La pile est vide. -- CR agit, et affiche un retour à la ligne. -- le code compilé par S" agit et pousse sur la pile l'adresse et la longueur de la chaîne "Zoinx RuLeZ !!!". La pile contient : 672506924 15 -- TYPE agit, enlève ces deux valeurs de la pile (qui devient vide) et affiche "Zoinx RuLeZ !!!". -- CR agit et affiche un retour à la ligne. Puis la main est rendue à l'interpréteur, qui affiche son prompt ("ok"). 4. Extension ------------ Forth est dit "extensible" en ce sens qu'il permet de définir de nouveaux mots qui en changent la syntaxe. Par exemple, il s'avère que le mot "\" introduit un commentaire qui va jusqu'au bout de la ligne. Si ce mot n'est pas défini, qu'à cela ne tienne : on peut le rajouter avec la définition suivante : : \ SOURCE NIP >IN ! ; IMMEDIATE \ et hop, on a les commentaires en \ backslash, immédiatement utilisables Analyse : : on commence à définir un nouveau mot, dont le nom est "\" SOURCE pousse sur la pile "addr" et "u" où "addr" est l'adresse du buffer d'entrée (qui contient la ligne courante) et "u" est sa longueur (en caractères). Compilé. NIP supprime l'élément de la pile juste en dessous du sommet de pile. Compilé. >IN renvoie l'adresse de la variable qui contient le pointeur d'entrée courant. Ce pointeur est un offset dans le buffer d'entrée, qui dit où est le prochain caractère non encore pris en compte. Compilé. ! dépile "addr" puis "v" et stocke la valeur "v" à l'adresse "addr". Compilé. ; termine la définition du mot "\". IMMEDIATE rend le dernier mot défini (ici "\") immédiat : les "compilation semantics" de ce mot sont rendues égales aux "interpretation semantics". Donc, à partir de ce point, quand le système Forth rencontre le mot "\", il exécute cette définition. SOURCE renvoie l'adresse et la taille du buffer d'entrée, NIP vire l'adresse (qui nous indiffère), >IN pose l'adresse de la variable contenant le pointeur d'entrée, et ! stocke dans le pointeur d'entrée la longueur du buffer d'entrée. En d'autres termes, le pointeur d'entrée est déplacé jusqu'à la fin du buffer, ce qui revient à ignorer tous les caractères qui suivent le "\", et ce jusqu'à la fin de la ligne. On peut créer des "defining words" qui, à la manière de ":", servent à définir d'autres mots. Il y en a un qui est standard et qui s'appelle CONSTANT. On s'en sert comme ça : 42 CONSTANT ZOINX Ceci définit le mot ZOINX dont l'exécution ne fait qu'une chose : pousser la valeur 42 sur la pile. Voici une façon de définir le mot CONSTANT : : CONSTANT >R : R> POSTPONE LITERAL POSTPONE ; ; Ça utilise le mot POSTPONE qui a une action particulière : il compile les "compilation semantics". Autrement dit, quand on rencontre POSTPONE FOO, ça compile dans le mot courant l'action de faire comme si on venait de taper FOO. Ça permet, en quelque sorte, d'émuler une entrée. Ou de la retarder. Ça dépend comment on le voit. Analyse de CONSTANT : >R envoie le sommet de pile sur la "return stack". Compilé. : déjà vu. Mais ici, ce mot est juste compilé (ce n'est pas un mot immédiat, il a les "default compilation semantics") : il fera son travail de définition d'un nouveau mot quand CONSTANT sera exécuté. R> retire un élément de la "return stack" et l'envoie sur la "data stack". Compilé. POSTPONE mot immédiat à effet retardateur. Il agit ici sur LITERAL. LITERAL est un mot immédiat, mais ici il est "POSTPONEd". Quand CONSTANT sera _exécuté_, l'effet de LITERAL sera appliqué. POSTPONE le deuxième POSTPONE agit sur le mot ";" qui est donc lui aussi retardé. ; le deuxième ";" n'est pas concerné par un POSTPONE. Il agit immédiatement en terminant la définition de "CONSTANT". Les >R et R> servent à sauver temporairement une valeur sur la "return stack". Ils sont utiles ici parce que le ":" compilé dans CONSTANT pose des choses sur la pile. À l'exécution : -- la valeur 42 est envoyée par >R vers la "return stack" ; -- : agit, extrait le nom de la constante ("ZOINX" ici) et crée un mot de ce nom-là. Ça devient le mot en train d'être défini. On passe incidemment en mode "compilation" ; -- le R> remet en place la valeur 42 sur la pile ; -- le LITERAL qui avait été POSTPONEd agit : son travail est d'extraire le sommet de pile courant (ici la valeur 42) et de compiler cette valeur dans la définition courante (celle de "ZOINX") ; -- le ";" qui avait été POSTPONEd agit : il termine la définition de "ZOINX". Donc, au total, quand l'interpréteur exécute ceci : 42 CONSTANT ZOINX tout se passe comme si on avait tapé ceci : : ZOINX 42 ; ce qui définit bien une constante ZOINX de valeur 42. Outre ":" et CONSTANT, il existe quelques autres "defining words" standard. Le plus utilisé est CREATE qui marche en tandem avec DOES>. CREATE crée un mot (en extrayant le nom du buffer d'entrée) ; ce mot ne sait faire qu'une seule chose, qui est de poser sur la pile une adresse. Cette adresse est celle d'un buffer (éventuellement de taille vide) placé dans le dictionnaire juste après l'entête du mot crée. Quant à DOES>, il sert à rajouter du code au bout du mot créé. Ainsi, soit la définition suivante : : BAR CREATE , DOES> @ ; BAR invoque CREATE, qui, à l'exécution, extraiera un mot du buffer d'entrée et créera un mot de ce nom. Ce mot aura lui pour seule action d'envoyer sur la pile l'adresse de la cell qui suit son entête dans le dictionnaire. "," est un mot qui, justement, prend une valeur sur la pile et l'envoie dans la première cell libre au bout du dictionnaire. Dans notre cas, ce sera justement celle dont l'adresse est placée sur la pile par l'exécution du mot créé. DOES> termine l'exécution du mot courant (BAR) et place ce qui a été compilé ensuite (jusqu'au ; ) comme suite du code du dernier mot créé par CREATE. Ici, il s'agit d'une simple invocation de "@", qui, à l'exécution, enlève une valeur de la pile, la considère comme une adresse, va chercher le contenu de la cell à cette adresse, et pose cette valeur sur la pile. Donc, avec ceci : 42 BAR ZOINX on a ceci : -- CREATE agit et crée le mot ZOINX, qui à l'exécution posera sur la pile une adresse constante, spécifique à ZOINX, et que j'appelle x. -- , agit, sort le 42 de la pile et l'écrit à l'adresse x. Il alloue également une cell dans le dictionnaire, ce qui fait que la cell contenant désormais le 42 ne sera plus utilisée pour autre chose. -- DOES> termine l'exécution de BAR et rajoute comme code pour ZOINX le fait d'exécuter "@". Ensuite, si ZOINX est exécuté, il pose sur la pile l'adresse de la cell qui contient 42, puis exécute "@" (comme il lui a été spécifié par DOES>) qui dépile l'adresse en question, va chercher le 42, et pose le 42 sur la pile. L'effet net est que ZOINX agit comme une constante définie avec CONSTANT. BAR peut donc être vu comme une autre définition possible de CONSTANT. En fait, c'est même une définition possible de VALUE : VALUE est un mot standard qui définit une valeur, similaire à une constante, mais dont on peut changer la valeur ensuite avec le mot standard TO. On peut définir TO avec >BODY, mot standard qui permet de retrouver l'adresse constante associée à un mot créé avec CREATE. (Note conceptuelle : un mot créé avec CREATE est un objet avec une seule méthode. L'adresse constante poussé sur la pile en entrée du code est le pointeur "this".) Un point à noter : l'interpréteur remplit et reremplit le buffer d'entrée chaque fois qu'il est au bout d'une ligne ; en revanche, les "defining words" ne le font pas (ce n'est pas qu'ils ne pourraient pas, c'est juste que ceux définis en standard ne le font pas). Donc, dans ": ZOINX", le ZOINX doit être sur la même ligne que le ":" et il ne peut pas y avoir de commentaires entre les deux. En effet, c'est le ":" qui prend en charge temporairement l'analyse syntaxique et lexicale, et il le fait de façon assez limitée. Dans la pratique, ce n'est pas une limitation très grave. 5. Contrôle de flux ------------------- Il existe des mots standard qui permettent de changer le flux de contrôle : IF, THEN, WHILE, AGAIN, REPEAT... En fait, il existe même des mots pour fabriquer de nouvelles structures (avec une pile, comme d'habitude). Ça permet de définir CASE (comme un "switch" du C) en pur Forth standard. Voilà ce que ça donne : 0 CONSTANT CASE IMMEDIATE ( init count of OFs ) : OF ( #of -- orig #of+1 / x -- ) 1+ >R POSTPONE OVER POSTPONE = POSTPONE IF POSTPONE DROP R> ; IMMEDIATE : ENDOF ( orig1 #of -- orig2 #of ) >R POSTPONE ELSE R> ; IMMEDIATE : ENDCASE ( orig1..orign #of -- ) POSTPONE DROP 0 ?DO POSTPONE THEN LOOP ; IMMEDIATE Le mot "(" introduit des commentaires qui durent jusqu'au prochain ")", _en reremplissant le buffer d'entrée si besoin_. Traditionnellement, ça sert à indiquer les "stack comments" qui indiquent l'état de la pile avant et après exécution d'un mot. ?DO et LOOP permet de faire des boucles à la FOR, avec un compteur accessible sous le nom I. Cette définition de CASE (extraite des annexes du standard) implémente le CASE comme une série de IF. On peut bien sûr imaginer une implémentation à base de tables. Par ailleurs, ce n'est pas parce qu'on _peut_ définir CASE en pur Forth qu'on _doit_ le faire : n'importe quel système ANS Forth a le droit de rajouter autant de magie qu'il veut. C'est juste que la magie est quelque chose de relativement mal vu en Forth, car peu minimaliste. 6. Concepts ----------- Programmer consiste essentiellement à bouger des données, et les différents langage de programmation se distinguent surtout sur la façon qu'ils ont de nommer ces données. Le C donne des noms aux paramètres de fonctions. Le Forth désigne les paramètres implicitement par leur place sur la pile. Ceci permet une économie de noms et un code moins verbeux que le C (le C est verbeux par rapport au Forth, de la même façon que le Pascal est verbeux par rapport au C). En contrepartie, Forth favorise une forte "factorisation" : ce qui se ferait en une fonction avec trois blocs imbriqués en C, se fait en trois mots distincts en Forth. Forth permet une conception "bottom-up" où on commence par des micro-mots qu'on assemble peu à peu. Le fait d'avoir un interpréteur en ligne permet de tester les mots au fur et à mesure ; supposément, si on a un bon éditeur de texte qui permet de taper des définitions et de les envoyer en cours de route à un système Forth, ça rend le développement beaucoup plus rapide. (Note : c'est ce que racontent les adeptes du Forth. Je ne suis pas personnellement convaincu. Je verrai à l'usage. De même, la forte factorisation est censée promouvoir la réutilisation de code.) Un problème possible de Forth est qu'il génère beaucoup de noms à portée globale. En contrepartie, on peut redéfinir un nom sans affecter les mots qui utilisent l'ancienne définition (c'est du "early binding"). On peut aussi tester les collisions, et, là encore, c'est faisable et même facile en Forth pur : \ Redefine : so as to report word redefinitions. : : >IN @ >R \ save current entry pointer BL WORD FIND IF \ parse the word and try to find it ." [word redefined !]" \ warn if the word is found THEN DROP \ cleanup a bit R> >IN ! \ restore entry pointer : ; \ invoke previous definition of : On notera que lorsque l'on définit un mot, son nom n'est accessible qu'une fois le mot pleinement défini ; ici, on peut appeler ":" et obtenir l'ancienne définition. La récursion simple s'obtient avec le mot RECURSE. La récursion complexe (mot F appelle mot G appelle mot F) peut se faire avec une indirection ; le mot DEFER (défini dans Gforth, définissable en Forth standard) permet de déclarer "en avance" des mots (c'est le "FORWARD" du Pascal). Forth-83 contenait des "vocabulaires" pour organiser les noms des mots en espaces de noms. ANS Forth fait, là encore, dans le conceptuel, en définissant des "wordlists" qui sont des primitives permettant d'implémenter, entre autres, les vocabulaires de Forth-83, mais aussi d'autres schémas de nommage. Forth est un système qui contient un interpréteur/compilateur. L'idée d'origine est que l'utilisateur soit aussi un peu programmeur sur les bords, et puisse toujours interagir un peu avec l'application. Il paraît que ça a déjà permis de contourner des pannes et des bugs à bord de la navette spatiale (environnement où il est difficile de faire venir un réparateur extérieur). L'idée est intéressante ; mais parfois, on voudrait avoir des binaires "stand-alone". Certains Forth permettent ça ; ça consiste essentiellement à éjecter les mots non utilisés (notamment les mots comme ":", qui ont fait leur office une fois le programme compilé) et à fabriquer soit une sorte de bytecode pour un interpréteur très compact, soit à carrément pondre du code natif. Gforth contient des choses comme ça, cf la doc. Un des points où Forth pêche le plus à mon avis, c'est que rien n'est prévu pour faire des modules compilables séparément. Dans l'optique minimaliste, tout le programme rentre en un seul bloc dans la mémoire du système Forth. C'est très valable sur des systèmes embarqués qui n'ont pas de disque dur ni de mémoire virtuelle ; mais je n'ose pas trop imaginer un Mozilla écrit en Forth : le temps de lancement serait prohibitif. Maintenant, si rien n'est fait, rien ne l'empêche non plus. Il y a des sujets de recherche là. Il est tentant de prendre Forth et de l'étendre avec un analyseur syntaxique qui connaisse des opérateurs infixes avec précédence et toutes sortes de commodités dont on a pris l'habitude si on a beaucoup programmé en d'autres langages. C'est tout-à-fait faisable, ce n'est même pas très difficile. Mais il s'avère que c'est rare. Apparemment, la notation polonaise inversée et la pile sont des concepts à accoutumance rapide, et quand on en est au point où on sait programmer l'analyseur syntaxique susdit, eh bien on n'a plus envie. Comme je le disais en introduction, la communauté des programmeurs Forth est très jalouse de ses spécificités, et très affectée par le manque de considération dont leur langage favori souffre. Ils sont aussi très religieux dans leurs point de vue : si on ose écrire dans comp.lang.forth que Forth est un langage de programmation, on se fait descendre par quelques résidents qui répètent que Forth est une _philosophie_. Ensuite, ça dégénère en flamewar (pas longtemps, quelques dizaines de messages au plus). Dans comp.lang.forth, il faut lire les messages d'Anton Ertl. C'est un des auteurs principaux de Gforth et il sait ce qu'il dit.