Sciences, Technologies et Jeux



Guide du Métacompilateur PHENIX TURBO-FORTH 83
Paramétrable, Homologique, Elliptique, Néoténique, Intégal, eXtra-segment (explications dans le texte).

Qu'est-ce qu'un métacompilateur

Phénix : oiseau mythique qui renaît de ses cendres.

La métacompilation est sans doute le summum de la complexité mais aussi de l'intelligence en matière de programmation. Le but de la métacompilation consiste à redéfinir à partir d'un système donné l'intégralité d'un nouveau système. De tous les langages évolués, seul FORTH est suffisamment puissant et maniable pour disposer du concept de métacompilation. Tout système FORTH, sauf peut-être les versions des toutes premières origines du langage, est lui-même conçu et écrit en FORTH. L'outil FORTH d'écriture d'un système FORTH est son métacompilateur et il est devenu d'usage de fournir à l'utilisateur les sources du métacompilateur aussi bien que les sources du langage lui-même métacompilé. Il s'agit-là d'une caractéristique absolument unique pour un langage de programmation.

Pour l'utilisateur, disposer du métacompilateur et des sources du langage signifie qu'il possède avec son outil de programmation d'un SYSGEN extrêmement puissant l'autorisant à redéfinir selon ses besoins propres l'outil-même dont il se sert. Les perspectives offertes sont vertigineuses: le concept illustre à ravir les fondements de l'intelligence créatrice auto-référencée tels que l'a développé par exemple Douglas Hofstadter dans son livre "Gödel, Escher, Bach Les Brins d'une Guirlande Eternelle" (InterEditions 1985).

Historiquement, TURBO-FORTH a été métacompilé en FORTH-83 à l'aide du métacompilateur F83 de Laxen et Perry. Au fil des générations successives, le métacompilateur et le source métacompilé ont été modifiés jusqu'au système actuel qui assure sa propre autogénération intégrale. Rien n'empêche le Forthien de poursuivre cette "boucle étrange" pour son propre compte ou pour créer un FORTH différent.

Nous verrons toutefois que toute entreprise de métacompilation requiert de solides connaissances en FORTH, un QI confort(h)able, beaucoup de rigueur et pas mal de patience dans les débuts pour appréhender le plus complexe de tous les concepts FORTH.

Les vocabulaires de métacompilation

Toute la difficulté de compréhension d'un métacompilateur réside dans l'ambiguïté des mots. Il n'est pas possible d'échapper à cette ambiguïté dans la mesure où le système créé reprend le langage du système créateur. On parle ici de système-hôte et de système-cible. L'ambiguïté est encore accrue du fait qu'un bon métacompilateur est "homologique": il compile "comme" un compilateur usuel, c'est-à-dire que l'écriture de la cible se fait en Forth naturel comme si le code devait être une simple extension de l'hôte. Or la cible, système complet et autonome, ne peut bien entendu pas être un sous-ensemble de l'hôte: elle réside obligatoirement à l'extérieur de celui-ci.

Pour lever l'ambiguïté des mots, FORTH utilise son concept de VOCABULAIRES. Un même mot aura une signification différente selon qu'il appartient à tel ou tel vocabulaire. C'est le CONTEXT, c'est-à-dire la liste des vocabulaires actifs à un instant donné, qui permet au système hôte de savoir quel est de tous les homonymes le sens du mot à interpréter. La gestion des vocabulaires en métacompilation est une étape-clé qui conditionne tout le processus.

Il n'y a pas moins de 8 vocabulaires de travail pour l'hôte métacompilant sans compter les images des vocabulaires de la cible.

Il importe que le métacompilateur dispose de mots permettant de bien établir à chaque instant les vocabulaires de contexte ainsi que le vocabulaire courant où sont apportées les nouveaux mots. Il n'est pas inutile même de prévoir des synonymes dans META dans la mesure où celui-ci va rapidement se charger avec des références portant des noms de vocabulaires de la cible. Ainsi le mot FORTH dans META ne va plus placer FORTH dans CONTEXT mais référencer le vocabulaire FORTH de la cible: il faudra utiliser le synonyme immédiat [FORTH] pour parler du vocabulaire FORTH de l'hôte.

Les mots de META comme IN-META, IN-TRANSITION, IN-TARGET ou SWITCH (qui conserve et restitue un contexte), définis en début du métacompilateur sont fondamentaux: c'est leur oubli ou mauvais usages qui sont responsables de tant d'erreurs ou incompréhensions de métacompilation.

La cible

Le système-cible est toujours inerte pour le système-hôte. Pour celui-ci, il ne s'agit que d'une suite d'adresses où est compilé un code qui lui est étranger. En fin de métacompilation, cette zone sera sauvegardée et l'utilisateur pourra sortir du système hôte pour lancer le nouveau système. Ce lancement peut toutefois être réalisé par l'hôte s'il possède un intégrateur. Nous verrons que TURBO-FORTH se génère ainsi en deux étapes: métacompilation du KERNEL puis lancement de celui-ci qui s'étend lui-même avec le NOYAU.

Mais revenons à la cible. Son site peut être très variable selon les systèmes. Ce pourra être une zone non occupée du système hôte, par exemple en mémoire haute entre dictionnaire et tampons. Ce peut être en mémoire virtuelle dans des tampons régulièrement sauvés dans la mémoire de masse. Sur un système disposant d'une grande mémoire multi-programmes, le plus logique est d'utiliser l'espace mémoire hors-hôte. Dans le cas du TURBO-FORTH, la métacompilation s'opère dans un extra-segment alloué. L'avantage d'un tel système est de pouvoir utiliser des adresses 16 bits réelles, sans aucun calcul de déplacement entre les adresses de métacompilation et les adresses définitives du système créé.

Quelque soit le site de métacompilation retenu, le métacompilateur doit disposer en premier lieu d'une série de mots très simples permettant de lire ou d'écrire dans ce site ainsi que d'un pointeur avançant au fil de la métacompilation dans cet espace. Par analogie avec les mots fondamentaux du compilateur forth @ C@ ! C! DP HERE ALLOT , C, le métacompilateur dispose des mots @-T C@-T !-T C!-T DP-T HERE-T ALLOT-T ,-T C,-T etc qui lui suffisent à définir par la suite toutes les opérations sur la cible. Cette portion du métacompilateur est spécifique: son changement permet d'envisager tout autre site interne ou externe de métacompilation.

Ces mêmes mots d'accès mémoire au site temporaire de construction de la cible seront en outre utilisés pour obliger l'assembleur à n'assembler du code que dans cet espace objet réservé.

Interpréteur et mécanique de métacompilation

Tout compilateur commence par un interpréteur: un texte source est interprété et les mots traduits en ordres de rangement de certaines valeurs à certaines adresses. Le compilateur usuel compile de nouvelles définitions dans le système FORTH, le métacompilateur compile de nouvelles définitions dans un système cible.

En métacompilation, l'interpréteur de l'hôte reste l'interpréteur naturel du FORTH: les notions comme SOURCE, INTERPRET, INCLUDE ne sont pas modifiées. Le texte source à métacompiler est interprété mot-à-mot en une seule passe. Les mots sont exécutés après recherche dans le ou les vocabulaires de contexte. Bien souvent cependant, l'interpréteur est obligé de faire de courts retour en arrière dans le source pour à la fois métacompiler et conserver les références.

Prenons un exemple qui expliquera bien toute la mécanique de la métacompilation. Supposons que nous soyons en métacompilation META IN-META et que nous désirions métacompiler une variable. Nous écrirons dans le texte:

Le mot VARIABLE de META va en réalité faire trois compilations : Cette triple compilation suppose évidemment une triple interprétation du mot TEST après VARIABLE avec deux retours en arrière devant TEST dans le flot d'interprétation. Ainsi s'explique la mécanique particulière des mots HEADER TARGET-CREATE ou RECREATE qui compilent une fois, deux fois, trois fois le même mot à des endroits différents.

Toutes les définitions ne sont pas compilées trois fois. Un LABEL par exemple n'est compilé qu'une fois dans ASSEMBLER: c'est une étiquette qui n'a pas de référence dans la cible et n'est utilisée que par le méta-assembleur. Un mot codé ou en haut niveau n'est compilé que dans la cible et dans TARGET: la seule référence de son cfa est suffisante au métacompilateur.

Les références avant

D'ordinaire, pour limiter les risques d'erreur le FORTH refuse de reconnaître un mot qui n'a pas été préalablement défini. En métacompilation, il est très difficile de concevoir un système où tous les mots n'utilisent que ses prédécesseurs. Bien souvent il arrive qu'un mot contiennne la référence d'un mot ultérieur. C'est pourquoi, le métacompilateur dispose d'un système particulier de définition et de résolution des références avant.

