web-dev-qa-db-fra.com

Supprimer les commentaires C et C ++ en utilisant Python?

Je recherche Python qui supprime les commentaires C et C++ d'une chaîne. (Supposons que la chaîne contienne un fichier source C complet.)

Je me rends compte que je pourrais sous-chaînes .match () avec un Regex, mais cela ne résout pas l'imbrication /*, ou ayant un // à l'intérieur d'un /* */.

Idéalement, je préférerais une implémentation non naïve qui gère correctement les cas délicats.

43
TomZ

Je ne sais pas si vous connaissez sed, le programme d'analyse de texte basé sur UNIX (mais disponible sur Windows), mais j'ai trouvé un script sed ici qui supprimer les commentaires C/C++ d'un fichier. C'est très intelligent; par exemple, il ignorera '//' et '/ *' s'ils se trouvent dans une déclaration de chaîne, etc. Depuis Python, il peut être utilisé en utilisant le code suivant:

import subprocess
from cStringIO import StringIO

input = StringIO(source_code) # source_code is a string with the source code.
output = StringIO()

process = subprocess.Popen(['sed', '/path/to/remccoms3.sed'],
    input=input, output=output)
return_code = process.wait()

stripped_code = output.getvalue()

Dans ce programme, source_code est la variable contenant le code source C/C++, et finalement stripped_code contiendra le code C/C++ avec les commentaires supprimés. Bien sûr, si vous avez le fichier sur le disque, les variables input et output peuvent être des poignées de fichier pointant vers ces fichiers (input en mode lecture, output en écriture). remccoms3.sed est le fichier du lien ci-dessus, et il doit être enregistré dans un emplacement lisible sur le disque. sed est également disponible sur Windows et est installé par défaut sur la plupart des distributions GNU/Linux et Mac OS X.

Ce sera probablement mieux qu'une pure solution Python; pas besoin de réinventer la roue.

6
zvoase

Cela gère les commentaires de style C++, les commentaires de style C, les chaînes et leur imbrication simple.

def comment_remover(text):
    def replacer(match):
        s = match.group(0)
        if s.startswith('/'):
            return " " # note: a space and not an empty string
        else:
            return s
    pattern = re.compile(
        r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
        re.DOTALL | re.MULTILINE
    )
    return re.sub(pattern, replacer, text)

Les chaînes doivent être incluses, car les marqueurs de commentaire à l'intérieur ne commencent pas de commentaire.

Edit: re.sub n'a pris aucun indicateur, a donc dû compiler le modèle en premier.

Edit2: Ajout de littéraux de caractères, car ils pourraient contenir des guillemets qui seraient autrement reconnus comme délimiteurs de chaîne.

Edit3: Correction du cas où une expression légale int/**/x=5; deviendrait intx=5; qui ne se compilerait pas, en remplaçant le commentaire par un espace plutôt qu'une chaîne vide.

81
Markus Jarderot

Les commentaires C (et C++) ne peuvent pas être imbriqués. Les expressions régulières fonctionnent bien:

//.*?\n|/\*.*?\*/

Cela nécessite l'indicateur "Single line" (Re.S) car un commentaire C peut s'étendre sur plusieurs lignes.

def stripcomments(text):
    return re.sub('//.*?\n|/\*.*?\*/', '', text, flags=re.S)

Ce code devrait fonctionner.

/ EDIT: Notez que mon code ci-dessus fait en fait une hypothèse sur les fins de ligne! Ce code ne fonctionnera pas sur un fichier texte Mac. Cependant, cela peut être modifié relativement facilement:

//.*?(\r\n?|\n)|/\*.*?\*/

Cette expression régulière devrait fonctionner sur tous les fichiers texte, quelles que soient leurs fins de ligne (couvre les fins de ligne Windows, Unix et Mac).

/ EDIT: MizardX et Brian (dans les commentaires) ont fait une remarque valable sur la gestion des chaînes. J'ai complètement oublié cela parce que l'expression régulière ci-dessus est extraite d'un module d'analyse qui a une gestion supplémentaire pour les chaînes. La solution de MizardX devrait très bien fonctionner mais elle ne gère que les chaînes entre guillemets doubles.

25
Konrad Rudolph

Cette publication fournit une version codée de l'amélioration du code de Markus Jarderot qui a été décrite par atikat, dans un commentaire à la publication de Markus Jarderot. (Merci aux deux d'avoir fourni le code d'origine, ce qui m'a fait économiser beaucoup de travail.)

Pour décrire l'amélioration un peu plus complètement: L'amélioration conserve la numérotation des lignes intacte. (Cela se fait en gardant les caractères de nouvelle ligne intacts dans les chaînes par lesquelles les commentaires C/C++ sont remplacés.)

Cette version de la fonction de suppression des commentaires C/C++ convient lorsque vous souhaitez générer des messages d'erreur pour vos utilisateurs (par exemple des erreurs d'analyse) qui contiennent des numéros de ligne (c'est-à-dire des numéros de ligne valides pour le texte d'origine).

import re

def removeCCppComment( text ) :

    def blotOutNonNewlines( strIn ) :  # Return a string containing only the newline chars contained in strIn
        return "" + ("\n" * strIn.count('\n'))

    def replacer( match ) :
        s = match.group(0)
        if s.startswith('/'):  # Matched string is //...EOL or /*...*/  ==> Blot out all non-newline chars
            return blotOutNonNewlines(s)
        else:                  # Matched string is '...' or "..."  ==> Keep unchanged
            return s

    pattern = re.compile(
        r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
        re.DOTALL | re.MULTILINE
    )

    return re.sub(pattern, replacer, text)
6
Menno Rubingh

