Вопрос о регулярных выражениях в Python: удаление многострочных комментариев с сохранением разрыва строки - PullRequest
4 голосов
/ 10 мая 2009

Я анализирую файл исходного кода и хочу удалить все строчные комментарии (т.е. начинающиеся с "//") и многострочные комментарии (т.е. /..../). Однако, если в многострочном комментарии есть хотя бы один разрыв строки (\ n), я хочу, чтобы вывод имел ровно один разрыв строки.

Например, код:

qwe /* 123
456 
789 */ asd

должно превратиться точно в:

qwe
asd

а не "qweasd" или:

qwe

asd

Каков наилучший способ сделать это? Спасибо


EDIT: Пример кода для тестирования:

comments_test = "hello // comment\n"+\
                "line 2 /* a comment */\n"+\
                "line 3 /* a comment*/ /*comment*/\n"+\
                "line 4 /* a comment\n"+\
                "continuation of a comment*/ line 5\n"+\
                "/* comment */line 6\n"+\
                "line 7 /*********\n"+\
                "********************\n"+\
                "**************/\n"+\
                "line ?? /*********\n"+\
                "********************\n"+\
                "********************\n"+\
                "********************\n"+\
                "********************\n"+\
                "**************/\n"+\
                "line ??"

Ожидаемые результаты:

hello 
line 2 
line 3  
line 4
line 5
line 6
line 7
line ??
line ??

Ответы [ 5 ]

9 голосов
/ 10 мая 2009
comment_re = re.compile(
    r'(^)?[^\S\n]*/(?:\*(.*?)\*/[^\S\n]*|/[^\n]*)($)?',
    re.DOTALL | re.MULTILINE
)

def comment_replacer(match):
    start,mid,end = match.group(1,2,3)
    if mid is None:
        # single line comment
        return ''
    elif start is not None or end is not None:
        # multi line comment at start or end of a line
        return ''
    elif '\n' in mid:
        # multi line comment with line break
        return '\n'
    else:
        # multi line comment without line break
        return ' '

def remove_comments(text):
    return comment_re.sub(comment_replacer, text)
  • (^)? будет соответствовать, если комментарий начинается в начале строки, если используется флаг MULTILINE.
  • [^\S\n] будет соответствовать любому символу пробела, кроме новой строки. Мы не хотим сопоставлять разрывы строк, если комментарий начинается с отдельной строки.
  • /\*(.*?)\*/ будет соответствовать многострочному комментарию и захватывать содержимое. Ленивое соответствие, поэтому мы не сопоставляем два или более комментариев. DOTALL -flag делает . совпадение с новой строкой.
  • //[^\n] будет соответствовать однострочному комментарию. Не могу использовать . из-за флага DOTALL.
  • ($)? будет соответствовать, если комментарий останавливается в конце строки, если используется флаг MULTILINE.

Примеры:

>>> s = ("qwe /* 123\n"
         "456\n"
         "789 */ asd /* 123 */ zxc\n"
         "rty // fgh\n")
>>> print '"' + '"\n"'.join(
...     remove_comments(s).splitlines()
... ) + '"'
"qwe"
"asd zxc"
"rty"
>>> comments_test = ("hello // comment\n"
...                  "line 2 /* a comment */\n"
...                  "line 3 /* a comment*/ /*comment*/\n"
...                  "line 4 /* a comment\n"
...                  "continuation of a comment*/ line 5\n"
...                  "/* comment */line 6\n"
...                  "line 7 /*********\n"
...                  "********************\n"
...                  "**************/\n"
...                  "line ?? /*********\n"
...                  "********************\n"
...                  "********************\n"
...                  "********************\n"
...                  "********************\n"
...                  "**************/\n")
>>> print '"' + '"\n"'.join(
...     remove_comments(comments_test).splitlines()
... ) + '"'
"hello"
"line 2"
"line 3 "
"line 4"
"line 5"
"line 6"
"line 7"
"line ??"
"line ??"

редактирует:

  • Обновлено до новой спецификации.
  • Добавлен еще один пример.
5 голосов
/ 10 мая 2009

Тот факт, что вам даже нужно задать этот вопрос, и что приведенные решения, скажем так, не совсем читабельны :-), должны быть хорошим показателем того, что RE не являются реальным ответом на этот вопрос.

Было бы гораздо лучше, с точки зрения читабельности, фактически кодировать это как относительно простой синтаксический анализатор.

Слишком часто люди пытаются использовать RE, чтобы быть «умными» (я не имею в виду это уничижительным образом), думая, что единственная строка элегантна, но все, что они в итоге приводят, - это непреодолимая масса символов. Я предпочел бы иметь полностью прокомментированное 20-строчное решение, которое я могу понять в одно мгновение.

1 голос
/ 10 мая 2009

Как насчет этого:

re.sub(r'\s*/\*(.|\n)*?\*/\s*', '\n', s, re.DOTALL).strip()

Атакует начальный пробел, /*, любой текст и символ новой строки вплоть до первого *\, затем любой пробел после этого.

Это небольшой поворот на примере Сикора, но он также не жадный внутри. Вы также можете захотеть взглянуть на параметр Multiline.

1 голос
/ 10 мая 2009

Это то, что вы ищете?

>>> print(s)
qwe /* 123
456
789 */ asd
>>> print(re.sub(r'\s*/\*.*\n.*\*/\s*', '\n', s, flags=re.S))
qwe
asd

Это будет работать только для тех комментариев, которые содержат более одной строки, но оставят другие в покое.

0 голосов
/ 10 мая 2009

См. могут регулярные выражения могут быть использованы для сопоставления вложенных шаблонов - если вы рассматриваете вложенные комментарии, регулярные выражения не являются решением.

...