Soit à métacompiler la définition du mot EXEMPLE contenant le mot ESSAI qui ne sera défini que plus tard. Le métacompilateur va définir alors une référence ESSAI dans le vocabulaire FORWARD. Ce mot pointe sur la dernière adresse cible où doit être placée la vraie référence de ESSAI. Lors de la métacompilation de EXEMPLE, ESSAI placera une adresse-cible chaînant sur la précédente adresse à référencer tandis qu'ESSAI pointera sur cette adresse-cible dans EXEMPLE. Ainsi se construit à chaque appel de ESSAI avant sa véritable définition une chaîne d'adresses-cible à référencer. Lorsque ESSAI sera défini, la véritable référence à métacompiler sera replacée de proche en proche dans la chaîne des adresses-cible pointées par le FORWARD ESSAI. Cette opération s'appelle la résolution de la référence avant. Bien entendu la métacompilation complète suppose que la référence avant ESSAI soit résolue tôt ou tard. En fin de métacompilation il est d'ailleurs prévu que le système affiche la liste des références avant non résolues si le programmeur en a malheureusement oublié dans son source.

Le système de métacompilation des références avant peut être manuel: le programmeur donne l'ordre de créer une référence avant par FORWARD: en préambule de toute utilisation et plus tard ordonne la résolution par RESOLVES quand le mot référencé est métacompilé un peu à l'image de ce qu'on fait habituellement avec DEFER puis IS. Ce système peut être semi-automatique comme dans le métacompilateur F83: si le métacompilateur rencontre un mot inconnu, il crée de lui-même une référence FORWARD. La résolution, en général en fin de métacompilation, reste à la charge du programmeur par l'utilisation du mot RESOLVES.

Le métacompilateur PHENIX est "Elliptique": il possède un système de références avant entièrement automatique. Si un mot à référencer est inconnu, il est aussitôt créé dans FORWARD. La résolution est également automatique: à chaque définition d'une nouvelle référence, le métacompilateur cherche à résoudre le même mot dans FORWARD. Le programmeur n'a plus à se préoccuper des procédures finales de résolution: il lui suffit de définir tous les mots dont il aura besoin dans n'importe quel ordre.

Les en-têtes dans la cible

Si le système cible ne doit pas posséder d'interpréteur ou de compilateur ce qui est notamment le cas d'un programme ou module d'application, il est économique de métacompiler la cible sans en-têtes c'est-à-dire sans vfa, lfa et nfa (champs vue, lien et nom) pour ne garder que les cfa et pfa (champs code exécutif et paramètres). Il suffit pour ce faire de donner à la variable META WIDTH la valeur zéro. Cette variable contient en fait la longueur maximale d'un nom de mot: elle est d'ordinaire fixée à 31 (codage sur 5 bits) en FORTH.

Un système cible métacompilé sans en-tête est un objet FORTH totalement fermé: il ne contient que du code et des adresses de code appelées par l'interpréteur interne du langage. Il est impossible de le décompiler, un DUMP ne permet aucun repérage, le désassemblage est inefficace pour en saisir le fonctionnement. Outre le gain en place, un tel système offre donc une excellente protection du travail conceptuel de son auteur. Les grandes applications commerciales écrites en FORTH sont bien évidemment compilées sans en-têtes.

Dans un système ouvert, il peut être intéressant de métacompiler certains mots sans en-têtes: des primitives qui deviendront inutiles, des protections, des mots transitoires ultérieurement "patchés", des mots cachés. Attention cependant aux risques de plantages si ce système permet l'utilisation d'un décompilateur.

Les types de mots métacompilés

Le métacompilateur PHENIX TURBO-FORTH offre l'avantage d'être intégral: tous les types de mots utilisés par le système FORTH sont prévus dans le métacompilateur sans qu'il soit nécessaire de définir d'autres méta-définisseurs dans le source contrairement à celui du F83 où le KERNEL contient encore des définitions pour META. Ceci est possible grâce à l'utilisation plus large des références FORWARD. Voici les 12 types de mots métacompilables:

Méta-définisseurs de nouveaux types de mots

Il peut être nécessaire à l'utilisateur de définir d'autres types de mots que ceux prévus dans le FORTH usuel puisque c'est une des caractéristiques du FORTH d'autoriser justement la définition de nouveaux mots de définition. Citons par exemple les tableaux à une ou plusieur dimensions, le type fichier, les vecteurs multiples d'exécution etc...

Dans ce cas, il faudra définir pour et dans META, en cours de métacompilation du source, les mots de métadéfinition adéquats. Le processus est assez délicat bien-sur mais une bonne compréhension des mécanismes de métacompilation et l'exemple des douze types prédéfinis permet de métacompiler les plus complexes structures.

Tout d'abord, il faut définir dans FORWARD, avec FORWARD: "TYPE", le code exécutif des mots de deuxième génération. Ensuite il faut définir dans META (IN-META), le métadéfinisseur. Utiliser alors H: et H; pour définir ce mot puisque : et ; sont réservés à la métacompilation elle-même. Dans cette définition, on placera l'ordre de métacompilation du cfa du type spécial par [FORWARD] "TYPE". Si le mot métadéfini doit subir une initialisation quelconque en fin de métacompilation, prévoir une recréation (RECREATE) dans META. Si ce mot est susceptible d'être exécuté pour modifier la métacompilation elle-même, le recréé de META doit agir en conséquence. C'est le cas des pseudo-vocabulaires recréés par VOCABULARY. Lors de cette étape, bien faire attention à compiler dans META les bons mots du système hôte (utiliser [FORTH] et [META] dans la définition H:).

Ensuite, il faudra métacompiler le mot de définition et résoudre ainsi le code exécutif des mots métadéfinis. S'il s'agit d'un mot de définition bas niveau, un LABEL "TYPE" code et une définition :...;USES "TYPE" , ou une définition :...;CODE LABEL "TYPE" code résoudra toute la structure du nouveau type. S'il s'agit d'un mot de très haut niveau, c'est-à-dire d'une structure CREATE...DOES>, il faudra faire suivre le DOES> de META par la référence "TYPE" de FORWARD. Là encore, le méta-compilateur PHENIX permet une résolution immédiate.

La métacompilation contrôlée

Le métacompilateur PHENIX du TURBO-FORTH introduit un nouveau concept original: la métacompilation contrôlée d'applications compactes. Le terme "Néoténique" est un terme zoologique qualifiant un organisme capable de se reproduire sous forme larvaire. Mais il convient tout d'abord de rappeler ce qu'est un COMPACT.

Un COMPACT est une application FORTH compilée sans en-têtes ni librairie. Seuls sont compilés les mots nécessaires à l'application définitive. Un tel programme offre évidemment des avantages multiples: gain de place, chargement rapide, exécution optimisée, protection maximale, concept modulaire dans un environnement intégré.

Pour compiler un COMPACT, il convient de métacompiler sans en-tête un système FORTH d'abord limité aux primitives necessaires à l'application puis étendu à l'application complète elle-même. Un utilitaire que l'on appelle Scrutateur Récursif de Primitives permet par une décompilation récursive de connaître quels sont les mots utilisés dans un mot ou un ensemble de mots d'une application. Le principe en est le "marquage" des primitives au niveau de leurs view-fields.

Plutôt que d'obliger le programmeur de COMPACT à éditer un source limité aux primitives de son application, il est plus pratique de réutiliser le KERNEL propre de TURBO-FORTH avec un système de contrôle asservi au système hôte préalablement indexé. Dans cette option, le métacompilateur PHENIX ne conservera du KERNEL que les mots homonymes "marqués" dans le vocabulaire FORTH de l'hôte. Dans ce mode contrôlé, chaque métacompilation est soit validée dans la cible soit immédiatement oubliée par un retour en arrière. Les références TARGET et FORWARD sont toutefois conservées avec résolution toujours automatique des références valides. Les initialisations portant sur des données non valides n'ont pas d'effet.

Pour créer de façon rapide un COMPACT, il faut donc:

Un petit exemple de métacompilation d'un COMPACT accompagne le métacompilateur PHENIX. La réalisation de COMPACTS y apparaît comme un exercice FORTH somme toute assez simple !

Quelques précisions sur le système de contrôle. Le seul vocabulaire détenteur des marques est le vocabulaire FORTH de l'hôte. Si l'application utilise des mots non compilés dans FORTH, alors que dans le COMPACT il n'y aura bien-sur aucune notion de vocabulaires puisqu'il n'y aura ni en-têtes ni interpréteur, il faudra soit simplifier l'application sur ce point, soit définir dans FORTH des mots fictifs homonymes et individuellement marqués, soit encore supprimer le mode contrôlé lors de la métacompilation de l'application. Ainsi, même si tous les mots FORTH sont marqués, la métacompilation de KERNEL en CHECKING ON ne donnera pas un TURBO-FORTH complet (exemple le END-CODE du vocabulaire ASSEMBLER).

Le système de compilation contrôlée peut être suspendu avec la directive FORCED. Après cet ordre de META, toute métacompilation est forcée dans la cible sans contrôle d'asservissement. Le contrôle peut ensuite être rendu par la directive UNFORCED.

