¿Cómo se numeran los grupos de captura anidados en expresiones regulares?

84

¿Existe un comportamiento definido sobre cómo las expresiones regulares deben manejar el comportamiento de captura de paréntesis anidados? Más específicamente, ¿puede esperar razonablemente que diferentes motores capturen los paréntesis externos en la primera posición y los paréntesis anidados en las posiciones posteriores?

Considere el siguiente código PHP (usando expresiones regulares PCRE)

<?php
  $test_string = 'I want to test sub patterns';
  preg_match('{(I (want) (to) test) sub (patterns)}', $test_string, $matches);
  print_r($matches);
?>

Array
(
    [0] => I want to test sub patterns  //entire pattern
    [1] => I want to test           //entire outer parenthesis
    [2] => want             //first inner
    [3] => to               //second inner
    [4] => patterns             //next parentheses set
)

La expresión completa entre paréntesis se captura primero (quiero probar), y luego los patrones internos entre paréntesis se capturan a continuación ("quiero" y "para"). Esto tiene sentido lógico, pero pude ver un caso igualmente lógico para capturar primero los subparéntesis y LUEGO capturar el patrón completo.

Entonces, ¿este comportamiento definido de "capturar todo primero" en motores de expresión regular, o va a depender del contexto del patrón y / o del comportamiento del motor (PCRE es diferente de C # es diferente de Java es diferente que etc.)?

Alan Storm
fuente
Si está realmente interesado en todos los tipos de expresiones regulares, la etiqueta "independiente del idioma" es lo que desea. Hay demasiados sabores para enumerarlos todos, y la mayoría de ellos no se ajustan a ningún estándar real (aunque son notablemente consistentes cuando se trata de la numeración de grupos de captura).
Alan Moore
Se puede acceder al grupo usando $ 1, $ 2, $ 3 ... etc. ¿Cómo acceder al décimo grupo? ¿Serán $ 10? No creo que $ 10 funcione porque se interpretará como $ 1 seguido de 0. ¿Significa esto que solo podemos tener un máximo de 9 grupos? Si el autor puede, por favor, incluir esto como parte de la pregunta, entonces este será un lugar único para saber todo sobre los grupos anidados en expresiones regulares.
LionHeart

Respuestas:

59

De perlrequick

Si las agrupaciones en una expresión regular están anidadas, $ 1 obtiene el grupo con el paréntesis de apertura más a la izquierda, $ 2 el siguiente paréntesis de apertura, etc.

Advertencia : excluyendo el paréntesis de apertura del grupo sin captura (? =)

Actualizar

No uso mucho PCRE, ya que generalmente uso lo real;), pero los documentos de PCRE muestran lo mismo que los de Perl:

SUBPATRONES

2.Establece el subpatrón como un subpatrón de captura. Esto significa que, cuando todo el patrón coincide, esa parte de la cadena de asunto que coincide con el subpatrón se pasa de nuevo a la persona que llama a través del ovectorargumento de pcre_exec(). Los paréntesis de apertura se cuentan de izquierda a derecha (comenzando desde 1) para obtener el número de los subpatrones de captura.

Por ejemplo, si la cadena "el rey rojo" coincide con el patrón

the ((red|white) (king|queen))

las subcadenas capturadas son "rey rojo", "rojo" y "rey", y están numeradas 1, 2 y 3, respectivamente.

Si PCRE se está alejando de la compatibilidad de expresiones regulares de Perl, quizás el acrónimo debería redefinirse: "Expresiones regulares afines de Perl", "Expresiones regulares comparables de Perl" o algo así. O simplemente deshacerse de las letras de significado.

daotoad
fuente
1
@Sinan: está usando PCRE en PHP, que es "Expresiones regulares compatibles con Perl"; por lo que debería ser lo mismo que usar Perl directamente
Pascal MARTIN
3
Pascal, PCRE comenzó como un intento de ser un conjunto de expresiones regulares compatibles con Perl, pero en los últimos años los dos han divergido ligeramente. Sigue siendo muy similar, pero hay diferencias sutiles en los conjuntos de funciones avanzadas. (Además, según la pregunta, estoy interesado en todas las plataformas)
Alan Storm
1
En realidad, es Perl el que está haciendo la mayor parte del "alejamiento" en estos días, pero tienes razón: "compatible con Perl" está cambiando rápidamente de un nombre inapropiado a un non sequitur. : D
Alan Moore
1
@Alan, Perl definitivamente está en movimiento. P5.10 cambió algunas cosas, pero 6 será muy diferente. Es casi seguro que la P deberá interpretarse como "Perl 5". PCRE es un gran proyecto, que no puedo elogiar lo suficiente, ha sido una bendición en más de unos pocos proyectos.
daotoad
1
Agregué esto debajo de la primera cita. Advertencia : excluyendo el paréntesis de apertura del grupo sin captura (? =). No me di cuenta de que no estaba conectado cuando lo edité. Solo cuando agregué este comentario se me solicitaron las credenciales. Entonces, ¡ahora necesita 1 persona más para aprobar!
JGFMK
17

