【问题标题】:Python Regex reading in c style comments在 c 样式注释中读取 Python 正则表达式
【发布时间】:2014-09-09 01:36:01
【问题描述】:

我试图在 c 文件中查找 c 样式的 cmets,但如果恰好在引号内有 //,我会遇到麻烦。这是文件:

/*My function
is great.*/
int j = 0//hello world
void foo(){
    //tricky example
    cout << "This // is // not a comment\n";
}

它将与该 cout 匹配。这是我目前所拥有的(我已经可以匹配 /**/ cmets)

fp = open(s)

p = re.compile(r'//(.+)')
txt = p.findall(fp.read())
print (txt)

【问题讨论】:

  • 用正则表达式解析 C 是不可行的。也许使用其他东西,比如 GCC-XML 或 CLang 前端。
  • 我把它想象成一个 txt 文件或一个字符串
  • 你的想象不会改变现实。在这种情况下,它实际上可以使用正则表达式(非常时尚,Casimir!),但在某些情况下,正则表达式要么有很多困难,要么完全无法做到(匹配括号是一个经典的例子,除非你'正在使用,比如说,Oniguruma,它有非常不规则的扩展)。这就像,“我可以用叉子吃汤吗?我把它想象成一顿饭。”你当然可以。这真的是最好的方法吗?

标签: python c regex


【解决方案1】:

第一步是确定不能将///* 解释为注释子字符串开头的情况。例如,当它们位于字符串 (引号之间) 内时。为了避免引号(或其他东西)之间的内容,诀窍是将它们放在捕获组中并在替换模式中插入反向引用:

模式:

(
    "(?:[^"\\]|\\[\s\S])*"
  |
    '(?:[^'\\]|\\[\s\S])*'
)
|
//.*
|
/\*(?:[^*]|\*(?!/))*\*/

替换:

\1

online demo

由于引用的部分是最先搜索的,每次你找到///*...*/,你可以确定你不在一个字符串中。

请注意,该模式是自愿低效的(由于(A|B)* 子模式)以使其更易于理解。为了提高效率,您可以像这样重写它:

("(?=((?:[^"\\]+|\\[\s\S])*))\2"|'(?=((?:[^'\\]+|\\[\s\S])*))\3')|//.*|/\*(?=((?:[^*]+|\*(?!/))*))\4\*/

(?=(something+))\1 只是模拟atomic group (?&gt;something+) 的一种方式

online demo

所以,如果您只想找到 cmets(而不是删除它们),最方便的方法是将模式的 cmets 部分放在捕获组中并测试它是否不为空。 (在 Jonathan Leffler 评论之后) 对以下模式进行了修改,以处理被预处理器解释为反斜杠字符的三元组 ??/(我假设代码不是为-trigraphs option) 并处理后跟换行符的反斜杠,该换行符允许在多行中格式化单行:

fp = open(s)

p = re.compile(r'''(?x)
(?=["'/])      # trick to make it faster, a kind of anchor
(?:
    "(?=((?:[^"\\?]+|\?(?!\?/)|(?:\?\?/|\\)[\s\S])*))\1" # double quotes string
  |
    '(?=((?:[^'\\?]+|\?(?!\?/)|(?:\?\?/|\\)[\s\S])*))\2' # single quotes string
  |
    (
        /(?:(?:\?\?/|\\)\n)*/(?:.*(?:\?\?|\\)/\n)*.* # single line comment
      |
        /(?:(?:\?\?/|\\)\n)*\*                       # multiline comment
        (?=((?:[^*]+|\*+(?!(?:(?:\?\?/|\\)\n)*/))*))\4
        \*(?:(?:\?\?/|\\)\n)*/             
    )
)
''')

for m in p.findall(fp.read()):
    if (m[2]):    
        print m[2]

