Browsing Erlang code

EmacsでErlangのコードをリファクタリングしていると、ある関数を呼び出している関数をすべてピックアップしたい、ということがあります。これまではEtagsを使っていましたが、以下のページでerlcscpeというツールが紹介されていたので、試してみました。

erlcscpeのプロジェクトサマリに「Program which builds cscope database for erlang files」とあるように、これはErlangのコードを解析してcscopeのデータベースを作成するツールです。

Cscopeについては、以前Linux Kernelのコードを読む際に使ったことがあり、使い方等は以下のページに記載しています。

How use

一般的なデータベース作成の流れは、ソースコードのコンパイルしが完了したら即erlcscopeでデータベースを再作成する、という方式だと思います。

私は通常、作りながらコードナビゲーションを多用することはそんなにないので、任意の契機でデータベースを再作成・再読み込みできれば良いと考えています。そこで今回は、それらを実行するキーバインドを用意し、好きな契機でデータベースの再作成を行うようにしました。

erlcscope

erlcscopeを実行すると、以下の2つのファイルが生成されます。

「cscope.files」はデータベースに登録されるソースコードのリスト、「cscope.out」はデータベースです。現状ではこの2つのファイルはカレントディレクトリに生成されるのですが、elispから呼び出す場合はこのカレントディレクトリの扱いが面倒なので、erlcscopeの引数に指定するInPathの直下に出力するように手を入れました。以降は、以下のページにあるerlcscopeを使用する前提で記載します。

本来であれば以下の3つの場所をそれぞれ指定できるようにするべきだと思うのですが、面倒なので今回はInPathの直下に纏めています。

インストールはREADME.mdを参照してください。そのままだと「/usr/local/bin」にインストールされるので、変更したい場合はMakefileに記載されている「DEST_BIN」の値を修正する必要があります。

ascope.el

Emacsでcscopeのデータベースを読み取ってナビゲートするelispは、ascope.elを使うことにしました。これは単にpackege.elでインストールできるから、という理由です。

インストール後、ascope.elを使用する為に、以下のようなelispファイルを作成し、Emacs起動時にinit-loader.elから読み込むようにしました。カレントディレクトリにデーターベースファイル(cscope.out)があると、自動的に読み込みます。

70-ascope.el:

 1(require 'ascope)
 2
 3(global-set-key (kbd "C-c s I") 'ascope-init)
 4(global-set-key (kbd "C-c s s") 'ascope-find-this-symbol)
 5(global-set-key (kbd "C-c s d") 'ascope-find-global-definition)
 6(global-set-key (kbd "C-c s g") 'ascope-find-global-definition)
 7(global-set-key (kbd "C-c s t") 'ascope-find-this-text-string)
 8(global-set-key (kbd "C-c s c") 'ascope-find-functions-calling-this-function)
 9(global-set-key (kbd "C-c s C") 'ascope-find-called-functions)
10(global-set-key (kbd "C-c s i") 'ascope-find-files-including-file)
11(global-set-key (kbd "C-c s a") 'ascope-all-symbol-assignments)
12(global-set-key (kbd "C-c s o") 'ascope-clear-overlay-arrow)
13(global-set-key (kbd "C-c s p") 'ascope-pop-mark)
14
15(setq pwd (getenv "PWD"))
16(cond ((file-exists-p (expand-file-name "cscope.out" pwd))
17  (ascope-init (concat pwd "/"))))

erlcscope.el

次に、erlcscopeでデータベースを作成し、それをascope.elで再読み込みするelispファイルを作成しました。このファイルもinit-loader.elで読み込むようにしておきます。

90-erlcscope.el:

 1(defun erlcscope-reload-all (rdir)
 2  (interactive "DSpecify the top directory: ")
 3  (setq dir (file-truename rdir))
 4  (setq cscope_files_path (expand-file-name "cscope.files" dir))
 5  (cond ((file-exists-p cscope_files_path)
 6    (delete-file cscope_files_path)))
 7  (message (concat "generate index.. " dir))
 8  (call-process "erlcscope" nil "*erlcscope*" nil dir)
 9  (message "erlcscope: generate ok")
10  (ascope-init (concat dir "/"))
11)
12
13(global-set-key (kbd "C-c s R") 'erlcscope-reload-all)

これで、Emacsを起動して”C-c s R”を実行してInPathを指定すると、InPathの下にデータベースを作成してascope.elで再読み込みします。

Conclusion

erlcscopeascope.elを導入して、Erlangのコードをブラウジングできるようになりました。InPathに含めればdepのコードも対象になります。こういったツールは、ガリガリとコードを書いている時にはあまり使わないかもしれませんが、デバッグやリファクタリング時に関数の呼び出しの流れを確認したい場合に便利です。