使用像perl这样的递归正则表达式匹配Ruby中的平衡括号

时间:2011-06-13 13:26:40

标签: ruby regex perl

我一直在寻找一种在正则表达式中匹配平衡括号的方法,并在Perl中找到了一种使用递归正则表达式的方法:

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

有没有办法用Ruby或类似的语言做到这一点?

更新

对于那些感兴趣的人来说,有一些有趣的链接:

Oniguruma manual - 来自Sawa的回答。

Pragmatic Programmers' Ruby 1.9 Regular Expressions Sample Chapter

2 个答案:

答案 0 :(得分:20)

是。使用{1.9}内置的oniguruma regex引擎,可以在Ruby 1.8上安装,你可以做到这一点。您使用(?<name>...)(?'name'...)命名子目标。然后,您在同一个正则表达式中使用\g<name>\g'name'调用subregex。所以你的正则表达式转换为oniguruma正则表达式将是:

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

另请注意,PHP&gt; = 5中的多字节字符串模块使用oniguruma regex引擎,因此您也可以这样做。

oniguruma手册是here

答案 1 :(得分:0)

我喜欢上述解决方案但经常有人希望忽略转义字符。假设\转义后续字符,以下正则表达式也处理转义字符。

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

鉴于负面观察的局限性,匹配的parens划分的部分将是第一次捕获而不是整个匹配(整个匹配可能包含前导的反斜杠)。

ESC和UNESC的复杂性的原因是假设\\是逃避反斜杠。我们只在初始paren匹配之前使用UNESC序列,因为任何其他转义括号将在原子组内匹配并且永远不会回溯。实际上,如果我们尝试将UNESC前缀用于内部或最终的匹配,当原子组内的[^()]与前导匹配并且拒绝回溯时,它将失败。

这个正则表达式将扫描第一个限定有效平衡括号的paren。因此,给定字符串“((stuff)”它将匹配“(stuff)”。通常,期望的行为是找到第一个(未转义的)括号并且匹配内部(如果是平衡的)或者不匹配。不幸的是,原子分组不会阻止整个正则表达式退出,并且稍后会尝试匹配,所以我们必须在字符串的开头锚定并只查看第一次捕获。以下正则表达式进行了此更改:

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