Pregunta

Estoy buscando un código Python que elimine los comentarios de C y C ++ de una cadena. (Suponga que la cadena contiene un archivo fuente completo de C).

Me doy cuenta de que podría hacer coincidir subcadenas () con un Regex, pero eso no resuelve el anidado / * , o tener un // dentro de un / * * / .

Idealmente, preferiría una implementación no ingenua que maneje adecuadamente los casos incómodos.

¿Fue útil?

Solución

No sé si está familiarizado con sed , el programa de análisis de texto basado en UNIX (pero disponible en Windows), pero he encontrado un script sed here que eliminará los comentarios de C / C ++ de un archivo. Es muy inteligente; por ejemplo, ignorará '//' y '/ *' si se encuentra en una declaración de cadena, etc. Desde Python, se puede usar usando el siguiente código:

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

En este programa, source_code es la variable que contiene el código fuente de C / C ++ y, finalmente, stripped_code mantendrá el código C / C ++ con los comentarios eliminados. Por supuesto, si tiene el archivo en el disco, puede hacer que las variables input y output sean manijas de archivo que apuntan a esos archivos ( input en modo de lectura, salida en modo de escritura). remccoms3.sed es el archivo del enlace anterior y debe guardarse en una ubicación legible en el disco. sed también está disponible en Windows, y viene instalado de manera predeterminada en la mayoría de las distribuciones de GNU / Linux y Mac OS X.

Esto probablemente será mejor que una solución pura de Python; No es necesario reinventar la rueda.

Otros consejos

Maneja los comentarios de estilo C ++, los comentarios de estilo C, las cadenas y su anidado 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)

es necesario incluir cadenas, porque los marcadores de comentarios dentro de ellas no inician un comentario.

Editar: re.sub no tomó ninguna marca, así que primero tuvo que compilar el patrón.

Edit2: Se agregaron literales de caracteres, ya que podrían contener citas que de otra forma se reconocerían como delimitadores de cadena.

Edit3: Se corrigió el caso donde una expresión legal int / ** / x = 5; se convertiría en intx = 5; que no compilar, reemplazando el comentario con un espacio en lugar de una cadena vacía.

Los comentarios de

C (y C ++) no se pueden anidar. Las expresiones regulares funcionan bien:

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

Esto requiere la marca "Single line" ( Re.S ) porque un comentario en C puede abarcar varias líneas.

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

Este código debería funcionar.

/ EDIT: ¡Observe que mi código anterior en realidad hace una suposición sobre los finales de línea! Este código no funcionará en un archivo de texto de Mac. Sin embargo, esto puede modificarse con relativa facilidad:

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

Esta expresión regular debería funcionar en todos los archivos de texto, independientemente de sus finales de línea (cubre los finales de línea de Windows, Unix y Mac).

/ EDIT: MizardX y Brian (en los comentarios) hicieron un comentario válido sobre el manejo de cadenas. Me olvidé completamente de eso porque la expresión regular anterior se arranca de un módulo de análisis que tiene un manejo adicional para cadenas. La solución de MizardX debería funcionar muy bien pero solo maneja cadenas entre comillas dobles.

No olvide que en C, backslash-newline se elimina antes de que se procesen los comentarios, y los trigraphs se procesan antes de eso (porque ?? / es el trigraph para backslash). Tengo un programa en C llamado SCC (tira C / C ++ comentarios), y aquí es parte del código de prueba ...

" */ /* 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 */

Esto no ilustra los trigraphs. Tenga en cuenta que puede tener varias barras invertidas al final de una línea, pero al empalme de línea no le importa cuántos hay, pero el procesamiento posterior podría hacerlo. Etc. Escribir una expresión regular para manejar todos estos casos no será trivial (pero eso es diferente de lo imposible).

Esta publicación proporciona una versión codificada de la mejora del código de Markus Jarderot que fue descrita por atikat, en un comentario a la publicación de Markus Jarderot. (Gracias a ambos por proporcionar el código original, que me ahorró mucho trabajo).

Para describir la mejora de forma más completa: la mejora mantiene la numeración de líneas intacta. (Esto se hace manteniendo intactos los caracteres de nueva línea en las cadenas por las que se reemplazan los comentarios de C / C ++).

