Expresión regular para eliminación de comentarios de código

publicado por: Anonymous

Estoy tratando de hacer una expresión regular que elimine comentarios del estilo // y /**/, de momento utilizaba una sacada de este sitio:

(/*([^*]|[rn]|(*+([^*/]|[rn])))**+/)|(//.*)

El problema estaba cuando el comentario estaba en forma de string literal (tanto con comillas simples como dobles), por ej.:

var a = "//Holaaa";

Por lo que traté de usar lookbehind y lookahead juntos para escapar ambas comillas y me quedó así:

(?<!"|')((/*([^*]|[rn]|(*+([^*/]|[rn])))**+/)|(//.*))(?!"|')

El problema de esto es que para casos como los siguientes no funciona:

var ar = "asasasas /*dsdsdsd*/ "
var ar = "asaasas //dsdsdsd"
var ar = "asasasas /*dsdsdsd*/ dsadasdsda"
var ar = "asaasas //dsdsdsd asdsadsadsad"

Traté cambiando (?<!"|') por (?<!"|'.*) y (?!"|') por (?!.*"|'), pero tampoco resultó.

¿Que me estoy perdiendo?

Nota: la idea es utilizarla en Java, pero no necesariamente la respuesta tiene que ser en su estandar, con que sepa la expresión más tarde puedo adapartarla por mi cuenta.

solución

Problema

Honestamente, el regex que sacaste de esa página es muy malo, no sólo en lo que deja sin considerar, sino también en términos de eficiencia. Tu intento de arreglarlo con aserciones (lookahead/lookbehind) es bueno, pero es una estrategia que no tiene buenos resultados. La explicación del por qué no va a funcionar es demasiado extensa, pero se podría resumir en que algo como (?<!"|') sólo verifica 1 caracter hacia atrás de la posición actual y, por más que pudiésemos hacerlo de largo variable (-no, no se puede), no podrías determinar si la comilla previa está abriendo o cerrando un comentario. En resumen: estrategia equivocada (en la que todos hemos caído).

Solución

Para este tipo de casos, donde es relevante toda la sintaxis previa a la posición en la que se busca coincidir, la forma de llegar a ese punto es consumiendo cada parte del texto, mientras se valida cada estructura.

El regex, debería estar anclado al principio del texto, o el final del reemplazo anterior (G), e ir coincidiendo el texto donde un comentario no tiene significado, hasta encontrar el comentario. A grandes rasgos sería reemplazar

G  ([lo que no es comentario]*)  comentario

donde se captura todo el texto previo y se incluye en al reemplazar por

$1

Expresión regular

Ahora bien, encontrar todo lo que no es comentario, implica coincidir con todos los caracteres excepto los que tienen un significado especial, e ir agregando reglas para que coincida con cada una de esas excepciones (una que escapa a un caracter, texto entre comillas, etc).

Como forma de simplificar la explicación, comenté el regex con el objetivo de cada estructura:

G                                  # Anclar a A o fin de coincidencia previa
(                                   # GRUPO 1: capturar todo lo que no es comentario en $1:
  [^"'/\]*                         #   caracteres sin significado especial
  (?:                               #   estructuras especiales:
    (?: \.                         #       a. barra escapando caracter
      | /(?![*/])                   #       b. una / que no está seguida de / o *
      | "[^"\]*(?:\.[^"\]*)*"    #       c. texto entre comillas dobles
      | '[^'\]*(?:\.[^'\]*)*'    #       d. texo entre comillas simples
    )                               #
    [^"'/\]*                       #     seguido de más caracteres sin significado
  )*+                               #   (estructuras especiales repetidas 0 a inf)
)                                   # fin de Grupo 1

(?:                                 # COMENTARIOS (no está dentro de $1)
   //.*                             #   a. // hasta el final de la linea
|  /*[^*]*(?:*(?!/)[^*]*)**/     #   b. /* hasta el siguiente */
)

O en una línea sin comentarios:

G([^"'/\]*(?:(?:\.|/(?![*/])|"[^"\]*(?:\.[^"\]*)*"|'[^'\]*(?:\.[^'\]*)*')[^"'/\]*)*+)(?://.*|/*[^*]*(?:*(?!/)[^*]*)**/)

Con barras y comillas escapadas para Java:

final String regex = "\G([^"'/\\]*(?:(?:\\.|/(?![*/])|"[^"\\]*(?:\\.[^"\\]*)*"|'[^'\\]*(?:\\.[^'\\]*)*')[^"'/\\]*)*+)(?://.*|/\*[^*]*(?:\*(?!/)[^*]*)*\*/)";

Demo

https://regex101.com/r/wDg8LJ/1/


Código en Java

import java.util.regex.Matcher;
import java.util.regex.Pattern;
final String regex = 
       "\G"                                         // Anclar a \A o fin de coincidencia previa
     + "("                                           // GRUPO 1: capturar todo lo que no es comentario en $1:
     + "  [^"'/\\]*"                              //   caracteres sin significado especial
     + "  (?:"                                       //   estructuras especiales:
     + "    (?: \\."                               //       a. barra escapando caracter
     + "      | /(?![*/])"                           //       b. una / que no está seguida de / o *
     + "      | "[^"\\]*(?:\\.[^"\\]*)*""  //       c. texto entre comillas dobles
     + "      | '[^'\\]*(?:\\.[^'\\]*)*'"      //       d. texo entre comillas simples
     + "    )"                                       //
     + "    [^"'/\\]*"                            //     seguido de más caracteres sin significado
     + "  )*+"                                       //   (estructuras especiales repetidas 0 a inf)
     + ")"                                           // fin de Grupo 1
     + "(?:"                                         // COMENTARIOS (no está dentro de $1)
     + "   //.*"                                     //   a. // hasta el final de la linea
     + "|  /\*[^*]*(?:\*(?!/)[^*]*)*\*/"          //   b. /* hasta el siguiente */
     + ")";

final String texto = 
       "var ar1 = "asasasas /*dsdsdsd*/ "n"
     + "var ar2 = "asaasas //dsdsdsd"n"
     + "var ar3 // = "asaasas //dsdsdsd asdsadsadsad"n"
     + "/*var ar4*/ = "asasasas /*dsdsdsd*/ dsadasdsda" /*n"
     + "var ar5 = "asaasas //dsdsdsd asdsadsadsad" //comentarion"
     + "var ar6 = "x" */ + "asaasas //dsdsdsd asdsadsadsad"n"
     + "var ar7 = "x" // + "asaasas //dsdsdsd asdsadsadsad"n"
     + "var ar8 = "asaasas //dsdsdsd asdsadsadsad" //comentarion"
     + "var ar9 = "asaasas //dsdsdsd asdsadsadsad"";

final String reempl = "$1";

final Pattern pattern = Pattern.compile(regex, Pattern.COMMENTS);
final Matcher matcher = pattern.matcher(texto);

final String resultado = matcher.replaceAll(reempl);

System.out.println("TEXTO:n" + texto);
System.out.println("nRESULTADO:n" + resultado);

Resultado

TEXTO:
var ar1 = "asasasas /*dsdsdsd*/ "
var ar2 = "asaasas //dsdsdsd"
var ar3 // = "asaasas //dsdsdsd asdsadsadsad"
/*var ar4*/ = "asasasas /*dsdsdsd*/ dsadasdsda" /*
var ar5 = "asaasas //dsdsdsd asdsadsadsad" //comentario
var ar6 = "x" */ + "asaasas //dsdsdsd asdsadsadsad"
var ar7 = "x" // + "asaasas //dsdsdsd asdsadsadsad"
var ar8 = "asaasas //dsdsdsd asdsadsadsad" //comentario
var ar9 = "asaasas //dsdsdsd asdsadsadsad"

RESULTADO:
var ar1 = "asasasas /*dsdsdsd*/ "
var ar2 = "asaasas //dsdsdsd"
var ar3 
 = "asasasas /*dsdsdsd*/ dsadasdsda"  + "asaasas //dsdsdsd asdsadsadsad"
var ar7 = "x" 
var ar8 = "asaasas //dsdsdsd asdsadsadsad" 
var ar9 = "asaasas //dsdsdsd asdsadsadsad"

Demo

http://ideone.com/NSGmCL

Respondido por: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *