コーディングの業務をやっていると静的解析ツールから受けた指摘を対処する仕事がある。今回はif文に対してelseブロックを自動追加するスクリプトを作成する。
ifブロックをマッチさせるパターン
前回書いた記事をベースに考える。
下記の正規表現でifブロックまたはelseブロックをマッチさせる。
(?<=\n)\s*(?:\bif\b|\belse\b\s*\bif\b)\s*\([^()]*\)\s*(\{(?:[^{}]|(?1))*\})(?!\s*\belse\b\s*\bif\b|\s*\belse\b)
少々、長いパターンだが、ifまたはelse ifから始まるブロックを探す正規表現である。
また、ブロックの後ろにelse ifまたはelseがないことを保証する。正規表現の末尾の(?!\s*\belse\b\s*\bif\b|\s*\belse\b)で実現している。
これによってelse ifブロックまたはelseブロックが続かない、ifブロックとelse ifブロックを抜き出せる。つまり下記のようなif文のコード。
if (a < 10)
{
printf("a is small value");
}
if(b < 10)
{
printf("b is small value");
}
else if(c < 10)
{
printf("c is small value");
}
マッチさせたifブロックにelseブロックを追加する
regex.subを使ってelseブロックを追加する。コードは下記。
import regex
def elseblock_repl(match):
elseblock = "\nelse\n{\n\t/* 処理なし */\n}"
matchstr = match.group()
# if文のインデントを取得する
indent = regex.search(r"[\t ]*(?=\bif\b|\belse\b\s*\bif\b)", matchstr, regex.DOTALL).group()
return matchstr + elseblock.replace("\n", "\n" + indent)
with open("test.c", "r", encoding="utf-8", errors="ignore") as file_obj:
codetext = file_obj.read()
ret = regex.sub(r"(?<=\n)\s*(?:\bif\b|\belse\b\s*\bif\b)\s*\([^()]*\)\s*(\{(?:[^{}]|(?1))*\})(?!\s*\belse\b\s*\bif\b|\s*\belse\b)", elseblock_repl, codetext, regex.DOTALL)
print(ret)
regex.subで置換先の文字列はelseblock_repl関数を指定する。
まずはif文のインデントを図る。下記の正規表現を使うことでifまたはelse ifの前にあるスペースとタブを抽出できる。
[\t ]*(?=\bif\b|\belse\b\s*\bif\b
抽出したインデントをelseブロックのテンプレートであるelseblockに対してインデントを追加させ、実際に追加する場所のインデントと合うようにする。
elseblock_repl関数の引数であるmatchにはマッチ情報が入ってくるため、マッチした文字列にインデントを調整したelseブロックの文字列を加えたものをreturnする。すると、elseがないifブロックに対してelseブロックが追加される。
まとめ
このスクリプトを考えた経緯は静的解析ツールの指摘に自動で対応したいから。プロジェクトの規模にもよるが「elseがありません!」という指摘が500件とか1000件になると、それを手動で直すだけで数日掛かるし指も痛める。こういった単調作業はどんどん自動化したい。![]()