Удалить комментарии C и C++ с помощью Python?

Я ищу код Python, который удаляет комментарии C и C++ из строки. (Предположим, что строка содержит весь исходный файл C.)

Я понимаю, что могу использовать подстроки .match () с помощью Regex, но это не решает проблемы вложенности /* или наличия // внутри /* */.

В идеале я бы предпочел не наивную реализацию, которая должным образом справляется с неудобными случаями.

Зачем вам нужны комментарии Удалить из источника ???

Peter Kühne 28.10.2008 11:09

@QuantumPete, чтобы улучшить читаемость и понятность. Самый быстрый способ - использовать редактор раскраски и установить цвет комментария, равный цвету фона.

Thomas L Holaday 23.05.2009 04:12

@QuantumPete Или потому, что мы пытаемся предварительно обработать исходный код для последующего процессора, который не принимает вменяемые комментарии

Damian Yerrick 06.02.2017 06:27

Я бы посоветовал это. (Я это написал.)

qeatzy 30.11.2018 08:46
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
48
4
39 229
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

Комментарии 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' и т. д.

jfs 28.10.2008 00:46

Это не относится к случаю, когда строка заканчивается обратной косой чертой, которая указывает на продолжение строки, но такой случай встречается крайне редко.

Adam Rosenfield 28.10.2008 01:00

Вы пропустили заменяющую пустую строку в re.sub. Кроме того, это не сработает для строк. Например. рассмотрим 'string uncPath = "// some_path";' или 'char операторы [] = "/ * + -";' Для синтаксического анализа языка лучше всего использовать настоящий синтаксический анализатор.

Brian 28.10.2008 01:01

Ваш код не обрабатывает злоупотребления комментариями, например обратную косую черту-новую строку между двумя символами начала комментария или косую черту в виде звездочки, завершающую классический комментарий в стиле C. В сильном смысле это «не имеет значения; никто в здравом уме не пишет подобные комментарии». YMMV.

Jonathan Leffler 28.10.2008 20:55

@Jonathan: Вау, я не думал, что это будет компилироваться. Новое определение слова «лексема». Кстати, есть ли подсветчики синтаксиса (IDE, редакторы кода), которые это поддерживают? Ни VIM, ни Visual Studio этого не делают.

Konrad Rudolph 28.10.2008 23:13

«Комментарии C (и C++) не могут быть вложенными». Некоторые компиляторы (ну, по крайней мере, Borland (бесплатная) версия 5.5.1) разрешают вложенные комментарии в стиле C через переключатель командной строки.

PTBNL 18.08.2009 18:47

вы можете использовать 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, который правильно обрабатывает все возможные «неудобные» случаи, если вы не ограничиваете ввод (например, без макросов). для пуленепробиваемого решения у вас действительно нет выбора, кроме как использовать настоящую грамматику.

Кроме того, как упоминает Алекс Ковентри, простые регулярные выражения будут содержать строковые литералы, которые могут содержать маркеры комментариев (что совершенно законно).

nobody 28.10.2008 06:42

Это обрабатывает комментарии в стиле 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 = ". \" /; / * комментарий * /

Brian 29.10.2008 15:45

Да, это так. \\. будет соответствовать любому экранированному символу, включая \".

Markus Jarderot 29.10.2008 22:37

Также вы можете сохранить нумерацию строк относительно входного файла, изменив первый возврат на: return "" + "\ n" * s.count ('\ n') Мне нужно было сделать это в моей ситуации.

atikat 03.02.2010 09:27

