コーディングの業務をやっていると静的解析ツールから受けた指摘を対処する仕事がある。今回は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件になると、それを手動で直すだけで数日掛かるし指も痛める。こういった単調作業はどんどん自動化したい。