Sous-masques uniques
Avec les quantificateurs de répétitions, l'échec
d'une recherche conduit normalement à une autre recherche, avec
un nombre différent de répétitions, pour
voir si le masque ne s'applique pas dans d'autres conditions.
Parfois, il est pratique d'éviter ce comportement, soit
pour changer la nature de la recherche, soit pour la faire abandonner
plus tôt, si on pense qu'il n'est pas besoin d'aller plus loin.
Considérons, par exemple, le masque \d+foo
appliqué à la ligne
123456bar
.
Après avoir tenté d'utiliser les 6 chiffres suivis
de "foo
" qui font échouer, l'action habituelle
sera de réessayer avec 5 chiffres, puis avec 4, et ainsi de
suite jusqu'à l'échec final. Un sous-masque évalué une seule fois
permettrait d'indiquer que lorsqu'une partie du masque est trouvée,
elle n'a pas besoin d'être réévaluée à chaque tentative. Ceci
conduirait à ce que la recherche échoue immédiatement après le
premier test. Ces assertions ont leur propre notation, commençant avec
(?>
comme ceci :
(?>\d+)bar
Ce type de parenthèses verrouille le sous-masque qu'il contient
une fois qu'il a été trouvé, et empêche un
échec ultérieur d'y repasser, mais autorise à
revenir plus loin en arrière.
Une autre description est que les sous-masques de ce type
recherchent les chaînes de caractères, et ancre le sous-masque
à l'intérieur de la chaîne.
Les sous-masques uniques ne sont pas capturants. Des cas simples comme
ceux présentés ci-dessus peuvent être pris comme
des situations maximales, qui réservent le maximum de
caractères. En effet, alors que \d+
et
\d+?
ajustent le nombre de chiffres trouvés
de manière à laisser la possibilité au masque de
réussir, (?>\d+)
ne peut retenir que la
séquence entière de chiffres.
Cette construction peut contenir un nombre arbitraire de sous-masques
complexes, et ils peuvent être imbriqués.
Les sous-masques uniques ne peuvent être utilisés qu'avec
les assertions arrière, pour effectuer une recherche efficace
en fin de chaîne. Considérons un masque simple tel
abcd$
appliqué à une très longue chaîne qui ne lui correspond pas.
À cause du système de recherche de gauche à droite, PCRE va
commencer par rechercher un "a
" dans la
chaîne sujet, puis vérifier si ce qui suit convient au reste
du masque. Si le masque est spécifié sous la forme
^.*abcd$
alors, la séquence .*
remplace en premier
lieu la chaîne entière, et échoue, repart en
arrière, et remplace tous les caractères sauf le dernier,
échoue, retourne en arrière, prend un caractère
de moins, etc. et ainsi de suite. Encore une fois, la recherche du
"a
" passe en revue toute la chaîne de gauche
à droite, ce qui n'est pas très efficace. Par contre,
si le masque était écrit
^(?>.*)(?<=abcd)
alors il n'y aurait pas de retour en arrière, pour satisfaire
la séquence .*
, car elle ne peut que remplacer
toute la chaîne. L'assertion arrière consécutive
va alors faire un test sur les 4 derniers caractères. Si elle
échoue, la recherche est immédiatement interrompue.
Pour les chaînes très longues, cette approche fait la
différence en termes de performances et de temps de recherche.
Lorsqu'un masque contient une répétition illimitée
dans un sous-masque, qui contient lui-même un nombre
illimité de répétiteurs, l'utilisation des
sous-masques à utilisation unique est la seule façon
d'éviter l'échec de la recherche après un
temps de calcul trop long.
Le masque
(\D+|<\d+>)*[!?]
recherche un nombre illimité de sous-chaînes, qui contiennent soit
des non-chiffres, soit des chiffres inclus dans <>, suivi soit
par !
ou par ?
. Lorsqu'il trouve
une solution, ce masque va très vite. Mais, lorsqu'il est
appliqué à une chaîne telle :
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
,
il lui faut beaucoup de temps pour annoncer un échec. Cela est
dû au fait que la chaîne peut être divisée en deux
sous-chaînes d'un grand nombre de façons, et qu'elles
ont toutes été essayées. (Cet exemple utilisait
[!?]
plutôt qu'un caractère simple, car
PCRE et PHP utilise une optimisation qui leur permet de détecter
rapidement l'échec lorsqu'un caractère unique est
trouvé. Il se souvient du dernier caractère qui est
attendu, et s'aperçoit rapidement qu'il n'y a pas ce caractère).
Si le masque utilisé est
((?>\D+)|<\d+>)*[!?]
les séquences de chiffres ne peuvent pas être
trouvées, et l'échec intervient rapidement.