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

本文关键字:转义 平衡 正则表达式 何进行 | 更新日期: 2023-09-27 17:55:32

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

fun( fun3'(') ) + fun4()

在这里,"fun"接受"fun3()"作为其参数。 fun4() 留待以后评估。

现在假设我有一个不同的字符串:

fun( fun3()'') )
在这里,"

fun"应该收到"fun3()''",我们有一个)剩余的。

通过执行"''"来转义"''"意味着我们从字面上得到它 - 因此,/那对/的"''"不再转义括号。第三个''将再次逃离括号,依此类推。

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

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

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]); // ''

因为那行不通。

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

我提出这个正则表达式:

@"^fun'(((?:[^()'']|''.|(?<o>'()|(?<-o>')))+(?(o)(?!)))')(.*$)"

IDEe演示

我删除了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