[英]Regex for matching Functions and Capturing their Arguments
我正在研究一个计算器,它需要字符串表达式并对它们进行评估。 我有一个函数,使用Regex在表达式中搜索数学函数,检索参数,查找函数名称并对其进行求值。 我遇到的问题是,如果我知道将会有多少参数,我只能做到这一点,我无法正确使用正则表达式。 如果我只是用字符分割(
和)
字符的内容,
那么我就不能在该参数中进行其他函数调用。
这是函数匹配模式: \\b([az][a-z0-9_]*)\\((..*)\\)\\b
它只适用于一个参数,我可以为每个参数创建一个组,不包括嵌套函数内的参数吗? 例如,它匹配: func1(2 * 7, func2(3, 5))
并创建捕获组: 2 * 7
和func2(3, 5)
这里我用来评估表达式的函数:
/// <summary>
/// Attempts to evaluate and store the result of the given mathematical expression.
/// </summary>
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<string> args = new List<string>();
List<double> argVals = new List<double>();
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 < argCount; ++i)
argPattTmp += "(..*)" + ((i == argCount - 1) ? ",":"") + "\\s*";
argPattTmp += "\\)";
// Get all of the argument strings.
Regex argPatt = new Regex(argPattTmp);
// Evaluate and store all argument values.
foreach (Group argMatch in argPatt.Matches(match.Value.Trim())[0].Groups)
{
string arg = argMatch.Value.Trim();
System.Windows.Forms.MessageBox.Show(arg);
if (arg.Length > 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 ---- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
/// <summary>
/// The pattern used for function calls.
/// </summary>
public static Regex PatternFunc = new Regex(@"([a-z][a-z0-9_]*)\((..*)\)");
正如您所看到的,构建正则表达式以匹配参数的尝试非常糟糕。 它不起作用。
我所要做的就是从表达式func1(2 * 7, func2(3, 5))
提取2 * 7
和func2(3, 5)
func1(2 * 7, func2(3, 5))
但它也必须适用于具有不同参数计数的函数。 如果有一种方法可以在不使用同样好的正则表达式的情况下执行此操作。
有一个简单的解决方案和一个更高级的解决方案( 编辑后添加)来处理更复杂的功能。
为了实现您发布的示例,我建议分两步执行此操作,第一步是提取参数(最后解释正则表达式):
\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>)|\)(?<-open>))*\)))*)+
以下是一些测试用例,以显示它的运行情况:
string extractFuncRegex = @"\b[^()]+\((.*)\)$";
string extractArgsRegex = @"(?:[^,()]+((?:\((?>[^()]+|\((?<open>)|\)(?<-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>)|\)(?<-open>))*\)))*)+
总之,它从不是逗号或括号的字符开始。 然后,如果参数中有括号,则匹配并减去括号直到它们平衡。 然后,如果参数中有其他函数,它会尝试重复该匹配。 然后进入下一个参数(逗号后面)。 详细地:
[^,()]+
匹配任何不是',()'的东西 ?:
表示非捕获组,即不将匹配存储在组中的括号内。 \\(
意味着从一个开放式支架开始。 ?>
意味着原子分组 - 实际上,这意味着它不记得回溯位置。 这也有助于提高性能,因为尝试不同组合的步骤较少。 [^()]+|
除了开口或结束括号之外的任何东西。 接下来是| (要么) \\((?<open>)|
这是好东西,并说匹配'('或 (?<-open>)
这是更好的东西,说匹配')'并平衡'('。这意味着匹配的这一部分(第一个括号后的所有内容)将继续,直到所有内部括号匹配如果没有平衡表达式,匹配将在第一个结束括号中完成。关键是引擎与''''相对于最终')'不匹配,而是从匹配'(')中减去。没有进一步的突出'(', - 开启失败,所以最终')'可以匹配。 一个最后的点缀:
如果你将(?(open)(?!))
到正则表达式:
(?:[^,()]+((?:\((?>[^()]+|\((?<open>)|\)(?<-open>))*(?(open)(?!))\)))*)+
如果打开已捕获某些东西(尚未减去),则(?!)将始终失败,即如果有没有右括号的开括号,它将始终失败。 这是测试平衡是否失败的有用方法。
一些说明:
希望有所帮助。
我很抱歉破坏了RegEx泡沫,但这是你单独使用正则表达式无法有效完成的事情之一。
你实现的基本上是一个运算符优先级解析器 ,支持子表达式和参数列表。 该语句作为标记流处理 - 可能使用正则表达式 - 子表达式作为高优先级操作处理。
使用正确的代码,您可以将此作为完整令牌流的迭代,但递归解析器也很常见。 无论哪种方式,您都必须能够有效地推送状态并在每个子表达式入口点重新启动解析 - 一个(
,
或<function_name>(
令牌 - 并将结果推送到子表达式出口点处的解析器链) - )
或者,
令牌。
这个正则表达式做你想要的:
^(?<FunctionName>\w+)\((?>(?(param),)(?<param>(?>(?>[^\(\),"]|(?<p>\()|(?<-p>\))|(?(p)[^\(\)]|(?!))|(?(g)(?:""|[^"]|(?<-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 ")"
错误。
然后,在开始括号和右括号之间以及任何逗号之间取出文本块,并对每个参数重复上述操作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.