Domanda

Sto cercando il codice Python che rimuove i commenti C e C ++ da una stringa. (Supponiamo che la stringa contenga un intero file sorgente C.)

Mi rendo conto che potrei usare le sottostringhe .match () con un Regex, ma ciò non risolve l'annidamento / * , o avere un // all'interno di un / * * / .

Idealmente, preferirei un'implementazione non ingenua che gestisca correttamente i casi imbarazzanti.

È stato utile?

Soluzione

Non so se hai familiarità con sed , il programma di analisi del testo basato su UNIX (ma disponibile per Windows), ma ho trovato uno script sed qui che rimuoverà i commenti C / C ++ da un file. È molto intelligente; per esempio, ignorerà '//' e '/ *' se trovato in una dichiarazione di stringa, ecc. Da Python, può essere usato usando il seguente codice:

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()

In questo programma, source_code è la variabile che contiene il codice sorgente C / C ++, e infine stripped_code conterrà il codice C / C ++ con i commenti rimossi. Naturalmente, se hai il file su disco, potresti avere le variabili input e output come handle di file che puntano a quei file ( input in modalità di lettura, output in modalità di scrittura). remccoms3.sed è il file dal link sopra e dovrebbe essere salvato in una posizione leggibile sul disco. sed è disponibile anche su Windows e viene installato per impostazione predefinita sulla maggior parte delle distribuzioni GNU / Linux e Mac OS X.

Probabilmente sarà meglio di una soluzione Python pura; non è necessario reinventare la ruota.

Altri suggerimenti

Gestisce i commenti in stile C ++, i commenti in stile C, le stringhe e il loro semplice annidamento.

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)

Le stringhe devono essere incluse, poiché i marcatori di commento al loro interno non iniziano un commento.

Modifica: re.sub non ha preso alcun flag, quindi doveva prima compilare il modello.

Modifica2: aggiunti caratteri letterali dei caratteri, poiché potrebbero contenere virgolette che altrimenti verrebbero riconosciute come delimitatori di stringa.

Edit3: risolto il caso in cui un'espressione legale int / ** / x = 5; diventava intx = 5; che non compilare, sostituendo il commento con uno spazio anziché una stringa vuota.

I commenti C (e C ++) non possono essere nidificati. Le espressioni regolari funzionano bene:

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

Questo richiede il flag “Linea singola” ( Re.S ) perché un commento C può estendersi su più righe.

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

Questo codice dovrebbe funzionare.

/ EDIT: nota che il mio codice sopra fa in realtà un'ipotesi sulle terminazioni di riga! Questo codice non funzionerà su un file di testo Mac. Tuttavia, questo può essere modificato relativamente facilmente:

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

Questa espressione regolare dovrebbe funzionare su tutti i file di testo, indipendentemente dalle terminazioni di riga (copre le terminazioni di riga Windows, Unix e Mac).

/ EDIT: MizardX e Brian (nei commenti) hanno fatto un'osservazione valida sulla gestione delle stringhe. Me ne sono completamente dimenticato perché il regex sopra è estratto da un modulo di analisi che ha una gestione aggiuntiva per le stringhe. La soluzione di MizardX dovrebbe funzionare molto bene, ma gestisce solo stringhe tra virgolette doppie.

Non dimenticare che in C, la barra rovesciata-nuova riga viene eliminata prima che i commenti vengano elaborati e le trigrafi vengano elaborate prima (perché ?? / è la trigrafia per la barra rovesciata). Ho un programma C chiamato SCC (commenti strip C / C ++), e qui fa parte del codice di 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 */

Questo non illustra le trigrafi. Si noti che è possibile avere più barre rovesciate alla fine di una linea, ma la giunzione delle linee non si preoccupa di quante ce ne siano, ma l'elaborazione successiva potrebbe. Ecc. Scrivere un unico regex per gestire tutti questi casi non sarà banale (ma è diverso dall'impossibile).

Questo post fornisce una versione codificata del miglioramento del codice di Markus Jarderot che è stato descritto da atikat, in un commento al post di Markus Jarderot. (Grazie ad entrambi per aver fornito il codice originale, che mi ha risparmiato molto lavoro.)

Per descrivere il miglioramento in qualche modo in modo più completo: il miglioramento mantiene intatta la numerazione delle righe. (Questo viene fatto mantenendo intatti i caratteri di nuova riga nelle stringhe con le quali vengono sostituiti i commenti C / C ++.)

Questa versione della funzione di rimozione dei commenti C / C ++ è adatta quando si desidera generare messaggi di errore per gli utenti (ad es. errori di analisi) che contengono numeri di riga (ovvero numeri di riga validi per il testo originale).

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)

I casi di espressione regolare cadranno in alcune situazioni, come in cui una stringa letterale contiene una sottosequenza che corrisponde alla sintassi del commento. Hai davvero bisogno di un albero di analisi per far fronte a questo.

