【问题标题】:Using regex to remove comments from source files使用正则表达式从源文件中删除注释
【发布时间】:2011-01-20 02:43:25
【问题描述】:

我正在编写一个程序来自动编写一些 C 代码,(我正在编写将字符串解析为具有相同名称的枚举) C 对字符串的处理不是很好。 所以一直有人唠叨我要试试python。

我创建了一个应该删除 C 风格 /* COMMENT *///COMMENT 的函数 从一个字符串: 代码如下:

def removeComments(string):
    re.sub(re.compile("/\*.*?\*/",re.DOTALL ) ,"" ,string) # remove all occurance streamed comments (/*COMMENT */) from string
    re.sub(re.compile("//.*?\n" ) ,"" ,string) # remove all occurance singleline comments (//COMMENT\n ) from string

所以我尝试了这段代码。

str="/* spam * spam */ eggs"
removeComments(str)
print str

它显然什么也没做。

关于我做错了什么有什么建议吗?

有句话我听过几次:

如果您遇到问题并尝试使用正则表达式解决它,您最终会遇到两个问题。


编辑: 多年以后再回首。 (经过相当多的解析经验)

我认为正则表达式可能是正确的解决方案。 这里使用的简单正则表达式“足够好”。 我在问题中可能没有足够强调这一点。 这是针对单个特定文件的。这没有棘手的情况。 我认为让正则表达式解析的文件足够简单,而不是将正则表达式复杂化为不可读的符号汤,维护工作会少得多。 (例如,要求文件仅使用 // 单行 cmets。)

【问题讨论】:

  • 只有一个合理的回复:kore-nordmann.de/blog/do_NOT_parse_using_regexp.html。他说的是另一种语言,但他的结论仍然有效。
  • @Jerry - 严格来说,您通常可以猜测出合理的嵌套限制,并定义语言的常规近似值。无论如何,许多编译器都有注释嵌套限制。但是 - 什么限制是安全的?另外,我不想调试正则表达式。无论哪种方式都很好。
  • @Steve314:您可以猜测一个合理的嵌套限制(例如,在 C 中,cmets 根本不嵌套),但这并没有什么好处。举一个明显的例子,字符串文字中的注释定界符不算数,但跨行的注释定界符(字符之间有反斜杠)确实计数。在 RE 中正确考虑其中任何一个都不是微不足道的。
  • 你上面的一些正则表达式救了我的命;)
  • @JerryCoffin 其实合理的回复应该是stackoverflow.com/a/1732454/321973

标签: python regex string


【解决方案1】:

你做错了。

正则表达式适用于Regular Languages,而 C 不是。

【讨论】:

  • 当然,词法分析器和解析器之间的常见预期差异之一是词法分析器仅支持常规语言。与正则表达式一样,当然并不总是如此(例如,参见 Ragel)。一个好的词法分析器可以完成这项工作,但与使用解析器一样,仅用于注释剥离似乎有点过分了。
  • @Steve314,如果您所说的“矫枉过正”是指完全适合这项工作的工具,那么是的。此处发布的所有正则表达式都非常错误,并且在面对有效的、现实的 C(++) 代码时不会做正确的事情。
  • 阅读有关词法分析器的信息,删除了我对词法分析器的推荐
  • @Mike - 再三考虑,我同意 - 但具体原因尚未提及(尽管这是您的“有效、现实”观点的特例)。我只是想到了看起来像注释标记的东西,但实际上只是字符串文字中的字符。避免那些没有正确工具的人将是一项令人讨厌的工作。获取现有的 C 词法分析器(只要它保留空格) - 还不错。
  • @Mike - 我自己的答案因此被删除 - 被认为是有害的。
【解决方案2】:

我建议使用像SimpleParsePyParsing 这样的真实解析器。 SimpleParse 要求您实际了解 EBNF,但速度非常快。 PyParsing 有自己的类似 EBNF 的语法,但它适用于 Python,让构建强大而准确的解析器变得轻而易举。

编辑:

下面是一个例子,说明在这种情况下使用 PyParsing 是多么容易:

>>> test = '/* spam * spam */ eggs'
>>> import pyparsing
>>> comment = pyparsing.nestedExpr("/*", "*/").suppress()
>>> print comment.transformString(test)         
' eggs'

这是一个使用单行和多行 cmets 的更复杂的示例。

之前:

/*
 * multiline comments
 * abc 2323jklj
 * this is the worst C code ever!!
*/
void
do_stuff ( int shoe, short foot ) {
    /* this is a comment
     * multiline again! 
     */
    exciting_function(whee);
} /* extraneous comment */