Esta versión de la función de eliminación de comentarios C / C ++ es adecuada cuando desea generar mensajes de error para sus usuarios (por ejemplo, errores de análisis) que contienen números de línea (es decir, números de línea válidos para el texto original).

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)

Los casos de expresiones regulares caerán en algunas situaciones, como cuando un literal de cadena contiene una subsecuencia que coincide con la sintaxis del comentario. Realmente necesitas un árbol de análisis para lidiar con esto.

es posible que puedas aprovechar py ++ para analizar la fuente de C ++ con GCC.

  

Py ++ no reinventa la rueda. Eso   usa el compilador GCC C ++ para analizar C ++   archivos fuente. Para ser más precisos, el   La cadena de herramientas se ve así:

     

el código fuente se pasa a GCC-XML   GCC-XML lo pasa al compilador GCC C ++   GCC-XML genera una descripción XML   de un programa en C ++ de GCC interno   representación. Py ++ utiliza pygccxml   paquete para leer GCC-XML generado   expediente. La línea de fondo - usted puede ser   claro que todas tus declaraciones son   leer correctamente.

o, tal vez no. Independientemente, esto no es un análisis trivial.

Soluciones basadas en RE - es poco probable que encuentre un RE que maneje todos los casos "incómodos" posibles, a menos que limite la entrada (por ejemplo, sin macros). para una solución a prueba de balas, no tiene más remedio que aprovechar la gramática real.

Lo siento, no es una solución de Python, pero también podría usar una herramienta que entienda cómo eliminar comentarios, como su preprocesador C / C ++. Así es como GNU CPP lo hace .

cpp -fpreprocessed foo.c

También hay una respuesta que no es python: use el programa stripcmt :

  

StripCmt es una sencilla utilidad escrita   en C para eliminar comentarios de C, C ++,   y archivos fuente de Java. En el gran   Tradición del procesamiento de texto Unix.   programas, puede funcionar ya sea como   Filtro FIFO (primero en entrar, primero en salir) o   aceptar argumentos en la línea de comandos.

Lo siguiente me funcionó:

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

Esta es una combinación del subproceso y el preprocesador cpp. Para mi proyecto tengo una clase de utilidad llamada " Util " que guardo varias herramientas que uso / necesito.

Realmente no necesita un árbol de análisis para hacer esto perfectamente, pero en realidad necesita la secuencia de tokens equivalente a la que produce el extremo frontal del compilador. Tal flujo de token debe necesariamente ocuparse de todas las rarezas, como el inicio de comentarios de línea continuada, el inicio de comentarios en la cadena, la normalización de trigraph, etc. Si tiene el flujo de tokens, eliminar los comentarios es fácil. (Tengo una herramienta que produce exactamente dichos flujos de token, como, adivina qué, el extremo frontal de un analizador real que produce un árbol de análisis real :).

El hecho de que los tokens sean reconocidos individualmente por expresiones regulares sugiere que, en principio, puede escribir una expresión regular que seleccione los lexemas de comentarios. La verdadera complejidad de las expresiones regulares establecidas para el tokenizador (al menos la que escribimos) sugiere que no se puede hacer esto en la práctica; escribirlos individualmente ya era bastante difícil. Si no quieres hacerlo a la perfección, bueno, entonces, la mayoría de las soluciones de RE anteriores están bien.

Ahora, por qué usted querría quitar los comentarios está más allá de mí, a menos que esté construyendo un ofuscador de código. En este caso, debes tenerlo perfectamente correcto.

Hace poco me encontré con este problema cuando tomé una clase en la que el profesor nos exigió que elimináramos javadoc de nuestro código fuente antes de enviárselo para una revisión del código. Tuvimos que hacer esto varias veces, pero no pudimos eliminar el javadoc de forma permanente porque también teníamos que generar archivos html javadoc. Aquí hay un pequeño script en python que hice para hacer el truco. Como javadoc comienza con / ** y termina con * /, el script busca estos tokens, pero el script puede modificarse para adaptarse a sus necesidades. También maneja los comentarios de bloque de una sola línea y los casos en los que un comentario de bloque finaliza pero todavía hay un código sin comentarios en la misma línea que el final del comentario de bloque. Espero que esto ayude!

ADVERTENCIA: este script modifica el contenido de los archivos pasados ??y los guarda en los archivos originales. Sería prudente tener una copia de seguridad en otro lugar

#!/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)
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top