繁体   English   中英

在 c 样式注释中读取 Python 正则表达式

[英]Python Regex reading in c style comments

我试图在 ac 文件中找到 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)

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

图案:

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

替代品:

\1

在线演示

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

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

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

(?=(something+))\1只是模拟原子组(?>something+)的一种方式

在线演示

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

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 语法!

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. (请参阅下面的一些更令人恼火的案例)

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

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

最后,目标是找到 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]

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM