Предотвращение зацикливания при поиске и замене. Якорь \G, итеративный поиск с модификаторами g и gc
6.3 Лексический анализ текста с помощью якоря \G и модификатора gc
С помощью конструкции /\G шаблон /gc можно делать лексический анализ текста, производя извлечение из него нужных фрагментов один за другим, без пропусков. Это учебный пример, который показывает принцип разбора:
my $text='123 234 345 456';
while ($text =~ /\d+/g) { print "Найдено число $&\npos=".pos($text)."\n" }На печати увидим:
Найдено число 123 pos=3 Найдено число 234 pos=7 Найдено число 345 pos=11 Найдено число 456 pos=15
Отпечатаются позиции за каждым найденным числом.
Предположим, что мы делаем разбор текста на имена, которые состоят из латинских букв, чисел и символов перевода строк. Мы должны последовательно извлекать из текста эти объекты, называть и выводить их. Если встречается что-то, что не соответствует синтаксису ни одного объекта, то это будет называться unknown (неизвестно). Регулярное выражение будет начинаться с \G и представлять собой высокоуровневую конструкцию выбора:
$_=<<EOF;
123 abc
-234 def
\$\@ xyz
EOF
while (/\G
(?:(?>[\000-\011\013-\040]+)| # все от \0 до пробела кроме \n
(\b(?>[a-zA-Z]+)\b)(?{ print "Name: $^N\n" })| # имя
((?>[+-]?)(?>\d+)\b)(?{ print "Number: $^N\n" })| # число
(\n)(?{ print "Newline:\n" })| # \n
((?>\S+))(?{ print "Unknown: $^N\n" }) # все остальное
)/gx) {}Чтобы отделить первую альтернативу выбора от \G, необходимо всю конструкцию выбора взять в скобки. На печать выйдет
Number: 123 Name: abc Newline: Number: -234 Name: def Newline: Unknown: $@ Name: xyz Newline:
В этом примере обратите внимание на то, что ветка \S+ для unknown поставлена последней, иначе разбор получился бы некорректным.
Не обязательно все шаблоны хранить в одной большой конструкции выбора, можно делать разбор текста по нескольким регулярным выражениям, но тогда надо использовать модификатор gc. Вот упрощенный пример программы проверки кода HTML с проверкой правильности закрытия тега A:
#!/usr/bin/perl -w
use strict;
use bytes;
use locale;
$_=<<EOF;
<html>
<body>
<слово1
слово2 1234>
<a href="http://www.intuit.ru">Это ссылка</a>
</body>
</html>
EOF
my $close_a=0;
while (!/\G\z/gc)
{ if (/\G<(\w+)[^>]*>\s*/gc)
{ print "Тег $^N открылся\n";
if ($^N =~ /^a$/i)
{ print "Ошибка: вложенный тег A\n" if $close_a++;
}
}
elsif (m!\G</(\w+)[^>]*>\s*!gc)
{ print "Тег $^N закрылся\n";
if ($^N =~ /^a$/i)
{ print "Ошибка: нет парного тега A\n" if !$close_a--;
}
}
elsif (/\G(\w+)\s*/gc)
{ print "Найдено слово/число $^N\n";
}
elsif (/\G(&#?\w+;)\s*/gc)
{ print "Найдена подстановка $^N\n";
}
# Пропускаем все кроме тегов, слов и подстановок
elsif (/\G[^<>&\w]+/gc)
{
}
else
# Нашли ошибку, сделаем сообщение об этом
{ my $offset=pos($_);
my ($error)=$_ =~ /\G.{1,10}/gs;
die "Непонятные символы\n$error\nв позиции $offset\n";
}
}
print "Имеется незакрытый тег A\n" if $close_a;Эта программа напечатает следующее:
Тег html открылся Тег body открылся Найдена подстановка < Найдено слово/число слово1 Найдено слово/число слово2 Найдено слово/число 1234 Найдена подстановка > Тег a открылся Найдено слово/число Это Найдено слово/число ссылка Тег a закрылся Тег body закрылся Тег html закрылся