Как пронумеровываются вложенные группы захвата в регулярных выражениях?

85

Существует ли определенное поведение для того, как регулярные выражения должны обрабатывать захват вложенных круглых скобок? Более конкретно, можете ли вы разумно ожидать, что разные движки будут фиксировать внешние скобки в первой позиции и вложенные скобки в последующих позициях?

Рассмотрим следующий код PHP (с использованием регулярных выражений PCRE)

<?php
  $test_string = 'I want to test sub patterns';
  preg_match('{(I (want) (to) test) sub (patterns)}', $test_string, $matches);
  print_r($matches);
?>

Array
(
    [0] => I want to test sub patterns  //entire pattern
    [1] => I want to test           //entire outer parenthesis
    [2] => want             //first inner
    [3] => to               //second inner
    [4] => patterns             //next parentheses set
)

Сначала захватывается все выражение в скобках (я хочу протестировать), а затем следом захватываются внутренние шаблоны в скобках («хочу» и «к»). Это имеет логический смысл, но я мог видеть не менее логичный случай для первого захвата дополнительных скобок, а ЗАТЕМ захват всего шаблона.

Итак, это определенное поведение в механизмах регулярных выражений "сначала захватить все", или оно будет зависеть от контекста шаблона и / или поведения механизма (PCRE отличается от C #, чем отличается от Java, чем и т.д.)?

Алан Сторм
источник
Если вас действительно интересуют все разновидности регулярных выражений, вам нужен тег "языковой независимости". Существует слишком много разновидностей, чтобы перечислить их все, и большинство из них не соответствуют какому-либо реальному стандарту (хотя они удивительно последовательны, когда дело доходит до нумерации групп захвата).
Алан Мур,
Доступ к группе можно получить с помощью $ 1, $ 2, $ 3 и т. Д. Как попасть в 10-ю группу? Это будет 10 долларов? Я не думаю, что 10 долларов будут работать, потому что они будут интерпретированы как 1 доллар, за которым следует 0. Означает ли это, что мы можем иметь максимум 9 групп? Если автор может, пожалуйста, включить это как часть вопроса, тогда это будет единое место, где можно будет узнать все о вложенных группах в регулярных выражениях.
LionHeart

Ответы:

59

Из perlrequick

Если группировки в регулярном выражении являются вложенными, $ 1 получает группу с самой левой открывающей скобкой, $ 2 - следующей открывающей скобкой и т. Д.

Предостережение : исключение открывающей скобки для группы, не связанной с захватом (? =)

Обновить

Я не часто использую PCRE, так как обычно использую настоящий;), но документы PCRE показывают то же, что и Perl:

ПОДРАЗДЕЛЕНИЯ

2.Он устанавливает подшаблон как захватывающий подшаблон. Это означает, что при совпадении всего шаблона та часть строки темы, которая соответствует подшаблону, передается обратно вызывающей стороне через ovectorаргумент pcre_exec(). Открывающие скобки подсчитываются слева направо (начиная с 1), чтобы получить номер для захвата подшаблонов.

Например, если строка «красный король» сопоставлена ​​с шаблоном

the ((red|white) (king|queen))

захваченные подстроки "красный король", "красный" и "король" пронумерованы 1, 2 и 3 соответственно.

Если PCRE отходит от совместимости с регулярными выражениями Perl, возможно, следует переопределить аббревиатуру - «Perl Cognate Regular Expressions», «Perl Comparable Regular Expressions» или что-то в этом роде. Или просто избавьтесь от смысловых букв.

Daotoad
источник
1
@Sinan: он использует PCRE в PHP, который является «Perl-совместимыми регулярными выражениями»; так что это должно быть точно так же, как при использовании Perl напрямую
Паскаль МАРТИН
3
Pascal, PCRE начинался как попытка быть Perl-совместимым набором регулярных выражений, но в последние годы они немного разошлись. По-прежнему очень похожи, но есть небольшие различия в расширенных наборах функций. (Кроме того, в соответствии с вопросом, меня интересуют все платформы)
Алан Сторм,
1
На самом деле, именно Perl в наши дни делает большую часть «отхода», но вы правы: термин «Perl-совместимый» быстро меняется от неправильного названия к несоответствию. : D
Алан Мур
1
@ Алан, Perl определенно в движении. P5.10 изменил несколько вещей, но 6 будет сильно отличаться. Букву P почти наверняка следует интерпретировать как «Perl 5». PCRE - отличный проект, и я не могу его достаточно похвалить, он был находкой для многих проектов.
daotoad
1
Я добавил это под первой цитатой. Предостережение : исключение открывающей скобки для группы, не связанной с захватом (? =). Я не осознавал, что не авторизовался, когда редактировал его. И только когда я добавляю этот комментарий, мне было предложено ввести учетные данные. Итак, теперь для одобрения требуется еще 1 человек!
JGFMK
17

