Article de reference

Conception et mise en œuvre des langages de programmation

Les langages de programmation sont généralement créés en concevant une forme de représentation d'un programme informatique et en écrivant une implémentation pour le concept déve...

Les langages de programmation sont généralement créés en concevant une forme de représentation d'un programme informatique et en écrivant une implémentation pour le concept développé, généralement un interpréteur ou un compilateur . Les interpréteurs sont conçus pour lire les programmes, généralement dans une variante d'un format texte , et effectuer des actions en fonction de ce qu'ils lisent, tandis que les compilateurs convertissent le code en une forme de plus bas niveau, telle que le code objet.

Conception

Lors de la conception d'un langage de programmation, de nombreux facteurs doivent être pris en compte. Certains peuvent être contradictoires (par exemple, sécurité et rapidité). Il peut être nécessaire de déterminer si un langage de programmation sera plus performant interprété ou compilé, s'il doit être typé dynamiquement ou statiquement, si l'héritage sera intégré et quelle sera sa syntaxe générale. De nombreux facteurs liés à la conception d'un langage dépendent des objectifs poursuivis. Il est important de considérer le public cible, les caractéristiques uniques et la finalité du langage. Il est judicieux d'analyser les lacunes ou les difficultés rencontrées par les langages existants afin de s'assurer qu'un langage réponde à un besoin.

Plusieurs experts ont proposé des principes de conception utiles :

  • Dans le dernier paragraphe d'un article publié en 1972, Tony Hoare a fourni quelques conseils généraux pour tout projet logiciel :
    « En résumé, mon conseil aux concepteurs et aux développeurs de logiciels de demain est le suivant : ne décidez pas précisément de ce que vous allez faire avant de savoir comment le faire ; et ne décidez pas comment le faire avant d’avoir évalué votre plan au regard de tous les critères de qualité souhaités. Et si vous ne pouvez pas le faire, simplifiez votre conception jusqu’à ce que vous y parveniez. »
  • Lors d'un symposium SIGPLAN en 1973, Tony Hoare a discuté en détail de divers aspects du langage. Il a également identifié un certain nombre de lacunes dans les langages de programmation (alors) courants.
    « Un langage de programmation est un outil qui doit aider le programmeur dans les aspects les plus difficiles de son art, à savoir la conception, la documentation et le débogage des programmes. »
    « Les critères objectifs d'une bonne conception linguistique peuvent se résumer en cinq mots clés : simplicité, sécurité, traduction rapide, code objet efficace et lisibilité. »
    « Il est absurde de mettre en place des contrôles de sécurité complexes lors des phases de débogage, sans se fier aux résultats, puis de les supprimer en production, alors qu'un résultat erroné pourrait s'avérer coûteux, voire catastrophique. Que penserions-nous d'un passionné de voile qui porte son gilet de sauvetage à l'entraînement à terre mais l'enlève dès qu'il prend la mer ? »
  • Lors du congrès IFIP de 1974, Niklaus Wirth , concepteur de Pascal , a présenté un article intitulé « Sur la conception des langages de programmation ». Wirth a énuméré un certain nombre de suggestions concurrentes, notamment qu'un langage devrait être facile à apprendre et à utiliser, qu'il devrait être utilisable sans ajout de nouvelles fonctionnalités, que le compilateur devrait générer un code efficace, qu'un compilateur devrait être rapide et qu'un langage devrait être compatible avec les bibliothèques, le système sur lequel il s'exécute et les programmes écrits dans d'autres langages.

De nombreux langages de programmation possèdent des caractéristiques de conception destinées à faciliter l'implémentation, au moins de la première version du compilateur ou de l'interpréteur. Par exemple, Pascal, Forth et de nombreux langages assembleur sont spécifiquement conçus pour prendre en charge la compilation en une seule passe .

Les nouveaux langages de programmation sont souvent conçus pour corriger des problèmes (perçus) des langages plus anciens, généralement en ajoutant des fonctionnalités qui, bien qu'elles puissent complexifier l'interpréteur ou le compilateur, simplifient les programmes écrits dans ces langages. Par exemple, les langages intégrant une gestion automatique de la mémoire et un ramasse-miettes ; les langages intégrant des tableaux associatifs ; etc.

En revanche, certains langages de programmation ont été spécifiquement conçus pour faciliter l'écriture d'un compilateur auto-hébergé , généralement en omettant délibérément des fonctionnalités qui rendent la compilation difficile, comme BCPL , Pascal et RPython .

Mise en œuvre

Il existe deux approches générales pour l'implémentation des langages de programmation :

  • Interprétation : Le programme est lu en entrée par un interpréteur, qui exécute les actions écrites dans le programme.
  • Compilation : Le programme est lu par un compilateur, qui le traduit dans un autre langage, tel que le bytecode ou le code machine . Le code traduit peut être exécuté directement par le matériel ou servir d’entrée à un autre interpréteur ou à un autre compilateur.

Outre ces deux extrêmes, de nombreuses implémentations utilisent des approches hybrides telles que la compilation juste-à-temps et les interpréteurs de bytecode.

Les interpréteurs présentent certains avantages par rapport aux compilateurs JIT et aux compilateurs AOT. En général, les interpréteurs prennent en charge une boucle lecture-évaluation-affichage qui rend le développement de nouveaux programmes beaucoup plus rapide ; les compilateurs obligent les développeurs à utiliser une boucle édition-compilation-exécution-débogage beaucoup plus lente.