N'oubliez pas qu'en C, la nouvelle barre oblique inversée est éliminée avant le traitement des commentaires et les trigraphes sont traités avant cela (car ??/est le trigraphe de la barre oblique inverse). J'ai un programme C appelé SCC (supprimer les commentaires C/C++), et voici une partie du code de test ...

" */ /* SCC has been trained to know about strings /* */ */"!
"\"Double quotes embedded in strings, \\\" too\'!"
"And \
newlines in them"

"And escaped double quotes at the end of a string\""

aa '\\
n' OK
aa "\""
aa "\
\n"

This is followed by C++/C99 comment number 1.
// C++/C99 comment with \
continuation character \
on three source lines (this should not be seen with the -C fla
The C++/C99 comment number 1 has finished.

This is followed by C++/C99 comment number 2.
/\
/\
C++/C99 comment (this should not be seen with the -C flag)
The C++/C99 comment number 2 has finished.

This is followed by regular C comment number 1.
/\
*\
Regular
comment
*\
/
The regular C comment number 1 has finished.

/\
\/ This is not a C++/C99 comment!

This is followed by C++/C99 comment number 3.
/\
\
\
/ But this is a C++/C99 comment!
The C++/C99 comment number 3 has finished.

/\
\* This is not a C or C++  comment!

This is followed by regular C comment number 2.
/\
*/ This is a regular C comment *\
but this is just a routine continuation *\
and that was not the end either - but this is *\
\
/
The regular C comment number 2 has finished.

This is followed by regular C comment number 3.
/\
\
\
\
* C comment */

Cela n'illustre pas les trigraphes. Notez que vous pouvez avoir plusieurs barres obliques inverses à la fin d'une ligne, mais l'épissage de ligne ne se soucie pas du nombre, mais le traitement ultérieur pourrait le faire. Etc. L'écriture d'une seule expression régulière pour gérer tous ces cas sera non triviale (mais c'est différent d'impossible).

6
Jonathan Leffler

Les cas d'expressions régulières tomberont dans certaines situations, comme lorsqu'un littéral de chaîne contient une sous-séquence qui correspond à la syntaxe du commentaire. Vous avez vraiment besoin d'un arbre d'analyse pour gérer cela.

4
Alex Coventry

vous pourrez peut-être utiliser py ++ pour analyser la source C++ avec GCC.

Py ++ ne réinvente pas la roue. Il utilise le compilateur GCC C++ pour analyser les fichiers source C++. Pour être plus précis, la chaîne d'outils ressemble à ceci:

le code source est transmis à GCC-XML GCC-XML le transmet au compilateur GCC C++ GCC-XML génère une description XML d'un programme C++ à partir de la représentation interne de GCC. Py ++ utilise le package pygccxml pour lire le fichier généré par GCC-XML. L'essentiel - vous pouvez être sûr que toutes vos déclarations sont lues correctement.

ou peut être pas. quoi qu'il en soit, ce n'est pas une analyse triviale.

@ Solutions basées sur les RE - il est peu probable que vous trouviez une RE qui gère correctement tous les cas `` maladroits '' possibles, sauf si vous contraignez la saisie (par exemple, pas de macros). pour une solution pare-balles, vous n'avez vraiment pas d'autre choix que de tirer parti de la vraie grammaire.

3
Dustin Getz

Il y a aussi une réponse non python: utilisez le programme stripcmt :

StripCmt est un utilitaire simple écrit en C pour supprimer les commentaires des fichiers source C, C++ et Java. Dans la grande tradition des programmes de traitement de texte Unix, il peut fonctionner comme un FIFO (First In - First Out) filtre ou accepte les arguments sur la ligne de commande.

1
hlovdal

Ce qui suit a fonctionné pour moi:

from subprocess import check_output

class Util:
  def strip_comments(self,source_code):
    process = check_output(['cpp', '-fpreprocessed', source_code],Shell=False)
    return process 

if __name__ == "__main__":
  util = Util()
  print util.strip_comments("somefile.ext")

Il s'agit d'une combinaison du sous-processus et du préprocesseur cpp. Pour mon projet, j'ai une classe utilitaire appelée "Util" que je garde divers outils que j'utilise/besoin.

1
Antonio Arredondo

Je suis désolé, ce n'est pas une solution Python, mais vous pouvez également utiliser un outil qui comprend comment supprimer des commentaires, comme votre préprocesseur C/C++. Voici comment GNU CPP le fait .

cpp -fpreprocessed foo.c
1
sigjuice

Vous n'avez pas vraiment besoin d'un arbre d'analyse pour le faire parfaitement, mais vous avez en fait besoin du flux de jetons équivalent à ce qui est produit par le frontal du compilateur. Un tel flux de jeton doit nécessairement prendre en charge toutes les bizarreries telles que le début de commentaire continu, le début de commentaire dans la chaîne, la normalisation du trigraphe, etc. Si vous avez le flux de jeton, la suppression des commentaires est facile. (J'ai un outil qui produit exactement de tels flux de jetons, comme, devinez quoi, l'extrémité avant d'un véritable analyseur qui produit un véritable arbre d'analyse :).

Le fait que les jetons soient individuellement reconnus par des expressions régulières suggère que vous pouvez, en principe, écrire une expression régulière qui sélectionnera les lexèmes de commentaire. La réelle complexité des expressions régulières définies pour le tokenizer (au moins celui que nous avons écrit) suggère que vous ne pouvez pas faire cela en pratique; les écrire individuellement était déjà assez difficile. Si vous ne voulez pas le faire parfaitement, eh bien, la plupart des solutions RE ci-dessus sont très bien.

Maintenant, pourquoi vous voudriez que les commentaires de bande me dépassent, à moins que vous ne construisiez un obfuscateur de code. Dans ce cas, vous devez avoir parfaitement raison.

0
Ira Baxter