La programmation extensible est un terme utilisé en informatique pour décrire un style de programmation informatique qui se concentre sur les mécanismes permettant d'étendre le langage de programmation , le compilateur et le système d'exécution (environnement). Les langages de programmation extensibles, prenant en charge ce style de programmation, étaient un domaine de travail actif dans les années 1960, mais le mouvement a été marginalisé dans les années 1970. La programmation extensible est devenue un sujet d'intérêt renouvelé au 21e siècle.
Mouvement historique
Le premier article généralement associé au mouvement des langages de programmation extensibles est l'article de M. Douglas McIlroy de 1960 sur les macros pour les langages de programmation de haut niveau . Une autre description précoce du principe d'extensibilité apparaît dans l'article de Brooker et Morris de 1960 sur le compilateur-compilateur . L'apogée du mouvement a été marquée par deux symposiums universitaires, en 1969 et 1971. En 1975, un article de synthèse sur le mouvement par Thomas A. Standish était essentiellement une autopsie. Le Forth était une exception, mais il est passé essentiellement inaperçu.
Caractère du mouvement historique
Comme on l'imagine généralement, un langage extensible est constitué d'un langage de base fournissant des fonctionnalités de calcul élémentaires et d'un métalangage capable de modifier le langage de base. Un programme est alors constitué de modifications du métalangage et de code dans le langage de base modifié.
La technique d'extension de langage la plus importante utilisée dans le mouvement était la macro-définition. La modification de la grammaire était également étroitement associée au mouvement, ce qui a abouti au développement éventuel de formalismes de grammaire adaptative . La communauté du langage Lisp est restée séparée de la communauté du langage extensible, apparemment parce que, comme l'a observé un chercheur,
tout langage de programmation dans lequel les programmes et les données sont essentiellement interchangeables peut être considéré comme un langage extensible. ... cela peut être facilement constaté par le fait que Lisp est utilisé comme langage extensible depuis des années.
Lors de la conférence de 1969, Simula a été présenté comme un langage extensible.
Standish a décrit trois classes d'extensions de langage, qu'il a nommées paraphrase , orthophrase et métaphrase (sinon paraphrase et métaphrase étant des termes de traduction ).
- Paraphrase définit une fonctionnalité en montrant comment l'échanger contre quelque chose de défini auparavant (ou à définir). À titre d'exemples, il cite les définitions de macros, les définitions de procédures ordinaires, les extensions grammaticales, les définitions de données, les définitions d'opérateurs et les extensions de structures de contrôle.
- L'orthophrase ajoute à un langage des fonctionnalités qui ne pourraient pas être obtenues à l'aide du langage de base, comme l'ajout d'un système d'entrée/sortie (E/S) à un langage de base auparavant dépourvu de primitives d'E/S. Les extensions doivent être comprises comme des orthophrases relatives à un langage de base donné, car une fonctionnalité non définie en termes de langage de base doit être définie en termes d'un autre langage. Cela correspond à la notion moderne de plug-ins .
- La métaphrase modifie les règles d'interprétation utilisées pour les expressions préexistantes. Cela correspond à la notion moderne de programmation réflexive (réflexion).
La mort du mouvement historique
Standish a attribué l'échec du mouvement d'extensibilité à la difficulté de programmer des extensions successives. Un programmeur peut construire un premier shell de macros autour d'un langage de base. Ensuite, si un deuxième shell de macros est construit autour de celui-ci, tout programmeur ultérieur doit être intimement familier avec le langage de base et le premier shell. Un troisième shell nécessiterait une familiarité avec le langage de base et les deux premier et deuxième shells, et ainsi de suite. Protéger un programmeur des détails de niveau inférieur est l'objectif du mouvement d'abstraction qui a supplanté le mouvement d'extensibilité.
Malgré la présentation antérieure de Simula comme extensible, en 1975, l'étude de Standish ne semble pas avoir inclus dans la pratique les nouvelles technologies basées sur l'abstraction (bien qu'il ait utilisé une définition très générale de l'extensibilité qui aurait techniquement pu les inclure). Une histoire de l'abstraction de la programmation de 1978, depuis l'invention de l'ordinateur jusqu'à cette date, ne faisait aucune mention des macros et ne donnait aucune indication que le mouvement des langages extensibles ait jamais eu lieu. Les macros ont été provisoirement admises dans le mouvement d'abstraction à la fin des années 1980 (peut-être en raison de l'avènement des macros hygiéniques ), en se voyant attribuer le pseudonyme d'abstractions syntaxiques .
Mouvement moderne
Au sens moderne, un système qui prend en charge la programmation extensible fournira toutes les fonctionnalités décrites ci-dessous .
Syntaxe extensible
Cela signifie simplement que le ou les langages sources à compiler ne doivent pas être fermés, fixes ou statiques. Il doit être possible d'ajouter de nouveaux mots-clés, concepts et structures au(x) langage(s) source(s). Les langages qui permettent l'ajout de constructions avec une syntaxe définie par l'utilisateur incluent Coq , Racket , Camlp4 , OpenC++, Seed7 , Red , Rebol et Felix. Bien qu'il soit acceptable que certaines fonctionnalités fondamentales et intrinsèques du langage soient immuables, le système ne doit pas s'appuyer uniquement sur ces fonctionnalités du langage. Il doit être possible d'en ajouter de nouvelles.
Compilateur extensible
Dans la programmation extensible, un compilateur n'est pas un programme monolithique qui convertit le code source en sortie exécutable binaire. Le compilateur lui-même doit être extensible au point d'être en réalité une collection de plugins qui aident à la traduction du code source en n'importe quel autre langage . Par exemple, un compilateur extensible prendra en charge la génération de code objet, de documentation de code, de code source reformaté ou de toute autre sortie souhaitée. L'architecture du compilateur doit permettre à ses utilisateurs de « pénétrer dans » le processus de compilation et de fournir des tâches de traitement alternatives à chaque étape raisonnable du processus de compilation.
Pour la simple tâche de traduction du code source en quelque chose qui peut être exécuté sur un ordinateur, un compilateur extensible doit :
- utiliser une architecture de plug-in ou de composant pour presque tous les aspects de sa fonction
- déterminer quelle langue ou variante de langue est en cours de compilation et localiser le plug-in approprié pour reconnaître et valider cette langue
- utiliser des spécifications de langage formel pour valider syntaxiquement et structurellement des langages sources arbitraires
- aider à la validation sémantique de langages sources arbitraires en invoquant un plug-in de validation approprié
- permettre aux utilisateurs de choisir parmi différents types de générateurs de code afin que l'exécutable résultant puisse être ciblé pour différents processeurs, systèmes d'exploitation, machines virtuelles ou autres environnements d'exécution.
- fournir des fonctionnalités pour la génération d'erreurs et des extensions à celle-ci
- autoriser de nouveaux types de nœuds dans l' arbre de syntaxe abstraite (AST),
- autoriser de nouvelles valeurs dans les nœuds de l'AST,
- permettre de nouveaux types d'arêtes entre les nœuds,
- prendre en charge la transformation de l'AST d'entrée, ou de parties de celui-ci, par un « pass » externe
- prendre en charge la traduction de l'AST d'entrée, ou de parties de celui-ci, dans une autre forme par un « pass » externe
- aider au flux d'informations entre les passes internes et externes car ils transforment et traduisent l'AST en de nouvelles AST ou d'autres représentations
Durée d'exécution extensible
Lors de l'exécution, les systèmes de programmation extensibles doivent permettre aux langages d'étendre l'ensemble des opérations qu'ils autorisent. Par exemple, si le système utilise un interpréteur de byte-code , il doit permettre la définition de nouvelles valeurs de byte-code. Comme pour la syntaxe extensible, il est acceptable qu'il existe un ensemble (assez restreint) d'opérations fondamentales ou intrinsèques qui soient immuables. Cependant, il doit être possible de surcharger ou d'augmenter ces opérations intrinsèques afin que des comportements nouveaux ou supplémentaires puissent être pris en charge.
Contenu séparé de la forme
Les systèmes de programmation extensibles doivent considérer les programmes comme des données à traiter. Ces programmes doivent être complètement dépourvus de toute sorte d'informations de formatage. L'affichage visuel et l'édition des programmes pour les utilisateurs doivent être une fonction de traduction, prise en charge par le compilateur extensible, qui traduit les données du programme dans des formes plus adaptées à la visualisation ou à l'édition. Naturellement, il doit s'agir d'une traduction bidirectionnelle. Cela est important car il doit être possible de traiter facilement les programmes extensibles de diverses manières. Il est inacceptable que les seules utilisations de l'entrée en langage source soient l'édition, la visualisation et la traduction en code machine. Le traitement arbitraire des programmes est facilité par le découplage de l'entrée source des spécifications sur la façon dont elle doit être traitée (formatée, stockée, affichée, éditée, etc.).
Prise en charge du débogage de la langue source
Les systèmes de programmation extensibles doivent prendre en charge le débogage des programmes utilisant les constructions du langage source d'origine, quelles que soient les extensions ou la transformation subies par le programme pour le rendre exécutable. Plus particulièrement, on ne peut pas supposer que la seule façon d'afficher les données d'exécution est dans des structures ou des tableaux . Le débogueur, ou plus exactement « l'inspecteur de programme », doit permettre l'affichage des données d'exécution dans des formes adaptées au langage source. Par exemple, si le langage prend en charge une structure de données pour un processus métier ou un flux de travail , il doit être possible pour le débogueur d'afficher cette structure de données sous la forme d'un graphique en arêtes de poisson ou d'une autre forme fournie par un plug-in.