Comment m’y prendrais-je pour créer un langage de programmation ?

Le titre de cet article reflète une question que j’entends sans cesse dans les forums ou dans les courriels que je reçois.

Je pense que tous les développeurs curieux se la sont posée au moins une fois. Il est normal d’être fasciné par le fonctionnement des langages de programmation. Malheureusement, la plupart des réponses que nous lisons sont très académiques ou théoriques. D’autres contiennent trop de détails d’implémentation. Après les avoir lues, nous nous demandons toujours comment les choses fonctionnent en pratique.

Nous allons donc y répondre. Oui, nous allons voir quel est le processus pour créer votre propre langage complet avec un compilateur pour celui-ci et quoi encore.

La vue d’ensemble

La plupart des personnes qui veulent apprendre à « créer un langage de programmation » recherchent en fait des informations sur la façon de construire un compilateur. Elles veulent comprendre la mécanique qui permet d’exécuter un nouveau langage de programmation.
Un compilateur est une pièce fondamentale du puzzle mais faire un nouveau langage de programmation nécessite plus que cela :

1) Un langage doit être conçu : le créateur du langage doit prendre des décisions fondamentales sur les paradigmes à utiliser et la syntaxe du langage
2) Un compilateur doit être créé
3) Une bibliothèque standard doit être implémentée
4) Des outils de support comme des éditeurs et des systèmes de construction doivent être fournis

Voyons plus en détail ce que chacun de ces points implique.

Conception d’un langage de programmation

Si vous voulez juste écrire votre propre compilateur pour apprendre comment ces choses fonctionnent, vous pouvez sauter cette phase. Vous pouvez simplement prendre un sous-ensemble d’un langage existant ou proposer une simple variation de celui-ci et commencer. Cependant, si vous avez des plans pour créer votre propre langage de programmation, vous devrez y réfléchir.

Je pense que la conception d’un langage de programmation est divisée en deux phases :

  1. La phase de grande vision
  2. La phase de raffinement

Dans la première phase, nous répondons aux questions fondamentales sur notre langage.

  • Quel paradigme d’exécution voulons-nous utiliser ? Sera-t-il impératif ou fonctionnel ? Ou peut-être basé sur des machines à états ou des règles métier ?
  • Voulons-nous un typage statique ou un typage dynamique ?
  • Pour quel type de programmes ce langage sera-t-il le meilleur ? Sera-t-il utilisé pour de petits scripts ou de grands systèmes ?
  • Qu’est-ce qui compte le plus pour nous : les performances ? La lisibilité ?
  • Désirons-nous qu’il soit similaire à un langage de programmation existant ? Sera-t-il destiné aux développeurs C ou facile à apprendre pour qui vient de Python ?
  • Désirons-nous qu’il fonctionne sur une plateforme spécifique (JVM, CLR) ?
  • Quel type de capacités de métaprogrammation voulons-nous supporter, le cas échéant ? Macros ? Modèles ? Réflexion?

Dans la deuxième phase, nous continuerons à faire évoluer le langage au fur et à mesure que nous l’utiliserons. Nous rencontrerons des problèmes, des choses qui sont très difficiles ou impossibles à exprimer dans notre langage et nous finirons par le faire évoluer. La deuxième phase pourrait ne pas être aussi glamour que la première, mais c’est la phase dans laquelle nous continuons à régler notre langage pour le rendre utilisable en pratique, donc nous ne devrions pas la sous-estimer.

Construire un compilateur

Construire un compilateur est l’étape la plus excitante dans la création d’un langage de programmation. Une fois que nous avons un compilateur, nous pouvons réellement donner vie à notre langage. Un compilateur nous permet de commencer à jouer avec le langage, de l’utiliser et d’identifier ce qui nous manque dans la conception initiale. Il permet de voir les premiers résultats. Il est difficile de battre la joie d’exécuter le premier programme écrit dans notre tout nouveau langage de programmation, peu importe la simplicité de ce programme.

Mais comment construire un compilateur ?

Comme tout ce qui est complexe, nous le faisons par étapes :

  1. Nous construisons un analyseur syntaxique : l’analyseur syntaxique est la partie de notre compilateur qui prend le texte de nos programmes et comprend quelles commandes ils expriment. Il reconnaît les expressions, les déclarations, les classes et il crée des structures de données internes pour les représenter. Le reste de l’analyseur syntaxique travaillera avec ces structures de données, pas avec le texte original
  2. (optionnel) Nous traduisons l’arbre d’analyse en un arbre de syntaxe abstraite. Typiquement, les structures de données produites par l’analyseur syntaxique sont un peu bas niveau car elles contiennent beaucoup de détails qui ne sont pas cruciaux pour notre compilateur. Pour cette raison, nous voulons fréquemment réarranger les structures de données dans quelque chose de légèrement plus haut niveau
  3. Nous résolvons les symboles. Dans le code, nous écrivons des choses comme a + 1. Notre compilateur doit comprendre à quoi a fait référence. Est-ce un champ ? Est-ce une variable ? Est-ce un paramètre de méthode ? Nous examinons le code pour répondre à cette question
  4. Nous validons l’arbre. Nous devons vérifier que le programmeur n’a pas commis d’erreurs. Est-ce qu’il essaie d’additionner un booléen et un int ? Ou d’accéder à un champ inexistant ? Nous devons produire des messages d’erreur appropriés
  5. Nous générons le code machine. A ce stade, nous traduisons le code en quelque chose que la machine peut exécuter. Cela pourrait être un code machine approprié ou un bytecode pour une certaine machine virtuelle
  6. (facultatif) Nous effectuons la liaison. Dans certains cas, nous devons combiner le code machine produit pour nos programmes avec le code des bibliothèques statiques que nous voulons inclure, afin de générer un seul exécutable

