Фрагмент Python для удаления комментариев C и C++
Я ищу код Python, который удаляет комментарии C и C++ из строки. (Предположим, что строка содержит весь исходный файл C).
Я понимаю, что я мог бы.match() подстроки с регулярным выражением, но это не решает вложения /*
или имея //
внутри /* */
,
В идеале я предпочел бы не наивную реализацию, которая правильно обрабатывает неловкие случаи.
12 ответов
Я не знаю, знакомы ли вы с sed
основанная на UNIX (но доступная для Windows) программа синтаксического анализа текста, но я нашел здесь скрипт sed, который удалит комментарии C/C++ из файла. Это очень умно; например, он будет игнорировать "//" и "/*", если он найден в объявлении строки и т. д. В 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
будет содержать код C/C++ с удаленными комментариями. Конечно, если у вас есть файл на диске, вы могли бы иметь input
а также output
переменные будут файловыми дескрипторами, указывающими на эти файлы (input
в режиме чтения, output
в режиме записи). remccoms3.sed
это файл по ссылке выше, и он должен быть сохранен в читаемом месте на диске. sed
также доступен в Windows и устанавливается по умолчанию в большинстве дистрибутивов GNU/Linux и Mac OS X.
Это, вероятно, будет лучше, чем чистое решение Python; Не нужно изобретать велосипед.
Это обрабатывает комментарии в стиле C++, комментарии в стиле C, строки и их простое вложение.
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;
который не будет компилироваться, заменяя комментарий пробелом, а не пустой строкой.
Комментарии C (и C++) не могут быть вложенными. Регулярные выражения работают хорошо:
//.*?\n|/\*.*?\*/
Для этого требуется флаг "Одна строка" (Re.S
) потому что комментарий C может занимать несколько строк.
def stripcomments(text):
return re.sub('//.*?\n|/\*.*?\*/', '', text, flags=re.S)
Этот код должен работать.
/ РЕДАКТИРОВАТЬ: обратите внимание, что мой код выше делает предположение об окончании строки! Этот код не будет работать с текстовым файлом Mac. Тем не менее, это может быть исправлено относительно легко:
//.*?(\r\n?|\n)|/\*.*?\*/
Это регулярное выражение должно работать со всеми текстовыми файлами, независимо от их окончаний строк (охватывает окончания строк Windows, Unix и Mac).
/ РЕДАКТИРОВАТЬ: MizardX и Брайан (в комментариях) сделали правильное замечание об обработке строк. Я полностью забыл об этом, потому что приведенное выше регулярное выражение извлечено из модуля синтаксического анализа, который имеет дополнительную обработку для строк. Решение MizardX должно работать очень хорошо, но оно обрабатывает только строки в двойных кавычках.
Не забывайте, что в C обратная косая черта-перевод строки удаляется до обработки комментариев, а триграфы обрабатываются до этого (потому что??/ - это триграф обратной косой черты). У меня есть программа на C под названием SCC (раздели комментарии C/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 */
Это не иллюстрирует триграфы. Обратите внимание, что у вас может быть несколько обратных косых черт в конце строки, но сращивание строки не заботится о том, сколько их есть, но возможна последующая обработка. И т.д. Написание одного регулярного выражения для обработки всех этих случаев будет нетривиальным (но это отличается от невозможного).
Эта публикация предоставляет зашифрованную версию улучшения кода Маркуса Жардеро, который был описан atikat в комментарии к публикации Маркуса Жардеро. (Спасибо за то, что предоставили оригинальный код, который сэкономил мне много работы.)
Чтобы описать улучшение несколько более полно: улучшение сохраняет нетронутой нумерацию строк. (Это достигается сохранением символов новой строки в строках, которыми заменяются комментарии C/C++.)
Эта версия функции удаления комментариев 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)
Случаи регулярного выражения в некоторых ситуациях будут падать, например, когда строковый литерал содержит подпоследовательность, которая соответствует синтаксису комментария. Вам действительно нужно дерево разбора, чтобы справиться с этим.
Вы можете использовать py ++ для анализа исходного кода C++ с помощью GCC.
Py ++ не изобретает колесо. Он использует компилятор GCC C++ для разбора исходных файлов C++. Чтобы быть более точным, цепочка инструментов выглядит следующим образом:
исходный код передается в GCC-XML. GCC-XML передает его в компилятор GCC C++. GCC-XML генерирует XML-описание программы на C++ из внутреннего представления GCC. Py++ использует пакет pygccxml для чтения сгенерированного файла GCC-XML. Суть - вы можете быть уверены, что все ваши объявления прочитаны правильно.
а может и нет. независимо, это не тривиальный анализ.
@ Решения на основе RE - вы вряд ли найдете RE, который правильно обрабатывает все возможные "неловкие" случаи, если вы не ограничиваете ввод (например, не используете макросы). для пуленепробиваемого решения у вас действительно нет выбора, кроме как использовать настоящую грамматику.
Я использую пигменты для анализа строки, а затем игнорирую все токены, которые являются комментариями к ней. Прекрасно работает с любым лексером в списке пигментов, включая 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'
Поскольку этот код удаляет только комментарии, любое странное значение останется. Итак, это очень надежное решение, способное работать даже с недопустимыми входными данными.
Следующее работало для меня:
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", в котором хранятся различные инструменты, которые я использую / нуждаюсь.
Я сожалею, что это не решение Python, но вы также можете использовать инструмент, который понимает, как удалять комментарии, например, ваш препроцессор C/C++. Вот как это делает GNU CPP.
cpp -fpreprocessed foo.c
Существует также не-Python ответ: используйте программу stripcmt:
StripCmt - это простая утилита, написанная на C для удаления комментариев из исходных файлов C, C++ и Java. В соответствии с великой традицией программ обработки текста Unix, он может функционировать как фильтр FIFO (First In - First Out) или принимать аргументы в командной строке.
Вам на самом деле не нужно дерево разбора, чтобы сделать это идеально, но на самом деле вам нужен поток токенов, эквивалентный тому, что генерируется внешним интерфейсом компилятора. Такой поток токенов обязательно должен позаботиться обо всех странностях, таких как начало комментария с продолжения строки, начало комментария в строке, нормализация триграфа и т. Д. Если у вас есть поток токенов, удаление комментариев легко. (У меня есть инструмент, который производит именно такие потоки токенов, как, угадайте, внешний интерфейс реального синтаксического анализатора, который создает настоящее дерево синтаксического анализа:).
Тот факт, что токены индивидуально распознаются регулярными выражениями, говорит о том, что в принципе можно написать регулярное выражение, которое выберет лексемы комментариев. Реальная сложность набора регулярных выражений для токенизатора (по крайней мере, того, что мы написали) предполагает, что вы не можете сделать это на практике; писать их по отдельности было достаточно сложно. Если вы не хотите делать это идеально, тогда большинство из приведенных выше решений RE просто хороши.
Теперь, почему вы хотите, чтобы раздельные комментарии были вне меня, если вы не создаете обфускатор кода. В этом случае вы должны быть совершенно правы.
Я столкнулся с этой проблемой недавно, когда я взял урок, где профессор потребовал, чтобы мы убрали javadoc из нашего исходного кода, прежде чем представить его ему для проверки кода. Нам приходилось делать это несколько раз, но мы не могли просто удалить Javadoc навсегда, потому что нам также требовалось создавать HTML-файлы Javadoc. Вот небольшой скрипт на python, который я сделал, чтобы добиться цели. Поскольку javadoc начинается с /** и заканчивается */, скрипт ищет эти токены, но скрипт может быть изменен в соответствии с вашими потребностями. Он также обрабатывает однострочные комментарии к блоку и случаи, когда комментарий блока заканчивается, но в той же строке, что и окончание комментария блока, все еще есть код без комментариев. Надеюсь, это поможет!
ВНИМАНИЕ: Этот сценарий изменяет содержимое переданных файлов и сохраняет их в исходных файлах. Было бы целесообразно иметь резервную копию в другом месте
#!/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)