Python正则表达式阅读c风格的评论

时间:2014-09-09 01:36:01

标签: python c regex

我试图在c文件中找到c样式的注释但是如果碰巧在引号内部有问题。这是文件:

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

它将与那个cout匹配。这是我到目前为止(我可以匹配/ ** /评论)

fp = open(s)

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

2 个答案:

答案 0 :(得分:4)

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

图案:

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

替换:

\1

online demo

由于引用的部分首先进行搜索,因此每次找到///*...*/时,您都可以确定自己不在字符串中。

请注意,该模式是自愿低效的,以便更容易理解,为了提高效率,您可以像这样重写它:

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

(?=(something+))\1只是模仿atomic group (?>something+)

的一种方法

online demo

因此,如果您只想查找注释(而不是删除它们),最方便的是将注释的一部分放在捕获组中并测试它是否为空。以下模式已被(在Jonathan Leffler评论之后)用于处理由预处理器解释为反斜杠字符的三字组??/(我假设代码不是&#39) ;为-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语法!

答案 1 :(得分:1)

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

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

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

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

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

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

  1. 单行评论:// Comment
  2. 多行评论:/* Comment */
  3. 双引号字符串:"Might include escapes like \n"
  4. 单引号字符:'\t'
  5. (参见下面的一些刺激性案例)
  6. 考虑到这一点,让我们为上面的每一个创建regexen。

    1. 两个斜杠后跟换行符以外的任何内容://[^\n]*
    2. 这个正则表达式很难解释:/*[^*]*[*]+(?:[^/*][^*]*[*]+)*/ 请注意,它使用(?:...)来避免捕获重复的组。
    3. 引用,除了引号和反斜杠之外的任何字符的重复,或任何字符后面的反斜杠。这不是转义序列的精确定义,但它足以检测何时终止字符串,这就是我们所关心的:"(?:[^"\\]|\\.*)"
    4. 与(3)相同,但使用单引号:'(?:[^'\\]|\\.)*'
    5. 最后,目标是找到C风格评论的文本。所以我们只需要避免在任何其他组中捕获。因此:

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

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

      1. #include <...>指令中,<...>本质上是一个字符串。从理论上讲,它可能包含看起来像评论的引号或序列,但在实践中你永远不会看到:

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

        /\
        **************** Surprise! **************\
        //////////////////////////////////////////
        
      3. 为了使上述情况更糟,三元组??/ \ 相同,并且在继续处理之前发生了替换。

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

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

        return [c[2:]
                for c in p.findall(text.replace('//?','\\').replace('\\\n',''))
                if c]
        
      4. 处理#include <...>问题的唯一方法是,如果您真的关心它,可以添加一个模式,例如#define\s*<[^>\n]*>