El poder de reconocimiento de las expresiones regulares “modernas”

¿Qué clase de idiomas reconocen realmente las expresiones reales reales?

Siempre que haya un grupo de captura de longitud ilimitada con una referencia retrospectiva (p (.*)_\1 Ej (.*)_\1 ) una expresión regular ahora se corresponde con un idioma no habitual. Pero esto, por sí solo, no es suficiente para que coincida con algo como S ::= '(' S ')' | ε S ::= '(' S ')' | ε – el lenguaje libre de contexto de emparejar pares de parens.

Las expresiones regulares recursivas (que son nuevas para mí, pero estoy seguro que existen en Perl y PCRE) parecen reconocer al menos la mayoría de las CFL.

¿Alguien ha hecho o leído alguna investigación en esta área? ¿Cuáles son las limitaciones de estos regex “modernos”? ¿Reconocen estrictamente más o estrictamente menos que los CFG, de las gramáticas LL o LR? ¿O existen ambos idiomas que pueden ser reconocidos por una expresión regular pero no un CFG y todo lo contrario?

Los enlaces a documentos relevantes serían muy apreciados.

Recursión de patrón

Con patrones recursivos, tiene una forma de coincidencia de descenso recursivo.

Esto está bien para una variedad de problemas, pero una vez que desee realizar un análisis de descenso recursivo, debe insertar grupos de captura aquí y allá, y es difícil recuperar la estructura de análisis completo de esta manera. El módulo Regexp :: Grammars de Damian Conway para Perl transforma el patrón simple en uno equivalente que automáticamente hace que todo eso se llame capturar en una estructura recursiva de datos, facilitando la recuperación de la estructura analizada. Tengo una muestra que compara estos dos enfoques al final de esta publicación.

Restricciones a la recursión

La pregunta era qué tipo de gramáticas pueden coincidir con los patrones recursivos. Bueno, ciertamente son emparejamientos de tipo descendiente recursivo . Lo único que se me ocurre es que los patrones recursivos no pueden manejar la recursividad a la izquierda . Esto pone una restricción en el tipo de gramáticas a las que puedes aplicarlas. A veces puede reordenar sus producciones para eliminar la recursividad a la izquierda.

Por cierto, PCRE y Perl difieren ligeramente en cómo se le permite express la recursión. Consulte las secciones sobre “PATRONES RECURSIVOS” y “Diferencia de recursividad de Perl” en la página de manual de pcrepattern . por ejemplo: Perl puede manejar ^(.|(.)(?1)\2)$ donde PCRE requiere ^((.)(?1)\2|.)$ lugar.

Demostraciones de recursión

La necesidad de patrones recursivos surge sorprendentemente con frecuencia. Un ejemplo bien visitado es cuando necesita hacer coincidir algo que puede anidar, como paréntesis equilibrados, comillas o incluso tags HTML / XML. Aquí está el partido para pareados balenced:

 \((?:[^()]*+|(?0))*\) 

Me parece más complicado de leer debido a su naturaleza compacta. Esto es fácilmente curable con el modo /x para hacer que el espacio en blanco ya no sea significativo:

 \( (?: [^()] *+ | (?0) )* \) 

Por otra parte, dado que estamos utilizando parens para nuestra recursión, un ejemplo más claro sería hacer coincidir las comillas simples anidadas:

 ' (?: [^''] *+ | (?0) )* ' 

Otra cosa recursivamente definida que puede desear emparejar sería un palíndromo. Este patrón simple funciona en Perl:

 ^((.)(?1)\2|.?)$ 

que puedes probar en la mayoría de los sistemas usando algo como esto:

 $ perl -nle 'print if /^((.)(?1)\2|.?)$/i' /usr/share/dict/words 

Tenga en cuenta que la implementación de recursión de PCRE requiere los más elaborados

 ^(?:((.)(?1)\2|)|((.)(?3)\4|.)) 

Esto se debe a las restricciones sobre cómo funciona la recursión de PCRE.

Análisis correcto

