文字列から C および C++ コメントを削除する Python コードを探しています。(文字列には C ソース ファイル全体が含まれていると仮定します。)
正規表現で .match() 部分文字列を使用できることに気付きましたが、それではネストが解決されなかっ/*
たり、//
内部に/* */
.
理想的には、厄介なケースを適切に処理する単純でない実装を好みます。
これは、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 と Brian (コメント内) は、文字列の処理について有効な発言をしました。上記の正規表現は、文字列の追加処理を持つ解析モジュールから抽出されているため、私はそれを完全に忘れていました。MizardX のソリューションは非常にうまく機能するはずですが、二重引用符で囲まれた文字列しか処理できません。
C では、コメントが処理される前にバックスラッシュと改行が削除され、トリグラフがその前に処理されることを忘れないでください (??/ はバックスラッシュのトリグラフであるため)。私は SCC (strip C/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 */
これはトリグラフを示していません。行末に複数のバックスラッシュを使用できることに注意してください。ただし、行のスプライシングでは、バックスラッシュの数は気にされませんが、その後の処理では問題になる可能性があります。これらすべてのケースを処理する単一の正規表現を作成することは自明ではありません (しかし、それは不可能ではありません)。
この投稿は、Markus Jarderot の投稿へのコメントで、atikat によって説明された Markus Jarderot のコードに対する改善のコード化されたバージョンを提供します。(元のコードを提供してくれた両方に感謝します。これにより、多くの作業が節約されました。)
改善をもう少し完全に説明するには: 改善により、行番号はそのまま維持されます。(これは、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)
sed
UNIX ベースの (ただし Windows で利用可能な) テキスト解析プログラムである に精通しているかどうかはわかりませんが、ファイルから C/C++ コメントを削除するsed スクリプトを見つけました。とても賢いです。たとえば、文字列宣言などで見つかった場合、「//」と「/*」は無視されます。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 ソリューションよりも優れています。車輪を再発明する必要はありません。
正規表現のケースは、文字列リテラルにコメント構文に一致するサブシーケンスが含まれる場合など、状況によっては当てはまらない場合があります。これに対処するには、解析ツリーが本当に必要です。
py++を活用して、C++ ソースを GCC で解析できる場合があります。
Py++ は車輪を再発明しません。GCC C++ コンパイラを使用して C++ ソース ファイルを解析します。より正確には、ツール チェーンは次のようになります。
ソース コードは GCC-XML に渡されます。 GCC-XML はそれを GCC C++ コンパイラに渡します。 GCC-XML は、GCC の内部表現から C++ プログラムの XML 記述を生成します。Py++ は pygccxml パッケージを使用して、GCC-XML 生成ファイルを読み取ります。結論として、すべての宣言が正しく読み取られていることを確認できます。
またはそうでないかもしれません。とにかく、これは簡単な解析ではありません。
@ RE ベースのソリューション - 入力を制限しない限り (マクロを使用しないなど)、考えられるすべての「厄介な」ケースを正しく処理する RE を見つけることはまずありません。防弾ソリューションの場合、実際の文法を活用する以外に選択肢はありません。
Python以外の答えもあります:プログラムstripcmtを使用してください:
StripCmtは、C、C ++、およびJavaソースファイルからコメントを削除するためにCで記述された単純なユーティリティです。Unixテキスト処理プログラムの壮大な伝統では、FIFO(First In-First Out)フィルターとして機能するか、コマンドラインで引数を受け入れることができます。
申し訳ありませんが、これはPythonソリューションではありませんが、C / C ++プリプロセッサなど、コメントを削除する方法を理解するツールを使用することもできます。GNUCPPがそれを行う方法は次のとおりです。
cpp -fpreprocessed foo.c
以下は私のために働いた:
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」というユーティリティ クラスがあり、使用/必要なさまざまなツールを保持しています。
これを完全に行うために解析ツリーは実際には必要ありませんが、実際には、コンパイラのフロントエンドによって生成されるものと同等のトークン ストリームが必要です。このようなトークン ストリームは、行に続くコメントの開始、文字列内のコメントの開始、trigraph の正規化など、すべての奇妙さを必ず処理する必要があります。トークン ストリームがあれば、コメントの削除は簡単です。(私は、まさにそのようなトークン ストリームを生成するツールを持っています。たとえば、実際の解析ツリーを生成する実際のパーサーのフロント エンドです :)。
トークンが正規表現によって個別に認識されるという事実は、原則として、コメント語彙素を選択する正規表現を作成できることを示唆しています。トークナイザー (少なくとも私たちが書いたもの) のセット正規表現の実際の複雑さは、実際にはこれを行うことができないことを示唆しています。それらを個別に書くことは十分に困難でした。完全にやりたくない場合は、上記の 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)