Может быть проще разбить его на две задачи: найти часть строки, которая не является комментарием, а затем выполнить подстановку в этой части.Вот один из подходов к этому:
use strict;
use warnings;
my $str = 'One dog ate a rat but 5\% of dogs ate the apple % dog??';
if (my ($first, $second) = $str =~ m/\A(.*?)((?<!\\)%.*)?\z/s) {
$first =~ s/dog/CAT/g;
$str = defined $second ? "$first$second" : $first;
}
При этом используется отрицательный lookbehind , чтобы найти первый неэкранированный знак процента, даже если это первый символ строки, и делает комментарий наполовину необязательнымтак что он все равно заменит, если нет комментариев.Однако это все равно потребует большого количества возвратов , поэтому, если производительность вызывает беспокойство, может быть предпочтительнее более обширная реализация.делать что-то регулярное выражение не очень хорошо.Вы хотите найти вещи в строке на основе контекстного состояния.«Лучший» способ сделать это - разобрать строку в токены, что обычно делается с помощью цикла, который сохраняет состояние и регулярное выражение (что хорошо в этой части);даже если это просто токены «строки без комментариев», «начала комментария», «строки комментария».Тогда вы можете легко оперировать только строками без комментариев.
Вот как может выглядеть расширенный алгоритм, я попытался упростить его до объема синтаксического анализа, необходимого для этого случая, и он, безусловно, может быть использован в дальнейшем.Ключ должен использовать m/\G.../g
для поэтапного анализа строки (\G
привязывает совпадение к концу последнего совпадения с модификатором /g
в скалярном контексте) и полагаться на механизм регулярных выражений, выбирающий первый вариант чередования, которыйсоответствует этой точке в строке.Таким образом, вы последовательно проходите строку без обратного отслеживания и сохраняете состояние вне цикла.
use strict;
use warnings;
my $str = 'One dog ate a rat but 5\% of dogs ate the apple % dog??';
my $in_comment;
my ($text, $comment) = ('','');
while ($str =~ m/\G(((?<!\\)%)|%|[^%]+)/g) {
my ($token, $start_comment) = ($1, $2);
$in_comment = 1 if defined $start_comment;
if ($in_comment) {
$comment .= $token;
} else {
$text .= $token;
}
}
$text =~ s/dog/CAT/g;
$str = "$text$comment";
Вот другой подход токенизации, который позволяет обрабатывать экранированные обратные слэши, если это разрешено, путем отслеживанияо том, экранируется ли следующий токен:
my $escaping;
while ($str =~ m/\G((\\+)|(%)|[^\\%]+)/g) {
my ($token, $backslashes, $percent) = ($1, $2, $3);
$in_comment = 1 if defined $percent and !$escaping;
$escaping = (defined $backslashes and length($backslashes) % 2) ? 1 : 0;
Parser :: MGC является абстракцией этой концепции для интерфейса объекта.
(Также: этот методне всегда будет быстрее, чем одно регулярное выражение, особенно с более простым анализом и более короткими строками.)