Поэтому я думаю, что это не сработает для различных строк RegExp (например, /// или //*/ или /'/; //blah) и многострочных строк (davidwalsh.name/multiline-javascript-strings). т.е. может использоваться для простого кода, но, вероятно, не для больших производственных кодовых баз. Если бы мне пришлось использовать Python, я бы искал решения, используя pynoceros или pynarcissus. Если вы можете использовать node.js, тогда UglifyJS2 станет хорошей базой для изменения кода JavaScript.

robocat 26.04.2013 10:00

@robocat Верно. Но литералы Regex не являются частью языка C. Если вы хотите анализировать код с помощью литералов Regex, вы можете добавить это в конец Regex: |/(?:\\.|[^\/])+/. Также необходимо изменить условие в функции replacer().

Markus Jarderot 26.04.2013 10:12

@ markus-jarderot - Хорошее замечание! Я забыл, что это был C, потому что искал решение для ECMAScript! С C регулярное выражение также может давать сбой в инструкциях препроцессора (удаление строк, начинающихся с #, вероятно, является простым решением этой проблемы), поэтому в существующем виде оно не решает «должным образом обрабатывает неудобные случаи». Кроме того, в C нет многострочных строк, использующих \, и обрабатывает ли он их?

robocat 08.05.2013 08:42

@robocat Он обрабатывает escape-последовательности, но не инструкции препроцессора. Он также не обрабатывает Диграфы и триграфы, но обычно это не проблема. Для операторов препроцессора вы можете добавить |#[^\r\n]*(?:\\\r?\n[^\r\n]*)* в конце регулярного выражения.

Markus Jarderot 08.05.2013 09:39

Это не удается для меня (python2 и python3) в простой строке blah "blah" с ошибкой TypeError: sequence item 1: expected string, module found.

Mark Smith 08.03.2018 17:20

После удаления многострочного комментария он оставляет новую строку. какое-нибудь исправление для этого?

Aman Deep 12.05.2020 17:24

@AmanDeep Вы можете добавить [^\S\r\n]*\r?\n? после \*/, чтобы включить пробелы до следующей новой строки включительно, если таковая имеется.

Markus Jarderot 13.05.2020 13:38

Не забывайте, что в 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.

Jonathan Leffler 05.07.2010 21:13

В некоторых ситуациях регистры регулярных выражений будут падать, например, когда строковый литерал содержит подпоследовательность, соответствующую синтаксису комментария. Чтобы справиться с этим, вам действительно нужно дерево синтаксического анализа.

Это единственный ответ, который не предполагает уродливого взлома.

sim642 08.04.2016 18:26

Но это тоже не совсем ответ на вопрос.

tripleee 03.07.2019 11:21
Ответ принят как подходящий

Я не знаю, знакомы ли вы с 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, а не оба сразу.

Stephen Niedzielski 26.03.2013 00:56

Открывать другой процесс - нехорошо. Это дорого и рискованно. Я предлагаю придерживаться чистого питона.

rfportilla 25.03.2019 20:45

Это не питон. Это оболочка. А если в окне?

Samuel Chen 06.11.2019 07:11

На самом деле вам не нужно дерево синтаксического анализа, чтобы сделать это идеально, но вам действительно нужен поток токенов, эквивалентный тому, что создается клиентской частью компилятора. Такой поток токенов обязательно должен позаботиться обо всех странностях, таких как начало комментария с продолжением строки, начало комментария в строке, нормализация триграфа и т. д. Если у вас есть поток токенов, удалить комментарии легко. (У меня есть инструмент, который генерирует именно такие потоки токенов, как, угадайте, что, интерфейс реального парсера, который производит реальное дерево синтаксического анализа :).

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

Теперь, Почему, вы хотите, чтобы комментарии к полоске были вне меня, если вы не создаете обфускатор кода. В этом случае вы должны сделать все правильно.

Извините, это не решение Python, но вы также можете использовать инструмент, который понимает, как удалять комментарии, например препроцессор C / C++. Вот как GNU CPP Имеет ли это.

cpp -fpreprocessed foo.c

хорошее мышление, хотя жаль, что он делает больше, чем просто удаляет комментарии!

frankster 02.08.2012 14:57

Есть также ответ, отличный от 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)

Это наверняка не сработает, если в строке или в регулярном выражении с разделителями // есть /* или /.

robocat 26.04.2013 06:35

Нет, это не так. Он ищет комментарии java-блока в стиле /** */, как указано в описании. Он не обрабатывает //, /* или даже / ... он не идеален, но он не «терпит неудачу», просто игнорирует указанные вами случаи. Это была просто ссылка для тех, кто ищет что-то подобное.

slottermoser 02.06.2013 03:39

Эта публикация представляет собой закодированную версию улучшения кода Маркуса Джардерота, которое было описано 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'

Поскольку этот код удаляет только комментарии, любое странное значение останется. Итак, это очень надежное решение, способное работать даже с недопустимыми входными данными.

Другие вопросы по теме