【问题标题】:Finding all strings within source code that are not inside comments using Python使用 Python 在源代码中查找不在注释中的所有字符串
【发布时间】:2017-12-22 18:26:51
【问题描述】:

我有一个类似 C 的源代码,我正在尝试提取此源代码中的所有字符串并将其保存到一个列表中,但不包括 cmets 中的字符串。 此源代码中的字符串可以包含任何字符、空格,甚至是 cmets。

例子:

// this is an inline comment with the name "Alpha 1"

string x = "Alpha 2";
/** this is a block comment with the string "Alpha 3" */
foo("Alpha 4");
string y = "Alpha /*  */ 5 // with comments";

输出:

["Alpha 2", "Alpha 4", "Alpha /*  */ 5 // with comments"]

我不能使用正则表达式的问题,因为我可以在给定的字符串中包含 cmets(这是有效的),当然我可以在内联注释或块注释中包含字符串。

我使用这种方法来获取代码中的所有字符串:

re.findall(r'\"(.+?)\"', code)

但它也为我提供了 cmets 中的字符串。

有什么帮助吗?

【问题讨论】:

  • 正则表达式对于解析非常规语言来说是一个糟糕的主意。您是否考虑过使用实际的词法分析器/解析器,例如yacc、lex、flex、野牛?是的,学习它们的开销更高,但这是正确解析甚至中等复杂的语法的唯一方法,而不会留下边缘情况。
  • 您可能需要使用lookaheadlookbehind 断言。尝试pythex.org 进行正则表达式匹配和提示。
  • @ShadowRanger:由于 cmets 和字符串常量都不是任意嵌套的,因此语言是规则的。我同意使用一个简单的词法分析器 + 一个显式的 FSM 来处理它比提出一个正则表达式要容易很多,特别是如果我们不希望内存中的整个文件作为一个字符串。

标签: python regex


【解决方案1】:

如果语言像您描述的那样简单,我想我会手动编写解析器。我仍然会使用正则表达式来标记输入。

给你:

import re
from itertools import takewhile


def extract_strings(source):
    def consume(it, end):
        return list(takewhile(lambda x: x != end, it))
    tokens = iter(re.split(r'''("|/\*|\*/|//|\n)''', source))
    strings = []
    for token in tokens:
        if token == '"':
            strings.append(''.join(consume(tokens, '"')))
        elif token == '//':
            consume(tokens, '\n')
        elif token == '/*':
            consume(tokens, '*/')
    return strings

data = '''
// this is an inline comment with the name "Alpha 1"

string x = "Alpha 2";
/** this is a block comment with the string "Alpha 3" */
foo("Alpha 4");
string y = "Alpha /*  */ 5 // with comments";
'''
print(extract_strings(data))

【讨论】:

    【解决方案2】:
    re = r'(?:\/\/.+\n|\/\*.+\*\/)|(\".+\"|\'.+\')'
    

    这应该主要工作。只需将类似 // comment 的 cmets 以换行符结尾即可。所有不在评论中的单词都将在捕获组 1 中返回。但是请注意,代码中的每条注释都会有None

    【讨论】:

      【解决方案3】:

      给定:

      >>> src='''\
      ... // this is an inline comment with the name "Alpha 1"
      ... 
      ... string x = "Alpha 2";
      ... /** this is a block comment with the string "Alpha 3" */
      ... foo("Alpha 4");
      ... string y = "Alpha /*  */ 5 // with comments";'''
      

      这个正则表达式可以工作:

      >>> pat=re.compile(r"(?:\/\/.+$|/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)|(\"[^\"]*\"|\'[^']*\')", re.M)
      >>> [m.group(1) for m in pat.finditer(src) if m.group(1)]
      ['"Alpha 2"', '"Alpha 4"', '"Alpha /*  */ 5 // with comments"']
      

      正则表达式解释here

      (但是使用解析器更健壮...)

      【讨论】:

        【解决方案4】:

        关于编写解析器,我借此机会使用状态机编写自己的:

        import sys
        
        
        ASTERISK = '*'
        DEFAULT = 'default'
        EOL = '\n'
        ESCAPE = '\\'
        QUOTE = '"'
        SLASH = '/'
        
        
        class ExtractStrings:
            def __init__(self, multiline_string):
                self.buffer = multiline_string
                self.chars_collected = ''
                self.strings = None
        
            def noop(self, ch):
                pass
        
            def collect_char(self, ch):
                self.chars_collected += ch
        
            def return_string(self, ch):
                self.strings.append(self.chars_collected)
                self.chars_collected = ''
        
            def parse(self):
                self.strings = []
                state = {
                    'start': {
                        QUOTE: (self.noop, 'in_string'),
                        SLASH: (self.noop, 'first_slash'),
                        DEFAULT: (self.noop, 'start'),
                    },
                    'in_string': {
                        QUOTE: (self.return_string, 'start'),
                        ESCAPE: (self.collect_char, 'escaping'),
                        DEFAULT: (self.collect_char, 'in_string'),
                    },
                    'escaping': {
                        DEFAULT: (self.collect_char, 'in_string'),
                    },
                    'first_slash': {
                        SLASH: (self.noop, 'line_comment'),
                        ASTERISK: (self.noop, 'block_comment'),
                        DEFAULT: (self.noop, 'start'),
                    },
                    'line_comment': {
                        EOL: (self.noop, 'start'),
                        DEFAULT: (self.noop, 'line_comment'),
                    },
                    'block_comment': {
                        ASTERISK: (self.noop, 'near_comment_block_end'),
                        DEFAULT: (self.noop, 'block_comment'),
                    },
                    'near_comment_block_end': {
                        SLASH: (self.noop, 'start'),
                        ASTERISK: (self.noop, 'near_comment_block_end'),
                        DEFAULT: (self.noop, 'block_comment'),
                    }
        
                }
        
                current = 'start'
                for ch in self.buffer:
                    default = state[current][DEFAULT]
                    action, next_state = state[current].get(ch, default)
                    action(ch)
                    current = next_state
        
            def __iter__(self):
                if self.strings is None:
                    self.parse()
        
                return iter(self.strings)
        
        if __name__ == '__main__':
            with open(sys.argv[1]) as f:
                code = f.read()
        
            for string_literal in ExtractStrings(code):
                print('"%s"' % string_literal)
        

        它是如何工作的?

        状态机定义了不同的状态、在每个状态下要做什么(图中未显示)以及到下一个状态的转换。一旦 状态机被定义(作为嵌套字典),它只是执行状态的动作,读取下一个字符并查找状态机以查看我们应该转换到哪个状态。

        状态机是一个嵌套字典。对于外部字典,键是状态名称,值是内部字典。对于内部字典,键是下一个字符,值是 (action, next state) 的元组。

        【讨论】:

          猜你喜欢
          • 2013-03-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-09-14
          • 2013-08-06
          • 2011-10-02
          • 2013-04-07
          • 1970-01-01
          相关资源
          最近更新 更多