Да, это все довольно хорошо определено для всех интересующих вас языков:

  • Java - http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html#cg
    "Группы захвата нумеруются путем подсчета их открывающих скобок слева направо. ... Группа ноль всегда означает все выражение ".
  • .Net - http://msdn.microsoft.com/en-us/library/bs2twtah(VS.71).aspx
    "Захваты с использованием () нумеруются автоматически в зависимости от порядка открывающих скобок, начиная с единицы. Первый захват, элемент захвата с нулевым номером - это текст, совпадающий со всем шаблоном регулярного выражения. ")
  • PHP (функции PCRE) - http://www.php.net/manual/en/function.preg-replace.php#function.preg-replace.parameters
    "\ 0 или $ 0 относится к тексту, совпадающему со всем шаблоном. Открывающие скобки подсчитываются слева направо (начиная с 1), чтобы получить номер подшаблона захвата ". (То же самое было и с устаревшими функциями POSIX)
  • PCRE - http://www.pcre.org/pcre.txt
    Чтобы добавить к сказанному Аланом М., найдите «Как pcre_exec () возвращает захваченные подстроки» и прочтите пятый абзац, который следует ниже:

    Первая пара целых чисел, ovector [0] и ovector [1], идентифицирует
    часть строки темы, соответствующая всему шаблону. Следующий
    пара используется для первого подшаблона захвата и так далее. Значение
    возвращается pcre_exec (), это на единицу больше, чем пара с самым большим номером, которая
    был установлен. Например, если были захвачены две подстроки,
    возвращаемое значение - 3. Если нет захватывающих подшаблонов, возврат
    значение успешного совпадения равно 1, что указывает на то, что только первая пара
    офсетов.
    
  • Другое дело Perl - http://perldoc.perl.org/perlre.html#Capture-buffers
    $ 1, $ 2 и т. Д. Соответствуют группам захвата, как и следовало ожидать (т.е. по появлению открывающей скобки), однако $ 0 возвращает имя программы, а не вся строка запроса - чтобы получить вместо этого $ &.

Скорее всего, вы найдете аналогичные результаты для других языков (Python, Ruby и других).

Вы говорите, что столь же логично сначала перечислить внутренние группы захвата, и вы правы - это просто вопрос индексации при закрытии, а не при открытии скобок. (если я вас правильно понял). Однако это менее естественно (например, не следует соглашению о направлении чтения) и поэтому затрудняет (вероятно, незначительно) определение путем проверки, какая группа захвата будет с заданным индексом результата.

Также имеет смысл помещать всю строку соответствия в позицию 0 - в основном для согласованности. Это позволяет всей согласованной строке оставаться в том же индексе независимо от числа групп захвата от регулярного выражения до регулярного выражения и независимо от количества групп захвата, которые фактически соответствуют чему-либо (например, Java будет свертывать длину массива согласованных групп для каждого захвата group не соответствует какому-либо контенту (подумайте, например, о чем-то вроде "шаблона (. *)"). Вы всегда можете проверить capturing_group_results [capturing_group_results_length - 2], но это плохо переводится на языки Perl, которые динамически создают переменные ($ 1 , $ 2 и т. Д.) (Perl, конечно, плохой пример, поскольку он использует $ & для совпадающего выражения, но вы поняли идею :).

Алан Доннелли
источник
1
Хороший ответ .. Но как насчет обновления для Python (2 и 3) :-)
JGFMK
А как насчет JavaScript!?!
mesqueeb
9

Я знаю, что все разновидности регулярных выражений группируются в порядке появления открывающих скобок. То, что внешние группы нумеруются перед содержащимися в них подгруппами, - это просто естественный результат, а не явная политика.

Что интересно, так это с именованными группами . В большинстве случаев они следуют той же политике нумерации по относительному положению скобок - имя является просто псевдонимом для номера. Однако в регулярных выражениях .NET именованные группы нумеруются отдельно от нумерованных групп. Например:

Regex.Replace(@"one two three four", 
              @"(?<one>\w+) (\w+) (?<three>\w+) (\w+)",
              @"$1 $2 $3 $4")

// result: "two four one three"

Фактически, номер является псевдонимом для имени ; номера, присвоенные именованным группам, начинаются там, где заканчиваются «настоящие» пронумерованные группы. Это может показаться странной политикой, но для этого есть веская причина: в регулярных выражениях .NET вы можете использовать одно и то же имя группы более одного раза в регулярном выражении. Это делает возможным использование регулярных выражений, подобных тому из этого потока, для сопоставления чисел с плавающей запятой из разных языков:

^[+-]?[0-9]{1,3}
(?:
    (?:(?<thousand>\,)[0-9]{3})*
    (?:(?<decimal>\.)[0-9]{2})?
|
    (?:(?<thousand>\.)[0-9]{3})*
    (?:(?<decimal>\,)[0-9]{2})?
|
    [0-9]*
    (?:(?<decimal>[\.\,])[0-9]{2})?
)$

Если есть разделитель тысяч, он будет сохранен в группе «тысяча» независимо от того, какая часть регулярного выражения ему соответствует. Точно так же десятичный разделитель (если он есть) всегда будет сохранен в группе «десятичный». Конечно, есть способы идентифицировать и извлекать разделители без многоразовых именованных групп, но этот способ намного удобнее, я думаю, что он более чем оправдывает странную схему нумерации.

А еще есть Perl 5.10+, который дает нам больше контроля над захватом групп, чем я знаю, что делать. : D

Алан Мур
источник
4

Порядок захвата в порядке левого парена является стандартным для всех платформ, на которых я работал. (Perl, php, ruby, egrep)

Девин Сеартас
источник
"захват в порядке левого парена" Спасибо за это, это гораздо более лаконичный способ описания поведения.
Алан Сторм,
1
Вы можете перенумеровать захваты в Perl 5.10 и Perl 6.
Брэд Гилберт,