Go言語で書いたソースコードを解析する。
Go言語ではgo/ast、go/parser、go/tokenパッケージを使用することで、Go言語で書かれたソースコードを解析することができます。
解析したソースコードを表現する抽象構文木(AST:abstract syntax tree)の形で出力されます。
文章では難しいので実際のサンプルで試してみたいと思います。
以下の様な解析対象のソースコードを用意します。
package main import ( "fmt" "time" ) type MyInterface interface { Greeting() } type MyStruct struct { Name string } func (m *MyStruct) Greeting() { fmt.Printf("%v, %sさん、こんにちは!\n", time.Now(), m.Name) } func (m *MyStruct) Message(s string) time.Time { fmt.Printf("%s さん、%s\n", m.Name, s) return time.Now() } func main() { ms := MyStruct{"山田"} ms.Greeting() fmt.Println(ms.Message("さようなら")) }
このソースコードを解析するサンプルです。
(酷いネストですがご容赦ください)
package main import ( "fmt" "go/ast" "go/parser" "go/token" ) func main() { fset := token.NewFileSet() file, err := parser.ParseFile(fset, "sample.go", nil, 0) if err != nil { panic(err) } for _, decl := range file.Decls { switch td := decl.(type) { case *ast.GenDecl: switch td.Tok { case token.IMPORT: fmt.Println("### import") for _, sp := range td.Specs { s := sp.(*ast.ImportSpec) fmt.Println(s.Path.Value) } case token.TYPE: fmt.Println("### type") for _, sp := range td.Specs { s := sp.(*ast.TypeSpec) fmt.Println(s.Name) switch t := s.Type.(type) { case *ast.InterfaceType: for _, m := range t.Methods.List { fmt.Println(m) } case *ast.StructType: for _, f := range t.Fields.List { fmt.Println(f) } default: fmt.Println(3, t) } } case token.CONST: case token.VAR: default: } case *ast.FuncDecl: fmt.Println("### function") fmt.Println(td.Name) if td.Recv != nil { fmt.Println(td.Recv.List[0].Type) } if td.Type.Params != nil && td.Type.Params.NumFields() > 0 { fmt.Println("##### args") for _, p := range td.Type.Params.List { fmt.Println(p.Type, p.Names) } } if td.Type.Results != nil && td.Type.Results.NumFields() > 0 { fmt.Println("##### returns") for _, r := range td.Type.Results.List { fmt.Println(r.Type, r.Names) } } default: } fmt.Println() } }
実行結果は以下のとおりです。
$ go run parse_code.go ### import "fmt" "time" ### type MyInterface &{<nil> [Greeting] 0xc0840058a0 <nil> <nil>} ### type MyStruct &{<nil> [Name] string <nil> <nil>} ### function Greeting &{148 MyStruct} ### function Message &{258 MyStruct} ##### args string [s] ##### returns &{time Time} [] ### function main
parser.ParseFile関数にtoken.FileSet、ソースファイル名、ソース、parser.Modeを指定して呼び出します。
ソースファイル名とソースはいずれかを指定します。
戻り値としてast.Fileとerrorが返ってきます。このast.Fileが解析されたソースコードの情報を保持しています。
複数のソースコード(パッケージ)を一気に解析するには、parser.ParseDir関数を使用します。戻り値はパッケージごとのマップになります。
ast.File.Declsを参照すると、ソースコードのトップレベルに定義されているast.Declを実装したXXXXDeclの一覧を取得できます。
一覧の中には、ast.GenDeclやast.FuncDecelがあります。
例えば、GenDeclでGenDecl.Tokがtoken.TYPEの場合だと(case token.TYPE:)、
トップレベルに定義されているtype宣言なので、interface定義やstruct定義を取得することができます。
FuncDeclの場合だと、トップレベルに定義されている関数を取得することができます。
またその関数がレシーバーと指定定義されているかどうか、パラメータはどうか、戻り値はどうかなど詳細な情報まで取得できます。