Subpatrones de una sola vez
Con la repetición de maximización y minimización, el fracaso de lo que sigue normalmente hace que el elemento repetido se reevalúe para ver si un número diferente de repeticiones permite que el resto del patrón coincida. A veces es útil
evitar esto, ya sea para cambiar la naturaleza de la coincidencia, o
para hacer que falle antes de lo que lo haría de otra manera, cuando el autor del patrón sabe que no hay punto en continuar.
Considere, por ejemplo, el patrón \d+foo cuando se aplica a
la línea de sujeto
123456bar
Después de coincidir con todos los 6 dígitos y luego fallar al coincidir con "foo",
la acción normal del coincidente es intentar nuevamente con solo 5
dígitos coincidiendo con el elemento \d+, y luego con 4, y así sucesivamente,
antes de fallar finalmente. Los subpatrones de una sola vez proporcionan el medio para especificar que una vez que una parte del patrón ha coincidido, no debe ser revaluada de esta manera, por lo que el coincidente se rendiría inmediatamente al fallar en coincidir con "foo"
la primera vez. La notación es otro tipo de paréntesis especial, comenzando con (?> como en este ejemplo:
(?>\d+)bar
Este tipo de paréntesis "bloquea" la parte del patrón que contiene una vez que ha coincidido, y un fallo más adelante en el patrón se impide retroceder hacia él.
Retroceder más allá de él, sin embargo, funciona como de costumbre.
Una descripción alternativa es que un subpatrón de este tipo
coincide con la cadena de caracteres que un patrón independiente idéntico coincidiría, si se anclara en el punto
actual en la cadena de sujeto.
Los subpatrones de una sola vez no son subpatrones de captura. Casos simples como el ejemplo anterior pueden pensarse como un repetidor maximizador que debe tragarse todo lo que pueda. Por lo tanto,
mientras que tanto \d+ como \d+? están dispuestos a ajustar el número de dígitos que coinciden para hacer que el resto del patrón coincida, (?>\d+) solo puede coincidir con una secuencia completa de dígitos.
Esta construcción puede contener, por supuesto, subpatrones arbitrariamente complejos, y puede anidarse.
Los subpatrones de una sola vez pueden usarse en conjunto con afirmaciones de retroceso para especificar coincidencias eficientes al final de la cadena de sujeto. Considere un patrón simple como
abcd$
cuando se aplica a una cadena larga que no coincide. Dado que la coincidencia procede de izquierda a derecha, PCRE buscará cada "a" en el sujeto y luego verá si lo que sigue coincide con el resto del patrón. Si el patrón se especifica como
^.*abcd$
entonces el .* inicial coincide con toda la cadena al principio, pero
cuando esto falla (porque no hay un "a" siguiente), retrocede para coincidir con todo excepto el último carácter, luego todo excepto los dos últimos caracteres, y así sucesivamente. Una vez más, la búsqueda de "a" cubre toda la cadena, de derecha a izquierda, por lo que no estamos mejor. Sin embargo, si el patrón se escribe como
^(?>.*)(?<=abcd)
entonces no puede haber retroceso para el elemento .*; solo puede coincidir con toda la cadena. La afirmación de retroceso posterior realiza una sola prueba en los últimos cuatro caracteres. Si falla, la coincidencia falla inmediatamente. Para cadenas largas,
este enfoque hace una diferencia significativa en el tiempo de procesamiento.
Cuando un patrón contiene una repetición ilimitada dentro de un subpatrón que a su vez puede repetirse un número ilimitado de veces, el uso de un subpatrón de una sola vez es la única manera de evitar que algunas coincidencias fallidas tomen un tiempo muy largo. El patrón
(\D+|<\d+>)*[!?]
coincide con un número ilimitado de subcadenas que consisten en no dígitos, o dígitos encerrados en <>, seguido de
! o ?. Cuando coincide, funciona rápidamente. Sin embargo, si se aplica a
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
tarda mucho tiempo en informar el fallo. Esto se debe a
que la cadena puede dividirse entre las dos repeticiones de muchas maneras, y todas tienen que ser probadas antes de que se pueda informar el fallo. (El ejemplo usó [!?] en lugar de un solo carácter al final,
porque tanto PCRE como Perl tienen una optimización que permite un fallo rápido cuando se usa un solo carácter. Recuerdan el último carácter único que se requiere para una coincidencia, y fallan temprano si no está presente en la cadena.)
Si el patrón se cambia a
((?>\D+)|<\d+>)*[!?]
las secuencias de no dígitos no pueden romperse, y el fallo ocurre rápidamente.