Согласование сбалансированных круглых скобок в Ruby с использованием рекурсивных регулярных выражений, таких как perl

Я искал способ сопоставить сбалансированную скобку в регулярном выражении и нашел способ в Perl, который использует рекурсивное регулярное выражение:

my $re;
$re = qr{
           \(
              (?:
                 (?> [^()]+ )       # Non-parens without backtracking
                 |
                 (??{ $re })        # Group with matching parens
              )*
           \)
         }x;

из сайта регулярного выражения perl .

Есть ли способ сделать это в Ruby или аналогичном языке?

UPDATE

Для заинтересованных здесь есть интересные ссылки:

Руководство Oniguruma - от ответа Саввы.

Прагматический программист Ruby 1.9 Regular Expressions Пример главы

Ответ 1

Да. С oniguruma движком regex, который встроен в Ruby 1.9, и устанавливается на Ruby 1.8, вы можете это сделать. Вы называете subregex (?<name>...) или (?'name'...). Затем вы вызываете subregex с \g<name> или \g'name' в пределах одного и того же регулярного выражения. Поэтому ваше регулярное выражение, переведенное в oniguruma regex, будет:

re = %r{
  (?<re>
    \(
      (?:
        (?> [^()]+ )
        |
        \g<re>
      )*
    \)
  )
}x

Также обратите внимание, что многобайтовый строковый модуль в PHP >= 5 использует движок regex oniguruma, поэтому вы сможете сделать то же самое.

Руководство для oniguruma здесь.

Ответ 2

Мне нравится вышеупомянутое решение, но часто хочется игнорировать экранированные символы. Предполагая, что\экранирует следующий символ, следующее regex обрабатывает также экранированные символы.

ESC= /(?<![\\])(?>[\\](?:[\\][\\])*)/
UNESC= /(?:\A|(?<=[^\\]))(?:[\\][\\])*/
BALANCED_PARENS = /#{UNESC}(
                   (?<bal>\(
                    (?>
                      (?>  (?:#{ESC}\(|#{ESC}\)|[^()])+     )
                      |\g<bal>
                    )*
                    \))    ) /xm

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

Причиной сложности ESC и UNESC является предположение о том, что a\\является сбрасываемой обратной косой чертой. Мы используем только последовательность UNESC перед исходным совпадением парнов, так как любая другая экранированная скобка будет сопоставлена ​​внутри атомной группы и никогда не будет возвращаться. В самом деле, если бы мы попытались использовать префикс UNESC для внутреннего или финального совпадения парнов, это не сработало бы, когда [^()] внутри атомной группы соответствовало ведущему\и отказалась от обратного хода.

Это регулярное выражение сканирует первый параграф, который ограничивает действительную балансировку в скобках. Таким образом, учитывая строку "((материал)" она будет соответствовать "(материал)". Часто желаемое поведение состоит в том, чтобы найти первую (неэкранированную) скобку и либо соответствовать внутренней (если сбалансированной), либо не соответствовать. К сожалению, атомарная группировка не остановит все повторное выражение, и попытка повторения в более поздней точке должна быть привязана к началу строки и только смотреть на первый захват. Следующее regex делает это изменение:

BALANCED_PARENS = /\A(?:#{ESC}\(|#{ESC}\)|[^()])*+
                  (?<match>\(
                   (?<bal>
                    (?>
                      (?>  (?:#{ESC}\(|#{ESC}\)|[^()])+     )
                      |\(\g<bal>
                    )*
                    \))    ) /xm