【问题标题】:Remove C and C++ comments using Python?使用 Python 删除 C 和 C++ 注释?
【发布时间】:2010-09-19 11:19:36
【问题描述】:

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

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

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

【问题讨论】:

  • 您到底为什么要从源中删除 cmets???
  • @QuantumPete,以提高可读性和可理解性。最快的方法是使用着色编辑器并将评论颜色设置为背景颜色。
  • @QuantumPete 或者是因为我们正在尝试为后续处理器预处理源代码,而该处理器不需要理智的 cmets
  • 我建议this。 (我写的。)

标签: c++ python c regex comments


【解决方案1】:

C(和 C++)cmets 不能嵌套。正则表达式效果很好:

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

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

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

这段代码应该可以工作。

/EDIT:请注意,我上面的代码实际上对行尾做了一个假设!此代码不适用于 Mac 文本文件。但是,这可以相对容易地修改:

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

此正则表达式应适用于所有文本文件,无论其行尾如何(包括 Windows、Unix 和 Mac 行尾)。

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

【讨论】:

  • 1.使用 $ 和 re.MULTILINE 而不是 ''\n'、'\r\n' 等
  • 这不处理以反斜杠结尾的行的情况,表示续行,但这种情况极为罕见
  • 您错过了 re.sub 中的替换空白字符串。此外,这不适用于字符串。例如。考虑 'string uncPath = "//some_path";'或'字符运算符[]="/*+-";'对于语言解析,我认为你最好使用真正的解析器。
  • 您的代码不会处理 cmets 的滥用,例如两个注释开头符号之间的反斜杠换行符,或结束经典 C 样式注释的星斜杠之间的反斜杠换行符。有一种强烈的感觉,“无关紧要;没有人在他们正常的头脑中写出那样的 cmets”。 YMMV。
  • @Jonathan:哇,我认为这不会编译。重新定义了“词素”的含义。顺便说一句,是否有支持此功能的语法荧光笔(IDE、代码编辑器)? VIM 和 Visual Studio 都没有。
【解决方案2】:

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

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

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

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

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

【讨论】:

  • 另外,正如 Alex Coventry 所提到的,简单的正则表达式将处理恰好包含注释标记的字符串文字(这是完全合法的)。
【解决方案3】:

这处理 C++ 风格的 cmets、C 风格的 cmets、字符串及其简单的嵌套。

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; 的情况,通过将注释替换为空格而不是空字符串。

【讨论】:

  • 这不处理字符串中转义的 " 字符。例如:char some_punctuation_chars=".\"/"; /* 评论 */
  • 是的。 \\. 将匹配任何转义字符,包括 \"
  • 您还可以通过将第一个返回更改为:return "" + "\n" * s.count('\n') 我需要在我的情况。
  • 所以我认为它会在各种 RegExp 字符串(例如 /\///\/*//'/; //blah)和多行字符串(davidwalsh.name/multiline-javascript-strings)上失败。即可用于简单代码,但可能不适用于较大的生产代码库。如果我必须使用 Python,我会寻找使用 pynoceros 或 pynarcissus 的解决方案。如果您可以使用 node.js,那么 UglifyJS2 是处理 JavaScript 代码的良好基础。
  • @markus-jarderot - 好点!我忘了它是 C,因为我正在寻找一个 ECMAScript 解决方案!使用 C,正则表达式也可能在预处理器语句上失败(删除以 # 开头的行可能是解决该问题的一个简单方法),因此它不能解决“正确处理尴尬的情况”。 C 也没有使用 \ 的多行字符串,这是否处理这些?
【解决方案4】:

不要忘记,在 C 中,反斜杠换行符在处理 cmets 之前被消除,而三元组在此之前被处理(因为 ??/ 是反斜杠的三元组)。我有一个叫SCC(strip C/C++ cmets)的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 */

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

【讨论】:

  • 我还要补充一点,如果有人写了评论,评论开始或结束符号分成几行,我会说服他们他们的方式错误。使用尾部反斜杠扩展单行注释也同样邪恶。所以,这里的问题更多的是想象而不是真实——除非你是一个 C 编译器作者。
【解决方案5】:

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

【讨论】:

  • 这是唯一不涉及丑陋黑客的回应。
  • 但它也没有真正回答这个问题。
【解决方案6】:

我不知道你是否熟悉 sed,这是一个基于 UNIX(但 Windows 可用)的文本解析程序,但我发现了一个 sed 脚本 here,它将删除 C/C++ cmets从一个文件。它非常聪明;例如,如果在字符串声明等中找到 '//' 和 '/*',它将忽略它。在 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 将保存删除 cmets 的 C/C++ 代码。当然,如果您在磁盘上有文件,您可以将inputoutput 变量作为指向这些文件的文件句柄(input 处于读取模式,output 处于写入模式)。 remccoms3.sed 是上述链接中的文件,它应该保存在磁盘上的可读位置。 sed 也可在 Windows 上使用,并且默认安装在大多数 GNU/Linux 发行版和 Mac OS X 上。

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

【讨论】:

  • 不要使用 Sed 向 Python 脚本引入额外的脚本和工具依赖项。选择 Sed 或 Python,不要同时选择两者。
  • 打开另一个进程不好。这是昂贵且有风险的。我建议坚持使用纯 python。
  • 这不是 python。是贝壳。如果在窗口上怎么办?
【解决方案7】:

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

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

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

【讨论】:

    【解决方案8】:

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

    cpp -fpreprocessed foo.c
    

    【讨论】:

    • 不错的想法,虽然很遗憾,但它不仅仅是删除 cmets!
    【解决方案9】:

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

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

    【讨论】:

      【解决方案10】:

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

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

      #!/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)
      

      【讨论】:

      • 如果字符串中有///*/ 分隔的正则表达式,那肯定会失败。
      • 不,它没有。如描述中所述,它正在寻找/** */ 样式的java 块cmets。它不处理///* 甚至/...它并不完美,但它不会“失败”,只是忽略了你所说的情况。这只是为寻找类似东西的人提供的参考。
      【解决方案11】:

      此帖子提供了对 Markus Jarderot 代码改进的编码版本,由 atikat 在对 Markus Jarderot 帖子的评论中描述。 (感谢两位提供原始代码,为我节省了大量工作。)

      更全面地描述改进:改进保持行号不变。 (这是通过在替换 C/C++ cmets 的字符串中保持换行符不变来完成的。)

      此版本的 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)
      

      【讨论】:

        【解决方案12】:

        以下对我有用:

        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”的实用程序类,我保留了我使用/需要的各种工具。

        【讨论】:

          【解决方案13】:

          我使用 pygments 来解析字符串,然后忽略所有来自它的 cmets 标记。与 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'
          

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

          【讨论】:

          • 这个答案发布已经有一段时间了,但我只想说我发现它非常有用。我一直在尝试使用 Thiago 的上述解决方案,但想指出,如果您正在解析 C 代码,您可能希望使用以下导入而不是利用 pygments.lexers.c_like 的导入:from pygments.lexers.c_cpp import CLexer。我仍在对此进行试验,但为我使用了以前丢弃的预处理器定义。
          猜你喜欢
          • 1970-01-01
          • 2013-04-12
          • 2014-05-16
          • 2012-06-18
          • 2014-08-27
          • 2022-10-22
          • 2018-09-06
          • 1970-01-01
          • 2017-01-19
          相关资源
          最近更新 更多