如何在正则表达式中指定匹配模式的优先级?

时间:2010-12-13 01:32:03

标签: c# .net regex parsing

我正在编写一个函数解析引擎,它使用正则表达式来分隔各个术语(定义为常量或变量,后跟(可选)由运算符)。它工作得很好,除非我在其他分组术语中对术语进行分组。这是我正在使用的代码:

//This matches an opening delimiter
Regex openers = new Regex("[\\[\\{\\(]");

//This matches a closing delimiter
Regex closers = new Regex("[\\]\\}\\)]");

//This matches the name of a variable (\w+) or a constant numeric value (\d+(\.\d+)?)
Regex VariableOrConstant = new Regex("((\\d+(\\.\\d+)?)|\\w+)" + FunctionTerm.opRegex + "?");

//This matches the binary operators +, *, -, or /
Regex ops = new Regex("[\\*\\+\\-/]");

//This compound Regex finds a single variable or constant term (including a proceeding operator,
//if any) OR a group containing multiple terms (and their proceeding operators, if any)
//and a proceeding operator, if any.
//Matches that match this second pattern need to be added to the function as sub-functions,
//not as individual terms, to ensure the correct evalutation order with parentheses.
Regex splitter = new Regex(
openers + 
"(" + VariableOrConstant + ")+" + closers + ops + "?" +
"|" +
"(" + VariableOrConstant + ")" + ops + "?");

当“splitter”与字符串“4 /(2 * X * [2 + 1])”匹配时,匹配的值为“4 /”,“2 *”,“X *”,“2 +“和”1“,完全忽略所有分隔括号和括号。我相信这是因为“分离器”正则表达式的后半部分(“|”之后的部分)正在匹配并覆盖模式的其他部分。这很糟糕 - 我希望分组表达式优先于单个术语。有谁知道我怎么做到这一点?我研究过使用正面/负面的前瞻和外观,但我真的不确定如何使用它们,或者它们甚至是什么,就此而言,我找不到任何相关的例子......提前谢谢

2 个答案:

答案 0 :(得分:5)

你没有告诉我们你是如何应用正则表达式的,所以这是一个我掀起的演示:

private static void ParseIt(string subject)
{
  Console.WriteLine("subject : {0}\n", subject);

  Regex openers = new Regex(@"[[{(]");
  Regex closers = new Regex(@"[]})]");
  Regex ops = new Regex(@"[*+/-]");
  Regex VariableOrConstant = new Regex(@"((\d+(\.\d+)?)|\w+)" + ops + "?");

  Regex splitter = new Regex(
    openers + @"(?<FIRST>" + VariableOrConstant + @")+" + closers + ops + @"?" +
    @"|" +
    @"(?<SECOND>" + VariableOrConstant + @")" + ops + @"?",
    RegexOptions.ExplicitCapture
  );

  foreach (Match m in splitter.Matches(subject))
  {
    foreach (string s in splitter.GetGroupNames())
    {
      Console.WriteLine("group {0,-8}: {1}", s, m.Groups[s]);
    }
    Console.WriteLine();
  }
}

输出:

subject : 4/(2*X*[2+1])

group 0       : 4/
group FIRST   :
group SECOND  : 4/

group 0       : 2*
group FIRST   :
group SECOND  : 2*

group 0       : X*
group FIRST   :
group SECOND  : X*

group 0       : [2+1]
group FIRST   : 1
group SECOND  :

正如您所看到的,术语[2+1] 与正则表达式的第一部分匹配,如您所愿。但是,对于(,它无法执行任何操作,因为之后的下一个包围角色是另一个“开启者”([),并且它正在寻找“更接近”。

可以使用.NET的“平衡匹配”功能来允许其他组中包含的分组术语,但这不值得付出努力。正则表达式不是为解析而设计的 - 实际上,解析和正则表达式匹配是根本上不同的操作类型。这是差异的一个很好的例子:正则表达式主动寻找匹配,跳过它不能使用的任何东西(比如你的例子中的开括号),但解析器必须检查每个字符(即使它只是为了决定忽略它。)

关于演示:我尝试进行必要的最小功能更改以使代码正常工作(这就是为什么我没有更正放置+ 外部捕获组),但我也进行了几次表面更改,这些更改代表了有效建议。即:

  • 在C#中创建正则表达式时,始终使用逐字字符串文字(@"...")(我认为原因很明显)。
  • 如果您正在使用捕获组,请尽可能使用命名组,但不要在同一个正则表达式中使用命名组和编号组。命名组可以省去跟踪捕获的位置的麻烦,而ExplicitCapture选项可以让您在需要非捕获组的任何地方使用(?:...)来清理正则表达式。

最后,从一堆较小的正则表达式构建大型正则表达式的整个方案对IMO的用处非常有限。跟踪部件之间的相互作用非常困难,例如哪个部件在哪个部件内。 C#的逐字字符串的另一个优点是它们是多行的,所以你可以利用自由间隔模式(a.k.a。/x或COMMENTS模式):

  Regex r = new Regex(@"
    (?<GROUPED>
      [[{(]                  # opening bracket
      (                      # group containing:
        ((\d+(\.\d+)?)|\w+)     # number or variable
        [*+/-]?                 # and proceeding operator
      )+                     # ...one or more times
      []})]                  # closing bracket
      [*+/-]?                # and proceeding operator
    )
    |
    (?<UNGROUPED>
      ((\d+(\.\d+)?)|\w+)    # number or variable
      [*+/-]?                # and proceeding operator
    )
    ",
    RegexOptions.ExplicitCapture | RegexOptions.IgnorePatternWhitespace
  );

这不是解决问题的方法;正如我所说,这不是正则表达式的工作。这只是一些有用的正则表达式技术的演示。

答案 1 :(得分:2)

尝试使用不同的量词

贪婪:

*  +  ?

所有格:

*+ ++ ?+

懒:

*? +? ??

尝试阅读thisthis

也许是非捕获组:

(?:your expr here)
尝试尝试尝试!实践使完美! :)