goパッケージで簡単に静的解析して世界を広げよう #golang

January 16, 2017

この記事はQiitaの記事をエクスポートしたものです。内容が古くなっている可能性があります。

はじめに

Goの標準パッケージには、goパッケージというものがあり、Goのソースコードを静的解析するのに便利な機能が提供されています。 goパッケージを使うと、以下のようなツールが簡単に作れます。

  • ソースコードを自動生成するツール
  • ソースコードに対してlintやフォーマットを行うツール
  • リファクタリングツール
  • ソースコードから特定の何かを検索するツール
  • ミニ言語の処理系

また、goパッケージを学ぶとコンパイラや言語処理系の知識も学べるので、他の標準パッケージとはまた少し違った知識を得ることができ、プログラミングの知識の幅が広がります。

しかしながら、goパッケージに関する日本語の文献は少なく、学習しづらいのが辛いところです。 私もこれまでgoパッケージについて、Qiita上でいくつか記事を書いてきましたが、どの記事が何をするときに役に立つのか分かりづらくなっていました。 そこでこの記事では、どいういうときにどの記事を読めば良いのかをまとめてみました。

公式ドキュメントなど

まずは1次ソースとなる公式ドキュメントを挙げておきたいと思います。 英語がスラスラと読める方には、公式ドキュメントを読むことをオススメします。 また、多少英語が苦手でも、まずは公式ドキュメントを読むと良いと思いますが、英語だけで理解が難しい場合は、標準パッケージのソースコードも合わせても読むと理解が進むと思います。 なおソースコードは、GoDoc上かリンクを辿るか、GitHub上のsrc/go以下のコードを読むと良いでしょう。

以下に静的解析を行うプログラムを作る際に読むと便利なドキュメントを挙げておきます。

goパッケージ

goパッケージは、いくつかのサブパッケージから構成されています。 まずは、それらのパッケージがざっくり何をやってるのかを説明します。

  • go/tokenパッケージ
    • Goの字句(トークン)を定義したパッケージ
    • ソースコード上の位置を表すtoken.Posなども定義されてる
    • token.FileSetについてもよく使う
  • go/scannerパッケージ
    • 字句解析を行うパッケージ
    • 字句解析からやることはあまりないので、あまり使わないかも
  • go/astパッケージ
    • 抽象構文木(AST)を定義したパッケージ
    • よく使う
  • go/parserパッケージ
    • 構文解析を行うパッケージ
    • ソースコードを与えると、字句解析して構文解析し、ASTまで作ってくれる
  • go/constantパッケージ
    • Goの定数について定義したパッケージ
    • 定数を表すデータ型や演算を行う関数などが定義されてる
  • go/typesパッケージ
    • 型チェックの機能を提供するパッケージ
    • 定数の評価、型推論、識別子の解決などを行う

golang.org/x/tools/goパッケージ

標準パッケージ以外にも、準標準パッケージのgolang.org/x/tools/goパッケージでも静的解析に関する機能が提供されています。 結構な数のパッケージがありますが、特に以下の2つは便利だと思いますので、ここでは挙げておきます。

  • golang.org/x/tools/go/ast/astutilパッケージ
    • ASTに関する便利な処理を提供するパッケージ
    • astutil.PathEnclosingIntervalで指定したソースコード上の位置のノードを取得したりできます
  • golang.org/x/tools/go/types/typeutilパッケージ
    • go/typesパッケージを補助する機能を提供するパッケージ
    • types.typeは単純にマップのキーにできないので、typeutil.Mapなどを使う
  • golang.org/x/tools/go/loaderパッケージ
    • パースから型チェックまでやってくれる機能を提供するパッケージ

ソースコードを自動生成したい

ソースコードを自動生成するには、おそらくだいたい以下の手順で処理をするでしょう。

  1. 元になるソースコードを読み込む
  2. ソースコード上からコメントなどを取得し、そこに書いてある情報を取得
  3. ソースコードを生成する
  4. 生成したソースコードをフォーマットを書ける

1.〜2.に関しては、入力となる情報をソースコードから取得する話です。もしかすると、ここはソースコードを解析せずに、引数としてパッケージ名や型名を直接もらってくるかもしれませんね。

ソースコードを読み込むには、ソースコードをパースする必要があるので、以下の記事が参考になるでしょう。

また、取得したASTを解析するのは、以下の記事が参考になります。

コメントに書かれた文言を元に自動生成を行う場合は、以下の記事も参考になります。

さて、ソースコードを生成するには、ASTを直接いじるか、text/templateパッケージなどを使って文字列としてソースコードを生成する方法があるでしょう。

ASTをいじる方法としては、以下の記事が参考になるでしょう。

また、text/templateを使ってソースコードを生成する方法は、go testやGAE/Goで行っている方法ですが、GAE/Goに関しては以下の記事が参考になるでしょう。

生成したソースコードをフォーマットする必要がありますが、上述の「抽象構文木(AST)をいじってフォーマットをかける」という記事に書いてあります。

リファクタリングツールを作る

goパッケージを使うと、簡単にGoのソースコードを静的解析し、リファクタリングを行うツールを作ることができます。 たとえば、gorenameは識別子をリネームする際に使うツールですが、内部ではgoパッケージが使われています。 gorenameはライブラリとしても使うことができ、リファクタリングツールを作る上で、gorenameの構成は非常に参考になります。以下の記事では、gorenameとその構成について書いています。

リファクタリングツールを作る際には、上述したASTを取得したり、トラバースしたりすることはもちろんですが、型情報に関する機能が必要になってきます。go/typesは型チェックを行うパッケージで、上述のgorenameでも使われています。

go/typesパッケージについては、以下の記事で解説しています。

スコープに関する記事は、案外知らなかったGoのスコープについても知ることができるかもしれません。

なお、golang.org/x/toolsパッケージ以下にも多くのリファクタリングツールが存在するので、参考にすると良いでしょう。

処理系を作る

goパッケージを使うと、字句解析や構文解析の手間を省いて、GoのASTを流用したりすることで、Goと文法が近いミニ言語を簡単に作ることができます。

たとえば、式の評価を行うプログラムなど、案外作るのが面倒な処理もgo/astパッケージやgo/constantパッケージ、go/typesパッケージなどを使うと簡単に作ることができます。

以下の記事を参考にして、自作の処理系などが作れるでしょう。

おわりに

この記事では、今まで書いたgoパッケージの記事について、何がどう役に立つのかまとめてみました。 今まで「静的解析なんて難しそう」と思っていた方が、goパッケージを知ることで「よし、何か作ってみるか」という気持ちになってくれれば良いなと思います。