関数内の未使用変数を削除するスクリプトを考える。
関数のブロックと変数宣言を抜き取る
関数ブロックは下記の正規表現でマッチできる。
(?:(?:auto|register|static|extern)\s+)?(?:(?:const|volatile|restrict|signed|unsigned|short|long)\s+)*\w+\s+\w+\s*\([^()]*\)\s*(\{([^{}]|(?1))*\})
関数ブロックの抜き出し方はソースツリー作成のスクリプトを考えたときに似たような正規表現を作成したため、こちらの記事を参考にすること。
変数宣言のマッチは下記。単一変数宣言であることを前提とする。
[\t ]*(?:(?:auto|register|static|extern)\s+)?(?:(?:const|volatile|restrict|signed|unsigned|short|long)\s+)*\w+[\s*]+\w+\s*(?:=[^;]+)?;[^\n]*\n
こちらも前に複数変数宣言のマッチを考えたため、こちらの記事を参考にする。
未使用変数の宣言は行ごと削除する
下記のスクリプトで実現する。
import regex
pattern = r"(?:(?:auto|register|static|extern)\s+)?(?:(?:const|volatile|restrict|signed|unsigned|short|long)\s+)*\w+\s+\w+\s*\([^()]*\)\s*(\{([^{}]|(?1))*\})"
pattern_val = r"[\t ]*(?:(?:auto|register|static|extern)\s+)?(?:(?:const|volatile|restrict|signed|unsigned|short|long)\s+)*\w+[\s*]+\w+\s*(?:=[^;]+)?;[^\n]*\n"
with open("test.c", "r", encoding="utf-8", errors="ignore") as file_obj:
codetext = file_obj.read()
functions = regex.finditer(pattern, codetext, regex.DOTALL)
for func in functions:
functext = func.group()
functext_edit = functext
values = regex.finditer(pattern_val, functext, regex.DOTALL)
for val in values:
valtext = val.group()
if "=" in valtext:
# 代入演算子がある場合は=の左にある文字列が変数名.
valname = regex.search("\w+(?=\s*=)", valtext).group()
else:
# 代入演算子がない場合は;の左にある文字列が変数名.
valname = regex.search("\w+(?=\s*;)", valtext).group()
valmatchs = regex.finditer(rf"\b{valname}\b", functext, regex.DOTALL)
usedcount = 0
for vallll in valmatchs:
usedcount += 1
if usedcount == 1:
# 一度しか記述されていない場合は未使用変数と判断して削除.
functext_edit = functext_edit.replace(valtext, "")
pass
codetext = codetext.replace(functext, functext_edit)
print(codetext)
大まかな流れは下記である。
まとめ
未使用変数の変数宣言部を""で置換することにより削除することを実現している。こういった自動化を検討して思ったことだが、ソースコードなどテキストデータをプログラムで編集する場合は置換が扱いやすい。特定の行を削除などといった処理を組むと行数管理が煩雑になる。消したいところを検索して、""で置換するのが簡単だし、バグも起きにくい。