C言語のソースコードを解析するうえで、ブロック単位でソースコードを抜き取れると何かと便利。
正規表現を利用してブロック単位で抜き取る方法を解説する。
ブロック単位で抜き取るための正規表現の基本
ブロックの開始から終了までマッチさせるための正規表現はこちら。
例えば、{AAAA}やブロックがネストされた{BBB{CCC}}のようなものにマッチする。
(\{(?:[^{}]|(?1))*\})
この正規表現について説明する。
-
(:キャプチャー開始して、正規表現を再帰させる範囲を決める -
\{:ブロックの開始 -
(?::非キャプチャーグループの開始 -
[^{}]:{と}以外の文字にマッチ -
|:ORの意味で、[^{}]か(?1)にマッチする -
(?1):正規表現を再帰させ、1つ目のキャプチャーグループを再帰させる -
*:この正規表現であれば(?:[^{}]|(?1))が0回以上連続することを示す -
\}:ブロックの終了 -
):キャプチャーの終了
ネストのブロックが出てきたときに(?1)で再帰させることにより、
またブロック開始の\{からマッチを開始することができる。これにより、どれだけ深いネストのブロックもマッチさせることが出来る。
ifブロックを抜き出してみる
ifブロックを抜き出す前に、if文をマッチさせる正規表現を考える。
この正規表現でif文と条件式までマッチさせる。
(?<=\n)\s*\bif\b\([^()]*\)\s*
この正規表現について説明する。
-
(?<=\n):肯定後読みで改行直後にマッチする、行頭にマッチさせるため必要- ※行頭のマッチといえば
^だが、ソースコード全体の先頭にしかマッチしないため今回は使用できない
- ※行頭のマッチといえば
-
\s*:if文前のインデントにマッチする -
\b:ifの前に他の英数字がないことを保証する -
if:ifにマッチする -
\b:ifの後に他の英数字がないことを保証する -
\(:条件式の開始である括弧にマッチする -
[^()]*:条件式にマッチする -
\):条件式の終了である括弧にマッチする -
\s*:条件式の後のスペースや改行にマッチする
if文とブロックをマッチする正規表現を組み合わせたものがこちら。
(?<=\n)\s*\bif\b\s*\([^()]*\)\s*(\{(?:[^{}]|(?1))*\})
下記のソースコードを例に試してみる。
#include <stdio.h>
void main(void)
{
int a = 0;
if(a < 10)
{
printf("a is small value");
}
}
この場合、マッチするのはこの部分。
if(a < 10)
{
printf("a is small value");
}
pythonで抜き出す場合は、下記のコードでOK。
import regex
with open("test.c", "r", encoding="utf-8", errors="ignore") as file_obj:
codetext = file_obj.read()
ret = regex.search(r"(?<=\n)\s*\bif\b\([^()]*\)\s*(\{(?:[^{}]|(?1))*\})", codetext, regex.DOTALL).group()
print(ret)
まとめ
Cコードに対してブロック単位でコードを抜き出す方法を説明した。
これを応用すればifブロックだけではなく、switch, for, whileなどのブロックも抜き出し可能だし、関数自体の抜き出しも可能である。
また、regex.searchだけではなく、regex.findallやregex.subを使うことで対象のブロックをすべて抜き出したり、置換したりすることが可能。
もう少し工夫すれば、ifブロックに対してelseブロックを追加する処理も可能。
静的解析ツールでelseブロックがないという指摘があったりするため、一括で追加したりできそう。