之后:

>>> print comment.transformString(code)   

void
do_stuff ( int shoe, short foot ) {

     exciting_function(whee);
} 

它在剥离 cmets 的地方留下了一个额外的换行符,但这可以解决。

【讨论】:

  • 正则表达式不好,但解析是矫枉过正?我很迷惑;还有什么?
  • 我看错了问题 - 基于简单的交替正则表达式进行搜索比编写解析器要容易得多。也就是说,它并没有解决字符串中的东西引起的混乱。 Mike 所评论的解析器(或 Lexer)可能正是适合这项工作的工具。
  • 是的,如果您的输入很简单,例如 IP 地址或电话号码等格式一致的内容,Regex 就是“简单”的。对于所有其他事情:词法分析器。
  • 我不认为它会留下额外的换行符 - 换行符只是不是评论的一部分,所以它没有被剥离,而且这样做不一定安全,因为换行符 可以 i> 在 C 中用作重要的空格
  • 啊,这是一个很好的观察,现在我再看一遍我同意。 :)
【解决方案3】:

re.sub 返回一个字符串,因此将您的代码更改为以下将给出结果:

def removeComments(string):
    string = re.sub(re.compile("/\*.*?\*/",re.DOTALL ) ,"" ,string) # remove all occurrences streamed comments (/*COMMENT */) from string
    string = re.sub(re.compile("//.*?\n" ) ,"" ,string) # remove all occurrence single-line comments (//COMMENT\n ) from string
    return string

【讨论】:

  • char *note = "You can make a one-line comment with //"; 哎呀。
  • 确实如此。这只能解释为什么 OP 的函数什么都不返回。
  • 这是该问题的技术正确答案。也许使用相量是解决我的问题的更好方法,但这使我的代码工作。
  • string = re.sub(re.compile("^//.*?$", re.MULTILINE ) ,"" ,string)
  • 上面的代码对我来说不适用于单行 cmets。 removeComments("// 单行 cmets") 返回 '// 单行 cmets'
【解决方案4】:

我建议您阅读此页面,该页面对问题进行了非常详细的分析,并很好地理解了您的方法为何不起作用:http://ostermiller.org/findcomment.html

短版:您正在寻找的正则表达式是这样的:

(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)|(//.*)

这应该匹配两种类型的注释块。如果您在关注它时遇到问题,请阅读我链接的页面。

【讨论】:

  • 除非我错过了什么,否则这会错过跨行拼接的注释分隔符(斜杠、反斜杠、换行符、星号或星号、反斜杠、换行符、斜杠)。更糟糕的是,反斜杠可以生成为三元组序列??/(尽管我承认三元组非常罕见)。
【解决方案5】:

我看到了一些你可能想要修改的地方。

首先,Python 按值传递对象,但有些对象类型是不可变的。字符串和整数属于这些不可变类型。因此,如果您将字符串传递给函数,那么您在函数中对字符串所做的任何更改都不会影响您传入的字符串。您应该尝试返回一个字符串。此外,在 removeComments() 函数中,您需要将 re.sub() 返回的值分配给一个新变量——就像任何将字符串作为参数的函数一样,re.sub() 不会修改字符串。

其次,我会回应其他人所说的关于解析 C 代码的内容。正则表达式不是最好的方法。

【讨论】:

    【解决方案6】:

    正如我在其他 cmets 中所指出的,注释嵌套并不是真正的问题(在 C 中,cmets 不嵌套,尽管有一些编译器支持嵌套 cmets)。问题在于字符串文字之类的东西,它可以包含与注释分隔符完全相同的字符序列,而实际上却不是一个。

    正如 Mike Graham 所说,适合这项工作的工具是词法分析器。解析器是不必要的,而且会过大,但词法分析器是完全正确的。碰巧的是,我今天早上早些时候为 C(和 C++)发布了(部分)lexer。它不会尝试正确识别所有词法元素(即所有关键字和运算符),但它对于剥离 cmets 是完全足够的。不过,它在“使用 Python”方面没有任何好处,因为它完全是用 C 编写的(它比我使用 C++ 的时间要早​​得多,而不仅仅是实验性代码)。

    【讨论】:

      【解决方案7】:
      mystring="""
      blah1 /* comments with
      multiline */
      
      blah2
      blah3
      // double slashes comments
      blah4 // some junk comments
      
      """
      for s in mystring.split("*/"):
          s=s[:s.find("/*")]
          print s[:s.find("//")]
      

      输出

      $ ./python.py
      
      blah1
      
      
      blah2
      blah3
      

      【讨论】:

        【解决方案8】:

        这个程序从给定的文件中删除带有 // 和 /* */ 的 cmets:

        #! /usr/bin/python3
        import sys
        import re
        if len(sys.argv)!=2:
             exit("Syntax:python3 exe18.py inputfile.cc ")
        else:
             print ('The following files are given by you:',sys.argv[0],sys.argv[1])
        with open(sys.argv[1],'r') as ifile:
            newstring=re.sub(r'/\*.*?\*/',' ',ifile.read(),flags=re.S)
        with open(sys.argv[1],'w') as ifile:
            ifile.write(newstring)
        print('/* */ have been removed from the inputfile')
        with open(sys.argv[1],'r') as ifile:
              newstring1=re.sub(r'//.*',' ',ifile.read())
        with open(sys.argv[1],'w') as ifile:
              ifile.write(newstring1)
        print('// have been removed from the inputfile')
        

        【讨论】:

        • 抱歉,不明白什么是 PO?
        • 对不起,我的意思是 OP (meta.stackexchange.com/questions/79804/…)
        • 人们可能对您投了反对票,因为您多次从文件中读取和写入。宁愿读入内存一次,执行您的操作,然后在完成后写入文件一次。从内存中工作的速度要快得多。
        【解决方案9】:

        "//comment-like strings inside quotes" 呢?

        OP 正在询问如何使用正则表达式来做到这一点;所以:

        def remove_comments(string):
            pattern = r"(\".*?\"|\'.*?\')|(/\*.*?\*/|//[^\r\n]*$)"
            # first group captures quoted strings (double or single)
            # second group captures comments (//single-line or /* multi-line */)
            regex = re.compile(pattern, re.MULTILINE|re.DOTALL)
            def _replacer(match):
                # if the 2nd group (capturing comments) is not None,
                # it means we have captured a non-quoted (real) comment string.
                if match.group(2) is not None:
                    return "" # so we will return empty to remove the comment
                else: # otherwise, we will return the 1st group
                    return match.group(1) # captured quoted-string
            return regex.sub(_replacer, string)
        

        删除:

        • /* multi-line comments */
        • // single-line comments

        不会删除:

        • String var1 = "this is /* not a comment. */";
        • char *var2 = "this is // not a comment, either.";
        • url = 'http://not.comment.com';

        注意:这也适用于 Javascript 源代码。

        【讨论】:

        • /* Maybe we // "shouldn't /* do this */// but let's do it */ //" anyway 怎么样(随意插入换行符)
        • 整洁,它甚至可以管理交错 à la 'a = /* a "-nested string */ "comments can end with */" // comment2'!我看到分组使正则表达式更加强大易于理解。我想知道是否仍然可以构造使您的正则表达式失败的有效 C 代码;)但这可能会涉及一些非常讨厌的预编译器内容...
        • 如果你有转义引号,这个方法会失败,例如"some 2\" string" /* remove this */ "another string"
        • 这是一个改进的正则表达式,它执行否定的向后查找以避免转义引号。 r"(\".*?(?<!\\)\"|\'.*?(?<!\\)\')|(/\*.*?\*/|//[^\r\n]*$)"
        • @ishmael "string with backslash \\" /* remove this */ "..." 怎么样?
        【解决方案10】:

        只想添加另一个正则表达式,我们必须删除 * 和 ; 之间的任何内容在python中

        data = re.sub(re.compile("*.*?\;",re.DOTALL),' ',data)

        在 * 之前有一个反斜杠来转义元字符。

        【讨论】:

        • 为什么和原来的问题有关?
        【解决方案11】:

        在 Jathanism 之后找到了另一个使用 pyparsing 的解决方案。

        import pyparsing
        
        test = """
        /* Code my code
        xx to remove comments in C++
        or C or python */
        
        include <iostream> // Some comment
        
        int main (){
            cout << "hello world" << std::endl; // comment
        }
        """
        commentFilter = pyparsing.cppStyleComment.suppress()
        # To filter python style comment, use
        # commentFilter = pyparsing.pythonStyleComment.suppress()
        # To filter C style comment, use
        # commentFilter = pyparsing.cStyleComment.suppress()
        
        newtest = commentFilter.transformString(test)
        print(newest)
        

        产生以下输出:

        include <iostream> 
        
        int main (){
            cout << "hello world" << std::endl; 
        }
        

        也可以使用pythonStyleComment、javaStyleComment、cppStyleComment。发现它非常有用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-04-01
          • 2016-07-07
          • 2020-09-20
          • 2011-01-28
          • 1970-01-01
          • 2011-08-04
          • 2011-05-02
          相关资源
          最近更新 更多