Avons-nous toujours besoin d’un compilateur ? Non. Nous pouvons le remplacer par d’autres moyens d’exécuter le code :

  • Nous pouvons écrire un interpréteur : un interpréteur est essentiellement un programme qui fait les étapes 1 à 4 d’un compilateur et qui exécute ensuite directement ce qui est spécifié par l’arbre de syntaxe abstraite
  • Nous pouvons écrire un transpilateur : un transpilateur fera ce qui est spécifié dans les étapes 1-4 et ensuite sortira un certain code dans un langage pour lequel nous avons déjà un compilateur (par exemple C++ ou Java)

Ces deux alternatives sont parfaitement valides et fréquemment il est logique de choisir l’une de ces deux parce que l’effort requis est typiquement plus petit.

Nous avons écrit un article expliquant comment écrire un transpilateur. Jetez-y un œil si vous voulez voir un exemple pratique, avec du code.

Dans cet article, nous expliquons plus en détail la différence entre un compilateur et un interprète.

Une bibliothèque standard pour votre langage de programmation

Tout langage de programmation doit faire quelques choses :

  • Imprimer sur l’écran
  • Accéder au système de fichiers
  • Utiliser les connexions réseau
  • Créer des interfaces graphiques

Ce sont les fonctionnalités de base pour interagir avec le reste du système. Sans elles, un langage est fondamentalement inutile. Comment fournir ces fonctionnalités ? En créant une bibliothèque standard. Il s’agit d’un ensemble de fonctions ou de classes qui peuvent être appelées dans les programmes écrits dans notre langage de programmation, mais qui seront écrits dans un autre langage. Par exemple, de nombreux langages ont des bibliothèques standard écrites au moins partiellement en C.

Une bibliothèque standard peut ensuite contenir beaucoup plus. Par exemple des classes pour représenter les principales collections comme les listes et les cartes, ou pour traiter des formats courants comme JSON ou XML. Souvent, elle contiendra des fonctionnalités avancées pour traiter les chaînes de caractères et les expressions régulières.

En d’autres termes, écrire une bibliothèque standard est un travail considérable. Ce n’est pas glamour, ce n’est pas conceptuellement aussi intéressant que d’écrire un compilateur, mais c’est toujours un composant fondamental pour rendre un langage de programmation viable.

Il y a des moyens d’éviter cette exigence. L’une d’elles consiste à faire en sorte que le langage s’exécute sur une certaine plateforme et à rendre possible la réutilisation de la bibliothèque standard d’un autre langage. Par exemple, tous les langages fonctionnant sur la JVM peuvent simplement réutiliser la bibliothèque standard de Java.

Outils de support pour un nouveau langage de programmation

Pour rendre un langage utilisable en pratique, nous devons fréquemment écrire quelques outils de support.

Le plus évident est un éditeur. Un éditeur spécialisé avec coloration syntaxique, vérification des erreurs en ligne, et auto-complétion est aujourd’hui un must have pour rendre tout développeur productif.

Mais aujourd’hui les développeurs sont gâtés et ils s’attendront à toutes sortes d’autres outils de soutien. Par exemple, un débogueur pourrait être vraiment utile pour traiter un méchant bug. Ou un système de construction similaire à maven ou gradle pourrait être quelque chose que les utilisateurs demanderont plus tard.

Au tout début, un éditeur pourrait suffire, mais au fur et à mesure que votre base d’utilisateurs augmentera également la complexité des projets et plus d’outils de soutien seront nécessaires. Espérons qu’à ce moment-là, il y aura une communauté prête à aider à les construire.

Résumé

Créer un langage de programmation est un processus qui semble mystérieux pour de nombreux développeurs. Dans cet article, nous avons essayé de montrer que ce n’est qu’un processus. C’est fascinant et pas facile, mais cela peut être fait.

Vous pouvez vouloir construire un langage de programmation pour une variété de raisons. Une bonne raison est de s’amuser, une autre est d’apprendre comment fonctionnent les compilateurs. Votre langage pourrait finir par être très utile ou non, en fonction de nombreux facteurs. Cependant, si vous vous amusez et/ou apprenez tout en le construisant, alors cela vaut la peine d’investir un peu de temps là-dessus.

Et bien sûr, vous pourrez vous vanter auprès de vos collègues développeurs.

Si vous voulez en savoir plus sur la création d’un langage, jetez un coup d’œil aux autres ressources que nous avons créées : apprenez à construire des langages.

Vous pourriez également être intéressé par certains de nos articles :

.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.