Les compilateurs avancés comme gcc
compilent des codes dans des fichiers lisibles par machine selon le langage dans lequel le code a été écrit (par exemple C, C++, etc.). En fait, ils interprètent la signification de chaque code selon la bibliothèque et les fonctions des langues correspondantes. Corrige moi si je me trompe.
Je souhaite mieux comprendre les compilateurs en écrivant un compilateur très basique (probablement en C) pour compiler un fichier statique (par exemple Hello World dans un fichier texte). J'ai essayé des tutoriels et des livres, mais tous sont pour des cas pratiques. Ils traitent de la compilation de codes dynamiques avec des significations liées au langage correspondant.
Comment puis-je écrire un compilateur de base pour convertir un texte statique en un fichier lisible par machine?
La prochaine étape consistera à introduire des variables dans le compilateur; imaginez que nous voulons écrire un compilateur qui ne compile que certaines fonctions d'un langage.
L'introduction de didacticiels et de ressources pratiques est très appréciée :-)
Un compilateur typique effectue les étapes suivantes:
La plupart des compilateurs modernes (par exemple, gcc et clang) répètent à nouveau les deux dernières étapes. Ils utilisent un langage intermédiaire de bas niveau mais indépendant de la plateforme pour la génération initiale de code. Ensuite, ce langage est converti en code spécifique à la plate-forme (x86, ARM, etc.) faisant à peu près la même chose d'une manière optimisée pour la plate-forme. Cela comprend par exemple l'utilisation d'instructions vectorielles lorsque cela est possible, la réorganisation des instructions pour augmenter l'efficacité de la prédiction de branchement, etc.
Après cela, le code objet est prêt pour la liaison. La plupart des compilateurs de code natif savent comment appeler un éditeur de liens pour produire un exécutable, mais ce n'est pas une étape de compilation en soi. Dans des langages comme Java et la liaison C # peut être totalement dynamique, effectuée par le VM au moment du chargement).
Cette séquence classique s'applique à tous les développements logiciels, mais mérite d'être répétée.
Concentrez-vous sur la première étape de la séquence. Créez la chose la plus simple qui pourrait fonctionner.
Lisez le Dragon Book par Aho et Ullman. Ceci est classique et est encore tout à fait applicable aujourd'hui.
Modern Compiler Design est également loué.
Si ce truc est trop difficile pour vous en ce moment, lisez d'abord quelques intros sur l'analyse; l'analyse des bibliothèques comprend généralement des intros et des exemples.
Assurez-vous que vous êtes à l'aise avec les graphiques, en particulier les arbres. Ces choses sont les trucs dont les programmes sont faits au niveau logique.
Utilisez la notation que vous voulez, mais assurez-vous d'avoir une description complète et cohérente de votre langue. Cela inclut à la fois la syntaxe et la sémantique.
Il est grand temps d'écrire des extraits de code dans votre nouveau langage comme cas de test pour le futur compilateur.
Il est tout à fait correct d'écrire un compilateur en Python ou Ruby ou tout autre langage qui vous est facile. Utilisez des algorithmes simples que vous comprenez bien. La première version n'a pas pour être rapide, efficace ou complet. Il suffit qu'il soit suffisamment correct et facile à modifier.
Il est également possible d'écrire différentes étapes d'un compilateur dans différentes langues, si nécessaire.
Votre langue entière devrait être couverte par des cas de test; il sera effectivement défini par eux. Familiarisez-vous avec votre framework de test préféré. Écrivez des tests dès le premier jour. Concentrez-vous sur des tests "positifs" qui acceptent le code correct, par opposition à la détection d'un code incorrect.
Exécutez tous les tests régulièrement. Corrigez les tests cassés avant de continuer. Il serait dommage de se retrouver avec un langage mal défini qui ne peut pas accepter de code valide.
Les générateurs d'analyseurs sont nombreux . Choisissez ce que vous voulez. Vous pouvez également écrire votre propre analyseur à partir de zéro, mais cela ne vaut la peine que si la syntaxe de votre langue est morte simple.
L'analyseur doit détecter et signaler les erreurs de syntaxe. Écrivez un grand nombre de cas de test, positifs et négatifs; réutilisez le code que vous avez écrit tout en définissant la langue.
La sortie de votre analyseur est un arbre de syntaxe abstrait.
Si votre langage possède des modules, la sortie de l'analyseur peut être la représentation la plus simple du "code objet" que vous générez. Il existe de nombreuses façons simples de sauvegarder un arbre dans un fichier et de le recharger rapidement.
Votre langage permet très probablement des constructions syntaxiquement correctes qui peuvent ne pas avoir de sens dans certains contextes. Un exemple est une déclaration en double de la même variable ou la transmission d'un paramètre d'un type incorrect. Le validateur détectera ces erreurs en regardant l'arbre.
Le validateur résoudra également les références à d'autres modules écrits dans votre langue, chargera ces autres modules et les utilisera dans le processus de validation. Par exemple, cette étape s'assurera que le nombre de paramètres transmis à une fonction à partir d'un autre module est correct.
Encore une fois, écrivez et exécutez de nombreux cas de test. Les cas triviaux sont aussi indispensables au dépannage que intelligents et complexes.
Utilisez les techniques les plus simples que vous connaissez. Souvent, il est OK de traduire directement une construction de langage (comme une instruction if
) en un modèle de code légèrement paramétré, un peu comme un modèle HTML.
Encore une fois, ignorez l'efficacité et concentrez-vous sur l'exactitude.
Je suppose que vous ignorez les choses de bas niveau, sauf si vous êtes vivement intéressé par les détails spécifiques au matériel. Ces détails sont sanglants et complexes.
Vos options:
L'optimisation est difficile. L'optimisation est presque toujours prématurée. Générez du code inefficace mais correct. Implémentez l'ensemble du langage avant d'essayer d'optimiser le code résultant.
Bien sûr, des optimisations triviales peuvent être introduites. Mais évitez tout truc rusé et poilu avant que votre compilateur ne soit stable.
Si tout cela n'est pas trop intimidant pour vous, continuez! Pour une langue simple, chacune des étapes peut être plus simple que vous ne le pensez.
Voir un "Hello world" à partir d'un programme créé par votre compilateur pourrait valoir la peine.
Créons un compilateur de Jack Crenshaw, bien qu'il soit inachevé, est une introduction et un tutoriel éminemment lisibles.
Compiler Construction de Nicklaus Wirth est un très bon manuel sur les bases de la construction d'un compilateur simple. Il se concentre sur la descente récursive de haut en bas, qui, avouons-le, est beaucoup plus facile que Lex/yacc ou flex/bison. Le compilateur Pascal original que son groupe a écrit a été fait de cette façon.
D'autres personnes ont mentionné les différents livres Dragon.
Je commencerais en fait par écrire un compilateur pour Brainfuck . C'est un langage assez obtus pour programmer mais il n'a que 8 instructions à mettre en œuvre. C'est aussi simple que possible et il existe des instructions C équivalentes pour les commandes impliquées si vous trouvez la syntaxe rebutante.
Si vous voulez vraiment écrire du code lisible par machine uniquement et non destiné à une machine virtuelle, vous devrez lire les manuels Intel et comprendre
une. Liaison et chargement de code exécutable
b. Formats COFF et PE (pour Windows), comprendre également le format ELF (pour Linux)
Beaucoup plus difficile que prévu. Je vous suggère de lire les compilateurs et interprètes en C++ comme point de départ (par Ronald Mak). Alternativement, "permet de construire un compilateur" par Crenshaw est OK.
Si vous ne voulez pas faire cela, vous pouvez aussi bien écrire votre propre VM et écrire un générateur de code ciblé pour cette VM.
Conseils: apprenez d'abord Flex et Bison. Continuez ensuite à construire votre propre compilateur/VM.
Bonne chance!
L'approche de bricolage pour un compilateur simple pourrait ressembler à ceci (du moins c'est à quoi ressemblait mon projet uni):
Il devrait y avoir beaucoup de littérature décrivant chaque étape en détail.