Мне нравится вышеуказанное решение, но часто хочется игнорировать экранированные символы.Предполагая, что \ экранирует следующий символ, следующее регулярное выражение также обрабатывает экранированные символы.
ESC= /(?<![\\])(?>[\\](?:[\\][\\])*)/
UNESC= /(?:\A|(?<=[^\\]))(?:[\\][\\])*/
BALANCED_PARENS = /#{UNESC}(
(?<bal>\(
(?>
(?> (?:#{ESC}\(|#{ESC}\)|[^()])+ )
|\g<bal>
)*
\)) ) /xm
Принимая во внимание ограничения негативного взгляда, часть, разделенная соответствующими паренами, будет первым захватом, а не всем соответствием (все совпадениеможет содержать начальные экранированные обратные косые черты).
Причиной сложности ESC и UNESC является предположение о том, что \\ является экранированной обратной косой чертой.Мы используем только последовательность UNESC до начального совпадения, так как любые другие экранированные скобки будут сопоставлены внутри атомарной группы и никогда не будут возвращены.В самом деле, если бы мы попытались использовать префикс UNESC для внутреннего или окончательного совпадения, то произойдет сбой, когда [^ ()] внутри атомарной группы совпадет с ведущими и откажется вернуться назад.
Это регулярное выражениеотсканирует первое имя, которое ограничивает правильную сбалансированную скобку.Таким образом, учитывая строку "((stuff)", она будет соответствовать "(stuff)". Часто желаемое поведение - найти первую (неэкранированную) скобку и либо соответствовать внутренней (если сбалансирована), либо не соответствовать. К сожалению,атомарная группировка не остановит резервирование всего регулярного выражения и попытку сопоставления на более позднем этапе, поэтому мы должны закрепиться в начале строки и посмотреть только на первый захват. Следующее регулярное выражение делает это изменение:
BALANCED_PARENS = /\A(?:#{ESC}\(|#{ESC}\)|[^()])*+
(?<match>\(
(?<bal>
(?>
(?> (?:#{ESC}\(|#{ESC}\)|[^()])+ )
|\(\g<bal>
)*
\)) ) /xm