Регулярное выражение Perl недостаточно жадное - PullRequest
3 голосов
/ 13 марта 2012

Я пишу регулярное выражение в perl для соответствия коду perl, который запускает определение подпрограммы perl. Вот мое регулярное выражение:

my $regex = '\s*sub\s+([a-zA-Z_]\w*)(\s*#.*\n)*\s*\{';

$ regex соответствует коду, который запускает подпрограмму. Я также пытаюсь записать имя подпрограммы в 1 долл. США и любые пробелы и комментарии между именем подпрограммы и начальной открытой скобкой в ​​2 долл. США. Это 2 доллара, которые доставляют мне проблемы.

Рассмотрим следующий код Perl:

my $x = 1;

sub zz
# This is comment 1.
# This is comment 2.
# This is comment 3.
{
    $x = 2;
    return;
}

Когда я помещаю этот perl-код в строку и сопоставляю его с $ regex, $ 2 - это "# Это комментарий 3. \ n", а не три строки комментариев, которые я хочу. Я думал, что регулярное выражение жадно поместит все три строки комментариев в $ 2, но, похоже, это не так.

Я хотел бы понять, почему $ regex не работает, и разработать простую замену. Как показывает программа ниже, у меня есть более сложная замена ($ re3), которая работает. Но я думаю, что для меня важно понять, почему $ regex не работает.

use strict;
use English;

my $code_string = <<END_CODE;
my \$x = 1;

sub zz
# This is comment 1.
# This is comment 2.
# This is comment 3.
{
    \$x = 2;
    return;
}
END_CODE

my $re1 = '\s*sub\s+([a-zA-Z_]\w*)(\s*#.*\n)*\s*\{';
my $re2 = '\s*sub\s+([a-zA-Z_]\w*)(\s*#.*\n){0,}\s*\{';
my $re3 = '\s*sub\s+([a-zA-Z_]\w*)((\s*#.*\n)+)?\s*\{';

print "\$code_string is '$code_string'\n";
if  ($code_string =~ /$re1/) {print "For '$re1', \$2 is '$2'\n";}
if  ($code_string =~ /$re2/) {print "For '$re2', \$2 is '$2'\n";}
if  ($code_string =~ /$re3/) {print "For '$re3', \$2 is '$2'\n";}
exit 0;

__END__

Вывод приведенного выше сценария perl следующий:

$code_string is 'my $x = 1;

sub zz
# This is comment 1.
# This is comment 2.
# This is comment 3.
{
    $x = 2;
    return;
} # sub zz
'
For '\s*sub\s+([a-zA-Z_]\w*)(\s*#.*\n)*\s*\{', $2 is '# This is comment 3.
'
For '\s*sub\s+([a-zA-Z_]\w*)(\s*#.*\n){0,}\s*\{', $2 is '# This is comment 3.
'
For '\s*sub\s+([a-zA-Z_]\w*)((\s*#.*\n)+)?\s*\{', $2 is '
# This is comment 1.
# This is comment 2.
# This is comment 3.
'

Ответы [ 3 ]

7 голосов
/ 14 марта 2012

Посмотрите на только часть вашего регулярного выражения, которая захватывает $2.Это (\s*#.*\n).Само по себе это может захватить только одну строку комментария.У вас есть звездочка после него, чтобы захватить несколько строк комментариев, и это прекрасно работает.Он захватывает несколько строк комментариев и помещает каждую из них в $2, одну за другой, каждый раз заменяя предыдущее значение $2.Таким образом, окончательное значение $2, когда регулярное выражение выполнено, совпадает с последним , с которым сопоставилась группа захвата, то есть с последней строкой комментария.Только.Чтобы исправить это, вам нужно поместить звездочку в группу захвата.Но затем вам нужно поставить еще один набор скобок (на этот раз без захвата), чтобы убедиться, что звездочка относится ко всему.Поэтому вместо (\s*#.*\n)* вам понадобится ((?:\s*#.*\n)*).

Ваш третий регулярный оператор работает, потому что вы невольно заключили все выражение в скобки, чтобы после него можно было поставить вопросительный знак.Это заставило $2 захватить все комментарии одновременно, а $3 - только последний комментарий.

Когда вы отлаживаете свое регулярное выражение, убедитесь, что вы распечатываете значения все переменных соответствия, которые вы используете: $1, $2, $3 и т. Д. Вывидел бы, что $1 было просто названием подпрограммы, а $2 было только третьим комментарием.Возможно, это заставило вас задуматься о том, как ваше регулярное выражение пропустило первые два комментария, когда между первой и второй группами захвата ничего нет, что в конечном итоге приведет вас к обнаружению того, что происходит, когда группа захвата совпадает несколько раз.

Кстати, похоже, что вы также захватываете любой пробел после имени подпрограммы в $1.Это намеренно? (Ой, я испортил мнемонику и подумал, что \w было "w для пробела".)

4 голосов
/ 13 марта 2012

Если вы добавите повторение в группу захвата, она будет захватывать только финальное совпадение этой группы.Вот почему $regex соответствует только последней строке комментария.

Вот как я бы переписал вам регулярное выражение:

my $regex = '\s*sub\s+([a-zA-Z_]\w*)((?:\s*#.*\n)*)\s*\{';

Это очень похоже на ваш $re3, за исключениемследующие изменения:

  • Часть соответствия пробелов и комментариев теперь находится в группе без захвата
  • Я изменил эту часть регулярного выражения с ((...)+)? на ((...)*), чтоэквивалент.
1 голос
/ 13 марта 2012

Проблема в том, что по умолчанию \n не является частью строки.Регулярное выражение прекращает сопоставление на \n.

. Для многострочных совпадений необходимо использовать модификатор s:

if  ($code_string =~ /$re1/s) {print "For '$re1', \$2 is '$2'\n";}

Обратите внимание на s после регулярного выражения.

...