Un programme typique, lorsqu'il est compilé avec un compilateur AOT (Ahead-of-Time), s'exécutera (après compilation) plus rapidement que le même programme traité et exécuté avec un compilateur JIT ; lequel peut à son tour s'exécuter plus rapidement que ce même programme partiellement compilé en un langage intermédiaire de type p-code, tel qu'un bytecode , et interprété par une machine virtuelle d'application ; lequel s'exécute à son tour beaucoup plus rapidement qu'un interpréteur pur.

En théorie, un langage de programmation peut d'abord être spécifié, puis son interpréteur ou compilateur implémenté ultérieurement (modèle en cascade). En pratique, les enseignements tirés de l'implémentation d'un langage influencent souvent les versions ultérieures de sa spécification, ce qui conduit à une conception et une implémentation intégrées du langage.

Les interpréteurs et les compilateurs implémentent généralement une sorte de table de symboles .

Interprètes

Un interpréteur est un programme qui lit un autre programme, généralement sous forme de texte , comme c'est le cas pour des langages tels que Python . Les interpréteurs lisent le code et produisent directement le résultat . Ils lisent généralement le code ligne par ligne, l'analysent pour le convertir et l'exécuter sous forme d'opérations et d'actions

Un interpréteur est composé de deux parties : un analyseur syntaxique et un évaluateur . Après avoir été lu en entrée par l’interpréteur, un programme est traité par l’analyseur syntaxique. Ce dernier décompose le programme en éléments de langage pour former un arbre d’analyse syntaxique . L’évaluateur utilise ensuite cet arbre pour exécuter le programme.

machine virtuelle

Une machine virtuelle est un type particulier d'interpréteur qui interprète le bytecode. Le bytecode est un code portable de bas niveau similaire au code machine, bien qu'il soit généralement exécuté sur une machine virtuelle plutôt que sur une machine physique. Afin d'améliorer leur efficacité, de nombreux langages de programmation tels que Java , Python , et C# sont compilés en bytecode avant d'être interprétés.

Compilateur juste-à-temps

Certaines machines virtuelles intègrent un compilateur à la volée (JIT) afin d'améliorer l'efficacité de l'exécution du bytecode. Lors de l'exécution du bytecode par la machine virtuelle, si le compilateur JIT détermine qu'une portion de celui-ci sera utilisée de manière répétée, il compile cette portion en code machine. Le compilateur JIT stocke ensuite ce code machine en mémoire afin qu'il puisse être utilisé par la machine virtuelle. Les compilateurs JIT s'efforcent de trouver un compromis entre un temps de compilation plus long et un temps d'exécution plus rapide.

Compilateurs

Un compilateur traduit des programmes écrits dans un langage vers un autre. La plupart des compilateurs sont organisés en trois étapes : le front-end , l’ optimiseur et le back-end . Le front-end est chargé de comprendre le programme. Il vérifie sa validité et le transforme en une représentation intermédiaire , une structure de données utilisée par le compilateur pour le représenter. L’optimiseur améliore cette représentation intermédiaire afin d’accroître la vitesse ou de réduire la taille de l’ exécutable finalement produit par le compilateur. Le back-end convertit la représentation intermédiaire optimisée dans le langage de sortie du compilateur.

Si un compilateur d'un langage de haut niveau donné produit un autre langage de haut niveau, il est appelé transpileur . Les transpileurs peuvent être utilisés pour étendre les langages existants ou pour simplifier le développement de compilateurs en exploitant des implémentations portables et bien optimisées d'autres langages (tels que C ).

De nombreuses combinaisons d'interprétation et de compilation sont possibles, et de nombreuses implémentations de langages de programmation modernes intègrent des éléments des deux. Par exemple, le langage de programmation Smalltalk est généralement implémenté par compilation en bytecode , qui est ensuite interprété ou compilé par une machine virtuelle . Le bytecode Smalltalk étant exécuté sur une machine virtuelle, il est portable sur différentes plateformes matérielles.

Plusieurs implémentations

Les langages de programmation peuvent avoir plusieurs implémentations. Différentes implémentations peuvent être écrites dans différents langages et utiliser différentes méthodes pour compiler ou interpréter le code. Par exemple, les implémentations de Python incluent :

Processus

Les méthodes de création d'un langage de programmation peuvent varier d'un développeur à l'autre ; toutefois, voici un processus général de création d'un langage de programmation, qui inclut des concepts communs :

  • Conception : Les aspects de conception sont pris en compte, tels que les types, la syntaxe, la sémantique et l'utilisation des bibliothèques, pour développer un langage.
  • Considérations : La syntaxe, l’implémentation et d’autres facteurs sont pris en compte. Les langages comme Python interprètent le code à l’exécution, tandis que les langages comme C++ adoptent une approche consistant à baser leur compilateur sur celui du C.
  • Création d'une implémentation : Une première implémentation est écrite. Les compilateurs la convertiront en d'autres formats, aboutissant généralement à un niveau aussi bas que l'assembleur, voire au binaire.
  • Améliorez votre implémentation : les implémentations doivent être améliorées. Développez le langage de programmation afin qu’il possède suffisamment de fonctionnalités pour permettre l’auto-démarrage , c’est-à-dire la capacité d’un langage de programmation à écrire sa propre implémentation.
  • Amorçage : Si un compilateur est utilisé, un développeur peut recourir à l’amorçage, qui consiste à réécrire le compilateur du langage de programmation. Cette méthode est utile pour la détection des bogues et la validation du compilateur. L’amorçage présente également l’avantage de ne plus avoir à programmer que le langage lui-même par la suite.