Les LABELS sont toujours en compilation forcée, même ceux qui peuvent être inutiles au COMPACT, ceci pour éviter tout oubli ou erreur difficile à détecter dans la mesure où les labels ne sont pas marquables et peuvent être utilisés très à distance de leur définition. De même, pour simplifier le marquage et limiter les erreurs, un petit nombre de mots est d'emblée forcé dans KERNEL. Il s'agit de COLD, BYE, de la zone USER avec UP et de 'TIB qui comme UP est initialisé par la procédure codée de démarrage à froid. Dans ces conditions, un COMPACT minimal, qui ne ferait absolument rien, en plaçant juste BYE dans COLD occuperait 340 octets. C'est là le proton du noyau TURBO-FORTH.

La compilation contrôlée peut aussi s'effectuer avec des en-têtes si l'utilisateur désire métacompiler un système avec interpréteur voire compilateur simplifié.

Un système de contrôle différent de celui basé sur l'asservissement au FORTH hôte via les vfa marqués peut être installé en changeant le vecteur du seul mot ?VALID. Ce mot est celui qui contrôle la validité de chaque métacompilation en affectant la variable VALID.

Options et utilisation du métacompilateur PHENIX

Le métacompilateur PHENIX propose 4 options modifiables par l'utilisateur. Ces options sont fixées par défaut dans l'optique du clonage in extenso de TURBO-FORTH lui-même.

L'opération de méta-reproduction de TURBO se réalise entièrement sous TURBO-FORTH par la commande INCLUDE META-BAT.

Dans le détail, cette génération s'opère en deux temps. Tout d'abord le métacompilateur produit un KERNEL complet dans le fichier TEST.COM. Puis l'intégrateur lance TEST.COM avec la commande de s'auto-étendre avec le NOYAU. Le noyau comprend les utilitaires usuels de TURBO-FORTH.

Ce procédé offre plusieurs avantages. La compilation du noyau est plus rapide que sa métacompilation. Le noyau comporte plusieurs mots de définitions dont une quinzaine pour le seul assembleur qu'il aurait fallu prédéfinir dans le métacompilateur. Le noyau n'offre guère d'utilité dans un système compact. Il est plus facile d'étendre TURBO en compilation qu'en métacompilation. La compilation autorise la gestion de plusieurs vocabulaires.

En fin d'autogénération du NOYAU, l'utilisateur pourra indexer ses vocabulaires avec les fichiers d'auto-documentation par la commande LEARN puis sauver sa version totale de TURBO par un SAVE-SYSTEM. Avant de quitter ce TURBO-FORTH cible par BYE ou F8, vérifier que le répertoire courant retrouvera bien META-BAT. En effet cette sortie se fait dans le TURBO-FORTH hôte lequel était en cours d'interprétation de META-BAT. De là, un deuxième BYE ou F8 permet enfin de revenir au DOS et de lancer le nouveau système métacompilé.

Les options du métacompilateur seront modifiées avant le chargement du source dans le cas d'une autre utilisation que la méta-reproduction de TURBO-FORTH.

Le rappel des options du métacompilateur s'obtient avec le mot OPTIONS de META. La fin d'une session de métacompilation s'effectue avec END-META qui affiche un compte-rendu et sauve éventuellement le code métacompilé. On vérifiera surtout qu'il ne reste pas des références FORWARD non résolues. Le mot .SYMBOLS peut en outre être utile pour lister la liste des mots métacompilés et validés.

Exemples

La meilleure façon de se familiariser avec les procédures de métacompilation consiste à lire et comprendre les sources. Voyez tout d'abord comment recréer tout TURBO-FORTH avec la commande

Lisez META-BAT.FTH, examinez META.FTH, parcourrez KERNEL.TXT et NOYAU.TXT.

Voyez ensuite comment créer un petit COMPACT QUESTION.COM en lançant la commande

Ce petit compact sera bien utile dans vos fichier batch: il permet de poser une question à laquelle on répondra par O ou N. Il reconnaît aussi le Y de YES et possède une réponse de normand ??? qui permet de choisir une réponse par défaut ou de reposer la question. Il fournit le paramètre 0 1 ou 2 pour la commande ERRORLEVEL.

L'application est écrite et commentée dans QUESTION.FTH. C'est volontairement qu'est utilisée une structure CASE...ENDCASE pour montrer que PHENIX est intégral: il sait métacompiler cette structure de contrôle.

D'après un texte original de Michel ZUPAN.