Я ищу код Python, который удаляет комментарии C и C++ из строки. (Предположим, что строка содержит весь исходный файл C.)
Я понимаю, что могу использовать подстроки .match () с помощью Regex, но это не решает проблемы вложенности /*
или наличия //
внутри /* */
.
В идеале я бы предпочел не наивную реализацию, которая должным образом справляется с неудобными случаями.
@QuantumPete, чтобы улучшить читаемость и понятность. Самый быстрый способ - использовать редактор раскраски и установить цвет комментария, равный цвету фона.
@QuantumPete Или потому, что мы пытаемся предварительно обработать исходный код для последующего процессора, который не принимает вменяемые комментарии
Я бы посоветовал это. (Я это написал.)
Комментарии C (и C++) не могут быть вложенными. Регулярные выражения работают хорошо:
//.*?\n|/\*.*?\*/
Для этого требуется флаг «Одна строка» (Re.S
), потому что комментарий C может занимать несколько строк.
def stripcomments(text):
return re.sub('//.*?\n|/\*.*?\*/', '', text, flags=re.S)
Этот код должен работать.
/ Обновлено: Обратите внимание, что мой приведенный выше код фактически делает предположение о окончании строки! Этот код не будет работать с текстовым файлом Mac. Однако это можно исправить относительно легко:
//.*?(\r\n?|\n)|/\*.*?\*/
Это регулярное выражение должно работать во всех текстовых файлах, независимо от окончания их строк (охватывает окончания строк в Windows, Unix и Mac).
/ EDIT: MizardX и Брайан (в комментариях) сделали правильное замечание об обработке строк. Я полностью забыл об этом, потому что приведенное выше регулярное выражение извлечено из модуля синтаксического анализа, который имеет дополнительную обработку строк. Решение MizardX должно работать очень хорошо, но оно обрабатывает только строки с двойными кавычками.
1. используйте $
и re.MULTILINE вместо '\ n', '\ r \ n' и т. д.
Это не относится к случаю, когда строка заканчивается обратной косой чертой, которая указывает на продолжение строки, но такой случай встречается крайне редко.
Вы пропустили заменяющую пустую строку в re.sub. Кроме того, это не сработает для строк. Например. рассмотрим 'string uncPath = "// some_path";' или 'char операторы [] = "/ * + -";' Для синтаксического анализа языка лучше всего использовать настоящий синтаксический анализатор.
Ваш код не обрабатывает злоупотребления комментариями, например обратную косую черту-новую строку между двумя символами начала комментария или косую черту в виде звездочки, завершающую классический комментарий в стиле C. В сильном смысле это «не имеет значения; никто в здравом уме не пишет подобные комментарии». YMMV.
@Jonathan: Вау, я не думал, что это будет компилироваться. Новое определение слова «лексема». Кстати, есть ли подсветчики синтаксиса (IDE, редакторы кода), которые это поддерживают? Ни VIM, ни Visual Studio этого не делают.
«Комментарии C (и C++) не могут быть вложенными». Некоторые компиляторы (ну, по крайней мере, Borland (бесплатная) версия 5.5.1) разрешают вложенные комментарии в стиле C через переключатель командной строки.
вы можете использовать py ++ для синтаксического анализа исходного кода C++ с помощью GCC.
Py++ does not reinvent the wheel. It uses GCC C++ compiler to parse C++ source files. To be more precise, the tool chain looks like this:
source code is passed to GCC-XML GCC-XML passes it to GCC C++ compiler GCC-XML generates an XML description of a C++ program from GCC's internal representation. Py++ uses pygccxml package to read GCC-XML generated file. The bottom line - you can be sure, that all your declarations are read correctly.
а может и нет. тем не менее, это нетривиальный синтаксический анализ.
Решения на основе @ RE - вы вряд ли найдете RE, который правильно обрабатывает все возможные «неудобные» случаи, если вы не ограничиваете ввод (например, без макросов). для пуленепробиваемого решения у вас действительно нет выбора, кроме как использовать настоящую грамматику.
Кроме того, как упоминает Алекс Ковентри, простые регулярные выражения будут содержать строковые литералы, которые могут содержать маркеры комментариев (что совершенно законно).
Это обрабатывает комментарии в стиле 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;
, которое не компилировалось, путем замены комментария пробелом, а не пустой строкой.
Это не обрабатывает экранированные символы в строках. Например: char some_punctuation_chars = ". \" /; / * комментарий * /
Да, это так. \\.
будет соответствовать любому экранированному символу, включая \"
.
Также вы можете сохранить нумерацию строк относительно входного файла, изменив первый возврат на: return "" + "\ n" * s.count ('\ n') Мне нужно было сделать это в моей ситуации.
Поэтому я думаю, что это не сработает для различных строк RegExp (например, ///
или //*/
или /'/; //blah
) и многострочных строк (davidwalsh.name/multiline-javascript-strings). т.е. может использоваться для простого кода, но, вероятно, не для больших производственных кодовых баз. Если бы мне пришлось использовать Python, я бы искал решения, используя pynoceros или pynarcissus. Если вы можете использовать node.js, тогда UglifyJS2 станет хорошей базой для изменения кода JavaScript.
@robocat Верно. Но литералы Regex не являются частью языка C. Если вы хотите анализировать код с помощью литералов Regex, вы можете добавить это в конец Regex: |/(?:\\.|[^\/])+/
. Также необходимо изменить условие в функции replacer()
.
@ markus-jarderot - Хорошее замечание! Я забыл, что это был C, потому что искал решение для ECMAScript! С C регулярное выражение также может давать сбой в инструкциях препроцессора (удаление строк, начинающихся с #, вероятно, является простым решением этой проблемы), поэтому в существующем виде оно не решает «должным образом обрабатывает неудобные случаи». Кроме того, в C нет многострочных строк, использующих \, и обрабатывает ли он их?
@robocat Он обрабатывает escape-последовательности, но не инструкции препроцессора. Он также не обрабатывает Диграфы и триграфы, но обычно это не проблема. Для операторов препроцессора вы можете добавить |#[^\r\n]*(?:\\\r?\n[^\r\n]*)*
в конце регулярного выражения.
Это не удается для меня (python2 и python3) в простой строке blah "blah"
с ошибкой TypeError: sequence item 1: expected string, module found
.
После удаления многострочного комментария он оставляет новую строку. какое-нибудь исправление для этого?
@AmanDeep Вы можете добавить [^\S\r\n]*\r?\n?
после \*/
, чтобы включить пробелы до следующей новой строки включительно, если таковая имеется.
Не забывайте, что в 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 */
Это не иллюстрирует триграфы. Обратите внимание, что у вас может быть несколько обратных косых черт в конце строки, но сращивание строк не заботится о том, сколько их есть, но может потребоваться последующая обработка. И т.д. Написать одно регулярное выражение для обработки всех этих случаев будет нетривиально (но это отличается от невозможного).
Я бы также добавил, что если бы кто-нибудь написал комментарий с символами начала или конца комментария, разделенными на строки, я бы убедил их в их ошибочности. И расширение однострочного комментария обратной косой чертой в конце также является злом. Итак, проблемы здесь скорее воображаемые, чем реальные - если только вы не пишете компилятор C.
В некоторых ситуациях регистры регулярных выражений будут падать, например, когда строковый литерал содержит подпоследовательность, соответствующую синтаксису комментария. Чтобы справиться с этим, вам действительно нужно дерево синтаксического анализа.
Это единственный ответ, который не предполагает уродливого взлома.
Но это тоже не совсем ответ на вопрос.
Я не знаю, знакомы ли вы с 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; не нужно изобретать велосипед.
Не вводите дополнительную зависимость скрипта и инструмента в свой скрипт Python с помощью Sed. Выберите Sed или Python, а не оба сразу.
Открывать другой процесс - нехорошо. Это дорого и рискованно. Я предлагаю придерживаться чистого питона.
Это не питон. Это оболочка. А если в окне?
На самом деле вам не нужно дерево синтаксического анализа, чтобы сделать это идеально, но вам действительно нужен поток токенов, эквивалентный тому, что создается клиентской частью компилятора. Такой поток токенов обязательно должен позаботиться обо всех странностях, таких как начало комментария с продолжением строки, начало комментария в строке, нормализация триграфа и т. д. Если у вас есть поток токенов, удалить комментарии легко. (У меня есть инструмент, который генерирует именно такие потоки токенов, как, угадайте, что, интерфейс реального парсера, который производит реальное дерево синтаксического анализа :).
Тот факт, что токены индивидуально распознаются регулярными выражениями, предполагает, что вы, в принципе, можете написать регулярное выражение, которое будет выделять лексемы комментариев. Реальная сложность установленных регулярных выражений для токенизатора (по крайней мере, написанного нами) предполагает, что вы не можете сделать это на практике; писать их по отдельности было достаточно сложно. Если вы не хотите делать это идеально, что ж, тогда большинство решений RE, приведенных выше, вполне подойдут.
Теперь, Почему, вы хотите, чтобы комментарии к полоске были вне меня, если вы не создаете обфускатор кода. В этом случае вы должны сделать все правильно.
Извините, это не решение Python, но вы также можете использовать инструмент, который понимает, как удалять комментарии, например препроцессор C / C++. Вот как GNU CPP Имеет ли это.
cpp -fpreprocessed foo.c
хорошее мышление, хотя жаль, что он делает больше, чем просто удаляет комментарии!
Есть также ответ, отличный от Python: используйте программу stripcmt:
StripCmt is a simple utility written in C to remove comments from C, C++, and Java source files. In the grand tradition of Unix text processing programs, it can function either as a FIFO (First In - First Out) filter or accept arguments on the commandline.
Я столкнулся с этой проблемой недавно, когда посещал курс, где профессор требовал, чтобы мы удалили javadoc из нашего исходного кода, прежде чем отправлять его ему на проверку кода. Нам приходилось делать это несколько раз, но мы не могли просто удалить javadoc навсегда, потому что нам также требовалось сгенерировать файлы javadoc html. Вот небольшой скрипт на 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)
Это наверняка не сработает, если в строке или в регулярном выражении с разделителями //
есть /*
или /
.
Нет, это не так. Он ищет комментарии java-блока в стиле /** */
, как указано в описании. Он не обрабатывает //
, /*
или даже /
... он не идеален, но он не «терпит неудачу», просто игнорирует указанные вами случаи. Это была просто ссылка для тех, кто ищет что-то подобное.
Эта публикация представляет собой закодированную версию улучшения кода Маркуса Джардерота, которое было описано 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)
Для меня сработало следующее:
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», в котором я храню различные инструменты, которые мне нужны / используются.
Я использую пигменты для анализа строки, а затем игнорирую все токены, которые являются комментариями к ней. Прекрасно работает с любым лексером в списке пигментов, включая 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'
Поскольку этот код удаляет только комментарии, любое странное значение останется. Итак, это очень надежное решение, способное работать даже с недопустимыми входными данными.
Зачем вам нужны комментарии Удалить из источника ???