Дополнительные конструкции в регулярных выражениях
3.3. Опережающая проверка
Это сложное условие ( мнимый символ, якорь ). Эту проверку еще называют "заглядыванием вперед". Вводится она конструкцией
(?= шаблон )
Шаблон может быть как угодно сложным. Это условие истинно, если в текущей позиции находится текст, совпадающий с заданным шаблоном. Обратите внимание, что эти условные конструкции совпадают не с текстом, а с позицией в тексте подобно своим более простым собратьям \b, \A, $ и т.д.
Замечу, что атомарная группировка имитируется позитивной опережающей проверкой и обратной ссылкой:
(?> шаблон ) эквивалентно (?=( шаблон ))\1
Происходит это потому, что после выполнения опережающей (как и ретроспективной) проверки уничтожаются все сохраненные состояния, возникшие внутри нее.
Имеется противоположный случай проверки - негативная опережающая проверка:
(?! шаблон )
Такое условие истинно, если в текущей позиции нет текста, совпадающего с заданным шаблоном.
Например, шаблон
\w+(?=\s+)
найдет слово, за которым стоит один или несколько пробельных символов, но сами пробельные символы в результат поиска не войдут. Дело в том, что позиционная проверка не поглощает текст: после применения этих условных конструкций текущая позиция в тексте вернется на прежнее место, где была до применения этого якоря. Интересно, что захватывающие скобки внутри этих проверок захватывают текст, о чем забывают упомянуть авторы книг по Perl.
Рассмотрим такой пример:
$_='abc'; print "1\n" if /a(?=(b))bc/; print $1;
Будут напечатаны такие строки:
1 b
Как видим, после совпадения с символом a после него ищется символ b. Он находится, и поиск продолжается дальше. Если сразу бы после a не стоял символ b, то поиск потерпел бы неудачу и началась бы следующая итерация, т.к. сохраненных состояний, к которым можно вернуться, нет. Внутри опережающей проверки этот символ захватывается в переменную $1, после чего проверка заканчивается успешно, и текущая позиция в строке возвращается к символу b. Затем выполняется соответствие подшаблона bc символам bc, на этом оператор поиска успешно завершается.
Подобные сложные проверки бывают необходимы в сложных случаях, когда надо принять решение в зависимости от того, какой фрагмент текста находится в текущей позиции (или вообще впереди нее). Эти условия обычно используются в условных конструкциях, о которых речь впереди.
Обратите внимание на интересную особенность сложных условий: в результате работы оператора
"a" =~ /(?=(a))/
переменная $1 получит значение a, а в результате работы похожего регулярного выражения
"a" =~ /((?=a))/
переменная $1 получит пустое значение. Это можно объяснить тем, что в первом случае внутри сложного условия происходит захват буквы a, а затем после выхода за это сложное условие (на конец регулярного выражения ) текущая позиция в шаблоне возвращается в точку перед буквой a, но сама буква уже сидит в переменной $1. Во втором случае после выполнения фрагмента шаблона ((?=a текущая позиция в шаблоне передвигается за букву a, но после закрытия скобки в сложном условии ((?=a) происходит возврат текущей позиции в шаблоне перед буквой а, и после закрытия захватывающей скобки эта текущая позиция остается перед буквой a, как и в момент открытия захватывающей пары скобок. В результата эта пара скобок захватывает пустой фрагмент текста.