Traduire en code C est une habitude bien établie. Le C d'origine avec les classes (et les premières implémentations de C ++, appelées alors Cfront ) l'ont fait avec succès. Plusieurs implémentations de Lisp ou de Scheme le font, par exemple Chicken Scheme , Scheme48 , Bigloo . Certaines personnes ont traduit Prolog à C . Il en a été de même pour certaines versions de Mozart (et il y a eu plusieurs tentatives pour compiler le bytecode Ocaml en C ). Le système CAIA d' intelligence artificielle de J.Pitrat est également amorcé et génère tout son code C. Vala se traduit également en C, pour le code lié à GTK. Le livre de Queinnec Lisp In Small Pieces avoir un chapitre sur la traduction en C.
L'un des problèmes rencontrés lors de la traduction en C concerne les appels en queue . La norme C ne garantit pas qu'un compilateur C les traduit correctement (en un "saut avec arguments", c'est-à-dire sans manger de pile d'appels), même si dans certains cas, les versions récentes de GCC (ou de Clang / LLVM) effectuent cette optimisation. .
Un autre problème est le ramassage des ordures . Plusieurs implémentations utilisent simplement le récupérateur de déchets conservateur Boehm (qui respecte le C ...). Si vous vouliez récupérer du code (comme le font plusieurs implémentations de Lisp, par exemple SBCL), cela pourrait être un cauchemar (vous voudriez dlclose
sur Posix).
Un autre problème concerne les poursuites de première classe et call / cc . Mais des astuces intelligentes sont possibles (regardez à l'intérieur du schéma de poulet). Accéder à la pile d'appels peut nécessiter de nombreuses astuces (mais voir la trace GNU , etc ...). La persistance orthogonale des suites (c.-à-d. Des piles ou des fils) serait difficile chez C.
La gestion des exceptions est souvent une question d'émettre des appels intelligents à longjmp, etc.
Vous voudrez peut-être générer (dans votre code C émis) les #line
directives appropriées . C'est ennuyeux et demande beaucoup de travail (vous voudrez par exemple produire plus facilement gdb
du code non débugable).
Mon langage spécifique au domaine lispy de MELT (pour personnaliser ou étendre GCC ) est traduit en C (actuellement en C ++ médiocre). Il possède son propre collecteur de déchets de copie générationnelle. (Vous pourriez être intéressé par Qish ou Ravenbrook MPS ). En fait, le GC générationnel est plus facile dans le code C généré par machine que dans le code C écrit à la main (car vous allez adapter votre générateur de code C à votre barrière en écriture et à vos machines GC).
Je ne connais aucune implémentation de langage traduisant en code C ++ authentique , c'est-à-dire utilisant une technique de "récupération de place compile" pour émettre du code C ++ en utilisant de nombreux modèles STL et en respectant l' idiome RAII . (s'il vous plaît dites si vous en connaissez un).
Ce qui est amusant aujourd’hui, c’est que (sur les bureaux Linux actuels), les compilateurs C sont assez rapides pour implémenter une boucle interactive read-eval-print- level de niveau supérieur traduite en C: vous allez émettre du code C (quelques centaines de lignes) pour chaque utilisateur. interaction, vous en aurez fork
une compilation dans un objet partagé, que vous feriez ensuite dlopen
. (MELT le fait tout prêt, et il est généralement assez rapide). Tout cela pourrait prendre quelques dixièmes de seconde et être acceptable pour les utilisateurs finaux.
Lorsque cela est possible, je recommanderais de traduire en C, pas en C ++, en particulier parce que la compilation en C ++ est lente.
Si vous implémentez votre langage, vous pouvez également envisager (au lieu d'émettre du code C) certaines bibliothèques JIT telles que libjit , GNU lightning , asmjit ou même LLVM ou GCCJIT . Si vous souhaitez traduire en C, vous pouvez parfois utiliser tinycc : il compile très rapidement le code C généré (même en mémoire) pour ralentir le code machine. Mais en général, vous souhaitez tirer parti des optimisations effectuées par un vrai compilateur C tel que GCC.
Si vous traduisez en C votre langue, assurez-vous de commencer par créer l'ensemble de l' AST du code C généré en mémoire (cela facilite également la génération de toutes les déclarations, puis de toutes les définitions et du code de fonction). Vous seriez capable de faire des optimisations / normalisations de cette façon. En outre, vous pourriez être intéressé par plusieurs extensions GCC (par exemple, les gotos calculés). Vous voudrez probablement éviter de générer des fonctions C énormes - par exemple, une ligne de C générée de plusieurs centaines de milliers - (vous ferez mieux de les scinder en morceaux plus petits), car l'optimisation des compilateurs C est très mécontente des très grandes fonctions C (en pratique, et expérimentalement,gcc -O
le temps de compilation de grandes fonctions est proportionnel au carré de la taille du code de fonction). Limitez donc la taille de vos fonctions C générées à quelques milliers de lignes chacune.
Notez que les compilateurs C & C ++ de Clang (via LLVM ) et GCC (via libgccjit ) offrent un moyen d'émettre des représentations internes adaptées à ces compilateurs, mais cela pourrait (ou non) être plus difficile que d'émettre du code C (ou C ++), et est spécifique à chaque compilateur.
Si vous concevez un langage à traduire en C, vous souhaiterez probablement disposer de plusieurs astuces (ou constructions) pour générer un mélange de C avec votre langage. Mon document DSL2011 MELT: un langage spécifique à un domaine traduit intégré au compilateur GCC devrait vous donner des conseils utiles.