【问题标题】:Improving/Fixing a Regex for C style block comments改进/修复 C 样式块注释的正则表达式
【发布时间】:2010-10-02 13:08:27
【问题描述】:

我正在(用 C# 编写)一个简单的解析器来处理一种看起来很像经典 C 的脚本语言。

在我拥有的一个脚本文件中,我用来识别 /* block cmets */ 的正则表达式正在进入某种无限循环,占用 100% 的 CPU 很长时间。

我使用的正则表达式是这样的:

/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/

关于为什么这可能会被锁定的任何建议?

或者,我可以使用什么其他正则表达式来代替?

更多信息:

  • 在面向 .NET 3.5 的 C# 3.0 中工作;
  • 我正在使用 Regex.Match(string,int) 方法从字符串的特定索引处开始匹配;
  • 我已经让程序运行了一个多小时,但比赛还没有完成;
  • 传递给 Regex 构造函数的选项是 RegexOptions.MultilineRegexOptions.IgnorePatternWhitespace
  • 正则表达式适用于我的 453 个测试文件中的 452 个。

【问题讨论】:

  • 以这种方式使用正则表达式很困难。如何区分注释和包含与注释相同语法的序列的字符串?
  • 在他的《掌握正则表达式》一书中,Jeffrey Friedl 解释了人们认为几乎不可能获得一个涵盖所有 /* 引用字符串组合的 C 注释查找器,反之亦然,等等。但他接着描述了一个复杂的正则表达式。
  • @Gumbo - 我使用这个正则表达式来检查从已知索引点开始的评论,而不是文本中的任何地方。当我找到一个字符串或评论时,我会跳过整个跨度,然后再检查另一个匹配项。

标签: c# c regex parsing comments


【解决方案1】:

我看到你的正则表达式的一些问题:

您的正则表达式中不需要|[\r\n] 序列;像[^*] 这样的否定字符类匹配除* 之外的所有内容,包括行分隔符。只有.(点)元字符与那些不匹配。

进入评论后,您唯一需要寻找的字符就是星号;只要您没有看到其中之一,您就可以吞噬任意数量的角色。这意味着当您可以使用 [^*]+ 时,使用 [^*] 是没有意义的。事实上,你不妨把它放在一个原子组中——(?>[^*]+)——因为一旦你匹配了这些非星号,你将永远没有任何理由放弃它们。

过滤掉无关的垃圾,最外层括号内的最终选择是\*+[^*/],这意味着“一个或多个星号,后跟一个不是星号或斜杠的字符”。这将始终与注释末尾的星号匹配,并且总是不得不再次放弃它,因为下一个字符是斜杠。事实上,如果在最后一个斜线之前有 20 个星号,那么你的正则表达式的那一部分将匹配它们,然后它会一个接一个地放弃它们。然后最后一部分——\*+/——将匹配它们以保持。

为了获得最佳性能,我会使用这个正则表达式:

/\*(?>(?:(?>[^*]+)|\*(?!/))*)\*/

这将很快匹配格式良好的评论,但更重要的是,如果它开始匹配不是有效评论的内容,它将尽快失败。


感谢David,这是一个匹配任何嵌套级别的嵌套 cmets 的版本:

(?s)/\*(?>/\*(?<LEVEL>)|\*/(?<-LEVEL>)|(?!/\*|\*/).)+(?(LEVEL)(?!))\*/

它使用 .NET 的平衡组,因此它不会以任何其他方式工作。为了完整起见,这是另一个版本(来自 RegexBuddy 的库),它使用 Perl、PCRE 和 Oniguruma/Onigmo 支持的递归组语法:

/\*(?>[^*/]+|\*[^/]|/[^*])*(?>(?R)(?>[^*/]+|\*[^/]|/[^*])*)*\*/

【讨论】:

  • 谢谢艾伦,这看起来像是我需要的——虽然我需要研究一下以确保我理解它!我试过了会回来报告的。
  • 道歉 - 忘记及时报告。是的,这就是我需要的。感谢您的帮助。
  • 这不适用于嵌套的 C 样式 cmets。它会在第一次出现 */ 时中断,而不考虑之前看到过另一个 /*。
  • @David:你知道一种支持嵌套 cmets 的语言吗?
  • @David:我已将您的正则表达式添加到答案中,它更具可读性。我一直认为嵌套 cmets 只是一个神话。 :-/
【解决方案2】:

我现在正在使用这个

\/\*[\s\S]*?\*\/

【讨论】:

    【解决方案3】:

    不不不!没有其他人读过掌握正则表达式(第 3 版)!?在这篇文章中,Jeffrey Friedl 研究了这个确切的问题并将其用作示例(第 272-276 页)来说明他的“展开循环”技术。他对大多数正则表达式引擎的解决方案是这样的:

    /\*[^*]*\*+(?:[^*/][^*]*\*+)*/

    但是,如果正则表达式引擎经过优化以处理惰性量词(就像 Perl 的那样),那么最有效的表达式会简单得多(如上所述):

    /\*.*?\*/

    (当然应用了等效的 's' “点匹配所有”修饰符。) 请注意,我不使用 .NET,所以我不能说哪个版本的引擎更快。

    【讨论】:

    • +1,在超过 7 个关于该主题的 stackoverflow 问题的几种可能性中,这是唯一一个真正有效的问题!
    • +1 尽管我不明白在最初的/\* 之后发生的事情。我只关心它是否成功匹配 cmets。
    【解决方案4】:

    您可能想尝试使用 Singleline 而不是 Multiline 选项,那么您不必担心 \r\n。启用该功能后,以下对我有用的简单测试包括跨越多行的 cmets:

    /\*.*?\*/
    

    【讨论】:

    • 但是正则表达式匹配器的贪婪匹配是否意味着这将跨越从/*打开第一个评论*/到*/关闭最后一个*/?
    • 那个?使 .* 不贪婪或懒惰,如此处所述:regular-expressions.info/repeat.html#lazy
    【解决方案5】:

    我觉得你的表达方式太复杂了。应用于大字符串,许多替代方案意味着很多回溯。我想这就是你看到的性能影响的来源。

    如果基本假设是匹配从"/*" 直到遇到第一个"*/" 的所有内容,那么一种方法就是这样做(像往常一样,正则表达式不适合嵌套结构,因此嵌套块 cmets不起作用):

    /\*(.(?!\*/))*.?\*/             // run this in single line (dotall) mode
    

    基本上是这样写的:"/*",后面是任何本身没有跟在"*/"之后的东西,然后是"*/"

    或者,您可以使用更简单的:

    /\*.*?\*/                       // run this in single line (dotall) mode
    

    像这样的非贪婪匹配在极端情况下可能会出错 - 目前我想不出这个表达式可能会失败,但我不完全确定。

    【讨论】:

      猜你喜欢
      • 2013-04-16
      • 2011-11-25
      • 1970-01-01
      • 1970-01-01
      • 2020-02-07
      • 2014-04-18
      • 2014-07-26
      • 1970-01-01
      • 2014-12-17
      相关资源
      最近更新 更多