fkm blog

software開発に関することを書いていきます

GoのASTをちょっと触ってみる

「やってみた」系の記事はときどきここに書きます.

Goはソースファイルを食べさせるとASTにしてくれるライブラリが標準であります. ここまではちょっとぐぐると出てくる.

package main

import (
        "go/parser"
        "go/token"
)

func main() {
        src := "package model\n" +
                "type Moke struct{\n" +
                "  Name string `myTag`\n" +
                "  Age int\n" +
                "}\n"
        fset := token.NewFileSet()
        f, err := parser.ParseFile(fset, "", src, 0)
        if err != nil {
                return
        }
        // fの型は*ast.File型
        // このfを覗くといろいろ取れるはずだが。。。
}

で, この後「このASTをどうやって扱うか?」があんまり例としてでてこなかったのでここに書いてみる.

構造体を認識してみる

入力となるファイルを次のような構造体が1つ定義されているだけのものとする.

package model

type Moke struct{
  Name string `myTag`
  Age int
}

ast.File型には, トップレベルでの宣言がスライスで入っている. 今回の構造体もトップレベルで宣言されているので, きっとこの中に入っているはず.

ということでまずはforで1つずつ中身を見てみる.

for _, decl := range f.Decls {
    // declはast.Decl型. これはinterfaceなので実際にはいろんな型
}

いろんな宣言がとれるので, ここでは構造体のときにでてくる*ast.GenDeclを探す. GenDeclは「一般的な宣言」ノードなので, importとかもこれになるっぽい. なのでtypeであるものを探す.

for _, decl := range f.Decls {
        genDecl, ok := decl.(*ast.GenDecl)
        if !ok {
                continue
        }
        if genDecl.Tok != token.TYPE {
                continue
        }
}

型宣言であることまでわかったので, どんな型宣言なのかをgenDecl.Specsを覗いて調べる。genDecl.Specsはast.Specのスライス.

for _, spec := range genDecl.Specs {
     // specの型はast.Spec型^H. interfaceなので実際はImportSpecやTypeSpecなど
}

ast.TypeSpecであるかを一応調べ, TypeSpecの場合はstructであるかを調べる.

for _, spec := range genDecl.Specs {
        typeSpec, ok := spec.(*ast.TypeSpec)
        if !ok {
                continue
        }
        // 宣言した構造体名
        name := typeSpec.Name.Name 
        structType, ok := typeSpec.Type.(*ast.StructType)
        if !ok {
                continue
        }
}

構造体名は取れた. 次はフィールド. structType.Fields.Listに入っている.

for _, field := range structType.Fields.List {
    // ``の部分はTag. 両端の`も取れてしまうので中身だけ欲しい場合は次のようにする
    tagValue := field.Tag.Value[1 : len(field.Tag.Value)-1]
    // フィールドの型は1度ast.Identにしてから. 
    identType, ok := field.Type.(*ast.Ident)
    if !ok {
        continue
    }
    fieldName := field.Names[0].Name
    fieldType := identType.Name
}

これでシンプルな構造体の定義から, なにかツール作れそう.