Программирование > Регулярные выражения
Объясните работу re
Serg31416:
print "Match: \\$1=$1" if \'abcde\' =~ /^((\\w\\w+)(?{print defined $2 ? "-$2-\\n" : "--\\n"})){2}$/;
Выводит:
-abcde-
--
--
-de-
Match: $1=de
Объясните, почему 2 раза $2 оказался undefined, ведь после выхода за 2-ю захватывающую скобку в $2 должно быть минимум 2 символа.
Сергей
arto:
сравните:
# perl -le \'print "Match: \\$1=$1" if "abcde" =~ /^(?:(\\w\\w+)(?{print defined $2 ? "-$2-" : "--"})){2}$/g\'
--
--
--
--
Match: $1=de
Serg31416:
Там немного не так надо было выводить, а то не видно различий между undefined и null. Вот минимальный пример:
#!perl -w
use strict;
#use re qw(debug);
print "Match: \\$1=$1 \\$2=$2" if \'ab\' =~
/^((\\w+)
(?{print defined $2 ? "\\$2=$2\\n" : "\\$2 not defined\\n"})
){2}$
/x;
Выводит:
$2=ab
$2 not defined
$2=b
Match: $1=b $2=b
Если включить use re qw(debug), то видно, что 2-я пара скобок открывается и закрывается перед буквой b. В этом случае должны создаваться переменные $2, $+, $^N и обр. ссылка \\2 с определёнными значениями, а они все неопределены. Я написал на ...@perl.org, пусть разъяснят.
Хм, вроде я понял, ошибок тут, похоже, нет, зря гуру беспокоил...
Undefined выводится, когда предыдущий захват 2-й парой скобок откачен, а нового захвата ещё не было. Хотя в re мы находимся за 2-й парой скобок. Вот так ставить квантификаторы к захватывающим скобкам... ;-\\
===
Я ещё поумал и вижу, что я ошибался: когда \\w+ отдаёт символ b, то мы
не заходим левее открывающей скобки в выражении (\\w+), поэтому $2 остаётся неопределённым, что и печатается.
===
Опа, я через неск. часов после этого опять раза 2 подумал и пришёл к выводу, что $2=undefined интуитивно должно быть ошибочно, правильно всё-таки выводить $2=a, как я раньше писал.
Проверил почту - вот это да, пришёл ответ от гуру рег. выражений Дж. Фридла, который живёт на regex.info. (В сети есть 3-е издание его книги "Регулярные выражения"). Он написал:
Hi Serge,
I\'ve been thinking about this for a while, and as far as I can tell it does seem
to be a bug. By definition, $2 must be defined before the (?{...}) can run.
It\'s probably a problem with how it backtracks. I\'d suggest filing a bug report..
Поначалу я раздвоил это выражение ввиду квантификатора {2}, получилось такое:
((\\w+)(?{print...}))((\\w+)(?{print...}))
(Здесь имеем в виду, что вторая копия также производит $1 и $2). Но так рассуждать неправильно, реально ничего не раздваивается.
Вот почему выводится $2=undefined, по моему мнению:
вначале (\\w+) захватывает всё и выводится, что $2=ab.
Далее выходим на модификатор {2}. Текущая позиция отмечена через |:
(\\w+)) | {2}
Видим, что повтор \\w не совпадает. Делаем бэктрекинг и по пути входим справа за закрывающую скобку:
(\\w+ | )
Видимо, в этом случае движок делает $2 undefined, а почему? Интуитивно кажется, что это надо делать только, когда мы выходим левее соответствующей открывающей скобки.
Но я сомневаюсь, что авторы движка станут это исправлять: это вопрос идеологии работы движка, и ведь тогда теоретически некоторые старые программы могут начать не так работать...
Serg31416:
Извините, почему-то исправляется не последнее, а предпоследнее моё сообщение, а потом форум не даёт его редактировать, поэтому оно раздвоилось.
========================================
Я над этим серьёзно ещё не думал, а сейчас подумал раза 2 и считаю, что это всё-таки ошибка. Поспешил я со 2-м письмом в perl.org, ладно, посмотрим, что завтра-послезавтра оттуда ответят.
Вот мой разбор работы этого re, как я понимаю этот механизм.
Итак, имеем минимальный пример с подобной ошибкой:
\'ab\' =~ /((\\w+)(?{print defined $2 ? "\\$2=$2\\n" : "\\$2 not defined\\n"})){2}/;
Оно выводит:
$2=ab
$2 not defined
$2=b
Проблема: почему $2 not defined? Ведь мы визуально только что перед этим выводом закрыли 2-ю скобку и должны получить $2 (а также $+, $^N и \\2 определёнными, т.к. 2-я пара скобок только что участвовала в совпадении. А они все неопределённые. Что-то не то...
Я подозреваю, что возможная ошибка возникла от того, что нумерованные переменные (и вообще спец. переменные re) локализуются не только при входе в каждый блок программы (т.е. имеют структуру стека)), но и внутри re, а это неправильно.
При об-ке этого re оно должно как бы раскручиваться: аналогично тому, как \\w{2} эквивалентно \\w\\w, так и всё re эквивалентно
((\\w+)({print ...})((\\w+)({print ...})
Но при этом во 2-м экз. скобки нумеруются тоже 1 и 2 и порождают $1 и $2.
Вначале 1-е \\w+ захватит всё ab и на печать выйдет $2=ab.
Затем входим во 2-й экз. re и когда входим во 2-1 экз. 2-й скобки, $2 получает значение undefined. Видим, что \\w не совпадает. Не беда, у нас есть сохр. состояние, возвращаемся к 1-му экз. 2-й скобки. Но при возврате мы входим справа в 1-й экз. 2-й пары скобок, поэтому $2 опять получает значение undefined, которое наслаивается на предыдущее, а это неправильно. Квантификатор + отдаёт 1 букву, мы захватываем 1-м экз. 2-й пары скобок символ a, затем выходим за скобку. Тут надо сформировать $2 и т.д., но, видимо, здесь механизм об-ки re видит, что $2 уже имеет значение, поэтому вместо этого отменяется предыдущее значение undefined для $2, в результате $2 опять получает значение undefined, что и выводится, а надо вывести a.
Вот так мне кажется...
===
Я ещё поумал и вижу, что я ошибался: когда \\w+ отдаёт символ b, то мы
не заходим левее открывающей скобки в выражении (\\w+), поэтому $2 остаётся неопределённым, что и печатается.
===
Опа, я через неск. часов после этого опять раза 2 подумал и пришёл к выводу, что $2=undefined интуитивно должно быть ошибочно, правильно всё-таки выводить $2=a, как я раньше писал.
Проверил почту - вот это да, пришёл ответ от гуру рег. выражений Дж. Фридла, который живёт на regex.info. (В сети есть 3-е издание его книги "Регулярные выражения"). Он написал:
Hi Serge,
I\'ve been thinking about this for a while, and as far as I can tell it does seem
to be a bug. By definition, $2 must be defined before the (?{...}) can run.
It\'s probably a problem with how it backtracks. I\'d suggest filing a bug report..
Поначалу я раздвоил это выражение ввиду квантификатора {2}, получилось такое:
((\\w+)(?{print...}))((\\w+)(?{print...}))
(Здесь имеем в виду, что вторая копия также производит $1 и $2). Но так рассуждать неправильно, реально ничего не раздваивается.
Вот почему выводится $2=undefined, по моему мнению:
вначале (\\w+) захватывает всё и выводится, что $2=ab.
Далее выходим на модификатор {2}. Текущая позиция отмечена через |:
(\\w+)) | {2}
Видим, что повтор \\w не совпадает. Делаем бэктрекинг и по пути входим справа за закрывающую скобку:
(\\w+ | )
Видимо, в этом случае движок делает $2 undefined, а почему? Интуитивно кажется, что это надо делать только, когда мы выходим левее соответствующей открывающей скобки.
Но я сомневаюсь, что авторы движка станут это исправлять: это вопрос идеологии работы движка, и ведь тогда теоретически некоторые старые программы могут начать не так работать...
Serg31416:
Народ, который занимается разработкой Перла, окончательно признал, что "This *is* a bug. An optimization bug probably. In the PLUS regop." И вот интересный пример такого человека, который написал вышеприведённые слова: заменим \\w+ на (?:\\w|z)+ и потом на (?:\\w|zz)+, получим разный вывод:
\'ab\' =~ /(((?:\\w|z)+)(?{print defined $2 ? "\\$2=$2\\n" : "\\$2 not defined\\n"})){2}/;
$2=ab
$2 not defined
$2=b
\'ab\' =~ /(((?:\\w|zz)+)(?{print defined $2 ? "\\$2=$2\\n" : "\\$2 not defined\\n"})){2}/;
$2=ab
$2=a
$2=b
Кстати, я на днях засабмитил ещё один багрепорт, связанный с неправильной работой операторов \\L, \\l, \\U и \\u. Эти операторы в отличие от функций lc, lcfirst, uc и ucfirst выполняются слева направо:
print "\\L\\udD\\n"; # Dd верно
print "\\LdD\\udD\\n"; # dddd уже неверно!
А если рядом поставить \\U и \\L (4 комбинации, то вообще будет кошмар: ошибка синтаксиса:
print "\\U\\La\\n";
Навигация
Перейти к полной версии