¿Expresiones regulares legibles sin perder su poder?

74

Muchos programadores conocen la alegría de crear rápidamente una expresión regular, en estos días a menudo con la ayuda de algún servicio web, o más tradicionalmente en el indicador interactivo, o tal vez escribiendo un pequeño script que tiene la expresión regular en desarrollo y una colección de casos de prueba. En cualquier caso, el proceso es iterativo y bastante rápido: siga pirateando la cadena de aspecto críptico hasta que coincida y capture lo que desea y rechace lo que no desea.

Para un caso simple, el resultado podría ser algo como esto, como una expresión regular de Java:

Pattern re = Pattern.compile(
  "^\s*(?:(?:([\d]+)\s*:\s*)?(?:([\d]+)\s*:\s*))?([\d]+)(?:\s*[.,]\s*([0-9]+))?\s*$"
);

Muchos programadores también conocen el dolor de la necesidad de editar una expresión regular, o simplemente codifican alrededor de una expresión regular en una base de código heredada. Con un poco de edición para dividirlo, por encima de regexp todavía es muy fácil de comprender para cualquiera que esté razonablemente familiarizado con regexps, y un veterano de regexp debería ver de inmediato lo que hace (responda al final del post, en caso de que alguien quiera el ejercicio de averiguarlo ellos mismos.

Sin embargo, las cosas no necesitan ser mucho más complejas para que una expresión regular se convierta en algo realmente de solo escritura, e incluso con documentación diligente (lo que todos por supuesto hacen para todas las expresiones regulares complejas que escriben ...), la modificación de las expresiones regulares se convierte en una tarea desalentadora. También puede ser una tarea muy peligrosa, si regexp no se prueba cuidadosamente por unidad (pero todos por supuesto tienen pruebas de unidad completas para todas sus expresiones regulares complejas, tanto positivas como negativas ...).

Entonces, cuento, ¿existe una solución / alternativa de escritura y lectura para expresiones regulares sin perder su poder? ¿Cómo se vería la expresión regular anterior con un enfoque alternativo? Cualquier idioma está bien, aunque una solución multilingüe sería la mejor, en la medida en que las expresiones regulares sean multilingües.

Y luego, lo que hace la expresión regular anterior es esto: analiza una cadena de números en formato 1:2:3.4 , capturando cada número, donde se permiten espacios y solo se requiere 3 .

    
pregunta hyde 15.04.2013 - 14:44

11 respuestas

80

Algunas personas han mencionado componer desde partes más pequeñas, pero nadie ha dado un ejemplo todavía, así que aquí está el mío:

string number = "(\d+)";
string unit = "(?:" + number + "\s*:\s*)";
string optionalDecimal = "(?:\s*[.,]\s*" + number + ")?";

Pattern re = Pattern.compile(
  "^\s*(?:" + unit + "?" + unit + ")?" + number + optionalDecimal + "\s*$"
);

No es el más legible, pero creo que está más claro que el original.

Además, C # tiene el operador @ que se puede añadir a una cadena para indicar que debe tomarse literalmente (sin caracteres de escape), por lo que number sería @"([\d]+)";

    
respondido por el Bobson 15.04.2013 - 17:04
42

La clave para documentar la expresión regular es documentarla. Con demasiada frecuencia, la gente lanza lo que parece ser un ruido de línea y lo deja así.

Dentro de perl el operador /x al final de la expresión regular suprime los espacios en blanco permitiendo que uno documente la expresión regular .

La expresión regular anterior se convertiría en:

$re = qr/
  ^\s*
  (?:
    (?:       
      ([\d]+)\s*:\s*
    )?
    (?:
      ([\d]+)\s*:\s*
    )
  )?
  ([\d]+)
  (?:
    \s*[.,]\s*([\d]+)
  )?
  \s*$
/x;

Sí, consume un poco de espacio en blanco vertical, aunque uno podría acortarlo sin sacrificar demasiada legibilidad.

  

Y luego, lo que hace la expresión regular anterior es esto: analice una cadena de números en formato 1: 2: 3.4, capturando cada número, donde se permiten espacios y solo se requieren 3.

Mirando esta expresión regular, uno puede ver cómo funciona (y no funciona). En este caso, esta expresión regular coincidirá con la cadena 1 .

Se pueden tomar enfoques similares en otro idioma. La opción Python re.VERBOSE funciona allí.

Perl6 (el ejemplo anterior fue para perl5) lleva esto más lejos con el concepto de rules que lleva a aún más poderoso estructuras que el PCRE (proporciona acceso a otras gramáticas (sin contexto y sensibles al contexto) que solo las regulares regulares y extendidas).

En Java (de donde proviene este ejemplo), se puede usar la concatenación de cadenas para formar la expresión regular.

Pattern re = Pattern.compile(
  "^\s*"+
  "(?:"+
    "(?:"+
      "([\d]+)\s*:\s*"+  // Capture group #1
    ")?"+
    "(?:"+
      "([\d]+)\s*:\s*"+  // Capture group #2
    ")"+
  ")?"+ // First groups match 0 or 1 times
  "([\d]+)"+ // Capture group #3
  "(?:\s*[.,]\s*([0-9]+))?"+ // Capture group #4 (0 or 1 times)
  "\s*$"
);

Es cierto que esto crea mucho más " en la cadena, lo que puede generar cierta confusión, y se puede leer más fácilmente (especialmente con el resaltado de sintaxis en la mayoría de los IDE) y documentarse.

La clave es reconocer el poder y la naturaleza de "escribir una vez" en la que a menudo caen las expresiones regulares. Escribir el código para evitar esto de manera defensiva para que la expresión regular se mantenga clara y comprensible es la clave. Damos formato al código de Java para mayor claridad: las expresiones regulares no son diferentes cuando el lenguaje te da la opción de hacerlo.

    
respondido por el user40980 15.04.2013 - 16:54
26

El modo "detallado" que ofrecen algunos idiomas y bibliotecas es una de las respuestas a estas inquietudes. En este modo, los espacios en blanco en la cadena de expresión regular se eliminan (por lo que necesita usar \s ) y los comentarios son posibles. Aquí hay un breve ejemplo en Python que admite esto de forma predeterminada:

email_regex = re.compile(r"""
    ([\w\.\+]+) # username (captured)
    @
    \w+         # minimal viable domain part
    (?:\.w+)    # rest of the domain, after first dot
""", re.VERBOSE)

En cualquier idioma que no lo sea, la implementación de un traductor del modo detallado al "normal" debería ser una tarea sencilla. Si le preocupa la legibilidad de sus expresiones regulares, probablemente justifique esta inversión de tiempo con bastante facilidad.

    
respondido por el Xion 15.04.2013 - 16:28
15

Todos los idiomas que usan expresiones regulares te permiten componerlos de bloques más simples para facilitar la lectura, y con cualquier cosa más complicada que (o tan complicada como) tu ejemplo, definitivamente debes aprovechar esa opción. El problema particular con Java y muchos otros lenguajes es que no tratan las expresiones regulares como ciudadanos "de primera clase", en lugar de requerir que se introduzcan en el lenguaje a través de cadenas literales. Esto significa muchas comillas y barras invertidas que no son en realidad parte de la sintaxis de expresiones regulares y que hacen que las cosas sean difíciles de leer, y también significa que no puede ser mucho más legible que eso sin definir efectivamente su propio mini-lenguaje e intérprete. / p>

La mejor forma prototípica de integrar expresiones regulares fue, por supuesto, Perl, con su opción de espacio en blanco y operadores de citas regulares. Perl 6 amplía el concepto de crear expresiones regulares de partes a gramáticas recursivas reales, lo que es mucho mejor de usar y realmente no tiene comparación. Es posible que el idioma haya pasado por alto el momento oportuno, pero su soporte de expresiones regulares fue The Good Stuff (tm).

    
respondido por el Kilian Foth 15.04.2013 - 14:52
11

Me gusta usar Expresso: enlace

Esta aplicación gratuita tiene las siguientes funciones que me parecen útiles con el tiempo:

  • Simplemente puede copiar y pegar su expresión regular y la aplicación lo analizará por usted
  • Una vez que se haya escrito su expresión regular, puede probarla directamente desde la aplicación (la aplicación le dará la lista de capturas, reemplazos ...)
  • Una vez que lo hayas probado, generará el código C # para implementarlo (ten en cuenta que el código contendrá las explicaciones sobre tu expresión regular).

Por ejemplo, con la expresión regular que acaba de enviar, se vería así:

Por supuesto, intentarlo vale más que mil palabras para describirlo. Tenga en cuenta también que estoy relacionado con el editor de esta aplicación.

    
respondido por el E. Jaep 15.04.2013 - 16:10
8

Para algunas cosas, podría ser útil usar solo una gramática como BNF. Estos pueden ser mucho más fáciles de leer que las expresiones regulares. Una herramienta como GoldParser Builder puede convertir la gramática en un analizador que hace el trabajo pesado por usted.

Las gramáticas BNF, EBNF, etc. pueden ser mucho más fáciles de leer y hacer que una expresión regular complicada. El ORO es una herramienta para tales cosas.

El enlace c2 wiki a continuación tiene una lista de posibles alternativas que se pueden buscar en Google, con alguna discusión sobre ellas incluidas. Básicamente es un enlace "ver también" para completar la recomendación de mi motor de gramática:

Alternativas a las expresiones regulares

  

Tomando "alternativa" para significar "facilidad semánticamente equivalente con diferente sintaxis", existen al menos estas alternativas para / con Expresiones Regulares:

     
  • expresiones regulares básicas
  •   
  • Expresiones regulares "extendidas"
  •   
  • expresiones regulares compatibles con Perl
  •   
  • ... y muchas otras variantes ...
  •   
  • Sintaxis RE de estilo SNOBOL (SnobolLanguage, IconLanguage)
  •   
  • Sintaxis de SRE (RE's como EssExpressions)
  •   
  • diferentes sintácticos FSM
  •   
  • Gramáticas de intersección de estado finito (bastante expresivas)
  •   
  • ParsingExpressionGrammars, como en OMetaLanguage y LuaLanguage ( enlace )
  •   
  • El modo de análisis de RebolLanguage
  •   
  • ProbabilityBasedParsing ...
  •   
    
respondido por el Nick P 15.04.2013 - 20:06
4

Esta es una pregunta antigua y no vi ninguna mención de Expresiones verbales , así que pensé en agregar esa información aquí como Bien para los futuros buscadores. Las expresiones verbales fueron diseñadas específicamente para hacer que las expresiones regulares sean comprensibles para los humanos, sin necesidad de aprender el significado del símbolo de expresiones regulares. Vea el siguiente ejemplo. Creo que esto hace mejor lo que estás pidiendo.

// Create an example of how to test for correctly formed URLs
var tester = VerEx()
    .startOfLine()
    .then('http')
    .maybe('s')
    .then('://')
    .maybe('www.')
    .anythingBut(' ')
    .endOfLine();

// Create an example URL
var testMe = 'https://www.google.com';

// Use RegExp object's native test() function
if (tester.test(testMe)) {
    alert('We have a correct URL '); // This output will fire}
} else {
    alert('The URL is incorrect');
}

console.log(tester); // Outputs the actual expression used: /^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$/

Este ejemplo es para javascript, puede encontrar esta biblioteca ahora para muchos de los los lenguajes de programación.

    
respondido por el Parivar Saraff 13.10.2016 - 21:15
3

La forma más sencilla sería seguir usando expresiones regulares pero construyendo tu expresión a partir de la composición de expresiones más simples con nombres descriptivos, por ejemplo. enlace (y sí, esto es de concat cadena)

sin embargo, como alternativa, también puede utilizar una biblioteca de combinadores de analizadores, p. ej. enlace que te dará un analizador decente recursivo completo. Una vez más, el verdadero poder aquí proviene de la composición (esta vez la composición funcional).

    
respondido por el jk. 15.04.2013 - 16:23
3

Pensé que valdría la pena mencionar las expresiones grok de logstash. Grok se basa en la idea de componer largas expresiones de análisis a partir de otras más cortas. Permite realizar pruebas convenientes de estos bloques de construcción y viene preempaquetado con más de 100 patrones de uso común . Aparte de estos patrones, permite el uso de la sintaxis de todas las expresiones regulares.

El patrón anterior expresado en grok es (probé en la aplicación de depuración pero podría haber fallado):

"(( *%{NUMBER:a} *:)? *%{NUMBER:b} *:)? *%{NUMBER:c} *(. *%{NUMBER:d} *)?"

Las partes y espacios opcionales hacen que parezca un poco más feo de lo normal, pero tanto aquí como en otros casos, usar grok puede hacer que la vida sea mucho mejor.

    
respondido por el yoniLavi 17.04.2013 - 23:02
2

En F # tiene el módulo FsVerbalExpressions . Le permite componer Regexes a partir de expresiones verbales, también tiene algunos regex pre-construidos (como URL).

Uno de los ejemplos de esta sintaxis es el siguiente:

let groupName =  "GroupNumber"

VerbEx()
|> add "COD"
|> beginCaptureNamed groupName
|> any "0-9"
|> repeatPrevious 3
|> endCapture
|> then' "END"
|> capture "COD123END" groupName
|> printfn "%s"

// 123

Si no está familiarizado con la sintaxis de F #, groupName es la cadena "GroupNumber".

Luego crean una expresión verbal (VerbEx) que construyen como "COD (? < GroupNumber > [0-9] {3}) FIN". Que luego prueban en la cadena "COD123END", donde obtienen el grupo de captura llamado "GroupNumber". Esto resulta en 123.

Honestamente, encuentro que las expresiones regulares son mucho más fáciles de comprender.

    
respondido por el CodeMonkey 08.02.2017 - 13:26
-2

Primero, comprenda que el código que simplemente funciona es un código incorrecto. Un buen código también debe informar con precisión cualquier error encontrado.

Por ejemplo, si está escribiendo una función para transferir efectivo de la cuenta de un usuario a la cuenta de otro usuario; simplemente no devolvería un booleano "trabajado o fallido" porque eso no le da a la persona que llama ninguna idea de lo que salió mal y no permite que la persona que llama le informe al usuario correctamente. En su lugar, es posible que tenga un conjunto de códigos de error (o un conjunto de excepciones): no pudo encontrar la cuenta de destino, fondos insuficientes en la cuenta de origen, permiso denegado, no se puede conectar a la base de datos, demasiada carga (vuelva a intentarlo más tarde), etc. .

Ahora piense en su ejemplo "analice una cadena de números en formato 1: 2: 3.4". Todo lo que hace la expresión regular es informar un "aprobado / reprobado" que no permite que se presente una retroalimentación adecuada al usuario (si esta retroalimentación es un mensaje de error en un registro o una GUI interactiva donde los errores se muestran en rojo como tipos de usuario, o cualquier otra cosa). ¿Qué tipos de errores no puede describir correctamente? Mal carácter en el primer número, el primer número es demasiado grande, faltan dos puntos después del primer número, etc.

Para convertir "código incorrecto que simplemente funciona" en "código bueno que proporciona errores descriptivos adecuados", tiene que dividir la expresión regular en muchas expresiones regulares más pequeñas (normalmente, expresiones regulares que son tan pequeñas que es más fácil hacerlo sin expresiones regulares en el primer lugar).

Hacer que el código sea legible / mantenible es solo una consecuencia accidental de hacer que el código sea bueno.

    
respondido por el Brendan 18.04.2013 - 02:02

Lea otras preguntas en las etiquetas