NClangとは何か?
ClangはLLVMプロジェクトの一環として開発されているC/C++/Objective-Cのコンパイラだ。このClangには、実はlibclangというCで呼び出せるAPIのライブラリが存在する。そしてこのAPIは、単なるコンパイラの呼び出しだけでなく、ソースの部分的な解析や、解析結果をもとにした宣言・参照の類を検索できたり、文脈に応じた自動補完の候補をリストアップすることもできる。言うなればC/C++/Objective-Cを対象とするLanguage Serviceの実装である。
libclangはCで呼び出せるのだから、P/InvokeすればC#から呼び出すこともできるはずだ。というわけで、C#ラッパーを書いてみた。
https://github.com/atsushieno/nclang
(書き始めた時点で、libclangを使ったことは一度もなく、何が出来るのかも全く把握していなかったので、このラッパーを書きながら勉強していったということになる。)
clangのライブラリは、実のところCのAPIであるlibclangでカバーされているものだけではなく、C++のAPIも存在しており、そこではlibclangよりも幅広い機能が用意されているようだ。C++のAPIを叩く用意はまだ出来ていないので、今回はlibclangの範囲でのみサポートしている。
NClangのAPI
ここはREADME.mdに書いてある内容とほぼ同じだが、一応日本語で説明しておこう。
NClangは、P/InvokeであるLibClangクラスと各種ネイティブ構造体のバインディングをNClang.Nativeネームスペース、それらをよりC#で親和的に使えるようにした各種クラスをNClangネームスペースとして、構成している。public APIは後者のみだ。ユーザー視点では、NClang.Nativeを知る必要はないので、NClangネームスペースのみ、代表的なものに限定して説明しよう。
-
ClangServiceクラスは、いずれのクラスのインスタンスにも依存しないstatic APIのエントリポイントとなる。 -
ClangIndexクラスは、libclang APIの実質的なエントリポイントになる。これは主に(Clang)TranslationUnitと(Clang)IndexActionを作成するためにある。 -
ClangTranslationUnitは、解析されたソースの抽象表現ということになる。実ファイルや、次に説明するClangUnsavedFileから作成できる。 -
ClangUnsavedFileは、ファイル名とファイル内容(文字列)からなる、仮想的な「ファイル」のオブジェクトである。 -
ClangCursorは、ソースドキュメントにおける「カーソル」をあらわす。 -
ClangSourceLocationは、ソースドキュメントにおける位置(行番号・列番号、あるいは先頭からのインデックス、location)をあらわす。 -
ClangSourceRangeは、2つの位置からなる範囲(range)をあらわす。 -
ClangDiagnosticSetは、libclangがソースを解析した結果検出したコンパイルエラーや警告、およびその修正案を取得できるようにしてくれる。 -
ClangCodeCompleteResultsは、コード補完をあらわす。 -
ClangCompilationDatabaseは、「コンパイルデータベース」をあらわす。これはCMakeで生成できるcompile_commands.jsonなどをサポートするものらしい。じつのところNClangでは動作が確認できていない。 -
ClangIndexActionは、ClangIndexCallbacksという解析イベントハンドラーの集合体をもとに、コードを解析し、その解析結果のインデックスを生成する。 -
ClangTokenSetは、ソースコードを解析した結果生成されたトークン(識別子、キーワード、リテラル、など)の集合をあらわす。 -
ClangStringは、libclangで使用される文字列をあらわす。
これらのClangXxxYyyクラスの多くは、CXXxxYyyという構造体に対応して定義されている。多くはIntPtrから成るもので、クラスの複数のインスタンスが同一のものを指していることが多い。
NClangの使用例
以下のサンプルコードでは、int bar () { return 3; } int foo () { return 5; } int main () { return foo () * bar (); }という1行コードの中で、最後に return (空白を含む)と打った後に何が出現できるか、コード補完候補を列挙している。
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NClang;
class Test
{
public static void Main ()
{
string filename = "ClangCodeCompleteResults.CodeCompleteAt2.c";
string code = "int bar () { return 3; } int foo () { return 5; } int main () { return foo () * bar (); }";
File.WriteAllText (filename, code);
using (var idx = ClangService.CreateIndex ())
using (var tu = idx.CreateTranslationUnitFromSourceFile (filename, new string [0], new ClangUnsavedFile [0]))
using (var res = tu.CodeCompleteAt (filename, 1, 45, null, CodeCompleteFlags.None)) { // index 45 is after "return " in foo
var valid = new List<string> ();
foreach (var r in res.Results.Where (r => r.CursorKind != CursorKind.NotImplemented)) // not implementedなものは除外
Console.WriteLine (string.Join (" ", r.CompletionString.Chunks.Select (c => c.Text)));
}
}
}
出力は次のようになるだろう:
int bar ( )
int foo ( )
簡単なlibclangのラッパーだが、libclangでそれなりのことは出来るので、可能性は見出してもらえただろうと思う。
書くまでもないと思うが、libclang(.so/.dylib/.dll)が適切なパスにあってP/Invokeで呼び出せる必要がある。
ちなみに
clangの他言語のバインディングとしては、PythonやJavaの実装が存在するようなので、他の言語の開発者が無理にNClangを使用する必要は特に無いだろう。
気が向いたら他の機能の紹介も書くことにする。向かないかも。