Механизм работы регулярных выражений. Поиск с возвратами
Вы можете распечатывать отладочную информацию и наблюдать процесс возвратов и итераций. Для этого надо вставить внутрь регулярного выражения код Perl, который будет распечатывать текущую позицию в тексте всегда, когда этот код будет попадаться при продвижении текущей позиции по регулярному выражению слева направо. О вставке кода и функции pos, возвращающей текущую позицию поиска, мы поговорим в дальнейших лекциях, а сейчас рассмотрим пример из главы 3, но в свободном форматировании с модификатором x, о котором речь пойдет также в начале этой главы:
#!/usr/bin/perl -w use strict; use re 'eval'; $_='abcd'; m!(?{ print "Starting from ".pos($_)."\n" }) (\w (?{ print pos($_)."\n$1\n" }) )*!x;
Если убрать код Perl, то оператор поиска будет выглядеть так:
m!(\w)*!x;
На печать выведется следующее:
Starting from 0 1 Use of uninitialized value in concatenation (.) or string at (re_eval 2) line 1. 2 a 3 b 4 c
Конструкция
(?{ код Perl })
вставляет код Perl в шаблон. Иногда при вставке кода в регулярное выражение транслятор выдает ошибку
Eval-group not allowed at runtime, use re 'eval' in regex m/…
Чтобы избежать этой ошибки, нужно директивой
use re 'eval';
разрешить вставку кода Perl внутрь шаблонов.
Этот код у нас распечатывает текущую позицию поиска pos в строке $_ (позиция отсчитывается от нуля) и значение переменной $1.
Вначале мы видим строку
Starting from 0
Она говорит о старте очередной итерации поиска. Эта итерация в нашем примере единственная. Далее происходит сопоставление \w и символа a, а затем - печать текущей позиции поиска (2) и значения переменной $1. Но т.к. эта печать происходит еще до закрытия захватывающей скобки, когда переменная $1 еще не появилась, то возникает предупреждающее сообщение об использовании неопределенной переменной. Конечно, его можно было бы избежать, если написать
print "$1\n" if defined $1;
Но я не стал излишне загромождать код.
Затем скобки захватывают символ a, и переменная $1 приобретает значение $1='a', а квантификатор * заставляет вернуться за закрывающую скобку и повторить поиск, начиная от \w. Теперь \w соответствует символу b, а распечатывается текущая позиция 2 и текущее значение $1, которое равно a. Затем повторяется тот же самый цикл, который печатает очередные текущие позиции и захваченные символы. После того, как подшаблон \w совпадает с d, печатается последняя позиция этой строки 4 и текущее значение $1, которое равно c. Значение d не вышло на печать, т.к. печать происходит до закрытия захватывающей скобки. А если бы мы вставили печать после нее или следующим оператором за оператором поиска, то увидели бы и значение d.
С помощью такого диагностического вывода можно отследить, как выполняется поиск. Иногда вывод показывает слишком большое число итераций и/или возвратов, которые надо оптимизировать.