Опубликован: 18.06.2007 | Доступ: свободный | Студентов: 1354 / 35 | Оценка: 4.14 / 3.29 | Длительность: 12:44:00
ISBN: 978-5-94774-604-4
Лекция 3:

Дополнительные конструкции в регулярных выражениях

< Лекция 2 || Лекция 3: 12345 || Лекция 4 >

3.4. Ретроспективная проверка

Имеется аналогичная возможность также "заглянуть назад":

(?<= шаблон )

Это условие истинно, если перед текущей позицией имеется текст, совпадающий с шаблоном. Но не надо думать, что проверка здесь идет справа налево. Например:

$_='abcd';
print "1\n" if /ab(?<=(ab))/;
print $1;

В результате получим вывод

1
ab

Как видим, захват текста в этих якорях также происходит.

Негативная ретроспективная проверка задается выражением

(?<! шаблон )

и требует, чтобы непосредственно перед текущей позицией не было текста, соответствующего данному шаблону.

Но в ретроспективных проверках шаблон уже не может быть каким угодно, а должен соответствовать только определенному числу символов. Механизм поиска должен по этому шаблону определить длину текста, которому он может соответствовать, и искать левее текущей позиции на число символов, с которыми этот шаблон может совпасть.

Иначе пришлось бы искать с начала всего текста, а это большие непроизводительные затраты времени. Поэтому шаблон не может содержать квантификаторы переменной длины, а все альтернативы в альтернативных конструкциях должны совпадать с текстом одной и той же длины. Вот примеры ошибок:

(?<=\w+)
(?<=\w{1,2})
(?<=\w{3}|abcd)

В первых двух случаях имеем переменную длину квантификатора, а в последнем - разную длину альтернатив.

В качестве примера использования этих якорей рассмотрим задачу разделения разрядов числа запятыми. Большие числа удобнее воспринимать, когда они поделены запятыми по три разряда: 12,345,678. Хотя тройки разрядов отсчитываются справа, а механизм поиска просматривает текст слева, эту техническую сложность нетрудно обойти. Сформулируем условие вставки запятой так: запятая вставляется в позицию, слева от которой имеется цифра, а справа - произвольное ненулевое число групп из трех цифр и далее не стоит цифра. Вот программа, которая моделирует этот пример:

$_='number 1: 12345678 number 2: 98765432154321';
s/(?<=\d)(?=(?:\d{3})+(?!\d))/,/g;
print $_;

На печать выведется

number 1: 12,345,678 number 2: 98,765,432,154,321

Вторая проверка здесь сложная: она включает в себя еще одну проверку того, что дальше не стоит цифра. Модификатор g обеспечивает расстановку запятых по всему тексту в нужных местах.

Если бы мы вынесли за скобку проверку (?!\d) и написали бы так:

s/(?<=\d)(?=(?:\d{3})+)(?!\d)/,/g;

то это было бы неправильно. Смысл получился бы таким: позиция, перед которой стоит цифра, за которой идет группа из троек цифр и за которой нет цифры. Этот набор условий никогда не выполняется.

Благодаря тому, что позиционные проверки не поглощают текст, для данной позиции текста можно записать сколько угодно таких проверок. Все они должны выполняться, иначе произойдет локальная неудача поиска. Например:

/(?=\d)(?![09])/;

означает, что в этой позиции должна стоять цифра и она не должна быть равна нулю и девяти. Это равносильно условию

/(?=[1-8])/;

Ко всему сказанному добавлю, что к позиционным проверкам, как и вообще к условиям с нулевой длиной совпадения, нельзя ставить квантификаторы.

3.5. Встроенный код Perl

Встроенный код Perl является условием, которое всегда выполняется. Когда такой код попадается при движении в шаблоне слева направо, он выполняется. В этом коде можно проделать полезную работу, например, запомнить промежуточные значения нумерованных переменных, ведь позже поиск может завершиться неудачей. При прохождении встроенного кода в обратном направлении в случае возврата тоже могут происходить интересные и тонкие действия, которые мы рассмотрим в соответствующих лекциях. Но не вставляйте в этот код операторы перехода next, last, break, redo, goto и вызовы подпрограмм - это не предусмотрено правилами, хотя транслятор за этим не следит. Не следут также использовать во встроенном коде операторы, в которые входят регулярные выражения, - я не слышал, чтобы кто-то гарантировал правильность работы таких нестандартных приемов. Если вы хотите выйти из регулярного выражения при каком-то условии, то надо воспользоваться возможностями, которые даются регулярными выражениями. Иначе интерпретатор Perl может зависнуть, ведь перед выполнением оператора с регулярным выражением он выделяет память, инициализирует структуры данных. Эти действия корректно завершаются только при нормальном завершении оператора.

Также не пытайтесь изменять во встроенном коде результирующий текст - это не даст эффекта.

Иногда вы можете обнаружить, что код Perl должен был бы выполниться, но почему-то не выполняется. Здесь причина в том, что Perl имеет много оптимизаций при поиске по регулярному выражению. Если по каким-то причинам механизм поиска решит, что совпадения быть не может, он не будет пытаться делать этот поиск. Вот здесь поиск вообще не начнется:

'abc' =~ /(?{ print '123' })abcd/;

Система поиска определит, что для совпадения длина результирующего текста должна быть не меньше четырех символов, а их в тексте только три. Оператор закончится неудачно без применения поиска по регулярному выражению, и напечатано ничего не будет.

Мы уже знаем, как выглядит конструкция вставки кода Perl:

(?{ код Perl })

Подробнее о встроенном коде мы поговорим в дальнейшем, когда наберемся опыта.

< Лекция 2 || Лекция 3: 12345 || Лекция 4 >
Сергей Войко
Сергей Войко
Россия, г. Кемерово