Согласование сбалансированных круглых скобок в 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