我正在研究一个计算器,它需要字符串表达式并对它们进行评估.我有一个函数,使用Regex在表达式中搜索数学函数,检索参数,查找函数名称并对其进行求值.我遇到的问题是,如果我知道将会有多少参数,我只能做到这一点,我无法正确使用正则表达式.如果我只是通过字符拆分(
和)
字符的内容,,
那么我不能在该参数中进行其他函数调用.
这是函数匹配模式: \b([a-z][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_]*)\((..*)\)");
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,构建正则表达式以匹配参数的尝试非常糟糕.它不起作用.
我所要做的就是从表达式中提取2 * 7
,但它必须适用于具有不同参数计数的函数.如果有一种方法可以做到这一点,而不使用也很好的正则表达式.func2(3, 5)
func1(2 * 7, func2(3, 5))
aca*_*lon 34
有一个简单的解决方案和一个更高级的解决方案(编辑后添加)来处理更复杂的功能.
为了实现您发布的示例,我建议分两步执行此操作,第一步是提取参数(最后解释正则表达式):
\b[^()]+\((.*)\)$
Run Code Online (Sandbox Code Playgroud)
现在,要解析参数.
简单解决方案
使用以下方法提取参数:
([^,]+\(.+?\))|([^,]+)
Run Code Online (Sandbox Code Playgroud)
以下是一些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)" );
Run Code Online (Sandbox Code Playgroud)
正则表达式的解释.将参数提取为单个字符串:
\b[^()]+\((.*)\)$
Run Code Online (Sandbox Code Playgroud)
哪里:
[^()]+
不是开场,结束括号的字符. \((.*)\)
括号内的一切args提取:
([^,]+\(.+?\))|([^,]+)
Run Code Online (Sandbox Code Playgroud)
哪里:
([^,]+\(.+?\))
不是逗号的字符后跟括号中的字符.这会获取func参数.注意+?所以这场比赛是懒惰的并且在第一场比赛时停止了.|([^,]+)
如果前一个不匹配,则匹配不是逗号的连续字符.这些比赛分组.更高级的解决方案
现在,这种方法存在一些明显的局限性,例如它匹配第一个右括号,因此它不能很好地处理嵌套函数.对于更全面的解决方案(如果需要),我们需要使用平衡组定义(正如我在此编辑之前提到的).出于我们的目的,平衡组定义允许我们跟踪开括号的实例并减去右括号实例.本质上,开启和关闭括号将在搜索的平衡部分中相互抵消,直到找到最后的结束括号.也就是说,匹配将继续,直到括号平衡并找到最后的结束括号.
因此,现在提取parms的正则表达式(函数提取可以保持不变):
(?:[^,()]+((?:\((?>[^()]+|\((?<open>)|\)(?<-open>))*\)))*)+
Run Code Online (Sandbox Code Playgroud)
以下是一些测试用例,以显示它的运行情况:
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" );
Run Code Online (Sandbox Code Playgroud)
特别注意该方法现在非常先进:
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)
Run Code Online (Sandbox Code Playgroud)
那么,再看一下正则表达式:
(?:[^,()]+((?:\((?>[^()]+|\((?<open>)|\)(?<-open>))*\)))*)+
Run Code Online (Sandbox Code Playgroud)
总之,它从不是逗号或括号的字符开始.然后,如果参数中有括号,则匹配并减去括号直到它们平衡.然后,如果参数中有其他函数,它会尝试重复该匹配.然后进入下一个参数(逗号后面).详细地:
[^,()]+
匹配任何不是',()'的东西 ?:
表示非捕获组,即不将匹配存储在组中的括号内.\(
意味着从一个开放的支架开始. ?>
意味着原子分组 - 实质上,这意味着它不记得回溯位置.这也有助于提高性能,因为尝试不同组合的步骤较少.[^()]+|
除了开口或结束括号之外的任何东西.接下来是| (要么)\((?<open>)|
这是好东西,并说匹配'('或 (?<-open>)
这是更好的东西,说匹配')'并平衡'('.这意味着匹配的这一部分(第一个括号后的所有内容)将继续,直到所有内部括号匹配.没有平衡表达式,匹配将在第一个结束括号结束.关键是引擎与''''不匹配最后')',而是从匹配'('.当没有进一步突出'时减去'(', -open失败,所以最终')'可以匹配.一个最后的点缀:
如果你添加(?(open)(?!))
到正则表达式:
(?:[^,()]+((?:\((?>[^()]+|\((?<open>)|\)(?<-open>))*(?(open)(?!))\)))*)+
Run Code Online (Sandbox Code Playgroud)
如果打开已捕获某些内容(尚未减去),则(?!)将始终失败,即如果有一个没有右括号的开括号,它将始终失败.这是测试平衡是否失败的有用方法.
一些说明:
希望有所帮助.
小智 5
这个正则表达式做你想要的:
^(?<FunctionName>\w+)\((?>(?(param),)(?<param>(?>(?>[^\(\),"]|(?<p>\()|(?<-p>\))|(?(p)[^\(\)]|(?!))|(?(g)(?:""|[^"]|(?<-g>"))|(?!))|(?<g>")))*))+\)$
Run Code Online (Sandbox Code Playgroud)
在将其粘贴到代码中时,不要忘记转义反斜杠和双引号。
它将正确匹配双引号、内部函数和数字中的参数,如下所示:
f1(123,"df""j"" , dhf",abc12,func2(),func(123,a>2))
参数堆栈将包含
123 个
"df""j"" , dhf"
abc12
func2()
func(123,a>2)
正则表达式不会让你完全摆脱这个麻烦......
由于您有嵌套括号,因此您需要修改代码以对 进行(
计数)
。当您遇到 时(
,您需要记下该位置,然后向前看,为您找到的每个额外值 (
增加一个计数器,并为您找到的每个额外值减少一个计数器)
。当计数器为 0 并且找到 a 时)
,即是函数参数块的末尾,然后您可以解析括号之间的文本。,
您还可以在计数器为 0 时拆分文本以获取函数参数。
如果在计数器为 0 时遇到字符串结尾,则会出现错误"(" without ")"
。
然后,您可以获取左括号和右括号以及任何逗号之间的文本块,并对每个参数重复上述操作。