目的
1.C++でソースコードを書くにあたって、ハンガリアン記法に独自のクラスもくっつけたオリジナル命名規則に従いたい
例:intは頭に「n」を付けるようにMyTestClassは頭に「mtc」を付ける、といったことをしたい
2.目で見て確認するのは嫌なのでツールがほしい
3.そしてそのツールはチーム内の他の人たちも簡単に使えるものが良い
4.命名規則はものによって気軽に付け加えたい
5.別にビルドまではしなくていい、ただ命名規則だけチェックしたい
ということで、いろいろ紆余曲折あった末にClang(読み:クラン)でチェックすることになりました。
言語はこちらも紆余曲折あった末Pythonです。
環境
OS:Windows10
Python:3.8.8
処理
まず、使いたい人が自由に設定できるように、命名規則をcsvファイルにします。
Type,prefix
CString,str
int,n
double,r
MyTestClass,mtc
My田中太郎,m田中
また、ポインタの場合はさらに頭に「p」が付くようにします。My田中太郎* pm田中;
って感じですね。
この規則に従ってチェックしていきます。
clangを使ってみる
clanはpipで取得できるのでhttps://pypi.org/project/clang/ から取得します。
実際のどういうのが取れるのかを確認するにあたってClangでC++をパースしてしてみようの記述を参考に試してみます。
import sys
import os
import clang.cindex
index = clang.cindex.Index.create()
tree = index.parse('test.cpp')
def dump(cursor, indent=0):
text = cursor.kind.name
text += ' 名前[' + cursor.spelling + ']'
text += ' 型[' + cursor.type.spelling + ']'
if cursor.type.spelling != cursor.type.get_canonical().spelling:
text += ' 元型[' + cursor.type.get_canonical().spelling + ']'
print('\t' * indent + text)
for child in cursor.get_children():
dump(child, indent+1)
dump(tree.cursor)
これを、例えば以下のC++ファイルに実施してみます。変数名、関数名などは完全に適当です。「テスト.h」はtest.cppと同じ階層に置いてあるヘッダーファイルです。
#include "テスト.h"
class TestClass
{
int a;
double r縦;
double R縦;
CString mmmger;
My田中太郎 田中ratear;
My田中太郎 m田中ratear;
My佐藤太郎 m佐藤aeareg;
int Foo(int a)
{
if(a <= 0.0)
return 5;
return a;
}
public int GetAdd(int a, int b){
return a+b;
}
public int setAdd(int a, int b){
int nタコス;
int rパゴス;
CString sssゴメス;
return a+b;
}
private int IAbs(int x)
{
return ((x >= 0)? x : -x)
}
};
class CString;
class My田中太郎;
すると以下のように出力されます。
TRANSLATION_UNIT 名前[./test.cpp] 型[]
TRANSLATION_UNIT 名前[./test/test copy.cpp] 型[]
CLASS_DECL 名前[CString] 型[CString]
CLASS_DECL 名前[My田中太郎] 型[My田中太郎]
CLASS_DECL 名前[TestClass] 型[TestClass]
FIELD_DECL 名前[a] 型[int]
FIELD_DECL 名前[r縦] 型[double]
FIELD_DECL 名前[R縦] 型[double]
FIELD_DECL 名前[mmmger] 型[CString]
TYPE_REF 名前[class CString] 型[CString]
FIELD_DECL 名前[田中ratear] 型[My田中太郎]
TYPE_REF 名前[class My田中太郎] 型[My田中太郎]
FIELD_DECL 名前[m田中ratear] 型[My田中太郎]
TYPE_REF 名前[class My田中太郎] 型[My田中太郎]
FIELD_DECL 名前[m佐藤aeareg] 型[int]
CXX_METHOD 名前[Foo] 型[int (int)]
PARM_DECL 名前[a] 型[int]
COMPOUND_STMT 名前[] 型[]
IF_STMT 名前[] 型[]
BINARY_OPERATOR 名前[] 型[bool]
UNEXPOSED_EXPR 名前[a] 型[double]
UNEXPOSED_EXPR 名前[a] 型[int]
DECL_REF_EXPR 名前[a] 型[int]
FLOATING_LITERAL 名前[] 型[double]
RETURN_STMT 名前[] 型[]
INTEGER_LITERAL 名前[] 型[int]
RETURN_STMT 名前[] 型[]
UNEXPOSED_EXPR 名前[a] 型[int]
DECL_REF_EXPR 名前[a] 型[int]
CXX_ACCESS_SPEC_DECL 名前[] 型[]
CXX_METHOD 名前[GetAdd] 型[int (int, int)]
PARM_DECL 名前[a] 型[int]
PARM_DECL 名前[b] 型[int]
COMPOUND_STMT 名前[] 型[]
RETURN_STMT 名前[] 型[]
BINARY_OPERATOR 名前[] 型[int]
UNEXPOSED_EXPR 名前[a] 型[int]
DECL_REF_EXPR 名前[a] 型[int]
UNEXPOSED_EXPR 名前[b] 型[int]
DECL_REF_EXPR 名前[b] 型[int]
CXX_ACCESS_SPEC_DECL 名前[] 型[]
CXX_METHOD 名前[setAdd] 型[int (int, int)]
PARM_DECL 名前[a] 型[int]
PARM_DECL 名前[b] 型[int]
COMPOUND_STMT 名前[] 型[]
DECL_STMT 名前[] 型[]
VAR_DECL 名前[nタコス] 型[int]
DECL_STMT 名前[] 型[]
VAR_DECL 名前[rパゴス] 型[int]
DECL_STMT 名前[] 型[]
VAR_DECL 名前[sssゴメス] 型[CString]
TYPE_REF 名前[class CString] 型[CString]
RETURN_STMT 名前[] 型[]
BINARY_OPERATOR 名前[] 型[int]
UNEXPOSED_EXPR 名前[a] 型[int]
DECL_REF_EXPR 名前[a] 型[int]
UNEXPOSED_EXPR 名前[b] 型[int]
DECL_REF_EXPR 名前[b] 型[int]
CXX_ACCESS_SPEC_DECL 名前[] 型[]
CXX_METHOD 名前[IAbs] 型[int (int)]
PARM_DECL 名前[x] 型[int]
COMPOUND_STMT 名前[] 型[]
RETURN_STMT 名前[] 型[]
PAREN_EXPR 名前[] 型[int]
CONDITIONAL_OPERATOR 名前[] 型[int]
PAREN_EXPR 名前[] 型[bool]
BINARY_OPERATOR 名前[] 型[bool]
UNEXPOSED_EXPR 名前[x] 型[int]
DECL_REF_EXPR 名前[x] 型[int]
INTEGER_LITERAL 名前[] 型[int]
UNEXPOSED_EXPR 名前[x] 型[int]
DECL_REF_EXPR 名前[x] 型[int]
UNARY_OPERATOR 名前[] 型[int]
UNEXPOSED_EXPR 名前[x] 型[int]
DECL_REF_EXPR 名前[x] 型[int]
出力結果の意味
cursor.kind
に種類、cursor.type
に型名、cursor.spelling
に変数名が入っている形ですね。
ということはcursor.kind
を見て変数であればチェックするとすれば良さそうです。
また、先頭の方にCLASS_DECL 名前[CString] 型[CString]
のような記述があり、ヘッダーファイルも読み込んでくれていることが分かります。
クラス名についてはヘッダーファイルに定義してあるCString
とMy田中太郎
はきちんと型名として出力されていますが、定義の無いMy佐藤太郎
型の変数はint
として認識されてしまうようです。
種類の意味は読んで字の如くといったところですが、英語で書かれているので念のため日本語にしておきます。(全部列挙すると300弱あるみたいなので、よく見るものだけ)
種類 | 意味 |
---|---|
CLASS_DECL | クラスの定義 |
FIELD_DECL | フィールド変数(メンバ変数)の定義 |
CXX_ACCESS_SPEC_DECL | アクセス修飾子 |
CXX_METHOD | 関数の定義 |
PARM_DECL | 関数が受け取る引数 |
IF_STMT | If文 |
BINARY_OPERATOR | 二項演算子 |
UNARY_OPERATOR | 単項演算子 |
VAR_DECL | 変数の宣言 |
RETURN_STMT | return文 |
FLOATING_LITERAL | 実数 |
INTEGER_LITERAL | 整数 |
つまり、今回の目的のように変数の命名規則をチェックしたい場合は、FIELD_DECL
とVAR_DECL
の時だけ確認すれば良さそうです。
命名規則チェックの実装
ということで、上記の内容をもとに作ってみたのが以下です。
CursorTypeは文字列(.spelling
)で判定するよりCursorKind.VAR_DECL
でやった方が安心なのでそういう形にしています。
import pandas as pd
import clang.cindex
from clang.cindex import CursorKind
def showmessage_value(typename, valuename, corrprefix):
"""
表示メッセージを作成
"""
print("ERROR: "+ typename + " 型の変数 " + valuename + " は "+ corrprefix + " で始まらなければなりません。")
def checkprefix(cursor, dfprefix):
"""
変数、関数のプレフィックスを確認し、NGならその内容を表示
"""
if cursor.kind == CursorKind.FIELD_DECL or cursor.kind == CursorKind.VAR_DECL:
tarValType = cursor.type.spelling
isPointer = False
if tarValType.endswith(" *"): # ポインタのとき
isPointer = True
tarValType = tarValType.split(" *")[0]
if tarValType in dfprefix.Type.values:
dfcurrent = dfprefix[dfprefix['Type'] == tarValType]
corrPrefix = dfcurrent.prefix.values[0]
if isPointer is True:
corrPrefix = "p" + corrPrefix
if cursor.spelling.startswith(corrPrefix) is False:
showmessage_value(cursor.type.spelling, cursor.spelling, corrPrefix)
for child in cursor.get_children():
checkprefix(child, dfprefix)
dfprefix = pd.read_csv('./CorrTable.csv')
index = clang.cindex.Index.create()
filepath = "./test.cpp"
tree = index.parse(filepath)
checkprefix(tree.cursor, dfprefix)
実行すると以下になります。ちゃんとダメな変数はダメだと言われていることが分かります。
ERROR: int 型の変数 a は n で始まらなければなりません。
ERROR: double 型の変数 R縦 は r で始まらなければなりません。
ERROR: CString 型の変数 mmmger は str で始まらなければなりません。
ERROR: My田中太郎 型の変数 田中ratear は m田中 で始まらなければなりません。
ERROR: int 型の変数 m佐藤aeareg は n で始まらなければなりません。
ERROR: int 型の変数 rパゴス は n で始まらなければなりません。
ERROR: CString 型の変数 sssゴメス は str で始まらなければなりません。
その他の項目メモ
今回は使いませんでしたが、いつか使うかもしれない項目の取得方法についてもメモしておきます。
目的 | 書き方 |
---|---|
何行目で宣言しているか知りたい | cursor.location.line |
アクセス修飾子を取得したい | cursor.access_specifier |
おわりに
とりあえずできましたが、ちゃんとしたプロジェクトでもっと複雑なファイル校正になる場合はもしかしたらClangのビルド環境を作るくらいじゃないと駄目なのかもしれない…という不吉な予感がします。
また、動かないなどなにか問題があったり、もっと簡単な方法がある場合はコメントいただけるとありがたいです。
参考リンク
pip:https://pypi.org/project/clang/
パースの際のソースコード参考:http://www.lancarse.co.jp/blog/?p=2885
cursorのメソッド参考:https://metacpan.org/pod/Clang::Cursor
kind参考:https://clang.llvm.org/doxygen/group__CINDEX.html#gaaccc432245b4cd9f2d470913f9ef0013