Nous avons choisi de partir de travaux antérieurs de l'équipe sur la gestion de mémoire des langages de programmation logique [Bekkers et al. 84, Ridoux 86, Ridoux 87, Bekkers et al. 86, Bekkers et al. 92]. Ces travaux ont abouti à la définition d'une mémoire virtuelle adaptée à l'exécution des programmes logiques, et munie d'une gestion de mémoire efficace : la mémoire * [Ridoux 91]. La mémoire fait comme seules hypothèses sur l'exécution des programmes logiques que ceux-ci créent dynamiquement des structures contenant des variables, qu'ils peuvent substituer des structures quelconques aux variables, et qu'ils peuvent effectuer des retours en arrière pour parcourir des arbres de recherche en profondeur. Les substitutions peuvent donc être défaites. La mémoire peut donc être définie comme un organe capable de stocker une pile de termes modifiables réversiblement.
Nous avions montré par le passé comment plusieurs extensions de Prolog pouvaient être codées dans : l'unification de termes rationnels et le «gel» (freeze) de la résolution [Le Huitouze 88, Le Huitouze 90b], l'unification de termes «à traits» (feature terms ou -termes) [Ridoux 89] et l'unification d'expressions booléennes [Ridoux et Tonneau 90]. Nous étions donc confiants dans la possibilité de coder les -termes et leur réduction, l'augmentation dynamique du programme par l'implication, et l'unification d'ordre supérieur. Nous étions aussi assurés de bénéficier d'une gestion de mémoire efficace quelle que soit la complexité des structures de données.
L'implémentation d'un langage de programmation doit bien sûr simuler fidèlement la logique du langage, mais aussi décider de son intégration dans un environnement de programmation, même réduit à un système d'exploitation. C'est pourquoi nous parlons de «système-langage» pour décrire une implémentation. Nous avons décrit dans la section «Langage et machine»* -- comment ces choix induisent la perception qu'un utilisateur a d'un langage de programmation. Utiliser est notre premier choix, mais ne répond pas à toutes les questions.
Utiliser pour implémenter un langage de programmation logique nécessite de la spécialiser et de l'étendre. D'une part, offre un ensemble de constructions qui sont orthogonales, mais dont toutes les combinaisons ne sont pas utiles pour toutes les applications. On spécialise en encapsulant dans une mémoire de plus haut niveau les combinaisons utilisées par une implémentation d'un langage de programmation. Dans le cas de Prolog, il faut se demander comment représenter les continuations*, les -termes*, les variables logiques*, les constantes universelles*, etc [Brisset et Ridoux 92b, Brisset et Ridoux 92a]. L'idée générale de la réponse est que selon le point de vue où contient une pile de termes modifiables réversiblement, la pile représente la continuation d´échec*, et que tout le reste, y compris les continuations de succès* et de programme*, est représenté par des termes. Les rédex, les variables logiques et la continuation de programme sont représentés par des termes modifiables. Les représentations étant définies, il faut ensuite réaliser les opérations de Prolog (création et parcours de termes, de clauses ou de points de choix, unification, -réduction, etc) à l'aide de celle de (création, parcours, substitution, empilement, dépilement, etc).
D'autre part, n'offre aucun service pour décrire l'enchaînement des opérations (le contrôle), ni pour allouer la mémoire qu'elle gère. Ce dernier point garantit son indépendance d'avec les systèmes d'exploitation. Il laisse choisir au concepteur du système-langage une «politique de gestion de mémoire» qui décrit d'où vient la mémoire gérée par (allocation statique à la création du processus d'exécution, ou dynamique, avec ou sans limite, etc.) et que faire de la mémoire rendue inutile par (la rendre au système, la garder pour plus tard, dans quelle proportion ? Etc.). Concernant le contrôle, il faut se rappeler que est une mémoire abstraite, alors que la et la plupart des dispositifs décrits pour implémenter des langages de programmation en les compilant sont des machines abstraites. Il faut donc adjoindre à une «unité centrale» sous la forme de l'interpréteur d'un langage de programmation (dans le cas présent, le langage C) dont on utilisera essentiellement les structures de contrôle. Dans le cas de la réalisation d'un système Prolog ou Prolog, l'idée est de traduire chaque prédicat en une procédure dont le seul effet est d'altérer les continuations ( *) et de revenir à une forme dégénérée de boucle d'interprétation [Brisset et Ridoux 92b, Brisset et Ridoux 92a]. En particulier, cette procédure n'est pas récursive, même si le prédicat l'est. Il s'agit là bien évidemment d'un schéma général, et il est très important de reconnaître des situations où il n'est pas nécessaire de revenir à la boucle d'interprétation.
Figure 1: Constitution des applications
La figure 1 illustre l'architecture du système . Le quart sud-ouest, «Noyau», symbolise la constitution du noyau : principalement spécialisation et extension de . Cette partie est produite par les concepteurs du système et préexiste aux autres parties. Le quart nord-ouest, «Bibliothèques», symbolise la constitution des bibliothèques du système. Ces bibliothèques préexistent aux deux parties restantes et l'une d'entre elle, la bibliothèque standard, préexiste à toutes les autres. Cette partie est aussi produite par les concepteurs du système. Le quart nord-est, «Modules d'application», symbolise la constitution d'une application par un utilisateur quelconque. Elle peut être faite de plusieurs modules, compilés séparément. Enfin, le quart sud-est, «Exploitation», symbolise l'exécution d'une application. C'est à ce moment là seulement que les ressources gérées par sont allouées et utilisées. Entre-temps, les programmes issus des différentes parties ont été compilés et reliés entre eux. Les compilations sont figurées par les flèches qui traversent les lignes discontinues. Elles peuvent être faites dans un ordre quelconque pourvu qu'il respecte les dépendances entre modules et bibliothèques. En général, les compilations sont faites aussitôt que le source correspondant est constitué, mais ce n'est pas obligatoire. L'édition de lien est figurée par les lignes qui convergent vers la case marquée «Exécutable». Elle utilise pour la plus grande part l'éditeur de lien du système hôte.
Les quarts nord-ouest et nord-est figurent tous les deux une activité de programmation en Prolog, mais le programmeur n'est pas le même pour les deux parties. Les bibliothèques sont produites par les concepteurs du système : elles sont leurs premières applications. Les modules d'application viennent en second et sont programmés par les utilisateurs.
Les flèches qui pointent vers les cases marquées «relogeable» symbolisent la compilation des fichiers sources. Celles venant du quart sud-ouest impliquent qu'un compilateur pour le langage d'implantation de la mémoire Prolog est disponible. Il s'agit de C et il est facile de trouver un compilateur C sur la plupart des installations. Les flèches venant des quarts nord-ouest et nord-est impliquent qu'un compilateur Prolog est disponible. En fait, le compilateur Prolog de est lui aussi une application écrite en Prolog et il est donc aussi constitué de la manière décrite par la figure 1. On voit poindre le problème de l'autocompilation qui se résout partiellement en notant que le premier système (premier dans le temps du développement) n'était pas écrit en Prolog. Il reste cependant une difficulté qui dure tout le temps du développement : les bibliothèques et le compilateur n'appartiennent pas à la même version de système-langage. Ils peuvent différer par la logique de leur langage d'implémentation, en cas de changement de version avec modification de la spécification du langage, mais ils peuvent aussi différer par les idiomes recommandés, en cas de changement de version avec modification du schéma d'exécution. Il est donc quasiment impossible de partager les modules qui dans les bibliothèques et dans le compilateur implémentent les mêmes fonctionnalités.
Le choix du point de départ ne produit pas automatiquement un schéma d'exécution efficace. Nous n'allons pas détailler tous les points qui concourent selon nous à l'efficacité de notre système. Nous n'en retenons que deux qui sont des exemples des interactions entre utilisation et implémentation. Le premier est la matérialisation au niveau de l'implémentation d'une idée qui est pertinente à tous les niveaux d'explication de Prolog : on ne calcule qu'avec des combinateurs*. Le second est le support d'une autre idée : la -équivalence* donne des rôles symétriques à la quantification universelle dans les buts* et à la -abstraction*.