用于匹配函数和捕获其参数的正则表达式

我正在研究一个计算器,它需要字符串表达式并对它们进行评估。 我有一个函数,使用Regex在表达式中搜索数学函数,检索参数,查找函数名称并对其进行求值。 我遇到的问题是,如果我知道将会有多少参数,我只能这样做,我无法正确使用正则表达式。 如果我只是用字符分割()字符的内容,那么我就不能在该参数中进行其他函数调用。

这是函数匹配模式: \b([az][a-z0-9_]*)\((..*)\)\b

它只适用于一个参数,我可以为每个参数创建一个组,不包括嵌套函数内的参数吗? 例如,它匹配: func1(2 * 7, func2(3, 5))并创建捕获组: 2 * 7func2(3, 5)

这里我用来评估表达式的函数:

  ///  /// Attempts to evaluate and store the result of the given mathematical expression. ///  public static bool Evaluate(string expr, ref double result) { expr = expr.ToLower(); try { // Matches for result identifiers, constants/variables objects, and functions. MatchCollection results = Calculator.PatternResult.Matches(expr); MatchCollection objs = Calculator.PatternObjId.Matches(expr); MatchCollection funcs = Calculator.PatternFunc.Matches(expr); // Parse the expression for functions. foreach (Match match in funcs) { System.Windows.Forms.MessageBox.Show("Function found. - " + match.Groups[1].Value + "(" + match.Groups[2].Value + ")"); int argCount = 0; List args = new List(); List argVals = new List(); string funcName = match.Groups[1].Value; // Ensure the function exists. if (_Functions.ContainsKey(funcName)) { argCount = _Functions[funcName].ArgCount; } else { Error("The function '"+funcName+"' does not exist."); return false; } // Create the pattern for matching arguments. string argPattTmp = funcName + "\\(\\s*"; for (int i = 0; i  0) { double argVal = 0; // Check if the argument is a double or expression. try { argVal = Convert.ToDouble(arg); } catch { // Attempt to evaluate the arguments expression. System.Windows.Forms.MessageBox.Show("Argument is an expression: " + arg); if (!Evaluate(arg, ref argVal)) { Error("Invalid arguments were passed to the function '" + funcName + "'."); return false; } } // Store the value of the argument. System.Windows.Forms.MessageBox.Show("ArgVal = " + argVal.ToString()); argVals.Add(argVal); } else { Error("Invalid arguments were passed to the function '" + funcName + "'."); return false; } } // Parse the function and replace with the result. double funcResult = RunFunction(funcName, argVals.ToArray()); expr = new Regex("\\b"+match.Value+"\\b").Replace(expr, funcResult.ToString()); } // Final evaluation. result = Program.Scripting.Eval(expr); } catch (Exception ex) { Error(ex.Message); return false; } return true; } ////////////////////////////////// ---- PATTERNS ---- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ ///  /// The pattern used for function calls. ///  public static Regex PatternFunc = new Regex(@"([az][a-z0-9_]*)\((..*)\)"); 

正如您所看到的,构建正则表达式以匹配参数的尝试非常糟糕。 它不起作用。

我所要做的就是从表达式func1(2 * 7, func2(3, 5))提取2 * 7func2(3, 5) func1(2 * 7, func2(3, 5))但它也必须适用于具有不同参数计数的函数。 如果有一种方法可以做到这一点,而不使用也很好的正则表达式。

有一个简单的解决方案和一个更高级的解决方案( 编辑后添加)来处理更复杂的function。

为了实现您发布的示例,我建议分两步执行此操作,第一步是提取参数(最后解释正则表达式):

 \b[^()]+\((.*)\)$ 

现在,要解析参数。

简单解决方案

使用以下方法提取参数:

 ([^,]+\(.+?\))|([^,]+) 

以下是一些C#代码示例(所有断言传递):

 string extractFuncRegex = @"\b[^()]+\((.*)\)$"; string extractArgsRegex = @"([^,]+\(.+?\))|([^,]+)"; //Your test string string test = @"func1(2 * 7, func2(3, 5))"; var match = Regex.Match( test, extractFuncRegex ); string innerArgs = match.Groups[1].Value; Assert.AreEqual( innerArgs, @"2 * 7, func2(3, 5)" ); var matches = Regex.Matches( innerArgs, extractArgsRegex ); Assert.AreEqual( matches[0].Value, "2 * 7" ); Assert.AreEqual( matches[1].Value.Trim(), "func2(3, 5)" ); 

正则表达式的解释。 将参数提取为单个字符串:

 \b[^()]+\((.*)\)$ 

哪里:

  • [^()]+不是开头,结束括号的字符。
  • \((.*)\)括号内的所有内容

args提取:

 ([^,]+\(.+?\))|([^,]+) 

哪里:

  • ([^,]+\(.+?\))字符不是逗号,后跟括号中的字符。 这会获取func参数。 注意+? 所以这场比赛是懒惰的并且在第一场比赛时停止了。
  • |([^,]+)如果前一个不匹配,则匹配不是逗号的连续字符。 这些比赛分组。

更高级的解决方案

现在,这种方法存在一些明显的局限性,例如它匹配第一个右括号,因此它不能很好地处理嵌套函数。 对于更全面的解决方案(如果需要),我们需要使用平衡组定义 (正如我在此编辑之前提到的)。 出于我们的目的,平衡组定义允许我们跟踪打开括号的实例并减去右括号实例。 本质上,开启和关闭括号将在搜索的平衡部分中相互抵消,直到找到最终的结束括号。 也就是说,匹配将继续,直到括号平衡并找到最后的结束括号。

因此,现在提取parms的正则表达式(函数提取可以保持不变):

 (?:[^,()]+((?:\((?>[^()]+|\((?)|\)(?<-open>))*\)))*)+ 

以下是一些测试用例,以显示它的运行情况:

 string extractFuncRegex = @"\b[^()]+\((.*)\)$"; string extractArgsRegex = @"(?:[^,()]+((?:\((?>[^()]+|\((?)|\)(?<-open>))*\)))*)+"; //Your test string string test = @"func1(2 * 7, func2(3, 5))"; var match = Regex.Match( test, extractFuncRegex ); string innerArgs = match.Groups[1].Value; Assert.AreEqual( innerArgs, @"2 * 7, func2(3, 5)" ); var matches = Regex.Matches( innerArgs, extractArgsRegex ); Assert.AreEqual( matches[0].Value, "2 * 7" ); Assert.AreEqual( matches[1].Value.Trim(), "func2(3, 5)" ); //A more advanced test string test = @"someFunc(a,b,func1(a,b+c),func2(a*b,func3(a+b,c)),func4(e)+func5(f),func6(func7(g,h)+func8(i,(a)=>a+2)),g+2)"; match = Regex.Match( test, extractFuncRegex ); innerArgs = match.Groups[1].Value; Assert.AreEqual( innerArgs, @"a,b,func1(a,b+c),func2(a*b,func3(a+b,c)),func4(e)+func5(f),func6(func7(g,h)+func8(i,(a)=>a+2)),g+2" ); matches = Regex.Matches( innerArgs, extractArgsRegex ); Assert.AreEqual( matches[0].Value, "a" ); Assert.AreEqual( matches[1].Value.Trim(), "b" ); Assert.AreEqual( matches[2].Value.Trim(), "func1(a,b+c)" ); Assert.AreEqual( matches[3].Value.Trim(), "func2(a*b,func3(a+b,c))" ); Assert.AreEqual( matches[4].Value.Trim(), "func4(e)+func5(f)" ); Assert.AreEqual( matches[5].Value.Trim(), "func6(func7(g,h)+func8(i,(a)=>a+2))" ); Assert.AreEqual( matches[6].Value.Trim(), "g+2" ); 

特别注意,该方法现在非常先进:

 someFunc(a,b,func1(a,b+c),func2(a*b,func3(a+b,c)),func4(e)+func5(f),func6(func7(g,h)+func8(i,(a)=>a+2)),g+2) 

所以,再看一下正则表达式:

 (?:[^,()]+((?:\((?>[^()]+|\((?)|\)(?<-open>))*\)))*)+ 

