当括号可能被转义时,如何进行正则表达式平衡匹配

3

假设我有一种玩具语言,它具有以下字符串:

fun( fun3\(\) ) + fun4()

在这里,'fun'将'fun3()'作为其参数。而fun4()则留待以后评估。
现在假设我有一个不同的字符串:
fun( fun3()\\) )

在这里,“fun”应该接收“fun3()\”,我们还剩下一个)。

通过使用“\”来转义“\”,我们可以将其文字化 - 因此,/那对/的“\”不再转义括号。第三个\会再次转义括号,依此类推。

现在,假设我想使用C#更强大的Regex库来匹配此字符串,以与它匹配括号的方式,并且特别是以这种方式;我知道通常我应该使用适当的解析方法而不是(扩展的)正则表达式。这与我应该使用什么工具有关,而更多地是关于这个工具能做什么。

我将使用以下三个字符串作为我的测试。

fun(abc) fun3()

这意味着fun()将接收'abc'作为其参数。fun3()被保留下来。
fun(\\\)\)) fun3()

这意味着fun()将接收'\))'作为其参数。fun3()是剩余的。
fun(fun2(\)\\\() ) fun3()

这意味着fun()将接收'fun2()\()'作为其参数。fun3()是剩余的。

正如Alan Moore在这个StackOverflow问题中所预测的那样,我想要使用的第一件事是LookBehind。 下面的正则表达式处理了第一种情况,但显然无法处理第二种情况。它太快地看到了第一个')'。

Regex catchRegex = new Regex(@"^fun\((.*?(?<!\\)(?:\\\\)*)(?<ClosingChar>[\)])(.*$)");
string testcase0 = @"fun(abc) fun3()";
string testcase1 = @"fun(\\\)\)) fun3()";
string testcase2 = @"fun(fun2(\)\\\() ) fun3()";
Console.WriteLine(catchRegex.Match(testcase0).Groups[1]); // 'abc'
Console.WriteLine(catchRegex.Match(testcase0).Groups[2]); // ' fun3()'
Console.WriteLine(catchRegex.Match(testcase0).Groups[3]); // ')'

Console.WriteLine(catchRegex.Match(testcase1).Groups[1]); // '\\\)\)'
Console.WriteLine(catchRegex.Match(testcase1).Groups[2]); // ' fun3()'
Console.WriteLine(catchRegex.Match(testcase1).Groups[3]); // ')'

Console.WriteLine(catchRegex.Match(testcase2).Groups[1]); // 'fun2(\)\\\(' <--!
Console.WriteLine(catchRegex.Match(testcase2).Groups[2]); // ' ) fun3()' <--!
Console.WriteLine(catchRegex.Match(testcase2).Groups[3]); // ')'

现在我们只需要做.NET能够做的事情,即括号匹配。它通过了第一个测试......但是因为我没有告诉它不要关心已经转义的内容,所以它失败了其他测试。这是公平的。

Regex bracketRegex = new Regex(@"^fun\(([^\)]*|(?<BR>)\(|(?<-BR>)\))(?<ClosingChar>[\)])(.*$)");
Console.WriteLine(bracketRegex.Match(testcase0).Groups[1]); // 'abc'
Console.WriteLine(bracketRegex.Match(testcase0).Groups[2]); // ' fun3()'
Console.WriteLine(bracketRegex.Match(testcase0).Groups[3]); // ''

Console.WriteLine(bracketRegex.Match(testcase1).Groups[1]); // '\\\'
Console.WriteLine(bracketRegex.Match(testcase1).Groups[2]); // '\)) fun3()'
Console.WriteLine(bracketRegex.Match(testcase1).Groups[3]); // ''

Console.WriteLine(bracketRegex.Match(testcase2).Groups[1]); // 'fun2(\' <--!
Console.WriteLine(bracketRegex.Match(testcase2).Groups[2]); // '\\\() ) fun3()' <--!
Console.WriteLine(bracketRegex.Match(testcase2).Groups[3]); // ''

但问题在于下一步。将版本1和版本2合并实际上并没有给我带来任何东西或任何进展。所以我的问题是,StackOverflow,有没有办法做到这一点?

Regex bracketAwareRegex = new Regex(@"^fun\(([^\)]*|(?<BR>)(?<!\\)(?:\\\\)*\(|(?<-BR>)(?<!\\)(?:\\\\)*\))(?<ClosingChar>[\)])(.*$)");
Console.WriteLine(bracketAwareRegex.Match(testcase0).Groups[1]); // 'abc'
Console.WriteLine(bracketAwareRegex.Match(testcase0).Groups[2]); // ' fun3()'
Console.WriteLine(bracketAwareRegex.Match(testcase0).Groups[3]); // ''

Console.WriteLine(bracketAwareRegex.Match(testcase1).Groups[1]); // '\\\'
Console.WriteLine(bracketAwareRegex.Match(testcase1).Groups[2]); // '\)) fun3()'
Console.WriteLine(bracketAwareRegex.Match(testcase1).Groups[3]); // ''

Console.WriteLine(bracketAwareRegex.Match(testcase2).Groups[1]); // 'fun2(\' <--!
Console.WriteLine(bracketAwareRegex.Match(testcase2).Groups[2]); // '\\\() ) fun3()' <--!
Console.WriteLine(bracketAwareRegex.Match(testcase2).Groups[3]); // ''

因为那行不通。

2
@leppie 对我来说,这更像是您没有仔细阅读问题。 - Jerry
1个回答

3
我建议使用以下正则表达式:
@"^fun\(((?:[^()\\]|\\.|(?<o>\()|(?<-o>\)))+(?(o)(?!)))\)(.*$)"

ideone演示

我移除了ClosingChar的捕获。

结果:

string testcase0 = @"fun(abc) fun3()";
Console.WriteLine(catchRegex.Match(testcase0).Groups[1]); // 'abc'
Console.WriteLine(catchRegex.Match(testcase0).Groups[2]); // ' fun3()'

string testcase1 = @"fun(\\\)\)) fun3()";
Console.WriteLine(catchRegex.Match(testcase1).Groups[1]); // '\\\)\)'
Console.WriteLine(catchRegex.Match(testcase1).Groups[2]); // ' fun3()'

string testcase2 = @"fun(fun2(\)\\\() ) fun3()";
Console.WriteLine(catchRegex.Match(testcase2).Groups[1]); // 'fun2(\)\\\()'
Console.WriteLine(catchRegex.Match(testcase2).Groups[2]); // ' fun3()'

我有另一种处理转义字符的方法,就是使用类似以下的方式:

(?:[^()\\]|\\.)

当与平衡组结合时,它以上面的结束。

^fun\(            Match 'fun(' literally at the beginning
(                
  (?:            
    [^()\\]       Match anything not '(', ')' or '\'
  |              
    \\.           Match any escaped char
  |              
    (?<o>\()    Match a '(' and name it 'o'
  |            
    (?<-o>\))   Match a ')' and remove the named 'o' capture
  )+           
  (?(o)(?!))    Make regex fail if 'o' doesn't exist
)                
\)(.*$)           Match anything leftover

非常感谢你,Jerry。我简直不敢相信我没有想到转义字符的方法。 - Mercutio

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