使用Python查找源代码中不在注释中的所有字符串

时间:2017-12-22 18:26:52

标签: python regex

我有一个类似C的源代码,我试图将此源代码中的所有字符串提取并保存到列表中,而不包括注释中的字符串。 此源代码中的字符串可以包含任何字符,空格,甚至注释。

示例:

// 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"]

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

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

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

但它也给了我评论中的字符串。

任何帮助?

4 个答案:

答案 0 :(得分:2)

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

你走了:

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))

答案 1 :(得分:1)

re = r'(?:\/\/.+\n|\/\*.+\*\/)|(\".+\"|\'.+\')'

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

答案 2 :(得分:1)

假设:

>>> 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

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

答案 3 :(得分:0)

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

State Machine for Extracting Strings

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)

它是如何工作的?

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

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