使用正则表达式平衡匹配括号

36

我想创建一个.NET正则表达式,能够正确平衡我的括号。我有以下正则表达式:

func([a-zA-Z_][a-zA-Z0-9_]*)\(.*\)

我试图匹配的字符串是这个:

"test -> funcPow((3),2) * (9+1)"

正则表达式应该匹配从funcPow到第二个右括号的所有内容,然后在第二个右括号之后停止。但实际上,它一直匹配到最后一个右括号。正则表达式返回:

"funcPow((3),2) * (9+1)"

它应该返回这个:

"funcPow((3),2)"
任何帮助将不胜感激。
4个回答

63

正则表达式绝对可以进行平衡括号匹配。这可能有些棘手,需要一些更高级的正则表达式功能,但并不太难。

示例:

var r = new Regex(@"
    func([a-zA-Z_][a-zA-Z0-9_]*) # The func name

    \(                      # First '('
        (?:                 
        [^()]               # Match all non-braces
        |
        (?<open> \( )       # Match '(', and capture into 'open'
        |
        (?<-open> \) )      # Match ')', and delete the 'open' capture
        )+
        (?(open)(?!))       # Fails if 'open' stack isn't empty!

    \)                      # Last ')'
", RegexOptions.IgnorePatternWhitespace);

平衡匹配组具有一些特点,但对于此示例,我们只使用了捕获删除功能。行(?<-open> \) )将匹配一个)并删除先前的“open”捕获。
最棘手的一行是(?(open)(?!)),所以让我解释一下。 (?(open)是一个条件表达式,仅在存在“open”捕获时匹配。 (?!)是一个始终失败的否定表达式。因此,(?(open)(?!))表示“如果存在open捕获,则失败”。
Microsoft的文档也非常有帮助。(原文链接)

1
我修改了这行代码 [^()]* # 匹配所有非括号字符,使其匹配空括号 ()。 - Tono Nam

22

使用平衡组,可以做到:

Regex rx = new Regex(@"func([a-zA-Z_][a-zA-Z0-9_]*)\(((?<BR>\()|(?<-BR>\))|[^()]*)+\)");

var match = rx.Match("funcPow((3),2) * (9+1)");

var str = match.Value; // funcPow((3),2)

(?<BR>\()|(?<-BR>\))平衡组的表示方法(我用BR命名是指括号)。也许这样写更清晰一些:(?<BR>\()|(?<-BR>\)),这样\(\)就更加明显了。

如果你真的足够恨自己(以及世界/你的同行程序员)来使用这些东西,我建议使用RegexOptions.IgnorePatternWhitespace并在各处添加空格 :-)


我认为您缺少了最后的关键部分,(?(BR)(?!)) - Scott Rippey
@ScottRippey 不是的。在右括号 ) 之后还有其他表达式。OP 的问题非常明确。他想要的是 funcsomething(),而不是解析整个表达式。因此,我找到的第一个“不平衡”的括号是我的子表达式的右括号。funcPow((3),2) * (9+1) -> funcPow((3),2) - xanatos
1
哦,我意识到(?(BR)(?!))只是为了确保开括号有对应的闭括号。微软的网站上写道:"最后一个子表达式(?(Open)(?!))表示输入字符串中的嵌套结构是否正确平衡"。 - Scott Rippey
@ScottRippey 就像你写的一样 :-) 请注意,它是一个类型为 (?(name)yes|no) 的交替结构 http://msdn.microsoft.com/en-us/library/36xybswe(v=VS.71).aspx 与一个总是失败的负向先行断言表达式 (?!) 的组合。因此,它的含义是 "如果有名为 name 的捕获,则失败"。 - xanatos
1
这是一个很好的讨论 :-) 我们想法一致。太一致了。现在我必须消灭你。对不起。 - Scott Rippey
2
仅供记录,包含 (?(BR)(?!)) 和不包含它的区别在于,如果没有足够的闭合括号,则不包含它的表达式将匹配到并包括最后一个闭合括号。而包含它的表达式作为整体将不会匹配。 - Rawling

1

正则表达式只能用于正则语言。这意味着正则表达式可以找到“a和b的任意组合”。(abbabbabaaa等)但是它们无法找到“n个a,一个b,n个a”。(a^n b a^n)正则表达式无法保证第一组a与第二组a匹配。

因此,它们无法匹配相等数量的开放和关闭括号。编写一个函数逐个字符遍历字符串就足够了。有两个计数器,一个用于开放括号,一个用于关闭括号。在遍历字符串时增加指针,如果opening_paren_count != closing_parent_count则返回false。


7
或许是这样,但只要你理解了它们的限制,正则表达式几乎可以用于任何类型的文本。递归/平衡模式虽然难看(在我看来),而且很少值得花费心思,但许多正则表达式工具都支持它们。 - Alan Moore

-1
func[a-zA-Z0-9_]*\((([^()])|(\([^()]*\)))*\)

你可以使用它,但如果你正在使用.NET,可能会有更好的选择。

这部分你已经知道了:

 func[a-zA-Z0-9_]*\( --weird part-- \)

--奇怪的部分--只是意味着:(允许任何字符.,或|任何部分(.*)存在多次,尽可能多地匹配)*。唯一的问题是,你不能匹配任何字符.,必须使用[^()]来排除括号。

(([^()])|(\([^()]*\)))*

你应该明确指出这只适用于一层嵌套。 - Scott Rippey
@ScottRippey:如果在该函数内部存在一个函数,它仍然有效。 | 条件可以处理这个问题。你能举一个这个正则表达式会提供错误匹配的例子吗? - rkw
1
它能够正确地工作,并且正好符合OP的要求,所以这是一个好答案。然而,它是硬编码为仅匹配一个嵌套级别,因此无法匹配:func(a(b(c)d)e)。不清楚OP是否需要这个。 - Scott Rippey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接