IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C# Discussion :

[C#] Parser code source C avec regex


Sujet :

C#

  1. #1
    Membre Expert
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Par défaut [C#] Parser code source C avec regex
    Bonjour à tous,

    Je souhaiterais parser un code source C classique afin de récupérer les fonctions et le mettre dans un tableau :
    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
    16
    17
    18
    19
    20
    21
    22
     
    // Exemple de code source à parser
    void 
    a(int p1, float p2)
    {
     
    }
     
    void b()
    {
        int p1=5;
        float p2 = 10.0;
        a(p1,p2);
    }
     
    int main()
    {
        int p1=5;
        float p2 = 10.0;
        a(p1,p2);
        b();
    }
    Je voudrais isoler chaque bloc de fonction et le mettre en mémoire :
    tab[0] = "void
    a(int p1, float p2)
    {

    }"

    tab[1] = "void b()
    {
    int p1=5;
    float p2 = 10.0;
    a(p1,p2);
    }"

    tab[2] = "int main()
    {
    int p1=5;
    float p2 = 10.0;
    a(p1,p2);
    b();
    }"
    Je ne suis pas du tout doué avec les regex, je sais utiliser la classe Regex en C# mais pour trouver l'expression régulière c'est autre chose

    Je pense qu'il faut définir un pattern du genre :
    (type_retour|void) (espaces|tabulations|saut ligne) nom_fonction (espaces|tabulations|saut ligne) (paramètres) (espaces|tabulations|saut ligne)
    {
    DU TEXTE
    }


    Enfin, je ne suis même pas sur. Pouvez vous m'aider ?

    Merci d'avance
    Qui ne tente rien n'a rien !
    Ce qui ne nous tue pas nous rends plus fort !!
    Mon projet ZELDA en C++/Allegro
    http://www.tutoworld.com - Le Forum -
    Mes ressources Dotnet (cours, sources, tutos)
    --------------------------------------------
    + + =

    Ne pas oublier le Tag !

  2. #2
    Membre confirmé

    Homme Profil pro
    Ingénieur intégration
    Inscrit en
    Juillet 2009
    Messages
    62
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Eure (Haute Normandie)

    Informations professionnelles :
    Activité : Ingénieur intégration

    Informations forums :
    Inscription : Juillet 2009
    Messages : 62
    Par défaut
    Je pense que l'approche via les les expressions régulières ne soit pas la plus adaptée pour ce type de problématique.

    Il faut plutôt voir du coté des parsers/analyseurs de textes tel que ANTLR

    A voir Tutoriel sur la mise en place d'une grammaire avec ANTLR

  3. #3
    Membre Expert Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Par défaut
    Pour la regex on peut tenter mais ça ne fonctionnera pas forcément. Le problème est que sans information de contexte on peut seulement chercher les formes "identifiant + identifiant + parenthèse gauche". Or même s'il ne m'en vient pas à l'esprit il y a peut-être des ambiguïtés, notamment du côté du préprocesseur (et comment gérer ce dernier d'ailleurs ?). Et si des infos de contexte sont nécessaires alors la regex ne peut pas gérer ça et il faut un parser "à pile" (pense au parsing d'expressions mises entre parenthèses).

    Autres contraintes: quid du code malformé et des caractères exotiques? Je doute que le C suive les catégories d'unicode, qui ne devait même pas exister à l'époque. Donc les catégories regex pourraient ne pas coller.

    Tout dépend donc des sources à parser et de ce qu'on veut exactement mais la regex peut se révéler insuffisante. Dans ce cas on peut tenter d'écrire un parsing manuel incluant un comptage de parenthèses (ce qui peut-être simple si on ne veut parser qu'une petite partie). Ou bien avoir recours à un parser generator comme suggéré par le moussel. Mais outre que cela ajoute une dépendance au projet, leur utilisation est malheureusement souvent fastidieuse et t'imposera d'apprendre le fonctionnement d'un générateur de parser. Un comble ! Cela dit c'est une compétence utile.


    Si au vu des contraintes que j'ai énumérées la regex peut fonctionner alors il suffit de concaténer comme suit :
    Identifiant: \w[\w\d]*
    Espaces, tab et sauts de ligne: ([\s\n]|(\r\n))*
    Pour le début: (\w[\w\d]*|void)

  4. #4
    Membre Expert
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Par défaut
    Bonsoir,

    Effectivement il semble que l'approche regex ne soit pas la bonne.
    Alors j'avais déjà vu du côté de ANTLR mais il faudrait des semaines pour comprendre comment ca marche et cela rajoute une dépendance au projet.

    Au final même si le style de code à parser reste simple (on part du principe que le code est correct syntaxiquement et avec une imbrication { et } parfaite), je ne vois pas comment régler le problème des accolades à l'intérieur d'une fonction.

    Exemple :
    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
    16
    17
    18
    void b()
    {
        int p1=5;
        float p2 = 10.0;
        a(p1,p2);
        if ( blabla) {
    
        }
        else {
            while (blabla)
            {
    
            }
        }
    } // <--- il faudrait s'arrêter à cette accolade dès qu'on a matché le prototype de la fonction...
    
    float autre_fonction() {}
    Impossible de dire avec une regex, je dois m'arrête à la "bonne" accolade (celle qui ferme la fonction)

    Ou alors j'ai loupé quelque chose...

    Merci à vous
    Qui ne tente rien n'a rien !
    Ce qui ne nous tue pas nous rends plus fort !!
    Mon projet ZELDA en C++/Allegro
    http://www.tutoworld.com - Le Forum -
    Mes ressources Dotnet (cours, sources, tutos)
    --------------------------------------------
    + + =

    Ne pas oublier le Tag !

  5. #5
    Membre Expert Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Par défaut
    Effectivement une regex ne peut traiter des structures récursives (tu peux bidouiller si la profondeur max de la récursion est connue et faible). J'avais zappé le fait que tu voulais également capturer le corps. Tu pourrais toujours bidouiller en utilisant la regex pour capturer l'en-tête puis utiliser une procédure manuelle pour trouver l'accolade de fin. Resteraient quand même le problème du préprocesseur et celui des chaînes de caractères qui pourraient contenir des accolades ou des motifs équivalents à des en-têtes de fonction.

    Bref, la regex est limitée mais avec un peu de bricolage et des sources simples ça peut marcher. Une solution manuelle sera un peu plus puissante mais à nouveau elle échouera face à certains cas particuliers, sauf à y passer beaucoup de temps pour créer un parser complet. Quant à antlr ça ne prend pas des semaines et ça reste la façon la plus rapide d'avoir un parser 100% compatible.

    Tout dépend des sources. Si tu peux bricoler, bricole.

  6. #6
    Expert confirmé
    Avatar de Mat.M
    Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2006
    Messages
    8 540
    Détails du profil
    Informations personnelles :
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Novembre 2006
    Messages : 8 540
    Par défaut
    Citation Envoyé par Aspic Voir le message
    Je souhaiterais parser un code source C classique afin de récupérer les fonctions et le mettre dans un tableau :
    je te souhaite un bon courage c'est assez ardu à faire...
    j'avais commencé à faire un interpréteur de langage genre Javascript,langage C pour mon projet ( que je souhaite commercialiser..) mais un jour j'ai vite changé pour faire un interpréteur BASIC.

    Ceci dit si je peux essayer de donner des tuyaux je peux
    Je n'utilise pas du tout les expressions régulières.
    J'analyse caractère par caractère pour voir si c'est un espace, un chiffre...
    Ensuite là où je me suis arraché les cheveux de la tête c'est pour délimiter les blocs avec les accolades.
    En Basic c'est plus facile étant donné que ça finit avec End Sub ou End Function
    Maintenant si on souhaite délimiter les blocs il faut empiler les blocs à l'aide d'une pile ; les blocs de fonctions et d'expressions délimités par des accolades doivent être symmétriques évidemment.
    Une pile permet de restituer le dernier élément empilé.


    Citation Envoyé par Aspic Voir le message
    Au final même si le style de code à parser reste simple (on part du principe que le code est correct syntaxiquement et avec une imbrication { et } parfaite), je ne vois pas comment régler le problème des accolades à l'intérieur d'une fonction.

    s
    pour analyser et délimiter les blocs on peut aussi éventuellement utiliser un arbre binaire; chaque noeud de l'arbre est en fait un bloc du source : par exemple pour une condition if , pour une boucle etc...

    ce que j'ai fait dans mon projet d'interpréteur c'est que le code source initialement en Javascript ( syntaxe proche du C donc ) est compilé dans un pseudo-code ( un peu comme l'Intermediate Language de .NET) et comme ça il est interprété linéairement par le module d'exécution.

  7. #7
    Membre Expert
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Par défaut
    Citation Envoyé par DonQuiche Voir le message
    Resteraient quand même le problème du préprocesseur et celui des chaînes de caractères qui pourraient contenir des accolades ou des motifs équivalents à des en-têtes de fonction.
    Pour le préprocesseur, à tout hasard ca ne serait pas possible de récupérer un code intermédiaire où les #define sont remplacés juste avant la compilation ? (je pense à une option de gcc qui permettrait de sortir un fichier en code C mais avec une passe du préprocesseur ?)

    Pour les accolades dans une chaine de caractères, j'ai avait pensé on ne peut pas les virer avec une regex ? Voire même virer tout le texte dans les guillemets ? Exemple : printf("je fais foirer le parser { ahahahah"); devient printf(""); ou alors printf();
    Citation Envoyé par DonQuiche Voir le message
    Quant à antlr ça ne prend pas des semaines et ça reste la façon la plus rapide d'avoir un parser 100% compatible.
    Un jour, j'apprendrais ce nouvel outil, ca à vraiment l'air pas mal mais au premier abord (en regardant le tuto sur DVP), cela avait l'air compliqué...
    Pour l'instant étant donné que les sources sont simples, je continue en mode bricolage même si c'est pas bien à termes j'en suis conscient

    Citation Envoyé par Mat.M
    Ensuite là où je me suis arraché les cheveux de la tête c'est pour délimiter les blocs avec les accolades.
    Tu m'étonnes, je suis entrain de péter un câble dessus actuellement
    C'est clair que du VB.net aurait été plus simple
    Citation Envoyé par Mat.M
    Maintenant si on souhaite délimiter les blocs il faut empiler les blocs à l'aide d'une pile ; les blocs de fonctions et d'expressions délimités par des accolades doivent être symmétriques évidemment.
    Une pile permet de restituer le dernier élément empilé.
    Alors une pile, j'avais eu l'idée mais je n'ai pas trouvé comment faire au final. Dès que je trouve une accolade ouvrante, j'empile le code lu précédemment (de la précédente accolade ouvrante jusqu'à celle que je viens de trouver) et que je trouve une accolade fermante, je dépile le dernier élément mais au final comment reconstituer les blocs ?
    Avec un arbre binaire, je ne vois simplement pas comment on pourrait faire.
    Citation Envoyé par Mat.M
    ce que j'ai fait dans mon projet d'interpréteur c'est que le code source initialement en Javascript ( syntaxe proche du C donc ) est compilé dans un pseudo-code ( un peu comme l'Intermediate Language de .NET) et comme ça il est interprété linéairement par le module d'exécution.
    C'est toi qui a créé le pseudo-code ? Si oui, comment as tu fait ? Et je suppose que parser le pseudo-code était plus facile que le code source d'origine ?

    PS : Au final, ton projet a été terminé à 100% ?

    Merci beaucoup.
    Qui ne tente rien n'a rien !
    Ce qui ne nous tue pas nous rends plus fort !!
    Mon projet ZELDA en C++/Allegro
    http://www.tutoworld.com - Le Forum -
    Mes ressources Dotnet (cours, sources, tutos)
    --------------------------------------------
    + + =

    Ne pas oublier le Tag !

  8. #8
    Expert confirmé
    Avatar de Mat.M
    Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2006
    Messages
    8 540
    Détails du profil
    Informations personnelles :
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Novembre 2006
    Messages : 8 540
    Par défaut
    salut,

    *pour l'arbre binaire : le main() du programme constitue la tête de l'arbre
    Ensuite à l'analyse du code source,
    si on rencontre un "if" un "for" ou la déclaration d'une fonction , on créer un nouveau noeud qu'on ajoute à l'arbre.
    Soit au noeud principal soit à noeud sous-jacent qui peut-être une instruction ou une fonction.
    Puisqu'un if peut encapsuler plusieurs autres if , for etc
    Il faut s'assurer que le nom de fonction ne soit pas déjà déclaré ; dans ce cas-là on parcourir de manière récursive l'arbre.

    *pour mon projet perso,c'est moi qui ait crée totalement la syntaxe du pseudo-code en m'inspirant un peu de la syntaxe assembleur x86.
    C'est parfaitement fonctionnel ; l'utilisateur peut créer des formulaires, déclarer des variables, des noms de procédure, faire des boucles et des conditions.

    Pour la finalisation de mon projet ce n'est pas un langage informatique que je veux faire c'est une sorte d'Atelier Logiciel qui pilote des bases de données et je suis en train de le finaliser

  9. #9
    Expert confirmé
    Avatar de Mat.M
    Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2006
    Messages
    8 540
    Détails du profil
    Informations personnelles :
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Novembre 2006
    Messages : 8 540
    Par défaut
    Citation Envoyé par Aspic Voir le message

    Pour les accolades dans une chaine de caractères, j'ai avait pensé on ne peut pas les virer avec une regex ? Voire même virer tout le texte dans les guillemets ? Exemple : printf("je fais foirer le parser { ahahahah"); devient printf(""); ou alors printf();
    si tu crées ton propre analyseur lexical/syntaxique et si tu rencontres une chaîne de caractères dans le code source , il suffit de mettre un booléen à true qui indique qu'on a rencontré une chaine de caractères.

    Ce que j'ai fait au niveau de l'analyse du code source :
    si le code source est le suivant
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    int i; 
    int j;
    for(i=0;i<100;i++)
    {
     
    }
    l'analyseur syntaxique prend caractère par caractère, enlève les espaces, tous les caractères indésirables
    les mots sont ajoutés à une collection genre ArrayList ou tout ce que tu veux ce qui au final va donner une collection de chaînes de caractères
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    ArrayList instructions=new ArrayList;
    //....
    string strInstruction;
    For( i=0;i<instructions.length()-1;i++)
    { strInstruction =instructions[i];
    // strInstruction vaudra "int" "j" ";""for" etc..
    C'est peut-être plus lourd qu'avec des expressions régulières mais ça fonctionne
    Ensuite de cette liste de mots/instructions on regarde si on trouve des mots réservés comme "if" ou "for" etc sinon on construit une table de symboles comme les variables, les procédures
    C'est assez basique il y a certainement plus subtil mais ça fonctionne

  10. #10
    Membre Expert Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Par défaut
    Citation Envoyé par Aspic Voir le message
    Pour le préprocesseur, à tout hasard ca ne serait pas possible de récupérer un code intermédiaire où les #define sont remplacés juste avant la compilation ? (je pense à une option de gcc qui permettrait de sortir un fichier en code C mais avec une passe du préprocesseur ?)
    SI c'est le cas, c'est vraiment cool.

    Pour les accolades dans une chaine de caractères, j'ai avait pensé on ne peut pas les virer avec une regex ? Voire même virer tout le texte dans les guillemets ? Exemple : printf("je fais foirer le parser { ahahahah"); devient printf(""); ou alors printf();
    Si tu fais ça tu vas perdre tes chaînes dans les blocs capturés et les indices des captures seront erronés. Du coup il va te falloir une grosse plomberie pour corriger ces informations a posterior.

    Non, si tu restes sur les regex, le mieux à faire à mon avis est d'avoir une simple passe manuelle capable d'identifier les chaînes et les paires d'accolades. Avec ces infos tu devras créer deux fonctions: bool IsInString(int index) et int GetMatchingEndBrace(int index). Ainsi après chaque capture de la regex tu vérifies qu'elle n'était pas dans une chaîne et tu cherches l'accolade fermante.


    Un jour, j'apprendrais ce nouvel outil, ca à vraiment l'air pas mal mais au premier abord (en regardant le tuto sur DVP), cela avait l'air compliqué...
    Pas tant que ça en fait, ça s'apprend plutôt bien. Qui plus est c'est ce qu'on appelle un parser LL et c'est beaucoup plus facile à utiliser que les LR avec leurs messages d'erreurs incompréhensibles (une techno qui devrait à mon avis tomber dans l'oubli). Cela dit, encore une fois, si tu peux t'en passer, fais-le.

    Alors une pile, j'avais eu l'idée
    Oublie cette approche, il ne faut pas chercher à écrire un vrai parser, ou alors autant se mettre à Antlr. Si tu pars sur une approche manuelle tu dois simplement mettre au point une méthode capable de trouver la prochaine virgule ou l'accolade correspondante tout en ignorant les chaînes. Tu n'as pas besoin d'un véritable parser capable de reconnaître les expressions.

  11. #11
    Membre Expert
    Avatar de Aspic
    Homme Profil pro
    Étudiant
    Inscrit en
    Août 2005
    Messages
    3 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Août 2005
    Messages : 3 905
    Par défaut
    Merci à vous pour vos remarques

    Je suis parti sur une approche plutôt manuelle, je pense que cela suffira dans mon cas, mais je garde à l'idée l'utilisation d'arbres binaires ou piles si j'en ai besoin à un moment donné.

    Si je rencontre des problèmes précis, je continuerai à poster sur ce topic pour ne pas spoiler le forum ^^

    Bonnes fêtes de fin d'année

    PS : Puis-je commercialiser un produit utilisant des outils externes sous licence GNU GPLv2/3 ?
    http://www.developpez.net/forums/d14...ercialisation/
    Qui ne tente rien n'a rien !
    Ce qui ne nous tue pas nous rends plus fort !!
    Mon projet ZELDA en C++/Allegro
    http://www.tutoworld.com - Le Forum -
    Mes ressources Dotnet (cours, sources, tutos)
    --------------------------------------------
    + + =

    Ne pas oublier le Tag !

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Code source projet avec CORBA
    Par mourad1987 dans le forum CORBA
    Réponses: 1
    Dernier message: 30/04/2012, 13h13
  2. Réponses: 0
    Dernier message: 07/10/2011, 14h17
  3. parser code source jave pour obtenir un AST
    Par cdm1024 dans le forum Général Java
    Réponses: 1
    Dernier message: 10/08/2009, 09h19
  4. Réponses: 6
    Dernier message: 19/07/2007, 12h30

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo