とりあえず日記

VIM→秀丸エディタ→VIM→秀丸エディタ→VIM→秀丸エディタ→VIM→秀丸エディタ→VIM→秀丸エディタ→VIM→秀丸エディタ(いまここ🍄)

clangでコード補完してみる(1回目)

clangとはC++の「静的コード解析」ができるコマンドラインツールです。
(本家)http://clang.llvm.org/get_started.html

clang (クラン(グ)のように発音 [2] )とは、C、C++Objective-Cプログラミング言語向けのコンパイ
ラのフロントエンドであり、Low Level Virtual Machine (LLVM) をバックエンドとして使用する。

wikipediaから引用)

まずは、コマンドライン上で動かしてみる

(test2.cpp)

class foo{
    int     get();
    void    set(int x);
};

int hoge(foo &c){
    c.           //←ここの候補をリストアップする。
}

コマンドライン

clang.exe -cc1 -fsyntax-only -code-completion-at=test2.cpp:7:7 test2.cpp
COMPLETION: foo : foo::
COMPLETION: get : [#int#]get()
COMPLETION: operator= : [#foo &#]operator=(<#const foo &#>)
COMPLETION: set : [#void#]set(<#int x#>)
COMPLETION: ~foo : [#void#]~foo()

こんなかんじで、COMPLETION の後に補完候補が現われます。


Windows用バイナリ

ビルド済みのモノをありがたく頂戴しま、ゲフンゲフン。
http://unitedsoft.ch/clang/clang.exe

上記バイナリはGCCでビルドされているようです。VisualStudio2010でビルドしたものとはまた違うらしいです。

(4月に llvm 2.9の正式版がリリースされるらしいので、そのときになったらVC2010でビルドしてみます。llvm 2.8のC++0x対応は弱いらしい)

秀丸エディタに組み込んでみる

とりあえず、秀丸エディタに組み込んでみました。

メソッドの補完


スコープも対応しています。変数cはスコープによって型が異なる。


std::stringのメソッド候補


std::vectorのメソッド候補


クラス中でtypedefされた型の候補


operator[]


iteratorの型を正しく認識しています。(その1)


iteratorの型を正しく認識しています。(その2)


typedef も認識しています。

とりあえず作った秀丸マクロ

githubはこちら。(右上のダウンロードからzipを選択)
https://github.com/ohtorii/clang

/* clang.mac
clangでキーワード補完する秀丸マクロ
*/

////////////////////////////////////////////////////////////////////////////
//      ユーザーカスタマイズ
////////////////////////////////////////////////////////////////////////////

//clang.exeへのパス
//ここから落としたもので動作検証しています。
//http://unitedsoft.ch/clang/clang.exe
//
//GCC版にVisual Studioのヘッダをくわせているので色々怪しいですが、一応動いています。
//
$g_clang_exe = "C:\\Users\\hoge\\Documents\\hidemaru_macro\\clang\\clang.exe";


//includeディレクトリのパス(Visual Studio 2010)
$g_include_dir[0] = "C:\\Program Files\\Microsoft Visual Studio 10.0\\VC\\include";
$g_include_dir[1] = "C:\\Program Files\\Microsoft Visual Studio 10.0\\VC\\atlmfc\\include";
$g_include_dir[2] = "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.0A\\include";
#g_include_dir_num=3;

//define定義
//備考:_MSC_VER=1600はVisual Studio 2010の場合
$g_define = "-DWIN32 -D_WINDOWS -DNDEBUG -D_MSC_VER=1600 -D__MSVCRT_VERSION__=0x700 -D_WIN32_WINNT=0x0500";

//オプション定義
$g_option = "-cc1 -fsyntax-only -x c++ -fno-caret-diagnostics -fdiagnostics-print-source-range-info -std=c++0x -fms-extensions -fmsc-version=1600 -fgnu-runtime";




////////////////////////////////////////////////////////////////////////////
//      メイン処理
////////////////////////////////////////////////////////////////////////////

//環境毎に異なるオプションを固定する、マクロ終了時に戻るので利用者の環境を変更することはない。
//折りたたみを維持,部分編集を維持,範囲選択,最後に移動
setcompatiblemode 0x0003|0x000c|0x0200|0x2000;

$old_searchbuffer = searchbuffer;
#old_searchoption = searchoption;
call Main;
setsearch $old_searchbuffer, #old_searchoption;
endmacro;


Main:
    if("new"==filetype){
        message("ファイルに保存してから試してください。\nこのマクロはサンプルなので手抜きです。");
        return false;
    }
    if(updated){
        message("ファイルが更新されています、まずは保存してから試してください。\nこのマクロはサンプルなので手抜きです。");
        return false;
    }

    call MakeIncludeOption;
    $$include = $$return;
    if(0==strlen($$include)){
        message("includeディレクトリが空白です。グローバル変数 $g_include_dir[] の定義が間違っているかも。");
        return false;
    }
    $$fn = basename;
    $$completion = "-code-completion-at=" + $$fn + ":"+str(lineno)+":"+str(column+1)+" " + $$fn;
    $$arg = $g_option + " " + $$completion + " " + $g_define + " " + $$include;

    title "running clang.";
    ##old_hidemaru = hidemaruhandle(0);
    openfile "/h ";
    disabledraw;
    disableinvert;
    ##cur_hidemaru = hidemaruhandle(0);

    runex $g_clang_exe + " " + $$arg, 1
            , 0, ""
            , 5, ""
            , 0, ""
            , 1, ""
            , 2, 1, 0;
    if(!result){
        message("runexの呼び出しで失敗");
        setactivehidemaru ##old_hidemaru;
        closehidemaruforced ##cur_hidemaru;
        return false;
    }

    //補完の候補を $$guess[] へ代入する。
    gofiletop;
    ##guess_num = 0;
    $$guess[0]  = "";
    $$guess_str = "";
    searchdown2 "(?<=^COMPLETION:[ \\t]+)[^ ]+",casesense,regular,nohilight;
    while(result){
        $$tmp = gettext2(seltopcolumn,seltoplineno,selendcolumn,selendlineno);
        if(strlen($$tmp)){
            $$guess_str = $$guess_str + $$tmp + "\n";
            $$guess[##guess_num] = $$tmp;
            ##guess_num = ##guess_num + 1;
        }
        finddown2;
    }

    if(##guess_num){
        selectall;
        backspace;
        insert($$guess_str);
        setactivehidemaru ##old_hidemaru;
        //直前の秀丸を辞書として使用する。
        autocomplete -1, 0x00000040, 0x00008000;
    }else{
        setactivehidemaru ##old_hidemaru;
    }
    closehidemaruforced ##cur_hidemaru;
    title 0;
    return true;

MakeIncludeOption:
    ##i     = 0;
    $$retult= "";
    while(##i < #g_include_dir_num){
        if(0 < ##i){
            $$retult = $$retult + " ";
        }
        call ConvertShortFolderName $g_include_dir[##i];
        $$short_name = $$return;
        if(! strlen($$short_name)){
            return"";
        }
        $$retult = $$retult + "-I" + $$short_name;
        ##i=##i+1;
    }
    return $$retult;

//長い文字列を runex に与えるとハングするっぽいので回避。
ConvertShortFolderName:
    ConvertShortName:
    $$fn = $$1;
    ##fso = createobject("Scripting.FileSystemObject");
    ##obj = callmethod_returnobj(##fso,"GetFolder",$$fn);
    $$short_name = getpropstr(##obj,"ShortPath");
    releaseobject(##fso);
    return $$short_name;

今回書いた秀丸マクロについて(まとめ)

clangを使用して型を認識した補完が出来ましたが、ヘッダファイルを増やすと候補リストが表示されるまで時間がかかります。clangはプリコンパイルヘッダ(.PCH)が使用できるようなので、今後はその対応とかでしょうか。
今のところ常用できるほど高速ではないです。
常用できるようにするにはバックグランドで更新したりキャッシュを導入したりして高速化しないと駄目っぽいです。

参考になるマクロとか

clang_complete.vim

vimのマクロです。
clang.exeを呼び出すだけの素直な実装と、pythonのctypeモジュール経由で libclang.dll を呼び出す実装がある。
unix 環境だと libclang.so を呼び出すらしい。

auto-complete-clang.el

emacsのマクロです。
clang.exeを呼び出すだけの素直な実装っぽいです。