LoginSignup
3
3

More than 5 years have passed since last update.

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

Posted at

はじめに

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

レポジトリ

できること

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

実行例

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

検索対象

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  }
3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3