Предотвращение зацикливания при поиске и замене. Якорь \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 закрылся