Bonjour à vous,
J'aimerais présenter un petit projet que j'ai en tête depuis quelques temps.
Comme vous le savez sûrement si vous programmez en C++, il est parfois pénible de gérer à la fois les fichiers d'entête .h/.hpp et les fichiers d'implémentation .cpp.
Par exemple, on doit (pour chaque classe si l'on est rigoureux) créer un couple .cpp/.h, alors que ces deux fichiers représentent une seule et même entité : la classe en question.
Grâce à certains IDE comme Code::Blocks, on peut créer les deux à la fois et y placer un squelette de classe en quelques clicks, mais il reste toujours le besoin de les maintenir séparément : changer le .h revient souvent à changer le .cpp (et réciproquement, mais c'est plus rare).
Du point de vue du langage, cette distinction entête/implémentation est absolument nécessaire, je ne le contredis pas.
Mais n'est-il pas possible de générer ces deux fichiers à partir d'un seul ?
On pourrait de fait réduire significativement le volume de code à maintenir, et simplifier les opérations de maintenance.
J'ai donc programmé rapidement un "compilateur" capable de transformer un fichier ".mpp" ("m" pour "module") en deux fichiers .cpp et .hpp, directement compilables via gcc ou autre.
Exemple (test.mpp) :
Code c++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 module class Test; // Nom de la classe à créer // Les dépendances de cette classe using std::string; using std::iostream; // Les variables membres sont listées directement string sMember; // ... tout comme les fonctions membres void MemberFunction(string s, float b) { cout << "MemberFunctionSet : " << s << endl; sMember = s; }... produit le fichier d'entête (test.hpp) :
Code : Sélectionner tout - Visualiser dans une fenêtre à part mcpp -c test.mpp
... et le fichier source (test.cpp) :
Code c++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 #ifndef TEST_INCLUDED #define TEST_INCLUDED #include <string> class Test { public : std::string sMember; void MemberFunction(std::string s, float b); }; #endif
Code c++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 #include "Test.hpp" #include <iostream> using std::string; using std::cout; using std::cin; using std::cerr; using std::endl; void Test::MemberFunction(string s, float b) { cout << "MemberFunctionSet : " << s << endl; sMember = s; }
Le code (cross-platform, pas de licence) est disponible à cette adresse :
- mcpp_src.7z (21Ko) (13/03/2011, 00h00)
- mcpp_src.zip (30Ko) (13/03/2011, 00h00)
Les dépendances (bibliothèque utilitaire sous licence GNU, compiler les targets Win32 ou Linux) :
- utils_src.7z (61Ko) (10/03/2011, 20h00)
- utils_src.zip (105Ko) (10/03/2011, 20h00)
L'exécutable (pour Windows XP) :
- mcpp_bin_win32.7z (381Ko) (13/03/2011, 00h00)
- mcpp_bin_win32.zip (541Ko) (13/03/2011, 00h00)
Les fichiers projets sont seulement disponibles pour Code::Blocks. J'imagine qu'il doit exister un convertisseur pour les importer dans Visual et consorts.
Le "pseudo-langage" utilisé dans les fichiers .mpp n'est pas encore totalement bien défini, il manque encore :
- le support des classes et structures templates (pour les types, c'est bon)
- le support de la bibliothèque standard
- le support de la compilation conditionnelle (#ifdef ...)
- un moyen simple d'interfacer des bibliothèques existantes sans avoir à les ré-écrire au format .mpp ? (mais pas sûr que ça vienne un jour)
J'ai déjà intégré les features suivantes :
- création d'une (ou plusieurs) classes et du couple .cpp/.hpp à partir d'un fichier .mpp
- variable membres (tous types imaginables)
- variables globales (définies à l'aide du mot clé "global")
- initialisation des variables globales et statiques dans le .cpp
- fonctions membres (statiques, virtuelles, purement virtuelles)
- fonctions libres (définies à l'aide du mot clé "free")
- constructeurs et destructeurs (mots clés "new" et "delete")
- mot clé "using { ... }" pour rajouter manuellement du code au .cpp
- mot clé "export { ... }" pour rajouter manuellement du code au .hpp
- support des portées : private, public, protected
- support de l'héritage (multiple, privé, public, etc)
- support des namespaces (un seul par fichier .mpp)
- support des dépendances ("using SomeModule;")
- support des commentaires (non reportés dans les fichiers C++, sauf si contenus dans le corps d'une fonction)
- support des commentaires de documentation (/// et /** */), reportés dans le fichier .hpp
- analyse des types utilisés pour gérer proprement les dépendances (traduire : que doit-on mettre dans le .hpp ? un #include "..." ? une déclaration anticipée ? rien du tout ?)
- support d'un fichier .mproj contenant les infos de compilation d'un ensemble de modules (la compilation d'un fichier .mpp seul est toujours possible)
- utilisation de gcc en interne pour compiler et linker les fichiers .cpp issus d'un fichier .mproj (le code peut être facilement adapté pour n'importe quel autre compilateur)
Comme vous pouvez le voir, ce "langage" se veut un poil moins généraliste que le C++ : il est plutôt orienté programmation avec classes et s'éloigne du C (pas ou peu de macros).
Le but n'étant pas de faire un (C++)++, ce qui est complètement en dehors de mes compétences, mais juste de développer un outil intermédiaire pour faciliter l'écriture et la maintenance du code pour un style de programmation particulier.
Ce qui est bon, c'est qu'on n'est pas dépendant du format ".mpp" : on peut toujours choisir de reprendre/distribuer le code C++ généré.
Si vous avez des commentaires, n'hésitez pas
Le code n'est probablement pas organisé au mieux, mais ça tourne pour le moment.
Pour ceux que le détails techniques intéressent, la "compilation" se fait de la manière suivante :
- création du module à compiler, avec son nom est ses namespaces
- lectures des dépendances ("using UnModule;") :
- recherche d'un fichier correspondant au module utilisé (Foo::Module -> foo_module.mpp)
- parsing du fichier sans vérification des types qui apparaissent, ajout des nouveaux types et variables dans la base de donnée de ce module
- parsing du fichier :
- chaque type qui apparait est confronté avec la base de donnée des modules utilisés (plus les types de base), erreur si aucun ou plus de un résultat, sinon le type est "décoré" par tout ce qui est nécessaire : namespaces, classes parent, ..., et ajoute éventuellement une dépendance plus ou moins forte envers d'autre modules (déclaration anticipée si pointeur, inclusion du header si type "nu" ou template)
- chaque type créé vient s'ajouter à la base de donnée des types du module
- sérialization du module en code C++
Pour "compiler" plus d'un fichier .mpp à la fois, il est probablement plus efficace de passer par un fichier projet .mproj.
En effet, tout se passera dans une seule même unité de compilation : il n'y aura donc besoin de lire chaque module qu'une seule fois, peu importe combien de fois celui-ci est utilisé par les autres modules.
Partager