Skip to content

Instantly share code, notes, and snippets.

@tatyam-prime
Last active October 21, 2024 06:34
Show Gist options
  • Save tatyam-prime/6361c86f1d7858b510c3a1432bd85c01 to your computer and use it in GitHub Desktop.
Save tatyam-prime/6361c86f1d7858b510c3a1432bd85c01 to your computer and use it in GitHub Desktop.
不要な関数・変数・コメントを削除するやつ
import subprocess
import sys
from pathlib import Path
from clang.cindex import CursorKind, Index
# argv チェック
if len(sys.argv) == 1:
print(f"Usage: python3 {sys.argv[0]} <filename> [compile options]")
sys.exit(1)
filename = Path(sys.argv[1]).absolute()
user_options = sys.argv[2:]
# コンパイルできることを確認
subprocess.check_call(["clang++", "-fsyntax-only", "-std=gnu++2b"] + user_options + [str(filename)])
# ファイルを読み込む
with open(filename, "rb") as f:
program = f.read().splitlines(keepends=True)
#line を削除
program = [line for line in program if not line.startswith(b"#line")]
#include をコメントアウト
prefix = b"//cleanup_Sq9XpeQ6"
for i, line in enumerate(program):
if line.startswith(b"#include"):
program[i] = prefix + line
# clang でマクロを展開
p = subprocess.run(["clang++", "-std=gnu++23", "-w", "-E", "-C", "-P", "-xc++", "-"] + user_options, input=b"".join(program), stdout=subprocess.PIPE)
program = p.stdout.splitlines(keepends=True)
#include を元に戻す
for i, line in enumerate(program):
if line.startswith(prefix):
program[i] = line.removeprefix(prefix)
# GCC でコメントを削除
p = subprocess.run(["gcc", "-std=gnu++23", "-w", "-fpreprocessed", "-E", "-P", "-xc++", "-"] + user_options, input=b"".join(program), stdout=subprocess.PIPE)
program = p.stdout.splitlines(keepends=True)
# bytearray に変換
program = [bytearray(line) for line in program]
# 指定された範囲を空白に変換
def erase(extent):
l1 = extent.start.line - 1
c1 = extent.start.column - 1
l2 = extent.end.line - 1
c2 = extent.end.column - 1
while l1 < l2 or (l1 == l2 and c1 < c2):
if program[l1][c1] == ord('\n'):
c1 = 0
l1 += 1
continue
program[l1][c1] = ord(' ')
c1 += 1
options = (
f"""
-resource-dir /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
-I /usr/local/include
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/local/include
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include
-I /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/include
-I /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include
-I {filename.parent}
-fcxx-exceptions
-fsyntax-only
-std=gnu++2b
-Wno-everything
-Wunused
-Wunused-template
-Wunused-macros
-Wunused-member-function
""".split() + user_options
)
program_fix = b"".join(program)
def cleanup():
# ファイルをパースして構文木を取得
index = Index.create()
tu = index.parse(path="main.cpp", args=options, unsaved_files=[("main.cpp", "".join(str(line, 'utf-8') for line in program))])
warnings = list(tu.diagnostics)
if not warnings:
return False
# 未使用のマクロ,変数,関数を列挙する
unused_macros = set()
unused_variables = set()
unused_functions = set()
for w in warnings:
match w.option:
case "-Wunused-macros":
unused_macros.add(w.location.line)
case "-Wunused-variable" | "-Wunused-const-variable" | "-Wunused-local-typedefs" | "-Wunused-private-field": # VAR_DECL | TYPE_ALIAS_DECL | FIELD_DECL
unused_variables.add((w.location.line, w.location.column))
case "-Wunused-function" | "-Wunneeded-internal-declaration" | "-Wunused-template" | "-Wunused-member-function" | "-Wunneeded-member-function": # FUNCTION_DECL | FUNCTION_TEMPLATE | CXX_METHOD
unused_functions.add((w.location.line, w.location.column))
# 構文木を DFS, 未使用の関数・変数を削除する
def dfs(node, depth=0):
if node.location.file is not None and node.location.file.name != "main.cpp":
return
# print(f"{node.location.file} {node.extent.start.line:03}:{node.extent.start.column:02}-{node.extent.end.line:03}:{node.extent.end.column:02}\tdep={depth}\t{str(node.kind)[11:]:20}\t{node.displayname}", file=sys.stderr)
match node.kind:
case CursorKind.FUNCTION_DECL | CursorKind.FUNCTION_TEMPLATE | CursorKind.CXX_METHOD | CursorKind.CONSTRUCTOR | CursorKind.DESTRUCTOR:
loc = node.location
if (loc.line, loc.column) in unused_functions:
# print(f"[UNUSED FUNCTION] {node.extent.start.line:>3}:{node.extent.start.column:<3}-{node.extent.end.line:>4}:{node.extent.end.column:<3}\t{node.displayname}", file=sys.stderr)
erase(node.extent)
return
case CursorKind.VAR_DECL | CursorKind.TYPE_ALIAS_DECL | CursorKind.FIELD_DECL:
loc = node.location
if (loc.line, loc.column) in unused_variables:
# print(f"[UNUSED VARIABLE] {node.extent.start.line:>3}:{node.extent.start.column:<3}-{node.extent.end.line:>4}:{node.extent.end.column:<3}\t{node.displayname}", file=sys.stderr)
erase(node.extent)
return
for child in node.get_children():
dfs(child, depth + 1)
dfs(tu.cursor)
# 未使用のマクロを削除する
for l in unused_macros:
l -= 1
line = program[l]
while len(line) >= 2 and line[-2] == ord('\\'):
line[:] = b"\n"
l += 1
if l >= len(program):
break
line = program[l]
line[:] = b"\n"
# 標準入力から変更後のプログラムを入れ,コンパイルできるか確認
program_ = b"".join(program)
p = subprocess.run(["clang++", "-fsyntax-only", "-std=gnu++2b", "-x", "c++", "-"] + user_options, input=program_, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(p.stderr.decode(), file=sys.stderr)
if p.returncode:
return False
global program_fix
if program_fix == program_:
return False
program_fix = program_
return True
# 5 回まで cleanup を試行
for _ in range(5):
if not cleanup():
break
# clang-format で整形する
p = subprocess.Popen(["clang-format"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
stdout, _ = p.communicate(input=b"".join(program))
# ";" のみの行を削除
program = stdout.splitlines(keepends=True)
program = [line for line in program if line != b";\n"]
# 結果を出力
sys.stdout.buffer.write(b"".join(program))

注: 完全に自分用コードになっています

  1. #include を一番上に
  2. #include と main 関数以外を無名 namespace で囲む (unused の warning を出すため, global にあると他のコードとリンクして使われる可能性がある)
  3. python cleanup.py <code>

依存関係

  • clang (unused に関する Warning が豊富)
  • gcc (コメントだけ削除するために -preprocessed を使っている)
  • libclang (構文木を作ってくれる) : pip install libclang
  • clang-format (最後にフォーマット)

options には clang++ -v -xc++ - で出てくるオプションから適当に取り出しています

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment