AST のノードを検索するツールを作った

# はじめに

* Go 言語で generate を使ってコードを生成するツールを作ろうとしているが AST ノードの構成を見るためにいちいち ast.Print するのが面倒だったのでツールを作ってみた。

# レポジトリ

* https://github.com/nirasan/ast-walker

# できること

* オプション無しでファイルを指定すると、ルートノードの ast.Print を表示する。
* `-type` オプションでノードの型を指定すると、対象のノードの ast.Print とそのノードを取得するためのコマンドが表示される。
  * コマンドとは構造体のフィールド名や必要ならインターフェイスからのキャストを記述した文字列で、ルートノードに実行することで対象のノードを取得できるもの。
  * 例えば一つ目の構造体宣言の取得コマンドは `Decls[0].(*ast.GenDecl).Specs[0]` 
* `-command` オプションでノードの取得コマンドを指定すると、対象のノードの ast.Print とそのノードを取得するためのコマンドが表示される。
* `-regex` オプションでノードの取得コマンドを正規表現で指定すると、対象のノードの ast.Print とそのノードを取得するためのコマンドが表示される。

# 実行例

## 型宣言のノードを型で検索して ast.Print を表示

### 検索対象

```go
package main

type ST1 struct {
	N int
	B bool
}

type ST2 struct {
	S  string
	IS []int
}
```

### 実行コマンドと結果

```
$ ast-walker -type *ast.TypeSpec file.go

Found!

COMMAND:
    Decls[0].(*ast.GenDecl).Specs[0]
AST_PRINT:
     0  *ast.TypeSpec {
     1  .  Name: *ast.Ident {
     2  .  .  NamePos: test/file2.go:3:6
     3  .  .  Name: "ST1"
     4  .  .  Obj: *ast.Object {
     5  .  .  .  Kind: type
     6  .  .  .  Name: "ST1"
     7  .  .  .  Decl: *(obj @ 0)
     8  .  .  }
     9  .  }
    10  .  Type: *ast.StructType {
    11  .  .  Struct: test/file2.go:3:10
    12  .  .  Fields: *ast.FieldList {
    13  .  .  .  Opening: test/file2.go:3:17
    14  .  .  .  List: []*ast.Field (len = 2) {
    15  .  .  .  .  0: *ast.Field {
    16  .  .  .  .  .  Names: []*ast.Ident (len = 1) {
    17  .  .  .  .  .  .  0: *ast.Ident {
    18  .  .  .  .  .  .  .  NamePos: test/file2.go:4:2
    19  .  .  .  .  .  .  .  Name: "N"
    20  .  .  .  .  .  .  .  Obj: *ast.Object {
    21  .  .  .  .  .  .  .  .  Kind: var
    22  .  .  .  .  .  .  .  .  Name: "N"
    23  .  .  .  .  .  .  .  .  Decl: *(obj @ 15)
    24  .  .  .  .  .  .  .  }
    25  .  .  .  .  .  .  }
    26  .  .  .  .  .  }
    27  .  .  .  .  .  Type: *ast.Ident {
    28  .  .  .  .  .  .  NamePos: test/file2.go:4:4
    29  .  .  .  .  .  .  Name: "int"
    30  .  .  .  .  .  }
    31  .  .  .  .  }
    32  .  .  .  .  1: *ast.Field {
    33  .  .  .  .  .  Names: []*ast.Ident (len = 1) {
    34  .  .  .  .  .  .  0: *ast.Ident {
    35  .  .  .  .  .  .  .  NamePos: test/file2.go:5:2
    36  .  .  .  .  .  .  .  Name: "B"
    37  .  .  .  .  .  .  .  Obj: *ast.Object {
    38  .  .  .  .  .  .  .  .  Kind: var
    39  .  .  .  .  .  .  .  .  Name: "B"
    40  .  .  .  .  .  .  .  .  Decl: *(obj @ 32)
    41  .  .  .  .  .  .  .  }
    42  .  .  .  .  .  .  }
    43  .  .  .  .  .  }
    44  .  .  .  .  .  Type: *ast.Ident {
    45  .  .  .  .  .  .  NamePos: test/file2.go:5:4
    46  .  .  .  .  .  .  Name: "bool"
    47  .  .  .  .  .  }
    48  .  .  .  .  }
    49  .  .  .  }
    50  .  .  .  Closing: test/file2.go:6:1
    51  .  .  }
    52  .  .  Incomplete: false
    53  .  }
    54  }

COMMAND:
    Decls[1].(*ast.GenDecl).Specs[0]
AST_PRINT:
     0  *ast.TypeSpec {
     1  .  Name: *ast.Ident {
     2  .  .  NamePos: test/file2.go:8:6
     3  .  .  Name: "ST2"
     4  .  .  Obj: *ast.Object {
     5  .  .  .  Kind: type
     6  .  .  .  Name: "ST2"
     7  .  .  .  Decl: *(obj @ 0)
     8  .  .  }
     9  .  }
    10  .  Type: *ast.StructType {
    11  .  .  Struct: test/file2.go:8:10
    12  .  .  Fields: *ast.FieldList {
    13  .  .  .  Opening: test/file2.go:8:17
    14  .  .  .  List: []*ast.Field (len = 2) {
    15  .  .  .  .  0: *ast.Field {
    16  .  .  .  .  .  Names: []*ast.Ident (len = 1) {
    17  .  .  .  .  .  .  0: *ast.Ident {
    18  .  .  .  .  .  .  .  NamePos: test/file2.go:9:2
    19  .  .  .  .  .  .  .  Name: "S"
    20  .  .  .  .  .  .  .  Obj: *ast.Object {
    21  .  .  .  .  .  .  .  .  Kind: var
    22  .  .  .  .  .  .  .  .  Name: "S"
    23  .  .  .  .  .  .  .  .  Decl: *(obj @ 15)
    24  .  .  .  .  .  .  .  }
    25  .  .  .  .  .  .  }
    26  .  .  .  .  .  }
    27  .  .  .  .  .  Type: *ast.Ident {
    28  .  .  .  .  .  .  NamePos: test/file2.go:9:5
    29  .  .  .  .  .  .  Name: "string"
    30  .  .  .  .  .  }
    31  .  .  .  .  }
    32  .  .  .  .  1: *ast.Field {
    33  .  .  .  .  .  Names: []*ast.Ident (len = 1) {
    34  .  .  .  .  .  .  0: *ast.Ident {
    35  .  .  .  .  .  .  .  NamePos: test/file2.go:10:2
    36  .  .  .  .  .  .  .  Name: "IS"
    37  .  .  .  .  .  .  .  Obj: *ast.Object {
    38  .  .  .  .  .  .  .  .  Kind: var
    39  .  .  .  .  .  .  .  .  Name: "IS"
    40  .  .  .  .  .  .  .  .  Decl: *(obj @ 32)
    41  .  .  .  .  .  .  .  }
    42  .  .  .  .  .  .  }
    43  .  .  .  .  .  }
    44  .  .  .  .  .  Type: *ast.ArrayType {
    45  .  .  .  .  .  .  Lbrack: test/file2.go:10:5
    46  .  .  .  .  .  .  Elt: *ast.Ident {
    47  .  .  .  .  .  .  .  NamePos: test/file2.go:10:7
    48  .  .  .  .  .  .  .  Name: "int"
    49  .  .  .  .  .  .  }
    50  .  .  .  .  .  }
    51  .  .  .  .  }
    52  .  .  .  }
    53  .  .  .  Closing: test/file2.go:11:1
    54  .  .  }
    55  .  .  Incomplete: false
    56  .  }
    57  }
```

## 一つ目の型宣言をコマンドで検索して表示

### 検索対象

* 前の例と同じファイル

### 実行コマンドと結果

```
$ ast-walker -command Decls[0].(*ast.GenDecl).Specs[0] file.go

Found!

COMMAND:
    Decls[0].(*ast.GenDecl).Specs[0]
AST_PRINT:
     0  *ast.TypeSpec {
     1  .  Name: *ast.Ident {
     2  .  .  NamePos: test/file2.go:3:6
     3  .  .  Name: "ST1"
     4  .  .  Obj: *ast.Object {
     5  .  .  .  Kind: type
     6  .  .  .  Name: "ST1"
     7  .  .  .  Decl: *(obj @ 0)
     8  .  .  }
     9  .  }
    10  .  Type: *ast.StructType {
    11  .  .  Struct: test/file2.go:3:10
    12  .  .  Fields: *ast.FieldList {
    13  .  .  .  Opening: test/file2.go:3:17
    14  .  .  .  List: []*ast.Field (len = 2) {
    15  .  .  .  .  0: *ast.Field {
    16  .  .  .  .  .  Names: []*ast.Ident (len = 1) {
    17  .  .  .  .  .  .  0: *ast.Ident {
    18  .  .  .  .  .  .  .  NamePos: test/file2.go:4:2
    19  .  .  .  .  .  .  .  Name: "N"
    20  .  .  .  .  .  .  .  Obj: *ast.Object {
    21  .  .  .  .  .  .  .  .  Kind: var
    22  .  .  .  .  .  .  .  .  Name: "N"
    23  .  .  .  .  .  .  .  .  Decl: *(obj @ 15)
    24  .  .  .  .  .  .  .  }
    25  .  .  .  .  .  .  }
    26  .  .  .  .  .  }
    27  .  .  .  .  .  Type: *ast.Ident {
    28  .  .  .  .  .  .  NamePos: test/file2.go:4:4
    29  .  .  .  .  .  .  Name: "int"
    30  .  .  .  .  .  }
    31  .  .  .  .  }
    32  .  .  .  .  1: *ast.Field {
    33  .  .  .  .  .  Names: []*ast.Ident (len = 1) {
    34  .  .  .  .  .  .  0: *ast.Ident {
    35  .  .  .  .  .  .  .  NamePos: test/file2.go:5:2
    36  .  .  .  .  .  .  .  Name: "B"
    37  .  .  .  .  .  .  .  Obj: *ast.Object {
    38  .  .  .  .  .  .  .  .  Kind: var
    39  .  .  .  .  .  .  .  .  Name: "B"
    40  .  .  .  .  .  .  .  .  Decl: *(obj @ 32)
    41  .  .  .  .  .  .  .  }
    42  .  .  .  .  .  .  }
    43  .  .  .  .  .  }
    44  .  .  .  .  .  Type: *ast.Ident {
    45  .  .  .  .  .  .  NamePos: test/file2.go:5:4
    46  .  .  .  .  .  .  Name: "bool"
    47  .  .  .  .  .  }
    48  .  .  .  .  }
    49  .  .  .  }
    50  .  .  .  Closing: test/file2.go:6:1
    51  .  .  }
    52  .  .  Incomplete: false
    53  .  }
    54  }
```

## 構造体宣言のフィールド定義部分をコマンドの正規表現で検索して表示

### 検索対象

* 前の例と同じファイル

### 実行コマンドと結果

```
$ ast-walker -regex 'Fields.List\[\d+\]$' file.go

Found!

COMMAND:
    Decls[0].(*ast.GenDecl).Specs[0].(*ast.TypeSpec).Type.(*ast.StructType).Fields.List[0]
AST_PRINT:
     0  *ast.Field {
     1  .  Names: []*ast.Ident (len = 1) {
     2  .  .  0: *ast.Ident {
     3  .  .  .  NamePos: test/file2.go:4:2
     4  .  .  .  Name: "N"
     5  .  .  .  Obj: *ast.Object {
     6  .  .  .  .  Kind: var
     7  .  .  .  .  Name: "N"
     8  .  .  .  .  Decl: *(obj @ 0)
     9  .  .  .  }
    10  .  .  }
    11  .  }
    12  .  Type: *ast.Ident {
    13  .  .  NamePos: test/file2.go:4:4
    14  .  .  Name: "int"
    15  .  }
    16  }

COMMAND:
    Decls[0].(*ast.GenDecl).Specs[0].(*ast.TypeSpec).Type.(*ast.StructType).Fields.List[1]
AST_PRINT:
     0  *ast.Field {
     1  .  Names: []*ast.Ident (len = 1) {
     2  .  .  0: *ast.Ident {
     3  .  .  .  NamePos: test/file2.go:5:2
     4  .  .  .  Name: "B"
     5  .  .  .  Obj: *ast.Object {
     6  .  .  .  .  Kind: var
     7  .  .  .  .  Name: "B"
     8  .  .  .  .  Decl: *(obj @ 0)
     9  .  .  .  }
    10  .  .  }
    11  .  }
    12  .  Type: *ast.Ident {
    13  .  .  NamePos: test/file2.go:5:4
    14  .  .  Name: "bool"
    15  .  }
    16  }

 COMMAND:
     Decls[1].(*ast.GenDecl).Specs[0].(*ast.TypeSpec).Type.(*ast.StructType).Fields.List[0]
 AST_PRINT:
      0  *ast.Field {
      1  .  Names: []*ast.Ident (len = 1) {
      2  .  .  0: *ast.Ident {
      3  .  .  .  NamePos: test/file2.go:9:2
      4  .  .  .  Name: "S"
      5  .  .  .  Obj: *ast.Object {
      6  .  .  .  .  Kind: var
      7  .  .  .  .  Name: "S"
      8  .  .  .  .  Decl: *(obj @ 0)
      9  .  .  .  }
     10  .  .  }
     11  .  }
     12  .  Type: *ast.Ident {
     13  .  .  NamePos: test/file2.go:9:5
     14  .  .  Name: "string"
     15  .  }
     16  }

 COMMAND:
     Decls[1].(*ast.GenDecl).Specs[0].(*ast.TypeSpec).Type.(*ast.StructType).Fields.List[1]
 AST_PRINT:
      0  *ast.Field {
      1  .  Names: []*ast.Ident (len = 1) {
      2  .  .  0: *ast.Ident {
      3  .  .  .  NamePos: test/file2.go:10:2
      4  .  .  .  Name: "IS"
      5  .  .  .  Obj: *ast.Object {
      6  .  .  .  .  Kind: var
      7  .  .  .  .  Name: "IS"
      8  .  .  .  .  Decl: *(obj @ 0)
      9  .  .  .  }
     10  .  .  }
     11  .  }
     12  .  Type: *ast.ArrayType {
     13  .  .  Lbrack: test/file2.go:10:5
     14  .  .  Elt: *ast.Ident {
     15  .  .  .  NamePos: test/file2.go:10:7
     16  .  .  .  Name: "int"
     17  .  .  }
     18  .  }
     19  }
```