用于删除C和C ++注释的Python代码段

时间:2008-10-27 20:47:24

标签: c++ python c regex comments

我正在寻找从字符串中删除C和C ++注释的Python代码。 (假设字符串包含整个C源文件。)

我意识到我可以使用正则表达式来匹配.match()子串,但这不能解决嵌套/*//内的/* */

理想情况下,我更喜欢一种能够正确处理尴尬案例的非天真实现。

13 个答案:

答案 0 :(得分:80)

它处理C ++风格的注释,C风格的注释,字符串和简单的嵌套。

def comment_remover(text):
    def replacer(match):
        s = match.group(0)
        if s.startswith('/'):
            return " " # note: a space and not an empty string
        else:
            return s
    pattern = re.compile(
        r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
        re.DOTALL | re.MULTILINE
    )
    return re.sub(pattern, replacer, text)

需要包含字符串,因为其中的注释标记不会启动注释。

编辑: re.sub没有带任何标志,所以必须先编译模式。

Edit2:添加了字符文字,因为它们可能包含引号,否则会被识别为字符串分隔符。

编辑3:修复了合法表达式int/**/x=5;将成为intx=5;且无法编译的情况,方法是将注释替换为空格而不是空字符串。< / p>

答案 1 :(得分:25)

C(和C ++)注释不能嵌套。正则表达式运作良好:

//.*?\n|/\*.*?\*/

这需要“单行”标记(Re.S),因为C注释可以跨越多行。

def stripcomments(text):
    return re.sub('//.*?\n|/\*.*?\*/', '', text, flags=re.S)

此代码应该有效。

/编辑:请注意,我的上述代码实际上是关于行结尾的假设!此代码不适用于Mac文本文件。但是,这可以相对容易地修改:

//.*?(\r\n?|\n)|/\*.*?\*/

这个正则表达式应该适用于所有文本文件,无论它们的行结尾如何(包括Windows,Unix和Mac行结尾)。

/编辑:MizardX和Brian(在评论中)对字符串的处理做了有效的评论。我完全忘记了这一点,因为上面的正则表达式是从一个解析模块中提取出来的,它对字符串有额外的处理。 MizardX的解决方案应该可以很好地工作,但它只处理双引号字符串。

答案 2 :(得分:7)

我不知道您是否熟悉sed,基于UNIX(但可用Windows)的文本解析程序,但我发现了一个sed脚本here,它将删除来自文件的C / C ++注释。它很聪明;例如,如果在字符串声明中找到它,它将忽略'//'和'/ *'等。在Python中,可以使用以下代码来使用它:

import subprocess
from cStringIO import StringIO

input = StringIO(source_code) # source_code is a string with the source code.
output = StringIO()

process = subprocess.Popen(['sed', '/path/to/remccoms3.sed'],
    input=input, output=output)
return_code = process.wait()

stripped_code = output.getvalue()

在这个程序中,source_code是包含C / C ++源代码的变量,最终stripped_code将保存带有注释的C / C ++代码。当然,如果您将文件放在磁盘上,那么inputoutput变量可以是指向这些文件的文件句柄(input处于读取模式,output在写模式下)。 remccoms3.sed是上述链接中的文件,应保存在磁盘上的可读位置。 sed也可在Windows上使用,默认安装在大多数GNU / Linux发行版和Mac OS X上。

这可能比纯Python解决方案更好;无需重新发明轮子。

答案 3 :(得分:6)

不要忘记在C中,在处理注释之前消除了反斜杠换行符,并且在此之前处理了三字符(因为?? /是反斜杠的三字符)。我有一个名为SCC的C程序(条带C / C ++注释),这是测试代码的一部分......

" */ /* SCC has been trained to know about strings /* */ */"!
"\"Double quotes embedded in strings, \\\" too\'!"
"And \
newlines in them"

"And escaped double quotes at the end of a string\""

aa '\\
n' OK
aa "\""
aa "\
\n"

