ソースファイル中に定義してある関数の名前を列挙する

仕事をしていて、時々ファイル中の関数名を列挙したいことがある。

例えば、単体テストする関数の数が多いので難易度別に分けようと思い立ったけど関数名を一々手作業で列挙するのが面倒なので自動的に抜き出して表にするとか、他人のソースを読む時に関数が幾つ定義されているか調べようとしてファイル中の関数を1行1関数名で抜き出してwcでカウントしたりとか、そんな使い方だ。

今まではfunclenというツールを使っていた。C言語のソースファイル中に定義されている関数の単純な行数を数えるツールだ。元々『Cプログラミング診断室』に載っていたソースを、勉強を兼ねてANSI CやC++に書き換えて使ってきた。関数名と行数が出力されるので、そこから関数名のみを抜き出していたのだ。

ところが最近、funclenが特定の条件の時に正しい関数名を表示しない*1ことが分かり、funclenを直すか別のツールを拵えるか選択することになった。

funclenを直すのは厳しそうなので別のツールを拵えることにしたのだが、正直一から自作するのはちょっと辛い。何より他人が既に解決済みの問題だろうから、他人の成果を流用するのが筋だろう。ということで既存のツールを流用することにした。

タグ生成ツールである。

手元にExuberant ctagsがあるので、これで生成したタグを解析してしまえば良いと気づいたのだ。これならタグファイルのテキストの構造を解析するだけなので難易度は低い。ソース解析という難しい部分はExuberant ctagsに任せてしまうのだ。

Exuberant ctagsはタグを標準出力に出力することができるので、それをgawkで処理することにした。解析対象のソースはC言語C++なので、取り合えずそれ用にでっち上げてみた。

#!/bin/sh

ctags --sort=no --fields=K -f - $* | gawk '
BEGIN {
    FS = "\t"
}

$NF == "function" {
    print $1
}'

関数の定義順が保持されるように、タグ名でソートしないようにオプション指定している。結構短いコードなのでワンライナーでもいけそうではある。

ところでExuberant ctagsは複数言語に対応したツールなので、もう少し工夫すると他のプログラミング言語でも使えるルーチン列挙用ツールにできる。

#!/bin/sh

ctags --sort=no --fields=K -f - $* | gawk '
BEGIN {
    FS = "\t"

    kinds["function"] = 0
    kinds["method"] = 0
    kinds["procedure"] = 0
    kinds["subroutine"] = 0
    kinds["property"] = 0
}

{
    if ($NF in kinds) {
        print $1
    }
}'

大抵の言語では関数はfunctionに割り振られているのだが、例えばPerlではsubroutine、Tclではprocedureに割り振られていたりする。またC#はmethodとproperty、Fortranはfunctionとsubroutine、Pascalはfunctionとprocedureに分かれていたりする。なので各種別を保持するテーブルを用意している。

このツールの利点は、Exuberant ctagsを使うことで比較的精度の高い解析や複数言語対応が可能となっていることだ。C言語からSchemeまで幅広く扱える。Exuberant ctags 5.8を使っているなら、41種類のプログラミング言語に対応しているらしい。

同時に欠点は、Exuberant ctagsが苦手なことはこのツールも苦手だという点だろう。例えばExuberant ctagsはJavaScriptの解析が微妙にイマイチで、一部の関数が解析結果から漏れてしまうことがある*2。その場合、解析結果に含まれない関数は列挙されない。

良くも悪くもExuberant ctagsに依存しているが、まあ、ラッパーだからなあ。

*1:関数定義の仮引数の部分を複数行に分けて書いていて、且つその中で関数名の書いてある行とは別の行で引数となる関数ポインタの宣言というか型を直書きしていると、関数ポインタの戻り値の型を関数名として出力してしまう。行数自体は正しい。typedefした関数ポインタの型を使用していれば問題ない。

*2:例えば無名関数中で定義しているローカルな変数ないし関数のうち、最初に定義しているもののみが解析結果から漏れてしまう。Exuberant ctags 5.7J1及び5.8J1で確認。