48

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

我意识到我可以使用正则表达式 .match() 子字符串,但这并不能解决嵌套问题/*,或者///* */.

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

4

13 回答 13

98

它处理 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:添加了字符文字,因为它们可能包含否则会被识别为字符串分隔符的引号。

Edit3:通过用空格而不是空字符串替换注释,修复了无法编译的合法表达式的情况int/**/x=5;intx=5;

于 2008-10-27T21:48:07.737 回答
29

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 的解决方案应该工作得很好,但它只处理双引号字符串。

于 2008-10-27T20:48:57.650 回答
7

不要忘记,在 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 */

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

于 2008-10-28T02:57:06.863 回答
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)
于 2013-08-14T14:36:25.220 回答
5

我不知道您是否熟悉sed基于 UNIX(但 Windows 可用)的文本解析程序,但我在这里找到了一个 sed 脚本,它将从文件中删除 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 解决方案更好;无需重新发明轮子。

于 2008-10-28T04:03:20.753 回答
4

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

于 2008-10-28T02:58:24.957 回答
3

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

Py++ 不会重新发明轮子。它使用 GCC C++ 编译器来解析 C++ 源文件。更准确地说,工具链如下所示:

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

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

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

于 2008-10-27T20:50:45.837 回答
1

很抱歉,这不是 Python 解决方案,但您也可以使用了解如何删除注释的工具,例如您的 C/C++ 预处理器。以下是 GNU CPP 的做法

cpp -fpreprocessed foo.c
于 2009-07-03T09:08:24.783 回答
1

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

StripCmt 是一个用 C 语言编写的简单实用程序,用于从 C、C++ 和 Java 源文件中删除注释。在 Unix 文本处理程序的伟大传统中,它既可以用作 FIFO(先进先出)过滤器,也可以在命令行上接受参数。

于 2009-08-18T14:18:07.737 回答
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”的实用程序类,我保留了我使用/需要的各种工具。

于 2013-09-25T05:27:45.570 回答
1

我已经使用 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'

使用类似 Javascript 的语言:

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

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

于 2020-12-02T07:54:54.263 回答
0

您实际上并不需要解析树来完美地执行此操作,但实际上您确实需要与编译器前端生成的令牌流等效的令牌流。这样的令牌流必须处理所有奇怪的问题,例如续行注释开始、以字符串开头的注释、三元组规范化等。如果您有令牌流,删除注释很容易。(我有一个工具可以准确地产生这样的令牌流,你猜怎么着,一个真正的解析器的前端会产生一个真正的解析树:)。

正则表达式可以单独识别标记这一事实表明,原则上,您可以编写一个正则表达式来挑选注释词位。为分词器设置的正则表达式的真正复杂性(至少是我们编写的那个)表明您在实践中不能这样做;单独写它们已经够难的了。如果您不想完美地做到这一点,那么,上面的大多数 RE 解决方案都可以。

现在,除非您正在构建代码混淆器,否则您为什么想要剥离注释超出了我的范围。在这种情况下,您必须完全正确。

于 2009-07-03T08:38:46.277 回答
-2

我最近在上课时遇到了这个问题,教授要求我们从源代码中删除 javadoc,然后再提交给他进行代码审查。我们不得不多次这样做,但我们不能只是永久删除 javadoc,因为我们还需要生成 javadoc html 文件。这是我为解决问题而制作的一个小 Python 脚本。由于 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)
于 2011-03-07T16:10:54.113 回答