如何进行正则表达式平衡匹配,当括号可能被“转义”时
本文关键字:转义 平衡 正则表达式 何进行 | 更新日期: 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