potresti essere in grado di sfruttare py ++ per analizzare l'origine C ++ con GCC.

  

Py ++ non reinventa la ruota. esso   utilizza il compilatore C ++ GCC per analizzare C ++   file sorgenti. Per essere più precisi, il   la catena degli strumenti si presenta così:

     

il codice sorgente viene passato a GCC-XML   GCC-XML lo passa al compilatore GCC C ++   GCC-XML genera una descrizione XML   di un programma C ++ dall'interno di GCC   rappresentazione. Py ++ utilizza pygccxml   pacchetto per leggere GCC-XML generato   file. La linea di fondo - puoi esserlo   certo che tutte le tue dichiarazioni lo sono   letto correttamente.

o, forse no. a prescindere, questa non è una banale analisi.

@ Soluzioni basate su RE - è improbabile che tu trovi una RE che gestisca correttamente tutti i possibili casi 'imbarazzanti', a meno che tu non limiti l'input (ad es. nessuna macro). per una soluzione antiproiettile, non hai davvero altra scelta che sfruttare la vera grammatica.

Mi dispiace che questa non sia una soluzione Python, ma potresti anche usare uno strumento che capisce come rimuovere i commenti, come il tuo preprocessore C / C ++. Ecco come funziona GNU CPP .

cpp -fpreprocessed foo.c

Esiste anche una risposta non python: utilizzare il programma stripcmt :

  

StripCmt è una semplice utility scritta   in C per rimuovere i commenti da C, C ++,   e file sorgente Java. Nel Grand   tradizione di elaborazione del testo Unix   programmi, può funzionare come a   Filtro FIFO (First In - First Out) o   accetta argomenti dalla riga di comando.

Per me ha funzionato:

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")

Questa è una combinazione del sottoprocesso e del preprocessore cpp. Per il mio progetto ho una classe di utilità chiamata " Util " che tengo vari strumenti che uso / necessito.

Non hai davvero bisogno di un albero di analisi per farlo perfettamente, ma in effetti hai bisogno del token stream equivalente a quello che viene prodotto dal front-end del compilatore. Un tale flusso di token deve necessariamente occuparsi di tutte le stranezze come l'inizio del commento a linea continua, l'inizio del commento in stringa, la normalizzazione della trigrafia, ecc. Se si dispone del flusso di token, l'eliminazione dei commenti è semplice. (Ho uno strumento che produce esattamente tali flussi di token, come, indovina, il front-end di un vero parser che produce un vero albero di analisi :).

Il fatto che i token siano individualmente riconosciuti dalle espressioni regolari suggerisce che in linea di principio è possibile scrivere un'espressione regolare che selezionerà i lessemi dei commenti. La reale complessità delle espressioni regolari impostate per il tokenizer (almeno quella che abbiamo scritto) suggerisce che non puoi farlo in pratica; scriverli individualmente è stato abbastanza difficile. Se non vuoi farlo alla perfezione, beh, allora, la maggior parte delle soluzioni RE di cui sopra vanno bene.

Ora, perché vuoi eliminare i commenti è oltre me, a meno che tu non stia costruendo un offuscatore di codice. In questo caso, devi avere perfettamente ragione.

Di recente ho riscontrato questo problema quando ho seguito un corso in cui il professore ci ha richiesto di eliminare javadoc dal nostro codice sorgente prima di inviarlo a lui per una revisione del codice. Abbiamo dovuto farlo più volte, ma non potevamo semplicemente rimuovere javadoc in modo permanente perché ci veniva richiesto di generare anche file html javadoc. Ecco un piccolo script Python che ho creato per fare il trucco. Poiché javadoc inizia con / ** e termina con * /, lo script cerca questi token, ma lo script può essere modificato in base alle proprie esigenze. Gestisce anche i commenti di blocco a riga singola e i casi in cui un commento di blocco termina ma esiste ancora un codice non commentato sulla stessa riga della fine del commento di blocco. Spero che questo aiuti!

ATTENZIONE: questo script modifica il contenuto dei file passati e li salva nei file originali. Sarebbe saggio avere un backup altrove

#!/usr/bin/python
"""
 A simple script to remove block comments of the form /** */ from files
 Use example: ./strip_comments.py *.java
 Author: holdtotherod
 Created: 3/6/11
"""
import sys
import fileinput

for file in sys.argv[1:]:
    inBlockComment = False
    for line in fileinput.input(file, inplace = 1):
        if "/**" in line:
            inBlockComment = True
        if inBlockComment and "*/" in line:
            inBlockComment = False
            # If the */ isn't last, remove through the */
            if line.find("*/") != len(line) - 3:
                line = line[line.find("*/")+2:]
            else:
                continue
        if inBlockComment:
            continue
        sys.stdout.write(line)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top