Sub-patrones de una sóla aplicación
Con las repeticiones maximizadoras y minimizadoras, el fallo de
lo que se encuentra a continuación causa normalmente que la repetición del elemento sea
re-evaluada para ver si un número diferente de repeticiones permite que el
resto del patrón coincida. A veces es útil prevenir
esto, tanto para cambiar la naturaleza de la comparación, como
para ocasionar que falle antes (ya que de otra manera ocurriría el fallo después)
cuando el autor del patrón sabe que no tiene sentido seguir.
Considere, por ejemplo, el patrón \d+foo cuando se aplica a
la línea objetivo
123456bar
Después de coincidir los 6 dígitos y luego fallar al coincidir con "foo",
la acción habitual del comparador es volver a intentarlo con sólo 5
dígitos, coincidiendo con el elemento \d+, y después con 4, y así sucesivamente,
antes de que, por último, falle. Los sub-patrones de una sóla aplicación proporcionan el
medio para especificar que una vez que una porción del patrón ha
coincidido, no será re-evaluado de esta forma, por lo que el
comparador se rendirá inmediatamente al fallar la comparación de "foo"
la primera vez. La notación es otro tipo de paréntesis
especiales, comenzando con (?> como en este ejemplo:
(?>\d+)bar
Este tipo de paréntesis "bloquea" la parte del patrón
que lo contiene una vez haya coincidido, y un fallo dentro
del patrón evita que éste retroceda sobre sí mismo.
El retroceso hacia elementos previos al sub-patrón funciona normalmente, después de todo.
Una descripción alternativa es que un sub-patrón de este tipo
coincide con la cadena de caracteres que coincidiría con un patrón
idéntico independiente, si está anclado en el punto actual
de la cadena objetivo.
Los sub-patrones de una sóla aplicación no son sub-patrones de captura. Los casos
sencillos como el anterior se pueden tomar como una repetición
maximizadora que debe tragar todo lo que pueda. Así,
mientras que \d+ y \d+? están preparados para adaptarse al número de
dígitos que comparan para hacer que el resto del patrón coincida,
(?>\d+) sólo puede conincidir una secuencia completa de dígitos.
Esta construcción puede, por supuesto, contener arbitrariamente sub-patrones
complicados, y pueden estar anidados.
Los sub-patrones de una sóla aplicación se pueden usar junto con
declaraciones de búsqueda hacia atrás para especificar una comparación eficiente
al final de la cadena objetivo. Considere un simple patrón como
abcd$
cuando se aplica a una cadena larga la cual no coincide. Ya que
la comparación se lleva a cabo de izquierda a derecha, PCRE buscará
cada "a" en el sujeto y después mirará si lo siguiente coincide con el
resto del patrón. Si el patrón se especifica así
^.*abcd$
el .* inicial coincide con la cadena entera primero, pero
cuando esto falla (debido a que no hay una "a" siguiente),
retrocede para comparar todos los caracteres excepto el último, después todos
excepto los dos últimos, y así sucesivamente. Una vez más la búsqueda
de "a" cubre la cadena entera, de derecha a izquierda, por lo que
no estamos en mejores circunstancias. Sin embargo, si el patrón se escribe así
^(?>.*)(?<=abcd)
entonces no puede haber retroceso para el elemento .* ; sólo puede
coincidir con la cadena entera. La subsiguiente declaración de búsqueda
hacia atrás realiza una única comprobación sobre los últimos cuatro caracteres. Si
falla, la comparación falla inmediatamente. Para cadenas largas,
este enfoque produce una diferencia significativa en el tiempo de procesamiento.
Cuando un patrón contiene una repetición ilimitada dentro de un sub-patrón
que puede repetirse un número ilimitado de veces,
el uso de un sub-patrón de una sóla aplicación es la única manera de
evitar que algunas comparaciones fallidas tomen mucho tiempo.
El patrón
(\D+|<\d+>)*[!?]
compara un número ilimitado de subcadenas que consisten
en algo que no sea un dígito, o dígitos encerrados entre <>, seguido de
! o ?. Cuando coincide, se ejecuta rápidamente. Sin embargo, si
se aplica a
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
toma mucho tiempo antes de informar del fallo. Esto es
porque la cadena puede ser dividida entre las dos repeticiones de
muchas maneras, y todo tiene que ser probado. (En el ejemplo
se usó [!?] en vez de un carácter único al final,
ya que PCRE y Perl están optimizados de forma que permiten
que se falle rápidamente cuando se usa un carácter único. Ambos
recuerdan el último carácter único que se requiere para una
coincidencia, y falla antes si no está presente en la cadena.)
Si el patrón se cambia a
((?>\D+)|<\d+>)*[!?]
las secuencias de los "no-dígitos" no pueden romperse, y el fallo ocurre rápidamente.