gpt4 book ai didi

c# - 正则表达式,用于匹配函数并捕获其参数

转载 作者:IT王子 更新时间:2023-10-29 04:53:13 24 4
gpt4 key购买 nike

我正在使用计算器,它需要字符串表达式并对它们求值。我有一个函数,可以使用Regex在表达式中搜索数学函数,检索参数,查找函数名称,然后对其求值。我遇到的问题是,只有在我知道会有多少论点时,我才能做到这一点,而我无法正确使用正则表达式。而且,如果我仅将()字符的内容除以,字符,那么该参数中将无法进行其他函数调用。

这是函数匹配模式:\b([a-z][a-z0-9_]*)\((..*)\)\b
它仅适用于一个参数,是否可以为每个参数(嵌套函数内部的参数除外)创建一个组?例如,它将匹配:func1(2 * 7, func2(3, 5))并为以下位置创建捕获组:2 * 7func2(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_]*)\((..*)\)");

如您所见,在构建正则表达式以匹配参数方面存在相当糟糕的尝试。没用

我要做的就是从 2 * 7表达式中提取 func2(3, 5)func1(2 * 7, func2(3, 5)),但它也必须对具有不同参数计数的函数起作用。如果有一种方法可以不使用Regex而这样做,那也很好。

最佳答案

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

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

\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参数。注意+吗?因此匹配是懒惰的,并在遇到的第一个()处停止。
  • |([^,]+)如果前一个字符不匹配,则匹配不是逗号的连续字符。这些比赛分为几组。

  • 更高级的解决方案

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

    因此,现在可以提取正则表达式的正则表达式(func提取可以保持不变):
    (?:[^,()]+((?:\((?>[^()]+|\((?<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>))*\)))*)+

    总之,它以非逗号或方括号开头的字符开头。然后,如果参数中包含方括号,它将匹配并减去方括号,直到它们平衡为止。然后,如果参数中还有其他函数,它将尝试重复该匹配。然后,它转到下一个参数(在逗号之后)。详细地:
  • [^,()]+与非',()'的任何内容匹配
  • ?:表示不捕获组,即不将匹配项存储在组的方括号内。
  • \(表示从一个开放的括号开始。
  • ?>表示atomic grouping-本质上,这意味着它不记得回溯位置。这也有助于提高性能,因为尝试不同组合的后退次数更少。
  • [^()]+|表示除左括号或右括号之外的任何内容。其次是| (或)
  • \((?<open>)|这是好东西,说匹配'('或
  • (?<-open>)这是更好的东西,它说匹配')'并平衡'('。这意味着匹配的这一部分(第一个括号之后的所有内容)将持续到所有内部括号都匹配为止。 ,则匹配将在第一个右方括号中完成。关键是引擎不匹配此')'和最后一个')',而是从匹配'('中减去它。当没有其他未完成的'( ',-open失败,因此可以匹配最后的')'。
  • 其余正则表达式包含该组的右括号和重复(和+),分别是:重复将内括号匹配0次或更多次,将全括号搜索重复0次或更多次(0允许不带括号的参数)并重复完整匹配1次或更多次(允许foo(1)+ foo(2))

  • 最后的装饰:

    如果将 (?(open)(?!))添加到正则表达式中:
    (?:[^,()]+((?:\((?>[^()]+|\((?<open>)|\)(?<-open>))*(?(open)(?!))\)))*)+

    (?!)如果open捕获了某些东西(没有被减去),则总是会失败,即,如果有一个没有括号的开括号,它将总是失败。这是测试平衡是否失败的有用方法。

    一些注意事项:

    当最后一个字符是')'时
  • \b将不匹配,因为它不是单词字符和\b tests for word character boundaries,因此您的正则表达式将不匹配。
  • 尽管正则表达式功能强大,但是除非您是专家中的专家,否则最好使表达式保持简单,因为否则它们将难以维护且难以被其他人理解。这就是为什么有时最好将问题分解为子问题和更简单的表达式,然后让该语言执行其擅长的一些非搜索/匹配操作。因此,根据您的舒适程度,您可能希望将简单的正则表达式与更复杂的代码混合使用,反之亦然。
  • 这将匹配一些非常复杂的函数,但它不是函数的词法分析器。
  • 如果参数中可以包含字符串,并且字符串本身可以包含方括号,例如“go(...”,那么您将需要修改正则表达式以将字符串从比较中删除。与注释相同。
  • 用于平衡组定义的一些链接:herehereherehere

  • 希望能有所帮助。

    关于c# - 正则表达式,用于匹配函数并捕获其参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18906514/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com