Interpréteur Forth
|
|||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
Table des matières:
Les documents (PDF, codes sources, programmes, ...) fournis sur ce site sont mis à disposition sous licences Page auto-générée le : |
Le langage ForthRéférences à ForthLe but de ce document est de faire découvrir (ou redécouvrir) le langage Forth créé par Charles Moore, d'expliquer son fonctionnement interne et de mieux faire comprendre certains mots dits de hauts niveaux. Ce document montre aussi qu'il existe, non pas un unique langage Forth standardisé mais des Forth personnalisés et adaptés à chaque projet. Avant de poursuivre la lecture de ce tutoriel, voici une sélection non exaustive d'ouvrages et de liens que je recommande de lire. Je suggère aux lecteurs qui n'ont jamais connu ce langage de s'initier avec les quatre premiers liens puis, si possible le livre de W.P. Salman, O. Tisserand, B. Toulout qui est, selon moi, un livre de référence expliquant complétement le fonctionnement interne du Forth.
Voici une liste non exhaustive de Forth non payants :
Histoire du ForthForth est un langage inventé par Charles H. Moore dans les années 1970 afin de palier la lourdeur des langages de l'époque incompatibles pour des applications temps réel, Moore travaillant à l'époque à l'Observatoire National de Radio-Astronomie des Etats-Unis. Forth a été conçu pour être le plus simple et minimaliste possible où avec environ 150 mots du langage, un petit système d'exploitation peut être créé. Forth fut, par la suite, devenu un langage largement utilisé dans ce milieu (comme par la NASA) mais aussi pour des machines personnelles telles que le Machintosh avec le MacForth (pour ne citer que lui). Forth a été utilisé, à ces époques, en industrie pour tester et déboguer les registres des nouveaux chips (SoC). Actuellement, le prix du stockage mémoire ayant fortement baissé et la puissance brute en calcul augmentée, l'optimisation des ressources des systèmes embarqués est devenu moins critique, ce langage est devenu beaucoup moins à la mode là où le C demeure toujours en maître. Comme nous allons le détailler tout le long de ce document, faire son propre interpréteur Forth est relativement aisé, chacun pouvant implémenter le sien mais pas forcément compatible avec celui d'un autre. Cela fut un point noir de ce langage, qui malgré plusieurs tentatives de standards (78, 79, 83, 2012 ...) pour normaliser les mots les plus courants, ne furent pas forcément bénéfique au langage: Charles H. Moore est toujours actif mais il a quitté le consortium, jugeant que la normalisation nuisait à l'innovation de nouveaux Forth. Il a ainsi pu continuer à simplifier les structures Forth comme expliqué dans le lien suivant, section Structures simplifiées . Avec colorForth Moore a ajouté un rôle aux mots en leur ajoutant une couleur . Pour plus d'information confère ce document . Moore a également fondé GreenArrays Inc. qui produit des puces GA144 contenant un réseau communicant de sous-processeurs Forth dont le brevet explique clairement le fonctionnement (et est très lisible pour un brevet). Nous allons, tout au long de ce document, revenir sur chaque éléments de la définition, mais en quelques mots Forth peut-être résumé à :
Coloration syntaxiquePour ce document, nous allons, concernant la coloration syntaxique, prendre une convention qui n'est pas officielle mais inspirée de colorForth, mais dans ce document la couleur est purement décorative, contrairement à colorForth qui ajoute une information aux mots Forth pour le compilateur:
Rapide aperçu de la théorie des langagesLe but de cette section n'est pas d'expliquer la théorie des langages mais plutôt de faire resentir au lecteur la difficulté de ce sujet et par contraste de montrer la simplicité du langage Forth. Par conséquent, cette section empruntera de grossiers racourcis. Pour rappel et faire très simple, en théorie des langages, un compilateur est un outil qui transforme un langage informatique en un autre langage. Par exemple du langage C au langage machine directement exécutable. Pour cela un compilateur utilise généralement deux outils:
Une fois l'AST construit, cette structure est plus simple pour le compilateur de la travailler. Un compilateur C aurait analyser un certain nombre fois une analyse de l'AST pour créer des AST intermédiaires contenant des pseudo-instructions de plus en plus proche des instructions assembleur de la machine cible. Il aurait également généré un graphe colorié afin de connaître le nombre minimal de registres nécessaires pour stocker les variables locales (par exemple dans notre cas, un registre pour stocker le résultat 14), etc. Donnons un exemple de code en langage C:
Un compilateur C, lors d'une première passe d'interprétation, aurait détecté la priorité de la multiplication sur l'addition via une grammaire non-ambigue et aurait ajouté implicitement les parenthèses suivantes:
puis aurait généré un ParsTree similaire à:
Puis un AST:
Puis des AST avec des instructions de plus en plus proches du langage machine et obtenir du code similaire à ça. Ici on ne montre pas le graphe coloré pour allouer le moins de registres possibles:
En langage LISP, grâce à sa syntaxe représentant des arbres, on aurait implicitement l'AST car on aurait écrit quelque chose d'approchant à :
En Forth, qui est un langage utilisant la notation polonaise inversée, on aurait écrit :
L'interpréteur Forth se contente simplement d'extraire les mots séparés par des espaces. Forth un langage sans syntaxeForth est à la fois un interpréteur et un compilateur. Forth ne compile pas le code en langage machine (comme pour le langage C) mais du byte-code dans une machine virtuelle (comme pour Java). Forth, contrairement aux langages tels que le C ou Python, n'a pas de grammaire ambiguë, ne nécessite pas de rétro-action lexicale, etc. Un script Forth est une simple suite de symboles séparés par des espaces. Il n'y a donc aucune syntaxe. Par conséquent un compilateur Forth est un simple lexer sans son parser. Quand Forth va lire un script, il n'a besoin que d'extraire et de reconnaître le symbole courant (qui doit être soit un symbole qui lui est "connu", soit un nombre) et parfois le symbole suivant quand celui-ci n'est pas encore connu (comme par exemple lors de la définition d'un nouveau symbole/définition Forth). Dans une section de ce document, on expliquera ce que le verbe "connaître" signifie concrètement. Nous verrons également que ce sont les symboles eux mêmes qui agissent directement sur le comportement du compilateur et non le compilateur qui s'adapte aux mots rencontrés. Dans la convention Forth, une unité lexicale est appelé mot (word en anglais) et sa signification est proche de celle du langage naturel humain. Egalement par convention, un mot Forth est formé de n'importe quelle suite de caractères ASCII (il n'y a réellement aucune restriction contrairement à des langages tels que C) dont, historiquement, la taille maximale est 32 caractères ASCII (mais cette limitation a disparue sur les Forth modernes tels que gForth). Certains Forth sont sensible à la case (sensible aux minuscules et majuscules) d'autres non. Au début Forth écrivait les mots en majuscules. Dans ce document on utilisera cette convention. Cette absence de syntaxe s'explique facilement: Forth utilise la wnotation polonaise inversée (Reverse Polish notation) à savoir que les opérandes (paramètres) sont notées avant les opérateurs. Cette notation évite l'utilisation de parenthèses palliant l'ordre de priorité des opérateurs (par exemple en mathématique la priorité de la multiplication sur celle de l'addition) et évitant ainsi l'utilisation d'une grammaire complexe impliquant la création d'un AST. Le code Forth suivant :
équivaut à l'expression arithmétique 2 * (3 + 4) alors que le code Forth suivant :
équivaut à l'expression arithmétique 2 + (3 * 4). Si l'on désire afficher à l'écran le résultat de l'opération on utilisera le mot Forth . qui affichera l'entier valant 14. Note: en Forth, le mot DISPLAY n'existe pas, son nom exact est . (dot en anglais).
Le travail de l'interpréteur Forth est assez trivial: il se contente d'extraire les symboles à la volée (simplement de gauche à droite), il empile les nombres qu'il rencontre et exécute les opérandes (les mots Forth) consommant une certaine quantité de données dans cette pile puis, si résultat il y a, empilera le résultat. La section suivante expliquera mieux ce processus. Mode compilation et mode interprétation: Si l'on désire créer un nouveau mot Forth, on utilisera le mot Forth : suivi du nom du nouveau mot Forth à définir, puis d'une suite de mots Forths connus ou de nombres et finalement du mot ; qui termine la définition. Les mots : et ; sont des mots primitifs du langage, à savoir des mots exécutant du code machine. Par exemple:
est l'équivalent du code C:
J'ai fait correspondre les couleurs. Par exemple : du Forth correspond au () { } du C. On remarquera qu'en Forth que contrairement au langage C le points virgules sont inutiles et ne servent pas à séparer les instructions entre elles. Une convention Forth suggère de séparés les phrases Forth par trois espaces. En colorForth, le point virgule sert de return. Chaque fois que MON-CALCUL sera extrait du flux d'entrée par le parseur, de l'interpréteur Forth, ce dernier calculera et retournera le nombre 14. Il est clair que MON-CALCUL est devenu un mot Forth connu mais revenons en arrière avec l'extrait de code : MON-CALCUL car on peut se poser la question: est ce que l'interpréteur échouera quand il tentera d'exécuter le mot MON-CALCUL qui lui est inconnu ? C'est exact mais c'est l'exécution du mot : qui va commuter l'interpréteur en mode compilation et lui indiquer que le mot suivant (à savoir MON-CALCUL) doit être ajouté dans l'index de recherche des mots connus. En mode compilation tous les mots connus sont compilés en byte code. Un mot inconnu est considéré purement et simplement comme un erreur. On pourrait croire que le mot ; sera lui aussi compilé mais ce n'est pas le cas. En effet ce mot est un mot de haut niveau qui, quand il est exécuté, et ce même quand le compilateur est en mode compilation, fait repasser l'interpréteur en mode interprétation. Nous analyserons plus en détail ce fonctionnement exact dans la section traitant des mots Forth de hauts niveaux. Mots primitifs et mots secondaires: il est important que rappeler que les mots bleus tels que + sont des primitives du langage à savoir appelant du code machine alors que les mots rouges tels que MON-CALCUL sont des mots secondaires (secondary words) définis par l'utilisateur et qui appelent d'autres mots Forth (primitifs et ou secondaires). Le mot MON-CALCUL a été compilé en byte-code et désormais il est maintenant un mot connu de l'interpréteur. Nous y reviendrons dans une section future. Nous pouvons imbriquer ce mot dans une nouvelle définition. Forth est un Langage tissé. Par exemple:
Quand on exécutera PRINT-CALCUL le nombre 14 sera affiché. En effet: PRINT-CALCUL appelera MON-CALCUL qui retournera 14, nombre qui sera consommé et affiché par le mot . est l'équivalent du code C:
Gestion des commentaires: Il existe plusieurs format de commentaires en Forth. Le mot \ ignore la ligne complète. Par exemple:
ignorera Fait un calcul. Attention \ est un mort Forth: il exécute du code: celui d'ignorer tous les caractères jusqu'à rencontrer le retour chariot. En conséquence, il faudra placer au moins un espace pour que l'interpréteur puisse le séparer des autres tokens. Par exemple:
Produira une erreur indiquant que le mot \Fait est inconnu de l'interpréteur. L'autre type de commentaire sont les parenthèses. Par exemple:
Tout comme le mot \, le mot ( est un mot Forth. il lui faut un espace pour être reconnu. Ce mot ignore tous les caractères jusqu'à rencontrer le premier caractère ) qui lui, pour le coup, n'est pas un mot Forth et n'a donc pas besoin d'être espacé. Le lecteur trouvera tout cela très compliqué comme règles ? Malgré le peu de vocabulaire Forth présenté jusqu'à présent dans ce document. Le lecteur changera probablement d'avis quand il constatera la facilité de construire son propre système de commentaire :
Explications: 41 est le code ASCII de ) qui sera placé sur le sommet de la pile de données. Le mot WORD le consommera et ignorera tous les caractères du flux de caractères jusqu'à rencontrer le bon code ASCII. WORD retourne la position du flux, information inutile et donc éliminée par le mot DROP. Finalement le mot IMMEDIAT (qui sera expliqué plus longuement dans une section suivante) rend ce mot exécutable lors de la compilation d'un mot. Par exemple, dans l'exemple précédant concernant MON-CALCUL, il aurait été gênant de compiler le commentaire Fait un calcul en byte code. Pour la définition du mot \ on remplacera le code ASCII de ) par le code ASCII du retour chariot. Le lecteur attentif aura remarqué que les commentaires Forth, avec cette définition issue du standard, ne permet pas de nicher des sous commentaires. JonesForth propose une solution . En conclusion pour cette section, nous venons de voir comment avec un langage sans syntaxe nous avons facilement pu implémenté un début de syntaxe. Nous verrons plus tard que les mots de liaisons tels que le IF THEN ELSE, boucles seront tout aussi facilement implémentées. Forth un langage à pilesEn informatique une pile (stack en anglais) est une structure pour stocker des éléments et par analogie avec la pile d'assiettes, c'est la dernière assiette (dernière donnée) déposée qu'on ira prélever en premier. Une pile est également nommée LIFO pour Last In First Out. Des langages, tels que le C, cachent délibérément au développeur l'utilisation d'une pile de contexte pour sauvegarder des informations. Par exemple, lorsque une fonction appelle une autre fonction, le compilateur va empiler:
Ceci peut parfois entraîner des pénalités en temps d'exécution du programme (passage de trop de paramètres, passage des paramètres volumineux par copie au lieu de leur adresse), voir des crashs par débordement de la pile d'appels (stack overflow) ce qui peut arriver, par exemple, dans les cas suivants:
Le lecteur notera, dans le titre, l'utilisation du pluriel pour le mot pile car nous allons voir que Forth utilise deux piles:
Pile de données: En Forth, avec la notation polonaise inversée, les opérandes sont directement stockés dans une pile de données et les opérateurs (les mot Forths) les consomment. Par conséquent, contrairement à des langages comme le C, la pile évite à Forth l'utilisation de variables temporaires nommées (équivalent aux variables locales du C). Ceci est à la fois un avantage et une faiblesse du langage: le programmeur Forth doit organiser l'ordre des éléments dans la pile avant leur consommation (avec des mots tels que DUP, SWAP, OVER) ce qui ajoute de la pollution à la lecture du code source. Certains Forth non-standards reprennent cette idée de variables locales nommées: une partie de la pile est transférée vers ces variables pour une utilisation directe et ainsi simplifié le code source. Supposons la pile S initialement vide puis exécutons le code suivant:
Il est de la responsabilité du développeur de vérifier qu'il y ait toujours le bon nombre d'éléments dans la pile. Si un mot veut consommer des éléments non présents dans la pile, certains interpréteurs vont interrompre tout le programme et le signaler à l'utilisateur par un message d'erreur de type "stack underflow", d'autres interpréteurs ne testeront même pas l'erreur pour des soucis de rapidité. En colorForth la pile est une simple pile circulaire pouvant contenir jusqu'à 8 éléments. En gforth la taille de la pile peut être définie par l'utilisateur est peut contenir par défaut des milliers d'élements. Pile de retour: cette pile est utilisée pour mémoriser l'ordre des appels des mots Forth appelant d'autres mots Forth (secondary words) tels que MON-CALCUL et PRINT-CALCUL (pour rappel le mot DUP est une primitive du langage à savoir appelant du code machine). Pour bien comprendre la raison de son existence, rappelons nous des algorithmes de parcours d'arbre. Sur la figure suivante, nous souhaitons simuler un repas en parcourant les noeuds de cet arbre avec un parcours préfixe main gauche main-gauche . Avant d'expliquer l'algorithme, voyons d'abord le résultat attendu d'un parcours préfixe: repas, entrée, déjeuner, poulet, frites, désert. Les mots repas, déjeuner, qui sont les noeuds, n'ont pas de signification intéressante, pour cela on ne gardera que les feuilles de l'arbre (feuille est noeud ayant aucun fils), on obtient alors: entrée, poulet, frites, désert. Pour obtenir ce résultat, on peut penser à deux algorithmes: -- l'un récursif, -- l'autre itératif. Voyons d'abord la version récursive de l'algorithme d'un parcours préfixe main gauche. Pour simplifier on supposera que l'arbre binaire (à savoir soit au max deux sous-arbres par noeud), en effet, pour un arbre général (comme sur la figure précédente), on remplacera fils gauche/droit par le N-ième fils:
Cet algorithme utilise implicitement une pile pour sauvegarder les noeuds parcourus. Le compilateur va gérer automatiquement la pile d'appel pour nous (Rappelez vous quand je disais que le langage C cachait au développeur l'utilisation de cette pile). Maintenant, voyons comment est gérée cette pile, en utilisant un algorithme itératif:
Quel est le lien avec Forth ? L'interpréteur Forth dès qu'il lit un nouveau symbole sur son entrée (par exemple MON-CALCUL) va l'interpréter de façon similaire à notre algorithme d'exploration d'arbre à la différence près qu'il ne se contentera pas d'afficher le noeud mais exécutera les instructions machine des mots Forth primitives (tels que DUP). La pile utilisée dans le pseudo-code est cette fameuse pile de retour R que l'on a introduit précédemment. On peut se poser une autre question: quel est le lien entre un mot Forth est un arbre ? Un mot Forth connu de l'interpréteur à implicitement une structure d'arbre. Supposons que notre Forth connaisse les mots primitifs suivants: ENTREE, POULET, FRITES, DESERT. Nous pouvons créer les mots suivants:
L'arbre général qui était REPAS est devenu un arbre binaire dans Forth et les noeuds qui avaient la même hauteur sont maintenant liés entre eux. Attention, en interne, une structure classique d'arbre (avec noeud et deux pointeurs) n'est pas utilisée. Les pointeurs ne sont rien d'autre que le byte-code (identifiant des mots compilés). Cela sera plus clair pour le lecteur lorsque nous aborderons le chapitre sur le dictionnaire car nous n'avons pas encore abordé comment les mots Forth sont représentés en mémoire (section suivante). Pour le moment le lecteur doit accepter le fait que les fils gauches sont une adresse vers l'emplacement mémoire d'un autre mot Forth et que les fils droits n'existent pas, ils sont implicites car les noeuds sont placés consécutivement dans la mémoire (par exemple le byte code des mots ENTREE, DEJEUNER et DESSERT sont consécutifs en mémoire dans la définition du mot REPAS). Maintenant, avec cet arbre binaire, si l'on exécute uniquement les noeuds qui n'ont pas de fils gauche, on obtiendra le résultat attendu: ENTREE, POULET, FRITES, DESERT qui sont uniquement des mots primitifs. On comprendra mieux par la suite comment est implémenté le parcours d'arbre des mots en Forth. La pile de retour un pile semi-privée ? Les débutants peuvent ignorer ce paragraphe dans un premier temps! La pile de retour n'est pas une pile privée totalement privée. Elle est en fait partiellement accessible au développeur mais cela peut présenter un énorme risque de crash du programme. En effet cette pile de retour peut être aussi utilisée comme pile de stockage temporaire de la pile de données avec des mots >R et R>. Cette pile est utilisé en même temps à la fois par l'interpréteur et le développeur. C'est le cas par exemple pour les mots implémentants les boucles : les compteurs sont parfois déplacés sur cette pile. Il faudra faire attention à laisser cette pile propre (pour cela il faut que le nombre mot >R et R> soient balancés) sous peine de faire crasher l'interpréteur en exécutant une valeur de la pile de données en le prenant pour un byte-code à exécuter. Pour rappel, Forth a été conçu dans les années 70 où le moindre octet était peu nombreux et coûteux et donc le moindre hack pour économiser de la mémoire était privilégié à la sécurité. Maintenant avec la puissance énorme des processeurs, il est plus simple de créer une deuxième pile de données. Piles auxiliaires: Pour éviter les crashs avec les mots >R et R> certains Forth les ont interdit et rend privée l'accès à la pile de retour. En contre-partie, il est proposé à l'utilisateur une seconde pile de stockage des données. De plus, les Forth initiaux ne manipulent pas de nombres flottants nativements (la pile de données stocke uniquement des entiers 16 bits). Ces pile sont émulées en Forth et résident dans le dictionaire. Notation des piles: Nous avons vu dans la section précédente que dés que le l'interpréteur exécutera le mot ( il ignorera tous les mots jusqu'à trouver le mot ). Ce type de commentaire est utilisé pour commenter les paramètres d'entrée et de sorties du mot et donc les piles. Il existe de nombreux documents sur internet qui en traite. Mais dans ce document nous allons beaucoup simplifié. Par exemple pour le mot NIP qui supprime le deuxième éléments au sommet de la pile:
Le commentaire indique que ce mot consomme deux nombres dans la pile de données: a et b (où a est le premier élément et b le second) et en produit un seul (b, le second paramètre) est équivalent au code suivant:
Comment sont implémentées les piles ? Deux réponses possible selon le langage qui implémente l'interpréteur Forth:
Dictionnaire Forth: une machine virtuellePour résumer ce qu'on l'a vu, Forth est un langage extensible (tissé): à partir de quelques mots de base on peut créer un nouveau mot plus évolué. Par exemple, le code suivant:
Permet de définir un nouveau mot Forth SQUARE via les mots : et ; (qui jouent le rôle de début et de fin de définition). Une fois ce mot défini, il sera reconnu en tant que mot Forth valide par l'interpréteur. Dans notre cas, quand SQUARE sera exécuté, il calculera la puissance deux du nombre stocké dans la pile. Par exemple, le code suivant, consommera le nombre 5 posé de la pile et affichera le nombre 25.
Nous avons également vu que l'exécution de mots Forth se rapproche d'un parcours d'arbre. Cette section fait enfin ce lien, en expliquant comment les mots Forth sont stockés en mémoire et se réfèrent entre eux. Il n'y a pas une unique structure de dictionaire possible. La norme Forth laisse libre son implémentation. Pour ce document nous nous baserons sur la structure originelle (Forth 78): les mots Forth (entrées) et leur définition sont stockés dans une structure de donnée appelée par convention le dictionnaire (dictonary en anglais). Un mot Forth stocké dans ce dictionnaire sera reconnu par l'interpréteur et sa défiontion pourra être exécutée. Le dictionnaire est un segment mémoire et peut être vu comme le ruban infini d'Alan Turing. Il est divisé en cases mémoire consécutives et par convention on les nomme cellule (cell en anglais). Une cellule stocke un token (byte code). Leur taille en octets dépend de l'architecture de la cible. Nous nous baserons pour ce document sur une architecture 16-bits, donc un mot sera codé sur deux octets. Par conséquent la taille d'un dictionnaire est 216-1 soit 64 Ko (ce qui est suffisant pour stocker un programme Forth). Pour rappel Forth étant né dans les années 70, il est très adapté aux micro-controlleurs 8 ou 16-bits et est moins bien adapté pour des architectures 32 ou 64-bits, à cause des alignements des adresses mémoires et du padding implicite. Pour ne pas compléxifier cette lecture nous ne attarderons pas au problème d'alignement. Voici la structure mémoire d'un mot Forth:
Certaines adresses sont importantes en Forth et possèdent un nom :
La primitive ' appelée tick extrait du flux d'entrée le mot suivant, cherche sa présence dans le dictionaire est place son token sur la pile de données. Le mot EXECUTE consomme ce token et l'exécute. Par exemple le code suivant:
Place l'execution token du mot DUP sur la pile. EXECUTE ... l'execute. L'entier 42 est dupliqué. Il y a désormais deux fois le noimbre 42 qui seroont tour à tour affiché. Dans cet exemple particulier, on aurait simplement pu faire 42 DUP . . mais ces mots ont quand même leur utilité. Il faut distinguer deux types de mots Forth:
Le script suivant :
va créer deux nouvelles entrées dans le dictionnaire comme indiqué dans la figure ci-dessous :
Regardons comment l'interpréteur exécute le script:
Il execute le mot :, active l'interpréteur en mode compilation. Celui-ci va stocker en fin de dictionnaire les CFA des mots. : crée l'entête avec le mot suivant 0x83 FOO DOCOL ainsi que le pointeur du mot précédent. L'interpréteur lit le mot * et place son CFA en fin de dictionnaire. Idem pour le deucième *. Enfin il lit le mot ; qui doit être immédiatement exécuté, ce mot finalise la définition du mot (smudge bit, met a jout LAST) puis remet l'interpréteur en mode execution. Les flèches représentent les adresses (LFA ou CFA) vers les mots existants du dictionnaire. Vu que BAR a été inséré dans le dictionnaire après FOO son LFA pointe vers FOO et le dictionnaire mémorise son LFA pour faire ses recherches sur les mots existants (la fameuse liste chainée). Les littéraux 0x81, 0x83 (notation base 16) sont les tailles des mots avec le marqueur de séparation des entrées du dictionnaire. Les mots Forth DOCOL et EXIT sont des mots particuliers que l'on décrira plus tard. HERE pointe sur l'emplacement après EXIT du mot BAR. Le dictionnaire possède deux informations supplémentaires qui sont eux-mêmes des mots Forth (donc aussi stockés dans le dictionnaire) :
A chaque fois qu'un mot est inséré à la définition d'un mot, c'est le mot HERE qui indique l'emplacement. Après l'ajout du mot, HERE est automatiquement déplacé pour toujours pointer sur un emplacement libre mais des mots comme ALLOC permettant de réserver de la mémoire déplace simplement HERE. Deux avantages de cette structure de donnée sont :
Interpréter BAR appellera deux additions. L'inconvénient majeur de cette structure de donnée est que la recherche se fait avec une complexité linéaire que l'on note O(n). Pour palier à ce problèmes des mots Forth de même nature peuvent être regroupés en vocabulaires. Le vocabulaire est l'ancêtre des espaces de nommage des langages modernes (comme en C++ avec par exemple std::cout). Un vocabulaire est un mot Forth gérant un index de LAST mais pointant sur des mots choisis par le développeur. On a donc une sorte d'arbre où une recherche partirait d'un feuille (d'un vocabulaire spécifié, donc un mot équivalent à LAST) et se terminerait à la racine de l'arbre. Moore avec son colorForth est passé à une table de hachage et pour sauver de la mémoire il compresse les noms des mots Forth avec un codage de Huffman. Rappelons qu'un des flags dans l'entête d'une entrée indique si le mot est valide ou non (c'est le smudge bit). S'il est mis, alors il sera ignoré lors d'une recherche. Le mot FORGET suivit d'un mot existant (par exemple FORGET FOO) permet de tronquer toutes les définitions du dictionnaire jusqu'à ce mot. FORGET change simplement la valeur de LATEST. On prendra garde à ne pas supprimer tout le dictionnaire en tentant par exemple de supprimer le premier mot du dictionnaire. Forth se prête bien à la récursivité : une récursivité terminale n'est rien d'autre qu'un saut en mémoire. Malheureusement, la syntaxe varie fortement d'un Forth à un autre. En effet, selon le Forth utilisé, lorsqu'une entête est créée dans le dictionnaire LATEST ne pointe pas encore dessus, d'autre Forth, le smudge bit est mis tant que le mot ; n'est pas exécuté effaçant le bit. Le mot SMUDGE doit être utilisé dans la définition pour commuter le flag en question. Voici un exemple de récursivité :
Les premiers Forth l'implémentent comme une liste chaînée où tous les mots sont stockés les uns à la suite des autres (sans trous); certains Forth séparent les entrées des définitions (index séparé) tout en gardant le principe de liste chaînée entre les mots; d'autres utilisent une table de hachage, etc. Différentes classes de mots
Néanmoins Forth offre la possibilité d'utiliser des
constantes et des variables mais qui doivent être vues comme des noms
sur des emplacements mémoire de longue durée (équivalent aux globales
du C) à savoir que leur durée de vie est lié à celle du programme.
Je décris rapidement les différents catégories de mot sachant que le lien Starting Forth les expliquera mieux :
Commentaires : il existe deux types de commentaires en Forth. Le premier type est le mot Forth immédiat ( qui ignore tout caractère jusqu'au premier caractère ) rencontré. Par conséquent il est donc impossible d'imbriquer des commentaires entre-eux. Et il ne faut pas oublier les espaces car ( est un mot Forth comme un autre. Ce type de commentaire est préféré pour indiquer les paramètres d'un mot Forth. Pour plus de renseignements sur la norme de la notation des paramètres voir ce site .
Indique que DIVISION va consommer deux nombres signés où n1 est déposé dans la pile avant n2 (dans l'ordre de gauche à droite) et retourner le résultat dans la pile. Pour preuve 1 0 DIVISION doit retourner une exception tentative de division par zéro. Le deuxième type de commentaire (ajouté tardivement) est le mot \ qui ignore la ligne entière. Il est utilisé pour de vrais commentaires autre que le renseignements de paramétres. Gestion de la pile : permet de commuter l'ordre des données dans la pile. Par exemple DUP duplique le sommet, DROP le supprime, SWAP permute le sommet de la pile avec l'avant dernier, le mot . consomme et affiche le sommet de la pile, etc. Mémoire : Le mot @ permet d'accéder au contenu d'une adresse. Le mot ! permet de stocker une valeur dans une adresse. Une variable en Forth est une mémoire nommée. Exemple :
La première ligne créé une variable nommée TOTO avec la valeur 15. La deuxième ligne permet d'accéder au contenu de la variable puis l'affiche. La troisième ligne change la valeur de TOTO avec la valeur 14. La quatrième ligne crée un mot couramment utilisé. La dernière affichera la valeur 14. Base : Forth permet d'afficher les nombres dans la base désirée. Exemple amusant :
La première ligne redéfinit un mot courant du Forth permettant de passer en base 16 (hexadécimal). Par défaut Forth est en base 10 (décimale) donc la deuxième ligne doit générée une erreur (l'utilisateur voulait empiler le nombre a en base 16 (valant 10 en base 10) mais comme nous sommes en base 10 Forth détecte que ce mot lui est inconnu. Les lignes 3 à 5 permet de vérifier que nous sommes en base 16, la ligne 5 ne doit plus générer d'erreur. Ligne 7 à 9, l'utilisateur tente naïvement de repasser en base décimale mais cela échoue car le nombre 10 est interprète en base hexadécimale valant 16 en base décimale. Donc il n'y a pas eu de changement de base Mots de structure : les classiques structures de contrôle connues des autres langages (comme le C) sont : le test conditionnel if-then-else, la boucle do-loop, la boiucle for, etc. Comme Forth utilise le système polonais inversé même le if-then-else du C devient IF ELSE THEN. Cela peut perturber le débutant mais on peut renommer THEN par FI ou ENDIF ce qui aidera les débutants.
De plus IF ELSE THEN doivent obligatoirement être utilisés dans une définition sinon le compilateur échouera. En mode interprétation if faudra utiliser les mots IF ELSE THEN qui équivaut au #if #else #endif du langage C. Le lien suivant explique en quoi Moore a encore réduit le nombre de mots de structure. Par exemple le ELSE n'existe plus. Avant :
Devient :
Arithmétique : addition, conversion entier signé et non signé, opération booléenne. Point important : contrairement à des langage tel que C où le faux vaut 0 et le vrai vaut toutes valeurs différentes de 0 (mais où 1 est en général retourné), en forth le faux vaut -1. Le lien en parle plus longuement. Sinon, voici un exemple :
La pile de données des Forth permet uniquement de manipuler des entiers (signés et non signés) ou des adresses de la machine virtuelle (qui sont finalement vues comme un entier), les valeurs en floatants (float et double du langage C) ne sont pas gérées nativement, une bibliothèque devant alors être chargée pour les gérer. En fait, par défaut, Forth n'utilise pas une seule pile mais deux piles. La deuxième, appelée pile de retour, sert à l'interpréteur Forth pour mémoriser l'ordre d'exécution des mots appelant d'autres mots. Cette pile est automatiquement manipulée par lui mais laisse, quand même, à l'utilisateur la possibilité de déplacer et stocker temporairement des éléments de la pile de données (ce qui n'est pas toujours sans risque). Nous y reviendrons plus tard. Des Forth modernes peuvent ajouter nativement des piles de données supplémentaires comme une pile d'entiers (appelés pile alternative) et/ou une pile des floatants. En général, la pile de donnée supplémentaire permet d'éviter l'utilisation de la pile de retour comme stockage temporaire et rendant l'interpréteur Forth plus fiable aux erreurs de programmation. Un système de sécurité permet de vérifier que les piles ne débordent pas (par le haut ou par le bas) et prévient l'utilisateur en arrêtant l'exécution du mot en cours. Charles H. Moore quand à lui préfère utiliser un tampon circulaire par rapport à une pile. Il n'y a plus de risque possible de débordement de mémoire mais il n'a pas ajouté de système pour prévenir l'utilisateur car selon lui le développeur doit maîtriser le nombre d'opérandes qu'il manipule. De plus, ce système permet de décider à tout instant la pile comme étant vide. On constatera après l'exécution du dernier exemple, que la profondeur de pile des donnée n'a pas été changée. Ceci n'est pas une contrainte mais le standard Forth impose de laisser intact la profondeur des piles pour au moins deux cas :
Fonctionnement de interpréteur externeUn interpréteur Forth fonctionne très simplement : tant que le flux d'entrée n'est pas fini, les mots sont lus les uns après les autres. L'interpréteur possède deux modes : soit il est mode exécution, soit il est en mode compilation. En mode EXECUTION :
En mode COMPILATION les mots non-immédiats qui sont lus, s'ils sont connus du dictionnaire, leurs CFA sont ajoutés les uns à la suite des autres au dans la définition courante du dictionnaire. Si un mot n'est pas connu une erreur prévient l'utilisateur et arrête le processus. Du peu de mots Forth que l'on a vus, c'est le mot : qui force le mode COMPILATION de l'interpréteur alors que le mot ; le fait passer en mode EXECUTION. On peut déjà se poser la question comment l'inter peut passer en mode exécution avec le mot ; s'il ne fait que compiler tous les mots qu'il voit; logiquement, il ne devrait jamais s'arrêter de compiler. La réponse vient du champ FLAGS où l'un des bits permet de rendre un mot Forth immédiat. Par immédiat on sous-entend que le mot sera immédiatement exécuté même si l'interpréteur est en mode compilation. Attention: un mot immédiat ne commute pas le mode de l'interpréteur : il est simplement interprété ! On raffine donc le définition et l'on profite pour ajouter un cas particulier : En mode COMPILATION :
Si l'inter lit tous les mots les uns après les autres, avec le code
FOO n'étant pas défini, l'interpréteur devrait déclencher une erreur. En fait non, car comme on l'a dit l'interpréteur sait lire le mot suivant et le mot : quand il est exécuté va lire le mot suivant, à savoir FOO afin de créer une nouvelle entrée dans le dictionnaire. Par conséquent le mot suivant que l'interpréteur va lire sera *. L'interpréteur Forth à deux modes (états) soit il est en mode interprétation soit en mode compilation. Des mots comme : et ; permettent respectivement de passer en mode compilation puis interprétation.
Le mot IMMEDIAT rend immédiat le dernier mot du dictionnaire. Pour rappel un mot immédiat n'est pas compilable mais directement interprété quand l'interpréteur est en mode compilation. Voici un exemple plus évolué de mot immédiat : on se propose d'ajouter un système de commentaires à Forth. Pour cela on suppose que le mot TYPE existe déjà (ce qui en général le cas) ce mot permet d'ignorer les caractères du tampon d'entrée jusqu'au caractère indiqué comme paramère.
Dés que le 'analyseur exécutera le mot ( il ignorera tous les mots jusqu'à trouver le mot ) et comme il est immédiat il va fonctionner dans une définition (quand l'interpréteur sera en mode compilation). Notons qu'il existe un autre type de commentaire Forth le mot \ ignore entièrement la ligne courante. Si l'on désire rendre immédiat un certains nombre de mots de façon temporaire dans une définition il faut les délimiter par les mots [ et #]. Ces mots modifient l'état de l'interpréteur: interprétation ou compilation mais ne changent pas le smudge bit des mots. Si l'on désire compiler un mot immédiat il faut placer avant le mot [COMPILE#] (qui lui aussi est immédiat). Le mot COMPILE joue le même rôle que [COMPILE#] mais est non immédiat. Il permet de ... TODO A FINIR ... Notons que ces mots sont désuets à cause du risque de confusion pour les développeurs pour se souvenir de qui est immédiat, par conséquent on utilisera plutôt le mot POSTPONE qui appelle l'un ou l'autre automatiquement selon le caractère immédiat du mot qui lui succéde. On se propose de créer deux nouveaux Forth : DO et LOOP qui permettent de faire une boucle. On supposera l'existence d'un mot Forth (non officiel) 0BRANCH qui fait un saut relatif à la condition que le sommet de la pile vaut 0.
Voici un exemple typique de ces mots qui affiche ...
Fonctionnement de interpréteur interne
Expliquer le mot COMPILE
Variables et constanteNous avons vu que Forth permet de stocker des mots Forth mais il peut également servir à stocker de la mémoire. En effet on a dit dans une section précédente que la variable DP permet de changer où HERE indique le premier emplacement libre. Par conséquent on peut se réserver des emplacements mémoires et l'idéal est de donner un nom aux emplacemets. Le mot ALLOC permet de déplacer DP du nombre d'octet voulu (un ALLOC pour 1 cellule est le mot ,) Un langage evolutif
BUILD DOES, OO
|
||||||||||||||||||||||||||||
|