Sí, todo esto está bastante bien definido para todos los idiomas que le interesan:

  • Java - http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html#cg
    "Los grupos de captura se numeran contando sus paréntesis de apertura de izquierda a derecha. ... Grupo cero siempre representa la expresión completa ".
  • .Net - http://msdn.microsoft.com/en-us/library/bs2twtah(VS.71).aspx
    "Las capturas que usan () se numeran automáticamente según el orden del paréntesis de apertura, comenzando desde uno. El primero capturar, capturar elemento número cero, es el texto que coincide con el patrón de expresión regular completo. ")
  • PHP (funciones PCRE) - http://www.php.net/manual/en/function.preg-replace.php#function.preg-replace.parameters
    "\ 0 o $ 0 se refiere al texto que coincide con el patrón completo. Los paréntesis de apertura se cuentan de izquierda a derecha (comenzando desde 1) para obtener el número del subpatrón de captura ". (También fue cierto para las funciones POSIX obsoletas)
  • PCRE - http://www.pcre.org/pcre.txt
    Para agregar a lo que dijo Alan M, busque "Cómo pcre_exec () devuelve subcadenas capturadas" y lea el quinto párrafo que sigue:

    El primer par de enteros, ovector [0] y ovector [1], identifica el
    parte de la cadena del sujeto que coincide con el patrón completo. El siguiente
    par se utiliza para el primer subpatrón de captura, y así sucesivamente. El valor
    devuelto por pcre_exec () es uno más que el par numerado más alto que
    ha sido establecido. Por ejemplo, si se han capturado dos subcadenas, la
    el valor devuelto es 3. Si no hay subpatrones de captura, la devolución
    El valor de una coincidencia exitosa es 1, lo que indica que solo el primer par
    de compensaciones se ha establecido.
    
  • Perl es diferente : http://perldoc.perl.org/perlre.html#Capture-buffers
    $ 1, $ 2, etc., coinciden con los grupos de captura como se esperaría (es decir, por la aparición del corchete de apertura), sin embargo $ 0 devuelve el nombre del programa, no la cadena de consulta completa - para conseguir que use $ & en su lugar.

Es muy probable que encuentre resultados similares para otros lenguajes (Python, Ruby y otros).

Dice que es igualmente lógico enumerar los grupos de captura internos primero y tiene razón: es solo una cuestión de indexar al cerrar, en lugar de abrir, parens. (si te entiendo bien). Sin embargo, hacer esto es menos natural (por ejemplo, no sigue la convención de dirección de lectura) y, por lo tanto, hace que sea más difícil (probablemente no significativamente) determinar, por insepección, qué grupo de captura estará en un índice de resultado dado.

Poner toda la cadena de coincidencias en la posición 0 también tiene sentido, principalmente por coherencia. Permite que toda la cadena coincidente permanezca en el mismo índice independientemente del número de grupos de captura de expresión regular a expresión regular e independientemente del número de grupos de captura que realmente coincidan con cualquier cosa (Java, por ejemplo, colapsará la longitud de la matriz de grupos coincidentes para cada captura grupo no coincide con ningún contenido (piense, por ejemplo, algo como "un patrón (. *)"). Siempre puede inspeccionar capturing_group_results [capturing_group_results_length - 2], pero eso no se traduce bien en los idiomas de Perl que crean variables de forma dinámica ($ 1 , $ 2 etc.) (Perl es un mal ejemplo, por supuesto, ya que usa $ & para la expresión coincidente, pero entiendes la idea :).

Alan Donnelly
fuente
1
Buena respuesta ... Pero, ¿qué tal actualizar para Python (2 y 3) también :-)
JGFMK
¿¡Qué pasa con JavaScript!?!
mesqueeb
9

Cada sabor de expresión regular que conozco agrupa los grupos por el orden en que aparecen los paréntesis de apertura. El hecho de que los grupos externos se enumeren antes de sus subgrupos contenidos es solo un resultado natural, no una política explícita.

Donde se pone interesante es con los grupos con nombre . En la mayoría de los casos, siguen la misma política de numeración por las posiciones relativas de los paréntesis: el nombre es simplemente un alias para el número. Sin embargo, en las expresiones regulares de .NET, los grupos con nombre se numeran por separado de los grupos numerados. Por ejemplo:

Regex.Replace(@"one two three four", 
              @"(?<one>\w+) (\w+) (?<three>\w+) (\w+)",
              @"$1 $2 $3 $4")

// result: "two four one three"

En efecto, el número es un alias del nombre ; los números asignados a los grupos nombrados comienzan donde terminan los grupos numerados "reales". Eso puede parecer una política extraña, pero hay una buena razón para ello: en las expresiones regulares de .NET puede usar el mismo nombre de grupo más de una vez en una expresión regular. Eso hace posibles expresiones regulares como la de este hilo para hacer coincidir números de punto flotante de diferentes configuraciones regionales:

^[+-]?[0-9]{1,3}
(?:
    (?:(?<thousand>\,)[0-9]{3})*
    (?:(?<decimal>\.)[0-9]{2})?
|
    (?:(?<thousand>\.)[0-9]{3})*
    (?:(?<decimal>\,)[0-9]{2})?
|
    [0-9]*
    (?:(?<decimal>[\.\,])[0-9]{2})?
)$

Si hay un separador de miles, se guardará en el grupo "mil" sin importar qué parte de la expresión regular coincida con él. Del mismo modo, el separador decimal (si lo hay) siempre se guardará en el grupo "decimal". Por supuesto, hay formas de identificar y extraer los separadores sin grupos con nombre reutilizables, pero de esta forma es mucho más conveniente, creo que justifica con creces el extraño esquema de numeración.

Y luego está Perl 5.10+, que nos da más control sobre la captura de grupos del que sé qué hacer. :RE

Alan Moore
fuente
4

El orden de captura en el orden del par izquierdo es estándar en todas las plataformas en las que he trabajado (perl, php, ruby, egrep)

Devin Ceartas
fuente
"capturar en el orden de los parientes izquierdos" Gracias por eso, es una manera mucho más sucinta de describir el comportamiento.
Alan Storm
1
Puede volver a numerar las capturas en Perl 5.10 y Perl 6.
Brad Gilbert