这些更改不会影响模式效率,因为正则表达式引擎的主要工作是查找以引号或斜杠开头的位置。通过在模式 (?=["'/]) 的开头存在一个前瞻来简化此任务,它允许内部优化以快速找到第一个字符。

另一个优化是使用模拟原子组,这将回溯减少到最小,并允许在重复组内使用贪婪量词。

注意:C 中可能没有heredoc 语法!

【讨论】:

  • 非常好,涵盖了绝大多数实际的 cmets。编译器必须更加小心。它必须考虑是否处理三元组(因为反斜杠可以写成??/)。包含// 注释的行末尾的反斜杠将注释继续到下一行。此外,即使没有人会在编写代码时将/ 与第二个/* 分开一个或多个反斜杠-换行符对,但它仍然是注释的开始。 * 后跟一个或多个反斜杠换行符对,然后 / 是多行注释的结尾。咳!
  • @JonathanLeffler:很高兴知道,我将尝试构建一个模式来处理这些语法特殊性。三元组也可以在字符串中解释吗?
  • @CasimiretHippolyte..只问一个问题,这是更快 (?=["'/]) 还是更快 (?=.*?["'/]),为什么?谢谢
  • @walidtoumi:将(?=["'/])放在模式的开头是一种称为“首字符识别”的技术。目标是限制字符串中将测试模式的位置,以显示可能的第一个字符 (此处为 "'/。在优化阶段(称为transmission)会进行快速搜索以查找这些字符的位置。一旦完成,整个模式只在这些位置进行测试。使用这个技巧,您可以跳过正则表达式引擎的默认行为,即从左到右测试字符串中的所有位置。
  • @walidtoumi: 不是错字,前面还有第三组,包含两个评论说明。
【解决方案2】:

Python 的re.findall 方法基本上与大多数词法分析器的工作方式相同:它连续返回从前一个匹配完成的位置开始的最长匹配。所需要的只是产生所有词汇模式的析取:

(<pattern 1>)|(<pattern 2>)|...|(<pattern n>)

与大多数词法分析器不同,它不需要匹配是连续的,但这并不是一个显着的区别,因为您总是可以添加 (.) 作为最后一个模式,以便单独匹配所有其他不匹配的字符。

re.findall 的一个重要特性是,如果正则表达式有任何组,则只会返回组。因此,您可以通过简单地省略括号或将它们更改为非捕获括号来排除替代方案:

(<pattern 1>)|(?:<unimportant pattern 2>)|(<pattern 3)

考虑到这一点,让我们看看如何将 C 标记为足以识别 cmets。我们需要处理:

  1. 单行cmets:// Comment
  2. 多行cmets:/* Comment */
  3. 双引号字符串:"Might include escapes like \n"
  4. 单引号字符:'\t'
  5. (见下文,了解更多令人不快的案例)

考虑到这一点,让我们为上述每个创建正则表达式。

  1. 两个斜杠后跟除换行符以外的任何内容://[^\n]*
  2. 这个正则表达式解释起来很乏味:/*[^*]*[*]+(?:[^/*][^*]*[*]+)*/ 请注意,它使用(?:...) 来避免捕获重复的组。
  3. 引号,除引号和反斜杠之外的字符的任何重复,或反斜杠后跟任何字符。这不是转义序列的精确定义,但它足以检测 " 何时终止字符串,这是我们所关心的:"(?:[^"\\]|\\.*)"
  4. 与 (3) 相同,但带有单引号:'(?:[^'\\]|\\.)*'

最后,目标是找到 C 风格的 cmets 的文本。所以我们只需要避免在任何其他组中捕获。因此:

p = re.compile('|'.join((r"(//[^\n])*"
                        ,r"/*[^*]*[*]+(?:[^/*][^*]*[*]+)*/"
                        ,'"'+r"""(?:[^"\\]|\\.)*"""+'"'
                        ,r"'(?:[^'\\]|\\.)*'")))
return [c[2:] for c in p.findall(text) if c]

上面,我省略了一些不太可能出现的模糊案例:

  1. #include &lt;...&gt; 指令中,&lt;...&gt; 本质上是一个字符串。理论上,它可以包含看起来像 cmets 的引号或序列,但实际上你永远不会看到:

    #include </*This looks like a comment but it is a filename*/>
    
  2. \ 结尾的行在下一行继续; \ 和后面的换行符只是从输入中删除。这发生在任何词法扫描之前,因此以下是完全合法的注释(实际上是两个 cmets):

    /\
    **************** Surprise! **************\
    //////////////////////////////////////////
    
  3. 更糟糕的是,三元组??/\ 相同,并且替换发生在继续处理之前。

    /************************************//??/
    **************** Surprise! ************??/
    //////////////////////////////////////////
    

    在混淆竞赛之外,没有人真正使用三元组。但它们仍然符合标准。处理这两个问题的最简单方法是预扫描字符串:

    return [c[2:]
            for c in p.findall(text.replace('//?','\\').replace('\\\n',''))
            if c]
    

处理#include &lt;...&gt; 问题的唯一方法是,如果您真的关心它,那就是再添加一个模式,例如#define\s*&lt;[^&gt;\n]*&gt;

【讨论】:

  • “没有人真正使用三元组”:现在,我每天都会写一个三元组。
猜你喜欢
  • 2011-11-25
  • 1970-01-01
  • 2013-04-16
  • 2014-04-18
  • 2010-10-02
  • 2014-07-26
  • 1970-01-01
  • 1970-01-01
  • 2015-02-25
相关资源
最近更新 更多