This is followed by C++/C99 comment number 1.
// C++/C99 comment with \
continuation character \
on three source lines (this should not be seen with the -C fla
The C++/C99 comment number 1 has finished.

This is followed by C++/C99 comment number 2.
/\
/\
C++/C99 comment (this should not be seen with the -C flag)
The C++/C99 comment number 2 has finished.

This is followed by regular C comment number 1.
/\
*\
Regular
comment
*\
/
The regular C comment number 1 has finished.

/\
\/ This is not a C++/C99 comment!

This is followed by C++/C99 comment number 3.
/\
\
\
/ But this is a C++/C99 comment!
The C++/C99 comment number 3 has finished.

/\
\* This is not a C or C++  comment!

This is followed by regular C comment number 2.
/\
*/ This is a regular C comment *\
but this is just a routine continuation *\
and that was not the end either - but this is *\
\
/
The regular C comment number 2 has finished.

This is followed by regular C comment number 3.
/\
\
\
\
* C comment */

这并不是说明三字母。请注意,在行的末尾可以有多个反斜杠,但是行拼接并不关心有多少反斜杠,但后续处理可能会。等等。编写单个正则表达式以处理所有这些情况将是非平凡的(但这是不可能的)。

答案 4 :(得分:6)

这篇帖子提供了一个编码出来的版本,改进了Markus Jarderot的代码,由atikat在Markus Jarderot发表的评论中描述。 (感谢两者提供原始代码,这为我节省了很多工作。)

更全面地描述改进:改进使线路编号完好无损。 (这是通过在替换C / C ++注释的字符串中保持换行符完整来完成的。)

如果您想为用户生成包含行号的错误消息(例如解析错误)(即对原始文本有效的行号),则此版本的C / C ++注释删除功能非常适用。

import re

def removeCCppComment( text ) :

    def blotOutNonNewlines( strIn ) :  # Return a string containing only the newline chars contained in strIn
        return "" + ("\n" * strIn.count('\n'))

    def replacer( match ) :
        s = match.group(0)
        if s.startswith('/'):  # Matched string is //...EOL or /*...*/  ==> Blot out all non-newline chars
            return blotOutNonNewlines(s)
        else:                  # Matched string is '...' or "..."  ==> Keep unchanged
            return s

    pattern = re.compile(
        r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
        re.DOTALL | re.MULTILINE
    )

    return re.sub(pattern, replacer, text)

答案 5 :(得分:4)

正则表达式案例在某些情况下会失败,例如字符串文字包含与注释语法匹配的子序列。你真的需要一个解析树来处理这个问题。

答案 6 :(得分:3)

您可以利用py++来解析使用GCC的C ++源代码。

  

Py ++不会重新发明轮子。它   使用GCC C ++编译器来解析C ++   源文件。更准确地说,   工具链看起来像这样:

     

源代码传递给GCC-XML   GCC-XML将它传递给GCC C ++编译器   GCC-XML生成XML描述   来自GCC内部的C ++程序   表示。 Py ++使用pygccxml   包读取GCC-XML生成   文件。底线 - 你可以   当然,你所有的声明都是   正确阅读。

或者,也许不是。无论如何,这不是一个简单的解析。

基于@RE的解决方案 - 除非您限制输入(例如,没有宏),否则您不太可能找到正确处理所有可能“尴尬”情况的RE。对于一个防弹解决方案,你真的别无选择,只能利用真正的语法。

答案 7 :(得分:1)

我很抱歉这不是Python解决方案,但你也可以使用一个了解如何删除注释的工具,比如你的C / C ++预处理器。以下是GNU CPP does it

的方法
cpp -fpreprocessed foo.c

答案 8 :(得分:1)

还有一个非python答案:使用程序stripcmt

  

StripCmt是一个简单的实用程序   在C中删除C,C ++中的注释,   和Java源文件。在盛大   Unix文本处理的传统   程序,它可以作为一个   FIFO(先进先出)滤波器或   接受命令行上的参数。

答案 9 :(得分:1)

以下对我有用:

from subprocess import check_output

class Util:
  def strip_comments(self,source_code):
    process = check_output(['cpp', '-fpreprocessed', source_code],shell=False)
    return process 

if __name__ == "__main__":
  util = Util()
  print util.strip_comments("somefile.ext")

这是子进程和cpp预处理器的组合。对于我的项目,我有一个名为“Util”的实用程序类,我保留了我使用/需要的各种工具。

答案 10 :(得分:0)

你真的不需要一个解析树来完美地完成这个任务,但实际上你需要的令牌流等同于编译器前端产生的令牌流。这样的令牌流必须处理所有奇怪的事情,例如行继续注释开始,字符串中的注释开始,三字符规范化等。如果您有令牌流,则删除注释很容易。 (我有一个工具可以产生完全相同的令牌流,因为,猜猜是什么,一个真正的解析器的前端产生一个真正的解析树:)。

令牌被正则表达式单独识别的事实表明,原则上你可以编写一个正则表达式来挑选注释词。令牌化器的正则表达式的真正复杂性(至少是我们写的那个)表明你不能在实践中这样做;单独写它们很难。如果你不想完美地做到这一点,那么,上面的大多数RE解决方案都很好。

现在,除非您正在构建代码混淆器,否则为什么您希望条带注释超出我的范围。在这种情况下,你必须完全正确。

答案 11 :(得分:0)

我已经使用pygments解析该字符串,然后忽略了所有来自其注释的标记。在pygments列表上的所有词法分析器(包括Javascript,SQL和C Like)上,都可以像超级按钮一样工作。

from pygments import lex
from pygments.token import Token as ParseToken

def strip_comments(replace_query, lexer):
    generator = lex(replace_query, lexer)
    line = []
    lines = []
    for token in generator:
        token_type = token[0]
        token_text = token[1]
        if token_type in ParseToken.Comment:
            continue
        line.append(token_text)
        if token_text == '\n':
            lines.append(''.join(line))
            line = []
    if line:
        line.append('\n')
        lines.append(''.join(line))
    strip_query = "\n".join(lines)
    return strip_query

使用类似C的语言

from pygments.lexers.c_like import CLexer

strip_comments("class Bla /*; complicated // stuff */ example; // out",CLexer())
# 'class Bla  example; \n'

使用SQL语言:

from pygments.lexers.sql import SqlLexer

strip_comments("select * /* this is cool */ from table -- more comments",SqlLexer())
# 'select *  from table \n'

使用类似Java语言的语言

from pygments.lexers.javascript import JavascriptLexer
strip_comments("function cool /* not cool*/(x){ return x++ } /** something **/ // end",JavascriptLexer())
# 'function cool (x){ return x++ }  \n'

由于此代码仅删除注释,因此将保留任何奇怪的值。因此,这是一个非常强大的解决方案,甚至可以处理无效的输入。

答案 12 :(得分:-1)

我最近遇到了这个问题,当时我上课时教授要求我们从源代码中删除javadoc,然后再将其提交给他进行代码审查。我们不得不多次这样做,但我们不能永久删除javadoc,因为我们还需要生成javadoc html文件。这是我做的一个小蟒蛇脚本。由于javadoc以/ **开头并以* /结尾,因此脚本会查找这些标记,但可以修改脚本以满足您的需求。它还处理单行块注释和块注释结束的情况,但在块注释结束的同一行上仍有未注释的代码。我希望这有帮助!

警告:此脚本会修改传入的文件的内容并将其保存到原始文件中。在其他地方备份

是明智的
#!/usr/bin/python
"""
 A simple script to remove block comments of the form /** */ from files
 Use example: ./strip_comments.py *.java
 Author: holdtotherod
 Created: 3/6/11
"""
import sys
import fileinput

for file in sys.argv[1:]:
    inBlockComment = False
    for line in fileinput.input(file, inplace = 1):
        if "/**" in line:
            inBlockComment = True
        if inBlockComment and "*/" in line:
            inBlockComment = False
            # If the */ isn't last, remove through the */
            if line.find("*/") != len(line) - 3:
                line = line[line.find("*/")+2:]
            else:
                continue
        if inBlockComment:
            continue
        sys.stdout.write(line)