Publicité
+ Répondre à la discussion
Affichage des résultats 1 à 12 sur 12
  1. #1
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    360
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 360
    Points : 535
    Points
    535

    Par défaut [OpenSource][C++]Générateur de code C++

    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++ :
    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 c++ :
    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
    ... et le fichier source (test.cpp) :
    Code c++ :
    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 :

    Les dépendances (bibliothèque utilitaire sous licence GNU, compiler les targets Win32 ou Linux) :

    L'exécutable (pour Windows XP) :


    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.

  2. #2
    Membre confirmé
    Avatar de Chatanga
    Inscrit en
    décembre 2005
    Messages
    184
    Détails du profil
    Informations forums :
    Inscription : décembre 2005
    Messages : 184
    Points : 261
    Points
    261

    Par défaut

    C'est ce genre de lourdeurs et limitations qui me fait préférer les langages modernes "orientés imports binaires" (Java, C#, Haskell, etc.) aux vieux langages "orientés includes sources" (C, C++, Objective C, etc.).

    Quoiqu'il en soit, j'aurais procédé différement pour automatiser la production de fichiers d'entêtes. Je me serais contenté de définir des macros vides nommées PUBLIC, PROTECTED et PRIVATE afin d'annoter mes fichiers "cpp", sans introduire un nouveau format "mpp" (ce qui ressemble beaucoup à un nouveau langage qui marcherait dans les pas du D). Par exemple :

    Code :
    1
    2
    3
    4
    5
    6
    7
    PRIVATE string Test::sMember;
     
    PUBLIC void Test::MemberFunction(string s, float b)
    {
        cout << "MemberFunctionSet : " << s << endl;
        sMember = s;
    }
    Ca reste du code C++ valide et le pré-préprocesseur a les informations qui lui manque pour produire automatiquement les fichiers d'entêtes. En terme de parsing c'est relativement simple puisqu'on ignore les blocs d'implémentation. J'avais d'ailleurs fait un truc similaire en Ruby pour produire des Stubs / Skeletons à partir d'interfaces C++.

    Note : nested -> imbriqué.

  3. #3
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    360
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 360
    Points : 535
    Points
    535

    Par défaut

    Je ne sais pas si ton exemple est réellement possible à parser.

    Un exemple simple (et c'est ce pour quoi j'ai utilisé les std::string dans mon bout de code) c'est la gestion des namespaces.
    Il est hors de question de coller un "using namespace std;" dans le fichier header, donc tous les types standards devraient être préfixés par "std::".
    Ca rajoute une lourdeur qui rendrait probablement le tout pas très intéressant comparé au C++ seul.

    C'est ce genre de lourdeurs et limitations qui me fait préférer les langages modernes "orientés imports binaires" (Java, C#, Haskell, etc.) aux vieux langages "orientés includes sources" (C, C++, Objective C, etc.).
    Je ne pense pas que ça soit une limitation, au contraire je dirais que c'est presque trop permissif C'est d'ailleurs une des forces du C et du C++. Mais une lourdeur oui, clairement.
    Il faudra que je jette un œil au Haskell un de ces jours, j'en entend beaucoup parler et souvent en bien.

  4. #4
    Membre confirmé
    Avatar de Chatanga
    Inscrit en
    décembre 2005
    Messages
    184
    Détails du profil
    Informations forums :
    Inscription : décembre 2005
    Messages : 184
    Points : 261
    Points
    261

    Par défaut

    Citation Envoyé par Kalith Voir le message
    Je ne sais pas si ton exemple est réellement possible à parser.

    Un exemple simple (et c'est ce pour quoi j'ai utilisé les std::string dans mon bout de code) c'est la gestion des namespaces.
    Il est hors de question de coller un "using namespace std;" dans le fichier header, donc tous les types standards devraient être préfixés par "std::".
    Ca rajoute une lourdeur qui rendrait probablement le tout pas très intéressant comparé au C++ seul.
    Les using n'ont effectivement rien à faire dans les ".h". Dans ma solution, il suffirait toutefois de les préciser dans les seuls prototypes, même si ça reste un peu lourd. La solution pour s'en débarasser serait cependant la même que dans ton approche où tu proposes de gérer "en dur" ce genre de cas (p. ex string => std::string). Solution qui pourrait tout aussi bien s'appliquer à mon approche.

    Citation Envoyé par Kalith Voir le message
    Je ne pense pas que ça soit une limitation, au contraire je dirais que c'est presque trop permissif C'est d'ailleurs une des forces du C et du C++. Mais une lourdeur oui, clairement.
    C'est en effet plus correct de dire ça et c'est un peu là que réside le compromis de langages comme Java : simplifier en retirant des possibilités pas vraiment utilisées. D'ailleurs B. Soustroup avait émis un souhait dans ce sens pour un C++ 2.0 (et sauf erreur de ma part, relativement à la notion de modules en C++).

  5. #5
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    360
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 360
    Points : 535
    Points
    535

    Par défaut

    Dans ma solution, il suffirait toutefois de les préciser dans les seuls prototypes, même si ça reste un peu lourd.
    Ca donnerait surtout une syntaxe irrégulière, et pourrait dérouter un débutant.
    La solution que j'utilise n'est pas exactement codée "en dur". A termes, le but est de supporter n'importe quel namespace (boost, MyLibrary, ...) grâce au concept de "dépendance".
    Par exemple le "using std::string" dans le .mpp n'a pas le même sens qu'un "using std::string" en C++ : il indique que le module "Test" dépend du module "std::string", lequel met à disposition la classe "string" dans le namespace "std". C'est cette dernière information qui est codée en dur dans mon programme, rien de plus.

    D'ailleurs B. Soustroup avait émis un souhait dans ce sens pour un C++ 2.0 (et sauf erreur de ma part, relativement à la notion de modules en C++).
    Ah je n'avais pas fait attention à ça. J'ai beaucoup suivit la discussion sur les concepts (et j'ai été déçu de les voir retirés du lot), mais j'ai dû passer à côté des modules... J'irai voir quand j'aurai le temps, merci

  6. #6
    Membre confirmé
    Avatar de Chatanga
    Inscrit en
    décembre 2005
    Messages
    184
    Détails du profil
    Informations forums :
    Inscription : décembre 2005
    Messages : 184
    Points : 261
    Points
    261

    Par défaut

    Citation Envoyé par Kalith Voir le message
    J'ai beaucoup suivit la discussion sur les concepts (et j'ai été déçu de les voir retirés du lot), mais j'ai dû passer à côté des modules... J'irai voir quand j'aurai le temps, merci
    Il s'agissait d'une réponse de B. Soustroup à la question de savoir ce qu'il aimerait faire si on lui donnait la possibilité de faire un nouveau C++ (2.0 plutôt que C++ x0), sans soucis de rétro-compatibilité. Et, après vérification, aucune allusion à des modules en C++.

    Dans mes recherches, je suis toutefois tombé là dessus : http://os.inf.tu-dresden.de/~hohmuth/prj/preprocess. Les espaces de nommages n'y sont cependant pas gérés...

  7. #7
    gl
    gl est déconnecté
    Rédacteur/Modérateur

    Homme Profil pro
    Inscrit en
    juin 2002
    Messages
    2 077
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Isère (Rhône Alpes)

    Informations forums :
    Inscription : juin 2002
    Messages : 2 077
    Points : 4 192
    Points
    4 192

    Par défaut

    Citation Envoyé par Chatanga Voir le message
    Et, après vérification, aucune allusion à des modules en C++.
    http://www.open-std.org/JTC1/SC22/WG...2007/n2316.pdf

  8. #8
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    360
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 360
    Points : 535
    Points
    535

    Par défaut

    Intéressant ce draft ! Ça n'a pas donné de suite ?

  9. #9
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    360
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 360
    Points : 535
    Points
    535

    Par défaut

    J'ai mis à jour les fichiers du premier post avec la toute dernière version.
    Il ne manque plus grand chose pour qu'il soit pleinement fonctionnel, mais on peut déjà faire beaucoup !

    J'ai introduit un nouveau type de fichier, .mproj, qui rassemble les modules d'un même projet pour compiler le tout en un .exe (ou plus tard, .lib, .a, etc).
    Pour cela, le programme utilise le compilateur C++ installé sur votre machine (pour le moment seul GCC est supporté, mais rajouter le support pour Visual ou autres ne devrait pas poser trop de problème : c'est taillé pour).

    Pour plus d'info, utiliser la commande "-h" ou lire le readme.txt fourni dans mcpp_bin_win32.7z/zip.

  10. #10
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    360
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 360
    Points : 535
    Points
    535

    Par défaut

    Mise à jour des fichiers du premier post avec la dernière version.

    Le programme supporte maintenant les classes, structures et unions imbriquées (nested), ainsi que les fonctions templates.
    Comme le contenu des fonctions n'est pas analysé, le programme n'a aucun moyen de savoir quelles dépendances sont exposées dans le fichier .hpp par ces fonctions template.
    Du coup j'ai introduit une nouvelle notation :
    Code c++ :
    1
    2
    3
    4
    5
    template<class T>, using std::iostream,
    void MemberFunction2(T t)
    {
        std::cout << "MemberFunction2 : " << t << std::endl;
    }
    L'instruction "using std::iostream" signifie que cette fonction se sert du module "std::iostream", et donc que celui-ci doit figurer dans le .hpp.
    C'est peut être une solution temporaire. Je n'ai pas envie pour l'instant d'écrire un vrai parser de C++ pour analyser le corps des fonctions

    Devraient venir prochainement : les typedefs et enums.

  11. #11
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    360
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 360
    Points : 535
    Points
    535

    Par défaut

    Comme prévu, voilà le support des typedefs ("normaux", de fonction, de fonction membre) et des enums, imbriqués ou en portée globale.

    Comme il s'agit de nouveaux types dont on ne peut pas faire de déclaration anticipée (forward declaration), l'utilisation de ceux-ci dans un header implique obligatoirement l'inclusion du header dans lequel ils sont définis (d'autant plus s'ils s'agit de types imbriqués). Bien sûr, ce programme s'en occupe tout seul

    Ne reste plus que le support des modules et classes templates.
    Une fois que ça sera fait, je m'attellerai au problème des bibliothèques externes, et en particulier à la STL (qui, comme son nom l'indique, est principalement composée de types templates).

    Pour le préprocesseur, j'ai quelques doutes.
    Je ne peux pas utiliser la même syntaxe que pré-processeur du C++ a priori, car il y aurait risque de conflit avec le contenu des fonctions par exemple.
    Une solution pourrait être d'utiliser un double dièse : "##", mais c'est moche.

    Une autre solution serait de ne pas implémenter de pré-processeur du tout...
    L'intérêt de celui-ci est principalement la compilation conditionnelle. Les autres features (constantes et macros, j'en oublie ?) sont plutôt héritées du C, et on dispose d'équivalent plus propres en C++.
    Du coup il suffirait de trouver un autre moyen de faire de la compilation conditionnelle, par exemple en implémentant une structure proche de celle du C++ :
    Code c++ :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     
    // Blabla, du code
     
    if (!BIDULE)
        define TRUC;
     
    if (WIN32)
    {
        if (TRUC)
        {
            // Du code
        }
    }
    else
    {
        // Autre code
    }

    ... si possible avec d'autres mots clés que "if" et "else", de manière à pouvoir les utiliser aussi à l'intérieur des fonctions (pas de "#ifdef" dans les fonctions et "if/else" ailleurs).
    Le tout serait alors transformé en pré-processeur C++.

  12. #12
    Inactif
    Inscrit en
    novembre 2002
    Messages
    123
    Détails du profil
    Informations forums :
    Inscription : novembre 2002
    Messages : 123
    Points : 101
    Points
    101

    Par défaut Avis sur ce projet

    Bonjour,

    C'est effectivement intéressant de réduire le taux de code qui est présent dans les sources.
    Si l'on part du principe, qu'il faut faire deux fois la même chose dans le header et dans l'implémentation alors effectivement, c'est efficient.

Liens sociaux

Règles de messages

  • Vous ne pouvez pas créer de nouvelles discussions
  • Vous ne pouvez pas envoyer des réponses
  • Vous ne pouvez pas envoyer des pièces jointes
  • Vous ne pouvez pas modifier vos messages
  •