总之,它以不是逗号或括号的字符开头。 然后,如果参数中有括号,则匹配并减去括号直到它们平衡。 然后,如果参数中有其他函数,它会尝试重复该匹配。 然后它进入下一个参数(在逗号之后)。 详细地:

  • [^,()]+匹配任何不是’,()’的东西
  • ?:表示非捕获组,即不将匹配存储在组中的括号内。
  • \(意味着从一个开放式支架开始。
  • ?>意味着primefaces分组 – 基本上,这意味着它不记得回溯位置。 这也有助于提高性能,因为尝试不同组合的步骤较少。
  • [^()]+| 除了开口或右边括号之外的任何东西。 接下来是| (要么)
  • \((?)|这是好东西,并说匹配’(’或
  • (?<-open>)这是更好的东西,说匹配’)’并平衡’(’。这意味着匹配的这一部分(第一个括号后的所有内容)将继续,直到所有内部括号匹配如果没有平衡表达式,匹配就会在第一个结束括号中完成。难点在于引擎与””匹配最后’)’,而是从匹配’(’)中减去。没有进一步的突出’(’, – 开启失败,所以最终’)’可以匹配。
  • 正则表达式的其余部分包含组的右括号和重复( 和+)分别为:重复内括号匹配0次或更多次,重复全括号搜索0次或更多次(0允许不带括号的参数)并重复完整匹配1次或多次(允许foo(1)+ foo(2))

一个最后的点缀:

如果你将(?(open)(?!))到正则表达式:

 (?:[^,()]+((?:\((?>[^()]+|\((?)|\)(?<-open>))*(?(open)(?!))\)))*)+ 

如果打开已捕获的东西(尚未减去),则(?!)将始终失败,即如果有没有右括号的开括号,它将始终失败。 这是测试平衡是否失败的有用方法。

一些说明:

  • 当最后一个字符是’)’时,\ b将不匹配,因为它不是单词字符,\ b 测试单词字符边界,因此你的正则表达式不匹配。
  • 虽然正则表达式很强大,但除非你是大师中的大师,否则最好保持表达式简单,否则它们难以维护并且难以让其他人理解。 这就是为什么有时最好将问题分解为子问题和更简单的表达式,并让语言执行一些擅长的非搜索/匹配操作。 因此,您可能希望将简单的正则表达式与更复杂的代码混合使用,反之亦然,具体取决于您的舒适度。
  • 这将匹配一些非常复杂的函数,但它不是函数的词法分析器。
  • 如果您可以在参数中包含字符串,并且字符串本身可以包含括号,例如“go(…”那么您将需要修改正则表达式以从比较中取出字符串。与注释相同。
  • 用于平衡组定义的一些链接: 此处 , 此处 , 此处和此处 。

希望有所帮助。

我很抱歉破坏了RegEx的泡沫,但这是你单独使用正则表达式无法有效完成的事情之一。

您正在实现的基本上是一个运算符优先级解析器 ,支持子表达式和参数列表。 该语句作为标记流处理 – 可能使用正则表达式 – 子表达式作为高优先级操作处理。

使用正确的代码,您可以将此作为完整令牌流的迭代,但递归解析器也很常见。 无论哪种方式,您都必须能够有效地推送状态并在每个子表达式入口点重新启动解析 – 一个( ,(令牌 – 并将结果推送到子表达式出口点处的解析器链) – )或者,令牌。

这个正则表达式做你想要的:

 ^(?\w+)\((?>(?(param),)(?(?>(?>[^\(\),"]|(?

\()|(?<-p>\))|(?(p)[^\(\)]|(?!))|(?(g)(?:""|[^"]|(?<-g>"))|(?!))|(?")))*))+\)$

在代码中粘贴它时,不要忘记转义反斜杠和双引号。

它将正确匹配双引号,内部函数和数字中的参数,如下所示:
f1(123,“df”“j”“,dhf”,abc12,func2(),func(123,a> 2))

param堆栈将包含
123
“df”“j”“,dhf”
ABC12
FUNC2()
FUNC(123,> 2)

正则表达式不会让你彻底摆脱这个麻烦……

由于您有嵌套括号,因此需要修改代码以进行计数(反对) 。 当你遇到一个( ,你需要注意到这个位置然后向前看,为每个额外增加一个计数器(你找到并递减它)你找到。当你的计数器是0并且你找到a ) ,那就是函数参数块的结尾,然后您可以在括号之间解析文本。 当计数器为0时,您还可以拆分文本以获取函数参数。

如果在计数器为0时遇到字符串的结尾,则表示"(" without ")"错误。

然后,在开始括号和右括号之间以及任何逗号之间取出文本块,并对每个参数重复上述操作。

正则表达式有一些新的(相对非常新的)特定于语言的增强function,可以将无上下文语言与“正则表达式”相匹配,但在使用更常用于此类任务的工具时,您会发现更多资源和更多帮助:

最好使用解析器生成器,如ANTLR,LEX + YACC,FLEX + BISON或任何其他常用的解析器生成器 。 他们中的大多数都提供了关于如何构建支持分组和函数调用的简单计算器的完整示例。