【问题标题】:Find a pattern, but not within a C++ comment查找模式,但不在 C++ 注释中
【发布时间】:2019-01-23 21:11:30
【问题描述】:

我有一个正则表达式,用于搜索大型代码库以查找用作类型或变量的特定标记的使用情况。假设令牌是“foo”,我想单独找到它。

我最初的正则表达式是这样的:

foo$|foo\s|foo\[|foo\*|<foo|foo>

匹配:foo 在行尾,foo 带有空格,foo 指针,foo 在集合中,等等...

我想排除 C++ 注释块中的实例。比如下面的例子。

// consume the foo and read another.

我尝试使用负前瞻来修改正则表达式,但这似乎不起作用

(?!\/\/).*(foo$|foo\s|foo\[|foo\*|<foo|foo>)

有人知道如何在正则表达式中执行此操作吗?

更新:

我只是想随便过滤掉在目标模式之前可能有两个正斜杠的行。我不关心嵌套 cmets、C 样式 cmets (/* */) 或任何跨越多行的内容。

【问题讨论】:

  • 这很重要。为了可靠地检测 cmets,您还必须同时识别字符和字符串文字。基本上你需要做的是编写一个完整的 C++ 词法分析器。
  • 顺便问一下,C++ 还支持三元组吗?

标签: regex regex-negation


【解决方案1】:

这是您所要求的相当全面的正则表达式(在 Perl 中测试):

my $foo_regex = qr{
    \G
    (?>
        # // comment
        / (?: \\ \n )*+ / (?> \\ \n | [^\n] )*+
    |
        # /* comment */
        / (?: \\ \n )*+ \* (?> .*? \* (?: \\ \n )*+ / )
    |
        # 'c'
        ' (?: [^'\\\n] | \\ . )++ '
    |
        # "string"
        " (?: [^"\\\n] | \\ . )*+ "
    |
        # R"(raw string)"
        \b
        (?: (?> [LU] | u (?: \\ \n )*+ 8?+ ) (?: \\ \n )*+ )?+
        R
        (?: \\ \n )*+
        "
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        ( [^()\\\s]?+ )
        (?: \\ \n )*+
        \(
        (?>
            .*?
            \)
            (?: \\ \n )*+
            \g{-16}
            (?: \\ \n )*+
            \g{-15}
            (?: \\ \n )*+
            \g{-14}
            (?: \\ \n )*+
            \g{-13}
            (?: \\ \n )*+
            \g{-12}
            (?: \\ \n )*+
            \g{-11}
            (?: \\ \n )*+
            \g{-10}
            (?: \\ \n )*+
            \g{-9}
            (?: \\ \n )*+
            \g{-8}
            (?: \\ \n )*+
            \g{-7}
            (?: \\ \n )*+
            \g{-6}
            (?: \\ \n )*+
            \g{-5}
            (?: \\ \n )*+
            \g{-4}
            (?: \\ \n )*+
            \g{-3}
            (?: \\ \n )*+
            \g{-2}
            (?: \\ \n )*+
            \g{-1}
            (?: \\ \n )*+
            "
        )
    |
        # / (not starting a comment)
        / (?! (?: \\ \n )*+ [/*] )
    |
        # identifier
        \w (?: (?: \\ \n )*+ \w )*+
    |
        # arbitrary other character
        [^/"'\w]
    )*?
    \b
    (
        f
        (?: \\ \n )*+
        o
        (?: \\ \n )*+
        o
    )
    (?!
        (?: \\ \n )*+
        \w
    )
}xms;

它考虑的并发症概述:

  • "foo"'foo'// foo/* foo */ 不是出现foo,而是分别出现的字符串文字、多字符常量、单行注释和块注释。
  • /* " */// "" /* "'//' 等分别是注释、注释、字符串文字和多字符常量。这意味着您不能分阶段过滤掉字符串文字、cmets 等;您必须一次全部解析它们,以避免将带引号的构造的内容误认为是另一个带引号的构造的分隔符。
  • 必须忽略反斜杠换行符组合(就好像它们在源文件中不存在一样):

      /\
      * this is a comment */
      /\
      / and so is this
      foo\
      bar  // this is a single identifier, 'foobar'
      f\
      oo  // ... but this is 'foo'
      "this is a string\\
      " <- that's not the end of the string; this is: "
    
  • 这个正则表达式的很大一部分处理形式为R"delim(...)delim" 的原始字符串文字以及可以散布在任何地方的任意反斜杠换行符对。幸运的是,C++ 指定了最多 16 个自定义分隔符的上限;否则我们将不得不使用运行时代码执行/动态正则表达式生成。
  • 不处理三元组。如果您想添加支持,首先将正则表达式中出现的\\ 更改为(?&gt; \\ | \?\?/ )

更新:为了您的简化要求(在字符串中找到前面没有// 的单词foo),您可以简单地执行^(?:[^/]|/(?!/))*?\bfoo\b

【讨论】:

  • 谢谢。你更新中的后一个表达是我所追求的。
【解决方案2】:

正则表达式不是最好的工具。

我已经编写了一个 C 到 Delphi 转换器 (https://github.com/WouterVanNifterick/C-To-Delphi),我确实在某些任务中使用了正则表达式,但我的结论是,正则表达式并不是你想要做的事情的正确工具。 我可以说出来,因为我已经尝试过了,并决定放弃正则表达式,因为事情变得过于复杂并且事情无法可靠地工作。

您可以快速创建适用于 90% 情况的内容,但如果您想正确处理嵌套的 cmets 或看起来像 cmets 的字符串,解析是唯一的选择。

您不需要完整的 C++ 解析器。您需要遍历所有字符,并跟踪您是否在 /* */ 块、"" 字符串块或 // 部分中,并执行您需要执行的操作。

【讨论】:

  • C++ 没有嵌套的 cmets。 (我什至认为“嵌套评论”是矛盾的。)
  • 什么意思?您可以在块 cmets 中拥有看起来像 line cmets 的东西。你可以将它包含在一个字符串中。你可以在它周围放置新的块 cmets :) 关键是:这不是你想要用正则表达式做的事情。
  • 让我设定期望。我只是想随便过滤掉在目标模式之前可能有两个正斜杠的行。我不关心嵌套 cmets、C 样式 cmets (/* */) 或任何跨越多行的内容。
  • @Selbie:我知道您并不特别关心字符串和嵌套的 cmets,但事实是您不能忽略它们。您将要使用的正则表达式会将以 // 开头的 anything 视为行注释,这显然是不正确的。例如,它可能是字符串的一部分。
  • 我应该非常明确地说明我想要完成的事情。我真的不应该提到“C++”,因为它吸引了很多不同的受众和心态。我应该说的是:“如果模式出现在同一行上的一对正斜杠之后,我如何扩展正则表达式使其不匹配。”
猜你喜欢
  • 2022-01-23
  • 1970-01-01
  • 2020-02-07
  • 1970-01-01
  • 2011-07-19
  • 2012-10-09
  • 2015-01-06
  • 2011-12-06
  • 1970-01-01
相关资源
最近更新 更多