Lim est un langage de programmation que je développe de A à Z. C’est un projet qui n’est pas voué à répondre à un besoin autre que celui d’assouvir ma curiosité. J’aime apprendre par moi-même et « réinventer la roue » pour la comprendre. Je n’utilise d’ailleurs aucune IA générative pour le développement : je tiens à résoudre chaque problème de logique par mes propres moyens, car c’est là que réside tout l’intérêt de la démarche.
Dans cet article, je vais principalement parler de limc, le compilateur de Lim. Plus précisément, il s’agit d’un transpileur : le code source Lim est converti en code C, lequel est ensuite compilé par un outil externe comme GCC. C’est une approche éprouvée, utilisée par des langages comme Nim.
Pipeline de compilation d'un programme Lim
Le langage Lim
Après des années d’expérimentations, la syntaxe de Lim arrive enfin à maturité. C’est un langage compilé à typage statique fort, inspiré par la clarté de Python. Mon constat était le suivant : j’apprécie l’aspect concis de Python, mais j'aime le cadre qu’offre un langage typé et l'aspect « terre à terre » du C ou du Rust. J’aime avoir l’impression de programmer plus près de ce que la machine va exécuter, tout en gardant une syntaxe moderne.
Caractéristiques techniques :
Interopérabilité C : J'essaie de limiter au maximum les fonctionnalités "hardcodées" dans le compilateur. La bibliothèque standard est écrite en Lim et utilise des blocs de code C pour définir les types et opérations de base.
Système de types : Lim supporte les structures, les records (structures immuables à définition simplifiée), les classes, les types génériques et un système d'exceptions.
Gestion de la mémoire : Lim intègre un Garbage Collector. Malgré certains essais, j’ai préféré utiliser un système déjà développé (lien dans le repo GitHub) plutôt qu’une solution faite maison pour le moment. Bien que le système de borrowing de Rust m'intéresse, je n’ai pas assez d'expérience avec ce système pour être à l'aise avec celui-ci.
Futur : Je travaille sur l'ajout d'enums à valeurs et sur les fonctions en tant que citoyens de premier rang (functions as values). Bien que je ne souhaite pas ajouter d’héritage, j’aimerais ajouter une forme d’abstraction par le biais d’interfaces.
Exemple de syntaxe Lim :
record point(x:int, y:int)
func create_random_point:point
return point{ math::random(1, 10), math::random(1, 10) }
func main
let points = new list<point>
for i from 0 to 50
points.add(create_random_point())
La Codebase : Pourquoi Visual Basic ?
Le choix de Visual Basic (VB.NET) pour écrire le compilateur peut sembler désuet, mais il est simple : c'est le langage avec lequel je suis le plus à l'aise. Comme il compile en CIL (Common Intermediate Language), il est totalement compatible avec l'écosystème .NET (C#, F#). Ce qui compte ici, c’est l’implémentation de la logique de compilation, pas le langage en lui-même.
Une architecture « Lazy »
L’architecture de limc repose sur un chargement paresseux (lazy). C’est l’approche que j’ai utilisée par défaut car c’est la plus intuitive :
Le compilateur charge le fichier source principal.
Il le convertit en tokens, puis génère un AST (Abstract Syntax Tree).
Les fichiers importés sont analysés récursivement.
Une fois l'arbre complet, Lim compile le point d’entrée : la fonction main.
Ainsi, la fonction main va faire appel à d’autres fonctions, à leur tour compilées via récurrence. De cette manière, seules les dépendances réellement utilisées sont résolues et compilées, ce qui permet d'optimiser le résultat final.
Gestion de projet
Mon tout premier programme a été une simple application WinForms générant des scripts Batch. J'ai toujours été fasciné par les outils qui servent à créer d'autres outils. Lim existe sous sa forme actuelle depuis 2022, mais mes premiers essais remontent à bien plus loin.
Pendant toutes ces années, j’ai fait bon nombre d’erreurs. J’ai recommencé de zéro un nombre incommensurable de fois. Mais surtout, j’ai appris. J’ai appris à définir ce que je pouvais garder ou non, ce qui relevait d’une bonne ou d’une fausse bonne idée. J’ai appris à prévoir, à réfléchir en amont. J’ai appris que le plus compliqué n’était pas tellement de coder, mais plutôt de conceptualiser un système qui fonctionne, et qui fonctionne pour tout. J’ai appris à réellement tester les cas extrêmes, à refactoriser, à m'organiser. Et surtout, j’ai appris à me définir des objectifs et à ne pas m’éparpiller. C’est un vrai théâtre d’entraînement et de persévérance que rien ne peut remplacer.