Para mí, los ejemplos anteriores son en su mayoría partidos de juguete, realmente no todos interesantes. Cuando se vuelve interesante es cuando tienes una gramática real que estás tratando de analizar. Por ejemplo, RFC 5322 define una dirección de correo bastante elaborada. Aquí hay un patrón “gtwigtical” para que coincida:

 $rfc5322 = qr{ (?(DEFINE) (?
(?&mailbox) | (?&group)) (? (?&name_addr) | (?&addr_spec)) (? (?&display_name)? (?&angle_addr)) (? (?&CFWS)? < (?&addr_spec) > (?&CFWS)?) (? (?&display_name) : (?:(?&mailbox_list) | (?&CFWS))? ; (?&CFWS)?) (? (?&phrase)) (? (?&mailbox) (?: , (?&mailbox))*) (? (?&local_part) \@ (?&domain)) (? (?&dot_atom) | (?&quoted_string)) (? (?&dot_atom) | (?&domain_literal)) (? (?&CFWS)? \[ (?: (?&FWS)? (?&dcontent))* (?&FWS)? \] (?&CFWS)?) (? (?&dtext) | (?&quoted_pair)) (? (?&NO_WS_CTL) | [\x21-\x5a\x5e-\x7e]) (? (?&ALPHA) | (?&DIGIT) | [!#\$%&'*+-/=?^_`{|}~]) (? (?&CFWS)? (?&atext)+ (?&CFWS)?) (? (?&CFWS)? (?&dot_atom_text) (?&CFWS)?) (? (?&atext)+ (?: \. (?&atext)+)*) (? [\x01-\x09\x0b\x0c\x0e-\x7f]) (? \\ (?&text)) (? (?&NO_WS_CTL) | [\x21\x23-\x5b\x5d-\x7e]) (? (?&qtext) | (?&quoted_pair)) (? (?&CFWS)? (?&DQUOTE) (?:(?&FWS)? (?&qcontent))* (?&FWS)? (?&DQUOTE) (?&CFWS)?) (? (?&atom) | (?&quoted_string)) (? (?&word)+) # Folding white space (? (?: (?&WSP)* (?&CRLF))? (?&WSP)+) (? (?&NO_WS_CTL) | [\x21-\x27\x2a-\x5b\x5d-\x7e]) (? (?&ctext) | (?&quoted_pair) | (?&comment)) (? \( (?: (?&FWS)? (?&ccontent))* (?&FWS)? \) ) (? (?: (?&FWS)? (?&comment))* (?: (?:(?&FWS)? (?&comment)) | (?&FWS))) # No whitespace control (? [\x01-\x08\x0b\x0c\x0e-\x1f\x7f]) (? [A-Za-z]) (? [0-9]) (? \x0d \x0a) (? ") (? [\x20\x09]) ) (?&address) }x;

Como ve, eso es muy parecido a BNF. El problema es que es solo una coincidencia, no una captura. Y realmente no quieres rodear todo el asunto con la captura de parens porque eso no te dice qué producción coincide con qué parte. Usando el módulo Regexp :: Grammars mencionado anteriormente, podemos.

 #!/usr/bin/env perl use strict; use warnings; use 5.010; use Data::Dumper "Dumper"; my $rfc5322 = do { use Regexp::Grammars; # ...the magic is lexically scoped qr{ # Keep the big stick handy, just in case... #  # Match this... 
# As defined by these... | | ? ? \< \> ? : (?: | )? ; ? <[mailbox]> ** (,) \@ | | ? \[ (?: ? <[dcontent]>)* ? | <.NO_WS_CTL> | [\x21-\x5a\x5e-\x7e] <.ALPHA> | <.DIGIT> | [!#\$%&'*+-/=?^_`{|}~] <.CFWS>? <.atext>+ <.CFWS>? <.CFWS>? <.dot_atom_text> <.CFWS>? <.atext>+ (?: \. <.atext>+)* [\x01-\x09\x0b\x0c\x0e-\x7f] \\ <.text> <.NO_WS_CTL> | [\x21\x23-\x5b\x5d-\x7e] <.qtext> | <.quoted_pair> <.CFWS>? <.DQUOTE> (?:<.FWS>? <.qcontent>)* <.FWS>? <.DQUOTE> <.CFWS>? <.atom> | <.quoted_string> <.word>+ # Folding white space (?: <.WSP>* <.CRLF>)? <.WSP>+ <.NO_WS_CTL> | [\x21-\x27\x2a-\x5b\x5d-\x7e] <.ctext> | <.quoted_pair> | <.comment> \( (?: <.FWS>? <.ccontent>)* <.FWS>? \) (?: <.FWS>? <.comment>)* (?: (?:<.FWS>? <.comment>) | <.FWS>) # No whitespace control [\x01-\x08\x0b\x0c\x0e-\x1f\x7f] [A-Za-z] [0-9] \x0d \x0a " [\x20\x09] }x; }; while (my $input = <>) { if ($input =~ $rfc5322) { say Dumper \%/; # ...the parse tree of any successful match # appears in this punctuation variable } }

Como puede ver, al usar una notación muy diferente en el patrón, ahora obtiene algo que almacena todo el árbol de análisis para usted en %/ variable, con todo claramente etiquetado. El resultado de la transformación sigue siendo un patrón, como puede ver con el operador =~ . Es solo un poco mágico.

    Intereting Posts