Однократные подмаски
Как для минимального, так и максимального количества повторений,
если последующая часть шаблона терпит неудачу при сопоставлении,
происходит повторный анализ повторяемого выражения на предмет того,
возможно ли успешное сопоставление всего шаблона при другом количестве
повторений. Бывают случаи, когда необходимо изменить описанную логику
работы для реализации специфического сопоставления либо оптимизации шаблона
(если автор уверен, что других вариантов соответствия нет).
В качестве примера, рассмотрим шаблон \d+foo в применении к строке
123456bar
После того, как \d+ будет сопоставлен с первыми шестью цифрами,
сопоставление "foo" потерпит неудачу. После этого, в соответствие
\d+, будет сопоставлено 5 цифр, после очередной неудачи будет сопоставлено
4 цифры и так далее. В конце концов весь шаблон потерпит неудачу.
Однократные подмаски указывают, что если одна часть шаблона была
сопоставлена, ее не стоит анализировать повторно. Применимо к приведенному
выше примеру весь шаблон потерпел бы неудачу после первого же
неудачного сопоставления с "foo". Записываются однократные шаблоны
при помощи круглых скобок следующим образом: (?>. Например:
(?>\d+)bar
Этот вид подмаски предотвращает повторный ее анализ в случае, если
сопоставление последующих элементов терпят неудачу. Однако, это не мешает
повторно анализировать любые другие элементы, в том числе предшествующие
однократной подмаске.
Говоря другими словами, подмаски такого типа соответствуют
той части подстроки, которой соответствовала бы одиночная
изолированная маска, заякоренная на текущей позиции обрабатываемого
текста.
Однократные подмаски являются незахватывающими. Простые примеры,
подобные приведенному выше, можно охарактеризовать как безусловный
захват максимального количества повторений. В то время как
\d+ и \d+? корректируются так, чтобы остальные части шаблона
так же совпали, (?>\d+) соответствует исключительно максимальной по
длине последовательности цифр, даже если это приводит к неудаче при
сопоставлении других частей шаблона.
Однократные подмаски могут включать в себя более сложные конструкции,
а также могут быть вложенными.
Однократные подмаски могут использоваться совместно с утверждениями
касательно предшествующего текста для описания эффективных сопоставлений
в конце обрабатываемого текста. Рассмотрим простой шаблон
abcd$
в применении к длинному тексту, который не соответствует указанной маске.
Поскольку поиск происходит слева направо, вначале PCRE будет
искать букву "a", и только потом анализировать следующие
записи в шаблоне. В случае, если шаблон указан в виде
^.*abcd$.
В таком случае вначале .* сопоставляется со всей строкой, после
чего сопоставление терпит неудачу (так как нет последующего символа 'a').
После чего .* сопоставляется со всей строкой, кроме последнего символа,
потом кроме двух последних символов, и так далее. В конечном итоге
поиск символа 'a' происходит по всей строке. Однако, если шаблон записать
в виде:
^(?>.*)(?<=abcd)
повторный анализ для .* не выполняется, и, как следствие, может
соответствовать только всей строке целиком. После чего утверждение
проверяет последние четыре символа на совпадение с 'abcd', и в случае
неудачи все сопоставление терпит неудачу. Для больших объемов
обрабатываемого текста этот подход имеет значительный выигрыш во времени
выполнения.
Если шаблон содержит неограниченное повторение внутри подмаски,
которая в свою очередь также может повторяться неограниченное количество
раз, использование однократных подмасок позволяет
избегать многократных неудачных сопоставлений,
которые длятся достаточно продолжительное время. Шаблон
(\D+|<\d+>)*[!?]
соответствует неограниченному количеству подстрок, которые состоят
не из цифр, либо из цифр заключенных в <>, за которыми следует
? либо !. В случае, если в обрабатываемом тексте содержатся
соответствия, время работы регулярного выражения будет невелико.
Но если его применить к строке
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
это займет длительное время. Это связанно с тем, что строка
может быть разделена между двумя частями шаблона многими способами,
и все они будут опробованы (в примере мы использовали [?!], поскольку
в случае одиночного символа в конце шаблона и PCRE и Perl выполняют
оптимизацию. Они запоминают последний одиночный символ и в случае
его отсутствия выдают неудачу). Если изменить шаблон на
((?>\D+)|<\d+>)*[!?],
нецифровые последовательности не могут быть разорваны, и
невозможность сопоставления обнаруживается гораздо быстрее.